mirror of
https://github.com/SAP/jenkins-library.git
synced 2024-12-12 10:55:20 +02:00
Writing secrets to vault (#2770)
Co-authored-by: Kevin Stiehl <kevin.stiehl@numericas.de> Co-authored-by: Oliver Nocon <33484802+OliverNocon@users.noreply.github.com>
This commit is contained in:
parent
4ffd61d34c
commit
0673d3fed6
24
go.mod
24
go.mod
@ -9,33 +9,26 @@ require (
|
||||
github.com/Masterminds/goutils v1.1.0 // indirect
|
||||
github.com/Masterminds/semver v1.5.0 // indirect
|
||||
github.com/Masterminds/sprig v2.22.0+incompatible
|
||||
github.com/Microsoft/hcsshim v0.8.10 // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20200907205600-7a23bdc65eef // indirect
|
||||
github.com/bmatcuk/doublestar v1.3.2
|
||||
github.com/bndr/gojenkins v1.0.1
|
||||
github.com/containerd/containerd v1.4.1 // indirect
|
||||
github.com/docker/docker v1.4.2-0.20200114201811-16a3519d870b // indirect
|
||||
github.com/elliotchance/orderedmap v1.3.0
|
||||
github.com/evanphx/json-patch v4.9.0+incompatible
|
||||
github.com/frankban/quicktest v1.10.0 // indirect
|
||||
github.com/getsentry/sentry-go v0.7.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/ghodss/yaml v1.0.1-0.20190212211648-25d852aebe32
|
||||
github.com/go-git/go-billy/v5 v5.0.0
|
||||
github.com/go-git/go-git/v5 v5.1.0
|
||||
github.com/go-openapi/runtime v0.19.22
|
||||
github.com/go-openapi/spec v0.19.9 // indirect
|
||||
github.com/go-openapi/strfmt v0.19.5
|
||||
github.com/go-openapi/validate v0.19.11 // indirect
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/google/go-cmp v0.5.2
|
||||
github.com/google/go-cmp v0.5.4
|
||||
github.com/google/go-containerregistry v0.1.3
|
||||
github.com/google/go-github/v32 v32.1.0
|
||||
github.com/google/uuid v1.1.2
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.0 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.6.7
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/vault/api v1.0.4
|
||||
github.com/hashicorp/vault v1.7.1
|
||||
github.com/hashicorp/vault/api v1.1.0
|
||||
github.com/huandu/xstrings v1.3.2 // indirect
|
||||
github.com/imdario/mergo v0.3.11 // indirect
|
||||
github.com/jarcoal/httpmock v1.0.8
|
||||
@ -43,11 +36,7 @@ require (
|
||||
github.com/magicsong/color-glog v0.0.1 // indirect
|
||||
github.com/magicsong/sonargo v0.0.1
|
||||
github.com/mailru/easyjson v0.7.6 // indirect
|
||||
github.com/mitchellh/mapstructure v1.3.3 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.1 // indirect
|
||||
github.com/motemen/go-nuts v0.0.0-20200601065735-3df31f16cb2f
|
||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||
github.com/pierrec/lz4 v2.5.2+incompatible // indirect
|
||||
github.com/piper-validation/fortify-client-go v0.0.0-20210114140201-1261216783c6
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/sirupsen/logrus v1.7.0
|
||||
@ -56,16 +45,11 @@ require (
|
||||
github.com/spf13/pflag v1.0.5
|
||||
github.com/stretchr/testify v1.6.1
|
||||
github.com/testcontainers/testcontainers-go v0.5.1
|
||||
go.mongodb.org/mongo-driver v1.4.1 // indirect
|
||||
golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 // indirect
|
||||
golang.org/x/mod v0.3.0
|
||||
golang.org/x/net v0.0.0-20201002202402-0a1ea396d57c // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43
|
||||
golang.org/x/sync v0.0.0-20200930132711-30421366ff76 // indirect
|
||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
|
||||
google.golang.org/genproto v0.0.0-20201002142447-3860012362da // indirect
|
||||
google.golang.org/grpc v1.32.0 // indirect
|
||||
gopkg.in/ini.v1 v1.61.0
|
||||
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
|
||||
gopkg.in/yaml.v2 v2.3.0
|
||||
)
|
||||
|
@ -65,6 +65,70 @@ func TestGetVaultSecret(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestWriteVaultSecret(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
const testToken = "vault-token"
|
||||
|
||||
req := testcontainers.GenericContainerRequest{
|
||||
ContainerRequest: testcontainers.ContainerRequest{
|
||||
AlwaysPullImage: true,
|
||||
Image: "vault:1.4.3",
|
||||
ExposedPorts: []string{"8200/tcp"},
|
||||
Env: map[string]string{"VAULT_DEV_ROOT_TOKEN_ID": testToken},
|
||||
WaitingFor: wait.ForLog("Vault server started!").WithStartupTimeout(20 * time.Second)},
|
||||
|
||||
Started: true,
|
||||
}
|
||||
|
||||
vaultContainer, err := testcontainers.GenericContainer(ctx, req)
|
||||
assert.NoError(t, err)
|
||||
defer vaultContainer.Terminate(ctx)
|
||||
|
||||
ip, err := vaultContainer.Host(ctx)
|
||||
assert.NoError(t, err)
|
||||
port, err := vaultContainer.MappedPort(ctx, "8200")
|
||||
host := fmt.Sprintf("http://%s:%s", ip, port.Port())
|
||||
config := &vault.Config{Config: &api.Config{Address: host}}
|
||||
// setup vault for testing
|
||||
secretData := map[string]string{
|
||||
"key1": "value1",
|
||||
"key2": "value2",
|
||||
}
|
||||
|
||||
client, err := vault.NewClient(config, testToken)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = client.WriteKvSecret("secret/test", secretData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
secret, err := client.GetKvSecret("secret/test")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value1", secret["key1"])
|
||||
assert.Equal(t, "value2", secret["key2"])
|
||||
|
||||
// enabling KV engine 1
|
||||
vaultClient, err := api.NewClient(config.Config)
|
||||
assert.NoError(t, err)
|
||||
vaultClient.SetToken(testToken)
|
||||
_, err = vaultClient.Logical().Write("sys/mounts/kv", SecretData{
|
||||
"path": "kv",
|
||||
"type": "kv",
|
||||
"options": SecretData{
|
||||
"version": "1",
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = client.WriteKvSecret("secret/test1", secretData)
|
||||
assert.NoError(t, err)
|
||||
|
||||
secret, err = client.GetKvSecret("secret/test1")
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "value1", secret["key1"])
|
||||
assert.Equal(t, "value2", secret["key2"])
|
||||
}
|
||||
|
||||
func TestVaultAppRole(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
@ -326,7 +326,7 @@ steps:
|
||||
var c Config
|
||||
myConfig := ioutil.NopCloser(strings.NewReader("invalid config"))
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, nil, false, StepFilters{}, []StepParameters{}, nil, nil, "stage1", "step1", []Alias{})
|
||||
assert.EqualError(t, err, "failed to parse custom pipeline configuration: format of configuration is invalid \"invalid config\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
assert.EqualError(t, err, "failed to parse custom pipeline configuration: format of configuration is invalid \"invalid config\": error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
})
|
||||
|
||||
t.Run("Failure case defaults", func(t *testing.T) {
|
||||
@ -334,7 +334,7 @@ steps:
|
||||
myConfig := ioutil.NopCloser(strings.NewReader(""))
|
||||
myDefaults := []io.ReadCloser{ioutil.NopCloser(strings.NewReader("invalid defaults"))}
|
||||
_, err := c.GetStepConfig(nil, "", myConfig, myDefaults, false, StepFilters{}, []StepParameters{}, nil, nil, "stage1", "step1", []Alias{})
|
||||
assert.EqualError(t, err, "failed to read default configuration: error unmarshalling \"invalid defaults\": error unmarshaling JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
assert.EqualError(t, err, "failed to read default configuration: error unmarshalling \"invalid defaults\": error unmarshaling JSON: while decoding JSON: json: cannot unmarshal string into Go value of type config.Config", "default error expected")
|
||||
})
|
||||
|
||||
//ToDo: test merging of env and parameters/flags
|
||||
|
@ -146,6 +146,35 @@ func (v Client) GetKvSecret(path string) (map[string]string, error) {
|
||||
return secretData, nil
|
||||
}
|
||||
|
||||
// WriteKvSecret writes secret to kv engine
|
||||
func (v Client) WriteKvSecret(path string, newSecret map[string]string) error {
|
||||
oldSecret, err := v.GetKvSecret(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
secret := make(map[string]interface{}, len(oldSecret))
|
||||
for k, v := range oldSecret {
|
||||
secret[k] = v
|
||||
}
|
||||
for k, v := range newSecret {
|
||||
secret[k] = v
|
||||
}
|
||||
path = sanitizePath(path)
|
||||
mountpath, version, err := v.getKvInfo(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if version == 2 {
|
||||
path = addPrefixToKvPath(path, mountpath, "data")
|
||||
secret = map[string]interface{}{"data": secret}
|
||||
} else if version != 1 {
|
||||
return fmt.Errorf("KV Engine in version %d is currently not supported", version)
|
||||
}
|
||||
|
||||
_, err = v.lClient.Write(path, secret)
|
||||
return err
|
||||
}
|
||||
|
||||
// GenerateNewAppRoleSecret creates a new secret-id
|
||||
func (v *Client) GenerateNewAppRoleSecret(secretID, appRoleName string) (string, error) {
|
||||
appRolePath := v.getAppRolePath(appRoleName)
|
||||
|
@ -3,18 +3,22 @@ package vault
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"path"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
mocks "github.com/SAP/jenkins-library/pkg/vault/mocks"
|
||||
|
||||
"github.com/hashicorp/vault/api"
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
vaulthttp "github.com/hashicorp/vault/http"
|
||||
"github.com/hashicorp/vault/vault"
|
||||
)
|
||||
|
||||
type SecretData = map[string]interface{}
|
||||
@ -111,6 +115,63 @@ func TestGetKV1Secret(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestWriteKvSecret(t *testing.T) {
|
||||
const secretName = "secret/test"
|
||||
tests := []struct {
|
||||
name string
|
||||
initialSecret map[string]string
|
||||
writingSecret map[string]string
|
||||
expectedSecret map[string]string
|
||||
}{
|
||||
{
|
||||
name: "Test write new KV2 secret",
|
||||
initialSecret: nil,
|
||||
writingSecret: map[string]string{"key": "value"},
|
||||
expectedSecret: map[string]string{"key": "value"},
|
||||
},
|
||||
{
|
||||
name: "Test rewrite KV2 secret with new keys",
|
||||
initialSecret: map[string]string{"key1": "value1"},
|
||||
writingSecret: map[string]string{"key2": "value2"},
|
||||
expectedSecret: map[string]string{"key1": "value1", "key2": "value2"},
|
||||
},
|
||||
{
|
||||
name: "Test rewrite KV2 secret with existed keys",
|
||||
initialSecret: map[string]string{"key1": "value1"},
|
||||
writingSecret: map[string]string{"key1": "value2"},
|
||||
expectedSecret: map[string]string{"key1": "value2"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
cluster := vault.NewTestCluster(t, &vault.CoreConfig{
|
||||
DevToken: "token",
|
||||
}, &vault.TestClusterOptions{
|
||||
HandlerFunc: vaulthttp.Handler,
|
||||
})
|
||||
|
||||
core := cluster.Cores[0].Core
|
||||
vault.TestWaitActive(t, core)
|
||||
vaultClient := cluster.Cores[0].Client
|
||||
client := Client{vaultClient.Logical(), &Config{}}
|
||||
cluster.Start()
|
||||
defer cluster.Cleanup()
|
||||
|
||||
if test.initialSecret != nil {
|
||||
err := client.WriteKvSecret(secretName, test.initialSecret)
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
err := client.WriteKvSecret(secretName, test.writingSecret)
|
||||
assert.NoError(t, err)
|
||||
secret, err := client.GetKvSecret(secretName)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, test.expectedSecret, secret)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretIDGeneration(t *testing.T) {
|
||||
t.Parallel()
|
||||
const secretID = "secret-id"
|
||||
|
Loading…
Reference in New Issue
Block a user