Service-account tokens
ServiceAccount::requestToken() mints a short-lived token the way kubectl create token does: a TokenRequest (authentication.k8s.io/v1) on the service account’s token subresource. Only the fields you give go into its spec — expirationSeconds, audiences, boundObjectRef — so the apiserver fills in its own defaults (usually an hour and the cluster’s audiences) and may shorten the lifetime. You get back a ServiceAccountToken:
use RoundlyConsulting\KubernetesApi\Facades\Kubernetes;
$token = Kubernetes::namespace('ci')->serviceAccounts()->setName('deployer')->requestToken(
expirationSeconds: 900, // 600 or more; null = the apiserver's default (usually an hour)
audiences: ['vault'], // empty = the apiserver's default audiences
boundTo: $pod, // an existing Pod, Secret or Node: deleting it invalidates the token
);
$token->token; // the bearer token — masked in var_dump(), print_r() and dump()
$token->expiresAt; // CarbonImmutable, UTC
$token->audiences; // list<string>, as the apiserver answered
$token->boundObjectRef; // ['kind' => 'Pod', 'apiVersion' => 'v1', 'name' => …, 'uid' => …] or null
$token->isExpired(); // or isExpired($at)
// Use it like any other token
Kubernetes::url('https://api.prod.example.com:6443')->withToken($token->token)->pods()->get();ServiceAccountToken
| Property · method | Holds |
|---|---|
token | The bearer token — a #[SensitiveParameter], masked in var_dump(), print_r() and dump(). |
expiresAt | When the token expires, as a CarbonImmutable in UTC. |
audiences | list<string> — the audiences the apiserver answered. |
boundObjectRef | kind, apiVersion, name and uid of the object the token is bound to, or null. |
isExpired(?CarbonInterface $at = null) | Whether the token has expired by $at — now by default. |
Checked before any request
Input the apiserver would refuse throws InvalidResourceException before anything is sent:
- an unnamed service account — setName() first;
- a dryRun() resource — a dry-run TokenRequest mints nothing;
- expirationSeconds under 600 (ServiceAccount::MIN_TOKEN_EXPIRATION_SECONDS);
- a blank audience;
- a boundTo that isn’t a Pod, Secret or Node (ServiceAccount::TOKEN_BINDABLE_KINDS), or lacks metadata.name and metadata.uid — find() it first.
Failures and permissions
- Failures are always body-free — HTTP 403 Forbidden, HTTP 404 NotFound — whatever the cluster’s redaction setting, because this is a credential endpoint. $e->apiMessage() still reads the apiserver’s Status message.
- An answer without a non-empty status.token or an RFC 3339 status.expirationTimestamp throws KubernetesException::malformedResponse().
- The request goes through the cluster’s transport, so client.options.timeout, the rate limiter and Kubernetes::fake() all apply. The cluster’s own credentials need create on serviceaccounts/token.
From any entry point
requestToken() is a resource operation like any other, so it works from the facade, an injected manager or any cluster — including an ad-hoc one that carries a credential allowed to mint tokens:
// Through an injected manager or any cluster, like every resource operation
$token = $this->kubernetes->cluster('dev')->serviceAccounts()
->setNamespace('ci')
->setName('deployer')
->requestToken();
// An ad-hoc cluster whose own credential may mint tokens
$token = Kubernetes::url($server)->withToken($minter)->serviceAccounts()
->setNamespace($namespace)
->setName($serviceAccount)
->requestToken($ttl);Under Kubernetes::fake() a token request answers for seeded service accounts with a deterministic token, and assertTokenRequested() checks it — see Testing.
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.