# ansipa Setup Guide

**ansipa** = **Ans**ible + Free**IPA**: a GPO-like management plane for Linux
fleets. FreeIPA provides identity, Kerberos, sudo/HBAC and host/user groups;
a lightweight client-side enforcer turns group membership into enforced policy;
CheckMK monitors the fleet; Keycloak adds SSO; and an nginx gateway fronts all
the web UIs behind one hostname.

This guide covers a full deployment: architecture, every port, the reverse-proxy
gateway, the exact snippet to expose it behind your own reverse proxy, the
NAT / behind-firewall behaviour, and known distro caveats. For the policy
catalogue and playbook reference see [FreeIPA & Ansible](freeipa-ansible.md).

---

## Architecture

```
                    ┌─────────────────────────── docker host ───────────────────────────┐
                    │  docker network ipa-net (172.30.0.0/24)                            │
   your reverse     │                                                                    │
   proxy (TLS)      │   ┌── ansipa-nginx (gateway) ──┐                                   │
        │           │   │  /      portal start page  │                                   │
   https://ansipa ──┼──►│  /ipa/  ─► freeipa  :443 ───┼──► 172.30.0.10  FreeIPA          │
   .example.com     │   │  /cmk/  ─► checkmk  :5000 ──┼──► 172.30.0.12  CheckMK          │
   (:443)           │   │  /auth/ ─► keycloak :8080 ──┼──► keycloak     Keycloak         │
                    │   └────────────────────────────┘         │                        │
                    │        :8088 (http)                       └─ postgres (KC backend) │
                    └────────────────────────────────────────────────────────────────────┘
                                     ▲                                   ▲
              client-initiated only  │ (enroll, kinit, policy pull,      │
              — works across NAT     │  CheckMK push :8000)              │
                    ┌────────────────┴───────────┐        ┌──────────────┴─────────────┐
                    │ enrolled client (Arch)     │        │ enrolled client (Fedora…)  │
                    │ ansipa-enforce-policies     │        │ SSSD + Kerberos + enforcer │
                    │ .timer (every 30 min)       │        │                            │
                    └─────────────────────────────┘        └────────────────────────────┘
```

The design is **pull-based**: the server never initiates connections to clients.
Every enrolled host runs `ansipa-enforce-policies.sh` on a 30-minute timer, does
`kinit -k host/$FQDN`, reads its own group memberships from FreeIPA, and applies
the matching policies locally. This is what makes the whole system work when
clients are behind NAT (see [Clients behind NAT](#clients-behind-nat)).

---

## Bringing up the server stack

```bash
cd setup/modules/FreeipaAnsible/image
cp .env.example .env && $EDITOR .env      # set domain, realm, passwords, ports

# cgroup v2 hosts (WSL2, modern Fedora/Ubuntu/Arch): use the run.sh wrapper —
# the FreeIPA container needs --cgroupns host, which compose cannot express.
./run.sh all        # freeipa + postgres + keycloak + checkmk + nginx gateway
#   ./run.sh        # freeipa only
#   ./run.sh down   # stop everything

# plain cgroup v1 hosts can use compose directly:
docker compose up -d

docker compose logs -f freeipa      # watch first-boot (~10 min)
./keycloak-configure.sh             # wire Keycloak → FreeIPA LDAP (once healthy)
```

---

## Ports

### Server (published on the docker host)

| Port | Proto | Service | Notes |
|------|-------|---------|-------|
| 389 | TCP | LDAP | FreeIPA directory |
| 636 | TCP | LDAPS | FreeIPA directory (TLS) |
| 88 | TCP+UDP | Kerberos | KDC |
| 464 | TCP+UDP | kpasswd | password changes |
| 443 | TCP | HTTPS | FreeIPA web UI (also proxied at `/ipa/`) |
| 445 | TCP | SMB | Samba shares (scans, LUKS keys, policy store) |
| 137 | UDP | NetBIOS name | Samba |
| 138 | UDP | NetBIOS datagram | Samba |
| 139 | TCP | NetBIOS session | Samba |
| 8090 | TCP | CheckMK web UI | container :5000 → host 8090 (also proxied at `/cmk/`) |
| 8000 | TCP | CheckMK agent receiver | **push-mode** target for NAT clients |
| 6557 | TCP | CheckMK livestatus | optional, external tooling |
| 8080 | TCP | Keycloak HTTP | dev mode (also proxied at `/auth/`) |
| 8443 | TCP | Keycloak HTTPS | needs a cert for the `start` command |
| **8088** | TCP | **nginx gateway** | single entry for the portal + all UIs (`ANSIPA_HTTP_PORT`) |

### Client (only needed for CheckMK **pull** mode)

| Port | Proto | Service | Notes |
|------|-------|---------|-------|
| 6556 | TCP | check_mk agent socket | server → client; **unreachable across NAT**, use push mode instead |
| 22 | TCP | sshd | if the client is in `dev_daemon-enable-sshd` and you manage over SSH |

---

## Firewall

All ansipa traffic is **client-initiated**, so the client firewall needs almost
nothing open inbound; the server firewall is where you open ports.

### Server — inbound (open these)

Minimum for identity + policy enforcement:

| Port(s) | Proto | Why |
|---------|-------|-----|
| 389, 636 | TCP | LDAP / LDAPS — SSSD directory lookups |
| 88, 464 | TCP+UDP | Kerberos KDC + kpasswd |
| 443 | TCP | FreeIPA web UI + enrollment (also behind the gateway) |

Add per feature you use:

| Port(s) | Proto | Feature |
|---------|-------|---------|
| 137-138 | UDP | SMB (policy store, scan upload, LUKS keys) |
| 139, 445 | TCP | SMB |
| 8090 | TCP | CheckMK web UI |
| 8000 | TCP | CheckMK agent receiver — **required for NAT/push clients** |
| 6557 | TCP | CheckMK livestatus (only if external tools query it) |
| 8080, 8443 | TCP | Keycloak (usually only via the gateway, not exposed directly) |
| 8088 | TCP | nginx gateway — the one port your reverse proxy talks to |

> If you front everything with the nginx gateway + your own reverse proxy, the
> only web port that needs to be reachable from outside is the one your reverse
> proxy uses to reach **8088** (or your proxy’s own 443). LDAP/Kerberos/SMB are
> still needed directly by enrolled clients and are not proxied.

### Client — inbound

| Port | Proto | When |
|------|-------|------|
| 6556 | TCP | **only** if using CheckMK pull mode (server reaches client). Not needed — and not possible — for NAT clients, which use push mode (outbound to server :8000). |
| 22 | TCP | only if you manage the client over SSH |

Push-mode and policy enforcement need **no inbound ports on the client**.

### Example rules (server)

**firewalld** (RHEL/Fedora/Rocky):

```bash
for p in 389/tcp 636/tcp 88/tcp 88/udp 464/tcp 464/udp 443/tcp \
         137/udp 138/udp 139/tcp 445/tcp 8090/tcp 8000/tcp 8088/tcp; do
    firewall-cmd --permanent --add-port=$p
done
firewall-cmd --reload
```

**ufw** (Debian/Ubuntu):

```bash
for p in 389 636 88 464 443 139 445 8090 8000 8088; do ufw allow $p/tcp; done
for p in 88 464 137 138; do ufw allow $p/udp; done
```

**nftables** (excerpt for an `inet filter` input chain):

```nft
tcp dport { 389, 636, 88, 464, 443, 139, 445, 8090, 8000, 8088 } accept
udp dport { 88, 464, 137, 138 } accept
```

---

## The nginx gateway (portal + reverse proxy)

The `ansipa-nginx` service fronts all three web UIs on **one hostname/port** and
serves a portal landing page. Each backend is proxied at the path it already
uses natively, so there is no path rewriting to fight:

| Path | Backend | Notes |
|------|---------|-------|
| `/` | portal start page | static, links to the three UIs |
| `/ipa/` | FreeIPA `:443` | Host + Referer pinned to the IPA hostname (IPA anti-CSRF) |
| `/cmk/` | CheckMK `:5000` | CheckMK already serves under `/cmk` |
| `/auth/` | Keycloak `:8080` | Keycloak runs with `KC_HTTP_RELATIVE_PATH=/auth` |

The gateway speaks plain HTTP on `ANSIPA_HTTP_PORT` (default **8088**) and trusts
`X-Forwarded-*`, so it is designed to sit **behind your own TLS-terminating
reverse proxy**.

### Exposing ansipa behind your main reverse proxy

Point one hostname at the gateway. TLS is terminated by your proxy; ansipa
receives plain HTTP and reads `X-Forwarded-Proto` to build correct URLs.

**nginx** (add to your main reverse proxy):

```nginx
server {
    listen 443 ssl;
    server_name ansipa.example.com;
    # ssl_certificate / ssl_certificate_key ...

    location / {
        proxy_pass http://DOCKER_HOST_IP:8088;
        proxy_set_header Host              $host;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header X-Forwarded-For   $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host  $host;
    }
}
```

**Caddy** (one line):

```caddy
ansipa.example.com {
    reverse_proxy DOCKER_HOST_IP:8088
}
```

**Traefik** (docker labels on the nginx service): route the host rule to the
`ansipa-nginx` service on port 80.

### Per-app hostname settings (recommended for production)

Both CheckMK and Keycloak generate some absolute URLs from their own configured
hostname rather than the forwarded one. For a clean deployment behind a public
name, set:

- **Keycloak**: `KC_HOSTNAME=https://ansipa.example.com/auth` in `.env`.
- **CheckMK**: set the site's URL prefix / base URL to your public `https://ansipa.example.com/cmk/` (OMD → *Global settings → User interface → Base URL*), so login redirects use `https` and the public host.

Without these the UIs are still reachable through the proxy; only some redirects
fall back to the container-internal scheme/host.

---

## Clients behind NAT

The controller can live on the public internet while clients sit behind NAT — the
architecture is client-initiated end to end:

| Function | Direction | Works behind NAT? |
|----------|-----------|-------------------|
| Enrollment (`ipa-client-install`) | client → server | ✅ |
| Policy enforcement (30-min timer) | client → server (kinit + `ipa` reads) | ✅ |
| Policy store sync | client → server SMB | ✅ |
| CheckMK **pull** (`:6556`) | **server → client** | ❌ — blocked by NAT |
| CheckMK **push** (`cmk-agent-ctl` → `:8000`) | client → server | ✅ |

So policies and identity Just Work across NAT. For monitoring, NAT clients must
use **push mode**: the server records a push request for each host in
`dev_mon_base` (`ansipa-checkmk-setup.sh` writes `/cmk-creds/push-mode-<host>.req`,
the CheckMK container consumes it and flips the host to `cmk_agent_connection:
push-agent`), and the client registers with `cmk-agent-ctl register --server
<host>:8000`. Advertise a **client-routable** CheckMK URL so agents can reach the
server — set `CMK_ADVERTISED_URL` in `.env` (e.g. `http://mon.example.com:8090`);
it is stored in the `dev_mon_base` group description and must NOT be the
container-internal `172.30.0.12`.

> **Note:** push mode needs the full CheckMK agent’s `cmk-agent-ctl` binary.
> RPM/DEB distros ship it in the agent package; on Arch the portable
> `check_mk_agent` fallback lacks it, so an Arch NAT client enrolls and gets
> policies but needs `cmk-agent-ctl` installed manually for push monitoring.

---

## Deploying as a Proxmox LXC container

To run the whole stack as a single Proxmox container, use the generator:

```bash
bash setup/tools/ansipa-proxmox-lxc.sh [OUT_DIR]   # default ~/ansipa-lxc-out
```

It prompts for domain/realm/VMID/resources, generates all secrets, and writes:

| File | Purpose |
|------|---------|
| `pct-create-<vmid>.sh` | `pct create` command — run on the Proxmox host |
| `ansipa-<vmid>.conf` | LXC options to append to `/etc/pve/lxc/<vmid>.conf` |
| `ansipa-provision.sh` | first-boot provisioner (installs Docker, brings the stack up) |
| `.env` | filled-in stack environment (generated passwords — mode 600) |
| `DEPLOY-<vmid>.md` | step-by-step guide |

The container is **privileged** with `nesting=1,keyctl=1,fuse=1` — required for
FreeIPA's systemd + Kerberos keyrings and Docker's overlay2. It runs the same
tested docker-compose stack inside the CT ("Docker-in-LXC"), so it is the full
ansipa deployment in one Proxmox container. Size it ≥6 GB RAM.

> `setup/tools/freeipa-image.sh` has a separate `proxmox-lxc` target that
> exports a **FreeIPA-only** CT rootfs (no CheckMK/Keycloak/nginx) — use that if
> you want just the identity server; use `ansipa-proxmox-lxc.sh` for the full stack.

---

## Client enrollment

```bash
# on the client, as root:
sudo ./freeipa-enroll.sh --domain ansipa.example.com --realm ANSIPA.EXAMPLE.COM \
     --server ipa.ansipa.example.com --hostname client01.ansipa.example.com
# (IPA_PASSWORD env var or --password; --no-dns-update if IPA does not run DNS)
```

Supported distros: **Arch**, **Fedora**, **RHEL/Rocky/AlmaLinux**, **Debian/Ubuntu**.
The script picks the right package manager and package names automatically.

### Arch caveat — autofs / freeipa-client from the AUR

On Arch, `ipa-client-install` comes from the AUR `freeipa-client` package, which
pulls the entire FreeIPA suite plus `autofs` from source — a long build
(tens of minutes on a small VM). ansipa bundles its own `autofs-pkgbuild/` that
builds autofs from upstream git, because the stock AUR `autofs` currently fails
(its pinned `autofs-5.1.9.tar.xz` was pruned from kernel.org). `freeipa-enroll.sh`
builds and installs it automatically before the AUR `freeipa-client` step, so no
manual intervention is needed — just expect the first enrollment to take a while.

---

## Verifying a deployment

```bash
# server
docker compose ps                         # all healthy
curl -fsS http://DOCKER_HOST_IP:8088/ | grep ansipa   # portal
# client (after enrollment + one enforcer run)
id admin                                  # SSSD resolves IPA users
kinit admin && klist                      # Kerberos works
sudo /usr/local/bin/ansipa-enforce-policies.sh   # apply policies now
sudo systemctl list-timers ansipa-enforce-policies.timer
```
