Keystone Federation via mod_lookup_identity

In a recent post, I described how I configured a web server to user mod_lookup_identity. Now, I use that configuration to provide a test for the recent Federation work in Keystone. This is a really rough proof of concept; do not expect to be able to use this in your production environments yet.

The Keystone server is a modified devstack deployment using the Keystone LDAP Identity Backend to talk to a FreeIPA server. I’ve made an additional WSGIScriptAlias specific to Kerberos authentication.

My HTTPD configuration for Keystone looks like this.

LoadModule lookup_identity_module modules/mod_lookup_identity.so
 
WSGIScriptAlias /keystone/main  /var/www/cgi-bin/keystone/main
WSGIScriptAlias /keystone/krb   /var/www/cgi-bin/keystone/main
WSGIScriptAlias /keystone/admin /var/www/cgi-bin/keystone/admin
WSGIScriptAlias /keystone/hello /var/www/cgi-bin/keystone/hello.py
WSGIScriptAlias /keystone/sss   /var/www/cgi-bin/keystone/main
 
WSGIDaemonProcess keystone_admin user=fedora group=wheel maximum-requests=10000
WSGIDaemonProcess keystone_main user=fedora group=wheel maximum-requests=10000
WSGIDaemonProcess keystone_krb_wsgi user=fedora group=wheel maximum-requests=10000
WSGIDaemonProcess keystone_hello_wsgi user=fedora group=wheel maximum-requests=10000
WSGIDaemonProcess keystone_sss_wsgi user=fedora group=wheel maximum-requests=10000
 
<Location "/keystone/admin">
 WSGIProcessGroup keystone_admin
 NSSRequireSSL
 Authtype none
</Location>
 
<Location "/keystone/main">
 WSGIProcessGroup keystone_main
 NSSRequireSSL
 Authtype none
</Location>
 
<Location "/keystone/krb">
  WSGIProcessGroup keystone_krb_wsgi
  AuthType Kerberos
  AuthName "Kerberos Login"
  KrbMethodNegotiate on
  KrbMethodK5Passwd off
  KrbServiceName HTTP
  KrbAuthRealms IPA.CLOUDLAB.FREEIPA.ORG
  Krb5KeyTab /etc/httpd/conf/openstack.keytab
  KrbSaveCredentials on
  KrbLocalUserMapping on
  Require valid-user
 NSSRequireSSL
</Location>
 
<Location "/keystone/sss">
  WSGIProcessGroup keystone_sss_wsgi
  AuthType Kerberos
  AuthName "Kerberos Login"
  KrbMethodNegotiate on
  KrbMethodK5Passwd off
  KrbServiceName HTTP
  KrbAuthRealms IPA.CLOUDLAB.FREEIPA.ORG
  Krb5KeyTab /etc/httpd/conf/openstack.keytab
  KrbSaveCredentials on
  KrbLocalUserMapping on
  Require valid-user
  NSSRequireSSL
  LookupUserAttr mail REMOTE_USER_EMAIL " "
  LookupUserGroups REMOTE_USER_GROUPS ";"
</Location>
 
<Location "/keystone/hello">
  WSGIProcessGroup keystone_hello_wsgi
  AuthType Kerberos
  AuthName "Kerberos Login"
  KrbMethodNegotiate on
  KrbMethodK5Passwd off
  KrbServiceName HTTP
  KrbAuthRealms IPA.CLOUDLAB.FREEIPA.ORG
  Krb5KeyTab /etc/httpd/conf/openstack.keytab
  KrbSaveCredentials on
  KrbLocalUserMapping on
  Require valid-user
  NSSRequireSSL
  LookupUserAttr mail REMOTE_USER_EMAIL " "
  LookupUserGroups REMOTE_USER_GROUPS ";"
</Location>
[pipeline:api_v3]
pipeline = sizelimit url_normalize build_auth_context token_auth admin_token_auth xml_body_v3 json_body ec2_extension_v3 s3_extension simple_cert_extension federation_extension service_v3

And performed migration to get SQL tables:

bin/keystone-manage db_sync --extension federation

Added the SAML Auth plugin in Keystone.conf

 
# Default auth methods. (list value)
methods=external,password,token,saml2
saml2=keystone.auth.plugins.saml2.Saml2

Here is my code to build the mapping file.

#    Licensed under the Apache License, Version 2.0 (the "License"); you may
#    not use this file except in compliance with the License. You may obtain
#    a copy of the License at
#
#         http://www.apache.org/licenses/LICENSE-2.0
#
#    Unless required by applicable law or agreed to in writing, software
#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
#    License for the specific language governing permissions and limitations
#    under the License.
 
import os
 
from keystoneclient import client
from keystoneclient import exceptions
from keystoneclient import session
from keystoneclient.auth.identity import v3
 
 
def display(keystone_client, stage):
    print(stage)
 
    for mapping in keystone_client.federation.mappings.list():
        print(mapping.rules)
    for provider in keystone_client.federation.identity_providers.list():
        print("provider = %s" % provider.id)
        for protocol in keystone_client.federation.protocols.list(provider):
            print("  protocol = %s" % protocol.id)
    print()
 
 
def create_entries(keystone_client):
    rules = [
        {
            "local": [
                {
                    "user": {
                        "name": "{0}",
                        "id": "{0}",
                    }
                }
            ],
            "remote": [
                {
                    "type": "REMOTE_USER"
                }
            ]
        },
        {
            "local": [
                {
                    "group": {
                        "id": "narnians"
                    }
                }
            ],
            "remote": [
                {
                    "type": "REMOTE_USER_GROUPS",
                    "any_one_of": ["narnians"]
                }
            ]
        },
        {
            "local": [
                {
                    "group": {
                        "id": "telmarines"
                    }
                }
            ],
            "remote": [
                {
                    "type": "REMOTE_USER_GROUPS",
                    "any_one_of": ["telmarines"]
                }
            ]
        },
        {
            "local": [
                {
                    "group": {
                        "id": "osprey"
                    }
                }
            ],
            "remote": [
                {
                    "type": "REMOTE_USER_GROUPS",
                    "any_one_of": ["osprey"]
                }
            ]
        },
    ]
 
    keystone_client.federation.mappings.create(mapping_id='cloudlab',
                                               rules=rules)
    keystone_client.federation.identity_providers.create(id='sssd')
    keystone_client.federation.protocols.create(identity_provider_id='sssd',
                                                protocol_id='kerberos',
                                                mapping_id='cloudlab')
    display(keystone_client, 'Created Protocol')
 
 
def delete_entries(keystone_client):
    try:
        keystone_client.federation.mappings.delete(mapping='cloudlab')
    except exceptions.NotFound:
        pass
    try:
        keystone_client.federation.protocols.delete(identity_provider='sssd',
                                                    protocol='kerberos')
    except exceptions.NotFound:
        pass
    try:
        keystone_client.federation.identity_providers.delete(
            identity_provider='sssd')
    except exceptions.NotFound:
        pass
 
 
def assign_role_to_group(keystone_client, group_name='osprey', role_name='Member',
                         project_name='demo'):
    group = keystone_client.groups.get(group_name)
    role = keystone_client.roles.find(name=role_name)
    project = keystone_client.projects.find(name=project_name)
    keystone_client.roles.grant(role=role, group=group, project=project)
 
 
def main():
    try:
        os_password = os.environ['OS_PASSWORD']
        os_username = os.environ['OS_USERNAME']
        os_auth_url = os.environ['OS_AUTH_URL']
        os_project_name = os.environ['OS_PROJECT_NAME']
        os_ca_cert = os.environ['OS_CA_CERT']
 
    except KeyError as e:
        print('%s environment variables not set.' % e)
        exit(1)
 
    auth = v3.Password(auth_url=os_auth_url,
                       username=os_username,
                       password=os_password,
                       user_domain_name='Default',
                       project_domain_name='Default',
                       project_name=os_project_name)
 
    sess = session.Session(auth=auth, verify=os_ca_cert)
 
    keystone_client = client.Client(('3', '0'),
                                    session=sess,
                                    endpoint=os_auth_url,
                                    )
    display(keystone_client, 'Start')
    for group in keystone_client.groups.list():
        print("group id = %s" % group.id)
        if group.id == 'narnians':
            group_narnians = group
 
 
    #keystone_client.projects.create(name='Castle', domain='default')
    #assign_role_to_group(keystone_client, group_name='telmarines', role_name='Member',
    #                     project_name='Castle')
 
    #keystone_client.projects.create(name='Woods', domain='default')
    #assign_role_to_group(keystone_client, group_name='narnians', role_name='Member',
    #                     project_name='Woods')
 
    project_woods  = keystone_client.projects.find(name="Woods")
 
    for role in keystone_client.roles.list(group=group_narnians,
                                           project=project_woods):
        print("members of group id= %s have  role %s in project %s" %
              (group_narnians.id, role.name, project_woods.name))
 
    #assign_role_to_group(keystone_client)
 
    delete_entries(keystone_client)
    create_entries(keystone_client)
 
if __name__ == "__main__":
    main()

Yes, I’ve been reading C.S. Lewis to my son.

I have it in a branch in my personal github, as it depends on a handful of patches to Keystone Client that have not yet merged.

Here is how I fetch a token.

#!/usr/bin/bash
curl -v  \
-H "Content-Type:application/json" \
--negotiate -u : \
--cacert ca.crt  \
-d  '{ "auth": { "identity": { "methods": ["saml2"], "saml2":{"identity_provider":"sssd", "protocol":"kerberos"}}, "scope": { "project": { "domain": { "name": "Default" }, "name": "Castle" } } } }' \
-X POST https://ayoungdevstack20.cloudlab.freeipa.org/keystone/sss/v3/auth/tokens

I modify the Project’s name field and kinit as various users to test. For example:

$ echo FreeIPA4All | kinit caspian@IPA.CLOUDLAB.FREEIPA.ORG
Password for caspian@IPA.CLOUDLAB.FREEIPA.ORG: 
$ ./saml-token-get.sh 
{"token": {"methods": ["saml2"], "roles": [{"id": "a18fd6adab1e4f238dd8da598615c3ce", "name": "Member"}], "expires_at": "2014-05-05T22:05:00.255831Z", "project": {"domain": {"id": "default", "name": "Default"}, "id": "bef67b3705b54c1386d8a9499112f0cd", "name": "Castle"}, "catalog": [{"endpoints": [{"url": "http://192.168.187.14:8774/v2/bef67b3705b54c1386d8a9499112f0cd", "region": "RegionOne", "interface": "admin", "id": "486ce25c849b471a806d55304cd18191"}, {"url": "http://192.168.187.14:8774/v2/bef67b3705b54c1386d8a9499112f0cd", "region": "RegionOne", "interface": "internal", "id": "63076e278a9e4560a08ce962e49c3618"}, {"url": "http://192.168.187.14:8774/v2/bef67b3705b54c1386d8a9499112f0cd", "region": "RegionOne", "interface": "public", "id": "9b6e089b91734c508bbe117ea0865c76"}], "type": "compute", "id": "04f4b44b07304e2ba61a24f6e764cc79", "name": "nova"}, {"endpoints": [], "type": "s3", "id": "4fdd44afc74c4d9fb08ace5e0a76d8cb", "name": "s3"}, {"endpoints": [], "type": "orchestration", "id": "7ee1e9905ebf4a99a7b96834ee5a7d17", "name": "heat"}, {"endpoints": [{"url": "http://192.168.187.14:8773/services/Cloud", "region": "RegionOne", "interface": "public", "id": "1c05feddaf17430fa9bacfccc9a87959"}, {"url": "http://192.168.187.14:8773/services/Cloud", "region": "RegionOne", "interface": "internal", "id": "e43ca2dd223e446a8814d26754379d3b"}, {"url": "http://192.168.187.14:8773/services/Admin", "region": "RegionOne", "interface": "admin", "id": "f59c5b46b46944c9a62850b54f912b58"}], "type": "ec2", "id": "80e0b5f6d90d489b87c363da3a297311", "name": "ec2"}, {"endpoints": [{"url": "https://ayoungdevstack20.cloudlab.freeipa.org/keystone/main/v2.0", "region": "RegionOne", "interface": "internal", "id": "58f3fc22ed7f4f7f875de217f58e0fb4"}, {"url": "https://ayoungdevstack20.cloudlab.freeipa.org/keystone/admin/v2.0", "region": "RegionOne", "interface": "admin", "id": "7041002b84244f9f83ed111554bcd731"}, {"url": "https://ayoungdevstack20.cloudlab.freeipa.org/keystone/main/v2.0", "region": "RegionOne", "interface": "public", "id": "a4e2611d469845ce86504f3f530dda2f"}], "type": "identity", "id": "e2ff04fe1dba430280684b81502875a0", "name": "keystone"}, {"endpoints": [], "type": "cloudformation", "id": "e90714ecb2be4536b24cd15652f84aeb", "name": "heat"}, {"endpoints": [{"url": "http://192.168.187.14:9292", "region": "RegionOne", "interface": "public", "id": "339bb5dada5d457a8a05733fa303bed2"}, {"url": "http://192.168.187.14:9292", "region": "RegionOne", "interface": "admin", "id": "47c53b4133464c86b0df45b87b243a2d"}, {"url": "http://192.168.187.14:9292", "region": "RegionOne", "interface": "internal", "id": "8184480bc0b042f68031ff66dcd0b53c"}], "type": "image", "id": "f7919d3d4fe04834ae96d8480bf333e7", "name": "glance"}, {"endpoints": [{"url": "http://192.168.187.14:8774/v3", "region": "RegionOne", "interface": "internal", "id": "97d1f2b2e3f64f138287e1fb4a9a6999"}, {"url": "http://192.168.187.14:8774/v3", "region": "RegionOne", "interface": "public", "id": "df1ecdb2378c49ea89555b1e8aa31875"}, {"url": "http://192.168.187.14:8774/v3", "region": "RegionOne", "interface": "admin", "id": "f19d005bbd664751b0870515ecf2e903"}], "type": "computev3", "id": "ffc3df66bc0048c9895868222b9f427c", "name": "novav3"}], "extras": {}, "user": {"id": "caspian", "name": "caspian"}, "issued_at": "2014-05-05T21:05:00.255868Z"}}

3 thoughts on “Keystone Federation via mod_lookup_identity

  1. I want to map a ‘federated’ user to a project in openstack with same name [user name]. What will be the mapping rules for the same.

  2. Mapping is a two step process. Auth->Identity->Assignment. You need a role assignment, and there is no way, presently to wildcard role assignments. You need to either assign the user or group to the role; and at the present, only groups will work.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.