mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
trust-fetch-domains: use custom krb5.conf overlay for all trust operations
Operations in FIPS mode make impossible use of NTLMSSP when authenticating to trusted Active Directory domain controllers because RC4 cipher is not allowed. Instead, Kerberos authentication have to be used. We switched to enforce Kerberos authentication when communicating with trusted domains' domain controllers everywhere. Kerberos library uses system wide configuration which in IPA defaults to resolving location of KDCs via DNS SRV records. Once trust is established, SSSD will populate a list of closest DCs and provide them through the KDC locator plugin. But at the time the trust is established performing DNS SRV-based discovery of Kerberos KDCs might fail due to multiple reasons. It might also succeed but point to a DC that doesn't know about the account we have to use to establish trust. One edge case is when DNS SRV record points to an unreachable DC, whether due to a firewall or a network topology limitations. In such case an administrator would pass --server <server> option to 'ipa trust-add' or 'ipa trust-fetch-domains' commands. 'ipa trust-fetch-domains' runs a helper via oddjobd. This helper was already modified to support --server option and generated custom krb5.conf overlay to pin to a specific AD DC. However, this configuration was removed as soon as we finished talking to AD DCs. With switch to always use Kebreros to authenticate in retrieval of the topology information, we have to use the overlay everywhere as well. Convert the code that generated the overlay file into a context that generates the overlay and sets environment. Reuse it in other trust-related places where this matters. Oddjob helper runs as root and can write to /run/ipa for the krb5.conf overlay. Server side of 'ipa trust-add' code calls into ipaserver/dcerpc.py and runs under ipaapi so can only write to /tmp. Since it is a part of the Apache instance, it uses private /tmp mounted on tmpfs. Fixes: https://pagure.io/freeipa/issue/8664 Related: https://pagure.io/freeipa/issue/8655 Signed-off-by: Alexander Bokovoy <abokovoy@redhat.com> Reviewed-By: Christian Heimes <cheimes@redhat.com> Reviewed-By: Rob Crittenden <rcritten@redhat.com>
This commit is contained in:
committed by
Rob Crittenden
parent
94242563d5
commit
ae7cd4702d
@@ -4,16 +4,12 @@ from ipaserver import dcerpc
|
||||
from ipaserver.install.installutils import ScriptError
|
||||
from ipapython import config, ipautil
|
||||
from ipalib import api
|
||||
from ipalib.facts import is_ipa_configured
|
||||
from ipapython.dn import DN
|
||||
from ipapython.dnsutil import DNSName
|
||||
from ipaplatform.constants import constants
|
||||
from ipaplatform.paths import paths
|
||||
import io
|
||||
import sys
|
||||
import os
|
||||
import tempfile
|
||||
import textwrap
|
||||
|
||||
import six
|
||||
import gssapi
|
||||
@@ -121,43 +117,6 @@ def get_forest_root_domain(api_instance, trusted_domain, server=None):
|
||||
return remote_domain.info["dns_forest"]
|
||||
|
||||
|
||||
def generate_krb5_config(realm, server):
|
||||
"""Generate override krb5 config file for trusted domain DC access
|
||||
|
||||
:param realm: realm of the trusted AD domain
|
||||
:param server: server to override KDC to
|
||||
|
||||
:returns: tuple (temporary config file name, KRB5_CONFIG string)
|
||||
"""
|
||||
cfg = paths.KRB5_CONF
|
||||
tcfg = None
|
||||
if server:
|
||||
content = textwrap.dedent(u"""
|
||||
[realms]
|
||||
%s = {
|
||||
kdc = %s
|
||||
}
|
||||
""") % (
|
||||
realm.upper(),
|
||||
server,
|
||||
)
|
||||
|
||||
(fd, tcfg) = tempfile.mkstemp(dir="/run/ipa",
|
||||
prefix="krb5conf", text=True)
|
||||
with io.open(fd, mode='w', encoding='utf-8') as o:
|
||||
o.write(content)
|
||||
cfg = ":".join([tcfg, cfg])
|
||||
return (tcfg, cfg)
|
||||
|
||||
|
||||
if not is_ipa_configured():
|
||||
# LSB status code 6: program is not configured
|
||||
raise ScriptError(
|
||||
"IPA is not configured "
|
||||
+ "(see man pages of ipa-server-install for help)",
|
||||
6,
|
||||
)
|
||||
|
||||
if not os.getegid() == 0:
|
||||
# LSB status code 4: user had insufficient privilege
|
||||
raise ScriptError("You must be root to run ipactl.", 4)
|
||||
@@ -234,20 +193,24 @@ trusted_domain = trusted_domain_entry.single_value.get("cn").lower()
|
||||
# At this point if we didn't find trusted forest name, an exception will be raised
|
||||
# and script will quit. This is actually intended.
|
||||
|
||||
rc = 0
|
||||
|
||||
# Generate MIT Kerberos configuration file that potentially overlays
|
||||
# the KDC to connect to for a trusted domain to allow --server option
|
||||
# to take precedence.
|
||||
cfg_file, cfg = generate_krb5_config(trusted_domain, options.server)
|
||||
|
||||
with ipautil.private_krb5_config(trusted_domain, options.server) as cfg_file:
|
||||
if not (options.admin and options.password):
|
||||
oneway_keytab_name = "/var/lib/sss/keytabs/" + trusted_domain + ".keytab"
|
||||
oneway_keytab_name = os.path.join("/var/lib/sss/keytabs/",
|
||||
trusted_domain + ".keytab")
|
||||
|
||||
oneway_principal = str(
|
||||
"%s$@%s" % (own_trust_flatname, trusted_domain.upper())
|
||||
)
|
||||
|
||||
# If keytab does not exist, retrieve it
|
||||
if not os.path.isfile(oneway_keytab_name):
|
||||
retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
|
||||
retrieve_keytab(api, ccache_name,
|
||||
oneway_keytab_name, oneway_principal)
|
||||
|
||||
try:
|
||||
have_ccache = False
|
||||
@@ -257,7 +220,6 @@ if not (options.admin and options.password):
|
||||
oneway_principal,
|
||||
oneway_keytab_name,
|
||||
oneway_ccache_name,
|
||||
config=cfg,
|
||||
)
|
||||
if cred.lifetime > 0:
|
||||
have_ccache = True
|
||||
@@ -270,7 +232,6 @@ if not (options.admin and options.password):
|
||||
oneway_principal,
|
||||
oneway_keytab_name,
|
||||
oneway_ccache_name,
|
||||
config=cfg,
|
||||
)
|
||||
except (gssapi.exceptions.GSSError, gssapi.raw.misc.GSSError):
|
||||
# If there was failure on using keytab, assume it is stale and retrieve again
|
||||
@@ -281,7 +242,6 @@ if not (options.admin and options.password):
|
||||
oneway_principal,
|
||||
oneway_keytab_name,
|
||||
oneway_ccache_name,
|
||||
config=cfg,
|
||||
)
|
||||
else:
|
||||
cred = kinit_password(
|
||||
@@ -290,19 +250,18 @@ else:
|
||||
oneway_ccache_name,
|
||||
canonicalize=True,
|
||||
enterprise=True,
|
||||
config=cfg,
|
||||
)
|
||||
|
||||
if cred and cred.lifetime > 0:
|
||||
have_ccache = True
|
||||
|
||||
if not have_ccache:
|
||||
sys.exit(1)
|
||||
rc = 1
|
||||
raise GeneratorExit
|
||||
|
||||
# We are done: we have ccache with TDO credentials and can fetch domains
|
||||
ipa_domain = api.env.domain
|
||||
os.environ["KRB5CCNAME"] = oneway_ccache_name
|
||||
os.environ["KRB5_CONFIG"] = cfg
|
||||
|
||||
# retrieve the forest root domain name and contact it to retrieve trust
|
||||
# topology info
|
||||
@@ -313,18 +272,16 @@ domains = dcerpc.fetch_domains(
|
||||
api, ipa_domain, forest_root, creds=True, server=options.server
|
||||
)
|
||||
|
||||
# We still need to use the override for KDC configuration in case the --server
|
||||
# was forced, thus only switch to the old ccache.
|
||||
if old_ccache:
|
||||
os.environ["KRB5CCNAME"] = old_ccache
|
||||
|
||||
if old_config:
|
||||
os.environ["KRB5_CONFIG"] = old_config
|
||||
|
||||
if cfg_file:
|
||||
os.remove(cfg_file)
|
||||
|
||||
trust_domain_object = api.Command.trust_show(trusted_domain, raw=True)[
|
||||
"result"
|
||||
]
|
||||
|
||||
trust.add_new_domains_from_trust(api, None, trust_domain_object, domains)
|
||||
|
||||
sys.exit(0)
|
||||
sys.exit(rc)
|
||||
|
||||
@@ -36,6 +36,8 @@ import re
|
||||
import datetime
|
||||
import netaddr
|
||||
import time
|
||||
import textwrap
|
||||
import io
|
||||
from contextlib import contextmanager
|
||||
import locale
|
||||
import collections
|
||||
@@ -1447,6 +1449,54 @@ def private_ccache(path=None):
|
||||
pass
|
||||
|
||||
|
||||
@contextmanager
|
||||
def private_krb5_config(realm, server, dir="/run/ipa"):
|
||||
"""Generate override krb5 config file for a trusted domain DC access
|
||||
Provide a context where environment variable KRB5_CONFIG is set
|
||||
with the overlay on top of paths.KRB5_CONF. Overlay's file path
|
||||
is passed to the context in case it is needed for something else
|
||||
|
||||
:param realm: realm of the trusted AD domain
|
||||
:param server: server to override KDC to
|
||||
:param dir: path where to create a temporary krb5.conf overlay
|
||||
"""
|
||||
cfg = paths.KRB5_CONF
|
||||
tcfg = None
|
||||
if server:
|
||||
content = textwrap.dedent(u"""
|
||||
[realms]
|
||||
%s = {
|
||||
kdc = %s
|
||||
}
|
||||
""") % (
|
||||
realm.upper(),
|
||||
server,
|
||||
)
|
||||
|
||||
(fd, tcfg) = tempfile.mkstemp(dir=dir, prefix="krb5conf", text=True)
|
||||
|
||||
with io.open(fd, mode='w', encoding='utf-8') as o:
|
||||
o.write(content)
|
||||
cfg = ":".join([tcfg, cfg])
|
||||
|
||||
original_value = os.environ.get('KRB5_CONFIG', None)
|
||||
|
||||
os.environ['KRB5_CONFIG'] = cfg
|
||||
|
||||
try:
|
||||
yield tcfg
|
||||
except GeneratorExit:
|
||||
pass
|
||||
finally:
|
||||
if original_value is not None:
|
||||
os.environ['KRB5_CONFIG'] = original_value
|
||||
else:
|
||||
os.environ.pop('KRB5_CONFIG', None)
|
||||
|
||||
if tcfg is not None and os.path.exists(tcfg):
|
||||
os.remove(tcfg)
|
||||
|
||||
|
||||
if six.PY2:
|
||||
def fsdecode(value):
|
||||
"""
|
||||
|
||||
@@ -1698,9 +1698,12 @@ def retrieve_remote_domain(hostname, local_flatname,
|
||||
error=_('Non-Kerberos user name was specified, '
|
||||
'please provide user@REALM variant instead'))
|
||||
realm_admin = r"%s@%s" % (
|
||||
realm_admin, rd.info['dns_forest'].upper())
|
||||
realm_admin, rd.info['dns_domain'].upper())
|
||||
realm = rd.info['dns_domain'].upper()
|
||||
auth_string = r"%s%%%s" \
|
||||
% (realm_admin, realm_passwd)
|
||||
with ipautil.private_krb5_config(realm, realm_server, dir='/tmp'):
|
||||
with ipautil.private_ccache():
|
||||
td = get_instance(local_flatname)
|
||||
td.creds.set_kerberos_state(credentials.MUST_USE_KERBEROS)
|
||||
enforce_smb_encryption(td.creds)
|
||||
@@ -1832,15 +1835,18 @@ class TrustDomainJoins:
|
||||
# Establishing trust may throw an exception for topology
|
||||
# conflict. If it was solved, re-establish the trust again
|
||||
# Otherwise let the CLI to display a message about the conflict
|
||||
with ipautil.private_krb5_config(realm, realm_server, dir='/tmp'):
|
||||
try:
|
||||
self.remote_domain.establish_trust(self.local_domain,
|
||||
trustdom_pass,
|
||||
trust_type, trust_external)
|
||||
trust_type,
|
||||
trust_external)
|
||||
except TrustTopologyConflictSolved:
|
||||
# we solved topology conflict, retry again
|
||||
self.remote_domain.establish_trust(self.local_domain,
|
||||
trustdom_pass,
|
||||
trust_type, trust_external)
|
||||
trust_type,
|
||||
trust_external)
|
||||
|
||||
try:
|
||||
self.local_domain.establish_trust(self.remote_domain,
|
||||
@@ -1856,6 +1862,8 @@ class TrustDomainJoins:
|
||||
# it only does verification for outbound trusts.
|
||||
result = True
|
||||
if trust_type == TRUST_BIDIRECTIONAL:
|
||||
with ipautil.private_krb5_config(realm,
|
||||
realm_server, dir='/tmp'):
|
||||
result = self.remote_domain.verify_trust(self.local_domain)
|
||||
return dict(
|
||||
local=self.local_domain,
|
||||
|
||||
Reference in New Issue
Block a user