From ae39d1f0509dadc4fbd2fd436b8f39f998bdcee1 Mon Sep 17 00:00:00 2001 From: William Brown Date: Feb 01 2019 00:48:39 +0000 Subject: Ticket 50159 - sssd and config display Bug Description: It can be very hard and confusing for an admin when they first start with LDAP to know how to configure clients both generic, ldapcli tools or sssd. Fix Description: Add a subcommand to dsidm that allows generation of example configs for ldap.conf, sssd.conf and generic display of parameters for LDAP clients. These have been tested to work on SUSE and Fedora, and they are well commented to advise admins to review and improve the configurations. https://pagure.io/389-ds-base/issue/50159 Author: William Brown Review by: ??? --- diff --git a/src/lib389/cli/dsidm b/src/lib389/cli/dsidm index 386bc11..9e5b998 100755 --- a/src/lib389/cli/dsidm +++ b/src/lib389/cli/dsidm @@ -22,6 +22,7 @@ from lib389.cli_idm import organizationalunit as cli_ou from lib389.cli_idm import group as cli_group from lib389.cli_idm import posixgroup as cli_posixgroup from lib389.cli_idm import user as cli_user +from lib389.cli_idm import client_config as cli_client_config from lib389.cli_base import connect_instance, disconnect_instance, setup_script_logger from lib389.cli_base.dsrc import dsrc_to_ldap, dsrc_arg_concat @@ -71,6 +72,7 @@ cli_init.create_parser(subparsers) cli_ou.create_parser(subparsers) cli_posixgroup.create_parser(subparsers) cli_user.create_parser(subparsers) +cli_client_config.create_parser(subparsers) argcomplete.autocomplete(parser) diff --git a/src/lib389/lib389/_mapped_object.py b/src/lib389/lib389/_mapped_object.py index bc0c8e6..eae3154 100644 --- a/src/lib389/lib389/_mapped_object.py +++ b/src/lib389/lib389/_mapped_object.py @@ -871,6 +871,16 @@ class DSLdapObjects(DSLogging): _gen_filter(_term_gen('objectclass'), self._objectclasses) ) + def _get_selector_filter(self, selector): + return _gen_and([ + self._get_objectclass_filter(), + _gen_or( + # This will yield all combinations of selector to filterattrs. + # This won't work with multiple values in selector (yet) + _gen_filter(self._filterattrs, _term_gen(selector)) + ), + ]) + def _entry_to_instance(self, dn=None, entry=None): # Normally this won't be used. But for say the plugin type where we # have "many" possible child types, this allows us to overload @@ -951,14 +961,7 @@ class DSLdapObjects(DSLogging): # Filter based on the objectclasses and the basedn # Based on the selector, we should filter on that too. # This will yield and & filter for objectClass with as many terms as needed. - filterstr=_gen_and([ - self._get_objectclass_filter(), - _gen_or( - # This will yield all combinations of selector to filterattrs. - # This won't work with multiple values in selector (yet) - _gen_filter(self._filterattrs, _term_gen(selector)) - ), - ]) + filterstr=self._get_selector_filter(selector) self._log.debug('_gen_selector filter = %s' % filterstr) return self._instance.search_ext_s( base=self._basedn, diff --git a/src/lib389/lib389/cli_idm/client_config.py b/src/lib389/lib389/cli_idm/client_config.py new file mode 100644 index 0000000..61c6fd5 --- /dev/null +++ b/src/lib389/lib389/cli_idm/client_config.py @@ -0,0 +1,290 @@ +# --- BEGIN COPYRIGHT BLOCK --- +# Copyright (C) 2019, William Brown +# All rights reserved. +# +# License: GPL (version 3 or any later version). +# See LICENSE for details. +# --- END COPYRIGHT BLOCK --- + +import argparse + +from lib389.idm.user import nsUserAccount, nsUserAccounts +from lib389.idm.group import Group, Groups +from lib389.plugins import MemberOfPlugin + +from lib389.utils import basedn_to_ldap_dns_uri + +SSSD_CONF_TEMPLATE = """ +# +# sssd.conf +# Generated by 389 Directory Server - dsidm +# +# For more details see man sssd.conf and man sssd-ldap +# Be sure to review the content of this file to ensure it is secure and correct +# in your environment. + +[domain/ldap] +# Uncomment this for more verbose logging. +# debug_level=3 + +# Cache hashes of user authentication for offline auth. +cache_credentials = True +id_provider = ldap +auth_provider = ldap +access_provider = ldap +chpass_provider = ldap +ldap_schema = {schema_type} +ldap_search_base = {basedn} +ldap_uri = {ldap_uri} +# If you have DNS SRV records, you can use the following instead. This derives +# from your ldap_search_base. +# ldap_uri = _srv_ + +ldap_tls_reqcert = demand +# To use cacert dir, place *.crt files in this path then run: +# /usr/bin/c_rehash /etc/openldap/certs +ldap_tls_cacertdir = /etc/openldap/certs + +# Path to the cacert +# ldap_tls_cacert = /etc/openldap/certs/ca.crt + +# Only users who match this filter can login and authorise to this machine. Note +# that users who do NOT match, will still have their uid/gid resolve, but they +# can't login. +ldap_access_filter = {ldap_access_filter} + +enumerate = false +access_provider = ldap +ldap_user_member_of = memberof +ldap_user_gecos = cn +ldap_user_uuid = nsUniqueId +ldap_group_uuid = nsUniqueId +# This is really important as it allows SSSD to respect nsAccountLock +ldap_account_expire_policy = rhds +ldap_access_order = filter, expire +# Setup for ssh keys +# Inside /etc/ssh/sshd_config add the lines: +# AuthorizedKeysCommand /usr/bin/sss_ssh_authorizedkeys +# AuthorizedKeysCommandUser nobody +# You can test with the command: sss_ssh_authorizedkeys +ldap_user_ssh_public_key = nsSshPublicKey + +# This prevents an issue where the Directory is recursively walked on group +# and user look ups. It makes the client faster and more responsive in almost +# every scenario. +ignore_group_members = False + +[sssd] +services = nss, pam, ssh, sudo +config_file_version = 2 + +domains = ldap +[nss] +homedir_substring = /home + +""" + +def sssd_conf(inst, basedn, log, args): + + schema_type = "rfc2307" + try: + mo_plugin = MemberOfPlugin(inst) + if mo_plugin.status(): + schema_type = "rfc2307bis" + except: + schema_type = "unknown - likely access denied to memberof plugin config" + + ldap_access_filter = None + if args.allowed_group: + groups = Groups(inst, basedn) + g_access = groups.get(args.allowed_group) + ldap_access_filter = '(memberOf=%s)' % g_access.dn + + # Print a customised sssd.config. + print(SSSD_CONF_TEMPLATE.format( + basedn=basedn, + schema_type=schema_type, + ldap_uri=inst.ldapuri, + ldap_access_filter=ldap_access_filter, + )) + +LDAP_CONF_TEMPLATE = """ +# +# OpenLDAP client configuration +# Generated by 389 Directory Server - dsidm +# + +# See ldap.conf(5) for details +# This file should be world readable but not world writable. + +BASE {basedn} +# Remember to check this: you can have multiple uris on this line. You may have +# multiple servers or load balancers in your environment. +URI {ldap_uri} +# If you have DNS SRV records you can use: +# URI {ldap_dns_uri} + +DEREF never +# To use cacert dir, place *.crt files in this path then run: +# /usr/bin/c_rehash /etc/openldap/certs +TLS_CACERTDIR /etc/openldap/certs +# TLS_CACERT /etc/openldap/certs/ca.crt + +""" + +def ldap_conf(inst, basedn, log, args): + # Print a customised ldap.conf or ldaprc + print(LDAP_CONF_TEMPLATE.format( + basedn=basedn, + ldap_uri=inst.ldapuri, + ldap_dns_uri=basedn_to_ldap_dns_uri(basedn), + )) + +DISPLAY_TEMPLATE = """ +# This is a generic list of LDAP client configuration parameters you may require +# for connecting a client to this server. Some of them may or may not apply +# to your application, so consult your application documentation for further +# assistance. +# +# This program makes a number of assumptions about your data and configuration +# which may not be correct. Be sure to check these values for your situation. + +; ldap uri +; This is the uri of the server you will connect to and authenticate to. It +; must be a valid subjectAltName in the presented TLS certificate. Note that this +; is not an exhaustive list of your LDAP servers, and other applications in your +; network like load balancers may affect this. This is just what we derive from +; your current connection. +ldap_uri = {ldap_uri} + +; ldap dns discovery uri +; In some environments, you may have DNS SRV records such as +; "_ldap._tcp.". If these are present in your dns server, you can +; use the following uri. +ldap_uri = {ldap_dns_uri} + +; ca_cert +; To correctly use TLS, you require the valid CA cert that issued your LDAP TLS +; certificates. Sometimes a copy of this may be in your server instance as +ca_cert = /etc/dirsrv/slapd-/ca.crt +; However that's not guaranteed. You can show the certs from the LDAP server +; by sshing to the server and running: +certutil -L -d /etc/dirsrv/slapd-/ +; If you can identify the CA certificate name, you can then view it with: +certutil -L -n -a -d /etc/dirsrv/slapd-/ +; This should be a pem file you can use in your application's CA. +; Some applications don't require a ca certificate parameter, and will use the +; ca certificate from /etc/openldap/ldap.conf. You should configure ldap.conf +; in these cases. See the 'client_config ldap.conf' command in dsidm. + +; basedn +; The basedn is the root suffix where all searches will originate from for +; LDAP objects. +basedn = {basedn} + +; schema_type +; LDAP servers have different ways to structure their objects and group +; relationships. Legacy servers will use rfc2307, where as modern servers will +; use rfc2307bis (requires MemberOf plugin to be enabled). This is the schema +; setting of your directory based on your running configuration (if we can +; detect it). +schema_type = {schema_type} + +; user/account basedn +; Some applications may optionally use a user/account basedn to limit searches +; in the directory. This can be for performance or security reasons. Generally +; you shouldn't need this, preferring to use groups and filters for access +; control. +user_basedn = {user_basedn} + +; user filter +; This is an ldap filter that will return only user objects. Additionally some +; applications will template into the filter (similar to sql statements) or they +; will generate the filter based on attributes. We list a number of possible +; filters you might use, but you should customise this for your application. +; +; If you are using rfc2307bis, you can use this filter to provide authorisation +; support by adding filters such as: (memberOf=) +user_filter = {user_filter} +user_filter = {user_id_filter} + +; group basedn +; Some applications may optionnaly use a group basedn to limit searches in the +; directory. This can be for performance or security reasons. Generally you +; shouldn't need this, preferring to use groups and filters for access control. +group_basedn = {group_basedn} + +; group filter +; This is an ldap filter that will return only group objects. Additionally +; some applications will template into the filter (similar to sql statements) +; or they will generate the filter base on attributes. We list a number of +; possible filters you might use, but you should customise this for your +; application. +group_filter = {group_filter} +group_filter = {group_id_filter} + +; attribute mappings +; Due to the variety of schemas and attribute mappings in LDAP, there are +; different representations of attributes and values. This is a guess at +; the mappings that exist in your server, and what attributes you should +; configure and use. +unique id = {uuid_attr} +user rdn = {user_rdn} +user identifier = {user_rdn} +group rdn = {group_rdn} +group member attribute = {group_member} + +""" + + +def display(inst, basedn, log, args): + + users = nsUserAccounts(inst, basedn) + groups = Groups(inst, basedn) + + schema_type = "rfc2307" + try: + mo_plugin = MemberOfPlugin(inst) + if mo_plugin.status(): + schema_type = "rfc2307bis" + except: + schema_type = "unknown - likely access denied to memberof plugin config" + + # Get required information + print(DISPLAY_TEMPLATE.format( + ldap_uri=inst.ldapuri, + ldap_dns_uri=basedn_to_ldap_dns_uri(basedn), + basedn=basedn, + schema_type=schema_type, + user_basedn=users._basedn, + user_filter=users._get_objectclass_filter(), + user_id_filter=users._get_selector_filter(''), + group_basedn=groups._basedn, + group_filter=groups._get_objectclass_filter(), + group_id_filter=groups._get_selector_filter(''), + uuid_attr='nsUniqueId', + user_rdn=users._filterattrs[0], + group_rdn=groups._filterattrs[0], + group_member='member', + )) + +def create_parser(subparsers): + client_config_parser = subparsers.add_parser('client_config', + help="Display and generate client example configs for this LDAP server") + + subcommands = client_config_parser.add_subparsers(help='action') + + sssd_conf_parser = subcommands.add_parser('sssd.conf', + help="Generate a SSSD configuration for this LDAP server") + # Allowed login groups for filter? + sssd_conf_parser.add_argument('allowed_group', nargs='?', help="The name of the group allowed access to this system") + sssd_conf_parser.set_defaults(func=sssd_conf) + + ldap_conf_parser = subcommands.add_parser('ldap.conf', + help="Generate an OpenLDAP ldap.conf configuration for this LDAP server") + ldap_conf_parser.set_defaults(func=ldap_conf) + + display_parser = subcommands.add_parser('display', + help="Display generic application parameters for LDAP connection") + display_parser.set_defaults(func=display) + diff --git a/src/lib389/lib389/utils.py b/src/lib389/lib389/utils.py index 0b90da2..987176b 100644 --- a/src/lib389/lib389/utils.py +++ b/src/lib389/lib389/utils.py @@ -1185,4 +1185,9 @@ def get_user_is_ds_owner(): return True return False +def basedn_to_ldap_dns_uri(basedn): + # ldap:///dc%3Dexample%2Cdc%3Dcom + return "ldaps:///" + basedn.replace("=", "%3D").replace(",", "%2C") + +