Spring Client in Kubernetes - Connect to SSL/TLS Enabled RabbitMQ Server

Secure Spring Client Connections to RabbitMQ in Kubernetes

Posted by Alfus Jaganathan on Thursday, March 14, 2024

Background

Say, we have a RabbitMQ server where SSL/TLS is enabled, so thats clients are enforced to connect to the server using the AMQPS protocol via port 5671. To further continue with this article, it is advisable to have a basic understanding of key concepts like SSL, TLS, mutual TLS, etc. Knowing the fundamentals of kubernetes is also important as the client under this article is assumed to be running in a kubernetes pod.

Following the below instructions or steps will help in configuring the client and the necessary credentials for the above purpose. Refer here for general, official documentation and samples.

Prerequisites

  • RabbitMQ server with SSL/TLS enabled
  • Kubernetes environment and required privileges to create a pod (client application)
  • Client certificate, key, and root certificate (for mutual TLS)
  • Spring boot java application (client application)
  • Java installed & %PATH% configured accordingly

Getting Started

As spring java client requires a keystore to pull the certificates from rather than a certificate file path, we need to create the required keystores in the first place and then import the required certificates and keys into. This is one of the main reasons for this article to be uniquely focussed for spring or java client applications.

This article doesn’t focus on keytool and the creation of the keystore and it’s advanced options, but provided below are some basic instructions and commands that can be used to import the certificates and keys into a keystore. Please refer to the official keytool documentation for more information.

As mentioned in the prerequisites, make sure that the required certificates are available. Ensure that the right CN or SAN is used. Ideally, for the client certificates, $(hostname) can be used or else if permitted, a wildcard * can be used. Refer here for more details on this topic.

To import a client certificate client.crt and its corresponding private key client.key into a keystore file called keystore.jks using keytool, the certificate should be usually in PEM or DER format, and the private key in PEM or PKCS#12 format. You can convert them using OpenSSL, as below. You may protect the client.p12 file using a password, which will be the source keystore password source_password in the later steps.

openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name client_certs

Next, we need to import client.p12 file, as below. We may be prompted for creating password for the new jks keystore, so provide one as needed.

keytool -importkeystore -srckeystore client.p12 -srcstoretype PKCS12 -destkeystore keystore.jks -deststoretype JKS -srcstorepass <source_password> -deststorepass <destination_password>

Now, we need to create truststore.jks and import certificates from trusted Certificate Authorities (CAs), as below.

keytool -import -trustcacerts -file root_ca.crt -keystore truststore.jks -alias ca_certs

We have both keystore ane truststore created, need to get them into kubernetes so that it can be consumed by the client application. For that, let’s create the kubernetes secrets to store the keystore and truststore files, using below commands. Please not that these secrets to be created in the same namespace as your application resides.

kubectl create secret generic rabbitmq-spring-client-keystore --from-file=keystore.jks
kubectl create secret generic rabbitmq-spring-client-truststore --from-file=truststore.jks

We need to add the password of the each keystores into kubernetes secrets. Below is the sample yaml (secret_definition.yaml) we can use. Replace <BASE64_ENCODED_KEYSTORE_PASSWORD> with the correct passwords that are base64 encoded, sample command echo -n "password_value" | base64. Use kubectl apply -f secret_definition.yaml command to create the below kubernetes secrets. If we don’t want to create an yaml file, use the second option below.

apiVersion: v1
kind: Secret
metadata:
  name: rabbitmq-spring-client-keystore-password
type: Opaque
data:
  password: <BASE64_ENCODED_KEYSTORE_PASSWORD>
---
apiVersion: v1
kind: Secret
metadata:
  name: rabbitmq-spring-client-truststore-password
type: Opaque
data:
  password: <BASE64_ENCODED_TRUSTSTORE_PASSWORD>

Second option is to use inline command as below to create the kubernetes secrets.

kubectl apply -f - <<EOF
apiVersion: v1
kind: Secret
metadata:
  name: rabbitmq-spring-client-keystore-password
type: Opaque
data:
  password: $(echo -n "password_value" | base64)
---
apiVersion: v1
kind: Secret
metadata:
  name: rabbitmq-spring-client-truststore-password
type: Opaque
data:
  password: $(echo -n "password_value" | base64)
EOF

Now that the secrets are created, we need to have them used by the application kubernetes pods, for which we need to mount these keystore files in the kubernetes pod or deployment similar to the definition as below. A full deployment_definition.yaml file content looks like the below sample. Use kubectl apply -f deployment_definition.yaml command to create the below kubernetes deployment.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: rabbitmq-spring-client-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: rabbitmq-spring-client-app
  template:
    metadata:
      labels:
        app: rabbitmq-spring-client-app
    spec:
      containers:
      - name: rabbitmq-spring-client-app
        image: rabbitmq-spring-client-app-image:tag
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: rabbitmq-spring-client-keystore
          mountPath: /etc/keystore
          readOnly: true
        - name: rabbitmq-spring-client-truststore
          mountPath: /etc/truststore
          readOnly: true
        env:
        - name: KEYSTORE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: rabbitmq-spring-client-keystore-password
              key: password
        - name: TRUSTSTORE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: rabbitmq-spring-client-truststore-password
              key: password
      volumes:
      - name: rabbitmq-spring-client-keystore
        secret:
          secretName: rabbitmq-spring-client-keystore
      - name: rabbitmq-spring-client-truststore
        secret:
          secretName: rabbitmq-spring-client-truststore

We are into the final step, where we have to apply the configuration (yaml) to the spring application as below. Below configuration can be modified according to the requirements, say tls-version, key-store-type, etc.

spring:
  rabbitmq:
    host: <rabbitmq-host>
    port: 5671
    username: <rabbitmq-username>
    password: <rabbitmq-password>
    ssl:
      enabled: true
      key-store: file:/etc/keystore/keystore.jks
      key-store-password: ${KEYSTORE_PASSWORD}
      key-store-type: JKS
      trust-store: file:/etc/truststore/truststore.jks
      trust-store-password: ${TRUSTSTORE_PASSWORD}
      trust-store-type: JKS
      verify-hostname: true
      tls-version: TLSv1.2

If the certificates and credentials are valid, the client should be able to establish a successful connection with the RabbitMQ Server.

Disclaimer: I have not tested the above example definitions and commands, just scripted them for demonstration purposes only. However, this should give an overall idea of, what to do!

Additional References

Hope you had fun coding!


comments powered by Disqus