Running Nginx in pod without privilege escalation
Asked Answered
V

1

9

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 chmoding, 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
Vanmeter answered 31/3, 2023 at 12:58 Comment(7)
I think you need more info to proceed. Can you get into the container and find /var/log/syslog? There might be some info there. Also, there might be some information inside nginx's error.log.Herzegovina
error.log points to stderr which gets picked up automatically by kubectl 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 #6Vanmeter
Not that familiar with kubernetes - can't you enter into a container like you can in docker? Pretty sure you'd find some extra info on error 13 in syslog.Herzegovina
Yes, just like in Docker you can enter a container. Anyway, /var/log/syslog doesn't exist.Vanmeter
Mind sharing a working .yaml file to deploy the container?Herzegovina
I've added the yaml of an unmodified nginx pod with the security context and mounted directories so it results in the same bind error.Vanmeter
You might be overthinking this. Run nginx in your pods on an unprivileged port and put a Service object in front of it to listen on 443 or 80 and load-balance requests to the pods.Venditti
O
2

You can't find privileged ports (less than 1024) without privileged access. https://medium.com/@natrajrams4/binding-to-low-ports-as-non-root-user-in-kubernetes-79666f52c6ae has some hack in it. But I will suggest you use higher numbers ports

Outspread answered 19/9, 2024 at 3:53 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.