Spring Cloud Kubernetes is not loading secret keys with pattern like xx.yy - spring

I am trying to learn about Spring Cloud Kubernetes for loading secrets and what I have observed is if a property has yml like structure, then it doesn't get loaded in app.
Ex:
kind: Secret
metadata:
name: activemq-secrets
labels:
broker: activemq
type: Opaque
data:
amqusername: bXl1c2VyCg==
amq.password: MWYyZDFlMmU2N2Rm
K8 Manifest
template:
spec:
volumes:
- name: secretvolume
secret:
secretName: activemq-secrets
containers:
-
volumeMounts:
- name: secretvolume
readOnly: true
mountPath: /etc/secrets/
jvm args:
-Dspring.cloud.kubernetes.secrets.paths=/etc/secrets/
-Dspring.cloud.kubernetes.secrets.enabled=true
Trying to load #Value("${amqusername}")works
But when I try to read this property with #Value("${amq.password}") I get error with placeholder not found. I have tried printing all spring configs and it doesn't show up. How can I fix this.

Try changing the variable name in the secret to amq_password
Update:
If you use environment variables rather than system properties, most operating systems disallow period-separated key names, but you can use underscores instead (e.g. SPRING_CONFIG_NAME instead of spring.config.name).
https://docs.spring.io/spring-boot/docs/1.5.6.RELEASE/reference/html/boot-features-external-config.html

Related

passing application configuration using K8s configmaps

How to pass in the application.properties to the Spring boot application using configmaps. Since the application.yml file contains sensitive information, this requires to pass in secrets and configmaps. In this case what options do we have to pass in both the sensitive and non-sensitive configuration data to the Spring boot pod.
I am currently using Spring cloud config server and Spring cloud config server can encrypt the sensitive data using the encrypt.key and decrypt the key.
ConfigMaps as described by #paltaa would do the trick for non-sensitive information. For sensitive information I would use a sealedSecret.
Sealed Secrets is composed of two parts:
A cluster-side controller / operator
A client-side utility: kubeseal
The kubeseal utility uses asymmetric crypto to encrypt secrets that only the controller can decrypt.
These encrypted secrets are encoded in a SealedSecret resource, which you can see as a recipe for creating a secret.
Once installed you create your secret as normal and you can then:
kubeseal --format=yaml < secret.yaml > sealed-secret.yaml
You can safely push your sealedSecret to github etc.
This normal kubernetes secret will appear in the cluster after a few seconds and you can use it as you would use any secret that you would have created directly (e.g. reference it from a Pod).
You can mount Secret as volumes, the same as ConfigMaps. For example:
Create the secret.
kubectl create secret generic ssh-key-secret --from-file=application.properties
Then mount it as volume:
apiVersion: v1
kind: Pod
metadata:
name: secret-test-pod
labels:
name: secret-test
spec:
volumes:
- name: secret-volume
secret:
secretName: ssh-key-secret
containers:
- name: ssh-test-container
image: mySshImage
volumeMounts:
- name: secret-volume
readOnly: true
mountPath: "/etc/secret-volume"
More information in https://kubernetes.io/docs/concepts/configuration/secret/

How to populate application.properties file value from kubernetes Secrets mounted as file

I am working on Springboot and Kubernetes and I have really simple application that connects to Postgres database. I want to get the value of datasource from configmap and password from secrets as mount file.
Configmap file :
apiVersion: v1
kind: ConfigMap
metadata:
name: customer-config
data:
application.properties: |
server.forward-headers-strategy=framework
spring.datasource.url=jdbc:postgresql://test/customer
spring.datasource.username=postgres
Secrets File :
apiVersion: v1
kind: Secret
metadata:
name: secret-demo
data:
spring.datasource.password: cG9zdGdyZXM=
deployment file :
spec:
containers:
- name: customerc
image: localhost:8080/customer
imagePullPolicy: IfNotPresent
ports:
- containerPort: 8282
volumeMounts:
- mountPath: /workspace/config/default
name: config-volume
- mountPath: /workspace/secret/default
name: secret-volume
volumes:
- name: config-volume
configMap:
name: customer-config
- name: secret-volume
secret:
secretName: secret-demo
items:
- key: spring.datasource.password
path: password
If I move spring.datasource.password prop from secret to configmap then it works fine or If I populate its value as env variable then also work fine.
But as we know both are not secure way to do so, can someone tell me what's wrong with file mounting for secrets.
Spring Boot 2.4 added support for importing a config tree. This support can be used to consume configuration from a volume mounted by Kubernetes.
As an example, let’s imagine that Kubernetes has mounted the following volume:
etc/
config/
myapp/
username
password
The contents of the username file would be a config value, and the contents of password would be a secret.
To import these properties, you can add the following to your application.properties file:
spring.config.import=optional:configtree:/etc/config/
This will result in the properties myapp.username and myapp.password being set . Their values will be the contents of /etc/config/myapp/username and /etc/config/myapp/password respectively.
By default, consuming secrets through the API is not enabled for security reasons.Spring Cloud Kubernetes requires access to Kubernetes API in order to be able to retrieve a list of addresses of pods running for a single service. The simplest way to do that when using Minikube is to create default ClusterRoleBinding with cluster-admin privilege.
Example on how to create one :-
$ kubectl create clusterrolebinding admin --clusterrole=cluster-admin --serviceaccount=default:default
You need to give secret type in manifest file. Hope it will work.
apiVersion: v1
kind: Secret
metadata:
name: secret-demo
type: Opaque
data:
spring.datasource.password: cG9zdGdyZXM

Retrieve Kubernetes Secrets mounted as volumes

Hi I am playing around with Kubernetes secrets.
My deployment file is :
---
apiVersion: v1
kind: Secret
metadata:
name: my-secrets
labels:
app: my-app
data:
username: dXNlcm5hbWU=
password: cGFzc3dvcmQ=
I am able to create secrets and I am mounting them in my deployments as below:
---
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: my-service
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: spring-service
labels:
app: spring-service
spec:
replicas: 1
selector:
matchLabels:
app: spring-service
template:
metadata:
labels:
app: spring-service
spec:
containers:
- name: spring-service
image: my-image:tag
imagePullPolicy: Always
ports:
- containerPort: 8080
volumeMounts:
- name: my-secret-vol
mountPath: "/app/secrets/my-secret"
readOnly: true
volumes:
- name: my-secret-vol
secret:
secretName: my-secrets
My question is how can I access username and password I created in secret in spring-boot app?
I have tried loading in with ${my-secrets.username} and ${username}, but it fails to find values.
I also tried adding secrets as enviroment variables as below in deployment.yml:
env:
- name: username
valueFrom:
secretKeyRef:
name: my-secrets
key: username
- name: password
valueFrom:
secretKeyRef:
name: my-secrets
key: password
In this case, values are loaded from secrets and when I change values of secrets in minikube dashboard, it does not reflect the changes.
Please help me to understand how this works.
I am using minikube and docker as containers
You don't inject the secret into properties.yml. Instead, you use the content of the secret as properties.yml. The process is look like the following:
Create a properties.yml with the sensitive data (e.g. password)
Base64 encode this file (e.g. base64 properties.yml).
Take the base64 encoded value and put that in the secret under the key properties.yaml.
You should end up with a secret in the following format:
apiVersion: v1
kind: Secret
metadata:
name: my-secrets
labels:
app: my-app
data:
properties.yml: dXNlcm5hbWU=
Now when you mount this secret on your pod, Kubernetes will decrypt the secret and put the value under the relevant path and you can just mount it.
The pattern is to have 2 configuration files - one with non-sensitive configurations that is stored with the code, and the second (which includes sensitive configurations) stored as a secret. I don't know if that possible to load multiple config files using Spring Boot.
And one final comment - this process is cumbersome and error-prone. Each change to the configuration file requires decoding the original secret and repeating this manual process. Also, it's very hard to understand what changed - all you see is the entire content has changed. For that reason, we build Kamus. It let you encrypt only the sensitive value instead of the entire file. Let me know if that could be relevant for you :)
For the first approach you'll find the values on:
- /app/secrets/my-secret/username
- /app/secrets/my-secret/password
and for the second approach you can't change the value of env vars during runtime, you need to restart or redeploy the pod

Openshift - Variables in Config for different Environments

I am currently trying to make deployments on two different openshift clusters, but I only want to use one deploymentconfig file. Is there a good way to overcome the current problem
apiVersion: v1
kind: DeploymentConfig
metadata:
labels:
app: my-app
deploymentconfig: my-app
name: my-app
spec:
selector:
app: my-app
deploymentconfig: my-app
strategy:
type: Rolling
rollingParams:
intervalSeconds: 1
maxSurge: 25%
maxUnavailability: 25%
timeoutSeconds: 600
updatePeriodSeconds: 1
replicas: 1
template:
metadata:
labels:
app: my-app
deploymentconfig: my-app
spec:
containers:
- name: my-app-container
image: 172.0.0.1:5000/int-myproject/my-app:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
protocol: TCP
env:
- name: ROUTE_PATH
value: /my-app
- name: HTTP_PORT
value: "8080"
- name: HTTPS_PORT
value: "8081"
restartPolicy: Always
dnsPolicy: ClusterFirst
Now if you look at spec.template.spec.containers[0].image there are two problems with this
Nr.1
172.0.0.1:5000/int-myproject/my-app:latest
The IP of the internal registry will differ between the two environments
Nr.2
172.0.0.1:5000/int-myproject/my-app:latest
The namespace will also not be the same. In this scenario I want this to be int-myproject or prod-myproject depending on the environment i want to deploy to. I was thinking maybe there is a way to use parameters in the yaml and pass them to openshift somehow similar to this
oc create -f deploymentconfig.yaml --namespace=int-myproject
and have a parameter like ${namespace} in my yaml file. Is there a good way to achieve this?
Firstly, to answer your question, yes you can use parameters with OpenShift templates and pass the value and creation time.
To do this, you will add the required template values to your yaml file and instead of using oc create you will use oc new-app -f deploymentconfig.yaml --param=SOME_KEY=someValue. Check out oc new-app --help for more info here.
Some other points to note though: IF you are referencing images from internal registry you might be better off to use imagestreams. These provide an abstraction for images pulled from internal docker registry on OpenShift, as is the case you have outlined.
Finally, the namespace value is available via the downward API in every Pod and you should not need to (typically) inject that manually.

Openshift: Configmap not picked up by the application

I have a springboot application deployed in openshift with application.properties having
greeting.constant = HelloWorld.SpringProp
I have also defined the fabric8/configmap.yml
apiVersion: v1
kind: ConfigMap
metadata:
name: sampleappconfig
data:
greeting.constant: Hellowrold.Poc.ConfigMap.Test
and fabric8/deployment.yml
spec:
template:
spec:
containers:
- name: sampleappcontainer
env:
- name: greeting.constant
valueFrom:
configMapKeyRef:
name: sampleappconfig
key: greeting.constant
envFrom:
- configMapRef:
name: sampleappconfig
resources:
requests:
cpu: "0.2"
# memory: 256Mi
limits:
cpu: "1.0"
# memory: 256Mi
On deploying the application using fabric8, it creates the Configmap in the Openshift and I also see "greeting.constant" in the "Environment" tab of the Application in openshift webconsole.
The issue is I would expect the application to pick up the values given in the Configmap instead of Spring application.properties as Env variables takes precendence. But, running the application logs "HelloWorld.SpringProp" instead of "Hellowrold.Poc.ConfigMap.Test".
How do I make my application to refer the properties from Configmap?
ConfigMap changes are only reflected in the container automatically if mounting the ConfigMap as a file and the application can detect changes to the file and re-read it.
If the ConfigMap is used to populate environment variables, it is necessary to trigger a new deployment for the environment variables to be updated. There is no way to update live the values of environment variables that the application sees by changing the ConfigMap.

Resources