jamesmcm / vopono

Run applications through VPN tunnels with temporary network namespaces
GNU General Public License v3.0
831 stars 44 forks source link

Is it possible to use this without sudo? #189

Open TheDcoder opened 2 years ago

TheDcoder commented 2 years ago

Hi,

Is there any way to use vopono without using sudo? I don't use it on my system (I prefer using doas) and it seems to be a hard requirement as vopono automatically calls sudo.

So is there any way to use this without sudo? Even as root directly.

Ideally we want to implement an alternative mechanism for privilege escalation which is generic and not dependent on a single utility like sudo. For example this can be easily done by a script which handles escalation.

jamesmcm commented 2 years ago

The issue I hit before was how to then run the program in the netns as the target user and with the target user environment, etc.

I think it'd require making more system calls directly, rather than using Rust's Command API.

But this would be good to add - see issue #48 too.

TheDcoder commented 2 years ago

I'm not suggesting a paradigm shift, just an alternative to sudo. I guess one could write a sudo shim program which will automatically translate calls to sudo to their own method of escalation, but it would be nice to have some kind of official support so that users don't have to muck around with manipulating their PATH so that vopono can find the sudo shim.

I'd be content even with being able to run vopono as root directly, is this possible?

jamesmcm commented 2 years ago

It used to work, but I think the switch to the sudo crate might have broken it, I'll take a look when I have time.

In the long-term it'd be best to avoid shelling out entirely, separate privilege escalation if necessary and make syscalls directly - like issue #49

TheDcoder commented 2 years ago

@jamesmcm I did some testing and I running vopono as root, it seems to be stuck starting OpenVPN (even though I can see the process in htop):

root@rpi /t/testing# vopono exec --provider privateinternetaccess --server switzerland 'sleep inf'
 2022-09-12T21:41:00.999Z WARN  vopono > Could not parse PULSE_SERVER from pactl info output: Err(Could not parse pactl output!:
)
 2022-09-12T21:41:00.999Z WARN  vopono::util > Running vopono as root user directly!
 2022-09-12T21:41:01.017Z INFO  vopono::util > Chosen config: /root/.config/vopono/pia/openvpn/switzerland-ch.ovpn
 2022-09-12T21:41:01.031Z INFO  vopono::netns > Created new network namespace: vopono_pia_switzerland
 2022-09-12T21:41:01.229Z INFO  vopono::netns > IP address of namespace as seen from host: 10.200.2.2
 2022-09-12T21:41:01.229Z INFO  vopono::netns > IP address of host as seen from namespace: 10.200.2.1
 2022-09-12T21:41:01.244Z INFO  vopono::openvpn > Launching OpenVPN...

It's been around like this for a day.


I agree with what you regarding shelling, direct syscalls would be much better.


How does privilege escalation with sudo work? I did some investigation by using a sudo shim script to print out what's being ran and the env variables being set, but I did not see any differences in the variables nor the command which was ran (it was the same as the one I ran).

So how does it know that it's a sudo invocation and not a direct root invocation? How does it identify the original user? Do you just check $USER because you are calling sudo with the -E option to preserve user vars? :thinking:

jamesmcm commented 2 years ago

If you run it with -v for verbose you should see exactly what it runs.

Privilege escalation is currently handled by:

pub fn elevate_privileges(askpass: bool) -> anyhow::Result<()> {
    use signal_hook::{consts::SIGINT, flag};
    use std::sync::atomic::{AtomicBool, Ordering};
    use std::sync::Arc;

    // Check if already running as root
    if nix::unistd::getuid().as_raw() != 0 {
        info!("Calling sudo for elevated privileges, current user will be used as default user");
        let args: Vec<String> = std::env::args().collect();

        let terminated = Arc::new(AtomicBool::new(false));
        flag::register(SIGINT, Arc::clone(&terminated))?;

        let sudo_flags = if askpass { "-AE" } else { "-E" };

        debug!("Args: {:?}", &args);
        // status blocks until the process has ended
        let _status = Command::new("sudo")
            .arg(sudo_flags)
            .args(args.clone())
            .status()
            .context(format!("Executing sudo {} {:?}", sudo_flags, &args))?;

        // Deprecated - do we need to handle flag here?
        // cleanup::cleanup_signal(SIGINT)?;

        if terminated.load(Ordering::SeqCst) {
            // we received a sigint,
            // so we want to pass it on by terminating with a sigint
            nix::sys::signal::kill(nix::unistd::getpid(), nix::sys::signal::Signal::SIGINT)
                .expect("failed to send SIGINT");
        }

        std::process::exit(0);
    } else if std::env::var("SUDO_USER").is_err() {
        warn!("Running vopono as root user directly!");
    }
    Ok(())
}

You can also check the OpenVPN log which should be at /root/.config/vopono/logs/ in this case (when it is running).

I think the main remaining problematic part is https://github.com/jamesmcm/vopono/blob/master/vopono_core/src/network/netns.rs#L132-L136 - using sudo to run as the given user.

TheDcoder commented 2 years ago

You can also check the OpenVPN log which should be at /root/.config/vopono/logs/ in this case (when it is running).

I can't see the logs directory (vopono is still running):

root@rpi ~# ls ~/.config/vopono
config.toml  pia/

I think the main remaining problematic part is (...) using sudo to run as the given user.

Not sure I understand, I haven't used Rust before so I can't really read the code well given its verbose syntax. I will try the --verbose option to see what vopono is doing.

TheDcoder commented 2 years ago

I found the logs at /etc/netns/vopono_pia_switzerland/openvpn.log and I think I found the issue:

(...more of the stuff below...)
1663106301.439663 1 SIGUSR1[soft,init_instance] received, process restarting
1663106621.460569 4000021 RESOLVE: Cannot resolve host address: swiss.privacy.network:1198 (Temporary failure in name resolution)
1663106641.481329 4000021 RESOLVE: Cannot resolve host address: swiss.privacy.network:1198 (Temporary failure in name resolution)
1663106641.481373 40 Could not determine IPv4/IPv6 protocol
1663106641.481467 1 SIGUSR1[soft,init_instance] received, process restarting
1663106961.499285 4000021 RESOLVE: Cannot resolve host address: swiss.privacy.network:1198 (Temporary failure in name resolution)
1663106981.519281 4000021 RESOLVE: Cannot resolve host address: swiss.privacy.network:1198 (Temporary failure in name resolution)
1663106981.519328 40 Could not determine IPv4/IPv6 protocol
1663106981.519423 1 SIGUSR1[soft,init_instance] received, process restarting

And the contents of the two other .conf files in that directory:

root@rpi ~# cat /etc/netns/vopono_pia_switzerland/{nsswitch,resolv}.conf
# Name Service Switch configuration file.
# See nsswitch.conf(5) for details.

passwd: files systemd
group: files [SUCCESS=merge] systemd
shadow: files systemd
gshadow: files systemd

publickey: files

hosts: files mymachines myhostname dns
networks: files

protocols: files
services: files
ethers: files
rpc: files

netgroup: files
nameserver 209.222.18.222
nameserver 209.222.18.218

Obviously OpenVPN is having trouble with resolving the address of the VPN server, but I'm not sure how to fix the issue. Perhaps I should sync and try the config files with the IP addresses directly instead of domain names.

TheDcoder commented 2 years ago

There is some progress I guess, OpenVPN is running with errors when I use the config files with the IP address directly:

 2022-09-13T22:32:38.160Z DEBUG vopono::openvpn           > 1663108358.160084 3000021 TLS Error: TLS handshake failed
 2022-09-13T22:32:38.161Z DEBUG vopono::openvpn           > 1663108358.160261 1 SIGUSR1[soft,tls-error] received, process restarting
 2022-09-13T22:32:43.161Z DEBUG vopono::openvpn           > 1663108363.161624 1 TCP/UDP: Preserving recently used remote address: [AF_INET]212.102.37.19:1198
 2022-09-13T22:32:43.161Z DEBUG vopono::openvpn           > 1663108363.161689 1 UDP link local: (not bound)
 2022-09-13T22:32:43.161Z DEBUG vopono::openvpn           > 1663108363.161705 1 UDP link remote: [AF_INET]212.102.37.19:1198
 2022-09-13T22:33:43.876Z DEBUG vopono::openvpn           > 1663108423.876649 3000021 TLS Error: TLS key negotiation failed to occur within 60 seconds (check
your network connectivity)
 2022-09-13T22:33:43.876Z DEBUG vopono::openvpn           > 1663108423.876692 3000021 TLS Error: TLS handshake failed
 2022-09-13T22:33:43.876Z DEBUG vopono::openvpn           > 1663108423.876869 1 SIGUSR1[soft,tls-error] received, process restarting
 2022-09-13T22:33:53.877Z DEBUG vopono::openvpn           > 1663108433.877155 1 TCP/UDP: Preserving recently used remote address: [AF_INET]212.102.37.19:1198
 2022-09-13T22:33:53.877Z DEBUG vopono::openvpn           > 1663108433.877227 1 UDP link local: (not bound)
 2022-09-13T22:33:53.877Z DEBUG vopono::openvpn           > 1663108433.877284 1 UDP link remote: [AF_INET]212.102.37.19:1198
 2022-09-13T22:34:54.081Z DEBUG vopono::openvpn           > 1663108494.081120 3000021 TLS Error: TLS key negotiation failed to occur within 60 seconds (check
your network connectivity)
 2022-09-13T22:34:54.081Z DEBUG vopono::openvpn           > 1663108494.081154 3000021 TLS Error: TLS handshake failed
jamesmcm commented 2 years ago

It's interesting that it's a DNS issue, since that is set in /etc/netns/{netns.name}/hosts

Are you using any service to manage DNS automatically?

TheDcoder commented 2 years ago

I'm using Arch Linux ARM's default network stack so I guess I'm using systemd-resolved for the DNS.

alexmo1997 commented 1 year ago

Given that lots of installations have firejail installed, using that for creating network namespaces if available (requires no privileges) would be an option as well. Another option might be bwrap, which is even more common due to flatpak, which also supports new creating network namespaces, but I'm not sure if those are customizable enough.

Edit: Even with bwrap, I think this should be very possible as you should be able to just bindmount something to /etc/resolv.conf for instance.

Lcchy commented 11 months ago

For anyone looking for a way to run a program through a vpn connection without using sudo, I found that directly setting up a linux namespace (like here) and then using firejail --netns=... command works really well. Thanks @alexmo1997 for the pointer.

It would be nice to be able to do it via vopono at some point but as I understand it this would be a larger undertaking

jamesmcm commented 11 months ago

FWIW I intend to add creating just the configured network namespace in the next release. You still need sudo to create the network namespace though, no?

Lcchy commented 11 months ago

Yes indeed, sudo is needed to setup the namespace. Adding a namespace setup command to vopono as you said, that allows to later run a program in it without sudo would be a good solution in my opinion.

musjj commented 7 months ago

I'm trying to create a user service with vopono and it's been kind of a pain, so the ability to run vopono exec without sudo would be a blessing!

@Lcchy Do you have any pointers for setting up something like this for ProtonVPN?

Lcchy commented 7 months ago

@musjj You can find the scripts I've ended up writing here

The rest of the repo just adds some utility to automatically fetch the wg config files from Mullvad and other things, but is not really necessary.

You would need to manually or automatically fetch a wireguard config file from your vpn provider

musjj commented 7 months ago

@Lcchy Thanks, but I realized that vopono actually supports this now (kind of), should've read the guide more carefully: https://github.com/jamesmcm/vopono/blob/master/USERGUIDE.md#creating-only-network-namespace.

I tried to turn it into a service, but it doesn't correctly start for some reason:

Mar 13 03:13:08 $HOST sudo[2110670]: pam_unix(sudo:session): session opened for user root(uid=0) by $USER(uid=1000)
Mar 13 03:13:09 $HOST sudo[2110670]: pam_unix(sudo:session): session closed for user root

It just starts and stops without any errors.

Here's my service (don't mind the nix paths):

[Unit]
Description=Run applications through VPN tunnels with temporary network namespaces
Wants=network-online.target
After=network-online.target

[Service]
Environment=PATH=/run/wrappers/bin:/nix/store/j0i0p3mzlf7p1j69ximz567cpvhlnk2b-openvpn-2.6.9/bin:/nix/store/imf924bs6lwrvkpdairl6sw48a2aljra-vopono-0.10.9/bin
Environment=SSH_AUTH_SOCK=%t/gnupg/S.gpg-agent.ssh
Environment=SSH_ASKPASS=1
ExecStart=/nix/store/imf924bs6lwrvkpdairl6sw48a2aljra-vopono-0.10.9/bin/vopono -v exec --provider protonvpn --server japan --create-netns-only none
Type=simple

[Install]
WantedBy=multi-user.target

It looks like the sudo was authenticated successfully, but then it just does nothing and shuts down.

Lcchy commented 6 months ago

Hi! sorry for answering so late. In my opinion it seems like vopono tries to authenticate but doesn't manage to attach to any input so it closes without creating the namespace. But I am not so sure, it could be something else.

EDIT: as written in the userguide, vopono would need to be setup on root to not ask for a password I think: https://github.com/jamesmcm/vopono/blob/master/USERGUIDE.md#systemd-service