Load WIF Issuer Thumbprint(s)

Octopus.Script exported 2018-06-07 by sbrickey belongs to ‘XML’ category.

Updates the web/app config files’ WIF TrustedIssuer thumbprints based on a realtime metadata request.

Changes are made to the following section: /configuration/system.identityModel/identityConfiguration/issuerNameRegistry/trustedIssuers

Parameters

When steps based on the template are included in a project’s deployment process, the parameters below can be set.

web config file path

FilePath =

null

Trusted Issuer Name

TrustedIssuerName = https://adfs/adfs/services/trust

Metadata Uri

MetadataUri = https://adfs/FederationMetadata/2007-06/FederationMetadata.xml

null

Script body

Steps based on this template will execute the following PowerShell script.

$FilePath          = "#{FilePath}"
$TrustedIssuerName = "#{TrustedIssuerName}"
$MetadataUri       = "#{MetadataUri}"


[void][System.Reflection.Assembly]::LoadWithPartialName("System.Xml.Linq")

# because Octo calls powershell steps in a stupid manor...
$charGT = [System.Text.Encoding]::ASCII.GetString( @(62) )

function Get-Thumbprints($MetadataUri) {
    $MetadataTxt = Invoke-WebRequest -Uri $MetadataUri
    $MetadataXml = [xml]($MetadataTxt.Content)
    
    $outval = @()
    # new certs
    
    $MetadataXml.EntityDescriptor.IDPSSODescriptor.KeyDescriptor | ? { $_.use -eq "signing" } | % {
        $Cert_Bytes = [System.Convert]::FromBase64String( $_.KeyInfo.X509Data.X509Certificate )
        $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( , $Cert_Bytes ) # powershell is stupid about arrays
        Write-Host "Found certificate for [$($_.use)] : [$($cert.NotBefore.ToString("yyyyMMdd")) - $($cert.NotAfter.ToString("yyyyMMdd"))] : Thumbprint [$($Cert.Thumbprint)] for Subject [$($Cert.Subject)]"
        $outval += $Cert.Thumbprint
    }
    return $outval
}

function Get-TextIndex([string]$text, [int]$LineNumber = 0, [int]$LinePosition = 0) {
    # Ported from : https://github.com/futurist/src-location/blob/master/index.js  function locationToIndex
    # NOTE: diff from source to address bug. Test-GetTextIndex validates the changes.
    $strLF = [char]10 # \n
    $strCR = [char]13 # \r
    $idx   = -1       # text index
    $lc    = 1        # Line Count
    for($i = 0; $lc -lt $LineNumber -and $i -lt $text.Length; $i++) {
        $idx++
        $c = $text[$i] # cur char
        if ($c -eq $strLF -or $c -eq $strCR) {
            $lc++
            if ($c -eq $strCR -and $text[$i + 1] -eq $strLF) { # DOS CRLF
                $i++
                $idx++
            }
        }
    }
    return $idx + $LinePosition
}

function Replace-TrustedIssuerThumbprints($FilePath, $TrustedIssuerName, $Thumbprints) {
    # Load the file twice - once as text for manipulation, once as XML for xpath and positions
    $fileText      = [System.IO.File]::ReadAllText($FilePath)
    $fileXml       = [System.Xml.Linq.XDocument]::Load($FilePath, [System.Xml.Linq.LoadOptions]::SetLineInfo -bor [System.Xml.Linq.LoadOptions]::PreserveWhitespace )
    $IdpsXml       = $fileXml.Descendants("configuration")[0].Descendants("system.identityModel")[0].Descendants("identityConfiguration")[0].Descendants("issuerNameRegistry")[0].Descendants("trustedIssuers")[0].Descendants("add")

    # Figure out which elements to manipulate... First delete from the bottom up, then replace the top-most element
    $IdpMatches    = $IdpsXml | ? { $_.Attribute("name").Value -eq $TrustedIssuerName } | Sort-Object -Property LineNumber, LinePosition -Descending
    $IdpsToDelete  = $IdpMatches | Select-Object -First ($IdpMatches.Count - 1)
    $IdpsToReplace = $IdpMatches | Select-Object -Last 1

    # Delete from the bottom up, so that the LineNumber/LinePosition remain valid during the manipulation
    foreach ($IdP in $IdpsToDelete) {
        Write-Host ( "DEL [{0}:{1}] {2}" -f $IdP.LineNumber, $IdP.LinePosition, $IdP.ToString() )

        $fileIdxOpen  = Get-TextIndex -text $fileText -LineNumber $IdP.LineNumber -LinePosition ( $IdP.LinePosition - 1 )
        $fileIdxClose = $fileText.IndexOf($charGT, $fileIdxOpen) + 1 # add one to include the closing >
        $fileSubstr   = $fileText.Substring($fileIdxOpen, $fileIdxClose - $fileIdxOpen)
        Write-Host ( "    [$fileIdxOpen .. $fileIdxClose] : $fileSubstr" )

        $fileIdxPrior = $fileText.LastIndexOf($charGT, $fileIdxOpen) + 1
        $fileText     = $fileText.Remove($fileIdxPrior, $fileIdxClose - $fileIdxPrior)
    }
    # Replace the top-most element with each thumbprint
    foreach ($IdP in $IdpsToReplace) {
        Write-Host ( "FIX [{0}:{1}] {2}" -f $IdP.LineNumber, $IdP.LinePosition, $IdP.ToString() )

        $fileIdxOpen  = Get-TextIndex -text $fileText -LineNumber $IdP.LineNumber -LinePosition ( $IdP.LinePosition - 1 )
        $fileIdxClose = $fileText.IndexOf($charGT, $fileIdxOpen) + 1 # add one to include the closing >
        $fileSubstr   = $fileText.Substring($fileIdxOpen, $fileIdxClose - $fileIdxOpen)
        Write-Host ( "    [$fileIdxOpen .. $fileIdxClose] : $fileSubstr" )

        $fileIdxPrior = $fileText.LastIndexOf($charGT, $fileIdxOpen) + 1
        $ElementDelim = $fileText.Substring($fileIdxPrior, $fileIdxOpen - $fileIdxPrior)
        Write-Host ( "   -[{0} .. {1}]" -f $fileIdxPrior, $fileIdxClose )
        $fileText     = $fileText.Remove($fileIdxPrior, $fileIdxClose - $fileIdxPrior)
        foreach ($Thumbprint in $Thumbprints) {
            $newAttribs = [System.Xml.Linq.XAttribute[]]@(
                                ( New-Object System.Xml.Linq.XAttribute("thumbprint", $Thumbprint       ) ),
                                ( New-Object System.Xml.Linq.XAttribute("name"      , $TrustedIssuerName) )
                            )
            $newValue = ( New-Object System.Xml.Linq.XElement("add", $newAttribs) ).ToString()
            $fileText = $fileText.Insert($fileIdxPrior, $ElementDelim + $newValue)
        }
    }
    return $fileText
}


$ThumbPrints       = Get-Thumbprints -MetadataUri $MetadataUri
$fileContent       = Replace-TrustedIssuerThumbprints -FilePath $FilePath -TrustedIssuerName $TrustedIssuerName -Thumbprints $ThumbPrints
[System.IO.File]::WriteAllText($FilePath, $fileContent)

Provided under the Apache License version 2.0.

Report an issue

To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.

{
  "Id": "77331575-0628-455d-b484-cfd4703e2081",
  "Name": "Load WIF Issuer Thumbprint(s)",
  "Description": "Updates the web/app config files' WIF TrustedIssuer thumbprints based on a realtime metadata request.\n\nChanges are made to the following section:\n/configuration/system.identityModel/identityConfiguration/issuerNameRegistry/trustedIssuers",
  "Version": 5,
  "ExportedAt": "2018-06-07T19:59:41.925Z",
  "ActionType": "Octopus.Script",
  "Author": "sbrickey",
  "Parameters": [
    {
      "Id": "9fa8d0c0-53a3-4d38-b183-bab04037869e",
      "Name": "FilePath",
      "Label": "web config file path",
      "HelpText": null,
      "DefaultValue": "",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "e97dcceb-fbf1-41f2-9829-e32c6322fe58",
      "Name": "TrustedIssuerName",
      "Label": "Trusted Issuer Name",
      "HelpText": "",
      "DefaultValue": "https://adfs/adfs/services/trust",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    },
    {
      "Id": "9670671a-5d7e-4232-9d45-5fc47d005167",
      "Name": "MetadataUri",
      "Label": "Metadata Uri",
      "HelpText": null,
      "DefaultValue": "https://adfs/FederationMetadata/2007-06/FederationMetadata.xml",
      "DisplaySettings": {
        "Octopus.ControlType": "SingleLineText"
      },
      "Links": {}
    }
  ],
  "Properties": {
    "Octopus.Action.Script.Syntax": "PowerShell",
    "Octopus.Action.Script.ScriptSource": "Inline",
    "Octopus.Action.RunOnServer": "false",
    "Octopus.Action.Script.ScriptBody": "$FilePath          = \"#{FilePath}\"\n$TrustedIssuerName = \"#{TrustedIssuerName}\"\n$MetadataUri       = \"#{MetadataUri}\"\n\n\n[void][System.Reflection.Assembly]::LoadWithPartialName(\"System.Xml.Linq\")\n\n# because Octo calls powershell steps in a stupid manor...\n$charGT = [System.Text.Encoding]::ASCII.GetString( @(62) )\n\nfunction Get-Thumbprints($MetadataUri) {\n    $MetadataTxt = Invoke-WebRequest -Uri $MetadataUri\n    $MetadataXml = [xml]($MetadataTxt.Content)\n    \n    $outval = @()\n    # new certs\n    \n    $MetadataXml.EntityDescriptor.IDPSSODescriptor.KeyDescriptor | ? { $_.use -eq \"signing\" } | % {\n        $Cert_Bytes = [System.Convert]::FromBase64String( $_.KeyInfo.X509Data.X509Certificate )\n        $Cert = New-Object System.Security.Cryptography.X509Certificates.X509Certificate2( , $Cert_Bytes ) # powershell is stupid about arrays\n        Write-Host \"Found certificate for [$($_.use)] : [$($cert.NotBefore.ToString(\"yyyyMMdd\")) - $($cert.NotAfter.ToString(\"yyyyMMdd\"))] : Thumbprint [$($Cert.Thumbprint)] for Subject [$($Cert.Subject)]\"\n        $outval += $Cert.Thumbprint\n    }\n    return $outval\n}\n\nfunction Get-TextIndex([string]$text, [int]$LineNumber = 0, [int]$LinePosition = 0) {\n    # Ported from : https://github.com/futurist/src-location/blob/master/index.js  function locationToIndex\n    # NOTE: diff from source to address bug. Test-GetTextIndex validates the changes.\n    $strLF = [char]10 # \\n\n    $strCR = [char]13 # \\r\n    $idx   = -1       # text index\n    $lc    = 1        # Line Count\n    for($i = 0; $lc -lt $LineNumber -and $i -lt $text.Length; $i++) {\n        $idx++\n        $c = $text[$i] # cur char\n        if ($c -eq $strLF -or $c -eq $strCR) {\n            $lc++\n            if ($c -eq $strCR -and $text[$i + 1] -eq $strLF) { # DOS CRLF\n                $i++\n                $idx++\n            }\n        }\n    }\n    return $idx + $LinePosition\n}\n\nfunction Replace-TrustedIssuerThumbprints($FilePath, $TrustedIssuerName, $Thumbprints) {\n    # Load the file twice - once as text for manipulation, once as XML for xpath and positions\n    $fileText      = [System.IO.File]::ReadAllText($FilePath)\n    $fileXml       = [System.Xml.Linq.XDocument]::Load($FilePath, [System.Xml.Linq.LoadOptions]::SetLineInfo -bor [System.Xml.Linq.LoadOptions]::PreserveWhitespace )\n    $IdpsXml       = $fileXml.Descendants(\"configuration\")[0].Descendants(\"system.identityModel\")[0].Descendants(\"identityConfiguration\")[0].Descendants(\"issuerNameRegistry\")[0].Descendants(\"trustedIssuers\")[0].Descendants(\"add\")\n\n    # Figure out which elements to manipulate... First delete from the bottom up, then replace the top-most element\n    $IdpMatches    = $IdpsXml | ? { $_.Attribute(\"name\").Value -eq $TrustedIssuerName } | Sort-Object -Property LineNumber, LinePosition -Descending\n    $IdpsToDelete  = $IdpMatches | Select-Object -First ($IdpMatches.Count - 1)\n    $IdpsToReplace = $IdpMatches | Select-Object -Last 1\n\n    # Delete from the bottom up, so that the LineNumber/LinePosition remain valid during the manipulation\n    foreach ($IdP in $IdpsToDelete) {\n        Write-Host ( \"DEL [{0}:{1}] {2}\" -f $IdP.LineNumber, $IdP.LinePosition, $IdP.ToString() )\n\n        $fileIdxOpen  = Get-TextIndex -text $fileText -LineNumber $IdP.LineNumber -LinePosition ( $IdP.LinePosition - 1 )\n        $fileIdxClose = $fileText.IndexOf($charGT, $fileIdxOpen) + 1 # add one to include the closing >\n        $fileSubstr   = $fileText.Substring($fileIdxOpen, $fileIdxClose - $fileIdxOpen)\n        Write-Host ( \"    [$fileIdxOpen .. $fileIdxClose] : $fileSubstr\" )\n\n        $fileIdxPrior = $fileText.LastIndexOf($charGT, $fileIdxOpen) + 1\n        $fileText     = $fileText.Remove($fileIdxPrior, $fileIdxClose - $fileIdxPrior)\n    }\n    # Replace the top-most element with each thumbprint\n    foreach ($IdP in $IdpsToReplace) {\n        Write-Host ( \"FIX [{0}:{1}] {2}\" -f $IdP.LineNumber, $IdP.LinePosition, $IdP.ToString() )\n\n        $fileIdxOpen  = Get-TextIndex -text $fileText -LineNumber $IdP.LineNumber -LinePosition ( $IdP.LinePosition - 1 )\n        $fileIdxClose = $fileText.IndexOf($charGT, $fileIdxOpen) + 1 # add one to include the closing >\n        $fileSubstr   = $fileText.Substring($fileIdxOpen, $fileIdxClose - $fileIdxOpen)\n        Write-Host ( \"    [$fileIdxOpen .. $fileIdxClose] : $fileSubstr\" )\n\n        $fileIdxPrior = $fileText.LastIndexOf($charGT, $fileIdxOpen) + 1\n        $ElementDelim = $fileText.Substring($fileIdxPrior, $fileIdxOpen - $fileIdxPrior)\n        Write-Host ( \"   -[{0} .. {1}]\" -f $fileIdxPrior, $fileIdxClose )\n        $fileText     = $fileText.Remove($fileIdxPrior, $fileIdxClose - $fileIdxPrior)\n        foreach ($Thumbprint in $Thumbprints) {\n            $newAttribs = [System.Xml.Linq.XAttribute[]]@(\n                                ( New-Object System.Xml.Linq.XAttribute(\"thumbprint\", $Thumbprint       ) ),\n                                ( New-Object System.Xml.Linq.XAttribute(\"name\"      , $TrustedIssuerName) )\n                            )\n            $newValue = ( New-Object System.Xml.Linq.XElement(\"add\", $newAttribs) ).ToString()\n            $fileText = $fileText.Insert($fileIdxPrior, $ElementDelim + $newValue)\n        }\n    }\n    return $fileText\n}\n\n\n$ThumbPrints       = Get-Thumbprints -MetadataUri $MetadataUri\n$fileContent       = Replace-TrustedIssuerThumbprints -FilePath $FilePath -TrustedIssuerName $TrustedIssuerName -Thumbprints $ThumbPrints\n[System.IO.File]::WriteAllText($FilePath, $fileContent)\n",
    "Octopus.Action.Script.ScriptFileName": null,
    "Octopus.Action.Package.FeedId": null,
    "Octopus.Action.Package.PackageId": null
  },
  "Category": "XML",
  "HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/Load-WIF-Issuer-Thumbprints.json",
  "Website": "/step-templates/77331575-0628-455d-b484-cfd4703e2081",
  "Logo": "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",
  "$Meta": {
    "Type": "ActionTemplate"
  }
}

History

Page updated on Thursday, June 7, 2018