Jump to: navigation, search



Gerrit Patch [1]
Launchpad Blueprint [2]


This implements support for using FreeIPA as a backend. FreeIPA has full support for DNS, using the JSON RPC interface for dnszone (domain) and dnsrecord commands.


These requirements are _not_ in requirements.txt, so they are not installed by default. Designate provides a file ipa-requirements.txt which can be used with pip install -r ipa-requirements.txt. The specific requirements are:

  • python-kerberos 1.1 or later
  • MIT kerberos5 version 1.11.3 or later
  • A FreeIPA deployment, with an account that has access to manage the DNS portion. The admin@DOMAIN account can be used for testing, but is not recommended for production. You must generate a keytab file for this account, and Designate Central must have read access to the keytab file.
  • The CA cert file from FreeIPA (default /etc/ipa/ca.crt).


To use the IPA backend, in /etc/designate/designate.conf set

backend_driver = ipa

Then set the [backend:ipa] configuration parameters:

Name Required Default Description
ipa_host Yes None Name (FQDN) of IPA host
ipa_port No 443 Port number for IPA HTTPS service
ipa_client_keytab Yes None Absolute path to IPA client kerberos keytab file
ipa_ca_cert Yes None Absolute path to IPA CA cert file
ipa_base_url No https://$ipa_host[:$ipa_port]/ipa Base URL for IPA HTTPS RPC interfaces
ipa_json_url No $ipa_base_url/json Base URL for IPA JSON RPC interface
ipa_connect_retries No 1 How many times Designate will attempt to retry the connection to IPA before giving up
ipa_force_ns_use No False IPA requires that a specified name server or SOA MNAME is resolvable - if this option is set, Designate will force IPA to use a given name server even if it is not resolvable
ipa_version Yes 2.65 IPA JSON RPC version
ipa_auth_driver_class No designate.backend.impl_ipa.auth.IPAAuth Class that implements the authentication driver for IPA


IPA requires the use of HTTPS for security. By default, IPA generates a CA cert and stores it in /etc/ipa/ca.crt. For HTTPS communication with IPA, the requests module is used, and the request verify member is set to the IPA CA cert.


IPA requires the use of Kerberos for authentication. The IPA backend uses the KRB5_CLIENT_KEYTAB feature of MIT Kerberos 1.11. For the HTTP communication with IPA, the backend uses the requests module, and sets the header Authorization: negotiate $token where $token is the Kerberos token generated by the python-kerberos module from the keytab file. This is done automatically with the requests object via the auth member. There is a subclass of requests.auth.AuthBase called IPAAuth which handles the Kerberos authentication upon demand.

The Kerberos auth token will expire periodically. An IPA request will return with a status_code of 401 when the token expires, and the code in the backend will refresh the auth token.

One current limitation is that only one identity can be used at a time. This is because KRB5_CLIENT_KEYTAB is global.


This interface is not currently documented in the official documentation, but is officially supported. A JSON call to IPA looks like this:

  'method': $methodname,
  'params': [
    [ $positionalparam, $positionalparam2 ],
      $param1: $value1,
      'version': $ipa_version
  'id': 0

Where $methodname is one of 'dnszone_add', 'dnszone_mod', 'dnszone_del', 'dnsrecord_add', 'dnsrecord_mod', 'dnsrecord_del'.

All of the methods take $positionalparam, which is usually the name of the zone (domain). The dnsrecord methods use $positionalparam2 as the record name.

Depending on the method and record type, there will be keyword params $param1, etc. For example, a dnszone_add looks like this:

  'method': 'dnszone_add',
  'params': [
    [[ 'example.org.' ]],
      'idnssoamname': 'ns.example.org.',
      'idnssoarname': 'hostmaster@example.org.',
      'dnsttl': 3600,
      'idnssoaserial': 13789910123,
      'idnssoaexpire': 12345,
      'idnssoaminimum': 1200,
      'idnssoarefresh': 54321,
      'idnssoaretry': 11111,
      'version': '2.65'
  'id': 0

Adding an A record looks like this:

  'method': 'dnsrecord_add',
  'params': [
    [ 'example.org.', 'a' ],
      'arecord': '',
      'version': '2.65'
  'id': 0

The ipa backend code has mappings from the domain/recordset/record object properties to the IPA JSON parameters.

Create, Update, Delete Domain

backend.create_domain(self, context, domain)

The code first does

servers = self.central_service.find_servers(self.admin_context)

to get the list of name servers for the domain. Then it sends the following IPA JSON RPC:

  'method': 'dnszone_add',
  'params': [
    [[ domain['name'] ]],
      'idnssoamname': servers[0]['name'],
      'idnssoarname': domain['email'],
      'dnsttl': domain['ttl'],
      'idnssoaserial': domain['serial'],
      'idnssoaexpire': domain['expire'],
      'idnssoaminimum': domain['minimum'],
      'idnssoarefresh': domain['refresh'],
      'idnssoaretry': domain['retry'],
      'version': '2.65'
  'id': 0

Note that, in the dnszone_add method, only one name server can be specified with the idnssoamname parameter. This is the first one returned by the central_service.find_servers method. The other servers are added as NS records by the create_domain method. Also note that IPA requires that the idnssoamname, and any NS records added, must be resolvable. If you want to force the use of un-resolvable NS records, use the ipa-force-ns-use configuration option.

backend.update_domain(self, context, domain)

Same as above, but with method name dnszone_mod.

backend.delete_domain(self, context, domain)

The method name is dnszone_del, and the only parameter is the domain name.

Create, Update, Delete Recordset

IPA will "create" the corresponding recordset when the first record is added. Likewise, IPA will delete the corresponding recordset when the last record is removed. The only supported recordset operation is update.

backend.update_recordset(self, context, domain, recordset)

The only thing that IPA allows to change in a recordset is the ttl. However, IPA does not allow the creation of a recordset without at least one record. The IPA backend will fail to update a recordset, and silently succeed (i.e. not throw an exception), if the user attempts to update a recordset before there are records in the recordset. This should not be a problem, because the IPA backend will set the ttl when the first record is created, in the create_record() method.

Create, Update, Delete Record

backend.create_record(self, context, domain, recordset, record)

IPA wants the relative host/domain name, not the FQDN, for the record name. So, instead of adding an A record a.example.org., IPA wants to add a record named 'a' to domain 'example.org.'. Similarly for PTR records - IPA wants '100' in domain '122.168.192.in-addr.arpa.'. There is currently a bug in IPA - if you want to add a record to the domain itself, you must use '@' for the domain name. There is logic in the backend code to handle these cases. The recname parameter below is derived from recordset['name'] by this logic.

The type of record is derived from a mapping of the recordset['type'] to the IPA keyword for that type. So, recordset['type'] = 'A' is mapped to 'arecord', etc.

The JSON looks like this:

  'method': 'dnsrecord_add',
  'params': [
    [ domain['name'], recname ],
      'arecord': record['data'],
      'version': '2.65'
  'id': 0

For SRV and MX records, the record['priority'] is added to the beginning of record['data'].

backend.update_record(self, context, domain, recordset, record)

IPA doesn't have the same Designate concept as a unique identifier for a record. There isn't an easy way to just modify the data of a particular record. For example, there isn't a good way to just modify the priority of a particular MX record. For updates, the ipa backend will get all of the records of the given record['recordset_id'], and replace _all_ of the records of that type. For example, if there are MX records in IPA like this:

10 mx1.example.org.
20 mx2.example.org.
30 mx3.example.org.

And there is an update_record to update the second one to have a priority of 5, the ipa backend will do a dnsrecord_mod like this:

  'method': 'dnsrecord_mod',
  'params': [
    [ domain['name'], recname ],
      'mxrecord': ['10 mx1.example.org.', '5 mx2.example.org.', '30 mx3.example.org.'],
      'version': '2.65'
  'id': 0

This is not expected to cause performance problems, unless there are hundreds of records (possible? likely?).

backend.delete_domain(self, context, domain)

The method name is dnsrecord_del. The same mapping of type and data as above for create_record is done. IPA is able to delete specific record data e.g. you can delete just the '10 mx1.example.org.' MX record as above.

Testing with IPA

There is a test that is not run by default called _test_service_ipa.py. To run this test, you need a config file called designate-ipa-test.conf with the IPA configuration set as described above. This file should be in the same directory as your regular designate.conf. You will also need a live IPA server. You can run it like this (assuming you have set up your venv already):

cd ~/designate
tools/with_venv.sh python -m testtools.run designate.tests.test_central._test_service_ipa

Getting IPA Data into Designate

There is a new command line tool for Designate - ipaextractor.py. This allows you to populate the internal Designate database with the DNS domain and record information from IPA. It uses the IPA JSON interfaces for dnszone_find and dnsrecord_find to get all of the domains and records from IPA, then uses the Designate API JSON interface to erase and populate the Designate Central database.


WARNING: Edit designate.conf and set the service:central backend_driver to fake first! This will allow you to make changes to _only_ the Designate Central database. ipaextractor will attempt to prevent you from using it with a live IPA backend - it will check the designate.conf, and it will do a live check to see if the IPA backend is being used by Designate. The script will exit with an error if it detects that the IPA backend is being used.


contrib/ipaextractor.py [--debug|--nodebug] [--verbose|--noverbose] ... other args ...

This will use the default designate.conf for the configuration

contrib/ipaextractor.py --config-file file.conf

This will use the given config file

ipaextractor.py [--config-file file.conf] \
           --backend:ipa-ipa-host hostname \
           ... other ipa options specified as --backend:ipa-optionname ... \

This can be used to pass in the parameters manually. Parameters passed in on the command line will override parameters from the config file. The ipa parameters are as described above, and the designate URL is the designate API URL (https://localhost:9001/v1).

This will completely erase everything from Designate and replace it with the contents of IPA.

After this runs successfully, edit designate.conf to switch service:central backend_driver to ipa.

API Changes


Database Changes