Azure P2S IKEv2 VPN (Basic SKU) with certificate authentication won't connect on Fedora 44 with SELinux enforcing (NetworkManager-strongswan)

I’m trying to set up a client using StrongSwan + GNOME Settings (NetworkManager / nmcli) for my Azure Point-to-Site Basic SKU IKEv2 VPN on a fresh Fedora 44 install, without
disabling SELinux. I’d like the method to stay as close as possible to the official Microsoft guide:

https://learn.microsoft.com/en-us/azure/vpn-gateway/point-to-site-vpn-client-certificate-ike-linux

Also found this older Fedora discussion that looks related but doesn’t quite match my symptoms:

https://discussion.fedoraproject.org/t/after-upgrade-to-fedora-32-strongswan-vpn-issue/70501

The same VPN connects fine from Windows, macOS, and from my previous Ubuntu install on the same hardware.

My environment

  • Fedora 44 Workstation, Linux kernel 7.0.9-202.fc44, SELinux Enforcing
  • NetworkManager 1.56.0-1.fc44, NetworkManager-strongswan 1.6.0-12.fc44, strongswan 6.0.4-2.fc44
  • update-crypto-policies --showDEFAULT
  • VPN: Azure VPN Gateway, Basic SKU, IKEv2 only, certificate auth (self-signed root CA uploaded to Azure; X.509 client cert + 2048-bit RSA PKCS#8 key, unencrypted)

To anyone who might want to mention that the Azure Basic SKU VPN Gateway won’t support the IKEv2 protocol, please have a look here; unfortunately there are many contradictory documentation on Microsoft’s resources.

Also, if you’re curious and want to set it up for yourself and are following this guide but can’t see the IKEv2 protocol option on Azure Portal, use this workaround.

What I did

sudo dnf install strongswan tpm2-abrmd strongswan-charon-nm NetworkManager-strongswan-gnome

install -m 0755 -Z -d ~/.cert
install -m 0755 -Z -d ~/.cert/myvpn
install -m 0644 -Z "/path/to/downloaded/azure/vpnclientconfiguration/Generic/VpnServerRoot.cer_0" ~/.cert/myvpn/VpnServerRoot.cer
install -m 0644 -Z "/path/to/generated/azure/vpn/client/Cert.pem" ~/.cert/myvpn/client-cert.pem
install -m 0600 -Z "/path/to/generated/azure/vpn/client/Key.pem" ~/.cert/myvpn/client-key.pem
sudo restorecon -RvF ~/.cert

Result:

$ ls -laZR ~/.cert
.cert:
drwxr-xr-x.  3 myuser myuser unconfined_u:object_r:home_cert_t:s0     4096 .
drwx------. 35 myuser myuser unconfined_u:object_r:user_home_dir_t:s0 4096 ..
drwxr-xr-x.  2 myuser myuser unconfined_u:object_r:home_cert_t:s0     4096 myvpn

.cert/myvpn:
-rw-r--r--.  1 myuser myuser unconfined_u:object_r:home_cert_t:s0 1200 client-cert.pem
-rw-------.  1 myuser myuser unconfined_u:object_r:home_cert_t:s0 1704 client-key.pem
-rw-r--r--.  1 myuser myuser unconfined_u:object_r:home_cert_t:s0  914 VpnServerRoot.cer

Then following the GUI Steps of the Microsoft guide, opened GNOME Settings → Network → Add VPN → IPsec/IKEv2 (strongswan) and added the following:


The server and client certificate files were imported from the ~/.cert/myvpn/ directory. Everything else (the Algorithms section and all the other tabs) were left unchanged and in most cases empty.

The issue I ran into

When I tried to connect to it using GUI toggle, it wouldn’t turn on and immediately switches off; and when I tried to turn it on with nmcli, I got the following logs in the console:

$ sudo nmcli general logging level debug

$ nmcli general logging
LEVEL  DOMAINS
DEBUG  PLATFORM,RFKILL,ETHER,WIFI,BT,MB,DHCP4,DHCP6,PPP,IP4,IP6,AUTOIP4,DNS,VPN,SHARING,SUPPLICANT,AGENTS,SETTINGS,SUSPEND,CORE,DEVICE,OLPC,INFINIBAND,FIREWALL,ADSL,BOND,VLAN,BRIDGE,TEAM,CONCHECK,DCB,DISPATCH,AUDIT,SYSTEMD,PROXY

$ nmcli connection up myvpn
Error: Connection activation failed: No valid secrets
Hint: use 'journalctl -xe NM_CONNECTION=c8259051-72b6-443f-a718-35479d4744b8 + NM_DEVICE=enp70s0' to get more details.

$ journalctl -xe NM_CONNECTION=c8259051-72b6-443f-a718-35479d4744b8 + NM_DEVICE=enp70s0
May 21 22:59:37 MyComputerHostname NetworkManager[1612]: <info>  [1779429577.6508] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: starting strongswan
May 21 22:59:37 MyComputerHostname NetworkManager[1612]: <warn>  [1779429577.6564] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: plugin NeedSecrets request #1 failed: GDBus.Error:org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty: Failure creating the temporary file
May 21 23:50:38 MyComputerHostname NetworkManager[1612]: <debug> [1779432638.2233] ndisc-lndp[0x7f735c00cab0,"enp70s0"]: processing libndp events
May 21 23:50:38 MyComputerHostname NetworkManager[1612]: <debug> [1779432638.3204] ndisc-lndp[0x7f735c00cab0,"enp70s0"]: processing libndp events
May 21 23:50:42 MyComputerHostname NetworkManager[1612]: <debug> [1779432642.9932] device[30c479a3a8c2afdd] (enp70s0): add_pending_action (1): 'activation-16'
May 21 23:50:42 MyComputerHostname NetworkManager[1612]: <debug> [1779432642.9933] active-connection[cefeeab6d1ead33d]: constructed (NMVpnConnection, version-id 16, type managed)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <info>  [1779432643.0060] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: starting strongswan
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0060] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: starting: watch D-Bus service org.freedesktop.NetworkManager.strongswan
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0060] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: set state: prepare (was waiting)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0060] active-connection[cefeeab6d1ead33d]: set state activating (was unknown)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0061] active-connection[cefeeab6d1ead33d]: check-controller-ready: not signalling (state activating, no controller)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0085] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: starting: VPN service has PID 234994
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0306] platform: (enp70s0) signal: address 4 changed: 192.168.1.15/24 brd 192.168.1.255 lft 50364sec pref 50364sec lifetime 91998-55962[86400,86400] dev 2 flags noprefixroute src kernel
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0310] platform: (enp70s0) signal: address 6 changed: 2601:646:a000:8d0e:fb85:a6a8:932:8d5b/64 lft 252933sec pref 252933sec lifetime 91998-91960[252971,252971] dev 2 flags noprefixroute src kernel
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0452] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: set state: need-auth (was prepare)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0453] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: secrets: requesting VPN secrets pass #1
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0454] settings-connection[880c56810e638783,c8259051-72b6-443f-a718-35479d4744b8]: (vpn:0x55ba894daa90) secrets requested flags 0x80000004 hints '(none)'
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0456] settings-connection[880c56810e638783,c8259051-72b6-443f-a718-35479d4744b8]: (vpn:0x55ba8956a900) existing secrets returned
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0456] settings-connection[880c56810e638783,c8259051-72b6-443f-a718-35479d4744b8]: (vpn:0x55ba8956a900) secrets request completed
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0457] settings-connection[880c56810e638783,c8259051-72b6-443f-a718-35479d4744b8]: (vpn:0x55ba8956a900) new agent secrets processed
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0461] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: secrets: asking service if additional secrets are required
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <warn>  [1779432643.0510] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: plugin NeedSecrets request #1 failed: GDBus.Error:org.freedesktop.NetworkManager.Settings.Connection.InvalidProperty: Failure creating the temporary file
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0510] vpn[0x55ba894865e0,c8259051-72b6-443f-a718-35479d4744b8,"myvpn"]: set state: failed (was need-auth)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0510] active-connection[cefeeab6d1ead33d]: set state deactivated (was activating)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0511] active-connection[cefeeab6d1ead33d]: check-controller-ready: not signalling (state deactivated, no controller)
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0511] device[30c479a3a8c2afdd] (enp70s0): remove_pending_action (0): 'activation-16'
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.0514] active-connection[cefeeab6d1ead33d]: disposing
May 21 23:50:43 MyComputerHostname NetworkManager[1612]: <debug> [1779432643.3300] ndisc-lndp[0x7f735c00cab0,"enp70s0"]: processing libndp events
May 21 23:50:49 MyComputerHostname NetworkManager[1612]: <debug> [1779432649.0737] ndisc-lndp[0x7f735c00cab0,"enp70s0"]: processing libndp events

How do I fix this and get connected to my VPN?

My solution

I finally got it connecting reliably with SELinux left in enforcing mode, so I wanted to write up the full solution in case it helps the next person.

Where to put the certificates (this part matters)

The official guide tells you to drop the certs into system directories like /etc/ipsec.d/{certs,private,cacerts}. On Fedora that path belongs to libreswan, and strongswan uses /etc/strongswan/ipsec.d/ instead. I tried the strongswan system path and it failed:

plugin NeedSecrets request #1 failed: GDBus.Error:...: Failed to read file
'/etc/strongswan/ipsec.d/private/client-key.pem' as user 'sepehr':
error opening the file: 13 (Permission denied)

The reason is that NetworkManager 1.56 reads the certificate and key as the user activating the connection, not as root. System directories under /etc/strongswan/ipsec.d/ are mode 700 and owned by root, so a normal user cannot read them, and the connection fails before it even starts. Running sudo nmcli con up does not help either, because NetworkManager still resolves the requesting user from the D-Bus session.

The fix that works is to keep the certificates somewhere your own user can read, and the conventional spot is ~/.cert/. It already carries the right SELinux type (home_cert_t), so it is purpose-built for exactly this.

install -m 0755 -Z -d ~/.cert/myvpn
install -m 0644 -Z "/path/to/downloaded/azure/vpnclientconfiguration/Generic/VpnServerRoot.cer_0" ~/.cert/myvpn/VpnServerRoot.cer
install -m 0644 -Z "/path/to/generated/azure/vpn/client/Cert.pem" ~/.cert/myvpn/client-cert.pem
install -m 0600 -Z "/path/to/generated/azure/vpn/client/Key.pem" ~/.cert/myvpn/client-key.pem
restorecon -RvF ~/.cert        # confirm the SELinux context; check with: ls -Z ~/.cert/myvpn

Then create the connection through GNOME Settings → Network → VPN → IPsec/IKEv2 (strongswan), pointing the CA certificate, the user certificate, and the private key at the three files above, and enabling “Request an inner IP address”.

Blocker 1: SHA-1 crypto policy

Azure’s VPN gateway certificate is signed with SHA-1 by Microsoft’s CA. Fedora’s default crypto policy disables SHA-1 signature verification, and charon-nm validates that gateway certificate through OpenSSL, which consults the system-wide crypto policy. Until SHA-1 is re-enabled, authentication against the gateway fails.

sudo update-crypto-policies --set DEFAULT:SHA1     # check with: update-crypto-policies --show

Note that only Azure’s server side uses SHA-1. Your own client certificate can be SHA-256.

Blocker 2: the temporary cert file (SELinux plus directory permissions)

With the certs readable and SHA-1 allowed, the connection still failed:

Error: Connection activation failed: No valid secrets

and in the journal:

plugin NeedSecrets request #1 failed: GDBus.Error:...InvalidProperty:
Failure creating the temporary file

Here is what is happening. NetworkManager 1.56 reads your ~/.cert/ files as your user, then writes short-lived copies into /run/NetworkManager/cert/ and hands those temporary paths to charon-nm (strongswan’s NetworkManager backend), which reads them and brings up the IPsec tunnel. The temporary files are removed when the VPN disconnects. This copy-as-user behavior appears to be the hardening added for CVE-2025-9615, which is named in both the NetworkManager and strongswan package changelogs on Fedora.

Two things break that copy step, and they are on different layers:

1. Directory permissions (DAC). The /run/NetworkManager/cert/ directory gets created with mode 0600, which has no execute bit. NetworkManager itself can still use it because it runs with full capabilities, but charon-nm drops its Linux capabilities at startup (you can see dropped capabilities, running as uid 0, gid 0 in its log). Without CAP_DAC_OVERRIDE, even uid 0 cannot enter a directory that lacks the execute bit, so creating the temp file fails.

2. SELinux (MAC). charon-nm runs in the ipsec_t domain, and the cert directory is typed NetworkManager_var_run_t. The shipped policy does not allow that combination, so even after the permission issue is solved, SELinux blocks it. With dontaudit rules disabled, the denial shows up clearly:

avc: denied { write } for comm="charon-nm" name="cert" dev="tmpfs"
scontext=system_u:system_r:ipsec_t:s0
tcontext=system_u:object_r:NetworkManager_var_run_t:s0
tclass=dir permissive=0

Both layers have to be satisfied. Fixing only one still fails.

Fix for blocker 2

First, generate a small SELinux policy module from the real denials. Going permissive briefly and disabling dontaudit lets you capture the complete set in one pass:

sudo setenforce 0
sudo semodule -DB
sudo mkdir -p /run/NetworkManager/cert && sudo chmod 700 /run/NetworkManager/cert
nmcli con up myvpn
nmcli con down myvpn
sudo ausearch -m avc -ts recent --raw | grep 'ipsec_t' | audit2allow -M allow-strongswan-charon-nm-vpn-certs

Review what it will allow before installing:

cat allow-strongswan-charon-nm-vpn-certs.te

On my system it came out tightly scoped to just this interaction:

allow NetworkManager_t ipsec_t:process { noatsecure rlimitinh siginh };
allow ipsec_t NetworkManager_var_run_t:dir { add_name remove_name write };
allow ipsec_t NetworkManager_var_run_t:file { create getattr open read rename unlink write };

Install it, re-enable dontaudit, and go back to enforcing:

sudo semodule -i allow-strongswan-charon-nm-vpn-certs.pp
sudo semodule -B
sudo setenforce 1

Finally, make the directory permission fix survive reboots. /run is tmpfs, so it is recreated on every boot. A tmpfiles.d entry creates the directory with the right mode early, before the VPN backend needs it:

echo 'd /run/NetworkManager/cert 0700 root root -' | sudo tee /etc/tmpfiles.d/nm-cert-dir.conf

Result

After both blockers are addressed, the VPN connects with SELinux enforcing:

nmcli con up myvpn

A caveat on blocker 2, and a request

I want to be upfront that I am not fully certain this is the correct root-cause fix for the second blocker, only that it is consistent with what I observed and that it gets me connected reliably. The reasoning (the copy-as-user step, the mode 0600 directory, charon-nm dropping capabilities, and the SELinux type mismatch) is pieced together from the package changelogs, the daemon logs, and the AVC denials, not from reading the source.

One thing worth being precise about: the two layers are not interchangeable, and fixing only the permissions does not remove the need for the SELinux rule. SELinux type enforcement is independent of Linux capabilities and of file mode. CAP_DAC_OVERRIDE only bypasses the kernel’s owner/group/mode check; the SELinux decision is made separately, purely on the process domain and the target type. So a process in ipsec_t writing to NetworkManager_var_run_t is denied even with full capabilities and even if the directory were 0700 or 0777. In other words, simply having charon-nm create the directory before it drops capabilities, or having NetworkManager create it as 0700, would still leave the SELinux denial in place. As long as charon-nm (ipsec_t) is the process creating the file inside NetworkManager’s runtime directory, a policy rule is required.

That points at what the real root cause probably is. There is one change that would make both workarounds unnecessary at once: having NetworkManager itself (the NetworkManager_t daemon) create the temporary file, rather than charon-nm doing it. NetworkManager runs with full capabilities, so mode 0600 would not block it, and it is already allowed to write to its own NetworkManager_var_run_t directory, so SELinux would not block it either. The fact that charon-nm calls this copy step in its own deprivileged ipsec_t process suggests the hardening from CVE-2025-9615 was meant to run inside the NetworkManager daemon, and the strongswan plugin is invoking it from the wrong context. I have not confirmed this against the source, so treat it as a hypothesis.

If anyone closer to the NetworkManager or strongswan internals can confirm where this should properly be fixed (in strongswan’s charon-nm, in NetworkManager, or as a new rule in Fedora’s selinux-policy), or knows a cleaner approach that avoids a local policy module, I would genuinely appreciate the correction.