Added tranparent proyxing

This commit is contained in:
Yves Rutschle 2013-07-21 13:46:45 +02:00
parent d02ffcd154
commit 2781c75ff9
7 changed files with 96 additions and 27 deletions

View File

@ -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.

74
README
View File

@ -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? ====

View File

@ -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",

View File

@ -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;

View File

@ -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;

View File

@ -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' },

View File

@ -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);