FreeIPA management API in a nutshell

FreeIPA project unifies configuration and management of multiple components:

  • LDAP server named 389-ds;
  • Kerberos KDC, provided by MIT Kerberos project;
  • Certificate Authority, based on Dogtag project;
  • A SMB protocol domain controller, using Samba;
  • DNS server with DNSSEC support, based on ISC BIND with PKCS11 support.

Installation tools provided by FreeIPA allow to deploy these components and store their configuration and user data in an LDAP server database, accessible with credentials issued by the Kerberos KDC. FreeIPA itself provides a management framework through which the data in the LDAP server can easily be modified without the need to turn to specifics of each component’s software.

This document describes how to access FreeIPA management framework features in a programmatic way remotely over HTTPS connection. The framework itself is written in Python and is run as a web application. Its client side, also written in Python, hides all details on how to communicate with the server side. Thus, using client code in Python allows for easier consumption of FreeIPA management API under well-defined conditions. For more flexibility and other programming language environments a direct access to HTTPS-based end-points can be used.

Introduction

At its core FreeIPA management framework performs following actions:

  • Authenticates incoming HTTPS connection and maps authenticated entity to a known Kerberos principal in a FreeIPA realm;
  • Gathers input data (command name, arguments, options) from the JSON-RPC-formatted request;
  • Dispatches execution of a command referenced by the request;
  • Transparently allows to re-use authenticated Kerberos principal when a command needs to access LDAP data store;
  • Provides FreeIPA object mapping to objects stored in LDAP and allows to manipulate LDAP objects;
  • Gathers results of the command execution and marshals them to JSON-RPC format;
  • Sends out the result of the processed request as a JSON-RPC formatted response.

Majority of FreeIPA management commands require access to the LDAP data store. The access is performed under credentials of the entity authenticated to access the API, thus the entity must be known to the LDAP server. In more specific terms, since FreeIPA management framework always authenticates to the LDAP server using SASL GSSAPI authentication method, the authenticated entity must be a valid Kerberos principal which has a valid LDAP objects in the LDAP data store.

A FreeIPA framework exchanges are based on the JSON-RPC v1.0 format. The framework extends the specification to add few properties and behavior changes required to express communication within FreeIPA.

Request

A remote command is invoked by sending a request to a remote service. The request is a single object serialized using JSON notation.

It has three properties:

  • method - A String containing the name of the command to be invoked.
  • params - An Array of objects to pass as arguments to the command.
  • id - The request id. This can be of any type. It is used to match the response with the request that it is replying to.

Response

When the command invocation completes, the service must reply with a response. The response is a single object serialized using JSON.

It has four properties:

  • result - The Object that was returned by the invoked command. This must be null in case there was an error invoking the command.
  • princial - The Kerberos principal under which identity the request was performed. This is extension of JSON-RPC v1.0 format.
  • error - An Error object if there was an error invoking the command. It must be null if there was no error.
  • id - This must be the same id as the request it is responding to.

Structure of result JSON object differs from command to command. In addition to returned results, it may contain an array messages with a number of JSON objects containing additional diagnostic information provided by the FreeIPA management framework. The structure of the JSON object within the messages array is following:

  • code - numeric code of the message
  • data - a dictionary of properties related to the message
  • message - actual message string
  • name - name of the message class within the FreeIPA framework
  • type - type of the message (warning, etc)

FreeIPA command and object concepts

JSON-RPC format requires to specify a command to call and its parameters. To understand how commands are named and structured, we would first describe concepts behind FreeIPA management interface.

Commands in FreeIPA are split into two three different sets. Commands which operate on an LDAP object are called methods. Each method always has an associated LDAP object. Commands which don’t have associated LDAP objects are simply called commands. For example, certificates are typically stored in other LDAP objects as an attribute value, they don’t have own LDAP object.

All types of commands are structured into topics. Each topic gathers together operations related to the processing of the same object type or related to that object type. As an example, certificate-related commands are gathered together under cert topic. Since it is common to create, modify, and delete objects, many common operations can be found under multiple topics.

The FreeIPA framework command is addressed by specifying a topic and operation as a string, with underscore sign combining the two:

  • user_add – a command that creates (adds) a user object
  • group_mod – a command that changes (modifies) a group object
  • host_del – a command that removes (deletes) a host object

Each topic mentioned above (‘user’, ‘group’, or ‘host’) has all common operations implemented (‘add’, ‘mod’, ‘del’), as well as topic-specific actions. From JSON-RPC format perspective all of them can be addressed by constructing a command name of the topic and the operation name, with the underscore between them. At this level both commands and methods can be constructed this way. The difference is only visible when requesting a metadata about objects, commands, and methods.

Each JSON-RPC command has positional arguments and options. In JSON-RPC format these are specified as part of the params array in the following order:

  1. arguments as an array
  2. options as a dictionary

An example below shows a complete JSON-RPC request asking for execution of the user_show command with all option specified. version option is always required by the FreeIPA API conventions. If ipa command line utility were used, the corresponding command line would look like

$ ipa user-show --all john

Sample JSON-RPC:

{
    "id": 0, 
    "method": "user_show", 
    "params": [
        [
            "john"
        ], 
        {
            "all": true, 
            "version": "2.215"
        }
    ]
}

A JSON-RPC request without version option will be accepted by the FreeIPA server too but the response will contain a warning about a missing version in the messages object of the result similar to the following example (only relevant part of the result object is shown):

{
    "error": null, 
    "id": 0, 
    "principal": "admin@EXAMPLE.COM", 
    "result": {
        "messages": [
            {
                "code": 13001, 
                "data": {
                    "server_version": "2.212"
                }, 
                "message": "API Version number was not sent, forward compatibility not guaranteed. Assuming server's API version, 2.212", 
                "name": "VersionMissing", 
                "type": "warning"
            }
        ], 
    }, 
    "version": "4.4.1"
}

The response returned by the server will contain all attributes associated with the user object john. Some of them will be transformed by the framework from their internal LDAP representation but most attributes will stay the same.

Below is an example of a response with { "all": true } option specified.

{
    "error": null, 
    "id": 0, 
    "principal": "admin@EXAMPLE.COM", 
    "result": {
        "result": {
            "cn": [
                "John Smith"
            ], 
            "displayname": [
                "John Smith"
            ], 
            "dn": "uid=john,cn=users,cn=accounts,dc=example,dc=com", 
            "gecos": [
                "John Smith"
            ], 
            "gidnumber": [
                "1792600072"
            ], 
            "givenname": [
                "John"
            ], 
            "has_keytab": true, 
            "has_password": true, 
            "homedirectory": [
                "/home/john"
            ], 
            "initials": [
                "JS"
            ], 
            "ipantsecurityidentifier": [
                "S-1-5-21-245462123-1556911123-2572160461-1072"
            ], 
            "ipauniqueid": [
                "b071df7a-4038-11e6-844c-001a4a418612"
            ], 
            "krblastpwdchange": [
                {
                    "__datetime__": "20160702094511Z"
                }
            ], 
            "krbpasswordexpiration": [
                {
                    "__datetime__": "20160702094511Z"
                }
            ], 
            "krbprincipalname": [
                "john@EXAMPLE.COM"
            ], 
            "loginshell": [
                "/bin/sh"
            ], 
            "mail": [
                "john@example.com"
            ], 
            "memberof_group": [
                "ipausers"
            ], 
            "nsaccountlock": false, 
            "objectclass": [
                "ipaSshGroupOfPubKeys", 
                "krbticketpolicyaux", 
                "ipaobject", 
                "mepOriginEntry", 
                "ipantuserattrs", 
                "top", 
                "ipasshuser", 
                "inetorgperson", 
                "organizationalperson", 
                "person", 
                "krbprincipalaux", 
                "inetuser", 
                "posixaccount"
            ], 
            "preserved": false, 
            "sn": [
                "Smith"
            ], 
            "uid": [
                "john"
            ], 
            "uidnumber": [
                "1792600072"
            ]
        }, 
        "summary": null, 
        "value": "John"
    }, 
    "version": "4.4.1"
}

When option { "all": true } is not specified, the response is shorter:

{
    "error": null, 
    "id": 0, 
    "principal": "admin@EXAMPLE.COM", 
    "result": {
        "result": {
            "dn": "uid=john,cn=users,cn=accounts,dc=example,dc=com", 
            "gidnumber": [
                "1792600072"
            ], 
            "givenname": [
                "John"
            ], 
            "has_keytab": true, 
            "has_password": true, 
            "homedirectory": [
                "/home/john"
            ], 
            "krbprincipalname": [
                "john@EXAMPLE.COM"
            ], 
            "loginshell": [
                "/bin/sh"
            ], 
            "mail": [
                "john@example.com"
            ], 
            "memberof_group": [
                "ipausers"
            ], 
            "nsaccountlock": false, 
            "sn": [
                "Smith"
            ], 
            "uid": [
                "john"
            ], 
            "uidnumber": [
                "1792600072"
            ]
        }, 
        "summary": null, 
        "value": "john"
    }, 
    "version": "4.4.1"
}

Actual content of the response depends on the command being called, object it manipulates on, options specified, and access rights associated with the Kerberos principal authenticated to access the API end points.

FreeIPA API end points

On the server side FreeIPA management framework is run as a web application within Apache web server’s virtual host. As the framework is installed under https://ipa-server.example.com/ipa, below all end points will be shown as /ipa/path, without using full URL syntax.

Content under /ipa prefix is protected with GSSAPI authentication. To access it, every request must be authenticated. GSSAPI authentication negotiation requires multiple rounds but to allow convenient use of it FreeIPA framework supports use of HTTP sessions.

Access to the session initiation end points is provided for GSSAPI and password-based authentication. Once a client performed authentication, a session cookie is issued which is valid for certain time. When client presents the valid cookie upon next request, no redirect to authentication end-points is performed.

All end points expect that a client sets HTTP referrer value to the framework’s primary URL, https://ipa-server.example.com/ipa. This means HTTP request must have HTTP ‘Referer’ header field set:

Referer: https://ipa-server.example.com/ipa

Note that a referrer field is called ‘referer’ in the HTTP 1.1 specification, not ‘referrer’.

/ipa/session/json is the primary end point for accessing FreeIPA API via JSON-RPC with HTTP sessions. The end point expects an HTTP POST request with a session cookie which was previously obtained through any of the authentication end points. The body of the request is the JSON-formated data for the JSON-RPC request.

Since /ipa/session/json end point deals with JSON requests, a request HTTP header should contain two additional fields, specifying expected and accepted content types to applicaiton/json. Altogether, a valid JSON-RPC request must contain the following HTTP headers when accessing JSON-RPC over the session end point:

POST /ipa/session/json HTTP/1.1
Host: ipa-server.example.com
Referer: https://ipa-server.example.com/ipa
Content-type: application/json
Accept: application/json
Cookie: ipa_session=...cookie..value...

Authentication and Session management

Session authentication end points handle GSSAPI and password-based logon access. Both end points require HTTP POST requests, similar to the /ipa/session/json end point.

GSSAPI authentication

/ipa/session/login_kerberos is the end point to authenticate with GSSAPI negotiation according to the RFC 4559. In FreeIPA 4.4 and earlier only Kerberos principals from the FreeIPA realm are accepted.

The request may require several rounds, as defined in the RFC 4559. In the case of successful GSSAPI negotiation a session cookie is returned with HTTP 1.1 status code ‘200 Success’. This cookie should then be presented to /ipa/session/json end point.

Following example shows how to access /ipa/usession/login_kerberos end point from curl command. $COOKIEJAR should contain a name of the file to store resulting cookies. The example assumes that Kerberos credentials were earlier obtains with the help of kinit command or by login to the system.

    # Login with Kerberos
    $ curl -v  \
    -H Referer:https://ipa-server.example.com/ipa  \
    -c $COOKIEJAR -b $COOKIEJAR \
    --cacert /etc/ipa/ca.crt  \
    --negotiate -u : \
    -X POST \
    https://ipa-server.example.com/ipa/session/login_kerberos

Password-based authentication

/ipa/session/login_password end point implements parsing of an HTML form with two mandatory fields, username and password. The request must contain two following HTTP headers in addition the Referer header:

Content-Type: application/x-www-form-urlencoded
Accept: text/plain

The successful authentication returns HTTP 1.1 status code ‘200 Success’ and sets a Cookie header to the cookie value which should be stored and later presented to /ipa/session/json end point.

Following example shows how to access /ipa/usession/login_password end point from curl command. $COOKIEJAR should contain a name of the file to store resulting cookies. $_USERNAME should contain the name of the user to authenticate. $_PASSWORD should contain the password of the authenticating user.

    # Login with user name and password
    $ curl -v  \
    -H Referer:https://ipa-server.example.com/ipa  \
    -H "Content-Type:application/x-www-form-urlencoded" \
    -H "Accept:text/plain"\
    -c $COOKIEJAR -b $COOKIEJAR \
    --cacert /etc/ipa/ca.crt  \
    --data "user=$_USERNAME&password=$_PASSWORD" \
    -X POST \
    https://ipa-server.example.com/ipa/session/login_password

Examples

All examples in this section are based on the common pattern shown with the help of curl utility. In the examples below $JSON_PAYLOAD contains actual JSON-RPC request body.

Send JSON-RPC request with curl

Below is the common pattern to access FreeIPA JSON-RPC end point with curl.

curl -v  \
    -H referer:https://ipa-server.example.com/ipa  \
    -H "Content-Type:application/json" \
    -H "Accept:applicaton/json"\
    -c $COOKIEJAR -b $COOKIEJAR \
    --cacert /etc/ipa/ca.crt  \
    -d  $JSON_PAYLOAD \
    -X POST \
    https://ipa-server.example.com/ipa/session/json

Request host details

In this example we would ask FreeIPA framework to return information about a specific host.

{
    "method" : "host_show",
    "params":[
        ["ipa-client.example.com"],
        {
            "version": "2.215",
        },
    ],
    "id":0
}

The response would look like the following output:

{
    "error": null, 
    "id": 0, 
    "principal": "admin@EXAMPLE.COM", 
    "result": { 
        "result": {
            "dn": "fqdn=ipa-client.example.com,cn=computers,cn=accounts,dc=example,dc=com", 
            "fqdn": [
                "ipa-client.example.com"
            ], 
            "has_keytab": true, 
            "has_password": false, 
            "krbcanonicalname": [
                "host/ipa-client.example.com@EXAMPLE.COM"
            ], 
            "krbprincipalname": [
                "host/ipa-client.example.com@EXAMPLE.COM"
            ], 
            "managedby_host": [
                "ipa-client.example.com"
            ],  
            "sshpubkeyfp": [
                "3C:1D:3C:B4:9A:75:91:75:39:E7:FA:95:4D:13:CB:88 (ssh-ed25519)", 
                "24:35:0D:D7:E6:58:36:93:0C:E8:02:22:06:FB:F3:A8 (ecdsa-sha2-nistp256)", 
                "FF:59:31:6D:2A:2A:29:D8:5B:76:A6:CD:BC:5F:D2:70 (ssh-rsa)"
            ]
        }, 
        "summary": null, 
        "value": "ipa-client.example.com"
    }, 
    "version": "4.4.1"
}

Request server environment details

A client needs to know an API version supported by the server. However, not all servers return a warning message with the API version and a server in JSON-RPC typically returns only a server version, not the API version.

To discover API version one can request information about the server environment:

{
    "method" : "env",
    "params":[
        [],
        {},
    ],
    "id":0
}

The response will contain a result that has api_version property:

{
    "error": null, 
    "id": 0, 
    "principal": "admin@EXAMPLE.COM", 
    "result": {
        "count": 109, 
        "messages": [
            {
                "code": 13001, 
                "data": {
                    "server_version": "2.212"
                }, 
                "message": "API Version number was not sent, forward compatibility not guaranteed. Assuming server's API version, 2.212", 
                "name": "VersionMissing", 
                "type": "warning"
            }
        ], 
        "result": {
            "api_version": "2.212", 
            "basedn": "dc=example,dc=com", 
            "bin": "/", 
            "ca_agent_install_port": null, 
            "ca_agent_port": 443, 
            "ca_ee_install_port": null, 
            "ca_ee_port": 443, 
            "ca_host": "ipa-server.example.com", 
            "ca_install_port": null, 
            "ca_port": 80, 
            "conf": "/etc/ipa/server.conf", 
            "conf_default": "/etc/ipa/default.conf", 
            "confdir": "/etc/ipa", 
            "config_loaded": true, 
            "container_accounts": "cn=accounts", 
            "container_adtrusts": "cn=ad,cn=trusts", 
            "container_applications": "cn=applications,cn=configs,cn=policies", 
            "container_automember": "cn=automember,cn=etc", 
            "container_automount": "cn=automount", 
            "container_ca": "cn=cas,cn=ca", 
            "container_caacl": "cn=caacls,cn=ca", 
            "container_certprofile": "cn=certprofiles,cn=ca", 
            "container_cifsdomains": "cn=ad,cn=etc", 
            "container_configs": "cn=configs,cn=policies", 
            "container_custodia": "cn=custodia,cn=ipa,cn=etc", 
            "container_deleteuser": "cn=deleted users,cn=accounts,cn=provisioning", 
            "container_deskprofile": "cn=desktop-profile", 
            "container_deskprofilerule": "cn=rules,cn=desktop-profile", 
            "container_dna": "cn=dna,cn=ipa,cn=etc", 
            "container_dna_posix_ids": "cn=posix-ids,cn=dna,cn=ipa,cn=etc", 
            "container_dns": "cn=dns", 
            "container_dnsservers": "cn=servers,cn=dns", 
            "container_group": "cn=groups,cn=accounts", 
            "container_hbac": "cn=hbac", 
            "container_hbacservice": "cn=hbacservices,cn=hbac", 
            "container_hbacservicegroup": "cn=hbacservicegroups,cn=hbac", 
            "container_host": "cn=computers,cn=accounts", 
            "container_hostgroup": "cn=hostgroups,cn=accounts", 
            "container_locations": "cn=locations,cn=etc", 
            "container_masters": "cn=masters,cn=ipa,cn=etc", 
            "container_netgroup": "cn=ng,cn=alt", 
            "container_otp": "cn=otp", 
            "container_permission": "cn=permissions,cn=pbac", 
            "container_policies": "cn=policies", 
            "container_policygroups": "cn=policygroups,cn=configs,cn=policies", 
            "container_policylinks": "cn=policylinks,cn=configs,cn=policies", 
            "container_privilege": "cn=privileges,cn=pbac", 
            "container_radiusproxy": "cn=radiusproxy", 
            "container_ranges": "cn=ranges,cn=etc", 
            "container_realm_domains": "cn=Realm Domains,cn=ipa,cn=etc", 
            "container_rolegroup": "cn=roles,cn=accounts", 
            "container_roles": "cn=roles,cn=policies", 
            "container_s4u2proxy": "cn=s4u2proxy,cn=etc", 
            "container_selinux": "cn=usermap,cn=selinux", 
            "container_service": "cn=services,cn=accounts", 
            "container_stageuser": "cn=staged users,cn=accounts,cn=provisioning", 
            "container_sudocmd": "cn=sudocmds,cn=sudo", 
            "container_sudocmdgroup": "cn=sudocmdgroups,cn=sudo", 
            "container_sudorule": "cn=sudorules,cn=sudo", 
            "container_topology": "cn=topology,cn=ipa,cn=etc", 
            "container_trusts": "cn=trusts", 
            "container_user": "cn=users,cn=accounts", 
            "container_vault": "cn=vaults,cn=kra", 
            "container_views": "cn=views,cn=accounts", 
            "container_virtual": "cn=virtual operations,cn=etc", 
            "context": "server", 
            "debug": false, 
            "delegate": false, 
            "dogtag_version": 10, 
            "domain": "example.com", 
            "dot_ipa": "/usr/share/httpd/.ipa", 
            "enable_ra": true, 
            "fallback": true, 
            "force_schema_check": false, 
            "home": "/usr/share/httpd", 
            "host": "ipa-server.example.com", 
            "in_server": true, 
            "in_tree": false, 
            "interactive": true, 
            "ipalib": "/usr/lib/python2.7/site-packages/ipalib", 
            "jsonrpc_uri": "https://ipa-server.example.com/ipa/json", 
            "ldap_uri": "ldapi://%2fvar%2frun%2fslapd-EXAMPLE-COM.socket", 
            "log": null, 
            "logdir": "/var/log/ipa", 
            "mode": "production", 
            "mount_ipa": "/ipa/", 
            "nss_dir": "/etc/ipa/nssdb", 
            "plugins_on_demand": false, 
            "prompt_all": false, 
            "ra_plugin": "dogtag", 
            "realm": "EXAMPLE.COM", 
            "recommended_max_agmts": 4, 
            "rpc_protocol": "jsonrpc", 
            "script": "/mod_wsgi", 
            "server": "ipa-server.example.com", 
            "session_auth_duration": "20 minutes", 
            "session_duration_type": "inactivity_timeout", 
            "site_packages": "/usr/lib/python2.7/site-packages", 
            "skip_version_check": false, 
            "startup_timeout": 300, 
            "startup_traceback": false, 
            "tls_version_max": "tls1.2", 
            "tls_version_min": "tls1.0", 
            "validate_api": false, 
            "verbose": 0, 
            "version": "4.4.1", 
            "wait_for_dns": 0, 
            "webui_prod": true, 
            "xmlrpc_uri": "https://ipa-server.example.com/ipa/xml"
        }, 
        "summary": "109 variables", 
        "total": 109
    }, 
    "version": "4.4.1"
}

As can be noted, the server environment information provides a lot of useful details that can be used to tune the client. For example, session_auth_duration says how long the cookie would be valid.

Discovering API structure

FreeIPA 4.2 included an API browser to the Web UI, available under IPA Server tab. It allows to browse through all API commands, see information about each of them, including their arguments and options.

With FreeIPA 4.4 new methods were added to the framework to help with discovery of API structure. The most comprehensive method is called schema and accepts no arguments or options.

{
    "method" : "schema",
    "params":[
        [],
        {},
    ],
    "id":0
}

It returns all details about FreeIPA API schema which include all commands, all object classes, and all topics. For all commands structure of their parameters and options is reported. For all object classes their properties are reported. This command is not available directly in the FreeIPA command line interface (ipa utility). The resulting JSON output from executing this JSON-RPC command is large (around 2 MiB in FreeIPA 4.4) and it is advised to cache the result.

It is possible to discover topics, classes, and commands individually with topic_find and topic_show, class_find and class_show, and command_findand command_show methods. These commands are available in the FreeIPA command line interface.

FreeIPA command line interface can also be used to discover proper format of JSON-RPC payload: with ipa -vv the command line tool will print out details of its communication with FreeIPA server.