They're complete opposites:
AmbientCapabilitiesgrants 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.
CapabilityBoundingSetlimits 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.
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?
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.
Use AmbientCapabilities=CAP_NET_BIND_SERVICE
Add `AmbientCapabilities=CAP_NET_BIND_SERVICE` to `graylog-server.service` to allow binding to port <1024
How do you allow an arbitrary process invocation to bind to port 80 without root?
linux - Script with cap_net_bind_service can't listen on port 80 - Stack Overflow
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:
- You will need at least a 2.6.24 kernel
- 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. - Linux will disable
LD_LIBRARY_PATHon anyprogramthat has elevated privileges likesetcaporsuid. So if yourprogramuses 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.
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_SERVICEgrants trust to the binary but provides no control over per-port access.Authbindgrants trust to the user/group and provides control over per-port access, and supports both IPv4 and IPv6
Install:
apt-get install authbindConfigure 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/443Execute your command via
authbind
(optionally specifying--deepor other arguments, seeman 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
iptablesif you want to stay secure, - compile the kernel if you want to be sure this restriction never bothers you again.
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?
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
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.