2020-07-22 11:15:48 +02:00
package config
import (
2020-10-26 15:20:04 +02:00
"io/ioutil"
"os"
2021-09-21 13:06:32 +02:00
"path"
2021-04-09 10:04:35 +02:00
"regexp"
"strings"
2020-10-26 15:20:04 +02:00
2020-09-16 14:50:09 +02:00
"github.com/SAP/jenkins-library/pkg/config/interpolation"
"github.com/SAP/jenkins-library/pkg/log"
2022-01-13 14:08:19 +02:00
CredentialUtils "github.com/SAP/jenkins-library/pkg/piperutils"
2020-07-22 11:15:48 +02:00
"github.com/SAP/jenkins-library/pkg/vault"
"github.com/hashicorp/vault/api"
)
2021-04-09 10:04:35 +02:00
const (
2021-09-21 13:06:32 +02:00
vaultRootPaths = "vaultRootPaths"
vaultTestCredentialPath = "vaultTestCredentialPath"
2021-12-21 12:00:13 +02:00
vaultCredentialPath = "vaultCredentialPath"
2021-09-21 13:06:32 +02:00
vaultTestCredentialKeys = "vaultTestCredentialKeys"
2021-12-21 12:00:13 +02:00
vaultCredentialKeys = "vaultCredentialKeys"
2021-09-21 13:06:32 +02:00
vaultAppRoleID = "vaultAppRoleID"
vaultAppRoleSecretID = "vaultAppRoleSecreId"
vaultServerUrl = "vaultServerUrl"
vaultNamespace = "vaultNamespace"
vaultBasePath = "vaultBasePath"
vaultPipelineName = "vaultPipelineName"
vaultPath = "vaultPath"
skipVault = "skipVault"
vaultDisableOverwrite = "vaultDisableOverwrite"
2021-11-19 11:05:39 +02:00
vaultTestCredentialEnvPrefix = "vaultTestCredentialEnvPrefix"
2021-12-21 12:00:13 +02:00
vaultCredentialEnvPrefix = "vaultCredentialEnvPrefix"
2021-09-21 13:06:32 +02:00
vaultTestCredentialEnvPrefixDefault = "PIPER_TESTCREDENTIAL_"
2021-12-21 12:00:13 +02:00
vaultCredentialEnvPrefixDefault = "PIPER_VAULTCREDENTIAL_"
2021-04-09 10:04:35 +02:00
)
2020-10-26 15:20:04 +02:00
var (
vaultFilter = [ ] string {
2021-09-21 13:06:32 +02:00
vaultRootPaths ,
vaultAppRoleID ,
vaultAppRoleSecretID ,
vaultServerUrl ,
vaultNamespace ,
vaultBasePath ,
vaultPipelineName ,
vaultPath ,
skipVault ,
vaultDisableOverwrite ,
2021-04-09 10:04:35 +02:00
vaultTestCredentialPath ,
vaultTestCredentialKeys ,
2021-11-19 11:05:39 +02:00
vaultTestCredentialEnvPrefix ,
2021-12-21 12:00:13 +02:00
vaultCredentialPath ,
vaultCredentialKeys ,
vaultCredentialEnvPrefix ,
2020-10-26 15:20:04 +02:00
}
2021-09-21 13:06:32 +02:00
// VaultRootPaths are the lookup paths piper tries to use during the vault lookup.
// A path is only used if it's variables can be interpolated from the config
VaultRootPaths = [ ] string {
"$(vaultPath)" ,
"$(vaultBasePath)/$(vaultPipelineName)" ,
"$(vaultBasePath)/GROUP-SECRETS" ,
}
2020-10-26 15:20:04 +02: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 10:48:51 +02: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-03-10 09:36:50 +02:00
MustRevokeToken ( )
2020-07-22 11:15:48 +02:00
}
2021-09-21 13:06:32 +02:00
func ( s * StepConfig ) mixinVaultConfig ( parameters [ ] StepParameters , configs ... map [ string ] interface { } ) {
2021-01-20 15:59:47 +02:00
for _ , config := range configs {
s . mixIn ( config , vaultFilter )
2021-09-21 13:06:32 +02:00
// when an empty filter is returned we skip the mixin call since an empty filter will allow everything
if referencesFilter := getFilterForResourceReferences ( parameters ) ; len ( referencesFilter ) > 0 {
s . mixIn ( config , referencesFilter )
}
2021-01-20 15:59:47 +02:00
}
}
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 10:48:51 +02:00
if ! addressOk || creds . VaultToken == "" && ( creds . AppRoleID == "" || creds . AppRoleSecretID == "" ) {
2021-12-21 12:52:10 +02: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 )
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Debugf ( "Using Vault namespace %s" , namespace )
2020-10-13 14:14:47 +02:00
}
2020-07-22 11:15:48 +02:00
2021-02-15 10:48:51 +02: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 {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Debugf ( "Using Vault AppRole Authentication" )
2021-02-15 10:48:51 +02:00
client , err = vault . NewClientWithAppRole ( clientConfig , creds . AppRoleID , creds . AppRoleSecretID )
}
2020-07-22 11:15:48 +02:00
if err != nil {
return nil , err
}
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Infof ( "Fetching secrets from Vault at %s" , address )
2021-02-15 10:48:51 +02:00
return client , nil
2020-07-22 11:15:48 +02:00
}
2020-10-26 15:20:04 +02:00
func resolveAllVaultReferences ( config * StepConfig , client vaultClient , params [ ] StepParameters ) {
2020-07-22 11:15:48 +02:00
for _ , param := range params {
2020-10-26 15:20:04 +02: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 10:48:51 +02:00
vaultDisableOverwrite , _ := config . Config [ "vaultDisableOverwrite" ] . ( bool )
if _ , ok := config . Config [ param . Name ] . ( string ) ; vaultDisableOverwrite && ok {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Debugf ( "Not fetching '%s' from Vault since it has already been set" , param . Name )
2021-02-15 10:48:51 +02:00
return
}
2020-10-26 15:20:04 +02:00
var secretValue * string
2021-09-21 13:06:32 +02:00
for _ , vaultPath := range getSecretReferencePaths ( ref , config . Config ) {
2020-10-26 15:20:04 +02:00
// 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 15:20:04 +02:00
secretValue = lookupPath ( client , vaultPath , & param )
if secretValue != nil {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Debugf ( "Resolved param '%s' with Vault path '%s'" , param . Name , vaultPath )
2020-10-26 15:20:04 +02:00
if ref . Type == "vaultSecret" {
2020-10-13 14:14:47 +02:00
config . Config [ param . Name ] = * secretValue
2020-10-26 15:20:04 +02: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 15:20:04 +02:00
break
2020-07-22 11:15:48 +02:00
}
2020-10-26 15:20:04 +02:00
}
if secretValue == nil {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Warnf ( "Could not resolve param '%s' from Vault" , param . Name )
2020-10-26 15:20:04 +02:00
}
}
2021-04-09 10:04:35 +02:00
// resolve test credential keys and expose as environment variables
func resolveVaultTestCredentials ( config * StepConfig , client vaultClient ) {
credPath , pathOk := config . Config [ vaultTestCredentialPath ] . ( string )
keys := getTestCredentialKeys ( config )
if ! ( pathOk && keys != nil ) || credPath == "" || len ( keys ) == 0 {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Debugf ( "Not fetching test credentials from Vault since they are not (properly) configured" )
2021-04-09 10:04:35 +02:00
return
}
lookupPath := make ( [ ] string , 3 )
lookupPath [ 0 ] = "$(vaultPath)/" + credPath
lookupPath [ 1 ] = "$(vaultBasePath)/$(vaultPipelineName)/" + credPath
lookupPath [ 2 ] = "$(vaultBasePath)/GROUP-SECRETS/" + credPath
for _ , path := range lookupPath {
vaultPath , ok := interpolation . ResolveString ( path , config . Config )
if ! ok {
continue
}
secret , err := client . GetKvSecret ( vaultPath )
if err != nil {
log . Entry ( ) . WithError ( err ) . Debugf ( "Couldn't fetch secret at '%s'" , vaultPath )
continue
}
if secret == nil {
continue
}
secretsResolved := false
2021-08-11 16:20:08 +02:00
secretsResolved = populateTestCredentialsAsEnvs ( config , secret , keys )
2021-04-09 10:04:35 +02:00
if secretsResolved {
// prevent overwriting resolved secrets
// only allows vault test credentials on one / the same vault path
break
}
}
}
2021-12-21 12:00:13 +02:00
func resolveVaultCredentials ( config * StepConfig , client vaultClient ) {
credPath , pathOk := config . Config [ vaultCredentialPath ] . ( string )
keys := getCredentialKeys ( config )
if ! ( pathOk && keys != nil ) || credPath == "" || len ( keys ) == 0 {
log . Entry ( ) . Debugf ( "Not fetching test credentials from vault since they are not (properly) configured" )
return
}
lookupPath := make ( [ ] string , 3 )
lookupPath [ 0 ] = "$(vaultPath)/" + credPath
lookupPath [ 1 ] = "$(vaultBasePath)/$(vaultPipelineName)/" + credPath
lookupPath [ 2 ] = "$(vaultBasePath)/GROUP-SECRETS/" + credPath
for _ , path := range lookupPath {
vaultPath , ok := interpolation . ResolveString ( path , config . Config )
if ! ok {
continue
}
secret , err := client . GetKvSecret ( vaultPath )
if err != nil {
log . Entry ( ) . WithError ( err ) . Debugf ( "Couldn't fetch secret at '%s'" , vaultPath )
continue
}
if secret == nil {
continue
}
secretsResolved := false
secretsResolved = populateCredentialsAsEnvs ( config , secret , keys )
if secretsResolved {
// prevent overwriting resolved secrets
// only allows vault test credentials on one / the same vault path
break
}
}
}
2021-08-11 16:20:08 +02:00
func populateTestCredentialsAsEnvs ( config * StepConfig , secret map [ string ] string , keys [ ] string ) ( matched bool ) {
vaultTestCredentialEnvPrefix , ok := config . Config [ "vaultTestCredentialEnvPrefix" ] . ( string )
if ! ok || len ( vaultTestCredentialEnvPrefix ) == 0 {
2021-09-21 13:06:32 +02:00
vaultTestCredentialEnvPrefix = vaultTestCredentialEnvPrefixDefault
2021-08-11 16:20:08 +02:00
}
2021-04-09 10:04:35 +02:00
for secretKey , secretValue := range secret {
for _ , key := range keys {
if secretKey == key {
log . RegisterSecret ( secretValue )
envVariable := vaultTestCredentialEnvPrefix + convertEnvVar ( secretKey )
log . Entry ( ) . Debugf ( "Exposing test credential '%v' as '%v'" , key , envVariable )
os . Setenv ( envVariable , secretValue )
matched = true
}
}
}
return
}
2021-12-21 12:00:13 +02:00
func populateCredentialsAsEnvs ( config * StepConfig , secret map [ string ] string , keys [ ] string ) ( matched bool ) {
vaultCredentialEnvPrefix , ok := config . Config [ "vaultCredentialEnvPrefix" ] . ( string )
isCredentialEnvPrefixDefault := false
if ! ok || len ( vaultCredentialEnvPrefix ) == 0 {
vaultCredentialEnvPrefix = vaultCredentialEnvPrefixDefault
isCredentialEnvPrefixDefault = true
}
for secretKey , secretValue := range secret {
for _ , key := range keys {
if secretKey == key {
log . RegisterSecret ( secretValue )
envVariable := vaultCredentialEnvPrefix + convertEnvVar ( secretKey )
log . Entry ( ) . Debugf ( "Exposing general purpose credential '%v' as '%v'" , key , envVariable )
os . Setenv ( envVariable , secretValue )
2022-01-13 14:08:19 +02:00
envVariable = vaultCredentialEnvPrefix + convertEnvVar ( secretKey ) + "_BASE64"
log . Entry ( ) . Debugf ( "Exposing general purpose base64 encoded credential '%v' as '%v'" , key , envVariable )
os . Setenv ( envVariable , CredentialUtils . EncodeString ( secretValue ) )
2021-12-21 12:00:13 +02:00
matched = true
}
}
}
// we always create a standard env variable with the default prefx so that
// we can always refer to it in steps if its to be hard-coded
if ! isCredentialEnvPrefixDefault {
for secretKey , secretValue := range secret {
for _ , key := range keys {
if secretKey == key {
log . RegisterSecret ( secretValue )
envVariable := vaultCredentialEnvPrefixDefault + convertEnvVar ( secretKey )
log . Entry ( ) . Debugf ( "Exposing general purpose credential '%v' as '%v'" , key , envVariable )
os . Setenv ( envVariable , secretValue )
2022-01-13 14:08:19 +02:00
envVariable = vaultCredentialEnvPrefixDefault + convertEnvVar ( secretKey ) + "_BASE64"
log . Entry ( ) . Debugf ( "Exposing general purpose base64 encoded credential '%v' as '%v'" , key , envVariable )
os . Setenv ( envVariable , CredentialUtils . EncodeString ( secretValue ) )
2021-12-21 12:00:13 +02:00
matched = true
}
}
}
}
return
}
2021-04-09 10:04:35 +02:00
func getTestCredentialKeys ( config * StepConfig ) [ ] string {
keysRaw , ok := config . Config [ vaultTestCredentialKeys ] . ( [ ] interface { } )
if ! ok {
return nil
}
keys := make ( [ ] string , 0 , len ( keysRaw ) )
for _ , keyRaw := range keysRaw {
key , ok := keyRaw . ( string )
if ! ok {
2021-12-21 12:52:10 +02:00
log . Entry ( ) . Warnf ( "%s needs to be an array of strings" , vaultTestCredentialKeys )
2021-04-09 10:04:35 +02:00
return nil
}
keys = append ( keys , key )
}
return keys
}
2021-12-21 12:00:13 +02:00
func getCredentialKeys ( config * StepConfig ) [ ] string {
keysRaw , ok := config . Config [ vaultCredentialKeys ] . ( [ ] interface { } )
if ! ok {
log . Entry ( ) . Debugf ( "Not fetching general purpose credentials from vault since they are not (properly) configured" )
return nil
}
keys := make ( [ ] string , 0 , len ( keysRaw ) )
for _ , keyRaw := range keysRaw {
key , ok := keyRaw . ( string )
if ! ok {
log . Entry ( ) . Warnf ( "%s is needs to be an array of strings" , vaultCredentialKeys )
return nil
}
keys = append ( keys , key )
}
return keys
}
2021-04-09 10:04:35 +02:00
// converts to a valid environment variable string
func convertEnvVar ( s string ) string {
r := strings . ToUpper ( s )
r = strings . ReplaceAll ( r , "-" , "_" )
reg , err := regexp . Compile ( "[^a-zA-Z0-9_]*" )
if err != nil {
log . Entry ( ) . Debugf ( "could not compile regex of convertEnvVar: %v" , err )
}
replacedString := reg . ReplaceAllString ( r , "" )
return replacedString
}
2020-10-26 15:20:04 +02:00
// 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 15:20:04 +02: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 {
2021-12-21 12:52:10 +02: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 14:49:31 +02: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 14:49:31 +02:00
log . Entry ( ) . Debugf ( "Trying alias field name '%s'" , alias . Name )
2020-11-06 18:54:01 +02:00
field := secret [ alias . Name ]
2020-10-13 14:14:47 +02:00
if field != "" {
log . RegisterSecret ( field )
if alias . Deprecated {
2021-12-21 12:52:10 +02:00
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 )
2020-10-13 14:14:47 +02:00
}
return & field
}
2020-07-22 11:15:48 +02:00
}
return nil
}
2021-09-21 13:06:32 +02:00
func getSecretReferencePaths ( reference * ResourceReference , config map [ string ] interface { } ) [ ] string {
retPaths := make ( [ ] string , 0 , len ( VaultRootPaths ) )
secretName := reference . Default
if providedName , ok := config [ reference . Name ] . ( string ) ; ok && providedName != "" {
secretName = providedName
}
for _ , rootPath := range VaultRootPaths {
fullPath := path . Join ( rootPath , secretName )
retPaths = append ( retPaths , fullPath )
}
return retPaths
}
func toStringSlice ( interfaceSlice [ ] interface { } ) [ ] string {
retSlice := make ( [ ] string , 0 , len ( interfaceSlice ) )
for _ , vRaw := range interfaceSlice {
if v , ok := vRaw . ( string ) ; ok {
retSlice = append ( retSlice , v )
continue
}
log . Entry ( ) . Warnf ( "'%s' needs to be of type string or an array of strings but got %T (%[2]v)" , vaultPath , vRaw )
}
return retSlice
}