0

I wanted to try socket activation with Systemd and Java on my ubuntu server 16.04. My idea is to make my program able to open directly a standard socket number with a user that is not root.

I currently use the iptable NAT rules, but I wanted to put it away after reading the article of liquidat and this one from Pid Eins,

My test environement is pretty simple. I've got the following java program :

package com.test.bindTCP;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class App 
{

private static void acceptConnection(ServerSocket serverSocket) {
    while (true) {
        try (
                Socket clientSocket = serverSocket.accept();
                PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
                BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
        ) {
            String inputLine;
            while ((inputLine = in.readLine()) != null) {
                out.println(inputLine);
            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

public static void main( String[] args )
{
    int portNumber = Integer.parseInt(args[0]);
    try (
        ServerSocket serverSocket = new ServerSocket(portNumber);
    ) {
        acceptConnection(serverSocket);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

}

I can call it though the following command line : java -cp target/bindTCP-1.0-SNAPSHOT.jar com.test.bindTCP.App 60606

It echoes all commands sent though tcp, it's able to serve only one client at time. I tested with "nc" and it work properly on socket upper than 1000.

To set my systemd deamon, I wrote the following config files :

cat /lib/systemd/system/bindTCP.socket

[Unit]
Description=bindTCP Java 23

[Socket]
ListenStream=23

cat /lib/systemd/system/bindTCP.service

[Unit]
Description=bindTCP service
Requires=bindTCP.socket
After=syslog.target
After=network.target

[Service]
User=mylogin
Group=mygroup

ExecStart=/usr/lib/jvm/oracle-java8-jdk-amd64/bin/java  -cp /home/mylogin/bindTCP-1.0-SNAPSHOT.jar com.test.bindTCP.App 23
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=bindTCP
Restart=always


[Install]
WantedBy=multi-user.target

cat /etc/rsyslog.d/bindTCP.conf

$template bindTCPlog,"%msg%\n"
if $programname == 'bindTCP' then /var/log/bindTCP.log;bindTCPlog
if $programname == 'bindTCP' then stop

Then I reload my systemctl config : sudo systemctl daemon-reload

I restart rsyslog deamon : sudo systemctl restart rsyslog

And then, I try to start my own service sudo systemctl start bindTCP

But it doen't work, in my log (/var/log/bindTCP.log), I found the following java stacktrace

 java.net.BindException: Permission denied (Bind failed)
    at java.net.PlainSocketImpl.socketBind(Native Method)
    at java.net.AbstractPlainSocketImpl.bind(AbstractPlainSocketImpl.java:387)
    at java.net.ServerSocket.bind(ServerSocket.java:375)
    at java.net.ServerSocket.<init>(ServerSocket.java:237)
    at java.net.ServerSocket.<init>(ServerSocket.java:128)
    at com.test.bindTCP.App.main(App.java:38)  

Any idea how to setup my service properly ?

EDIT: A solution based on authbind is working. The one proposed with setcap 'cap_net_bind_service=+ep' doesn't change anything. Clearly systemd is able to deal with this issue, so I'm still looking for a solution 100% based on Systemd. I'm thinking is more secure.

2 Answers 2

4

You’re not actually using the systemd-provided socket in your daemon. Instead of creating a new ServerSocket, use System.inheritedChannel() and check if it’s a ServerSocketChannel. If yes, you inherited a socket from systemd and can start to accept() connections on that, otherwise you can still create a new ServerSocket or ServerSocketChannel (e. g. for development purposes). Note that for this to work, you need to set StandardInput=socket on the service unit: Java expects the inherited channel to be file descriptor 0 (inetd style), but systemd by default adds sockets starting at file descriptor 3.

Alternatively, you can add this to your service file (in the Service section):

AmbientCapabilities=CAP_NET_BIND_SERVICE

This will allow Java to allocate reserved port numbers itself. Using a socket unit is definitely the better choice, though. (If you want to use AmbientCapabilities, you’ll have to disable the socket unit, because systemd and your service can’t both bind to the same port. That’s probably why the setcap suggestion didn’t work.)


Side note: since your unit files are managed by you, the system administrator, and not by the package manager, they belong in /etc, not in /lib (that is, /etc/systemd/system/bindTCP.{service,socket}).

0

Use setcap to allow the java binary itself the capability to bind to privileged ports, without the requirement to be running as root:

`sudo setcap 'cap_net_bind_service=+ep' /usr/lib/jvm/oracle-java8-jdk-amd64/bin/java`
2
  • this is not working in my case.
    – Jérôme B
    Jun 14, 2018 at 9:00
  • 3
    Note that this grants the capability to any Java program. setcap on interpreter binaries is generally not a good idea. Jun 23, 2018 at 21:39

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .