unc0ded
unc0ded

Reputation: 121

Permission denied on port 80 for Node process on Kubernetes

I am trying to run a Node.js application in a Kubernetes container (Azure Kubernetes Service) which listens on port 80 for HTTP connections. Trying to run as non-root user in the Dockerfile results in the following error:

node:events:496
      throw er; // Unhandled 'error' event
      ^

Error: listen EACCES: permission denied 0.0.0.0:80
    at listenInCluster (node:net:1945:12)
    at Server.listen (node:net:2037:7)
    at Function.listen (/data/app/node_modules/express/lib/application.js:635:24)
    at /data/app/app.js:67:24
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
Emitted 'error' event on Server instance at:
    at emitErrorNT (node:net:1924:8)
    at process.processTicksAndRejections (node:internal/process/task_queues:82:21) {
  code: 'EACCES',
 errno: -13,
  syscall: 'listen',
  address: '0.0.0.0',
  port: 80
}

Found a reference here for giving the node executable the capability to bind to a privileged port (< 1024), and modified my Dockerfile as follows (the base image is a Red Hat UBI Minimal one):

USER root

RUN microdnf install sudo

RUN sudo setcap cap_net_bind_service=+ep `readlink -f $(which node)`

USER appUser

CMD node app.js

And I have verified that the setcap command is working as expected using getcap as well, but it does not seem to solve the issue. I am running this in an enterprise Kubernetes environment, so I'm not sure if there is anything else interfering here.

One thing to note is that the entrypoint specified in the Dockerfile is being overriden in Kubernetes by the command and args being specified in the container spec (invokes a shell script to load the environment variables from a file to memory and then runs the node app.js command from there). Is it possible that this is causing the problem?

Upvotes: 1

Views: 1912

Answers (2)

MSwehli
MSwehli

Reputation: 513

You can enable the use of privellaged ports in the deployment under spec->template->spec->securityContext by adding the following:

sysctls:              
  - name: net.ipv4.ip_unprivileged_port_start
    value: "0"

Some people disapprove of it, not entirely sure, but it works and doesn't require privilege escalations. More info available here: https://kubernetes.io/docs/tasks/administer-cluster/sysctl-cluster/

Upvotes: 1

Arko
Arko

Reputation: 3781

As suggested by David, one of the simplest solutions is to run your application on a non-privileged port (e.g., 8080) inside the container and map it to port 80 at the Kubernetes Service level. Change your Node.js application to listen on port 8080 instead of 80. This avoids the need for special permissions.

  1. Modify the Node.js application to use a non-privileged port.
  2. Map the Kubernetes Service to port 80.
  3. Set up the Ingress controller to expose the application.

In your Node.js application, change the port from 80 to 8080:

const express = require('express');
const app = express();
const port = process.env.PORT || 8080;

app.get('/', (req, res) => {
  res.send('Hello World!');
});

app.listen(port, () => {
  console.log(`Server running on port ${port}`);
});

Update your Kubernetes Service to map port 80 to the container port 8080:

apiVersion: v1
kind: Service
metadata:
  name: my-node-app
spec:
  selector:
    app: my-node-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
kubectl create namespace node-app

enter image description here

apiVersion: apps/v1
kind: Deployment
metadata:
  name: node-app
  namespace: node-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: node-app
  template:
    metadata:
      labels:
        app: node-app
    spec:
      containers:
      - name: node-app
        image: <your-dockerhub-username>/node-app:latest
        ports:
        - containerPort: 8080
apiVersion: v1
kind: Service
metadata:
  name: node-app
  namespace: node-app
spec:
  selector:
    app: node-app
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8080
kubectl apply -f deployment.yaml
kubectl apply -f service.yaml

Expose the Application with an Ingress Controller

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: node-app-ingress
  namespace: node-app
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: node-app
            port:
              number: 80
kubectl apply -f ingress.yaml

enter image description here enter image description here

enter image description here

By following these steps, you should be able to run your Node.js application as a non-root user.

References:

Upvotes: -1

Related Questions