Beispiel #1
0
def normalize_user_principal(value):
    principal = kerberos.Principal(normalize_principal(value))
    lowercase_components = ((principal.username.lower(),) +
                            principal.components[1:])

    return unicode(
        kerberos.Principal(lowercase_components, realm=principal.realm))
Beispiel #2
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
                     **options):
        assert isinstance(dn, DN)

        if not self.api.Command.kra_is_enabled()['result']:
            raise errors.InvocationError(
                format=_('KRA service is not enabled'))

        principal = kerberos.Principal(getattr(context, 'principal'))
        if principal.is_service:
            owner_dn = self.api.Object.service.get_dn(unicode(principal))
        else:
            owner_dn = self.api.Object.user.get_dn(principal.username)

        parent_dn = DN(*dn[1:])

        try:
            self.obj.create_container(parent_dn, owner_dn)
        except (errors.DuplicateEntry, errors.ACIError):
            pass

        # vault should be owned by the creator
        entry_attrs['owner'] = owner_dn

        return dn
Beispiel #3
0
    def principal(self):
        if any(attr is None
               for attr in (self.realm, self.fqdn, self.service_prefix)):
            return

        return unicode(
            kerberos.Principal((self.service_prefix, self.fqdn),
                               realm=self.realm))
Beispiel #4
0
    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)
Beispiel #5
0
def normalize_principal(value):
    """
    Ensure that the name in the principal is lower-case. The realm is
    upper-case by convention but it isn't required.

    The principal is validated at this point.
    """
    try:
        principal = kerberos.Principal(value, realm=api.env.realm)
    except ValueError:
        raise errors.ValidationError(
            name='principal', reason=_("Malformed principal"))

    return unicode(principal)
Beispiel #6
0
def bind_principal_can_manage_cert(cert):
    """Check that the bind principal can manage the given cert.

    ``cert``
        An NSS certificate object.

    """
    bind_principal = kerberos.Principal(getattr(context, 'principal'))
    if not bind_principal.is_host:
        return False

    hostname = bind_principal.hostname

    # If we have a hostname we want to verify that the subject
    # of the certificate matches it.
    return hostname == cert.subject.common_name  #pylint: disable=E1101
Beispiel #7
0
    def get_dn(self, *keys, **options):
        """
        Generates vault DN from parameters.
        """
        service = options.get('service')
        shared = options.get('shared')
        user = options.get('username')

        count = (bool(service) + bool(shared) + bool(user))
        if count > 1:
            raise errors.MutuallyExclusiveError(
                reason=_('Service, shared, and user options ' +
                         'cannot be specified simultaneously'))

        # TODO: create container_dn after object initialization then reuse it
        container_dn = DN(self.container_dn, self.api.env.basedn)

        dn = super(vault, self).get_dn(*keys, **options)
        assert dn.endswith(container_dn)
        rdns = DN(*dn[:-len(container_dn)])

        if not count:
            principal = kerberos.Principal(getattr(context, 'principal'))

            if principal.is_host:
                raise errors.NotImplementedError(
                    reason=_('Host is not supported'))
            elif principal.is_service:
                service = unicode(principal)
            else:
                user = principal.username

        if service:
            parent_dn = DN(('cn', service), ('cn', 'services'), container_dn)
        elif shared:
            parent_dn = DN(('cn', 'shared'), container_dn)
        elif user:
            parent_dn = DN(('cn', user), ('cn', 'users'), container_dn)
        else:
            raise RuntimeError

        return DN(rdns, parent_dn)
Beispiel #8
0
def bind_principal_can_manage_cert(cert):
    """Check that the bind principal can manage the given cert.

    ``cert``
        A python-cryptography ``Certificate`` object.

    """
    bind_principal = kerberos.Principal(getattr(context, 'principal'))
    if not bind_principal.is_host:
        return False

    hostname = bind_principal.hostname

    # Verify that hostname matches subject of cert.
    # We check the "most-specific" CN value.
    cns = cert.subject.get_attributes_for_oid(
        cryptography.x509.oid.NameOID.COMMON_NAME)
    if len(cns) == 0:
        return False  # no CN in subject
    else:
        return hostname == cns[-1].value
Beispiel #9
0
    def get_dn(self, *keys, **options):
        """
        Generates vault DN from parameters.
        """
        service = options.get('service')
        shared = options.get('shared')
        user = options.get('username')

        count = (bool(service) + bool(shared) + bool(user))
        if count > 1:
            raise errors.MutuallyExclusiveError(
                reason=_('Service, shared and user options ' +
                         'cannot be specified simultaneously'))

        parent_dn = super(vaultcontainer, self).get_dn(*keys, **options)

        if not count:
            principal = kerberos.Principal(getattr(context, 'principal'))

            if principal.is_host:
                raise errors.NotImplementedError(
                    reason=_('Host is not supported'))
            elif principal.is_service:
                service = unicode(principal)
            else:
                user = principal.username

        if service:
            dn = DN(('cn', service), ('cn', 'services'), parent_dn)
        elif shared:
            dn = DN(('cn', 'shared'), parent_dn)
        elif user:
            dn = DN(('cn', user), ('cn', 'users'), parent_dn)
        else:
            raise RuntimeError

        return dn
Beispiel #10
0
def validate_auth_indicator(entry):
    new_value = entry.get('krbprincipalauthind', None)
    if not new_value:
        return
    # The following services are considered internal IPA services
    # and shouldn't be allowed to have auth indicators.
    # https://pagure.io/freeipa/issue/8206
    pkey = api.Object['service'].get_primary_key_from_dn(entry.dn)
    if pkey == str(entry.dn):
        # krbcanonicalname may not be set yet if this is a host entry,
        # try krbprincipalname
        if 'krbprincipalname' in entry:
            pkey = entry['krbprincipalname']
    principal = kerberos.Principal(pkey)
    server = api.Command.server_find(principal.hostname)['result']
    if server:
        prefixes = ("host", "cifs", "ldap", "HTTP")
    else:
        prefixes = ("cifs", )
    if principal.service_name in prefixes:
        raise errors.ValidationError(
            name='krbprincipalauthind',
            error=_('authentication indicators not allowed '
                    'in service "%s"' % principal.service_name))
Beispiel #11
0
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'
    ]
    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'
    ]
    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'],
        '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=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'),
        ),
        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='^(([a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;q\=((0(\.[0-9]{0,3})?)|(1(\.0{0,3})?)))?' \
             + '(\s*,\s*[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?(;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"',
        ),
        Bytes('usercertificate*', validate_certificate,
            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'],
        ),
    )

    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, six.string_types):
                    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
Beispiel #12
0
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=_('The OTP if the user has a token configured'),
        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,
        )
Beispiel #13
0
    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),
        )
Beispiel #14
0
    def __call__(self, environ, start_response):
        def attempt_kinit(user_principal,
                          password,
                          ipa_ccache_name,
                          use_armor=True):
            try:
                # try to remove in case an old file was there
                os.unlink(ipa_ccache_name)
            except OSError:
                pass
            try:
                self.kinit(user_principal,
                           password,
                           ipa_ccache_name,
                           use_armor=use_armor)
            except PasswordExpired as e:
                return self.unauthorized(environ, start_response, str(e),
                                         'password-expired')
            except InvalidSessionPassword as e:
                return self.unauthorized(environ, start_response, str(e),
                                         'invalid-password')
            except KrbPrincipalExpired as e:
                return self.unauthorized(environ, start_response, str(e),
                                         'krbprincipal-expired')
            except UserLocked as e:
                return self.unauthorized(environ, start_response, str(e),
                                         'user-locked')
            return None

        logger.debug('WSGI login_password.__call__:')

        # Get the user and password parameters from the request
        content_type = environ.get('CONTENT_TYPE', '').lower()
        if not content_type.startswith('application/x-www-form-urlencoded'):
            return self.bad_request(
                environ, start_response,
                "Content-Type must be application/x-www-form-urlencoded")

        method = environ.get('REQUEST_METHOD', '').upper()
        if method == 'POST':
            query_string = read_input(environ)
        else:
            return self.bad_request(environ, start_response,
                                    "HTTP request method must be POST")

        try:
            query_dict = parse_qs(query_string)
        except Exception:
            return self.bad_request(environ, start_response,
                                    "cannot parse query data")

        user = query_dict.get('user', None)
        if user is not None:
            if len(user) == 1:
                user = user[0]
            else:
                return self.bad_request(environ, start_response,
                                        "more than one user parameter")
        else:
            return self.bad_request(environ, start_response,
                                    "no user specified")

        # allows login in the form user@SERVER_REALM or user@server_realm
        # we kinit as enterprise principal so we can assume that unknown realms
        # are UPN
        try:
            user_principal = kerberos.Principal(user)
        except Exception:
            # the principal is malformed in some way (e.g. user@REALM1@REALM2)
            # netbios names (NetBIOS1\user) are also not accepted (yet)
            return self.unauthorized(environ, start_response, '', 'denied')

        password = query_dict.get('password', None)
        if password is not None:
            if len(password) == 1:
                password = password[0]
            else:
                return self.bad_request(environ, start_response,
                                        "more than one password parameter")
        else:
            return self.bad_request(environ, start_response,
                                    "no password specified")

        # Get the ccache we'll use and attempt to get credentials in it with user,password
        ipa_ccache_name = os.path.join(paths.IPA_CCACHES,
                                       'kinit_{}'.format(os.getpid()))
        try:
            result = attempt_kinit(user_principal,
                                   password,
                                   ipa_ccache_name,
                                   use_armor=True)
        except KrbPrincipalWrongFAST:
            result = attempt_kinit(user_principal,
                                   password,
                                   ipa_ccache_name,
                                   use_armor=False)

        if result is not None:
            return result

        result = self.finalize_kerberos_acquisition('login_password',
                                                    ipa_ccache_name, environ,
                                                    start_response)
        try:
            # Try not to litter the filesystem with unused TGTs
            os.unlink(ipa_ccache_name)
        except OSError:
            pass
        return result
Beispiel #15
0
    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:
            csr_obj = pkcs10.load_certificate_request(csr)
        except ValueError as e:
            raise errors.CertificateOperationError(
                error=_("Failure decoding Certificate Signing Request: %s") %
                e)

        try:
            ext_san = csr_obj.extensions.get_extension_for_oid(
                cryptography.x509.oid.ExtensionOID.SUBJECT_ALTERNATIVE_NAME)
        except cryptography.x509.extensions.ExtensionNotFound:
            ext_san = None

        # self-service and host principals may bypass SAN permission check
        if (bind_principal_string != principal_string
                and bind_principal_type != HOST):
            if ext_san is not None:
                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
        #
        # We only look at the "most specific" CN value
        cns = csr_obj.subject.get_attributes_for_oid(
            cryptography.x509.oid.NameOID.COMMON_NAME)
        if len(cns) == 0:
            raise errors.ValidationError(
                name='csr',
                error=_("No Common Name was found in subject of request."))
        cn = cns[-1].value  # "most specific" is end of list

        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
            #
            # fail if any email addr from DN does not appear in ldap entry
            email_addrs = csr_obj.subject.get_attributes_for_oid(
                cryptography.x509.oid.NameOID.EMAIL_ADDRESS)
            if len(set(email_addrs) - set(principal_obj.get('mail', []))) > 0:
                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
        generalnames = []
        if ext_san is not None:
            generalnames = x509.process_othernames(ext_san.value)
        for gn in generalnames:
            if isinstance(gn, cryptography.x509.general_name.DNSName):
                name = gn.value
                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") % "DNSName")
                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 isinstance(gn, (x509.KRB5PrincipalName, x509.UPN)):
                if gn.name != principal_string:
                    raise errors.ACIError(
                        info=_("Principal '%s' in subject alt name does not "
                               "match requested principal") % gn.name)
            elif isinstance(gn, cryptography.x509.general_name.RFC822Name):
                if principal_type == USER:
                    if gn.value 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") % "RFC822Name")
            else:
                raise errors.ACIError(
                    info=_("Subject alt name type %s is forbidden") %
                    type(gn).__name__)

        # Request the certificate
        try:
            # re-serialise to PEM, in case the user-supplied data has
            # extraneous material that will cause Dogtag to freak out
            csr_pem = csr_obj.public_bytes(serialization.Encoding.PEM)
            result = self.Backend.ra.request_certificate(
                csr_pem, 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),
        )
Beispiel #16
0
    def pre_callback(self, ldap, dn, entry_attrs, attrs_list, *keys,
                     **options):
        assert isinstance(dn, DN)
        hostname = keys[0]
        if len(keys) == 2:
            netbiosname = keys[1]
        else:
            # By default take leftmost label from the host name
            netbiosname = DNSName.from_text(hostname)[0].decode().upper()

        # SMB service requires existence of the host object
        # because DCE RPC calls authenticated with GSSAPI are using
        # host/.. principal by default for validation
        try:
            hostresult = self.api.Command['host_show'](hostname)['result']
        except errors.NotFound:
            raise errors.NotFound(
                reason=_("The host '%s' does not exist to add a service to.") %
                hostname)

        # We cannot afford the host not being resolvable even for
        # clustered environments with CTDB because the target name
        # has to exist even in that case
        util.verify_host_resolvable(hostname)

        smbaccount = '{name}$'.format(name=netbiosname)
        smbprincipal = 'cifs/{hostname}'.format(hostname=hostname)

        entry_attrs['krbprincipalname'] = [
            str(kerberos.Principal(smbprincipal, realm=self.api.env.realm)),
            str(kerberos.Principal(smbaccount, realm=self.api.env.realm))
        ]

        entry_attrs['krbcanonicalname'] = entry_attrs['krbprincipalname'][0]

        # Rewrite DN using proper rdn and new canonical name because when
        # LDAPCreate.execute() was called, it set DN to krbcanonicalname=$value
        dn = DN(('krbprincipalname', entry_attrs['krbcanonicalname']),
                DN(self.obj.container_dn, api.env.basedn))

        # Enforce ipaKrbPrincipalAlias to aid case-insensitive searches as
        # krbPrincipalName/krbCanonicalName are case-sensitive in Kerberos
        # schema
        entry_attrs['ipakrbprincipalalias'] = entry_attrs['krbcanonicalname']

        for o in ('ipakrbprincipal', 'ipaidobject', 'krbprincipalaux',
                  'posixaccount'):
            if o not in entry_attrs['objectclass']:
                entry_attrs['objectclass'].append(o)

        entry_attrs['uid'] = [
            '/'.join(kerberos.Principal(smbprincipal).components)
        ]
        entry_attrs['uid'].append(smbaccount)
        entry_attrs['cn'] = netbiosname
        entry_attrs['homeDirectory'] = '/dev/null'
        entry_attrs['uidNumber'] = DNA_MAGIC
        entry_attrs['gidNumber'] = DNA_MAGIC

        self.obj.validate_ipakrbauthzdata(entry_attrs)

        if 'managedby' not in entry_attrs:
            entry_attrs['managedby'] = hostresult['dn']

        update_krbticketflags(ldap, entry_attrs, attrs_list, options, False)

        return dn