FreeIPA is pretty cool but it is a complex beast with a lot of moving parts. Its documentation is alright but there are many things that were (as least to me) not obvious about it.
Contents
- Official Documentation
- Directory suffix
- Connecting to the directory via UNIX sockets
- Root DSE attributes
- Password storage
- Log files
- Replication status monitoring
- Exposing to the Internet
- Certificate storage locations
- Host Aliases
- CA renewal server/CRL publisher promotion
- PKI topology goes out of sync with LDAP server topology
- Extending FreeIPA
- DNS: long TXT records
Official Documentation
Google often lands you onto Fedora's documentation site (which is outdated) or various parts of the FreeIPA wiki (which often related to planned features, as opposed to implemented ones).
The best place to start when looking for reference documentation is Red Hat's Red Hat Identity Management Documenatation index.
See also the FreeIPA documentation index.
Documentation for components
FreeIPA is fundamentally an opinionated configuration/distribution of individual components; keep their documentation to hand as well.
Red Hat Directory Server; upstream documentation: 389 Directory Server Documentation
Red Hat Certificate System; upstream documentation: Dogtag PKI Wiki
Directory suffix
The examples below assume a FreeIPA domain of ipa.example.com. When naming directory entries, replace SUFFIX, with dc=ipa,dc=example,dc=com.
If you use the integrated CA feature, then Dogtag's state will be stored in the directory at another suffix: o=ipaca. You mostly don't have to worry about that detail except when configuring and monitoring replication status.
Connecting to the directory via UNIX sockets
To administer the directory server, you have to use Simple Authentication, specifying cn=Directory Manager as the password. Having to keep the password handy is a bit annoying.
There is an alternative: a process connecting via a UNIX socket can use SASL EXTERNAL authentication in order to be identified by their UID/GID. On the command line:
# ldapwhoami -H ldapi://%2frun%2fslapd-IPA-EXAMPLE-COM.socket -Y EXTERNAL SASL/EXTERNAL authentication started SASL username: gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth SASL SSF: 0 dn: cn=Directory Manager
Note that we weren't prompted for a password--the root user is mapped to cn=Directory Manager by default.
This can be made the default for the root user by putting the following in /root/.ldaprc:
URI ldapi://%2frun%2fslapd-IPA-EXAMPLE-COM.socket SASL_MECH EXTERNAL
Root DSE attributes
As with all LDAP directories, some interesting details can be queried anonymously by performing a search for the root entry:
$ ldapsearch -LLL -x -H ldaps://ipa0.ipa.exampe.com -s base -b '' vendorname vendorversion netscapemdsuffix namingcontexts defaultnamingcontext dn: vendorname: 389 Project vendorversion: 389-Directory/1.4.0.2010 B2019.175.2029 netscapemdsuffix: cn=ldap://dc=ipa0,dc=ipa,dc=example,dc=com namingcontexts: cn=changelog namingcontexts: dc=ipa,dc=example,dc=com namingcontexts: o=ipaca defaultnamingcontext: dc=ipa,dc=example,dc=com
See Root DSE Attributes for an explanation of what they mean.
Password storage
The cn=Directory Manager password is stored on the nsslapd-rootpw attribute of cn=config.
Password hashes are stored in the userPassword attribute.
The default hash format is (now) {PBKDF2_SHA256}; it used to be the (weak) {SSHA512}.
The hash format for newly-changed passwords can be get/set via dsconf IPA-EXAMPLE-COM pwpolicy ...
To list all enabled formats: ldapsearch -b 'cn=Password Storage Schemes,cn=plugins,cn=config' nsslapd-pluginenabled=on cn nsslapd-plugindescription nsslapd-pluginenabled
The AllowNThash password plugin policy is enabled by default. If ipa-adtrust-install has been run, then the NT hash (unsalted MD4, gotta love that security) will be written to the sambaNTPassword and/or ipaNTHash attributes during password change (also maybe during Kerberos authentication to a Sabma?)]]
Log files
Audit logging
Audit logging for the directory server is not enabled by default. Enable it with:
# dsconf IPA-EXAMPLE-COM config replace nsslapd-auditlog-logging-enabled=on
The log file is /var/log/dirsrv/slapd-IPA-EXAMPLE-COM/audit.
Replication status monitoring
There's a general purpose dsctl IPA-EXAMPLE-COM healthcheck command. This outputs a list of problems detected with the directory server. It doesn't seem to be called by ipa-healthcheck, maybe it's a relatively new command. I don't know if it checks for problems with replication status.
Monitoring the Replication Topology in the RHDS documentation leads us to:
dsconf IPA-EXAMPLE-COM replication monitor: port of repl-monitor.pl to Python. Added in #50545 which made it into 389-ds 1.4.2.2; the original Perl script is still shipped in the 389-ds-base-legacy-tools package. You have to provide credentials for each replication agreement, although this can be done via a config file dropped onto each server by a configuration management system. I haven't confirmed that the connections made by this tool are secure; I have a vague memory of the legacy Perl script doing simple binds via plaintext transport, which exposes the credentials to passive observers on the network. Rather than using the directory manager account, it would probably be better to create a separate root DN account with read-only privileges...
Here's an alternative that you can run on each server to check its view of each of its replication agreements:
# dsconf -j INSTANCE replication list | jq '.items[]' -r | xargs -n1 -P8 -i -- dsconf -j IPA-EXMPLA-COM repl-agmt list --suffix={} | jq '.items[].attrs | (.nsds5replicalastupdatestatusjson[0] | fromjson) as $j | [.nsds5replicaroot[0], .cn[0], $j.state, $j.date, .nsds5replicalastupdatestatus[0]] | @tsv' -r | column -s$'\t' -t -N SUFFIX,AGREEMENT,STATE,DATE,LAST-UPDATE-STATUS
When things are good, it will look like:SUFFIX AGREEMENT DATE STATE LAST-UPDATE-STATUS dc=ipa,dc=example,dc=com ipa3.ipa.example.com-to-ipa4.example.com 2021-04-30T11:57:02Z green Error (0) Replica acquired successfully: Incremental update succeeded dc=ipa,dc=example,dc=com meToipa2.ipa.example.com 2021-04-30T11:57:02Z green Error (0) Replica acquired successfully: Incremental update succeeded o=ipaca caToipa2.ipa.example.com 2021-04-30T11:48:13Z green Error (0) Replica acquired successfully: Incremental update succeeded o=ipaca ipa3.ipa.example.com-to-ipa4.ipa.example.com 2021-04-30T11:48:13Z green Error (0) Replica acquired successfully: Incremental update succeeded
When things are bad:SUFFIX AGREEMENT STATE DATE LAST-UPDATE-STATUS o=ipaca caToipa5.example.com red 2022-04-15T09:23:19Z Error (-1) Problem connecting to replica - LDAP error: Can't contact LDAP server (connection error) dc=ipa,dc=example,dc=com meToipa5.example.com red 2022-04-15T09:21:19Z Error (-1) Problem connecting to replica - LDAP error: Can't contact LDAP server (connection error)
The replicaLastUpdateStatusJSON attribute was added in 389-ds 1.4.1.4: #2661. The JSON includes helpful fields such as state which is green, amber, red and is ideal for alerting.
Prior to that version, check the nsds5replicalastupdatestatus attribute of each nsds5replicationagreemet entry, match ^Error \((\d+)\) and alert if non-zero.
You can view details for a single replication agreement with:
# dsconf -j IPA-EXAMPLE-COM repl-agmt status --suffix=dc=ipa,dc=example,dc=com meToipa5.example.com
This pretty much shows you the same information as dsconf IPA-EXAMPLE-COM repl-agmt list. It's documented in Displaying the status of a specific replication agreement.
Some low-level details of replication agreements can be viewed with:
dsconf IPA-EXAMPLE-COM repl-agmt get (requires --suffix and agreement name)
dsctl IPA-EXAMPLE-COM get-nsstate (outputs state of all replication agreements for all suffixes, seems to ignore --suffix parameter) These are pretty technical. Probably if you need to interpret this information you're best off asking for help on the mailing lists.
Two other parts of the RHDS documentation are worth pointing out:
But generally the entire Managing Replication chapter is worth a read.
Exposing to the Internet
- Create a CA certificate offline, and use it to sign FreeIPA's own CA certificate
- I'm not sure how to handle revocation of FreeIPA's certificate without creating another set of infrastructure for OCSP and CRL publishing, and even then, are all TLS clients actaully able to make use of them? Certainly not...
Use a firewall and only expose the correct ports (the freeipa-ldap{,s} services in firewalld are outdated, newer firewalld has a freeipa-4 service which is better to use; and don't bother with freeipa-replication as it hasn't been used since FreeIPA 4).
- Delegate a zone to your IPA servers properly, so that clients find them via the public DNS.
- Configure each DNS server object to 'forward only' and specify your operator's recursive DNS servers as forwarders
Don't be tempted to set the recursion option to no: BIND provides recursive DNS for the IPA server itself.
The default value of the allow-recursion option is { localhost; localnets; } which is likely OK if you trust other things on localnets.
Configure TLS version min/max for httpd and dirsrv to TLSv1.2/TLSv1.3
RHEL8 uses system-wide crypto-policies settings that might make this un-necessary for httpd, but I don't think dirsrv (which still uses NSS rather than OpenSSL) obeys them yet
Configure sensible TLS ciphers for httpd and nss.
- Originally I also did this for Dogtag's tomcat, but this was before I realised that this is only accessed from the local host so isn't necessary.
Disable access to dirsrv's root user by enabling the rootdn_access plugin and configuring a whitelist of IP addresses able to use it (only ::1/127.0.0.1 is a good start).
repl-monitor.pl won't like this. It can't use TLS or GSSAPI to protect the transport so we definitely don't want it running over the internet. SSH tunnel?
ds-replcheck can use TLS, so the whitelist approach will work.
But let's just use SSH tunnelling which will work for both (as long as the source address on the server for forwarded connections is ::1/127.0.0.1.
Disable the default admin user.
Create a new admin user with a different name and put it in the admins group.
Make sure your HBAC and Sudo rules refer to the admins group rather than the admin user.
Log in as the new admin user, and then disable the original admin user.
It would be neat to dynamically adjust firewall rules to only allow SSH in from hosts within a particular host group. But that relies on Kerberos being up and DNS updates working correctly (i.e., not being intercepted and blocked by your ISP).
- Then again there's always the operator console for troubleshooting.
- Just disable password authentication entirely and rely on GSSAPI or public key authentication... the basic SSH stuff really!
Additional options for dirsrv:
nsslapd-minssf: requires use of a protected transport. Use it.
FreeIPA sets nsslapd-minssf-exclude-rootdse by default, so anonymous users can still retrieve basic information from the root DSE entry (ldapsearch -H ldap://server.example.com -s base -b '').
Also known to break realmd but who uses that to join FreeIPA?
nsslapd-allow-anonymous-access to prevent reading the directory without authentication.
nsslapd-require-secure-binds: require a protected transport for authentication requests
- Can't stop a misconfigured client from blurting out a password unsolicited, via a simple bind on the plaintext port!
- Would be nice to audit when this happens
- We can't just block port 389 because replication requires it (with confidentiality protected by GSSAPI)
- But we could block port 389 from non-IPA servers...
- Can't stop a misconfigured client from blurting out a password unsolicited, via a simple bind on the plaintext port!
Also has the effect of breaking repl-monitor.pl, which is denied from doing a simple bind to localhost - the server isn't able to waive nsslapd-require-secure-binds (and presumably nsslapd-minssf for connections from ::1/127.0.0.0/8.
TODO: file a bug upstream suggesting that connections from localhost should be considered 'secure' for the purposes of nsslapd-require-secure-binds
Certificate storage locations
Trust Store
The list of known CA certificates that the FreeIPA installation trusts is kept in the directory under cn=certificates,cn=ipa,cn=etc,SUFFIX. For a stand-alone CA installation, the store will normally only contain the CA's certificate (IPA.EXAMPLE.COM IPA CA). But for an externally signed CA installation, it will contain the external CA certificate as well. This bug shows examples of the schema.
The ipa-cacert-manage list command will perform an LDAP search, and print an entry for each certificate it finds.
Each entry can have more than one certificate, since the cACertificate and ipaCertIssuerSerial fields are multi-valued. (Note that although LDAP does not preserve the order of multi-valued attributes, the ipaCertIssuerSerial is also present in the caCertificate so the entries can be matched that way)
New certificates can be installed with ipa-cacert-manage install. There's no command to remove them.
The ipa-certupdate can be run on a client to make sure any new or renewed certificates added to the trust store are applied to the local machine. On a server it will additionally ensure that new or renewed certificates added to the trust store are installed into the the KDC, web and directory server certificate databases (or is this done instead by ipa-server-certinstall?)
If you have more than one certificate in the trust store, this bug means that OpenSSL on Debian-based systems won't trust *any* of them. I'm working on a fix for this.
When certmonger starts up, it uses these entries to refresh its cached copies of the CA certificates for the IPA CA (ref: fetch_roots function).
Legacy CAcert container
The entry cn=CAcert,cn=ipa,cn=etc,SUFFIX contains the IPA CA certificate. It's used by:
Older (RHEL6) ipa-certupdate command, which only retrieves this single CA certificate
Older certmonger, which refreshes its copy of the IPA CA certificate when it starts up.
IPA CAs
cn=cas,cn=ca,SUFFIX contains the IPA CA certificates themselves (plural because IPA supports multiple 'lightweight sub-CAs'.
$ ipa ca-find --raw --pkey-only --all ------------ 1 CA matched ------------ dn: cn=ipa,cn=cas,cn=ca,dc=ipa,dc=example,dc=com cn: ipa ---------------------------- Number of entries returned 1 ----------------------------
Host Aliases
Say you have a host, foo.ipa.example.com, with an IP address of 192.0.2.0.
The host is behind a NAT gateway with an address on the Internet of 203.0.113.0.
If you enable dyndns_update, a foo.ipa.example.com will point at 192.0.2.0. Although this leaks information about foo's local network to the public, at least users on foo's network will be able to reach it using that FQDN.
But what if the NAT gateway is provided by Sky Broadband? Sky's routers intercept DNS traffic, preventing their customers from using third-party DNS resolvers, doubtless so that Sky can sell information about the web sites their customers visit to advertisers. As a side effect of this, some more exotic forms of DNS traffic are blocked. This includes the GSS-TSIG messages from nsupdate, which is the mechanism that sssd uses to perform DNS updates. (Debugging this was not fun.)
One alternative is for users on foo's network to make use of mDNS and reach it at foo.local. In order to make sure that they can use Kerberos to log in, we must make use of FreeIPA's Kerberos Principal Alias feature:
$ ipa host-add-principal foo.ipa.example.com host/foo.local ------------------------------------------------------ Added new aliases to host "foo.ipa.example.com" ------------------------------------------------------ Host name: foo.ipa.example.com Principal alias: host/foo.ipa.example.com@IPA.EXAMPLE.COM, host/foo.local@IPA.EXAMPLE.COM
Users can now obtain a ticket for host/foo.local and use it to authenticate to the SSH server running on foo.
However, users will still be prompted to confirm foo's SSH host key; this is because sss_ssh_knownhostsproxy requests the host keys for foo.local, and SSSD's SSH responder isn't able to search the directory for hosts by alias.
Using Kerberos for key agreement (via the gssapi-keyex mechanism) entirely bypasses the use of SSH public keys for host verification & key agreement, making this seamless.
CA renewal server/CRL publisher promotion
The documentation for removing a server from the topology doesn't mention that you need to move the CA renewal server and CRL publisher roles to another server with the CA role.
If you don't do this, it's not fatal, but your CRL file won't be updated.
Both steps are explained in a separate chapter, Decommissioning a server that performs the CA renewal server and CRL publisher role. There is overlap with content from the Managing Replication Topology chapter.
If you want to check the status of your CRL file:
$ curl -sS -L http://ipa-ca.ipa.robots.org.uk/ipa/crl/MasterCRL.bin | openssl crl -inform der -noout -lastupdate lastUpdate=Nov 16 15:13:34 2021 GMT
... you can view the whole CRL with -text and [other options are available|https://www.openssl.org/docs/manmaster/man1/openssl-req.html].
The rest of the notes under this heading are obsolete now that I've straightened things up, linked to the docs and filed the above bugs to try to get them cross linked... just skip past them. I'll remove them eventually...
The Starting CRL generation on RHEL 8 chapter of the "Migrating IdM from RHEL 7 to RHEL 8 and keeping it up-to-date" section of the RHEL 8 Installing IdM manual doesn't make much sense. The new replica is running RHEL 8, so why do the prerequisites talk about RHEL 7.6/7.7?
I guess the text was sloppily copied from the previous chapter: https://bugzilla.redhat.com/show_bug.cgi?id=1785595
After following these steps on a CentOS 8.0.1905 machine, the CRL is not available:
$ curl -s -I http://ipa2.ipa.example.com/ipa/crl/MasterCRL.bin | head -n1 HTTP/1.1 404 Not Found
The manual mentions a ipa-crlgen-manage command, which does not exist in CentOS 8, but it does exist in CentOS 7:
centos8$ ipa-crlgen-manage --version -bash: ipa-crlgen-manage: command not found $ rpm -q centos-release ipa-server centos-release-8.0-0.1905.0.9.el8.x86_64 ipa-server-4.7.1-11.module_el8.0.0+79+bbd20d7b.x86_64
$ ipa-crlgen-manage --version 4.6.5 $ rpm -q centos-release ipa-server centos-release-7-7.1908.0.el7.centos.x86_64 ipa-server-4.6.5-11.el7.centos.3.x86_64
Examining the FreeIPA source reveals that this script was added in FreeIPA 4.8:
$ git checkout master $ git log --oneline ./install/tools/ipa-crlgen-manage.in 6d02eddd3 Replace PYTHONSHEBANG with valid shebang 0d23fa927 CRL generation master: new utility to enable|disable $ git describe --contains 0d23fa927 rc_4-8-0-1~126
And backported to FreeIPA 4.7.3 and 4.6.5:
$ git checkout ipa-4-7 $ git log --oneline ./install/tools/ipa-crlgen-manage.in 452fe2fdc Replace PYTHONSHEBANG with valid shebang 52770aa5f CRL generation master: new utility to enable|disable $ git describe --contains 52770aa5f release-4-7-3~202
$ git checkout ipa-4-6 $ git log --oneline ./install/tools/man/ipa-crlgen-manage.1 af5abe0d7 CRL generation master: new utility to enable|disable $ git describe --contains af5abe0d7 release-4-6-5~4
So I guess it will show up in CentOS 8.1. But until then, examining the source code reveals that, after manually enabling CRL generation, there is an additional undocumented step:
1 # make sure a CRL is generated if setup_crl is True 2 if setup_crlgen: 3 logger.info("Forcing CRL update") 4 api.Backend.ra.override_port = 8443 5 result = api.Backend.ra.updateCRL(wait='true') 6 if result.get('crlUpdate', 'Failure') == 'Success': 7 logger.debug("Successfully updated CRL") 8 api.Backend.ra.override_port = None
Unfortunately the updateCRL method was likewise only added in FreeIPA 4.7.3. Fortunately it's not too complex to prevent me from bodging together the following:
1 from pprint import pprint 2 3 from ipalib import api 4 from ipaplatform.paths import paths 5 6 api.bootstrap(in_server=True, confdir=paths.ETC_IPA) 7 api.finalize() 8 9 api.Backend.ldap2.connect() 10 11 pprint(api.Backend.ra._sslget('/ca/agent/ca/updateCRL', 12 8443, 13 crlIssuingPoint='MasterCRL', 14 waitForUpdate=True, 15 xml='true'))
# python3 force.py (200, <http.client.HTTPMessage object at 0x7f8ddd4e9a58>, b'<?xml version="1.0" encoding="UTF-8" standalone="no"?><xml><header><crlIssui' b'ngPoint>MasterCRL</crlIssuingPoint><crlUpdate>Scheduled</crlUpdate></header>' b'<fixed/><records/></xml>')
And behold, we now have a CRL on the new master:
$ curl -s -I http://ipa2.ipa.example.com/ipa/crl/MasterCRL.bin | head -n1 HTTP/1.1 200 OK
Remaining questions
There are still seemingly significant differences in Tomcat's configuration between the old and new masters:
property
ipa0
ipa2
ca.certStatusUpdateInterval
unset
0
ca.listenToCloneModifications
true
false
master.ca.agent.host
unset
ipa0.ipa.example.com
master.ca.agent.port
unset
443
These settings are mentioned in older documentation and on a post to the mailing list:
https://docs.fedoraproject.org/en-US/Fedora/15/html/FreeIPA_Guide/promoting-replica.html
https://www.redhat.com/archives/freeipa-users/2012-May/msg00389.html
So I'm still not sure whether my new master CRL generator will actually listen to updates from replicas and/or periodically check the status of certificates for updates. I also wonder whether master.ca.agent.host has to be updated on ipa1...
PKI topology goes out of sync with LDAP server topology
After removing some servers with the CA/KRA roles, the list of servers in Dogtag will get out of sync with the replication topology in the directory.
# pki -d /etc/pki/pki-tomcat/alias/ -n 'subsystemCert cert-pki-ca' -C /etc/pki/pki-tomcat/alias/pwdfile.txt securitydomain-host-find Host ID: CA ipa0.ipa.example.com 443 Hostname: ipa0.ipa.example.com Port: 80 Secure Port: 443 Domain Manager: TRUE Clone: FALSE Host ID: KRA ipa0.ipa.example.com 443 Hostname: ipa0.ipa.robots.org.uk Port: 80 Secure Port: 443 Domain Manager: TRUE Clone: TRUE [...]
This will cause ipa-healthcheck to complain about non-contactable CA/KRA servers.
The upstream bug shows how to fix this:
# pki -d /etc/pki/pki-tomcat/alias/ -n 'subsystemCert cert-pki-ca' -C /etc/pki/pki-tomcat/alias/pwdfile.txt securitydomain-host-del 'CA ipa0.ipa.example.com 443' # pki -d /etc/pki/pki-tomcat/alias/ -n 'subsystemCert cert-pki-ca' -C /etc/pki/pki-tomcat/alias/pwdfile.txt securitydomain-host-del 'KRA ipa0.ipa.example.com 443'
... run that for each removed CA/KRA server.
Extending FreeIPA
Extending FreeIPA (also found in PDF form attached to https://www.redhat.com/archives/freeipa-users/2012-February/msg00230.html)
https://www.freeipa.org/images/5/5b/FreeIPA33-extending-freeipa.pdf
Custom subtrees should go in an nsContainer directly under SUFFIX.
DNS: long TXT records
DMARC requires rather long TXT records. Attempting to create one of these in the web UI or on the command line works, but then named-pkcs11 logs:
failed to parse RR entry: resource record DN 'idnsname=0._domainkey,idnsname=example.com.,cn=dns,dc=ipa,dc=example,dc=com': data 'v=DKIM1; t=s; p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApIoCiX7nfTcACGVVNxUbxIrhtYrBCDeRFVxGwAbkraMI+6qtVicNu1XB6i26TcVz+azCGl6oZALS32oJJGrcdJlqmHb9egzwUMs9cgksiaLI5D54fT7rWi9JEgsu59PJE9uFPQ2PBnWeDpblQc5q1D0G15/udTKp06QV17qkCemdvLoNMYh+BtPT4cn1AqDvXKHPSt/H5Lrr9ZpNNbw5hhIHAho7zJRUsL4wTImL5WFahX4HUc3XDmTrdw9CJbSU4gZRODfh0G7g28MxybfiltxNWDYPelgxsPFJFifo6vrXyUZQbQwyqvCvpyJbJZ66u2EOtYQ6VCUH6l9CJdkADQIDAQAB': syntax error
The reason for this is not obvious.
So, RFC 1035 describes the data of a TXT RR as simply "One or more <character-string>s". And describes <character-string> as "a single length octet followed by that number of characters. <character-string> is treated as binary information, and can be up to 256 characters in length (including the length octet)."
So our everyday experience of a TXT record is wrong! We naïvely think of it as "just a string", when really it has structure; in this case, one or more arrays of bytes each of which can be up to 255 characters. BIND is choking because it's trying to compile a TXT record consisting of a single <character-string> of well over 300 bytes!
The RFC also defines how to represent a <character-string> in a Zone file, as parsed by BIND:
- Entries are predominantly line-oriented, though parentheses can be used to continue a list of items across a line boundary
and
<character-string> is expressed in one or two ways: as a contiguous set of characters without interior spaces, or as a string beginning with a " and ending with a ". Inside a " delimited string any character can occur, except for a " itself, which must be quoted using \ (back slash).
So it turns out that zone file format is more of a literal transcription of DNS data into a binary format than I had expected. So where you express the following in a zone file:
0._domainkey IN TXT ( "v=DKIM1;" "t=s;" "p=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq1cxaCim2Tx+HfuB4oGG" "IpQaynGyJV0vi0uQZPBE7/uCVL7/+yJKll+Ec07RHkUg4j3tMltpeuSJd6Hddkj8" "OPDtFYI3EyqexUQe04NsTB9gNAmA1ag4vYb3YCCA2KsbAipIDchkUL31I1XYW8cL" "U/4R2aWYxEJNnPkt/Y0ljy5oKx5HLUs46Miqvz4BvQPKy6MvTEci7yJ5pLWcYGzd" "PjGRZuuBcQGPXB26dVZ7LTEHMrhiJ2uQ7ukoFw//nDU8weypvymSX9AVIWmq9ZXr" "CORBeCspOCupO5jAyGNJjBvs8IoVzis/iICqCgFv7XNgGTEs8nC5L9QXvX/lrSI6" "HwIDAQAB" )
... the compiled RRDATA for the record consists of *nine* length-prefixed byte strings. It's only RFC 7489, sec. 6.1 that determines that they are stitched together into a single string by the entity making use of the records:
- a TXT record can comprise several "character-string" objects. Where this is the case, the module performing DMARC evaluation MUST concatenate these strings by joining together the objects in order and parsing the result as a single string.
Stitching this knowledge together we can now create the record in the web UI or the ipa command, simply by adding spaces to the record data so that no one run of characters lasts for more than 255 bytes. For example:
$ ipa dnsrecord-mod example.com 0._domainkey --txt-rec='"v=DKIM1; t=s; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApIoCiX7nfTcACGVVNxUb" "xIrhtYrBCDeRFVxGwAbkraMI+6qtVicNu1XB6i26TcVz+azCGl6oZALS32oJJGrc" "dJlqmHb9egzwUMs9cgksiaLI5D54fT7rWi9JEgsu59PJE9uFPQ2PBnWeDpblQc5q" "1D0G15/udTKp06QV17qkCemdvLoNMYh+BtPT4cn1AqDvXKHPSt/H5Lrr9ZpNNbw5" "hhIHAho7zJRUsL4wTImL5WFahX4HUc3XDmTrdw9CJbSU4gZRODfh0G7g28Mxybfi" "ltxNWDYPelgxsPFJFifo6vrXyUZQbQwyqvCvpyJbJZ66u2EOtYQ6VCUH6l9CJdkA" "DQIDAQAB"' Record name: 0._domainkey TXT record: "v=DKIM1; t=s; p=" "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApIoCiX7nfTcACGVVNxUb" "xIrhtYrBCDeRFVxGwAbkraMI+6qtVicNu1XB6i26TcVz+azCGl6oZALS32oJJGrc" "dJlqmHb9egzwUMs9cgksiaLI5D54fT7rWi9JEgsu59PJE9uFPQ2PBnWeDpblQc5q" "1D0G15/udTKp06QV17qkCemdvLoNMYh+BtPT4cn1AqDvXKHPSt/H5Lrr9ZpNNbw5" "hhIHAho7zJRUsL4wTImL5WFahX4HUc3XDmTrdw9CJbSU4gZRODfh0G7g28Mxybfi" "ltxNWDYPelgxsPFJFifo6vrXyUZQbQwyqvCvpyJbJZ66u2EOtYQ6VCUH6l9CJdkA" "DQIDAQAB"
While figuring out the above I ran into RFC 1464, which proposes a way to store structured data within TXT records, by having each <character-string> within the RDATA for a TXT record represent an attribute and value pair, separated by =. e.g.:
host.widgets.com IN TXT "printer=lpr5" sam.widgets.com IN TXT "favorite drink=orange juice"
It does not state, although I presume, that you can have multiple attributes by using multiple <character-string>s within the RDATA:
record.example.com IN TXT "foo=bar" "baz=qux"
... up to the maximum length of the RRDATA for a record which is 65536 bytes. I guess that one of the reasons this never took off is that the length of each value would be limited to 254 - (length of attribute) bytes.