An Azure service that enables the connection of on-premises networks to Azure through site-to-site virtual private networks.
My solution
Thanks Vallepu, your pointers got me looking in the right direction. SELinux was indeed part of the problem, but it turned out there were actually two independent blockers stacked on top of each other, and the system-wide cert placement suggestion did not work on current Fedora. 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.
and “up-vote” wherever the information provided helps you, **this can be beneficial to other community members.