Add a read-only filesystem for Kubernetes
This page describes how to add a read-only filesystem when deploying F5 WAF for NGINX when using Kubernetes.
It restricts the root filesystem to read-only mode, improving security by limiting potential write access in case of compromise.
To complete this guide, you will need the following prerequisites:
- A Kubernetes cluster that supports read-only root file systems
- The cluster must have access to the NGINX and F5 WAF configuration files
You may need to identify any extra paths that need to be writable by F5 WAF for NGINX during runtime: this document assumes you are using the default paths.
The first step is to add the readOnlyRootFilesystem
value (as true) to your Kubernetes pod security context as follows:
containers:
- name: nginx
...
securityContext:
readOnlyRootFilesystem: true
- name: waf-enforcer
...
securityContext:
readOnlyRootFilesystem: true
- name: waf-config-mgr
...
securityContext:
readOnlyRootFilesystem: true
With a read-only root file system, you will likely still require write access for certain directories, such as logs and temporary files. You can add these directories by mounting them as writable volumes in your Kubernetes deployment.
In the following example, /tmp
and /var/log/nginx
are writable directories, essential for NGINX and F5 WAF operations.
containers:
- name: nginx
...
volumeMounts:
- name: app-protect-bd-config
mountPath: /opt/app_protect/bd_config
- name: app-protect-config
mountPath: /opt/app_protect/config
- name: tmp-volume
mountPath: /tmp
- name: nginx-log
mountPath: /var/log/nginx
- name: app-protect-bundles
mountPath: /etc/app_protect/bundles
...
volumes:
- name: app-protect-bd-config
emptyDir: {}
- name: app-protect-config
emptyDir: {}
- name: nginx-log
emptyDir: {}
- name: tmp-volume
emptyDir: {}
- name: app-protect-bundles
persistentVolumeClaim:
claimName: nap5-bundles-pvc
A full example could look like the following:
apiVersion: apps/v1
kind: Deployment
metadata:
name: nap5-deployment
spec:
selector:
matchLabels:
app: nap5
replicas: 2
template:
metadata:
labels:
app: nap5
spec:
imagePullSecrets:
- name: regcred
containers:
- name: nginx
image: <your-private-registry>/nginx-app-protect-5:<your-tag>
imagePullPolicy: IfNotPresent
securityContext:
readOnlyRootFilesystem: true
volumeMounts:
- name: app-protect-bd-config
mountPath: /opt/app_protect/bd_config
- name: app-protect-config
mountPath: /opt/app_protect/config
- name: tmp-volume
mountPath: /tmp
- name: nginx-log
mountPath: /var/log/nginx
- name: app-protect-bundles
mountPath: /etc/app_protect/bundles
- name: waf-enforcer
image: private-registry.nginx.com/nap/waf-enforcer:<version-tag>
imagePullPolicy: IfNotPresent
securityContext:
readOnlyRootFilesystem: true
env:
- name: ENFORCER_PORT
value: "50000"
volumeMounts:
- name: app-protect-bd-config
mountPath: /opt/app_protect/bd_config
- name: waf-config-mgr
image: private-registry.nginx.com/nap/waf-config-mgr:<version-tag>
imagePullPolicy: IfNotPresent
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- all
volumeMounts:
- name: app-protect-bd-config
mountPath: /opt/app_protect/bd_config
- name: app-protect-config
mountPath: /opt/app_protect/config
- name: app-protect-bundles
mountPath: /etc/app_protect/bundles
volumes:
- name: app-protect-bd-config
emptyDir: {}
- name: app-protect-config
emptyDir: {}
- name: nginx-log
emptyDir: {}
- name: tmp-volume
emptyDir: {}
- name: app-protect-bundles
persistentVolumeClaim:
claimName: nap5-bundles-pvc
Once you have created writable paths in your Kubernetes cluster, you should update your NGINX configuration to use these paths.
The following are fields in nginx.conf you should update, which correspond to writable volumes configured during the last step:
user nginx;
worker_processes auto;
# F5 WAF for NGINX
load_module modules/ngx_http_app_protect_module.so;
error_log /var/log/nginx/error.log debug;
pid /tmp/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log;
# Temporary directories for kubernetes "readonlyfilesystem"
client_body_temp_path /tmp/nginx-client-body;
proxy_temp_path /tmp/nginx-proxy;
fastcgi_temp_path /tmp/nginx-fastcgi;
uwsgi_temp_path /tmp/nginx-uwsgi;
scgi_temp_path /tmp/nginx-scgi;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
# F5 WAF for NGINX
app_protect_enforcer_address 127.0.0.1:50000;
include /etc/nginx/conf.d/*.conf;
}