How authentication works across Asya components. This page maps every credential to the namespace it lives in, the component that uses it, and the Helm value that configures it.


Architecture Overview#

crossplane-system/              keda/                   $NS (actor namespace)
+---------------------------+   +-------------------+   +----------------------------------+
| gcp-creds (Secret)        |   | gcp-keda-secret   |   | asya-runtime (ConfigMap)         |
|   credentials.json        |   |   credentials.json|   |   asya_runtime.py                |
|   Used by: GCP provider   |   |   Used by: KEDA   |   |   Used by: all actor pods        |
|   Auth: Pub/Sub admin     |   |   Auth: read      |   |                                  |
|                           |   |   metrics          |   | gcp-keda-secret (Secret)         |
| (or: WI on provider KSA) |   +-------------------+   |   credentials.json               |
+---------------------------+                           |   Used by: TriggerAuthentication  |
                                                        |                                  |
                                                        | asya-gateway-postgresql (Secret)  |
                                                        |   password                       |
                                                        |   Used by: gateway init container |
                                                        |                                  |
                                                        | asya-gateway-db (Secret)          |
                                                        |   database-url (DSN)             |
                                                        |   Used by: gateway main container |
                                                        |                                  |
                                                        | asya-gateway-auth (Secret)        |
                                                        |   a2a-api-key, mcp-api-key       |
                                                        |   Used by: gateway (env)         |
                                                        |                                  |
                                                        | default KSA (ServiceAccount)      |
                                                        |   annotation: iam.gke.io/gcp-sa  |
                                                        |   Used by: all actor pods (WI)   |
                                                        +----------------------------------+

GCP Pub/Sub Transport#

Service Accounts (GCP IAM)#

GCP SA Purpose IAM Roles Auth mechanism
asya-crossplane Crossplane creates/deletes Pub/Sub topics + subscriptions roles/pubsub.admin JSON key in crossplane-system/gcp-creds, or WI on provider KSA
asya-actor Actor sidecars publish/consume Pub/Sub; handlers call Vertex AI roles/pubsub.publisher, roles/pubsub.subscriber, roles/aiplatform.user GKE Workload Identity on default KSA in actor namespace
asya-keda KEDA reads subscription backlog for autoscaling roles/monitoring.viewer, roles/pubsub.viewer JSON key in gcp-keda-secret

Kubernetes Secrets#

Secret Namespace Key(s) Created by Used by Helm value
gcp-creds crossplane-system credentials.json Manual (kubectl create secret) Crossplane GCP provider gcpProviderConfig.secretRef.*
gcp-keda-secret $NS (actor namespace) credentials.json Manual KEDA TriggerAuthentication (per-actor, created by composition) pubsub.keda.secretRef.name
asya-runtime $NS asya_runtime.py asya-crossplane chart (ConfigMap) All actor pods (volume mount) Automatic
asya-gateway-postgresql $NS password Manual or gateway chart Gateway init container (PGPASSWORD) externalDatabase.existingSecret
asya-gateway-db $NS database-url Gateway chart (auto) or manual Gateway main container (ASYA_DATABASE_URL) Auto-generated from externalDatabase.*
asya-gateway-auth $NS a2a-api-key, mcp-api-key Manual (openssl rand) Gateway (ASYA_A2A_API_KEY, ASYA_MCP_API_KEY) Via env[].valueFrom.secretKeyRef

Workload Identity Bindings#

KSA Namespace GCP SA Purpose
default $NS asya-actor@PROJECT Actor pods authenticate to Pub/Sub without JSON keys
crossplane-provider-gcp-pubsub-* crossplane-system asya-crossplane@PROJECT (Optional) Provider uses WI instead of JSON key

Setting up WI requires two steps:

# 1. Annotate KSA
kubectl annotate serviceaccount default -n $NS \
  iam.gke.io/gcp-service-account=asya-actor@${PROJECT}.iam.gserviceaccount.com

# 2. Bind WI User role (requires gcloud auth login with IAM permissions)
gcloud iam service-accounts add-iam-policy-binding \
  asya-actor@${PROJECT}.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:${PROJECT}.svc.id.goog[${NS}/default]" \
  --condition=None --project=$PROJECT

AWS SQS Transport#

IAM#

Identity Purpose Required Policies
IRSA role for actors Sidecars send/receive SQS sqs:SendMessage, sqs:ReceiveMessage, sqs:DeleteMessage, sqs:GetQueueUrl, sqs:GetQueueAttributes
Crossplane AWS provider Create/delete SQS queues sqs:CreateQueue, sqs:DeleteQueue, sqs:TagQueue, sqs:GetQueueAttributes

Kubernetes Secrets#

Secret Namespace Key(s) Used by Helm value
aws-creds crossplane-system credentials (INI format) Crossplane AWS provider awsProviderConfig.secretRef.*
asya-runtime $NS asya_runtime.py All actor pods Automatic

Deploying Actors to a New Namespace#

When creating actors in a namespace other than the original $NS, these resources must exist in the new namespace:

Resource Type Source Purpose
asya-runtime ConfigMap Copy from $NS Python runtime mounted in every actor pod
gcp-keda-secret Secret Copy from $NS or create new KEDA TriggerAuthentication reads it locally
default KSA annotation ServiceAccount kubectl annotate WI: links pods to GCP SA
WI IAM binding GCP IAM gcloud iam Allows the new namespace's KSA to impersonate the GCP SA
NEW_NS=my-new-namespace
kubectl create namespace $NEW_NS

# Copy asya-runtime ConfigMap
kubectl -n $NS get cm asya-runtime -o json | \
  jq '.metadata = {name: "asya-runtime", namespace: "'$NEW_NS'"}' | \
  kubectl apply -f -

# Copy gcp-keda-secret
kubectl -n $NS get secret gcp-keda-secret -o json | \
  jq '.metadata = {name: "gcp-keda-secret", namespace: "'$NEW_NS'"}' | \
  kubectl apply -f -

# Annotate default KSA for Workload Identity
kubectl annotate serviceaccount default -n $NEW_NS \
  iam.gke.io/gcp-service-account=asya-actor@${PROJECT}.iam.gserviceaccount.com

# Bind WI (requires gcloud auth with IAM permissions)
gcloud iam service-accounts add-iam-policy-binding \
  asya-actor@${PROJECT}.iam.gserviceaccount.com \
  --role=roles/iam.workloadIdentityUser \
  --member="serviceAccount:${PROJECT}.svc.id.goog[${NEW_NS}/default]" \
  --condition=None --project=$PROJECT

Common Pitfalls#

KEDA secret must be in actor namespace, not keda namespace. The Crossplane composition creates a TriggerAuthentication in the actor namespace. It references gcp-keda-secret as a local secret. If the secret is only in the keda namespace, the ScaledObject will never become Ready.

Crossplane locks compositionRef on first creation. If an AsyncActor is created before defaultCompositionRef is set on the XRD, it picks up whatever composition Crossplane selects (often alphabetically). Changing the XRD default later does NOT affect existing actors. Delete and recreate them.

Gateway needs two DB secrets. The asya-gateway-postgresql secret holds the raw password (used by the init container for psql health checks). The asya-gateway-db secret holds the full DSN (used by the main container for ASYA_DATABASE_URL). When using externalDatabase.existingSecret, the chart reads the password for init but still needs the DSN secret.

PostgreSQL password is set at initdb time. The PG StatefulSet reads POSTGRES_PASSWORD from the secret on first start and stores it in the PVC. If the secret is later changed, PG still uses the old password. Fix with ALTER USER asya WITH PASSWORD '...' inside the running pod.

Actor pods use WI, not JSON keys. The sidecar.gcpCredsSecret helm value mounts a secret via envFrom. But Kubernetes silently drops env var names containing dots (like sa-key.json), so ADC falls back to the node SA. Workload Identity bypasses this entirely. Leave gcpCredsSecret empty when WI is configured.