def change_principal(principal, password=None, client=None, path=None, canonicalize=False, enterprise=False, keytab=None): """Temporarily change the kerberos principal Most of the test cases run with the admin ipa user which is granted all access and exceptions from rules on some occasions. When the test needs to test for an application of some kind of a restriction it needs to authenticate as a different principal with required set of rights to the operation. The context manager changes the principal identity in two ways: * using password * using keytab If the context manager is to be used with a keytab, the keytab option must be its absolute path. The context manager can be used to authenticate with enterprise principals and aliases when given respective options. """ if path: ccache_name = path else: ccache_name = os.path.join('/tmp', str(uuid.uuid4())) if client is None: client = api client.Backend.rpcclient.disconnect() try: if keytab: kinit_keytab(principal, keytab, ccache_name) else: kinit_password(principal, password, ccache_name, canonicalize=canonicalize, enterprise=enterprise) client.Backend.rpcclient.connect(ccache=ccache_name) try: yield finally: client.Backend.rpcclient.disconnect() finally: # If we generated a ccache name, try to remove it, but don't fail if not path: try: os.remove(ccache_name) except OSError: pass client.Backend.rpcclient.connect()
def temp_kinit(principal, password): """ kinit with password using a temporary ccache """ if not password: raise RuntimeError("The password is not set") if not principal: principal = "admin" ccache_dir = tempfile.mkdtemp(prefix='krbcc') ccache_name = os.path.join(ccache_dir, 'ccache') try: kinit_password(principal, password, ccache_name) except RuntimeError as e: raise RuntimeError("Kerberos authentication failed: {}".format(e)) return ccache_dir, ccache_name
def kinit(self, principal, password, ccache_name): # 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 in ccache %s', armor_path) try: kinit_armor( armor_path, pkinit_anchors=[paths.KDC_CERT, paths.KDC_CA_BUNDLE_PEM], ) 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 try: kinit_password( unicode(principal), password, ccache_name, armor_ccache_name=armor_path, enterprise=True, lifetime=self.api.env.kinit_lifetime) 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): raise PasswordExpired(principal=principal, message=unicode(e)) elif ('kinit: Client\'s entry in database' ' has expired while getting initial credentials') in str(e): raise KrbPrincipalExpired(principal=principal, message=unicode(e)) elif ('kinit: Clients credentials have been revoked ' 'while getting initial credentials') in str(e): raise UserLocked(principal=principal, message=unicode(e)) raise InvalidSessionPassword(principal=principal, message=unicode(e))
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 armor_name = "%sA_%s" % (krbccache_prefix, user) armor_path = os.path.join(krbccache_dir, armor_name) self.debug('Obtaining armor ccache: principal=%s keytab=%s ccache=%s', armor_principal, keytab, armor_path) try: kinit_keytab(armor_principal, paths.IPA_KEYTAB, armor_path) except gssapi.exceptions.GSSError as e: raise CCacheError(message=unicode(e)) # Format the user as a kerberos principal principal = krb5_format_principal_name(user, realm) try: 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) except RuntimeError as e: if ('kinit: Cannot read password while ' 'getting initial credentials') in str(e): raise PasswordExpired(principal=principal, message=unicode(e)) elif ('kinit: Client\'s entry in database' ' has expired while getting initial credentials') in str(e): raise KrbPrincipalExpired(principal=principal, message=unicode(e)) elif ('kinit: Clients credentials have been revoked ' 'while getting initial credentials') in str(e): raise UserLocked(principal=principal, message=unicode(e)) raise InvalidSessionPassword(principal=principal, message=unicode(e))
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 armor_name = "%sA_%s" % (krbccache_prefix, user) armor_path = os.path.join(krbccache_dir, armor_name) self.debug('Obtaining armor ccache: principal=%s keytab=%s ccache=%s', armor_principal, keytab, armor_path) try: kinit_keytab(armor_principal, paths.IPA_KEYTAB, armor_path) except gssapi.exceptions.GSSError as e: raise CCacheError(message=unicode(e)) # Format the user as a kerberos principal principal = krb5_format_principal_name(user, realm) try: 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) except RuntimeError as e: if ('kinit: Cannot read password while ' 'getting initial credentials') in str(e): raise PasswordExpired(principal=principal, message=unicode(e)) elif ('kinit: Client\'s entry in database' ' has expired while getting initial credentials') in str(e): raise KrbPrincipalExpired(principal=principal, message=unicode(e)) elif ('kinit: Clients credentials have been revoked ' 'while getting initial credentials') in str(e): raise UserLocked(principal=principal, message=unicode(e)) raise InvalidSessionPassword(principal=principal, message=unicode(e))
def check_creds(options, realm_name): # Check if ccache is available default_cred = None try: logger.debug('KRB5CCNAME set to %s', os.environ.get('KRB5CCNAME', None)) # get default creds, will raise if none found default_cred = gssapi.creds.Credentials() principal = str(default_cred.name) except gssapi.raw.misc.GSSError as e: logger.debug('Failed to find default ccache: %s', e) principal = None # Check if the principal matches the requested one (if any) if principal is not None and options.principal is not None: op = options.principal if op.find('@') == -1: op = '%s@%s' % (op, realm_name) if principal != op: logger.debug('Specified principal %s does not match ' 'available credentials (%s)', options.principal, principal) principal = None if principal is None: (ccache_fd, ccache_name) = tempfile.mkstemp() os.close(ccache_fd) options.created_ccache_file = ccache_name if options.principal is not None: principal = options.principal else: principal = 'admin' stdin = None if principal.find('@') == -1: principal = '%s@%s' % (principal, realm_name) if options.admin_password is not None: stdin = options.admin_password else: if not options.unattended: try: stdin = getpass.getpass("Password for %s: " % principal) except EOFError: stdin = None if not stdin: logger.error( "Password must be provided for %s.", principal) raise ScriptError("Missing password for %s" % principal) else: if sys.stdin.isatty(): logger.error("Password must be provided in " "non-interactive mode.") logger.info("This can be done via " "echo password | ipa-client-install " "... or with the -w option.") raise ScriptError("Missing password for %s" % principal) else: stdin = sys.stdin.readline() # set options.admin_password for future use options.admin_password = stdin try: kinit_password(principal, stdin, ccache_name) except RuntimeError as e: logger.error("Kerberos authentication failed: %s", e) raise ScriptError("Invalid credentials: %s" % e) os.environ['KRB5CCNAME'] = ccache_name
def main(): module = AnsibleModule( argument_spec = dict( servers=dict(required=True, type='list'), domain=dict(required=True), realm=dict(required=True), hostname=dict(required=True), kdc=dict(required=True), basedn=dict(required=True), principal=dict(required=False), password=dict(required=False, no_log=True), keytab=dict(required=False), ca_cert_file=dict(required=False), force_join=dict(required=False, type='bool'), kinit_attempts=dict(required=False, type='int', default=5), debug=dict(required=False, type='bool'), ), supports_check_mode = True, ) module._ansible_debug = True servers = module.params.get('servers') domain = module.params.get('domain') realm = module.params.get('realm') hostname = module.params.get('hostname') basedn = module.params.get('basedn') kdc = module.params.get('kdc') force_join = module.params.get('force_join') principal = module.params.get('principal') password = module.params.get('password') keytab = module.params.get('keytab') ca_cert_file = module.params.get('ca_cert_file') kinit_attempts = module.params.get('kinit_attempts') debug = module.params.get('debug') if password is not None and password != "" and \ keytab is not None and keytab != "": module.fail_json(msg="Password and keytab cannot be used together") client_domain = hostname[hostname.find(".")+1:] nolog = tuple() env = {'PATH': SECURE_PATH} fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE) host_principal = 'host/%s@%s' % (hostname, realm) sssd = True options.ca_cert_file = ca_cert_file options.unattended = True options.principal = principal if principal != "" else None options.force = False options.password = password ccache_dir = None changed = False already_joined = False try: (krb_fd, krb_name) = tempfile.mkstemp() os.close(krb_fd) configure_krb5_conf( cli_realm=realm, cli_domain=domain, cli_server=servers, cli_kdc=kdc, dnsok=False, filename=krb_name, client_domain=client_domain, client_hostname=hostname, configure_sssd=sssd, force=False) env['KRB5_CONFIG'] = krb_name ccache_dir = tempfile.mkdtemp(prefix='krbcc') ccache_name = os.path.join(ccache_dir, 'ccache') join_args = [paths.SBIN_IPA_JOIN, "-s", servers[0], "-b", str(realm_to_suffix(realm)), "-h", hostname] if debug: join_args.append("-d") env['XMLRPC_TRACE_CURL'] = 'yes' if force_join: join_args.append("-f") if principal: if principal.find('@') == -1: principal = '%s@%s' % (principal, realm) try: kinit_password(principal, password, ccache_name, config=krb_name) except RuntimeError as e: module.fail_json( msg="Kerberos authentication failed: {}".format(e)) elif keytab: join_args.append("-f") if os.path.exists(keytab): try: kinit_keytab(host_principal, keytab, ccache_name, config=krb_name, attempts=kinit_attempts) except gssapi.exceptions.GSSError as e: module.fail_json( msg="Kerberos authentication failed: {}".format(e)) else: module.fail_json( msg="Keytab file could not be found: {}".format(keytab)) elif password: join_args.append("-w") join_args.append(password) nolog = (password,) env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = ccache_name # Get the CA certificate try: os.environ['KRB5_CONFIG'] = env['KRB5_CONFIG'] if NUM_VERSION < 40100: get_ca_cert(fstore, options, servers[0], basedn) else: get_ca_certs(fstore, options, servers[0], basedn, realm) del os.environ['KRB5_CONFIG'] except errors.FileError as e: module.fail_json(msg='%s' % e) except Exception as e: module.fail_json(msg="Cannot obtain CA certificate\n%s" % e) # Now join the domain result = run( join_args, raiseonerr=False, env=env, nolog=nolog, capture_error=True) stderr = result.error_output if result.returncode != 0: if result.returncode == 13: already_joined = True module.log("Host is already joined") else: if principal: run(["kdestroy"], raiseonerr=False, env=env) module.fail_json(msg="Joining realm failed: %s" % stderr) else: changed = True module.log("Enrolled in IPA realm %s" % realm) # Fail for missing krb5.keytab on already joined host if already_joined and not os.path.exists(paths.KRB5_KEYTAB): module.fail_json(msg="krb5.keytab missing! Retry with ipaclient_force_join=yes to generate a new one.") if principal: run(["kdestroy"], raiseonerr=False, env=env) # Obtain the TGT. We do it with the temporary krb5.conf, sot # tha only the KDC we're installing under is contacted. # Other KDCs might not have replicated the principal yet. # Once we have the TGT, it's usable on any server. try: kinit_keytab(host_principal, paths.KRB5_KEYTAB, paths.IPA_DNS_CCACHE, config=krb_name, attempts=kinit_attempts) env['KRB5CCNAME'] = os.environ['KRB5CCNAME'] = paths.IPA_DNS_CCACHE except gssapi.exceptions.GSSError as e: # failure to get ticket makes it impossible to login and # bind from sssd to LDAP, abort installation module.fail_json(msg="Failed to obtain host TGT: %s" % e) finally: try: os.remove(krb_name) except OSError: module.fail_json(msg="Could not remove %s" % krb_name) if ccache_dir is not None: try: os.rmdir(ccache_dir) except OSError: pass if os.path.exists(krb_name + ".ipabkp"): try: os.remove(krb_name + ".ipabkp") except OSError: module.fail_json(msg="Could not remove %s.ipabkp" % krb_name) module.exit_json(changed=changed, already_joined=already_joined)