Access policies
An access policy decides who may call a model's public HTTPS endpoint. The gateway on the model's cluster checks each caller's credential before the request reaches the model and refuses everything else, so a refused request never uses accelerator time. A policy holds one mechanism: API keys that AstroPulse keeps and you hand out one per caller, or JWTs from an OpenID Connect issuer you already run. It belongs to the organization and is reused by name across models and clusters.
Before you start
- On the model's cluster, the Gateway, Certificates and Model Serving add-ons.
- A verified domain to serve the model on.
- An organization owner creates, changes and deletes policies. An owner or admin manages API keys.
In the console
-
Open Integrations > Access Policies. Under Policies, select API keys or Identity provider (OIDC).
-
Enter the Policy name, for example
partner-keys, and select Create. An identity-provider policy also needs its issuer and audiences: see Let your services call with tokens.Open zoom view -
For an API-key policy, select Keys on its row and create one key per caller: Give callers API keys. For an identity-provider policy, select Test a token to check a real token: Test a token.
-
Open Workloads > Models > Deploy model. On the Access step, pick Serve on <domain> under External access, then the policy under Who may call it.
Open zoom view -
Select Review, then Deploy.
You should see
The policy's row reads 1 application under Used by. On the model's Overview, the Endpoint section shows Ready with what callers must send, for example "Callers must send an API key from access policy qwen-keys.", the public URL, and the policy and its revision under Who may call it.
What a policy protects
- An exposed model must name a policy.
externalAccess.accessPolicyRefis required on a model withexternalAccess; there is no silent open default. - Serving with no authentication is an explicit choice. In the console it is Anyone (no authentication) under Who may call it; in a manifest,
accessPolicyRef.none: UnauthenticatedPublicInference. It needs organization-owner authority and is recorded in the audit log. - A model that is not exposed has no public address, and an
accessPolicyRefon it is refused.
| Mechanism | You bring | The caller sends |
|---|---|---|
apiKeys | Nothing: AstroPulse keeps the keys and you issue one per caller | Authorization: Bearer <key>, or x-api-key: <key> if the policy sets header: x-api-key |
jwt | An OpenID Connect issuer that publishes a JWKS | Authorization: Bearer <token> |
A SAML or social identity provider reaches a model the way every API does: by issuing JWTs, which jwt validates.
API keys or JWT
API keys (apiKeys) | JWT (jwt) | |
|---|---|---|
| Who calls | A partner, a customer, a script: anyone you can hand a secret to | Your own workloads that already have an identity: a pod's service account, a CI job, a service registered with your identity provider |
| Setup | The policy, then one key per caller | An issuer reachable over HTTPS, and a policy naming its issuer, audiences and optional require claims |
| What the caller holds | A long-lived key, valid until its expiresAt or until you revoke it | A short-lived token from your issuer |
| Rotation | Create a new key, hand it over, revoke the old one | Your issuer issues new tokens and rotates its signing keys; the gateway reads the JWKS |
| Revocation | Revoke the key: every endpoint on the policy refuses it within about a minute | Stop issuing. Tokens already issued work until exp, plus a minute of clock skew |
| Secrets AstroPulse stores | The keys, in its vault; each value is shown once | None |
| Check a credential first | Call the endpoint | Test a token, or astroctl access-policy test |
Use JWT for traffic between your own systems: nothing long-lived to leak, and rotation is your issuer's job. Use API keys for callers who cannot get a token from your issuer, and for a first test. AstroPulse issues no tokens and runs no identity provider; for jwt it only validates your issuer's tokens.
Revisions
Every save creates a new, immutable revision. An application keeps the revision it was deployed with, so a change reaches a model on its next deploy, never silently. API keys are not part of a revision: creating or revoking one needs no new apply.
A policy an application enforces cannot be deleted: Delete is disabled with "In use: delete the applications that enforce it first". Deleting the policy removes every revision, and recreating the name starts again at revision 1.
Check the endpoint
astroctl app endpoint get qwen-chat
application: qwen-chat
cluster: prod-inference
policy: partner-keys
policyRevision: 1
desiredState: Open
phase: Ready
url: https://qwen-chat.example.com/v1
message: Callers must send an API key from access policy partner-keys. …
probe:
method: GET
path: /
vantage: Cluster
result: Refused
policy, policyRevisionThe policy and the revision the endpoint enforces.
desiredStateOpen or Closed: what AstroPulse wants.
phaseWhat the cluster last reported: Pending (checks running, route unpublished), Ready, Closed, or Error (a check failed and the route is withdrawn; message says which).
probeThe last negative sample: an anonymous and an invalid-credential GET / through the gateway, and whether both were refused with 401 or 403 (result: Refused). Evidence for one path at one moment.
urlShown only while the endpoint is open and the report is fresh and Ready.
messageWhile Ready, what callers must send and how far the address was checked; on Pending and Error, why.
curl -s -o /dev/null -w '%{http_code}\n' https://qwen-chat.example.com/v1/models # 401
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $AP_KEY" https://qwen-chat.example.com/v1/models # 200
The gateway strips the credential before the request reaches the model, so a model's logs never hold a key or a token.
When a call is refused
The gateway answers a refused call with a status code and a short plain-text body that does not say which check failed.
| The caller sees | API-key policy | JWT policy | Next step |
|---|---|---|---|
401 | No key in the policy's header, a wrong key, or a revoked or expired key | No token, or a token whose signature or iss fails, or whose exp or nbf is more than a minute off | Check the key, or test the token |
403 | Not used | Another audience (aud), or a require claim missing, of another type, or holding another value | Test the token: reason is AudienceMismatch, MissingClaim, ClaimType or ClaimValue |
| No answer, or another status | The endpoint is not serving | The endpoint is not serving | Read the endpoint |
API key refused
- The header.
astroctl access-policy get <policy> -o yamlshowsspec.apiKeys.header. WithAuthorization, or none, sendAuthorization: Bearer <key>. Withx-api-key, sendx-api-key: <key>and nothing else. - The key exists and is unexpired.
astroctl access-policy key list <policy>must list its label, withEXPIRESempty or in the future. A revoked key is gone and cannot be restored: create a new one. - A new key. It reaches the gateway within about a minute. Wait, then call again.
- The right policy.
astroctl app endpoint get <app>showspolicy. A key works only on models attached to its policy.
Token refused
Save the exact token the caller sent and run astroctl access-policy test <policy> --token-file <file>, or paste it into Test a token. Each reason:
reason: Malformedreason: UnknownKeykid is not in the policy's key set.iss with issuer; if they match, check jwksUri is the issuer's current key set.reason: BadSignaturereason: IssuerMismatchiss differs from issuer.issuer to the token's iss exactly, trailing slash included.reason: AudienceMismatch403).audiences.reason: Expiredexp passed, beyond a minute of clock skew.reason: NotYetValidnbf is in the future.reason: MissingClaimrequire claim is absent (403).require.reason: ClaimType403). A requirement reads scope and scp as a list and any other claim as a string, unless its type says otherwise.type to String or List, the way your issuer always sends the claim.reason: ClaimValue403).require[].values.reason: KeySetUnusablejwksUri, or leave it empty to take it from discovery.reason: ProviderUnreachableAccepted, but the call still fails: the model may be bound to an older revision. Compare policyRevision from astroctl app endpoint get <app> with the latest from astroctl access-policy get <policy>, and redeploy the model to take the latest.
The endpoint is not serving
astroctl app endpoint get <app> reads:
desiredState: Closedastroctl app endpoint reopen <app>.phase: Pendingmessage says what is pending.phase: Errormessage names, then reopen. On an API-key policy with no live key, create a key.probe.result: UnexpectedResponseprobe.message and message.Close the endpoint
On the model's Overview, select Close endpoint and confirm "Close this endpoint?". Callers can no longer reach the model at its address; the model keeps running and its policy stays. Reopen endpoint re-validates the policy, its keys and TLS, then serves again.
Closing works whatever state your issuer or key set is in, and it lasts: a redeploy or a rollback does not reopen a closed endpoint. Deleting the application retires the endpoint and releases the policy.
What is guaranteed, and what is not
- An unauthenticated request is refused at the gateway and never costs accelerator time.
- The endpoint is published only after the certificate, the gateway, the policy and the credential source are accepted, and withdrawn when any of them fails.
- A cluster AstroPulse cannot reach keeps enforcing its last configuration. Nothing opens because AstroPulse went away.
- Not guaranteed: callers inside the cluster still reach the model's own Service directly; a revoked key or rotated signing key takes effect when the gateway next reads it, not instantly; a JWT stays valid until its
exp; keys are plaintext at rest on the cluster, and with your own Secret anyone with Secret read in the model's namespace can read them.
Limits in this release
Model applications only; one endpoint per application; the Gateway add-on as the enforcement point; no per-caller quotas, rate limits or usage records; no projection of a JWT claim into an upstream header. Attaching a policy to another workload type does not gate its callers today.
Other ways in
- Nova: ask it, for example "create an API-key policy for qwen-chat's partners".
- SDK and API: the same operations, in the API reference.
With astroctl
apiVersion: platform.astropulse.io/v1
kind: AccessPolicy
metadata:
name: partner-keys
spec:
apiKeys:
managed: true
astroctl access-policy apply -f partner-keys.yaml
astroctl access-policy get partner-keys -o yaml
# delete takes the current revision; it asks you to type the name, --yes skips that
astroctl access-policy delete partner-keys --revision 1
astroctl app endpoint close qwen-chat # asks you to type the application's name
astroctl app endpoint reopen qwen-chat
Attach it in the model's manifest:
spec:
externalAccess:
domain: example.com
accessPolicyRef:
name: partner-keys # or: none: UnauthenticatedPublicInference
Every flag: astroctl access-policy.
Settings
An AccessPolicy is a control-plane object, not a Kubernetes resource: the same object through astroctl access-policy apply, the API or the console form.
metadata.nameRequiredThe name applications reference.
-, starting and ending alphanumeric.spec.apiKeys.managedtrue: AstroPulse keeps the keys; issue them. Exactly one of managed or secretRef.
spec.apiKeys.secretRef.nameLast resort: an Opaque Secret you keep in the application's namespace. See Your own Secret.
spec.apiKeys.headerThe header callers send the key in. Set only with astroctl or the API.
Authorization or x-api-key.Authorizationspec.jwt.issuerRequiredMatches the token's iss exactly.
spec.jwt.jwksUriWhere the gateway reads the issuer's signing keys. It must be reachable from the cluster.
jwks_uri of the issuer's discovery document.spec.jwt.audiencesRequiredA token whose aud holds any one of them passes.
spec.jwt.requireClaims every token must carry. All entries must hold; within an entry, any one of values passes. A dotted claim reaches a nested claim; an absent claim denies.
claim: at most 253 characters. values: 1 to 16 strings, each at most 512 characters, matched exactly.revisionOn an update, the revision get returned. A mismatch is refused rather than overwriting someone else's edit.
0: create.externalAccess.accessPolicyRef.nameOn a model: the policy the endpoint enforces. Exactly one of name or none.
externalAccess.accessPolicyRef.noneUnauthenticatedPublicInference, that exact string. Organization-owner authority, and audited with the hostname it opens.
externalAccess.accessPolicyRef.revisionThe policy revision the deployment binds to. Kept through deployment and rollback.
Read-only fields on a policy: updatedAt, references (the applications referencing it; non-empty blocks delete) and openEndpoints (the applications whose open endpoint it protects now).
If it's refused
externalAccess but no accessPolicyRef.accessPolicyRef.name), choose Anyone (no authentication), or remove externalAccess to keep the model inside the cluster.get it again and apply your change on the current revision. A delete with an old revision reads "policy revision changed; fetch the policy and retry".422 IdentityProviderUnreachable: AstroPulse could not reach the issuer or its key set, or it is not on the public internet.apply --allow-unverified) with JWKS URI set.secretRef.managed: true, or manage the Secret yourself.