gsm-controller is a Kubernetes controller that copies secrets from Google Secrets Manager into Kubernetes secrets. The controller watches Kubernetes secrets looking for an annotation, if the annotation is not found on the secret nothing more is done.
If the secret does have the annotation then the controller will query Google Secrets Manager, access the matching secret and copy the value into the Kubernetes secret and save it in the cluster.
Note in this example we are creating secrets and running the Kubernetes cluster in the same Google Cloud Project, the same
approach will work if Secrets Manager is enabled in a different project to store your secrets, just set the env var SECRETS_MANAGER_PROJECT_ID
below to a different GCP project id.
Set some environment variables:
export NAMESPACE=foo
export CLUSTER_NAME=test-cluster-foo
export PROJECT_ID=my-cool-project
export SECRETS_MANAGER_PROJECT_ID=my-cool-project # change if you want you secrets stored in Secrets Manager from another GCP project
First enable Google Secrets Manager
gcloud services enable secretmanager.googleapis.com --project $SECRETS_MANAGER_PROJECT_ID
Create a secret
- Using a file:
gcloud beta secrets create foo --replication-policy automatic --project $SECRETS_MANAGER_PROJECT_ID --data-file=-=my_secrets.yaml
- or for a single key=value secret:
echo -n bar | gcloud beta secrets create foo --replication-policy automatic --project $SECRETS_MANAGER_PROJECT_ID --data-file=-
So that gsm-controller
can access secrets in Google Secrets Manager so it can populate Kubernetes secrets in a namespace, it
requires a GCP service account with a role to access the secrets in a given GCP project.
kubectl create serviceaccount gsm-sa -n $NAMESPACE
kubectl annotate sa gsm-sa iam.gke.io/gcp-service-account=$CLUSTER_NAME-sm@$SECRETS_MANAGER_PROJECT_ID.iam.gserviceaccount.com
gcloud iam service-accounts create $CLUSTER_NAME-sm --project $SECRETS_MANAGER_PROJECT_ID
gcloud iam service-accounts add-iam-policy-binding \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:$PROJECT_ID.svc.id.goog[$NAMESPACE/gsm-sa]" \
$CLUSTER_NAME-sm@$SECRETS_MANAGER_PROJECT_ID.iam.gserviceaccount.com \
--project $SECRETS_MANAGER_PROJECT_ID
gcloud projects add-iam-policy-binding $SECRETS_MANAGER_PROJECT_ID \
--role roles/secretmanager.secretAccessor \
--member "serviceAccount:$CLUSTER_NAME-sm@$SECRETS_MANAGER_PROJECT_ID.iam.gserviceaccount.com" \
--project $SECRETS_MANAGER_PROJECT_ID
It can take a little while for permissions to propagate when using workload identity so it's a good idea to validate auth is working before continuing to the next step.
run a temporary pod with our kubernetes service accounts
kubectl run --rm -it \
--generator=run-pod/v1 \
--image google/cloud-sdk:slim \
--serviceaccount gsm-sa \
--namespace $NAMESPACE \
workload-identity-test
use gcloud to verify you can auth, it make take a few tries over a few minutes
gcloud auth list
add the helm repo or update it to get the latest charts
helm plugin install https://github.com/hayorov/helm-gcs
helm repo add jx3 https://jenkins-x-charts.github.io/repo
helm repo update
install the helm chart, this includes a kubernetes controller that always runs and watches for new or updated secrets. We also install a kubernetes CronJob that periodically triggers and checks for updated secret versions in Google Secret Manager.
helm install --set projectID=$SECRETS_MANAGER_PROJECT_ID gsm-controller jx3/gsm-controller
Now that the controller is running we can create a Kubernetes secret and annotate it with the id we stored the secret with above.
kubectl create secret generic my-secret
kubectl annotate secret my-secret jenkins-x.io/gsm-kubernetes-secret-key=credentials.json
kubectl annotate secret my-secret jenkins-x.io/gsm-secret-id=foo
After a short wait you should be able to see the base64 encoded data in the secret
kubectl get secret my-secret -oyaml
If not check the logs of the controller
kubectl logs deployment/gsm-controller
gcloud iam service-accounts create $CLUSTER_NAME-sm --project $SECRETS_PROJECT_ID
gcloud iam service-accounts keys create ~/.secret/key.json \
--iam-account $CLUSTER_NAME-sm@$PROJECT_ID.iam.gserviceaccount.com
gcloud projects add-iam-policy-binding $PROJECT_ID \
--role roles/secretmanager.secretAccessor \
--member "serviceAccount:$CLUSTER_NAME-sm@$PROJECT_ID.iam.gserviceaccount.com"
Create a GCP secret in the project your secrets are stored, assign the accessor role, download the key.json and...
export GOOGLE_APPLICATION_CREDENTIALS=~/.secret/key.json
make build
./build/gsm-controller my-cool-project
gsm-controller supports following Google Secret Manager's event notifications.
gcloud beta services identity create \
--service "secretmanager.googleapis.com" \
--project $SECRETS_MANAGER_PROJECT_ID
# ^ This will print out a service account which can be constructed as follows ˅
PROJECT_NUMBER=gcloud projects describe $SECRETS_MANAGER_PROJECT_ID --format='value(projectNumber)'
export SM_SERVICE_ACCOUNT=service-${PROJECT_NUMBER}@gcp-sa-secretmanager.iam.gserviceaccount.com
gcloud pubsub topics create "projects/${SECRETS_MANAGER_PROJECT_ID}/topics/secrets.events"
gcloud pubsub topics add-iam-policy-binding secrets.events \
--member "serviceAccount:${SM_SERVICE_ACCOUNT}" \
--role "roles/pubsub.publisher" \
--project $SECRETS_MANAGER_PROJECT_ID
Create a subscription for the Kubernetes cluster.
gcloud pubsub subscriptions create \
"projects/${SECRETS_MANAGER_PROJECT_ID}/subscriptions/secrets.events.$CLUSTER_NAME.gsm-pubsub" \
--topic "projects/${SECRETS_MANAGER_PROJECT_ID}/topics/secrets.events"
Allow reading the subscription
gcloud pubsub subscriptions add-iam-policy-binding secrets.events.$CLUSTER_NAME.gsm-pubsub \
--member "serviceAccount:$CLUSTER_NAME-sm@$SECRETS_MANAGER_PROJECT_ID.iam.gserviceaccount.com" \
--role "roles/pubsub.subscriber" \
--project $SECRETS_MANAGER_PROJECT_ID
When creating secrets, make sure to specify the topic
gcloud beta secrets create foo --replication-policy automatic \
--project $SECRETS_MANAGER_PROJECT_ID --data-file=-=my_secrets.yaml \
--topics "projects/${SECRETS_MANAGER_PROJECT_ID}/topics/secrets.events"
When following the install instructions, also set the deployment.pubsub
values.
helm install \
--set projectID=$SECRETS_MANAGER_PROJECT_ID \
--set deployment.pubsub.enabled=true \
--set deployment.pubsub.subscription=secrets.events.$CLUSTER_NAME.gsm-pubsub \
--set cron.schedule="23 */2 * * *" \
gsm-controller jx3/gsm-controller
The cron schedule of "23 */2 * * *"
means the cronjob will run every 2 hours rather than every 5 minutes.