Skip to content

Commit 13b49f6

Browse files
Merge pull request #12 from gitcommitankit/phase-2
feat: implement opt-in cloud workload identity for Azure AKS and AWS …
2 parents 820046e + b43de96 commit 13b49f6

6 files changed

Lines changed: 122 additions & 3 deletions

File tree

.agents/skills/agentrax-context/SKILL.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ description: Project context and settled architecture decisions for the Agentrax
2020

2121
- **Autoscaling**: native `HorizontalPodAutoscaler` pointed at Prometheus Adapter custom metrics (`queueDepth` or `gpuUtilization`). No custom scaling loop. During active canary, the stable HPA is paused (deleted) and no canary HPA is created — autoscaling resumes only after promotion or rollback.
2222
- **Traffic splitting**: Gateway API `HTTPRoute` weighted backends. Not Istio, not ingress annotations.
23-
- **Network Isolation**: Two-tier Kubernetes `NetworkPolicy` (`allow-metrics-traffic` in `agentrax-system` allowing operator metrics on TCP 8443; `tenant-agent-isolation` rendered into every `tenant-*` namespace selecting agent pods with `agentrax.io/agent: "true"` for scraping on TCP 8080 and egress to API server/CoreDNS). No service mesh.
23+
- **Network Isolation**: Two-tier Kubernetes `NetworkPolicy` (`allow-metrics-traffic` in `agentrax-system` allowing operator metrics on TCP 8443; `tenant-agent-isolation` rendered into every `tenant-*` namespace selecting agent pods with `agentrax.io/agent: "true"` for scraping on TCP 8080 and egress to API server/CoreDNS in `kube-system`). No service mesh.
24+
- **Cloud Workload Identity**: No static cloud credentials ever. Azure deployments use AKS Workload Identity (`azure.workload.identity/client-id` + `/tenant-id` ServiceAccount annotations; `azure.workload.identity/use: "true"` pod label). AWS deployments use IRSA (`eks.amazonaws.com/role-arn` annotation). In Helm deployments, both are opt-in via `workloadIdentity.enabled` in `charts/agentrax/values.yaml` (disabled by default for portability); in Kustomize deployments, AWS IRSA is activated via the `config/workload-identity/irsa-serviceaccount.yaml` strategic-merge patch.
2425
- **MCP registry**: embedded HTTP handler inside the operator process, backed by a `ConfigMap`. Not a separate Deployment, not a new database — HA storage is a v2 item.
2526
- **Non-goals**: no model training/fine-tuning, no general-purpose workload management, no service mesh, no UI in v1. Flag any drift toward these rather than quietly implementing them.
2627

charts/agentrax/templates/deployment.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ spec:
1919
{{- end }}
2020
labels:
2121
{{- include "agentrax.selectorLabels" . | nindent 8 }}
22+
{{- if and .Values.workloadIdentity.enabled (eq .Values.workloadIdentity.provider "azure") }}
23+
azure.workload.identity/use: "true"
24+
{{- end }}
2225
spec:
2326
{{- with .Values.imagePullSecrets }}
2427
imagePullSecrets:
Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
{{- if .Values.workloadIdentity.enabled }}
2+
{{- if not .Values.serviceAccount.create }}
3+
{{- fail "workloadIdentity.enabled requires serviceAccount.create to be true so the required cloud identity annotations can be attached" }}
4+
{{- end }}
5+
{{- if not (has .Values.workloadIdentity.provider (list "azure" "aws")) }}
6+
{{- fail (printf "workloadIdentity.provider must be either 'azure' or 'aws', got '%s'" .Values.workloadIdentity.provider) }}
7+
{{- end }}
8+
{{- if eq .Values.workloadIdentity.provider "azure" }}
9+
{{- if or (not .Values.workloadIdentity.azureClientId) (not .Values.workloadIdentity.azureTenantId) }}
10+
{{- fail "workloadIdentity.azureClientId and workloadIdentity.azureTenantId must be non-empty when provider is 'azure'" }}
11+
{{- end }}
12+
{{- end }}
13+
{{- if eq .Values.workloadIdentity.provider "aws" }}
14+
{{- if not .Values.workloadIdentity.awsRoleArn }}
15+
{{- fail "workloadIdentity.awsRoleArn must be non-empty when provider is 'aws'" }}
16+
{{- end }}
17+
{{- end }}
18+
{{- end }}
119
{{- if .Values.serviceAccount.create -}}
220
apiVersion: v1
321
kind: ServiceAccount
@@ -6,8 +24,15 @@ metadata:
624
namespace: {{ .Release.Namespace }}
725
labels:
826
{{- include "agentrax.labels" . | nindent 4 }}
9-
{{- with .Values.serviceAccount.annotations }}
1027
annotations:
28+
{{- with .Values.serviceAccount.annotations }}
1129
{{- toYaml . | nindent 4 }}
12-
{{- end }}
30+
{{- end }}
31+
{{- if and .Values.workloadIdentity.enabled (eq .Values.workloadIdentity.provider "azure") }}
32+
azure.workload.identity/client-id: {{ .Values.workloadIdentity.azureClientId | quote }}
33+
azure.workload.identity/tenant-id: {{ .Values.workloadIdentity.azureTenantId | quote }}
34+
{{- end }}
35+
{{- if and .Values.workloadIdentity.enabled (eq .Values.workloadIdentity.provider "aws") }}
36+
eks.amazonaws.com/role-arn: {{ .Values.workloadIdentity.awsRoleArn | quote }}
37+
{{- end }}
1338
{{- end }}

charts/agentrax/values.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,21 @@ mcp:
104104
# -- Periodic health check interval for registered agents (e.g. "30s")
105105
healthInterval: "30s"
106106

107+
# -- Cloud Workload Identity — secretless pod-level IAM.
108+
# Supported providers: azure (AKS Workload Identity), aws (EKS IRSA).
109+
# When disabled (the default), no annotations or labels are injected.
110+
workloadIdentity:
111+
# -- Set to true to enable workload identity for the controller-manager pod.
112+
enabled: false
113+
# -- Cloud provider. Accepted values: "azure" | "aws".
114+
provider: azure
115+
# -- Azure only: Application (client) ID of the managed identity.
116+
azureClientId: ""
117+
# -- Azure only: Azure AD tenant ID.
118+
azureTenantId: ""
119+
# -- AWS only: Full ARN of the IAM role to assume via IRSA.
120+
awsRoleArn: ""
121+
107122
# -- Environment variables injected into the manager container.
108123
# These values are automatically populated from registry.ttl and mcp.healthInterval above.
109124
env: {}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# Kustomize strategic-merge patch for AWS IRSA (IAM Roles for Service Accounts).
2+
# Apply this patch in your cluster-specific overlay to bind the controller-manager
3+
# ServiceAccount to an IAM role without static AWS credentials.
4+
#
5+
# Usage — in your overlay kustomization.yaml:
6+
#
7+
# resources:
8+
# - ../../config/default
9+
# patches:
10+
# - path: ../../config/workload-identity/irsa-serviceaccount.yaml
11+
# replacements:
12+
# - source:
13+
# kind: ConfigMap # or any source carrying your role ARN
14+
# name: cluster-metadata
15+
# fieldPath: data.roleArn
16+
# targets:
17+
# - select:
18+
# kind: ServiceAccount
19+
# name: agentrax-controller-manager
20+
# fieldPaths:
21+
# - metadata.annotations.[eks.amazonaws.com/role-arn]
22+
#
23+
# The role must have a trust policy allowing the OIDC provider of your EKS cluster
24+
# to assume it on behalf of the agentrax-system/agentrax-controller-manager subject.
25+
apiVersion: v1
26+
kind: ServiceAccount
27+
metadata:
28+
name: agentrax-controller-manager # Kustomize ServiceAccount name (Helm chart uses "agentrax" by default via workloadIdentity.awsRoleArn)
29+
namespace: agentrax-system
30+
annotations:
31+
# Replace this placeholder with the actual IAM role ARN before applying.
32+
eks.amazonaws.com/role-arn: "arn:aws:iam::ACCOUNT_ID:role/ROLE_NAME"

docs/ARCHITECTURE.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,48 @@ Agentrax maintains a **two-tier network policy model**:
338338

339339
---
340340

341+
### 4.7 Keyless Cloud IAM — Workload Identity
342+
343+
Agentrax and managed agent pods often require cloud API access (e.g., Azure Key Vault, Azure OpenAI, AWS Secrets Manager) in production. Note that private container image pulls (such as ACR) are handled independently at the node layer via AKS Kubelet Managed Identity (with the `AcrPull` role) or Kubernetes `imagePullSecrets`, while Workload Identity specifically covers runtime cloud SDK calls originating from running pods.
344+
345+
Static credentials baked into `Secret` objects rotate manually, are visible in etcd, and create a long-lived blast radius if leaked. The operator and tenant agents are instead bound to a cloud-managed identity at the pod level:
346+
347+
| Cloud | Mechanism | How it works |
348+
| :------ | :------------------------------ | :------------------------------------------------------------------------------------------------------------------------------------- |
349+
| **Azure** (AKS) | Azure Workload Identity | An OIDC-projected service account token is exchanged for a short-lived Azure AD access token by the Azure Identity SDK. The AKS admission webhook injects the projected volume and `AZURE_*` env vars when the pod carries `azure.workload.identity/use: "true"`. |
350+
| **AWS** (EKS) | IRSA (IAM Roles for SA) | EKS projects a signed OIDC token into the pod; the AWS SDK exchanges it for temporary STS credentials scoped to the bound IAM role via `eks.amazonaws.com/role-arn` annotation. |
351+
352+
#### Helm Configuration
353+
354+
Workload identity is off by default. Enable it via `values.yaml` or `--set`:
355+
356+
```yaml
357+
# Azure AKS
358+
workloadIdentity:
359+
enabled: true
360+
provider: azure
361+
azureClientId: "<managed-identity-client-id>"
362+
azureTenantId: "<azure-ad-tenant-id>"
363+
364+
# AWS EKS
365+
workloadIdentity:
366+
enabled: true
367+
provider: aws
368+
awsRoleArn: "arn:aws:iam::<account>:role/<role-name>"
369+
```
370+
371+
When `workloadIdentity.enabled=true`:
372+
373+
- **Azure**: The `ServiceAccount` gains `azure.workload.identity/client-id` and `azure.workload.identity/tenant-id` annotations; the manager pod gains the `azure.workload.identity/use: "true"` label required by the AKS mutating webhook.
374+
- **AWS**: The `ServiceAccount` gains the `eks.amazonaws.com/role-arn` annotation consumed by the EKS pod identity webhook. For Kustomize-based cluster overlays, use `config/workload-identity/irsa-serviceaccount.yaml` as a strategic-merge patch.
375+
376+
#### Invariants
377+
378+
- No static cloud credentials (`client_secret`, `AWS_SECRET_ACCESS_KEY`) are ever stored in cluster `Secret` objects.
379+
- `workloadIdentity.enabled=false` (the default) renders no identity annotations or pod labels — the chart remains fully portable to on-premises or non-cloud environments.
380+
381+
---
382+
341383
## 5. Architectural Decision Records (ADRs) & Trade-Offs
342384

343385
| Decision | Alternative Considered | Trade-Off & Rationale for Agentrax |
@@ -347,6 +389,7 @@ Agentrax maintains a **two-tier network policy model**:
347389
| **Native HPA via Custom Metrics** | KEDA (`ScaledObject`) | KEDA is powerful but adds external CRD dependencies. Generating native Kubernetes `HorizontalPodAutoscaler` objects tied to the Prometheus Adapter custom metrics pipeline minimized dependencies while giving full control over stabilization windows. |
348390
| **Embedded Registry + ConfigMap Store** | Dedicated etcd / Redis / Database | Adding a dedicated database for service discovery increases operator operational complexity. The in-operator HTTP server with ConfigMap write-through store provides simple, robust storage for hundreds of agent services with cold-restart recovery. |
349391
| **Two-Tier NetworkPolicy** | Istio / Linkerd Service Mesh | Service mesh requires sidecar injection and significant control plane memory overhead. Native Kubernetes NetworkPolicy with label-selector binding (`agentrax.io/agent: "true"`) provides lightweight, CNI-enforced zero-trust tenant isolation with default-deny rules. |
392+
| **Workload Identity (no static secrets)** | Kubernetes `Secret` with cloud credentials | Static credentials require manual rotation, are stored in etcd, and present a wide blast radius on leak. OIDC-projected pod tokens (Azure Workload Identity / AWS IRSA) are short-lived, auto-rotated, and scoped to a single identity. |
350393
| **Go (`controller-runtime`)** | Python (`Kopf`) | Go provides native compile-time safety, seamless alignment with Kubernetes upstream libraries, and access to `setup-envtest` for isolated in-process integration testing. |
351394

352395
---

0 commit comments

Comments
 (0)