Joalon a software engineer

Installing FreeIPA, Pihole and Cockpit in the lab

After expanding my machine park with a new Raspberry Pi 4 I started looking at installing some infrastructure for development at home as well as trying out some open source services. This includes using FreeIPA for logins and home directories over NFS as well as Pihole for DNS and ad blocking.

I created a couple of ansible roles for these which I gathered on Github


SD-card configuration

Since I’m going to use FreeIPA as the domain controller I thought I’d use CentOS as the base operating system. They don’t have an officially supported image but I burnt the “RaspberryPI-Minimal-7-1908” to an SD-card and it booted without any trouble. When I insert an SD-card in my computer it shows up as /dev/sdb:

xzcat ~/Downloads/CentOS-Userland-7-armv7hl-RaspberryPI-Minimal-4-1908-sda.raw.xz | sudo dd of=/dev/sdb bs=64k oflag=dsync status=progress

After booting it was time to set things up. Here’s a good post on Reddit which I mostly followed for setting up the system afterwards. Here’s a quick recap:

ssh root@   # Used default password 'centos'

Since I’m not going to use the Wifi or Bluetooth in the near future I decided to turn them off:

cat > /etc/modprobe.d/raspi-blklst.conf <<EOF

blacklist brcmfmac
blacklist brcmutil

blacklist btbcm
blacklist hci_uart

Then I found how to enable EPEL on the CentOS AltArch SIG:

cat > /etc/yum.repos.d/epel.repo <<EOF
name=Epel rebuild for armhfp

And then updating the system: yum update -y.

After the update I disabled chrony and enabled ntpd instead (Apparently FreeIPA prefers ntpd):

systemctl disable chronyd
systemctl stop chronyd
yum install -y ntp

I changed the pool servers in the ntp config file /etc/ntp.conf to the south American ones.

timedatectl set-timezone "America/Sao_Paulo"
systemctl enable --now ntpd
ntpdate -u

Setup FirewallD:

yum install -y firewalld
systemctl enable --now firewalld

And finally set the hostname for each host with hostnamectl set-hostname and reboot.


I wanted to have an Identity solution in the environment to get stuff like single sign on and TLS certificate handling for internal services and I settled for FreeIPA, the open source version of Red Hats IdM (Identity Management).

Installing the FreeIPA master took a couple of tries to get right, I kept running into a bug where the json API would time out during installation so it wouldn’t install correctly. After some time searching I found this issue which was similar where the author resolved it by adding a GSSAPI config option to the /etc/httpd/conf.d/ipa.conf file. So, while the ipa-server-install command ran, I waited for that file to be created and then I added GssapiDelegCcacheEnvVar KRB5CCNAME under the Location "/ipa". I also increased the installation timeout, since this has historically been a problem when installing FreeIPA on a Pi. After these modifications I ran the following commands on my node:

yum install -y ipa-server ipa-server-dns bind-dyndb-ldap

for service in ntp http https ldap ldaps kerberos kpasswd dns; do firewall-cmd --permanent --add-service=$service; done
firewall-cmd --reload

ipa-server-install --setup-dns

To verify:

kinit admin
ipa user-find admin

IPA master verification

To test this I installed the FreeIPA client on my pihole node according to these instructions.

On the IPA master I ran:

kinit admin
ipa dnsrecord-add client --a-rec

And on the client:

cat > /etc/resolv.conf <<EOF

firewall-cmd --add-service=dns --permanent
firewall-cmd --reload

hostname-ctl set-hostname

echo "     ipa" >> /etc/hosts


After the reboot I installed the FreeIPA client:

yum -y install freeipa-client ipa-admintools
ipa-client-install --mkhomedir --force-ntpd --enable-dns-updates

Client installation successful

Something to note with a new setup of FreeIPA is that the default HBAC (Host Based Access Control) rule allows all users access to all machines. This can be tweaked by disabling the default rule and create some host groups to use with new rules. I’ll leave the default for now.


This is a tool to do system administration in a web interface. I’ll install this mostly for the graphs in the overview:

Cockpit overview

yum install -y realmd setroubleshoot
yum install -y cockpit cockpit-storaged cockpit-packagekit

Since this is a pretty sensitive service I wanted to get TLS working with a trusted cert from my new FreeIPA master. First I added a new private TLS key:

cd /etc/pki/tls/certs
make cockpit.key
mv cockpit.key ../private/cockpit.key

TLS key generation

Then I could use this private key together with Certmonger, included in FreeIPA, to request a certificate for the cockpit service on that host:

ipa service-add cockpit/
ipa-getcert request -K -P host/ -d /etc/cockpit/ws-certificates.d/ipa.crt

This failed for me on hosts with Selinux since the requested certificate doesn’t get the ‘cert_t’ context when created. To get around this I added the selinux file context cert_t recursively to the directory:

semanage fcontext -a -t cert_t "/etc/cockpit/ws-certificates.d(/.*)?"

Finally, on my Arch laptop, I had to trust the FreeIPA cert by running:

scp .
sudo trust anchor --store ipa-ca.crt

After this I got the green TLS checkbox when visiting the web interface:

Trusted Cockpit interface


This service was the easiest one to set up. After the common configuration I only had to run the install script and create the TLS configuration:

wget -O

To request a new TLS cert from FreeIPA for the web interface I used nearly the same commands as when configuring Cockpit. Except I also got the public CA trust chain from the FreeIPA master and converted it to a usable format:

pushd /etc/pki/tls/certs
make pihole-w-pass.key
openssl rsa -in pihole-w-pass.key -out
mv /etc/pki/tls/private/


ipa service-add pihole/
ipa-getcert request -f ${CERT_FILE} -k ${KEY_FILE} --principal pihole/$(hostname) -C "cat ${KEY_FILE} >> ${CERT_FILE}"

scp root@ipa:~/ca-agent.p12 /tmp/
openssl pkcs12 -in /tmp/ca-agent.p12 -out /etc/pki/tls/certs/ipa-ca-chain.pem 
systemctl restart lighttpd

After getting the combined key and certificate I followed the official Pihole instructions to configure lighttpd to use them. In short I added the following code snippet to /etc/lighttpd/external.conf:

$HTTP["host"] == "" {
  # Ensure the Pi-hole Block Page knows that this is not a blocked domain
  setenv.add-environment = ("fqdn" => "true")

  $SERVER["socket"] == ":443" {
    ssl.engine = "enable"
    ssl.pemfile = "/etc/pki/tls/certs/" =  "/etc/pki/tls/certs/ipa-ca-chain.pem"
    ssl.honor-cipher-order = "enable"
    ssl.cipher-list = "EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH"
    ssl.use-sslv2 = "disable"
    ssl.use-sslv3 = "disable"       

  # Redirect HTTP to HTTPS
  $HTTP["scheme"] == "http" {
    $HTTP["host"] =~ ".*" {
      url.redirect = (".*" => "https://%0$0")

Working pihole interface

After verifying Pihole I added it as the global forwarder in FreeIPAs interface:

Global forwarder


Molecule is a tool for speeding up the development cycle of ansible roles. When running molecule it starts a container, applies the role and then allows for running unit- or integration tests as well as logging in and poking around with a shell. It’s still a bit clunky to get to work and it takes a minute or two to spin each run up but it’s loads better than manually resetting a virtual machine. A typical run looks like this:

Running Molecule

When developing the Ansible roles for these services, the biggest initial issue was getting Systemd to work in a docker container. I tried the suggestions from the Molecule documentation, as well as from Jeff Geerling’s blog post and a few other ideas until I found a docker systemd image from lean-delivery.

Since I already have docker setup on my laptop, I ran these commands to scaffold a new Ansible role:

pip3 install --user molecule
molecule init role joalon.freeipa-client
cd joalon.freeipa-client

And changed the molecule/default/molecule.yml to use the lean-delivery docker image:

- name: instance
    image: leandelivery/docker-systemd:centos7
    privileged: True
      - seccomp=unconfined
      - /sys/fs/cgroup:/sys/fs/cgroup:ro
      - /tmp
      - /run
      - SYS_ADMIN

Big disclaimer, using privileged docker containers and/or the SYS_ADMIN capability is like giving the image developer root access to the host system. I feel bad doing it but I didn’t get it to work any other way.

After these steps I could incrementally develop a role:

# Do some changes to the ansible role
molecule converge

# Destroy the current container
molecule destroy

# Create it again and check that the role is idempotent
molecule converge
molecule converge


The home lab took a bit longer to setup than I anticipated but I’m very glad with the automation and identity management. If I get around to enrolling my laptop into the domain I’ll be able to use Ansible’s dynamic inventory to get the hosts list from FreeIPA using this script. Anyway, thanks for reading!

comments powered by Disqus