I'm trying to get Nginx to run with minimal privileges while being able to act as a proxy on port 80. In other words, this is the securityContext I'm working with:
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 12321
runAsGroup: 12321
privileged: false
capabilities:
drop:
- all
add: ["NET_BIND_SERVICE"]
From what I can gather most stackoverflow questions around this subject set allowPrivilegeEscalation
to true
. Other than that I have found this blog from Bridcrew that set it to false
but I can't reproduce their example on my end.
I also tried to use the nginxinc/nginx-unprivileged
base image but also with no luck.
Here's my Dockerfile. It consists of a lot of chown
and chmod
ing, but I also install libcap2 so I can set the NET_BIND_SERVICE
capability and move some files around for a custom entrypoint.
FROM nginx
ENV NGINX_USER="proxy-user" \
NGINXR_UID="12321" \
NGINX_GROUP="proxy-group" \
NGINX_GID="12321"
RUN set -ex; \
groupadd -r --gid "$NGINX_GID" "$NGINX_GROUP"; \
useradd -r --uid "$NGINXR_UID" --gid "$NGINX_GID" "$NGINX_USER"
# Create empty pid file and assign it to the proxy user
RUN touch /var/run/nginx.pid && \
chown -R proxy-user:proxy-group /var/run/nginx.pid
# Change permissions of other nginx files
RUN chmod +x /usr/sbin/nginx && \
chown -R proxy-user:proxy-group /usr/share/nginx/html && \
chown -R proxy-user:proxy-group /var/cache/nginx && \
chown -R proxy-user:proxy-group /var/log/nginx && \
chown -R proxy-user:proxy-group /etc/nginx/conf.d
# Install libcap and set the NET_BIND_SERVICE capability to the Nginx binary
RUN apt-get -qq update && \
apt-get -qq install --no-install-recommends libcap2-bin -y && \
setcap CAP_NET_BIND_SERVICE=ep /usr/sbin/nginx
COPY nginx.conf /etc/nginx/nginx.conf
COPY nginx.conf.template /etc/nginx/templates/
# Move all nginx files to another directory. We will later mount a writable directory under /etc/nginx/ and copy these files back before launching Nginx
RUN mv /etc/nginx /etc/nginx-defaults
# This file does the copying and launches nginx (via docker-entrypoint.sh)
COPY entrypoint.sh .
RUN chmod +x ./entrypoint.sh
USER proxy-user
ENTRYPOINT ./entrypoint.sh
As mentioned in the comments we will mount a couple of writable emptydir directories Nginx can use:
volumeMounts:
- name: nginx-etc
mountPath: /etc/nginx
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-tmp
mountPath: /tmp/nginx
- name: nginx-pid
mountPath: /var/run
And in the nginx.conf a couple of paths to point to these directories in case they weren't already the default value:
http {
proxy_temp_path /tmp/nginx/proxy_temp;
client_body_temp_path /tmp/nginx/client_temp;
fastcgi_temp_path /tmp/nginx/fastcgi_temp;
uwsgi_temp_path /tmp/nginx/uwsgi_temp;
scgi_temp_path /tmp/nginx/scgi_temp;
...
}
And here is the custom entrypoint
#!/bin/sh
echo "Running from custom entrypoint"
# Move the nginx files back to the default directory
cp -a /etc/nginx-defaults/. /etc/nginx/
# Launch nginx through the docker entrypoint
./docker-entrypoint.sh nginx
The error I'm getting is that it can't bind to port 80 due to permission denied:
2023/03/31 12:31:03 [emerg] 11#11: bind() to 0.0.0.0:80 failed (13: Permission denied)
nginx: [emerg] bind() to 0.0.0.0:80 failed (13: Permission denied)
Setting allowPrivilegeEscalation
to true
fixes the issue. As well as binding to a non privileged port (>1024), but is there a way to work around the privilege escalation issue without having to bind to a higher port?
Edit, on request the yaml of an entire pod to work with. This example uses the unmodified nginx image which also tries to bind to port 80 and shows the bind error.
apiVersion: v1
kind: Pod
metadata:
name: example
spec:
containers:
- name: proxy
image: nginx
imagePullPolicy: Always
volumeMounts:
- name: nginx-cache
mountPath: /var/cache/nginx
- name: nginx-tmp
mountPath: /tmp/nginx
- name: nginx-pid
mountPath: /var/run
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 12321
runAsGroup: 12321
privileged: false
capabilities:
drop:
- all
add: ["NET_BIND_SERVICE"]
volumes:
- name: nginx-cache
emptyDir:
sizeLimit: 1Mi
- name: nginx-tmp
emptyDir:
sizeLimit: 1Mi
- name: nginx-pid
emptyDir:
sizeLimit: 1Mi
/var/log/syslog
? There might be some info there. Also, there might be some information inside nginx'serror.log
. – Herzegovinaerror.log
points to stderr which gets picked up automatically bykubectl logs
so this is all that is logged. Even with debug logging there's one more line that doesn't tell anything new:2023/04/03 09:17:04 [debug] 11#11: bind() 0.0.0.0:80 #6
– Vanmeter/var/log/syslog
doesn't exist. – Vanmeter