Initial import: phase docs, configs, Claude conventions
Phase 1-3 runbooks and design notes, version pins, config templates, project-history conventions, cluster-inspector read-only credentials setup. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
# cluster-inspector: credentials setup
|
||||
|
||||
The `cluster-inspector` agent (`.claude/agents/cluster-inspector.md`) only ever talks to clusters
|
||||
through the read-only kubeconfigs in `kubeconfigs/` here. They're gitignored and minted by
|
||||
`make-kubeconfig.sh` from a short-lived ServiceAccount token.
|
||||
|
||||
| File | Purpose |
|
||||
| --- | --- |
|
||||
| `rbac.yaml` | ServiceAccount `kube-system/readonly-inspector` + `view` + read on CAPI CRDs and pod logs (no Secrets) |
|
||||
| `make-kubeconfig.sh` | applies RBAC (once), mints a TokenRequest token, writes `kubeconfigs/<name>.kubeconfig`, runs `can-i` checks |
|
||||
| `kubeconfigs/` | output, **gitignored**; one file per cluster, context `<name>-ro` |
|
||||
|
||||
## 1. Management cluster (after phase 1 step 2)
|
||||
|
||||
k3s writes `server: https://127.0.0.1:6443`, so pass the VM address. k3s puts node IPs into the
|
||||
API cert SANs, so the IP works; add `--tls-san` for a DNS name.
|
||||
|
||||
```bash
|
||||
scp ubuntu@<mgmt-vm>:/etc/rancher/k3s/k3s.yaml /tmp/lab-mgmt-admin.yaml # or use your existing admin kubeconfig
|
||||
.claude/cluster-inspector/make-kubeconfig.sh lab-mgmt \
|
||||
--admin-kubeconfig /tmp/lab-mgmt-admin.yaml \
|
||||
--server https://<mgmt-vm-ip>:6443 \
|
||||
--apply-rbac
|
||||
rm /tmp/lab-mgmt-admin.yaml
|
||||
```
|
||||
|
||||
## 2. Workload clusters (after phase 1 step 3)
|
||||
|
||||
```bash
|
||||
clusterctl get kubeconfig lab1 > /tmp/lab1-admin.yaml # run against the mgmt cluster (admin)
|
||||
.claude/cluster-inspector/make-kubeconfig.sh lab1 --admin-kubeconfig /tmp/lab1-admin.yaml --apply-rbac
|
||||
rm /tmp/lab1-admin.yaml
|
||||
```
|
||||
|
||||
## Expected verification output
|
||||
|
||||
```
|
||||
list pods yes
|
||||
get machines.cluster.x-k8s.io yes # "no" on workload clusters (no CAPI CRDs), fine
|
||||
delete machines.cluster.x-k8s.io no
|
||||
patch machinedeployments.cluster.x-k8s.io no
|
||||
get secrets no
|
||||
```
|
||||
|
||||
Anything else means the RBAC is wrong. Don't use the agent until it matches.
|
||||
|
||||
## Refresh / revoke
|
||||
|
||||
- **Refresh:** re-run the same command without `--apply-rbac`. The default lifetime is 7 days
|
||||
(`--duration`); the API server may cap it (`--service-account-max-token-expiration`).
|
||||
- **Revoke everything:** `kubectl delete sa -n kube-system readonly-inspector` (as admin). That
|
||||
invalidates every minted token; re-apply `rbac.yaml` to start over.
|
||||
|
||||
## Making read-only the default for this repo (recommended)
|
||||
|
||||
RBAC only protects you when the read-only kubeconfig is actually used. Your admin
|
||||
`~/.kube/config` is still reachable from any shell. To make Claude Code sessions in this repo
|
||||
default to the read-only files, add this to `.claude/settings.local.json` (personal, not committed):
|
||||
|
||||
```json
|
||||
{
|
||||
"env": {
|
||||
"KUBECONFIG": "/Users/jan.novak/srv/go/cluster-autoscaler-with-capi/.claude/cluster-inspector/kubeconfigs/lab-mgmt.kubeconfig:/Users/jan.novak/srv/go/cluster-autoscaler-with-capi/.claude/cluster-inspector/kubeconfigs/lab1.kubeconfig"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then plain `kubectl --context lab-mgmt-ro …` / `--context lab1-ro …` works everywhere in the
|
||||
session, and mutations fail on RBAC even if something tries them.
|
||||
Executable
+66
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env bash
|
||||
# Mint a read-only kubeconfig for the cluster-inspector agent.
|
||||
#
|
||||
# Usage: make-kubeconfig.sh <name> [options]
|
||||
# <name> cluster name, e.g. lab-mgmt or lab1 -> kubeconfigs/<name>.kubeconfig
|
||||
# --admin-kubeconfig PATH admin kubeconfig used to apply RBAC / mint the token (default: $KUBECONFIG or ~/.kube/config)
|
||||
# --context CTX context inside the admin kubeconfig
|
||||
# --server URL override API server URL (k3s.yaml says https://127.0.0.1:6443)
|
||||
# --duration DUR token lifetime (default 168h); re-run the script to refresh
|
||||
# --apply-rbac kubectl apply rbac.yaml first (needed once per cluster)
|
||||
set -euo pipefail
|
||||
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
name=""; admin="${KUBECONFIG:-$HOME/.kube/config}"; ctx=""; server=""; duration="168h"; apply=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--admin-kubeconfig) admin="$2"; shift 2 ;;
|
||||
--context) ctx="$2"; shift 2 ;;
|
||||
--server) server="$2"; shift 2 ;;
|
||||
--duration) duration="$2"; shift 2 ;;
|
||||
--apply-rbac) apply=1; shift ;;
|
||||
-h|--help) sed -n '2,10p' "$0"; exit 0 ;;
|
||||
-*) echo "unknown option $1" >&2; exit 1 ;;
|
||||
*) name="$1"; shift ;;
|
||||
esac
|
||||
done
|
||||
[[ -n "$name" ]] || { sed -n '2,10p' "$0" >&2; exit 1; }
|
||||
|
||||
k=(kubectl --kubeconfig "$admin")
|
||||
[[ -n "$ctx" ]] && k+=(--context "$ctx")
|
||||
|
||||
if (( apply )); then "${k[@]}" apply -f "$here/rbac.yaml"; fi
|
||||
|
||||
token="$("${k[@]}" -n kube-system create token readonly-inspector --duration "$duration")"
|
||||
[[ -n "$server" ]] || server="$("${k[@]}" config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.server}')"
|
||||
ca="$("${k[@]}" config view --raw --minify --flatten -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')"
|
||||
[[ -n "$ca" ]] || { echo "no certificate-authority-data in admin kubeconfig" >&2; exit 1; }
|
||||
|
||||
mkdir -p "$here/kubeconfigs"
|
||||
out="$here/kubeconfigs/$name.kubeconfig"
|
||||
umask 077
|
||||
cat > "$out" <<EOF
|
||||
apiVersion: v1
|
||||
kind: Config
|
||||
clusters:
|
||||
- name: $name
|
||||
cluster:
|
||||
server: $server
|
||||
certificate-authority-data: $ca
|
||||
users:
|
||||
- name: $name-readonly-inspector
|
||||
user:
|
||||
token: $token
|
||||
contexts:
|
||||
- name: $name-ro
|
||||
context:
|
||||
cluster: $name
|
||||
user: $name-readonly-inspector
|
||||
current-context: $name-ro
|
||||
EOF
|
||||
echo "wrote $out (context $name-ro, server $server, token valid $duration)"
|
||||
|
||||
echo "verification (expect yes, yes, no, no, no; CAPI rows say no on workload clusters):"
|
||||
for check in "list pods" "get machines.cluster.x-k8s.io" "delete machines.cluster.x-k8s.io" \
|
||||
"patch machinedeployments.cluster.x-k8s.io" "get secrets"; do
|
||||
printf ' %-45s %s\n' "$check" "$(KUBECONFIG="$out" kubectl auth can-i $check -A 2>/dev/null || true)"
|
||||
done
|
||||
@@ -0,0 +1,43 @@
|
||||
# Read-only identity for the cluster-inspector agent.
|
||||
# Apply to the management cluster and to every workload cluster the agent should inspect
|
||||
# (CAPI rules are simply unused on workload clusters).
|
||||
# Built-in `view` excludes Secrets; capi-readonly adds read access to CAPI CRDs and pod logs.
|
||||
# Tokens are short-lived TokenRequest tokens minted by make-kubeconfig.sh — no Secret-based token.
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: readonly-inspector
|
||||
namespace: kube-system
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRole
|
||||
metadata:
|
||||
name: capi-readonly
|
||||
rules:
|
||||
- apiGroups:
|
||||
- cluster.x-k8s.io
|
||||
- infrastructure.cluster.x-k8s.io
|
||||
- controlplane.cluster.x-k8s.io
|
||||
- bootstrap.cluster.x-k8s.io
|
||||
- ipam.cluster.x-k8s.io
|
||||
- addons.cluster.x-k8s.io
|
||||
- clusterctl.cluster.x-k8s.io
|
||||
resources: ["*"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods/log"]
|
||||
verbs: ["get", "list"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: readonly-inspector-view
|
||||
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: view }
|
||||
subjects: [{ kind: ServiceAccount, name: readonly-inspector, namespace: kube-system }]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: ClusterRoleBinding
|
||||
metadata:
|
||||
name: readonly-inspector-capi
|
||||
roleRef: { apiGroup: rbac.authorization.k8s.io, kind: ClusterRole, name: capi-readonly }
|
||||
subjects: [{ kind: ServiceAccount, name: readonly-inspector, namespace: kube-system }]
|
||||
Reference in New Issue
Block a user