safeguard-devops.psm1
# Make sure that safeguard-ps is installed if (-not (Get-Module safeguard-ps)) { Import-Module safeguard-ps } if (-not (Get-Module safeguard-ps)) { throw "safeguard-devops requires safeguard-ps. Please using Install-Module to install safeguard-ps." } # Global session variable for login information Remove-Variable -Name "SgDevOpsSession" -Scope Global -ErrorAction "SilentlyContinue" New-Variable -Name "SgDevOpsSession" -Scope Global -Value $null $MyInvocation.MyCommand.ScriptBlock.Module.OnRemove = { Set-Variable -Name "SgDevOpsSession" -Scope Global -Value $null -ErrorAction "SilentlyContinue" } # Make sure SSL is configured properly to use TLS instead Edit-SslVersionSupport # Helpers function Out-SgDevOpsExceptionIfPossible { [CmdletBinding()] Param( [Parameter(Mandatory=$true,Position=0)] [object]$ThrownException ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if (-not ([System.Management.Automation.PSTypeName]"Ex.SgDevOpsMethodException").Type) { Add-Type -TypeDefinition @" using System; using System.Runtime.Serialization; namespace Ex { public class SgDevOpsMethodException : System.Exception { public SgDevOpsMethodException() : base("Unknown SgDevOpsMethodException") {} public SgDevOpsMethodException(int httpCode, string httpMessage, string errorMessage, string errorJson) : base(httpCode + ": " + httpMessage + " -- " + errorMessage) { HttpStatusCode = httpCode; ErrorMessage = errorMessage; ErrorJson = errorJson; } public SgDevOpsMethodException(string message, Exception innerException) : base(message, innerException) {} protected SgDevOpsMethodException (SerializationInfo info, StreamingContext context) : base(info, context) {} public int HttpStatusCode { get; set; } public string ErrorMessage { get; set; } public string ErrorJson { get; set; } } } "@ } $local:ExceptionToThrow = $ThrownException if ($ThrownException.Response) { Write-Verbose "---Response Status---" if ($ThrownException.Response | Get-Member StatusDescription -MemberType Properties) { $local:StatusDescription = $ThrownException.Response.StatusDescription } elseif ($ThrownException.Response | Get-Member ReasonPhrase -MemberType Properties) { $local:StatusDescription = $ThrownException.Response.ReasonPhrase } Write-Verbose "$([int]$ThrownException.Response.StatusCode) $($local:StatusDescription)" Write-Verbose "---Response Body---" if ($ThrownException.Response | Get-Member GetResponseStream -MemberType Methods) { $local:Stream = $ThrownException.Response.GetResponseStream() $local:Reader = New-Object System.IO.StreamReader($local:Stream) $local:Reader.BaseStream.Position = 0 $local:Reader.DiscardBufferedData() $local:ResponseBody = $local:Reader.ReadToEnd() $local:Reader.Dispose() } elseif ($ThrownException.Response | Get-Member Content -MemberType Properties) { # different properties and methods on net core try { $local:ResponseBody = $ThrownException.Response.Content.ReadAsStringAsync().Result } catch {} } if ($local:ResponseBody) { Write-Verbose $local:ResponseBody try # try/catch is a workaround for this bug in PowerShell: { # https://stackoverflow.com/questions/41272128/does-convertfrom-json-respect-erroraction $local:ResponseObject = (ConvertFrom-Json $local:ResponseBody) # -ErrorAction SilentlyContinue } catch { try { $local:ResponseObject = (ConvertFrom-Json ($local:ResponseBody -replace '""','" "')) } catch {} } if ($local:ResponseObject.Message) # SgDevOps error { $local:Message = $local:ResponseObject.Message $local:ExceptionToThrow = (New-Object Ex.SgDevOpsMethodException -ArgumentList @( [int]$ThrownException.Response.StatusCode, $local:StatusDescription, $local:Message, $local:ResponseBody )) } elseif ($local:ResponseObject.errors) # validation error { $local:ExceptionToThrow = (New-Object Ex.SgDevOpsMethodException -ArgumentList @( [int]$ThrownException.Response.StatusCode, $local:StatusDescription, [string]($local:ResponseObject.errors." " -join ", "), $local:ResponseBody )) } elseif ($local:ResponseObject.error_description) # rSTS error { $local:ExceptionToThrow = (New-Object Ex.SgDevOpsMethodException -ArgumentList @( [int]$ThrownException.Response.StatusCode, $local:StatusDescription, $local:ResponseObject.error_description, $local:ResponseBody )) } else # ?? { $local:ExceptionToThrow = (New-Object Ex.SgDevOpsMethodException -ArgumentList @( [int]$ThrownException.Response.StatusCode, $local:StatusDescription, "Unknown error", $local:ResponseBody )) } } else # ?? { $local:ExceptionToThrow = (New-Object Ex.SgDevOpsMethodException -ArgumentList @( [int]$ThrownException.Response.StatusCode, $local:StatusDescription, "", "<unable to retrieve response content>" )) } } if ($ThrownException.Status -eq "TrustFailure") { Write-Host -ForegroundColor Magenta "To ignore SSL/TLS trust failure use the -Insecure parameter to bypass server certificate validation." } Write-Verbose "---Exception---" $ThrownException | Format-List * -Force | Out-String | Write-Verbose if ($ThrownException.InnerException) { Write-Verbose "---Inner Exception---" $ThrownException.InnerException | Format-List * -Force | Out-String | Write-Verbose } throw $local:ExceptionToThrow } function Resolve-ServiceAddressAndPort { [CmdletBinding()] Param( [Parameter(Mandatory=$false, Position=0)] [string]$ServiceAddress, [Parameter(Mandatory=$false, Position=1)] [int]$ServicePort ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if (-not $ServiceAddress) { if ($SgDevOpsSession -and $SgDevOpsSession.ServiceAddress) { $ServiceAddress = $SgDevOpsSession.ServiceAddress } else { $ServiceAddress = (Read-Host "ServiceAddress") } } if ($ServicePort) { $local:SpecifiedPort = $ServicePort } if ($ServiceAddress.ToCharArray() -contains ':') { $local:Values = $ServiceAddress.Split(":"); $ServiceAddress = $local:Values[0] if ($local:SpecifiedPort -and $local:SpecifiedPort -ne $ServicePort) { throw "Specified different ports in ServiceAddress (${ServiceAddress}) and in ServicePort (${ServicePort})" } $ServicePort = [int]$local:Values[1] } if (-not $local:SpecifiedPort -and -not $ServicePort) { if ($SgDevOpsSession -and $SgDevOpsSession.ServicePort) { $ServicePort = $SgDevOpsSession.ServicePort } else { $ServicePort = 443 # default } } @{ ServiceAddress = $ServiceAddress; ServicePort = $ServicePort } } function Get-TlsCertificateFromEndpoint { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [string]$ServerAddress, [Parameter(Mandatory=$true, Position=1)] [int]$ServerPort ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Certificate = $null $local:TcpClient = New-Object -TypeName System.Net.Sockets.TcpClient try { $local:TcpClient.Connect($ServerAddress, $ServerPort) $local:TcpStream = $local:TcpClient.GetStream() $local:Callback = { param($s, $c, $ch, $e) return $true } $local:SslStream = New-Object -TypeName System.Net.Security.SslStream -ArgumentList @($local:TcpStream, $true, $local:Callback) try { $local:AllowedProtocols = ([System.Security.Authentication.SslProtocols]::Tls11 ` -bor [System.Security.Authentication.SslProtocols]::Tls12 ` -bor [System.Security.Authentication.SslProtocols]::Tls13) $local:SslStream.AuthenticateAsClient('', $null, $local:AllowedProtocols, $false) $local:Certificate = $local:SslStream.RemoteCertificate } finally { $local:SslStream.Dispose() } } finally { $local:TcpClient.Dispose() } if ($local:Certificate) { if ($local:Certificate -isnot [System.Security.Cryptography.X509Certificates.X509Certificate2]) { $local:Certificate = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList $Certificate } $local:Certificate } else { throw "Unable to get TLS server certificate information for ${ServerAddress}:${ServerPort}" } } function Confirm-TlsCertificateInformation { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [string]$ServerName, [Parameter(Mandatory=$true, Position=1)] [string]$ServerAddress, [Parameter(Mandatory=$true, Position=2)] [int]$ServerPort ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } Write-Host -ForegroundColor Yellow "VALIDATE certificate information for $ServerName (${ServerAddress}:${ServerPort}):" $local:Certificate = (Get-TlsCertificateFromEndpoint $ServerAddress $ServerPort) Write-Host "Thumbprint: $($local:Certificate.Thumbprint)" Write-Host "-----BEGIN CERTIFICATE-----" Write-Host ([System.Convert]::ToBase64String($Certificate.RawData) -replace ".{64}","`$0`n") Write-Host "-----END CERTIFICATE-----" (Get-Confirmation "Validate $ServerName Certificate" "Do you want to trust this connection to ${ServerName}?" ` "Validate." "Cancels this operation.") Write-Host "" Write-Host "" } function Get-ApplianceToken { [CmdletBinding()] Param( [Parameter(Mandatory=$false, Position=0)] [string]$Appliance, [Parameter(Mandatory=$false)] [switch]$Gui, [Parameter(Mandatory=$false)] [switch]$Insecure, [Parameter(Mandatory=$false)] [int]$ApplianceApiVersion = 3 ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if ($SafeguardSession -and $SafeguardSession.AccessToken) { $SafeguardSession.AccessToken } else { if ($Gui) { (Connect-Safeguard $Appliance -Gui -Version $ApplianceApiVersion -Insecure:$Insecure -NoSessionVariable) } else { (Connect-Safeguard $Appliance -Version $ApplianceApiVersion -Insecure:$Insecure -NoSessionVariable) } } } function New-SgDevOpsUrl { [CmdletBinding()] Param( [Parameter(Mandatory=$true,Position=3)] [string]$Url, [Parameter(Mandatory=$false)] [object]$Parameters ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if ($Parameters -and $Parameters.Length -gt 0) { $Url += "?" $Parameters.Keys | ForEach-Object { $Url += ($_ + "=" + [uri]::EscapeDataString($Parameters.Item($_)) + "&") } $Url = $local:Url -replace ".$" } $Url } function Invoke-WithoutBody { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [object]$HttpSession, [Parameter(Mandatory=$true, Position=1)] [string]$Method, [Parameter(Mandatory=$true, Position=2)] [string]$Url, [Parameter(Mandatory=$true, Position=3)] [object]$Headers, [Parameter(Mandatory=$false)] [object]$Parameters, [Parameter(Mandatory=$false)] [string]$InFile, [Parameter(Mandatory=$false)] [string]$OutFile, [Parameter(Mandatory=$false)] [int]$Timeout ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $Url = (New-SgDevOpsUrl $Url -Parameters $Parameters) Write-Verbose "Url=$($Url)" Write-Verbose "Parameters=$(ConvertTo-Json -InputObject $Parameters)" if ($InFile) { Write-Verbose "File-based payload -- $InFile" Invoke-RestMethod -Method $Method -Headers $Headers -Uri $Url -InFile $InFile -OutFile $OutFile -TimeoutSec $Timeout -WebSession $HttpSession } else { Invoke-RestMethod -Method $Method -Headers $Headers -Uri $Url -OutFile $OutFile -TimeoutSec $Timeout -WebSession $HttpSession } } function Invoke-WithBody { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [object]$HttpSession, [Parameter(Mandatory=$true, Position=1)] [string]$Method, [Parameter(Mandatory=$true, Position=2)] [string]$Url, [Parameter(Mandatory=$true, Position=3)] [object]$Headers, [Parameter(Mandatory=$false)] [object]$Parameters, [Parameter(Mandatory=$false)] [object]$Body, [Parameter(Mandatory=$false)] [object]$JsonBody, [Parameter(Mandatory=$false)] [string]$OutFile, [Parameter(Mandatory=$false)] [int]$Timeout ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:BodyInternal = $JsonBody if ($Body) { $local:BodyInternal = (ConvertTo-Json -Depth 20 -InputObject $Body) } $Url = (New-SgDevOpsUrl $Url -Parameters $Parameters) Write-Verbose "Url=$($Url)" Write-Verbose "Parameters=$(ConvertTo-Json -InputObject $Parameters)" Write-Verbose "---Request Body---" Write-Verbose "$($local:BodyInternal)" Invoke-RestMethod -Method $Method -Headers $Headers -Uri $Url ` -Body ([System.Text.Encoding]::UTF8.GetBytes($local:BodyInternal)) ` -OutFile $OutFile -TimeoutSec $Timeout -WebSession $HttpSession } function Invoke-Internal { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [object]$HttpSession, [Parameter(Mandatory=$true, Position=1)] [string]$Method, [Parameter(Mandatory=$true, Position=2)] [string]$Url, [Parameter(Mandatory=$true, Position=3)] [object]$Headers, [Parameter(Mandatory=$false)] [object]$Parameters, [Parameter(Mandatory=$false)] [object]$Body, [Parameter(Mandatory=$false)] [object]$JsonBody, [Parameter(Mandatory=$false)] [string]$InFile, [Parameter(Mandatory=$false)] [string]$OutFile, [Parameter(Mandatory=$false)] [int]$Timeout ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } try { switch ($Method.ToLower()) { {$_ -in "get","delete"} { if ($Body -or $JsonBody) { Invoke-WithBody $HttpSession $Method $Url $Headers -Parameters $Parameters -Body $Body -JsonBody $JsonBody -OutFile $OutFile -Timeout $Timeout } else { Invoke-WithoutBody $HttpSession $Method $Url $Headers -Parameters $Parameters -Timeout $Timeout -OutFile $OutFile } break } {$_ -in "put","post"} { if ($InFile) { Invoke-WithoutBody $HttpSession $Method $Version $Url $Headers -Parameters $Parameters -InFile $InFile -OutFile $OutFile -Timeout $Timeout } else { Invoke-WithBody $HttpSession $Method $Url $Headers -Parameters $Parameters -Body $Body -JsonBody $JsonBody -OutFile $OutFile -Timeout $Timeout } break } } } catch { if ($_.ErrorDetails.Message) { $messageObject = $_.ErrorDetails.Message | ConvertFrom-Json Write-Host -foreground Red $messageObject.Message } Out-SgDevOpsExceptionIfPossible $_.Exception } } <# .SYNOPSIS Get the status of the Safeguard appliance associated with this Secrets Broker. .DESCRIPTION The status information includes Appliance ID, Appliance Name, Appliance Version, Appliance State, Appliance Network Address, and API version. It also includes whether or not Secrets Broker has been instructed to ignore validation of the Appliance's TLS server certificate. If there is no associated Secrets Broker the response will contain empty values. .PARAMETER ServiceAddress Network address (IP or DNS) of the Secrets Broker. This value may also include the port information delimited with a colon (e.g. ssbdevops.example.com:12345). .PARAMETER ServicePort Port information for connecting to the Secrets Broker. (default: 443) .PARAMETER Insecure Whether or not to validate the Secrets Broker's TLS server certificate. .PARAMETER ServiceApiVersion API version for the Secrets Broker. (default: 1) .EXAMPLE Get-SgDevOpsApplianceStatus localhost -Insecure .EXAMPLE Get-SgDevOpsApplianceStatus ssbdevops.example.com:12345 #> function Get-SgDevOpsApplianceStatus { [CmdletBinding()] Param( [Parameter(Mandatory=$false, Position=0)] [string]$ServiceAddress, [Parameter(Mandatory=$false, Position=1)] [int]$ServicePort, [Parameter(Mandatory=$false)] [switch]$Insecure, [Parameter(Mandatory=$false)] [int]$ServiceApiVersion = 1 ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } try { Edit-SslVersionSupport if ($Insecure -or $SgDevOpsSession.Insecure) { Disable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } $local:Resolved = (Resolve-ServiceAddressAndPort $ServiceAddress $ServicePort) $ServiceAddress = $local:Resolved.ServiceAddress $ServicePort = $local:Resolved.ServicePort $local:Url = "https://${ServiceAddress}:${ServicePort}/service/devops/v${ServiceApiVersion}/Safeguard" Invoke-RestMethod -Method GET -Uri $local:Url -Headers @{ "Accept" = "application/json"; "Content-type" = "application/json"; } } catch { Out-SgDevOpsExceptionIfPossible $_.Exception } finally { if ($Insecure -or $SgDevOpsSession.Insecure) { Enable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } } } <# .SYNOPSIS Get the current state of the Secrets Broker TLS certificate validation. .DESCRIPTION The Secrets Broker can be instructed to ignore TLS certificate validation when interacting with the Safeguard appliance. This cmdlet gets the current state of the TLS certificate validation. .EXAMPLE Get-SgDevOpsTlsValidation #> function Get-SgDevOpsTlsValidation { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } (-not (Invoke-SgDevOpsMethod GET Safeguard).IgnoreSsl) } <# .SYNOPSIS Enables TLS certificate validation for the Secrets Broker. .DESCRIPTION The Secrets Broker can be instructed to ignore TLS certificate validation when interacting with the Safeguard appliance. This cmdlet enables the Secrets Broker for TLS certificate validation. .EXAMPLE Enable-SgDevOpsTlsValidation #> function Enable-SgDevOpsTlsValidation { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Sg = (Invoke-SgDevOpsMethod GET Safeguard) $local:Sg.IgnoreSsl = $false Invoke-SgDevOpsMethod PUT Safeguard -Body $local:Sg } <# .SYNOPSIS Disables TLS certificate validation for the Secrets Broker. .DESCRIPTION The Secrets Broker can be instructed to ignore TLS certificate validation when interacting with the Safeguard appliance. This cmdlet disables the Secrets Broker for TLS certificate validation. .EXAMPLE Disable-SgDevOpsTlsValidation #> function Disable-SgDevOpsTlsValidation { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Sg = (Invoke-SgDevOpsMethod GET Safeguard) $local:Sg.IgnoreSsl = $true Invoke-SgDevOpsMethod PUT Safeguard -Body $local:Sg } <# .SYNOPSIS Log into Secrets Broker in this Powershell session for the purposes of using the API. .DESCRIPTION Secrets Broker relies on Safeguard for authentication and authorization. This cmdlet will authenticate the caller against the associated Safeguard appliance and use that to establish a login session for the . The other cmdlets in this module require that you first establish a login session using this cmdlet. Before you can establish a login session the first immediately after deployment you need to associate the Secrets Broker with a Safeguard Appliance. This can be done using Initialize-SgDevOps, which will walk you through the process. .PARAMETER ServiceAddress Network address (IP or DNS) of the Secrets Broker. This value may also include the port information delimited with a colon (e.g. ssbdevops.example.com:12345). .PARAMETER ServicePort Port information for connecting to the Secrets Broker. (default: 443) .PARAMETER Gui Display Safeguard login window in a browser. Supports 2FA. .PARAMETER Insecure Whether or not to validate the Secrets Broker's TLS server certificate. .PARAMETER ServiceApiVersion API version for the Secrets Broker. (default: 1) .EXAMPLE Connect-SgDevOps localhost -Insecure .EXAMPLE Connect-SgDevOps ssbdevops.example.com:12345 #> function Connect-SgDevOps { [CmdletBinding()] Param( [Parameter(Mandatory=$false, Position=0)] [string]$ServiceAddress, [Parameter(Mandatory=$false)] [int]$ServicePort, [Parameter(Mandatory=$false)] [switch]$Gui, [Parameter(Mandatory=$false)] [switch]$Insecure, [Parameter(Mandatory=$false)] [int]$ServiceApiVersion = 1 ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Resolved = (Resolve-ServiceAddressAndPort $ServiceAddress $ServicePort) $ServiceAddress = $local:Resolved.ServiceAddress $ServicePort = $local:Resolved.ServicePort try { $local:Status = (Get-SgDevOpsApplianceStatus $ServiceAddress $ServicePort -Insecure:$Insecure -ServiceApiVersion $ServiceApiVersion) } catch { Write-Host -ForegroundColor Yellow "WARNING: This Secrets Broker configuration has an error, usually this is because of failed TLS server validation." Write-Host -ForegroundColor Magenta "You must run Initialize-SgDevOps or Initialize-SgDevOpsAppliance with -Insecure option to correct TLS server validation." throw } if (-not $local:Status.ApplianceId) { Write-Host -ForegroundColor Magenta "Run Initialize-SgDevOps to assocate to a Safeguard appliance." throw "This Secrets Broker is not associated with a Safeguard appliance" } $local:Token = (Get-ApplianceToken $local:Status.ApplianceAddress -Gui:$Gui -Insecure:$Insecure -ApplianceApiVersion $local:Status.ApiVersion) $local:OldProgressPreference = $ProgressPreference try { Edit-SslVersionSupport if ($Insecure) { Disable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } $local:HttpSession = $null $local:Url = "https://${ServiceAddress}:${ServicePort}/service/devops/v${ServiceApiVersion}/Safeguard/Logon" $ProgressPreference = "SilentlyContinue" $local:Response = (Invoke-WebRequest -Method GET -UseBasicParsing -Uri $local:Url -Headers @{ "Accept" = "application/json"; "Content-type" = "application/json"; "Authorization" = "spp-token ${local:Token}" } -SessionVariable HttpSession) Write-Verbose "Setting up the SafeguardSession variable" Set-Variable -Name "SgDevOpsSession" -Scope Global -Value @{ "ServiceAddress" = $ServiceAddress; "ServicePort" = $ServicePort; "Insecure" = $Insecure; "AccessToken" = $local:Token; "Session" = $local:HttpSession; "Gui" = $Gui; "ServiceApiVersion" = $ServiceApiVersion } Write-Verbose $local:Response.Content Write-Host "Login Successful." } catch { Out-SgDevOpsExceptionIfPossible $_.Exception } finally { $ProgressPreference = $local:OldProgressPreference if ($Insecure) { Enable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } } } <# .SYNOPSIS Call a method in the Secrets Broker API. .DESCRIPTION This utility is useful for calling the Secrets Broker API for testing or scripting purposes. It provides a couple benefits over using curl.exe or Invoke-RestMethod by handling authentication, composing the Url, parameters, and body for the request. This script is meant to be used with the Connect-SgDevOps cmdlet which will create a login session so that it doesn't need to be passed to each call to this cmdlet. Call Disconnect-SgDevOps when finished. .PARAMETER Method HTTP method verb you would like to use: GET, PUT, POST, DELETE. .PARAMETER RelativeUrl Relative portion of the Url you would like to call starting after the version. .PARAMETER Parameters A hash table containing the HTTP query parameters to add to the Url. .PARAMETER Body A hash table containing an object to PUT or POST to the Url. .PARAMETER JsonBody A pre-formatted JSON string to PUT or Post to the URl. If -Body is also specified, this is ignored. It can sometimes be difficult to get arrays of objects to behave properly with hashtables in Powershell. .PARAMETER ExtraHeaders A hash table containing additional headers to add to the request. .EXAMPLE Invoke-SgDevOpsMethod GET Safeguard/A2ARegistration/RetrievableAccounts .EXAMPLE Invoke-SgDevOpsMethod PUT Plugins/HashiCorpVault/Accounts -Body $accounts .EXAMPLE Invoke-SgDevOpsMethod DELETE Plugins/HashiCorpVault/Accounts -Parameters @{ removeAll = true } #> function Invoke-SgDevOpsMethod { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [ValidateSet("Get","Put","Post","Delete",IgnoreCase=$true)] [string]$Method, [Parameter(Mandatory=$true, Position=1)] [string]$RelativeUrl, [Parameter(Mandatory=$false)] [HashTable]$Parameters, [Parameter(Mandatory=$false)] [object]$Body, [Parameter(Mandatory=$false)] [string]$JsonBody, [Parameter(Mandatory=$false)] [HashTable]$ExtraHeaders, [Parameter(Mandatory=$false)] [string]$OutFile = $null ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if (-not $SgDevOpsSession) { Write-Host -ForegroundColor Magenta "Run Connect-SgDevOps to initialize a session." throw "This cmdlet requires a connect session with the Secrets Broker" } try { Edit-SslVersionSupport if ($SgDevOpsSession.Insecure) { Disable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } $local:Url = "https://$($SgDevOpsSession.ServiceAddress):$($SgDevOpsSession.ServicePort)/service/devops/v$($SgDevOpsSession.ServiceApiVersion)/${RelativeUrl}" $local:Headers = @{ "Accept" = "application/json"; "Content-type" = "application/json"; } foreach ($local:Key in $ExtraHeaders.Keys) { $local:Headers[$local:Key] = $ExtraHeaders[$local:Key] } Write-Verbose "---Request---" Write-Verbose "Headers=$(ConvertTo-Json -InputObject $local:Headers)" $local:Headers["Authorization"] = "spp-token $($SgDevOpsSession.AccessToken)" Invoke-Internal $SgDevOpsSession.Session $Method $local:Url $local:Headers -Parameters $Parameters -Body $Body -JsonBody $JsonBody -OutFile $OutFile } finally { if ($SgDevOpsSession.Insecure) { Enable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } } } <# .SYNOPSIS Log out of a Secrets Broker in this Powershell session when finished using the API. .DESCRIPTION This utility will end your login session and remove the PowerShell session variable that was created by the Connect-SgDevOps cmdlet. .EXAMPLE Disconnect-SgDevOps #> function Disconnect-SgDevOps { [CmdletBinding()] Param( ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } try { if (-not $SgDevOpsSession) { Write-Host "Not logged in." } else { Invoke-SgDevOpsMethod GET "Safeguard/Logoff" } Write-Host "Log out Successful." } finally { Write-Host "Session variable removed." Set-Variable -Name "SgDevOpsSession" -Scope Global -Value $null } } <# .SYNOPSIS Associate this Secrets Broker with a Safeguard Appliance. .DESCRIPTION Associating a Secrets Broker with Safeguard allows it to be used for authentication. Secrets Broker can then be configured to use the Safeguard A2A API to synchronize secrets with target secret stores via plugins. Before you can establish a login session the first immediately after deployment you need to associate the Secrets Broker with a Safeguard Appliance. The best way to do that is to call Initialize-SgDevOps, which will walk you through the process interactively. Initialize-SgDevOps calls this cmdlet. If a Safeguard login session has already been established using Connect-Safeguard, then this cmdlet will reuse that connection, otherwise this cmdlet will authenticate to the Safeguard appliance interactively. .PARAMETER ServiceAddress Network address (IP or DNS) of the Secrets Broker. This value may also include the port information delimited with a colon (e.g. ssbdevops.example.com:12345). .PARAMETER ServicePort Port information for connecting to the Secrets Broker. (default: 443) .PARAMETER Appliance Network address (IP or DNS) of the Safeguard appliance. .PARAMETER Gui Display Safeguard login window in a browser. Supports 2FA. .PARAMETER Insecure Whether or not to store the Insecure flag in the Safeguard Appliance connection to ignore TLS server certificate validation. (IMPORTANT! This is not the same as the -Insecure option in other cmdlets--this is communications between Secrets Broker and Safeguard Appliance). It can be changed later using the Enable-SgDevOpsTlsValidation cmdlet. .PARAMETER Force This option will force Secrets Broker to associate with another Safeguard appliance even if it has already been associated. This option wil also remove any other prompts that might occur. .PARAMETER ApplianceApiVersion API version for the Safeguard Appliance. (default: 3) .PARAMETER ServiceApiVersion API version for the Secrets Broker. (default: 1) .EXAMPLE Initialize-SgDevOpsAppliance localhost -Insecure .EXAMPLE Initialize-SgDevOpsAppliance ssbdevops.example.com:12345 -Gui #> function Initialize-SgDevOpsAppliance { [CmdletBinding()] Param( [Parameter(Mandatory=$false, Position=0)] [string]$ServiceAddress, [Parameter(Mandatory=$false)] [int]$ServicePort, [Parameter(Mandatory=$false, Position=1)] [string]$Appliance, [Parameter(Mandatory=$false)] [switch]$Gui, [Parameter(Mandatory=$false)] [switch]$Insecure, [Parameter(Mandatory=$false)] [switch]$Force, [Parameter(Mandatory=$false)] [int]$ApplianceApiVersion = 3, [Parameter(Mandatory=$false)] [int]$ServiceApiVersion = 1 ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Resolved = (Resolve-ServiceAddressAndPort $ServiceAddress $ServicePort) $ServiceAddress = $local:Resolved.ServiceAddress $ServicePort = $local:Resolved.ServicePort try { # Initial check to status is always done with Insecure flag... out of box Secrets Broker uses a self-signed certificate $local:Status = (Get-SgDevOpsApplianceStatus $ServiceAddress $ServicePort -Insecure -ServiceApiVersion $ServiceApiVersion) if ($local:Status.ApplianceId) { Write-Host -ForegroundColor Yellow "WARNING: This Secrets Broker is currently associated with $($local:Status.ApplianceName) ($($local:Status.ApplianceAddress))." if ($Force) { $local:Confirmed = $true } else { $local:Confirmed = (Get-Confirmation "Initialize Secrets Broker" "Do you want to associate with a different Safeguard appliance?" ` "Initialize." "Cancels this operation.") Write-Host "" Write-Host "" } } else { # not associated yet $local:Confirmed = $true } } catch { Write-Host -ForegroundColor Yellow "WARNING: This Secrets Broker configuration has an error." Write-Host -ForegroundColor Magenta $_ $local:Confirmed = (Get-Confirmation "Initialize Secrets Broker" "Do you want to associate with a different Safeguard appliance?" ` "Initialize." "Cancels this operation.") } if ($local:Confirmed) { if ($Force -or (Confirm-TlsCertificateInformation "Secrets Broker" $ServiceAddress $ServicePort)) { if (-not $Appliance) { if ($SafeguardSession -and $SafeguardSession.Appliance) { $Appliance = $SafeguardSession.Appliance } else { $Appliance = (Read-Host "Appliance") } } if ($Force -or (Confirm-TlsCertificateInformation "Safeguard Appliance" $Appliance 443)) { $local:Token = (Get-ApplianceToken $Appliance -Gui:$Gui -Insecure:$Insecure -ApplianceApiVersion $ApplianceApiVersion) try { Edit-SslVersionSupport if ($Insecure) { Disable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } $local:Url = "https://${ServiceAddress}:${ServicePort}/service/devops/v${ServiceApiVersion}/Safeguard" $local:Status = (Invoke-RestMethod -Method PUT -Uri $local:Url -Headers @{ "Accept" = "application/json"; "Content-type" = "application/json"; "Authorization" = "spp-token ${local:Token}" } -Body @" { "ApplianceAddress": "$Appliance", "ApiVersion": "$ApplianceApiVersion", "IgnoreSsl": $(([string]([bool]$Insecure)).ToLower()) } "@) $local:Status } finally { if ($Insecure) { Enable-SslVerification if ($global:PSDefaultParameterValues) { $PSDefaultParameterValues = $global:PSDefaultParameterValues.Clone() } } } } else { Write-Host -ForegroundColor Yellow "Operation canceled." } } else { Write-Host -ForegroundColor Yellow "Operation canceled." } } else { Write-Host -ForegroundColor Yellow "Operation canceled." } } <# .SYNOPSIS Clear this Secrets Broker's association with a Safeguard Appliance. .DESCRIPTION Associating a Secrets Broker with Safeguard allows it to be used for authentication. Secrets Broker can then be configured to use the Safeguard A2A API to synchronize secrets with target secret stores via plugins. Removing this configuration using this cmdlet will most likely break your Secrets Broker. To completely reset, use Clear-SgDevOps instead. .PARAMETER Force This option will force clearing the association without confirmation. .EXAMPLE Clear-SgDevOpsAppliance #> function Clear-SgDevOpsAppliance { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [switch]$Force ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } $local:Status = (Get-SgDevOpsApplianceStatus) if ($local:Status.ApplianceId) { Write-Host -ForegroundColor Yellow "WARNING: This Secrets Broker is currently associated with $($local:Status.ApplianceName) ($($local:Status.ApplianceAddress))." if ($Force) { $local:Confirmed = $true } else { $local:Confirmed = (Get-Confirmation "Clear Secrets Broker" "Do you want to clear the association with this Safeguard appliance?" ` "Clear." "Cancels this operation.") } } if ($local:Confirmed) { Invoke-SgDevOpsMethod DELETE "Safeguard" -Parameters @{ confirm = "yes" } Write-Host "Appliance information has been cleared." Write-Host "The Secrets Broker will restart, you must reinitialize using Initialize-SgDevOpsAppliance." } else { Write-Host -ForegroundColor Yellow "Operation canceled." } } <# .SYNOPSIS Request a restart of the Secrets Broker. .DESCRIPTION Restarting the Secrets Broker is sometimes necessary to ensure that plugins and certificates are initialized properly after a configuration change. .PARAMETER Force This option will force restarting the Secrets Broker without confirmation. .EXAMPLE Restart-SgDevOps #> function Restart-SgDevOps { [CmdletBinding()] Param( [Parameter(Mandatory=$false)] [switch]$Force ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } Write-Host -ForegroundColor Yellow "WARNING: Restarting this Secrets Broker will require you to log in again." if ($Force) { $local:Confirmed = $true } else { $local:Confirmed = (Get-Confirmation "Restart Secrets Broker" "Do you want to restart this Secrets Broker?" ` "Restart." "Cancels this operation.") } if ($local:Confirmed) { Invoke-SgDevOpsMethod POST "Safeguard/Restart" -Parameters @{ confirm = "yes" } Write-Host "Secrets Broker has restarted, you must reconnect using Connect-SgDevOps." } else { Write-Host -ForegroundColor Yellow "Operation canceled." } } <# .SYNOPSIS Get Secrets Broker logs. .DESCRIPTION Get Secrets Broker logs and write them to a file or display them to stdout. .PARAMETER StdOut This option will cause logs to be written to stdout. .PARAMETER OutFile File location to write the logs to. .EXAMPLE Get-SgDevOpsLog .EXAMPLE Get-SgDevOpsLog -OutFile mysecretsbroker.log #> function Get-SgDevOpsLog { [CmdletBinding(DefaultParameterSetName="File")] Param( [Parameter(ParameterSetName="StdOut",Mandatory=$false)] [switch]$StdOut, [Parameter(ParameterSetName="File",Mandatory=$false,Position=0)] [string]$OutFile ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if ($StdOut) { Invoke-SgDevOpsMethod Get "Safeguard/Log" } else { if (-not $OutFile) { $OutFile = "sgdevops-$((Get-Date).ToString("yyyyMMddTHHmmssZz")).log" } Invoke-SgDevOpsMethod Get "Safeguard/Log" | Out-File -Force -Encoding utf8 -FilePath $OutFile Write-Host "Log file written to $OutFile." } } <# .SYNOPSIS Get Secrets Broker configuration backup. .DESCRIPTION Get Secrets Broker backup of the current configuration. .PARAMETER Passphrase Passphrase used to encrypt the database key in the backup file. If no passphrase is provided, the database key will be written to the backup file unencrypted. .PARAMETER OutFile File location to write the backup file. .EXAMPLE Get-SgDevOpsBackup .EXAMPLE Get-SgDevOpsBackup -OutFile mysecretsbrokerconfig.sbbf #> function Get-SgDevOpsBackup { [CmdletBinding(DefaultParameterSetName="File")] Param( [Parameter(ParameterSetName="Passphrase",Mandatory=$false)] [securestring]$Passphrase, [Parameter(ParameterSetName="File",Mandatory=$false,Position=0)] [string]$OutFile ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if (-not $Passphrase) { Write-Host "For no passphrase just press enter..." $Passphrase = (Read-host "Passphrase" -AsSecureString) } $local:Parameters = @{} $local:passphrasePlainText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Passphrase)) if ($local:passphrasePlainText) { $local:Parameters.passphrase = $local:passphrasePlainText } $local:outFile = "sgdevops-Backup-$((Get-Date).ToString("yyyyMMddTHHmmssZz")).sbbf" if (-not $OutFile) { $local:outFile = $OutFile } $local:ExtraHeaders = @{ "Accept" = "Application/Octet-Stream"; } Invoke-SgDevOpsMethod Get "Safeguard/Configuration/Backup" -Parameters $local:Parameters -ExtraHeaders $local:ExtraHeaders -OutFile $local:outFile Write-Host "Log file written to $local:outFile." } <# .SYNOPSIS Upload and restore a Secrets Broker backup file. .DESCRIPTION Upload and restore a Secrets Broker backup file. This will replace the current configuration after Secrets Broker service has been restarted. .PARAMETER BackupFile The full path and file name of the backup file used to restore the configuration. .PARAMETER Passphrase Passphrase used to decrypt the database key in the backup file. If no passphrase is provided, the restore process will not attempt to decrypt the database key. .PARAMETER DoNotRestart A flag that indicates that the Secrets Broker service should not be restarted automatically after restoring the configuration. If set, the Secrets Broker service will need to be restarted manually to complete the restore. .EXAMPLE Restore-SgDevOpsBackup c:\my\backup\path\myconfigurationbackup.sbbf #> function Restore-SgDevOpsBackup { [CmdletBinding()] Param( [Parameter(Mandatory=$true, Position=0)] [string]$BackupFile, [Parameter(ParameterSetName="Passphrase",Mandatory=$false)] [securestring]$Passphrase, [Parameter(Mandatory=$false)] [switch]$DoNotRestart ) if (-not $PSBoundParameters.ContainsKey("ErrorAction")) { $ErrorActionPreference = "Stop" } if (-not $PSBoundParameters.ContainsKey("Verbose")) { $VerbosePreference = $PSCmdlet.GetVariableValue("VerbosePreference") } if (-not $Passphrase) { Write-Host "For no passphrase just press enter..." $Passphrase = (Read-host "Passphrase" -AsSecureString) } $local:Parameters = @{ restart = $([bool]$DoNotRestart -eq $false) } $local:passphrasePlainText = [Runtime.InteropServices.Marshal]::PtrToStringAuto([Runtime.InteropServices.Marshal]::SecureStringToBSTR($Passphrase)) if ($local:passphrasePlainText) { $local:Parameters.passphrase = $local:passphrasePlainText } try { $BackupFile = (Resolve-Path $BackupFile) $local:Bytes = [System.IO.File]::ReadAllBytes($BackupFile) } catch { Write-Host -ForegroundColor Magenta "Unable to read the backup file." Write-Host -ForegroundColor Red $_ throw "Invalid backup file specified" } $local:Base64BackupData = [System.Convert]::ToBase64String($local:Bytes) Invoke-SgDevOpsMethod POST "Safeguard/Configuration/Restore" -Parameters $local:Parameters -Body @{ Base64BackupData = $local:Base64BackupData } Write-Host "Backup has been restored." if ($DoNotRestart) { Write-Host "The Secrets Broker service must be restarted to complete the backup restore." } else { Write-Host "The Secrets Broker service will restart, you must reconnect using Connect-SgDevOps." } } |