HashiCorp Vault - Key Value (v1) retrieve secrets

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:


Secrets Path:

Specify the full path to the secret(s) you want to retrieve. e.g./secret/config.

This value should contain:

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 a vault 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 a vault 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 key
  • OutputVariableName 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:

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:

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 key
  • OutputVariableName 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.

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.

Report an issue

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

{
  "Id": "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"
  }
}

History

Page updated on Wednesday, September 21, 2022