diff options
author | Simo Sorce <simo@redhat.com> | 2016-12-02 06:48:35 -0500 |
---|---|---|
committer | Jan Cholasta <jcholast@redhat.com> | 2017-02-15 07:13:37 +0100 |
commit | b6741d81e187fc84177c12ef8ad900d3b5cda6a4 (patch) | |
tree | 32e5c708bb5f5c2d3552d34c881facc890ee4cf8 | |
parent | b109f5d850ce13585d4392ca48896dc069a746e5 (diff) | |
download | freeipa-b6741d81e187fc84177c12ef8ad900d3b5cda6a4.tar.gz freeipa-b6741d81e187fc84177c12ef8ad900d3b5cda6a4.tar.xz freeipa-b6741d81e187fc84177c12ef8ad900d3b5cda6a4.zip |
Use Anonymous user to obtain FAST armor ccache
The anonymous user allows the framework to obtain an armor ccache without
relying on usable credentials, either via a keytab or a pkinit and
public certificates. This will be needed once the HTTP keytab is moved away
for privilege separation.
https://fedorahosted.org/freeipa/ticket/5959
Signed-off-by: Simo Sorce <simo@redhat.com>
Reviewed-By: Jan Cholasta <jcholast@redhat.com>
-rw-r--r-- | install/share/Makefile.am | 1 | ||||
-rw-r--r-- | install/share/anon-princ-aci.ldif | 10 | ||||
-rw-r--r-- | install/updates/20-aci.update | 6 | ||||
-rw-r--r-- | ipalib/constants.py | 3 | ||||
-rw-r--r-- | ipalib/install/kinit.py | 30 | ||||
-rw-r--r-- | ipaplatform/base/paths.py | 1 | ||||
-rw-r--r-- | ipaserver/install/httpinstance.py | 13 | ||||
-rw-r--r-- | ipaserver/install/krbinstance.py | 5 | ||||
-rw-r--r-- | ipaserver/install/server/upgrade.py | 1 | ||||
-rw-r--r-- | ipaserver/install/service.py | 16 | ||||
-rw-r--r-- | ipaserver/plugins/pkinit.py | 3 | ||||
-rw-r--r-- | ipaserver/rpcserver.py | 29 |
12 files changed, 91 insertions, 27 deletions
diff --git a/install/share/Makefile.am b/install/share/Makefile.am index 72f474a47..bb09c9882 100644 --- a/install/share/Makefile.am +++ b/install/share/Makefile.am @@ -27,6 +27,7 @@ dist_app_DATA = \ 70topology.ldif \ 71idviews.ldif \ 72domainlevels.ldif \ + anon-princ-aci.ldif \ bootstrap-template.ldif \ ca-topology.uldif \ caJarSigningCert.cfg.template \ diff --git a/install/share/anon-princ-aci.ldif b/install/share/anon-princ-aci.ldif new file mode 100644 index 000000000..384a500be --- /dev/null +++ b/install/share/anon-princ-aci.ldif @@ -0,0 +1,10 @@ +dn: krbPrincipalName=WELLKNOWN/ANONYMOUS@$REALM,cn=$REALM,cn=kerberos,$SUFFIX +changetype: modify +add: objectclass +objectclass: ipaAllowedOperations +- +add: aci +aci: (targetattr="ipaProtectedOperation;read_keys")(version 3.0; acl "Allow to retrieve keytab keys of the anonymous user"; allow(read) userattr="ipaAllowedToPerform;read_keys#GROUPDN";) +- +add: ipaAllowedToPerform;read_keys +ipaAllowedToPerform;read_keys: cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX diff --git a/install/updates/20-aci.update b/install/updates/20-aci.update index e9c10f54a..a15f9fec2 100644 --- a/install/updates/20-aci.update +++ b/install/updates/20-aci.update @@ -147,3 +147,9 @@ add:aci: (target = "ldap:///cn=*/($$dn),cn=dogtag,cn=custodia,cn=ipa,cn=etc,$SUF # Dogtag service principals can search Custodia keys add:aci: (target = "ldap:///cn=*,cn=custodia,cn=ipa,cn=etc,$SUFFIX")(targetattr = "ipaPublicKey || ipaKeyUsage || memberPrincipal")(version 3.0; acl "Dogtag service principals can search Custodia keys"; allow(read, search, compare) userdn = "ldap:///krbprincipalname=dogtag/*@$REALM,cn=services,cn=accounts,$SUFFIX";) + +# Anonymous Principal key retrieval +dn: krbPrincipalName=WELLKNOWN/ANONYMOUS@$REALM,cn=$REALM,cn=kerberos,$SUFFIX +addifexist: objectclass: ipaAllowedOperations +addifexist: aci: (targetattr="ipaProtectedOperation;read_keys")(version 3.0; acl "Allow to retrieve keytab keys of the anonymous user"; allow(read) userattr="ipaAllowedToPerform;read_keys#GROUPDN";) +addifexist: ipaAllowedToPerform;read_keys: cn=ipaservers,cn=hostgroups,cn=accounts,$SUFFIX diff --git a/ipalib/constants.py b/ipalib/constants.py index 81643da1b..c67340751 100644 --- a/ipalib/constants.py +++ b/ipalib/constants.py @@ -276,3 +276,6 @@ RENEWAL_CA_NAME = 'dogtag-ipa-ca-renew-agent' # regexp definitions PATTERN_GROUPUSER_NAME = '^[a-zA-Z0-9_.][a-zA-Z0-9_.-]*[a-zA-Z0-9_.$-]?$' + +# Kerberos Anonymous principal name +ANON_USER = 'WELLKNOWN/ANONYMOUS' diff --git a/ipalib/install/kinit.py b/ipalib/install/kinit.py index 2c59b5e13..1e4d1a82f 100644 --- a/ipalib/install/kinit.py +++ b/ipalib/install/kinit.py @@ -7,6 +7,7 @@ import time import gssapi +from ipalib.constants import ANON_USER from ipaplatform.paths import paths from ipapython.ipa_log_manager import root_logger from ipapython.ipautil import run @@ -61,7 +62,6 @@ def kinit_keytab(principal, keytab, ccache_name, config=None, attempts=1): else: os.environ.pop('KRB5_CONFIG', None) - def kinit_password(principal, password, ccache_name, config=None, armor_ccache_name=None, canonicalize=False, enterprise=False): @@ -95,3 +95,31 @@ def kinit_password(principal, password, ccache_name, config=None, capture_error=True) if result.returncode: raise RuntimeError(result.error_output) + + +def kinit_armor(ccache_name): + """ + perform kinit to obtain anonymous ticket to be used as armor for FAST. + """ + root_logger.debug("Initializing anonymous ccache") + + env = {'LC_ALL': 'C'} + # try with the keytab first and then again fallback to try with pkinit in + # case someone decided it is fun to remove Anonymous keys from the entry + # or in future pkinit enabled principal enforce the use of pkinit + try: + # Gssapi does not understand anonymous cred use kinit command instead + args = [paths.KINIT, '-k', '-t', paths.ANON_KEYTAB, + ANON_USER, '-c', ccache_name] + run(args, env=env, raiseonerr=True, capture_error=True) + return + except Exception as e: + root_logger.debug("Failed to init Anonymous keytab: %s", e, + exc_info=True) + + root_logger.debug("Fallback to slower Anonymous PKINIT") + args = [paths.KINIT, '-n', '-c', ccache_name] + + # this workaround enables us to capture stderr and put it + # into the raised exception in case of unsuccessful authentication + run(args, env=env, raiseonerr=True, capture_error=True) diff --git a/ipaplatform/base/paths.py b/ipaplatform/base/paths.py index d62ffa224..374a1987b 100644 --- a/ipaplatform/base/paths.py +++ b/ipaplatform/base/paths.py @@ -50,6 +50,7 @@ class BasePathNamespace(object): HTTPD_NSS_CONF = "/etc/httpd/conf.d/nss.conf" HTTPD_SSL_CONF = "/etc/httpd/conf.d/ssl.conf" IPA_KEYTAB = "/etc/httpd/conf/ipa.keytab" + ANON_KEYTAB = "/var/lib/ipa/api/anon.keytab" HTTPD_PASSWORD_CONF = "/etc/httpd/conf/password.conf" IDMAPD_CONF = "/etc/idmapd.conf" ETC_IPA = "/etc/ipa" diff --git a/ipaserver/install/httpinstance.py b/ipaserver/install/httpinstance.py index a4e895cb4..d07b32253 100644 --- a/ipaserver/install/httpinstance.py +++ b/ipaserver/install/httpinstance.py @@ -43,6 +43,7 @@ import ipapython.errors from ipaserver.install import sysupgrade from ipalib import api from ipalib import errors +from ipalib.constants import ANON_USER from ipaplatform.constants import constants from ipaplatform.tasks import tasks from ipaplatform.paths import paths @@ -167,6 +168,7 @@ class HTTPInstance(service.Service): self.step("adding URL rewriting rules", self.__add_include) self.step("configuring httpd", self.__configure_http) self.step("setting up httpd keytab", self._request_service_keytab) + self.step("retrieving anonymous keytab", self.request_anon_keytab) self.step("setting up ssl", self.__setup_ssl) if self.ca_is_configured: self.step("configure certmonger for renewals", @@ -333,6 +335,17 @@ class HTTPInstance(service.Service): os.chown(nss_path, 0, pent.pw_gid) tasks.restore_context(nss_path) + def request_anon_keytab(self): + parent = os.path.dirname(paths.ANON_KEYTAB) + if not os.path.exists(parent): + os.makedirs(parent, 0o755) + self.run_getkeytab(self.api.env.ldap_uri, paths.ANON_KEYTAB, ANON_USER) + + pent = pwd.getpwnam(self.service_user) + os.chmod(parent, 0o700) + os.chown(parent, pent.pw_uid, pent.pw_gid) + os.chown(paths.ANON_KEYTAB, pent.pw_uid, pent.pw_gid) + def __setup_ssl(self): db = certs.CertDB(self.realm, subject_base=self.subject_base) if self.pkcs12_info: diff --git a/ipaserver/install/krbinstance.py b/ipaserver/install/krbinstance.py index b52b0c3f9..44b382126 100644 --- a/ipaserver/install/krbinstance.py +++ b/ipaserver/install/krbinstance.py @@ -33,6 +33,7 @@ from ipaserver.install import installutils from ipapython import ipautil from ipapython import kernel_keyring from ipalib import api +from ipalib.constants import ANON_USER from ipalib.install import certmonger from ipapython.ipa_log_manager import root_logger from ipapython.dn import DN @@ -381,13 +382,13 @@ class KrbInstance(service.Service): shutil.copyfile(paths.IPA_CA_CRT, paths.CACERT_PEM) def get_anonymous_principal_name(self): - princ = "WELLKNOWN/ANONYMOUS" - return "%s@%s" % (princ, self.realm) + return "%s@%s" % (ANON_USER, self.realm) def add_anonymous_principal(self): # Create the special anonymous principal princ_realm = self.get_anonymous_principal_name() installutils.kadmin_addprinc(princ_realm) + self._ldap_mod("anon-princ-aci.ldif", self.sub_dict) def __convert_to_gssapi_replication(self): repl = replication.ReplicationManager(self.realm, diff --git a/ipaserver/install/server/upgrade.py b/ipaserver/install/server/upgrade.py index c7f0f9f44..80abeba53 100644 --- a/ipaserver/install/server/upgrade.py +++ b/ipaserver/install/server/upgrade.py @@ -1757,6 +1757,7 @@ def upgrade_configuration(): krb.stop() krb.start() enable_anonymous_principal(krb) + http.request_anon_keytab() if not ds_running: ds.stop(ds_serverid) diff --git a/ipaserver/install/service.py b/ipaserver/install/service.py index b80044f4b..fe6defc9c 100644 --- a/ipaserver/install/service.py +++ b/ipaserver/install/service.py @@ -539,7 +539,7 @@ class Service(object): except errors.DuplicateEntry: pass - def _run_getkeytab(self): + def run_getkeytab(self, ldap_uri, keytab, principal, retrieve=False): """ backup and remove old service keytab (if present) and fetch a new one using ipa-getkeytab. This assumes that the service principal is already @@ -549,16 +549,15 @@ class Service(object): * self.dm_password is not none, then DM credentials are used to fetch keytab """ - self.fstore.backup_file(self.keytab) + self.fstore.backup_file(keytab) try: - os.unlink(self.keytab) + os.unlink(keytab) except OSError: pass - ldap_uri = self.api.env.ldap_uri args = [paths.IPA_GETKEYTAB, - '-k', self.keytab, - '-p', self.principal, + '-k', keytab, + '-p', principal, '-H', ldap_uri] nolog = tuple() @@ -570,6 +569,9 @@ class Service(object): '-w', self.dm_password]) nolog += (self.dm_password,) + if retrieve: + args.extend(['-r']) + ipautil.run(args, nolog=nolog) def _request_service_keytab(self): @@ -580,7 +582,7 @@ class Service(object): "name, keytab, and username") self._add_service_principal() - self._run_getkeytab() + self.run_getkeytab(self.api.env.ldap_uri, self.keytab, self.principal) pent = pwd.getpwnam(self.service_user) os.chown(self.keytab, pent.pw_uid, pent.pw_gid) diff --git a/ipaserver/plugins/pkinit.py b/ipaserver/plugins/pkinit.py index 0ad4b8571..b6b3f3882 100644 --- a/ipaserver/plugins/pkinit.py +++ b/ipaserver/plugins/pkinit.py @@ -22,6 +22,7 @@ from ipalib import Str from ipalib import Object, Command from ipalib import _ from ipalib.plugable import Registry +from ipalib.constants import ANON_USER from ipapython.dn import DN __doc__ = _(""" @@ -71,7 +72,7 @@ def valid_arg(ugettext, action): class pkinit_anonymous(Command): __doc__ = _('Enable or Disable Anonymous PKINIT.') - princ_name = 'WELLKNOWN/ANONYMOUS@%s' % api.env.realm + princ_name = '%s@%s' % (ANON_USER, api.env.realm) default_dn = DN(('krbprincipalname', princ_name), ('cn', api.env.realm), ('cn', 'kerberos'), api.env.basedn) takes_args = ( diff --git a/ipaserver/rpcserver.py b/ipaserver/rpcserver.py index 34106ee86..357e836f9 100644 --- a/ipaserver/rpcserver.py +++ b/ipaserver/rpcserver.py @@ -42,7 +42,7 @@ from six.moves.xmlrpc_client import Fault from ipalib import plugable, errors from ipalib.capabilities import VERSION_WITHOUT_CAPABILITIES from ipalib.frontend import Local -from ipalib.install.kinit import kinit_keytab, kinit_password +from ipalib.install.kinit import kinit_armor, kinit_password from ipalib.backend import Executioner from ipalib.errors import (PublicError, InternalError, JSONError, CCacheError, RefererError, InvalidSessionPassword, NotFound, ACIError, @@ -56,7 +56,7 @@ from ipaserver.plugins.ldap2 import ldap2 from ipalib.backend import Backend from ipalib.krb_utils import ( krb5_format_principal_name, - krb5_format_service_principal_name, get_credentials_if_valid) + get_credentials_if_valid) from ipapython import ipautil from ipaplatform.paths import paths from ipapython.version import VERSION @@ -945,20 +945,18 @@ class login_password(Backend, KerberosSession): return result def kinit(self, user, realm, password, ccache_name): - # get http service ccache as an armor for FAST to enable OTP authentication - armor_principal = str(krb5_format_service_principal_name( - 'HTTP', self.api.env.host, realm)) - keytab = paths.IPA_KEYTAB + # get anonymous ccache as an armor for FAST to enable OTP auth armor_path = os.path.join(paths.IPA_CCACHES, "armor_{}".format(os.getpid())) - self.debug('Obtaining armor ccache: principal=%s keytab=%s ccache=%s', - armor_principal, keytab, armor_path) + self.debug('Obtaining armor in ccache %s', armor_path) try: - kinit_keytab(armor_principal, paths.IPA_KEYTAB, armor_path) - except gssapi.exceptions.GSSError as e: - raise CCacheError(message=unicode(e)) + kinit_armor(armor_path) + except RuntimeError as e: + self.error("Failed to obtain armor cache") + # We try to continue w/o armor, 2FA will be impacted + armor_path = None # Format the user as a kerberos principal principal = krb5_format_principal_name(user, realm) @@ -967,11 +965,10 @@ class login_password(Backend, KerberosSession): kinit_password(principal, password, ccache_name, armor_ccache_name=armor_path) - self.debug('Cleanup the armor ccache') - ipautil.run( - [paths.KDESTROY, '-A', '-c', armor_path], - env={'KRB5CCNAME': armor_path}, - raiseonerr=False) + if armor_path: + self.debug('Cleanup the armor ccache') + ipautil.run([paths.KDESTROY, '-A', '-c', armor_path], + env={'KRB5CCNAME': armor_path}, raiseonerr=False) except RuntimeError as e: if ('kinit: Cannot read password while ' 'getting initial credentials') in str(e): |