Occasionally I see questions on how to drive FreeIPA programmatically. One can use ipa <command> from enrolled IPA clients or go directly to Python API (as /usr/sbin/ipa utility is just a tiny shim over the Python API). However, if you want to drive operations from other frameworks or from non-IPA clients, there is another way and it is actually very simple.

FreeIPA web UI is one example of such use. It is a JavaScript-based application which is downloaded by the browser when visiting IPA web site. The application bootstraps itself and issues JSON-RPC requests to the server. Browser does authentication and caching via cookies.

Surely, we can achieve the same from any other framework. There are two separate stages we’d have to go through to avoid constant re-authentication:

  1. Authenticate against IPA server and remember the cookie for our session

  2. Use cookie with session data while issuing our commands

There are separate authentication end points in IPA, one for Kerberos and another for password-based authentication. Both return a session cookie which we need to store and present to the server. If cookie is invalid, authentication need to be repeated again. This flow is well-known to any web application developer so no surprises here.

FreeIPA RPC expectations

In order to support secure web application development, FreeIPA web API expects web applications set HTTP referer back to the IPA host. Web browsers do this automatically, other frameworks have to provide it explicitly.

For example, with curl you’d need to specify -H referer:https://$IPAHOSTNAME/ipa.

Kerberos authentication

To authenticate with Kerberos, post an authorization request using Negotiate scheme over HTTPS to the end point at https://$IPAHOSTNAME/ipa/session/login_kerberos. You need to have actual credentials in your credentials cache (ccache) prior to sending the request. The way Kerberos authentication is done, one obtains a ticket granting ticket (TGT) first, storing it in a ccache and then an application can request a ticket to a service using existing TGT. A typical non-interactive use is when ccache is initialized with the TGT based on pre-existing key from a service keytab:

$ export KRB5CCNAME=FILE:/path/to/ccache
$ export COOKIEJAR=/path/to/my.cookie
$ export IPAHOSTNAME=ipa-master.example.com
$ kinit -k -t /path/to/service.keytab service/ipa-client.example.com
$ curl -v  \
        -H referer:https://$IPAHOSTNAME/ipa  \
        -c $COOKIEJAR -b $COOKIEJAR \
        --cacert /etc/ipa/ca.crt  \
        --negotiate -u : \
        -X POST \
        https://$IPAHOSTNAME/ipa/session/login_kerberos

If authentication was successful, our $COOKIEJAR file will contain all session cookies returned by FreeIPA web server. We don’t need to authenticate anymore until the session expires. All we need to do is to ensure we pass back the session cookies back to the server.

Password Authentication

Password authentication is more traditional: post over HTTPS a form with username and password fields to the end point at https://$IPAHOSTNAME/ipa/session/login_password. A form has to be setup with appropriate MIME type, application/x-www-form-urlencoded but the rest is plain old HTTPS post.

$ export COOKIEJAR=/path/to/my.cookie
$ export IPAHOSTNAME=ipa-master.example.com
$ s_username=admin s_password=mYSecReT1P2 curl -v  \
        -H referer:https://$IPAHOSTNAME/ipa  \
        -H "Content-Type:application/x-www-form-urlencoded" \
        -H "Accept:text/plain"\
        -c $COOKIEJAR -b $COOKIEJAR \
        --cacert /etc/ipa/ca.crt  \
        --data "user=$s_username&password=$s_password" \
        -X POST \
        https://$IPAHOSTNAME/ipa/session/login_password

If authentication was successful, our $COOKIEJAR file will contain all session cookies returned by FreeIPA web server. We don’t need to authenticate anymore until the session expires. All we need to do is to ensure we pass back the session cookies back to the server.

Sending JSON-RPC request

A session-based request needs to be posted to https://$IPAHOSTNAME/ipa/session/json end point over HTTPS. A content type should set to application/json and HTTP POST method has to be used. There isn’t any difference, again, from a typical JSON-RPC. Session cookies should not be forgotten, of course, or our request will fail spectacular.

FreeIPA JSON-RPC interface is not documented. However, it is easy to discover via existing command line utility, /usr/sbin/ipa by supplying -vv option to it:

$ ipa -vv ping
ipa: INFO: trying https://ipa-master.example.com/ipa/session/json
ipa: INFO: Forwarding 'ping' to json server 'https://ipa-master.example.com/ipa/session/json'
ipa: INFO: Request: {
    "id": 0,
    "method": "ping",
    "params": [
        [],
        {
            "version": "2.117"
        }
    ]
}
ipa: INFO: Response: {
    "error": null,
    "id": 0,
    "principal": "admin@EXAMPLE.COM",
    "result": {
        "summary": "IPA server version 4.1.99.201505121153GITed639c7. API version 2.117"
    },
    "version": "4.1.99.201505121153GITed639c7"
}
-------------------------------------------------------------------
IPA server version 4.1.99.201505121153GITed639c7. API version 2.117
-------------------------------------------------------------------

Each request has a method and parameters, params structure. A method is a command to execute. Commands of /usr/sbin/ipa utility have simple structure of topic-action and methods corresponding for them are topic_action, i.e. dash is replaced by underscore. This is because they map one to one to Python API classes.

Structure of parameters is simple too. params is an array of two elements: . an array of positional arguments, and . a dictionary of options

As can be seen above, ping method does not have positional arguments and IPA command line client always sends own version as an option.

Another example is user-show with --raw option passed:

$ ipa -vv user-show admin --raw
ipa: INFO: trying https://ipa-master.example.com/ipa/session/json
ipa: INFO: Forwarding 'user_show' to json server 'https://ipa-master.example.com/ipa/session/json'
ipa: INFO: Request: {
    "id": 0,
    "method": "user_show",
    "params": [
        [
            "admin"
        ],
        {
            "all": false,
            "no_members": false,
            "raw": true,
            "rights": false,
            "version": "2.117"
        }
    ]
}
[.... lots of output ....]

The resulting entry output is skipped for brevity.

Gathering things together

Finally, a script below combines both password and Kerberos authentication and then sends user_find request to receive list of all users.

# testcurl.sh
s_username=admin
s_password=mYSecReT1P2
IPAHOSTNAME=ipa-master.example.com
COOKIEJAR=my.cookie.jar
#rm -f $COOKIEJAR

klist -s
use_kerberos=$?

if [ ! -f $COOKIEJAR ] ; then
 if [ $use_kerberos -eq 0 ] ; then
        # Login with Kerberos
        curl -v  \
        -H referer:https://$IPAHOSTNAME/ipa  \
        -c $COOKIEJAR -b $COOKIEJAR \
        --cacert /etc/ipa/ca.crt  \
        --negotiate -u : \
        -X POST \
        https://$IPAHOSTNAME/ipa/session/login_kerberos
  else
        # Login with user name and password
        curl -v  \
        -H referer:https://$IPAHOSTNAME/ipa  \
        -H "Content-Type:application/x-www-form-urlencoded" \
        -H "Accept:text/plain"\
        -c $COOKIEJAR -b $COOKIEJAR \
        --cacert /etc/ipa/ca.crt  \
        --data "user=$s_username&password=$s_password" \
        -X POST \
        https://$IPAHOSTNAME/ipa/session/login_password
  fi
fi

# Send user_find method request
curl -v  \
	-H referer:https://$IPAHOSTNAME/ipa  \
        -H "Content-Type:application/json" \
        -H "Accept:applicaton/json"\
        -c $COOKIEJAR -b $COOKIEJAR \
        --cacert /etc/ipa/ca.crt  \
        -d  '{"method":"user_find","params":[[""],{}],"id":0}' \
        -X POST \
        https://$IPAHOSTNAME/ipa/session/json

Using JSON-RPC calls on non-IPA clients

If FreeIPA JSON-RPC API needs to be access from non-enrolled client, there is a bit more work. Kerberos authentication would most likely require properly configured /etc/krb5.conf. Luckily, the configuration can be copied over from existing IPA client and placed somewhere --- Kerberos library allows to specify configuration file via KRB5_CONFIG environmental variable. A keytab can be copied over too but make sure to store it securely.

Another thing to copy over to a non-IPA client is a CA root certificate to allow secure HTTPS communication. Other than that, everything stays the same — authenticate first, store session cookies, and re-use them.