Skip to content

feat(openab): add existingSecret support for Slack agent credentials #900

@antigenius0910

Description

@antigenius0910

Summary

Add agents.<name>.slack.existingSecret to the openab chart. When set, the chart references the named Kubernetes Secret for SLACK_BOT_TOKEN and SLACK_APP_TOKEN instead of creating a chart-managed Secret from Helm values.

This adapts the existingSecret pattern introduced in the openab-telegram chart (PR #873) to the multi-agent structure of the openab chart — scoped per-agent rather than chart-level.


Motivation

GitOps / External Secrets Operator deployments. In GitOps environments, secrets are typically managed by an operator such as External Secrets Operator (ESO) that syncs values from an external store (AWS Secrets Manager, Vault, etc.) into Kubernetes Secrets on a refresh interval.

The openab chart already supports this pattern for ANTHROPIC_API_KEY via secretEnv (rendered as valueFrom.secretKeyRef). Slack tokens currently cannot follow the same path because the chart creates its own Secret from Helm values, requiring them to be passed at helm install/helm upgrade time:

External Secret Store
  ├── ANTHROPIC_API_KEY  → secretEnv → valueFrom.secretKeyRef  ✅
  ├── SLACK_BOT_TOKEN    → must be passed as Helm value         ❌
  └── SLACK_APP_TOKEN    → must be passed as Helm value         ❌

This means every token rotation requires a Helm re-apply, blocking automation and making platform engineers a bottleneck for what should be a self-service operation.

With existingSecret, all credentials follow the same ESO path:

External Secret Store
  ├── ANTHROPIC_API_KEY  → secretEnv → valueFrom.secretKeyRef  ✅
  ├── SLACK_BOT_TOKEN    → existingSecret → valueFrom.secretKeyRef  ✅
  └── SLACK_APP_TOKEN    → existingSecret → valueFrom.secretKeyRef  ✅

When the external store is updated, ESO syncs the Kubernetes Secret within its refresh interval. The pod picks up the new tokens on its next restart — no Helm re-apply, no manual intervention.


Self-Service Bot Platform Context

We deploy OpenAB as a multi-tenant platform where each team owns a skill repo — a Git repository containing a SKILL.md that defines the bot's behaviour. When a team merges a change to SKILL.md, a CI job automatically restarts the pod (~90 seconds end-to-end). Teams iterate on bot behaviour with zero platform involvement.

The infrastructure workload (namespace, ServiceAccount, ESO ExternalSecret, Helm release) is provisioned once via a standard Terraform module. After that initial setup, the platform team should never need to touch it again — but today they must re-apply whenever Slack tokens rotate, because of helm_release_set_sensitive.

existingSecret closes this last gap:

Team rotates Slack token in external store
    → ESO syncs K8s Secret (automatic, within refresh interval)
    → Team restarts pod (or waits for next skill update to trigger restart)
    → Bot reconnects with new token
    → Platform team involvement: zero

Proposed Change

values.yaml

agents:
  claude:
    slack:
      enabled: false
      botToken: ""    # used only when existingSecret is not set
      appToken: ""    # used only when existingSecret is not set
      existingSecret: ""  # if set: reference this K8s Secret for slack-bot-token
                          #         and slack-app-token; skip chart-managed Secret

templates/secret.yaml (change)

The current $hasSlack variable is:

$hasSlack := and ($cfg.slack).enabled (or ($cfg.slack).botToken ($cfg.slack).appToken)

When existingSecret is set and both botToken/appToken are left empty, $hasSlack is already false — for Slack-only agents, the chart-managed Secret is not created at all. For agents that also use Discord, STT, or gateway, the chart-managed Secret is still created for those tokens; only the Slack entries are suppressed.

Guard the Slack entries explicitly:

{{- if and ($cfg.slack).enabled ($cfg.slack).botToken (not ($cfg.slack).existingSecret) }}
slack-bot-token: {{ $cfg.slack.botToken | b64enc | quote }}
{{- end }}
{{- if and ($cfg.slack).enabled ($cfg.slack).appToken (not ($cfg.slack).existingSecret) }}
slack-app-token: {{ $cfg.slack.appToken | b64enc | quote }}
{{- end }}

templates/_helpers.tpl (addition)

{{/*
Return the secret name to use for Slack credentials.
If existingSecret is set, use it; otherwise fall back to the chart-managed agent secret.
*/}}
{{- define "openab.slackSecretName" -}}
{{- if .cfg.slack.existingSecret -}}
{{ .cfg.slack.existingSecret }}
{{- else -}}
{{ include "openab.agentFullname" (dict "ctx" .root "agent" .agent "cfg" .cfg) }}
{{- end -}}
{{- end -}}

templates/deployment.yaml (change)

Use the helper for the Slack secretKeyRef names:

{{- if ($cfg.slack).enabled }}
- name: SLACK_BOT_TOKEN
  valueFrom:
    secretKeyRef:
      name: {{ include "openab.slackSecretName" (dict "root" $ "agent" $name "cfg" $cfg) }}
      key: slack-bot-token
- name: SLACK_APP_TOKEN
  valueFrom:
    secretKeyRef:
      name: {{ include "openab.slackSecretName" (dict "root" $ "agent" $name "cfg" $cfg) }}
      key: slack-app-token
{{- end }}

Expected secret key names

The existing secret must contain:

Key Value
slack-bot-token Bot User OAuth Token (xoxb-...)
slack-app-token App-Level Token (xapp-...)

These match the keys the chart already manages internally today — only the source of the Secret changes.


Behavior by agent configuration

Agent uses existingSecret set? Result
Slack only No Chart creates agent Secret with slack-bot-token, slack-app-token (current behavior)
Slack only Yes No chart-managed Secret created; deployment references existingSecret directly
Slack + Discord No Chart creates agent Secret with all tokens (current behavior)
Slack + Discord Yes Chart creates agent Secret with discord-bot-token only; deployment references existingSecret for Slack keys

The dual-secret case (Slack + Discord with existingSecret) is intentional: each token type can be managed independently.


Adaptation from openab-telegram

The openab-telegram chart (PR #873) implements existingSecret at chart level (single-agent chart):

# openab-telegram: chart-level, single agent
existingSecret: "my-bot-creds"

The openab chart is multi-agent, so the field is scoped per-agent:

# openab: per-agent, consistent with other per-agent fields
agents:
  claude:
    slack:
      existingSecret: "my-bot-creds"

The underlying mechanism is identical: when existingSecret is set, the chart skips creating the Secret and references the named Secret in secretKeyRef.


Backwards Compatibility

  • existingSecret defaults to "" — no change in behavior for any existing deployment.
  • botToken and appToken fields remain; they are used when existingSecret is unset.
  • No breaking change.

Validation

When existingSecret is set:

  • botToken and appToken are ignored (no validation required).
  • Optionally emit a Helm note if both existingSecret and botToken/appToken are non-empty simultaneously, to surface potential misconfiguration.

Testing

  • existingSecret unset — existing behavior unchanged (chart creates Secret from values)
  • existingSecret set, Slack-only agent — no chart-managed Secret created; bot connects using tokens from external secret
  • existingSecret set, Slack + Discord agent — chart-managed Secret contains only discord-bot-token; Slack env vars reference external secret
  • existingSecret set, referenced secret missing — pod fails with clear secretKeyRef error (expected Kubernetes behavior)
  • Token rotated in external store, pod restarted — bot reconnects with new token

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions