Skip to content

Pinboard identifies callers with Passport, configured in PINBOARD_PASSPORT. Passport answers who the caller is; Pinboard decides what they may do.

SurfaceCredentialRequired
/registry/*, /metricsPassport credential with pinboard:worker or pinboard:operatorAlways
/{namespace}/{id}/...Passport credential, or an attested instancePer namespace rule; open when no rules are configured
/healthz, /readyzNoneNever
Pinboard → workerPinboard-Attestation, or Pinboard-Proxy-SecretAlways

Sending a credential

By default every provider reads a bearer token:

curl https://pinboard.example.com/registry/workers \
  -H "Authorization: Bearer $PINBOARD_OPERATOR_TOKEN"

A provider can read a cookie or a named header instead (credential: {cookie: "name"} or {header: "name"}).

Resolution order:

  1. Providers run in config order, staticToken providers first.
  2. A provider with no credential in its location is skipped.
  3. A jwt provider skips a token whose iss is not its issuer.
  4. A staticToken provider skips a token that matches none of its keys.
  5. The first provider that accepts the credential names the principal. One that rejects it fails the request.
  6. No provider accepting means no credential: anonymous.
ProviderPrincipal subScopesClaims
staticTokenthe key's namethe key's scopesthe key's claims
jwttoken subtoken scope (space-separated), else the provider's scopestoken claims
introspectresponse subresponse scoperesponse claims

Registry roles

ScopeRoutes
pinboard:workerregister, heartbeat, release, tag, deregister, and any unknown /registry/* path
pinboard:operatorhealth, workers, overview, stats, wind_down, drain, undrain, /metrics

A credential needs the exact scope of the route; pinboard:operator does not imply pinboard:worker.

The pinboard claim

Registry access is scoped by the principal's claims.pinboard object:

FieldTypeRequiredMeaning
namespacesstring[]Yes, both rolesNamespaces (/threads) the principal may touch. ["*"] grants all.
allowedBackendHostsstring[]pinboard:workerHosts a worker may register as its advertiseUrl.
allowedPortsinteger[]NoPorts (1–65535) allowed in advertiseUrl. Absent: any port.
hostnamesstring[]pinboard:worker in hostname-aware modeHostnames a worker may register.
envsstring[]pinboard:worker in hostname-aware modeEnvs a worker may register.

Host rules in allowedBackendHosts, hostnames and envs:

RuleMatches
*anything
10.0.0.0/8IPv4 addresses in the CIDR; IPv6 CIDRs are rejected
.example.comnames ending in .example.com
10.0.0.7, ::1that IP
worker.internalthat exact name, case-insensitive

For operators, absent hostnames and envs mean unrestricted.

{
  "name": "fleet-worker",
  "token": { "env": "PINBOARD_WORKER_TOKEN" },
  "scopes": ["pinboard:worker"],
  "claims": {
    "pinboard": {
      "namespaces": ["/threads"],
      "allowedBackendHosts": [".workers.internal"],
      "allowedPorts": [8000]
    }
  }
}

Registry auth errors

StatusBodyWhen
401{"error":"unauthorized"}No credential, or a provider rejected it
403{"error":"forbidden"}Credential lacks the route's scope, or its pinboard claim is malformed or missing namespaces

Out-of-scope request bodies answer 403 with a specific message; see each endpoint.

Account ownership

Register stores the principal's sub as the worker's account. heartbeat, release, tag and deregister require the same sub, else 403 {"error":"forbidden"}. Operator routes judge only the claim scope, never the account.

Data-plane rules

The namespaces object in PINBOARD_PASSPORT turns on data-plane checks. Without it, every routed request passes.

{
  "providers": [ ... ],
  "namespaces": {
    "/threads": { "allow": ["threads:write"] },
    "/public": { "allow": "anonymous" },
    "*": { "allow": "authenticated" }
  }
}
allowPasses
"anonymous"everyone; a valid credential still rides as acct
"authenticated"any accepted credential
string[]a credential holding at least one listed scope
  • The exact namespace key wins over "*". A namespace matching neither is denied.
  • Scopes starting pinboard: are reserved; the config is rejected at boot.
  • A credential a provider rejects fails the request even under "anonymous".
StatusBodyWhen
401{"error":"invalid credential"}A provider rejected the credential
401{"error":"authentication required"}Rule is not anonymous and no credential was accepted
403{"error":"namespace not allowed"}No rule for the namespace and no "*"
403{"error":"insufficient scope"}Credential holds none of the listed scopes

Instance attestation

An instance calling another instance through Pinboard proves its identity with its worker session instead of a Passport credential. See Call from an instance.

What workers receive

Pinboard strips every inbound Pinboard-* header and stamps its own. Workers trust only these.

HeaderOnValue
Pinboard-Attestationevery forward, _pinned/stats, _wake, _alarmHS256 JWS signed with the worker's registered secret
Pinboard-Epochevery forward, _wake, _alarmplacement epoch of the resolved row
Pinboard-Proxy-Secret_pinned/challenge, _alarmthe worker's registered secret

Attestation JWS: header {"alg":"HS256","typ":"pinboard+jwt"}.

ClaimTypeMeaning
audstringthe target worker's sessionId
iatintegerissue time, Unix seconds
expintegeriat + 30
callerstring{namespace}/{id} of an attested calling instance
depthintegersubrequest depth the caller sent
acctobjectthe data-plane principal: sub, scopes (without pinboard:*; omitted when empty), claims (without pinboard)