From df48ce8a95da95b1e8959b7e1e77f7139671e17c Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Sun, 28 Jul 2024 18:30:08 +0200 Subject: [PATCH 1/3] Documentation, why Daisy-Chaining IP-Transparent applications will fail. Discuss this in some examples. --- doc/Daisy-Chaining-Transparency-Explained.md | 283 +++ doc/detailed-ip-transparency.png | Bin 0 -> 29950 bytes doc/detailed-ip-transparency.svg | 1694 ++++++++++++++++++ 3 files changed, 1977 insertions(+) create mode 100644 doc/Daisy-Chaining-Transparency-Explained.md create mode 100644 doc/detailed-ip-transparency.png create mode 100644 doc/detailed-ip-transparency.svg diff --git a/doc/Daisy-Chaining-Transparency-Explained.md b/doc/Daisy-Chaining-Transparency-Explained.md new file mode 100644 index 0000000..e23d568 --- /dev/null +++ b/doc/Daisy-Chaining-Transparency-Explained.md @@ -0,0 +1,283 @@ +# Daisy-Chaining-Transparency # +This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method. +There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.
+Unfortunately you will see a lot of errors popping out: **Address already in use**
+This article explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem. + +Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections. + +![Dataflow-Of-Daisy-Chain](./detailed-ip-transparency.png) + +But unfortunately we are receiving in many constellations errors, when M tries to open its connection to our final target sshd, here called T. +Let us look more close, why that is happening. We need for this two terminal windows on the same server.
+### First example, uncooperative applications ### +As the problem has nothing to do with transparency itself, but only of reuse of same IP-addresses and ports, we avoid the overhead of the additional capablities, to keep the example easy and clear. +In the first terminal we are starting python3 and entering the following three lines: +``` +user@host:~$ python3 +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.bind(("192.168.255.254", 12345)) +>>> +``` + +Now we are going to the second terminal window, and trying just the same: +``` +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.bind(("192.168.255.254", 12345)) +Traceback (most recent call last): + File "", line 1, in +OSError: [Errno 98] Address already in use +sock.bind(("192.168.255.254", 12346)) ##this however works!! +``` +Here we are getting the error, which caused many of us hours of research. +The problem is caused by the fact, that the kernel does not know at the moment of the bind()-call, how we want to use the socket. If we use this as a server socket, and will do a listen()-call as next, this will not work, as for server sockets, the two-value tuple ADDRESS:PORT needs to be unique only to the process, tied to this socket. +That is the reason, that there are port ranges, reserved for servers, where the administrator is responsible not to assign the same port to two applications. +But as server ports are coming from a range, which will not be used for client connections, a server can be sure, that if it is started at any time in the future, no outgoing client has used its port for an outbound connection. +Clients are usually using ports from a so called [ephemeral port range](https://en.wikipedia.org/wiki/Ephemeral_port). +However, for clients each connection is valid, as long as one value in the four-tuples describing the connection is different. In our example above, the two values from the destination are different, so this connection could be established (in theory) without conflicts. +To make that happen, you need to deploy a special socket option to this socket, to explain, that we "know the risks" and we will reuse the ip-port combination. +And, as we see from the second bind() in the second example, the error message: _**Address already in use**_ really means: _**Address:Port already in use**_ + +### Taking Care, part I ### + +Ok, now we are entering our two terminals again, pressing Ctrl-D to finish python, and start a new session like this in both terminals: +``` +user@host:~$ python3 +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +>>> sock.bind(("192.168.255.254", 12345)) +>>> +``` +And we will see, that the problem is solved, when both applications are taking care. + +### Taking Care, part II ### + +Ok, now we are going back to our terminals, pressing again Ctrl-D to finish python, and start new sessions like this: +In the first terminal we repeat the input from our first example, without the setsockopt() call: +``` +user@host:~$ python3 +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.bind(("192.168.255.254", 12345)) +>>> +``` +in the second terminal, we enter our modified cooperative example: + +``` +user@host:~$ python3 +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +>>> sock.bind(("192.168.255.254", 12345)) +Traceback (most recent call last): + File "", line 1, in +OSError: [Errno 98] Address already in use +>>> +``` +Oops, here is our error again. And this is the reason, why I called this method "cooperative". As the first application has bound to that IP:PORT combination, without telling, that "it knows the risk", the kernel denies us, to use this combinations, as we may break the already active application. The first application is not "cooperative" :-(
+ Ok, but the kernel gives as some more possibilities: As we now get connections from a uncoperative application, we can no longer use the Client-IP-Port combination. We need to use a conflict free port for the client IP, to succeed. So lets get back to terminal 2 and continue after the error with the following commands: +``` +user@host:~$ python3 +Python 3.11.2 (main, May 2 2024, 11:59:08) [GCC 12.2.0] on linux +Type "help", "copyright", "credits" or "license" for more information. +>>> import socket +>>> sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) +>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) +>>> sock.bind(("192.168.255.254", 12345)) +Traceback (most recent call last): + File "", line 1, in +OSError: [Errno 98] Address already in use +>>> sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 0) +>>> sock.setsockopt(socket.SOL_IP, socket.IP_BIND_ADDRESS_NO_PORT, 1) +>>> sock.bind(("192.168.255.254", 0)) +>>> +``` +This last example makes it neccessary, that you use a recent version of the python3 socket library, as older versions, have the option _IP_BIND_ADDRESS_NO_PORT_ not yet defined. +With his behaviour, we are telling the kernel, that the kernel should assign us a conflict free port address at latest possible moment, while calling connect(). +From `man ip`: +``` +Inform the kernel to not reserve an ephemeral port when using bind(2) +with a port number of 0. The port will later be automatically chosen +at connect(2) time, in a way that allows sharing a source port as +long as the 4-tuple is unique. +``` + +Ok, now with those two actions an application is really ready for cooperative working in ip-transparent chains. +If you are running in problems, with any kind of application, where you are redirecting transparent traffic to from sslh, check the following: +- Are you using a recent version of sslh, having the described feature enabled +- are you using the most recent version of the other application +If you can confirm both checks, tell the maintainers of the other application, about possible fixes, and send them a link to this article. + +## Practical Use Of Daisy-Chaining: Proxytunnel Endpoint ## + +One reasons, why we want to combine two programs is related to the core functionality of sslh. You wish to hide your ssh connection behind a https port. +But now you would like, to reach this port via the [proxy-tunnel application](https://github.com/proxytunnel/proxytunnel), though an restrictive http(s) proxy. This is in many cases, one of the few methods, to escape from restricted private networks, like in companies, schools and universities. Unfortunately, many of those proxy-servers will check, that the protocol leaving the proxy is really tls. Therefore you need and endpoint in your system, which will terminate the tls-connection and forward the encapsulated ssh stream to the sshd. Sslh can't do tls termination, as this is not a core job of tls. One of the solutions tried here is stunnel. Stunnel can do transparency like sslh, but unfortunately belongs to the uncooperative ip-transparent programs. At the time of writing this article, you can use stunnel as the first-in-chain, and a very recent sslh as second in chain. But nginx (or openresty) is capable of this, however it prefers (at least in the tested versions), mostly the second way just selecting a new random port and not (always) preserving the original source port, what makes debugging of events much easier. + + +``` +stream { + ssl_preread on; + + map $ssl_preread_server_name $name { + default master.yourdomain.top; + t1.yourdomain.top t1.yourdomain.top; + t2.yourdomain.top t2.yourdomain.top; + cryptic.foo.bar location.selfsigned.cert; + } + +## $destination port :443 is assumed, beeing as real +## webserver. Either anothe nginx http-server or apache +## or anything other ... + map $ssl_preread_server_name $dest { + default 192.168.255.254:443; + t1.yourdomain.top 192.168.255.254:443; + t2.yourdomain.top 192.168.255.254:444; + cryptic.foo.notexist 192.168.255.254:445; + } + + +## this is the server, to handle incoming tcp streams +## and dispatching them transparent to $dest +## 192.168.255.254:1443 is the address, where +## the front facing sslh sends traffic to + server { + listen 192.168.255.254:1443; + proxy_connect_timeout 5s; + proxy_timeout 3m; + proxy_bind $remote_addr transparent; + proxy_pass $dest; + ssl_preread on; + } + + ## this is a basic endpoint for proxy-tunnel connections + server { + listen 192.168.255.254:444 ssl; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; + ## give facl to the nginx user, see later in article + ssl_certificate /etc/letsencrypt/live/$name/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/$name/privkey.pem; + ssl_dhparam /etc/nginx/dhparam-nginx.pem; + ssl_session_cache shared:MySSL:10m; + ssl_session_tickets off; + proxy_connect_timeout 5s; + proxy_timeout 3m; + proxy_bind $remote_addr transparent; + proxy_pass 192.168.255.254:22 ; + } + + +## this is a fancy destination, using some tricks, to fool +## some proxies. + server { + listen 192.168.255.254:445 ssl; + ssl_protocols TLSv1.2 TLSv1.3; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 10m; + ssl_prefer_server_ciphers on; + ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256"; + ## this points to a directory, containg a self signed certificate + ## created with CN and SAN to an not officially existing + ## domain, e.g. cryptic.foo.notexists + ssl_certificate /etc/nginx/$name/fullchain.pem; + ssl_certificate_key /etc/nginx/$name/privkey.pem; + ssl_dhparam /etc/nginx/dhparam-nginx.pem; + ssl_session_cache shared:MySSL:10m; + ssl_session_tickets off; + ## additional trick: requiring client certificate authentication + ssl_client_certificate /etc/nginx/public.cert ; + ssl_verify_client on ; + proxy_connect_timeout 5s; + proxy_timeout 3m; + proxy_bind $remote_addr transparent; + proxy_pass 192.168.255.254:22; + } +} +``` + +This nginx stream server shows a combination of different possibilities, how to establish an endpoint for proxytunnel or similar programs, to hide your ssh connection. Remember: Hiding ssh tunneled in tls does not neccessarly raise the security of your connection. In theory a combination of two methods may also lower the security. In addition there are more drawbacks: Such a connection needs more cpu on both ends for doing double crypto, and the throughput through he connection drops, as the ratio payload/header increases. the single ssh-packets are smaller, than usual. +So it is not recommended for doing scp or sftp through such connections. + +### Nginx Configration Explained ### + +However, there are situations, where this is the only way, to reach your server. +So we have now this nginx instance helping us out. +The server stanza listening on _192.168.255.254:1443_ is the main process, accepting incoming connections forwarded from sslh. It prereads tls SNI names, to get destination information. When in the nginx-configuration a variable is used, which is set by an map action, the mapping takes place just in that moment, when the variable needs to be expanded. +So we have either default values, in case there is no SNI, or values for individual SNI. + +The connection stream is than forwarded ip-transparent to a given destination, which is itself another nginx instance, defined in the configuration file. + +#### The Standard TLS-Termination #### + +The server stanza listening on _192.168.255.254:444_ is a very basic TLS terminating endpoint. In most cases, a proxy is happy with a destination like this. Here nginx just terminates the TLS connection and shovels the incoming packets over to sshd in ip-transparent way. The proxytunnel configuration for this target will look like: +``` +host t2.yourdomain.top + ProxyCommand /path/to/proxytunnel -e -p PROXY-IP:3128 -d t2.yourdomain.top:443 + ServerAliveInterval 30 +``` +If you have no proxytunnel on your system, you can also use openssl. In this case the configuration looks like: +``` +host t2-alias + ProxyCommand openssl s_client -proxy PROXY-IP:3128 -connect t2.yourdomain.top:443 + ServerAliveInterval 30 +``` +`ssh ts-alias` will use this configuration. + +One point from the above configuration needs further explanation, as we just refer to our common letsencrypt store, for certificates. + +##### Excurse To File ACLs ##### + +We are using here our common letsencrypt certificate store, as copying certificates after renewal is a pain in the as and prone to errors. +A small script makes sure, that all applications can read the certificates: +``` +for i in Debian-exim dovecot mail www-data nginx ; do + setfacl -Rdm u:$i:rX ./archive/ + setfacl -Rdm u:$i:rX ./live/ + setfacl -Rm u:$i:rX ./archive/ + setfacl -Rm u:$i:rX ./live/ + for file in $( find ./live ./archive -type l,f ) ; do + echo -e "$i $file set" + setfacl -m u:$i:r $file + done +done +``` +This script needs only to be run, when a new application user needs access. +The first two lines are making sure (watch the **d**), that the given options +on those directories will be the default for newly created files below. +The uppercase **X** means, that **x**-access will only be granted to directories or files, having already **x** set for others. + +#### The Tricky TLS Termination #### + +The server stanza listening on _192.168.255.254:445_ is a more tricky TLS terminating endpoint. Some proxies are trying to inspect the destination, before letting you go. Some of them you can fool, others not. +One trick can be to use an phantasy domain name with a self signed certificate, no dns server will ever resolve. As you are using this name as SNI name in your proxy-tunnel connection, it will work. This can be also a way, to hide your sshd, if someone, who knows you are using sslh tries to find your sshd. As long, as this person does not get access to proxies you are using, this may help in some situations. +Another trick is, requiring a client certificate for authentication. This is in each case the much better approach, as you can combine this also with official connected domain names. Without the client certificate no sshd! +The client certificate does not increase the above mentioned ratio between payload and headers, as this is only used while establishing the TLS connection. +It prevents however a proxy, doing a parallel sneak to your destination, to figure out what is behind. I have seen proxies letting you connect with this method, others are denying access. + +To access this endpoint, the proxytunnel configuration inside `~/.ssh/config` +will look like: +``` +host cryptic.foo.notexists + ProxyCommand /path/to/proxytunnel -e -c ~/public.cert -k ~/private.pem -C ~/myOwnCA.cert -p PROXY-IP:3128 -o cryptic.foo.notexists -d t1.yourdomain.top:443 + ServerAliveInterval 30 +``` +-C gives the certfile to your certificate used for selfsigning the server certificate. You can also set -z for not verifying the certificate, but that is not recommended! `t1.yourdomain.top` represents a valid domain name, where the listening sslh can be reached. This name is not taken for SNI, because **-o** sets our hidden SNI name. Whenever you enter `ssh cryptic.foo.notexists` you will get connected to your server! + +The best reommendation however is: Avoid self signed certs, if you are not really sure, what you are doing. + diff --git a/doc/detailed-ip-transparency.png b/doc/detailed-ip-transparency.png new file mode 100644 index 0000000000000000000000000000000000000000..a14958c0124fb38ec4349f78eddec74b881dbb22 GIT binary patch literal 29950 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 literal 0 HcmV?d00001 diff --git a/doc/detailed-ip-transparency.svg b/doc/detailed-ip-transparency.svg new file mode 100644 index 0000000..f5f0eb2 --- /dev/null +++ b/doc/detailed-ip-transparency.svg @@ -0,0 +1,1694 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Viewer does not support full SVG 1.1 + + + + + + + + + Families + + + + + + + + + + + + + Prerouting + + + + + + + + + + + +Hook + + + + + + + + + + + clientC-IP + + + + + + + + + + Prerouting + + + + + + + + + + + +Hook + + + + + sslh + + + + + + + + + + Prerouting + + + + + + + + + + + +Hook + + + + + sshd + + + + + + + + + Prerouting + + + + + + + + + + + +Hook + + + + + stunnel/nginx + + + C-IP:1000-->M-IP:443 + T-IP:22<--C-IP:1000 + + + + S-IP:443<--C-IP:1000 + + From 6999a9db43c4548e3b7417f0ac2f08679e4044ac Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Wed, 31 Jul 2024 12:49:18 +0200 Subject: [PATCH 2/3] added forgotten link to cloudflare blog. Without this link, the wording maked no sense. --- doc/Daisy-Chaining-Transparency-Explained.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/Daisy-Chaining-Transparency-Explained.md b/doc/Daisy-Chaining-Transparency-Explained.md index e23d568..4e64632 100644 --- a/doc/Daisy-Chaining-Transparency-Explained.md +++ b/doc/Daisy-Chaining-Transparency-Explained.md @@ -2,7 +2,7 @@ This documentation goes a level deeper, what happens in the operating system with IP-addresses, and why some combinations of programs are failing, when they use the same transparency method. There are situations, where you need to combine two applications, both working as ip-transparent proxies, to reach your goal. One example is, having nginx or stunnel as an proxytunnel-endpoint for tls tunneled ssh connections through https-proxies. An example for such a combination will be desribed at the end of this article.
Unfortunately you will see a lot of errors popping out: **Address already in use**
-This article explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem. +[This article from Cloudflare blog](https://blog.cloudflare.com/how-to-stop-running-out-of-ephemeral-ports-and-start-to-love-long-lived-connections) explains why this is happening, while it is describing the solution to another problem. However this is a close relative to our problem. Let us look to the following example: We have sslh (S) accepting connections from a client (C) and forwarding one of those connections to a man-in-the-middle (M), which finally forwards this connection to sshd. If everything works perfectly, we would like to see those connections. From fbbeb7072fc9228cf4f5fec2d4258aaaf6c14822 Mon Sep 17 00:00:00 2001 From: ftasnetamot Date: Mon, 5 Aug 2024 21:07:45 +0200 Subject: [PATCH 3/3] Added some clarifications to the transparent proxy documentation. --- doc/scenarios-for-simple-transparent-proxy.md | 4 +++- doc/simple_transparent_proxy.md | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/doc/scenarios-for-simple-transparent-proxy.md b/doc/scenarios-for-simple-transparent-proxy.md index d8e15dd..e4a642a 100644 --- a/doc/scenarios-for-simple-transparent-proxy.md +++ b/doc/scenarios-for-simple-transparent-proxy.md @@ -3,7 +3,7 @@ ![Simple Transparent Proxy Examples](./sslh-examples-v3.png) ## Introduction ## -The first example is the configuration, which was described in the previousd document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture. +The first example is the configuration, which was described in the previous document. I omitted the loopback interface "lo" in those diagrams, trying not no overload the picture. The connections have two different endings, showing the direction of the opening connection (SYN flag) and the answer connection (SYN-ACK flags). This is important, as the traffic in the transparent proxy setup flows somewhat unexpected. ## Example 1 ## @@ -41,6 +41,8 @@ ip route add default via SERVER1_ETH1_IP dev eth0 table sslh_routeback ``` This is setting up a default route for all traffic, originating from the ip address sshd (or any other service) is using, back to the host, hosting sslh. On that host, those packets will be deflected again with the same rule from scenario 2. +Be aware, that scenario 3 can look very different. Each configuration, where packets from the target system can find their way back, without beeing forcibly routed through the sslh hosting system, belongs into this category. This are e.g. virtual machines or containers, having interfaces in the same network, like the sslh hosting system. Even, when they look in some drawings embedded in their host, as soon, as they have network interfaces, allowing a direct connection to the client, it is scenario 3! + ## Modifications ## Now you can think about many modifications, but the tools will be the same, for all other thinkable scenarios. You must always make sure, that packets from foreign hosts, will find their way back to the sslh host. So if the chain consists of three or four servers, all need the deflection rules. diff --git a/doc/simple_transparent_proxy.md b/doc/simple_transparent_proxy.md index 13625ac..27e8e19 100644 --- a/doc/simple_transparent_proxy.md +++ b/doc/simple_transparent_proxy.md @@ -68,9 +68,11 @@ To do so go to _**/etc/iproute2/rt_tables**_ and add a line ``` 111 sslh ``` +With newer versions of iproute2 the /etc/iproute2 directory with the embedded templates got no longer installed. The cause maybe, that the example names, which were not used in any configuration, generated confusion. However, once you need those files, generate them and they will be honoured. You still can use just numbers for your routing table. But doing this, and having more than one routing table, you need a list, which numer belongs to which configuration. +And seeing in the output from `ip route list table all ` the tables names instead just numbers is worth creating the file. ### Dummy Interface ### -Now we configure our dedicatet interface. +Now we configure our dedicated interface. In the file _**/etc/network/interfaces**_, we place this entry: ``` auto dummy0 @@ -91,7 +93,7 @@ If you are updating a older current configuration, make sure, that you have no l sysctl net.ipv4.conf.default.route_localnet sysctl net.ipv4.conf.all.route_localnet ``` -should both report "0"! +should both report "0"! ### Explanation Of The Routing Rules ###