Import
This commit is contained in:
245
README.md
Normal file
245
README.md
Normal file
@@ -0,0 +1,245 @@
|
||||
# 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
|
||||
```
|
||||
Reference in New Issue
Block a user