mirror of
https://github.com/kanidm/kanidm.git
synced 2025-02-23 04:27:02 +01:00
Improve radius to support eap-tls with ca-dir (#957)
* Improve radius to support eap-tls with ca-dir, and also mschap
This commit is contained in:
parent
845cabb206
commit
7f7e882f24
|
@ -2,11 +2,10 @@ FROM opensuse/tumbleweed:latest
|
|||
|
||||
EXPOSE 1812 1813
|
||||
|
||||
# TODO: remove this once the freeradius python fix has been rolled into tumbleweed main
|
||||
RUN zypper ar -f obs://home:firstyear:branches:network home:firstyear:branches:network
|
||||
|
||||
RUN zypper --gpg-auto-import-keys refresh --force
|
||||
RUN zypper install -y \
|
||||
# These all need to be on one line else the rpm cache ends
|
||||
# up in the layers.
|
||||
RUN zypper refresh --force && \
|
||||
zypper install -y \
|
||||
freeradius-client \
|
||||
freeradius-server \
|
||||
freeradius-server-python3 \
|
||||
|
@ -18,8 +17,9 @@ RUN zypper install -y \
|
|||
timezone \
|
||||
iproute2 \
|
||||
iputils \
|
||||
curl
|
||||
RUN zypper clean
|
||||
openssl \
|
||||
curl && \
|
||||
zypper clean
|
||||
|
||||
ADD kanidm_rlm_python/mods-available/ /etc/raddb/mods-available/
|
||||
COPY kanidm_rlm_python/sites-available/ /etc/raddb/sites-available/
|
||||
|
@ -29,6 +29,7 @@ WORKDIR /etc/raddb
|
|||
|
||||
# Enable the python and cache module.
|
||||
RUN ln -s /etc/raddb/mods-available/python3 /etc/raddb/mods-enabled/python3
|
||||
RUN ln -s /etc/raddb/sites-available/check-eap-tls /etc/raddb/sites-enabled/check-eap-tls
|
||||
|
||||
# disable auth via methods we don't support!
|
||||
RUN rm /etc/raddb/mods-available/sql
|
||||
|
@ -54,6 +55,7 @@ RUN python3 -m pip install --no-cache-dir --no-warn-script-location /pkg/kanidmr
|
|||
RUN rm -rf /pkg/*
|
||||
|
||||
USER radiusd
|
||||
ENV LD_PRELOAD=/usr/lib64/libpython3.so
|
||||
|
||||
COPY kanidm_rlm_python/entrypoint.py /entrypoint.py
|
||||
CMD [ "/usr/bin/python3", "/entrypoint.py" ]
|
||||
|
|
|
@ -21,6 +21,7 @@ CONFIG_FILE_PATH = "/data/kanidm"
|
|||
|
||||
CERT_SERVER_DEST = "/etc/raddb/certs/server.pem"
|
||||
CERT_CA_DEST = "/etc/raddb/certs/ca.pem"
|
||||
CERT_CA_DIR = "/etc/raddb/certs/"
|
||||
CERT_DH_DEST = "/etc/raddb/certs/dh.pem"
|
||||
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -60,6 +61,20 @@ def setup_certs(
|
|||
print(f"Copying {cert_ca} to {CERT_CA_DEST}")
|
||||
shutil.copyfile(cert_ca, CERT_CA_DEST)
|
||||
|
||||
# This dir can also contain crls!
|
||||
if kanidm_config_object.radius_ca_dir:
|
||||
cert_ca_dir = Path(kanidm_config_object.radius_ca_dir).expanduser().resolve()
|
||||
if not cert_ca_dir.exists():
|
||||
print(f"Failed to find radiusd ca dir ({cert_ca_dir}), quitting!", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if cert_ca_dir != CERT_CA_DIR:
|
||||
print(f"Copying {cert_ca_dir} to {CERT_CA_DIR}")
|
||||
shutil.copytree(cert_ca_dir, CERT_CA_DIR, dirs_exist_ok=True)
|
||||
|
||||
# Setup the ca-dir correctly now. We do this before we add server.pem so that it's
|
||||
# not hashed as a ca.
|
||||
subprocess.check_call(["openssl", "rehash", CERT_CA_DIR])
|
||||
|
||||
# let's put some dhparams in place
|
||||
if kanidm_config_object.radius_dh_path is not None:
|
||||
cert_dh = Path(kanidm_config_object.radius_dh_path).expanduser().resolve()
|
||||
|
|
|
@ -138,17 +138,25 @@ def authorize(
|
|||
|
||||
dargs = dict(args)
|
||||
logging.error("Authorise: %s", json.dumps(dargs))
|
||||
cn_uuid = dargs.get('TLS-Client-Cert-Common-Name', None)
|
||||
username = dargs['User-Name']
|
||||
|
||||
if cn_uuid is not None:
|
||||
logging.debug("Using TLS-Client-Cert-Common-Name")
|
||||
user_id = cn_uuid
|
||||
else:
|
||||
logging.debug("Using User-Name")
|
||||
user_id = username
|
||||
|
||||
tok = None
|
||||
try:
|
||||
loop = asyncio.get_event_loop()
|
||||
tok = loop.run_until_complete(_get_radius_token(username=username))
|
||||
tok = loop.run_until_complete(_get_radius_token(username=user_id))
|
||||
logging.debug("radius_token: %s", tok)
|
||||
except NoMatchingEntries as error_message:
|
||||
logging.info(
|
||||
'kanidm RLM_MODULE_NOTFOUND after NoMatchingEntries for user %s: %s',
|
||||
username,
|
||||
'kanidm RLM_MODULE_NOTFOUND after NoMatchingEntries for user_id %s: %s',
|
||||
user_id,
|
||||
error_message,
|
||||
)
|
||||
return radiusd.RLM_MODULE_NOTFOUND
|
||||
|
@ -158,14 +166,19 @@ def authorize(
|
|||
logging.info('kanidm RLM_MODULE_NOTFOUND due to no auth token')
|
||||
return radiusd.RLM_MODULE_NOTFOUND
|
||||
|
||||
# Get values out of the token
|
||||
name = tok["name"]
|
||||
secret = tok["secret"]
|
||||
uuid = tok["uuid"]
|
||||
|
||||
# Are they in the required group?
|
||||
req_sat = False
|
||||
for group in tok["groups"]:
|
||||
if group['name'] in kanidm_client.config.radius_required_groups:
|
||||
req_sat = True
|
||||
logging.info("User %s has a required group (%s)", username, group['name'])
|
||||
logging.info("User %s has a required group (%s)", name, group['name'])
|
||||
if req_sat is not True:
|
||||
logging.info("User %s doesn't have a group from the required list.", username)
|
||||
logging.info("User %s doesn't have a group from the required list.", name)
|
||||
return radiusd.RLM_MODULE_NOTFOUND
|
||||
|
||||
# look up them in config for group vlan if possible.
|
||||
|
@ -179,14 +192,11 @@ def authorize(
|
|||
logging.info("Invalid uservlan of 0")
|
||||
|
||||
|
||||
logging.info("selected vlan %s:%s", username, uservlan)
|
||||
# Convert the tok groups to groups.
|
||||
name = tok["name"]
|
||||
secret = tok["secret"]
|
||||
logging.info("selected vlan %s:%s", name, uservlan)
|
||||
|
||||
reply = (
|
||||
('User-Name', str(name)),
|
||||
('Reply-Message', 'Welcome'),
|
||||
('Reply-Message', f"Kanidm-Uuid: {uuid}"),
|
||||
('Tunnel-Type', '13'),
|
||||
('Tunnel-Medium-Type', '6'),
|
||||
('Tunnel-Private-Group-ID', str(uservlan)),
|
||||
|
@ -195,5 +205,5 @@ def authorize(
|
|||
('Cleartext-Password', str(secret)),
|
||||
)
|
||||
|
||||
logging.info("OK! Returning details to radius for %s ...", username)
|
||||
logging.info("OK! Returning details to radius for %s ...", name)
|
||||
return (radiusd.RLM_MODULE_OK, reply, config_object)
|
||||
|
|
|
@ -195,7 +195,7 @@ eap {
|
|||
# In that case, this CA file should contain
|
||||
# *one* CA certificate.
|
||||
#
|
||||
ca_file = ${cadir}/ca.pem
|
||||
# ca_file = ${cadir}/ca.pem
|
||||
|
||||
# OpenSSL will automatically create certificate chains,
|
||||
# unless we tell it to not do that. The problem is that
|
||||
|
@ -287,7 +287,7 @@ eap {
|
|||
|
||||
# Accept an expired Certificate Revocation List
|
||||
#
|
||||
# allow_expired_crl = no
|
||||
allow_expired_crl = no
|
||||
|
||||
#
|
||||
# If check_cert_issuer is set, the value will
|
||||
|
@ -456,7 +456,7 @@ eap {
|
|||
#
|
||||
# This feature REQUIRES "name" option be set above.
|
||||
#
|
||||
#persist_dir = "${logdir}/tlscache"
|
||||
persist_dir = "${logdir}/tlscache"
|
||||
}
|
||||
|
||||
#
|
||||
|
@ -595,7 +595,7 @@ eap {
|
|||
# virtual server has access to these attributes, and can
|
||||
# be used to accept or reject the request.
|
||||
#
|
||||
# virtual_server = check-eap-tls
|
||||
virtual_server = check-eap-tls
|
||||
}
|
||||
|
||||
|
||||
|
|
134
kanidm_rlm_python/sites-available/check-eap-tls
Normal file
134
kanidm_rlm_python/sites-available/check-eap-tls
Normal file
|
@ -0,0 +1,134 @@
|
|||
# This virtual server allows EAP-TLS to reject access requests
|
||||
# based on some attributes of the certificates involved.
|
||||
#
|
||||
# To use this virtual server, you must enable it in the tls
|
||||
# section of mods-enabled/eap as well as adding a link to this
|
||||
# file in sites-enabled/.
|
||||
#
|
||||
#
|
||||
# Value-pairs that are available for checking include:
|
||||
#
|
||||
# TLS-Client-Cert-Subject
|
||||
# TLS-Client-Cert-Issuer
|
||||
# TLS-Client-Cert-Common-Name
|
||||
# TLS-Client-Cert-Subject-Alt-Name-Email
|
||||
#
|
||||
# To see a full list of attributes, run the server in debug mode
|
||||
# with this virtual server configured, and look at the attributes
|
||||
# passed in to this virtual server.
|
||||
#
|
||||
#
|
||||
# This virtual server is also useful when using EAP-TLS as it is
|
||||
# only called once, just before the final Accept is about to be
|
||||
# returned from eap, whereas the outer authorize section is called
|
||||
# multiple times for each challenge / response. For this reason,
|
||||
# here may be a good location to put authentication logging, and
|
||||
# modules that check for further authorization, especially if they
|
||||
# hit external services such as sql or ldap.
|
||||
|
||||
|
||||
server check-eap-tls {
|
||||
|
||||
|
||||
# Authorize - this is the only section required.
|
||||
#
|
||||
# To accept the access request, set Auth-Type = Accept, otherwise
|
||||
# set it to Reject.
|
||||
|
||||
authorize {
|
||||
|
||||
#
|
||||
# By default, we just accept the request:
|
||||
#
|
||||
update config {
|
||||
&Auth-Type := Accept
|
||||
}
|
||||
|
||||
|
||||
#
|
||||
# Check the client certificate matches a string, and reject otherwise
|
||||
#
|
||||
|
||||
# if ("%{TLS-Client-Cert-Common-Name}" == 'client.example.com') {
|
||||
# update config {
|
||||
# &Auth-Type := Accept
|
||||
# }
|
||||
# }
|
||||
# else {
|
||||
# update config {
|
||||
# &Auth-Type := Reject
|
||||
# }
|
||||
# update reply {
|
||||
# &Reply-Message := "Your certificate is not valid."
|
||||
# }
|
||||
# }
|
||||
|
||||
|
||||
#
|
||||
# Check the client certificate common name against the supplied User-Name
|
||||
#
|
||||
# if (&User-Name == "host/%{TLS-Client-Cert-Common-Name}") {
|
||||
# update config {
|
||||
# &Auth-Type := Accept
|
||||
# }
|
||||
# }
|
||||
# else {
|
||||
# update config {
|
||||
# &Auth-Type := Reject
|
||||
# }
|
||||
# }
|
||||
|
||||
#
|
||||
# This is a convenient place to call LDAP, for example, when using
|
||||
# EAP-TLS, as it will only be called once, after all certificates as
|
||||
# part of the EAP-TLS challenge process have been verified.
|
||||
#
|
||||
# An example could be to use LDAP to check that the connecting host, as
|
||||
# well as presenting a valid certificate, is also in a group based on
|
||||
# the User-Name (assuming this contains the service principal name).
|
||||
# Settings such as the following could be used in the ldap module
|
||||
# configuration:
|
||||
#
|
||||
# basedn = "dc=example, dc=com"
|
||||
# filter = "(servicePrincipalName=%{User-Name})"
|
||||
# base_filter = "(objectClass=computer)"
|
||||
# groupname_attribute = cn
|
||||
# groupmembership_filter = "(&(objectClass=group)(member=%{control:Ldap-UserDn}))"
|
||||
|
||||
# ldap
|
||||
|
||||
# Now let's test membership of an LDAP group (the ldap bind user will
|
||||
# need permission to read this group membership):
|
||||
|
||||
# if (!(Ldap-Group == "Permitted-Laptops")) {
|
||||
# update config {
|
||||
# &Auth-Type := Reject
|
||||
# }
|
||||
# }
|
||||
|
||||
# or, to be more specific, you could use the group's full DN:
|
||||
# if (!(Ldap-Group == "CN=Permitted-Laptops,OU=Groups,DC=example,DC=org")) {
|
||||
|
||||
python3
|
||||
|
||||
#
|
||||
# This may be a better place to call the files modules when using
|
||||
# EAP-TLS, as it will only be called once, after the challenge-response
|
||||
# iteration has completed.
|
||||
#
|
||||
|
||||
# files
|
||||
|
||||
|
||||
#
|
||||
# Log all request attributes, plus TLS certificate details, to the
|
||||
# auth_log file. Again, this is just once per connection request, so
|
||||
# may be preferable than in the outer authorize section. It is
|
||||
# suggested that 'auth_log' also be in the outer post-auth and
|
||||
# Post-Auth REJECT sections to log reply packet details, too.
|
||||
#
|
||||
|
||||
auth_log
|
||||
|
||||
}
|
||||
}
|
|
@ -143,10 +143,11 @@ class KanidmClientConfig(BaseModel):
|
|||
username: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
radius_cert_path: str = "/etc/raddb/certs/cert.pem"
|
||||
radius_key_path: str = "/etc/raddb/certs/key.pem" # the signing key for radius TLS
|
||||
radius_dh_path: str = "/etc/raddb/certs/dh.pem" # the diffie-hellman output
|
||||
radius_ca_path: str = "/etc/raddb/certs/ca.pem" # the diffie-hellman output
|
||||
radius_cert_path: str = "/data/cert.pem"
|
||||
radius_key_path: str = "/data/key.pem" # the signing key for radius TLS
|
||||
radius_dh_path: str = "/data/dh.pem" # the diffie-hellman output
|
||||
radius_ca_path: Optional[str] = None
|
||||
radius_ca_dir: Optional[str] = None
|
||||
|
||||
radius_required_groups: List[str] = []
|
||||
radius_default_vlan: int = 1
|
||||
|
|
Loading…
Reference in a new issue