@dooer/dooer-test-env 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,8 @@ fetch the BankID cert. That needs two things, in this order:
6
6
  1. **VPN** — the cluster API is not reachable from the open internet.
7
7
  2. **kubectl access** — a client certificate identifying you to the cluster.
8
8
 
9
- The VPN part is self-service. The kubectl part needs an admin to sign your certificate see
9
+ **Both come from an admin**: they create your VPN tunnel and your cluster credential, and send you two
10
+ files — a WireGuard `.conf` and a `kubeconfig`. You install them and check they work. See
10
11
  [issuing-access.md](./issuing-access.md) for their side.
11
12
 
12
13
  ---
@@ -14,98 +15,50 @@ The VPN part is self-service. The kubectl part needs an admin to sign your certi
14
15
  ## 0. Prerequisites
15
16
 
16
17
  ```bash
17
- brew install wireguard-tools go jq kubernetes-cli
18
+ brew install wireguard-tools kubernetes-cli
18
19
  ```
19
20
 
20
21
  `openssl` ships with macOS. Check everything is there:
21
22
 
22
23
  ```bash
23
- wg --version && go version && jq --version && kubectl version --client && openssl version
24
+ wg --version && kubectl version --client && openssl version
24
25
  ```
25
26
 
26
27
  ---
27
28
 
28
29
  ## 1. VPN
29
30
 
30
- ### 1a. Get a Robo10 token
31
+ **Your admin creates the tunnel and sends you a `.conf` file.** You do not create it yourself and you do
32
+ not need a Robo10 token for this — ask an admin, who follows
33
+ [issuing-access.md § 1](./issuing-access.md#1-vpn--create-and-hand-over-a-tunnel).
31
34
 
32
- The VPN API is authenticated with a Robo10 access token. This opens a browser **log in with your
33
- `@robo10.com` email**.
35
+ The file they send you contains a **private key**. Treat it like a password: keep it out of chat, tickets
36
+ and git, install it, and delete the copy they sent you.
34
37
 
35
- ```bash
36
- TOKEN="$(go run github.com/strehle/cmdline-openid-client/openid-client@latest \
37
- -issuer https://oidc-provider-backend.api.staging.roboten-dev.com \
38
- -client_id ro_authentication_cl_id \
39
- -port 34466 | awk '/===/ { if (start) nextfile; start = 1; next } start { print }' | jq -r .access_token)"
40
-
41
- [ -n "$TOKEN" ] && echo "token acquired" || echo "no token — did the browser login complete?"
42
- ```
43
-
44
- Keep using **this same terminal** for the next steps: `$TOKEN` only exists in this shell.
45
-
46
- <details>
47
- <summary>Alternative if the Go client will not run</summary>
38
+ ### 1a. Install the config
48
39
 
49
- 1. Open <https://chat.roboten.com> and log in.
50
- 2. DevTools **Application** **Storage Cookies**.
51
- 3. Copy the value of the `x-op-token` cookie.
52
- 4. In your terminal: `TOKEN=<the-cookie-value>`
53
- </details>
54
-
55
- ### 1b. Create your tunnel
56
-
57
- One tunnel per person per machine. Name it after yourself so admins can tell them apart:
40
+ `wg-quick` takes the interface name from the **filename**, so the name you pick here is the name you use in
41
+ every later command. Check first what is already there — **do not overwrite an existing tunnel**:
58
42
 
59
43
  ```bash
60
- NAME="$(whoami)-$(hostname -s)"
61
- curl -sS -H "Authorization: Bearer $TOKEN" \
62
- -H 'Content-Type: application/json' \
63
- -d "{ \"name\": \"$NAME\" }" \
64
- https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels
65
- ```
66
-
67
- The response contains the tunnel's **id** — you need it next. To find it again later:
68
-
69
- ```bash
70
- curl -sS -H "Authorization: Bearer $TOKEN" https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels
71
- ```
72
-
73
- ### 1c. Download the tunnel config
74
-
75
- ```bash
76
- ID=<the id from the previous step>
77
- curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
78
- https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels/$ID/actions/get-config \
79
- > ~/roboten.conf
44
+ ls /opt/homebrew/etc/wireguard/
80
45
  ```
81
46
 
82
- > This file contains your **private key**. Treat it like a password: never commit it, never paste it into
83
- > chat or a ticket.
84
-
85
- ### 1d. Install it
86
-
87
- `wg-quick` takes the interface name from the **filename**, so the name you choose here is the name you use
88
- in every later command. Pick something that will not collide with a tunnel you already have:
47
+ Then install it (using `roboten` as the interface name):
89
48
 
90
49
  ```bash
91
50
  sudo mkdir -p /opt/homebrew/etc/wireguard
92
- sudo cp ~/roboten.conf /opt/homebrew/etc/wireguard/roboten.conf
51
+ sudo cp ~/Downloads/<the-file-they-sent>.conf /opt/homebrew/etc/wireguard/roboten.conf
93
52
  sudo chmod 600 /opt/homebrew/etc/wireguard/roboten.conf
94
- rm ~/roboten.conf # the copy under /opt/homebrew is the one wg-quick reads
53
+ rm ~/Downloads/<the-file-they-sent>.conf # the copy under /opt/homebrew is the one wg-quick reads
95
54
  ```
96
55
 
97
- Check first whether that name is already taken — **do not overwrite someone else's tunnel**:
98
-
99
- ```bash
100
- ls /opt/homebrew/etc/wireguard/
101
- ```
102
-
103
- ### 1e. Connect, disconnect, restart
56
+ ### 1b. Connect, disconnect, restart
104
57
 
105
58
  ```bash
106
59
  sudo wg-quick up roboten # connect
107
60
  sudo wg-quick down roboten # disconnect
108
- sudo wg-quick down roboten && sudo wg-quick up roboten # restart (after a config change or a sleep/wake)
61
+ sudo wg-quick down roboten && sudo wg-quick up roboten # restart (after a config change or sleep/wake)
109
62
 
110
63
  sudo wg show # status: handshake time + transfer counters
111
64
  ```
@@ -122,75 +75,59 @@ sudo brew services start wireguard-tools # starts wg0 by default
122
75
 
123
76
  That service is hard-wired to the `wg0` interface, so it only helps if you named your file `wg0.conf`.
124
77
 
125
- ### 1f. Verify the VPN works
78
+ ### 1c. Verify the VPN works
126
79
 
127
80
  ```bash
128
81
  sudo wg show # expect a recent "latest handshake"
129
82
  nc -vz k8s.roboten-infra.com 64430 # expect: succeeded / open
130
83
  ```
131
84
 
132
- If `nc` hangs or is refused, the tunnel is not carrying traffic — restart it (1e). Until this passes, no
85
+ If `nc` hangs or is refused, the tunnel is not carrying traffic — restart it (1b). Until this passes, no
133
86
  amount of kubectl configuration will help.
134
87
 
88
+ > The tunnel routes the cluster network (including the API server) through WireGuard. If you already have
89
+ > another VPN claiming those ranges, they will fight over the routes — bring only one up at a time.
90
+
135
91
  ---
136
92
 
137
93
  ## 2. kubectl access
138
94
 
139
- Authentication is a **client certificate**. You generate the key and the signing request; only the request
140
- leaves your machine. Your private key never does.
95
+ **Your admin sends you a ready-made `kubeconfig` file.** You do not generate keys or assemble anything
96
+ they create the credential and hand it over
97
+ ([issuing-access.md § 2](./issuing-access.md#2-kubectl--create-their-credential)).
141
98
 
142
- ### 2a. Generate your key and CSR
99
+ Like the VPN config, that file contains a **private key**. Same handling: install it, then delete the copy
100
+ they sent you.
143
101
 
144
- Use `<firstname>-admin` as the name — that string becomes your username in the cluster, and it is what the
145
- admin binds permissions to.
102
+ ### 2a. Install it
146
103
 
147
- ```bash
148
- USER_NAME=<firstname>-admin
149
- mkdir -p ~/.kube/roboten && cd ~/.kube/roboten
104
+ **If you have no other clusters** — simplest, just put it in place:
150
105
 
151
- openssl genrsa -out "$USER_NAME.key" 2048
152
- openssl req -new -key "$USER_NAME.key" -out "$USER_NAME.csr" -subj "/CN=$USER_NAME"
153
- chmod 600 "$USER_NAME.key"
106
+ ```bash
107
+ mkdir -p ~/.kube
108
+ cp ~/Downloads/<yourname>.kubeconfig ~/.kube/config
109
+ chmod 600 ~/.kube/config
110
+ rm ~/Downloads/<yourname>.kubeconfig
154
111
  ```
155
112
 
156
- ### 2b. Send the CSR to an admin
157
-
158
- Send **only** `<firstname>-admin.csr`. It is not secret — but the `.key` next to it is, and must never
159
- leave your machine.
160
-
161
- Ask them for three things back:
162
-
163
- | | |
164
- | --- | --- |
165
- | `<firstname>-admin.crt` | your signed certificate |
166
- | `ca.crt` | the cluster CA certificate |
167
- | the API server URL | currently `https://k8s.roboten-infra.com:64430` |
168
-
169
- ### 2c. Assemble your kubeconfig
170
-
171
- Put the two `.crt` files next to your key in `~/.kube/roboten`, then:
113
+ **If you already use kubectl for something else**, merge instead of overwriting:
172
114
 
173
115
  ```bash
174
- cd ~/.kube/roboten
175
- USER_NAME=<firstname>-admin
176
- SERVER=https://k8s.roboten-infra.com:64430
177
-
178
- KUBECONFIG=~/.kube/config kubectl config set-cluster kubernetes \
179
- --server="$SERVER" --certificate-authority=ca.crt --embed-certs=true
180
-
181
- KUBECONFIG=~/.kube/config kubectl config set-credentials "$USER_NAME" \
182
- --client-certificate="$USER_NAME.crt" --client-key="$USER_NAME.key" --embed-certs=true
116
+ cp ~/.kube/config ~/.kube/config.backup # always back up first
117
+ KUBECONFIG=~/.kube/config:~/Downloads/<yourname>.kubeconfig \
118
+ kubectl config view --flatten > /tmp/merged && mv /tmp/merged ~/.kube/config
119
+ chmod 600 ~/.kube/config
120
+ rm ~/Downloads/<yourname>.kubeconfig
121
+ ```
183
122
 
184
- KUBECONFIG=~/.kube/config kubectl config set-context "$USER_NAME@kubernetes" \
185
- --cluster=kubernetes --user="$USER_NAME"
123
+ Then select the context (the admin will tell you its name, `<firstname>-admin@kubernetes`):
186
124
 
187
- KUBECONFIG=~/.kube/config kubectl config use-context "$USER_NAME@kubernetes"
125
+ ```bash
126
+ kubectl config get-contexts
127
+ kubectl config use-context <firstname>-admin@kubernetes
188
128
  ```
189
129
 
190
- `--embed-certs=true` copies the certificates *into* `~/.kube/config`, so the files in `~/.kube/roboten` are
191
- only a backup after this point. Keep the `.key` anyway — you need it if you ever rebuild the config.
192
-
193
- ### 2d. Verify
130
+ ### 2b. Verify
194
131
 
195
132
  With the VPN up:
196
133
 
@@ -228,12 +165,11 @@ npx @dooer/dooer-test-env@latest setup
228
165
 
229
166
  | Symptom | Cause |
230
167
  | --- | --- |
231
- | `dial tcp … i/o timeout` from kubectl | VPN is down — `sudo wg show`, then restart it (1e) |
168
+ | `dial tcp … i/o timeout` from kubectl | VPN is down — `sudo wg show`, then restart it (1b) |
232
169
  | `nc` to the API server hangs | Tunnel is up but not carrying traffic; restart it |
233
- | `error: You must be logged in to the server (Unauthorized)` | Certificate is wrong/expired, or the cluster does not know that CN — back to 2b |
234
- | `Error from server (Forbidden)` | You *are* authenticated; your user has no permissions bound yet — ask the admin to complete their step 3 |
235
- | `no token — did the browser login complete?` | The OIDC flow did not finish; make sure you used your `@robo10.com` account |
170
+ | `error: You must be logged in to the server (Unauthorized)` | The certificate in your kubeconfig is expired or unknown to the cluster ask for a re-issue |
171
+ | `Error from server (Forbidden)` | You *are* authenticated; your user has no permissions bound yet — ask the admin to complete their § 2e (bind permissions) |
236
172
  | `wg-quick: 'roboten' already exists` | The interface is already up — `sudo wg-quick down roboten` first |
237
173
 
238
- Certificates are issued for **one year**. When yours expires you repeat section 2 the admin only has to
239
- re-sign; your permissions (step 3 on their side) stay in place.
174
+ Certificates are issued for **one year**. When yours expires, ask your admin for a new kubeconfig and
175
+ repeat section 2 — your permissions stay in place, so they only have to re-issue the credential.
@@ -4,66 +4,133 @@ The other side of [getting-access.md](./getting-access.md). Two independent thin
4
4
 
5
5
  | | Who does it | What you do |
6
6
  | --- | --- | --- |
7
- | **VPN tunnel** | self-service | make sure they have a Robo10 login; nothing to hand out |
8
- | **kubectl credential** | needs you | sign their CSR and bind their permissions |
7
+ | **VPN tunnel** | you | create it and send them the config |
8
+ | **kubectl credential** | you | create the certificate, bind permissions, send a kubeconfig |
9
9
 
10
- You need `kubectl` with cluster-admin (`system:masters`) and the VPN up.
10
+ You need a Robo10 token (§1a), `kubectl` with cluster-admin (`system:masters`), and your own VPN up.
11
11
 
12
12
  ---
13
13
 
14
- ## 1. VPN — what you actually have to do
14
+ ## 1. VPN — create and hand over a tunnel
15
15
 
16
- Tunnels are **self-service**: any valid Robo10 token can create one, and the config is generated on demand
17
- by the VPN API. So there is no artefact for you to produce or send.
16
+ You create the tunnel and send the developer its config. They do not need a Robo10 token, which matters:
17
+ a brand-new joiner often has no working login yet, and the VPN API generates the keypair **server-side**
18
+ anyway — the config comes back with the private key in it either way, so there is no custody advantage to
19
+ making them do it.
18
20
 
19
- Your only job is to make sure the person can log in at <https://chat.roboten.com> with their
20
- **`@robo10.com`** account — that same identity is what the token flow authenticates. If they cannot log in,
21
- that is an account-provisioning issue in Robo10, not something this guide covers.
21
+ ### 1a. Get a Robo10 token (yours)
22
+
23
+ Opens a browser log in with **your `@robo10.com` account**:
24
+
25
+ ```bash
26
+ TOKEN="$(go run github.com/strehle/cmdline-openid-client/openid-client@latest \
27
+ -issuer https://oidc-provider-backend.api.staging.roboten-dev.com \
28
+ -client_id ro_authentication_cl_id \
29
+ -port 34466 | awk '/===/ { if (start) nextfile; start = 1; next } start { print }' | jq -r .access_token)"
30
+
31
+ [ -n "$TOKEN" ] && echo "token acquired" || echo "no token — did the browser login complete?"
32
+ ```
33
+
34
+ Needs `go` and `jq` (`brew install go jq`). Keep using **this same terminal**: `$TOKEN` lives only in this
35
+ shell, and everything below needs it.
36
+
37
+ <details>
38
+ <summary>Alternative if the Go client will not run</summary>
39
+
40
+ 1. Open <https://chat.roboten.com> and log in.
41
+ 2. DevTools → **Application** → **Storage → Cookies**.
42
+ 3. Copy the value of the `x-op-token` cookie.
43
+ 4. In your terminal: `TOKEN=<the-cookie-value>`
44
+ </details>
45
+
46
+ ### 1b. Create their tunnel
47
+
48
+ Name it after the person (and their machine if they need more than one) so the list stays readable:
49
+
50
+ ```bash
51
+ NAME="Firstname Lastname"
52
+ curl -sS -H "Authorization: Bearer $TOKEN" \
53
+ -H 'Content-Type: application/json' \
54
+ -d "{ \"name\": \"$NAME\" }" \
55
+ https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels
56
+ ```
57
+
58
+ The response contains the tunnel's **id**. To find it again later, list them (1d).
59
+
60
+ ### 1c. Download the config and hand it over
61
+
62
+ ```bash
63
+ ID=<the id from the previous step>
64
+ curl -sS -X POST -H "Authorization: Bearer $TOKEN" \
65
+ https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels/$ID/actions/get-config \
66
+ > ~/Downloads/$NAME.conf
67
+ ```
68
+
69
+ > **This file contains a private key.** Send it over a channel you would send a password over — a shared
70
+ > vault entry or an expiring secure link — not email, Slack or a ticket. Tell them to install it and delete
71
+ > the copy you sent, and delete your own copy once they confirm.
22
72
 
23
73
  Then point them at [getting-access.md § 1](./getting-access.md#1-vpn).
24
74
 
25
- **Never** create the tunnel on their behalf and send them the config: the config embeds a private key, so a
26
- tunnel they generated themselves is the only one only they hold.
75
+ One tunnel per person per machine: two devices sharing one config will fight over the session, so create a
76
+ second tunnel rather than re-sending the first.
27
77
 
28
- To see who has tunnels (with your own token, as in the dev guide):
78
+ ### 1d. List tunnels
29
79
 
30
80
  ```bash
31
81
  curl -sS -H "Authorization: Bearer $TOKEN" https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels
32
82
  ```
33
83
 
34
- To revoke one `DELETE` is not in the VPN service's own docs, but it works (verified 2026-09-04, returns
35
- `204` and the tunnel disappears from the list):
84
+ ### 1e. Revoke a tunnel
85
+
86
+ `DELETE` is not in the VPN service's own docs, but it works (verified 2026-09-04 — returns `204` and the
87
+ tunnel disappears from the list):
36
88
 
37
89
  ```bash
38
90
  curl -sS -X DELETE -H "Authorization: Bearer $TOKEN" \
39
91
  https://vpn.api.staging.roboten-dev.com/v1/vpn-tunnels/$ID
40
92
  ```
41
93
 
42
- That invalidates the tunnel's key server-side, so it is the real off-switch for VPN access when someone
43
- leaves — do it *and* revoke their kubectl permissions (§3).
94
+ That invalidates the tunnel server-side, so it is the real off-switch for VPN access when someone leaves —
95
+ do it *and* revoke their kubectl permissions (§3).
44
96
 
45
97
  ---
46
98
 
47
- ## 2. kubectl — sign their certificate
99
+ ## 2. kubectl — create their credential
48
100
 
49
101
  The cluster authenticates users with **x509 client certificates** signed by the cluster CA. The username is
50
102
  the certificate's **Common Name (CN)**; RBAC is bound to that string.
51
103
 
52
- You never see their private key. They send you a `.csr`; you send back a `.crt`.
104
+ You do all of it and hand over a finished `kubeconfig`.
53
105
 
54
- ### 2a. Submit their CSR to the cluster
106
+ > Everything below was verified end to end against the live cluster (2026-09-04) with a throwaway
107
+ > `guidetest-admin`: the resulting kubeconfig read `dooer-production` and `dooer-staging`, including
108
+ > secrets. The test user was then removed.
55
109
 
56
110
  ```bash
57
- USER_NAME=<firstname>-admin # must match the CN in their CSR
58
- CSR_FILE=~/Downloads/$USER_NAME.csr # what they sent you
111
+ USER_NAME=<firstname>-admin # becomes their cluster username
112
+ WORKDIR=$(mktemp -d) && cd "$WORKDIR"
113
+ ```
59
114
 
115
+ Work in a temp directory — it holds their private key until you hand it over, and you delete it at the end.
116
+
117
+ ### 2a. Generate their key and CSR
118
+
119
+ ```bash
120
+ openssl genrsa -out "$USER_NAME.key" 2048
121
+ openssl req -new -key "$USER_NAME.key" -out "$USER_NAME.csr" -subj "/CN=$USER_NAME"
122
+ ```
123
+
124
+ ### 2b. Submit it as a CertificateSigningRequest
125
+
126
+ ```bash
60
127
  cat <<EOF | kubectl apply -f -
61
128
  apiVersion: certificates.k8s.io/v1
62
129
  kind: CertificateSigningRequest
63
130
  metadata:
64
131
  name: $USER_NAME
65
132
  spec:
66
- request: $(base64 -i "$CSR_FILE" | tr -d '\n')
133
+ request: $(base64 -i "$USER_NAME.csr" | tr -d '\n')
67
134
  signerName: kubernetes.io/kube-apiserver-client
68
135
  expirationSeconds: 31536000 # 1 year
69
136
  usages:
@@ -71,15 +138,10 @@ spec:
71
138
  EOF
72
139
  ```
73
140
 
74
- Sanity-check the CN is what you expect before approving — the CN is the identity you are about to grant
75
- admin to:
76
-
77
- ```bash
78
- openssl req -in "$CSR_FILE" -noout -subject
79
- # subject=CN=<firstname>-admin
80
- ```
141
+ If the name already exists (a renewal), delete the old object first:
142
+ `kubectl delete csr "$USER_NAME" --ignore-not-found`.
81
143
 
82
- ### 2b. Approve and extract the certificate
144
+ ### 2c. Approve and extract the certificate
83
145
 
84
146
  ```bash
85
147
  kubectl certificate approve "$USER_NAME"
@@ -90,18 +152,16 @@ openssl x509 -in "$USER_NAME.crt" -noout -subject -issuer -dates
90
152
 
91
153
  Expect `subject=CN=<firstname>-admin`, `issuer=CN=kubernetes`, and a one-year window.
92
154
 
93
- If `.status.certificate` is empty, the signer has not run yet — wait a couple of seconds and repeat. If it
94
- stays empty, the CSR was approved but not signed; check `kubectl describe csr "$USER_NAME"`.
95
-
96
- ### 2c. Extract the cluster CA
155
+ If `.status.certificate` is empty the signer has not run yet — wait a couple of seconds and repeat. If it
156
+ stays empty, check `kubectl describe csr "$USER_NAME"`.
97
157
 
98
- The same file for everyone:
158
+ ### 2d. Extract the cluster CA
99
159
 
100
160
  ```bash
101
161
  kubectl config view --raw -o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.crt
102
162
  ```
103
163
 
104
- ### 2d. Bind permissions
164
+ ### 2e. Bind permissions
105
165
 
106
166
  This is what makes the certificate useful — without it they authenticate but can do nothing. These are the
107
167
  same five ClusterRoles the existing admins (`jimmy-admin`, `sam-admin`, `trajko-admin`) hold:
@@ -125,25 +185,54 @@ The declarative equivalent lives in
125
185
  is enough for the cluster, but that file is the record of who has access — **add the new user there and
126
186
  commit it**, or the next person reading it will get a false picture.
127
187
 
128
- ### 2e. Hand back
188
+ On a renewal, skip this step: the bindings are attached to the username, not to the certificate.
189
+
190
+ ### 2f. Assemble the kubeconfig
129
191
 
130
- Send them three things (none are secret the secret half never left their machine):
192
+ `--embed-certs=true` inlines the key and certificates, so the file you hand over is self-contained:
131
193
 
132
- 1. `<firstname>-admin.crt`
133
- 2. `ca.crt`
134
- 3. the API server URL — currently `https://k8s.roboten-infra.com:64430`
194
+ ```bash
195
+ SERVER=https://k8s.roboten-infra.com:64430
196
+ KC="$WORKDIR/$USER_NAME.kubeconfig"
197
+
198
+ KUBECONFIG="$KC" kubectl config set-cluster kubernetes \
199
+ --server="$SERVER" --certificate-authority=ca.crt --embed-certs=true
135
200
 
136
- Then they follow [getting-access.md § 2c](./getting-access.md#2c-assemble-your-kubeconfig).
201
+ KUBECONFIG="$KC" kubectl config set-credentials "$USER_NAME" \
202
+ --client-certificate="$USER_NAME.crt" --client-key="$USER_NAME.key" --embed-certs=true
137
203
 
138
- ### 2f. Verify before you tell them it is done
204
+ KUBECONFIG="$KC" kubectl config set-context "$USER_NAME@kubernetes" \
205
+ --cluster=kubernetes --user="$USER_NAME"
206
+
207
+ KUBECONFIG="$KC" kubectl config use-context "$USER_NAME@kubernetes"
208
+ ```
209
+
210
+ ### 2g. Verify it before handing it over
211
+
212
+ Test the actual file, not just the RBAC — with your own VPN up:
139
213
 
140
214
  ```bash
141
- kubectl get clusterrolebinding | grep "$USER_NAME" # expect 5 rows
142
- kubectl auth can-i get pods -n dooer-production --as "$USER_NAME"
143
- kubectl auth can-i get secrets -n dooer-staging --as "$USER_NAME"
215
+ KUBECONFIG="$KC" kubectl auth whoami # Username: <firstname>-admin
216
+ KUBECONFIG="$KC" kubectl get pods -n dooer-staging | head -3
217
+ KUBECONFIG="$KC" kubectl get pods -n dooer-production | head -3
218
+ KUBECONFIG="$KC" kubectl auth can-i get secrets -n dooer-staging # yes
144
219
  ```
145
220
 
146
- `--as` impersonates the user, so you are testing the real RBAC without needing their certificate.
221
+ If `whoami` works but the reads say `Forbidden`, step 2e did not take.
222
+
223
+ ### 2h. Hand it over, then clean up
224
+
225
+ > **The kubeconfig contains their private key.** Send it the same way as the VPN config — a shared vault
226
+ > entry or an expiring secure link, never email, Slack or a ticket.
227
+
228
+ Tell them the context name (`<firstname>-admin@kubernetes`) and point them at
229
+ [getting-access.md § 2](./getting-access.md#2-kubectl-access).
230
+
231
+ Once they confirm it works, destroy your copy — you are holding their key until you do:
232
+
233
+ ```bash
234
+ rm -rf "$WORKDIR"
235
+ ```
147
236
 
148
237
  ---
149
238
 
@@ -169,9 +258,11 @@ Also remove them from `clusterrole-admin.yaml` and commit.
169
258
 
170
259
  ## Renewals
171
260
 
172
- A certificate expiring is routine: the user repeats [getting-access.md § 2a–2c](./getting-access.md#2a-generate-your-key-and-csr)
173
- and you repeat **2a–2c and 2e** here. Skip 2d — their ClusterRoleBindings are bound to the username, not to
174
- the certificate, so they survive a re-issue untouched.
261
+ A certificate expiring is routine. Repeat **§2a–2d and 2f–2h** and send them a fresh kubeconfig; they
262
+ re-run [getting-access.md § 2](./getting-access.md#2-kubectl-access).
263
+
264
+ Skip **2e** — ClusterRoleBindings are bound to the username, not to the certificate, so they survive a
265
+ re-issue untouched.
175
266
 
176
267
  Delete the old CSR object first, or the new one collides on the name:
177
268
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dooer/dooer-test-env",
3
- "version": "1.12.0",
3
+ "version": "1.13.0",
4
4
  "description": "Run the whole Dooer backend locally (staging DB minus customers), copy/purge customers between environments, and shred — one CLI.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": "Dooer/cli-dooer-test-env",