diff --git a/install/share/bind.named.conf.template b/install/share/bind.named.conf.template index 3e1cfe612..28c21d0a7 100644 --- a/install/share/bind.named.conf.template +++ b/install/share/bind.named.conf.template @@ -57,6 +57,5 @@ dyndb "ipa" "$BIND_LDAP_SO" { base "cn=dns,$SUFFIX"; server_id "$FQDN"; auth_method "sasl"; - sasl_mech "GSSAPI"; - sasl_user "DNS/$FQDN"; + sasl_mech "EXTERNAL"; }; diff --git a/install/updates/20-autobind.update b/install/updates/20-autobind.update index 3a06f84b9..436ffbc2e 100644 --- a/install/updates/20-autobind.update +++ b/install/updates/20-autobind.update @@ -1,6 +1,14 @@ +# countainer for autobind, already exists in new 389-DS instances +dn: cn=auto_bind,cn=config +default: objectClass: nsContainer +default: objectClass: top +default: cn: auto_bind + # map LDAPI autobind uid/gid to user entries (not used by root autobind) dn: cn=config only: nsslapd-ldapimaptoentries: on +only: nsslapd-ldapientrysearchbase: cn=auto_bind,cn=config +only: nsslapd-ldapidnmappingbase: cn=auto_bind,cn=config # lib389 configures 389-DS for root-autobind. This entry is no longer needed. dn: cn=root-autobind,cn=config diff --git a/install/updates/49-autobind-services.update b/install/updates/49-autobind-services.update new file mode 100644 index 000000000..e521bce6b --- /dev/null +++ b/install/updates/49-autobind-services.update @@ -0,0 +1,7 @@ +# create / update LDAPI autobind rules for services +# also used by ipa-restore to ensure correct mappings +# NOTE: ipa-ldap-updater runs reload ldapi mappings tasks + +dn: cn=named,cn=auto_bind,cn=config +onlyifexist: uidNumber: $NAMED_UID +onlyifexist: gidNumber: $NAMED_GID diff --git a/install/updates/Makefile.am b/install/updates/Makefile.am index 5741805a6..1f3a9caf1 100644 --- a/install/updates/Makefile.am +++ b/install/updates/Makefile.am @@ -45,6 +45,7 @@ app_DATA = \ 41-caacl.update \ 41-lightweight-cas.update \ 45-roles.update \ + 49-autobind-services.update \ 50-7_bit_check.update \ 50-dogtag10-migration.update \ 50-groupuuid.update \ diff --git a/ipapython/ipaldap.py b/ipapython/ipaldap.py index c9b54ff79..dc14a1811 100644 --- a/ipapython/ipaldap.py +++ b/ipapython/ipaldap.py @@ -763,6 +763,8 @@ class LDAPClient: 'nsslapd-enable-upgrade-hash': True, 'nsslapd-db-locks': True, 'nsslapd-logging-hr-timestamps-enabled': True, + 'nsslapd-ldapientrysearchbase': True, + 'nsslapd-ldapidnmappingbase': True, }) time_limit = -1.0 # unlimited diff --git a/ipaserver/install/bindinstance.py b/ipaserver/install/bindinstance.py index a5aef03d4..3afc63aee 100644 --- a/ipaserver/install/bindinstance.py +++ b/ipaserver/install/bindinstance.py @@ -760,8 +760,11 @@ class BindInstance(service.Service): self.step("setting up records for other masters", self.__add_others) # all zones must be created before this step self.step("adding NS record to the zones", self.__add_self_ns) - + # The service entry is used for LDAPI autobind. The keytab is no + # longer used to authenticate the server. The server still needs + # the keytab to handle incoming nsupdate requests with TSIG. self.step("setting up kerberos principal", self.__setup_principal) + self.step("setting up LDAPI autobind", self.setup_autobind) self.step("setting up named.conf", self.setup_named_conf) self.step("setting up server configuration", self.__setup_server_configuration) @@ -1029,6 +1032,11 @@ class BindInstance(service.Service): dns_principal, str(e)) raise + def setup_autobind(self): + self.add_autobind_entry( + constants.NAMED_USER, constants.NAMED_GROUP, self.principal + ) + def setup_named_conf(self, backup=False): """Create, update, or migrate named configuration files diff --git a/ipaserver/install/ipa_restore.py b/ipaserver/install/ipa_restore.py index 02550e043..726f9f1ce 100644 --- a/ipaserver/install/ipa_restore.py +++ b/ipaserver/install/ipa_restore.py @@ -40,7 +40,7 @@ from ipapython import admintool, certdb from ipapython.dn import DN from ipaserver.install.replication import (wait_for_task, ReplicationManager, get_cs_replication_manager) -from ipaserver.install import installutils +from ipaserver.install import installutils, ldapupdate from ipaserver.install import dsinstance, httpinstance, cainstance, krbinstance from ipaserver.masters import get_masters from ipapython import ipaldap @@ -467,6 +467,12 @@ class Restore(admintool.AdminTool): oddjobd.enable() oddjobd.start() http.remove_httpd_ccaches() + # update autobind configuration in case uid/gid have changed + ld = ldapupdate.LDAPUpdate(api=api) + autobind_update = os.path.join( + paths.UPDATES_DIR, "49-autobind-services.update" + ) + ld.update([autobind_update]) # have the daemons pick up their restored configs tasks.systemd_daemon_reload() # Restart IPA a final time. diff --git a/ipaserver/install/ldapupdate.py b/ipaserver/install/ldapupdate.py index 15c0ccb50..056a6e6bb 100644 --- a/ipaserver/install/ldapupdate.py +++ b/ipaserver/install/ldapupdate.py @@ -34,7 +34,6 @@ import warnings import six -from ipaserver.install import installutils from ipapython import ipautil, ipaldap from ipalib import errors from ipalib import api, create_api @@ -43,6 +42,7 @@ from ipaplatform.constants import constants as platformconstants from ipaplatform.paths import paths from ipaplatform.tasks import tasks from ipapython.dn import DN +from ipaserver.install import installutils, replication if six.PY3: unicode = str @@ -137,6 +137,35 @@ def safe_output(attr, values): _sentinel = object() +def run_ldapi_reload_task(conn): + """Create and wait for reload ldapi mappings task + + :param conn: ldap2 connection + :return: exitcode + """ + task_cn = "reload_{}".format(int(time.time())) + task_dn = DN( + ('cn', task_cn), ('cn', 'reload ldapi mappings'), + ('cn', 'tasks'), ('cn', 'config') + ) + entry = conn.make_entry( + task_dn, + objectClass=['top', 'extensibleObject'], + cn=[task_cn], + ttl=[10], + ) + logger.debug('Creating reload task %s', task_dn) + conn.add_entry(entry) + # task usually finishes in a few ms, avoid 1 sec delay in wait_for_task + time.sleep(0.1) + exitcode = replication.wait_for_task(api.Backend.ldap2, task_dn) + logger.debug( + 'Task %s has finished with exit code %i', + task_dn, exitcode + ) + return exitcode + + class LDAPUpdate: action_keywords = { "default", "add", "remove", "only", "onlyifexist", "deleteentry", @@ -146,6 +175,7 @@ class LDAPUpdate: ('cn', 'index'), ('cn', 'userRoot'), ('cn', 'ldbm database'), ('cn', 'plugins'), ('cn', 'config') ) + ldapi_autobind_suffix = DN(('cn', 'auto_bind'), ('cn', 'config')) def __init__(self, dm_password=_sentinel, sub_dict=None, online=_sentinel, ldapi=_sentinel, api=api): @@ -296,6 +326,9 @@ class LDAPUpdate: SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT, SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER, FIPS="#" if tasks.is_fips_enabled() else "", + # uid / gid for autobind + NAMED_UID=platformconstants.NAMED_USER.uid, + NAMED_GID=platformconstants.NAMED_GROUP.gid, ) for k, v in default_sub.items(): self.sub_dict.setdefault(k, v) @@ -889,6 +922,7 @@ class LDAPUpdate: def _run_updates(self, all_updates): index_attributes = set() + update_ldapi_mappings = False for update in all_updates: if 'deleteentry' in update: self._delete_record(update) @@ -896,8 +930,16 @@ class LDAPUpdate: self._run_update_plugin(update['plugin']) else: entry, modified = self._update_record(update) - if modified and entry.dn.endswith(self.index_suffix): - index_attributes.add(entry.single_value['cn']) + if modified: + if entry.dn.endswith(self.index_suffix): + index_attributes.add(entry.single_value['cn']) + if ( + entry.dn.endswith(self.ldapi_autobind_suffix) + and "nsLDAPIFixedAuthMap" in entry.get( + "objectClass", () + ) + ): + update_ldapi_mappings = True if index_attributes: # The LDAPUpdate framework now keeps record of all changed/added @@ -907,6 +949,10 @@ class LDAPUpdate: task_dn = self.create_index_task(*sorted(index_attributes)) self.monitor_index_task(task_dn) + if update_ldapi_mappings: + # update mappings when any autobind entry is added or modified + run_ldapi_reload_task(self.conn) + def update(self, files, ordered=True): """Execute the update. files is a list of the update files to use. :param ordered: Update files are executed in alphabetical order diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index 75bf26b8e..6bc63661e 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1454,6 +1454,9 @@ def upgrade_bind(fstore): else: bind_started = False + # create or update autobind entry + bind.setup_autobind() + try: changed = bind.setup_named_conf(backup=True) if changed: diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index 13ae346bc..4c366a184 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -42,7 +42,7 @@ from ipaserver.masters import ( CONFIGURED_SERVICE, ENABLED_SERVICE, HIDDEN_SERVICE, SERVICE_LIST ) from ipaserver.servroles import HIDDEN -from ipaserver.install.ldapupdate import LDAPUpdate +from ipaserver.install.ldapupdate import LDAPUpdate, run_ldapi_reload_task logger = logging.getLogger(__name__) @@ -332,6 +332,15 @@ class Service: kerberos.Principal( (self.service_prefix, self.fqdn), realm=self.realm)) + def get_principal_dn(self, principal=None): + if principal is None: + principal = self.principal + return DN( + ('krbprincipalname', principal), + self.api.env.container_service, + self.suffix + ) + def _ldap_update(self, filenames, *, basedir=paths.UPDATES_DIR): """Apply update ldif files @@ -419,7 +428,7 @@ class Service: # This can happen when installing a replica return None entry.pop('krbpwdpolicyreference', None) # don't copy virtual attr - newdn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + newdn = self.get_principal_dn(principal) hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) api.Backend.ldap2.delete_entry(entry) entry.dn = newdn @@ -437,7 +446,7 @@ class Service: The principal needs to be fully-formed: service/host@REALM """ - dn = DN(('krbprincipalname', principal), ('cn', 'services'), ('cn', 'accounts'), self.suffix) + dn = self.get_principal_dn(principal) hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix) entry = api.Backend.ldap2.make_entry( dn, @@ -451,6 +460,49 @@ class Service: api.Backend.ldap2.add_entry(entry) return dn + def add_autobind_entry(self, user, group, principal): + """Add or update LDAPI autobind entry to map uid/gid to principal + + :param user: ipaplatform User object + :param group: ipaplatform Group object + :param principal: service principal to bind as + :return: dn of new autobind entry + """ + authdn = self.get_principal_dn(principal) + dn = DN( + ("cn", self.service_name), ("cn", "auto_bind"), ("cn", "config") + ) + settings = { + "uidNumber": [user.uid], + "gidNumber": [group.gid], + "nsslapd-authenticateAsDN": [authdn] + } + ldap2 = self.api.Backend.ldap2 + try: + entry = ldap2.get_entry(dn) + except errors.NotFound: + entry = ldap2.make_entry( + dn, + objectclass=["top", "nsLDAPIFixedAuthMap"], + cn=[self.service_name], + **settings, + ) + ldap2.add_entry(entry) + logger.debug("Created autobind entry %s", dn) + else: + entry.update(settings) + try: + ldap2.update_entry(entry) + except errors.EmptyModlist: + logger.debug("Autobind entry %s already configured", dn) + else: + logger.debug("Updated autobind entry %s", dn) + + # refresh LDAPI mappings + run_ldapi_reload_task(self.api.Backend.ldap2) + + return dn + def add_cert_to_service(self): """ Add a certificate to a service @@ -459,8 +511,7 @@ class Service: """ if self.cert is None: raise ValueError("{} has no cert".format(self.service_name)) - dn = DN(('krbprincipalname', self.principal), ('cn', 'services'), - ('cn', 'accounts'), self.suffix) + dn = self.get_principal_dn() entry = api.Backend.ldap2.get_entry(dn) entry.setdefault('userCertificate', []).append(self.cert) try: