Introduction
Until recently, it wasn't straightforward for applications running on OpenShift to authenticate with Vault in order to retrieve secrets. It involved a complex orchestration workflow, involving multiple actors (including a Vault Controller and init container as outlined in this post) in order to retrieve secrets stored in Vault.
In this post, we demonstrate a simpler approach for applications to authenticate with Vault, in a way more native to Kubernetes. This is made possible using by using the Kubernetes authentication method that has been added (since Vault 0.8.3), to integrate Vault directly with Kubernetes.
Additionally, in this blog post, we demonstrate how to run Vault on OpenShift. We then configure Vault to use the Kubernetes Auth Method that can be used by client applications to authenticate with Vault. Finally, we demonstrate running a Spring Boot application client on OpenShift that connects to Vault using the Kubernetes Auth Method to retrieve secrets. It makes use of Spring Cloud Vault Config that supports this method out of the box.
Why Vault
Vault is a secret store software created by HashiCorp. With Vault you have a central place to manage external secret properties for your applications across all environments. Vault can manage static and dynamic secrets such as username/password and manage credentials for external services such as MySQL, PostgreSQL, Apache Cassandra, MongoDB, Consul, AWS and more. Vault can run in the cloud and also encrypts credentials at rest. Additionally, no OpenShift cluster admin can see the credentials, plus in Vault you can create sharded master keys so that no Vault admin can, by themselves, un-encrypt the credentials.
Kubernetes Auth Method
The Kubernetes authentication method can be used to authenticate with Vault using a Kubernetes Service Account Token. The token for a pod’s service account is automatically mounted within a pod at /var/run/secrets/kubernetes.io/serviceaccount/token
and is sent to Vault for authentication. Vault is configured with a service account that has permissions to access the TokenReview API. This service account can then be used to make authenticated calls to Kubernetes to verify tokens of the service accounts of pods that want to connect to Vault to get secrets.
This is depicted in the diagram below.
- Use the JWT token for a pod’s service account to authenticate with Vault. A file containing the token is automatically mounted at
/var/run/secrets/kubernetes.io/serviceaccount/token
. - Vault sends the service account token of a pod that wants to access it to the OpenShift master API for authentication. During this call, Vault uses the token of the service account with token reviewer permissions to authenticate with the master API. If the service account token of the pod is successfully authenticated, then a Vault token correctly scoped is returned to the pod.
- The Vault token is subsequently used to retrieve the secrets from Vault. The Vault token is also short-lived: It's the application’s responsibility to renew it if new secrets need to be retrieved later.
Below are the steps to install Vault, enable the Kubernetes authentication method, and configure a Spring Boot application to authenticate with Vault using this method and retrieve secrets.
A. Install Vault
1. Requirements
You need the Vault CLI installed on your machine.<
2. Clone the repository below containing a Vault aware Spring Boot application that is using the Vault Kubernetes authentication method.
git clone https://github.com/raffaelespazzoli/credscontroller
3. Create a new project
oc new-project vault-controller
4. Install Vault.
Note: You need a cluster administrator role to execute the first step below. As you can see, we are adding the default serviceaccount to the anyuid SCC as the application needs to run as user root in the container. We then create a configmap that contains configuration data for Vault, and create the OpenShift service, deployment config, persistent volume claim that are defined in vault.yaml, and finally expose a reencrypt route on port 8200.
oc adm policy add-scc-to-user anyuid -z default
oc create configmap vault-config --from-file=vault-config=./openshift/vault-config.json
oc create -f ./openshift/vault.yaml
oc create route reencrypt vault --port=8200 --service=vault
5. Initialize Vault
export VAULT_ADDR=https://`oc get route | grep -m1 vault | awk '{print $2}'`
vault init -tls-skip-verify -key-shares=1 -key-threshold=1
Save the generated key and token.
6. Unseal Vault
You have to repeat this step every time you start Vault. Don't try to automate this step, this is manual by design. You can make the initial seal stronger by increasing the number of keys.
We will assume that the KEYS environment variable contains the key necessary to unseal Vault and that ROOT_TOKEN
contains the root token.
For example:
export KEYS=tjgv5s7M4CtMeUz92dU9jV3EudPawgNz6euEnciZoFs=
export ROOT_TOKEN=1487cceb-f05d-63be-3e24-d08e429c760c
vault unseal -tls-skip-verify $KEYS
B. Setup Kubernetes Vault auth backend
1. Create a token reviewer service account called vault-auth
in the vault-controller project
oc create sa vault-auth
2. Give the vault-auth service account permissions to create tokenreviews.authentication.k8s.io
at the cluster scope
oc adm policy add-cluster-role-to-user system:auth-delegator system:serviceaccount:vault-controller:vault-auth
3. Get the token for the vault-auth
service account
reviewer_service_account_jwt=$(oc serviceaccounts get-token vault-auth)
4. Get the internal OpenShift Certificate Authority from the pod running Vault.
pod=`oc get pods -n $(oc project -q) | grep vault | awk '{print $1}'`
oc exec $pod -- cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt >> ca.crt
5. Setup the Kubernetes auth backend
You need to have a Kubernetes mount in Vault per Kubernetes/OpenShift cluster you want it to connect to. Enable the Kubernetes authentication backend. If you don't use the -path
variable, then it defaults to the path /auth/kubernetes
. If you want to mount other Kubernetes backends, use the-path=
variable... i.e. a -path=kubernetes/cluster1
will create a path /auth/kubernetes/cluster1
export VAULT_TOKEN=$ROOT_TOKEN
vault auth-enable -tls-skip-verify kubernetes
6. Configure the Kubernetes authentication method to use the vault-auth
service account token to authenticate with the OpenShift master API, in order to verify other service account tokens of pods that want to access Vault.
vault write -tls-skip-verify auth/kubernetes/config token_reviewer_jwt=$reviewer_service_account_jwt kubernetes_host=<server-url> kubernetes_ca_cert=@ca.crt
rm ca.crt
7. A sample policy file called spring-native-example.hcl
has already been provided to you that allows a token to get a secret from the generic secret backend for the client role.
path "secret/application" {
capabilities = ["read", "list"]
}
path "secret/spring-native-example" {
capabilities = ["read", "list"]
}
8. Create the policy
vault policy-write -tls-skip-verify spring-native-example ./examples/spring-native-example/spring-native-example.hcl
9. Authorization with this backend is role based. Before a token can be used to login, it must be configured in a role.
vault write -tls-skip-verify auth/kubernetes/role/spring-native-example bound_service_account_names=default bound_service_account_namespaces='*' policies=spring-native-example ttl=2h
This authorizes all default service accounts (and pods running with the default service accounts) in all namespaces and gives them the spring-native-example policy, which allows them to read secrets from Vault.
10. Confirm that the policy enables secret creation only under the path secret/spring-native-example
export VAULT_TOKEN=$ROOT_TOKEN
vault write -tls-skip-verify secret/spring-native-example password=pwd
11. Verify that the backend can now be used to authenticate Vault requests using the default service account.
a. Get the default service account token from the default namespace
default_account_token=$(oc serviceaccounts get-token default -n default)
b. Log in to the Kubernetes auth backend using the service account token. The output returns a Vault token that can be subsequently used to read secrets.
vault write -tls-skip-verify auth/kubernetes/login role=spring-native-example jwt=${default_account_token}
Key Value
--- -----
token 74603479-607d-4ab8-a406-d0456d9f3d65
token_accessor 4893b0a1-f42a-bfd8-cd9c-c14b9bdb6095
token_duration 1h0m0s
token_renewable true
token_policies [default spring-native-example]
token_meta_role "spring-native-example"
token_meta_service_account_name "default"
token_meta_service_account_namespace "default"
token_meta_service_account_secret_name "default-token-fndln"
token_meta_service_account_uid "aaf6c23c-b04a-11e7-9aea-0245c85cf1cc"
C. Configure Spring Boot Client application to Use the Kubernetes Auth Method to Authenticate with Vault
In this example, we are using Spring Cloud Vault in order to bind properties based on secrets. You need to have the following two prerequisites in your project.
1. Add the dependency on Spring Cloud Vault
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-vault-dependencies</artifactId>
<version>1.1.0.RELEASE</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-vault-config</artifactId>
</dependency>
</dependencies>
2. Configure Vault information in the bootstrap.yml
file of the project
spring.application.name: spring-native-example
spring.cloud.vault:
host: ${vault.host:localhost}
port: ${vault.port:8200}
scheme: https
authentication: KUBERNETES
kubernetes:
role: spring-native-example
service-account-token-file: /var/run/secrets/kubernetes.io/serviceaccount/token
The Kubernetes authentication mechanism is role-based and the role is bound to a service account name and namespace.
- role sets the Role.
- service-account-token-file sets the location of the file containing the Kubernetes Service Account Token. Defaults to
/var/run/secrets/kubernetes.io/serviceaccount/token
.
Deploy the Spring Boot application on OpenShift
1. Create the project
oc new-project spring-native-example
oc new-build registry.access.redhat.com/redhat-openjdk-18/openjdk18-openshift~https://github.com/raffaelespazzoli/credscontroller --context-dir=examples/spring-native-example --name spring-native-example
2. Join the project with vault-controller
to be able to call the Vault service
Note: This is only required when using the multitenant SDN plugin
oc adm pod-network join-projects --to vault-controller spring-native-example
3. Deploy the spring-native-example
application
oc create -f ./examples/spring-native-example/spring-native-example.yaml
oc expose svc spring-native-example
4. Now you should be able to call a service that returns the secret
export SPRING_EXAMPLE_ADDR=http://`oc get route | grep -m1 spring | awk '{print $2}'`
curl $SPRING_EXAMPLE_ADDR/secret
Summary
In this post, we saw how to run Vault on OpenShift and configure it to use the Kubernetes authentication method. We also showed how to deploy a reference Spring Boot application that makes use of this authentication method to authenticate with Vault and bind application properties to secrets stored in Vault.
About the authors
Raffaele is a full-stack enterprise architect with 20+ years of experience. Raffaele started his career in Italy as a Java Architect then gradually moved to Integration Architect and then Enterprise Architect. Later he moved to the United States to eventually become an OpenShift Architect for Red Hat consulting services, acquiring, in the process, knowledge of the infrastructure side of IT.
Currently Raffaele covers a consulting position of cross-portfolio application architect with a focus on OpenShift. Most of his career Raffaele worked with large financial institutions allowing him to acquire an understanding of enterprise processes and security and compliance requirements of large enterprise customers.
Raffaele has become part of the CNCF TAG Storage and contributed to the Cloud Native Disaster Recovery whitepaper.
Recently Raffaele has been focusing on how to improve the developer experience by implementing internal development platforms (IDP).
Browse by channel
Automation
The latest on IT automation for tech, teams, and environments
Artificial intelligence
Updates on the platforms that free customers to run AI workloads anywhere
Open hybrid cloud
Explore how we build a more flexible future with hybrid cloud
Security
The latest on how we reduce risks across environments and technologies
Edge computing
Updates on the platforms that simplify operations at the edge
Infrastructure
The latest on the world’s leading enterprise Linux platform
Applications
Inside our solutions to the toughest application challenges
Original shows
Entertaining stories from the makers and leaders in enterprise tech
Products
- Red Hat Enterprise Linux
- Red Hat OpenShift
- Red Hat Ansible Automation Platform
- Cloud services
- See all products
Tools
- Training and certification
- My account
- Customer support
- Developer resources
- Find a partner
- Red Hat Ecosystem Catalog
- Red Hat value calculator
- Documentation
Try, buy, & sell
Communicate
About Red Hat
We’re the world’s leading provider of enterprise open source solutions—including Linux, cloud, container, and Kubernetes. We deliver hardened solutions that make it easier for enterprises to work across platforms and environments, from the core datacenter to the network edge.
Select a language
Red Hat legal and privacy links
- About Red Hat
- Jobs
- Events
- Locations
- Contact Red Hat
- Red Hat Blog
- Diversity, equity, and inclusion
- Cool Stuff Store
- Red Hat Summit