Skip to content

Reach the admin API without your identity provider

Every admin caller over the network presents a JWT. A person presents one by way of pistra login, a workload by way of the token its platform already projects. So the admin API is only as reachable as the issuer behind it. When that issuer is the thing that broke, from an expired signing key, a directory outage, or a misapplied policy that locked out the group you were in, the admin API is a locked door. You cannot fix the issuer through it.

The local socket is the other door. It answers to the filesystem instead of to an issuer, so it does not depend on anything that can go down somewhere else.

issuers:
- name: corp
url: https://login.example.com
admin:
listen: 0.0.0.0:8485 # the network door, needs an issuer
local_socket: /run/pistra/admin.sock
policy_file: /etc/pistra/admin.cedar
issuers:
- issuer: corp
audience: pistra-admin

That configuration turns both doors on at once. The socket is not a mode the gateway is in instead of serving the network. It is a second listener on the same API, and configuring an issuer does not take it away.

Turn it on before you need it. Turning it on is a configuration change, and a configuration change is what somebody locked out of the admin API cannot make.

Use the following example:

Terminal window
pistra admin -socket /run/pistra/admin.sock /admin/v1/config

Or set PISTRA_ADMIN_SOCKET and drop the flag. On Kubernetes the chart turns the socket on by default and the door is kubectl exec:

Terminal window
kubectl exec pistra-0 -n pistra-system -- pistra admin /admin/v1/suspensions
kubectl exec pistra-0 -n pistra-system -- pistra admin \
PUT /admin/v1/suspensions/provider:openai \
-d '{"reason":"INC-4471 leaking prompt text","duration":"4h"}'

Use pistra admin rather than curl, because the image is distroless and has neither. It is a transport: a method, a path, a body, and whatever the API answered. Every endpoint in the OpenAPI document is reachable through it, and it exits non-zero on 400 and above.

That kubectl exec is worth noticing rather than working around. Reaching this door means holding a Kubernetes credential. Kubernetes RBAC has already gated that credential and its audit log has already recorded it. It is an identity system that is still up when yours is the thing that broke. Delegating to it avoids inventing a second credential of our own to lose.

The caller is whoever opened the socket, named by the peer credentials the kernel recorded:

actor.kind=local actor.name=unix:501 actor.subject=31904

unix:501 is a uid the operating system enforces, and the subject is the pid that asked, so the record names more than “somebody local”. That is the difference between this and a loopback TCP listener, where every caller is the same anonymous principal and the trail cannot tell two of them apart.

The socket is created 0600 and owned by the gateway’s own user, so “who may administer this gateway locally” is a question the filesystem answers. Put it in a directory only that user can enter, because the mode on the socket is the access control. The chart uses an emptyDir at /run/pistra.

Policy decides, the same as for anyone else. Local callers hold Role::"local":

@id("local-break-glass")
permit(principal in Role::"local", action, resource);

That is the grant to narrow first, because it is the one with no directory behind it. A useful shape gives on-call what an incident needs and nothing more:

@id("local-can-look")
permit(principal in Role::"local", action in Action::"read", resource);
@id("local-can-take-a-provider-out")
permit(principal in Role::"local", action == Action::"suspend", resource)
when { resource.id like "provider:*" };

Now exec’ing into the pod lets you see the deployment and take a provider out of service, and does not let you mint yourself a key. Pair it with Suspend a key, provider or rule. The suspension expires on its own, so the incident does not leave a degraded state behind it.

You can also name one uid, though it is usually the wrong instinct in a container where everything runs as the same user:

@id("just-me")
permit(principal, action, resource) when { principal.name == "unix:501" };

A gateway with no issuer and no policy_file lets local callers through unconditionally. That is the state a fresh deployment is in, and it is how the first key gets minted and the first policy installed. There is nothing else to authenticate with yet, and no policy could be written about an identity provider that has not been configured.

Once an issuer is configured a policy is required, and it then decides for local callers too. Without the grant above the socket is open and permits nothing, so the grant is not optional in production.

This door does not cover a cluster that has lost quorum. No admin write succeeds without raft, suspensions included, so getting in through this door leaves you holding an API that can only read. That has its own way out, offline and on a stopped node. See Recover a cluster that has lost quorum.

The gateway announces the socket at WARN on every start:

level=WARN msg="local admin socket listening: whoever can open this file
administers this gateway" path=/run/pistra/admin.sock policy=true

policy=false means no policy is loaded and a local caller can do anything on the admin plane. That is correct for a deployment that has not been set up yet and is a finding anywhere else.

This door is a fallback for Connect your identity provider. Authorization says why both exist.