1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-12 10:55:20 +02:00

feat(vault): support for multiple general purpose credential paths (#4360)

* created wrapper

* tests added

* update documentation

* tests data race fix

---------

Co-authored-by: Jordi van Liempt <35920075+jliempt@users.noreply.github.com>
This commit is contained in:
Jk1484 2023-05-22 13:49:28 +05:00 committed by GitHub
parent 1e4b88a6f8
commit 27c3c3c4c7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 170 additions and 19 deletions

View File

@ -109,6 +109,18 @@ steps:
vaultCredentialKeys: ['myAppId', 'myAppSecret']
```
In case if you want to retrieve secrets from multiple vault folders, pass several paths with keys:
```yaml
general:
< your Vault configuration > # see above
...
steps:
< piper go step >:
vaultCredentialPath: ['myStepCredentials1', 'myStepCredentials2']
vaultCredentialKeys: [['myAppId1', 'myAppSecret1'], ['myAppId2', 'myAppSecret2']]
```
The `vaultCredentialPath` parameter is the endpoint of your credential path in Vault. Depending on your _general_ config, the lookup for the credential IDs will be done in the following order respectively locations. The first path with found general purpose credentials will be used.
1. `<vaultPath>/<vaultCredentialPath>`

View File

@ -265,8 +265,8 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
if vaultClient != nil {
defer vaultClient.MustRevokeToken()
resolveAllVaultReferences(&stepConfig, vaultClient, append(parameters, ReportingParameters.Parameters...))
resolveVaultTestCredentials(&stepConfig, vaultClient)
resolveVaultCredentials(&stepConfig, vaultClient)
resolveVaultTestCredentialsWrapper(&stepConfig, vaultClient)
resolveVaultCredentialsWrapper(&stepConfig, vaultClient)
}
}

View File

@ -172,6 +172,52 @@ func resolveVaultReference(ref *ResourceReference, config *StepConfig, client va
}
}
func resolveVaultTestCredentialsWrapper(config *StepConfig, client vaultClient) {
log.Entry().Debugln("resolveVaultTestCredentialsWrapper")
resolveVaultTestCredentialsWrapperBase(config, client, vaultTestCredentialPath, vaultTestCredentialKeys, resolveVaultTestCredentials)
}
func resolveVaultCredentialsWrapper(config *StepConfig, client vaultClient) {
log.Entry().Debugln("resolveVaultCredentialsWrapper")
resolveVaultTestCredentialsWrapperBase(config, client, vaultCredentialPath, vaultCredentialKeys, resolveVaultCredentials)
}
func resolveVaultTestCredentialsWrapperBase(
config *StepConfig, client vaultClient,
vaultCredPath, vaultCredKeys string,
resolveVaultCredentials func(config *StepConfig, client vaultClient),
) {
switch config.Config[vaultCredPath].(type) {
case string:
resolveVaultCredentials(config, client)
case []interface{}:
vaultCredentialPathCopy := config.Config[vaultCredPath]
vaultCredentialKeysCopy := config.Config[vaultCredKeys]
if _, ok := vaultCredentialKeysCopy.([]interface{}); !ok {
log.Entry().Debugf("Not fetching credentials from vault since they are not (properly) configured: unknown type of keys")
return
}
if len(vaultCredentialKeysCopy.([]interface{})) != len(vaultCredentialPathCopy.([]interface{})) {
log.Entry().Debugf("Not fetching credentials from vault since they are not (properly) configured: not same count of values and keys")
return
}
for i := 0; i < len(vaultCredentialPathCopy.([]interface{})); i++ {
config.Config[vaultCredPath] = vaultCredentialPathCopy.([]interface{})[i]
config.Config[vaultCredKeys] = vaultCredentialKeysCopy.([]interface{})[i]
resolveVaultCredentials(config, client)
}
config.Config[vaultCredPath] = vaultCredentialPathCopy
config.Config[vaultCredKeys] = vaultCredentialKeysCopy
default:
log.Entry().Debugf("Not fetching credentials from vault since they are not (properly) configured: unknown type of path")
return
}
}
// resolve test credential keys and expose as environment variables
func resolveVaultTestCredentials(config *StepConfig, client vaultClient) {
credPath, pathOk := config.Config[vaultTestCredentialPath].(string)

View File

@ -8,6 +8,7 @@ import (
"io/ioutil"
"os"
"path"
"strconv"
"strings"
"testing"
@ -227,6 +228,93 @@ func addAlias(param *StepParameters, aliasName string) {
param.Aliases = append(param.Aliases, alias)
}
func TestResolveVaultTestCredentialsWrapper(t *testing.T) {
t.Parallel()
t.Run("Default test credential prefix", func(t *testing.T) {
t.Parallel()
// init
vaultMock := &mocks.VaultMock{}
envPrefix := "PIPER_TESTCREDENTIAL_"
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultPath": "team1",
"vaultTestCredentialPath": []interface{}{"appCredentials1", "appCredentials2"},
"vaultTestCredentialKeys": []interface{}{[]interface{}{"appUser1", "appUserPw1"}, []interface{}{"appUser2", "appUserPw2"}},
}}
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER1")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW1")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER2")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW2")
// mock
vaultData1 := map[string]string{"appUser1": "test-user", "appUserPw1": "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials1").Return(vaultData1, nil)
vaultData2 := map[string]string{"appUser2": "test-user", "appUserPw2": "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials2").Return(vaultData2, nil)
// test
resolveVaultTestCredentialsWrapper(&stepConfig, vaultMock)
// assert
for k, expectedValue := range vaultData1 {
env := envPrefix + strings.ToUpper(k)
assert.NotEmpty(t, os.Getenv(env))
assert.Equal(t, expectedValue, os.Getenv(env))
}
// assert
for k, expectedValue := range vaultData2 {
env := envPrefix + strings.ToUpper(k)
assert.NotEmpty(t, os.Getenv(env))
assert.Equal(t, expectedValue, os.Getenv(env))
}
})
// Test empty and non-empty custom general purpose credential prefix
envPrefixes := []string{"CUSTOM_MYCRED1_", ""}
for idx, envPrefix := range envPrefixes {
tEnvPrefix := envPrefix
// this variable is used to avoid race condition, because tests are running in parallel
// env variable with default prefix is being created for each iteration and being set and unset asynchronously
// race condition may occur while one function sets and tries to assert if it exists but the other unsets it before it
stIdx := strconv.Itoa(idx)
t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
t.Parallel()
// init
vaultMock := &mocks.VaultMock{}
standardEnvPrefix := "PIPER_VAULTCREDENTIAL_"
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultPath": "team1",
"vaultCredentialPath": "appCredentials3",
"vaultCredentialKeys": []interface{}{"appUser3" + stIdx, "appUserPw3" + stIdx},
"vaultCredentialEnvPrefix": tEnvPrefix,
}}
defer os.Unsetenv(tEnvPrefix + "APPUSER3" + stIdx)
defer os.Unsetenv(tEnvPrefix + "APPUSERPW3" + stIdx)
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER3" + stIdx)
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW3" + stIdx)
// mock
vaultData := map[string]string{"appUser3" + stIdx: "test-user", "appUserPw3" + stIdx: "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials3").Return(vaultData, nil)
// test
resolveVaultCredentialsWrapper(&stepConfig, vaultMock)
// assert
for k, expectedValue := range vaultData {
env := tEnvPrefix + strings.ToUpper(k)
assert.NotEmpty(t, os.Getenv(env))
assert.Equal(t, expectedValue, os.Getenv(env))
standardEnv := standardEnvPrefix + strings.ToUpper(k)
assert.NotEmpty(t, os.Getenv(standardEnv))
assert.Equal(t, expectedValue, os.Getenv(standardEnv))
}
})
}
}
func TestResolveVaultTestCredentials(t *testing.T) {
t.Parallel()
t.Run("Default test credential prefix", func(t *testing.T) {
@ -237,14 +325,14 @@ func TestResolveVaultTestCredentials(t *testing.T) {
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultPath": "team1",
"vaultTestCredentialPath": "appCredentials",
"vaultTestCredentialKeys": []interface{}{"appUser", "appUserPw"},
"vaultTestCredentialKeys": []interface{}{"appUser4", "appUserPw4"},
}}
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSER4")
defer os.Unsetenv("PIPER_TESTCREDENTIAL_APPUSERPW4")
// mock
vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
vaultData := map[string]string{"appUser4": "test-user", "appUserPw4": "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
// test
@ -260,7 +348,12 @@ func TestResolveVaultTestCredentials(t *testing.T) {
// Test empty and non-empty custom general purpose credential prefix
envPrefixes := []string{"CUSTOM_MYCRED_", ""}
for _, envPrefix := range envPrefixes {
for idx, envPrefix := range envPrefixes {
tEnvPrefix := envPrefix
// this variable is used to avoid race condition, because tests are running in parallel
// env variable with default prefix is being created for each iteration and being set and unset asynchronously
// race condition may occur while one function sets and tries to assert if it exists but the other unsets it before it
stIdx := strconv.Itoa(idx)
t.Run("Custom general purpose credential prefix along with fixed standard prefix", func(t *testing.T) {
t.Parallel()
// init
@ -269,17 +362,17 @@ func TestResolveVaultTestCredentials(t *testing.T) {
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultPath": "team1",
"vaultCredentialPath": "appCredentials",
"vaultCredentialKeys": []interface{}{"appUser", "appUserPw"},
"vaultCredentialEnvPrefix": envPrefix,
"vaultCredentialKeys": []interface{}{"appUser5" + stIdx, "appUserPw5" + stIdx},
"vaultCredentialEnvPrefix": tEnvPrefix,
}}
defer os.Unsetenv(envPrefix + "APPUSER")
defer os.Unsetenv(envPrefix + "APPUSERPW")
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER")
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW")
defer os.Unsetenv(tEnvPrefix + "APPUSER5" + stIdx)
defer os.Unsetenv(tEnvPrefix + "APPUSERPW5" + stIdx)
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSER5" + stIdx)
defer os.Unsetenv("PIPER_VAULTCREDENTIAL_APPUSERPW5" + stIdx)
// mock
vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
vaultData := map[string]string{"appUser5" + stIdx: "test-user", "appUserPw5" + stIdx: "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
// test
@ -287,7 +380,7 @@ func TestResolveVaultTestCredentials(t *testing.T) {
// assert
for k, expectedValue := range vaultData {
env := envPrefix + strings.ToUpper(k)
env := tEnvPrefix + strings.ToUpper(k)
assert.NotEmpty(t, os.Getenv(env))
assert.Equal(t, expectedValue, os.Getenv(env))
standardEnv := standardEnvPrefix + strings.ToUpper(k)
@ -305,15 +398,15 @@ func TestResolveVaultTestCredentials(t *testing.T) {
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultPath": "team1",
"vaultTestCredentialPath": "appCredentials",
"vaultTestCredentialKeys": []interface{}{"appUser", "appUserPw"},
"vaultTestCredentialKeys": []interface{}{"appUser6", "appUserPw6"},
"vaultTestCredentialEnvPrefix": envPrefix,
}}
defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSER")
defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSERPW")
defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSER6")
defer os.Unsetenv("CUSTOM_CREDENTIAL_APPUSERPW6")
// mock
vaultData := map[string]string{"appUser": "test-user", "appUserPw": "password1234"}
vaultData := map[string]string{"appUser6": "test-user", "appUserPw6": "password1234"}
vaultMock.On("GetKvSecret", "team1/appCredentials").Return(vaultData, nil)
// test