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"}} |
Adam, I was able to set up keystone federation using shibboleth, I came across an error when I tried to use the scoped token for doing anything useful with nova or client. Its a simple fix, thought I would bring to your notice.
https://bugs.launchpad.net/keystone/+bug/1346820
Thanks,
Mahesh
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.
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.