1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2025-01-20 05:19:40 +02:00

feat(vault): fetch secrets from vault (#2032)

* cloud-foundry & sonar from vault

* add vault development hint

* don't abort on vault errors

* cloudfoundry make credentialsId only mandatory when vault is not configured

* add vault ref to step ymls

* rename vaultAddress to vaultServerUrl

* rename PIPER_vaultRole* to PIPER_vaultAppRole*

* add resourceRef for detect step

* fix error when no namespace is set

* added debug logs

* added debug logs

* fix vault resolving

* add vaultCustomBasePath

* rename vault_test.go to client_test.go

* refactored vault logging

* refactored config param lookup for vault

* added tüddelchen

* rename vaultCustomBasePath to vaultPath

* fix tests

* change lookup path for group secrets

* fix interpolation tests

* added vault resource ref to versioning

* execute go generate

* rename Approle to AppRole

* change verbose back to false

Co-authored-by: Leander Schulz <leander.schulz01@sap.com>
Co-authored-by: Christopher Fenner <26137398+CCFenner@users.noreply.github.com>
This commit is contained in:
Kevin Stiehl 2020-10-13 14:14:47 +02:00 committed by GitHub
parent 2db5c11047
commit 3eae0c5f68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
32 changed files with 323 additions and 70 deletions

View File

@ -481,3 +481,7 @@ On initialization, it merges the provided custom default configurations with the
Note, the list of configurations cached by `DefaultValueCache` is used to pass path to the (custom) default configurations of each Go step.
It only contains the paths of configurations which are **not** provided via `customDefaults` parameter of the project configuration, since the Go layer already resolves configurations provided via `customDefaults` parameter independently.
## Additional Developer Hints
You can find additional hints at [documentation/developer-hints](./documentation/developer_hints)

View File

@ -312,6 +312,12 @@ func artifactPrepareVersionMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/versioning", "$(vaultBasePath)/$(vaultPipelineName)/versioning", "$(vaultBasePath)/GROUP-SECRETS/versioning"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -358,6 +364,12 @@ func artifactPrepareVersionMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/versioning", "$(vaultBasePath)/$(vaultPipelineName)/versioning", "$(vaultBasePath)/GROUP-SECRETS/versioning"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -323,6 +323,12 @@ func checkmarxExecuteScanMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/checkmarx", "$(vaultBasePath)/$(vaultPipelineName)/checkmarx", "$(vaultBasePath)/GROUP-SECRETS/checkmarx"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -393,6 +399,12 @@ func checkmarxExecuteScanMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/checkmarx", "$(vaultBasePath)/$(vaultPipelineName)/checkmarx", "$(vaultBasePath)/GROUP-SECRETS/checkmarx"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -126,6 +126,12 @@ func cloudFoundryCreateServiceKeyMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -140,6 +146,12 @@ func cloudFoundryCreateServiceKeyMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -142,6 +142,12 @@ func cloudFoundryCreateServiceMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -156,6 +162,12 @@ func cloudFoundryCreateServiceMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -123,6 +123,12 @@ func cloudFoundryDeleteServiceMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -137,6 +143,12 @@ func cloudFoundryDeleteServiceMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -402,6 +402,12 @@ func cloudFoundryDeployMetadata() config.StepData {
Param: "password",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",
@ -440,6 +446,12 @@ func cloudFoundryDeployMetadata() config.StepData {
Param: "username",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)", "$(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -126,6 +126,12 @@ func detectExecuteScanMetadata() config.StepData {
Name: "detectTokenCredentialsId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/detect", "$(vaultBasePath)/$(vaultPipelineName)/detect", "$(vaultBasePath)/GROUP-SECRETS/detect"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -260,6 +260,12 @@ func fortifyExecuteScanMetadata() config.StepData {
Name: "fortifyCredentialsId",
Type: "secret",
},
{
Name: "",
Paths: []string{"$(vaultPath)/fortify", "$(vaultBasePath)/$(vaultPipelineName)/fortify", "$(vaultBasePath)/GROUP-SECRETS/fortify"},
Type: "vaultSecret",
},
},
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
Type: "string",

View File

@ -207,10 +207,10 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
// add vault credentials so that configuration can be fetched from vault
if GeneralConfig.VaultRoleID == "" {
GeneralConfig.VaultRoleID = os.Getenv("PIPER_vaultRoleID")
GeneralConfig.VaultRoleID = os.Getenv("PIPER_vaultAppRoleID")
}
if GeneralConfig.VaultRoleSecretID == "" {
GeneralConfig.VaultRoleSecretID = os.Getenv("PIPER_vaultRoleSecretID")
GeneralConfig.VaultRoleSecretID = os.Getenv("PIPER_vaultAppRoleSecretID")
}
myConfig.SetVaultCredentials(GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID)

View File

@ -193,6 +193,12 @@ func sonarExecuteScanMetadata() config.StepData {
{
Name: "token",
ResourceRef: []config.ResourceReference{
{
Name: "",
Paths: []string{"$(vaultPath)/sonar", "$(vaultBasePath)/$(vaultPipelineName)/sonar", "$(vaultBasePath)/GROUP-SECRETS/sonar"},
Type: "vaultSecret",
},
{
Name: "sonarTokenCredentialsId",
Type: "secret",

View File

@ -0,0 +1,29 @@
# The Vault ResourceRef
## Preconditions
Parameters that have a ResourceReference of type `vaultSecret` will be looked up from vault when all of the following things are true...
* The environment variables `PIPER_vaultAppRoleID` and `PIPER_vaultAppRoleSecretID` must both be set to the Vault AppRole role ID and to the Vault AppRole secret ID. See [Vault AppRole docs](https://www.vaultproject.io/docs/auth/approle)
* `vaultServerUrl` ist set in the `general` section of the configuration file.
* The parameter must not be set by the configuration file, as a CLI Parameter or an environment variable. Any parameter that has already been set won't be resolved via vault.
## Lookup
```
- name: token
type: string
description: "Token used to authenticate with the Sonar Server."
scope:
- PARAMETERS
secret: true
resourceRef:
- type: vaultSecret
paths:
- $(vaultBasePath)/$(vaultPipelineName)/sonar
- $(vaultBasePath)/__group/sonar
```
With the example above piper will check whether the the `token` parameter has already been set when the config was resolved. If `token` hasn't be resolved yet we will go through every item of the `paths` array, interpolate every string by using the already resolved config and then check whether there is a secret stored at the given path.
In case we find a secret we check whether it has a field (secrets in vault are **flat** json documents) that matches the parameters name (or one of the alias names), in the example above this would be `token`.

View File

@ -234,10 +234,7 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
return StepConfig{}, err
}
if vaultClient != nil {
err = addVaultCredentials(&stepConfig, vaultClient, parameters)
if err != nil {
return StepConfig{}, err
}
addVaultCredentials(&stepConfig, vaultClient, parameters)
}
// finally do the condition evaluation post processing

View File

@ -4,6 +4,8 @@ import (
"fmt"
"regexp"
"strings"
"github.com/SAP/jenkins-library/pkg/log"
)
const (
@ -16,33 +18,35 @@ var (
)
// ResolveMap interpolates every string value of a map and tries to lookup references to other properties of that map
func ResolveMap(config map[string]interface{}) error {
func ResolveMap(config map[string]interface{}) bool {
for key, value := range config {
if str, ok := value.(string); ok {
resolvedStr, err := ResolveString(str, config)
if err != nil {
return err
resolvedStr, ok := ResolveString(str, config)
if !ok {
return false
}
config[key] = resolvedStr
}
}
return nil
return true
}
func resolveString(str string, lookupMap map[string]interface{}, n int) (string, error) {
func resolveString(str string, lookupMap map[string]interface{}, n int) (string, bool) {
matches := lookupRegex.FindAllStringSubmatch(str, -1)
if len(matches) == 0 {
return str, nil
return str, true
}
if n == maxLookupDepth {
return "", fmt.Errorf("Property could not be resolved with a depth of %d. '%s' is still left to resolve", n, str)
log.Entry().Errorf("Property could not be resolved with a depth of %d. '%s' is still left to resolve", n, str)
return "", false
}
for _, match := range matches {
property := match[captureGroups["property"]]
if propVal, ok := lookupMap[property]; ok {
str = strings.ReplaceAll(str, fmt.Sprintf("$(%s)", property), propVal.(string))
} else {
str = strings.ReplaceAll(str, fmt.Sprintf("$(%s)", property), "")
// value not found
return "", false
}
}
return resolveString(str, lookupMap, n+1)
@ -50,7 +54,7 @@ func resolveString(str string, lookupMap map[string]interface{}, n int) (string,
// ResolveString takes a string and replaces all references inside of it with values from the given lookupMap.
// This is being done recursively until the maxLookupDepth is reached.
func ResolveString(str string, lookupMap map[string]interface{}) (string, error) {
func ResolveString(str string, lookupMap map[string]interface{}) (string, bool) {
return resolveString(str, lookupMap, 0)
}

View File

@ -9,26 +9,37 @@ import (
func TestResolveMap(t *testing.T) {
t.Parallel()
t.Run("Lookup lookup works", func(t *testing.T) {
t.Run("That lookup works", func(t *testing.T) {
testMap := map[string]interface{}{
"prop1": "val1",
"prop2": "val2",
"prop3": "$(prop1)/$(prop2)",
}
err := ResolveMap(testMap)
assert.NoError(t, err)
ok := ResolveMap(testMap)
assert.True(t, ok)
assert.Equal(t, "val1/val2", testMap["prop3"])
})
t.Run("That lookups fails when property is not found", func(t *testing.T) {
testMap := map[string]interface{}{
"prop1": "val1",
"prop2": "val2",
"prop3": "$(prop1)/$(prop2)/$(prop5)",
}
ok := ResolveMap(testMap)
assert.False(t, ok)
})
t.Run("That resolve loops are aborted", func(t *testing.T) {
testMap := map[string]interface{}{
"prop1": "$(prop2)",
"prop2": "$(prop1)",
}
err := ResolveMap(testMap)
assert.Error(t, err)
ok := ResolveMap(testMap)
assert.False(t, ok)
})
}

View File

@ -233,7 +233,7 @@ func (m *StepData) GetContextParameterFilters() StepFilters {
}
if m.HasReference("vaultSecret") {
contextFilters = append(contextFilters, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}...)
contextFilters = append(contextFilters, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}...)
}
if len(contextFilters) > 0 {

View File

@ -300,12 +300,12 @@ func TestGetContextParameterFilters(t *testing.T) {
t.Run("Vault", func(t *testing.T) {
filters := metadata4.GetContextParameterFilters()
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.All, "incorrect filter All")
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.General, "incorrect filter General")
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.Steps, "incorrect filter Steps")
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.Stages, "incorrect filter Stages")
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.Parameters, "incorrect filter Parameters")
assert.Equal(t, []string{"vaultAppRoleCredentialId", "vaultAppRoleSecretCredentialId"}, filters.Env, "incorrect filter Env")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.All, "incorrect filter All")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.General, "incorrect filter General")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.Steps, "incorrect filter Steps")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.Stages, "incorrect filter Stages")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.Parameters, "incorrect filter Parameters")
assert.Equal(t, []string{"vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"}, filters.Env, "incorrect filter Env")
})
}

View File

@ -8,12 +8,13 @@ import (
)
var vaultFilter = []string{
"vaultApproleID",
"vaultApproleSecreId",
"vaultAddress",
"vaultAppRoleID",
"vaultAppRoleSecreId",
"vaultServerUrl",
"vaultNamespace",
"vaultBasePath",
"vaultPipelineName",
"vaultPath",
}
// VaultCredentials hold all the auth information needed to fetch configuration from vault
@ -28,16 +29,18 @@ type vaultClient interface {
}
func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultClient, error) {
address, addressOk := config.Config["vaultAddress"].(string)
log.Entry().Infof("config received %#v", config.Config)
address, addressOk := config.Config["vaultServerUrl"].(string)
// if vault isn't used it's not an error
if !addressOk || creds.AppRoleID == "" || creds.AppRoleSecretID == "" {
log.Entry().Info("Skipping fetching secrets from vault since it is not configured")
return nil, nil
}
namespace := ""
// namespaces are only available in vault enterprise so using them should be optional
namespace := config.Config["vaultNamespace"].(string)
if config.Config["vaultNamespace"] != nil {
namespace = config.Config["vaultNamespace"].(string)
log.Entry().Debugf("Using vault namespace %s", namespace)
}
client, err := vault.NewClientWithAppRole(&api.Config{Address: address}, creds.AppRoleID, creds.AppRoleSecretID, namespace)
if err != nil {
@ -48,9 +51,8 @@ func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultC
return &client, nil
}
func addVaultCredentials(config *StepConfig, client vaultClient, params []StepParameters) error {
func addVaultCredentials(config *StepConfig, client vaultClient, params []StepParameters) {
for _, param := range params {
// we don't overwrite secrets that have already been set in any way
if _, ok := config.Config[param.Name].(string); ok {
continue
@ -59,29 +61,54 @@ func addVaultCredentials(config *StepConfig, client vaultClient, params []StepPa
if ref == nil {
continue
}
var secretValue *string
for _, vaultPath := range ref.Paths {
// it should be possible to configure the root path were the secret is stored
var err error
vaultPath, err = interpolation.ResolveString(vaultPath, config.Config)
if err != nil {
return err
}
secret, err := client.GetKvSecret(vaultPath)
if err != nil {
return err
}
if secret == nil {
vaultPath, ok := interpolation.ResolveString(vaultPath, config.Config)
if !ok {
continue
}
field := secret[param.Name]
if field != "" {
log.RegisterSecret(field)
config.Config[param.Name] = field
secretValue = lookupPath(client, vaultPath, &param)
if secretValue != nil {
config.Config[param.Name] = *secretValue
log.Entry().Infof("Resolved param '%s' with vault path '%s'", param.Name, vaultPath)
break
}
}
if secretValue == nil {
log.Entry().Warnf("Could not resolve param '%s' from vault", param.Name)
}
}
}
func lookupPath(client vaultClient, path string, param *StepParameters) *string {
log.Entry().Infof("Trying to resolve vault parameter '%s' at '%s'", param.Name, path)
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
}
// try parameter aliases
for _, alias := range param.Aliases {
field := secret[param.Name]
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
}
}
return nil
}

View File

@ -4,6 +4,8 @@ import (
"fmt"
"testing"
"github.com/stretchr/testify/mock"
"github.com/SAP/jenkins-library/pkg/config/mocks"
"github.com/stretchr/testify/assert"
)
@ -20,8 +22,7 @@ func TestVaultConfigLoad(t *testing.T) {
vaultData := map[string]string{secretName: "value1"}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(vaultData, nil)
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.NoError(t, err)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Equal(t, "value1", stepConfig.Config[secretName])
})
@ -34,9 +35,8 @@ func TestVaultConfigLoad(t *testing.T) {
stepParams := []StepParameters{stepParam(secretName, "vaultSecret", "$(vaultBasePath)/pipelineA")}
vaultData := map[string]string{secretName: "value1"}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(vaultData, nil)
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.NoError(t, err)
assert.Equal(t, "preset value", stepConfig.Config[secretName])
})
@ -47,9 +47,8 @@ func TestVaultConfigLoad(t *testing.T) {
}}
stepParams := []StepParameters{stepParam(secretName, "vaultSecret", "$(vaultBasePath)/pipelineA")}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(nil, fmt.Errorf("test"))
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Len(t, stepConfig.Config, 1)
assert.EqualError(t, err, "test")
})
t.Run("Secret doesn't exist", func(t *testing.T) {
@ -59,8 +58,7 @@ func TestVaultConfigLoad(t *testing.T) {
}}
stepParams := []StepParameters{stepParam(secretName, "vaultSecret", "$(vaultBasePath)/pipelineA")}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(nil, nil)
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.NoError(t, err)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Len(t, stepConfig.Config, 1)
})
@ -75,22 +73,33 @@ func TestVaultConfigLoad(t *testing.T) {
vaultData := map[string]string{secretName: "value1"}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(nil, nil)
vaultMock.On("GetKvSecret", "team1/pipelineB").Return(vaultData, nil)
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.NoError(t, err)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Equal(t, "value1", stepConfig.Config[secretName])
})
t.Run("Stop lookup when secret was found", func(t *testing.T) {
vaultMock := &mocks.VaultMock{}
stepConfig := StepConfig{Config: map[string]interface{}{
"vaultBasePath": "team1",
}}
stepParams := []StepParameters{
stepParam(secretName, "vaultSecret", "$(vaultBasePath)/pipelineA", "$(vaultBasePath)/pipelineB"),
}
vaultData := map[string]string{secretName: "value1"}
vaultMock.On("GetKvSecret", "team1/pipelineA").Return(vaultData, nil)
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Equal(t, "value1", stepConfig.Config[secretName])
vaultMock.AssertNotCalled(t, "GetKvSecret", "team1/pipelineB")
})
t.Run("No BasePath is stepConfig.Configured", func(t *testing.T) {
vaultMock := &mocks.VaultMock{}
stepConfig := StepConfig{Config: map[string]interface{}{}}
stepParams := []StepParameters{stepParam(secretName, "vaultSecret", "$(vaultBasePath)/pipelineA")}
vaultData := map[string]string{secretName: "value1"}
vaultMock.On("GetKvSecret", "/pipelineA").Return(vaultData, nil)
err := addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.NoError(t, err)
assert.Equal(t, "value1", stepConfig.Config[secretName])
addVaultCredentials(&stepConfig, vaultMock, stepParams)
assert.Equal(t, nil, stepConfig.Config[secretName])
vaultMock.AssertNotCalled(t, "GetKvSecret", mock.AnythingOfType("string"))
})
}
func stepParam(name string, refType string, refPaths ...string) StepParameters {

View File

@ -6,6 +6,7 @@ import (
"strconv"
"strings"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/hashicorp/vault/api"
)
@ -52,6 +53,7 @@ func NewClientWithAppRole(config *api.Config, roleID, secretID, namespace string
client.SetNamespace(namespace)
}
log.Entry().Debug("Using approle login")
result, err := client.Logical().Write("auth/approle/login", map[string]interface{}{
"role_id": roleID,
"secret_id": secretID,
@ -62,10 +64,11 @@ func NewClientWithAppRole(config *api.Config, roleID, secretID, namespace string
}
authInfo := result.Auth
if authInfo == nil {
if authInfo == nil || authInfo.ClientToken == "" {
return Client{}, fmt.Errorf("Could not obtain token from approle with role_id %s", roleID)
}
log.Entry().Debugf("Login to vault %s in namespace %s successfull", config.Address, namespace)
return NewClient(config, authInfo.ClientToken, namespace)
}

View File

@ -85,6 +85,11 @@ spec:
- name: checkmarxCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/checkmarx
- $(vaultBasePath)/$(vaultPipelineName)/checkmarx
- $(vaultBasePath)/GROUP-SECRETS/checkmarx
- name: preset
type: string
description: The preset to use for scanning, if not set explicitly the step will attempt to look up the project's setting based on the availability of `checkmarxCredentialsId`
@ -163,6 +168,11 @@ spec:
- name: checkmarxCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/checkmarx
- $(vaultBasePath)/$(vaultPipelineName)/checkmarx
- $(vaultBasePath)/GROUP-SECRETS/checkmarx
- name: verifyOnly
type: bool
description: Whether the step shall only apply verification checks or whether it does a full scan and check cycle

View File

@ -46,6 +46,11 @@ spec:
- name: cfCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: password
type: string
description: Password for Cloud Foundry User
@ -59,6 +64,11 @@ spec:
- name: cfCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: cfOrg
type: string
description: Cloud Foundry org

View File

@ -34,6 +34,11 @@ spec:
- name: cfCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: password
type: string
description: User Password for CF User
@ -47,6 +52,11 @@ spec:
- name: cfCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: cfOrg
type: string
description: CF org

View File

@ -34,6 +34,11 @@ spec:
- name: cfCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: password
type: string
description: User Password for CF User
@ -47,6 +52,11 @@ spec:
- name: cfCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: cfOrg
type: string
description: CF org

View File

@ -297,6 +297,11 @@ spec:
- name: cfCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
- name: smokeTestScript
type: string
description:
@ -347,6 +352,11 @@ spec:
- name: cfCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/$(vaultPipelineName)/cloudfoundry-$(cfOrg)-$(cfSpace)
- $(vaultBasePath)/GROUP-SECRETS/cloudfoundry-$(cfOrg)-$(cfSpace)
containers:
- name: cfDeploy
image: ppiper/cf-cli

View File

@ -34,6 +34,11 @@ spec:
resourceRef:
- name: detectTokenCredentialsId
type: secret
- type: vaultSecret
paths:
- $(vaultPath)/detect
- $(vaultBasePath)/$(vaultPipelineName)/detect
- $(vaultBasePath)/GROUP-SECRETS/detect
scope:
- PARAMETERS
- STAGES

View File

@ -42,6 +42,11 @@ spec:
resourceRef:
- name: fortifyCredentialsId
type: secret
- type: vaultSecret
paths:
- $(vaultPath)/fortify
- $(vaultBasePath)/$(vaultPipelineName)/fortify
- $(vaultBasePath)/GROUP-SECRETS/fortify
- name: githubToken
description: "GitHub personal access token as per
https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line"

View File

@ -41,6 +41,11 @@ spec:
- PARAMETERS
secret: true
resourceRef:
- type: vaultSecret
paths:
- $(vaultPath)/sonar
- $(vaultBasePath)/$(vaultPipelineName)/sonar
- $(vaultBasePath)/GROUP-SECRETS/sonar
- name: sonarTokenCredentialsId
type: secret
aliases:

View File

@ -186,6 +186,11 @@ spec:
- name: gitHttpsCredentialsId
type: secret
param: password
- type: vaultSecret
paths:
- $(vaultPath)/versioning
- $(vaultBasePath)/$(vaultPipelineName)/versioning
- $(vaultBasePath)/GROUP-SECRETS/versioning
- name: projectSettingsFile
aliases:
- name: maven/projectSettingsFile
@ -230,6 +235,11 @@ spec:
- name: gitHttpsCredentialsId
type: secret
param: username
- type: vaultSecret
paths:
- $(vaultPath)/versioning
- $(vaultBasePath)/$(vaultPipelineName)/versioning
- $(vaultBasePath)/GROUP-SECRETS/versioning
- name: versioningTemplate
type: string
description: "DEPRECATED: Defines the template for the automatic version which will be created"

View File

@ -224,7 +224,7 @@ void call(Map parameters = [:]) {
.dependingOn('deployTool').mixin('dockerWorkspace')
.withMandatoryProperty('cloudFoundry/org')
.withMandatoryProperty('cloudFoundry/space')
.withMandatoryProperty('cloudFoundry/credentialsId')
.withMandatoryProperty('cloudFoundry/credentialsId', null, {c -> return !c.containsKey('vaultAppRoleTokenCredentialsId') || !c.containsKey('vaultAppRoleSecretTokenCredentialsId')})
.use()
if (config.useGoStep == true) {

View File

@ -156,6 +156,10 @@ void dockerWrapper(script, stepName, config, body) {
// reused in sonarExecuteScan
void credentialWrapper(config, List credentialInfo, body) {
if (config.containsKey('vaultAppRoleTokenCredentialsId') && config.containsKey('vaultAppRoleSecretTokenCredentialsId')) {
credentialInfo = [[type: 'token', id: 'vaultAppRoleTokenCredentialsId', env: ['PIPER_vaultAppRoleID']],
[type: 'token', id: 'vaultAppRoleSecretTokenCredentialsId', env: ['PIPER_vaultAppRoleSecretID']]]
}
if (credentialInfo.size() > 0) {
def creds = []
def sshCreds = []