Monday, October 24, 2011

Migrating a lot of zones from Microsoft DNS to BIND

In my last post, I gave some solutions to migrating zones from Microsoft DNS to BIND dns zones. If you have a lot of zones, you may be wondering if there is an easy way to run these steps on all of them to migrate the whole configuration. Well with dnscmd and some basic scripting, that is just adding a few more minutes of work.

When you want to see all of your zones:

dnscmd [dnsserver] /enumzones

will list out every zone on the server. The format needs some work though, and you will need to ignore the header and footer output for the command. The rest comes out in the format of Zone Type Partition Options, with no standardization to the whitespacing between them. Since the zone happens to be what we need, we can easily extract that and use it for follow on commands.

If we want to export our zone's to files, we can try this in powershell:

$zones = dnscmd $dnsserver /enumzones
for ($i = 7; $i -lt ($zones.length -3); $i++) {
$zonename = $zones[$i].substring(1)
$zonename = $zonename.substring(0,$zonename.indexof(" "))
$file = $zonename[$i] + ".txt"
dnscmd $dnsserver /exportzone $zonename $file
}

This will go through all the output, strip out the zone names and export them all to a text file named after the zone name. You could use this same method as a way to backup records if you have issues with them being deleted, or zones going missing. Here I started at the 7th line of output, which should bypass all of the headers and ignore the first zone, which will likely be the "." zone. You can check where you want to start by looking at the lines of the $zone array before doing a loop. We end 3 lines short of the end of the output to skip the footer information.

Alternatively if you were to go with the secondary zone on BIND method, you could use dnscmd to set up the allow zone transfer and provide the BINDS server's IP. While doing this and the above example, you could even throw in extra output to file using the zone name to build all of the BIND config file's entries for the new zones (primary or secondary).

In any case, to use dnscmd to set an IP for zone transfers:

dnscmd [dnsservername] /zoneresetsecondaries /Securelist [secondary dns server ip]

If you are running this on a lot of zones and you don't want to do this to all of them, find another method. This will reset the existing settings on the zone.

These examples are just a few ways to do this. There may be some existing powershell cmdlets available that will accomplish some of these tasks. For obtaining a list of zones, and better filtering/handling of them, you could also take a look at doing this with: Get-wmiobject -namespace root\microsoftdns -class microsoftdns_zone -computer [remote dns server name].

Migrating Windows DNS to Linux BIND

Recently I have encountered several people who were trying to do DNS migrations between operating systems for various reasons. I thought it would be nice to put together a good tutorial on this. If you search around you will find other answers, most of which tell you to pull the DNS text file from a windows machine and copy it over to Linux. That works if you have a non-active directory integrated DNS zone and the file is already there. I wouldn't suggest trying to convert an AD integrated zone that is used in production to a primary non-AD integrate zone just to do a migration. There are two good ways to get a zone file that BIND can use.

1) Export the zone from windows.
dnscmd [dns server name] /exportzone [zone name] [file name]
This command will export all the zone records into a text file and put it in the %windir%\system32\dns folder.

2) On your Linux machine, create secondary zones for your Windows zones. On the Windows machine, allow zone transfer to the Linux machine. Once the transfer is done, you will have a text copy of the zone file that you can modify and reuse as a master zone.

Example Linux machine 10.1.3.2 and windows machine 10.1.3.10






In both cases, you will need to do some editing to the zone file. You need to update the SOA information and the NS record


Change these values to the name of your BIND server. Place the zone file where BIND can read it, and update your named.conf or related include file to host the zone as a master. Reload BIND and you will be hosting DNS there.

There are always more considerations to a migration than this. You need to consider what IP addresses the clients use for nameservers. If they were pointing to the server you are migrating away from, you may want to do a IP address swap on your servers as a last step of the transfer. Besides clients, you need to be concerned with domain name registration services pointing to the appropriate servers that manage your registered domain names, as well as any DNS forwarders being used. If you are using dynamic dns and you have a lot of registrations from DHCP clients, migrating them as-is would cause their records to now become static.  So you want to look at cleaning up your zone file of this type of entry prior to migration if you want to continue with dynamic dns in the BIND server.  Another big concern is for Active Directory environments. It is not recommended to go away from Microsoft DNS when using active directory due to the large number of records that are required to make that function properly. Failing to keep up with all of the manual changes can greatly impact your AD environment. One method to help avoid some of the headache would be to use both, and leave the _msdcs zone on your windows system. This will require some delegations to be put in place on the BIND server.

Tuesday, October 11, 2011

Viewing McAfee Exclusions (Powershell)

The following script is an example of remote registry key reading using Powershell with .Net classes. If you need to examine Mcafee scan exclusions, you can find them in one of three subkeys. Depending on the risk level of the process, you will need to look in Default, Low risk or High risk locations. Each entry for exclusions is in a named key with numeric increments in the names. Each value contains a pipe separated triple of information which describes the type of the rule, when the rule should be applied (and if it applies to subfolders), and the exclusion pattern. The script will return all of the exclusions for the specified process classification, in the format of a PSObject Array with decoded rule information. Due to the length of the exclusion pattern value, you may need to further format the output or limit the columns returned to better view the results.


#Get-McAfeeExclusions

$server = $Args[0]
$level = $args[1]

if (($server -eq $null) -or ($Server -eq "")) {
  write-host -foregroundcolor "yellow"  "usage:  Get-McAfeeExclusions servername [level]"
  write-host -foregroundcolor "yellow"  "    Enter Server name to list Mcafee AV exclusion list.  Optionally"
  Write-Host -ForegroundColor "yellow"  "    you can enter the level to view (Default, High, Low)."
  write-host 
  exit
}

if ($level -ne $null) {
 if (-not (("Default","High","Low") -contains $level)) {
  Write-Host -ForegroundColor "yellow" "Invalid level specified, use Default | High | Low"
  write-host
  exit
 }
} else {
 $level = "Default"
}

function decode-mcafee-exclusion-code([int]$code) {
 switch ($code) {
  5 { return "Windows File Protection" }
  4 { return "Extension" }
  3 { return "FilePath" }
  2 { return "CreationDate" }
  0 { return "ModifiedDate" }
 }
}

function decode-second-vals([int]$code) {
#for some reason I see path rules with values above 10 which have the same settings for below 10 rules.  7=15, 3=11
 switch ($code) {
  1 {return ("write")}
  2 {return ("read")}
  3 {return ("read","write")}
  5 {return ("subfolder","write")}
  6 {return ("subfolder","read")}
  7 {return ("subfolder","read","write")}
  11 {return ("read","write")}
  15 { return ("subfolder","read","write")}
 }
}

$key = "Software\McAfee\VSCore\On Access Scanner\McShield\Configuration\" + $level
$type = [Microsoft.Win32.RegistryHive]::LocalMachine
$regkey = [Microsoft.win32.registrykey]::OpenRemoteBaseKey($type,$server)
$regkey = $regkey.opensubkey($key)

if (-not ($?)) {
 #error opening key, mcafee may not be installed
 Write-Error ("Unable to open mcafee registry key: " + $key)
 exit 1
}

$vals = $regkey.getvaluenames()
$results = New-Object collections.ArrayList

foreach ($val in $vals) {
 if ($val -match "ExcludedItem") {
  $entry = $regkey.getvalue($val)
  $exclusionvals = $entry.split("|")
  $ruletype = decode-mcafee-exclusion-code $exclusionvals[0]
  $settings = decode-second-vals $exclusionvals[1]
  $excludeditem = $exclusionvals[2]
  $myresult = New-Object psobject
  Add-Member -InputObject $myresult NoteProperty System $server
  Add-Member -InputObject $myresult NoteProperty RuleType $ruletype
  Add-Member -InputObject $myresult NoteProperty Settings $settings
  Add-Member -InputObject $myresult NoteProperty Exclusion $excludeditem
  $results.add($myresult) >$null
 }
}

return $results 
Update: Jan 31, 2013
Now that I have come across some other versions of mcafee, it looks like the registry key structure is not standardized. If you get no values with the script, you can poke around in that same general registry area and find the appropriate key for your implementation.

Friday, October 7, 2011

Website automation for monitoring (oracle access manager 10, diagnostics)

I wanted to share an example that I had building a script for monitoring a web application by looking at its diagnostic page. To get to this page, there are one or more layers of logons, cookies that need to be collected, and forms to fill in before you get to the actual diagnostics information. To build this, I first looked at powershell, but there seemed to be no easy method of doing web interactions involving cookies. Apparently you need to extend the webclient class to allow cookies, which would require developing an app instead of just using built in commands. After looking around a bit, I found PERL LWP which is simple and easy to use. To get going with a complicated web transaction, you can use tools like fiddler or iehttpheaders to walk yourself through the transaction manually and gather the data. This will show you where cookies are used, and what information is passed in POST requests, as well as all of the URL's to use. Strip out all of the image download requests and similar javascript junk you don't need, then create a step by step walk through this with corresponding PERL LWP methods. The results can be processed as text, or any other method you can come up with using other parsing or modules operations. In my example, I just do some text matching and counting to determine what is a successful check and what is a failure.


use LWP::UserAgent;
use HTTP::Cookies;

#put args processing in here
if ($#ARGV < 3) {
 print "usage: runaccess-diag.pl user password server domain";
 exit 1;
}

$user = $ARGV[0];
$pass = $ARGV[1];
$server = $ARGV[2];
$domain = $ARGV[3];
$debug = $ARGV[4];



#################################################################################################
###This url is where you would end up if you tried to directly access the diagnostics page without
###logging in.  This will keep us out of frames and keep it all simple.
#################################################################################

$url = "http://" . $server . '/identity/oblix/apps/admin/bin/front_page_admin.cgi?program=commonLogin&returnUrl=..%2F..%2F..%2F..%2F..%2Faccess%2Foblix%2Fapps%2Fadmin%2Fbin%2Fsysmgmt.cgi%3FloginTry%3D1%26pluginName%3Dsysmgmt%26program%3DgenDiagnostics&backUrl=..%2F..%2F..%2F..%2F..%2Faccess%2Foblix%2Fapps%2Fadmin%2Fbin%2Fsysmgmt.cgi';

#####################################################################################
#Define a few other URLS for handling webgate and logoff, as well as diagnostics page code
#####################################################################################

$starturl = "http://" . $server . "/access/oblix/apps/admin/bin/front_page_admin.cgi";

$altLoginurl = "http://" . $server . "/login/OAMlogin.htm";

$webgateurl = "http://" . $server . "/access/oblix/apps/webgate/bin/webgate.dll";

$diagurl = "http://" . $server . "/access/oblix/apps/admin/bin/sysmgmt.cgi";

$endurl = "http://" . $server . "/access/oblix/lang/en-us/logout.html";

$secondaryurl = "http://" . $server . "/access/oblix/apps/admin/bin/sysmgmt.cgi?loginTry=1&pluginName=sysmgmt&program=genDiagnostics";


$cookie_jar = HTTP::Cookies->new(file => "c:\temp\cookie.lwp");
$browser = LWP::UserAgent->new;
$browser->cookie_jar( $cookie_jar);

######################################################
#open up diag url initially to get logon page cookie##
######################################################

$response = $browser->get($starturl);
sleep 5;
$response = $browser->get($altLoginurl);

if (!($response->content =~ /document.loginform.password.onkeypress/)) {
 print "

Could not load access page for $server

\n"; if ($debug) { print $response->content;} exit 2; } if ($debug) { print $response->content; } ######################################## #POST back to form with logon details ## #NOTE: Not all access servers are the same # They use different POST values and logon # methods, some requireing webgate interaction ######################################## $response = $browser->post($url,[ 'fromloginpage' => "true", 'comp' => "", 'login' => $user, 'password' => $pass, 'LoginDomain' => $domain]); if ($debug) { print "\n\n5\n" . $response->content; } $response = $browser->get($secondaryurl); if ($debug) { print "\n\n6\n" . $response->content; } if ((!($response->content =~ /Please select Access Server/))){ #$response = $browser->get($secondaryurl); $response = $browser->get($altLoginurl); if ($debug) { print "\n\n7\n" . $response->content; } $response = $browser->post($webgateurl,[ 'fromloginpage' => "true", 'comp' => "", 'uid' => $user, 'password' => $pass]); if ($debug) { print "\n\n8\n" . $response->content; } $response = $browser->get($secondaryurl); if ($debug) { print "\n\n9\n" . $response->content; } if ((!($response->content =~ /Please select Access Server/))){ print "

Logon failure for $server

\n"; print "\n\n10\n" . $response->content; exit 2; } } ################################################## #POST back again to run the diagnostics # # #That 'Program' var is not a typo in the script, # #problem is in the access code ################################################## $response = $browser->post($diagurl,[ 'program' => "generateDiagnositcsReportPage", 'allAsServers' => "true", 'as_server' => "true"]); if ($debug) { print "\n\n\n" . $response->content; } ################################################# #READ results of content from diagnostics page ## ################################################# $results = $response->content; if ($debug) { print "\n\n\n" . $response->content; } ################# #Do UP and DOWN status match counting. For a valid server #it will be UP with its overall status and UP for 3 components #and down for 3 components. # #This is not the best checking, ok for 2 servers ############### $match = ">Up<"; $UPcount = () = $results =~ /$match/g; $match = ">Down<"; $DOWNCount = () = $results =~ /$match/g; if ($UPcount < ($DOWNCount +2)) { print "

$server: Diagnostics showing failure

"; print $results; my $response = $browser->get($endurl); exit 3; } else { my $response = $browser->get($endurl); exit 0; }