About Adam Young

Once upon a time I was an Army Officer, but that was long ago. Now I work as a Software Engineer. I climb rocks, play saxophone, and spend way too much time in front of a computer.

Keeping DHCP from changing the Nameserver

I’m running FreeIPA in an OpenStack lab. I don’t control the DHCP server. When a host renews its lease, the dhclient code overwrites the nameserver values in /etc/resolv.conf. To avoid this, I modified /etc/dhcp/dhclient.conf

interface "eth0" {
 prepend domain-name-servers 192.168.187.12;
}

This makes sure my custom nameserver stays at the top of the list. Its a small hack that is perfect for developer work.

Kerberizing Keystone in HTTPD

Configuring Kerberos as the authentication mechanism for Keystone is not much different than Kerberizing any other Web application. The general steps are:

  1. Configure Keystone to Run with an LDAP backend
  2. Configure Keystone to Run in Apache HTTPD
  3. Register the Keystone server as an Kerberos Client (I use FreeIPA)
  4. Establish a Kerberized URL for $OS_AUTH_URL

Continue reading

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


 WSGIProcessGroup keystone_admin
 NSSRequireSSL
 Authtype none



 WSGIProcessGroup keystone_main
 NSSRequireSSL
 Authtype none



  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



  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 ";"



  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 ";"

[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"}}