Injecting CA certificate inside OpenJDK container at runtime and build time
Introduction
This article discusses how to import a CA root certificate to trust store inside a OpenJDK container at runtime and build time, building on top of the solution How do I import a CA root certificate to trust store in JBoss EAP for OpenShift?, which tackles this problem at runtime (Option 1 - Mount cacerts as Secret) and build time (Option 2 - Add a CA certification when building a container), but with other resources for runtime injection:
- At build time time, the user has flexibility on the injection of files and permissions for deployment.
- At runtime, although the user can set a difference SCC, it does not give flexibility on the permissions for
/etc/pki/ca-trust/extracted
There are several ways of generating JKS, specifically for /etc/pki/ca-trust/extracted (which is RHEL's standard way of doing certs) Service-CA Operator can be used to inject CA root certificates at runtime inside a container. However, similarly to RHEL standard injection, update-ca-trust execution is required with extract argument (for JKS creation), which requires root user, see notes for clarification.
Important notes
- update-ca-trust command requires root user to run and cannot be run on OpenJDK's container inside a OpenShift Pod.
- The default java trust store
/etc/pki/ca-trust/extracted/java/cacertsis read-only. - The Service-CA Operator injection still requires the
update-caLinux command to be executed
Consequently, one cannot update cacerts after OpenJDK container has been started already.
Therefore, the following approaches can be used to inject a CA certificate inside a OpenJDK container:
| Approaches | Build time vs Runtime |
|---|---|
| Service-CA+Sidecar or initcontainer to run update-ca | Runtime |
| chown extracted/* at image build | Buildtime |
| mount a PVC (or similar) the user can write to over extracted/* | Runtime |
| Set ROOT as user or add sudoers entry to allow update-ca (that requires root user) | Buildtime |
Build example 1
Example below at Build Time sets an OpenJDK deployment with the ROOT user.
spec:
nodeSelector: null
output:
to:
kind: ImageStreamTag
name: 'openjdk-root:latest'
resources: {}
successfulBuildsHistoryLimit: 5
failedBuildsHistoryLimit: 5
strategy:
type: Docker
dockerStrategy:
from:
kind: ImageStreamTag
namespace: openshift
name: 'ubi8-openjdk-17:1.18'
imageOptimizationPolicy: SkipLayers
postCommit: {}
source:
type: Dockerfile
dockerfile: |-
FROM ubi8-openjdk-17:1.18
USER root
RUN microdnf -y install wget
RUN wget https://github.com/path/jkube.jar -O jkube.jar
RUN mv jkube.jar /deployments/jkube.jar
runPolicy: Serial
status:
lastVersion: 8
Notes about the build above:
- The solution above is sets ROOT as user for running a pod
- Above requires SCC with
any-uid
Runtime example 1
spec:
replicas: 1
selector:
matchLabels:
deployment: openjdk11-init
template:
metadata:
creationTimestamp: null
labels:
deployment: openjdk11-init
annotations:
openshift.io/generated-by: OpenShiftNewApp
spec:
restartPolicy: Always
initContainers:
- resources: {}
terminationMessagePath: /dev/termination-log
name: init-myservice
command:
- /bin/bash
securityContext:
privileged: true
imagePullPolicy: IfNotPresent
volumeMounts:
- name: trusted-ca
mountPath: /etc/pki/ca-trust/source/anchors/
- name: new-cacerts
mountPath: /opt/new-cacerts/
terminationMessagePolicy: File
image: registry.access.redhat.com/ubi9/openjdk-17
args:
- '-c'
- update-ca-trust extract && echo "Injection" && ls -las /etc/pki/ca-trust/extracted/ && cp -a /etc/pki/ca-trust/extracted/. /opt/new-cacerts/ && ls -las /opt/new-cacerts/
serviceAccountName: sa-with-anyuid
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
securityContext: {}
containers:
- name: openjdk11-init
image: 'image-registry.openshift-image-registry.svc:5000/openjdk/openjdk11-injected@sha256:fe07aae3b4fdeec0d1e91f7c00ebb318204468b849475ea1b550a39154d7bddb'
ports:
- containerPort: 8080
protocol: TCP
- containerPort: 8443
protocol: TCP
- containerPort: 8778
protocol: TCP
resources: {}
volumeMounts:
- name: new-cacerts
mountPath: /etc/pki/ca-trust/extracted/
terminationMessagePath: /dev/termination-log
terminationMessagePolicy: File
imagePullPolicy: IfNotPresent
serviceAccount: sa-with-anyuid
volumes:
- name: new-cacerts
emptyDir: {}
- name: trusted-ca
configMap:
name: ca-inject
items:
- key: service-ca.crt
path: example.pem
defaultMode: 420
dnsPolicy: ClusterFirst
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 25%
maxSurge: 25%
revisionHistoryLimit: 10
progressDeadlineSeconds: 600
And Configmap with the annotation service.beta.openshift.io/inject-cabundle: 'true', which makes the Service-CA generate the certificates - after the user uses the command:
kind: ConfigMap
apiVersion: v1
metadata:
name: ca-inject
namespace: openjdk
annotations:
service.beta.openshift.io/inject-cabundle: 'true'
immutable: false
Workflow for the Service-CA:
Streamlined process for the Service-CA Operator injection:
- Deploy app
- Create configmap annotate it
- Run update-ca extract -> but this requires write over the
/etc/pki/ca-trust/extracted/pem/, which requires either a secret to overwrite that or a build to set user as root.
Notes about the injection above:
- The solution above is not trivial and requires a creation of a configmap to be injected via service-ca and then calls the
update-ca-trust extract, which requires root for its execution. - Requires privileged SCC - for the whole pod not just the init container. That can be a concern.
- The priviledged SCC will be available on the namespace for usage of other pods
Related solutions
The solution How do I import a CA root certificate to trust store in JBoss EAP for OpenShift?, describes a runtime and build time alternative for injecting CA Root certificates:
- Runtime alternative: generate the certificate generated local and add inside a
configmapmounted to a writable location, then setJAVA_OPTS_APPENDfor that location for the JVM to pick it up, given /etc/pki/ca-trust/extracted/java/cacerts is read-only. - Build time alternative: copy the anchors inside the image and then run update-ca-trust as root on the build process, given the update-ca-trust command requires privileges and cannot be run on JBoss EAP for OpenShift Pod.
The default java trust store /etc/pki/ca-trust/extracted/java/cacerts is read-only.