mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -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";
|
base "cn=dns,$SUFFIX";
|
||||||
server_id "$FQDN";
|
server_id "$FQDN";
|
||||||
auth_method "sasl";
|
auth_method "sasl";
|
||||||
sasl_mech "GSSAPI";
|
sasl_mech "EXTERNAL";
|
||||||
sasl_user "DNS/$FQDN";
|
|
||||||
};
|
};
|
||||||
|
@ -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)
|
# map LDAPI autobind uid/gid to user entries (not used by root autobind)
|
||||||
dn: cn=config
|
dn: cn=config
|
||||||
only: nsslapd-ldapimaptoentries: on
|
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.
|
# lib389 configures 389-DS for root-autobind. This entry is no longer needed.
|
||||||
dn: cn=root-autobind,cn=config
|
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-caacl.update \
|
||||||
41-lightweight-cas.update \
|
41-lightweight-cas.update \
|
||||||
45-roles.update \
|
45-roles.update \
|
||||||
|
49-autobind-services.update \
|
||||||
50-7_bit_check.update \
|
50-7_bit_check.update \
|
||||||
50-dogtag10-migration.update \
|
50-dogtag10-migration.update \
|
||||||
50-groupuuid.update \
|
50-groupuuid.update \
|
||||||
|
@ -763,6 +763,8 @@ class LDAPClient:
|
|||||||
'nsslapd-enable-upgrade-hash': True,
|
'nsslapd-enable-upgrade-hash': True,
|
||||||
'nsslapd-db-locks': True,
|
'nsslapd-db-locks': True,
|
||||||
'nsslapd-logging-hr-timestamps-enabled': True,
|
'nsslapd-logging-hr-timestamps-enabled': True,
|
||||||
|
'nsslapd-ldapientrysearchbase': True,
|
||||||
|
'nsslapd-ldapidnmappingbase': True,
|
||||||
})
|
})
|
||||||
|
|
||||||
time_limit = -1.0 # unlimited
|
time_limit = -1.0 # unlimited
|
||||||
|
@ -760,8 +760,11 @@ class BindInstance(service.Service):
|
|||||||
self.step("setting up records for other masters", self.__add_others)
|
self.step("setting up records for other masters", self.__add_others)
|
||||||
# all zones must be created before this step
|
# all zones must be created before this step
|
||||||
self.step("adding NS record to the zones", self.__add_self_ns)
|
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 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 named.conf", self.setup_named_conf)
|
||||||
self.step("setting up server configuration",
|
self.step("setting up server configuration",
|
||||||
self.__setup_server_configuration)
|
self.__setup_server_configuration)
|
||||||
@ -1029,6 +1032,11 @@ class BindInstance(service.Service):
|
|||||||
dns_principal, str(e))
|
dns_principal, str(e))
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
def setup_autobind(self):
|
||||||
|
self.add_autobind_entry(
|
||||||
|
constants.NAMED_USER, constants.NAMED_GROUP, self.principal
|
||||||
|
)
|
||||||
|
|
||||||
def setup_named_conf(self, backup=False):
|
def setup_named_conf(self, backup=False):
|
||||||
"""Create, update, or migrate named configuration files
|
"""Create, update, or migrate named configuration files
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ from ipapython import admintool, certdb
|
|||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
from ipaserver.install.replication import (wait_for_task, ReplicationManager,
|
from ipaserver.install.replication import (wait_for_task, ReplicationManager,
|
||||||
get_cs_replication_manager)
|
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.install import dsinstance, httpinstance, cainstance, krbinstance
|
||||||
from ipaserver.masters import get_masters
|
from ipaserver.masters import get_masters
|
||||||
from ipapython import ipaldap
|
from ipapython import ipaldap
|
||||||
@ -467,6 +467,12 @@ class Restore(admintool.AdminTool):
|
|||||||
oddjobd.enable()
|
oddjobd.enable()
|
||||||
oddjobd.start()
|
oddjobd.start()
|
||||||
http.remove_httpd_ccaches()
|
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
|
# have the daemons pick up their restored configs
|
||||||
tasks.systemd_daemon_reload()
|
tasks.systemd_daemon_reload()
|
||||||
# Restart IPA a final time.
|
# Restart IPA a final time.
|
||||||
|
@ -34,7 +34,6 @@ import warnings
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from ipaserver.install import installutils
|
|
||||||
from ipapython import ipautil, ipaldap
|
from ipapython import ipautil, ipaldap
|
||||||
from ipalib import errors
|
from ipalib import errors
|
||||||
from ipalib import api, create_api
|
from ipalib import api, create_api
|
||||||
@ -43,6 +42,7 @@ from ipaplatform.constants import constants as platformconstants
|
|||||||
from ipaplatform.paths import paths
|
from ipaplatform.paths import paths
|
||||||
from ipaplatform.tasks import tasks
|
from ipaplatform.tasks import tasks
|
||||||
from ipapython.dn import DN
|
from ipapython.dn import DN
|
||||||
|
from ipaserver.install import installutils, replication
|
||||||
|
|
||||||
if six.PY3:
|
if six.PY3:
|
||||||
unicode = str
|
unicode = str
|
||||||
@ -137,6 +137,35 @@ def safe_output(attr, values):
|
|||||||
_sentinel = object()
|
_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:
|
class LDAPUpdate:
|
||||||
action_keywords = {
|
action_keywords = {
|
||||||
"default", "add", "remove", "only", "onlyifexist", "deleteentry",
|
"default", "add", "remove", "only", "onlyifexist", "deleteentry",
|
||||||
@ -146,6 +175,7 @@ class LDAPUpdate:
|
|||||||
('cn', 'index'), ('cn', 'userRoot'), ('cn', 'ldbm database'),
|
('cn', 'index'), ('cn', 'userRoot'), ('cn', 'ldbm database'),
|
||||||
('cn', 'plugins'), ('cn', 'config')
|
('cn', 'plugins'), ('cn', 'config')
|
||||||
)
|
)
|
||||||
|
ldapi_autobind_suffix = DN(('cn', 'auto_bind'), ('cn', 'config'))
|
||||||
|
|
||||||
def __init__(self, dm_password=_sentinel, sub_dict=None,
|
def __init__(self, dm_password=_sentinel, sub_dict=None,
|
||||||
online=_sentinel, ldapi=_sentinel, api=api):
|
online=_sentinel, ldapi=_sentinel, api=api):
|
||||||
@ -296,6 +326,9 @@ class LDAPUpdate:
|
|||||||
SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
|
SELINUX_USERMAP_DEFAULT=platformconstants.SELINUX_USERMAP_DEFAULT,
|
||||||
SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
|
SELINUX_USERMAP_ORDER=platformconstants.SELINUX_USERMAP_ORDER,
|
||||||
FIPS="#" if tasks.is_fips_enabled() else "",
|
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():
|
for k, v in default_sub.items():
|
||||||
self.sub_dict.setdefault(k, v)
|
self.sub_dict.setdefault(k, v)
|
||||||
@ -889,6 +922,7 @@ class LDAPUpdate:
|
|||||||
|
|
||||||
def _run_updates(self, all_updates):
|
def _run_updates(self, all_updates):
|
||||||
index_attributes = set()
|
index_attributes = set()
|
||||||
|
update_ldapi_mappings = False
|
||||||
for update in all_updates:
|
for update in all_updates:
|
||||||
if 'deleteentry' in update:
|
if 'deleteentry' in update:
|
||||||
self._delete_record(update)
|
self._delete_record(update)
|
||||||
@ -896,8 +930,16 @@ class LDAPUpdate:
|
|||||||
self._run_update_plugin(update['plugin'])
|
self._run_update_plugin(update['plugin'])
|
||||||
else:
|
else:
|
||||||
entry, modified = self._update_record(update)
|
entry, modified = self._update_record(update)
|
||||||
if modified and entry.dn.endswith(self.index_suffix):
|
if modified:
|
||||||
index_attributes.add(entry.single_value['cn'])
|
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:
|
if index_attributes:
|
||||||
# The LDAPUpdate framework now keeps record of all changed/added
|
# 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))
|
task_dn = self.create_index_task(*sorted(index_attributes))
|
||||||
self.monitor_index_task(task_dn)
|
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):
|
def update(self, files, ordered=True):
|
||||||
"""Execute the update. files is a list of the update files to use.
|
"""Execute the update. files is a list of the update files to use.
|
||||||
:param ordered: Update files are executed in alphabetical order
|
:param ordered: Update files are executed in alphabetical order
|
||||||
|
@ -1454,6 +1454,9 @@ def upgrade_bind(fstore):
|
|||||||
else:
|
else:
|
||||||
bind_started = False
|
bind_started = False
|
||||||
|
|
||||||
|
# create or update autobind entry
|
||||||
|
bind.setup_autobind()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
changed = bind.setup_named_conf(backup=True)
|
changed = bind.setup_named_conf(backup=True)
|
||||||
if changed:
|
if changed:
|
||||||
|
@ -42,7 +42,7 @@ from ipaserver.masters import (
|
|||||||
CONFIGURED_SERVICE, ENABLED_SERVICE, HIDDEN_SERVICE, SERVICE_LIST
|
CONFIGURED_SERVICE, ENABLED_SERVICE, HIDDEN_SERVICE, SERVICE_LIST
|
||||||
)
|
)
|
||||||
from ipaserver.servroles import HIDDEN
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -332,6 +332,15 @@ class Service:
|
|||||||
kerberos.Principal(
|
kerberos.Principal(
|
||||||
(self.service_prefix, self.fqdn), realm=self.realm))
|
(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):
|
def _ldap_update(self, filenames, *, basedir=paths.UPDATES_DIR):
|
||||||
"""Apply update ldif files
|
"""Apply update ldif files
|
||||||
|
|
||||||
@ -419,7 +428,7 @@ class Service:
|
|||||||
# This can happen when installing a replica
|
# This can happen when installing a replica
|
||||||
return None
|
return None
|
||||||
entry.pop('krbpwdpolicyreference', None) # don't copy virtual attr
|
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)
|
hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix)
|
||||||
api.Backend.ldap2.delete_entry(entry)
|
api.Backend.ldap2.delete_entry(entry)
|
||||||
entry.dn = newdn
|
entry.dn = newdn
|
||||||
@ -437,7 +446,7 @@ class Service:
|
|||||||
|
|
||||||
The principal needs to be fully-formed: service/host@REALM
|
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)
|
hostdn = DN(('fqdn', self.fqdn), ('cn', 'computers'), ('cn', 'accounts'), self.suffix)
|
||||||
entry = api.Backend.ldap2.make_entry(
|
entry = api.Backend.ldap2.make_entry(
|
||||||
dn,
|
dn,
|
||||||
@ -451,6 +460,49 @@ class Service:
|
|||||||
api.Backend.ldap2.add_entry(entry)
|
api.Backend.ldap2.add_entry(entry)
|
||||||
return dn
|
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):
|
def add_cert_to_service(self):
|
||||||
"""
|
"""
|
||||||
Add a certificate to a service
|
Add a certificate to a service
|
||||||
@ -459,8 +511,7 @@ class Service:
|
|||||||
"""
|
"""
|
||||||
if self.cert is None:
|
if self.cert is None:
|
||||||
raise ValueError("{} has no cert".format(self.service_name))
|
raise ValueError("{} has no cert".format(self.service_name))
|
||||||
dn = DN(('krbprincipalname', self.principal), ('cn', 'services'),
|
dn = self.get_principal_dn()
|
||||||
('cn', 'accounts'), self.suffix)
|
|
||||||
entry = api.Backend.ldap2.get_entry(dn)
|
entry = api.Backend.ldap2.get_entry(dn)
|
||||||
entry.setdefault('userCertificate', []).append(self.cert)
|
entry.setdefault('userCertificate', []).append(self.cert)
|
||||||
try:
|
try:
|
||||||
|
Loading…
Reference in New Issue
Block a user