kanidm/docs/radius.html

358 lines
21 KiB
HTML
Raw Normal View History

<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>RADIUS - Kanidm Administration</title>
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="intro.html"><strong aria-hidden="true">1.</strong> Introduction to Kanidm</a></li><li class="chapter-item expanded "><a href="installing_the_server.html"><strong aria-hidden="true">2.</strong> Installing the Server</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="installing_the_server.html"><strong aria-hidden="true">2.1.</strong> Server Configuration</a></li><li class="chapter-item expanded "><a href="security_hardening.html"><strong aria-hidden="true">2.2.</strong> Security Hardening</a></li></ol></li><li class="chapter-item expanded "><a href="client_tools.html"><strong aria-hidden="true">3.</strong> Client Tools</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="installing_client_tools.html"><strong aria-hidden="true">3.1.</strong> Installing client tools</a></li></ol></li><li class="chapter-item expanded "><a href="accounts_and_groups.html"><strong aria-hidden="true">4.</strong> Accounts and Groups</a></li><li class="chapter-item expanded "><a href="administrivia.html"><strong aria-hidden="true">5.</strong> Administrative Tasks</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="monitoring.html"><strong aria-hidden="true">5.1.</strong> Monitoring the platform</a></li><li class="chapter-item expanded "><a href="password_quality.html"><strong aria-hidden="true">5.2.</strong> Password Quality and Badlisting</a></li><li class="chapter-item expanded "><a href="posix_accounts.html"><strong aria-hidden="true">5.3.</strong> POSIX Accounts and Groups</a></li><li class="chapter-item expanded "><a href="ssh_key_dist.html"><strong aria-hidden="true">5.4.</strong> SSH Key Distribution</a></li><li class="chapter-item expanded "><a href="recycle_bin.html"><strong aria-hidden="true">5.5.</strong> The Recycle Bin</a></li></ol></li><li class="chapter-item expanded "><a href="ldap.html"><strong aria-hidden="true">6.</strong> LDAP</a></li><li class="chapter-item expanded "><a href="pam_and_nsswitch.html"><strong aria-hidden="true">7.</strong> PAM and nsswitch</a></li><li class="chapter-item expanded "><a href="radius.html" class="active"><strong aria-hidden="true">8.</strong> RADIUS</a></li><li class="chapter-item expanded "><a href="why_tls.html"><strong aria-hidden="true">9.</strong> Why TLS?</a></li><li class="chapter-item expanded "><a href="DEVELOPER_README.html"><strong aria-hidden="true">10.</strong> Developer Guide</a></li></ol> </div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">Kanidm Administration</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="radius"><a class="header" href="#radius">RADIUS</a></h1>
<p>RADIUS is a network protocol that is commonly used to allow wifi devices or
VPN's to authenticate users to a network boundary. While it should not be a
sole point of trust/authentication to an identity, it's still an important
control for improving barriers to attackers access to network resources.</p>
<p>Kanidm has a philosophy that each account can have multiple credentials which
are related to their devices and limited to specific resources. RADIUS is
no exception and has a separate credential for each account to use for
RADIUS access.</p>
<h2 id="disclaimer"><a class="header" href="#disclaimer">Disclaimer</a></h2>
<p>It's worth noting some disclaimers about Kanidm's RADIUS integration here</p>
<h3 id="one-credential---one-account"><a class="header" href="#one-credential---one-account">One Credential - One Account</a></h3>
<p>Kanidm normally attempts to have credentials for each <em>device</em> and <em>application</em>
rather than the legacy model of one to one.</p>
<p>The RADIUS protocol is only able to attest a <em>single</em> credential in an authentication
attempt, which limits us to storing a single RADIUS credential per account. However
despite this limitation, it still greatly improves the situation by isolating the
RADIUS credential from the primary or application credentials of the account. This
solves many common security concerns around credential loss or disclosure
and prevents rogue devices from locking out accounts as they attempt to
authenticate to wifi with expired credentials.</p>
<h3 id="cleartext-credential-storage"><a class="header" href="#cleartext-credential-storage">Cleartext Credential Storage</a></h3>
<p>RADIUS offers many different types of tunnels and authentication mechanisms.
However, most client devices &quot;out of the box&quot; only attempt a single type when you select
a WPA2-Enterprise network: MSCHAPv2 with PEAP. This is a challenge-response protocol
that requires cleartext or NTLM credentials.</p>
<p>As MSCHAPv2 with PEAP is the only practical, universal RADIUS type supported
on all devices with &quot;minimal&quot; configuration, we consider it imperative
that it MUST be supported as the default. Esoteric RADIUS types can be used
as well, but this is up to administrators to test and configure.</p>
<p>Due to this requirement, we must store the RADIUS material as cleartext or
NTLM hashes. It would be silly to think that NTLM is &quot;secure&quot; as it's md4
which is only an illusion of security.</p>
<p>This means Kanidm stores RADIUS credentials in the database as cleartext.</p>
<p>We believe this is a reasonable decision and is a low risk to security as:</p>
<ul>
<li>The access controls around RADIUS secrets by default are &quot;strong&quot;, limited
to only self-account read and RADIUS-server read.</li>
<li>As RADIUS credentials are separate from the primary account credentials and have
no other rights, their disclosure is not going to lead to a full account compromise.</li>
<li>Having the credentials in cleartext allows a better user experience as clients
can view the credentials at any time to enrol further devices.</li>
</ul>
<h2 id="account-credential-configuration"><a class="header" href="#account-credential-configuration">Account Credential Configuration</a></h2>
<p>For an account to use RADIUS they must first generate a RADIUS secret unique to
that account. By default, all accounts can self-create this secret.</p>
<pre><code>kanidm account radius generate_secret --name william william
kanidm account radius show_secret --name william william
</code></pre>
<h2 id="account-group-configuration"><a class="header" href="#account-group-configuration">Account group configuration</a></h2>
<p>Kanidm enforces that accounts which can authenticate to RADIUS must be a member
of an allowed group. This allows you to define which users or groups may use
wifi or VPN infrastructure and gives a path for &quot;revoking&quot; access to the resources
through group management. The key point of this is that service accounts should
not be part of this group.</p>
<pre><code>kanidm group create --name idm_admin radius_access_allowed
kanidm group add_members --name idm_admin radius_access_allowed william
</code></pre>
<h2 id="radius-server-service-account"><a class="header" href="#radius-server-service-account">RADIUS Server Service Account</a></h2>
<p>To read these secrets, the RADIUS server requires an account with the
correct privileges. This can be created and assigned through the group
&quot;idm_radius_servers&quot; which is provided by default.</p>
<pre><code>kanidm account create --name admin radius_service_account &quot;Radius Service Account&quot;
kanidm group add_members --name admin idm_radius_servers radius_service_account
kanidm account credential reset_credential --name admin radius_service_account
</code></pre>
<h2 id="deploying-a-radius-container"><a class="header" href="#deploying-a-radius-container">Deploying a RADIUS Container</a></h2>
<p>We provide a RADIUS container that has all the needed integrations.
This container requires some cryptographic material, laid out in a volume like so:</p>
<pre><code>data
data/ca.pem # This is the kanidm ca.pem
data/config.ini # This is the kanidm-radius configuration.
data/certs
data/certs/dh # openssl dhparam -out ./dh 2048
data/certs/key.pem # These are the radius ca/cert/key
data/certs/cert.pem
data/certs/ca.pem
</code></pre>
<p>The config.ini has the following template:</p>
<pre><code>[kanidm_client]
url = # URL to the kanidm server
strict = false # Strict CA verification
ca = /data/ca.pem # Path to the kanidm ca
user = # Username of the RADIUS service account
secret = # Generated secret for the service account
; default VLANs for groups that don't specify one.
[DEFAULT]
vlan = 1
; [group.test] # group.&lt;name&gt; will have these options applied
; vlan =
[radiusd]
ca = # Path to the radius server's CA
key = # Path to the radius servers key
cert = # Path to the radius servers cert
dh = # Path to the radius servers dh params
required_group = # Name of a kanidm group which you must be
# A member of to use radius.
cache_path = # A path to an area where cached user records can be stored.
# If in doubt, use /dev/shm/kanidmradiusd
; [client.localhost] # client.&lt;nas name&gt; configures wifi/vpn consumers
; ipaddr = # ipv4 or ipv6 address of the NAS
; secret = # Shared secret
</code></pre>
<p>A fully configured example is:</p>
<pre><code>[kanidm_client]
; be sure to check the listening port is correct, it's the docker internal port
; not the external one if these containers are on the same host.
url = https://&lt;kanidmd container name or ip&gt;:8443
strict = true # Adjust this if you have CA validation issues
ca = /data/ca.crt
user = radius_service_account
secret = # The generated password from above
; default vlans for groups that don't specify one.
[DEFAULT]
vlan = 1
[group.network_admins]
vlan = 10
[radiusd]
ca = /data/certs/ca.pem
key = /data/certs/key.pem
cert = /data/certs/cert.pem
dh = /data/certs/dh
required_group = radius_access_allowed
cache_path = /dev/shm/kanidmradiusd
[client.localhost]
ipaddr = 127.0.0.1
secret = testing123
[client.docker]
ipaddr = 172.17.0.0/16
secret = testing123
</code></pre>
<p>You can then run the container with:</p>
<pre><code>docker run --name radiusd -v ...:/data kanidm/radius:latest
</code></pre>
<p>Authentication can be tested through the client.localhost NAS configuration with:</p>
<pre><code>docker exec -i -t radiusd radtest &lt;username&gt; badpassword 127.0.0.1 10 testing123
docker exec -i -t radiusd radtest &lt;username&gt; &lt;radius show_secret value here&gt; 127.0.0.1 10 testing123
</code></pre>
<p>Finally, to expose this to a wifi infrastructure, add your NAS in <code>config.ini</code>:</p>
<pre><code>[client.access_point]
ipaddr = &lt;some ipadd&gt;
secret = &lt;random value&gt;
</code></pre>
<p>And re-create/run your docker instance with <code>-p 1812:1812 -p 1812:1812/udp</code> ...</p>
<p>If you have any issues, check the logs from the radius output they tend to indicate the cause
of the problem. To increase the logging you can re-run your environment with debug enabled:</p>
<pre><code>docker rm radiusd
docker run --name radiusd -e DEBUG=True -i -t -v ...:/data kanidm/radius:latest
</code></pre>
<p>Note the radius container <em>is</em> configured to provide Tunnel-Private-Group-ID so if you wish to use
wifi assigned VLANs on your infrastructure, you can assign these by groups in the config.ini as
shown in the above examples.</p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="pam_and_nsswitch.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="why_tls.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="pam_and_nsswitch.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next" href="why_tls.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</nav>
</div>
<script type="text/javascript">
window.playground_copyable = true;
</script>
<script src="elasticlunr.min.js" type="text/javascript" charset="utf-8"></script>
<script src="mark.min.js" type="text/javascript" charset="utf-8"></script>
<script src="searcher.js" type="text/javascript" charset="utf-8"></script>
<script src="clipboard.min.js" type="text/javascript" charset="utf-8"></script>
<script src="highlight.js" type="text/javascript" charset="utf-8"></script>
<script src="book.js" type="text/javascript" charset="utf-8"></script>
<!-- Custom JS scripts -->
</body>
</html>