Octopus.Script exported 2022-09-21 by harrisonmeister belongs to ‘HashiCorp Vault’ category.
This step retrieves one or more secrets in a v1 Key/Value secrets engine stored within a HashiCorp Vault server using a previously obtained authentication token.
This step template uses the Rest API, so no other dependencies are needed.
Authentication Tokens
Octopus recommends using one of the pre-existing Vault step templates to obtain an auth token, such as:
- The AppRole Login or
- The most secure AppRole Get Wrapped SecretID in conjunction with the AppRole Unwrap SecretID and Login template.
Secrets Path:
Specify the full path to the secret(s) you want to retrieve. e.g./secret/config
.
This value should contain:
- The location where the secrets engine has been enabled.
- The path to the secret(s) you want to retrieve.
For example, if the secrets engine was enabled at /my-secrets
and you wanted to retrieve the secret(s) from the path /config
, then the value you would enter is:
/my-secrets/config
Retrieval methods:
The step template operates in one of 2 retrieval modes that control how many Vault Key values are returned. The options are:
Single vault key
- a single key is returned. If this option is selected, the path specified will be assumed to be an individual vault key. This performs the equivalent of avault kv get
command using the Get method.Multiple vault keys
- multiple keys can be returned. If this option is selected, the path specified will be assumed to be able to be enumerated. This performs the equivalent of avault kv list
command using the List method.
The default is Single vault key
.
Optional field names:
Choose specific fields to be returned from the Vault key(s) found in the Key/value secrets engine, in the format FieldName | OutputVariableName
where:
FieldName
is the name of the field to retrieve from the keyOutputVariableName
is the optional Octopus output variable name to store the secret’s value in.
If this parameter is not set, all fields found from secret keys will be returned.
Note: Multiple fields can be retrieved by entering each one on a new line.
Sensitive output variables:
For each vault key’s field values, an Octopus sensitive output variable will be created for use in other steps.
Required:
Optional:
- A Vault namespace to use. Nested namespaces can also be supplied, e.g.
ns1/ns2
. Note: This field is only supported on Vault Enterprise.
Notes:
- Tested on Vault Server
1.11.3
. - Tested on both PowerShell Desktop and PowerShell Core.
Parameters
When steps based on the template are included in a project’s deployment process, the parameters below can be set.
Vault Server URL
Vault.Retrieve.KV.V1.Secrets.VaultAddress =
The URL of the Vault instance you are connecting to. Port should be included (The default is 8200
). For example:
https://myvault.local:8200/
API version
Vault.Retrieve.KV.V1.Secrets.ApiVersion = v1
All API routes are prefixed with a version e.g. /v1/
.
See the API documentation for further details.
Namespace (Optional)
Vault.Retrieve.KV.V1.Secrets.Namespace =
The optional namespace to use. Nested namespaces can also be supplied, e.g. ns1/ns2
.
Note: This field is only supported on Vault Enterprise .
Auth Token
Vault.Retrieve.KV.V1.Secrets.AuthToken =
The Auth Token used to authenticate to retrieve secrets.
Octopus recommends using one of the pre-existing Vault step templates to obtain an auth token, such as:
- The AppRole Login or
- The most secure AppRole Get Wrapped SecretID in conjunction with the AppRole Unwrap SecretID and Login template.
Secrets Path
Vault.Retrieve.KV.V1.Secrets.SecretsPath =
The full path to the secret(s) you want to retrieve. e.g./secret/config
.
This value should contain:
- The location where the secrets engine has been enabled.
- The path to the secret(s) you want to retrieve.
For example, if the secrets engine was enabled at /my-secrets
and you wanted to retrieve the secret(s) from the path /config
then the value you would enter is:
/my-secrets/config
Secrets retrieval method
Vault.Retrieve.KV.V1.Secrets.RetrievalMethod = Get
This controls how many Vault Key values are returned. The options are:
- A single key is returned. If this option is selected, the path specified will be assumed to be an individual vault key. This performs the equivalent of a
vault kv get
command using the Get method. - Multiple keys can be returned. If this option is selected, the path specified will be assumed to be able to be enumerated. This performs the equivalent of a
vault kv list
command using the List method.
The default is Single vault key
.
Recursive retrieval
Vault.Retrieve.KV.V1.Secrets.RecursiveSearch = False
If the path is being enumerated, should any secrets included in sub-folders also be retrieved? The default is: False
.
Field names
Vault.Retrieve.KV.V1.Secrets.FieldValues =
Choose specific fields to be returned from the Vault key(s) found in the Key/value secrets engine, in the format FieldName | OutputVariableName
where:
FieldName
is the name of the field to retrieve from the keyOutputVariableName
is the optional Octopus output variable name to store the secret’s value in.
If this value is not present, any fields found within secrets from the specified path will be retrieved.
Note: Multiple fields can be retrieved by entering each one on a new line.
Print output variable names
Vault.Retrieve.KV.V1.Secrets.PrintVariableNames = False
Write out the Octopus output variable names to the task log. Default: False
Script body
Steps based on this template will execute the following PowerShell script.
### Set TLS 1.2
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
# Required Variables
$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.VaultAddress"]
$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.ApiVersion"]
$VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.AuthToken"]
$VAULT_RETRIEVE_KV_V1_SECRETS_PATH = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.SecretsPath"]
$VAULT_RETRIEVE_KV_V1_SECRETS_METHOD = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.RetrievalMethod"]
$VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.RecursiveSearch"]
$VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.PrintVariableNames"]
# Optional variables
$VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.FieldValues"]
$VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE = $OctopusParameters["Vault.Retrieve.KV.V1.Secrets.Namespace"]
# Validation
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS not specified"
}
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION not specified"
}
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN not specified"
}
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_PATH)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_PATH not specified"
}
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_METHOD)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_METHOD not specified"
}
if ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE)) {
throw "Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE not specified"
}
# Helper functions
###############################################################################
function Get-WebRequestErrorBody {
param (
$RequestError
)
# Powershell < 6 you can read the Exception
if ($PSVersionTable.PSVersion.Major -lt 6) {
if ($RequestError.Exception.Response) {
$reader = New-Object System.IO.StreamReader($RequestError.Exception.Response.GetResponseStream())
$reader.BaseStream.Position = 0
$reader.DiscardBufferedData()
$rawResponse = $reader.ReadToEnd()
$response = ""
try {$response = $rawResponse | ConvertFrom-Json} catch {$response=$rawResponse}
return $response
}
}
else {
return $RequestError.ErrorDetails.Message
}
}
function Get-VaultSecret {
param (
[string]$SecretEnginePath,
[string]$SecretPath,
$Fields
)
try {
# Local variables
$VariablesCreated = 0
$FieldsSpecified = ($Fields.Count -gt 0)
$SecretPath = $SecretPath.TrimStart("/")
$WorkingPath = "$($SecretEnginePath)/$($SecretPath)"
$RequestPath = "$SecretEnginePath/$($SecretPath)"
$uri = "$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS/$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION/$([uri]::EscapeDataString($RequestPath))"
$Headers = @{"X-Vault-Token" = $VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN }
if (-not [string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)) {
$Headers.Add("X-Vault-Namespace", $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)
}
$response = Invoke-RestMethod -Uri $uri -Headers $Headers -Method GET
if ($null -ne $response) {
if ($FieldsSpecified -eq $True) {
foreach ($field in $Fields) {
$fieldName = $field.Name
$fieldVariableName = $field.VariableName
$fieldValue = $response.data.$fieldName
if ($null -ne $fieldValue) {
if ([string]::IsNullOrWhiteSpace($fieldVariableName)) {
$fieldVariableName = "$($WorkingPath.Replace("/",".")).$($fieldName.Trim())"
}
Set-OctopusVariable -Name $fieldVariableName -Value $fieldValue -Sensitive
if($VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES -eq $True) {
Write-Host "Created output variable: ##{Octopus.Action[$StepName].Output.$fieldVariableName}"
}
$VariablesCreated += 1
}
}
}
# No fields specified, iterate through each one.
else {
$secretFieldNames = $response.data | Get-Member | Where-Object { $_.MemberType -eq "NoteProperty" } | Select-Object -ExpandProperty "Name"
foreach ($fieldName in $secretFieldNames) {
$fieldVariableName = "$($WorkingPath.Replace("/",".")).$($fieldName.Trim())"
$fieldValue = $response.data.$fieldName
Set-OctopusVariable -Name $fieldVariableName -Value $fieldValue -Sensitive
if($VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES -eq $True) {
Write-Host "Created output variable: ##{Octopus.Action[$StepName].Output.$fieldVariableName}"
}
$VariablesCreated += 1
}
}
return $VariablesCreated
}
}
catch {
$ExceptionMessage = $_.Exception.Message
$ErrorBody = Get-WebRequestErrorBody -RequestError $_
$Message = "An error occurred logging in with AppRole: $ExceptionMessage"
$AdditionalDetail = ""
if (![string]::IsNullOrWhiteSpace($ErrorBody)) {
if ($null -ne $ErrorBody.errors) {
$AdditionalDetail = $ErrorBody.errors -Join ","
}
else {
$errorDetails = $null
try { $errorDetails = ConvertFrom-Json $ErrorBody } catch {}
$AdditionalDetail += if ($null -ne $errorDetails) { $errorDetails.errors -Join "," } else { $ErrorBody }
}
}
if (![string]::IsNullOrWhiteSpace($AdditionalDetail)) {
$Message += "`n`tDetail: $AdditionalDetail"
}
Write-Error $Message -Category ConnectionError
}
}
function List-VaultSecrets {
param (
[string]$SecretEnginePath,
[string]$SecretPath
)
try {
$SecretPath = $SecretPath.TrimStart("/")
$RequestPath = "$SecretEnginePath/$SecretPath"
# Vault uses the 'LIST' HTTP verb, which is only supported in PowerShell 6.0+ using -CustomMethod.
# Adding ?list=true will allow support for Windows Desktop PowerShell.
# See https://www.vaultproject.io/api#api-operations for further details/
$uri = "$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS/$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION/$([uri]::EscapeDataString($RequestPath))?list=true"
$Headers = @{"X-Vault-Token" = $VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN }
if (-not [string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)) {
$Headers.Add("X-Vault-Namespace", $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)
}
$response = Invoke-RestMethod -Uri $uri -Headers $Headers -Method GET
return $response
}
catch {
$ExceptionMessage = $_.Exception.Message
$ErrorBody = Get-WebRequestErrorBody -RequestError $_
$Message = "An error occurred logging in with AppRole: $ExceptionMessage"
$AdditionalDetail = ""
if (![string]::IsNullOrWhiteSpace($ErrorBody)) {
if ($null -ne $ErrorBody.errors) {
$AdditionalDetail = $ErrorBody.errors -Join ","
}
else {
$errorDetails = $null
try { $errorDetails = ConvertFrom-Json $ErrorBody } catch {}
$AdditionalDetail += if ($null -ne $errorDetails) { $errorDetails.errors -Join "," } else { $ErrorBody }
}
}
if (![string]::IsNullOrWhiteSpace($AdditionalDetail)) {
$Message += "`n`tDetail: $AdditionalDetail"
}
Write-Error $Message -Category ConnectionError
}
}
function Recursive-GetVaultSecrets {
param(
[string]$SecretEnginePath,
[string]$SecretPath
)
$VariablesCreated = 0
$SecretPath = $SecretPath.TrimStart("/")
$SecretPath = $SecretPath.TrimEnd("/")
Write-Verbose "Executing Recursive-GetVaultSecrets"
# Get list of secrets for path
$VaultKeysResponse = List-VaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath
if ($null -ne $VaultKeysResponse) {
$keys = $VaultKeysResponse.data.keys
if ($null -ne $keys) {
$secretKeys = $keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) -and !$_.EndsWith("/") }
foreach ($secretKey in $secretKeys) {
$secretKeyPath = "$($SecretPath)/$secretKey"
$variablesCreated += Get-VaultSecret -SecretEnginePath $SecretEnginePath -SecretPath $secretKeyPath -Fields $Fields
}
if ($VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE -eq $True) {
$folderKeys = $keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) -and $_.EndsWith("/") }
foreach ($folderKey in $folderKeys) {
$Depth = $Depth += 1
$folderPath = "$($SecretPath)/$folderKey"
$VariablesCreated += Recursive-GetVaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $folderPath
}
}
}
}
return $VariablesCreated
}
###############################################################################
$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS = $VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS.TrimEnd('/')
$VAULT_RETRIEVE_KV_V1_SECRETS_PATH = $VAULT_RETRIEVE_KV_V1_SECRETS_PATH.TrimStart('/')
# Local variables
$RetrieveMultipleKeys = $VAULT_RETRIEVE_KV_V1_SECRETS_METHOD.ToUpper().Trim() -ne "GET"
$SecretPathItems = ($VAULT_RETRIEVE_KV_V1_SECRETS_PATH -Split "/")
$SecretEnginePath = ($SecretPathItems | Select-Object -First 1)
$SecretPath = ($SecretPathItems | Select-Object -Skip 1) -Join "/"
$StepName = $OctopusParameters["Octopus.Step.Name"]
$Fields = @()
$VariablesCreated = 0
if (![string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES)) {
@(($VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES -Split "`n").Trim()) | ForEach-Object {
if (![string]::IsNullOrWhiteSpace($_)) {
Write-Verbose "Working on: '$_'"
$fieldDefinition = ($_ -Split "\|")
$name = $fieldDefinition[0].Trim()
if([string]::IsNullOrWhiteSpace($name)) {
throw "Unable to establish fieldname from: '$($_)'"
}
$field = [PsCustomObject]@{
Name = $name
VariableName = if (![string]::IsNullOrWhiteSpace($fieldDefinition[1])) { $fieldDefinition[1].Trim() } else { "" }
}
$Fields += $field
}
}
}
$FieldsSpecified = ($Fields.Count -gt 0)
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS: $VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION: $VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN: '********'"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_PATH: $VAULT_RETRIEVE_KV_V1_SECRETS_PATH"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_METHOD: $VAULT_RETRIEVE_KV_V1_SECRETS_METHOD"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE: $VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE"
Write-Verbose "VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE: $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE"
Write-Verbose "RetrieveMultipleKeys: $RetrieveMultipleKeys"
Write-Verbose "Fields Specified: $($FieldsSpecified)"
Write-Verbose "Engine Path: $SecretEnginePath"
Write-Verbose "Secret Path: $SecretPath"
$variablesCreated = 0
if ($RetrieveMultipleKeys -eq $false) {
$variablesCreated += Get-VaultSecret -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath -Fields $Fields
}
else {
$variablesCreated = Recursive-GetVaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath -Depth 0
}
Write-Host "Created $variablesCreated output variables"
Provided under the Apache License version 2.0.
To use this template in Octopus Deploy, copy the JSON below and paste it into the Library → Step templates → Import dialog.
{
"Id": "9aab9522-25e0-4539-841c-8b726e6b1520",
"Name": "HashiCorp Vault - Key Value (v1) retrieve secrets",
"Description": "This step retrieves one or more secrets in a v1 Key/Value secrets engine stored within a HashiCorp Vault server using a previously obtained authentication token.\n\nThis step template uses the [Rest API](https://www.vaultproject.io/api-docs/secret/kv/kv-v1), so no other dependencies are needed. \n\n---\n\n**Authentication Tokens**\n\nOctopus recommends using one of the pre-existing Vault step templates to obtain an auth token, such as:\n- The [AppRole Login](https://library.octopus.com/step-templates/e04a9cec-f04a-4da2-849b-1aed0fd408f0/actiontemplate-hashicorp-vault-approle-l) or \n- The most secure [AppRole Get Wrapped SecretID](https://library.octopus.com/step-templates/76827264-af27-46d0-913a-e093a4f0db48/actiontemplate-hashicorp-vault-approle-get-wrapped-secret-id) in conjunction with the [AppRole Unwrap SecretID and Login](https://library.octopus.com/step-templates/aa113393-e615-40ed-9c5a-f95f471d728f/actiontemplate-hashicorp-vault-approle-unwrap-secret-id-and-login) template.\n\n---\n**Secrets Path:**\n\nSpecify the full path to the secret(s) you want to retrieve. e.g.`/secret/config`.\n\nThis value should contain:\n- The location where the [secrets engine has been enabled](https://www.vaultproject.io/api-docs/secret/kv/kv-v1).\n- The path to the secret(s) you want to retrieve.\n\nFor example, if the secrets engine was enabled at `/my-secrets` and you wanted to retrieve the secret(s) from the path `/config`, then the value you would enter is:\n\n`/my-secrets/config`\n\n---\n\n**Retrieval methods:**\n\nThe step template operates in one of 2 retrieval modes that control how many Vault Key values are returned. The options are:\n- `Single vault key` - a single key is returned. If this option is selected, the path specified will be assumed to be an individual vault key. This performs the equivalent of a `vault kv get` command using the [Get](https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret) method.\n- `Multiple vault keys` - multiple keys can be returned. If this option is selected, the path specified will be assumed to be able to be enumerated. This performs the equivalent of a `vault kv list` command using the [List](https://www.vaultproject.io/api-docs/secret/kv/kv-v1#list-secrets) method.\n\nThe default is `Single vault key`.\n\n---\n\n**Optional field names:**\n\nChoose specific fields to be returned from the Vault key(s) found in the Key/value secrets engine, in the format `FieldName | OutputVariableName` where:\n\n- `FieldName` is the name of the field to retrieve from the key\n- `OutputVariableName` is the _optional_ Octopus [output variable](https://octopus.com/docs/projects/variables/output-variables) name to store the secret's value in.\n\nIf this parameter is not set, all fields found from secret keys will be returned.\n\n**Note:** Multiple fields can be retrieved by entering each one on a new line.\n\n---\n\n**Sensitive output variables:**\n\nFor each vault key's field values, an Octopus [sensitive output variable](https://octopus.com/docs/projects/variables/output-variables#sensitive-output-variables) will be created for use in other steps.\n\n---\n\n**Required:** \n- The Vault server must be [unsealed](https://www.vaultproject.io/docs/concepts/seal).\n- An authentication [token](https://www.vaultproject.io/docs/auth/token). \n\n*Optional*:\n- A Vault [namespace](https://www.vaultproject.io/docs/enterprise/namespaces) to use. Nested namespaces can also be supplied, e.g. `ns1/ns2`. **Note:** This field is only supported on [Vault Enterprise](https://www.hashicorp.com/products/vault).\n\nNotes:\n\n- Tested on Vault Server `1.11.3`.\n- Tested on both PowerShell Desktop and PowerShell Core.",
"Version": 6,
"ExportedAt": "2022-09-21T17:01:25.405Z",
"ActionType": "Octopus.Script",
"Author": "harrisonmeister",
"Packages": [],
"Parameters": [
{
"Id": "13d0b003-63ef-45d0-969d-e032ba5b41ee",
"Name": "Vault.Retrieve.KV.V1.Secrets.VaultAddress",
"Label": "Vault Server URL",
"HelpText": "The URL of the Vault instance you are connecting to. Port should be included (The default is `8200`). For example:\n\n\n`https://myvault.local:8200/`",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "e167ab26-3959-4e51-93fb-39c4a1ac76db",
"Name": "Vault.Retrieve.KV.V1.Secrets.ApiVersion",
"Label": "API version",
"HelpText": "All API routes are prefixed with a version e.g. `/v1/`.\n\nSee the [API documentation](https://www.vaultproject.io/api-docs) for further details.",
"DefaultValue": "v1",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "v1|v1"
}
},
{
"Id": "5f904ca6-299e-4591-a47b-a998c5aa9a9c",
"Name": "Vault.Retrieve.KV.V1.Secrets.Namespace",
"Label": "Namespace (Optional)",
"HelpText": "The _optional_ [namespace](https://www.vaultproject.io/docs/enterprise/namespaces) to use. Nested namespaces can also be supplied, e.g. `ns1/ns2`.\n\n**Note:** This field is only supported on [Vault Enterprise](https://www.hashicorp.com/products/vault) .",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "66a8d06e-08ac-4c9f-9e3a-a1727f2e0898",
"Name": "Vault.Retrieve.KV.V1.Secrets.AuthToken",
"Label": "Auth Token",
"HelpText": "The [Auth Token](https://www.vaultproject.io/docs/auth/token) used to authenticate to retrieve secrets.\n\nOctopus recommends using one of the pre-existing Vault step templates to obtain an auth token, such as:\n- The [AppRole Login](https://library.octopus.com/step-templates/e04a9cec-f04a-4da2-849b-1aed0fd408f0/actiontemplate-hashicorp-vault-approle-l) or \n- The most secure [AppRole Get Wrapped SecretID](https://library.octopus.com/step-templates/76827264-af27-46d0-913a-e093a4f0db48/actiontemplate-hashicorp-vault-approle-get-wrapped-secret-id) in conjunction with the [AppRole Unwrap SecretID and Login](https://library.octopus.com/step-templates/aa113393-e615-40ed-9c5a-f95f471d728f/actiontemplate-hashicorp-vault-approle-unwrap-secret-id-and-login) template.",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "Sensitive"
}
},
{
"Id": "2534a01a-db65-4bc5-9766-68c6567ed5f6",
"Name": "Vault.Retrieve.KV.V1.Secrets.SecretsPath",
"Label": "Secrets Path",
"HelpText": "The full path to the secret(s) you want to retrieve. e.g.`/secret/config`.\n\n**This value should contain:** \n- The location where the [secrets engine has been enabled](https://www.vaultproject.io/api-docs/secret/kv/kv-v1).\n- The path to the secret(s) you want to retrieve.\n\nFor example, if the secrets engine was enabled at `/my-secrets` and you wanted to retrieve the secret(s) from the path `/config` then the value you would enter is:\n\n`/my-secrets/config`",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "SingleLineText"
}
},
{
"Id": "01ca54f6-269a-447a-9193-d7de9195b164",
"Name": "Vault.Retrieve.KV.V1.Secrets.RetrievalMethod",
"Label": "Secrets retrieval method",
"HelpText": "This controls how many Vault Key values are returned. The options are:\n- A single key is returned. If this option is selected, the path specified will be assumed to be an individual vault key. This performs the equivalent of a `vault kv get` command using the [Get](https://www.vaultproject.io/api-docs/secret/kv/kv-v1#read-secret) method.\n- Multiple keys can be returned. If this option is selected, the path specified will be assumed to be able to be enumerated. This performs the equivalent of a `vault kv list` command using the [List](https://www.vaultproject.io/api-docs/secret/kv/kv-v2#list-secrets) method.\n\nThe default is `Single vault key`.",
"DefaultValue": "Get",
"DisplaySettings": {
"Octopus.ControlType": "Select",
"Octopus.SelectOptions": "Get|Single vault key \nList|Multiple vault keys"
}
},
{
"Id": "ab4e36f5-96d9-4969-a8a8-7c1c7a2de9ff",
"Name": "Vault.Retrieve.KV.V1.Secrets.RecursiveSearch",
"Label": "Recursive retrieval",
"HelpText": "If the path is being enumerated, should any secrets included in sub-folders also be retrieved? The default is: `False`.",
"DefaultValue": "False",
"DisplaySettings": {
"Octopus.ControlType": "Checkbox"
}
},
{
"Id": "20ae7130-dd7c-4abf-af2a-675c039df7a5",
"Name": "Vault.Retrieve.KV.V1.Secrets.FieldValues",
"Label": "Field names",
"HelpText": "Choose specific fields to be returned from the Vault key(s) found in the Key/value secrets engine, in the format `FieldName | OutputVariableName` where:\n\n- `FieldName` is the name of the field to retrieve from the key\n- `OutputVariableName` is the _optional_ Octopus [output variable](https://octopus.com/docs/projects/variables/output-variables) name to store the secret's value in.\n\nIf this value is not present, any fields found within secrets from the specified path will be retrieved.\n\n**Note:** Multiple fields can be retrieved by entering each one on a new line.\n",
"DefaultValue": "",
"DisplaySettings": {
"Octopus.ControlType": "MultiLineText"
}
},
{
"Id": "3885d50e-3be5-4bc2-bb97-6e2b23459b07",
"Name": "Vault.Retrieve.KV.V1.Secrets.PrintVariableNames",
"Label": "Print output variable names",
"HelpText": "Write out the Octopus [output variable](https://octopus.com/docs/projects/variables/output-variables) names to the task log. Default: `False`",
"DefaultValue": "False",
"DisplaySettings": {
"Octopus.ControlType": "Checkbox"
}
}
],
"Properties": {
"Octopus.Action.Script.ScriptSource": "Inline",
"Octopus.Action.Script.Syntax": "PowerShell",
"Octopus.Action.Script.ScriptBody": "### Set TLS 1.2\n[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12\n\n# Required Variables\n$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.VaultAddress\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.ApiVersion\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.AuthToken\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_PATH = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.SecretsPath\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_METHOD = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.RetrievalMethod\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.RecursiveSearch\"]\n$VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.PrintVariableNames\"]\n\n# Optional variables\n$VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.FieldValues\"]\n$VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE = $OctopusParameters[\"Vault.Retrieve.KV.V1.Secrets.Namespace\"]\n\n# Validation\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS not specified\"\n}\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION not specified\"\n}\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN not specified\"\n}\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_PATH)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_PATH not specified\"\n}\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_METHOD)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_METHOD not specified\"\n}\nif ([string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE)) {\n throw \"Required parameter VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE not specified\"\n}\n\n# Helper functions\n###############################################################################\nfunction Get-WebRequestErrorBody {\n param (\n $RequestError\n )\n\n # Powershell < 6 you can read the Exception\n if ($PSVersionTable.PSVersion.Major -lt 6) {\n if ($RequestError.Exception.Response) {\n $reader = New-Object System.IO.StreamReader($RequestError.Exception.Response.GetResponseStream())\n $reader.BaseStream.Position = 0\n $reader.DiscardBufferedData()\n $rawResponse = $reader.ReadToEnd()\n $response = \"\"\n try {$response = $rawResponse | ConvertFrom-Json} catch {$response=$rawResponse}\n return $response\n }\n }\n else {\n return $RequestError.ErrorDetails.Message\n }\n}\n\nfunction Get-VaultSecret {\n param (\n [string]$SecretEnginePath,\n [string]$SecretPath,\n $Fields\n )\n try {\n # Local variables\n $VariablesCreated = 0\n $FieldsSpecified = ($Fields.Count -gt 0)\n $SecretPath = $SecretPath.TrimStart(\"/\")\n $WorkingPath = \"$($SecretEnginePath)/$($SecretPath)\"\n $RequestPath = \"$SecretEnginePath/$($SecretPath)\"\n\n $uri = \"$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS/$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION/$([uri]::EscapeDataString($RequestPath))\"\n $Headers = @{\"X-Vault-Token\" = $VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN }\n \n if (-not [string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)) {\n $Headers.Add(\"X-Vault-Namespace\", $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE) \n }\n\n $response = Invoke-RestMethod -Uri $uri -Headers $Headers -Method GET\n\n if ($null -ne $response) {\n if ($FieldsSpecified -eq $True) {\n foreach ($field in $Fields) {\n $fieldName = $field.Name\n $fieldVariableName = $field.VariableName\n $fieldValue = $response.data.$fieldName\n\n if ($null -ne $fieldValue) {\n if ([string]::IsNullOrWhiteSpace($fieldVariableName)) {\n $fieldVariableName = \"$($WorkingPath.Replace(\"/\",\".\")).$($fieldName.Trim())\"\n }\n \n Set-OctopusVariable -Name $fieldVariableName -Value $fieldValue -Sensitive\n if($VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES -eq $True) {\n Write-Host \"Created output variable: ##{Octopus.Action[$StepName].Output.$fieldVariableName}\"\n }\n $VariablesCreated += 1\n }\n }\n } \n # No fields specified, iterate through each one.\n else {\n $secretFieldNames = $response.data | Get-Member | Where-Object { $_.MemberType -eq \"NoteProperty\" } | Select-Object -ExpandProperty \"Name\"\n foreach ($fieldName in $secretFieldNames) {\n $fieldVariableName = \"$($WorkingPath.Replace(\"/\",\".\")).$($fieldName.Trim())\"\n $fieldValue = $response.data.$fieldName\n \n Set-OctopusVariable -Name $fieldVariableName -Value $fieldValue -Sensitive \n if($VAULT_RETRIEVE_KV_V1_PRINT_VARIABLE_NAMES -eq $True) {\n Write-Host \"Created output variable: ##{Octopus.Action[$StepName].Output.$fieldVariableName}\"\n }\n $VariablesCreated += 1\n }\n }\n return $VariablesCreated\n } \n }\n catch {\n $ExceptionMessage = $_.Exception.Message\n $ErrorBody = Get-WebRequestErrorBody -RequestError $_\n $Message = \"An error occurred logging in with AppRole: $ExceptionMessage\"\n $AdditionalDetail = \"\"\n if (![string]::IsNullOrWhiteSpace($ErrorBody)) {\n if ($null -ne $ErrorBody.errors) {\n $AdditionalDetail = $ErrorBody.errors -Join \",\" \n }\n else {\n $errorDetails = $null\n try { $errorDetails = ConvertFrom-Json $ErrorBody } catch {}\n $AdditionalDetail += if ($null -ne $errorDetails) { $errorDetails.errors -Join \",\" } else { $ErrorBody } \n }\n }\n \n if (![string]::IsNullOrWhiteSpace($AdditionalDetail)) {\n $Message += \"`n`tDetail: $AdditionalDetail\"\n }\n \n Write-Error $Message -Category ConnectionError\n }\n}\n\nfunction List-VaultSecrets {\n param (\n [string]$SecretEnginePath,\n [string]$SecretPath\n )\n try {\n $SecretPath = $SecretPath.TrimStart(\"/\")\n $RequestPath = \"$SecretEnginePath/$SecretPath\"\n\n # Vault uses the 'LIST' HTTP verb, which is only supported in PowerShell 6.0+ using -CustomMethod.\n # Adding ?list=true will allow support for Windows Desktop PowerShell.\n # See https://www.vaultproject.io/api#api-operations for further details/\n $uri = \"$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS/$VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION/$([uri]::EscapeDataString($RequestPath))?list=true\"\n $Headers = @{\"X-Vault-Token\" = $VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN }\n \n if (-not [string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE)) {\n $Headers.Add(\"X-Vault-Namespace\", $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE) \n }\n\n $response = Invoke-RestMethod -Uri $uri -Headers $Headers -Method GET\n\n return $response\n }\n catch {\n $ExceptionMessage = $_.Exception.Message\n $ErrorBody = Get-WebRequestErrorBody -RequestError $_\n $Message = \"An error occurred logging in with AppRole: $ExceptionMessage\"\n $AdditionalDetail = \"\"\n if (![string]::IsNullOrWhiteSpace($ErrorBody)) {\n if ($null -ne $ErrorBody.errors) {\n $AdditionalDetail = $ErrorBody.errors -Join \",\" \n }\n else {\n $errorDetails = $null\n try { $errorDetails = ConvertFrom-Json $ErrorBody } catch {}\n $AdditionalDetail += if ($null -ne $errorDetails) { $errorDetails.errors -Join \",\" } else { $ErrorBody } \n }\n }\n \n if (![string]::IsNullOrWhiteSpace($AdditionalDetail)) {\n $Message += \"`n`tDetail: $AdditionalDetail\"\n }\n \n Write-Error $Message -Category ConnectionError\n }\n}\n\nfunction Recursive-GetVaultSecrets {\n param(\n [string]$SecretEnginePath,\n [string]$SecretPath\n )\n $VariablesCreated = 0\n $SecretPath = $SecretPath.TrimStart(\"/\")\n $SecretPath = $SecretPath.TrimEnd(\"/\")\n\n Write-Verbose \"Executing Recursive-GetVaultSecrets\"\n \n # Get list of secrets for path\n $VaultKeysResponse = List-VaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath \n \n if ($null -ne $VaultKeysResponse) {\n $keys = $VaultKeysResponse.data.keys\n if ($null -ne $keys) {\n $secretKeys = $keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) -and !$_.EndsWith(\"/\") }\n foreach ($secretKey in $secretKeys) {\n $secretKeyPath = \"$($SecretPath)/$secretKey\"\n $variablesCreated += Get-VaultSecret -SecretEnginePath $SecretEnginePath -SecretPath $secretKeyPath -Fields $Fields\n }\n\n if ($VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE -eq $True) {\n $folderKeys = $keys | Where-Object { ![string]::IsNullOrWhiteSpace($_) -and $_.EndsWith(\"/\") }\n foreach ($folderKey in $folderKeys) {\n $Depth = $Depth += 1\n $folderPath = \"$($SecretPath)/$folderKey\"\n $VariablesCreated += Recursive-GetVaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $folderPath\n }\n }\n }\n }\n return $VariablesCreated\n}\n\n###############################################################################\n$VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS = $VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS.TrimEnd('/')\n$VAULT_RETRIEVE_KV_V1_SECRETS_PATH = $VAULT_RETRIEVE_KV_V1_SECRETS_PATH.TrimStart('/')\n\n# Local variables\n$RetrieveMultipleKeys = $VAULT_RETRIEVE_KV_V1_SECRETS_METHOD.ToUpper().Trim() -ne \"GET\"\n$SecretPathItems = ($VAULT_RETRIEVE_KV_V1_SECRETS_PATH -Split \"/\")\n$SecretEnginePath = ($SecretPathItems | Select-Object -First 1)\n$SecretPath = ($SecretPathItems | Select-Object -Skip 1) -Join \"/\"\n$StepName = $OctopusParameters[\"Octopus.Step.Name\"]\n\n$Fields = @()\n$VariablesCreated = 0\n\nif (![string]::IsNullOrWhiteSpace($VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES)) {\n \n @(($VAULT_RETRIEVE_KV_V1_SECRETS_FIELD_VALUES -Split \"`n\").Trim()) | ForEach-Object {\n if (![string]::IsNullOrWhiteSpace($_)) {\n Write-Verbose \"Working on: '$_'\"\n $fieldDefinition = ($_ -Split \"\\|\")\n $name = $fieldDefinition[0].Trim()\n if([string]::IsNullOrWhiteSpace($name)) {\n throw \"Unable to establish fieldname from: '$($_)'\"\n }\n $field = [PsCustomObject]@{\n Name = $name\n VariableName = if (![string]::IsNullOrWhiteSpace($fieldDefinition[1])) { $fieldDefinition[1].Trim() } else { \"\" }\n }\n $Fields += $field\n }\n }\n}\n$FieldsSpecified = ($Fields.Count -gt 0)\n\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS: $VAULT_RETRIEVE_KV_V1_SECRETS_ADDRESS\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION: $VAULT_RETRIEVE_KV_V1_SECRETS_API_VERSION\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_TOKEN: '********'\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_PATH: $VAULT_RETRIEVE_KV_V1_SECRETS_PATH\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_METHOD: $VAULT_RETRIEVE_KV_V1_SECRETS_METHOD\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE: $VAULT_RETRIEVE_KV_V1_SECRETS_RECURSIVE\"\nWrite-Verbose \"VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE: $VAULT_RETRIEVE_KV_V1_SECRETS_NAMESPACE\"\nWrite-Verbose \"RetrieveMultipleKeys: $RetrieveMultipleKeys\"\nWrite-Verbose \"Fields Specified: $($FieldsSpecified)\"\nWrite-Verbose \"Engine Path: $SecretEnginePath\"\nWrite-Verbose \"Secret Path: $SecretPath\"\n\n$variablesCreated = 0\n\nif ($RetrieveMultipleKeys -eq $false) {\n $variablesCreated += Get-VaultSecret -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath -Fields $Fields\n}\nelse {\n $variablesCreated = Recursive-GetVaultSecrets -SecretEnginePath $SecretEnginePath -SecretPath $SecretPath -Depth 0\n}\nWrite-Host \"Created $variablesCreated output variables\""
},
"Category": "HashiCorp Vault",
"HistoryUrl": "https://github.com/OctopusDeploy/Library/commits/master/step-templates//opt/buildagent/work/75443764cd38076d/step-templates/hashicorp-vault-keyvalue-v1-retrieve-secrets.json",
"Website": "/step-templates/9aab9522-25e0-4539-841c-8b726e6b1520",
"Logo": "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",
"$Meta": {
"Type": "ActionTemplate"
}
}
Page updated on Wednesday, September 21, 2022