linuxserver / docker-wireguard

GNU General Public License v3.0
2.91k stars 360 forks source link

Wireguard leaks IP address in client mode if connection fails #139

Closed master-hax closed 2 years ago

master-hax commented 2 years ago

linuxserver.io


Expected Behavior

the command curl api.ipify.org from within the container should fail if the VPN connection is not up

Current Behavior

the command curl api.ipify.org from within the container succeeds & shows the docker host's external IP address if the VPN connection is not up

Steps to Reproduce

  1. put a valid wg0.conf in ./config
  2. start this docker-compose file to run wireguard in client mode:
    version: "3.7"
    services:
    vpn-client:
        image: linuxserver/wireguard
        restart: unless-stopped
        cap_add:
          - NET_ADMIN
          - SYS_MODULE
        environment:
          - TZ=America/Los_Angeles
        volumes:
          - /lib/modules:/lib/modules
          - ./config:/config
        sysctls:
          - net.ipv4.conf.all.src_valid_mark=1
          - net.ipv6.conf.all.disable_ipv6=0
  3. run docker exec -it vpn-client_1 curl api.ipify.org. you will see the IP address of your VPN server exit node as expected.
  4. delete ./config/wg0.conf then run docker restart vpn-client_1. now the connection should fail. we can confirm from the logs:
    **** Client mode selected. ****
    **** No client conf found. Provide your own client conf as "/config/wg0.conf" and restart the container. ****
  5. run docker exec -it vpn-client_1 curl api.ipify.org again. we expect the request to fail this time, but instead you will see the external IP address of the docker host

Environment

OS: dietpi CPU architecture: x86_64/arm32/arm64 How docker service was installed: i don't remember

Command used to create docker container (run/create/compose/screenshot)

Docker logs

github-actions[bot] commented 2 years ago

Thanks for opening your first issue here! Be sure to follow the bug or feature issue templates!

aptalca commented 2 years ago

Wireguard leaks IP address in client mode if connection fails

That's an inaccurate statement. The test you're performing is not of a failed connection, but of a scenario where wireguard is not even started. If there is no wg0.conf, all the container does is print an error message in the log and then run a single process of sleep infinite. No wireguard interface is created, no iptables rules or routes set. There is no connection to fail. At that point it's just a plain Ubuntu container with a sleep process running inside of it, using the docker network it's set up with.

An accurate test would be to first set it up with a valid wg0.conf, make sure it works as expected, then edit it to make a breaking change (like the server address, or the public key, etc.) and then see if there is a connection and/or if anything is leaking. You'll see that the outbound connections will still be attempted, the encrypted packets will still be sent out, but it won't hear anything back because the packets will never reach their destination properly. So the container will experience a loss of connection.

master-hax commented 2 years ago

the fast response is very much appreciated 🙂

i just tested what you mentioned with a malformed wg0.conf & curl api.ipify.org indeed failed.

two thoughts i still have:

  1. can we consider forcing container death if wg0.conf is missing? if wireguard doesn't even start in that scenario, it seems both incorrect & unexpected to leave the container up & running in host network mode
  2. even with a malformed wg0.conf, there is a few second window immediately after the container starts when the curl command does work and leaks the IP. i don't know if this is unavoidable due to the time it takes wireguard to start but i just thought i would bring it up.
aptalca commented 2 years ago

If there isn't a wg0.conf, it means wireguard is not configured. If you were to simply install the wireguard package on bare metal on a regular linux machine, but not configure it, you can't expect the internet to not work until wireguard is configured. That's not how it works.

Even if there is a wg0.conf and a wireguard tunnel set up, it doesn't necessarily mean IP won't leak (or if anything will go through the tunnel at all). It's all configured via the parameters in wg0.conf (ie. AllowedIPs). Without that, the container would have absolutely no knowledge of what your intentions are.

Also, the container still needs an internet connection, otherwise it can't connect to the wireguard server when creating the tunnel.

If you're worried about the split second after container start before wireguard tunnel creation, you can add a custom delay for the downstream container's init. Mullvad for instance has an api endpoint you can hit to make sure the connection goes through them. https://www.linuxserver.io/blog/2019-09-14-customizing-our-containers

laurids-reichardt commented 2 years ago

I share the concern expressed above. I believe that if anyone uses this image to route traffic of another container exclusively through WireGuard, they won't want their host IP address leaking under any circumstance.

My WireGuard and networking knowledge is very limited, but the section The New Namespace Solution of the official WireGuard documentation caught my eye: https://www.wireguard.com/netns/

Could the eth0 interface live inside a separate network namespace, as described inside the article, to prevent any connection going through it, even in case the wg0 interface isn't running yet or goes down?

aptalca commented 2 years ago

I don't think you understand what I wrote above. That article is telling you to configure it a certain way. What you're asking me is a scenario where you refuse to configure it at all and expect it to drop the internet connection.

Just configure it as you should (by putting in a wg0.conf, which you only have to do once, when you first set it up), then it will work as expected.

EDIT: to further clarify. as long as it's configured once, even if the configuration is not correct (but valid, meaning wireguard does not error out), your details won't leak as the packets will be sent to ether effectively appearing as no internet connection. But you have to configure it by providing a valid wg0.conf so the tunnel gets created (again, the tunnel may not go anywhere, but it needs to be there)

drizuid commented 2 years ago

Just to add a little here, if you dont understand VPNs and networking, as least at a basic fundamental level, a vpn isn't necessarily protecting you. That's not on us, users are responsible for their configurations AND understanding their configurations.

laurids-reichardt commented 2 years ago

@aptalca I appreciate your feedback. I understand your point that once a valid configuration is provided and the tunnel created, no unencrypted packages should leak.

My concern lays with two scenarios:

  1. As you previously stated, there's a short gap between the time the container starts and the WireGuard tunnel is created.
  2. If, for whatever unlikely reason, the wg0 interface goes down, all traffic seems to go through the unencrypted eth0 interface. I tried to confirm this via the following commands:
    docker exec -it wireguard bash
    ip link set wg0 down
    curl api.ipify.org

As I'm not particular experienced with WireGuard, my question is whether the namespace configuration mentioned in the linked WireGuard article would prevent both scenarios. Is the following file the right place to try implementing such a configuration? https://github.com/linuxserver/docker-wireguard/blob/master/root/etc/services.d/wireguard/run

aptalca commented 2 years ago

As you previously stated, there's a short gap between the time the container starts and the WireGuard tunnel is created.

You have the same issues on bare metal so nothing is different here. We also don't make any such guarantees about leakage. We don't even officially provide support for using this in client configuration. It's in the readme. The reason is that we don't want to give you the false sense of security. If you want to use this in client mode to be totally private, you need to know what you're doing. You can easily customize your downstream container (assuming it's lsio) to add a delay on start and check for the tunnel but that's on you.

If, for whatever unlikely reason, the wg0 interface goes down, all traffic seems to go through the unencrypted eth0 interface. I tried to confirm this via the following commands:

You'd need to give us a real world scenario where the tunnel goes down on its own, not one where you manually bring it down.

laurids-reichardt commented 2 years ago

Thanks for the feedback. Mullvad seems to mitigate the second scenario by adding the following lines to the supplied WireGuard conf:

PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
davidfrickert commented 2 years ago

Thanks for the feedback. Mullvad seems to mitigate the second scenario by adding the following lines to the supplied WireGuard conf:

PostUp = iptables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -I OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT
PreDown = iptables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT && ip6tables -D OUTPUT ! -o %i -m mark ! --mark $(wg show %i fwmark) -m addrtype ! --dst-type LOCAL -j REJECT

@laurids-reichardt, do these configs work for you? I get iptables: No chain/target/match by that name. sadly, running in armv8 platform (raspberry pi 4)

laurids-reichardt commented 2 years ago

@davidfrickert Yes. Check if the appropriate iptable chains exist with the following command: iptables -L -v

More information: https://linux.die.net/man/8/iptables

davidfrickert commented 2 years ago

Thanks! I ran that command inside the linuxserver/wireguard container and got this

root@e24380837a64:/# iptables -L -v
Chain INPUT (policy ACCEPT 3274K packets, 2240M bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain FORWARD (policy ACCEPT 0 packets, 0 bytes)
 pkts bytes target     prot opt in     out     source               destination

Chain OUTPUT (policy ACCEPT 3343K packets, 1704M bytes)
 pkts bytes target     prot opt in     out     source               destination
23768   34M ACCEPT     all  --  any    any     anywhere             192.168.0.0/16

I have very little understanding of iptables, but I seem to have all needed chains right? It's probably another part of the command that isn't working I guess.

laurids-reichardt commented 2 years ago

@davidfrickert Yeah sorry, I won't be able to help you debug the issue.

Here's something you could try. Try to manually execute the iptable commands inside the running container and see what comes up: docker exec -it wireguard bash

davidfrickert commented 2 years ago

Yeah I did that @laurids-reichardt The error I get is iptables: No chain/target/match by that name.. Don't really know how to test for what is missing in the system But I kinda gave up, using another image now. Thanks anyway!

lucasrangit commented 2 years ago

But I kinda gave up, using another image now.

@davidfrickert what image did you switch to?

master-hax commented 2 years ago

@aptalca it would be great if we could change this line to kill the container rather than sleep infinity since in this case, Wireguard is unable to start up.

i think this would go a long way to solve some of the concerns @laurids-reichardt & i had about IP leakage without causing any disruptions to how the image is used. as you said, if there isn't a wg0.conf, it means Wireguard is not configured, so i don't think it makes sense to leave the container running without doing anything. especially given that this misconfiguration can leak user IPs.

aptalca commented 2 years ago

It wouldn't make much of a difference because most people use restart=unless-stopped or restart=always so it will keep looping and the network stack will be up most of the time anyway. All it would do is, artificially inflate docker pull counter because lots of people also integrate a docker pull into their container start mechanism like a systemd service. We refrain from stopping containers for that reason.

Overall, I don't share your concerns at all. The issue you mention only affects a first time creation, without configuration scenario. Who in their right mind would route other containers through something that is not configured at all yet, and expect it to magically work? Like I said before, the user just has to drop a valid wg0.conf in there. It doesn't even have to be a working conf because wireguard doesn't care if the connection is established or not. As long as the conf has the necessary required info like the address and the keys, the tunnel is up and all is well.

Please keep in mind that we do not provide any guarantees about IP leakage as we can't control your environment or settings, and that you are the admin of your server, so it is really your responsibility. We don't even officially support the client configuration. We provide the tools, how you use them is up to you.

You can always customize it to suit your needs: https://www.linuxserver.io/blog/2019-09-14-customizing-our-containers

master-hax commented 2 years ago

okay - if this isn't the direction you want to take the project in i'll leave it at that.

one thing i want to point out though:

It wouldn't make much of a difference because most people use restart=unless-stopped or restart=always so it will keep looping and the network stack will be up most of the time anyway.

i believe restarting the wireguard container permanently breaks the network stack of any containers attached with network_mode:wireguard. i haven't been able to get it to reconnect without restarting the client containers anyway.

thanks again for the debugging help.

waweic commented 2 years ago

I do understand everything that has been written before, especially that you don't guarantee that there will be no leakage and don't officially support the client mode. But please consider the following:

I have two containers, one running your wireguard image and one running deluge using --net=container:wireguard. The volume with the wg0.conf is on a different hard drive than the config and torrent files for deluge.

Now, after a reboot, when the first hard drive fails to mount, wireguard doesn't see a wg0.conf and doesn't start. But because the second hard drive didn't fail to mount, Deluge will happily work without VPN protection. I live in Germany, torrenting without a VPN can get very expensive here. Without some sort of "killswitch" I don't really know how to proceed at this point. In any case, failing with no connection would be a lot better that failing without protection

drizuid commented 2 years ago

We accept PRs , but realistically speaking, your illegal activities don't affect us at all and thus add no urgency in our minds.

master-hax commented 2 years ago

Hi @drizuid what illegal activities are you referring to?

drizuid commented 2 years ago

The reply was intended for the person right above my comment, the reply function may have failed on mobile.. Regardless, it's not something we have any intention of working on at this point in time, if you'd like to submit a pr to resolve it while not negatively impacting intended functionality, we will definitely test and evaluate the PR.

smathev commented 1 year ago

@drizuid *Edited from a very negative reply to a more formal, friendly and neutral response.

I fear for all people using this implementation, myself included. Whether illegal activities in a democracy ,or fighting for democracy in Iran, Russia, China, etc.

Please inform me, why it's a better implementation when setting up a VPN-client to "allow unVPN-ed" traffic when no config is found (especially in a docker-image), than it is to actually "fail secure" and stop all outgoing traffic when no config file is found? Especially in an environment when said VPN is only taking command of traffic going through that docker-container?

drizuid commented 1 year ago

I have no intention of reading this message or further entertaining this topic. If someone wants to create a PR that garners the behavior you guys want while not negatively impacting the use of any other users, then submit it.