diff --git a/Makefile b/Makefile index 7f8f276..145f16f 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ build-in-docker: clean make build image: build-in-docker - docker build --build-arg VERSION=$(IMAGE_VERSION) -t $(IMAGE_TAG) . + docker build --no-cache --build-arg VERSION=$(IMAGE_VERSION) -t $(IMAGE_TAG) . e2e-container: REGISTRY_NAME="e2e" IMAGE_VERSION="latest" make image diff --git a/go.mod b/go.mod index 8946b3d..b1d8120 100644 --- a/go.mod +++ b/go.mod @@ -4,16 +4,14 @@ go 1.12 require ( github.com/gogo/protobuf v1.3.2 // indirect + github.com/google/go-cmp v0.5.5 // indirect github.com/hashicorp/go-hclog v0.8.0 github.com/hashicorp/vault/api v1.0.4 github.com/mitchellh/mapstructure v1.4.1 // indirect - github.com/pkg/errors v0.9.1 - github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.6.1 + golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 // indirect google.golang.org/grpc v1.29.1 - gopkg.in/yaml.v2 v2.3.0 - gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c // indirect - gotest.tools v2.2.0+incompatible + gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c k8s.io/api v0.20.0 k8s.io/apimachinery v0.20.0 k8s.io/client-go v0.20.0 diff --git a/go.sum b/go.sum index 5962288..53caaaa 100644 --- a/go.sum +++ b/go.sum @@ -252,8 +252,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/gofuzz v1.1.0 h1:Hsa8mG0dQ46ij8Sl2AYJDUv1oA9/d6Vk+3LG99Oe02g= @@ -459,7 +460,6 @@ github.com/pierrec/lz4 v2.0.5+incompatible h1:2xWsjqPFWcplujydGg4WmhC/6fZqK42wMM github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -716,8 +716,9 @@ golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201015000850-e3ed0017c211/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201107080550-4d91cf3a1aaf/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd h1:5CtCZbICpIOFdgO940moixOPjc0178IU44m4EjOO5IY= golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4 h1:myAQVi0cGEoqQVR5POX+8RR2mrocKqNN1hmeMqhX27k= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -885,7 +886,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c h1:grhR+C34yXImVGp7EzNk+DTIk+323eIUWOmEevy6bDo= gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/internal/config/config.go b/internal/config/config.go index f789ae8..88190e0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -2,12 +2,12 @@ package config import ( "encoding/json" + "errors" "os" "strconv" "github.com/hashicorp/go-hclog" - "github.com/pkg/errors" - "gopkg.in/yaml.v2" + "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/types" ) @@ -39,6 +39,7 @@ type Parameters struct { VaultAddress string VaultRoleName string VaultKubernetesMountPath string + VaultNamespace string VaultTLSConfig TLSConfig Secrets []Secret PodInfo PodInfo @@ -102,6 +103,7 @@ func parseParameters(logger hclog.Logger, parametersStr string) (Parameters, err var parameters Parameters parameters.VaultRoleName = params["roleName"] parameters.VaultAddress = params["vaultAddress"] + parameters.VaultNamespace = params["vaultNamespace"] parameters.VaultTLSConfig.CACertPath = params["vaultCACertPath"] parameters.VaultTLSConfig.CADirectory = params["vaultCADirectory"] parameters.VaultTLSConfig.TLSServerName = params["vaultTLSServerName"] @@ -151,7 +153,7 @@ func (c *Config) validate() error { return errors.New("missing target path field") } if c.Parameters.VaultRoleName == "" { - return errors.Errorf("missing 'roleName' in SecretProviderClass definition") + return errors.New("missing 'roleName' in SecretProviderClass definition") } if len(c.Parameters.Secrets) == 0 { return errors.New("no secrets configured - the provider will not read any secret material") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index b471797..07e8fed 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -8,8 +8,7 @@ import ( "github.com/hashicorp/go-hclog" "github.com/stretchr/testify/require" - "gopkg.in/yaml.v2" - "gotest.tools/assert" + "gopkg.in/yaml.v3" ) const ( @@ -54,7 +53,7 @@ func TestParseParametersFromYaml(t *testing.T) { params, err := parseParameters(hclog.NewNullLogger(), string(paramsBytes)) require.NoError(t, err) - assert.DeepEqual(t, Parameters{ + require.Equal(t, Parameters{ VaultAddress: defaultVaultAddress, VaultKubernetesMountPath: defaultVaultKubernetesMountPath, Secrets: []Secret{ @@ -105,7 +104,7 @@ func TestParseParameters(t *testing.T) { ServiceAccountName: "default", }, } - assert.DeepEqual(t, expected, actual) + require.Equal(t, expected, actual) } func TestParseConfig(t *testing.T) { @@ -114,6 +113,7 @@ func TestParseConfig(t *testing.T) { defaultParams := Parameters{ VaultAddress: defaultVaultAddress, VaultKubernetesMountPath: defaultVaultKubernetesMountPath, + VaultNamespace: "", } for _, tc := range []struct { name string @@ -150,6 +150,7 @@ func TestParseConfig(t *testing.T) { "roleName": "example-role", "vaultSkipTLSVerify": "true", "vaultAddress": "my-vault-address", + "vaultNamespace": "my-vault-namespace", "vaultKubernetesMountPath": "my-mount-path", "KubernetesServiceAccountPath": "my-account-path", "objects": objects, @@ -161,6 +162,7 @@ func TestParseConfig(t *testing.T) { expected := defaultParams expected.VaultRoleName = roleName expected.VaultAddress = "my-vault-address" + expected.VaultNamespace = "my-vault-namespace" expected.VaultKubernetesMountPath = "my-mount-path" expected.VaultTLSConfig.SkipVerify = true expected.Secrets = []Secret{ @@ -175,7 +177,7 @@ func TestParseConfig(t *testing.T) { require.NoError(t, err) cfg, err := Parse(hclog.NewNullLogger(), string(parametersStr), tc.targetPath, "420") require.NoError(t, err, tc.name) - assert.DeepEqual(t, tc.expected, cfg) + require.Equal(t, tc.expected, cfg) } } diff --git a/internal/provider/provider.go b/internal/provider/provider.go index 44d4b5a..56b2ed6 100644 --- a/internal/provider/provider.go +++ b/internal/provider/provider.go @@ -211,6 +211,12 @@ func (p *provider) MountSecretsStoreObjectContent(ctx context.Context, cfg confi return nil, err } + // Set Vault namespace if configured + if cfg.VaultNamespace != "" { + p.logger.Debug("setting Vault namespace", "namespace", cfg.VaultNamespace) + client.SetNamespace(cfg.VaultNamespace) + } + // Authenticate to vault using the jwt token _, err = p.login(ctx, client, cfg.Parameters) if err != nil { diff --git a/main.go b/main.go index 61ba80b..4587be8 100644 --- a/main.go +++ b/main.go @@ -14,17 +14,16 @@ import ( "github.com/hashicorp/go-hclog" providerserver "github.com/hashicorp/vault-csi-provider/internal/server" "github.com/hashicorp/vault-csi-provider/internal/version" - "github.com/spf13/pflag" "google.golang.org/grpc" "google.golang.org/grpc/status" pb "sigs.k8s.io/secrets-store-csi-driver/provider/v1alpha1" ) var ( - endpoint = pflag.String("endpoint", "/tmp/vault.sock", "path to socket on which to listen for driver gRPC calls") - debug = pflag.Bool("debug", false, "sets log to debug level") + endpoint = flag.String("endpoint", "/tmp/vault.sock", "path to socket on which to listen for driver gRPC calls") + debug = flag.Bool("debug", false, "sets log to debug level") healthAddr = flag.String("health_addr", ":8080", "configure http listener for reporting health") - selfVersion = pflag.Bool("version", false, "prints the version information") + selfVersion = flag.Bool("version", false, "prints the version information") ) func main() { @@ -37,7 +36,7 @@ func main() { } func realMain(logger hclog.Logger) error { - pflag.Parse() + flag.Parse() // set log level logger.SetLevel(hclog.Info) diff --git a/test/bats/configs/vault-kv-namespace-secretproviderclass.yaml b/test/bats/configs/vault-kv-namespace-secretproviderclass.yaml new file mode 100644 index 0000000..4d2b7de --- /dev/null +++ b/test/bats/configs/vault-kv-namespace-secretproviderclass.yaml @@ -0,0 +1,17 @@ +apiVersion: secrets-store.csi.x-k8s.io/v1alpha1 +kind: SecretProviderClass +metadata: + name: vault-kv-namespace +spec: + provider: vault + parameters: + roleName: "kv-namespace-role" + vaultAddress: https://vault:8200 + vaultNamespace: "acceptance" + vaultCACertPath: /mnt/tls/ca.crt + vaultTLSClientCertPath: /mnt/tls/client.crt + vaultTLSClientKeyPath: /mnt/tls/client.key + objects: | + - objectName: "secret-1" + secretPath: "secret/data/kv1-namespace" + secretKey: "greeting" diff --git a/test/bats/configs/vault-policy-kv-namespace.hcl b/test/bats/configs/vault-policy-kv-namespace.hcl new file mode 100644 index 0000000..ed9ec53 --- /dev/null +++ b/test/bats/configs/vault-policy-kv-namespace.hcl @@ -0,0 +1,3 @@ +path "secret/data/kv1-namespace" { + capabilities = ["read"] +} diff --git a/test/bats/configs/vault/vault.values.yaml b/test/bats/configs/vault/vault.values.yaml index 51b4c57..30605de 100644 --- a/test/bats/configs/vault/vault.values.yaml +++ b/test/bats/configs/vault/vault.values.yaml @@ -7,7 +7,8 @@ injector: server: image: - repository: docker.mirror.hashicorp.services/vault + repository: docker.mirror.hashicorp.services/hashicorp/vault-enterprise + tag: 1.7.0_ent volumes: - name: vault-server-tls secret: @@ -45,6 +46,12 @@ server: csi: enabled: true + debug: true + + image: + repository: "e2e/vault-csi-provider" + tag: "latest" + pullPolicy: Never volumes: - name: vault-client-tls diff --git a/test/bats/provider.bats b/test/bats/provider.bats index 1eb4ad2..b5f50fb 100644 --- a/test/bats/provider.bats +++ b/test/bats/provider.bats @@ -13,10 +13,13 @@ CONFIGS=test/bats/configs setup(){ { # Braces used to redirect all setup logs. # 1. Configure Vault. + kubectl --namespace=csi exec vault-0 -- vault namespace create acceptance + # 1. a) Vault policies cat $CONFIGS/vault-policy-db.hcl | kubectl --namespace=csi exec -i vault-0 -- vault policy write db-policy - cat $CONFIGS/vault-policy-kv.hcl | kubectl --namespace=csi exec -i vault-0 -- vault policy write kv-policy - cat $CONFIGS/vault-policy-pki.hcl | kubectl --namespace=csi exec -i vault-0 -- vault policy write pki-policy - + cat $CONFIGS/vault-policy-kv-namespace.hcl | kubectl --namespace=csi exec -i vault-0 -- vault policy write -namespace=acceptance kv-namespace-policy - # 1. b) Setup kubernetes auth engine. kubectl --namespace=csi exec vault-0 -- vault auth enable kubernetes @@ -30,6 +33,12 @@ setup(){ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \ kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ issuer="https://kubernetes.default.svc.cluster.local"' + kubectl --namespace=csi exec vault-0 -- vault auth enable -namespace=acceptance kubernetes + kubectl --namespace=csi exec vault-0 -- sh -c 'vault write -namespace=acceptance auth/kubernetes/config \ + token_reviewer_jwt="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)" \ + kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443" \ + kubernetes_ca_cert=@/var/run/secrets/kubernetes.io/serviceaccount/ca.crt \ + issuer="https://kubernetes.default.svc.cluster.local"' kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/db-role \ bound_service_account_names=nginx-db \ bound_service_account_namespaces=test \ @@ -40,6 +49,11 @@ setup(){ bound_service_account_namespaces=test \ policies=kv-policy \ ttl=20m + kubectl --namespace=csi exec vault-0 -- vault write -namespace=acceptance auth/kubernetes/role/kv-namespace-role \ + bound_service_account_names=nginx-kv-namespace \ + bound_service_account_namespaces=test \ + policies=kv-namespace-policy \ + ttl=20m kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/pki-role \ bound_service_account_names=nginx-pki \ bound_service_account_namespaces=test \ @@ -67,11 +81,14 @@ setup(){ kubectl --namespace=csi exec vault-0 -- vault kv put secret/kv2 bar2=hello2 kubectl --namespace=csi exec vault-0 -- vault kv put secret/kv-sync1 bar1=hello-sync1 kubectl --namespace=csi exec vault-0 -- vault kv put secret/kv-sync2 bar2=hello-sync2 + kubectl --namespace=csi exec vault-0 -- vault secrets enable -namespace=acceptance -path=secret -version=2 kv + kubectl --namespace=csi exec vault-0 -- vault kv put -namespace=acceptance secret/kv1-namespace greeting=hello-namespaces # 2. Create shared k8s resources. kubectl create namespace test kubectl --namespace=test apply -f $CONFIGS/vault-all-secretproviderclass.yaml kubectl --namespace=test apply -f $CONFIGS/vault-db-secretproviderclass.yaml + kubectl --namespace=test apply -f $CONFIGS/vault-kv-namespace-secretproviderclass.yaml kubectl --namespace=test apply -f $CONFIGS/vault-kv-secretproviderclass.yaml kubectl --namespace=test apply -f $CONFIGS/vault-kv-sync-secretproviderclass.yaml kubectl --namespace=test apply -f $CONFIGS/vault-kv-sync-multiple-secretproviderclass.yaml @@ -98,6 +115,7 @@ teardown(){ fi # Teardown Vault configuration. + kubectl --namespace=csi exec vault-0 -- vault namespace delete acceptance kubectl --namespace=csi exec vault-0 -- vault auth disable kubernetes kubectl --namespace=csi exec vault-0 -- vault secrets disable secret kubectl --namespace=csi exec vault-0 -- vault secrets disable pki @@ -274,3 +292,12 @@ teardown(){ wait_for_success "kubectl --namespace=test describe pod nginx-kv | grep 'FailedMount.*failed to mount secrets store objects for pod test/nginx-kv'" wait_for_success "kubectl --namespace=test describe pod nginx-kv | grep 'service account name not authorized'" } + +@test "9 Vault Enterprise namespace" { + helm --namespace=test install nginx $CONFIGS/nginx \ + --set engine=kv-namespace --set sa=kv-namespace \ + --wait --timeout=5m + + result=$(kubectl --namespace=test exec nginx-kv-namespace -- cat /mnt/secrets-store/secret-1) + [[ "$result" == "hello-namespaces" ]] +} \ No newline at end of file