Skip to content

[API Proposal] Make workload-identity federation reachable from a custom compute environment #19833

Description

@timritzer

Background and Motivation

Aspire.Hosting.Azure.Kubernetes already implements workload-identity federation end to end — but
only on a path a custom compute environment cannot reach.

AzureKubernetesEnvironmentExtensions.cs:835-862 emits the credential — and this is the only
occurrence of FederatedIdentityCredential anywhere in src/:

foreach (var (resourceName, identityResource) in aksResource.WorkloadIdentities)
{
    var existingIdentity = UserAssignedIdentity.FromExisting($"identity_{sanitizedName}");
    existingIdentity.Name = identityNameParam;
    infrastructure.Add(existingIdentity);

    var fedCred = new FederatedIdentityCredential($"fedcred_{sanitizedName}")
    {
        Parent = existingIdentity,
        Name = $"{resourceName}-fedcred",
        IssuerUri = /* ...aks... oidcIssuerProfile.issuerURL */,
        Subject = $"system:serviceaccount:{k8sNamespace}:{saName}",
        Audiences = { "api://AzureADTokenExchange" }
    };
    infrastructure.Add(fedCred);
}

and the matching runtime wiring (annotated ServiceAccountV1, podSpec.ServiceAccountName, the
azure.workload.identity/use label) happens in the AKS environment's prepare-aks-{name} pipeline
step — registered at AzureKubernetesEnvironmentResource.cs:47 and documented at :35 as applying
"node-pool/workload-identity annotations to compute".

Two things lock this away from anyone else:

  1. It runs only inside ConfigureAksInfrastructure (AzureKubernetesEnvironmentExtensions.cs:510),
    a private static method wired exclusively as
    AzureKubernetesEnvironmentResource's infrastructure callback.
  2. aksResource.WorkloadIdentities is internal
    (AzureKubernetesEnvironmentResource.cs:153 — internal Dictionary<string, IAppIdentityResource>).

A compute environment that is not the built-in AKS one therefore never runs prepare-aks, and its
generated chart has no ServiceAccount, no workload-identity pod label, and no federated
credential
— even though every type involved (FederatedIdentityCredential,
KubernetesServiceCustomizationAnnotation, AppIdentityAnnotation) is public.

Is this the right ask? (division of responsibility)

Yes, with one deliberate boundary. The credential federates an identity the AppHost declares
to a service account the AppHost's own chart creates — both sides are the app's, so this is
app-model work, not platform work.

The boundary: federating a platform-owned identity (say, a shared cluster identity) is a
platform onboarding operation and is explicitly not what this asks for. We do not do that, and an
upstream API should not encourage it either.

We are also not asking Aspire to invent anything — only to make an existing, working emission
reachable.

Proposed API

 namespace Aspire.Hosting;

 public static class AzureKubernetesEnvironmentExtensions
 {
+    /// <summary>
+    /// Emits a federated identity credential linking <paramref name="identity"/> to the
+    /// Kubernetes service account <c>system:serviceaccount:{namespace}:{serviceAccount}</c>.
+    /// </summary>
+    /// <remarks>
+    /// The OIDC issuer is supplied as a value rather than read from a provisioned cluster, so this
+    /// works for a pre-existing cluster and for compute environments that provision no cluster.
+    /// </remarks>
+    public static IResourceBuilder<AzureUserAssignedIdentityResource> WithKubernetesServiceAccountFederation(
+        this IResourceBuilder<AzureUserAssignedIdentityResource> identity,
+        BicepValue<string> oidcIssuerUrl,
+        string kubernetesNamespace,
+        string serviceAccountName);
 }

Taking oidcIssuerUrl as a value is the load-bearing detail: a custom compute environment
provisions no cluster template to read oidcIssuerProfile.issuerURL from.

Usage Examples

var identity = builder.AddAzureUserAssignedIdentity("workload-identity");

identity.WithKubernetesServiceAccountFederation(
    oidcIssuerUrl: clusterIssuerUrl,      // from a parameter or platform metadata
    kubernetesNamespace: "my-namespace",
    serviceAccountName: "my-workload");

The chart-side half (ServiceAccount + pod label) is already achievable today through the public
KubernetesServiceCustomizationAnnotation, so this one addition completes the scenario.

Alternative Designs

  • Make WorkloadIdentities public and extract the emission into a public static helper. Equally
    good, and arguably tidier — it would let the AKS environment and third parties share one code
    path rather than two.
  • Replicate it downstream. What we do today; it is ~40 lines of stock Azure.Provisioning, but
    it means the federation subject and the service-account name are owned in two different places
    and can drift.

Risks

Low — additive, and it emits the same stock FederatedIdentityCredential the AKS environment
already emits. The one judgement call is accepting the issuer as a value, which is strictly more
permissive than reading it from a provisioned cluster.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions