KeyControl.psm1
function ConvertFrom-Box { <# .SYNOPSIS Converts Box API responses into something presentable. .DESCRIPTION Converts Box API responses into something presentable. .PARAMETER InputObject The Box API response object to make pretty. .EXAMPLE PS C:\> (Invoke-KeyControlRequest @param).boxes | ConvertFrom-Box Searches for Boxes and makes them presentable. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject ) process { if (-not $InputObject) { return } [PSCustomObject]@{ PSTypeName = 'KeyControl.Box' BoxID = $InputObject.box_id Name = $InputObject.name Description = $InputObject.Description Created = $InputObject.created_at -as [datetime] Updated = $InputObject.updated_at -as [datetime] Tags = $InputObject.tags Revision = $InputObject.revision MaxSecretVersions = $InputObject.max_secret_versions Rotation = $InputObject.rotation ExclusiveCheckout = $InputObject.exclusive_checkout Object = $InputObject } } } function ConvertFrom-Secret { <# .SYNOPSIS Converts Secret API response objects into presentable data. .DESCRIPTION Converts Secret API response objects into presentable data. .PARAMETER InputObject The data to make pretty. .PARAMETER BoxID The ID of the box the secret is from. Added as data to the processed API response. .EXAMPLE PS C:\> (Invoke-KeyControlRequest @param).secrets | ConvertFrom-Secret -BoxID $BoxID Search for secrets abd make them pretty. #> [CmdletBinding()] param ( [Parameter(ValueFromPipeline = $true)] $InputObject, [string] $BoxID ) process { if (-not $InputObject) { return } [PSCustomObject]@{ PSTypeName = 'KeyControl.Secret' BoxID = $BoxID SecretID = $InputObject.secret_id Revision = $InputObject.revision Name = $InputObject.name Description = $InputObject.desc BoxName = $InputObject.box_name Tags = $InputObject.tags Expired = $InputObject.expired CanAccess = $InputObject.checkout_allowed CurrentVersion = $InputObject.current_version VersionCount = $InputObject.version_count VersionCreated = $InputObject.current_version_creation_time -as [datetime] FirstCreated = $InputObject.created_at -as [datetime] Updated = $InputObject.updated_at -as [datetime] Accessed = $InputObject.last_accessed -as [datetime] Owner = $InputObject.owner_name OwnerMail = $InputObject.owner_email Type = $InputObject.secret_type.type SubType = $InputObject.secret_subtype_info Info = $InputObject.secret_info Secret = $null Object = $InputObject } } } function Assert-KeyControlConnection { <# .SYNOPSIS Ensure there exists a working connection the an entrust Key Control secrets Vault. .DESCRIPTION Ensure there exists a working connection the an entrust Key Control secrets Vault. This function is mostly used internally to ensure commands fail early when not connected. Use the "Connect-KeyControl" command to establish a connection. .PARAMETER Cmdlet The $PSCmdlet variable of the command calling this command. By providing this parameter, the error thrown in case of a missing connection happens within the context of the calling command. In essence, this hides this function - Assert-KeyControlConnection - from the user and instead only shows the calling command. .EXAMPLE PS C:\> Assert-KeyControlConnection -Cmdlet $PSCmdlet Will do nothing if already connected or throw a terminating exception in the context of the calling command if not so. This function will be fully invisible to the end user. #> [CmdletBinding()] param ( $Cmdlet = $PSCmdlet ) process { if ($script:_KeyControlSession) { return } $Cmdlet.ThrowTerminatingError( [System.Management.Automation.ErrorRecord]::new( [System.Exception]::new("Not connected yet! Use Connect-KeyControl to connect to a KeyControl server first."), "NotAuthenticated", [System.Management.Automation.ErrorCategory]::AuthenticationError, $null ) ) } } function Connect-KeyControl { <# .SYNOPSIS Connects to a entrust Key Control secrets Vault. .DESCRIPTION Connects to a entrust Key Control secrets Vault. This module assumes regular account authentication settings (local or ldap). .PARAMETER ComputerName The computer hosting the vault. .PARAMETER Vault The ID of the vault to connect to. .PARAMETER Credential The credentials of the account used for authentication. .EXAMPLE PS C:\> Connect-KeyControl -ComputerName vault.contoso.com -Credential $cred -Vault $vaultID Connects to the entrust Key Control secrets Vault hosted on "vault.contoso.com", using the credentials provided in $cred. It specifically connects to the vault in $vaultID #> [CmdletBinding()] param ( [Parameter(Mandatory = $true)] [string] $ComputerName, [Parameter(Mandatory = $true)] [string] $Vault, [Parameter(Mandatory = $true)] [PSCredential] $Credential ) process { $session = [PSCustomObject]@{ ComputerName = $ComputerName Credential = $Credential Vault = $Vault Token = '' Expires = (Get-Date).AddMinutes(30) BasePath = "https://$ComputerName/vault/1.0" } $body = @{ username = $Credential.UserName password = $Credential.GetNetworkCredential().Password } $response = Invoke-RestMethod -Method POST -Uri "$($session.BasePath)/Login/$Vault/" -Body ($body | ConvertTo-Json) -ContentType 'application/json' if ($response.access_token) { $session.Token = $response.access_token $script:_KeyControlSession = $session } else { throw "Failed to connect: $($response | ConvertTo-Json)" } } } function Disconnect-KeyControl { <# .SYNOPSIS Disconnects from the previously connected entrust Key Control secrets Vault. .DESCRIPTION Disconnects from the previously connected entrust Key Control secrets Vault. Use "Connect-KeyControl" to first establish a connection. Does not act at all, when not already connected. .EXAMPLE PS C:\> Disconnect-KeyControl Disconnects from the previously connected entrust Key Control secrets Vault. #> [CmdletBinding()] param () process { if (-not $script:_KeyControlSession) { return } Invoke-KeyControlRequest -Path 'logout/' $script:_KeyControlSession = $null } } function Get-KeyControlBox { <# .SYNOPSIS Searches for boxes in the connected Key Control Vault. .DESCRIPTION Searches for boxes in the connected Key Control Vault. Requires an already established connection via "Connect-KeyControl". .PARAMETER Name The name to search by. .PARAMETER BoxID The specific ID of the box to retrieve. .PARAMETER Filter A custom filter condition to search by. For when the builtin parameters do not cover your need. Filter reference: https://docs.hytrust.com/DataControl/Online/Content/Books/Secrets-Vault-Programmers-Reference/API/API-Filters.html .EXAMPLE PS C:\> Get-KeyControlBox Lists all boxes in the connected vault. .EXAMPLE PS C:\> Get-KeyControlBox -Name d12* Lists all boxes in the connected vault whose name starts with "d12" .EXAMPLE PS C:\> Get-KeyControlBox -BoxID $boxID Retrieves the specifically requested box. #> [CmdletBinding()] param ( [Parameter(ParameterSetName = 'ByName')] [string] $Name = '*', [Parameter(Mandatory = $true, ParameterSetName = 'ByID')] [Alias('ID')] [string] $BoxID, [Parameter(Mandatory = $true, ParameterSetName = 'ByFilter')] [string] $Filter ) begin { Assert-KeyControlConnection -Cmdlet $PSCmdlet } process { if ($BoxID) { $body = @{ 'box_id' = $BoxID } Invoke-KeyControlRequest -Path 'GetBox/' -Body $body | ConvertFrom-Box return } $param = @{ Path = 'ListBoxes/' } if ($Name) { $filterString = "/name eq '$Name'" if ($Name -match '^\*') { $filterString = "endswith(/name, '$($Name.Trim('*'))')" } if ($Name -match '\*$') { $filterString = "startswith(/name, '$($Name.Trim('*'))')"} $body = @{ filters = $filterString } $param.Body = $body } if ($Filter) { $body = @{ filters = $Filter } $param.Body = $body } (Invoke-KeyControlRequest @param).boxes | ConvertFrom-Box } } function Get-KeyControlSecret { <# .SYNOPSIS Searches for secrets in a Key Control Vault's specified box. .DESCRIPTION Searches for secrets in a Key Control Vault's specified box. Secret Data is only included when asking for a specific secret by ID! Requires an already established connection via "Connect-KeyControl". .PARAMETER BoxID The ID of the box to search in. .PARAMETER SecretID The secret ID of the specific secret to retrieve. .PARAMETER Name The name to search by. Supports wildcards at the beginning or the end, but not in the middle of the name. .PARAMETER Tags Tags to search of (including their value). .PARAMETER Filter A custom filter condition to search by. For when the builtin parameters do not cover your need. Filter reference: https://docs.hytrust.com/DataControl/Online/Content/Books/Secrets-Vault-Programmers-Reference/API/API-Filters.html .PARAMETER Version The specific version of the secret to retrieve. .PARAMETER NameProperty The property on the info object to use for a credential name. If Specified, this command will return a PSCredential object, with the value of that property as Username and the secret as password. .EXAMPLE PS C:\> Get-KeyControlSecret -BoxID $boxID Lists all secrets' info from within the specified box. .EXAMPLE PS C:\> Get-KeyControlSecret -BoxID $boxID SecretID $secret.secret_id Retrieve the specified secret, including both metadata and the actual secret data. .EXAMPLE PS C:\> Get-KeyControlSecret -BoxID $boxID SecretID $secret.secret_id -NameProperty name Retrieve the specified secret, returning a PSCredential object with the secret name as username and secret as password. #> [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSAvoidUsingConvertToSecureStringWithPlainText", "")] [Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseOutputTypeCorrectly", "")] [CmdletBinding(DefaultParameterSetName = 'ByCondition')] param ( [Parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)] [string] $BoxID, [Parameter(ParameterSetName = 'ByID', Mandatory = $true, ValueFromPipelineByPropertyName = $true)] [string] $SecretID, [Parameter(ParameterSetName = 'ByCondition')] [string] $Name, [Parameter(ParameterSetName = 'ByCondition')] [hashtable] $Tags, [Parameter(ParameterSetName = 'ByFilter')] [string] $Filter, [Parameter(ParameterSetName = 'ByID', ValueFromPipelineByPropertyName = $true)] [Alias('CurrentVersion')] [string] $Version, [Parameter(ParameterSetName = 'ByID')] [string] $NameProperty ) process { if ($SecretID) { $body = @{ box_id = $BoxID secret_id = $SecretID } if ($Version) { $body['version'] = $Version } $secretInfo = Invoke-KeyControlRequest -Path 'GetSecret/' -Body $body | ConvertFrom-Secret -BoxID $BoxID $secret = Invoke-KeyControlRequest -Path 'CheckoutSecret/' -Body $body | Add-Member -MemberType NoteProperty -Name BoxID -Value $BoxID -PassThru if ($secret.secret_data -is [string]) { $secretInfo.Secret = $secret.secret_data | ConvertTo-SecureString -AsPlainText -Force } else { $secretInfo.Secret = $secret.secret_data | ConvertTo-Json -Depth 99 | ConvertTo-SecureString -AsPlainText -Force } if (-not $NameProperty) { return $secretInfo } [PSCredential]::new($secret.$NameProperty, $secret.Secret) return } $body = @{ box_id = $BoxID } $conditions = @() if ($Name) { if ($Name -notmatch '^\*|\*$') { $conditions += "/name eq '$Name'" } if ($Name -match '^\*') { $conditions += "endswith(/name, '$($Name.Trim('*'))')" } if ($Name -match '\*$') { $conditions += "startswith(/name, '$($Name.Trim('*'))')"} } if ($Tags) { foreach ($pair in $Tags.GetEnumerator()) { $conditions += "/tags/{0} eq '{1}'" -f $pair.Key, $pair.Value } } if ($Filter) { $conditions = @($Filter) } if ($conditions.Count -gt 0) { $body['filters'] = $conditions -join ' and ' } $param = @{ Path = 'ListSecrets/' Body = $body } (Invoke-KeyControlRequest @param).secrets | ConvertFrom-Secret -BoxID $BoxID } } function Invoke-KeyControlRequest { <# .SYNOPSIS Executes an API request against the entrust Key Control secrets Vault API. .DESCRIPTION Executes an API request against the entrust Key Control secrets Vault API. This tool is optimized to make request execution simple, handling authentication, headers & body formatting, while making path selection easy. Rather than specifying the full URL, with this helper you can provide simply the final endpoint you are executing against - e.g. "ListBoxes/". Will automatically refresh the session if it is about to expire (or already has expired). Requires an already established connection via "Connect-KeyControl". This command is mostly intended for internal use, but exposed for custom scenarios or non-implemented endpoints. Documentation: - KeyControl API Reference: https://docs.hytrust.com/DataControl/Online/Content/Books/Secrets-Vault-Programmers-Reference/API/Accessing-the-SV-API.html - Examples (in GO) for endpoints: https://github.com/EntrustCorporation/pasmcli/tree/master/pasmcli/cmd .PARAMETER Path Relative path to the base URI of the service. Usually expects something like "ListBoxes/" or "GetSecret/". Most paths expect a trailing "/" .PARAMETER Body The json payload to send. Will convert to json if not already a string. .EXAMPLE PS C:\> Invoke-KeyControlRequest -Path 'GetBox/' -Body @{ box_id = $id } Retrieves the specified box from the connected vault. #> [CmdletBinding()] param ( [string] $Path, $Body = @() ) begin { Assert-KeyControlConnection -Cmdlet $PSCmdlet if ($script:_KeyControlSession.Expires -lt (Get-Date).AddMinutes(2)) { Connect-KeyControl -ComputerName $script:_KeyControlSession.ComputerName -Credential $script:_KeyControlSession.Credential -Vault $script:_KeyControlSession.Vault } } process { $param = @{ Method = 'POST' Uri = "$($script:_KeyControlSession.BasePath.Trim('/\'))/$($Path.TrimStart('/\'))" Headers = @{ 'content-type' = 'application/json' 'x-vault-auth' = $script:_KeyControlSession.Token } } if ($Body) { $bodyJson = $Body if ($Body -isnot [string]) { $bodyJson = ConvertTo-Json -InputObject $Body } $param.Body = $bodyJson } Invoke-RestMethod @param -ErrorAction Stop } } # The currently connected Key Control session $script:_KeyControlSession = $null |