From 6fcba05e2b0168f302ef4d61a7dd52db8c92d731 Mon Sep 17 00:00:00 2001 From: Matheus Pimenta Date: Sun, 1 Feb 2026 22:05:27 +0000 Subject: [PATCH] [RFC-XXXX] Vendor-Agnostic Short-Lived Credentials Signed-off-by: Matheus Pimenta --- .../README.md | 654 ++++++++++++++++++ 1 file changed, 654 insertions(+) create mode 100644 rfcs/xxxx-vendor-agnostic-short-lived-credentials/README.md diff --git a/rfcs/xxxx-vendor-agnostic-short-lived-credentials/README.md b/rfcs/xxxx-vendor-agnostic-short-lived-credentials/README.md new file mode 100644 index 00000000..528897dc --- /dev/null +++ b/rfcs/xxxx-vendor-agnostic-short-lived-credentials/README.md @@ -0,0 +1,654 @@ +# RFC-XXXX Vendor-Agnostic Short-Lived Credentials + +**Status:** provisional + + + +**Creation date:** 2026-02-01 + +**Last update:** 2026-02-01 + +## Summary + +In [RFC-0010](https://github.com/fluxcd/flux2/tree/main/rfcs/0010-multi-tenant-workload-identity) +we implemented object-level workload identity for cloud providers leveraging +`ServiceAccount` tokens. This RFC proposes extending Flux with vendor-agnostic +short-lived credentials based on open standards (OIDC and SPIFFE), enabling +workload identity authentication with third-party services that are not +cloud-provider-managed, such as self-hosted container registries (e.g. +[Zot](https://zotregistry.dev/), [Harbor](https://goharbor.io/)). We propose +introducing a new spec field `.spec.credential` to the `OCIRepository` and +`ImageRepository` APIs, as a structured object with a `type` sub-field +supporting three credential types: `ServiceAccountToken`, `SpiffeJWT` and +`SpiffeCertificate`. +Once demand for other third-party services supporting these credential +standards show up for other Flux APIs, this pattern can be extended further. + +## Motivation + +RFC-0010 introduced multi-tenant workload identity for cloud providers (AWS, +Azure, GCP) by associating Flux objects with Kubernetes `ServiceAccounts`. +However, the current workload identity support is limited to cloud-provider +token exchange through their respective Security Token Services (STS). There +is a growing need for Flux to support short-lived credentials for third-party +services that implement open standards directly, without depending on any +cloud provider. + +Several real-world use cases motivate this work: + +- The `Kustomization` and `HelmRelease` APIs already support a vendor-agnostic + form of workload identity through the `generic` provider inside + `.spec.kubeConfig.configMapRef` -> `.data.provider`. This generic provider + issues a `ServiceAccount` token and uses it directly for authentication with + remote Kubernetes clusters configured with external OIDC authentication. + However, this pattern has not been extended to other Flux APIs. +- Container registries such as [Zot](https://github.com/project-zot/zot/pull/3711) + and [Harbor](https://github.com/goharbor/harbor/issues/22027) are implementing + OIDC workload identity federation, allowing workloads to authenticate using + Kubernetes `ServiceAccount` tokens directly, without cloud provider intermediaries. +- The [SPIFFE](https://spiffe.io/) standard provides an alternative identity + framework that allows workloads to be identified independently of + `ServiceAccounts`, enabling use cases where the identity is the Flux object + itself (kind, name, namespace) rather than a Kubernetes `ServiceAccount`. + +### Goals + +- Provide vendor-agnostic short-lived credential support in Flux, starting with + the `OCIRepository` and `ImageRepository` APIs. +- Support Kubernetes `ServiceAccount` tokens as OIDC credentials for + authenticating with third-party services that support OIDC federation. +- Support SPIFFE SVIDs (both JWT and x509 certificate) as credentials for + authenticating with third-party services that support SPIFFE. +- Establish a pattern that can be extended to other Flux APIs in the future. + +### Non-Goals + +- It's not a goal to replace or modify the existing cloud-provider workload + identity support introduced in RFC-0010. The `.spec.provider` field and its + cloud-provider-specific behavior remain unchanged. +- It's not a goal to implement a full SPIFFE runtime (SPIRE agent). Instead, + Flux controllers will issue short-lived SPIFFE SVIDs directly using a + private key provided via a Kubernetes `Secret`. +- It's not a goal to support all Flux APIs in the first iteration. The initial + implementation targets `OCIRepository` and `ImageRepository`, with other APIs + to follow in subsequent releases. + +## Proposal + +We propose introducing a new spec field `.spec.credential` to the `OCIRepository` +and `ImageRepository` APIs. This field specifies the type of vendor-agnostic +short-lived credential to use for authentication. The field is mutually exclusive +with `.spec.provider` (when set to a cloud provider) because "provider" conveys +cloud-provider-specific semantics, and because the existing `generic` provider +value already has specific behavior in the OCI APIs when used together with +`.spec.serviceAccountName` that differs from what is proposed here (see details +[here](#why-a-new-field-and-not-specprovider)). The mutual exclusivity is enforced +via a CEL validation rule: + +```yaml +x-kubernetes-validations: +- message: spec.credential can only be used with spec.provider 'generic' + rule: '!has(self.credential) || !has(self.provider) || self.provider == ''generic''' +``` + +### Credential Types + +The `.spec.credential` field is a structured object with the following +sub-fields: + +- **`.spec.credential.type`** (string, required): The type of vendor-agnostic + short-lived credential to use for authentication. +- **`.spec.credential.audiences`** (list of strings, optional): Specifies the + audiences (`aud` claim) for the issued JWT when `.spec.credential.type` is + `ServiceAccountToken` or `SpiffeJWT`. This allows the third-party service to + verify that the token was intended for it. If not specified, defaults to + `.spec.url` for `OCIRepository` and `.spec.image` for `ImageRepository`. + This is analogous to the `Kustomization` and `HelmRelease` APIs, where + the audience defaults to the remote cluster address. + +Grouping credential-related fields under `.spec.credential` keeps the main +spec clean and allows extending the credential configuration with additional +options in the future without polluting the top-level spec. + +The valid values for `.spec.credential.type` are: + +- **`ServiceAccountToken`**: The controller issues a Kubernetes `ServiceAccount` + token and uses it directly as a bearer token for authentication. The + `ServiceAccount` is determined by the existing `.spec.serviceAccountName` + field, which requires the `ObjectLevelWorkloadIdentity` feature gate + (introduced in RFC-0010) to be enabled. If `.spec.serviceAccountName` is + not set, the controller's own `ServiceAccount` is used. In multi-tenancy + lockdown scenarios, the `--default-service-account` controller flag can be + used to force a default `ServiceAccount` when `.spec.serviceAccountName` is + not specified, preventing the controller's own `ServiceAccount` from being + used. The third-party service must be configured with OIDC federation + trusting the Kubernetes `ServiceAccount` token issuer. This is the same + mechanism already used by the `generic` provider in the `Kustomization` and + `HelmRelease` APIs for remote cluster access. + +- **`SpiffeJWT`**: The controller issues a short-lived SPIFFE SVID JWT where + the identity (the `sub` claim) is the Flux object itself, encoded as a + [SPIFFE ID](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE-ID.md) + in the format + `spiffe://///`, where `` + is the lowercase plural form of the Kubernetes resource type, i.e. + `ocirepositories` for `OCIRepository` and `imagerepositories` for + `ImageRepository`. + The trust domain for the SPIFFE ID is configured via the controller flag + `--spiffe-trust-domain`. The `iss` claim is set to the controller flag + `--spiffe-issuer`, which third-party services use for OIDC discovery. The + JWT is signed using a private key provided via `--spiffe-secret-name`. Unlike + `ServiceAccountToken`, this credential type does **not** depend on a + Kubernetes `ServiceAccount` for identity. The third-party service must be + configured to trust the SPIFFE issuer by having access to the corresponding + JWKS document. + +- **`SpiffeCertificate`**: The controller issues a short-lived SPIFFE SVID + x509 certificate for client authentication via mTLS. The certificate encodes + the same SPIFFE ID as `SpiffeJWT` in the SAN URI field. The certificate is + signed using a CA private key and certificate provided via the controller + flag `--spiffe-secret-name`. Both `OCIRepository` and `ImageRepository` + already support client certificate authentication via mTLS, so this + integrates naturally with the existing transport layer. The third-party + service must be configured with the CA certificate for trust. Note that + `.spec.certSecretRef` may still be optionally used alongside + `SpiffeCertificate` for specifying a CA to trust the server's certificate. + +### Controller Flags + +The SPIFFE issuer and cryptographic material are configured as controller-level +flags rather than per-object spec fields. This is analogous to the +`ServiceAccountToken` credential type, which relies on the cluster-level +Kubernetes `ServiceAccount` token issuer PKI — an infrastructure-level concern, +not an object-level one. If these inputs were instead provided per-object (e.g. +via `.spec.secretRef`), the pattern would degenerate into a secret-based +authentication strategy similar to the `github` provider in the Git APIs, which +requires a GitHub App private key to be set in `.spec.secretRef`. The goal of +this RFC is for Flux objects to have their own identities with short-lived +credentials issued from a shared, controller-level PKI, just as Kubernetes +`ServiceAccount` tokens are issued from the cluster-level token issuer. + +- **`--spiffe-trust-domain`**: The SPIFFE trust domain used to construct + SPIFFE IDs for all SPIFFE credential types. The SPIFFE ID is encoded as + `spiffe://///` and is included as the + `sub` claim in `SpiffeJWT` tokens and in the SAN URI field of + `SpiffeCertificate` x509 certificates. Required when any object uses + `SpiffeJWT` or `SpiffeCertificate` as its `.spec.credential`. + +- **`--spiffe-issuer`**: The OIDC issuer URL for `SpiffeJWT` credentials. + Used as the `iss` claim in the issued JWTs. Third-party services use this + URL for OIDC discovery to fetch the JWKS and verify token signatures. + Required when any object uses `SpiffeJWT` as its `.spec.credential`. + +- **`--spiffe-secret-name`**: The name of a Kubernetes TLS `Secret` + (`type: kubernetes.io/tls`) in the controller's namespace containing the + cryptographic material for issuing SPIFFE SVIDs. This format is compatible + with [cert-manager](https://cert-manager.io/), allowing the `Secret` to be + automatically provisioned and rotated. The `Secret` must contain: + - `tls.key`: The private key. Used for signing JWTs (`SpiffeJWT`) and for + signing client certificates (`SpiffeCertificate`). + - `tls.crt`: The CA certificate. Used for signing client certificates + (`SpiffeCertificate`). Not required for `SpiffeJWT`. + + Required when any object uses `SpiffeJWT` or `SpiffeCertificate` as its + `.spec.credential`. + +### User Stories + +#### Story 1 + +> As a cluster administrator, I want tenant A to pull OCI artifacts from a +> self-hosted Zot registry repository belonging to tenant A using workload +> identity, without any cloud provider dependency. + +For example, I would like to have the following configuration: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: tenant-a-repo + namespace: tenant-a +spec: + url: oci://zot.zot.svc.cluster.local:5000/tenant-a + credential: + type: ServiceAccountToken + audiences: + - zot.zot.svc.cluster.local + serviceAccountName: tenant-a-sa +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: tenant-a-sa + namespace: tenant-a +``` + +The Zot registry is configured with OIDC federation trusting the Kubernetes +`ServiceAccount` token issuer, and an authorization policy granting the +`ServiceAccount` `tenant-a/tenant-a-sa` access only to the `tenant-a` +repository. + +#### Story 2 + +> As a cluster administrator, I want to authenticate Flux objects with a +> SPIFFE-aware service using the identity of the Flux object itself, not a +> `ServiceAccount`. + +For example, I would like to have the following configuration: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: my-app + namespace: production +spec: + url: oci://registry.example.com/my-app + credential: + type: SpiffeJWT + audiences: + - registry.example.com +``` + +The controller is started with `--spiffe-trust-domain=example.com`. +The SPIFFE ID for this object would be +`spiffe://example.com/ocirepositories/production/my-app`, +and the registry would authorize access based on this identity. + +#### Story 3 + +> As a cluster administrator, I want to use mTLS with a SPIFFE certificate +> to authenticate with a container registry that supports SPIFFE-based client +> certificate authentication. + +For example, I would like to have the following configuration: + +```yaml +apiVersion: source.toolkit.fluxcd.io/v1 +kind: OCIRepository +metadata: + name: secure-app + namespace: production +spec: + url: oci://registry.example.com/secure-app + credential: + type: SpiffeCertificate +``` + +The controller is started with `--spiffe-trust-domain=example.com`. +It issues a short-lived x509 certificate with the SPIFFE ID +`spiffe://example.com/ocirepositories/production/secure-app` +in the SAN URI field, and uses it for mTLS authentication with the registry. + +#### Story 4 + +> As a cluster administrator, I want to use `ServiceAccount` token +> authentication to scan container images from a Harbor registry that supports +> OIDC workload identity federation. + +```yaml +apiVersion: image.toolkit.fluxcd.io/v1 +kind: ImageRepository +metadata: + name: my-app + namespace: apps +spec: + image: harbor.example.com/apps/my-app + credential: + type: ServiceAccountToken + audiences: + - harbor.example.com + serviceAccountName: my-app-sa +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: my-app-sa + namespace: apps +``` + +### Why a New Field and Not `.spec.provider` + +- The word "provider" suggests a cloud provider, which is not what this feature targets. + "Credential" better communicates that we are implementing authentication standards, not + targeting cloud vendors. +- Using only `.spec.provider: generic`, like in the `Kustomization` and `HelmRelease` APIs, + is not viable because this value already has specific behavior in the OCI APIs when used + together with `.spec.serviceAccountName`, it means: "use `.imagePullSecrets` from the + `ServiceAccount` referenced by `.spec.serviceAccountName`". + +The `Kustomization` and `HelmRelease` APIs have a naming inconsistency where +`provider: generic` in `.spec.kubeConfig.configMapRef` is used for +`ServiceAccountToken`-based authentication with remote clusters. Updating this +ConfigMap API is out-of-scope for this RFC; the existing behavior remains +unchanged and is considered an exception to the standard proposed here. + +At the end of the day, `generic` is the default provider if none is specified, +and it will be the only accepted provider when using `.spec.credential`. This +is semantically correct because standard-based credentials are vendor-agnostic +by definition, and therefore a "generic" provider conveys the intended meaning. + +### How Trust Should Be Established + +#### For `ServiceAccountToken` + +The Kubernetes `ServiceAccount` token issuer already implements the OIDC +Discovery protocol. Third-party services must be configured to trust this +issuer by having network access to the OIDC discovery and JWKS endpoints, or +by having the JWKS document configured out-of-band. This is the same trust +model used by cloud providers for workload identity (see +[RFC-0010 Technical Background](https://github.com/fluxcd/flux2/tree/main/rfcs/0010-multi-tenant-workload-identity#technical-background)). +For clusters without a built-in public issuer URL, the responsibility of +serving the OIDC discovery and JWKS documents can be taken away from the +kube-apiserver by using the `--service-account-issuer` and +`--service-account-jwks-uri` apiserver flags to point to externally hosted +documents. Signing key rotation is also possible through the +`--service-account-signing-key-file` and `--service-account-key-file` +apiserver flags (see +[Flux cross-cloud integration docs](https://fluxcd.io/flux/integrations/cross-cloud/#for-clusters-without-a-built-in-public-issuer-url)). + +#### For `SpiffeJWT` + +`SpiffeJWT` is also OIDC-based — it is an extension of the same OIDC trust +model used by `ServiceAccountToken`, but with a separate issuer and key +material managed by the cluster administrator rather than by the Kubernetes API +server. The trust establishment follows the same OIDC Discovery protocol: users +must host the issuer and JWKS documents at the URL specified by +`--spiffe-issuer`, reachable from the third-party services they are integrating +with. The controller signs the JWTs with the private key from +`--spiffe-secret-name`, and the corresponding public key must be available in +the JWKS document at the issuer URL. + +#### For `SpiffeCertificate` + +Users must configure the third-party service with the CA certificate provided +via `--spiffe-secret-name` so it can verify client certificates issued by the +Flux controller. + +### Alternatives + +#### Using `.spec.provider` Instead of `.spec.credential` + +An alternative would be to add the new credential types as values of +`.spec.provider` directly (e.g. `provider: serviceaccounttoken`, +`provider: spiffejwt`), rather than introducing a separate `.spec.credential` +field. However, `.spec.provider` conveys cloud-provider-specific semantics and +uses lowercase values like `aws`, `azure`, `gcp` — mixing vendor-agnostic +credential types into this field would blur the distinction between targeting a +cloud provider and using an open standard. Additionally, the SPIFFE credential +types do not require a `ServiceAccount` at all, which is fundamentally +different from how `.spec.provider` operates today. + +#### Using an External SPIFFE Runtime (SPIRE) + +Instead of issuing SPIFFE SVIDs directly, we could require users to deploy a +full SPIRE agent and have the Flux controllers obtain SVIDs through the SPIFFE +Workload API or Delegated Identity API. However, the standard +[Workload API](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Workload_API.md) +only returns the identity assigned to the calling workload — it does not allow +the caller to request a specific SPIFFE ID. SPIRE's +[Delegated Identity API](https://spiffe.io/docs/latest/deploying/spire_agent/#delegated-identity-api) +does allow a trusted delegate (like a Flux controller) to obtain SVIDs on +behalf of other workloads, but those identities must still match pre-registered +entries on the SPIRE Server. This means a cluster administrator would need to +pre-register a SPIRE entry for every Flux object, which would be a significant +operational burden. Beyond this, deploying a full SPIRE infrastructure solely +for Flux authentication is a heavy dependency that many users would not want. +Our approach of issuing SVIDs directly from a provided private key is simpler +and self-contained, while still producing standard SPIFFE SVIDs that any +SPIFFE-aware service can verify. + +## Design Details + +### API Changes + +For the `OCIRepository` API, we introduce the following new fields: + +```go +// Credential specifies the configuration for vendor-agnostic short-lived +// credentials. +type Credential struct { + // Type specifies the type of credential to use for authentication. + // +required + Type string `json:"type"` + + // Audiences specifies the audiences for the issued JWT when Type is + // ServiceAccountToken or SpiffeJWT. Defaults to .spec.url for + // OCIRepository and .spec.image for ImageRepository. + // +optional + Audiences []string `json:"audiences,omitempty"` +} + +type OCIRepositorySpec struct { + // ... existing fields ... + + // Credential specifies the vendor-agnostic short-lived credential + // configuration for authentication. Mutually exclusive with using + // .spec.provider for cloud-provider workload identity and with + // .spec.secretRef. + // +optional + Credential *Credential `json:"credential,omitempty"` +} +``` + +Equivalent fields are introduced for the `ImageRepository` API. + +### Validation Rules + +- `.spec.credential` is mutually exclusive with `.spec.provider` when the + provider is set to anything other than `generic`. +- `.spec.credential` is mutually exclusive with `.spec.secretRef`. +- `.spec.credential.type` is required when `.spec.credential` is specified. +- `.spec.credential.audiences` is optional. When not specified and + `.spec.credential.type` is `ServiceAccountToken` or `SpiffeJWT`, the + audience defaults to `.spec.url` for `OCIRepository` and `.spec.image` + for `ImageRepository`. +- When `.spec.credential.type` is `SpiffeJWT`, the controller flags + `--spiffe-trust-domain`, `--spiffe-issuer` and `--spiffe-secret-name` must + be set, otherwise a terminal error is returned. +- When `.spec.credential.type` is `SpiffeCertificate`, the controller flags + `--spiffe-trust-domain` and `--spiffe-secret-name` must be set, otherwise a + terminal error is returned. +- When `.spec.credential.type` is `ServiceAccountToken`, + `.spec.serviceAccountName` is optional. If not set, the controller's own + `ServiceAccount` is used. Using `.spec.serviceAccountName` requires the + `ObjectLevelWorkloadIdentity` feature gate (introduced in RFC-0010) to be + enabled. In multi-tenancy lockdown scenarios, the + `--default-service-account` controller flag can be used to force a default + `ServiceAccount` when `.spec.serviceAccountName` is not specified, preventing + the controller's own `ServiceAccount` from being used. + +### Credential Issuance + +#### `ServiceAccountToken` + +The controller uses the Kubernetes `TokenRequest` API to issue a short-lived +token for the configured `ServiceAccount` (or the controller's own +`ServiceAccount` if none is configured). The token is issued with the audiences +specified in `.spec.credential.audiences`. The token is then used as a bearer token in the +`Authorization` header when authenticating with the third-party service (e.g. a +container registry). + +This reuses the same `ServiceAccount` token creation logic already present in +the `github.com/fluxcd/pkg/auth` library from RFC-0010, but skips the cloud +provider token exchange step. + +#### `SpiffeJWT` + +The controller constructs a JWT with the following claims: + +- `iss`: The value of `--spiffe-issuer`. +- `sub`: The SPIFFE ID in the format + `spiffe://///`, where the + trust domain is the value of `--spiffe-trust-domain`. +- `aud`: The values from `.spec.credential.audiences`. +- `exp`: One hour from the current time. +- `nbf`: The current time. +- `iat`: The current time. +- `jti`: A unique token identifier. + +The JWT is signed using the private key from the `Secret` referenced by +`--spiffe-secret-name`. The signing algorithm is determined by the key type +(e.g. RS256 for RSA, ES256 for EC P-256). + +#### `SpiffeCertificate` + +The controller generates a short-lived x509 certificate with: + +- The SPIFFE ID in the SAN URI field. +- A validity period of one hour. +- Signed by the CA from the `Secret` referenced by `--spiffe-secret-name`. + +The certificate and a freshly generated private key are used for mTLS +authentication with the third-party service. + +### Integration with the `auth` Library + +We propose extending the `github.com/fluxcd/pkg/auth` library to support +vendor-agnostic credentials alongside the existing cloud-provider workload +identity. This involves: + +1. Renaming the existing `auth/generic` package to `auth/serviceaccounttoken`. +2. Introducing `auth/spiffejwt` for issuing SPIFFE SVID JWTs, using a JWT + library (e.g. `golang.org/x/oauth2/jws` or `github.com/go-jose/go-jose`) + and standard Go crypto libraries (`crypto/rsa`, `crypto/ecdsa`) for signing. +3. Introducing `auth/spiffecertificate` for issuing SPIFFE SVID x509 + certificates, using standard Go crypto libraries (`crypto/x509`, + `crypto/rsa`, `crypto/ecdsa`, `encoding/pem`) for certificate generation + and signing. + +### CLI Support + +The `flux push artifact` command (and related commands: `list`, `pull`, `tag`, +`diff`) will support the following credential types via the `--creds` flag: + +#### `ServiceAccountToken` + +Two modes are supported: + +- **Token creation via `TokenRequest` API**: The CLI creates a short-lived + `ServiceAccount` token using the Kubernetes API. This mode works both inside + a pod (e.g. in a CI/CD pipeline running in-cluster) and locally with a + kubeconfig. + + ```shell + flux push artifact --creds=ServiceAccountToken \ + --audiences=aud1,aud2 \ + --sa-name=my-sa + ``` + + If `--sa-name` is not specified and the CLI is running inside a pod, it + reads the mounted `ServiceAccount` token file to determine the + `ServiceAccount` name. The `auth` library already has logic for finding + the current `ServiceAccount` when running inside a pod (used for issuing + controller-level tokens), so this can be reused by the CLI. If `--audiences` + is not specified, it defaults to the artifact URL. Note that this mode + requires the `create` verb on the `serviceaccounts/token` subresource for + the target `ServiceAccount`. + +- **Pre-existing token file**: The CLI reads a `ServiceAccount` token from a + file. This mode is useful when the token has already been obtained through + other means, e.g. a + [projected volume](https://kubernetes.io/docs/concepts/storage/projected-volumes/#serviceaccounttoken). + + ```shell + flux push artifact --creds=ServiceAccountToken \ + --sa-token=/path/to/token + ``` + +#### `SpiffeJWT` and `SpiffeCertificate` + +In the controller, SPIFFE SVIDs are minted directly by the controller with +identities tied to Flux objects. In the CLI, there is no Flux object, and +providing the private signing key to the CLI would undermine the security model. +Instead, the CLI integrates with SPIRE via the +[SPIFFE Workload API](https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Workload_API.md) +using the [`go-spiffe`](https://github.com/spiffe/go-spiffe) library. In this +model, SPIRE assigns the identity to the calling workload (e.g. a CI runner +pod) based on its registration entries — the caller does not choose the SPIFFE +ID. These are complementary identity models: the controller mints SVIDs for +Flux objects, while the CLI relies on SPIRE to attest the workload running the +CLI. + +For JWT SVIDs, the CLI calls `workloadapi.FetchJWTSVID()` with the specified +audiences: + +```shell +flux push artifact --creds=SpiffeJWT \ + --audiences=aud1,aud2 +``` + +If `--audiences` is not specified, it defaults to the artifact URL. + +For x509 SVIDs, the CLI calls `workloadapi.FetchX509SVID()` and uses the +returned certificate and private key for mTLS client authentication: + +```shell +flux push artifact --creds=SpiffeCertificate +``` + +Both communicate with the SPIRE Agent via a Unix domain socket configured +through the `SPIFFE_ENDPOINT_SOCKET` environment variable. + +Note that adding `github.com/spiffe/go-spiffe/v2` as a dependency to the CLI +binary pulls in gRPC (for the Workload API socket communication), which has a +non-trivial impact on binary size. + +#### `GitHubOIDCToken` + +As an exception to the "vendor-agnostic" scope of this RFC, the CLI will also +support GitHub Actions OIDC tokens. This is motivated by the fact that GitHub +Actions is a widely used CI/CD platform and its OIDC token API provides a +convenient way to obtain short-lived tokens without managing secrets. The CLI +obtains the token from the +[GitHub Actions OIDC Token API](https://docs.github.com/en/actions/reference/security/oidc#methods-for-requesting-the-oidc-token). + +```shell +flux push artifact --creds=GitHubOIDCToken \ + --audiences=aud1,aud2 +``` + +If `--audiences` is not specified, it defaults to the artifact URL. + +### Cache Considerations + +The existing token cache from RFC-0010 can be reused. Following the same +principle established there, any new fields that interfere with the +creation of the credential will be included in the cache key. + +### Security Considerations + +- **Private key protection**: The `Secret` referenced by + `--spiffe-secret-name` contains sensitive cryptographic material. It must be + protected with appropriate RBAC and ideally managed through a secrets + management solution. The controller should only have read access to this + `Secret`. +- **Short-lived credentials**: All credential types produce short-lived tokens + or certificates (with a validity of one hour), limiting the blast radius if a + credential is leaked. +- **SPIFFE ID namespacing**: The SPIFFE ID includes the namespace and object + resource/name, providing natural multi-tenant isolation. A tenant's Flux + objects will always have SPIFFE IDs scoped to their namespace. +- **SPIFFE ID uniqueness**: The SPIFFE ID encodes the resource type, namespace + and name of the Flux object, which are unique within a cluster. This + guarantees that no two objects can have the same SPIFFE identity, preventing + one object from impersonating another (which is otherwise possible if using + a `ServiceAccount`). +- **Trust boundary**: When using `ServiceAccountToken`, the trust boundary is + the same as RFC-0010 (Kubernetes `ServiceAccount` token issuer). When using + SPIFFE credentials, the trust boundary extends to whoever has access to the + private key referenced by `--spiffe-secret-name`. This key should be + treated with the same level of care as the Kubernetes CA key. + +## Implementation History + +