Reputation: 121
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
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
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.
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
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
By following these steps, you should be able to run your Node.js application as a non-root user.
References:
Upvotes: -1