From 2781c75ff99cd5da7b25f6b883d78ff3de987308 Mon Sep 17 00:00:00 2001 From: Yves Rutschle Date: Sun, 21 Jul 2013 13:46:45 +0200 Subject: [PATCH] Added tranparent proyxing --- ChangeLog | 4 +++ README | 74 +++++++++++++++++++++++++++++++++++++-------------- common.c | 33 +++++++++++++++++++++-- common.h | 7 ++--- sslh-fork.c | 2 +- sslh-main.c | 1 + sslh-select.c | 2 +- 7 files changed, 96 insertions(+), 27 deletions(-) diff --git a/ChangeLog b/ChangeLog index fd31dcd..048e132 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,8 @@ vNEXT: + Added --transparent option for transparent proxying. + See README for iptables magic and capability + management. + Fixed bug in sslh-select: if socket dropped while defered_data was present, sslh-select would crash. diff --git a/README b/README index dca21ab..138bcb4 100644 --- a/README +++ b/README @@ -157,31 +157,65 @@ in inetd mode. sslh options: -i for inetd mode, --http to forward http connexions to port 80, and SSH connexions to port 22. -==== IP_TPROXY support ==== +==== capapbilities support ==== -There is a netfilter patch that adds an option to the Linux -TCP/IP stack to allow a program to set the source address -of an IP packet that it sends. This could let sslh set the -address of packets to that of the actual client, so that -sshd would see and log the IP address of the client, making -sslh transparent. +On Linux (only?), you can use POSIX capabilities to reduce a +server's capabilities to the minimum it needs (see +capabilities(8). For sslh, this is CAP_NET_ADMIN (to +perform transparent proxy-ing) and CAP_NET_BIND_SERVICE (to +bind to port 443 without being root). -This is not, and won't be, implemented in sslh for the -following reasons (in increasing order of importance): +The simplest way to use capabilities is to give them to the +executable as root: - * It's not vital: the real connecting IP address can be - found in logs. Little gain. - * It's Linux only: it means increased complexity for no - gain to some users. - * It's a patch: it means it'd only be useful to Linux - users who compile their own kernel. - * Only root can use the feature: that's a definite no-no. - Sslh should not, must not, will never run as root. +# setcap cap_net_bind_service,cap_net_admin+pe sslh-select -This isn't to mean that it won't eventually get implemented, -when/if the feature finds its way into the main kernel and -it becomes usuable by non-root processes. +Then you can run sslh-select as an unpriviledged user, e.g.: +$ sslh-select -p myname:443 --ssh localhost:22 --ssl localhost:443 + +This has 2 advantages over starting as root with -u: +- You no longer start as root (duh) +- This enables transparent proxying. + +Caveat: CAP_NET_ADMIN does give sslh too many rights, e.g. +configuring the interface. If you're not going to use +transparent proxying, just don't use it. + +==== Transparent proxy support ==== + +On Linux (only?) you can use the --transparent option to +request transparent proying. This means services behind sslh +(Apache, sshd and so on) will see the external IP and ports +as if the external world connected directly to them. This +simplifies IP-based access control (or makes it possible at +all). + +sslh needs extended rights to perform this: you'll need to +give it cap_net_admin capabilities (see appropriate chapter) +or run it as root (but don't do that). + +The firewalling tables also need to be adjusted as follow +(example to connect to https on 4443 -- adapt to your needs +(I don't think it is possible to have httpd listen to 443 in +this scheme -- let me know if you manage that))): + +# iptables -t mangle -N SSLH +# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 22 --jump SSLH +# iptables -t mangle -A OUTPUT --protocol tcp --out-interface eth0 --sport 4443 --jump SSLH +# iptables -t mangle -A SSLH --jump MARK --set-mark 0x1 +# iptables -t mangle -A SSLH --jump ACCEPT +# ip rule add fwmark 0x1 lookup 100 +# ip route add local 0.0.0.0/0 dev lo table 100 + +This will only work if sslh does not use any loopback +addresses (no 127.0.0.1 or localhost), you'll need to use +explicit IP addresses (or names): + +sslh --listen 192.168.0.1:443 --ssh 192.168.0.1:22 --ssl 192.168.0.1:4443 + +This will not work: +sslh --listen 192.168.0.1:443 --ssh 127.0.0.1:22 --ssl 127.0.0.1:4443 ==== Comments? Questions? ==== diff --git a/common.c b/common.c index 9e9101b..4023733 100755 --- a/common.c +++ b/common.c @@ -25,6 +25,7 @@ int probing_timeout = 2; int inetd = 0; int foreground = 0; int background = 0; +int transparent = 0; int numeric = 0; const char *user_name, *pid_file; @@ -96,9 +97,35 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list) return num_addr; } +/* Transparent proxying: bind the peer address of fd to the peer address of + * fd_from */ +#define IP_TRANSPARENT 19 +int bind_peer(int fd, int fd_from) +{ + struct addrinfo from; + struct sockaddr_storage ss; + int res, trans = 1; + + memset(&from, 0, sizeof(from)); + from.ai_addr = (struct sockaddr*)&ss; + from.ai_addrlen = sizeof(ss); + + res = getpeername(fd_from, from.ai_addr, &from.ai_addrlen); + CHECK_RES_DIE(res, "getpeername"); + res = setsockopt(fd, SOL_IP, IP_TRANSPARENT, &trans, sizeof(trans)); + CHECK_RES_DIE(res, "setsockopt"); + res = bind(fd, from.ai_addr, from.ai_addrlen); + CHECK_RES_RETURN(res, "bind"); + + return 0; +} + /* Connect to first address that works and returns a file descriptor, or -1 if - * none work. cnx_name points to the name of the service (for logging) */ -int connect_addr(struct addrinfo *addr, const char* cnx_name) + * none work. + * If transparent proxying is on, use fd_from peer address on external address + * of new file descriptor. + * cnx_name points to the name of the service (for logging) */ +int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name) { struct addrinfo *a; char buf[NI_MAXHOST]; @@ -113,6 +140,8 @@ int connect_addr(struct addrinfo *addr, const char* cnx_name) if (fd == -1) { log_message(LOG_ERR, "forward to %s failed:socket: %s\n", cnx_name, strerror(errno)); } else { + if (transparent) + bind_peer(fd, fd_from); res = connect(fd, a->ai_addr, a->ai_addrlen); if (res == -1) { log_message(LOG_ERR, "forward to %s failed:connect: %s\n", diff --git a/common.h b/common.h index 206e86c..b247f07 100755 --- a/common.h +++ b/common.h @@ -34,7 +34,7 @@ #define CHECK_RES_RETURN(res, str) \ if (res == -1) { \ - log_message(LOG_CRIT, "%s: %d\n", str, errno); \ + log_message(LOG_CRIT, "%s:%d:%s\n", str, errno, strerror(errno)); \ return res; \ } @@ -80,7 +80,7 @@ struct connection { /* common.c */ void init_cnx(struct connection *cnx); -int connect_addr(struct addrinfo *addr, const char* cnx_name); +int connect_addr(struct addrinfo *addr, int fd_from, const char* cnx_name); int fd2fd(struct queue *target, struct queue *from); char* sprintaddr(char* buf, size_t size, struct addrinfo *a); void resolve_name(struct addrinfo **out, char* fullname); @@ -100,7 +100,8 @@ int start_listen_sockets(int *sockfd[], struct addrinfo *addr_list); int defer_write(struct queue *q, void* data, int data_size); int flush_defered(struct queue *q); -extern int probing_timeout, verbose, inetd, foreground, background, numeric; +extern int probing_timeout, verbose, inetd, foreground, + background, transparent, numeric; extern struct sockaddr_storage addr_ssl, addr_ssh, addr_openvpn; extern struct addrinfo *addr_listen; extern const char* USAGE_STRING; diff --git a/sslh-fork.c b/sslh-fork.c index 58ad985..c3afc84 100644 --- a/sslh-fork.c +++ b/sslh-fork.c @@ -102,7 +102,7 @@ void start_shoveler(int in_socket) } /* Connect the target socket */ - out_socket = connect_addr(saddr, prot->description); + out_socket = connect_addr(saddr, in_socket, prot->description); CHECK_RES_DIE(out_socket, "connect"); cnx.q[1].fd = out_socket; diff --git a/sslh-main.c b/sslh-main.c index f3b4619..66e64c0 100644 --- a/sslh-main.c +++ b/sslh-main.c @@ -58,6 +58,7 @@ static struct option const_options[] = { { "inetd", no_argument, &inetd, 1 }, { "foreground", no_argument, &foreground, 1 }, { "background", no_argument, &background, 1 }, + { "transparent", no_argument, &transparent, 1 }, { "numeric", no_argument, &numeric, 1 }, { "verbose", no_argument, &verbose, 1 }, { "user", required_argument, 0, 'u' }, diff --git a/sslh-select.c b/sslh-select.c index c59c5bf..3c12a64 100644 --- a/sslh-select.c +++ b/sslh-select.c @@ -122,7 +122,7 @@ int connect_queue(struct connection *cnx, struct addrinfo *addr, { struct queue *q = &cnx->q[1]; - q->fd = connect_addr(addr, cnx_name); + q->fd = connect_addr(addr, cnx->q[0].fd, cnx_name); if (q->fd != -1) { log_connection(cnx); set_nonblock(q->fd);