Run Microsoft Azurevpnclient On NixOS

I’m trying to get the azurevpnclient running on NixOS. They only have the client for Ubuntu. It can be found here: https://learn.microsoft.com/en-us/azure/vpn-gateway/point-to-site-entra-vpn-client-linux.

I’ve tried getting it to run, by following some of the steps described in this excellent Stack exchange post: Different methods to run a non-nixos executable on Nixos - Unix & Linux Stack Exchange.

I’ve downloaded the binary like this:

curl https://packages.microsoft.com/ubuntu/22.04/prod/pool/main/m/microsoft-azurevpnclient/microsoft-azurevpnclient_3.0.0_amd64.deb --output microsoft-azurevpnclient_3.0.0_amd64.deb

I was trying to use, what was described as, the quickest way to get it up and running, using steam-run.

So I dpkg the .deb file with the following:

pkg-deb -x microsoft-azurevpnclient_3.0.0_amd64.deb azurevpnclient

And then run it:

[nix-shell:~/Git/test]$ steam-run azurevpnclient/opt/microsoft/microsoft-azurevpnclient/microsoft-azurevpnclient
azurevpnclient/opt/microsoft/microsoft-azurevpnclient/microsoft-azurevpnclient: error while loading shared libraries: libflutter_secure_storage_linux_plugin.so: cannot open shared object file: No such file or directory

After seeing that, I also tried it with nix-ld. Simply enabling it yielded the exact same error, so I tried looking for the necessary library, to add it to the nix-ld.libraries.

I did this using:

[nix-shell:~]$ nix-locate libflutter_secure
spotube.out                                     172,880 r /nix/store/hv8lniqm6rhjigimiz8lrdrcfqs9hd58-spotube-3.8.3/share/spotube/lib/libflutter_secure_storage_linux_plugin.so
saber.out                                       190,120 x /nix/store/x9yndl7i82sk4jz824vyyma4ywfz6cp4-saber-0.25.2/app/saber/lib/libflutter_secure_storage_linux_plugin.so
ente-auth.out                                   182,744 x /nix/store/zwirjg9sclpxysg4k7rq40ll51b2h8ig-ente-auth-4.0.2/app/ente-auth/lib/libflutter_secure_storage_linux_plugin.so
butterfly.out                                   182,744 x /nix/store/hw76hl14bfmz581sxc4qig4nv0vrs9nw-butterfly-2.2.2/app/butterfly/lib/libflutter_secure_storage_linux_plugin.so

But, as far as I can tell, none of those are “providing the library”, but are simply apps using it.

I’m now not sure how to continue, since all other approaches I know (mostly the ones in the Stack Exchange post :smiley:) require me to find, and provide the libraries.

I’m not sure if I’m just doing something wrong, or I just haven’t found the right instructions yet.

1 Like

Were you able to find a complete solution for this? Thanks!

@dinvlad Calling it a “complete solution” would be far from the truth. I’m sure I could get it there with someone experienced in packaging nix applications. Currently, I have the following problems:

Problems

Capabilities

Apparently I’m missing some capabilities, so I’ve tried adding:

  security.wrappers = {
    microsoft-azurevpnclient = {
      owner = "root";
      group = "root";
      # setuid = true;
      capabilities = "cap_net_admin+ep";
      source = "${(pkgs.callPackage ../azurevpn.nix { })}/bin/microsoft-azurevpnclient";
    };
  };

But that now prevents me from launching the application:

bwrap: Unexpected capabilities but not setuid, old file caps config?

Polkit

I’m pretty sure that the above capabilities were working before I started using bwrap. I’m using bwrap to place the certs (well, currently just one cert, see the next point for details) in the directory azure vpn expects. But I was never able to figure out, how to write a proper polkit rule, so only azure vpn would have the permissions to modify the DNS. I got it working by always return YES though… which is… yay, but suboptimal for obvious reasons.

Certificates

Currently, I’ve just copied one of the certificates, and I’m inserting it harcoded to the location it is expected using bwrap. Optimally, this would be done placing all the existing certificates in the location, not hardcoding them. But I wasn’t able to successfully generate that. I think there is an attempt of doing that left in the repo.

How to get to where I am

If somebody can help me with those problems, I could clean the repo up and maybe even get it to a state where it could be submitted to nixpkgs. If you want to try and achieve that, I guess the easiest way would be to:

  1. Clone GitHub - Elias-Graf/nix-azure-vpn
  2. cd nix-azure-vpn
  3. nix-build && result/bin/microsoft-azurevpnclient

And work from there. I could post some more code that I’ve been trying to get to work in my system configuration, but as I mostly failed, I don’t think it would be too relevant. If you have solutions, feel free to get back to me though, I’d be happy to try them out :slight_smile:

Also linking: Help getting azure vpn to work, which was very helpful to get this far. Maybe @VTimofeenko is interested in helping even more, they seem to be quite talented.

1 Like

Thanks so much for the write-up, @Elias-Graf ! I’ll probably poke at it at some point. For now, I was considering just spinning up an Ubuntu VM for this specific use case, since it was needed only for one of my client projects.

Appreciate that :slight_smile: I don’t think I can help here, however. Looks like getting this client to work would imply having, well, access to Azure VPN, which I do not.

I would suggest also trying buildFHSenv and patchelf to inject the libraries into whatever is expecting them.

@Elias-Graf I’ve had the same problem as you and thanks to this current thread, this thread and a good dose of claude (full disclosure: i’m a total nixos noob), I’ve managed to get it working. Kinda. The connection does drop periodically but I’ve run nslookup against private Azure resources and they resolve to the correct private IP.

I built the client using what you shared in the above linked thread. Having extensively tried to build it previously with no success I was very impressed you got it working!

Below is a simplified configuration.nix which, after polluting with an embarrassing amount of AI slop and then tweaking to the best of my limited understanding, worked:

{ config, pkgs, lib, ... }:

let 
  azureVpn = pkgs.callPackage /path/to/my/azurevpn.nix {};
in
{
  # Usual choss

  users.users.me = {
    isNormalUser = true;
    description = "Me";
    extraGroups = [ "systemd-network" "network" "networkmanager" ];
    packages = with pkgs; [];
  };

  nixpkgs.config.allowUnfree = true;

  environment.systemPackages = with pkgs; [
    azureVpn
    openresolv
  ];

  environment.etc."ssl/certs/DigiCert_Global_Root_G2.pem".text = ''
<big ol'cert>
  '';

  security.wrappers = {
    microsoft-azurevpnclient = {
      owner = "root";
      group = "root";
      capabilities = "cap_net_admin,cap_net_raw,cap_net_bind_service,cap_setpcap,cap_setuid,cap_setgid,cap_sys_admin+ep";
      source = "${azureVpn}/bin/microsoft-azurevpnclient";
    };
  };

  services.resolved = {
    enable = true;
    dnssec = "false";
    domains = [ "~." ];
    fallbackDns = [ "1.1.1.1" "8.8.8.8" ];
    extraConfig = ''
      DNSStubListener=yes
      ResolveUnicastSingleLabel=yes
      Cache=yes
    '';
  };

  systemd.services.systemd-resolved = {
    serviceConfig = {
      SupplementaryGroups = [ "systemd-network" ];
      BusName = "org.freedesktop.resolve1";
    };
  };

  services.dbus.packages = [
    pkgs.gnome-keyring 
    (pkgs.writeTextFile {
      name = "azurevpn-dbus-policy";
      destination = "/share/dbus-1/system.d/org.freedesktop.resolve1.AzureVPN.conf";
      text = ''
        <?xml version="1.0" encoding="UTF-8"?>
        <!DOCTYPE busconfig PUBLIC "-//freedesktop//DTD D-BUS Bus Configuration 1.0//EN"
        "http://www.freedesktop.org/standards/dbus/1.0/busconfig.dtd">
        <busconfig>
          <policy user="root">
            <allow own="org.freedesktop.resolve1"/>
            <allow send_destination="org.freedesktop.resolve1"/>
          </policy>
          
          <!-- Allow all users to set DNS -->
          <policy context="default">
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.resolve1.Manager"
                   send_member="SetLinkDNS"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.resolve1.Manager" 
                   send_member="SetLinkDomains"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.resolve1.Manager"
                   send_member="SetLinkDefaultRoute"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.resolve1.Manager"
                   send_member="RevertLink"/>
            <!-- Add these additional permissions -->
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.DBus.Properties"
                   send_member="Get"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.DBus.Properties"
                   send_member="GetAll"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.DBus.Introspectable"
                   send_member="Introspect"/>
            <allow send_destination="org.freedesktop.resolve1"
                   send_interface="org.freedesktop.DBus.Peer"
                   send_member="Ping"/>
          </policy>
  
          <!-- Specific policy for your user -->
          <policy user="ed">
            <allow send_destination="org.freedesktop.resolve1"/>
          </policy>
        </busconfig>
      '';
    })
  ];
  
  security.polkit.extraConfig = ''
    polkit.addRule(function(action, subject) {
      // Allow DNS and network operations
      if (action.id.indexOf("org.freedesktop.resolve1.") == 0 ||
          action.id.indexOf("org.freedesktop.NetworkManager.") == 0) {
        // Check if it's our VPN client
        if (subject.programPath &&
            (subject.programPath.indexOf("microsoft-azurevpnclient") >= 0 ||
             subject.programPath.indexOf("openvpn") >= 0)) {
          return polkit.Result.YES;
        }
        
        if (subject.local && subject.active && subject.isInGroup("networkmanager")) {
          return polkit.Result.YES;
        }
      }
      
      if (action.id == "org.freedesktop.resolve1.set-dns-servers" ||
          action.id == "org.freedesktop.resolve1.set-domains" ||
          action.id == "org.freedesktop.resolve1.set-default-route" ||
          action.id == "org.freedesktop.resolve1.set-link-dns" ||
          action.id == "org.freedesktop.resolve1.set-link-domains" ||
          action.id == "org.freedesktop.resolve1.set-link-default-route" ||
          action.id == "org.freedesktop.resolve1.revert-link") {
        return polkit.Result.YES;
      }
    });
  '';

  networking.firewall = {
    allowedUDPPorts = [ 1194 ];
    checkReversePath = false;
  };

  ...

}

I’m sure this is a horrifying sight to anyone who knows what they are doing with nixos so would really appreciate any pointers/advice. For starters, I’d like to clean up the permissions since they seem far too lax.

If you have issues debugging I was running the wrapper (microsoft-azurevpnclient) in one terminal then getting the PID and running:

sudo strace -f -p $PID -e trace=network,open,openat,read,write,connect,socket -s 2000 -o vpn_dns_error.log

Super helpful logs.

In any case, @Elias-Graf I hope this helps you if you haven’t already figured it out :slight_smile: