Use the Secrets Proxy
This guide explains how to configure the Secrets Proxy on an existing UXP instance to retrieve secrets from an external HashiCorp Vault.
The Secret Store add-on securely stores your secrets instead of storing them as a Kubernetes secret in the cluster.
The example composition creates IAM users and access keys. Rather than writing connection secrets to Kubernetes, the Secrets Proxy transparently reads and writes them to Vault.
Prerequisites
Before you begin, ensure you have:
- Kind installed
- kubectl installed
- Helm installed
- AWS CLI installed
- The Vault CLI installed
- The
upCLI installed - Docker running
- AWS credentials with access to the Upbound ECR registry
- A running HashiCorp Vault instance (or use the bootstrap section below)
Configure ECR access
Set environment variables for pulling UXP images from the Upbound ECR registry:
export AWS_ACCESS_KEY_ID=<your-access-key-id>
export AWS_SECRET_ACCESS_KEY=<your-secret-access-key>
export AWS_DEFAULT_REGION=us-west-1
ECR_REGISTRY="609897127049.dkr.ecr.us-west-1.amazonaws.com"
ECR_PASSWORD=$(aws ecr get-login-password --region us-west-1)
VERSION=2.2.0-up.1.rc.0.80.ga9163a05
Create a Kind cluster
kind create cluster --name crossplane-secrets
kind export kubeconfig --name crossplane-secrets
Bootstrap a test Vault instance
If you don't have a Vault instance, you can deploy one in dev mode into your
cluster using the official Helm chart. Dev mode starts with a pre-configured
root token and KV v2 secrets engine enabled at secret/. It's not persistent
and is only suitable for testing.
- Add the HashiCorp Helm repo and install Vault in dev mode:
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
helm install vault hashicorp/vault \
--namespace vault-system \
--create-namespace \
--set server.dev.enabled=true \
--set server.dev.devRootToken=root
-
Wait for the Vault pod to be ready, then set your CLI environment variables:
kubectl wait pod/vault-0 -n vault-system --for=condition=Ready --timeout=60s
kubectl port-forward svc/vault 8200:8200 -n vault-system &
export VAULT_ADDR=http://127.0.0.1:8200
export VAULT_TOKEN=rootThe Secrets Proxy sidecar reaches Vault in-cluster at
http://vault.vault-system.svc.cluster.local:8200.
Dev mode Vault is in-memory only. All data is lost when the pod restarts.
Create an ECR pull secret
kubectl create ns crossplane-system
kubectl -n crossplane-system create secret docker-registry ecr-pull-secret \
--docker-server="${ECR_REGISTRY}" \
--docker-username=AWS \
--docker-password="${ECR_PASSWORD}"
Install UXP with Secrets Proxy
The --set upbound.secretsProxy.enabled=true flag enables the Secrets Proxy
component. To enable it on an existing UXP installation, run helm upgrade
with the same flag against your existing release.
helm upgrade --install crossplane \
oci://${ECR_REGISTRY}/upbound/crossplane \
--version "${VERSION#v}" \
--namespace crossplane-system \
--create-namespace \
--set upbound.manager.image.repository="${ECR_REGISTRY}/upbound/controller-manager" \
--set upbound.manager.image.tag="v${VERSION}" \
--set upbound.manager.metering.image.repository="${ECR_REGISTRY}/upbound/controller-manager" \
--set upbound.manager.metering.image.tag="v${VERSION}" \
--set 'upbound.manager.imagePullSecrets[0].name=ecr-pull-secret' \
--set 'upbound.manager.metering.imagePullSecrets[0].name=ecr-pull-secret' \
--set 'imagePullSecrets[0]=ecr-pull-secret' \
--set upbound.secretsProxy.enabled=true
Wait for all pods to be ready:
kubectl get pods -n crossplane-system -w
Apply a development license
up uxp license apply --dev
up uxp license show
Configure Vault
Store the AWS credentials that Crossplane providers use to manage resources, and create a policy granting the Secrets Proxy read access.
-
Store the AWS credentials in Vault. The Secrets Proxy serves these to providers as if they were a Kubernetes Secret, using the AWS credentials
iniformat expected by the provider family:vault kv put secret/crossplane-system/aws-official-creds credentials="
[default]
aws_access_key_id = <AWS_ACCESS_KEY_ID>
aws_secret_access_key = <AWS_SECRET_ACCESS_KEY>
" -
Create a Vault policy that grants read access to secrets in the
crossplane-systemscope:vault policy write crossplane-policy - <<'EOF'
path "secret/data/crossplane-system/*" {
capabilities = ["read", "list"]
}
EOF
Register your cluster with Vault
Configure Vault's Kubernetes auth method to trust service accounts running in your UXP cluster. This allows the Secrets Proxy sidecar to authenticate to Vault and read secrets on behalf of provider pods.
-
Create a service account for Vault token review and grant it permission to validate tokens cluster-wide:
kubectl create serviceaccount vault-auth -n crossplane-system
kubectl create clusterrolebinding vault-auth-delegator \
--clusterrole=system:auth-delegator \
--serviceaccount=crossplane-system:vault-auth -
Generate a long-lived token for the reviewer service account:
REVIEWER_JWT=$(kubectl create token vault-auth \
-n crossplane-system --duration=8760h) -
Retrieve the cluster CA certificate and API server URL:
KUBE_CA_CERT=$(kubectl config view --raw --minify --flatten \
--output='jsonpath={.clusters[].cluster.certificate-authority-data}' \
| base64 --decode)
KUBE_HOST=$(kubectl config view --raw --minify --flatten \
--output='jsonpath={.clusters[].cluster.server}') -
Enable the Kubernetes auth method in Vault and configure it with the cluster details:
vault auth enable kubernetes
vault write auth/kubernetes/config \
kubernetes_host="${KUBE_HOST}" \
kubernetes_ca_cert="${KUBE_CA_CERT}" \
token_reviewer_jwt="${REVIEWER_JWT}" -
Create a Vault role that binds all service accounts in
crossplane-systemto the policy:vault write auth/kubernetes/role/crossplane \
bound_service_account_names="*" \
bound_service_account_namespaces="crossplane-system" \
policies=crossplane-policy \
ttl=1h
Install the Secret Store add-on
Apply the Secret Store add-on to deploy the secret-store-vault component that
connects the Secrets Proxy to your Vault instance:
apiVersion: pkg.upbound.io/v1beta1
kind: AddOn
metadata:
name: secret-store-vault
spec:
package: xpkg.upbound.io/upbound/secret-store-vault-addon:v0.1.0
packagePullPolicy: Always
---
apiVersion: pkg.upbound.io/v1beta1
kind: AddOnRuntimeConfig
metadata:
name: default
spec:
helm:
values:
imagePullSecrets:
- name: ecr-pull-secret
kubectl apply -f secretstore.yaml
Configure the Secrets Proxy webhook
Apply the webhook configuration to inject the Secrets Proxy sidecar into Crossplane and provider pods. The webhook restarts all matching pods on apply:
apiVersion: secretsproxy.upbound.io/v1alpha1
kind: WebhookConfig
metadata:
name: crossplane-app
spec:
objectSelector:
matchLabels:
app: crossplane
---
apiVersion: secretsproxy.upbound.io/v1alpha1
kind: WebhookConfig
metadata:
name: crossplane-provider
spec:
objectSelector:
matchExpressions:
- key: pkg.crossplane.io/provider
operator: Exists
kubectl apply -f webhookconfig.yaml
Install providers and functions
-
Install the AWS provider family and IAM provider. Because the webhook matches pods with the
pkg.crossplane.io/providerlabel, provider pods start with the Secrets Proxy sidecar already injected:apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-aws-iam
spec:
package: xpkg.upbound.io/upbound/provider-aws-iam:v2.2.0
ignoreCrossplaneConstraints: true
---
apiVersion: pkg.crossplane.io/v1
kind: Provider
metadata:
name: upbound-provider-family-aws
spec:
package: xpkg.upbound.io/upbound/provider-family-aws:v2.2.0
ignoreCrossplaneConstraints: truekubectl apply -f providers.yaml -
Install the pipeline functions:
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
name: function-go-templating
spec:
package: xpkg.crossplane.io/crossplane-contrib/function-go-templating:v0.11.2
---
apiVersion: pkg.crossplane.io/v1
kind: Function
metadata:
name: function-auto-ready
spec:
package: xpkg.crossplane.io/crossplane-contrib/function-auto-ready:v0.6.0kubectl apply -f functions.yaml
- Wait for providers and functions to become healthy, then create the provider
config. The
aws-official-credssecret is stored in Vault. The Secrets Proxy intercepts the Secret API call and serves it transparently:
apiVersion: aws.m.upbound.io/v1beta1
kind: ClusterProviderConfig
metadata:
name: default
namespace: crossplane-system
spec:
credentials:
secretRef:
key: credentials
name: aws-official-creds
namespace: crossplane-system
source: Secret
kubectl apply -f provider-config.yaml
Deploy the composition
Apply the UserAccessKey XRD and composition. This composition creates an IAM
user and two access keys, writing connection details back to Vault through the
Secrets Proxy:
kubectl apply -f comp.yaml
Create the composite resource
-
Edit
xr.yamland replace<put-your-initials>with your initials in both themetadata.nameandspec.writeConnectionSecretToRef.namefields to create a unique name:apiVersion: example.org/v1alpha1
kind: UserAccessKey
metadata:
namespace: default
name: <your-initials>-keys
spec:
writeConnectionSecretToRef:
name: <your-initials>-keys-connection-details -
Apply the resource and verify reconciliation:
kubectl apply -f xr.yaml
kubectl get managed
kubectl get compositeConnection details are stored in Vault, not in Kubernetes. Confirm no new secrets were created:
kubectl get secret -n crossplane-system
Clean up
Delete the composite resource. The garbage collector automatically removes the associated secrets from Vault:
kubectl delete -f xr.yaml