Skip to content
🛡️ adminadmin@example.com · Domain admin in Acme Corp

Chapter 11 — Access without keys

A bucket has nothing to log in to

So this chapter provisions something you can actually reach — a Node — and then reaches it without ever holding a credential for it.

bash
CATALOG_ID=$(plexctl blueprint catalog list --output json \
  | jq -r '.items[] | select(.name == "Official Blueprint catalog") | .id')
plexctl blueprint import "$CATALOG_ID" kubernetes-cloudless-node

Same catalog as chapter 8, second Blueprint imported from it.

Capture the node version

bash
NODE_BLUEPRINT_ID=$(plexctl blueprint list --all --output json \
  | jq -r '.items[] | select(.slug == "kubernetes-cloudless-node") | .id')
NODE_VERSION_ID=$(plexctl blueprint get "$NODE_BLUEPRINT_ID" --output json \
  | jq -r '.versions[] | select(.version == "v1alpha1") | .id')
echo "$NODE_VERSION_ID"

A new Project, and the same governance again

bash
PROJECT_ID=$(plexctl project create \
  --domain "$DOMAIN_ID" \
  --slug reach-demo \
  --display-name "Reach demo" \
  --output json | jq -r '.id')
echo "project=$PROJECT_ID"
bash
plexctl credential assignment request \
  --project-id "$PROJECT_ID" \
  --cloud-credential-id "$CREDENTIAL_ID"

A second Project does not inherit the first one's credential. Every Project asks, and every request is decided.

🏗️ operatoroperator@example.com · platform grant, no Domain

Approve it, briskly

bash
APPROVAL_ID=$(plexctl approval list --profile operator --status pending-approval --output json \
  | jq -r --arg cred "$CREDENTIAL_ID" \
      'first(.items[] | select(.kind == "credential_assignment" and .target_resource == "cloudcredential:" + $cred) | .id)')

plexctl approval approve "$APPROVAL_ID" \
  --profile operator --output json | jq '{state, materialised}'

Exactly chapter 9's flow against a new Project. Do not re-narrate it — the point is that it is not optional the second time.

🛡️ adminadmin@example.com · Domain admin in Acme Corp

Provision the Node

bash
RESOURCE_ID=$(plexctl resource create \
  --project-id "$PROJECT_ID" \
  --kind node \
  --blueprint-version-id "$NODE_VERSION_ID" \
  --cloud-credential-id "$CREDENTIAL_ID" \
  --parameters '{}' \
  --output json | jq -r '.id')
echo "$RESOURCE_ID"

--kind node, and an empty parameter object.

The phase the bucket skipped

bash
while true; do
  PHASE=$(plexctl resource get "$RESOURCE_ID" --output json | jq -r '.provisioning.phase')
  echo "$(date +%T) $PHASE"
  case "$PHASE" in Ready|Failed) break ;; esac
  sleep 10
done
text
15:02:02 Pending
15:02:12 Provisioning
15:02:42 Enrolling
15:04:12 Ready

Enrolling is new. The broker is waiting for the node to redeem a bootstrap token and register as a live mesh peer.

Ready here means the node came back and joined — not that a VM booted. Chapter 13 drives that same registration by hand.

Access is a grant, not a key

You can already reach this Node — a Domain admin's authority walks down Domain to Project to Resource.

A teammate who is not a Domain admin needs it written out. And you do not write act: that is a permission the schema computes. You write the smallest relation that adds up to it.

bash
MEMBER_ID=$(plexctl identity list --domain "$DOMAIN_ID" --output json \
  | jq -r 'first(.items[] | select(.display_name == "member@example.com") | .id)')

plexctl rebac tuple add \
  --project "$PROJECT_ID" \
  --resource "resource:$RESOURCE_ID" \
  --relation operator \
  --subject "user:$MEMBER_ID"

Ask for a kubeconfig

bash
plexctl kubeconfig \
  --project-id "$PROJECT_ID" \
  --resource-id "$RESOURCE_ID" \
  --impersonate-user ops \
  --output-file ./resource.kubeconfig \
  --listener-timeout 0
text
plexctl: kubeconfig: session issued but the target node has not reported a listener endpoint

Read that message carefully — it is the honest one.

The session was issued, signed, and audited. Then the wait ended empty-handed: the dev-stack simulator Node reports no listener, so there is nothing to point kubectl at.

What it writes when a Node does answer

yaml
apiVersion: v1
kind: Config
clusters:
- name: plexsphere
  cluster:
    server: https://<session-listener-endpoint>
users:
- name: plexsphere
  user:
    token: <one-time-session-jwt>

No cluster credential anywhere in that file.

A one-time token, minted on this issuance, pointing at a listener the Node opened for this session.

The same shape for anything else

bash
plexctl tcp-forward \
  --project-id "$PROJECT_ID" \
  --resource-id "$RESOURCE_ID" \
  --host localhost \
  --port 5432 \
  --listener-timeout 0
bash
plexctl ssh \
  --project-id "$PROJECT_ID" \
  --resource-id "$RESOURCE_ID" \
  --login-user ops \
  --listener-timeout 0

A generic mediated tunnel, and an SSH login that hands control to your local ssh client with the one-time token passed through the environment — never on the command line.

Same issuance path, same audit trail, three shapes.

Why this is the interesting slide

Every session carries a TTL — 30 minutes by default, a 4-hour ceiling, a 15-minute idle timeout. A sweeper reclaims them the moment they expire.

Every issuance is written to the per-Domain audit log under access.issue. Who reached what, when is a query, not a guess.

And a Domain can demand step-up authentication for a session kind, in which case issuance is refused until you re-authenticate.

Nobody is holding a key. There is no key to hold.