{"id":23,"date":"2026-10-01T01:33:36","date_gmt":"2026-10-01T01:33:36","guid":{"rendered":"https:\/\/hoho.live\/luda\/index.php\/2026\/10\/01\/why-freeradius-stays-between-strongswan-and-openldap\/"},"modified":"2026-10-04T09:48:20","modified_gmt":"2026-10-04T01:48:20","slug":"how-freeradius-makes-ldap-authentication-work-with-strongswan","status":"publish","type":"post","link":"https:\/\/hoho.live\/luda\/index.php\/2026\/10\/01\/how-freeradius-makes-ldap-authentication-work-with-strongswan\/","title":{"rendered":"How FreeRADIUS makes LDAP authentication work with StrongSwan"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\"><strong>Short answer:<\/strong> StrongSwan IKEv2 road-warriors (iOS, macOS, Windows) speak <strong>EAP-MSCHAPv2<\/strong>. OpenLDAP can only <strong>simple-bind<\/strong> (PAP). Those are not the same protocol, so StrongSwan cannot authenticate an LDAP password by itself. FreeRADIUS sits in the middle: it reads the user\u2019s <code>ntPassword<\/code> from LDAP and finishes MS-CHAP. That is the supported design, not a leftover workaround.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>What the VPN client actually sends<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">Native IKEv2 profiles on phones and laptops do not send a clear password to the gateway. They speak <strong>EAP-MSCHAPv2<\/strong>: a challenge-response. The gateway (or a RADIUS server behind it) must already know the NT hash, or be able to compute one, in order to check the response.<\/p>\n\n\n<p class=\"wp-block-paragraph\">That rules out a direct LDAP bind from StrongSwan. A bind needs the plaintext password. The client never hands it over.<\/p>\n\n\n<p class=\"wp-block-paragraph\">StrongSwan also has no plugin that does EAP-MSCHAPv2 against OpenLDAP. The road-warrior conn that uses the directory is <code>rightauth=eap-radius<\/code> (the <code>eap-radius<\/code> \/ charon plugin), not <code>eap-mschapv2<\/code> with an LDAP URL.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>What OpenLDAP can and cannot do<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">OpenLDAP is a directory. For password checks it can <strong>simple-bind<\/strong>: accept a DN and a plaintext password, or reject them. That is PAP. It cannot answer an MS-CHAP challenge.<\/p>\n\n\n<p class=\"wp-block-paragraph\">So these do not work for phone IKEv2 against OpenLDAP:<\/p>\n\n\n<ul class=\"wp-block-list\">\n<li>Pointing StrongSwan at LDAP and hoping it binds<\/li>\n<li>EAP-GTC or PAP on the client \u2014 the stock phone profile still sends MSCHAPv2<\/li>\n<li>Uncommenting FreeRADIUS <code>Auth-Type LDAP<\/code> and expecting EAP to start working<\/li>\n<\/ul>\n\n\n<p class=\"wp-block-paragraph\">The last one is a common false lead. FreeRADIUS\u2019s own inner-tunnel example says, next to the commented <code>Auth-Type LDAP<\/code> block: that stanza means \u201ccheck a plaintext password against the LDAP database,\u201d and <strong>EAP will not work<\/strong> because EAP does not supply a plaintext password. The comment is explicit: LDAP servers are databases, not authentication servers. FreeRADIUS is the authentication server.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>The working path<\/strong><\/h4>\n\n<ol class=\"wp-block-list\">\n<li>Client EAP-MSCHAPv2 \u2192 StrongSwan (<code>rightauth=eap-radius<\/code>)<\/li>\n<li>StrongSwan RADIUS plugin \u2192 FreeRADIUS<\/li>\n<li>FreeRADIUS LDAP module searches the user, maps <code>ntPassword<\/code> \u2192 <code>control:NT-Password<\/code>, and completes MS-CHAP<\/li>\n<\/ol>\n\n\n<p class=\"wp-block-paragraph\">On the LDAP side you need an NT hash (<code>ntPassword<\/code> or equivalent), not only <code>userPassword<\/code>. Samba \/ mail schemas often already store that hash when the same password is used for other services. An authorization attribute (we use <code>mailActivated<\/code>) is optional but useful so disabled accounts cannot dial in.<\/p>\n\n\n<p class=\"wp-block-paragraph\">Site-to-site IPsec is a different problem. Those conns are usually PSK or certificates and never touch RADIUS. A road-warrior conn that does <strong>not<\/strong> use the directory can stay on local <code>eap-mschapv2<\/code> secrets.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>Approaches that look simpler and fail<\/strong><\/h4>\n\n<figure class=\"wp-block-table\">\n<table>\n<thead>\n<tr>\n<th>Approach<\/th>\n<th>Why it fails if the VPN password is the LDAP password<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>StrongSwan LDAP bind \/ EAP-GTC \/ PAP<\/td>\n<td>The client sends MSCHAPv2, not a clear password<\/td>\n<\/tr>\n<tr>\n<td>Authelia, Keycloak, or OIDC in front of IKEv2<\/td>\n<td>Native VPN clients do not speak OIDC<\/td>\n<\/tr>\n<tr>\n<td>Drop RADIUS, keep MSCHAPv2 hashes only on the gateway<\/td>\n<td>Duplicates secrets; the VPN drifts from the directory<\/td>\n<\/tr>\n<tr>\n<td><code>Auth-Type LDAP<\/code> inside FreeRADIUS<\/td>\n<td>Needs plaintext; EAP does not provide it<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/figure>\n\n\n<p class=\"wp-block-paragraph\">SSO in front of the web admin sites is the right tool for browsers. It is the wrong tool for IKEv2.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>When you can retire FreeRADIUS<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">You can drop the RADIUS hop if you drop \u201ctype your directory password in the VPN profile\u201d:<\/p>\n\n\n<ul class=\"wp-block-list\">\n<li>Client certificates (<code>rightauth=pubkey<\/code> or EAP-TLS)<\/li>\n<li>WireGuard (or similar) device keys<\/li>\n<li>Local EAP-MSCHAPv2 secrets, if you accept that the VPN password is not the directory password<\/li>\n<\/ul>\n\n\n<p class=\"wp-block-paragraph\">Those are UX and enrollment changes, not a missing StrongSwan switch. Until you accept one of them, keep FreeRADIUS.<\/p>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n<h4 class=\"wp-block-heading\"><strong>Why this stays published<\/strong><\/h4>\n\n<p class=\"wp-block-paragraph\">We hit this on a homelab IKEv2 setup that shares mail passwords with OpenLDAP, and the same protocol mismatch shows up in production stacks. The useful search result is not \u201cdelete RADIUS\u201d and not \u201cbind LDAP from charon\u201d. It is: <strong>EAP-MSCHAPv2 + OpenLDAP needs an authentication server that can read NT hashes. FreeRADIUS is that server.<\/strong><\/p>\n\n\n<p class=\"wp-block-paragraph\">\u672c\u6587\u7531 HoHo \u8207 AI \u5354\u4f5c\u6574\u7406\uff0c\u6700\u5f8c\u66f4\u65b0\u65bc 2026 \u5e74 10 \u6708 1 \u65e5\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Short answer: StrongSwan IKEv2 road-warriors (iOS, macOS, Windows) speak EAP-MSCHAPv2. OpenLDAP can only simple-bind (PAP). Those are not the same protocol, so StrongSwan cannot authenticate an LDAP password by itself. FreeRADIUS sits in the middle: it reads the user\u2019s ntPassword from LDAP and finishes MS-CHAP. That is the supported&hellip;<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,8,9],"tags":[6,4,10,5,3],"class_list":["post-23","post","type-post","status-publish","format-standard","hentry","category-homelab","category-ipsec","category-ldap","tag-eap-mschapv2","tag-freeradius","tag-ikev2","tag-openldap","tag-strongswan"],"_links":{"self":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/23","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/comments?post=23"}],"version-history":[{"count":2,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/23\/revisions"}],"predecessor-version":[{"id":49,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/posts\/23\/revisions\/49"}],"wp:attachment":[{"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/media?parent=23"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/categories?post=23"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/hoho.live\/luda\/index.php\/wp-json\/wp\/v2\/tags?post=23"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}