2020-07-22 11:15:48 +02:00
package config
import (
2020-10-26 14:20:04 +01:00
"io/ioutil"
"os"
2020-09-16 14:50:09 +02:00
"github.com/SAP/jenkins-library/pkg/config/interpolation"
"github.com/SAP/jenkins-library/pkg/log"
2020-07-22 11:15:48 +02:00
"github.com/SAP/jenkins-library/pkg/vault"
"github.com/hashicorp/vault/api"
)
2020-10-26 14:20:04 +01:00
var (
vaultFilter = [ ] string {
"vaultAppRoleID" ,
"vaultAppRoleSecreId" ,
"vaultServerUrl" ,
"vaultNamespace" ,
"vaultBasePath" ,
"vaultPipelineName" ,
"vaultPath" ,
2021-02-15 09:48:51 +01:00
"skipVault" ,
"vaultDisableOverwrite" ,
2020-10-26 14:20:04 +01:00
}
// VaultSecretFileDirectory holds the directory for the current step run to temporarily store secret files fetched from vault
VaultSecretFileDirectory = ""
)
2020-09-16 14:50:09 +02:00
// VaultCredentials hold all the auth information needed to fetch configuration from vault
type VaultCredentials struct {
AppRoleID string
AppRoleSecretID string
2021-02-15 09:48:51 +01:00
VaultToken string
2020-09-16 14:50:09 +02:00
}
2020-07-22 11:15:48 +02:00
// vaultClient interface for mocking
type vaultClient interface {
GetKvSecret ( string ) ( map [ string ] string , error )
}
2021-01-20 14:59:47 +01:00
func ( s * StepConfig ) mixinVaultConfig ( configs ... map [ string ] interface { } ) {
for _ , config := range configs {
s . mixIn ( config , vaultFilter )
}
}
2020-09-16 14:50:09 +02:00
func getVaultClientFromConfig ( config StepConfig , creds VaultCredentials ) ( vaultClient , error ) {
2020-10-13 14:14:47 +02:00
address , addressOk := config . Config [ "vaultServerUrl" ] . ( string )
2020-07-22 11:15:48 +02:00
// if vault isn't used it's not an error
2021-02-15 09:48:51 +01:00
if ! addressOk || creds . VaultToken == "" && ( creds . AppRoleID == "" || creds . AppRoleSecretID == "" ) {
2020-11-03 08:45:12 +01:00
log . Entry ( ) . Debug ( "Skipping fetching secrets from vault since it is not configured" )
2020-07-22 11:15:48 +02:00
return nil , nil
}
2020-10-13 14:14:47 +02:00
namespace := ""
2020-07-22 11:15:48 +02:00
// namespaces are only available in vault enterprise so using them should be optional
2020-10-13 14:14:47 +02:00
if config . Config [ "vaultNamespace" ] != nil {
namespace = config . Config [ "vaultNamespace" ] . ( string )
log . Entry ( ) . Debugf ( "Using vault namespace %s" , namespace )
}
2020-07-22 11:15:48 +02:00
2021-02-15 09:48:51 +01:00
var client vaultClient
var err error
clientConfig := & vault . Config { Config : & api . Config { Address : address } , Namespace : namespace }
if creds . VaultToken != "" {
log . Entry ( ) . Debugf ( "Using Vault Token Authentication" )
client , err = vault . NewClient ( clientConfig , creds . VaultToken )
} else {
log . Entry ( ) . Debugf ( "Using Vaults AppRole Authentication" )
client , err = vault . NewClientWithAppRole ( clientConfig , creds . AppRoleID , creds . AppRoleSecretID )
}
2020-07-22 11:15:48 +02:00
if err != nil {
return nil , err
}
2020-09-16 14:50:09 +02:00
log . Entry ( ) . Infof ( "Fetching secrets from vault at %s" , address )
2021-02-15 09:48:51 +01:00
return client , nil
2020-07-22 11:15:48 +02:00
}
2020-10-26 14:20:04 +01:00
func resolveAllVaultReferences ( config * StepConfig , client vaultClient , params [ ] StepParameters ) {
2020-07-22 11:15:48 +02:00
for _ , param := range params {
2020-10-26 14:20:04 +01:00
if ref := param . GetReference ( "vaultSecret" ) ; ref != nil {
resolveVaultReference ( ref , config , client , param )
}
if ref := param . GetReference ( "vaultSecretFile" ) ; ref != nil {
resolveVaultReference ( ref , config , client , param )
}
}
}
func resolveVaultReference ( ref * ResourceReference , config * StepConfig , client vaultClient , param StepParameters ) {
2021-02-15 09:48:51 +01:00
vaultDisableOverwrite , _ := config . Config [ "vaultDisableOverwrite" ] . ( bool )
if _ , ok := config . Config [ param . Name ] . ( string ) ; vaultDisableOverwrite && ok {
log . Entry ( ) . Debugf ( "Not fetching '%s' from vault since it has already been set" , param . Name )
return
}
2020-10-26 14:20:04 +01:00
var secretValue * string
for _ , vaultPath := range ref . Paths {
// it should be possible to configure the root path were the secret is stored
vaultPath , ok := interpolation . ResolveString ( vaultPath , config . Config )
if ! ok {
2020-07-22 11:15:48 +02:00
continue
}
2020-10-26 14:20:04 +01:00
secretValue = lookupPath ( client , vaultPath , & param )
if secretValue != nil {
2020-11-03 08:45:12 +01:00
log . Entry ( ) . Debugf ( "Resolved param '%s' with vault path '%s'" , param . Name , vaultPath )
2020-10-26 14:20:04 +01:00
if ref . Type == "vaultSecret" {
2020-10-13 14:14:47 +02:00
config . Config [ param . Name ] = * secretValue
2020-10-26 14:20:04 +01:00
} else if ref . Type == "vaultSecretFile" {
filePath , err := createTemporarySecretFile ( param . Name , * secretValue )
if err != nil {
log . Entry ( ) . WithError ( err ) . Warnf ( "Couldn't create temporary secret file for '%s'" , param . Name )
return
}
config . Config [ param . Name ] = filePath
2020-07-22 11:15:48 +02:00
}
2020-10-26 14:20:04 +01:00
break
2020-07-22 11:15:48 +02:00
}
2020-10-26 14:20:04 +01:00
}
if secretValue == nil {
log . Entry ( ) . Warnf ( "Could not resolve param '%s' from vault" , param . Name )
}
}
// RemoveVaultSecretFiles removes all secret files that have been created during execution
func RemoveVaultSecretFiles ( ) {
if VaultSecretFileDirectory != "" {
os . RemoveAll ( VaultSecretFileDirectory )
}
}
func createTemporarySecretFile ( namePattern string , content string ) ( string , error ) {
if VaultSecretFileDirectory == "" {
var err error
VaultSecretFileDirectory , err = ioutil . TempDir ( "" , "vault" )
if err != nil {
return "" , err
2020-10-13 14:14:47 +02:00
}
}
2020-10-26 14:20:04 +01:00
file , err := ioutil . TempFile ( VaultSecretFileDirectory , namePattern )
if err != nil {
return "" , err
}
defer file . Close ( )
_ , err = file . WriteString ( content )
if err != nil {
return "" , err
}
return file . Name ( ) , nil
2020-10-13 14:14:47 +02:00
}
func lookupPath ( client vaultClient , path string , param * StepParameters ) * string {
2020-11-03 08:45:12 +01:00
log . Entry ( ) . Debugf ( "Trying to resolve vault parameter '%s' at '%s'" , param . Name , path )
2020-10-13 14:14:47 +02:00
secret , err := client . GetKvSecret ( path )
if err != nil {
log . Entry ( ) . WithError ( err ) . Warnf ( "Couldn't fetch secret at '%s'" , path )
return nil
}
if secret == nil {
return nil
}
field := secret [ param . Name ]
if field != "" {
log . RegisterSecret ( field )
return & field
}
2020-11-17 13:49:31 +01:00
log . Entry ( ) . Debugf ( "Secret did not contain a field name '%s'" , param . Name )
2020-10-13 14:14:47 +02:00
// try parameter aliases
for _ , alias := range param . Aliases {
2020-11-17 13:49:31 +01:00
log . Entry ( ) . Debugf ( "Trying alias field name '%s'" , alias . Name )
2020-11-06 17:54:01 +01:00
field := secret [ alias . Name ]
2020-10-13 14:14:47 +02:00
if field != "" {
log . RegisterSecret ( field )
if alias . Deprecated {
log . Entry ( ) . WithField ( "package" , "SAP/jenkins-library/pkg/config" ) . Warningf ( "DEPRECATION NOTICE: old step config key '%s' used in vault. Please switch to '%s'!" , alias . Name , param . Name )
}
return & field
}
2020-07-22 11:15:48 +02:00
}
return nil
}