2

I want to set up sslh on port 443 to forward to https and openvpn which reside in two docker containers. Since I want to provide IP based ACLs on https in the long run, I want sslh to provide the true client IP to the webserver (aka sslh's "transparent" mode).

As I understand the documentation and this answer, sslh will fake outside requests on the internal network and needs to be fed back the servers answer to translate the port for communication with the client.

My problem is the iptables setup. There are two possibilities where to run sslh: on the host or in a container inside the docker network.

I managed to get the "default" setup as described in the documentation to work, i.e.: sslh on the host could transparently forward to a service on the host.

However, I cannot get transparent sslh either on the host or in a container to work with the containerized servers. More specifically, my iptables rules to mark the traffic that should be routed to sslh never match a packet.

E.g. for an https webserver I would:

iptables -t mangle -A OUTPUT -p tcp --sport 443 --source 172.25.0.2 --jump SSLH

Using this chart I tried to discern how the virtual ethernet devices of the container, the docker bridge, the docker NAT and iptables work together but I can't wrap my head around it.

To better discuss this let us assume the following:

  • host (eth0): 1.1.1.1
  • docker-bridge (br0): 172.25.0.0/16
    • webserver (veth1): 172.25.0.2:443 (exposed on host as 8443 if necessary)
    • openvpn (veth2): 172.25.0.3:1194 (forwarded to host if necessary)
    • sslh in container (mutually exclusive with host sslh): 172.25.0.4:443 (forwarded to host)
  • sslh on host (mutually exclusive with container sslh): port 443
  • client: 8.8.8.8

Here is my failing model of should happen when a browser connects to host:443 and sslh is on the host in transparent mode

  • sslh connects to 172.25.0.2:8443 posing as the original client 8.8.8.8
  • the webserver answers for the TCP handshake to 8.8.8.8 via veth1
  • the packet goes onto br0
  • the routing decides to send it via ent0
  • iptables' mangle output chain marks it as traffic for sslh (this is what does not work for me)
  • the mark indicates to route the packet to the local lo device instead
  • sslh picks it up, translates the port and sends it back to the client

My problem in the above list is, that I don't know the filter criteria for the webservers outbound traffic: Should I use the docker internal port 443 or the mapped port 8443? The docker IP 172.25.0.2 or another one? It boils down to: will by mangle output rule run before or after docker's NAT?

The I thought I could put sslh into the docker network to avoid thinking about the NAT but I still can't get the iptables rules to match.

I am at a loss how to proceed.

2 Answers 2

0

I know that this thread is rather "old", but I just ran into the same situation and I was literally lost in the beginning. But I managed to make it somehow work. It might not be the most elegant solution though (feel free to post suggestions!)

My setup is very similar to yours:

  • host (1.1.1.1)
    • sslh:443
    • ssh:2222
    • docker
      • container1 (container:443 -> host:4443)
      • container2 (container:443 -> host:1194)

Using the documentation of sslh allowed me to successfully use transparent mode to ssh:443. So aparently, routing to host processes in transparent mode works.

Here is my current set of (host) rules

iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
#The next line routes ssh@host via sslh back to the client. Add more rules like this for other services running on the host (with their respective port)
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 2222 -j SSLH
iptables -t mangle -A SSLH -j MARK --set-mark 0x1 
iptables -t mangle -A SSLH -j ACCEPT
ip rule add fwmark 0x1 lookup 100 
ip route add local 0.0.0.0/0 dev lo table 100

This route ssh traffic transparently between host (port 2222) and client via sshl. But adding a similar rule for docker containers however did not work for me (probably due dockers own extensive iptables rules/networking)

My solution now goes like this: Assign a fixed (container internal) ip to the docker containers and let sslh access the containers via this ip and the internal port of that container instead of via 1.1.1.1:4443 or 1.1.1.1:1194

  • container1: 10.1.0.100
  • container2: 10.2.0.100

My /etc/default/sslh file has this entry now

DAEMON_OPTS="-n --user sslh --transparent --timeout 5 --listen 0.0.0.0:443 --ssh 1.1.1.1:2222 --ssl 10.1.0.100:443 --openvpn 10.2.0.100:443 --pidfile /var/run/sslh/sslh.pid"

In order to assign a fixed ip for a container, you just create a new network e.g. via cli or via docker-compose.

Since I am using docker-compose, I did it like this:

version: '2.4'
services:
  nginx:
    image: nginx
    ports:
      - "80:80"
      - "4443:443"
    networks:
      static-network:
          ipv4_address: 10.1.0.100
      default: null

  static-network:
    ipam:
      config:
        - subnet: 10.1.0.0/16

That solution will add the container "nginx" to two networks: First of all the new "static-network" with a fixed ip 10.1.0.100 and also to the default network (convenient if you want to e.g. link two containers within the same docker-compose file via the autogenerated default network).

I have to admit though, that this solution is only a workaround. I simply do not understand the magic of routing and iptables in that depth, that I just can write better routing rules. Yet, this workaround does the job for me: Transparent sslh proxying to host and docker containers.

0
0

Thanks @systemofapwne !

I finally managed to get something working after reading your answer to this post.

However, I had to do some extra steps. I also made a small draw of the network setup for a better understanding:

networking draft

To allow access from host network (SSLH running on Host), I added a host route through the docker_gwbridge to be able to reach the container's (bridge/overlay) internal docker network:

ip route add $(docker network inspect proxy --format "{{(index .IPAM.Config 0).Subnet}}") via $(docker network inspect docker_gwbridge --format "{{(index .IPAM.Config 0).Gateway}}")

Note: replace "proxy" by the name of the docker network you need to reach from the host.

For the SSLH configuration:

# /etc/conf.d/sslh
# ssh: 192.168.10.250 as it's the only listening IP in the sshd config, and on port 50022 
# tls: traefik container ip address in its standalone bridge/overlay network
DAEMON_OPTS="-t 2 -p 192.168.10.250:443 --ssh 192.168.10.250:50022 --tls 172.16.100.2:443 --user sslh --transparent"

For the SSH traffic, my rules are the same. And as you wrote, you must not handle the SSL traffic, only the SSH one:

iptables -t mangle -N SSLH
iptables -t mangle -A PREROUTING -p tcp -m socket --transparent -j SSLH
#The next line routes ssh@host via sslh back to the client. Add more rules like this for other services running on the host (with their respective port)
iptables -t mangle -A OUTPUT -p tcp -m tcp --sport 50022 -j SSLH
iptables -t mangle -A SSLH -j MARK --set-mark 0x1 
iptables -t mangle -A SSLH -j ACCEPT
ip rule add fwmark 0x1 lookup 100 
ip route add local 0.0.0.0/0 dev lo table 100

I have posted more details on the SSLH's github for setup: https://github.com/yrutschle/sslh/issues/411

1
  • 3
    As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.
    – Community Bot
    Commented Nov 2, 2023 at 10:40

You must log in to answer this question.

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