k8s/agent-access: a namespace-limited service account and kubeconfig for a coding agent
Read-only by default, optional deploy role; no Secrets, exec, port-forward or RBAC. Tokens from kubectl create token, time-limited. Not applied. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QLJfZQZeAKAwSqViTmLzki
This commit is contained in:
@@ -118,6 +118,11 @@ tyre pressures, GPS (lat, lon, elevation, heading) and the is_* flags. It does *
|
|||||||
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
confirmed car facts, app structure, UI conventions, build/test/ship, documents. It replaces
|
||||||
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
upstream's EVABRPUploader text. The test emulator is now in the flake: `nix run .#car-emulator`
|
||||||
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
(Android 9 AOSP at 1920×720, 160 dpi; the car build installs as the system user — checked).
|
||||||
|
- 2026-10-19 — `k8s/agent-access/`: a service account `mg4-agent` for a coding agent, limited to
|
||||||
|
the `mg4` namespace. Read-only role by default; an optional deploy role. Neither may touch
|
||||||
|
Secrets, exec, port-forward or RBAC; the deploy role cannot delete PVCs. `make-kubeconfig.sh`
|
||||||
|
writes a kubeconfig with a `kubectl create token` token that expires (default 24 h). Validated
|
||||||
|
with kubeconform and shellcheck, the script against a stub kubectl; not applied.
|
||||||
- 2026-10-19 — App 2.19.0: EVHardware updated from malys/EVHardware `b595ccb` (2026-09-17, the
|
- 2026-10-19 — App 2.19.0: EVHardware updated from malys/EVHardware `b595ccb` (2026-09-17, the
|
||||||
base Fatih copied) to `e4e1158` (2026-10-18, after 1.18.0), 42 commits, merged three ways so Fatih's
|
base Fatih copied) to `e4e1158` (2026-10-18, after 1.18.0), 42 commits, merged three ways so Fatih's
|
||||||
changes stay (listed in `LICENSE.md`). Upstream brings among others: trip, charge and eco
|
changes stay (listed in `LICENSE.md`). Upstream brings among others: trip, charge and eco
|
||||||
|
|||||||
@@ -54,6 +54,12 @@ the forge that can only **read** the `nils/mg4` repository, put it there, apply
|
|||||||
restart the server. Without it, updates are off (the car's updater gets a 404). `FORGE_URL`
|
restart the server. Without it, updates are off (the car's updater gets a 404). `FORGE_URL`
|
||||||
and `FORGE_REPO` are set in `server.yaml`.
|
and `FORGE_REPO` are set in `server.yaml`.
|
||||||
|
|
||||||
|
## Agent access
|
||||||
|
|
||||||
|
[`agent-access/`](agent-access/README.md) creates a service account limited to the `mg4`
|
||||||
|
namespace (read-only by default, no Secrets, no exec) and a script that writes a kubeconfig with a
|
||||||
|
time-limited token for it. Applied separately, with `kubectl apply -k k8s/agent-access/`.
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
- InfluxDB's setup mode runs only on an empty volume. Changing `admin-token` in the Secret later
|
- InfluxDB's setup mode runs only on an empty volume. Changing `admin-token` in the Secret later
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# Agent access to the mg4 namespace
|
||||||
|
|
||||||
|
A kubeconfig for a coding agent that reaches **only the `mg4` namespace**, with a token that
|
||||||
|
expires on its own. You create and apply everything here yourself; the agent never does.
|
||||||
|
|
||||||
|
| File | What |
|
||||||
|
|---|---|
|
||||||
|
| `serviceaccount.yaml` | the `mg4-agent` service account in `mg4`, never mounted into a pod |
|
||||||
|
| `role-read.yaml` | read-only: workloads, pods and their logs, services, ConfigMaps, PVCs, events, network policies, IngressRoutes |
|
||||||
|
| `role-deploy.yaml` | **optional**: apply this repository's manifests and restart workloads |
|
||||||
|
| `make-kubeconfig.sh` | writes the kubeconfig with a time-limited token |
|
||||||
|
|
||||||
|
Neither role allows **Secrets** (any verb), `pods/exec`, `pods/attach`, `pods/portforward`, RBAC
|
||||||
|
objects, the Namespace itself, or anything in another namespace or at cluster scope. The deploy
|
||||||
|
role also cannot delete PersistentVolumeClaims, so InfluxDB's data volume stays safe.
|
||||||
|
|
||||||
|
## 1. Create the service account and its role
|
||||||
|
|
||||||
|
With your own kubeconfig, after `k8s/` itself is applied (the namespace must exist):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl apply -k k8s/agent-access/
|
||||||
|
```
|
||||||
|
|
||||||
|
That is read-only. To let the agent also apply changes, uncomment `role-deploy.yaml` in
|
||||||
|
`kustomization.yaml` first. Be aware of what that means: whoever may change a Deployment may run
|
||||||
|
any image in the namespace, and such a pod could mount mg4's Secrets and print them. Read-only is
|
||||||
|
the limit that holds even against a misbehaving agent; the deploy role is a convenience.
|
||||||
|
|
||||||
|
## 2. Check what it may do
|
||||||
|
|
||||||
|
```sh
|
||||||
|
kubectl auth can-i --list -n mg4 --as=system:serviceaccount:mg4:mg4-agent
|
||||||
|
kubectl auth can-i get secrets -n mg4 --as=system:serviceaccount:mg4:mg4-agent # no
|
||||||
|
kubectl auth can-i create pods/exec -n mg4 --as=system:serviceaccount:mg4:mg4-agent # no
|
||||||
|
kubectl auth can-i list pods -n kube-system --as=system:serviceaccount:mg4:mg4-agent # no
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Make the kubeconfig
|
||||||
|
|
||||||
|
```sh
|
||||||
|
k8s/agent-access/make-kubeconfig.sh 24h
|
||||||
|
```
|
||||||
|
|
||||||
|
It takes the API server address and CA from your current context, asks the cluster for a token
|
||||||
|
for `mg4-agent` (`kubectl create token`, valid for the duration you give; the API server may cap
|
||||||
|
it), and writes `~/.config/nilsmg4/kubeconfig-mg4-agent` with mode 600. The token is never
|
||||||
|
printed. The file is outside the repository, so it cannot be committed.
|
||||||
|
|
||||||
|
Then tell the agent it may use it, e.g. *"you may use ~/.config/nilsmg4/kubeconfig-mg4-agent"*.
|
||||||
|
It runs `kubectl` with `KUBECONFIG` pointing at that file, so your own kubeconfig is never used.
|
||||||
|
|
||||||
|
## Ending access
|
||||||
|
|
||||||
|
- **Wait:** the token expires after its duration. Run the script again for a new one.
|
||||||
|
- **Now:** `kubectl delete serviceaccount mg4-agent -n mg4` invalidates every token made for
|
||||||
|
it at once. Remove the rest with `kubectl delete -k k8s/agent-access/`.
|
||||||
|
- **Less:** take `role-deploy.yaml` out of the kustomization and
|
||||||
|
`kubectl delete rolebinding,role mg4-agent-deploy -n mg4`.
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
# Agent access to the mg4 namespace, applied separately from k8s/: `kubectl apply -k k8s/agent-access/`.
|
||||||
|
# Read-only by default; add role-deploy.yaml below to let the agent apply changes too.
|
||||||
|
apiVersion: kustomize.config.k8s.io/v1beta1
|
||||||
|
kind: Kustomization
|
||||||
|
resources:
|
||||||
|
- serviceaccount.yaml
|
||||||
|
- role-read.yaml
|
||||||
|
# - role-deploy.yaml
|
||||||
Executable
+49
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Writes a kubeconfig for the mg4-agent service account, valid for a limited time.
|
||||||
|
#
|
||||||
|
# k8s/agent-access/make-kubeconfig.sh [duration] [output]
|
||||||
|
#
|
||||||
|
# Run it yourself, with your own (admin) kubeconfig active. duration defaults to 24h; the API
|
||||||
|
# server may cap it (--service-account-max-token-expiration). The token is written to the file
|
||||||
|
# only, never printed.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
duration="${1:-24h}"
|
||||||
|
out="${2:-$HOME/.config/nilsmg4/kubeconfig-mg4-agent}"
|
||||||
|
|
||||||
|
cluster="$(kubectl config view --minify -o jsonpath='{.clusters[0].name}')"
|
||||||
|
server="$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')"
|
||||||
|
ca="$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}')"
|
||||||
|
if [ -z "$ca" ]; then
|
||||||
|
ca_file="$(kubectl config view --minify --raw -o jsonpath='{.clusters[0].cluster.certificate-authority}')"
|
||||||
|
[ -n "$ca_file" ] || { echo "no cluster CA in the current kubeconfig" >&2; exit 1; }
|
||||||
|
ca="$(base64 -w0 < "$ca_file")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
token="$(kubectl create token mg4-agent --namespace mg4 --duration "$duration")"
|
||||||
|
|
||||||
|
mkdir -p "$(dirname "$out")"
|
||||||
|
umask 077
|
||||||
|
cat > "$out" <<KUBECONFIG
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Config
|
||||||
|
clusters:
|
||||||
|
- name: ${cluster}
|
||||||
|
cluster:
|
||||||
|
server: ${server}
|
||||||
|
certificate-authority-data: ${ca}
|
||||||
|
users:
|
||||||
|
- name: mg4-agent
|
||||||
|
user:
|
||||||
|
token: ${token}
|
||||||
|
contexts:
|
||||||
|
- name: mg4-agent
|
||||||
|
context:
|
||||||
|
cluster: ${cluster}
|
||||||
|
namespace: mg4
|
||||||
|
user: mg4-agent
|
||||||
|
current-context: mg4-agent
|
||||||
|
KUBECONFIG
|
||||||
|
|
||||||
|
echo "Wrote $out (mode 600), token valid for $duration."
|
||||||
|
echo "Check: KUBECONFIG=$out kubectl auth can-i --list"
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
# OPTIONAL, on top of role-read.yaml: lets the agent apply this repository's manifests and
|
||||||
|
# restart workloads in mg4. Not in kustomization.yaml; add it there only if you want that.
|
||||||
|
#
|
||||||
|
# Still absent: Secrets, pods/exec, pods/attach, pods/portforward, RBAC, the Namespace itself,
|
||||||
|
# and deleting PersistentVolumeClaims (InfluxDB's data lives on one).
|
||||||
|
#
|
||||||
|
# Note what this does allow: whoever may change a Deployment may run any image in the namespace,
|
||||||
|
# and such a pod could mount mg4's Secrets and print them to its log. Read-only is the boundary
|
||||||
|
# that holds against a misbehaving agent; this role is for convenience, given trust.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: mg4-agent-deploy
|
||||||
|
namespace: mg4
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: mg4
|
||||||
|
app.kubernetes.io/component: agent-access
|
||||||
|
rules:
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [deployments, statefulsets]
|
||||||
|
verbs: [create, update, patch, delete]
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [deployments/scale, statefulsets/scale]
|
||||||
|
verbs: [get, update, patch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [services, configmaps]
|
||||||
|
verbs: [create, update, patch, delete]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [persistentvolumeclaims]
|
||||||
|
verbs: [create, update, patch]
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [pods]
|
||||||
|
verbs: [delete]
|
||||||
|
- apiGroups: [batch]
|
||||||
|
resources: [jobs]
|
||||||
|
verbs: [create, delete]
|
||||||
|
- apiGroups: [networking.k8s.io]
|
||||||
|
resources: [networkpolicies]
|
||||||
|
verbs: [create, update, patch, delete]
|
||||||
|
- apiGroups: [traefik.io]
|
||||||
|
resources: [ingressroutes]
|
||||||
|
verbs: [create, update, patch, delete]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: mg4-agent-deploy
|
||||||
|
namespace: mg4
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: mg4
|
||||||
|
app.kubernetes.io/component: agent-access
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: mg4-agent-deploy
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: mg4-agent
|
||||||
|
namespace: mg4
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
# Read-only view of the mg4 namespace: what runs, its logs and events, its configuration.
|
||||||
|
# Deliberately absent: Secrets (any verb), pods/exec, pods/attach, pods/portforward, RBAC.
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: Role
|
||||||
|
metadata:
|
||||||
|
name: mg4-agent-read
|
||||||
|
namespace: mg4
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: mg4
|
||||||
|
app.kubernetes.io/component: agent-access
|
||||||
|
rules:
|
||||||
|
- apiGroups: [""]
|
||||||
|
resources: [pods, pods/log, services, endpoints, configmaps, persistentvolumeclaims, events, serviceaccounts]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [apps]
|
||||||
|
resources: [deployments, statefulsets, replicasets]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [batch]
|
||||||
|
resources: [jobs, cronjobs]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [networking.k8s.io]
|
||||||
|
resources: [networkpolicies]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [events.k8s.io]
|
||||||
|
resources: [events]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [traefik.io]
|
||||||
|
resources: [ingressroutes, middlewares]
|
||||||
|
verbs: [get, list, watch]
|
||||||
|
- apiGroups: [metrics.k8s.io]
|
||||||
|
resources: [pods]
|
||||||
|
verbs: [get, list]
|
||||||
|
---
|
||||||
|
apiVersion: rbac.authorization.k8s.io/v1
|
||||||
|
kind: RoleBinding
|
||||||
|
metadata:
|
||||||
|
name: mg4-agent-read
|
||||||
|
namespace: mg4
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: mg4
|
||||||
|
app.kubernetes.io/component: agent-access
|
||||||
|
roleRef:
|
||||||
|
apiGroup: rbac.authorization.k8s.io
|
||||||
|
kind: Role
|
||||||
|
name: mg4-agent-read
|
||||||
|
subjects:
|
||||||
|
- kind: ServiceAccount
|
||||||
|
name: mg4-agent
|
||||||
|
namespace: mg4
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# The identity a coding agent uses on the cluster. It exists only in the mg4 namespace and is
|
||||||
|
# never mounted into a pod; its tokens are made by hand with `kubectl create token` (README.md).
|
||||||
|
apiVersion: v1
|
||||||
|
kind: ServiceAccount
|
||||||
|
metadata:
|
||||||
|
name: mg4-agent
|
||||||
|
namespace: mg4
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/part-of: mg4
|
||||||
|
app.kubernetes.io/component: agent-access
|
||||||
|
automountServiceAccountToken: false
|
||||||
Reference in New Issue
Block a user