mirror of
https://salsa.debian.org/freeipa-team/freeipa.git
synced 2025-02-25 18:55:28 -06:00
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>
This commit is contained in:
@@ -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 \
|
||||
|
||||
10
install/share/anon-princ-aci.ldif
Normal file
10
install/share/anon-princ-aci.ldif
Normal file
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 = (
|
||||
|
||||
@@ -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):
|
||||
|
||||
Reference in New Issue
Block a user