Categories
SysOps

How to start service on the privileged port as a regular user

Start service on the privileged port as a regular user.

I will use jenkins service as an example.

By default, jenkins the service starts at port 8080.

$ cat /etc/default/jenkins
[...]
# port for HTTP connector (default 8080; disable with -1)
HTTP_PORT=8080
[...]

Altering this value will prevent service from starting.

$ sudo tail -20 /var/log/jenkins/jenkins.log 
Caused: java.io.IOException: Failed to bind to 0.0.0.0/0.0.0.0:80
        at org.eclipse.jetty.server.ServerConnector.openAcceptChannel(ServerConnector.java:349)
        at org.eclipse.jetty.server.ServerConnector.open(ServerConnector.java:310)
        at org.eclipse.jetty.server.AbstractNetworkConnector.doStart(AbstractNetworkConnector.java:80)
        at org.eclipse.jetty.server.ServerConnector.doStart(ServerConnector.java:234)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
        at org.eclipse.jetty.server.Server.doStart(Server.java:401)
        at org.eclipse.jetty.util.component.AbstractLifeCycle.start(AbstractLifeCycle.java:73)
        at winstone.Launcher.(Launcher.java:192)
Caused: java.io.IOException: Failed to start Jetty
        at winstone.Launcher.(Launcher.java:194)
        at winstone.Launcher.main(Launcher.java:369)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.base/java.lang.reflect.Method.invoke(Method.java:566)
        at Main._main(Main.java:375)
        at Main.main(Main.java:151)

You can overcome this problem by using Linux capabilities, but this is a Java application that uses its own init script.

$ sudo systemctl status jenkins
● jenkins.service - LSB: Start Jenkins at boot time
     Loaded: loaded (/etc/init.d/jenkins; generated)
     Active: active (exited) since Tue 2021-03-30 20:22:52 UTC; 10s ago
       Docs: man:systemd-sysv-generator(8)
    Process: 7077 ExecStart=/etc/init.d/jenkins start (code=exited, status=0/SUCCESS)

Mar 30 20:22:51 jenkins systemd[1]: Starting LSB: Start Jenkins at boot time...
Mar 30 20:22:51 jenkins jenkins[7077]: Correct java version found
Mar 30 20:22:51 jenkins jenkins[7077]:  * Starting Jenkins Automation Server jenkins
Mar 30 20:22:51 jenkins su[7116]: (to jenkins) root on none
Mar 30 20:22:51 jenkins su[7116]: pam_unix(su-l:session): session opened for user jenkins by (uid=0)
Mar 30 20:22:51 jenkins su[7116]: pam_unix(su-l:session): session closed for user jenkins
Mar 30 20:22:52 jenkins jenkins[7077]:    ...done.
Mar 30 20:22:52 jenkins systemd[1]: Started LSB: Start Jenkins at boot time.

Stop the service if it is running at this moment.

$ sudo systemctl stop jenkins

Create a more direct service file.

$ cat <<EOF | tee /etc/systemd/system/jenkins.service
[Unit]
Description=Jenkins Service

[Service]
ExecStart=/usr/bin/java -Djava.awt.headless=true -jar /usr/share/jenkins/jenkins.war -DJENKINS_HOME=/var/lib/jenkins --webroot=/var/cache/jenkins/war --httpPort=80

User=jenkins
Restart=always

[Install]
WantedBy=multi-user.target
EOF

Create additional configuration file that will add CAP_NET_BIND_SERVICE capability.
I am using a drop-in configuration file so that I can use it for other existing services.

$ mkdir /etc/systemd/system/jenkins.service.d
$ echo -e "[Service]\nAmbientCapabilities=CAP_NET_BIND_SERVICE" | tee /etc/systemd/system/jenkins.service.d/capabilities.conf
[Service]
AmbientCapabilities=CAP_NET_BIND_SERVICE

Reload the systemd manager configuration.

$ systemctl daemon-reload

Start jenkins service.

$ systemctl start jenkins

Inspect service status.

$ systemctl status jenkins
● jenkins.service - Jenkins Service
     Loaded: loaded (/etc/systemd/system/jenkins.service; disabled; vendor preset: enabled)
    Drop-In: /etc/systemd/system/jenkins.service.d
             └─capabilities.conf
     Active: active (running) since Tue 2021-03-30 21:58:20 UTC; 10s ago
   Main PID: 10545 (java)
      Tasks: 57 (limit: 19005)
     Memory: 1.7G
     CGroup: /system.slice/jenkins.service
             └─10545 /usr/bin/java -Djava.awt.headless=true -jar /usr/share/jenkins/jenkins.war -DJENKINS_HOME=/var/lib/jenkins --webroot=/var/cache/jenkins/war --httpPort=80

Mar 30 21:58:23 jenkins java[10545]: *************************************************************
Mar 30 21:58:23 jenkins java[10545]: *************************************************************
Mar 30 21:58:23 jenkins java[10545]: *************************************************************
Mar 30 21:58:23 jenkins java[10545]: Jenkins initial setup is required. An admin user has been created and a password generated.
Mar 30 21:58:23 jenkins java[10545]: Please use the following password to proceed to installation:
Mar 30 21:58:23 jenkins java[10545]: 249a5f9f4c659a0438294a9325d91a20
Mar 30 21:58:23 jenkins java[10545]: This may also be found at: /var/lib/jenkins/.jenkins/secrets/initialAdminPassword
Mar 30 21:58:23 jenkins java[10545]: *************************************************************
Mar 30 21:58:23 jenkins java[10545]: *************************************************************
Mar 30 21:58:23 jenkins java[10545]: *************************************************************

Services like nginx, AdGuardHome require only the appropriate drop-in file. You can also set capability directly on an executable file in these cases, but please try to avoid such behavior.

$ sudo setcap CAP_NET_BIND_SERVICE=+ep /opt/AdGuardHome/AdGuardHome

Please read capabilities manual page, especially the Thread capability sets section.

$ man 7 capabilities