IBM Cloud Docs
Secure secrets for apps that run in your Kubernetes cluster

Secure secrets for apps that run in your Kubernetes cluster

In this tutorial, you learn how to use IBM Cloud® Secrets Manager to manage secrets for applications that run your IBM Cloud Kubernetes Service cluster by using the External Secrets Operator open-source tool.

Alternatively, you can use the Kubernetes Service CLI plug-in to manage TLS and non-TLS secrets. To learn more about this approach, see Setting up Kubernetes Ingress.

You're a developer for a large organization, and your team is using Kubernetes Service to deploy containerized apps and services on IBM Cloud. In your current flow, you use Kubernetes Secrets to store the sensitive data, such as passwords and API keys, that are used by the apps and services that run in your cluster. To have more control over your application secrets, you want the ability to store your cluster secrets in an external secrets management service, where you can encrypt them at rest, monitor their activity, and easily manage them.

With Secrets Manager, you can centralize and secure the secrets that are used by the apps that run in your Kubernetes clusters. Rather than injecting your secrets at deployment time, you can configure your apps to securely retrieve secrets from Secrets Manager at run time. When it's time to rotate the secret, you can do so from Secrets Manager. For example, consider the following scenario:

The diagram shows the basic flow between Secrets Manager and your Kubernetes cluster.
Figure 1. External Secrets flow

  1. As a developer, you use Secrets Manager to store a secret for an application that you want to deploy in a Kubernetes cluster.
  2. Secrets Manager provides an ID for the secret. You include the ID in the ExternalSecrets configuration file for your app and you apply the configuration to the cluster.
  3. The External Secrets controller fetches the ExternalSecrets objects in the configuration file that you defined by using the Kubernetes API.
  4. At application run time, the controller retrieves the secret data from Secrets Manager, and converts the ExternalSecrets objects to Kubernetes secrets for your cluster.

This scenario features a third-party tool that can impact the compliance readiness of workloads that run in your Kubernetes cluster. If you add a community or third-party tool, keep in mind that you are responsible for maintaining the compliance of your apps, and working with the appropriate provider to troubleshoot any issues. For more information, see Your responsibilities with using IBM Cloud Kubernetes Service.

Before you begin

Before you get started, be sure that you have Administrator platform access so that you can create account credentials and provision resources. You also need the following prerequisites:

Set up your environment

To work with Secrets Manager and Kubernetes Service, you need to create a cluster and a Secrets Manager instance in your IBM Cloud account. You also need to configure permissions so that you can run operations against both services.

In this step, you set up an access environment by creating a service ID and an IBM Cloud API key. At the end of the tutorial, you can easily remove your resources if you no longer need them. Alternatively, you can use a trusted profile to authorize the External Secrets operator.

Create a service ID and API key

Start by creating the account credentials that you need to be able to run operations against Secrets Manager and Kubernetes Service.

  1. From the command line, log in to IBM Cloud through the IBM Cloud CLI.

    ibmcloud login
    

    If the login fails, run the ibmcloud login --sso command to try again. The --sso parameter is required when you log in with a federated ID. If this option is used, go to the link listed in the CLI output to generate a one-time passcode.

  2. Create a service ID and set it as an environment variable.

    export SERVICE_ID=`ibmcloud iam service-id-create kubernetes-secrets-tutorial --description "A service ID for testing ESO integration" --output json | jq -r ".id"`; echo $SERVICE_ID
    
  3. Assign the service ID permissions to read secrets from Secrets Manager.

    ibmcloud iam service-policy-create $SERVICE_ID --roles "SecretsReader" --service-name secrets-manager
    

    By assigning SecretsReader service access, the External Secrets controller has the correct level of access to read secrets from Secrets Manager and populate them in a Kubernetes cluster.

  4. Create an IBM Cloud API key for your service ID.

    export IBM_CLOUD_API_KEY=`ibmcloud iam service-api-key-create kubernetes-secrets-tutorial $SERVICE_ID --description "An API key for testing ESO integration." --output json | jq -r ".apikey"`
    

    You use this API key later to configure Secrets Manager for your cluster deployment.

Create a Kubernetes cluster and Secrets Manager instance

Create a Kubernetes cluster and an instance of Secrets Manager in your IBM Cloud account.

You can create one free Kubernetes cluster and Secrets Manager service instance per IBM Cloud account. If you already have both resources in your account, you can use your existing free cluster and Secrets Manager instance to complete the tutorial.

  1. From the command line, select the account, region, and resource group where you want to create a Secrets Manager service instance.

    In this tutorial, you interact with the Dallas region. If you're logged in to a different region, be sure to set Dallas as your target region by running the following command.

    ibmcloud target -r us-south -g default
    
  2. Create a Kubernetes cluster.

    ibmcloud ks cluster create classic --zone dal10 --flavor free --name my-test-cluster
    
  3. Create a Secrets Manager instance.

    ibmcloud resource service-instance-create my-secrets-manager secrets-manager trial us-south
    

    Provisioning for both Secrets Manager and your Kubernetes cluster takes 5 - 15 minutes to complete.

  4. Before you continue to the next step, verify that your cluster and Secrets Manager instance are provisioned successfully.

    1. Verify that the deployment of your worker node is complete.

      ibmcloud ks worker ls --cluster my-test-cluster
      

      When your worker node is finished provisioning, the status changes to Ready.

      ID                                                       Public IP       Private IP      Flavor   State          Status                Zone    Version
      kube-c39pf4ld0m87o3fv1utg-mytestclust-default-000000dd   169.xx.xx.xxx   10.xxx.xx.xxx   free     normal   Ready   mex01   1.20.7_1543
      
    2. Next, verify that your Secrets Manager instance provisioned successfully.

      ibmcloud resource service-instance my-secrets-manager
      

      When the instance is finished provisioning, the state changes to Active.

      Name:                  my-secrets-manager
      ID:                    crn:v1:bluemix:public:secrets-manager:us-south:a/f047b55a3362ac06afad8a3f2f5586ea:fe06948b-0c6b-4183-8d4b-e6c1d38ff65f::
      GUID:                  fe06948b-0c6b-4183-8d4b-e6c1d38ff65f
      Location:              us-south
      Service Name:          secrets-manager
      Service Plan Name:     trial
      Resource Group Name:   default
      State:                 active
      Type:                  service_instance
      Sub Type:
      Created at:            2021-01-06T17:11:32Z
      Created by:            zara@example.com
      Updated at:            2021-03-31T02:33:26Z
      
  5. Set the context for your Kubernetes cluster in the CLI.

    ibmcloud ks cluster config --cluster my-test-cluster
    
  6. Verify that kubectl commands run properly and that the Kubernetes context is set to your cluster.

    kubectl config current-context
    

    Example output:

    my-test-cluster/<your_cluster_ID>
    

Create a trusted profile

A trusted profile enables the External Secrets operator to read from Secrets Manager, without having to create a service ID or manage an API key.

  1. Get the CRNs for your Secrets Manager instance and Kubernetes cluster.

    CLUSTER_CRN=$(ibmcloud ks cluster get --cluster my-test-cluster --output json | jq -r '.crn')
    SECRETS_MANAGER_CRN=$(ibmcloud resource service-instance my-secrets-manager --output JSON | jq -r '.[0].crn')
    
  2. Create the profile.

    ibmcloud iam trusted-profile-create 'External Secrets'
    
  3. Authorize the Kubernetes cluster to use the trusted profile.

    ibmcloud iam trusted-profile-rule-create 'External Secrets' --name kubernetes --type Profile-CR --conditions claim:namespace,operator:EQUALS,value:external-secrets --conditions claim:name,operator:EQUALS,value:external-secrets --conditions claim:crn,operator:EQUALS,value:$CLUSTER_CRN --cr-type IKS_SA
    
  4. Create an access policy that allows the trusted profile to read secrets from your Secrets Manager instance.

    ibmcloud iam trusted-profile-policy-create 'External Secrets' --roles SecretsReader --service-instance $SECRETS_MANAGER_CRN --service-name secrets-manager
    

Prepare your Secrets Manager instance

Finally, configure your Secrets Manager instance to start working with secrets.

  1. From the command line, verify that you can access the Secrets Manager CLI plug-in.

    ibmcloud secrets-manager --help
    

    Don't have the plug-in yet? To install the Secrets Manager CLI plug-in, run ibmcloud plugin install secrets-manager.

  2. Export an environment variable with your unique Secrets Manager API endpoint URL.

    export SECRETS_MANAGER_URL=`ibmcloud resource service-instance my-secrets-manager --output json | jq -r '.[].dashboard_url | .[0:-3]'`; echo $SECRETS_MANAGER_URL
    
  3. Create a secret group for your instance.

    Secret groups are a way to organize and control who on your team has access to specific secrets in your instance. To create a secret group from the IBM Cloud CLI, you use the ibmcloud secrets-manager secret-group-create command. Run the following command to create a secret group and store its ID as an environment variable.

    export SECRET_GROUP_ID=`ibmcloud secrets-manager secret-group-create --name my-test-secret-group --description "Read and write to my test app" --output json --service-url $SECRETS_MANAGER_URL | jq -r '.id'`; echo $SECRET_GROUP_ID
    

    Using a Windows™ command prompt (cmd.exe) or PowerShell? If you encounter errors with passing JSON content on the command line, you might need to adjust the strings for quotation-escaping requirements that are specific to your operating system. For more information, see Using quotation marks with strings in the IBM Cloud CLI.

    Success! Now you can store the secret in Secrets Manager that you want to populate in your Kubernetes cluster. Continue to the next step.

Create a secret in Secrets Manager

Secrets are application-specific and can vary based on the individual app or service that requires them. A secret might consist of a username, password, API key, or any other type of credential.

Secrets Manager supports various types of secrets that you can create and manage in the service. For example, if you need to manage an API key for an app that is protected by IBM Cloud IAM authentication, you can create an IAM credential. Or, if you need to manage a secret that can hold any type of structured or unstructured data, you can create an arbitrary secret.

In this tutorial, you create a username and password as an example. To create a secret from the IBM Cloud CLI, you use the ibmcloud secrets-manager secret-create command. Run the following command to create the secret and store its ID as an environment variable.

export SECRET_ID=`ibmcloud secrets-manager secret-create --secret-type=username_password --secret-name example_username_password --username-password-username user123 --username-password-password cloudy-rainy-coffee-book --secret-labels "my-test-cluster, tutorial" --secret-group-id $SECRET_GROUP_ID --output json $SECRETS_MANAGER_URL | jq -r '.id'`; echo $SECRET_ID

Be sure to update instance_id and region to yours.

The output shows the ID of your newly created secret. For example:

e0246cea-d668-aba7-eef2-58ca11ad3707

Set up External Secrets Operator

Now that you have a secret for your application, you can set up the External Secrets Operator tool for your cluster. This package configures the connection between Secrets Manager and your cluster by creating ExternalSecrets objects that are converted to Kubernetes secrets for your application.

External Secrets Operator is an open source tool that is not maintained by IBM. For more information about this tool or to troubleshoot any issues, refer to the project documentation.

Configure External Secrets Operator for your cluster

Kubernetes

First, add external-secrets resources to your cluster by installing the official Helm chart. For more installation options, check out the getting started guide.

  1. Run the following command to install External Secrets Operator helm repository:

    helm repo add external-secrets https://charts.external-secrets.io
    
  2. Configure authentication between External Secrets Operator and Secrets Manager.

    If you're using a service ID to authenticate:

    kubectl -n default create secret generic secret-api-key --from-literal=apikey=$IBM_CLOUD_API_KEY
    helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace --set installCRDs=true
    

    If you're using a trusted profile to authenticate:

    echo '
    installCRDs: true
    extraVolumes:
    - name: sa-token
      projected:
        defaultMode: 420
        sources:
        - serviceAccountToken:
            path: sa-token
            expirationSeconds: 3600
            audience: iam
    extraVolumeMounts:
    - mountPath: /var/run/secrets/tokens
      name: sa-token
    webhook:
      extraVolumes:
      - name: sa-token
        projected:
          defaultMode: 420
          sources:
          - serviceAccountToken:
              path: sa-token
              expirationSeconds: 3600
              audience: iam
      extraVolumeMounts:
      - mountPath: /var/run/secrets/tokens
        name: sa-token' >values.yml
    helm install external-secrets external-secrets/external-secrets -n external-secrets --create-namespace -f values.yml
    

OpenShift

  1. Install the External Secrets Operator by creating the following resources:

    echo '
    apiVersion: v1
    kind: Namespace
    metadata:
      name: external-secrets-operator
    ---
    apiVersion: operators.coreos.com/v1
    kind: OperatorGroup
    metadata:
      name: external-secrets-operator
      namespace: external-secrets-operator
    spec:
      targetNamespaces:
        - external-secrets-operator
    ---
    apiVersion: operators.coreos.com/v1beta1
    kind: Subscription
    metadata:
      name: external-secrets-operator
      namespace: external-secrets-operator
    spec:
      channel: stable
      installPlanApproval: Automatic
      name: external-secrets-operator
      source: community-operators
      sourceNamespace: openshift-marketplace
    ' | oc create -f-
    
  2. Configure authentication between External Secrets Operator and Secrets Manager.

    If you're using a service ID to authenticate:

    echo '
    apiVersion: operator.external-secrets.io/v1beta1
    kind: OperatorConfig
    metadata:
      name: cluster
      namespace: external-secrets-operator
    spec: {}
    ' | oc create -f-
    

    If you're using a trusted profile to authenticate:

    echo '
    apiVersion: operator.external-secrets.io/v1alpha1
    kind: OperatorConfig
    metadata:
      name: cluster
      namespace: external-secrets-operator
    spec:
      extraVolumeMounts:
      - mountPath: /var/run/secrets/tokens
        name: sa-token
      extraVolumes:
      - name: sa-token
        projected:
          defaultMode: 420
          sources:
          - serviceAccountToken:
              audience: iam
              expirationSeconds: 3600
              path: sa-token
      webhook:
        extraVolumeMounts:
        - mountPath: /var/run/secrets/tokens
          name: sa-token
        extraVolumes:
        - name: sa-token
          projected:
            defaultMode: 420
            sources:
            - serviceAccountToken:
                audience: iam
                expirationSeconds: 3600
                path: sa-token
    ' | oc create -f-
    

Update your app configuration

After you install External Secrets Operator in your cluster, you can define Secrets Manager as the secrets backend for your application. Start by creating a configuration file that targets the secret in Secrets Manager that you want to use.

  1. In the root directory of your application, create an external-secrets-example.yml file.

    touch external-secrets-example.yml
    
  2. Modify the file to include information about the secret that you want to fetch from your Secrets Manager instance.

    apiVersion: external-secrets.io/v1beta1
    kind: SecretStore
    metadata:
      name: ibmcloud-secrets-manager-example
    spec:
      provider:
        ibm:
          serviceUrl: <endpoint_url>
          auth:
            secretRef:
              secretApiKeySecretRef:
                name: secret-api-key
                key: apikey
    ---
    apiVersion: external-secrets.io/v1beta1
    kind: ExternalSecret
    metadata:
      name: ibmcloud-secrets-manager-example
    spec:
      secretStoreRef:
        name: ibmcloud-secrets-manager-example
        kind: SecretStore
      target:
        name: ibmcloud-secrets-manager-example
      data:
      - secretKey: username
        remoteRef:
          property: username
          key: username_password/<SECRET_ID>
      - secretKey: password
        remoteRef:
          property: password
          key: username_password/<SECRET_ID>
    

    There are two ways to pull secrets - by Secret ID or Secret name. To pull by Secret ID use secret_type/secret_id. To pull by Secret name use secret_group/secret_type/secret_name.

    Replace <endpoint_url> with the Secrets Manager endpoint URL that you retrieved earlier. Replace <SECRET_ID> with the unique ID of the secret that you created in the previous step.

    If you're using a trusted profile to authenticate, replace the auth block with the following snippet.

          auth:
            containerAuth:
              profile: "External Secrets"
              iamEndpoint: https://iam.cloud.ibm.com
              tokenLocation: /var/run/secrets/tokens/sa-token
    
  3. Apply the configuration to your cluster.

    kubectl apply -f external-secrets-example.yml
    
  4. Verify that the External Secrets Operator is able to fetch the secret that is stored in your Secrets Manager instance.

    kubectl get secret ibmcloud-secrets-manager-example -o json | jq '.data | map_values(@base64d)'
    

    Example output:

    {
        "password": "cloudy-rainy-coffee-book",
        "username": "user123"
    }
    

    Success! You're now able to fetch the secret data that is stored in your Secrets Manager instance. Continue to the next step.

Deploy an app to the cluster

Finally, you can deploy an application in your cluster that uses the Secrets Manager secret that you defined in the external-secret-example.yml file. At application run time, the secret data that is fetched from Secrets Manager is converted to a Kubernetes secret that can be used by your cluster.

Looking for examples on how to deploy an app? Check out Deploying Kubernetes-native apps in clusters to find out more about deploying a single instance of an app.

(Optional) Clean up resources

If you no longer need the resources that you created in this tutorial, you can complete the following steps to remove them from your account.

  1. Delete your test Kubernetes cluster.

    ibmcloud ks cluster rm --cluster my-test-cluster
    
  2. Delete your test Secrets Manager instance.

    ibmcloud resource service-instance-delete my-secrets-manager
    
  3. Delete your authorization.

    If you're using a service ID.

    ibmcloud iam service-id-delete $SERVICE_ID
    

    If you're working with a trusted profile.

    ibmcloud iam trusted-profile-delete 'External Secrets'
    

Best practices for using External Secrets Operator with Secrets Manager

Secrets Manager sets a limit on the rate in which a client can send API requests to it. The limit is 20 calls per second for all API methods.

As you construct your YAML document, keep in mind that each key in the data section is polled periodically by using REST from the Secrets Manager instance. Be aware that:

  1. By default, the polling interval is set to 1 hour. You can set this value by using spec.refreshInterval in the External Secrets template. The interval can be expressed in units of s, m, or h.
  2. If you set the YAML to fetch a Secrets Manager secret by name rather than ID (keyByName: true), an additional call is made by ESO to fetch the relevant secret ID. For more information, see the External Secrets documentation.

Next steps

Great job! In this tutorial, you learned how to set up Secrets Manager to securely populate application secrets to your cluster. Check out more resources to help you get started with Secrets Manager.