Issue in Kubernetes secret while mounting the secret into the volume

Hi team,

I am facing an issue where I created a Kubernetes secret containing two password files. In my deployment, I have two containers. I mounted the above secret into one of the containers using defaultMode: 0400 and readOnly: true. I expected the secret files to be mounted with 0400 permissions (i.e., readable only by the owner). However, when the secret is mounted into the specified directory, the file permissions are actually 0440 (i.e., readable by both the owner and the group).

I tried multiple methods to resolve this:

  • Using an init container to create an emptyDir volume and change the file permissions.
  • Using the cp command to copy the files to a writable directory and modify the permissions.

However, I do not want to use these workarounds in production.

Below is the actual YAML file:

apiVersion: apps/v1
kind: Deployment
metadata:
namespace:
labels:
app: pulse-grpc
name: pulse-grpc
spec:
replicas: 1
selector:
matchLabels:
app: pulse-grpc
strategy:
rollingUpdate:
maxSurge: 2
maxUnavailable: 1
type: RollingUpdate
template:
metadata:
labels:
app: pulse-grpc
spec:
securityContext:
fsGroup: 5000
initContainers:
- name: init-sysctl
image: busybox
# Updating the kernal properties
command:
- /bin/sh
- -c
- |
sysctl -w net.core.somaxconn=10000
sysctl -w net.ipv4.tcp_max_syn_backlog=4096
securityContext:
privileged: true

  containers:
    - name: prometheus-jmx-exporter
      securityContext:
        runAsUser: 5000
        runAsGroup: 5000
      image: <jmx-image>
      ports:
        - containerPort: 8081
          name: prometheus-jmx
      resources:
        limits:
          cpu: 100m
          memory: 200Mi
        requests:
          cpu: 100m
          memory: 200Mi
      command:
        - /usr/bin/java
        - -Djavax.net.ssl.trustStore=/usr/lib/pulse/ssl/client.truststore.jks
        - -Djavax.net.ssl.trustStorePassword=Ks56EEYFLFc3Ng19ZG
        - -Djavax.net.ssl.keyStore=/usr/lib/pulse/ssl/client.keystore.jks
        - -Djavax.net.ssl.keyStorePassword=Ks56EEYFLFc3Ng19ZG
        - -jar
        - /usr/lib/jmx_exporter/jmx_prometheus_exporter.jar
        - 0.0.0.0:8081
        - /usr/lib/jmx_exporter/pulse_prom_metrics.yaml
      volumeMounts:
        - name: pulse-config-secret
          mountPath: /usr/lib/jmx_exporter/pulse_prom_metrics.yaml
          subPath: pulse_prom_metrics.yaml
        - name: jmx-client-ssl
          mountPath: /usr/lib/pulse/ssl/
          readOnly: true

    - name: pulse-grpc
      securityContext:
        runAsUser: 5000
        runAsGroup: 5000
      image: <grpc-image>
      imagePullPolicy: Always
      command:
        - /usr/bin/java
        - -Dcom.sun.management.jmxremote
        - -Dcom.sun.management.jmxremote.ssl=true
        - -Djavax.net.ssl.keyStore=/usr/lib/pulse/ssl/server.keystore.jks
        - -Djavax.net.ssl.keyStorePassword=Ks56EEYFLFc3Ng19ZG
        - -Djavax.net.ssl.trustStore=/usr/lib/pulse/ssl/server.keystore.jks
        - -Djavax.net.ssl.trustStorePassword=Ks56EEYFLFc3Ng19ZG
        - -Dcom.sun.management.jmxremote.registry.ssl=true
        - -Dcom.sun.management.jmxremote.port=9999
        - -Dcom.sun.management.jmxremote.rmi.port=9999
        - -Djava.rmi.server.hostname=127.0.0.1
        - -Dcom.sun.management.jmxremote.authenticate=true
        - -Dcom.sun.management.jmxremote.password.file=/usr/lib/pulse/jmx-auth/jmxremote.password
        - -Dcom.sun.management.jmxremote.access.file=/usr/lib/pulse/jmx-auth/jmxremote.access
        - -Dvertx.metrics.options.enabled=true
        - -Dvertx.metrics.options.jmxEnabled=true
        - -Dvertx.metrics.options.jmxDomain=vertx
        - -Dvertx.logger-delegate-factory-class-name=io.vertx.core.logging.SLF4JLogDelegateFactory
        - -Dlog4j.configurationFile=/usr/lib/pulse/conf/log4j2.xml
        - -XX:+UseG1GC
        - -XX:MaxGCPauseMillis=20
        - -XX:G1HeapRegionSize=1M
        - -Xms2g
        - -Xmx2g
        - -XX:+AlwaysPreTouch
        - -XX:+UnlockExperimentalVMOptions
        - -XX:G1NewSizePercent=15
        - -XX:G1ReservePercent=15
        - -XX:+DisableExplicitGC
        - -XX:+ParallelRefProcEnabled
        - -XX:MaxRAMFraction=1
        - -XX:+UseCGroupMemoryLimitForHeap
        - -server
        - -jar
        - /usr/lib/pulse/pulse.jar
        - -conf
        - /usr/lib/pulse/conf/pulse.json
      env:
        - name: LOG4J_APPLEVEL
          value: ERROR
        - name: LOG4J_KAFKALEVEL
          value: ERROR
      ports:
        - containerPort: 8080
          name: grpc
          protocol: TCP
        - containerPort: 9999
          name: jmx
      resources:
        limits:
          cpu: "4"
          memory: 6000Mi
        requests:
          cpu: "4"
          memory: 6000Mi
      readinessProbe:
        exec:
          command: [ "./grpc_health_probe", "-addr=:8080" ]
        initialDelaySeconds: 10
        timeoutSeconds: 1
        failureThreshold: 3
        periodSeconds: 10
        successThreshold: 1
      livenessProbe:
        exec:
          command: [ "./grpc_health_probe", "-addr=:8080" ]
        failureThreshold: 6
        initialDelaySeconds: 10
        periodSeconds: 30
        successThreshold: 1
        timeoutSeconds: 1
      volumeMounts:
        - name: pulse-config-secret
          mountPath: /usr/lib/pulse/conf/log4j2.xml
          subPath: log4j2.xml
        - name: pulse-config-secret
          mountPath: /usr/lib/pulse/conf/pulse.json
          subPath: pulse.json
        - name: pulse-kafka-certificate
          mountPath: /usr/lib/pulse/client-ssl/
        - name: jmx-auth-secret
          mountPath: /usr/lib/pulse/jmx-auth/
          readOnly: true
        - name: grpc-server-ssl
          mountPath: /usr/lib/pulse/ssl/  
          readOnly: true
  terminationGracePeriodSeconds: 60
  volumes:
    - name: pulse-kafka-certificate
      secret:
        secretName: pulse-kafka-certificate
        defaultMode: 0400

    - name: pulse-config-secret
      secret:
        secretName: pulse-config-secret
        defaultMode: 0400

    - name: jmx-auth-secret
      secret:
        secretName: pulse-jmx-auth
        defaultMode: 0400

    - name: grpc-server-ssl
      secret:
        secretName: grpc-server-secret
        defaultMode: 0400
    - name: jmx-client-ssl
      secret:
        secretName: jmx-client-secret
        defaultMode: 0400

inside the pod the file permission after mounting
bash-4.2$ pwd
/usr/lib/pulse/jmx-auth
bash-4.2$ ls -al
total 8
drwxrwsrwt 3 root pulse 120 May 17 01:51 .
drwxr-x— 1 pulse pulse 4096 May 17 01:51 ..
drwxr-sr-x 2 root pulse 80 May 17 01:51 ..2025_05_17_01_51_55.1328754605
lrwxrwxrwx 1 root pulse 32 May 17 01:51 ..data → ..2025_05_17_01_51_55.1328754605
lrwxrwxrwx 1 root pulse 23 May 17 01:51 jmxremote.access → ..data/jmxremote.access
lrwxrwxrwx 1 root pulse 25 May 17 01:51 jmxremote.password → ..data/jmxremote.password
bash-4.2$ cd ..data/
bash-4.2$ ls -al
total 8
drwxr-sr-x 2 root pulse 80 May 17 01:51 .
drwxrwsrwt 3 root pulse 120 May 17 01:51 ..
-r–r----- 1 root pulse 26 May 17 01:51 jmxremote.access
-r–r----- 1 root pulse 33 May 17 01:51 jmxremote.password
bash-4.2$

Logs observed in the pod:
[root@pre-prod-manager new]# kubectl logs pulse-grpc-ebay-ssl-849897d7d7-chsg2 -n radware-botmanager -c pulse-grpc
Error: Password file read access must be restricted: /usr/lib/pulse/jmx-auth/jmxremote.password

Inside the pod, I see a JMX error stating that the password file must have “owner read permission only”.

My questions:

  • I specified 0400 permissions while mounting the secret, but the permissions are not being reflected correctly inside the pod — it is actually showing as 0440.
  • How can I solve this issue properly in production pods without using workarounds like init containers or cp?

Please, can someone help me resolve this issue?
Thanks.

We are running two containers in the pod — one container collects the metrics, and the other scrapes them. We are planning to set up secure SSL and authentication for this metrics flow to ensure a fully secure connection between the containers inside the pod.

please somebody help us to fix this issue ASAP!