246 lines
5.5 KiB
Markdown
246 lines
5.5 KiB
Markdown
# 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
|
|
```
|