Description
The chart has no serviceAccountName field in the pod spec. Every agent pod runs as the default service account regardless of what's in values. This breaks IRSA (IAM Roles for Service Accounts) on EKS: the pod-identity-webhook only injects AWS credentials (AWS_ROLE_ARN, AWS_WEB_IDENTITY_TOKEN_FILE) into pods whose service account carries an eks.amazonaws.com/role-arn annotation. Because the pod always uses default, IRSA is silently skipped — AWS CLI falls back to the broad EC2 node role instead of the intended least-privilege role.
This is a security concern, not just an inconvenience: it violates the principle of least privilege and gives workloads broader AWS permissions than designed.
Use Case
Running openab inside an EKS cluster with IDP-managed service accounts. The IDP provisions a dedicated SA per workload (e.g. openab) with an IRSA annotation scoped to a readonly IAM role (CloudWatch, EKS describe, EC2 describe). Without serviceAccountName, the pod uses default, IRSA never activates, and aws sts get-caller-identity returns the node role instead of the intended role.
In a multi-agent deployment, different agents may also need different SAs — Agent A uses the cluster default (no AWS access needed), Agent B uses a dedicated SA with IRSA.
Current Workaround
Manually patch the Deployment after every helm upgrade:
kubectl patch deployment <release>-<agentKey> -n <namespace> \
--type=json \
-p='[{"op":"add","path":"/spec/template/spec/serviceAccountName","value":"openab"}]'
Wiped on every upgrade — identical problem to #910.
Proposed Solution
Scope: string reference to an existing ServiceAccount only. The chart will not create a new SA or manage IRSA annotations — operators provision the SA out-of-band (Terraform / IDP / kubectl). This keeps the chart's blast radius small and matches how imagePullSecrets in #910 references existing secrets rather than creating them. Supporting chart-managed SA creation (with annotations, labels, ClusterRoleBindings) is a natural follow-up if there's demand.
values.yaml:
# Service account name for agent pods. Empty string = use cluster default SA.
# Per-agent value (agents.<name>.serviceAccountName) takes priority over global.
serviceAccountName: ""
templates/deployment.yaml:
spec:
{{- with $.Values.podSecurityContext }}
securityContext:
{{- toYaml . | nindent 8 }}
{{- end }}
+ {{- $svcAcct := default $.Values.serviceAccountName $cfg.serviceAccountName }}
+ {{- if $svcAcct }}
+ serviceAccountName: {{ $svcAcct }}
+ {{- end }}
{{- with (default $.Values.imagePullSecrets $cfg.imagePullSecrets) }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with $cfg.extraInitContainers }}
Note: diff shown assumes PR #911 is merged. If #911 is still in review, the new block goes immediately after securityContext and before extraInitContainers.
Default behaviour with empty strings: default $.Values.serviceAccountName $cfg.serviceAccountName returns the global value when per-agent is unset or empty string. If both are empty, the if block is skipped entirely — no serviceAccountName is rendered, Kubernetes uses the cluster default SA, current behaviour is preserved exactly.
Usage:
# All agents share one SA
serviceAccountName: "openab"
# Per-agent override
agents:
claude:
serviceAccountName: "openab"
public-bot:
image: "ghcr.io/openabdev/openab-claude:latest"
# no serviceAccountName → inherits global or uses cluster default
Out of scope (possible follow-up): automountServiceAccountToken: false for agents that don't need Kubernetes API access. Not included here to keep the change minimal.
Prior Art
PR #901 (feat(openab): add existingSecret support for Slack agent credentials) and PR #911 / issue #910 (imagePullSecrets) follow the same per-agent K8s-native reference pattern. Backward-compatible: empty string = current behaviour.
Test Plan
Following the style of imagepullsecrets_test.yaml:
- Global
serviceAccountName renders serviceAccountName: in pod spec
- Per-agent
serviceAccountName overrides global
- Empty global + empty per-agent → no
serviceAccountName field rendered
- Per-agent empty string + non-empty global → global value used
Happy to send a PR.
Description
The chart has no
serviceAccountNamefield in the pod spec. Every agent pod runs as thedefaultservice account regardless of what's in values. This breaks IRSA (IAM Roles for Service Accounts) on EKS: thepod-identity-webhookonly injects AWS credentials (AWS_ROLE_ARN,AWS_WEB_IDENTITY_TOKEN_FILE) into pods whose service account carries aneks.amazonaws.com/role-arnannotation. Because the pod always usesdefault, IRSA is silently skipped — AWS CLI falls back to the broad EC2 node role instead of the intended least-privilege role.This is a security concern, not just an inconvenience: it violates the principle of least privilege and gives workloads broader AWS permissions than designed.
Use Case
Running openab inside an EKS cluster with IDP-managed service accounts. The IDP provisions a dedicated SA per workload (e.g.
openab) with an IRSA annotation scoped to a readonly IAM role (CloudWatch, EKS describe, EC2 describe). WithoutserviceAccountName, the pod usesdefault, IRSA never activates, andaws sts get-caller-identityreturns the node role instead of the intended role.In a multi-agent deployment, different agents may also need different SAs — Agent A uses the cluster default (no AWS access needed), Agent B uses a dedicated SA with IRSA.
Current Workaround
Manually patch the Deployment after every
helm upgrade:Wiped on every upgrade — identical problem to #910.
Proposed Solution
Scope: string reference to an existing ServiceAccount only. The chart will not create a new SA or manage IRSA annotations — operators provision the SA out-of-band (Terraform / IDP / kubectl). This keeps the chart's blast radius small and matches how
imagePullSecretsin #910 references existing secrets rather than creating them. Supporting chart-managed SA creation (with annotations, labels, ClusterRoleBindings) is a natural follow-up if there's demand.values.yaml:templates/deployment.yaml:spec: {{- with $.Values.podSecurityContext }} securityContext: {{- toYaml . | nindent 8 }} {{- end }} + {{- $svcAcct := default $.Values.serviceAccountName $cfg.serviceAccountName }} + {{- if $svcAcct }} + serviceAccountName: {{ $svcAcct }} + {{- end }} {{- with (default $.Values.imagePullSecrets $cfg.imagePullSecrets) }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} {{- with $cfg.extraInitContainers }}Default behaviour with empty strings:
default $.Values.serviceAccountName $cfg.serviceAccountNamereturns the global value when per-agent is unset or empty string. If both are empty, theifblock is skipped entirely — noserviceAccountNameis rendered, Kubernetes uses the cluster default SA, current behaviour is preserved exactly.Usage:
Out of scope (possible follow-up):
automountServiceAccountToken: falsefor agents that don't need Kubernetes API access. Not included here to keep the change minimal.Prior Art
PR #901 (
feat(openab): add existingSecret support for Slack agent credentials) and PR #911 / issue #910 (imagePullSecrets) follow the same per-agent K8s-native reference pattern. Backward-compatible: empty string = current behaviour.Test Plan
Following the style of
imagepullsecrets_test.yaml:serviceAccountNamerendersserviceAccountName:in pod specserviceAccountNameoverrides globalserviceAccountNamefield renderedHappy to send a PR.