Open Source, WTF Is Cloud Native

Tutorial: How to Set External-Secrets with GCP Secret Manager

In this second article on our "how to" series on External Secrets we’re looking at the Google Cloud Secret Manager. We are going to configure External Secrets to use a Secret Manager instance as a secret provider.

But first, a quick recap. initiated by Container Solutions and GoDaddy, External Secrets is an Open Source Kubernetes operator that integrates external secret management systems including AWS Secrets Manager, HashiCorp Vault, Google Secrets Manager, Azure Key Vault, amongst others, and is designed to enable the synchronization of secrets from external APIs into Kubernetes. You can find comprehensive project documentation here.

In this tutorial we will configure GCP Secret Manager in order to have a safe way to access secrets, and then configure External-Secrets to fetch information from our Secret Manager. A first post in this series covered the AWS Secrets Manager, and in future articles we’ll explore how to set up external-secrets with Hashicorp Vault and Azure KeyVault.

Getting started

To properly follow this tutorial, make sure you have installed the following tools:

Configuration

External Secrets supports the configuration of several authentication methods for the GCP Secret Manager provider. In this guide we are using authentication through Service Account keys, as it doesn’t need any other GCP Resources. We are going to go through the following steps:

  1. Set up GCP Secret Manager
  2. Configure External-Secrets

Set up GCP Secret Manager

The following steps are all going to be used with gcloud CLI. The first thing we should do is configure properly the setup to the target project which we would like the Secret Manager to be, and enable its APIs:

project=<project-name-here>
gcloud gcloud config set project $project
gcloud services enable secretmanager.googleapis.com

After that, we can create a secret inside Secret Manager, and then configure a service account to access it

 

echo -ne '{"password":"itsasecret"}' | gcloud secrets create mysecret --data-file=-
gcloud iam service-accounts create external-secrets
gcloud secrets add-iam-policy-binding mysecret --member "serviceAccount:external-secrets@$project.iam.gserviceaccount.com" --role "roles/secretmanager.secretAccessor"

Here, we are following the least privilege principle, as we are only binding mysecret to the external-secrets service account. You can achieve a more wider permission through the command line gcloud projects add-iam-policy-binding $project --member "serviceAccount:external-secrets@$project.iam.gserviceaccount.com" --role "roles/secretmanager.secretAccessor", which would give external-secrets service account permission to access all secrets within the project.

After we have our service account configured, the next step is to create a key to that service account:

gcloud iam service-accounts keys create key.json --iam-account=external-secrets@$project.iam.gserviceaccount.com
kubectl create secret generic gcpsm-secret --from-file=secret-access-credentials=key.json

Configuring External Secrets

After setting up GCP Secret Manager, the next step is to create a SecretStore and an ExternalSecrets to fetch mysecret object that we’ve created earlier:

>cat <<EOF | kubectl apply -f - 
apiVersion: external-secrets.io/v1alpha1
kind: SecretStore
metadata:
  name: gcp-backend
spec:
  provider:
      gcpsm:                                                  
        auth:
          secretRef:
            secretAccessKeySecretRef:
              name: gcpsm-secret                        
              key: secret-access-credentials                              
        projectID: $project
---
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
  name: gcp-external-secret
spec:
  secretStoreRef:
    kind: SecretStore
    name: gcp-backend          
  target:                                                             
    name: secret-to-be-created 
  data:                                                       
  - secretKey: password_file 
    remoteRef:            
      key: mysecret       
EOF

And that’s it! We can check that our Secret has been created in Kubernetes:


kubectl get secret secret-to-be-created -o jsonpath='{.data.password_file}' | base64 -d
{"password":"itsasecret"}% 

We can also check ExternalSecrets status information:



kubectl get es
NAME                  STORE         REFRESH INTERVAL   STATUS
gcp-external-secret   gcp-backend   1h                 SecretSynced

A Note on SecretStore object

When we deploy a SecretStore reference to a backend provider, External Secrets needs to have any credentials information required to access that prodiver in the same namespace as the SecretStore is deployed. This allows developers to handle Secret Manager access as they wish using their own credentials, in a self-service manner.

However, this approach is not always desirable. For example, sometimes compliance policies may require us to isolate any backend access from the developer. To allow that External Secrets comes with a second backend resource called ClusterSecretStore. This is a cluster-scoped kubernetes object which can fetch credentials from any namespace, allowing users to deploy a Shared-Resource topology, as per picture below:

wtf diagram Dec_2

To see how this works, let's start by first creating two separate namespaces

kubectl create namespace admin-namespace
kubectl create namespace developer-namespace

Then, let’s create our credentials secret within the admin-namespace, and create our ClusterSecretStore

kubectl create secret generic gcpsm-secret --namespace admin-namespace --from-file=secret-access-credentials=key.json

cat <<EOF | kubectl apply -f - 
apiVersion: external-secrets.io/v1alpha1
kind: ClusterSecretStore
metadata:
  name: gcp-backend
spec:
  provider:
      gcpsm:                                                  
        auth:
          secretRef:
            secretAccessKeySecretRef:
              name: gcpsm-secret                        
              key: secret-access-credentials
              namespace: admin-namespace                              
        projectID: $project
EOF

Note that the only difference is that we need to specify a namespace where the credentials are stored.

After, we can simulate a developer experience to create an ExternalSecret within their own namespace:

cat <<EOF | kubectl apply -f -
apiVersion: external-secrets.io/v1alpha1
kind: ExternalSecret
metadata:
  name: gcp-external-secret-no-credentials
  namespace: developer-namespace
spec:
  secretStoreRef:
    kind: ClusterSecretStore
    name: gcp-backend          
  target:                                                             
    name: secret-to-be-created 
  data:                                                       
  - secretKey: password_file 
    remoteRef:            
      key: mysecret       
EOF

And that’s it! We can see that the only information the developer needed to know is the ClusterSecretStore name that they should reference, and the secret key they want to sync within their namespace.

Of course, we can check the secret has been fetch with the following command:

kubectl get secret -n developer-namespace secret-to-be-created -o jsonpath='{.data.password_file}' | base64 -d
{"password":"itsasecret"}%

And to check ExternalSecret status:

kubectl get es -n developer-namespace
NAME                                 STORE         REFRESH INTERVAL   STATUS
gcp-external-secret-no-credentials   gcp-backend   1h                 SecretSynced

Wrapping things up

In this post we’ve seen how to configure External Secrets to allow using the GCP Secret Manager as a provider. We also have explored different deployment setups: As a Service and Shared Store. You can check more about these subjects in the official external-secrets documentation.

Wrapping up what we have done:

  • Set up GCP Secret Manager
  • Configure External Secrets
  • Deploy Shared Store topology

In the next post, we’ll see how to configure Hashicorp Vault as a SecretStore! Stay tuned!

Comments
Leave your Comment