Write a templated policy to work with multiple applications
HashiCorp Vault is known for its ability to provide secrets at scale. An organization may have many applications that can potentially benefit from Vault's centralized secrets management. This tutorial shares patterns for onboarding applications to Vault while minimizing policy management overhead.
You will apply the pattern to a common use case where Vault provides secrets for applications running in Kubernetes clusters. An organization may have hundreds of applications spread out across multiple clusters. Creating a unique policy per application is an easy way to provide initial access. However, without careful planning, it may be difficult to effectively manage and audit application policies over time.
In this tutorial, you will use Vault's identity system and policy path templating features to create a single ACL policy that allows multiple applications to access unique secret paths in a secure and auditable way. This will reduce the total number of policies that need to be managed. The approach can be generalized to other use cases with other Auth Methods and secrets engines.
Prerequisites
This tutorial requires the Kubernetes command-line interface (CLI) and the Helm CLI installed, minikube, the Vault and Consul Helm charts, the sample web application, and additional configuration to bring it all together.
Next, retrieve the web application and additional configuration by cloning the hashicorp-education/learn-vault-policy-path-templates repository from GitHub.
$ git clone https://github.com/hashicorp-education/learn-vault-policy-path-templates
This repository contains supporting content for all of the Vault learn tutorials. The content specific to this tutorial can be found within a sub-directory.
Go into the learn-vault-policy-path-templates
directory.
$ cd learn-vault-policy-path-templates
Working directory
This tutorial assumes that the remainder of commands are executed within this directory.
Start minikube
minikube is a CLI tool that provisions and manages the lifecycle of single-node Kubernetes clusters locally inside Virtual Machines (VM) on your system.
Start a Kubernetes cluster.
$ minikube start --driver=docker😄 minikube v1.12.1 on Darwin 10.15.6✨ Using the docker driver based on user configuration👍 Starting control plane node minikube in cluster minikube🔥 Creating docker container (CPUs=2, Memory=1991MB) ...🐳 Preparing Kubernetes v1.18.3 on Docker 19.03.2 ...🔎 Verifying Kubernetes components...🌟 Enabled addons: default-storageclass, storage-provisioner🏄 Done! kubectl is now configured to use "minikube"
The initialization process takes several minutes as it retrieves any necessary dependencies and executes various container images.
Verify the status of the minikube cluster.
$ minikube status minikubetype: Control Planehost: Runningkubelet: Runningapiserver: Runningkubeconfig: Configured
Additional waiting
Even if this last command completed successfully, you may have to wait for minikube to be available. If an error is displayed, try again after a few minutes.
The host, kubelet, apiserver report that they are running. The
kubectl
, a command line interface (CLI) for running commands against Kubernetes cluster, is also configured to communicate with this recently started cluster.
Install the Vault Helm chart
The Vault Helm chart is able to install a Vault pod, with a vault
service, a
and vault
service account.
Add the HashiCorp Helm repository.
$ helm repo add hashicorp https://helm.releases.hashicorp.com"hashicorp" has been added to your repositories
Update all the repositories to ensure
helm
is aware of the latest versions.$ helm repo updateHang tight while we grab the latest from your chart repositories......Successfully got an update from the "hashicorp" chart repositoryUpdate Complete. ⎈Happy Helming!⎈
Install the latest version of the Vault server running in development mode.
$ helm install vault hashicorp/vault --set "server.dev.enabled=true"NAME: vault## ...
The Vault pod is deployed in the default namespace.
Display all the pods within the default namespace.
$ kubectl get podsNAME READY STATUS RESTARTS AGEvault-0 1/1 Running 0 80svault-agent-injector-5945fb98b5-tpglz 1/1 Running 0 80s
The
vault-0
pod runs a Vault server in development mode.Development mode
Running a Vault server in development is automatically initialized and unsealed. This is ideal in a learning environment but NOT recommended for a production environment.
Wait until the
vault-0
pod is running and ready (1/1
).
Configure Kubernetes auth method
Start an interactive shell session on the
vault-0
pod.$ kubectl exec -it vault-0 -- /bin/sh/ $
Your system prompt is replaced with a new prompt
/ $
. Commands issued at this prompt are executed on thevault-0
container.Based on the path naming convention, set the
CLUSTER_NAME
environment value to "minikube" since you are running Vault on minikube.$ export CLUSTER_NAME="minikube"
Enable the Kubernetes authentication method.
$ vault auth enable -path=$CLUSTER_NAME kubernetesSuccess! Enabled kubernetes auth method at: minikube/
Configure the Kubernetes authentication method to use the location of the Kubernetes host.
$ vault write auth/$CLUSTER_NAME/config \ kubernetes_host="https://$KUBERNETES_PORT_443_TCP_ADDR:443"
Output:
Success! Data written to: auth/minikube/config
The environment variable
KUBERNETES_PORT_443_TCP_ADDR
is defined and references the internal network address of the Kubernetes host.Enable the audit log.
$ vault audit enable file file_path=/tmp/audit_log.json
Exit the
vault-0
pod.$ exit
Workflow
The high level steps for this recommended pattern:
- Establish naming conventions
- Write a templated policy
- Onboard applications
- Check metadata populated by Vault
Upon login, Vault auto-populates certain metadata that can be used as part of templated ACL policies. The pattern can be extended by including custom metadata as part of the secret access path. This will be demonstrated in Step 5: Including custom metadata.
Step 1: Establish naming conventions
Best practice: Establish naming standards that allow applications to login and read secrets from consistent and predictable paths. Each application in an organization will likely have a unique identifier such as an application ID and be part of a logical group such as a line of business (LOB), project etc. These elements can be used to create the naming convention.
Example
The table below to help plan for the various relevant paths within Vault. It is encouraged that you build this out for a representative use-case in your organization. For this guide, the application grouping is based on Kubernetes Namespaces, where each Namespace represents a line of business (LOB). You will also create a Kubernetes Service Account per application which is considered a common best practice.
You will choose the LOB name retail, and app100, app200 and app300 as application IDs for the examples in this guide.
Vault item | Example for Kubernetes applications |
---|---|
Auth Method Mount path | The default path is kubernetes, but we recommend making it specific to a cluster name, since each cluster has a different API endpoint. - Naming convention: <cluster-name> - Examples: minikube , gke-useast1 etc. |
Auth Method Role name | We recommend making the role name something predictable for the application to login. - Naming convention: <namespace>-<app-name> - Example: retail-app100 |
Login path | The Login endpoint for Kubernetes Auth method will be: - API endpoint: auth/<auth-mount>/login Following our scheme, these will translate to: - Naming convention: auth/<cluster-name>/login/ - Example: auth/minikube/login |
Secret Engine Mount path | The secret engine can be mounted anywhere. In this example, you will mount one secret engine per Kubernetes Namespace: - Naming convention: secrets/<namespace> - Example: secret/data/retail |
Secret access path | We will assume that each application will access secrets at the following path: - Naming convention: secrets/<namespace>/<app-name> - Example: secret/data/retail/app100 |
The actual paths on the above table will vary depending on how the company is structured but it should give us a starting point for organizing elements within Vault.
Step 2: Write a templated policy and associate it with the login role
The Available Templating Parameters table shows various dynamic substitutions for a templated ACL policy. These parameters can be used to construct a policy that aligns with the naming conventions from step 1. We are interested in the <metadata key>
dynamic element for this pattern. For the Kubernetes Authentication method, the available metadata keys can be found in the Login endpoint API Sample Response.
Recall from the table in step 1 that the secret access path is: secrets/<namespace>/<app-name>
. Below is a mapping between the desired path elements and metadata keys.
<namespace>
:identity.entity.aliases.<mount accessor>.metadata.service_account_namespace.
This is the Kubernetes Namespace.<app-name>
:identity.entity.aliases.<mount accessor>.metadata.service_account_name.
This is the Kubernetes Service Account name for the application.
To access the metadata for an auth method requires its unique mount accessor. Display the mount accessors for all auth methods.
$ kubectl exec --namespace default vault-0 -- vault auth list
Example output:
Path Type Accessor Description---- ---- -------- -----------minikube/ kubernetes auth_kubernetes_f9be05d8 n/atoken/ token auth_token_8b8425b0 token based credentials
The "Accessor" column displays the
kubernetes
auth method's mount accessor.Create a variable to store the mount accessor for the
kubernetes
auth method.$ export MOUNT_ACCESSOR=$(kubectl exec --namespace default vault-0 -- vault auth list -format=json | jq -r '."minikube/".accessor')
Create an ACL policy, named
minikube-kv-read
, that grantsread
andlist
capabilities to secrets at the pathsecret/<service_account_namespace>/<service_account_name>
.$ kubectl exec -i --namespace default vault-0 -- \ vault policy write minikube-kv-read - << EOF path "secret/data/{{identity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_namespace}}/{{identity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_name}}" { capabilities=["read","list"] }EOF
This templatized policy uses
identity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_namespace
andidentity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_name
to create a policy that is flexible based on the Kubernetes namespace and service account.Refer to the complete list of available templating parameters for more information.
Display the
minikube-kv-read
policy.$ kubectl exec --namespace default vault-0 -- vault policy read minikube-kv-read
The created policy displays the templatized path with the
kubernetes/
auth method's mount accessor in the path.
Step 3: Onboard applications
In this step, you will deploy a demo application named basic-example
and ensure that it can read the intended secret using the templated policy. You will repeat the process once to see how multiple applications can access unique paths using the same ACL policy.
Create Role and write secret
Create a Kubernetes Service Account and a Vault login role. The role allows us to enforce the application Service Account name and Namespace, and associate the templated ACL policy. You will also store a secret in the KV secrets engine for this application.
Create an
APP_NAME
environment variable.$ export APP_NAME=app100
Create an
APP_NAMESPACE
environment variable.$ export APP_NAMESPACE=retail
Create an
CLUSTER_NAME
environment variable.$ export CLUSTER_NAME=minikube
Create a Kubernetes namespace called, "retail".
$ kubectl create ns $APP_NAMESPACE
Create application Service Account.
$ kubectl create sa $APP_NAME -n $APP_NAMESPACE
Each application requires a unique Kubernetes auth method role path based on the application's service account and namespace. Create a Kubernetes auth role named
$APP_NAMESPACE-$APP_NAME
.$ kubectl exec --namespace default -it vault-0 -- \ vault write auth/$CLUSTER_NAME/role/$APP_NAMESPACE-$APP_NAME \ bound_service_account_names=$APP_NAME \ bound_service_account_namespaces=$APP_NAMESPACE \ policies=$CLUSTER_NAME-kv-read \ period=120s
Output:
Success! Data written to: auth/minikube/role/retail-app100
Write a secret for the
secret/data/<APP_NAMESPACE>/<APP_NAME>
path.$ kubectl exec --namespace default -it vault-0 -- \ vault kv put secret/$APP_NAMESPACE/$APP_NAME \ app=$APP_NAME \ username=demo \ password=test
Output:
====== Secret Path ======secret/data/retail/app100======= Metadata =======Key Value--- -----created_time 2023-10-12T18:47:54.761554676Zcustom_metadata <nil>deletion_time n/adestroyed falseversion 1
Deploy application
Use the commands below to create a Kubernetes Deployment object. As a first step, you will make a series of substitutions using the sed
command to create a Deployment manifest yaml file from a template. Please display the resulting yaml file and validate the items below.
- The deployment yaml file should contain a
SECRET_PATH
environment variable with the value:secret/data/retail/app123
. The application will try to read secrets from this path. - The
VAULT_ADDR
environment variable should reflect the DNS name where application pods can reach Vault. If Vault is deployed in the same Kubernetes cluster, please issue the commandkubectl get svc
to view the vault service name. Assuming that the service name is vault, and the Namespace is default, then the FQDN will be vault.default.svc.cluster.local (please see Kubernetes DNS records).
Create the app deployment yaml file with a set of substitutions.
$ cat deployment-template.yaml \ | sed -e s/"my-deployment"/"$APP_NAME-deployment"/ \ -e s/"my-namespace"/$APP_NAMESPACE/ \ -e s/"my-app-name"/"$APP_NAME"/g \ -e s/"my-app-sa"/$APP_NAME/ \ -e s/"login-path"/"auth\/$CLUSTER_NAME\/login"/ \ -e s/"secret-path"/"secret\/data\/$APP_NAMESPACE\/$APP_NAME"/ \ -e s/"my-role"/"$APP_NAMESPACE-$APP_NAME"/ \ > deployment-$APP_NAMESPACE-$APP_NAME.yaml
Display the deployment file to ensure all the fields are correct.
$ cat deployment-$APP_NAMESPACE-$APP_NAME.yaml
Output:
apiVersion: apps/v1kind: Deploymentmetadata:name: app100-deploymentnamespace: retailspec:replicas: 1selector: matchLabels: app: app100template: metadata: labels: app: app100 spec: serviceAccountName: app100 containers: - name: app image: "kawsark/vault-example-init:0.0.9" imagePullPolicy: IfNotPresent env: - name: VAULT_ADDR value: "http://vault.default.svc.cluster.local:8200" - name: VAULT_ROLE value: retail-app100 - name: SECRET_PATH value: secret/data/retail/app100 - name: VAULT_LOGIN_PATH value: auth/minikube/login
Create the deployment.
$ kubectl create -f deployment-$APP_NAMESPACE-$APP_NAME.yaml
Verify that the pod is running.
$ kubectl get pods -n $APP_NAMESPACE NAME READY STATUS RESTARTS AGEapp100-deployment-74dcb4c76-gqxhz 1/1 Running 0 39s
Wait until the pod, prefixed with the
$APP_NAME
, is running and ready (1/1
).
Display logs
Create a variable to store the unique name of the pod.
$ export POD_NAME=$(kubectl get pods -l app=$APP_NAME \ -o jsonpath='{.items[0].metadata.name}' --namespace=$APP_NAMESPACE)
Display the logs of the pod.
$ kubectl logs $POD_NAME --namespace $APP_NAMESPACE 023/10/12 18:53:39 ==> WARNING: Don't ever write secrets to logs.2023/10/12 18:53:39 ==> This is for demonstration only.2023/10/12 18:53:39 hvs.CAESIGqKUpWZFQPuRW76Fb6prcXQ_fmN-SC7VPPc2Hz1UuGHGh4KHGh2cy5BQXl3RmI3RlZtTldzWlFXU25UbGVLbFQ2023/10/12 18:53:39 secret secret/data/retail/app100 -> &{2c89566f-1d2e-6348-5bbd-17b1f2597d83 0 false map[data:map[app:app100 password:test username:demo] metadata:map[created_time:2023-10-12T18:48:02.955848346Z custom_metadata:<nil> deletion_time: destroyed:false version:2]] [] <nil> <nil>}2023/10/12 18:53:39 Starting renewal loop2023/10/12 18:53:39 Successfully renewed: &api.RenewOutput{RenewedAt:time.Time{wall:0x11372678, ext:63832733619, loc:(*time.Location)(nil)}, Secret:(*api.Secret)(0xc000058f00)}2023/10/12 18:53:39 secret secret/data/retail/app100 -> &{6a6ad400-bf10-cd88-9705-55bb41481ef6 0 false map[data:map[app:app100 password:test username:demo] metadata:map[created_time:2023-10-12T18:48:02.955848346Z custom_metadata:<nil> deletion_time: destroyed:false version:2]] [] <nil> <nil>}
The log displays the secret path and secret that it request from Vault.
Warning
Do not write secrets to logs in production. This is for demonstration only. This application will read its service account JWT and use it to authenticate with Vault. It will then log the secret and keep the token renewed. Below is a snippet from the application log showing a successful secret read from the secret access path.
The Vault audit log should show that Vault applied the
minikube-kv-read
ACL policy upon login and dynamically allowed access to the secret path:secrets/data/retail/app100
.$ kubectl exec -it vault-0 -- cat /tmp/audit_log.json | jq .{ ...snip... "auth": { "client_token": "hmac-sha256:97aaf5c989f...", ...snip... "token_policies": [ "default", "minikube-kv-read" ], "metadata": { "role": "retail-app100", "service_account_name": "app100", "service_account_namespace": "retail", ...snip... "operation": "read", "mount_type": "kv", ...snip... "path": "secrets/data/retail/app100", }, "response": { "mount_type": "kv", ...snip... "data": { "app": "hmac-sha256:7af4ccfd...", "password": "hmac-sha256:da5ff1936...", "username": "hmac-sha256:ff948a5ec..." }
Onboard additional application
You will use this method to onboard another application called app200
in the same Namespace. Since the secret path scheme will remain the same, secret/data/<namespace>/<app-name>
, you will not need to create another policy.
Create Role and write secret
Issue the commands below to create an application role, associate the policy and store a secret.
Update the
APP_NAME
environment variable value.$ export APP_NAME=app200
Create an application Service Account.
$ kubectl create sa $APP_NAME -n $APP_NAMESPACE
Create role and associate
kv-read
policy.$ kubectl exec --namespace default -it vault-0 -- \ vault write auth/$CLUSTER_NAME/role/$APP_NAMESPACE-$APP_NAME \ bound_service_account_names=$APP_NAME \ bound_service_account_namespaces=$APP_NAMESPACE \ policies=$CLUSTER_NAME-kv-read \ period=120s
Write a secret for the
secret/data/<APP_NAMESPACE>/<APP_NAME>
path.$ kubectl exec --namespace default -it vault-0 -- \ vault kv put secret/$APP_NAMESPACE/$APP_NAME \ app=$APP_NAME \ username=demo \ password=test
Deploy application
Create the Deployment manifest file which should now contain a SECRET_PATH
with the value: secret/data/retail/app200
.
Create the app deployment yaml file with a set of substitutions.
$ cat deployment-template.yaml \ | sed -e s/"my-deployment"/"$APP_NAME-deployment"/ \ -e s/"my-namespace"/$APP_NAMESPACE/ \ -e s/"my-app-name"/"$APP_NAME"/g \ -e s/"my-app-sa"/$APP_NAME/ \ -e s/"login-path"/"auth\/$CLUSTER_NAME\/login"/ \ -e s/"secret-path"/"secret\/data\/$APP_NAMESPACE\/$APP_NAME"/ \ -e s/"my-role"/"$APP_NAMESPACE-$APP_NAME"/ \ > deployment-$APP_NAMESPACE-$APP_NAME.yaml
Create the application and display pods.
$ kubectl create -f deployment-$APP_NAMESPACE-$APP_NAME.yamldeployment.apps/app200-deployment created
Verify that the pod is running.
$ kubectl get pods -n $APP_NAMESPACE NAME READY STATUS RESTARTS AGEapp100-deployment-74dcb4c76-gqxhz 1/1 Running 0 12mapp200-deployment-7c876b7597-p75ns 1/1 Running 0 14s
Display logs
Display the application logs and check for a successful login and secrets retrieval from
secret/data/retail/app200
:$ export POD_NAME=$(kubectl get pods -l app=$APP_NAME \ -o jsonpath='{.items[0].metadata.name}' --namespace=$APP_NAMESPACE)
Read application logs.
$ kubectl logs $POD_NAME -n $APP_NAMESPACE
If you change the
SECRET_PATH
tosecret/data/retail/app100
in the deployment-retail-app200.yaml, then the secrets retrieval will fail forapp200
. Below is a snippet from the application logs when theapp200
was deployed with theSECRET_PATH: "secret/data/retail/app100"
:URL: GET http://vault.default.svc.cluster.local:8200/v1/secret/data/retail/app200Code: 403. Errors: * 1 error occurred: * permission denied
The above failure confirms that the templated policy is working as intended. This is because the ACL policy only allows access to a path ending with the application's own service account name which is app200
. This prevents service accounts from being reused.
Step 4: Check metadata populated by Vault
This is an optional step to view the entity and entity-alias objects that Vault created automatically upon login. It will also allow us to review the Entity alias metadata populated by Vault. Below are the steps to view these items using the UI and the CLI.
Note
At least one previous successful login using the Kubernetes Authentication method is needed before proceeding with these steps.
If you deployed both app100 and app200, you should see two Entity objects. Each Entity will be linked to an Authentication alias named as the Kubernetes service account ID (represented as service_account_uid
under alias metadata).
You can expose the Vault UI with port-forwarding.
$ kubectl port-forward vault-0 8200:8200Forwarding from 127.0.0.1:8200 -> 8200Forwarding from [::1]:8200 -> 8200##...
Login to Vault UI with address
http://127.0.0.1:8200
, and enterroot
as password.Select Access, and then Entities.
Select an entity and then select the Aliases tab.
Select the alias, and then Metadata tab to see auto-populated metadata keys.
Vault automatically created the entity and entity alias objects, and then added the Kubernetes namespace and Service Account names as the metadata. You used this information to create a templated ACL policy that applied to multiple applications. The uniqueness of the metadata is what allows us to create one ACL policy that can apply to multiple applications.
Step 5: Include custom metadata
Thus far the secret access path included auto-populated metadata: the Kubernetes Service Account Name and Namespace. However, you may want to include custom information as part of the secret access path that is not automatically populated by Vault.
To illustrate this scenario, let's imagine that the Secret access path from the table in step 1 needs to have a geography name in it.
- Naming convention:
secret/data/<namespace>/<geography>/<app-name>
- Examples:
In this scenario, presumably each geography has the same application deployed and the secrets stored in Vault need to contain some localized information. The <namespace>
and <app-name>
elements will be auto-populated by Vault as confirmed earlier in Step 4. However, the <geography>
portion will need to be added as a custom metadata. To add custom metadata the Vault Entity and Entity-Alias objects will need to be created prior to login.
Create application Service Account, Entity and Entity-Alias
Create the Kubernetes Service Account called app300, then create the Vault Entity and Entity Alias objects. You will add two metadata fields: geography and owner. The owner field is optional and only for tracking purposes, it will not be used as part of the secret access path.
Update the
APP_NAME
environment variable value.$ export APP_NAME=app300
Create application Service Account.
$ kubectl create sa $APP_NAME -n $APP_NAMESPACE
Create an entity with metadata information.
$ kubectl exec --namespace default -it vault-0 -- \ vault write identity/entity name=$APP_NAME \ metadata='geography=useast1' \ metadata='owner=foo'
Store the generated entity ID.
$ export ENTITY_ID=$(kubectl exec --namespace default -it vault-0 -- \ vault read identity/entity/name/$APP_NAME -format=json \ | jq -r '.data.id')
Obtain the service account name UID.
$ export SERVICE_ACCOUNT_UID=$(kubectl get sa $APP_NAME -n retail \ -o jsonpath="{.metadata.uid}")
Create the alias to associate the entity and alias. (You set the
MOUNT_ACCESSOR
environment variable in step 2.)$ kubectl exec --namespace default -it vault-0 -- \ vault write identity/entity-alias name=$SERVICE_ACCOUNT_UID \ canonical_id=$ENTITY_ID \ mount_accessor=$MOUNT_ACCESSOR
Example output:
Key Value--- -----canonical_id d31535dd-22db-ea1a-f187-a9a9fae2884bid ee47946c-008c-b15d-fd2a-c529b403bfdc
Check the UI to see the new Vault entity by selecting Access > Entities.
Select the app300 entity and choose the Metadata tab to view the custom metadata that was just entered.
The entity and metadata can be displayed using the CLI.
$ kubectl exec --namespace default -it vault-0 -- \ vault read identity/entity/name/app300
Example output:
Key Value--- -----aliases [map[canonical_id:d31535dd-22db-ea1a-f187-a9a9fae2884b creation_time:2021-02-19T10:20:34.4567655Z id:ee47946c-008c-b15d-fd2a-c529b403bfdc last_update_time:2021-02-19T10:20:34.4567655Z merged_from_canonical_ids:<nil> metadata:<nil> mount_accessor:auth_kubernetes_f9be05d8 mount_path:auth/minikube/ mount_type:kubernetes name:65652c6a-5352-4711-b37a-e1b4c64cbc7b]]creation_time 2021-02-19T10:09:59.1194815Zdirect_group_ids []disabled falsegroup_ids []id d31535dd-22db-ea1a-f187-a9a9fae2884binherited_group_ids []last_update_time 2021-02-19T10:20:34.4567531Zmerged_entity_ids <nil>metadata map[geography:useast1 owner:foo]name app300namespace_id rootpolicies <nil>
Create policy with custom metadata
Create a new templated policy which includes the custom metadata: geography. Note the additional templating element {{identity.entity.metadata.geography}}
being used to populate the <geography>
portion of the secret access path.
Write the templated ACL policy.
$ kubectl exec -i --namespace default vault-0 -- \ vault policy write $CLUSTER_NAME-geo-kv-read - << EOF path "secret/data/{{identity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_namespace}}/{{identity.entity.metadata.geography}}/{{identity.entity.aliases.$MOUNT_ACCESSOR.metadata.service_account_name}}" { capabilities=["read","list"]}EOF
The remaining steps to create the application role and deploy the application should be familiar from before. The snippets are included below.
Create Role and write secret
Update the
APP_NAME
environment variable value toapp300
.$ export APP_NAME=app300
Create a role and attach the
kv-read
policy.$ kubectl exec --namespace default -it vault-0 -- \ vault write auth/$CLUSTER_NAME/role/$APP_NAMESPACE-$APP_NAME \ bound_service_account_names=$APP_NAME \ bound_service_account_namespaces=$APP_NAMESPACE \ policies=$CLUSTER_NAME-geo-kv-read \ period=120s
Write a secret for the
secrets/data/<namespace>/<geo>/<APP_NAME>
path.$ kubectl exec --namespace default -it vault-0 -- \ vault kv put secret/$APP_NAMESPACE/useast1/$APP_NAME \ app=$APP_NAME \ username=demo \ password=test
Example output:
========== Secret Path ==========secret/data/retail/useast1/app300======= Metadata =======Key Value--- -----created_time 2024-11-08T03:34:41.721358004Zcustom_metadata <nil>deletion_time n/adestroyed falseversion 1
Deploy application
After executing the substitution commands, the resulting deployment yaml manifest should indicate SECRET_PATH
as secret/data/retail/useast1/app300
.
Create the app deployment yaml file with a set of substitutions.
$ cat deployment-template.yaml \ | sed -e s/"my-deployment"/"$APP_NAME-deployment"/ \ -e s/"my-namespace"/$APP_NAMESPACE/ \ -e s/"my-app-name"/"$APP_NAME"/g \ -e s/"my-app-sa"/$APP_NAME/ \ -e s/"login-path"/"auth\/$CLUSTER_NAME\/login"/ \ -e s/"secret-path"/"secret\/data\/$APP_NAMESPACE\/useast1\/$APP_NAME"/ \ -e s/"my-role"/"$APP_NAMESPACE-$APP_NAME"/ \ > deployment-$APP_NAMESPACE-$APP_NAME.yaml
Create the deployment.
$ kubectl create -f deployment-$APP_NAMESPACE-$APP_NAME.yaml
Verify that the pod is running.
$ kubectl get pods -n $APP_NAMESPACE NAME READY STATUS RESTARTS AGEapp100-deployment-74dcb4c76-gqxhz 1/1 Running 0 110mapp200-deployment-7c876b7597-p75ns 1/1 Running 0 97mapp300-deployment-7cb9dc57f8-668lb 1/1 Running 0 8s
Display logs
Display the application logs and check for a successful login and secrets retrieval from secret/data/retail/us-east1/app300
.
$ export POD_NAME=$(kubectl get pods -l app=$APP_NAME \ -o jsonpath='{.items[0].metadata.name}' --namespace=$APP_NAMESPACE)
Read the application logs.
$ kubectl logs $POD_NAME -n $APP_NAMESPACE
Example output:
==> WARNING: Don't ever write secrets to logs.==> This is for demonstration only.s.JRWjqG50ZzuQdihxpC51kFvlsecret secret/data/retail/useast1/app300 -> &{5a6b79be-719b-913b-5b38-867acacd0456 0 false map[data:map[app:app300 password:test username:demo] metadata:map[created_time:2021-02-19T11:31:12.8160839Z deletion_time: destroyed:false version:1]] [] <nil> <nil>}Starting renewal loopSuccessfully renewed: &api.RenewOutput{RenewedAt:time.Time{wall:0xa1d4520, ext:63749331458, loc:(*time.Location)(nil)}, Secret:(*api.Secret)(0xc000292900)}secret secret/data/retail/useast1/app300 -> &{1b2e9fab-b266-c2c0-b988-6c1840cf2a2f 0 false map[data:map[app:app300 password:test username:demo] metadata:map[created_time:2021-02-19T11:31:12.8160839Z deletion_time: destroyed:false version:1]] [] <nil> <nil>}
To deploy the application in another geography, the approximate steps will be as follows:
- Create the Kubernetes application Service Account
- Create the Entity (with metadata) and Entity-Alias
- Create Vault Role and write a secret
- Deploy application
Conclusion
In this pattern, you reviewed how to use a single templated ACL policy for multiple applications. The main advantage of this approach over writing a policy per application is to simplify onboarding steps. Fewer policies also result in easier policy management and auditing. Finally, this pattern enforces standard conventions on where applications can read and write secrets from within Vault, allowing for better organization and anomaly detection.
While you have provided a Kubernetes-based example, other Authentication methods will also have associated metadata that can be useful in creating templated policies. Similarly, while you accessed the static Key/Value secrets engine, the secret access path can be applied to dynamic secrets engines where the effective path corresponds to an application role.
Reference material
- Auth Methods: This documentation describes how clients can login to Vault using Auth Method plugins.
- Kubernetes Authentication Method: This documentation covers how to authenticate Kubernetes applications to Vault. The Recommended Pattern described here uses the Kubernetes Auth Method.
- Identity Entity: This documentation describes the Entity system in Vault.
- ACL Policies learn guide: This Learn guide covers how to restrict access to secret paths in Vault by defining ACL policies.
- ACL Policy Path Templates: This Learn guide covers how to use the ACL Templating feature.