Talking to FreeIPA JSON web API via curl

One of the benefits of web APIs is that we can use command line tools to call them. FreeIPA is no different, but perhaps a hair trickier, as it combines the use of Kerberos with a strict JSON format. Getting it right took a little trial and error.

Update:  The libcurl and culr packages have been changed to make credential delegation deliberate instead of automatic.  The command below has been modified from when I originally wrote this article.

Update 2:   The HTTP server now defends against  Cross Site Request Forgery attacks.  The examples now have a new header in them: -H referer:https://$HOSTNAME/ipa

Precondition: run kinit with a valid user name.

Here’s the successful request to list all users:

curl -v  \
         -H referer:https://$HOSTNAME/ipa  \
         -H "Content-Type:application/json" \
         -H "Accept:applicaton/json"\
         --negotiate -u : \
         --cacert /etc/ipa/ca.crt  \
         -d  '{"method":"user_find","params":[[""],{}],"id":0}' \
         -X POST       https://`hostname`/ipa/json

Lets take this by the numbers.

  1. curl command, plus the verbose option
  2. Add a header telling the server we are sending a JSON request
  3. Add a header telling the server we can accept JSON in the response
  4. Negotiate authentication.  We fake out curl by telling it to use no userid:password in the -u option
  5. UPDATE: Removed the option to Delegate credentials. No longer required due to S4U2Proxy. Delegating TGTs is a bad idea, but in the past, you had to send your TGT back to the FreeIPA server so it can potentially modify the LDAP database, and it needed to do so as ‘you.’
  6. I am posting from a CLI on  the server machine.  This just uses the same ca cert file that the as web server uses.  TODO: Figure out how to get this file from a remote machine.
  7. here is the json request.  Note that all quotes must be escaped, as we are sending this from the command line.
  8. Use the HTTP verb post to the URL specified.

If you want to do this from a remote machine, you can get the ca.cert with:

curl -k https://$YOURHOST/ipa/config/ca.crt >> /tmp/ipa.ca.cert

and modify the portion of the curl command line from:

–cacert /etc/ipa/ca.crt

to

–cacert /tmp/ipa.ca.cert

Update! Now using a file. Notice the @ sign in the -d option

curl -v  \
         -H referer:https://$HOSTNAME/ipa \
         -H "Content-Type:application/json" \
         -H "Accept:applicaton/json"\
         --negotiate -u : \
         --cacert /etc/ipa/ca.crt  \
         -d  @/home/ayoung/sampledata.json \
         -X POST       https://`hostname`/ipa/json

Update: About the curl change: Make sure your libraries are at least:

  1. curl-7.21.3-10
  2. python-pycurl-7.19.0-9
  3. libcurl-7.21.3-10
  4. libcurl-devel-7.21.3-10

One of the benefits of web APIs is that we can use command line tools to call them. FreeIPA is no different, but perhaps a hair trickier, as it combines the use of Kerberos with a strict JSON format. Getting it right took a little trial and error.

Update:  The libcurl and culr packages have been changed to make credential delegation deliberate instead of automatic.  The command below has been modified from when I originally wrote this article.

Precondition: run kinit with a valid user name.

Here’s the successful request to list all users:

curl -v  \
         -H "Content-Type:application/json" \
         -H "Accept:applicaton/json"\
         --negotiate -u : \
         --cacert /etc/ipa/ca.crt  \
         -d  '{"method":"user_find","params":[[""],{}],"id":0}' \
         -X POST       https://`hostname`/ipa/json

Lets take this by the numbers.

  1. curl command, plus the verbose option
  2. Add a header telling the server we are sending a JSON request
  3. Add a header telling the server we can accept JSON in the response
  4. Negotiate authentication.  We fake out curl by telling it to use no userid:password in the -u option
  5. Delegate credentials. Usually a bad idea, but in the case of FreeIPA, you are sending your TGT back to the FreeIPA server so it can potentially modify the LDAP database, and it needs to do so as ‘you.’
  6. I am posting from a CLI on  the server machine.  This just uses the same ca cert file that the as web server uses.
  7. here is the json request.  Note that all quotes must be escaped, as we are sending this from the command line.
  8. Use the HTTP verb post to the URL specified.

If you want to do this from a remote machine, you can get the ca.cert with:

curl -k https://$YOURHOST/ipa/config/ca.crt >> /tmp/ipa.ca.cert

and modify the portion of the curl command line from:

–cacert /etc/ipa/ca.crt

to

–cacert /tmp/ipa.ca.cert

Update! Now using a file. Notice the @ sign in the -d option

curl -v  \
         -H "Content-Type:application/json" \
         -H "Accept:applicaton/json"\
         --negotiate -u : \
         --cacert /etc/ipa/ca.crt  \
         -d  @/home/ayoung/sampledata.json \
         -X POST       https://`hostname`/ipa/json

Update: About the curl change: Make sure your libraries are at least:

  1. curl-7.21.3-10
  2. python-pycurl-7.19.0-9
  3. libcurl-7.21.3-10
  4. libcurl-devel-7.21.3-10

For more information about the change see this:  http://curl.haxx.se/docs/adv_20110623.html

 

Update:   We’ve put in code to pretect against a Cross Site Request Forgery.  The examples now have a new header: -H referer:https://$HOSTNAME/ipa

9 thoughts on “Talking to FreeIPA JSON web API via curl

  1. BTW- is there any way to avoid kinit before curl?
    I want to make IPA fully automatic in my environment

  2. OK, I’ve found a bit better way (keytab won’t work in IPA due to salted passwords).

    First, you have to authenticate to IPA and get cookie

    local sessid=$(curl -v \
    -H referer:https://ipa.mybox/ipa/ui/index.html \
    -H “Content-Type:application/x-www-form-urlencoded” \
    -H “Accept:*/*” \
    –negotiate -u : \
    –cacert /etc/ipa/ca.crt \
    -d “user=USERNAME” -d “password=PASSWORD” \
    -X POST \
    https://ipa.mybox/ipa/session/login_password \
    2>&1 | grep -o “ipa_session=[a-zA-Z0-9]*”)

    then use this cookie to authenticate in cookie version of JSON API:

    curl -v \
    -H referer:https://ipa.mybox/ipa/ui/index.html \
    -H “Content-Type:application/json” \
    -H “Accept:applicaton/json” \
    –negotiate -u : \
    –cacert /etc/ipa/ca.crt \
    –cookie “$sessid” \
    -d “$postdata” \
    -X POST \
    https://ipa.mybox/ipa/session/json

  3. I wouldn’t suggest doing that. Much better to do:

    echo $PASSWORD | kinit $USERNAME

    and then use Curl. I understand why we had to enable Password based authentication in recent versions of IPA, but I wouldn’t recommend using it unless absolutely needed.

    Can you expand on “keytab won’t work in IPA due to salted passwords”

  4. Here’s how you do it in python. 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

  5. @uosiu i used ur sollution concerning adding the cookie, but it gives me 401 when i try to send a curl request using the cookie: Unable to verify your Kerberos credentials …)
    any help would be apreciated wella 😉

  6. Thank for such a good post.I am facing issue when i try to call the rest api it says 500 internal server error.As per the log s it is not able to get the credentials for HTTP principal even though i am iniitalizing ticket using the keytab.Can anyone please help?

  7. The 500 means the server itself is misconfigured (obviously) so I suspect that either the keytab is in the wrong place, permissions, wrong principal in it, or some other mistconfiguration. Check SELinux, too.

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.