In Part 1 of this series, we saw how to use ConfigMaps to configure a Spring Boot app on Kubernetes. ConfigMaps are OK when we use simple configuration data that does not contain sensitive information. When using sensitive data like API keys, passwords, etc., Secrets are the preferred and recommended way. In this second part of the series, we will explore configuring Spring Boot on Kubernetes with Secrets.
The sources for this blog post are available in my GitHub repo.
You might need access to a Kubernetes cluster to play with this application. The easiest way to get a local Kubernetes cluster up and running is using minikube.The rest of this post assumes you have minikube up and running.
Like ConfigMaps, Secrets can be configured in two ways:
Secrets as Environment Variables
The Spring Boot application that we will build in this blog post uses spring-security. Spring Security, by default, enables security on the entire Spring Boot application.
The default user and password of the application will be displayed to the developer during application boot up.
Using default security password: 981d5f9f-c8ea-413f-8f3b-71daaa20d53c
To override the default security user/password, you need to update the application.properties to be:
Let’s now follow the next steps to inject the environment variables.
Developers can start by creating a Kubernetes Secret called spring-security. This is just the name I am using, but it could be anything of your choice, but remember to use the same name in our deployment.yaml that will configure later.
You can then add two properties — “spring.user.name” and “spring.user.password” — to the Secrets by executing the following command:
kubectl create secret generic spring-security \ --from-literal=spring.user.name=demo \ --from-literal=spring.user.password=password
If you wish to see how your Secrets look, execute the following command,
kubectl get secret spring-security -o yaml
The sample output of the above command is shown below.
apiVersion: v1 data: spring.user.name: ZGVtbw== spring.user.password: cGFzc3dvcmQ= kind: Secret metadata: creationTimestamp: 2017-09-19T15:24:29Z name: spring-security namespace: default resourceVersion: "71363" selfLink: /api/v1/namespaces/default/secrets/spring-security uid: a0e0254e-9d4e-11e7-9b8d-080027da6995 type: Opaque
NOTE: All the values of the properties in the Secrets will be displayed as base64 encoded values.
Create the Fragment deployment.yaml
To configure Spring Boot application on Kubernetes and inject environment variables from Secrets, we need to create the deployment.yaml fragment. Fragments are only bits and pieces of complete Kubernetes resources like deployments, services, etc. It is the responsibility of fabric8-maven-plugin to merge the existing fragments to a complete Kubernetes resource(s) or generate new and missing ones.
The following sections show the required fragments that can be created by the developers inside $PROJECT_HOME/src/main/fabric8 folder:
spec: template: spec: containers: - env: - name: SECRETS_DEMO_USER valueFrom: secretKeyRef: name: spring-security key: spring.user.name - name: SECRETS_DEMO_USER_PASSWD valueFrom: secretKeyRef: name: spring-security key: spring.user.password
The environment variables SECRETS_DEMO_USER and SECRETS_DEMO_USER_PASSWD will have its value injected from secret with name matching secretKeyRef –> name with its value from secret property specified by secretKeyRef –> value
NOTE: As the application is configured to use fabric8-maven-plugin, we can create a Kubernetes deployment and service as fragments in ‘$PROJECT_HOME/src/main/fabric8’. The fabric8-maven-plugin takes care of building the complete Kubernetes manifests by merging the contents of the fragment(s) from ‘$PROJECT_HOME/src/main/fabric8’ during the deploy.
Deploy the Application
To deploy the application, execute the following command from the $PROJECT_HOME:
./mvnw clean fabric8:deploy.
Access the Application
The application status can be checked with the command
kubectl get pods -w . Once the application is deployed, let’s do a simple curl like this:
curl $(minikube service spring-boot-secrets-demo --url)/; echo "";
It should return an HTTP 401 Unauthorized error, as we did not provide the credentials to access the app.
Now do a
curl -u demo:password $(minikube service spring-boot-secrets-demo --url)/; , which should still return HTTP 404, as we don’t have any resource at that URI, but we are now authorized.
- The very first deployment of this application tends to take a bit of time, as Kubernetes needs to download the required Docker images for application deployment.
- The application service URL is found using the command:
minikube service blog-configmaps-secrets-demo --url
Mounting Secrets as Files
Let’s consider a very simple scenario. Say you want to write a REST API that will call the GitHub API to get all the organizations that your GitHub user account is associated with. The GitHub API to get the organizations you belong to is an authorized call, meaning you need to send a GitHub Personal Access Token as part of the request. Injecting your personal access token as an environment variable might not be as secure as you think, so how do you do it?
The simple way for us to do that is by making the application mount the Secrets as volumes. Once we are able to do that, then we can alter and set permissions on those volumes like how we do for an ssh private key.
Before we get started, I assume that you have created a GitHub Personal Access Token. Once you have it, store them in files.
- github.user – will store your GitHub user ID.
- github.token – will store your GitHub Personal Access Token.
Create Secrets From a File
Let’s start creating a new secret called spring-github-demo, similar to how we configured a Spring Boot application on Kubernetes to use Secrets as Environment Variables.
kubectl create secret generic spring-github-demo \ --from-file ./github.user \ --from-file ./github.token
When we execute the command
kubectl get secret spring-github-demo -o yaml, it will display an output similar to the one shown below.
apiVersion: v1 data: github.token: NjE2OTliMjJjOWQ3YTQ5MDJjZjI5NjBhZThjOWMxNWIxMGQzMmI3Ngo= github.user: a2FtZXNoc2FtcGF0aAo= kind: Secret metadata: creationTimestamp: 2017-09-18T13:55:59Z name: spring-github-demo namespace: default resourceVersion: "28217" selfLink: /api/v1/namespaces/default/secrets/spring-github-demo uid: 19ad298b-9c79-11e7-9b8d-080027da6995 type: Opaque
Update Fragment deployment.yaml
Update the deployment.yaml to add the volume mounts that will allow us to mount the application.properties under /deployments/config inside the container.
spec: template: spec: containers: - env: - name: SECRETS_DEMO_USER valueFrom: secretKeyRef: name: spring-security key: spring.user.name - name: SECRETS_DEMO_USER_PASSWD valueFrom: secretKeyRef: name: spring-security key: spring.user.password volumeMounts: - name: github-user mountPath: "/deployments/github" readOnly: true volumes: - name: github-user secret: secretName: spring-github-demo items: - key: github.user path: user - key: github.token path: token
The container will now have the secrets:
- github.user mounted as a file inside the container at /deployments/github/user
- github.token mounted as a file inside the container at /deployments/github/token
The “GitHubController” REST Controller loads your GitHub user and token from the mounted paths and uses them when interacting with the GitHub API. You can access the REST URI path /mygithuborgs, which will return all your organizations that your GitHub ID is associated with as JSON.
Deploy the application again using the command
./mvnw clean fabric8:deploy and access the application using this curl command:
curl -u demo:password $(minikube service blog-configmaps-secrets-demo --url)/mygithuborgs
If you omit the
-u demo:password, then it will result in an HTTP 401 Unauthorized error.
In Part 2 of this blog series, we saw how to configure Spring Boot on Kubernetes with Secrets. In the next part, we will see on how to use the spring-cloud-kubernetes Spring module to configure Spring Boot applications on Kubernetes.