# ward — setup and wiring guide ward runs on the k3s master as root, issues short-lived client certificates, and lets users fetch a ready-to-use kubeconfig (or act as a kubectl exec credential plugin) without anyone having to touch the k3s CA by hand. --- ## 1. Build and install ```bash # On the k3s master (or cross-compile and copy) go build -o /usr/local/bin/ward . ``` The same binary is both the **server** and the **client-side exec plugin** — subcommand dispatch happens on the first argument. --- ## 2. Server setup ### Minimal (LDAP, auto-discovered via DNS SRV) ```ini ExecStart= ExecStart=/usr/local/bin/ward \ --k3s-server=https://k3s.example.com:6443 \ --addr=:8443 \ --ldap ``` ### With an explicit LDAP URI (no DNS SRV required) ``` /etc/systemd/system/ward.service.d/local.conf ``` ```ini [Service] ExecStart= ExecStart=/usr/local/bin/ward \ --k3s-server=https://k3s.example.com:6443 \ --addr=:8443 \ --ldap-uri=ldaps://ldap.example.com ``` ```bash systemctl daemon-reload systemctl enable --now ward ``` ### With Kerberos SPNEGO ```ini ExecStart= ExecStart=/usr/local/bin/ward \ --k3s-server=https://k3s.example.com:6443 \ --addr=:8443 \ --kerberos \ --keytab=/etc/krb5.keytab \ --spn=HTTP/k3s.example.com ``` #### Add the service principal to the KDC ```bash # On the KDC (MIT Kerberos) kadmin.local -q "addprinc -randkey HTTP/k3s.example.com@EXAMPLE.COM" kadmin.local -q "ktadd -k /etc/krb5.keytab HTTP/k3s.example.com@EXAMPLE.COM" # Verify klist -k /etc/krb5.keytab ``` For Samba AD / Heimdal, the equivalent is: ```bash samba-tool spn add HTTP/k3s.example.com ward_service_account net ads keytab add HTTP/k3s.example.com -U Administrator ``` ### With htpasswd (break-glass / service accounts) ```ini ExecStart= ExecStart=/usr/local/bin/ward \ --k3s-server=https://k3s.example.com:6443 \ --addr=:8443 \ --kerberos \ --htpasswd=/etc/ward/htpasswd ``` ```bash # Create the file and add a user (bcrypt is required for new entries) mkdir -p /etc/ward htpasswd -B -c /etc/ward/htpasswd alice # Add more users htpasswd -B /etc/ward/htpasswd bob # Reload without restarting (SIGHUP) systemctl reload ward ``` --- ## 3. Get the bootstrap kubeconfig The `/bootstrap` endpoint returns a ready-to-use kubeconfig with no embedded credentials — just the cluster endpoint, server CA, and the exec plugin stanza that tells kubectl to call `ward credential` on demand. No authentication required to fetch it. ```bash curl -s https://k3s.example.com:8443/bootstrap > ~/.kube/config ``` The username in the kubeconfig is resolved in priority order: 1. `?user=` query parameter — explicit override 2. Credentials in the request (Basic or Kerberos) — personalised on the fly 3. The placeholder `YOUR_USERNAME_HERE` — for anonymous fetches ```bash # Personalised via query param (useful for scripting) curl -s https://k3s.example.com:8443/bootstrap?user=alice > ~/.kube/config # Personalised by authenticating while fetching curl -su alice https://k3s.example.com:8443/bootstrap > ~/.kube/config ``` Distribute this URL to users alongside the `ward` binary. That's the entire onboarding process. --- ## 4. Client setup Each user needs: 1. The `ward` binary in their `$PATH` 2. The bootstrap kubeconfig ```bash # Install the binary (copy from the server, or build locally) sudo cp ward /usr/local/bin/ward # Install the kubeconfig cp bootstrap-kubeconfig.yaml ~/.kube/config # or merge manually ``` That's it. First use: ```bash # Kerberos path — if you have a valid ticket, it just works: kinit alice@EXAMPLE.COM kubectl get nodes # Basic auth path — prompted on first use, then cached for 24 h: kubectl get nodes # Password for alice: ▌ ``` Credentials are cached in `~/.cache/ward/` and reused until 5 minutes before expiry. kubectl re-invokes the plugin automatically at that point — no manual `kubectl login` step required. --- ## 5. RBAC setup The issued certificate has: - **CN** = username (LDAP `sAMAccountName` / `uid`, or Kerberos principal primary) - **O** = LDAP group CNs (maps to Kubernetes RBAC groups) Bind roles as usual: ```bash # Grant a specific user cluster-admin kubectl create clusterrolebinding alice-admin \ --clusterrole=cluster-admin \ --user=alice # Grant an LDAP group view access kubectl create clusterrolebinding k8s-readers \ --clusterrole=view \ --group=k8s-users ``` --- ## 6. Debugging ### Server-side ```bash # Enable verbose logging WARD_DEBUG=1 systemctl restart ward journalctl -u ward -f # Or pass the flag directly ward --debug --k3s-server=... --addr=:8443 ``` Server debug output shows: - LDAP connection attempts (host, TLS, bind DN) - Auth method selected per request (Kerberos / Basic) - Group lookup results - Cert issuance details ### Client-side ```bash # Inline for a single command WARD_DEBUG=1 kubectl get nodes # Or permanently in the kubeconfig exec env block: # env: # - name: WARD_DEBUG # value: "1" ``` Client debug output (`stderr`, passed through by kubectl) shows: - Which Kerberos credential cache was found and the principal in it - Cache hit/miss and time remaining on cached cert - HTTP request URL and response status - Fallback to Basic auth and why ### Bypass the cache ```bash ward credential --server=https://k3s.example.com:8443 --no-cache --debug ``` ### Test without kubectl ```bash # Kerberos kinit alice ward credential --server=https://k3s.example.com:8443 --debug | python3 -m json.tool # Basic auth ward credential --server=https://k3s.example.com:8443 --no-kerberos --debug ```