mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-01-26 16:16:31 -06:00
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:
parent
6434968a7c
commit
16e1cbdc5e
@ -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";
|
||||
};
|
||||
|
@ -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
|
||||
|
7
install/updates/49-autobind-services.update
Normal file
7
install/updates/49-autobind-services.update
Normal 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
|
@ -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 \
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
Loading…
Reference in New Issue
Block a user