diff --git a/README.md b/README.md index 0a85ba3..48d09db 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,7 @@ service type. ``` proxmox/ Proxmox VE hosts (pve1 production, pve-test sandbox) pihole/ Pi-hole DNS/DHCP (config snapshots, pull/apply scripts) +freeipa/ FreeIPA identity management server (domain-controller.sweet.home) scripts/ Repo-wide scripts (secret scanning, git hook installer) ``` @@ -25,6 +26,14 @@ Pi-hole v6 configuration management. Stores a sanitised snapshot of the live config and provides scripts to pull from or push to a running instance. See `pihole/README.md` for usage. +### `freeipa/` + +FreeIPA 4.x identity management server running on Rocky Linux 9 +(`domain-controller.sweet.home`, VMID 108 on pve1). Provides Kerberos, +LDAP, and integrated DNS for the `sweet.home` realm. See +`freeipa/README.md` for the quick-start and `freeipa/docs/install.md` +for the full reproduction procedure. + ## Secret scanning All commits are scanned for secrets by a CI pipeline that runs on both diff --git a/freeipa/CLAUDE.md b/freeipa/CLAUDE.md new file mode 100644 index 0000000..f5520f6 --- /dev/null +++ b/freeipa/CLAUDE.md @@ -0,0 +1,33 @@ +# freeipa/CLAUDE.md + +Host-specific guardrails for the `domain-controller` FreeIPA server. + +## Host: `domain-controller.sweet.home` + +- **VMID 108 on `pve1.sweet.home`** — this is production infrastructure. + Treat it the same as any other pve1 guest: no changes without explicit + same-session operator authorisation. +- **Read-only is always fine**: SSH in as `wayne`, inspect IPA state with + `ipa *` commands or `kinit admin && ipa ...`, check service status with + `ipactl status` — none of that needs authorisation. +- **Never modify FreeIPA topology, replicas, or the LDAP DIT directly** + without the operator's go-ahead. That means no `ipa user-del`, no + `ipa-replica-manage`, no `ldapmodify` against the live directory. +- **Do not commit secrets.** The Directory Manager password and the `admin` + Kerberos password must not appear in any file in this repo. Scripts that + need them must read from environment variables or prompt interactively. +- The `admin` password and Directory Manager password were generated at + install time and stored only in the operator's password manager — not in + this repo. See `README.md` for how to retrieve/reset them. + +## What is safe to run automatically + +- `scripts/verify.sh` — read-only health check, no side effects. +- `scripts/configure-pihole-dns.sh` — idempotent DNS forwarder setup in + Pi-hole; safe to re-run. + +## What requires operator go-ahead + +- `scripts/install.sh` — destructive if run against an already-provisioned + host. Always check first with `ipactl status`. +- Any `ipa-replica-install` or `ipa-server-upgrade` invocation. diff --git a/freeipa/README.md b/freeipa/README.md new file mode 100644 index 0000000..af3abf1 --- /dev/null +++ b/freeipa/README.md @@ -0,0 +1,101 @@ +# FreeIPA — domain-controller.sweet.home + +FreeIPA 4.x identity management server providing Kerberos, LDAP, and +integrated DNS for the `sweet.home` LAN. Runs on Rocky Linux 9 in a +Proxmox VM (VMID 108 on `pve1.sweet.home`). + +## Quick status + +| Item | Value | +|------|-------| +| Host | `domain-controller.sweet.home` | +| IP | `192.168.2.138` (static) | +| Realm | `SWEET.HOME` | +| Domain | `sweet.home` | +| IPA version | 4.13.x (Rocky Linux 9) | +| Web UI | `https://domain-controller.sweet.home/ipa/ui/` | +| VMID | 108 on `pve1.sweet.home` | +| OS | Rocky Linux 9 (GenericCloud image) | + +## What it provides + +- **Kerberos KDC** — SSO tickets for the `SWEET.HOME` realm +- **LDAP directory** — centralised user/group/host store (389-ds) +- **Integrated DNS** — authoritative for `sweet.home`, forwarding + everything else upstream to Pi-hole (`192.168.2.253`) +- **CA** — self-signed CA issuing certs for IPA services +- **Web UI** — at `https://domain-controller.sweet.home/ipa/ui/` + +## First-time use + +```bash +# SSH to the server +ssh wayne@domain-controller + +# Get a Kerberos ticket as admin +kinit admin + +# List IPA users +ipa user-find + +# Add a user +ipa user-add jdoe --first=John --last=Doe --password + +# Check service health +ipactl status +``` + +## Credentials + +The `admin` Kerberos password and Directory Manager password were +generated at install time. They are **not stored in this repo** — keep +them in your password manager. + +- **admin** — used for day-to-day IPA management (`kinit admin`) +- **Directory Manager** — low-level LDAP root, rarely needed + +To reset the admin password (requires being logged in as admin): +```bash +kinit admin +ipa passwd admin +``` + +## DNS integration with Pi-hole + +Pi-hole must forward `sweet.home` DNS queries to this server so +that Kerberos SRV records and IPA host records resolve correctly. +Run `scripts/configure-pihole-dns.sh` once, or add the following +to Pi-hole's custom DNS config manually: + +``` +server=/sweet.home/192.168.2.138 +``` + +See `docs/pihole-dns.md` for the full setup. + +## Ports required (firewalld) + +FreeIPA's firewalld config is applied by `ipa-server-install` automatically. +The following ports must be reachable from LAN clients: + +| Port | Proto | Service | +|------|-------|---------| +| 80 | TCP | HTTP (redirect to HTTPS) | +| 443 | TCP | HTTPS / Web UI | +| 389 | TCP | LDAP | +| 636 | TCP | LDAPS | +| 88 | TCP+UDP | Kerberos | +| 464 | TCP+UDP | Kerberos password change | +| 53 | TCP+UDP | DNS | + +## Reproducing this setup + +See `docs/install.md` for the full step-by-step install procedure, +or run `scripts/install.sh` on a fresh Rocky Linux 9 VM with the +correct hostname and IP already set. + +## Backup + +The CA certificates (required for replicas) are at `/root/cacert.p12` +on the server, encrypted with the Directory Manager password. +Back these up to a secure location. diff --git a/freeipa/docs/install.md b/freeipa/docs/install.md new file mode 100644 index 0000000..d938806 --- /dev/null +++ b/freeipa/docs/install.md @@ -0,0 +1,248 @@ +# FreeIPA Install Procedure + +Full reproduction guide for `domain-controller.sweet.home`. Tested on +Rocky Linux 9.8 (GenericCloud), VMID 108, `pve1.sweet.home`. + +## Prerequisites + +- Proxmox node with `local-zfs` storage and internet access from guests +- Rocky Linux 9 GenericCloud image downloaded (see step 1) +- SSH access to Proxmox node as a user with `sudo` for `qm`/`pvesm` +- The operator's SSH public key available to inject via cloud-init + +--- + +## Step 1 — Download Rocky Linux 9 GenericCloud image + +On the Proxmox node, download to your ISO/image store: + +```bash +wget -O /mnt/pve/server-iso/Rocky-9-GenericCloud.latest.x86_64.qcow2 \ + https://download.rockylinux.org/pub/rocky/9/images/x86_64/Rocky-9-GenericCloud.latest.x86_64.qcow2 +``` + +The image is ~617 MB. The Proxmox storage must be configured to accept +both ISO images and disk images (set "Content" to include "Disk image" +in the Proxmox UI for that storage). + +--- + +## Step 2 — Prepare the VM in Proxmox + +Use an existing VM or create a new one. The config used for VMID 108: + +- **CPU**: 2 cores, x86-64-v2-AES +- **RAM**: 2048 MB +- **Disk**: 32 GB on `local-zfs` +- **BIOS**: SeaBIOS (GenericCloud uses MBR — not UEFI) +- **Network**: virtio on `vmbr0`, firewall enabled +- **QEMU guest agent**: enabled + +If rebuilding an existing VM (e.g. replacing a prior OS): + +```bash +# On pve1 — stop the VM +sudo qm stop + +# Remove existing disks from config +sudo qm set --delete scsi0,efidisk0 + +# Switch to SeaBIOS if the VM was UEFI +sudo qm set --bios seabios + +# Free old disk volumes from storage +sudo pvesm free local-zfs:vm--disk-0 +sudo pvesm free local-zfs:vm--disk-1 +``` + +### Import the Rocky image and configure cloud-init + +```bash +# Import image as a new disk +sudo qm importdisk /mnt/pve/server-iso/Rocky-9-GenericCloud.latest.x86_64.qcow2 local-zfs + +# Check what disk name was assigned +sudo qm config # look for unused0: local-zfs:vm--disk-N + +# Attach as scsi0 (adjust disk name from above) +sudo qm set --scsi0 local-zfs:vm--disk-0,iothread=1 + +# Resize to 32 GB +sudo qm disk resize scsi0 32G + +# Add cloud-init drive +sudo qm set --ide2 local-zfs:cloudinit + +# Write SSH public key to a temp file +echo 'ssh-rsa AAAA... wayne@stream' > /tmp/admin-key.pub +# (use the key from variables.nix adminSshKey) + +# Configure cloud-init +sudo qm set \ + --ciuser wayne \ + --sshkeys /tmp/admin-key.pub \ + --ipconfig0 ip=dhcp \ + --nameserver 192.168.2.253 \ + --searchdomain sweet.home + +# Set boot order +sudo qm set --boot order=scsi0 + +# Start VM +sudo qm start +``` + +### Note on SSH key mismatch + +The GenericCloud image injects the cloud-init SSH key on first boot. +If you need to add an additional key (e.g. from a different machine) +after first boot, mount the disk via nbd while the VM is stopped: + +```bash +sudo qm stop +sudo qemu-nbd --connect=/dev/nbd1 --format=raw /dev/zvol/rpool/data/vm--disk-0 +# wait 2s, then: +sudo mount /dev/nbd1p4 /mnt/vm # p4 is the root partition on Rocky 9 GenericCloud +sudo tee -a /mnt/vm/home/wayne/.ssh/authorized_keys <<< 'ssh-ed25519 AAAA... extra-key' +sudo umount /mnt/vm +sudo qemu-nbd --disconnect /dev/nbd1 +sudo qm start +``` + +Rocky 9 GenericCloud partition layout: `p1`=BIOS boot (2M), +`p2`=EFI (100M), `p3`=/boot (1G), `p4`=/ (rest). + +--- + +## Step 3 — First-boot system preparation + +SSH in as `wayne` once cloud-init has completed (usually 60–90 s): + +```bash +ssh wayne@ +``` + +### Add swap (required — FreeIPA needs headroom beyond 1.7 GB RAM) + +```bash +sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress +sudo chmod 600 /swapfile +sudo mkswap /swapfile +sudo swapon /swapfile +echo '/swapfile none swap defaults 0 0' | sudo tee -a /etc/fstab +``` + +### Set static IP via NetworkManager + +```bash +CON=$(nmcli -t -f NAME con show --active | head -1) +sudo nmcli con mod "$CON" \ + ipv4.method manual \ + ipv4.addresses 192.168.2.138/24 \ + ipv4.gateway 192.168.2.254 \ + ipv4.dns 192.168.2.253 \ + ipv4.dns-search sweet.home +sudo nmcli con up "$CON" +``` + +### Fix /etc/hosts (cloud-init maps FQDN to 127.0.0.1 — IPA requires real IP) + +```bash +sudo sed -i '/domain-controller/d' /etc/hosts +echo '192.168.2.138 domain-controller.sweet.home domain-controller' \ + | sudo tee -a /etc/hosts + +# Prevent cloud-init from resetting this on reboot +sudo sed -i 's/manage_etc_hosts: true/manage_etc_hosts: false/' \ + /etc/cloud/cloud.cfg +``` + +--- + +## Step 4 — Install FreeIPA packages + +```bash +sudo dnf install -y ipa-server ipa-server-dns +``` + +This pulls ~200 packages including 389-ds, Dogtag PKI, BIND, and MIT +Kerberos. Takes 5–10 minutes depending on mirror speed. + +--- + +## Step 5 — Run the unattended install + +Generate strong passwords (min 8 chars; store them in your password manager): + +```bash +DM_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24) +ADMIN_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24) +echo "Directory Manager: $DM_PASS" +echo "IPA Admin: $ADMIN_PASS" +# Save both in your password manager NOW before proceeding +``` + +Run the installer (takes 15–20 minutes): + +```bash +sudo ipa-server-install \ + --realm=SWEET.HOME \ + --domain=sweet.home \ + --hostname=domain-controller.sweet.home \ + --ds-password="$DM_PASS" \ + --admin-password="$ADMIN_PASS" \ + --setup-dns \ + --forwarder=192.168.2.253 \ + --no-dnssec-validation \ + --no-ntp \ + --unattended +``` + +Key flags: +- `--setup-dns` — install BIND as IPA's authoritative DNS for `sweet.home` +- `--forwarder=192.168.2.253` — forward non-sweet.home queries to Pi-hole +- `--no-dnssec-validation` — skip DNSSEC (home lab has no DNSSEC chain) +- `--no-ntp` — Proxmox handles time sync for guests; don't install chrony + +--- + +## Step 6 — Verify + +```bash +# All services should show RUNNING +ipactl status + +# Get a Kerberos ticket and confirm +echo "$ADMIN_PASS" | kinit admin +klist + +# Check DNS SRV records are in place +dig +short _kerberos._udp.sweet.home SRV @127.0.0.1 +# Expected: 0 100 88 domain-controller.sweet.home. +``` + +--- + +## Step 7 — Configure Pi-hole to forward sweet.home DNS + +See `docs/pihole-dns.md` and `scripts/configure-pihole-dns.sh`. + +The short version — add to Pi-hole's custom dnsmasq config: +``` +server=/sweet.home/192.168.2.138 +``` + +--- + +## Step 8 — Back up the CA certificate + +```bash +# On domain-controller (encrypted with Directory Manager password) +ls -lh /root/cacert.p12 + +# Copy to a safe location +scp root@domain-controller:/root/cacert.p12 ~/backups/ipa-cacert.p12 +``` + +This file is required if you ever set up a replica or need to +re-issue service certificates. diff --git a/freeipa/docs/pihole-dns.md b/freeipa/docs/pihole-dns.md new file mode 100644 index 0000000..ac58964 --- /dev/null +++ b/freeipa/docs/pihole-dns.md @@ -0,0 +1,70 @@ +# Pi-hole DNS forwarding for sweet.home + +FreeIPA's BIND instance is the authoritative DNS server for the +`sweet.home` zone. Pi-hole (at `192.168.2.253`) is the LAN resolver +for everything else. To make Kerberos, LDAP, and host lookups work +from all LAN clients, Pi-hole must forward `sweet.home` queries to +the domain-controller. + +## What needs configuring + +One dnsmasq directive on Pi-hole: + +``` +server=/sweet.home/192.168.2.138 +``` + +This makes Pi-hole forward every `*.sweet.home` query (and the bare +`sweet.home` zone) to `192.168.2.138` (domain-controller) instead +of its upstream resolver. + +## How to apply + +Use `scripts/configure-pihole-dns.sh` for an automated, idempotent +application, or apply manually as below. + +### Manual — Pi-hole v6 (TOML config) + +Pi-hole v6 reads custom dnsmasq options from: +``` +/etc/pihole/pihole.toml → dns.customDnsEntries +``` +or from drop-in files in `/etc/dnsmasq.d/`. The drop-in approach +is more robust across upgrades: + +```bash +ssh +echo 'server=/sweet.home/192.168.2.138' \ + | sudo tee /etc/dnsmasq.d/10-ipa-sweet-home.conf +sudo pihole restartdns +``` + +Verify: +```bash +dig +short _kerberos._udp.sweet.home SRV @192.168.2.253 +# Should return: 0 100 88 domain-controller.sweet.home. +dig +short domain-controller.sweet.home A @192.168.2.253 +# Should return: 192.168.2.138 +``` + +## Why this is required + +FreeIPA clients use DNS SRV records to discover the KDC, LDAP server, +and other IPA services. Without the forwarder, Pi-hole returns NXDOMAIN +for `_kerberos._udp.sweet.home` and `_ldap._tcp.sweet.home`, which +causes `kinit` and IPA client enrollment to fail — even if the IP +address is reachable. + +## Records IPA publishes + +Once forwarding is set up, Pi-hole clients will be able to resolve: + +| Record | Type | Value | +|--------|------|-------| +| `domain-controller.sweet.home` | A | `192.168.2.138` | +| `_kerberos._udp.sweet.home` | SRV | `0 100 88 domain-controller.sweet.home` | +| `_kerberos._tcp.sweet.home` | SRV | `0 100 88 domain-controller.sweet.home` | +| `_ldap._tcp.sweet.home` | SRV | `0 100 389 domain-controller.sweet.home` | +| `_kpasswd._udp.sweet.home` | SRV | `0 100 464 domain-controller.sweet.home` | +| `_kerberos.sweet.home` | TXT | `"SWEET.HOME"` | +| `ipa-ca.sweet.home` | A | `192.168.2.138` | diff --git a/freeipa/scripts/configure-pihole-dns.sh b/freeipa/scripts/configure-pihole-dns.sh new file mode 100755 index 0000000..4cba0ce --- /dev/null +++ b/freeipa/scripts/configure-pihole-dns.sh @@ -0,0 +1,46 @@ +#!/usr/bin/env bash +# Configure Pi-hole to forward sweet.home DNS queries to FreeIPA. +# +# Usage: +# bash configure-pihole-dns.sh +# bash configure-pihole-dns.sh pihole.sweet.home +# +# Idempotent — safe to run multiple times. +# Requires SSH access to the Pi-hole host as a user with sudo. + +set -euo pipefail + +PIHOLE_HOST="${1:-}" +IPA_IP="192.168.2.138" +IPA_DOMAIN="sweet.home" +DNSMASQ_CONF="/etc/dnsmasq.d/10-ipa-${IPA_DOMAIN//./-}.conf" +DIRECTIVE="server=/${IPA_DOMAIN}/${IPA_IP}" + +if [[ -z "$PIHOLE_HOST" ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +echo "==> Configuring Pi-hole at $PIHOLE_HOST to forward $IPA_DOMAIN → $IPA_IP..." + +ssh "$PIHOLE_HOST" " + set -euo pipefail + + if grep -qF '${DIRECTIVE}' '${DNSMASQ_CONF}' 2>/dev/null; then + echo '==> Forwarder already configured, skipping write.' + else + echo '${DIRECTIVE}' | sudo tee '${DNSMASQ_CONF}' + echo '==> Written to ${DNSMASQ_CONF}' + fi + + echo '==> Restarting Pi-hole DNS...' + sudo pihole restartdns + + echo '==> Verifying SRV record resolution via Pi-hole...' + sleep 2 + dig +short _kerberos._udp.${IPA_DOMAIN} SRV @127.0.0.1 || true +" + +echo "==> Done. Pi-hole now forwards ${IPA_DOMAIN} queries to ${IPA_IP}." +echo " Verify from a LAN client:" +echo " dig +short _kerberos._udp.${IPA_DOMAIN} SRV @${PIHOLE_HOST}" diff --git a/freeipa/scripts/install.sh b/freeipa/scripts/install.sh new file mode 100755 index 0000000..f5ed70e --- /dev/null +++ b/freeipa/scripts/install.sh @@ -0,0 +1,130 @@ +#!/usr/bin/env bash +# FreeIPA server install script for domain-controller.sweet.home +# +# Run this on a fresh Rocky Linux 9 VM that already has: +# - Correct hostname: domain-controller.sweet.home +# - Static IP: 192.168.2.138/24 +# - Gateway: 192.168.2.254 +# - DNS: 192.168.2.253 (Pi-hole) +# - sudo access for the current user +# +# The script will prompt for passwords if not set via environment: +# IPA_DM_PASSWORD Directory Manager password (store in password manager) +# IPA_ADMIN_PASSWORD IPA admin Kerberos password (store in password manager) +# +# Full procedure: see docs/install.md + +set -euo pipefail + +IPA_REALM="SWEET.HOME" +IPA_DOMAIN="sweet.home" +IPA_HOSTNAME="domain-controller.sweet.home" +IPA_IP="192.168.2.138" +IPA_DNS_FORWARDER="192.168.2.253" + +# ── Password handling ──────────────────────────────────────────────────────── + +if [[ -z "${IPA_DM_PASSWORD:-}" ]]; then + read -r -s -p "Directory Manager password (min 8 chars): " IPA_DM_PASSWORD + echo +fi +if [[ -z "${IPA_ADMIN_PASSWORD:-}" ]]; then + read -r -s -p "IPA admin password (min 8 chars): " IPA_ADMIN_PASSWORD + echo +fi + +if [[ ${#IPA_DM_PASSWORD} -lt 8 || ${#IPA_ADMIN_PASSWORD} -lt 8 ]]; then + echo "ERROR: passwords must be at least 8 characters" >&2 + exit 1 +fi + +# ── Pre-flight checks ──────────────────────────────────────────────────────── + +echo "==> Checking hostname..." +actual_fqdn=$(hostname -f) +if [[ "$actual_fqdn" != "$IPA_HOSTNAME" ]]; then + echo "ERROR: hostname -f returned '$actual_fqdn', expected '$IPA_HOSTNAME'" >&2 + exit 1 +fi + +echo "==> Checking /etc/hosts entry..." +if ! grep -q "$IPA_IP $IPA_HOSTNAME" /etc/hosts; then + echo "ERROR: /etc/hosts does not have '$IPA_IP $IPA_HOSTNAME'" >&2 + echo "Fix: sudo sed -i '/$IPA_HOSTNAME/d' /etc/hosts && echo '$IPA_IP $IPA_HOSTNAME ${IPA_HOSTNAME%%.*}' | sudo tee -a /etc/hosts" >&2 + exit 1 +fi + +echo "==> Checking Python FQDN resolution..." +resolved=$(python3 -c "import socket; print(socket.gethostbyname('$IPA_HOSTNAME'))" 2>/dev/null || true) +if [[ "$resolved" != "$IPA_IP" ]]; then + echo "ERROR: $IPA_HOSTNAME resolves to '$resolved', expected '$IPA_IP'" >&2 + exit 1 +fi + +echo "==> Checking internet connectivity..." +if ! ping -c1 -W5 8.8.8.8 >/dev/null 2>&1; then + echo "ERROR: no internet connectivity (needed for package install)" >&2 + exit 1 +fi + +# ── Swap ───────────────────────────────────────────────────────────────────── + +if ! swapon --show | grep -q .; then + echo "==> Creating 2 GB swap file (FreeIPA needs headroom)..." + sudo dd if=/dev/zero of=/swapfile bs=1M count=2048 status=progress + sudo chmod 600 /swapfile + sudo mkswap /swapfile + sudo swapon /swapfile + grep -q '/swapfile' /etc/fstab || echo '/swapfile none swap defaults 0 0' | sudo tee -a /etc/fstab +else + echo "==> Swap already configured, skipping." +fi + +# ── Packages ───────────────────────────────────────────────────────────────── + +echo "==> Installing FreeIPA server packages..." +sudo dnf install -y ipa-server ipa-server-dns + +# ── Install ────────────────────────────────────────────────────────────────── + +echo "==> Running ipa-server-install (15–20 min)..." +sudo ipa-server-install \ + --realm="$IPA_REALM" \ + --domain="$IPA_DOMAIN" \ + --hostname="$IPA_HOSTNAME" \ + --ds-password="$IPA_DM_PASSWORD" \ + --admin-password="$IPA_ADMIN_PASSWORD" \ + --setup-dns \ + --forwarder="$IPA_DNS_FORWARDER" \ + --no-dnssec-validation \ + --no-ntp \ + --unattended + +# ── Verify ─────────────────────────────────────────────────────────────────── + +echo "==> Verifying services..." +sudo ipactl status + +echo "==> Verifying Kerberos ticket..." +echo "$IPA_ADMIN_PASSWORD" | kinit admin +klist + +echo "==> Verifying DNS SRV records..." +dig +short _kerberos._udp."$IPA_DOMAIN" SRV @127.0.0.1 + +echo "" +echo "======================================================================" +echo "Setup complete. Next steps:" +echo "" +echo " 1. Save passwords to your password manager (if not already done)." +echo "" +echo " 2. Configure Pi-hole to forward $IPA_DOMAIN DNS to $IPA_IP:" +echo " bash scripts/configure-pihole-dns.sh " +echo "" +echo " 3. Back up the CA certificates:" +echo " scp root@$IPA_HOSTNAME:/root/cacert.p12 ~/backups/ipa-cacert.p12" +echo " (Encrypted with the Directory Manager password)" +echo "" +echo " 4. Access the Web UI at:" +echo " https://$IPA_HOSTNAME/ipa/ui/" +echo "======================================================================" diff --git a/freeipa/scripts/verify.sh b/freeipa/scripts/verify.sh new file mode 100755 index 0000000..f6819bc --- /dev/null +++ b/freeipa/scripts/verify.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# Read-only health check for the FreeIPA server. +# Run locally on domain-controller or remotely: +# ssh wayne@domain-controller 'bash -s' < scripts/verify.sh +# +# Exit code 0 = all checks passed, non-zero = something is wrong. + +set -uo pipefail + +PASS=0 +FAIL=0 + +check() { + local label="$1" + shift + if "$@" >/dev/null 2>&1; then + echo " OK $label" + (( PASS++ )) || true + else + echo "FAIL $label" + (( FAIL++ )) || true + fi +} + +echo "=== FreeIPA health check: $(hostname -f) ===" +echo "" + +echo "--- Services ---" +check "ipactl status" sudo ipactl status +check "dirsrv running" systemctl is-active dirsrv.target +check "krb5kdc running" systemctl is-active krb5kdc +check "named running" systemctl is-active named +check "httpd running" systemctl is-active httpd +check "pki-tomcatd running" systemctl is-active pki-tomcatd.target + +echo "" +echo "--- DNS ---" +check "A record: domain-controller.sweet.home" dig +short domain-controller.sweet.home A @127.0.0.1 +check "SRV: _kerberos._udp.sweet.home" dig +short _kerberos._udp.sweet.home SRV @127.0.0.1 +check "SRV: _ldap._tcp.sweet.home" dig +short _ldap._tcp.sweet.home SRV @127.0.0.1 +check "TXT: _kerberos.sweet.home" dig +short _kerberos.sweet.home TXT @127.0.0.1 + +echo "" +echo "--- LDAP ---" +check "LDAP port 389 open" bash -c "exec 3<>/dev/tcp/127.0.0.1/389" +check "LDAPS port 636 open" bash -c "exec 3<>/dev/tcp/127.0.0.1/636" + +echo "" +echo "--- Kerberos ---" +check "KDC port 88 open" bash -c "exec 3<>/dev/tcp/127.0.0.1/88" + +echo "" +echo "--- HTTP ---" +check "IPA HTTP redirect" curl -sk -o /dev/null -w "%{http_code}" http://localhost/ | grep -qE "^(301|302|200)" +check "IPA HTTPS UI" curl -sk -o /dev/null -w "%{http_code}" https://localhost/ipa/ui/ | grep -q "200" + +echo "" +if [[ $FAIL -eq 0 ]]; then + echo "All $PASS checks passed." +else + echo "$FAIL check(s) FAILED, $PASS passed." + exit 1 +fi