mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-11-24 08:32:32 +02:00
Feature/approle secret id rotation (#2311)
* add new step vault secret * add debug log Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
parent
8653780cf6
commit
dfab156427
@ -117,6 +117,7 @@ func Execute() {
|
||||
rootCmd.AddCommand(AbapAddonAssemblyKitReserveNextPackagesCommand())
|
||||
rootCmd.AddCommand(CloudFoundryCreateSpaceCommand())
|
||||
rootCmd.AddCommand(CloudFoundryDeleteSpaceCommand())
|
||||
rootCmd.AddCommand(VaultRotateSecretIdCommand())
|
||||
|
||||
addRootFlags(rootCmd)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
@ -266,14 +267,6 @@ func PrepareConfig(cmd *cobra.Command, metadata *config.StepData, stepName strin
|
||||
GeneralConfig.NoTelemetry = true
|
||||
}
|
||||
|
||||
if !GeneralConfig.Verbose && stepConfig.Config["verbose"] != nil {
|
||||
if verboseValue, ok := stepConfig.Config["verbose"].(bool); ok {
|
||||
log.SetVerbose(verboseValue)
|
||||
} else {
|
||||
return fmt.Errorf("invalid value for parameter verbose: '%v'", stepConfig.Config["verbose"])
|
||||
}
|
||||
}
|
||||
|
||||
stepConfig.Config = checkTypes(stepConfig.Config, options)
|
||||
confJSON, _ := json.Marshal(stepConfig.Config)
|
||||
_ = json.Unmarshal(confJSON, &options)
|
||||
|
109
cmd/vaultRotateSecretId.go
Normal file
109
cmd/vaultRotateSecretId.go
Normal file
@ -0,0 +1,109 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/jenkins"
|
||||
"github.com/SAP/jenkins-library/pkg/vault"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
)
|
||||
|
||||
type vaultRotateSecretIDUtils interface {
|
||||
GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error)
|
||||
GetAppRoleName() (string, error)
|
||||
GenerateNewAppRoleSecret(secretID string, roleName string) (string, error)
|
||||
UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error
|
||||
GetConfig() *vaultRotateSecretIdOptions
|
||||
}
|
||||
|
||||
type vaultRotateSecretIDUtilsBundle struct {
|
||||
*vault.Client
|
||||
config *vaultRotateSecretIdOptions
|
||||
updateFunc func(config *vaultRotateSecretIdOptions, secretID string) error
|
||||
}
|
||||
|
||||
func (v vaultRotateSecretIDUtilsBundle) GetConfig() *vaultRotateSecretIdOptions {
|
||||
return v.config
|
||||
}
|
||||
|
||||
func (v vaultRotateSecretIDUtilsBundle) UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error {
|
||||
return v.updateFunc(config, secretID)
|
||||
}
|
||||
|
||||
func vaultRotateSecretId(config vaultRotateSecretIdOptions, telemetryData *telemetry.CustomData) {
|
||||
|
||||
client, err := vault.NewClientWithAppRole(&vault.Config{
|
||||
Config: &api.Config{
|
||||
Address: config.VaultServerURL,
|
||||
},
|
||||
Namespace: config.VaultNamespace,
|
||||
}, GeneralConfig.VaultRoleID, GeneralConfig.VaultRoleSecretID)
|
||||
|
||||
utils := vaultRotateSecretIDUtilsBundle{
|
||||
Client: &client,
|
||||
config: &config,
|
||||
updateFunc: writeVaultSecretIDToStore,
|
||||
}
|
||||
|
||||
err = runVaultRotateSecretID(utils)
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Fatal("step execution failed")
|
||||
}
|
||||
}
|
||||
|
||||
func runVaultRotateSecretID(utils vaultRotateSecretIDUtils) error {
|
||||
config := utils.GetConfig()
|
||||
|
||||
roleName, err := utils.GetAppRoleName()
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warn("Could not fetch approle role name from vault. Secret ID rotation failed!")
|
||||
return nil
|
||||
}
|
||||
|
||||
ttl, err := utils.GetAppRoleSecretIDTtl(GeneralConfig.VaultRoleSecretID, roleName)
|
||||
|
||||
if err != nil {
|
||||
log.Entry().WithError(err).Warn("Could not fetch secret ID TTL. Secret ID rotation failed!")
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Your secret ID is about to expire in %.0f", ttl.Round(time.Hour*24).Hours()/24)
|
||||
|
||||
if ttl > time.Duration(config.DaysBeforeExpiry)*24*time.Hour {
|
||||
return nil
|
||||
}
|
||||
|
||||
newSecretID, err := utils.GenerateNewAppRoleSecret(GeneralConfig.VaultRoleSecretID, roleName)
|
||||
|
||||
if err != nil || newSecretID == "" {
|
||||
log.Entry().WithError(err).Warn("Generating a new secret ID failed. Secret ID rotation faield!")
|
||||
return nil
|
||||
}
|
||||
|
||||
if err = utils.UpdateSecretInStore(config, newSecretID); err != nil {
|
||||
log.Entry().WithError(err).Warnf("Could not write secret back to secret store %s", config.SecretStore)
|
||||
}
|
||||
log.Entry().Infof("Secret has been successfully updated in secret store %s", config.SecretStore)
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func writeVaultSecretIDToStore(config *vaultRotateSecretIdOptions, secretID string) error {
|
||||
switch config.SecretStore {
|
||||
case "jenkins":
|
||||
instance, err := jenkins.Instance(&http.Client{}, config.JenkinsURL, config.JenkinsUsername, config.JenkinsToken)
|
||||
if err != nil {
|
||||
log.Entry().Warn("Could not write secret ID back to jenkins")
|
||||
return err
|
||||
}
|
||||
credManager := jenkins.NewCredentialsManager(instance)
|
||||
credential := jenkins.StringCredentials{ID: config.VaultAppRoleSecretTokenCredentialsID, Secret: secretID}
|
||||
return jenkins.UpdateCredential(credManager, config.JenkinsCredentialDomain, credential)
|
||||
}
|
||||
return nil
|
||||
}
|
207
cmd/vaultRotateSecretId_generated.go
Normal file
207
cmd/vaultRotateSecretId_generated.go
Normal file
@ -0,0 +1,207 @@
|
||||
// Code generated by piper's step-generator. DO NOT EDIT.
|
||||
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/config"
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/SAP/jenkins-library/pkg/telemetry"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type vaultRotateSecretIdOptions struct {
|
||||
SecretStore string `json:"secretStore,omitempty"`
|
||||
JenkinsURL string `json:"jenkinsUrl,omitempty"`
|
||||
JenkinsCredentialDomain string `json:"jenkinsCredentialDomain,omitempty"`
|
||||
JenkinsUsername string `json:"jenkinsUsername,omitempty"`
|
||||
JenkinsToken string `json:"jenkinsToken,omitempty"`
|
||||
VaultAppRoleSecretTokenCredentialsID string `json:"vaultAppRoleSecretTokenCredentialsId,omitempty"`
|
||||
VaultServerURL string `json:"vaultServerUrl,omitempty"`
|
||||
VaultNamespace string `json:"vaultNamespace,omitempty"`
|
||||
DaysBeforeExpiry int `json:"daysBeforeExpiry,omitempty"`
|
||||
}
|
||||
|
||||
// VaultRotateSecretIdCommand Rotate vault AppRole Secret ID
|
||||
func VaultRotateSecretIdCommand() *cobra.Command {
|
||||
const STEP_NAME = "vaultRotateSecretId"
|
||||
|
||||
metadata := vaultRotateSecretIdMetadata()
|
||||
var stepConfig vaultRotateSecretIdOptions
|
||||
var startTime time.Time
|
||||
|
||||
var createVaultRotateSecretIdCmd = &cobra.Command{
|
||||
Use: STEP_NAME,
|
||||
Short: "Rotate vault AppRole Secret ID",
|
||||
Long: `This step takes the given Vault secret ID and checks whether it needs to be renewed and if so it will update the secret ID in the configured secret store.`,
|
||||
PreRunE: func(cmd *cobra.Command, _ []string) error {
|
||||
startTime = time.Now()
|
||||
log.SetStepName(STEP_NAME)
|
||||
log.SetVerbose(GeneralConfig.Verbose)
|
||||
|
||||
path, _ := os.Getwd()
|
||||
fatalHook := &log.FatalHook{CorrelationID: GeneralConfig.CorrelationID, Path: path}
|
||||
log.RegisterHook(fatalHook)
|
||||
|
||||
err := PrepareConfig(cmd, &metadata, STEP_NAME, &stepConfig, config.OpenPiperFile)
|
||||
if err != nil {
|
||||
log.SetErrorCategory(log.ErrorConfiguration)
|
||||
return err
|
||||
}
|
||||
log.RegisterSecret(stepConfig.JenkinsURL)
|
||||
log.RegisterSecret(stepConfig.JenkinsUsername)
|
||||
log.RegisterSecret(stepConfig.JenkinsToken)
|
||||
|
||||
if len(GeneralConfig.HookConfig.SentryConfig.Dsn) > 0 {
|
||||
sentryHook := log.NewSentryHook(GeneralConfig.HookConfig.SentryConfig.Dsn, GeneralConfig.CorrelationID)
|
||||
log.RegisterHook(&sentryHook)
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
Run: func(_ *cobra.Command, _ []string) {
|
||||
telemetryData := telemetry.CustomData{}
|
||||
telemetryData.ErrorCode = "1"
|
||||
handler := func() {
|
||||
config.RemoveVaultSecretFiles()
|
||||
telemetryData.Duration = fmt.Sprintf("%v", time.Since(startTime).Milliseconds())
|
||||
telemetryData.ErrorCategory = log.GetErrorCategory().String()
|
||||
telemetry.Send(&telemetryData)
|
||||
}
|
||||
log.DeferExitHandler(handler)
|
||||
defer handler()
|
||||
telemetry.Initialize(GeneralConfig.NoTelemetry, STEP_NAME)
|
||||
vaultRotateSecretId(stepConfig, &telemetryData)
|
||||
telemetryData.ErrorCode = "0"
|
||||
log.Entry().Info("SUCCESS")
|
||||
},
|
||||
}
|
||||
|
||||
addVaultRotateSecretIdFlags(createVaultRotateSecretIdCmd, &stepConfig)
|
||||
return createVaultRotateSecretIdCmd
|
||||
}
|
||||
|
||||
func addVaultRotateSecretIdFlags(cmd *cobra.Command, stepConfig *vaultRotateSecretIdOptions) {
|
||||
cmd.Flags().StringVar(&stepConfig.SecretStore, "secretStore", `jenkins`, "The store to which the secret should be written back to")
|
||||
cmd.Flags().StringVar(&stepConfig.JenkinsURL, "jenkinsUrl", os.Getenv("PIPER_jenkinsUrl"), "The jenkins url")
|
||||
cmd.Flags().StringVar(&stepConfig.JenkinsCredentialDomain, "jenkinsCredentialDomain", `_`, "The jenkins credential domain which should be used")
|
||||
cmd.Flags().StringVar(&stepConfig.JenkinsUsername, "jenkinsUsername", os.Getenv("PIPER_jenkinsUsername"), "The jenkins username")
|
||||
cmd.Flags().StringVar(&stepConfig.JenkinsToken, "jenkinsToken", os.Getenv("PIPER_jenkinsToken"), "The jenkins token")
|
||||
cmd.Flags().StringVar(&stepConfig.VaultAppRoleSecretTokenCredentialsID, "vaultAppRoleSecretTokenCredentialsId", os.Getenv("PIPER_vaultAppRoleSecretTokenCredentialsId"), "The Jenkins credential ID for the Vault AppRole Secret ID credential")
|
||||
cmd.Flags().StringVar(&stepConfig.VaultServerURL, "vaultServerUrl", os.Getenv("PIPER_vaultServerUrl"), "The URL for the Vault server to use")
|
||||
cmd.Flags().StringVar(&stepConfig.VaultNamespace, "vaultNamespace", os.Getenv("PIPER_vaultNamespace"), "The vault namespace that should be used (optional)")
|
||||
cmd.Flags().IntVar(&stepConfig.DaysBeforeExpiry, "daysBeforeExpiry", 15, "The amount of days before expiry until the secret ID gets rotated")
|
||||
|
||||
cmd.MarkFlagRequired("vaultAppRoleSecretTokenCredentialsId")
|
||||
cmd.MarkFlagRequired("vaultServerUrl")
|
||||
}
|
||||
|
||||
// retrieve step metadata
|
||||
func vaultRotateSecretIdMetadata() config.StepData {
|
||||
var theMetaData = config.StepData{
|
||||
Metadata: config.StepMetadata{
|
||||
Name: "vaultRotateSecretId",
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
Spec: config.StepSpec{
|
||||
Inputs: config.StepInputs{
|
||||
Parameters: []config.StepParameters{
|
||||
{
|
||||
Name: "secretStore",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "jenkinsUrl",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "",
|
||||
Paths: []string{"$(vaultPath)/jenkins", "$(vaultBasePath)/$(vaultPipelineName)/jenkins", "$(vaultBasePath)/GROUP-SECRETS/jenkins"},
|
||||
Type: "vaultSecret",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "url"}},
|
||||
},
|
||||
{
|
||||
Name: "jenkinsCredentialDomain",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "jenkinsUsername",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "",
|
||||
Paths: []string{"$(vaultPath)/jenkins", "$(vaultBasePath)/$(vaultPipelineName)/jenkins", "$(vaultBasePath)/GROUP-SECRETS/jenkins"},
|
||||
Type: "vaultSecret",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "userId"}},
|
||||
},
|
||||
{
|
||||
Name: "jenkinsToken",
|
||||
ResourceRef: []config.ResourceReference{
|
||||
{
|
||||
Name: "",
|
||||
Paths: []string{"$(vaultPath)/jenkins", "$(vaultBasePath)/$(vaultPipelineName)/jenkins", "$(vaultBasePath)/GROUP-SECRETS/jenkins"},
|
||||
Type: "vaultSecret",
|
||||
},
|
||||
},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{{Name: "token"}},
|
||||
},
|
||||
{
|
||||
Name: "vaultAppRoleSecretTokenCredentialsId",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "vaultServerUrl",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: true,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "vaultNamespace",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"GENERAL", "PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "string",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
{
|
||||
Name: "daysBeforeExpiry",
|
||||
ResourceRef: []config.ResourceReference{},
|
||||
Scope: []string{"PARAMETERS", "STAGES", "STEPS"},
|
||||
Type: "int",
|
||||
Mandatory: false,
|
||||
Aliases: []config.Alias{},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
return theMetaData
|
||||
}
|
16
cmd/vaultRotateSecretId_generated_test.go
Normal file
16
cmd/vaultRotateSecretId_generated_test.go
Normal file
@ -0,0 +1,16 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestVaultRotateSecretIdCommand(t *testing.T) {
|
||||
|
||||
testCmd := VaultRotateSecretIdCommand()
|
||||
|
||||
// only high level testing performed - details are tested in step generation procedure
|
||||
assert.Equal(t, "vaultRotateSecretId", testCmd.Use, "command name incorrect")
|
||||
|
||||
}
|
49
cmd/vaultRotateSecretId_test.go
Normal file
49
cmd/vaultRotateSecretId_test.go
Normal file
@ -0,0 +1,49 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
type mockVaultRotateSecretIDUtilsBundle struct {
|
||||
t *testing.T
|
||||
newSecret string
|
||||
ttl time.Duration
|
||||
config *vaultRotateSecretIdOptions
|
||||
updateFuncCalled bool
|
||||
}
|
||||
|
||||
func TestRunVaultRotateSecretId(t *testing.T) {
|
||||
t.Parallel()
|
||||
mock := &mockVaultRotateSecretIDUtilsBundle{t, "test-secret", time.Hour, getTestConfig(), false}
|
||||
runVaultRotateSecretID(mock)
|
||||
assert.True(t, mock.updateFuncCalled)
|
||||
|
||||
}
|
||||
|
||||
func (v *mockVaultRotateSecretIDUtilsBundle) GenerateNewAppRoleSecret(secretID string, roleName string) (string, error) {
|
||||
return v.newSecret, nil
|
||||
}
|
||||
|
||||
func (v *mockVaultRotateSecretIDUtilsBundle) GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error) {
|
||||
return v.ttl, nil
|
||||
}
|
||||
func (v *mockVaultRotateSecretIDUtilsBundle) GetAppRoleName() (string, error) {
|
||||
return "test", nil
|
||||
}
|
||||
func (v *mockVaultRotateSecretIDUtilsBundle) UpdateSecretInStore(config *vaultRotateSecretIdOptions, secretID string) error {
|
||||
v.updateFuncCalled = true
|
||||
assert.Equal(v.t, v.newSecret, secretID)
|
||||
return nil
|
||||
}
|
||||
func (v *mockVaultRotateSecretIDUtilsBundle) GetConfig() *vaultRotateSecretIdOptions {
|
||||
return v.config
|
||||
}
|
||||
|
||||
func getTestConfig() *vaultRotateSecretIdOptions {
|
||||
return &vaultRotateSecretIdOptions{
|
||||
DaysBeforeExpiry: 5,
|
||||
}
|
||||
}
|
17
documentation/docs/steps/vaultRotateSecretId.md
Normal file
17
documentation/docs/steps/vaultRotateSecretId.md
Normal file
@ -0,0 +1,17 @@
|
||||
# ${docGenStepName}
|
||||
|
||||
## ${docGenDescription}
|
||||
|
||||
## Prerequisites
|
||||
|
||||
## ${docGenParameters}
|
||||
|
||||
## ${docGenConfiguration}
|
||||
|
||||
## ${docJenkinsPluginDependencies}
|
||||
|
||||
## Exceptions
|
||||
|
||||
none
|
||||
|
||||
## Examples
|
@ -139,6 +139,7 @@ nav:
|
||||
- transportRequestRelease: steps/transportRequestRelease.md
|
||||
- transportRequestUploadFile: steps/transportRequestUploadFile.md
|
||||
- uiVeri5ExecuteTests: steps/uiVeri5ExecuteTests.md
|
||||
- vaultRotateSecretId: steps/vaultRotateSecretId.md
|
||||
- whitesourceExecuteScan: steps/whitesourceExecuteScan.md
|
||||
- writeTemporaryCredentials: steps/writeTemporaryCredentials.md
|
||||
- xsDeploy: steps/xsDeploy.md
|
||||
|
@ -5,6 +5,7 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
@ -42,7 +43,7 @@ func TestGetVaultSecret(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
port, err := vaultContainer.MappedPort(ctx, "8200")
|
||||
host := fmt.Sprintf("http://%s:%s", ip, port.Port())
|
||||
config := &api.Config{Address: host}
|
||||
config := &vault.Config{Config: &api.Config{Address: host}}
|
||||
// setup vault for testing
|
||||
secretData := SecretData{
|
||||
"key1": "value1",
|
||||
@ -50,7 +51,7 @@ func TestGetVaultSecret(t *testing.T) {
|
||||
}
|
||||
setupVault(t, config, testToken, secretData)
|
||||
|
||||
client, err := vault.NewClient(config, testToken, "")
|
||||
client, err := vault.NewClient(config, testToken)
|
||||
assert.NoError(t, err)
|
||||
secret, err := client.GetKvSecret("secret/test")
|
||||
assert.NoError(t, err)
|
||||
@ -64,10 +65,12 @@ func TestGetVaultSecret(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestVaultAppRoleLogin(t *testing.T) {
|
||||
func TestVaultAppRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
const testToken = "vault-token"
|
||||
const appRolePath = "auth/approle/role/test"
|
||||
const appRoleName = "test"
|
||||
|
||||
req := testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
@ -88,31 +91,88 @@ func TestVaultAppRoleLogin(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
port, err := vaultContainer.MappedPort(ctx, "8200")
|
||||
host := fmt.Sprintf("http://%s:%s", ip, port.Port())
|
||||
config := &api.Config{Address: host}
|
||||
config := &vault.Config{Config: &api.Config{Address: host}}
|
||||
|
||||
roleID, secretID := setupVaultAppRole(t, config, testToken)
|
||||
client, err := vault.NewClientWithAppRole(config, roleID, secretID, "")
|
||||
assert.NoError(t, err)
|
||||
_, err = client.GetSecret("auth/token/lookup-self")
|
||||
assert.NoError(t, err)
|
||||
secretIDMetadata := map[string]interface{}{
|
||||
"field1": "value1",
|
||||
}
|
||||
|
||||
roleID, secretID := setupVaultAppRole(t, config, testToken, appRolePath, secretIDMetadata)
|
||||
|
||||
t.Run("Test Vault AppRole login", func(t *testing.T) {
|
||||
client, err := vault.NewClientWithAppRole(config, roleID, secretID)
|
||||
assert.NoError(t, err)
|
||||
secret, err := client.GetSecret("auth/token/lookup-self")
|
||||
meta := secret.Data["meta"].(SecretData)
|
||||
assert.Equal(t, meta["field1"], "value1")
|
||||
assert.Equal(t, meta["role_name"], "test")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test Vault AppRoleTTL Fetch", func(t *testing.T) {
|
||||
client, err := vault.NewClient(config, testToken)
|
||||
assert.NoError(t, err)
|
||||
ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, time.Duration(90*24*time.Hour), ttl.Round(time.Hour))
|
||||
})
|
||||
|
||||
t.Run("Test Vault AppRole Rotation", func(t *testing.T) {
|
||||
client, err := vault.NewClient(config, testToken)
|
||||
assert.NoError(t, err)
|
||||
newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
assert.NotEmpty(t, newSecretID)
|
||||
assert.NotEqual(t, secretID, newSecretID)
|
||||
|
||||
// verify metadata is not broken
|
||||
client, err = vault.NewClientWithAppRole(config, roleID, newSecretID)
|
||||
assert.NoError(t, err)
|
||||
secret, err := client.GetSecret("auth/token/lookup-self")
|
||||
meta := secret.Data["meta"].(SecretData)
|
||||
assert.Equal(t, meta["field1"], "value1")
|
||||
assert.Equal(t, meta["role_name"], "test")
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test Fetching RoleName from vault", func(t *testing.T) {
|
||||
client, err := vault.NewClientWithAppRole(config, roleID, secretID)
|
||||
assert.NoError(t, err)
|
||||
fetchedRoleName, err := client.GetAppRoleName()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, appRoleName, fetchedRoleName)
|
||||
})
|
||||
}
|
||||
|
||||
func setupVaultAppRole(t *testing.T, config *api.Config, token string) (string, string) {
|
||||
func setupVaultAppRole(t *testing.T, config *vault.Config, token, appRolePath string, metadata map[string]interface{}) (string, string) {
|
||||
t.Helper()
|
||||
client, err := api.NewClient(config)
|
||||
client, err := api.NewClient(config.Config)
|
||||
assert.NoError(t, err)
|
||||
client.SetToken(token)
|
||||
lClient := client.Logical()
|
||||
|
||||
_, err = lClient.Write("sys/auth/approle", SecretData{
|
||||
"type": "approle",
|
||||
"config": map[string]interface{}{
|
||||
"default_lease_ttl": "7776000s",
|
||||
"max_lease_ttl": "7776000s",
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
_, err = lClient.Write("auth/approle/role/test", SecretData{})
|
||||
_, err = lClient.Write("auth/approle/role/test", SecretData{
|
||||
"secret_id_ttl": 7776000,
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := lClient.Write("auth/approle/role/test/secret-id", SecretData{})
|
||||
metadataJson, err := json.Marshal(metadata)
|
||||
assert.NoError(t, err)
|
||||
|
||||
res, err := lClient.Write("auth/approle/role/test/secret-id", SecretData{
|
||||
"metadata": string(metadataJson),
|
||||
})
|
||||
|
||||
assert.NoError(t, err)
|
||||
secretID := res.Data["secret_id"]
|
||||
|
||||
@ -123,9 +183,9 @@ func setupVaultAppRole(t *testing.T, config *api.Config, token string) (string,
|
||||
return roleID.(string), secretID.(string)
|
||||
}
|
||||
|
||||
func setupVault(t *testing.T, config *api.Config, token string, secret SecretData) {
|
||||
func setupVault(t *testing.T, config *vault.Config, token string, secret SecretData) {
|
||||
t.Helper()
|
||||
client, err := api.NewClient(config)
|
||||
client, err := api.NewClient(config.Config)
|
||||
assert.NoError(t, err)
|
||||
client.SetToken(token)
|
||||
|
||||
|
@ -227,6 +227,12 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
stepConfig.mixIn(flagValues, filters.Parameters)
|
||||
}
|
||||
|
||||
if verbose, ok := stepConfig.Config["verbose"].(bool); ok && verbose {
|
||||
log.SetVerbose(verbose)
|
||||
} else if !ok {
|
||||
log.Entry().Warnf("invalid value for parameter verbose: '%v'", stepConfig.Config["verbose"])
|
||||
}
|
||||
|
||||
stepConfig.mixIn(c.General, vaultFilter)
|
||||
// fetch secrets from vault
|
||||
vaultClient, err := getVaultClientFromConfig(stepConfig, c.vaultCredentials)
|
||||
@ -243,9 +249,9 @@ func (c *Config) GetStepConfig(flagValues map[string]interface{}, paramJSON stri
|
||||
cp := p.Conditions[0].Params[0]
|
||||
dependentValue := stepConfig.Config[cp.Name]
|
||||
if cmp.Equal(dependentValue, cp.Value) && stepConfig.Config[p.Name] == nil {
|
||||
subMapValue := stepConfig.Config[dependentValue.(string)].(map[string]interface{})[p.Name]
|
||||
if subMapValue != nil {
|
||||
stepConfig.Config[p.Name] = subMapValue
|
||||
subMap, ok := stepConfig.Config[dependentValue.(string)].(map[string]interface{})
|
||||
if ok && subMap[p.Name] != nil {
|
||||
stepConfig.Config[p.Name] = subMap[p.Name]
|
||||
} else {
|
||||
stepConfig.Config[p.Name] = p.Default
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func getVaultClientFromConfig(config StepConfig, creds VaultCredentials) (vaultC
|
||||
log.Entry().Debugf("Using vault namespace %s", namespace)
|
||||
}
|
||||
|
||||
client, err := vault.NewClientWithAppRole(&api.Config{Address: address}, creds.AppRoleID, creds.AppRoleSecretID, namespace)
|
||||
client, err := vault.NewClientWithAppRole(&vault.Config{Config: &api.Config{Address: address}, Namespace: namespace}, creds.AppRoleID, creds.AppRoleSecretID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -148,9 +148,10 @@ func lookupPath(client vaultClient, path string, param *StepParameters) *string
|
||||
log.RegisterSecret(field)
|
||||
return &field
|
||||
}
|
||||
|
||||
log.Entry().Debugf("Secret did not contain a field name '%s'", param.Name)
|
||||
// try parameter aliases
|
||||
for _, alias := range param.Aliases {
|
||||
log.Entry().Debugf("Trying alias field name '%s'", alias.Name)
|
||||
field := secret[alias.Name]
|
||||
if field != "" {
|
||||
log.RegisterSecret(field)
|
||||
|
51
pkg/jenkins/credential.go
Normal file
51
pkg/jenkins/credential.go
Normal file
@ -0,0 +1,51 @@
|
||||
package jenkins
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/bndr/gojenkins"
|
||||
)
|
||||
|
||||
// StringCredentials store only secret text
|
||||
type StringCredentials = gojenkins.StringCredentials
|
||||
|
||||
//UsernameCredentials struct representing credential for storing username-password pair
|
||||
type UsernameCredentials = gojenkins.UsernameCredentials
|
||||
|
||||
// SSHCredentials store credentials for ssh keys.
|
||||
type SSHCredentials = gojenkins.SSHCredentials
|
||||
|
||||
// DockerServerCredentials store credentials for docker keys.
|
||||
type DockerServerCredentials = gojenkins.DockerServerCredentials
|
||||
|
||||
// CredentialsManager is utility to control credential plugin
|
||||
type CredentialsManager interface {
|
||||
Update(string, string, interface{}) error
|
||||
}
|
||||
|
||||
// NewCredentialsManager returns a new CredentialManager
|
||||
func NewCredentialsManager(jenkins *gojenkins.Jenkins) CredentialsManager {
|
||||
return gojenkins.CredentialsManager{J: jenkins}
|
||||
}
|
||||
|
||||
// UpdateCredential overwrites an existing credential
|
||||
func UpdateCredential(credentialsManager CredentialsManager, domain string, credential interface{}) error {
|
||||
credValue := reflect.ValueOf(credential)
|
||||
if credValue.Kind() != reflect.Struct {
|
||||
return fmt.Errorf("'credential' parameter is supposed to be a Credentials struct not '%s'", credValue.Type())
|
||||
}
|
||||
|
||||
idField := credValue.FieldByName("ID")
|
||||
if !idField.IsValid() || idField.Kind() != reflect.String {
|
||||
return fmt.Errorf("'credential' parameter is supposed to be a Credentials struct not '%s'", credValue.Type())
|
||||
}
|
||||
|
||||
secretID := idField.String()
|
||||
if secretID == "" {
|
||||
return errors.New("Secret ID should not be empty")
|
||||
}
|
||||
|
||||
return credentialsManager.Update(domain, secretID, credential)
|
||||
}
|
59
pkg/jenkins/credential_test.go
Normal file
59
pkg/jenkins/credential_test.go
Normal file
@ -0,0 +1,59 @@
|
||||
package jenkins
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/jenkins/mocks"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
func TestUpdateCredential(t *testing.T) {
|
||||
t.Parallel()
|
||||
const ID = "testID"
|
||||
const testSecret = "testSecret"
|
||||
const domain = "_"
|
||||
|
||||
t.Run("That secret is updated", func(t *testing.T) {
|
||||
credManagerMock := mocks.CredentialsManager{}
|
||||
testCredential := StringCredentials{ID: ID, Secret: testSecret}
|
||||
|
||||
credManagerMock.On("Update", domain, ID, mock.Anything).Return(nil)
|
||||
err := UpdateCredential(&credManagerMock, domain, testCredential)
|
||||
credManagerMock.AssertCalled(t, "Update", domain, ID, testCredential)
|
||||
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test that wrong credential type fails ", func(t *testing.T) {
|
||||
credManagerMock := mocks.CredentialsManager{}
|
||||
|
||||
credManagerMock.On("Update", domain, ID, mock.Anything).Return(nil)
|
||||
err := UpdateCredential(&credManagerMock, domain, 5)
|
||||
credManagerMock.AssertNotCalled(t, "Update", domain, ID, mock.Anything)
|
||||
assert.EqualError(t, err, "'credential' parameter is supposed to be a Credentials struct not 'int'")
|
||||
})
|
||||
|
||||
t.Run("Test that wrong credential type fails ", func(t *testing.T) {
|
||||
credManagerMock := mocks.CredentialsManager{}
|
||||
testCredential := struct{ Secret string }{
|
||||
Secret: "Test",
|
||||
}
|
||||
|
||||
credManagerMock.On("Update", domain, ID, mock.Anything).Return(nil)
|
||||
err := UpdateCredential(&credManagerMock, domain, testCredential)
|
||||
credManagerMock.AssertNotCalled(t, "Update", domain, ID, mock.Anything)
|
||||
assert.EqualError(t, err, "'credential' parameter is supposed to be a Credentials struct not 'struct { Secret string }'")
|
||||
})
|
||||
|
||||
t.Run("Test that empty secret id fails ", func(t *testing.T) {
|
||||
credManagerMock := mocks.CredentialsManager{}
|
||||
testCredential := StringCredentials{ID: "", Secret: testSecret}
|
||||
|
||||
credManagerMock.On("Update", domain, ID, mock.Anything).Return(nil)
|
||||
err := UpdateCredential(&credManagerMock, domain, testCredential)
|
||||
credManagerMock.AssertNotCalled(t, "Update", domain, ID, mock.Anything)
|
||||
assert.EqualError(t, err, "Secret ID should not be empty")
|
||||
})
|
||||
|
||||
}
|
24
pkg/jenkins/mocks/CredentialsManager.go
Normal file
24
pkg/jenkins/mocks/CredentialsManager.go
Normal file
@ -0,0 +1,24 @@
|
||||
// Code generated by mockery v2.3.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import mock "github.com/stretchr/testify/mock"
|
||||
|
||||
// CredentialsManager is an autogenerated mock type for the CredentialsManager type
|
||||
type CredentialsManager struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Update provides a mock function with given fields: _a0, _a1, _a2
|
||||
func (_m *CredentialsManager) Update(_a0 string, _a1 string, _a2 interface{}) error {
|
||||
ret := _m.Called(_a0, _a1, _a2)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, string, interface{}) error); ok {
|
||||
r0 = rf(_a0, _a1, _a2)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
@ -1,10 +1,12 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/SAP/jenkins-library/pkg/log"
|
||||
"github.com/hashicorp/vault/api"
|
||||
@ -13,48 +15,61 @@ import (
|
||||
// Client handles communication with Vault
|
||||
type Client struct {
|
||||
lClient logicalClient
|
||||
config *Config
|
||||
}
|
||||
|
||||
// Config contains the vault client configuration
|
||||
type Config struct {
|
||||
*api.Config
|
||||
AppRoleMountPoint string
|
||||
Namespace string
|
||||
}
|
||||
|
||||
// logicalClient interface for mocking
|
||||
type logicalClient interface {
|
||||
Read(string) (*api.Secret, error)
|
||||
Write(string, map[string]interface{}) (*api.Secret, error)
|
||||
}
|
||||
|
||||
// NewClient instantiates a Client and sets the specified token
|
||||
func NewClient(config *api.Config, token, namespace string) (Client, error) {
|
||||
func NewClient(config *Config, token string) (Client, error) {
|
||||
if config == nil {
|
||||
config = api.DefaultConfig()
|
||||
config = &Config{Config: api.DefaultConfig()}
|
||||
}
|
||||
client, err := api.NewClient(config)
|
||||
client, err := api.NewClient(config.Config)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
client.SetNamespace(namespace)
|
||||
if config.Namespace != "" {
|
||||
client.SetNamespace(config.Namespace)
|
||||
}
|
||||
|
||||
client.SetToken(token)
|
||||
return Client{client.Logical()}, nil
|
||||
return Client{client.Logical(), config}, nil
|
||||
}
|
||||
|
||||
// NewClientWithAppRole instantiates a new client and obtains a token via the AppRole auth method
|
||||
func NewClientWithAppRole(config *api.Config, roleID, secretID, namespace string) (Client, error) {
|
||||
func NewClientWithAppRole(config *Config, roleID, secretID string) (Client, error) {
|
||||
if config == nil {
|
||||
config = api.DefaultConfig()
|
||||
config = &Config{Config: api.DefaultConfig()}
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config)
|
||||
if config.AppRoleMountPoint == "" {
|
||||
config.AppRoleMountPoint = "auth/approle"
|
||||
}
|
||||
|
||||
client, err := api.NewClient(config.Config)
|
||||
if err != nil {
|
||||
return Client{}, err
|
||||
}
|
||||
|
||||
if namespace != "" {
|
||||
client.SetNamespace(namespace)
|
||||
if config.Namespace != "" {
|
||||
client.SetNamespace(config.Namespace)
|
||||
}
|
||||
|
||||
log.Entry().Debug("Using approle login")
|
||||
result, err := client.Logical().Write("auth/approle/login", map[string]interface{}{
|
||||
result, err := client.Logical().Write(path.Join(config.AppRoleMountPoint, "/login"), map[string]interface{}{
|
||||
"role_id": roleID,
|
||||
"secret_id": secretID,
|
||||
})
|
||||
@ -68,8 +83,8 @@ func NewClientWithAppRole(config *api.Config, roleID, secretID, namespace string
|
||||
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)
|
||||
log.Entry().Debugf("Login to vault %s in namespace %s successfull", config.Address, config.Namespace)
|
||||
return NewClient(config, authInfo.ClientToken)
|
||||
}
|
||||
|
||||
// GetSecret uses the given path to fetch a secret from vault
|
||||
@ -124,14 +139,145 @@ func (v Client) GetKvSecret(path string) (map[string]string, error) {
|
||||
secretData := make(map[string]string, len(data))
|
||||
for k, v := range data {
|
||||
valueStr, ok := v.(string)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Expected secret value to be a string but got %T instead", v)
|
||||
if ok {
|
||||
secretData[k] = valueStr
|
||||
}
|
||||
secretData[k] = valueStr
|
||||
}
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
// GenerateNewAppRoleSecret creates a new secret-id
|
||||
func (v *Client) GenerateNewAppRoleSecret(secretID, appRoleName string) (string, error) {
|
||||
appRolePath := v.getAppRolePath(appRoleName)
|
||||
secretIDData, err := v.lookupSecretID(secretID, appRolePath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
reqPath := sanitizePath(path.Join(appRolePath, "/secret-id"))
|
||||
|
||||
// we preserve metadata which was attached to the secret-id
|
||||
json, err := json.Marshal(secretIDData["metadata"])
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
secret, err := v.lClient.Write(reqPath, map[string]interface{}{
|
||||
"metadata": string(json),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if secret == nil || secret.Data == nil {
|
||||
return "", fmt.Errorf("Could not generate new approle secret-id for approle path %s", reqPath)
|
||||
}
|
||||
|
||||
secretIDRaw, ok := secret.Data["secret_id"]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Vault response for path %s did not contain a new secret-id", reqPath)
|
||||
}
|
||||
|
||||
newSecretID, ok := secretIDRaw.(string)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("New secret-id from approle path %s has an unexpected type %T expected 'string'", reqPath, secretIDRaw)
|
||||
}
|
||||
|
||||
return newSecretID, nil
|
||||
}
|
||||
|
||||
// GetAppRoleSecretIDTtl returns the remaining time until the given secret-id expires
|
||||
func (v *Client) GetAppRoleSecretIDTtl(secretID, roleName string) (time.Duration, error) {
|
||||
appRolePath := v.getAppRolePath(roleName)
|
||||
data, err := v.lookupSecretID(secretID, appRolePath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if data == nil || data["expiration_time"] == nil {
|
||||
return 0, fmt.Errorf("Could not load secret-id information from path %s", appRolePath)
|
||||
}
|
||||
|
||||
expiration, ok := data["expiration_time"].(string)
|
||||
if !ok || expiration == "" {
|
||||
return 0, fmt.Errorf("Could not handle get expiration time for secret-id from path %s", appRolePath)
|
||||
}
|
||||
|
||||
expirationDate, err := time.Parse(time.RFC3339, expiration)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
ttl := expirationDate.Sub(time.Now())
|
||||
if ttl < 0 {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return ttl, nil
|
||||
}
|
||||
|
||||
// GetAppRoleName returns the AppRole role name which was used to authenticate.
|
||||
// Returns "" when AppRole authentication wasn't used
|
||||
func (v *Client) GetAppRoleName() (string, error) {
|
||||
const lookupPath = "auth/token/lookup-self"
|
||||
secret, err := v.GetSecret(lookupPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if secret.Data == nil {
|
||||
return "", fmt.Errorf("Could not lookup token information: %s", lookupPath)
|
||||
}
|
||||
|
||||
meta, ok := secret.Data["meta"]
|
||||
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Token info did not contain metadata %s", lookupPath)
|
||||
}
|
||||
|
||||
metaMap, ok := meta.(map[string]interface{})
|
||||
|
||||
if !ok {
|
||||
return "", fmt.Errorf("Token info field 'meta' is not a map: %s", lookupPath)
|
||||
}
|
||||
|
||||
roleName := metaMap["role_name"]
|
||||
|
||||
if roleName == nil {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
roleNameStr, ok := roleName.(string)
|
||||
if !ok {
|
||||
// when approle authentication is not used vault admins can use the role_name field with other type
|
||||
// so no error in this case
|
||||
return "", nil
|
||||
}
|
||||
|
||||
return roleNameStr, nil
|
||||
}
|
||||
|
||||
// SetAppRoleMountPoint sets the path under which the approle auth backend is mounted
|
||||
func (v *Client) SetAppRoleMountPoint(appRoleMountpoint string) {
|
||||
v.config.AppRoleMountPoint = appRoleMountpoint
|
||||
}
|
||||
|
||||
func (v *Client) getAppRolePath(roleName string) string {
|
||||
appRoleMountPoint := v.config.AppRoleMountPoint
|
||||
if appRoleMountPoint == "" {
|
||||
appRoleMountPoint = "auth/approle"
|
||||
}
|
||||
return path.Join(appRoleMountPoint, "role", roleName)
|
||||
}
|
||||
|
||||
func sanitizePath(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
return path
|
||||
}
|
||||
|
||||
func addPrefixToKvPath(p, mountPath, apiPrefix string) string {
|
||||
switch {
|
||||
case p == mountPath, p == strings.TrimSuffix(mountPath, "/"):
|
||||
@ -180,9 +326,14 @@ func (v *Client) getKvInfo(path string) (string, int, error) {
|
||||
return mountPath, vNumber, nil
|
||||
}
|
||||
|
||||
func sanitizePath(path string) string {
|
||||
path = strings.TrimSpace(path)
|
||||
path = strings.TrimPrefix(path, "/")
|
||||
path = strings.TrimSuffix(path, "/")
|
||||
return path
|
||||
func (v *Client) lookupSecretID(secretID, appRolePath string) (map[string]interface{}, error) {
|
||||
reqPath := sanitizePath(path.Join(appRolePath, "/secret-id/lookup"))
|
||||
secret, err := v.lClient.Write(reqPath, map[string]interface{}{
|
||||
"secret_id": secretID,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return secret.Data, nil
|
||||
}
|
||||
|
@ -1,8 +1,12 @@
|
||||
package vault
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
@ -22,7 +26,7 @@ func TestGetKV2Secret(t *testing.T) {
|
||||
|
||||
t.Run("Test missing secret", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
setupMockKvV2(vaultMock)
|
||||
vaultMock.On("Read", "secret/data/notexist").Return(nil, nil)
|
||||
secret, err := client.GetKvSecret("secret/notexist")
|
||||
@ -37,7 +41,7 @@ func TestGetKV2Secret(t *testing.T) {
|
||||
t.Run("Getting secret from KV engine (v2)", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV2(vaultMock)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", secretAPIPath).Return(kv2Secret(SecretData{"key1": "value1"}), nil)
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
assert.NoError(t, err, "Expect GetKvSecret to succeed")
|
||||
@ -45,21 +49,20 @@ func TestGetKV2Secret(t *testing.T) {
|
||||
|
||||
})
|
||||
|
||||
t.Run("error is thrown when 'data' field can't be parsed", func(t *testing.T) {
|
||||
t.Run("field ignored when 'data' field can't be parsed", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV2(vaultMock)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", secretAPIPath).Return(kv2Secret(SecretData{"key1": "value1", "key2": 5}), nil)
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
assert.Error(t, err, "Excpected to fail since value is wrong data type")
|
||||
assert.Nil(t, secret)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, secret["key2"])
|
||||
})
|
||||
|
||||
t.Run("error is thrown when data field is missing", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV2(vaultMock)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", secretAPIPath).Return(kv1Secret(SecretData{"key1": "value1"}), nil)
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
assert.Error(t, err, "Expected to fail since 'data' field is missing")
|
||||
@ -70,13 +73,12 @@ func TestGetKV2Secret(t *testing.T) {
|
||||
|
||||
func TestGetKV1Secret(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const secretName = "secret/test"
|
||||
|
||||
t.Run("Test missing secret", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV1(vaultMock)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
|
||||
vaultMock.On("Read", mock.AnythingOfType("string")).Return(nil, nil)
|
||||
secret, err := client.GetKvSecret("secret/notexist")
|
||||
@ -87,7 +89,7 @@ func TestGetKV1Secret(t *testing.T) {
|
||||
t.Run("Test parsing KV1 secrets", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV1(vaultMock)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
|
||||
vaultMock.On("Read", secretName).Return(kv1Secret(SecretData{"key1": "value1"}), nil)
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
@ -99,18 +101,216 @@ func TestGetKV1Secret(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
setupMockKvV1(vaultMock)
|
||||
vaultMock.On("Read", secretName).Return(kv1Secret(SecretData{"key1": 5}), nil)
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, secret)
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, secret["key1"])
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecretIDGeneration(t *testing.T) {
|
||||
t.Parallel()
|
||||
const secretID = "secret-id"
|
||||
const appRoleName = "test"
|
||||
const appRolePath = "auth/approle/role/test"
|
||||
|
||||
t.Run("Test generating new secret-id", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
now := time.Now()
|
||||
expiry := now.Add(5 * time.Hour).Format(time.RFC3339)
|
||||
metadata := map[string]interface{}{
|
||||
"field1": "value1",
|
||||
}
|
||||
|
||||
metadataJSON, err := json.Marshal(metadata)
|
||||
assert.NoError(t, err)
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": expiry,
|
||||
"metadata": metadata,
|
||||
}), nil)
|
||||
|
||||
vaultMock.On("Write", path.Join(appRolePath, "/secret-id"), mapWith("metadata", string(metadataJSON))).Return(kv1Secret(SecretData{
|
||||
"secret_id": "newSecretId",
|
||||
}), nil)
|
||||
|
||||
newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "newSecretId", newSecretID)
|
||||
})
|
||||
|
||||
t.Run("Test with no secret-id returned", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
now := time.Now()
|
||||
expiry := now.Add(5 * time.Hour).Format(time.RFC3339)
|
||||
metadata := map[string]interface{}{
|
||||
"field1": "value1",
|
||||
}
|
||||
|
||||
metadataJSON, err := json.Marshal(metadata)
|
||||
assert.NoError(t, err)
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": expiry,
|
||||
"metadata": metadata,
|
||||
}), nil)
|
||||
|
||||
vaultMock.On("Write", path.Join(appRolePath, "/secret-id"), mapWith("metadata", string(metadataJSON))).Return(kv1Secret(SecretData{}), nil)
|
||||
|
||||
newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName)
|
||||
assert.EqualError(t, err, fmt.Sprintf("Vault response for path %s did not contain a new secret-id", path.Join(appRolePath, "secret-id")))
|
||||
assert.Equal(t, newSecretID, "")
|
||||
})
|
||||
|
||||
t.Run("Test with no new secret-id returned", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
now := time.Now()
|
||||
expiry := now.Add(5 * time.Hour).Format(time.RFC3339)
|
||||
metadata := map[string]interface{}{
|
||||
"field1": "value1",
|
||||
}
|
||||
|
||||
metadataJSON, err := json.Marshal(metadata)
|
||||
assert.NoError(t, err)
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": expiry,
|
||||
"metadata": metadata,
|
||||
}), nil)
|
||||
|
||||
vaultMock.On("Write", path.Join(appRolePath, "/secret-id"), mapWith("metadata", string(metadataJSON))).Return(kv1Secret(nil), nil)
|
||||
|
||||
newSecretID, err := client.GenerateNewAppRoleSecret(secretID, appRoleName)
|
||||
assert.EqualError(t, err, fmt.Sprintf("Could not generate new approle secret-id for approle path %s", path.Join(appRolePath, "secret-id")))
|
||||
assert.Equal(t, newSecretID, "")
|
||||
})
|
||||
}
|
||||
|
||||
func TestSecretIDTtl(t *testing.T) {
|
||||
t.Parallel()
|
||||
const secretID = "secret-id"
|
||||
const appRolePath = "auth/approle/role/test"
|
||||
const appRoleName = "test"
|
||||
|
||||
t.Run("Test fetching secreID TTL", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
now := time.Now()
|
||||
expiry := now.Add(5 * time.Hour).Format(time.RFC3339)
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": expiry,
|
||||
}), nil)
|
||||
|
||||
ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 5*time.Hour, ttl.Round(time.Hour))
|
||||
})
|
||||
|
||||
t.Run("Test with no expiration time", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{}), nil)
|
||||
ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName)
|
||||
assert.EqualError(t, err, fmt.Sprintf("Could not load secret-id information from path %s", appRolePath))
|
||||
assert.Equal(t, time.Duration(0), ttl)
|
||||
})
|
||||
|
||||
t.Run("Test with wrong date format", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": time.Now().String(),
|
||||
}), nil)
|
||||
ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName)
|
||||
assert.True(t, strings.HasPrefix(err.Error(), "parsing time"))
|
||||
assert.Equal(t, time.Duration(0), ttl)
|
||||
})
|
||||
|
||||
t.Run("Test with expired secret-id", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
now := time.Now()
|
||||
expiry := now.Add(-5 * time.Hour).Format(time.RFC3339)
|
||||
vaultMock.On("Write", path.Join(appRolePath, "secret-id/lookup"), mapWith("secret_id", secretID)).Return(kv1Secret(SecretData{
|
||||
"expiration_time": expiry,
|
||||
}), nil)
|
||||
|
||||
ttl, err := client.GetAppRoleSecretIDTtl(secretID, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, time.Duration(0), ttl)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetAppRoleName(t *testing.T) {
|
||||
t.Parallel()
|
||||
const secretID = "secret-id"
|
||||
|
||||
t.Run("Test that correct role name is returned", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{
|
||||
"meta": SecretData{
|
||||
"role_name": "test",
|
||||
},
|
||||
}), nil)
|
||||
|
||||
appRoleName, err := client.GetAppRoleName()
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "test", appRoleName)
|
||||
})
|
||||
|
||||
t.Run("Test without secret data", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(nil), nil)
|
||||
|
||||
appRoleName, err := client.GetAppRoleName()
|
||||
assert.EqualError(t, err, "Could not lookup token information: auth/token/lookup-self")
|
||||
assert.Empty(t, appRoleName)
|
||||
})
|
||||
|
||||
t.Run("Test without metadata data", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{}), nil)
|
||||
|
||||
appRoleName, err := client.GetAppRoleName()
|
||||
assert.EqualError(t, err, "Token info did not contain metadata auth/token/lookup-self")
|
||||
assert.Empty(t, appRoleName)
|
||||
})
|
||||
|
||||
t.Run("Test without role name in metadata", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{
|
||||
"meta": SecretData{},
|
||||
}), nil)
|
||||
|
||||
appRoleName, err := client.GetAppRoleName()
|
||||
assert.Empty(t, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("Test that different role_name types are ignored", func(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
vaultMock.On("Read", "auth/token/lookup-self").Return(kv1Secret(SecretData{
|
||||
"meta": SecretData{
|
||||
"role_name": 5,
|
||||
},
|
||||
}), nil)
|
||||
|
||||
appRoleName, err := client.GetAppRoleName()
|
||||
assert.Empty(t, appRoleName)
|
||||
assert.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestUnknownKvVersion(t *testing.T) {
|
||||
vaultMock := &mocks.VaultMock{}
|
||||
client := Client{vaultMock}
|
||||
client := Client{vaultMock, &Config{}}
|
||||
|
||||
vaultMock.On("Read", "sys/internal/ui/mounts/secret/secret").Return(&api.Secret{
|
||||
Data: map[string]interface{}{
|
||||
@ -126,6 +326,15 @@ func TestUnknownKvVersion(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestSetAppRoleMountPont(t *testing.T) {
|
||||
client := Client{nil, &Config{}}
|
||||
const newMountpoint = "auth/test"
|
||||
|
||||
client.SetAppRoleMountPoint("auth/test")
|
||||
|
||||
assert.Equal(t, newMountpoint, client.config.AppRoleMountPoint)
|
||||
}
|
||||
|
||||
func setupMockKvV2(vaultMock *mocks.VaultMock) {
|
||||
vaultMock.On("Read", mock.MatchedBy(func(path string) bool {
|
||||
return strings.HasPrefix(path, sysLookupPath)
|
||||
@ -174,3 +383,19 @@ func kv2Secret(data SecretData) *api.Secret {
|
||||
Data: SecretData{"data": data},
|
||||
}
|
||||
}
|
||||
|
||||
func mapWith(key, expectedValue string) interface{} {
|
||||
return mock.MatchedBy(func(arg map[string]interface{}) bool {
|
||||
valRaw, ok := arg[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
val, ok := valRaw.(string)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
return val == expectedValue
|
||||
})
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
// +build !release
|
||||
// Code generated by mockery v2.0.3. DO NOT EDIT.
|
||||
// Code generated by mockery v2.3.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
@ -35,3 +34,26 @@ func (_m *VaultMock) Read(_a0 string) (*api.Secret, error) {
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// Write provides a mock function with given fields: _a0, _a1
|
||||
func (_m *VaultMock) Write(_a0 string, _a1 map[string]interface{}) (*api.Secret, error) {
|
||||
ret := _m.Called(_a0, _a1)
|
||||
|
||||
var r0 *api.Secret
|
||||
if rf, ok := ret.Get(0).(func(string, map[string]interface{}) *api.Secret); ok {
|
||||
r0 = rf(_a0, _a1)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(*api.Secret)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string, map[string]interface{}) error); ok {
|
||||
r1 = rf(_a0, _a1)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
107
resources/metadata/vaultRotateSecretId.yaml
Normal file
107
resources/metadata/vaultRotateSecretId.yaml
Normal file
@ -0,0 +1,107 @@
|
||||
metadata:
|
||||
name: vaultRotateSecretId
|
||||
description: Rotate vault AppRole Secret ID
|
||||
longDescription: This step takes the given Vault secret ID and checks whether it needs to be renewed and if so it will update the secret ID in the configured secret store.
|
||||
spec:
|
||||
inputs:
|
||||
params:
|
||||
- name: secretStore
|
||||
type: string
|
||||
description: "The store to which the secret should be written back to"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: "jenkins"
|
||||
possibleValues:
|
||||
- jenkins
|
||||
- name: jenkinsUrl
|
||||
type: string
|
||||
description: "The jenkins url"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
secret: true
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
paths:
|
||||
- $(vaultPath)/jenkins
|
||||
- $(vaultBasePath)/$(vaultPipelineName)/jenkins
|
||||
- $(vaultBasePath)/GROUP-SECRETS/jenkins
|
||||
aliases:
|
||||
- name: url
|
||||
- name: jenkinsCredentialDomain
|
||||
type: string
|
||||
description: The jenkins credential domain which should be used
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: "_"
|
||||
- name: jenkinsUsername
|
||||
type: string
|
||||
description: "The jenkins username"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
secret: true
|
||||
aliases:
|
||||
- name: userId
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
paths:
|
||||
- $(vaultPath)/jenkins
|
||||
- $(vaultBasePath)/$(vaultPipelineName)/jenkins
|
||||
- $(vaultBasePath)/GROUP-SECRETS/jenkins
|
||||
- name: jenkinsToken
|
||||
type: string
|
||||
description: "The jenkins token"
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
secret: true
|
||||
aliases:
|
||||
- name: token
|
||||
resourceRef:
|
||||
- type: vaultSecret
|
||||
paths:
|
||||
- $(vaultPath)/jenkins
|
||||
- $(vaultBasePath)/$(vaultPipelineName)/jenkins
|
||||
- $(vaultBasePath)/GROUP-SECRETS/jenkins
|
||||
- name: vaultAppRoleSecretTokenCredentialsId
|
||||
type: string
|
||||
description: The Jenkins credential ID for the Vault AppRole Secret ID credential
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
mandatory: true
|
||||
- name: vaultServerUrl
|
||||
type: string
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
description: The URL for the Vault server to use
|
||||
mandatory: true
|
||||
- name: vaultNamespace
|
||||
type: string
|
||||
scope:
|
||||
- GENERAL
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
description: The vault namespace that should be used (optional)
|
||||
- name: daysBeforeExpiry
|
||||
type: int
|
||||
description: The amount of days before expiry until the secret ID gets rotated
|
||||
scope:
|
||||
- PARAMETERS
|
||||
- STAGES
|
||||
- STEPS
|
||||
default: 15
|
@ -55,7 +55,7 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'piperPipeline',
|
||||
'prepareDefaultValues',
|
||||
'runClosures',
|
||||
'setupCommonPipelineEnvironment'
|
||||
'setupCommonPipelineEnvironment',
|
||||
]
|
||||
|
||||
List steps = getSteps().stream()
|
||||
@ -166,7 +166,8 @@ public class CommonStepsTest extends BasePiperTest{
|
||||
'containerSaveImage', //implementing new golang pattern without fields
|
||||
'detectExecuteScan', //implementing new golang pattern without fields
|
||||
'kanikoExecute', //implementing new golang pattern without fields
|
||||
'gitopsUpdateDeployment' //implementing new golang pattern without fields
|
||||
'gitopsUpdateDeployment', //implementing new golang pattern without fields
|
||||
'vaultRotateSecretId' //implementing new golang pattern without fields
|
||||
]
|
||||
|
||||
@Test
|
||||
|
@ -8,9 +8,8 @@ import static com.sap.piper.Prerequisites.checkScript
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field String TECHNICAL_STAGE_NAME = 'postPipelineHook'
|
||||
|
||||
@Field Set GENERAL_CONFIG_KEYS = []
|
||||
@Field STAGE_STEP_KEYS = []
|
||||
@Field Set GENERAL_CONFIG_KEYS = ["vaultServerUrl", "vaultAppRoleTokenCredentialsId", "vaultAppRoleSecretTokenCredentialsId"]
|
||||
@Field STAGE_STEP_KEYS = ["vaultRotateSecretId"]
|
||||
@Field Set STEP_CONFIG_KEYS = GENERAL_CONFIG_KEYS.plus(STAGE_STEP_KEYS)
|
||||
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
|
||||
|
||||
@ -18,6 +17,7 @@ import static com.sap.piper.Prerequisites.checkScript
|
||||
* In this stage reporting actions like mail notification or telemetry reporting are executed.
|
||||
*
|
||||
* This stage contains following steps:
|
||||
* - [vaultRotateSecretId](./vaultRotateSecretId.md)
|
||||
* - [influxWriteData](./influxWriteData.md)
|
||||
* - [mailSendNotification](./mailSendNotification.md)
|
||||
*
|
||||
@ -37,14 +37,20 @@ void call(Map parameters = [:]) {
|
||||
.mixinGeneralConfig(script.commonPipelineEnvironment, GENERAL_CONFIG_KEYS)
|
||||
.mixinStageConfig(script.commonPipelineEnvironment, stageName, STEP_CONFIG_KEYS)
|
||||
.mixin(parameters, PARAMETER_KEYS)
|
||||
.addIfEmpty("vaultRotateSecretId", false)
|
||||
.use()
|
||||
|
||||
piperStageWrapper (script: script, stageName: stageName, stageLocking: false) {
|
||||
// rotate vault secret id if necessary
|
||||
if (config.vaultRotateSecretId && config.vaultServerUrl && config.vaultAppRoleSecretTokenCredentialsId
|
||||
&& config.vaultAppRoleTokenCredentialsId) {
|
||||
vaultRotateSecretId script: script
|
||||
}
|
||||
|
||||
// telemetry reporting
|
||||
utils.pushToSWA([step: STEP_NAME], config)
|
||||
|
||||
influxWriteData script: script
|
||||
|
||||
if(env.BRANCH_NAME == parameters.script.commonPipelineEnvironment.getStepConfiguration('', '').productiveBranch) {
|
||||
if(parameters.script.commonPipelineEnvironment.configuration.runStep?.get('Post Actions')?.slackSendNotification) {
|
||||
slackSendNotification script: parameters.script
|
||||
|
10
vars/vaultRotateSecretId.groovy
Normal file
10
vars/vaultRotateSecretId.groovy
Normal file
@ -0,0 +1,10 @@
|
||||
import groovy.transform.Field
|
||||
import static com.sap.piper.Prerequisites.checkScript
|
||||
|
||||
@Field String STEP_NAME = getClass().getName()
|
||||
@Field String METADATA_FILE = 'metadata/vaultRotateSecretId.yaml'
|
||||
|
||||
void call(Map parameters = [:]) {
|
||||
def script = checkScript(this, parameters) ?: this
|
||||
piperExecuteBin(parameters, STEP_NAME, METADATA_FILE, [], false, false, false)
|
||||
}
|
Loading…
Reference in New Issue
Block a user