Thursday, November 19, 2015

Monitoring your CDN's Webservers

(This post considers options for writing a monitor in powershell that can handle round-robin DNS load-distributed web servers, especially those that you do not control)

Premise

After observing some issues with a vendor's CDN having occasional server/service outages, I decided to write some powershell to dig through and give me a quick analysis so I could determine where the problem was and what effect it should have on the users. Some of you may read this and think "If the site is down, it's down, right?"

Well many websites now spread their resources across CDNs (Content Delivery Networks). This means that while the html page may come from one webserver (or even one CDN), the javascript and CSS file may come from another, and the images and videos from yet another. You can use the Developer Tools in your web browser to dig up out the sources for (at least THIS load) of the website.

On top of this, there are a few approaches to load balancing, where the exact same resources are duplicated across many servers, with the intent of spreading traffic around and reducing the chances of downtime. Citrix's Netscaler is a popular (and VERY capable) option, but sometimes you'll see the vendor go down the cheaper and easier path: Round-Robin DNS.

Approach

In Powershell, you can invoke the GetHostAddresses method to see how many IP addresses are linked to a DNS entry.

[System.Net.DNS]::GetHostAddresses("google.com").ipAddressToString

At the time of this writing, that returns 11 IP addresses. If you want to dig into nslookup, you can watch it cycle through the IPs in that list as you repeatedly query for google.com.

So what if your CDN is using Round Robin, and your customers are complaining of an outage, and the site appears up?


function test-CDNPage{
    Param
        ([string]$url) #Properly Formatted with http:// or https://
    try{
        write-host -ForegroundColor Cyan "Trying $url"
        Invoke-WebRequest -Uri $url
    }
    Catch [System.Net.WebException]
    {
    if ($_.exception -match "403")
        {
            # 403 is expected because CDNs typically dont allow IP access
            Write-host "403" -ForegroundColor Green
            $value = $true
        }
        ELSE
        {
            # A non-403 response is treated as 
            write-host "Non-403 Response" -ForegroundColor Red
            $_
            $value = $false
        }   
    }
    #Add Results to Object
    $thisResult = New-Object psobject -property @{
        url        = $url
        Responding = $value
        }
    return $thisResult
}

$data = @()
$site = "google.com"
$ipaddys = [system.net.dns]::GetHostAddresses($site).IPAddressToString
foreach ($ip in $ipaddys){
    $data += testpage "http://$ip"
}


Now this code probably won't return anything because Google isn't a CDN. They see you trying to access the IP directly and re-direct your web browser to https:\\google.com. They likely also have some smarter handling behind their Round-Robin DNS solution, as they are...Google.

But consider a company like CloudFlare. They may be hosting multiple different websites from multiple different companies on their servers.

When you try to access a site like that, you load a page telling you that "direct IP access is not allowed" in your browser, and powershell's Invoke-WebRequest throws a 403 Terminating error. This is where Powershell can do things that some other monitoring tools can not. The powershell snippet above uses a try/catch statement to look at the error. If the error is a 403, it sets the $value to true. If the error is NOT a 403, it sets the $value to false. I've set this up to output an XML file every minute that is consumable by my client's monitoring system, and now they can see when individual pieces of their vendor's web services go down.

Having solid data like this can frequently get you past the first tier support layer, that at many companies is incapable of solving network issues on their own end, and is typically only a fence I must cross to reach an actual solution.

Monday, October 12, 2015

Netscaler Gateway - Configure inside/outside access rules based on AD Groups

I fought with this one for awhile. Like many others, the client had remote access configured to run through a Netscaler to a Web Interface server. And also like many others, we had to migrate this one toward Netscaler + Storefront. The detail here was that the client had two groups of users: Those allowed to work from home, and those who were not. This was controlled by AD membership in a security group.

On the older setup, the membership was checked after the netscaler, at the web-interface server. So after much thinking, more coffee, and a couple applications of forehead to desk, I determined I was going to use session policies to determine which IP address the client was coming from, and filter on AD group if the client was coming from the IP of the firewall.

I'm not covering certificate configuration, or look and feel, there are quite a few blogs that have covered these topics better then I could have done. This is about session policies and security.


Configure AAA Groups

  1. After Setting up your Gateway vServer, make sure you configure your AD Domain under the "Authentication" panel of your vserver.
  2. Navigate to "Netscaler Gateway" -> "User Administration" -> "AAA Groups".
  3. Add a new group (The name you enter here has to match your AD Security group name EXACTLY, it's case sensitive. If there was a place to use copy and paste, this is one of them.)
  4. On the next screen, click "Authorization Policies" on the right bar.


  5. Click on "No Authorization Policy"
  6. Click "Add Binding"
  7. Click the "+" Next to "Select Policy" (after the first time, you can choose the created policy for any other AD groups)
  8. Give it a name like "Auth_Policy_Allow", make sure the action is set to "Allow" and enter "ns_true" (no quotes) for the Expression, then click "Create".
  9. Click "Bind"
  10. Click "Close"
  11. Repeat Steps 3-10 above for any other AD Groups you may have.

Configure Session Profile

  1. Within your Gateway vServer, navigate to the Policies tab, Click the "+" in the upper right to add. (If the Policies tab is not shown, add it from the "Advanced Settings" pane on the right.)
  2. Select "Session" and "Request", then click "Continue".
  3. Click "Add Binding"

  4. Click the "+" to the right of "Select Policy".
  5. Click the "+" next to the "Profile" selector.
  6. Give the Profile a name. As best as I understand, the Profile controls some of the connection settings, the Policy controls when the bound profile is used. The Binding links the previous two to a vServer, in an order of application. This may help you choose your name.
  7. Click the "Security" tab.
    - Check "Default Authorization Action" and set to "Deny" (This is why we set the auth policy on the AAA groups)
    - Check "Advanced Settings"
    - Check "Authorization Groups" and move any AAA groups form the left to the right to give them access.
  8. Click the "Published Applications" tab, and configure with your storefront Web Interface Address (your settings may differ)
  9. Click "Create" to save your Session Profile.
  10. Now your profile will be set, you need to name your Policy, and set an expression. If all your external traffic comes from a single IP, say.... 192.168.0.1 then your expression would be:

    REQ.IP.SOURCEIP == 192.168.0.1

    If your external traffic comes from a subnet, IP range, or a few scattered IPs, you'll need to build a bigger expression.
  11. Now that your profile and policy is set, you have to give it a Priority Number. I don't claim to have a full understanding of how Netscaler Priorities work, but I do know that the Deny binding number needs to be LOWER then the Allow binding (which we still have to create). Pick your number, and choose "Bind".
  12. Now redo the above steps, for your internal traffic, BUT
    - Leave out the security config (step 7)
    - For the policy expression (step 10)  enter:  ns_true
    - For the binding priority, go one lower (if you picked 100, go with 99
At this point, you should have a Gateway vServer that allows logins for all internally, but requires your AD membership when coming from outside addresses. If you need to test without actually going outside your network, you can use a different expression to treat your admin subnet or IP address as "outside". This expression allows you to filter on a subnet instead, just make sure you don't lock out anyone important :D

REQ.IP.SOURCEIP == 192.168.2.0 -netmask 255.255.255.0     













Wednesday, September 30, 2015

StoreFront 3 - Customize the Placeholder text on the username field, and a simple CSS background.

PlaceHolder Text

Many of us are making adjustments to a new StoreFront 3 implementation. I found that in particular circumstances, the placeholder text of "domain\user or domain@user.com" would just be confusing. This is due to the client having only one trusted domain. Username alone is sufficient there. I dug around in Javascript and jQuery for a bit, and came up with a script that works great in web and Mac receivers, but breaks the Windows Native Receiver due to it not dealing well with looping scripts.

Asked on the Citrix boards, and I got this response:
1) Navigate to the resources folder for the Authentication Service, typically C:\inetpub\wwwroot\Citrix\Authentication\App_Data\resources
2) Open ExplicitFormsCommon..resx, e.g. ExplicitFormsCommon.en.resx in a text editor
3) Search for DomainUserAssistiveText
4) Adjust the value and save.
5) Restart IIS

CSS Background

I was also looking at that vast white space around the desktop and app icons in receiver, and didn't care for the void. So I brewed up a stylish background for the "storeViewSection". It displays some blueish tie stripes at an angle behind the icons, all in CSS. Drop this into the "/Citrix/StoreNameWeb/custom/custom.css" to use, and tweak as you need.
.storeViewSection{
 background:
  repeating-linear-gradient(
   225deg,
    /* Blue stripe*/
   #f0f0ff,
   #f0f0ff 3px,
    /*White Stripe*/
   #ffffff 3px,
   #ffffff 6px,
    /*Light Stripe*/
   #f6f6ff 6px,
   #f6f6ff 56px,
    /*White Stripe*/
   #ffffff 56px,
   #ffffff 59px,
    /*Blue Stripe*/
   #f0f0ff 59px,
   #f0f0ff 62px,
    /*White Space*/
   #ffffff 62px,
   #ffffff 124px
 );
}

Monday, May 18, 2015

Custom Event Logs

Creating a new EventLog in powershell

Create the new log:
New-EventLog -LogName MyEventLogName -Source scripts
See that it exists:
Get-EventLog -List
Write to it:
Write-EventLog -LogName MyEventLogName -Source scripts -Message "Dude, it works ... COOL!" -EventId 0 -EntryType information
And then pull the events from the log:
Get-EventLog -LogName MyEventLogName
Eventlogs created in this manner will show under the "Applications and Services Logs" branch in the Event Viewer (which can be launched with "show-eventlog"). You can use either the GUI, or the "Limit-EventLog" cmdlet to make changes to the size and retention options for the log (shown with get-eventlog -list)

Custom Permissions on Eventlogs

In Windows 2008 the SDDL is set with the "wevtutil" app.
To begin, we need to get the current SDDL string. It's the "ChannelAccess" line in the output of the following command:
wevtutil gl MyEventLogName
Copy the channelAccess: line into the text editor of your choice:

channelAccess: O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) )

Each of the (A;;xxx;;;x-x-x-xx) strings is a SDDL permission, the first "xxx" sets the permission, according to this:
Clear 0x4
Read 0x1
Write 0x2

The second "x-x-x-xx" is a user or group SID. Depending on the SID's source, it may be longer (for instance if it's coming from active directory). There is a list of "well-known" SIDs here. Build a new SDDL permission, and tag it onto the end of the existing string.

O:BAG:SYD:(A;;0xf0007;;;SY)(A;;0x7;;;BA)(A;;0x5;;;SO)(A;;0x1;;;IU)(A;;0x1;;;AU)(A;;0x1;;;SU)(A;;0x1;;;S-1-5-3)(A;;0x2;;;LS)(A;;0x2;;;NS)(A;;0x2;;;S-1-5-33)(A;;0x1;;;S-1-5-32-573) (A;;0x1;;; S-1-5-3-3127463467463))

Finally, we need to apply the new SDDL. Paste your modified SDDL String (complete with the O:BAG:SYD on the front) after the /ca:
wevtutil sl MyEventLogName /ca:
Attempting to set the SDDL from powershell will cause a mess of syntax errors, due to the powershell interpreter trying to parse the SDDL string. This may be avoidable with single-quotes, but I just ran "cmd", re-ran the command, and then ran "exit" to return to powershell.
Now with a new eventlog with custom permissions, you can use write-eventlog to write output from your powershell scripts.

Sources:
Jane Lewis Blog
Hey Scripting Guy Blog

Friday, January 30, 2015

Confluence User Macro - Using a rest interface with jquery and javascript

While implementing Confluence, I took note of the Jira ticket integration. Upon looking at WebHelpDesk, I found an available rest interface. However, WebHelpDesk does not include the CORS compliant responses in it's response packets, so browsers won't allow cross site-scripting. Using a netscaler or other similar solution, one could make the requests through such a means and have the response packets add the CORS compliant header.

With that hurdle out of the way, it was simply a matter of parsing the REST response using jquery, and building the HTML. Using the existing CSS references, and a custom icon, I built a macro that takes a ticket number in confluence, and builds it like a Jira ticket link.


## Macro title: WHD Ticket Macro
## Macro has a body: Y or N
## Body processing: Selected body processing option
## Output: Selected output option
##
## Developed by: Jared Shippy
## Date created: 01/30/2015
## Installed by: My Name


## @param Ticket:title=TicketNumber|type=string|required=true|desc=WebHelpDesk Ticket Number









Thursday, October 9, 2014

Update Existing PST file path for Users

Replaced an aging file server, and ran into the issue of Terminal Services users not being able to load their Outlook pst data files. After much surfing, searching, and a call to Microsoft, I still had nothing. Thanks to a co-worker and mucho caffeine, we finally located the reg keys (in binary) that stored the pst location.

The script below is tested against Outlook 2010, but has been successful in my testing. Set the $oldpath and $newpath to fit your environment. Test it against a few accounts before deploying. I take no responsibility for the use of this in any org. :D

$encodingType = [Text.Encoding]::Default.HeaderName
$oldPath = "" #Old fileserver/path name here
$newPath = "" #New fileserver/path name here


function bin2string{
    Param(
        $thispath, #Path to the Key
        [string]$thisProperty #Name of the property (typically in HEX)
    )
    $binary = Get-ItemProperty -path $thisPath.pspath | select -expand $thisProperty
    $string = [Text.Encoding]::GetEncoding($encodingType).GetString( $binary )
    return $string
}

function string2bin{
    param(
        [string]$thisString
    )
    $newBinary = [Text.Encoding]::GetEncoding($encodingType).GetBytes( $thisString )
    return $newBinary
}


set-location "HKCU:\Software\Microsoft\Windows NT\CurrentVersion\Windows Messaging Subsystem\Profiles\Default Outlook Profile"
$path = get-location


$keys = get-childitem $path

foreach($key in $keys){
    #write-host $key -ForegroundColor DarkCyan
    $properties = get-itemproperty -path $key.pspath
    #$properties | where {$_ -match "01020fff"}
    if (($properties | where {$_ -match "01020fff"}).count -ne 0 -AND ($properties | where {$_ -match "001f3006"}).count -ne 0){
        #Make sure this is an Outlook Data File Key
        if ((bin2string $key "001f3006") -match "O\WU\WT\WL\WO\WO\WK\W+D\WA\WT\WA\W+F\WI\WL\WE"){
            Write-Host "Found $(bin2string $key "001f3006") in $Key" -ForegroundColor Yellow

            #Get string from Property 01020fff, the functional Path property
            $nowString = bin2string $key "01020fff"
            #Fix string in Property 01020fff, the functional Path property
            $nowString = $nowString -replace $oldPath,$newPath
            #Convert Fixed String Back into Binary
            $newBinary = string2bin $nowString
            #Set the Bin as the Value of Property 01020fff, the functional Path property
            Set-ItemProperty -path $key.pspath  -name "01020fff" -value $newBinary

            #Get string from Property 001f6700
            $nowString2 = bin2string $key "001f6700"
            $nowString2
            #Fix string in Property 001f6700
            $nowString2 = $nowString2 -replace ($oldPath.ToCharArray() -join "\W"),($newPath.ToCharArray() -join " ")
            $nowString2
            #Convert Fixed String Back into Binary
            $newBinary2 = string2bin $nowString2
            #Set the Bin as the Value of Property 001f6700
            Set-ItemProperty -path $key.pspath  -name "001f6700" -value $newBinary2

            }
        }
    }

Wednesday, October 1, 2014

Updating Shortcuts for a File Server replacement

When retiring an older File Server, one has to account for the many possible uses and references to the old file server name. Shortcuts is one that can be addressed via Powershell. The following script finds all shortcuts that have the $from server, and update the references to point to the $to server (these are set in the script beforehand). The only catch is that I didn't build in long file name support, so if you have paths over 255 characters, some additions will be required.
<# 
 .NOTES
 ===========================================================================
  Author: Jared Shippy
  Created: 09/19/2014
 ===========================================================================
 .DESCRIPTION
  Script for updating shortcuts for new file server location
 .PARAMETER Path
  Path to target folder.
 .PARAMETER Recurse
  Updates shortcuts through subfolders if used.
 .PARAMETER Whatif
  Writes no changes, displays files that would be affected.
 .EXAMPLE
  # See which shortcuts would be affected in c:\testFolder (subfolders included)
  lnkUpdate -path c:\testFolder -recurse -whatif
 .EXAMPLE
  # Make the perform the change in c:\testfolder ONLY (no subfolders)
  lnkUpdate -path c:\testfolder
#>

param (
 # Path to root of Targeted Folder
 [Parameter(Mandatory=$true)]
 [string]$path,
 # Perform Recursive?
 [switch]$recurse,
 # Does nothing, shows what it *would* do
 [switch]$whatif
)

# Initialization Variables
$shell = New-Object -ComObject wscript.shell
$from = "oldServer" #The name of the old file server
$to = "newServer" #The name of the new file server

# Targeting Logic - To recurse or not recurse
$lnkFiles = $null
if ($recurse -eq $true)
 {
  $lnkFiles = Get-ChildItem -Recurse (Join-Path $path \*.lnk)
 }
 else
 {
  $lnkFiles = Get-ChildItem (Join-Path $path \*.lnk)
}
foreach ($file in $lnkFiles)
{
 
 if (($shell.createShortcut($file.fullname)).targetPath -match "$from" -or ($shell.createShortcut($file.fullname)).workingdirectory -match "$from" -or ($shell.createShortcut($file.fullname)).arguements -match "$from")
 
 {
  Write-Host "Match on $($file.fullname)" -ForegroundColor 'DarkCyan'
  if ($whatif -eq $true)
  {
   Write-Host "WHATIF: WOULD replace path here"
  }
  else
  {
   $fixThis = $shell.createShortcut($file.fullname)
   $fixThis.targetPath = $fixThis.targetPath -replace "$from", "$to"
   $fixThis.workingDirectory = $fixThis.workingDirectory -replace "$from", "$to"
   $fixThis.arguments = $fixThis.arguments -replace "$from", "$to"
   $fixThis.Save()
  }
  
 }
}



# check and update links
foreach ($shortcut in $shortcuts)
{
 # Find the correct property for the next line:
 If ($shortcut.property -match "$from")
 {
  $shortcut.property -replace "$from", "$to"
 }
 else
 {
  Write-Host "No $from in this Shortcut" -ForegroundColor Green
  
 }
}