FreeIPA web call from Python

This was a response to a post of mine in 2010. The comment was unformatted in the response, and I wanted to get it readable. Its a great example of making a Kerberized web call.

Courtesy of Rich Megginson

Note: requires MIT kerberos 1.11 or later if you want to skip doing the kinit, and just let the script do the kinit implicitly with the keytab.

import kerberos
import sys
import os
from requests.auth import AuthBase
import requests
import json
 
class IPAAuth(AuthBase):
    def __init__(self, hostname, keytab):
        self.hostname = hostname
        self.keytab = keytab
        self.token = None
 
        self.refresh_auth()
 
    def __call__(self, request):
        if not self.token:
            self.refresh_auth()
 
        request.headers['Authorization'] = 'negotiate ' + self.token
 
        return request
 
    def refresh_auth(self):
        if self.keytab:
            os.environ['KRB5_CLIENT_KTNAME'] = self.keytab
        else:
            LOG.warn('No IPA client kerberos keytab file given')
        service = "HTTP@" + self.hostname
        flags = kerberos.GSS_C_MUTUAL_FLAG | kerberos.GSS_C_SEQUENCE_FLAG
        try:
            (_, vc) = kerberos.authGSSClientInit(service, flags)
        except kerberos.GSSError, e:
            LOG.error("caught kerberos exception %r" % e)
            raise e
        try:
            kerberos.authGSSClientStep(vc, "")
        except kerberos.GSSError, e:
            LOG.error("caught kerberos exception %r" % e)
            raise e
        self.token = kerberos.authGSSClientResponse(vc)
 
 
hostname, url, keytab, cacert = sys.argv[1:]
 
request = requests.Session()
request.auth = IPAAuth(hostname, keytab)
ipaurl = 'https://%s/ipa' % hostname
jsonurl = url % {'hostname': hostname}
request.headers.update({'Content-Type': 'application/json',
                        'Referer': ipaurl})
request.verify = cacert
 
myargs = {'method': 'dnsrecord_add',
          'params': [["testdomain.com", "test4.testdomain.com"],
                     {'a_part_ip_address': '172.31.11.4'}],
          'id': 0}
resp = request.post(jsonurl, data=json.dumps(myargs))
print resp.json()
 
myargs = {'method': 'dnsrecord_find', 'params': [["testdomain.com"], {}], 'id': 0}
resp = request.post(jsonurl, data=json.dumps(myargs))
print resp.json()

Run the script like this:

python script.py ipahost.domain.tld ‘https://%(hostname)s/ipa/json’ myuser.keytab /etc/ipa/ca.crt

Who holds the keys to the Kingdom

During the years I worked as a Web application developer, it seemed like every application had its own authentication mechanism. An application developer is thinking in terms of the domain model for their application whether it be eCommerce, Systems management, photography, or weblogs. Identity Management is a cross cutting concern, and it is hard to get right. Why, then, do so many applications have “user” tables in their databases?
Continue reading

Autoregistering an OpenStack Virtual Machine with FreeIPA

FreeIPA offers many benefits to an OpenStack deployment: Single Sign on and DNS-as-a-Service among others. In order to take advantage of freeIPA, the new host needs to be registered with the FreeIPA server. Here’s how to automate the process.

I started out with a FreeIPA server deployed in an a virtual machine inside out teams OpenStack based cloud. The server manages a domain that I have taken the liberty of calling openstack.freeipa.org. This is a non-public deployment, so don’t expect to resolve the DNS records yourself. However, IPA likes to work with Fully Qualified Domain Names, so I created one that is self documenting.

For my virtual machines images, I am using the Fedora 19 Cloud image. This is a very bare bones virtual machine.

The general steps to take in order to deploy are:

  1. Allocate a Floating IP address
  2. Generate an One Time Password (OTP)
  3. Create a Host entry in FreeIPA, using the IP Address and OTP
  4. Generate a user-data script
  5. Boot the virtual machine
  6. wait until the machine is running
  7. Allocate the Floating IP address to the Virtual Machine

Once the virtual machine is running,  the user-data script performs the following tasks:

  1. Sets the hostname of the virtual machine to match the VM name and the domain name of the IPA server
  2. Sets the FreeIPA install as the DNS server
  3. install freeipa-client via Yum
  4. register the host using the OTP

Here is the code:

#!/bin/bash
. ./keystone.rc

#These values should also come out of a configuration file:
#they are specific to your deployemnt

PUBKEY=ayoung-pubkey
IMAGE_ID=94d1dbba-9e65-471e-97d0-eb7966982c12
FLAVOR_ID=3
SECGROUP=all
DOMAIN=openstack.freeipa.org
NAMESERVER=10.16.16.143

OTP=`uuidgen -r | sed 's/-//g'`

#this should be initialized if does not yet exisit: 
#the index is an integer.
#it provides a way to keep each VM unique

INDEX=`cat index.dat`
VM_NAME=$USER-$INDEX

#get first floating IP
FLOAT_IP=`nova floating-ip-list | awk ' $4~/None/  {print $2 ; exit }' `

ipa host-add $VM_NAME.$DOMAIN --ip-address=$FLOAT_IP --password=$OTP

#increment  the index for next time
echo $(( $INDEX + 1 )) > index.dat


#Generate the user-data for postboot configuration
cat << END_HEREDOC > $VM_NAME.dat
#!/bin/bash
echo $VM_NAME.$DOMAIN > /etc/hostname
hostname $VM_NAME.$DOMAIN
echo nameserver $NAMESERVER > /etc/resolv.conf
yum -y install freeipa-client
ipa-client-install -U -w $OTP
END_HEREDOC

nova boot   --image $IMAGE_ID --flavor $FLAVOR_ID --key_name $PUBKEY --security_groups $SECGROUP  --user-data $VM_NAME.dat  $VM_NAME

#wait until the VM is out of the BUILD state before continuing
#otherwise, adding the floating IP might fail
while [ `nova show $VM_NAME | awk ' $2~ /status/ { print $4 }'` = BUILD ]
do
sleep 1
echo -n .
done
echo
echo  adding floating IP address $FLOAT_IP to $VM_NAME

nova add-floating-ip $VM_NAME $FLOAT_IP

There is more work do be done, here. DHCP integration would be preferable to this manner of munging resolv.conf. Without that, the image need to be modified to prevent DHCP from updating the resolv.conf if the VM is ever rebooted.

Care must be taken when deleting the host entries allocated to virtual machines. Since they have DNS A records, IPA will complain if you attempt to reuse an IP address without first cleaning up the DNS A record. To delete a VM, remove it from both IPA and nova like this:

nova delete ayoung-31
ipa host-del ayoung-31 --updatedns

Special thanks to Jamie Lennox for editing support.

IPTables rules for FreeIPA

I end up editing this so much, figure I’d post it here for all to use.  This is the standard IPtables config file augmented with those rules required to let through the protocols supported by FreeIPA

# Firewall configuration written by system-config-firewall
# Manual customization of this file is not recommended.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT

#TCP ports for FreeIPA
-A INPUT -m state --state NEW -m tcp -p tcp --dport 80 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 443  -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 389 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 636 -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 88  -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 464  -j ACCEPT
-A INPUT -m state --state NEW -m tcp -p tcp --dport 53  -j ACCEPT

#UDP ports for FreeIPA
-A INPUT -m state --state NEW -m udp -p udp --dport 88 -j ACCEPT
-A INPUT -m state --state NEW -m udp -p udp --dport 464 -j ACCEPT
-A INPUT -m state --state NEW -m udp -p udp --dport 123 -j ACCEPT
-A INPUT -m state --state NEW -m udp -p udp --dport 53 -j ACCEPT

-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
COMMIT