class baseuser(LDAPObject): """ baseuser object. """ stage_container_dn = api.env.container_stageuser active_container_dn = api.env.container_user delete_container_dn = api.env.container_deleteuser object_class = ['posixaccount'] object_class_config = 'ipauserobjectclasses' possible_objectclasses = [ 'meporiginentry', 'ipauserauthtypeclass', 'ipauser', 'ipatokenradiusproxyuser', 'ipacertmapobject', 'ipantuserattrs', ] disallow_object_classes = ['krbticketpolicyaux'] permission_filter_objectclasses = ['posixaccount'] search_attributes_config = 'ipausersearchfields' default_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'loginshell', 'uidnumber', 'gidnumber', 'mail', 'ou', 'telephonenumber', 'title', 'memberof', 'nsaccountlock', 'memberofindirect', 'ipauserauthtype', 'userclass', 'ipatokenradiusconfiglink', 'ipatokenradiususername', 'krbprincipalexpiration', 'usercertificate;binary', 'krbprincipalname', 'krbcanonicalname', 'ipacertmapdata', 'ipantlogonscript', 'ipantprofilepath', 'ipanthomedirectory', 'ipanthomedirectorydrive', ] search_display_attributes = [ 'uid', 'givenname', 'sn', 'homedirectory', 'krbcanonicalname', 'krbprincipalname', 'loginshell', 'mail', 'telephonenumber', 'title', 'nsaccountlock', 'uidnumber', 'gidnumber', 'sshpubkeyfp', ] uuid_attribute = 'ipauniqueid' attribute_members = { 'manager': ['user'], 'memberof': [ 'group', 'netgroup', 'role', 'hbacrule', 'sudorule', 'subid' ], 'memberofindirect': ['group', 'netgroup', 'role', 'hbacrule', 'sudorule'], } allow_rename = True bindable = True password_attributes = [('userpassword', 'has_password'), ('krbprincipalkey', 'has_keytab')] label = _('Users') label_singular = _('User') takes_params = ( Str('uid', pattern=constants.PATTERN_GROUPUSER_NAME, pattern_errmsg='may only include letters, numbers, _, -, . and $', maxlength=255, cli_name='login', label=_('User login'), primary_key=True, default_from=lambda givenname, sn: givenname[0] + sn, normalizer=lambda value: value.lower(), ), Str('givenname', cli_name='first', label=_('First name'), ), Str('sn', cli_name='last', label=_('Last name'), ), Str('cn', label=_('Full name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('displayname?', label=_('Display name'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('initials?', label=_('Initials'), default_from=lambda givenname, sn: '%c%c' % (givenname[0], sn[0]), autofill=True, ), Str('homedirectory?', cli_name='homedir', label=_('Home directory'), ), Str('gecos?', label=_('GECOS'), default_from=lambda givenname, sn: '%s %s' % (givenname, sn), autofill=True, ), Str('loginshell?', cli_name='shell', label=_('Login shell'), ), Principal( 'krbcanonicalname?', validate_realm, label=_('Principal name'), flags={'no_option', 'no_create', 'no_update', 'no_search'}, normalizer=normalize_user_principal ), Principal( 'krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), default_from=lambda uid: kerberos.Principal( uid.lower(), realm=api.env.realm), autofill=True, normalizer=normalize_user_principal, ), DateTime('krbprincipalexpiration?', cli_name='principal_expiration', label=_('Kerberos principal expiration'), ), DateTime('krbpasswordexpiration?', cli_name='password_expiration', label=_('User password expiration'), ), Str('mail*', cli_name='email', label=_('Email address'), ), Password('userpassword?', cli_name='password', label=_('Password'), doc=_('Prompt to set the user password'), # FIXME: This is temporary till bug is fixed causing updates to # bomb out via the webUI. exclude='webui', ), Flag('random?', doc=_('Generate a random user password'), flags=('no_search', 'virtual_attribute'), default=False, ), Str('randompassword?', label=_('Random password'), flags=('no_create', 'no_update', 'no_search', 'virtual_attribute'), ), Int('uidnumber?', cli_name='uid', label=_('UID'), doc=_('User ID Number (system will assign one if not provided)'), minvalue=1, ), Int('gidnumber?', label=_('GID'), doc=_('Group ID Number'), minvalue=1, ), Str('street?', cli_name='street', label=_('Street address'), ), Str('l?', cli_name='city', label=_('City'), ), Str('st?', cli_name='state', label=_('State/Province'), ), Str('postalcode?', label=_('ZIP'), ), Str('telephonenumber*', cli_name='phone', label=_('Telephone Number') ), Str('mobile*', label=_('Mobile Telephone Number') ), Str('pager*', label=_('Pager Number') ), Str('facsimiletelephonenumber*', cli_name='fax', label=_('Fax Number'), ), Str('ou?', cli_name='orgunit', label=_('Org. Unit'), ), Str('title?', label=_('Job Title'), ), # keep backward compatibility using single value manager option Str('manager?', label=_('Manager'), ), Str('carlicense*', label=_('Car License'), ), Str('ipasshpubkey*', validate_sshpubkey, cli_name='sshpubkey', label=_('SSH public key'), normalizer=normalize_sshpubkey, flags=['no_search'], ), Str('sshpubkeyfp*', label=_('SSH public key fingerprint'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum( 'ipauserauthtype*', cli_name='user_auth_type', label=_('User authentication types'), doc=_('Types of supported user authentication'), values=(u'password', u'radius', u'otp', u'pkinit', u'hardened'), ), Str('userclass*', cli_name='class', label=_('Class'), doc=_('User category (semantics placed on this attribute are for ' 'local interpretation)'), ), Str('ipatokenradiusconfiglink?', cli_name='radius', label=_('RADIUS proxy configuration'), ), Str('ipatokenradiususername?', cli_name='radius_username', label=_('RADIUS proxy username'), ), Str('departmentnumber*', label=_('Department Number'), ), Str('employeenumber?', label=_('Employee Number'), ), Str('employeetype?', label=_('Employee Type'), ), Str('preferredlanguage?', label=_('Preferred Language'), pattern=( r'^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?' r'(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' r'(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?' r'(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?)*)|(\*))$' ), pattern_errmsg='must match RFC 2068 - 14.4, e.g., "da, en-gb;q=0.8, en;q=0.7"', ), Certificate('usercertificate*', cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded user certificate'), ), Str( 'ipacertmapdata*', cli_name='certmapdata', label=_('Certificate mapping data'), doc=_('Certificate mapping data'), flags=['no_create', 'no_update', 'no_search'], ), Str('ipantlogonscript?', cli_name='smb_logon_script', label=_('SMB logon script path'), flags=['no_create'], ), Str('ipantprofilepath?', cli_name='smb_profile_path', label=_('SMB profile path'), flags=['no_create'], ), Str('ipanthomedirectory?', cli_name='smb_home_dir', label=_('SMB Home Directory'), flags=['no_create'], ), StrEnum('ipanthomedirectorydrive?', cli_name='smb_home_drive', label=_('SMB Home Directory Drive'), flags=['no_create'], values=( 'A:', 'B:', 'C:', 'D:', 'E:', 'F:', 'G:', 'H:', 'I:', 'J:', 'K:', 'L:', 'M:', 'N:', 'O:', 'P:', 'Q:', 'R:', 'S:', 'T:', 'U:', 'V:', 'W:', 'X:', 'Y:', 'Z:'), ), ) def normalize_and_validate_email(self, email, config=None): if not config: config = self.backend.get_ipa_config() # check if default email domain should be added defaultdomain = config.get('ipadefaultemaildomain', [None])[0] if email: norm_email = [] if not isinstance(email, (list, tuple)): email = [email] for m in email: if isinstance(m, str): if '@' not in m and defaultdomain: m = m + u'@' + defaultdomain if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) else: if not Email(m): raise errors.ValidationError(name='email', error=_('invalid e-mail format: %(email)s') % dict(email=m)) norm_email.append(m) return norm_email return email def normalize_manager(self, manager, container): """ Given a userid verify the user's existence (in the appropriate containter) and return the dn. """ if not manager: return None if not isinstance(manager, list): manager = [manager] try: container_dn = DN(container, api.env.basedn) for i, mgr in enumerate(manager): if isinstance(mgr, DN) and mgr.endswith(container_dn): continue entry_attrs = self.backend.find_entry_by_attr( self.primary_key.name, mgr, self.object_class, [''], container_dn ) manager[i] = entry_attrs.dn except errors.NotFound: raise errors.NotFound(reason=_('manager %(manager)s not found') % dict(manager=mgr)) return manager def _user_status(self, user, container): assert isinstance(user, DN) return user.endswith(container) def active_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.active_container_dn, api.env.basedn)) def stage_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.stage_container_dn, api.env.basedn)) def delete_user(self, user): assert isinstance(user, DN) return self._user_status(user, DN(self.delete_container_dn, api.env.basedn)) def convert_usercertificate_pre(self, entry_attrs): if 'usercertificate' in entry_attrs: entry_attrs['usercertificate;binary'] = entry_attrs.pop( 'usercertificate') def convert_usercertificate_post(self, entry_attrs, **options): if 'usercertificate;binary' in entry_attrs: entry_attrs['usercertificate'] = entry_attrs.pop( 'usercertificate;binary') def convert_attribute_members(self, entry_attrs, *keys, **options): super(baseuser, self).convert_attribute_members( entry_attrs, *keys, **options) if options.get("raw", False): return # due the backward compatibility, managers have to be returned in # 'manager' attribute instead of 'manager_user' try: entry_attrs['failed_manager'] = entry_attrs.pop('manager') except KeyError: pass try: entry_attrs['manager'] = entry_attrs.pop('manager_user') except KeyError: pass
[--user <user>|--service <service>|--shared] [--users <users>] [--groups <groups>] [--services <services>] """) + _(""" Delete vault members: ipa vault-remove-member <name> [--user <user>|--service <service>|--shared] [--users <users>] [--groups <groups>] [--services <services>] """) register = Registry() vault_options = ( Principal( 'service?', validate_realm, doc=_('Service name of the service vault'), normalizer=normalize_principal, ), Flag( 'shared?', doc=_('Shared vault'), ), Str( 'username?', cli_name='user', doc=_('Username of the user vault'), ), ) class VaultModMember(LDAPModMember):
class cert_get_requestdata(Local): __doc__ = _('Gather data for a certificate signing request.') takes_options = ( Principal( 'principal', label=_('Principal'), doc=_('Principal for this certificate (e.g.' ' HTTP/test.example.com)'), ), Str( 'profile_id', label=_('Profile ID'), doc=_('CSR Generation Profile to use'), ), Str( 'helper', label=_('Name of CSR generation tool'), doc=_('Name of tool (e.g. openssl, certutil) that will be used to' ' create CSR'), ), Str( 'out?', doc=_('Write CSR generation script to file'), ), ) has_output = (output.Output( 'result', type=dict, doc=_('Dictionary mapping variable name to value'), ), ) has_output_params = (Str( 'script', label=_('Generation script'), )) def execute(self, *args, **options): if 'out' in options: util.check_writable_file(options['out']) principal = options.get('principal') profile_id = options.get('profile_id') helper = options.get('helper') if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() try: if principal.is_host: principal_obj = api.Command.host_show(principal.hostname, all=True) elif principal.is_service: principal_obj = api.Command.service_show(unicode(principal), all=True) elif principal.is_user: principal_obj = api.Command.user_show(principal.username, all=True) except errors.NotFound: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] config = api.Command.config_show()['result'] generator = CSRGenerator(FileRuleProvider()) script = generator.csr_script(principal_obj, config, profile_id, helper) result = {} if 'out' in options: with open(options['out'], 'wb') as f: f.write(script) else: result = dict(script=script) return dict(result=result)
class service(LDAPObject): """ Service object. """ container_dn = api.env.container_service object_name = _('service') object_name_plural = _('services') object_class = [ 'krbprincipal', 'krbprincipalaux', 'krbticketpolicyaux', 'ipaobject', 'ipaservice', 'pkiuser' ] possible_objectclasses = ['ipakrbprincipal', 'ipaallowedoperations'] permission_filter_objectclasses = ['ipaservice'] search_attributes = ['krbprincipalname', 'managedby', 'ipakrbauthzdata'] default_attributes = [ 'krbprincipalname', 'krbcanonicalname', 'usercertificate', 'managedby', 'ipakrbauthzdata', 'memberof', 'ipaallowedtoperform', 'krbprincipalauthind' ] uuid_attribute = 'ipauniqueid' attribute_members = { 'managedby': ['host'], 'memberof': ['role'], 'ipaallowedtoperform_read_keys': ['user', 'group', 'host', 'hostgroup'], 'ipaallowedtoperform_write_keys': ['user', 'group', 'host', 'hostgroup'], } bindable = True relationships = { 'managedby': ('Managed by', 'man_by_', 'not_man_by_'), 'ipaallowedtoperform_read_keys': ('Allow to retrieve keytab by', 'retrieve_keytab_by_', 'not_retrieve_keytab_by_'), 'ipaallowedtoperform_write_keys': ('Allow to create keytab by', 'write_keytab_by_', 'not_write_keytab_by'), } password_attributes = [('krbprincipalkey', 'has_keytab')] managed_permissions = { 'System: Read Services': { 'replaces_global_anonymous_aci': True, 'ipapermbindruletype': 'all', 'ipapermright': {'read', 'search', 'compare'}, 'ipapermdefaultattr': { 'objectclass', 'ipauniqueid', 'managedby', 'memberof', 'usercertificate', 'krbprincipalname', 'krbcanonicalname', 'krbprincipalaliases', 'krbprincipalexpiration', 'krbpasswordexpiration', 'krblastpwdchange', 'ipakrbauthzdata', 'ipakrbprincipalalias', 'krbobjectreferences', 'krbprincipalauthind', }, }, 'System: Add Services': { 'ipapermright': {'add'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Add Services";allow (add) groupdn = "ldap:///cn=Add Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Keytab': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krblastpwdchange', 'krbprincipalkey'}, 'replaces': [ '(targetattr = "krbprincipalkey || krblastpwdchange")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Manage service keytab";allow (write) groupdn = "ldap:///cn=Manage service keytab,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Manage Service Keytab Permissions': { 'ipapermright': {'read', 'search', 'compare', 'write'}, 'ipapermdefaultattr': { 'ipaallowedtoperform;write_keys', 'ipaallowedtoperform;read_keys', 'objectclass' }, 'default_privileges': {'Service Administrators', 'Host Administrators'}, }, 'System: Modify Services': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'usercertificate', 'krbprincipalauthind'}, 'replaces': [ '(targetattr = "usercertificate")(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Modify Services";allow (write) groupdn = "ldap:///cn=Modify Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, 'System: Manage Service Principals': { 'ipapermright': {'write'}, 'ipapermdefaultattr': {'krbprincipalname', 'krbcanonicalname'}, 'default_privileges': { 'Service Administrators', }, }, 'System: Remove Services': { 'ipapermright': {'delete'}, 'replaces': [ '(target = "ldap:///krbprincipalname=*,cn=services,cn=accounts,$SUFFIX")(version 3.0;acl "permission:Remove Services";allow (delete) groupdn = "ldap:///cn=Remove Services,cn=permissions,cn=pbac,$SUFFIX";)', ], 'default_privileges': {'Service Administrators'}, }, } label = _('Services') label_singular = _('Service') takes_params = ( Principal('krbcanonicalname', validate_realm, cli_name='canonical_principal', label=_('Principal name'), doc=_('Service principal'), primary_key=True, normalizer=normalize_principal, require_service=True), Principal('krbprincipalname*', validate_realm, cli_name='principal', label=_('Principal alias'), doc=_('Service principal alias'), normalizer=normalize_principal, require_service=True, flags={'no_create'}), Bytes( 'usercertificate*', validate_certificate, cli_name='certificate', label=_('Certificate'), doc=_('Base-64 encoded service certificate'), flags=[ 'no_search', ], ), Str( 'subject', label=_('Subject'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number', label=_('Serial Number'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number_hex', label=_('Serial Number (hex)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'issuer', label=_('Issuer'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'valid_not_before', label=_('Not Before'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'valid_not_after', label=_('Not After'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'sha256_fingerprint', label=_('Fingerprint (SHA256)'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), Str( 'revocation_reason?', label=_('Revocation reason'), flags={'virtual_attribute', 'no_create', 'no_update', 'no_search'}, ), StrEnum( 'ipakrbauthzdata*', cli_name='pac_type', label=_('PAC type'), doc=_("Override default list of supported PAC types." " Use 'NONE' to disable PAC support for this service," " e.g. this might be necessary for NFS services."), values=(u'MS-PAC', u'PAD', u'NONE'), ), Str( 'krbprincipalauthind*', cli_name='auth_ind', label=_('Authentication Indicators'), doc=_("Defines a whitelist for Authentication Indicators." " Use 'otp' to allow OTP-based 2FA authentications." " Use 'radius' to allow RADIUS-based 2FA authentications." " Other values may be used for custom configurations."), ), ) + ticket_flags_params def validate_ipakrbauthzdata(self, entry): new_value = entry.get('ipakrbauthzdata', []) if not new_value: return if not isinstance(new_value, (list, tuple)): new_value = set([new_value]) else: new_value = set(new_value) if u'NONE' in new_value and len(new_value) > 1: raise errors.ValidationError( name='ipakrbauthzdata', error=_('NONE value cannot be combined with other PAC types')) def get_dn(self, *keys, **kwargs): key = keys[0] if isinstance(key, six.text_type): key = kerberos.Principal(key) key = unicode(normalize_principal(key)) parent_dn = DN(self.container_dn, self.api.env.basedn) true_rdn = 'krbprincipalname' return self.backend.make_dn_from_attr(true_rdn, key, parent_dn) def get_primary_key_from_dn(self, dn): """ If the entry has krbcanonicalname set return the value of the attribute. If the attribute is not found, assume old-style entry which should have only single value of krbprincipalname and return it. Otherwise return input DN. """ assert isinstance(dn, DN) try: entry_attrs = self.backend.get_entry(dn, [self.primary_key.name]) try: return entry_attrs[self.primary_key.name][0] except (KeyError, IndexError): return '' except errors.NotFound: pass try: return dn['krbprincipalname'] except KeyError: return unicode(dn) def populate_krbcanonicalname(self, entry_attrs, options): if options.get('raw', False): return entry_attrs.setdefault('krbcanonicalname', entry_attrs['krbprincipalname'])
class cert_get_requestdata(Local): __doc__ = _('Gather data for a certificate signing request.') NO_CLI = True takes_options = ( Principal( 'principal', label=_('Principal'), doc=_('Principal for this certificate (e.g.' ' HTTP/test.example.com)'), ), Str( 'profile_id?', label=_('Profile ID'), doc=_('CSR Generation Profile to use'), ), Bytes( 'public_key_info', label=_('Subject Public Key Info'), doc=_('DER-encoded SubjectPublicKeyInfo structure'), ), Str( 'out?', doc=_('Write CertificationRequestInfo to file'), ), ) has_output = (output.Output( 'result', type=dict, doc=_('Dictionary mapping variable name to value'), ), ) has_output_params = (Str( 'request_info', label=_('CertificationRequestInfo structure'), )) def execute(self, *args, **options): if 'out' in options: util.check_writable_file(options['out']) principal = options.get('principal') profile_id = options.get('profile_id') if profile_id is None: profile_id = dogtag.DEFAULT_PROFILE public_key_info = options.get('public_key_info') public_key_info = base64.b64decode(public_key_info) if self.api.env.in_server: backend = self.api.Backend.ldap2 else: backend = self.api.Backend.rpcclient if not backend.isconnected(): backend.connect() try: if principal.is_host: principal_obj = api.Command.host_show(principal.hostname, all=True) elif principal.is_service: principal_obj = api.Command.service_show(unicode(principal), all=True) elif principal.is_user: principal_obj = api.Command.user_show(principal.username, all=True) except errors.NotFound: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] config = api.Command.config_show()['result'] generator = csrgen.CSRGenerator(csrgen.FileRuleProvider()) csr_config = generator.csr_config(principal_obj, config, profile_id) request_info = base64.b64encode( csrgen_ffi.build_requestinfo(csr_config.encode('utf8'), public_key_info)) result = {} if 'out' in options: with open(options['out'], 'wb') as f: f.write(request_info) else: result = dict(request_info=request_info) return dict(result=result)
class GetKeytab(Command): __doc__ = _('Retrieve a keytab.') name = 'get_keytab' takes_args = ( Principal( 'krbcanonicalname', validate_realm, cli_name='canonical_principal', label=_('Principal name'), doc=_('Kerberos principal'), primary_key=True, normalizer=normalize_principal ) ) takes_options = ( Flag( 'retrieve', doc=_('Retrieve an existing keytab (service principals only)'), ) ) has_output = ( output.summary, output.Output( 'result', dict, _('The keytab response which has a base64 encoded keytab element.') ), ) # See https://web.mit.edu/kerberos/krb5-devel/doc/formats/keytab_file_format.html # for the keytab file format. # # NOTE: http://web.mit.edu/kerberos/www/krb5-1.12/doc/formats/keytab_file_format.html # has an incorrect size for the principal's count of components and it is # missing the entry's second key version. # Keytab header: # [0] = 5 (KRB5) # [1] = 2 (Big endian) __keytab_big_endian_header = b'\x05\x02' # The keytab record lenght is a signed 32 bit integers where 0 indicates # the end of records. __keytab_end_of_records = b'\x00\x00\x00\x00' # A keytab has a header followed by record lengths followed by records or # holes. __empty_keytab = __keytab_big_endian_header + __keytab_end_of_records def execute(self, krbcanonicalname, **options): retrieve = options.get('retrieve') principal_string = unicode(krbcanonicalname) keytab = self.get_keytab(principal_string, retrieve) base64_keytab = base64.b64encode(keytab).decode('ascii') action = ('Created', 'Retrieved')[retrieve] summary = u'{0} keytab for principal "{1}"'.format( action, principal_string) return dict( summary=summary, result=dict(keytab=base64_keytab) ) def get_keytab(self, principal, retrieve): action = ('creating', 'retrieving')[retrieve] logger.debug(u'%s keytab for principal "%s"', action, principal) try: # NOTE: This is run from httpd with PrivateTmp=yes so the # temporary files are not accessible to other services running on # the same VM. This is in addition to the security of the # discretionary access controls being locked down (using # NamedTemporaryFile creates files that are only accessible by the # user). with tempfile.NamedTemporaryFile(suffix='.keytab') as temp_keytab: # Since this uses a temporary file and ipa-getkkeytab tool can # either create a new keytab file or add to an existing keytab # file. A valid keytab file needs to be constructed. temp_keytab.write(self.__empty_keytab) temp_keytab.flush() args = ['ipa-getkeytab', '-p', principal, '-k', temp_keytab.name] if retrieve: args.append('-r') # The ipa-getkeytab utility enforces the authorization policy # for retrieving the keytab. p = subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True, shell=False) # There is no stdout output from ipa-getkeytab (_, err) = p.communicate() p_status = p.wait() if p_status != 0: logger.error( u'ipa-getkeytab failed %s keytab for principal "%s" with error code %d and standard error ' u'message "%s"', action, principal, p_status, err) raise errors.InternalError() temp_keytab.seek(0) keytab = temp_keytab.read() except errors.InternalError: # The details were already logged raise except Exception as e: logger.error(u'failed %s keytab for principal "%s" with error "%s"', action, principal, e) raise logger.debug(u'%s keytab for principal "%s" succeeded', action, principal) return keytab
class passwd(Command): __doc__ = _("Set a user's password.") takes_args = ( Principal( 'principal', validate_realm, cli_name='user', label=_('User name'), primary_key=True, autofill=True, default_from=lambda: kerberos.Principal(krb_utils.get_principal()), normalizer=lambda value: normalize_user_principal(value), ), Password('password', label=_('New Password'), ), Password('current_password', label=_('Current Password'), confirm=False, default_from=lambda principal: get_current_password(principal), autofill=True, ), ) takes_options = ( Password('otp?', label=_('OTP'), doc=_('One Time Password'), confirm=False, ), ) has_output = output.simple_value msg_summary = _('Changed password for "%(value)s"') def execute(self, principal, password, current_password, **options): """ Execute the passwd operation. The dn should not be passed as a keyword argument as it is constructed by this method. Returns the entry :param principal: The login name or principal of the user :param password: the new password :param current_password: the existing password, if applicable """ ldap = self.api.Backend.ldap2 principal = unicode(principal) entry_attrs = ldap.find_entry_by_attr( 'krbprincipalname', principal, 'posixaccount', [''], DN(api.env.container_user, api.env.basedn) ) if principal == getattr(context, 'principal') and \ current_password == MAGIC_VALUE: # No cheating logger.warning('User attempted to change password using magic ' 'value') raise errors.ACIError(info=_('Invalid credentials')) if current_password == MAGIC_VALUE: ldap.modify_password(entry_attrs.dn, password) else: otp = options.get('otp') ldap.modify_password(entry_attrs.dn, password, current_password, otp) return dict( result=True, value=principal, )
class cert_request(Create, BaseCertMethod, VirtualCommand): __doc__ = _('Submit a certificate signing request.') obj_name = 'certreq' attr_name = 'request' takes_args = (Str( 'csr', validate_csr, label=_('CSR'), cli_name='csr_file', normalizer=normalize_csr, noextrawhitespace=False, ), ) operation = "request certificate" takes_options = ( Principal( 'principal', validate_realm, label=_('Principal'), doc=_( 'Principal for this certificate (e.g. HTTP/test.example.com)'), normalizer=normalize_principal), Flag( 'add', doc=_("automatically add the principal if it doesn't exist " "(service principals only)"), ), ) def get_args(self): # FIXME: the 'no_create' flag is ignored for positional arguments for arg in super(cert_request, self).get_args(): if arg.name == 'request_id': continue yield arg def execute(self, csr, all=False, raw=False, **kw): ca_enabled_check() ldap = self.api.Backend.ldap2 add = kw.get('add') request_type = kw.get('request_type') profile_id = kw.get('profile_id', self.Backend.ra.DEFAULT_PROFILE) # Check that requested authority exists (done before CA ACL # enforcement so that user gets better error message if # referencing nonexistant CA) and look up authority ID. # ca = kw['cacn'] ca_obj = api.Command.ca_show(ca)['result'] ca_id = ca_obj['ipacaid'][0] """ Access control is partially handled by the ACI titled 'Hosts can modify service userCertificate'. This is for the case where a machine binds using a host/ prinicpal. It can only do the request if the target hostname is in the managedBy attribute which is managed using the add/del member commands. Binding with a user principal one needs to be in the request_certs taskgroup (directly or indirectly via role membership). """ principal = kw.get('principal') principal_string = unicode(principal) if principal.is_user: principal_type = USER elif principal.is_host: principal_type = HOST else: principal_type = SERVICE bind_principal = kerberos.Principal(getattr(context, 'principal')) bind_principal_string = unicode(bind_principal) if bind_principal.is_user: bind_principal_type = USER elif bind_principal.is_host: bind_principal_type = HOST else: bind_principal_type = SERVICE if (bind_principal_string != principal_string and bind_principal_type != HOST): # Can the bound principal request certs for another principal? self.check_access() try: self.check_access("request certificate ignore caacl") bypass_caacl = True except errors.ACIError: bypass_caacl = False if not bypass_caacl: caacl_check(principal_type, principal, ca, profile_id) try: subject = pkcs10.get_subject(csr) extensions = pkcs10.get_extensions(csr) subjectaltname = pkcs10.get_subjectaltname(csr) or () except (NSPRError, PyAsn1Error, ValueError) as e: raise errors.CertificateOperationError( error=_("Failure decoding Certificate Signing Request: %s") % e) # self-service and host principals may bypass SAN permission check if (bind_principal_string != principal_string and bind_principal_type != HOST): if '2.5.29.17' in extensions: self.check_access('request certificate with subjectaltname') dn = None principal_obj = None # See if the service exists and punt if it doesn't and we aren't # going to add it try: if principal_type == SERVICE: principal_obj = api.Command['service_show'](principal_string, all=True) elif principal_type == HOST: principal_obj = api.Command['host_show'](principal.hostname, all=True) elif principal_type == USER: principal_obj = api.Command['user_show'](principal.username, all=True) except errors.NotFound as e: if add: if principal_type == SERVICE: principal_obj = api.Command['service_add']( principal_string, force=True) else: princtype_str = PRINCIPAL_TYPE_STRING_MAP[principal_type] raise errors.OperationNotSupportedForPrincipalType( operation=_("'add' option"), principal_type=princtype_str) else: raise errors.NotFound( reason=_("The principal for this request doesn't exist.")) principal_obj = principal_obj['result'] dn = principal_obj['dn'] # Ensure that the DN in the CSR matches the principal cn = subject.common_name #pylint: disable=E1101 if not cn: raise errors.ValidationError( name='csr', error=_("No Common Name was found in subject of request.")) if principal_type in (SERVICE, HOST): if cn.lower() != principal.hostname.lower(): raise errors.ACIError(info=_( "hostname in subject of request '%(cn)s' " "does not match principal hostname '%(hostname)s'") % dict(cn=cn, hostname=principal.hostname)) elif principal_type == USER: # check user name if cn != principal.username: raise errors.ValidationError( name='csr', error=_("DN commonName does not match user's login")) # check email address mail = subject.email_address #pylint: disable=E1101 if mail is not None and mail not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("DN emailAddress does not match " "any of user's email addresses")) # We got this far so the principal entry exists, can we write it? if not ldap.can_write(dn, "usercertificate"): raise errors.ACIError( info=_("Insufficient 'write' privilege " "to the 'userCertificate' attribute of entry '%s'.") % dn) # Validate the subject alt name, if any for name_type, desc, name, _der_name in subjectaltname: if name_type == nss.certDNSName: name = unicode(name) alt_principal = None alt_principal_obj = None try: if principal_type == HOST: alt_principal = kerberos.Principal((u'host', name), principal.realm) alt_principal_obj = api.Command['host_show'](name, all=True) elif principal_type == SERVICE: alt_principal = kerberos.Principal( (principal.service_name, name), principal.realm) alt_principal_obj = api.Command['service_show']( alt_principal, all=True) elif principal_type == USER: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for user principals") % desc) except errors.NotFound: # We don't want to issue any certificates referencing # machines we don't know about. Nothing is stored in this # host record related to this certificate. raise errors.NotFound(reason=_( 'The service principal for ' 'subject alt name %s in certificate request does not ' 'exist') % name) if alt_principal_obj is not None: altdn = alt_principal_obj['result']['dn'] if not ldap.can_write(altdn, "usercertificate"): raise errors.ACIError(info=_( "Insufficient privilege to create a certificate " "with subject alt name '%s'.") % name) if alt_principal is not None and not bypass_caacl: caacl_check(principal_type, alt_principal, ca, profile_id) elif name_type in [ (nss.certOtherName, x509.SAN_UPN), (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME), ]: if name != principal_string: raise errors.ACIError( info=_("Principal '%s' in subject alt name does not " "match requested principal") % name) elif name_type == nss.certRFC822Name: if principal_type == USER: if name not in principal_obj.get('mail', []): raise errors.ValidationError( name='csr', error=_("RFC822Name does not match " "any of user's email addresses")) else: raise errors.ValidationError( name='csr', error=_("subject alt name type %s is forbidden " "for non-user principals") % desc) else: raise errors.ACIError( info=_("Subject alt name type %s is forbidden") % desc) # Request the certificate try: result = self.Backend.ra.request_certificate( csr, profile_id, ca_id, request_type=request_type) except errors.HTTPRequestError as e: if e.status == 409: # pylint: disable=no-member raise errors.CertificateOperationError( error=_("CA '%s' is disabled") % ca) else: raise e if not raw: self.obj._parse(result, all) result['request_id'] = int(result['request_id']) result['cacn'] = ca_obj['cn'][0] # Success? Then add it to the principal's entry # (unless the profile tells us not to) profile = api.Command['certprofile_show'](profile_id) store = profile['result']['ipacertprofilestoreissued'][0] == 'TRUE' if store and 'certificate' in result: cert = str(result.get('certificate')) kwargs = dict(addattr=u'usercertificate={}'.format(cert)) if principal_type == SERVICE: api.Command['service_mod'](principal_string, **kwargs) elif principal_type == HOST: api.Command['host_mod'](principal.hostname, **kwargs) elif principal_type == USER: api.Command['user_mod'](principal.username, **kwargs) return dict( result=result, value=pkey_to_value(int(result['request_id']), kw), )
class BaseCertObject(Object): takes_params = ( Str( 'cacn?', cli_name='ca', default=IPA_CA_CN, autofill=True, label=_('Issuing CA'), doc=_('Name of issuing CA'), flags={'no_create', 'no_update', 'no_search'}, ), Bytes( 'certificate', validate_certificate, label=_("Certificate"), doc=_("Base-64 encoded certificate."), normalizer=x509.normalize_certificate, flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'subject', label=_('Subject'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_rfc822name*', label=_('Subject email address'), flags={'no_create', 'no_update', 'no_search'}, ), DNSNameParam( 'san_dnsname*', label=_('Subject DNS name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_x400address*', label=_('Subject X.400 address'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'san_directoryname*', label=_('Subject directory name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_edipartyname*', label=_('Subject EDI Party name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_uri*', label=_('Subject URI'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_ipaddress*', label=_('Subject IP Address'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_oid*', label=_('Subject OID'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_upn*', label=_('Subject UPN'), flags={'no_create', 'no_update', 'no_search'}, ), Principal( 'san_other_kpn*', label=_('Subject Kerberos principal name'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'san_other*', label=_('Subject Other Name'), flags={'no_create', 'no_update', 'no_search'}, ), DNParam( 'issuer', label=_('Issuer'), doc=_('Issuer DN'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_before', label=_('Not Before'), flags={'no_create', 'no_update', 'no_search'}, ), DateTime( 'valid_not_after', label=_('Not After'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'md5_fingerprint', label=_('Fingerprint (MD5)'), flags={'no_create', 'no_update', 'no_search'}, ), Str( 'sha1_fingerprint', label=_('Fingerprint (SHA1)'), flags={'no_create', 'no_update', 'no_search'}, ), Int( 'serial_number', label=_('Serial number'), doc=_( 'Serial number in decimal or if prefixed with 0x in hexadecimal' ), normalizer=normalize_serial_number, flags={'no_create', 'no_update', 'no_search'}, ), Str( 'serial_number_hex', label=_('Serial number (hex)'), flags={'no_create', 'no_update', 'no_search'}, ), ) def _parse(self, obj, full=True): """Extract certificate-specific data into a result object. ``obj`` Result object containing certificate, into which extracted data will be inserted. ``full`` Whether to include all fields, or only the ones we guess people want to see most of the time. Also add recognised otherNames to the generic ``san_other`` attribute when ``True`` in addition to the specialised attribute. """ cert = obj.get('certificate') if cert is not None: cert = x509.load_certificate(cert) obj['subject'] = DN(unicode(cert.subject)) obj['issuer'] = DN(unicode(cert.issuer)) obj['serial_number'] = cert.serial_number obj['valid_not_before'] = unicode(cert.valid_not_before_str) obj['valid_not_after'] = unicode(cert.valid_not_after_str) if full: obj['md5_fingerprint'] = unicode( nss.data_to_hex(nss.md5_digest(cert.der_data), 64)[0]) obj['sha1_fingerprint'] = unicode( nss.data_to_hex(nss.sha1_digest(cert.der_data), 64)[0]) try: ext_san = cert.get_extension(nss.SEC_OID_X509_SUBJECT_ALT_NAME) general_names = x509.decode_generalnames(ext_san.value) except KeyError: general_names = [] for name_type, _desc, name, der_name in general_names: try: self._add_san_attribute(obj, full, name_type, name, der_name) except Exception: # Invalid GeneralName (i.e. not a valid X.509 cert); # don't fail but log something about it root_logger.warning( "Encountered bad GeneralName; skipping", exc_info=True) serial_number = obj.get('serial_number') if serial_number is not None: obj['serial_number_hex'] = u'0x%X' % serial_number def _add_san_attribute(self, obj, full, name_type, name, der_name): name_type_map = { nss.certRFC822Name: 'san_rfc822name', nss.certDNSName: 'san_dnsname', nss.certX400Address: 'san_x400address', nss.certDirectoryName: 'san_directoryname', nss.certEDIPartyName: 'san_edipartyname', nss.certURI: 'san_uri', nss.certIPAddress: 'san_ipaddress', nss.certRegisterID: 'san_oid', (nss.certOtherName, x509.SAN_UPN): 'san_other_upn', (nss.certOtherName, x509.SAN_KRB5PRINCIPALNAME): 'san_other_kpn', } default_attrs = { 'san_rfc822name', 'san_dnsname', 'san_other_upn', 'san_other_kpn', } attr_name = name_type_map.get(name_type, 'san_other') if full or attr_name in default_attrs: if attr_name != 'san_other': name_formatted = name else: # display as "OID : b64(DER)" name_formatted = u'{}:{}'.format(name_type[1], base64.b64encode(der_name)) attr_value = self.params[attr_name].type(name_formatted) obj.setdefault(attr_name, []).append(attr_value) if full and attr_name.startswith('san_other_'): # also include known otherName in generic otherName attribute name_formatted = u'{}:{}'.format(name_type[1], base64.b64encode(der_name)) attr_value = self.params['san_other'].type(name_formatted) obj.setdefault('san_other', []).append(attr_value)