2015-06-05 07:57:02 -05:00
|
|
|
#!/usr/bin/python2
|
|
|
|
|
|
|
|
from ipaserver import dcerpc
|
|
|
|
from ipaserver.install.installutils import is_ipa_configured, ScriptError
|
|
|
|
from ipapython import config, ipautil
|
2016-06-06 03:41:46 -05:00
|
|
|
from ipalib import api
|
2015-06-05 07:57:02 -05:00
|
|
|
from ipapython.dn import DN
|
|
|
|
from ipalib.config import Env
|
|
|
|
from ipalib.constants import DEFAULT_CONFIG
|
2016-03-18 05:22:33 -05:00
|
|
|
from ipaplatform.constants import constants
|
2015-06-05 07:57:02 -05:00
|
|
|
import sys
|
2016-03-20 15:21:10 -05:00
|
|
|
import os
|
|
|
|
import pwd
|
2015-09-11 06:43:28 -05:00
|
|
|
|
|
|
|
import six
|
2015-07-20 09:04:07 -05:00
|
|
|
import gssapi
|
2015-06-05 07:57:02 -05:00
|
|
|
|
2016-11-23 10:40:47 -06:00
|
|
|
from ipalib.install.kinit import kinit_keytab
|
|
|
|
|
2015-09-11 06:43:28 -05:00
|
|
|
if six.PY3:
|
|
|
|
unicode = str
|
|
|
|
|
2015-06-05 07:57:02 -05:00
|
|
|
def retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal):
|
|
|
|
getkeytab_args = ["/usr/sbin/ipa-getkeytab",
|
|
|
|
"-s", api.env.host,
|
|
|
|
"-p", oneway_principal,
|
|
|
|
"-k", oneway_keytab_name,
|
|
|
|
"-r"]
|
trusts: harden trust-fetch-domains oddjobd-based script
When ipa-getkeytab is used to fetch trusted domain object credentials,
the fetched entry has always kvno 1. ipa-getkeytab always adds a key to
keytab which means older key versions will be in the SSSD keytab and
will confuse libkrb5 ccache initialization code as all kvno values are
equal to 1. Wrong key is picked up then and kinit fails.
To solve this problem, always remove existing
/var/lib/sss/keytabs/forest.keytab before retrieving a new one.
To make sure script's input cannot be used to define what should be
removed (by passing a relative path), make sure we retrieve trusted
forest name from LDAP. If it is not possible to retrieve, the script
will issue an exception and quit. If abrtd is running, this will be
recorded as a 'crash' and an attempt to use script by malicious user
would be recorded as well in the abrtd journal.
Additionally, as com.redhat.idm.trust-fetch-domains will create
ID ranges for the domains of the trusted forest if they don't exist,
it needs permissions to do so. The permission should be granted only
to cifs/ipa.master@IPA.REALM services which means they must have
krbprincipalname=cifs/*@IPA.REALM,cn=services,... DN and be members of
cn=adtrust agents,cn=sysaccounts,... group.
Solves https://bugzilla.redhat.com/show_bug.cgi?id=1250190
Ticket https://fedorahosted.org/freeipa/ticket/5182
Reviewed-By: Tomas Babej <tbabej@redhat.com>
2015-08-13 09:18:57 -05:00
|
|
|
if os.path.isfile(oneway_keytab_name):
|
|
|
|
os.unlink(oneway_keytab_name)
|
|
|
|
|
2015-11-25 10:17:18 -06:00
|
|
|
ipautil.run(getkeytab_args, env={'KRB5CCNAME': ccache_name, 'LANG': 'C'},
|
|
|
|
raiseonerr=False)
|
2015-06-05 07:57:02 -05:00
|
|
|
# Make sure SSSD is able to read the keytab
|
2015-07-16 06:11:26 -05:00
|
|
|
try:
|
2016-03-18 05:22:33 -05:00
|
|
|
sssd = pwd.getpwnam(constants.SSSD_USER)
|
2015-07-16 06:11:26 -05:00
|
|
|
os.chown(oneway_keytab_name, sssd[2], sssd[3])
|
2016-09-26 07:08:17 -05:00
|
|
|
except KeyError:
|
2015-07-16 06:11:26 -05:00
|
|
|
# If user 'sssd' does not exist, we don't need to chown from root to sssd
|
|
|
|
# because it means SSSD does not run as sssd user
|
|
|
|
pass
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
|
2016-09-01 11:14:22 -05:00
|
|
|
def get_forest_root_domain(api_instance, trusted_domain):
|
|
|
|
"""
|
|
|
|
retrieve trusted forest root domain for given domain name
|
|
|
|
|
|
|
|
:param api_instance: IPA API instance
|
|
|
|
:param trusted_domain: trusted domain name
|
|
|
|
|
|
|
|
:returns: forest root domain DNS name
|
|
|
|
"""
|
|
|
|
trustconfig_show = api_instance.Command.trustconfig_show
|
|
|
|
flatname = trustconfig_show()['result']['ipantflatname'][0]
|
|
|
|
|
|
|
|
remote_domain = dcerpc.retrieve_remote_domain(
|
|
|
|
api_instance.env.host, flatname, trusted_domain)
|
|
|
|
|
|
|
|
return remote_domain.info['dns_forest']
|
|
|
|
|
|
|
|
|
2015-06-05 07:57:02 -05:00
|
|
|
def parse_options():
|
|
|
|
usage = "%prog <trusted domain name>\n"
|
|
|
|
parser = config.IPAOptionParser(usage=usage,
|
|
|
|
formatter=config.IPAFormatter())
|
|
|
|
|
|
|
|
parser.add_option("-d", "--debug", action="store_true", dest="debug",
|
|
|
|
help="Display debugging information")
|
|
|
|
|
|
|
|
options, args = parser.parse_args()
|
|
|
|
safe_options = parser.get_safe_opts(options)
|
|
|
|
|
|
|
|
return safe_options, options, args
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
safe_options, options, args = parse_options()
|
|
|
|
|
|
|
|
if len(args) != 1:
|
|
|
|
# LSB status code 2: invalid or excess argument(s)
|
|
|
|
raise ScriptError("You must specify trusted domain name", 2)
|
|
|
|
|
2015-08-31 02:08:38 -05:00
|
|
|
trusted_domain = ipautil.fsdecode(args[0]).lower()
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
env = Env()
|
2016-06-29 09:20:23 -05:00
|
|
|
env._bootstrap(debug=options.debug, log=None)
|
2015-06-05 07:57:02 -05:00
|
|
|
env._finalize_core(**dict(DEFAULT_CONFIG))
|
|
|
|
|
|
|
|
# Initialize the API with the proper debug level
|
2016-08-06 03:12:13 -05:00
|
|
|
api.bootstrap(in_server=True, debug=env.debug, log=None, context='server')
|
2015-06-05 07:57:02 -05:00
|
|
|
api.finalize()
|
|
|
|
|
|
|
|
# Only import trust plugin after api is initialized or internal imports
|
|
|
|
# within the plugin will not work
|
2016-04-28 03:30:05 -05:00
|
|
|
from ipaserver.plugins import trust
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
# We have to dance with two different credentials caches:
|
|
|
|
# ccache_name -- for cifs/ipa.master@IPA.REALM to communicate with LDAP
|
|
|
|
# oneway_ccache_name -- for IPA$@AD.REALM to communicate with AD DCs
|
|
|
|
#
|
|
|
|
# ccache_name may not exist, we'll have to initialize it from Samba's keytab
|
|
|
|
#
|
|
|
|
# oneway_ccache_name may not exist either but to initialize it, we need
|
|
|
|
# to check if oneway_keytab_name keytab exists and fetch it first otherwise.
|
|
|
|
#
|
|
|
|
# to fetch oneway_keytab_name keytab, we need to initialize ccache_name ccache first
|
|
|
|
# and retrieve our own NetBIOS domain name and use cifs/ipa.master@IPA.REALM to
|
|
|
|
# retrieve the keys to oneway_keytab_name.
|
|
|
|
|
|
|
|
keytab_name = '/etc/samba/samba.keytab'
|
|
|
|
|
|
|
|
principal = str('cifs/' + api.env.host)
|
|
|
|
|
|
|
|
oneway_ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts_fetch'
|
|
|
|
ccache_name = '/var/run/ipa/krb5cc_oddjob_trusts'
|
|
|
|
|
|
|
|
# Standard sequence:
|
|
|
|
# - check if ccache exists
|
|
|
|
# - if not, initialize it from Samba's keytab
|
|
|
|
# - check if ccache contains valid TGT
|
|
|
|
# - if not, initialize it from Samba's keytab
|
|
|
|
# - refer the correct ccache object for further use
|
|
|
|
#
|
2015-07-20 09:04:07 -05:00
|
|
|
have_ccache = False
|
|
|
|
try:
|
|
|
|
cred = kinit_keytab(principal, keytab_name, ccache_name)
|
|
|
|
if cred.lifetime > 0:
|
|
|
|
have_ccache = True
|
|
|
|
except gssapi.exceptions.ExpiredCredentialsError:
|
|
|
|
pass
|
|
|
|
if not have_ccache:
|
|
|
|
# delete stale ccache and try again
|
|
|
|
if os.path.exists(oneway_ccache_name):
|
|
|
|
os.unlink(ccache_name)
|
|
|
|
cred = kinit_keytab(principal, keytab_name, ccache_name)
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
old_ccache = os.environ.get('KRB5CCNAME')
|
2015-07-20 09:04:07 -05:00
|
|
|
api.Backend.ldap2.connect(ccache_name)
|
2015-06-05 07:57:02 -05:00
|
|
|
|
trusts: harden trust-fetch-domains oddjobd-based script
When ipa-getkeytab is used to fetch trusted domain object credentials,
the fetched entry has always kvno 1. ipa-getkeytab always adds a key to
keytab which means older key versions will be in the SSSD keytab and
will confuse libkrb5 ccache initialization code as all kvno values are
equal to 1. Wrong key is picked up then and kinit fails.
To solve this problem, always remove existing
/var/lib/sss/keytabs/forest.keytab before retrieving a new one.
To make sure script's input cannot be used to define what should be
removed (by passing a relative path), make sure we retrieve trusted
forest name from LDAP. If it is not possible to retrieve, the script
will issue an exception and quit. If abrtd is running, this will be
recorded as a 'crash' and an attempt to use script by malicious user
would be recorded as well in the abrtd journal.
Additionally, as com.redhat.idm.trust-fetch-domains will create
ID ranges for the domains of the trusted forest if they don't exist,
it needs permissions to do so. The permission should be granted only
to cifs/ipa.master@IPA.REALM services which means they must have
krbprincipalname=cifs/*@IPA.REALM,cn=services,... DN and be members of
cn=adtrust agents,cn=sysaccounts,... group.
Solves https://bugzilla.redhat.com/show_bug.cgi?id=1250190
Ticket https://fedorahosted.org/freeipa/ticket/5182
Reviewed-By: Tomas Babej <tbabej@redhat.com>
2015-08-13 09:18:57 -05:00
|
|
|
# Retrieve own NetBIOS name and trusted forest's name.
|
|
|
|
# We use script's input to retrieve the trusted forest's name to sanitize input
|
|
|
|
# for file-level access as we might need to wipe out keytab in /var/lib/sss/keytabs
|
2015-06-05 07:57:02 -05:00
|
|
|
own_trust_dn = DN(('cn', api.env.domain),('cn','ad'), ('cn', 'etc'), api.env.basedn)
|
|
|
|
own_trust_entry = api.Backend.ldap2.get_entry(own_trust_dn, ['ipantflatname'])
|
trusts: harden trust-fetch-domains oddjobd-based script
When ipa-getkeytab is used to fetch trusted domain object credentials,
the fetched entry has always kvno 1. ipa-getkeytab always adds a key to
keytab which means older key versions will be in the SSSD keytab and
will confuse libkrb5 ccache initialization code as all kvno values are
equal to 1. Wrong key is picked up then and kinit fails.
To solve this problem, always remove existing
/var/lib/sss/keytabs/forest.keytab before retrieving a new one.
To make sure script's input cannot be used to define what should be
removed (by passing a relative path), make sure we retrieve trusted
forest name from LDAP. If it is not possible to retrieve, the script
will issue an exception and quit. If abrtd is running, this will be
recorded as a 'crash' and an attempt to use script by malicious user
would be recorded as well in the abrtd journal.
Additionally, as com.redhat.idm.trust-fetch-domains will create
ID ranges for the domains of the trusted forest if they don't exist,
it needs permissions to do so. The permission should be granted only
to cifs/ipa.master@IPA.REALM services which means they must have
krbprincipalname=cifs/*@IPA.REALM,cn=services,... DN and be members of
cn=adtrust agents,cn=sysaccounts,... group.
Solves https://bugzilla.redhat.com/show_bug.cgi?id=1250190
Ticket https://fedorahosted.org/freeipa/ticket/5182
Reviewed-By: Tomas Babej <tbabej@redhat.com>
2015-08-13 09:18:57 -05:00
|
|
|
own_trust_flatname = own_trust_entry.single_value.get('ipantflatname').upper()
|
|
|
|
trusted_domain_dn = DN(('cn', trusted_domain.lower()), api.env.container_adtrusts, api.env.basedn)
|
|
|
|
trusted_domain_entry = api.Backend.ldap2.get_entry(trusted_domain_dn, ['cn'])
|
|
|
|
trusted_domain = trusted_domain_entry.single_value.get('cn').lower()
|
2015-06-05 07:57:02 -05:00
|
|
|
|
trusts: harden trust-fetch-domains oddjobd-based script
When ipa-getkeytab is used to fetch trusted domain object credentials,
the fetched entry has always kvno 1. ipa-getkeytab always adds a key to
keytab which means older key versions will be in the SSSD keytab and
will confuse libkrb5 ccache initialization code as all kvno values are
equal to 1. Wrong key is picked up then and kinit fails.
To solve this problem, always remove existing
/var/lib/sss/keytabs/forest.keytab before retrieving a new one.
To make sure script's input cannot be used to define what should be
removed (by passing a relative path), make sure we retrieve trusted
forest name from LDAP. If it is not possible to retrieve, the script
will issue an exception and quit. If abrtd is running, this will be
recorded as a 'crash' and an attempt to use script by malicious user
would be recorded as well in the abrtd journal.
Additionally, as com.redhat.idm.trust-fetch-domains will create
ID ranges for the domains of the trusted forest if they don't exist,
it needs permissions to do so. The permission should be granted only
to cifs/ipa.master@IPA.REALM services which means they must have
krbprincipalname=cifs/*@IPA.REALM,cn=services,... DN and be members of
cn=adtrust agents,cn=sysaccounts,... group.
Solves https://bugzilla.redhat.com/show_bug.cgi?id=1250190
Ticket https://fedorahosted.org/freeipa/ticket/5182
Reviewed-By: Tomas Babej <tbabej@redhat.com>
2015-08-13 09:18:57 -05:00
|
|
|
# At this point if we didn't find trusted forest name, an exception will be raised
|
|
|
|
# and script will quit. This is actually intended.
|
|
|
|
|
|
|
|
oneway_keytab_name = '/var/lib/sss/keytabs/' + trusted_domain + '.keytab'
|
2015-06-05 07:57:02 -05:00
|
|
|
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)
|
|
|
|
|
|
|
|
try:
|
2015-07-20 09:04:07 -05:00
|
|
|
have_ccache = False
|
|
|
|
try:
|
|
|
|
# The keytab may have stale key material (from older trust-add run)
|
|
|
|
cred = kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
|
|
|
|
if cred.lifetime > 0:
|
|
|
|
have_ccache = True
|
|
|
|
except gssapi.exceptions.ExpiredCredentialsError:
|
|
|
|
pass
|
|
|
|
if not have_ccache:
|
|
|
|
if os.path.exists(oneway_ccache_name):
|
|
|
|
os.unlink(oneway_ccache_name)
|
|
|
|
kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
|
|
|
|
except gssapi.exceptions.GSSError:
|
2015-06-05 07:57:02 -05:00
|
|
|
# If there was failure on using keytab, assume it is stale and retrieve again
|
|
|
|
retrieve_keytab(api, ccache_name, oneway_keytab_name, oneway_principal)
|
2015-07-20 09:04:07 -05:00
|
|
|
if os.path.exists(oneway_ccache_name):
|
|
|
|
os.unlink(oneway_ccache_name)
|
|
|
|
kinit_keytab(oneway_principal, oneway_keytab_name, oneway_ccache_name)
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
# We are done: we have ccache with TDO credentials and can fetch domains
|
|
|
|
ipa_domain = api.env.domain
|
|
|
|
os.environ['KRB5CCNAME'] = oneway_ccache_name
|
2016-09-01 11:14:22 -05:00
|
|
|
|
|
|
|
# retrieve the forest root domain name and contact it to retrieve trust
|
|
|
|
# topology info
|
|
|
|
forest_root = get_forest_root_domain(api, trusted_domain)
|
|
|
|
|
|
|
|
domains = dcerpc.fetch_domains(api, ipa_domain, forest_root, creds=True)
|
2016-06-06 03:41:46 -05:00
|
|
|
trust_domain_object = api.Command.trust_show(trusted_domain, raw=True)['result']
|
|
|
|
trust.add_new_domains_from_trust(api, None, trust_domain_object, domains)
|
2015-06-05 07:57:02 -05:00
|
|
|
|
|
|
|
if old_ccache:
|
|
|
|
os.environ['KRB5CCNAME'] = old_ccache
|
|
|
|
|
|
|
|
sys.exit(0)
|