Merge pull request #56 from hogarthj/master

Initial addition of systemd socket based activiation
This commit is contained in:
yrutschle 2016-02-05 16:46:47 +01:00
commit 8f39c106e1
5 changed files with 315 additions and 22 deletions

View File

@ -6,6 +6,7 @@ USELIBCONFIG=1 # Use libconfig? (necessary to use configuration files)
USELIBPCRE= # Use libpcre? (needed for regex on musl)
USELIBWRAP?= # Use libwrap?
USELIBCAP= # Use libcap?
USESYSTEMD= # Make use of systemd socket activation
COV_TEST= # Perform test coverage?
PREFIX?=/usr
BINDIR?=$(PREFIX)/sbin
@ -50,6 +51,12 @@ ifneq ($(strip $(USELIBCAP)),)
CPPFLAGS+=-DLIBCAP
endif
ifneq ($(strip $(USESYSTEMD)),)
LIBS:=$(LIBS) -lsystemd
CPPFLAGS+=-DSYSTEMD
endif
all: sslh $(MAN) echosrv
.c.o: *.h
@ -68,6 +75,9 @@ sslh-select: version.h $(OBJS) sslh-select.o Makefile common.h
$(CC) $(CFLAGS) $(LDFLAGS) -o sslh-select sslh-select.o $(OBJS) $(LIBS)
#strip sslh-select
systemd-sslh-generator: systemd-sslh-generator.o
$(CC) $(CFLAGS) $(LDFLAGS) -o systemd-sslh-generator systemd-sslh-generator.o -lconfig
echosrv: $(OBJS) echosrv.o
$(CC) $(CFLAGS) $(LDFLAGS) -o echosrv echosrv.o probe.o common.o tls.o $(LIBS)
@ -100,7 +110,7 @@ distclean: clean
rm -f tags cscope.*
clean:
rm -f sslh-fork sslh-select echosrv version.h $(MAN) *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
rm -f sslh-fork sslh-select echosrv version.h $(MAN) systemd-sslh-generator *.o *.gcov *.gcno *.gcda *.png *.html *.css *.info
tags:
ctags --globals -T *.[ch]

View File

@ -59,6 +59,8 @@ of the Makefile:
file. You will need `libconfig` headers to compile
(`libconfig8-dev` in Debian).
* `USESYSTEMD` compiles support for using systemd socket activation.
You will need `systemd` headers to compile (`systemd-devel` in Fedora).
Binaries
--------
@ -321,6 +323,90 @@ Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Systemd Socket Activation
-------------------------
If compiled with `USESYSTEMD` then it is possible to activate
the service on demand and avoid running any code as root.
In this mode any listen configuration options are ignored and
the sockets are passed by systemd to the service.
Example socket unit:
[Unit]
Before=sslh.service
[Socket]
ListenStream=1.2.3.4:443
ListenStream=5.6.7.8:444
ListenStream=9.10.11.12:445
FreeBind=true
[Install]
WantedBy=sockets.target
Example service unit:
[Unit]
PartOf=sslh.socket
[Service]
ExecStart=/usr/sbin/sslh -v -f --ssh 127.0.0.1:22 --ssl 127.0.0.1:443
KillMode=process
CapabilityBoundingSet=CAP_NET_BIND_SERVICE CAP_NET_ADMIN CAP_SETGID CAP_SETUID
PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
User=sslh
With this setup only the socket needs to be enabled. The sslh service
will be started on demand and does not need to run as root to bind the
sockets as systemd has already bound and passed them over. If the sslh
service is started on its own without the sockets being passed by systemd
then it will look to use those defined on the command line or config
file as usual. Any number of ListenStreams can be defined in the socket
file and systemd will pass them all over to sslh to use as usual.
To avoid inconsistency between starting via socket and starting directly
via the service Requires=sslh.socket can be added to the service unit to
mandate the use of the socket configuration.
Rather than overwriting the entire socket file drop in values can be placed
in /etc/systemd/system/sslh.socket.d/<name>.conf with additional ListenStream
values that will be merged.
In addition to the above with manual .socket file configuration there is an
optional systemd generator which can be compiled - systemd-sslh-generator
This parses the /etc/sslh.cfg (or /etc/sslh/sslh.cfg file if that exists
instead) configuration file and dynamically generates a socket file to use.
This will also merge with any sslh.socket.d drop in configuration but will be
overriden by a /etc/systemd/system/sslh.socket file.
To use the generator place it in /usr/lib/systemd/system-generators and then
call systemctl daemon-reload after any changes to /etc/sslh.cfg to generate
the new dynamic socket unit.
Transparent proxying means the target server sees the real
origin address, so it means if the client connects using
IPv6, the server must also support IPv6. It is easy to
support both IPv4 and IPv6 by configuring the server
accordingly, and setting `sslh` to connect to a name that
resolves to both IPv4 and IPv6, e.g.:
sslh --transparent --listen <extaddr>:443 --ssh insideaddr:22
/etc/hosts:
192.168.0.1 insideaddr
201::::2 insideaddr
Upon incoming IPv6 connection, `sslh` will first try to
connect to the IPv4 address (which will fail), then connect
to the IPv6 address.
Fail2ban
--------

View File

@ -17,6 +17,12 @@
#define SA_NOCLDWAIT 0
#endif
/* Make use of systemd socket activation
* */
#ifdef SYSTEMD
#include <systemd/sd-daemon.h>
#endif
/*
* Settings that depend on the command line. They're set in main(), but also
* used in other places in common.c, and it'd be heavy-handed to pass it all as
@ -52,6 +58,27 @@ void check_res_dumpdie(int res, struct addrinfo *addr, char* syscall)
}
}
int get_fd_sockets(int *sockfd[])
{
int sd = 0;
#ifdef SYSTEMD
sd = sd_listen_fds(0);
if (sd < 0) {
fprintf(stderr, "sd_listen_fds(): %s\n", strerror(-sd));
exit(1);
}
if (sd > 0) {
*sockfd = malloc(sd * sizeof(*sockfd[0]));
for (int i = 0; i < sd; i++) {
(*sockfd)[i] = SD_LISTEN_FDS_START + i;
}
}
#endif
return sd;
}
/* Starts listening sockets on specified addresses.
* IN: addr[], num_addr
* OUT: *sockfd[] pointer to newly-allocated array of file descriptors
@ -64,6 +91,13 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list)
struct addrinfo *addr;
int i, res, one;
int num_addr = 0;
int sd_socks = 0;
sd_socks = get_fd_sockets(sockfd);
if (sd_socks > 0) {
return sd_socks;
}
for (addr = addr_list; addr; addr = addr->ai_next)
num_addr++;

View File

@ -564,10 +564,13 @@ next_arg:
set_protocol_list(prots);
/* If compiling with systemd socket support no need to require listen address */
#ifndef SYSTEMD
if (!addr_listen && !inetd) {
fprintf(stderr, "No listening address specified; use at least one -p option\n");
exit(1);
}
#endif
/* Did command-line override foreground setting? */
if (background)
@ -604,6 +607,13 @@ int main(int argc, char *argv[])
num_addr_listen = start_listen_sockets(&listen_sockets, addr_listen);
#ifdef SYSTEMD
if (num_addr_listen < 1) {
fprintf(stderr, "No listening sockets found, restart sockets or specify addresses in config\n");
exit(1);
}
#endif
if (!foreground) {
if (fork() > 0) exit(0); /* Detach */

153
systemd-sslh-generator.c Normal file
View File

@ -0,0 +1,153 @@
#include <libconfig.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
static char* resolve_listen(const char *hostname, const char *port) {
/* Need room in the strcat for \0 and :
* the format in the socket unit file is hostname:port */
char *conn = (char*)malloc(strlen(hostname)+strlen(port)+2);
strcpy(conn, hostname);
strcat(conn, ":");
strcat(conn, port);
return conn;
}
static int get_listen_from_conf(const char *filename, char **listen) {
config_t config;
config_setting_t *setting, *addr;
const char *hostname, *port;
int len = 0;
/* look up the listen stanzas in the config file so these
* can be used in the socket file generated */
config_init(&config);
if (config_read_file(&config, filename) == CONFIG_FALSE) {
/* we don't care if file is missing, skip it */
if (config_error_line(&config) != 0) {
fprintf(stderr, "%s:%d:%s\n",
filename,
config_error_line(&config),
config_error_text(&config));
return -1;
}
} else {
setting = config_lookup(&config, "listen");
if (setting) {
len = config_setting_length(setting);
for (int i = 0; i < len; i++) {
addr = config_setting_get_elem(setting, i);
if (! (config_setting_lookup_string(addr, "host", &hostname) &&
config_setting_lookup_string(addr, "port", &port))) {
fprintf(stderr,
"line %d:Incomplete specification (hostname and port required)\n",
config_setting_source_line(addr));
return -1;
} else {
listen[i] = malloc(strlen(resolve_listen(hostname, port)));
strcpy(listen[i], resolve_listen(hostname, port));
}
}
}
}
return len;
}
static int write_socket_unit(FILE *socket, char **listen, int num_addr, const char *source) {
fprintf(socket,
"# Automatically generated by systemd-sslh-generator\n\n"
"[Unit]\n"
"Before=sslh.service\n"
"SourcePath=%s\n"
"Documentation=man:sslh(8) man:systemd-sslh-generator(8)\n\n"
"[Socket]\n"
"FreeBind=true\n",
source);
for (int i = 0; i < num_addr; i++) {
fprintf(socket, "ListenStream=%s\n", listen[i]);
}
return 0;
}
static int gen_sslh_config(char *runtime_unit_dir) {
char *sslh_conf;
int num_addr;
FILE *config;
char **listen;
FILE *runtime_conf_fd = stdout;
const char *unit_file;
/* There are two default locations so check both with first given preference */
sslh_conf = "/etc/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
sslh_conf="/etc/sslh/sslh.cfg";
config = fopen(sslh_conf, "r");
if (config == NULL) {
return -1;
}
}
fclose(config);
num_addr = get_listen_from_conf(sslh_conf, listen);
if (num_addr < 0)
return -1;
/* If this is run by systemd directly write to the location told to
* otherwise write to standard out so that it's trivial to check what
* will be written */
if (runtime_unit_dir != "") {
unit_file = "/sslh.socket";
size_t uf_len = strlen(unit_file);
size_t runtime_len = strlen(runtime_unit_dir) + uf_len + 1;
char *runtime_conf = malloc(runtime_len);
strcpy(runtime_conf, runtime_unit_dir);
strcat(runtime_conf, unit_file);
runtime_conf_fd = fopen(runtime_conf, "w");
}
return write_socket_unit(runtime_conf_fd, listen, num_addr, sslh_conf);
}
int main(int argc, char *argv[]){
int r = 0;
int k;
char *runtime_unit_dest = "";
if (argc > 1 && (argc != 4) ) {
printf("This program takes three or no arguments.\n");
return -1;
}
if (argc > 1)
runtime_unit_dest = argv[1];
k = gen_sslh_config(runtime_unit_dest);
if (k < 0)
r = k;
return r < 0 ? -1 : 0;
}