LDAP autobind authenticateAsDN for BIND named

Use new nsslapd-ldapiautoauthdnattr feature to switch BIND named from
GSSAPI bind to EXTERNAL LDAPI bind.

Requires 389-DS >= 1.4.4.11 or >= 2.0.2

Fixes: https://pagure.io/freeipa/issue/8544
See: https://github.com/389ds/389-ds-base/issues/4381
Signed-off-by: Christian Heimes <cheimes@redhat.com>
Reviewed-By: Alexander Bokovoy <abokovoy@redhat.com>
Reviewed-By: Francois Cami <fcami@redhat.com>
This commit is contained in:
Christian Heimes 2020-12-04 12:18:22 +01:00 committed by Alexander Bokovoy
parent 6434968a7c
commit 16e1cbdc5e
10 changed files with 143 additions and 12 deletions

View File

@ -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";
};

View File

@ -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

View File

@ -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

View File

@ -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 \

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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: