Integrate Jenkins, Hashicorp Vault, and PowerShell

Summary: Passwords, Secrets, and Credentials, stored in a Hashicorp Vault server, can easily be leveraged by Jenkins Projects (including projects that leverage PowerShell for the automation – or pure Microsoft shops). There is a common tension between automation and security and this example will show how they can co-exist.

The following steps are used to enable this automation:

  • Save a Vault access token as a Jenkins Credential
  • Bind the Jenkins Credential to a Jenkins Project
  • Access the Jenkins Secret as an environment variable from PowerShell
  • Run a Jenkins Project (PowerShell) that loads all of the Vault Secrets for the project

When a credential is stored in Jenkins, it is encrypted and the credential secret value cannot be viewed after the fact, outside of a Jenkins project. When the credential is bound to a Jenkins project, it is loaded as an Environment Variable when the project is executed and can be accessed by the automation (PowerShell) in the manner. If the credential or secret is exposed in the StdOut of the automation, Jenkins will mask the credential value when it logs the output (see below).

Step 1: Add the Credential (Vault Secret)

From the Jenkins home, select Credentials, hover over the down arrow next to the domain “(global)”, and select “Add credentials”.

Jenkins-Add-Creds

Step 2: Add the Credential (Vault Secret)

Add the credential as a “Secret text” item.

Jenkins-Add-Creds-2

Step 3: Bind the Credential to the Project

Bind the credential (Vault Secret) to the Jenkins Project.

Jenkns-Bind-Secret

Step 4: Reference the Credential in PowerShell

Reference the Jenkins Secret via an environmental variable within the PowerShell automation.

 

Jenkins-PowerShell

The following PowerShell script is an example which will download and list all Vault Secrets within a particular path. Of course displaying secrets used during automation is not advisable, but serve as an example and launching point for using them in code.

Pull-Vault.ps1 (example):

# Version:: 0.1.5 (3/16/2017)
# Script Description:: Pulls Vault secrets into environmental variables.
#
# Author(s):: Otto Helweg
#
param($token,$vaultSvr,$path)

# Display help
if ($Args -match "-\?|--\?|-help|--help|/\?|/help") {
  Write-Host "Usage: Pull-Vault.ps1"
  Write-Host "     -path [path to credentials]             Path the list of credentials."
  Write-Host "     -token [Vault auth token]               Authentication token for accesing the Vault server."
  Write-Host "     -vaultSvr [Vault server name or IP]     Vault server name or IP address."
  Write-Host ""
  Write-Host "Examples:"
  Write-Host "     Pull-Vault.ps1 -token '770da5b6-eff1-6fd6-f074-1e2604987340'"
  Write-Host "     Pull-Vault.ps1 -token '770da5b6-eff1-6fd6-f074-1e2604987340' -vaultSvr '10.102.76.4'"
  Write-Host ""
  exit
}

if (!($env:VAULT_TOKEN) -or !($env:VAULT_ADDR)) {
  if (!($token)) {
    $token = Read-Host -Prompt "Enter Token for Vault authentication" -AsSecureString
    $token = (New-Object PSCredential "token",$token).GetNetworkCredential().Password
  }

  if (!($vaultSvr)) {
    $vaultSvr = Read-Host -Prompt "Enter Vault Server"
  }

  $env:VAULT_ADDR = "http://$($vaultSvr):8200"
  $env:VAULT_TOKEN = $token
}

if (!($path)) {
  $path = Read-Host -Prompt "Enter Secrets Path"
}

$keys = vault list -format=json $path | ConvertFrom-Json

foreach ($key in $keys) {
  $vaultKey = "TF_VAR_$key"
  $value = vault read -format=json "$($path)/$($key)" | ConvertFrom-Json
  if ($Args -contains "-debug") {
    Write-Host "  $($path)/$($key) : $($value.data.value)"
  }
  Write-Host "Loading env var: $vaultKey"
  Set-Item -path "env:$vaultKey" -value "$($value.data.value)"
}

Note: The output from the Jenkins Project will mask out any output that matches the Jenkins Secret.

...
VAULT_ADDR http://10.10.10.10:8200 
VAULT_ROOT_TOKEN **** 
VAULT_TOKEN **** 
windir C:\Windows 
WINSW_EXECUTABLE C:\Program Files (x86)
...

Enjoy!

Advertisements