Skip to content

Commit

Permalink
Use token from CSI TokenRequests (#163)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomhjp committed Mar 20, 2023
1 parent 5d18ac4 commit fdb36cd
Show file tree
Hide file tree
Showing 8 changed files with 78 additions and 32 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ CHANGES:
CHANGES:

* Duplicate object names now trigger an error instead of silently overwriting files. [[GH-148](https://github.com/hashicorp/vault-csi-provider/pull/148)]
* Vault CSI Provider will use service account tokens passed from the Secrets Store CSI Driver instead of generating one if an appropriate token is provided. [[GH-163](https://github.com/hashicorp/vault-csi-provider/pull/163)]
* The Secrets Store CSI driver needs to be configured to generate tokens with the correct audience for this feature. Vault CSI Provider
will look for a token with the audience specified in the SecretProviderClass, or otherwise "vault". To configure the driver to generate
a token with the correct audience, use the
[`tokenRequests`](https://github.com/kubernetes-sigs/secrets-store-csi-driver/tree/main/charts/secrets-store-csi-driver#configuration)
option from the _driver_ helm chart via the flag `--set tokenRequests[0].audience="vault"`. See
[CSI TokenRequests documentation](https://kubernetes-csi.github.io/docs/token-requests.html) for further details.

BUGS:

Expand Down
7 changes: 4 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ PKG=github.com/hashicorp/vault-csi-provider/internal/version
LDFLAGS?="-X '$(PKG).BuildVersion=$(VERSION)' \
-X '$(PKG).BuildDate=$(BUILD_DATE)' \
-X '$(PKG).GoVersion=$(shell go version)'"
CSI_DRIVER_VERSION=1.2.4
VAULT_HELM_VERSION=0.22.1
CSI_DRIVER_VERSION=1.3.1
VAULT_HELM_VERSION=0.23.0
GOLANGCI_LINT_FORMAT?=colored-line-number

.PHONY: default build test bootstrap fmt lint image e2e-image e2e-setup e2e-teardown e2e-test mod setup-kind promote-staging-manifest
Expand Down Expand Up @@ -76,7 +76,8 @@ e2e-setup:
--wait --timeout=5m \
--namespace=csi \
--set linux.image.pullPolicy="IfNotPresent" \
--set syncSecret.enabled=true
--set syncSecret.enabled=true \
--set tokenRequests[0].audience="vault"
helm install vault-bootstrap test/bats/configs/vault \
--namespace=csi
helm install vault vault \
Expand Down
32 changes: 28 additions & 4 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,11 @@ type Parameters struct {
}

type PodInfo struct {
Name string
UID types.UID
Namespace string
ServiceAccountName string
Name string
UID types.UID
Namespace string
ServiceAccountName string
ServiceAccountToken string
}

type Secret struct {
Expand Down Expand Up @@ -151,6 +152,29 @@ func parseParameters(parametersStr string) (Parameters, error) {
return Parameters{}, err
}

tokensJSON := params["csi.storage.k8s.io/serviceAccount.tokens"]
if tokensJSON != "" {
// The csi.storage.k8s.io/serviceAccount.tokens field is a JSON object
// marshalled into a string. The object keys are audience name (string)
// and the values are embedded objects with "token" and
// "expirationTimestamp" fields for the corresponding audience.
var tokens map[string]struct {
Token string `json:"token"`
ExpirationTimestamp string `json:"expirationTimestamp"`
}
if err := json.Unmarshal([]byte(tokensJSON), &tokens); err != nil {
return Parameters{}, fmt.Errorf("failed to unmarshal service account tokens: %w", err)
}

audience := "vault"
if parameters.Audience != "" {
audience = parameters.Audience
}
if token, ok := tokens[audience]; ok {
parameters.PodInfo.ServiceAccountToken = token.Token
}
}

return parameters, nil
}

Expand Down
34 changes: 18 additions & 16 deletions internal/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -145,22 +145,23 @@ func TestParseConfig(t *testing.T) {
name: "set all options",
targetPath: targetPath,
parameters: map[string]string{
"roleName": "example-role",
"vaultSkipTLSVerify": "true",
"vaultAddress": "my-vault-address",
"vaultNamespace": "my-vault-namespace",
"vaultKubernetesMountPath": "my-mount-path",
"vaultCACertPath": "my-ca-cert-path",
"vaultCADirectory": "my-ca-directory",
"vaultTLSServerName": "mytls-server-name",
"vaultTLSClientCertPath": "my-tls-client-cert-path",
"vaultTLSClientKeyPath": "my-tls-client-key-path",
"csi.storage.k8s.io/pod.name": "my-pod-name",
"csi.storage.k8s.io/pod.uid": "my-pod-uid",
"csi.storage.k8s.io/pod.namespace": "my-pod-namespace",
"csi.storage.k8s.io/serviceAccount.name": "my-pod-sa-name",
"objects": objects,
"audience": "my-aud",
"roleName": "example-role",
"vaultSkipTLSVerify": "true",
"vaultAddress": "my-vault-address",
"vaultNamespace": "my-vault-namespace",
"vaultKubernetesMountPath": "my-mount-path",
"vaultCACertPath": "my-ca-cert-path",
"vaultCADirectory": "my-ca-directory",
"vaultTLSServerName": "mytls-server-name",
"vaultTLSClientCertPath": "my-tls-client-cert-path",
"vaultTLSClientKeyPath": "my-tls-client-key-path",
"csi.storage.k8s.io/pod.name": "my-pod-name",
"csi.storage.k8s.io/pod.uid": "my-pod-uid",
"csi.storage.k8s.io/pod.namespace": "my-pod-namespace",
"csi.storage.k8s.io/serviceAccount.name": "my-pod-sa-name",
"csi.storage.k8s.io/serviceAccount.tokens": `{"my-aud": {"token": "my-pod-sa-token", "expirationTimestamp": "bar"}, "other-aud": {"token": "unused-token"}}`,
"objects": objects,
"audience": "my-aud",
},
expected: Config{
TargetPath: targetPath,
Expand All @@ -186,6 +187,7 @@ func TestParseConfig(t *testing.T) {
"my-pod-uid",
"my-pod-namespace",
"my-pod-sa-name",
"my-pod-sa-token",
},
Audience: "my-aud",
},
Expand Down
17 changes: 11 additions & 6 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,19 +84,24 @@ func (p *provider) createJWTToken(ctx context.Context, podInfo config.PodInfo, a
func (p *provider) login(ctx context.Context, client *api.Client, params config.Parameters) error {
p.logger.Debug("performing vault login")

jwt, err := p.createJWTToken(ctx, params.PodInfo, params.Audience)
if err != nil {
return err
jwt := params.PodInfo.ServiceAccountToken
if jwt == "" {
p.logger.Debug("no suitable token found in mount request, falling back to generating service account JWT")
var err error
jwt, err = p.createJWTToken(ctx, params.PodInfo, params.Audience)
if err != nil {
return err
}
}

req := client.NewRequest(http.MethodPost, "/v1/auth/"+params.VaultKubernetesMountPath+"/login")
err = req.SetJSONBody(map[string]string{
if err := req.SetJSONBody(map[string]string{
"role": params.VaultRoleName,
"jwt": jwt,
})
if err != nil {
}); err != nil {
return err
}

secret, err := vaultclient.Do(ctx, client, req)
if err != nil {
return fmt.Errorf("failed to login: %w", err)
Expand Down
1 change: 0 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ func realMain(logger hclog.Logger) error {
grpc.UnaryInterceptor(func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
startTime := time.Now()
serverLogger.Info("Processing unary gRPC call", "grpc.method", info.FullMethod)
serverLogger.Debug("Request contents", "req", req)
resp, err := handler(ctx, req)
serverLogger.Info("Finished unary gRPC call", "grpc.method", info.FullMethod, "grpc.time", time.Since(startTime), "grpc.code", status.Code(err), "err", err)
return resp, err
Expand Down
7 changes: 5 additions & 2 deletions test/bats/_helpers.bash
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@ wait_for_success() {
}

setup_postgres() {
# Setup postgres
# Setup postgres, pulling the image first to help avoid CI timeouts.
POSTGRES_IMAGE="$(awk '/image:/{print $NF}' $CONFIGS/postgres.yaml)"
docker pull "${POSTGRES_IMAGE}"
kind load docker-image "${POSTGRES_IMAGE}"
POSTGRES_PASSWORD=$(openssl rand -base64 30)
kubectl --namespace=test create secret generic postgres-root \
--from-literal=POSTGRES_USER="root" \
--from-literal=POSTGRES_PASSWORD="${POSTGRES_PASSWORD}"
kubectl --namespace=test apply -f $CONFIGS/postgres.yaml
kubectl wait --namespace=test --for=condition=Ready --timeout=5m pod -l app=postgres
kubectl wait --namespace=test --for=condition=Ready --timeout=10m pod -l app=postgres

# Configure vault to manage postgres
kubectl --namespace=csi exec vault-0 -- vault secrets enable database
Expand Down
5 changes: 5 additions & 0 deletions test/bats/provider.bats
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ setup(){
kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/db-role \
bound_service_account_names=nginx-db \
bound_service_account_namespaces=test \
audience=vault \
policies=db-policy \
ttl=20m
kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/kv-role \
bound_service_account_names=nginx-kv \
bound_service_account_namespaces=test \
audience=vault \
policies=kv-policy \
ttl=20m
kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/kv-custom-audience-role \
Expand All @@ -59,16 +61,19 @@ setup(){
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 \
audience=vault \
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 \
audience=vault \
policies=pki-policy \
ttl=20m
kubectl --namespace=csi exec vault-0 -- vault write auth/kubernetes/role/all-role \
bound_service_account_names=nginx-all \
bound_service_account_namespaces=test \
audience=vault \
policies=db-policy,kv-policy,pki-policy \
ttl=20m

Expand Down

0 comments on commit fdb36cd

Please sign in to comment.