They're complete opposites:

  • AmbientCapabilities grants privileges that the process normally wouldn't have started with.

    In your example, an unprivileged service can be granted CAP_NET_BIND_SERVICE via ambient capabilities, in order to bind to 'low' TCP ports.

  • CapabilityBoundingSet limits privileges the process is allowed to obtain, and does not grant any. It is meant as a security hardening feature. For example, if the service is running as 'root' – or if it invokes a setuid program like 'sudo' – it will never be able to gain the privileges that aren't listed in its bounding set.

    In your example, the service can be restricted from "becoming root" should it turn out to be vulnerable to any exploits: if you know that it _doesn't need any other capabilities except for CAP_NET_BIND_SERVICE, you can list that as the bounding set.

For your task, it is enough to set AmbientCapabilities to grant the privileges, as the bounding set already allows everything by default – but it is a good idea to set both parameters.

Answer from grawity on Stack Exchange
Top answer
1 of 2
7

The reason Solution 1: using a systemd socket doesn't work is that the binary must be built to accept the socket from systemd. It doesn't "just work" unfortunately.

Systemd passes the socket as file descriptor 3 (after stdin, stdout, stderr). Here's an example program (from my blog post here)

package main

import (
    "log"
    "net"
    "net/http"
    "os"
    "strconv"
)

func main() {
    http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
        w.Write([]byte("Hello World!"))
    })

    if os.Getenv("LISTEN_PID") == strconv.Itoa(os.Getpid()) {
        // systemd run
        f := os.NewFile(3, "from systemd")
        l, err := net.FileListener(f)
        if err != nil {
            log.Fatal(err)
        }
        http.Serve(l, nil)
    } else {
        // manual run
        log.Fatal(http.ListenAndServe(":8080", nil))
    }
}

You would have to ask goldfish to add support.

For the systemd capabilities (Solution 2) I don't know, but could it be that you also need SecureBits=keep-caps ? systemd should set that automatically, but maybe your version of systemd (8 months ago) didn't? Vault uses it: https://learn.hashicorp.com/vault/operations/ops-deployment-guide#step-3-configure-systemd

Another option would be to use SELinux, although that might be a "now you have two problems".

Personally in your situation I think I would put nginx in front of it. What did you end up doing?

2 of 2
5

I know this isn't exactly what you wanted and adds "another piece" to the puzzle but you could consider creating a systemd .service file and moving the applications listening port to >1023 (this allows non-root to bind to it), then create an iptables rule that redirects all traffic from port 443 to your new custom port like this:

iptables -t nat -A PREROUTING -i <incoming_interface> -p tcp --dport 443 -j REDIRECT --to-port 8443

In this example, all tcp traffic to port 443 would be transparently redirected to port 8443.

Discussions

Use AmbientCapabilities=CAP_NET_BIND_SERVICE
Use AmbientCapabilities=CAP_NET_BIND_SERVICE in kodi.service to allow kodi use port 80 for webserver. More on github.com
🌐 github.com
8
August 27, 2017
Add `AmbientCapabilities=CAP_NET_BIND_SERVICE` to `graylog-server.service` to allow binding to port <1024
What? Add configuration to Graylog's service file that allows the Graylog-Server processes to bind to ports below 1024. Adding AmbientCapabilities=CAP_NET_BIND_SERVICE to /usr/lib/systemd/syste... More on github.com
🌐 github.com
2
March 6, 2023
How do you allow an arbitrary process invocation to bind to port 80 without root?
Under systemd, you have these options: Give it CAP_NET_BIND_SERVICE: [Service] ExecStart=/usr/bin/node ... User=whoever AmbientCapabilities=CAP_NET_BIND_SERVICE This is a bit better than setcap on /usr/bin/node, since it only applies to that particular service. Use socket activation: # your-service.socket [Socket] ListenStream=80 # your-service.service [Service] ExecStart=/usr/bin/node ... User=whoever # in your service // use systemd-provided file descriptor instead of binding in Node server.listen({fd: 3}); If you can use the second option (i. e., you control the source code), go for that one. It allows you to seamlessly restart the server while the socket itself stays available for new connections (the kernel will just queue them up until the new process is ready to accept them), and the permissions are also tighter (the first option allows you to also bind port 22, for example). More on reddit.com
🌐 r/linux
31
15
October 1, 2016
linux - Script with cap_net_bind_service can't listen on port 80 - Stack Overflow
I'm attempting to give a script the cap_net_bind_service Linux capability. However using setcap doesn't seem to be working. $ cat listen.sh #!/bin/bash python -m SimpleHTTPServer 80 $ getcap lis... More on stackoverflow.com
🌐 stackoverflow.com
Top answer
1 of 16
533

Okay, thanks to the people who pointed out the capabilities system and CAP_NET_BIND_SERVICE capability. If you have a recent kernel, it is indeed possible to use this to start a service as non-root but bind low ports. The short answer is that you do:

setcap 'cap_net_bind_service=+ep' /path/to/program

And then anytime program is executed thereafter it will have the CAP_NET_BIND_SERVICE capability. setcap is in the debian package libcap2-bin.

Now for the caveats:

  1. You will need at least a 2.6.24 kernel
  2. This won't work if your file is a script. (i.e. uses a #! line to launch an interpreter). In this case, as far I as understand, you'd have to apply the capability to the interpreter executable itself, which of course is a security nightmare, since any program using that interpreter will have the capability. I wasn't able to find any clean, easy way to work around this problem.
  3. Linux will disable LD_LIBRARY_PATH on any program that has elevated privileges like setcap or suid. So if your program uses its own .../lib/, you might have to look into another option like port forwarding.

Resources:

  • capabilities(7) man page. Read this long and hard if you're going to use capabilities in a production environment. There are some really tricky details of how capabilities are inherited across exec() calls that are detailed here.
  • setcap man page
  • "Bind ports below 1024 without root on GNU/Linux": The document that first pointed me towards setcap.

Note: RHEL first added this in v6.

2 of 16
82

You can use IPtables to redirect traffic on port 80 to another port on 127.0.0.1 (e.g. port 6666), which does not require root privileges.

# Enable packet forwarding in case it's disabled.
echo 1 > /proc/sys/net/ipv4/ip_forward

# Redirect all incoming traffic on port 80 to 127.0.0.1:6666
iptables -t nat -A PREROUTING -p tcp --dport 80 -j DNAT --to-destination 127.0.0.1:6666

# Ensure the response packets are routed correctly back to the client
iptables -t nat -A POSTROUTING -p tcp --dport 6666 -d 127.0.0.1 -j SNAT --to-source 127.0.0.1

You might also want to redirect udp packets:

# Redirect all incoming UDP traffic on port 80 to 127.0.0.1:6666
iptables -t nat -A PREROUTING -p udp --dport 80 -j DNAT --to-destination 127.0.0.1:6666

# Ensure the response packets are routed correctly back to the client for UDP
iptables -t nat -A POSTROUTING -p udp --dport 6666 -d 127.0.0.1 -j SNAT --to-source 127.0.0.1

Then you need to save the iptables config:

iptables-save > /etc/iptables/rules.v4

On Red Hat/CentOS systems:

service iptables save

As per 2010+, use authbind

IPv6 support has been added.

Disclaimer (update per 2021): Note that authbind works via LD_PRELOAD, which is only used if your program uses libc, which is (or might) not be the case if your program is compiled with GO/Rust, or any other compiler that avoids C. If you use go or rust, set the kernel parameter for the protected port range, see the sysctl method below </EndUpdate>

Authbind is much better than CAP_NET_BIND_SERVICE or a custom kernel.

  • CAP_NET_BIND_SERVICE grants trust to the binary but provides no control over per-port access.
  • Authbind grants trust to the user/group and provides control over per-port access, and supports both IPv4 and IPv6
  1. Install: apt-get install authbind

  2. Configure access to relevant ports, e.g. 80 and 443 for all users and groups:

    sudo touch /etc/authbind/byport/80
    sudo touch /etc/authbind/byport/443
    sudo chmod 777 /etc/authbind/byport/80
    sudo chmod 777 /etc/authbind/byport/443

  3. Execute your command via authbind
    (optionally specifying --deep or other arguments, see man authbind):

         authbind --deep /path/to/binary command line args
    
     e.g.  
    
         authbind --deep java -jar SomeServer.jar
    

If authbind doesn't cut it, because you use GO or Rust (or whatever), you can use the sysctl method.

That is, as you can see below, the kernel limited ports below 1000 to the root user. You could change that number in the kernel sources, but basically, as someone in the comments remarked, you don't want a custom kernel.

As of 2017, updating the kernel is no longer required.
You can now set the start number of the privileged ports with sysctl:

sysctl net.ipv4.ip_unprivileged_port_start=80

Or to persist:

sysctl -w net.ipv4.ip_unprivileged_port_start=80

And if that yields an error, simply edit /etc/sysctl.conf with nano and set the parameter there for persistence across reboots.

or via procfs

echo 80 | sudo tee /proc/sys/net/ipv4/ip_unprivileged_port_start

As a follow-up to Joshua's fabulous (=not recommended unless you know what you do) recommendation to hack the kernel:

I've first posted it here.

Simple. With a normal or old kernel, you don't.
As pointed out by others, iptables can forward a port.
As also pointed out by others, CAP_NET_BIND_SERVICE can also do the job.
Of course CAP_NET_BIND_SERVICE will fail if you launch your program from a script, unless you set the cap on the shell interpreter, which is pointless, you could just as well run your service as root...
e.g. for Java, you have to apply it to the JAVA JVM

sudo /sbin/setcap 'cap_net_bind_service=ep' /usr/lib/jvm/java-8-openjdk/jre/bin/java

Obviously, that then means any Java program can bind system ports.
Ditto for mono/.NET.

I'm also pretty sure xinetd isn't the best of ideas.
But since both methods are hacks, why not just lift the limit by lifting the restriction ?
Nobody said you have to run a normal kernel, so you can just run your own.

You just download the source for the latest kernel (or the same you currently have). Afterwards, you go to:

/usr/src/linux-<version_number>/include/net/sock.h:

There you look for this line

/* Sockets 0-1023 can't be bound to unless you are superuser */
#define PROT_SOCK       1024

and change it to:

#define PROT_SOCK 0

if you don't want to have an insecure ssh situation, you alter it to this:

#define PROT_SOCK 24

Generally, I'd use the lowest setting that you need, e.g. 79 for http, or 24 when using SMTP on port 25.

That's already all.
Compile the kernel, and install it.
Reboot.
Finished - that stupid limit is GONE, and that also works for scripts.

Here's how you compile a kernel:

https://help.ubuntu.com/community/Kernel/Compile

# You can get the kernel-source via package `linux-source`, no manual download required
apt-get install linux-source fakeroot

mkdir ~/src
cd ~/src
tar xjvf /usr/src/linux-source-<version>.tar.bz2
cd linux-source-<version>

# Apply the changes to PROT_SOCK define in /include/net/sock.h

# Copy the kernel config file you are currently using
cp -vi /boot/config-`uname -r` .config

# Install ncurses libary, if you want to run menuconfig
apt-get install libncurses5 libncurses5-dev

# Run menuconfig (optional)
make menuconfig

# Define the number of threads you wanna use when compiling (should be <number CPU cores> - 1), e.g. for quad-core
export CONCURRENCY_LEVEL=3
# Now compile the custom kernel
fakeroot make-kpkg --initrd --append-to-version=custom kernel-image kernel-headers

# And wait a long long time

cd ..

In a nutshell:

  • use iptables if you want to stay secure,
  • compile the kernel if you want to be sure this restriction never bothers you again.
🌐
GitHub
github.com › graysky2 › kodi-standalone-service › issues › 4
Use AmbientCapabilities=CAP_NET_BIND_SERVICE · Issue #4 · graysky2/kodi-standalone-service
August 27, 2017 - Use AmbientCapabilities=CAP_NET_BIND_SERVICE in kodi.service to allow kodi use port 80 for webserver.
Author   graysky2
🌐
ilManzo's blog
ilmanzo.github.io › posts › playing with linux kernel capabilities
Playing with Linux kernel capabilities | ilManzo's blog
August 2, 2024 - Here’s an example of a systemd unit service file that grants permission to bind to lower ports: [Service] User=bob AmbientCapabilities=CAP_NET_BIND_SERVICE
🌐
Sleepless Beastie
sleeplessbeastie.eu › how to start service on the privileged port as a regular user
How to start service on the privileged port as a regular user | sleeplessbeastie's notes
December 1, 2022 - $ echo -e "[Service]\nAmbientCapabilities=CAP_NET_BIND_SERVICE" | tee /etc/systemd/system/jenkins.service.d/capabilities.conf · [Service] AmbientCapabilities=CAP_NET_BIND_SERVICE
🌐
Arch Linux Forums
bbs.archlinux.org › viewtopic.php
[Solved] Unable to add CAP_NET_BIND_SERVICE capability to service / Networking, Server, and Protection / Arch Linux Forums
So I have added the following to /usr/lib/systemd/system/gitea.service · # Set port permissions capability SecureBits=keep-caps AmbientCapabilities=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN
Find elsewhere
🌐
Stbuehler
stbuehler.de › blog › article › 2017 › 06 › 23 › systemd__allow_normal_process_to_bind_to_privileged_port.html
Stefan's Blog - systemd: allow normal process to bind to privileged port
June 23, 2017 - Such services cannot bind to priliged ports (< 1024) usually – in this case I needed it to listen to port 443 though (additionally to some high port) to allow users behind “strange” firewall configurations to connect to the server. The solution is to add the following to the service (for example by running systemctl edit inspircd.service): [Service] AmbientCapabilities=CAP_NET...
🌐
Debian
wiki.debian.org › Hardening › Daemons
Hardening/Daemons - Debian Wiki
September 10, 2017 - bind · 1. systemd unit file: /etc/systemd/system/freeradius.service · [Unit] Description=FreeRADIUS multi-protocol policy server After=network.target Documentation=man:radiusd(8) man:radiusd.conf(5) http://wiki.freeradius.org/ http://networkradius.com/doc/ [Service] Type=forking PIDFile=/run/freeradius/freeradius.pid EnvironmentFile=-/etc/default/freeradius User=freerad AmbientCapabilities=CAP_NET_ADMIN CAP_NET_RAW CAP_NET_BIND_SERVICE ExecStart=/usr/sbin/freeradius $FREERADIUS_OPTIONS Restart=on-failure RestartSec=5 [Install] WantedBy=multi-user.target ·
🌐
Linux Man Pages
man7.org › linux › man-pages › man7 › capabilities.7.html
capabilities(7) - Linux manual page
CAP_NET_BIND_SERVICE Bind a socket to Internet domain privileged ports (port numbers less than 1024). CAP_NET_BROADCAST (Unused) Make socket broadcasts, and listen to multicasts. CAP_NET_RAW • Use RAW and PACKET sockets; • bind to any address for transparent proxying.
🌐
Ubuntu
bugs.launchpad.net › bugs › 1875370
Bug #1875370 “Add “AmbientCapabilities=CAP_NET_BIND_SERVICE” to ...” : Bugs : coturn package : Ubuntu
April 27, 2020 - Please add the line "AmbientCapabilities=CAP_NET_BIND_SERVICE" to the [Service] section of coturn.service so that coturn can bind to ports
🌐
Container Solutions
blog.container-solutions.com › linux-capabilities-in-practice
Linux Capabilities In Practice
September 25, 2019 - For example, we should be able to create a ‘webserver’ environment that can bind to port 80 by putting CAP_NET_BIND_SERVICE in the ambient capabilities, without requiring any other capabilities or running as the root user (note that there other solutions to this problem, such as using sysctl net.ipv4.ip_unprivileged_port_start).
🌐
Fabiolb
fabiolb.net › faq › binding-to-low-ports
Binding to Low Ports
$ cat /etc/systemd/system/fabio.service [Unit] Description=Fabio proxy After=syslog.target After=network.target [Service] LimitMEMLOCK=infinity LimitNOFILE=65535 Type=simple # unprivileged uid and gid User=fabio_user Group=fabio_group WorkingDirectory=/ ExecStart=/path/to/fabio -cfg /path/to/fabio.conf Restart=always # no need that fabio messes with /dev PrivateDevices=yes # dedicated /tmp PrivateTmp=yes # make /usr, /boot, /etc read only ProtectSystem=full # /home is not accessible at all ProtectHome=yes # to be able to bind port < 1024 AmbientCapabilities=CAP_NET_BIND_SERVICE NoNewPrivileges=yes # only ipv4, ipv6, unix socket and netlink networking is possible # netlink is necessary so that fabio can list available IPs on startup RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX AF_NETLINK ·
🌐
Reddit
reddit.com › r/linux › how do you allow an arbitrary process invocation to bind to port 80 without root?
r/linux on Reddit: How do you allow an arbitrary process invocation to bind to port 80 without root?
October 1, 2016 -

I'm working on a Node webservice that runs on Ubuntu 16.04, and I'd like to run it on port 80 without giving it root privileges. It runs as a systemd service.

I don't want it to listen on a high port and be forced to use Apache/nginx to redirect to localhost:3000. Node's built-in HTTP server is good enough for me, I don't see the point in adding a major, complex external dependency just to get around this issue.

Note that I already know about setcap 'cap_net_bind_service=+ep' /usr/bin/node That's what I'm doing now. What I don't like about it is that it whitelists the binary for good, instead of just my own service, so it feels wrong.

What are some simple alternatives?

Top answer
1 of 2
5

setcap(8) only sets capabilities on files. When it comes to interpreters, I think you're in for a rough ride. capabilities(7) -- I'm reading from RHEL 7.4 -- lists 'Thread' capability sets as well as 'File' capabilities. In 'Thread' capability sets, there is a notion of 'Ambient' sets, as well as 'Inheritable'. The important distinction is that 'inheritable capabilities are not generally preserved across execve(2) when running as a non-root user', you should set the Ambient capability set.

Note: RHEL 7 (7.4) has this backported. 'Ambient' capabilities apparently came out in Linux 4.3, and RHEL 7 is nominally a 3.10 series kernel.

I struck a similar issue as yourself, except I was trying to use a Python server under Systemd. I found that I needed to set the 'Ambient' capability set. I imagine this is what gets inherited. Here's an example Systemd unit file:

[Unit]
Description=Docker Ident Service
After=network.target
Wants=docker.service

[Service]
Type=simple
ExecStart=/usr/local/sbin/docker-identd
Restart=on-failure
RestartSec=43s

User=docker-identd
Group=docker

CapabilityBoundingSet=CAP_NET_BIND_SERVICE
AmbientCapabilities=CAP_NET_BIND_SERVICE
NoNewPrivileges=true

Nice=12

StandardOutput=syslog
StandardError=syslog
SyslogFacility=daemon
SyslogIdentifier=docker-identd
SyslogLevel=info

[Install]
WantedBy=multi-user.target

Thanks for the question. It gave me an opportunity to learn a bit more.

For some further reading as to how Ambiant Capabililities works in practice, have a look at Kubernetes should configure the ambient capability set #56374

Cheers, Cameron

2 of 2
1

File capabilities applied to scripts (executables with shebang headers) won't have any effect. Instead you need to apply the capabilities to the binary interpreter used to execute the script (usually the command mentioned in the shebang header).

In your case just apply the capabilities to the python executable.

🌐
GitHub
github.com › rootless-containers › slirp4netns › issues › 149
unable to bind to privileged ports with `CAP_NET_BIND_SERVICE` given to podman via systemd service · Issue #149 · rootless-containers/slirp4netns
September 4, 2019 - Note: I have included the AmbientCapabilities=CAP_NET_BIND_SERVICE line which should allow the processes that this service is responsible for to bind to the traditionally 'privileged' ports.
Author   rootless-containers
🌐
GitHub
github.com › timberio › vector › issues › 3935
systemd service: Should have CAP_NET_BIND_SERVICE · Issue #3935 · vectordotdev/vector
September 16, 2020 - In order to allow Vector to bind .../software/systemd/man/systemd.exec.html) to CAP_NET_BIND_SERVICE (https://man7.org/linux/man-pages/man7/capabilities.7.html) in our systemd unit....
Author   vectordotdev
🌐
Reddit
reddit.com › r/systemd › ambientcapabilities ignored in simple service
r/systemd on Reddit: AmbientCapabilities ignored in simple service
September 14, 2021 - [Service] AmbientCapabilities=CAP_NET_BIND_SERVICE CapabilityBoundingSet=CAP_NET_BIND_SERVICE