From 454b4965c020fabfd8e73c2c14edee9c6a94d3e0 Mon Sep 17 00:00:00 2001 From: masami-agent Date: Fri, 1 May 2026 07:16:36 +0000 Subject: [PATCH 1/3] docs(teams): add enterprise K8s deployment guide + Helm chart support - Add docs/msteams-enterprise.md: Azure Entra ID setup, Bot Service, Teams app manifest, Helm deployment, IT admin approval flow, tenant allowlist, sovereign cloud config - Add gateway.teams.* values to Helm chart (appId, appSecret, oauthEndpoint, openidMetadata, allowedTenants, webhookPath) - Add TEAMS_* env vars to deployment template (secret-backed) - Add teams-app-secret to secret template Closes #549 --- charts/openab/templates/deployment.yaml | 25 ++ charts/openab/templates/secret.yaml | 6 +- charts/openab/values.yaml | 11 + docs/msteams-enterprise.md | 341 ++++++++++++++++++++++++ 4 files changed, 382 insertions(+), 1 deletion(-) create mode 100644 docs/msteams-enterprise.md diff --git a/charts/openab/templates/deployment.yaml b/charts/openab/templates/deployment.yaml index 4484820b..c7d5806e 100644 --- a/charts/openab/templates/deployment.yaml +++ b/charts/openab/templates/deployment.yaml @@ -77,6 +77,31 @@ spec: name: {{ include "openab.agentFullname" $d }} key: gateway-ws-token {{- end }} + {{- if and ($cfg.gateway).enabled (($cfg.gateway).teams).appId }} + - name: TEAMS_APP_ID + value: {{ ($cfg.gateway).teams.appId | quote }} + - name: TEAMS_APP_SECRET + valueFrom: + secretKeyRef: + name: {{ include "openab.agentFullname" $d }} + key: teams-app-secret + {{- if ($cfg.gateway).teams.oauthEndpoint }} + - name: TEAMS_OAUTH_ENDPOINT + value: {{ ($cfg.gateway).teams.oauthEndpoint | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.openidMetadata }} + - name: TEAMS_OPENID_METADATA + value: {{ ($cfg.gateway).teams.openidMetadata | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.allowedTenants }} + - name: TEAMS_ALLOWED_TENANTS + value: {{ ($cfg.gateway).teams.allowedTenants | join "," | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.webhookPath }} + - name: TEAMS_WEBHOOK_PATH + value: {{ ($cfg.gateway).teams.webhookPath | quote }} + {{- end }} + {{- end }} - name: HOME value: {{ $cfg.workingDir | default "/home/agent" }} {{- range $k, $v := $cfg.env }} diff --git a/charts/openab/templates/secret.yaml b/charts/openab/templates/secret.yaml index 4dc5ba87..9544a011 100644 --- a/charts/openab/templates/secret.yaml +++ b/charts/openab/templates/secret.yaml @@ -4,7 +4,8 @@ {{- $hasSlack := and ($cfg.slack).enabled (or ($cfg.slack).botToken ($cfg.slack).appToken) }} {{- $hasStt := and ($cfg.stt).enabled ($cfg.stt).apiKey }} {{- $hasGateway := and ($cfg.gateway).enabled ($cfg.gateway).token }} -{{- if or $hasDiscord $hasSlack $hasStt $hasGateway }} +{{- $hasTeams := and ($cfg.gateway).enabled (($cfg.gateway).teams).appSecret }} +{{- if or $hasDiscord $hasSlack $hasStt $hasGateway $hasTeams }} {{- $d := dict "ctx" $ "agent" $name "cfg" $cfg }} --- apiVersion: v1 @@ -32,6 +33,9 @@ data: {{- if $hasGateway }} gateway-ws-token: {{ $cfg.gateway.token | b64enc | quote }} {{- end }} + {{- if $hasTeams }} + teams-app-secret: {{ ($cfg.gateway).teams.appSecret | b64enc | quote }} + {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/charts/openab/values.yaml b/charts/openab/values.yaml index 54661a38..9cf3e4e0 100644 --- a/charts/openab/values.yaml +++ b/charts/openab/values.yaml @@ -199,6 +199,17 @@ agents: platform: "telegram" # default platform when gateway is enabled token: "" # optional shared secret (injected via GATEWAY_WS_TOKEN env var) botUsername: "" # optional, for @mention gating + # MS Teams adapter config (gateway-side env vars) + # See docs/msteams-enterprise.md for full setup guide + teams: + appId: "" # Azure Entra ID application (client) ID → TEAMS_APP_ID + appSecret: "" # Azure Entra ID client secret → TEAMS_APP_SECRET + # ⚠️ Required for Single Tenant bots — use tenant-specific endpoint + # Default (Multi Tenant): https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token + oauthEndpoint: "" # → TEAMS_OAUTH_ENDPOINT + openidMetadata: "" # Override for sovereign clouds → TEAMS_OPENID_METADATA + allowedTenants: [] # Comma-separated tenant IDs → TEAMS_ALLOWED_TENANTS + webhookPath: "" # Default: /webhook/teams → TEAMS_WEBHOOK_PATH # Scheduled messages — config-driven cron (ADR: basic-cronjob) # Each entry sends a message to the agent at the specified schedule. # Example: diff --git a/docs/msteams-enterprise.md b/docs/msteams-enterprise.md new file mode 100644 index 00000000..be614c22 --- /dev/null +++ b/docs/msteams-enterprise.md @@ -0,0 +1,341 @@ +# Microsoft Teams Enterprise Deployment + +Deploy OpenAB with MS Teams in an enterprise Kubernetes environment using Helm. This guide covers Azure Entra ID configuration, Azure Bot Service setup, Teams app packaging, and Helm-based deployment. + +``` +Teams Client → Bot Framework → K8s Ingress (HTTPS + TLS) → Gateway pod → OAB pod + ↑ + Company's existing infrastructure +``` + +## Prerequisites + +- An Azure subscription with permissions to create resources +- A Microsoft 365 tenant with Teams enabled +- A Kubernetes cluster with an Ingress controller and TLS (e.g. AKS, EKS, GKE, on-prem) +- `kubectl` and `helm` CLI tools +- IT admin access to Teams Admin Center (for app approval) + +## Architecture Overview + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Your Kubernetes Cluster │ +│ │ +│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ +│ │ Ingress │───▶│ Gateway │◀──▶│ OAB │ │ +│ │ (HTTPS/TLS) │ │ Pod │ WS │ Pod │ │ +│ └──────┬───────┘ └──────────────┘ └──────────────┘ │ +│ │ │ +└─────────┼───────────────────────────────────────────────────┘ + │ HTTPS +┌─────────┴───────────┐ +│ Bot Framework │ +│ (Microsoft Cloud) │ +└─────────────────────┘ +``` + +- **Ingress** terminates TLS and routes `/webhook/teams` to the Gateway pod +- **Gateway pod** validates JWT, normalizes events, routes replies via Bot Framework REST API +- **OAB pod** connects outbound to Gateway via WebSocket — no inbound ports needed + +## Step 1: Register an Azure Entra ID Application + +1. Go to [Azure Portal → Microsoft Entra ID → App registrations](https://portal.azure.com/#blade/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) +2. Click **New registration** +3. Configure: + - **Name**: `openab-teams-bot` (or your preferred name) + - **Supported account types**: **Single tenant** (Accounts in this organizational directory only) + - **Redirect URI**: leave empty +4. Click **Register** + +After creation, note from the **Overview** page: + +| Value | Used As | +|---|---| +| Application (client) ID | `TEAMS_APP_ID` | +| Directory (tenant) ID | `` in OAuth endpoint | + +### Create a Client Secret + +1. Go to **Certificates & secrets** → **Client secrets** → **New client secret** +2. Set a description and expiration (recommended: 12 or 24 months) +3. Click **Add** +4. **Copy the Value immediately** — it is only shown once → `TEAMS_APP_SECRET` + +> **Security note**: Store the client secret in a Kubernetes Secret. Never commit it to source control. Set a calendar reminder to rotate before expiration. + +## Step 2: Create an Azure Bot Resource + +1. Go to [Azure Portal → Create a resource](https://portal.azure.com/#create/hub) → search **Azure Bot** → **Create** +2. Configure: + - **Bot handle**: a unique name (e.g. `openab-prod`) + - **Subscription / Resource group**: your enterprise subscription + - **Pricing tier**: F0 (free) for testing, S1 for production + - **Type of App**: **Single Tenant** + - **Creation type**: **Use existing app registration** + - **App ID**: paste `TEAMS_APP_ID` from Step 1 + - **App tenant ID**: paste your Directory (tenant) ID +3. Click **Review + Create** → **Create** + +> **Note**: Multi-tenant bot creation was deprecated by Microsoft on July 31, 2025. Single Tenant is the recommended path. Cross-tenant access is achieved via Teams App Store publishing. + +### Configure the Messaging Endpoint + +1. Go to the Bot resource → **Configuration** +2. Set **Messaging endpoint** to your Kubernetes Ingress URL: + ``` + https:///webhook/teams + ``` + +### Enable the Teams Channel + +1. Go to **Channels** → click **Microsoft Teams** +2. Accept the terms of service → **Save** + +## Step 3: Build a Teams App Manifest + +Create a directory with three files: + +### `manifest.json` + +```json +{ + "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.25/MicrosoftTeams.schema.json", + "manifestVersion": "1.25", + "version": "1.0.0", + "id": "", + "developer": { + "name": "", + "websiteUrl": "https://", + "privacyUrl": "https:///privacy", + "termsOfUseUrl": "https:///terms" + }, + "name": { + "short": "OpenAB", + "full": "OpenAB AI Assistant" + }, + "description": { + "short": "AI coding assistant powered by OpenAB", + "full": "Connect to an AI coding assistant through Microsoft Teams." + }, + "icons": { + "outline": "outline.png", + "color": "color.png" + }, + "accentColor": "#ffffff", + "bots": [ + { + "botId": "", + "scopes": ["personal", "team", "groupChat"], + "isNotificationOnly": false, + "supportsFiles": false + } + ], + "validDomains": [] +} +``` + +- `id` — Teams app ID (generate a fresh UUID v4, not the same as `botId`) +- `botId` — Azure Entra ID Application (client) ID from Step 1 + +### Icons + +- `outline.png` — 32×32 transparent background, white icon +- `color.png` — 192×192 full-color icon + +### Package + +```bash +zip openab-teams-app.zip manifest.json outline.png color.png +``` + +## Step 4: Deploy with Helm + +### Install the Gateway + OAB + +```bash +helm install openab oci://ghcr.io/openabdev/charts/openab \ + --set agents.kiro.gateway.enabled=true \ + --set agents.kiro.gateway.url="ws://openab-gateway:8080/ws" \ + --set agents.kiro.gateway.platform="teams" \ + --set agents.kiro.gateway.teams.appId="" \ + --set-literal agents.kiro.gateway.teams.appSecret="" \ + --set agents.kiro.gateway.teams.oauthEndpoint="https://login.microsoftonline.com//oauth2/v2.0/token" \ + --set-string agents.kiro.gateway.teams.allowedTenants[0]="" +``` + +> **Single Tenant bots must set `oauthEndpoint`** to the tenant-specific endpoint. The default (`botframework.com`) only works for Multi Tenant bots and will cause `401 Unauthorized` errors. + +> **Use `--set-literal` for `appSecret`** — the secret may contain `.` characters that Helm interprets as nested key separators. + +### Helm Values Reference + +```yaml +agents: + kiro: + gateway: + enabled: true + url: "ws://openab-gateway:8080/ws" + platform: "teams" + teams: + appId: "" # Azure Entra ID application (client) ID + appSecret: "" # Azure Entra ID client secret + oauthEndpoint: "" # Required for Single Tenant + openidMetadata: "" # Override for sovereign clouds + allowedTenants: [] # Restrict to specific tenants + webhookPath: "/webhook/teams" +``` + +### Ingress Configuration + +Route Bot Framework webhooks to the Gateway pod using your existing Ingress controller: + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: openab-gateway + annotations: + # Adjust for your Ingress controller (nginx, ALB, Traefik, etc.) + nginx.ingress.kubernetes.io/ssl-redirect: "true" +spec: + tls: + - hosts: + - + secretName: + rules: + - host: + http: + paths: + - path: /webhook/teams + pathType: Prefix + backend: + service: + name: openab-gateway + port: + number: 8080 +``` + +> Bot Framework requires HTTPS. Your Ingress controller handles TLS termination — the Gateway pod listens on plain HTTP (:8080). + +## Step 5: IT Admin — Approve the Teams App + +Enterprise tenants typically restrict custom app installation. An IT admin must approve the app. + +### Upload the App Package + +1. Go to [Teams Admin Center](https://admin.teams.microsoft.com/) → **Teams apps** → **Manage apps** +2. Click **Upload new app** → select `openab-teams-app.zip` +3. The app appears with status **Blocked** (default for new custom apps) + +### Configure Permission Policies + +1. Go to **Teams apps** → **Permission policies** +2. Edit the **Global (Org-wide default)** policy or create a new one: + - Under **Custom apps**, allow the OpenAB app +3. If using a custom policy, assign it to target users or groups + +### Configure Setup Policies (Optional) + +To pin the app for users automatically: + +1. Go to **Teams apps** → **Setup policies** +2. Edit the relevant policy → **Installed apps** → **Add apps** → select OpenAB +3. Optionally add to **Pinned apps** for sidebar visibility + +### Verify + +After policy propagation (may take up to 24 hours): + +1. Users go to **Apps** → **Built for your org** → find OpenAB → **Add** +2. For personal chat: open the app and start chatting +3. For channels: add the app to a team → use `@OpenAB` to mention the bot + +## Tenant Allowlist + +Restrict which Azure AD tenants can interact with the bot: + +```bash +--set-string agents.kiro.gateway.teams.allowedTenants[0]="" \ +--set-string agents.kiro.gateway.teams.allowedTenants[1]="" +``` + +If not set, all tenants are allowed. + +## Sovereign Cloud Configuration + +For Azure Government or Azure China deployments: + +| Cloud | `oauthEndpoint` | `openidMetadata` | +|---|---|---| +| Public (default) | `login.microsoftonline.com//...` | `login.botframework.com/...` | +| Azure Government | `login.microsoftonline.us//...` | `login.botframework.azure.us/...` | +| Azure China (21Vianet) | `login.partner.microsoftonline.cn//...` | `login.botframework.azure.cn/...` | + +```bash +# Azure Government example +--set agents.kiro.gateway.teams.oauthEndpoint="https://login.microsoftonline.us//oauth2/v2.0/token" \ +--set agents.kiro.gateway.teams.openidMetadata="https://login.botframework.azure.us/v1/.well-known/openidconfiguration" +``` + +## Environment Variables Reference + +| Variable | Required | Default | Description | +|---|---|---|---| +| `TEAMS_APP_ID` | Yes | — | Azure Entra ID application (client) ID | +| `TEAMS_APP_SECRET` | Yes | — | Azure Entra ID client secret | +| `TEAMS_OAUTH_ENDPOINT` | Yes (Single Tenant) | `https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token` | Tenant-specific OAuth endpoint | +| `TEAMS_OPENID_METADATA` | No | `https://login.botframework.com/v1/.well-known/openidconfiguration` | OpenID metadata for JWT validation | +| `TEAMS_ALLOWED_TENANTS` | No | (allow all) | Comma-separated tenant IDs | +| `TEAMS_WEBHOOK_PATH` | No | `/webhook/teams` | Webhook endpoint path | + +## Troubleshooting + +### 401 Unauthorized when bot tries to reply + +OAuth endpoint mismatch. Single Tenant bots must use the tenant-specific endpoint. + +**Fix**: Verify `oauthEndpoint` is set to `https://login.microsoftonline.com//oauth2/v2.0/token` + +### Bot doesn't appear in Teams + +IT admin has not approved the custom app, or permission policy hasn't propagated. + +**Fix**: +1. Verify the app is uploaded in Teams Admin Center → Manage apps +2. Check Permission policies allow the custom app +3. Wait up to 24 hours for policy propagation + +### Gateway receives webhook but no reply in Teams + +Check Gateway pod logs: +```bash +kubectl logs deployment/openab-gateway --tail=50 +``` + +Look for: `teams → gateway` (received) → `gateway → teams` (sent) → `teams activity sent` (success) or `teams send error` (failure). + +### JWT validation failed + +The Gateway auto-refreshes JWKS on cache miss. If persistent, verify OpenID metadata is reachable: +```bash +kubectl exec deployment/openab-gateway -- curl -s https://login.botframework.com/v1/.well-known/openidconfiguration +``` + +## Security Considerations + +- **Credentials in Kubernetes Secrets** — Helm chart stores `TEAMS_APP_SECRET` in a K8s Secret, not in ConfigMap +- **Rotate client secrets** before expiration — set a reminder based on the expiration chosen in Step 1 +- **Use tenant allowlist** in production — restrict to your organization's tenant ID +- **Network policies** — consider restricting Gateway pod egress to Bot Framework endpoints +- **OAB pod has no inbound exposure** — connects outbound to Gateway only + +## References + +- [Azure Bot Service documentation](https://learn.microsoft.com/en-us/azure/bot-service/) +- [Register a bot with Azure](https://learn.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) +- [Teams app permission policies](https://learn.microsoft.com/en-us/microsoftteams/teams-app-permission-policies) +- [Teams custom app policies](https://learn.microsoft.com/en-us/microsoftteams/teams-custom-app-policies-and-settings) +- [Bot Framework authentication](https://learn.microsoft.com/en-us/azure/bot-service/rest-api/bot-framework-rest-connector-authentication) +- [Teams app manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) From e88cbaf5a3ad7483b92c26572d8ded9b6c04ff22 Mon Sep 17 00:00:00 2001 From: masami-agent Date: Fri, 1 May 2026 10:28:49 +0000 Subject: [PATCH 2/3] fix(helm): add gateway Deployment/Service, fix env var injection target MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address four-monk review findings: 🔴🔴 BLOCKER — TEAMS_* env vars were injected into agent container instead of gateway. Fixed by: - Add gateway.yaml: Gateway Deployment + Service (created when gateway.enabled=true) - Add gateway-secret.yaml: Gateway-specific secrets - Remove TEAMS_* from agent deployment.yaml and secret.yaml 🔴 condition mismatch — deployment checked appId, secret checked appSecret. Fixed: both gateway templates use unified condition (appId AND appSecret). 🟡 NITs: - Fix allowedTenants comment: 'Comma-separated' → 'List of' - Fix docs webhookPath default mismatch - Add gateway image/tag to values.yaml - Fix docs Ingress service name to match Helm-generated name --- charts/openab/templates/deployment.yaml | 25 ----- charts/openab/templates/gateway-secret.yaml | 26 +++++ charts/openab/templates/gateway.yaml | 110 ++++++++++++++++++++ charts/openab/templates/secret.yaml | 6 +- charts/openab/values.yaml | 4 +- docs/msteams-enterprise.md | 14 ++- 6 files changed, 149 insertions(+), 36 deletions(-) create mode 100644 charts/openab/templates/gateway-secret.yaml create mode 100644 charts/openab/templates/gateway.yaml diff --git a/charts/openab/templates/deployment.yaml b/charts/openab/templates/deployment.yaml index c7d5806e..4484820b 100644 --- a/charts/openab/templates/deployment.yaml +++ b/charts/openab/templates/deployment.yaml @@ -77,31 +77,6 @@ spec: name: {{ include "openab.agentFullname" $d }} key: gateway-ws-token {{- end }} - {{- if and ($cfg.gateway).enabled (($cfg.gateway).teams).appId }} - - name: TEAMS_APP_ID - value: {{ ($cfg.gateway).teams.appId | quote }} - - name: TEAMS_APP_SECRET - valueFrom: - secretKeyRef: - name: {{ include "openab.agentFullname" $d }} - key: teams-app-secret - {{- if ($cfg.gateway).teams.oauthEndpoint }} - - name: TEAMS_OAUTH_ENDPOINT - value: {{ ($cfg.gateway).teams.oauthEndpoint | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.openidMetadata }} - - name: TEAMS_OPENID_METADATA - value: {{ ($cfg.gateway).teams.openidMetadata | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.allowedTenants }} - - name: TEAMS_ALLOWED_TENANTS - value: {{ ($cfg.gateway).teams.allowedTenants | join "," | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.webhookPath }} - - name: TEAMS_WEBHOOK_PATH - value: {{ ($cfg.gateway).teams.webhookPath | quote }} - {{- end }} - {{- end }} - name: HOME value: {{ $cfg.workingDir | default "/home/agent" }} {{- range $k, $v := $cfg.env }} diff --git a/charts/openab/templates/gateway-secret.yaml b/charts/openab/templates/gateway-secret.yaml new file mode 100644 index 00000000..5b2c26fc --- /dev/null +++ b/charts/openab/templates/gateway-secret.yaml @@ -0,0 +1,26 @@ +{{- range $name, $cfg := .Values.agents }} +{{- if and (ne (include "openab.agentEnabled" $cfg) "false") ($cfg.gateway).enabled }} +{{- $d := dict "ctx" $ "agent" (printf "%s-gateway" $name) "cfg" $cfg }} +{{- $hasToken := ($cfg.gateway).token }} +{{- $hasTeams := and (($cfg.gateway).teams).appId (($cfg.gateway).teams).appSecret }} +{{- if or $hasToken $hasTeams }} +--- +apiVersion: v1 +kind: Secret +metadata: + name: {{ include "openab.agentFullname" $d }} + labels: + {{- include "openab.labels" $d | nindent 4 }} + annotations: + "helm.sh/resource-policy": keep +type: Opaque +data: + {{- if $hasToken }} + gateway-ws-token: {{ $cfg.gateway.token | b64enc | quote }} + {{- end }} + {{- if $hasTeams }} + teams-app-secret: {{ ($cfg.gateway).teams.appSecret | b64enc | quote }} + {{- end }} +{{- end }} +{{- end }} +{{- end }} diff --git a/charts/openab/templates/gateway.yaml b/charts/openab/templates/gateway.yaml new file mode 100644 index 00000000..27ae60f7 --- /dev/null +++ b/charts/openab/templates/gateway.yaml @@ -0,0 +1,110 @@ +{{- range $name, $cfg := .Values.agents }} +{{- if and (ne (include "openab.agentEnabled" $cfg) "false") ($cfg.gateway).enabled }} +{{- $d := dict "ctx" $ "agent" (printf "%s-gateway" $name) "cfg" $cfg }} +{{- $hasTeams := and (($cfg.gateway).teams).appId (($cfg.gateway).teams).appSecret }} +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "openab.agentFullname" $d }} + labels: + {{- include "openab.labels" $d | nindent 4 }} +spec: + replicas: 1 + strategy: + type: Recreate + selector: + matchLabels: + {{- include "openab.selectorLabels" $d | nindent 6 }} + template: + metadata: + labels: + {{- include "openab.selectorLabels" $d | nindent 8 }} + spec: + {{- with $.Values.podSecurityContext }} + securityContext: + {{- toYaml . | nindent 8 }} + {{- end }} + containers: + - name: gateway + image: {{ printf "%s:%s" (($cfg.gateway).image | default "ghcr.io/openabdev/openab-gateway") (($cfg.gateway).tag | default $.Chart.AppVersion) }} + imagePullPolicy: {{ $.Values.image.pullPolicy }} + {{- with $.Values.containerSecurityContext }} + securityContext: + {{- toYaml . | nindent 12 }} + {{- end }} + ports: + - name: http + containerPort: 8080 + protocol: TCP + env: + {{- if ($cfg.gateway).token }} + - name: GATEWAY_WS_TOKEN + valueFrom: + secretKeyRef: + name: {{ include "openab.agentFullname" $d }} + key: gateway-ws-token + {{- end }} + {{- if $hasTeams }} + - name: TEAMS_APP_ID + value: {{ ($cfg.gateway).teams.appId | quote }} + - name: TEAMS_APP_SECRET + valueFrom: + secretKeyRef: + name: {{ include "openab.agentFullname" $d }} + key: teams-app-secret + {{- if ($cfg.gateway).teams.oauthEndpoint }} + - name: TEAMS_OAUTH_ENDPOINT + value: {{ ($cfg.gateway).teams.oauthEndpoint | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.openidMetadata }} + - name: TEAMS_OPENID_METADATA + value: {{ ($cfg.gateway).teams.openidMetadata | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.allowedTenants }} + - name: TEAMS_ALLOWED_TENANTS + value: {{ ($cfg.gateway).teams.allowedTenants | join "," | quote }} + {{- end }} + {{- if ($cfg.gateway).teams.webhookPath }} + - name: TEAMS_WEBHOOK_PATH + value: {{ ($cfg.gateway).teams.webhookPath | quote }} + {{- end }} + {{- end }} + - name: RUST_LOG + value: "info" + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 30 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 3 + periodSeconds: 10 + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + memory: 128Mi +--- +apiVersion: v1 +kind: Service +metadata: + name: {{ include "openab.agentFullname" $d }} + labels: + {{- include "openab.labels" $d | nindent 4 }} +spec: + type: ClusterIP + ports: + - port: 8080 + targetPort: http + protocol: TCP + name: http + selector: + {{- include "openab.selectorLabels" $d | nindent 4 }} +{{- end }} +{{- end }} diff --git a/charts/openab/templates/secret.yaml b/charts/openab/templates/secret.yaml index 9544a011..4dc5ba87 100644 --- a/charts/openab/templates/secret.yaml +++ b/charts/openab/templates/secret.yaml @@ -4,8 +4,7 @@ {{- $hasSlack := and ($cfg.slack).enabled (or ($cfg.slack).botToken ($cfg.slack).appToken) }} {{- $hasStt := and ($cfg.stt).enabled ($cfg.stt).apiKey }} {{- $hasGateway := and ($cfg.gateway).enabled ($cfg.gateway).token }} -{{- $hasTeams := and ($cfg.gateway).enabled (($cfg.gateway).teams).appSecret }} -{{- if or $hasDiscord $hasSlack $hasStt $hasGateway $hasTeams }} +{{- if or $hasDiscord $hasSlack $hasStt $hasGateway }} {{- $d := dict "ctx" $ "agent" $name "cfg" $cfg }} --- apiVersion: v1 @@ -33,9 +32,6 @@ data: {{- if $hasGateway }} gateway-ws-token: {{ $cfg.gateway.token | b64enc | quote }} {{- end }} - {{- if $hasTeams }} - teams-app-secret: {{ ($cfg.gateway).teams.appSecret | b64enc | quote }} - {{- end }} {{- end }} {{- end }} {{- end }} diff --git a/charts/openab/values.yaml b/charts/openab/values.yaml index 9cf3e4e0..dcf8c124 100644 --- a/charts/openab/values.yaml +++ b/charts/openab/values.yaml @@ -199,6 +199,8 @@ agents: platform: "telegram" # default platform when gateway is enabled token: "" # optional shared secret (injected via GATEWAY_WS_TOKEN env var) botUsername: "" # optional, for @mention gating + image: "ghcr.io/openabdev/openab-gateway" # gateway container image + tag: "" # defaults to Chart.AppVersion # MS Teams adapter config (gateway-side env vars) # See docs/msteams-enterprise.md for full setup guide teams: @@ -208,7 +210,7 @@ agents: # Default (Multi Tenant): https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token oauthEndpoint: "" # → TEAMS_OAUTH_ENDPOINT openidMetadata: "" # Override for sovereign clouds → TEAMS_OPENID_METADATA - allowedTenants: [] # Comma-separated tenant IDs → TEAMS_ALLOWED_TENANTS + allowedTenants: [] # List of tenant IDs → TEAMS_ALLOWED_TENANTS webhookPath: "" # Default: /webhook/teams → TEAMS_WEBHOOK_PATH # Scheduled messages — config-driven cron (ADR: basic-cronjob) # Each entry sends a message to the agent at the specified schedule. diff --git a/docs/msteams-enterprise.md b/docs/msteams-enterprise.md index be614c22..2918c7f3 100644 --- a/docs/msteams-enterprise.md +++ b/docs/msteams-enterprise.md @@ -157,7 +157,7 @@ zip openab-teams-app.zip manifest.json outline.png color.png ```bash helm install openab oci://ghcr.io/openabdev/charts/openab \ --set agents.kiro.gateway.enabled=true \ - --set agents.kiro.gateway.url="ws://openab-gateway:8080/ws" \ + --set agents.kiro.gateway.url="ws://openab-kiro-gateway:8080/ws" \ --set agents.kiro.gateway.platform="teams" \ --set agents.kiro.gateway.teams.appId="" \ --set-literal agents.kiro.gateway.teams.appSecret="" \ @@ -165,6 +165,8 @@ helm install openab oci://ghcr.io/openabdev/charts/openab \ --set-string agents.kiro.gateway.teams.allowedTenants[0]="" ``` +> The gateway Service name follows the pattern `--gateway` (e.g. `openab-kiro-gateway`). The chart automatically creates the gateway Deployment + Service when `gateway.enabled=true`. + > **Single Tenant bots must set `oauthEndpoint`** to the tenant-specific endpoint. The default (`botframework.com`) only works for Multi Tenant bots and will cause `401 Unauthorized` errors. > **Use `--set-literal` for `appSecret`** — the secret may contain `.` characters that Helm interprets as nested key separators. @@ -176,15 +178,17 @@ agents: kiro: gateway: enabled: true - url: "ws://openab-gateway:8080/ws" + url: "ws://openab-kiro-gateway:8080/ws" platform: "teams" + image: "ghcr.io/openabdev/openab-gateway" # gateway container image + tag: "" # defaults to Chart.AppVersion teams: appId: "" # Azure Entra ID application (client) ID appSecret: "" # Azure Entra ID client secret oauthEndpoint: "" # Required for Single Tenant openidMetadata: "" # Override for sovereign clouds - allowedTenants: [] # Restrict to specific tenants - webhookPath: "/webhook/teams" + allowedTenants: [] # List of tenant IDs + webhookPath: "" # Gateway default: /webhook/teams ``` ### Ingress Configuration @@ -212,7 +216,7 @@ spec: pathType: Prefix backend: service: - name: openab-gateway + name: openab-kiro-gateway # matches Helm-generated Service name port: number: 8080 ``` From 5544037c34254ef5d23d6ee77cea2fac5312e57d Mon Sep 17 00:00:00 2001 From: masami-agent Date: Fri, 1 May 2026 10:51:17 +0000 Subject: [PATCH 3/3] refactor: rescope to docs-only, BYO gateway approach Per maintainer direction: PR #674 is docs-only. - Revert gateway.yaml, gateway-secret.yaml (gateway Helm templates) - Revert deployment.yaml, secret.yaml, values.yaml to main - Rescope docs/msteams-enterprise.md to BYO gateway: - Gateway deployed as standalone K8s Deployment (not via Helm chart) - OAB deployed via Helm chart (gateway.url points to gateway Service) - TEAMS_* env vars in Gateway Secret (not agent pod) - Add E2E test findings: Web Chat limitation, bot discovery methods, M365 license requirement, Open in Teams link for quick testing - Gateway Helm chart templates tracked as follow-up Addresses maintainer response and four-monk review. --- charts/openab/templates/gateway-secret.yaml | 26 --- charts/openab/templates/gateway.yaml | 110 ------------ charts/openab/values.yaml | 13 -- docs/msteams-enterprise.md | 178 +++++++++++++------- 4 files changed, 115 insertions(+), 212 deletions(-) delete mode 100644 charts/openab/templates/gateway-secret.yaml delete mode 100644 charts/openab/templates/gateway.yaml diff --git a/charts/openab/templates/gateway-secret.yaml b/charts/openab/templates/gateway-secret.yaml deleted file mode 100644 index 5b2c26fc..00000000 --- a/charts/openab/templates/gateway-secret.yaml +++ /dev/null @@ -1,26 +0,0 @@ -{{- range $name, $cfg := .Values.agents }} -{{- if and (ne (include "openab.agentEnabled" $cfg) "false") ($cfg.gateway).enabled }} -{{- $d := dict "ctx" $ "agent" (printf "%s-gateway" $name) "cfg" $cfg }} -{{- $hasToken := ($cfg.gateway).token }} -{{- $hasTeams := and (($cfg.gateway).teams).appId (($cfg.gateway).teams).appSecret }} -{{- if or $hasToken $hasTeams }} ---- -apiVersion: v1 -kind: Secret -metadata: - name: {{ include "openab.agentFullname" $d }} - labels: - {{- include "openab.labels" $d | nindent 4 }} - annotations: - "helm.sh/resource-policy": keep -type: Opaque -data: - {{- if $hasToken }} - gateway-ws-token: {{ $cfg.gateway.token | b64enc | quote }} - {{- end }} - {{- if $hasTeams }} - teams-app-secret: {{ ($cfg.gateway).teams.appSecret | b64enc | quote }} - {{- end }} -{{- end }} -{{- end }} -{{- end }} diff --git a/charts/openab/templates/gateway.yaml b/charts/openab/templates/gateway.yaml deleted file mode 100644 index 27ae60f7..00000000 --- a/charts/openab/templates/gateway.yaml +++ /dev/null @@ -1,110 +0,0 @@ -{{- range $name, $cfg := .Values.agents }} -{{- if and (ne (include "openab.agentEnabled" $cfg) "false") ($cfg.gateway).enabled }} -{{- $d := dict "ctx" $ "agent" (printf "%s-gateway" $name) "cfg" $cfg }} -{{- $hasTeams := and (($cfg.gateway).teams).appId (($cfg.gateway).teams).appSecret }} ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "openab.agentFullname" $d }} - labels: - {{- include "openab.labels" $d | nindent 4 }} -spec: - replicas: 1 - strategy: - type: Recreate - selector: - matchLabels: - {{- include "openab.selectorLabels" $d | nindent 6 }} - template: - metadata: - labels: - {{- include "openab.selectorLabels" $d | nindent 8 }} - spec: - {{- with $.Values.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: gateway - image: {{ printf "%s:%s" (($cfg.gateway).image | default "ghcr.io/openabdev/openab-gateway") (($cfg.gateway).tag | default $.Chart.AppVersion) }} - imagePullPolicy: {{ $.Values.image.pullPolicy }} - {{- with $.Values.containerSecurityContext }} - securityContext: - {{- toYaml . | nindent 12 }} - {{- end }} - ports: - - name: http - containerPort: 8080 - protocol: TCP - env: - {{- if ($cfg.gateway).token }} - - name: GATEWAY_WS_TOKEN - valueFrom: - secretKeyRef: - name: {{ include "openab.agentFullname" $d }} - key: gateway-ws-token - {{- end }} - {{- if $hasTeams }} - - name: TEAMS_APP_ID - value: {{ ($cfg.gateway).teams.appId | quote }} - - name: TEAMS_APP_SECRET - valueFrom: - secretKeyRef: - name: {{ include "openab.agentFullname" $d }} - key: teams-app-secret - {{- if ($cfg.gateway).teams.oauthEndpoint }} - - name: TEAMS_OAUTH_ENDPOINT - value: {{ ($cfg.gateway).teams.oauthEndpoint | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.openidMetadata }} - - name: TEAMS_OPENID_METADATA - value: {{ ($cfg.gateway).teams.openidMetadata | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.allowedTenants }} - - name: TEAMS_ALLOWED_TENANTS - value: {{ ($cfg.gateway).teams.allowedTenants | join "," | quote }} - {{- end }} - {{- if ($cfg.gateway).teams.webhookPath }} - - name: TEAMS_WEBHOOK_PATH - value: {{ ($cfg.gateway).teams.webhookPath | quote }} - {{- end }} - {{- end }} - - name: RUST_LOG - value: "info" - livenessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 5 - periodSeconds: 30 - readinessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 3 - periodSeconds: 10 - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - memory: 128Mi ---- -apiVersion: v1 -kind: Service -metadata: - name: {{ include "openab.agentFullname" $d }} - labels: - {{- include "openab.labels" $d | nindent 4 }} -spec: - type: ClusterIP - ports: - - port: 8080 - targetPort: http - protocol: TCP - name: http - selector: - {{- include "openab.selectorLabels" $d | nindent 4 }} -{{- end }} -{{- end }} diff --git a/charts/openab/values.yaml b/charts/openab/values.yaml index dcf8c124..54661a38 100644 --- a/charts/openab/values.yaml +++ b/charts/openab/values.yaml @@ -199,19 +199,6 @@ agents: platform: "telegram" # default platform when gateway is enabled token: "" # optional shared secret (injected via GATEWAY_WS_TOKEN env var) botUsername: "" # optional, for @mention gating - image: "ghcr.io/openabdev/openab-gateway" # gateway container image - tag: "" # defaults to Chart.AppVersion - # MS Teams adapter config (gateway-side env vars) - # See docs/msteams-enterprise.md for full setup guide - teams: - appId: "" # Azure Entra ID application (client) ID → TEAMS_APP_ID - appSecret: "" # Azure Entra ID client secret → TEAMS_APP_SECRET - # ⚠️ Required for Single Tenant bots — use tenant-specific endpoint - # Default (Multi Tenant): https://login.microsoftonline.com/botframework.com/oauth2/v2.0/token - oauthEndpoint: "" # → TEAMS_OAUTH_ENDPOINT - openidMetadata: "" # Override for sovereign clouds → TEAMS_OPENID_METADATA - allowedTenants: [] # List of tenant IDs → TEAMS_ALLOWED_TENANTS - webhookPath: "" # Default: /webhook/teams → TEAMS_WEBHOOK_PATH # Scheduled messages — config-driven cron (ADR: basic-cronjob) # Each entry sends a message to the agent at the specified schedule. # Example: diff --git a/docs/msteams-enterprise.md b/docs/msteams-enterprise.md index 2918c7f3..45d97610 100644 --- a/docs/msteams-enterprise.md +++ b/docs/msteams-enterprise.md @@ -1,6 +1,6 @@ # Microsoft Teams Enterprise Deployment -Deploy OpenAB with MS Teams in an enterprise Kubernetes environment using Helm. This guide covers Azure Entra ID configuration, Azure Bot Service setup, Teams app packaging, and Helm-based deployment. +Deploy OpenAB with MS Teams in an enterprise Kubernetes environment. This guide covers Azure Entra ID configuration, Azure Bot Service setup, Teams app packaging, and Kubernetes deployment. ``` Teams Client → Bot Framework → K8s Ingress (HTTPS + TLS) → Gateway pod → OAB pod @@ -11,20 +11,22 @@ Teams Client → Bot Framework → K8s Ingress (HTTPS + TLS) → Gateway pod → ## Prerequisites - An Azure subscription with permissions to create resources -- A Microsoft 365 tenant with Teams enabled -- A Kubernetes cluster with an Ingress controller and TLS (e.g. AKS, EKS, GKE, on-prem) -- `kubectl` and `helm` CLI tools +- A Microsoft 365 tenant with Teams enabled (Commercial Cloud Trial works for testing) +- A Kubernetes cluster with an Ingress controller and TLS +- `kubectl` CLI - IT admin access to Teams Admin Center (for app approval) ## Architecture Overview +The deployment consists of two components: + ``` ┌─────────────────────────────────────────────────────────────┐ │ Your Kubernetes Cluster │ │ │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Ingress │───▶│ Gateway │◀──▶│ OAB │ │ -│ │ (HTTPS/TLS) │ │ Pod │ WS │ Pod │ │ +│ │ (HTTPS/TLS) │ │ (BYO deploy)│ WS │ (Helm chart)│ │ │ └──────┬───────┘ └──────────────┘ └──────────────┘ │ │ │ │ └─────────┼───────────────────────────────────────────────────┘ @@ -35,9 +37,10 @@ Teams Client → Bot Framework → K8s Ingress (HTTPS + TLS) → Gateway pod → └─────────────────────┘ ``` -- **Ingress** terminates TLS and routes `/webhook/teams` to the Gateway pod -- **Gateway pod** validates JWT, normalizes events, routes replies via Bot Framework REST API -- **OAB pod** connects outbound to Gateway via WebSocket — no inbound ports needed +| Component | Deployed by | Description | +|---|---|---| +| **Gateway** | You (K8s Deployment or Docker) | Receives Bot Framework webhooks, validates JWT, routes replies. Reads `TEAMS_*` env vars. | +| **OAB** | Helm chart (`openab`) | Connects outbound to Gateway via WebSocket. No inbound ports needed. | ## Step 1: Register an Azure Entra ID Application @@ -65,6 +68,8 @@ After creation, note from the **Overview** page: > **Security note**: Store the client secret in a Kubernetes Secret. Never commit it to source control. Set a calendar reminder to rotate before expiration. +> **Note**: Multi-tenant bot creation was deprecated by Microsoft on July 31, 2025. Single Tenant is the only supported path for new bots. + ## Step 2: Create an Azure Bot Resource 1. Go to [Azure Portal → Create a resource](https://portal.azure.com/#create/hub) → search **Azure Bot** → **Create** @@ -78,8 +83,6 @@ After creation, note from the **Overview** page: - **App tenant ID**: paste your Directory (tenant) ID 3. Click **Review + Create** → **Create** -> **Note**: Multi-tenant bot creation was deprecated by Microsoft on July 31, 2025. Single Tenant is the recommended path. Cross-tenant access is achieved via Teams App Store publishing. - ### Configure the Messaging Endpoint 1. Go to the Bot resource → **Configuration** @@ -93,6 +96,10 @@ After creation, note from the **Overview** page: 1. Go to **Channels** → click **Microsoft Teams** 2. Accept the terms of service → **Save** +> **Testing tip**: After enabling the Teams channel, use the **Open in Teams** link (Azure Bot → Channels → Teams) for quick testing without uploading an app package. This link only works for people who have it — it does not make the bot discoverable org-wide. + +> **⚠️ Do not use "Test in Web Chat"** for outbound reply testing. Azure Portal's Web Chat uses `webchat.botframework.com` which returns 403 for Single Tenant bot replies. Only real Teams clients (`smba.trafficmanager.net`) work for outbound. + ## Step 3: Build a Teams App Manifest Create a directory with three files: @@ -150,50 +157,74 @@ Create a directory with three files: zip openab-teams-app.zip manifest.json outline.png color.png ``` -## Step 4: Deploy with Helm - -### Install the Gateway + OAB +## Step 4: Deploy the Gateway -```bash -helm install openab oci://ghcr.io/openabdev/charts/openab \ - --set agents.kiro.gateway.enabled=true \ - --set agents.kiro.gateway.url="ws://openab-kiro-gateway:8080/ws" \ - --set agents.kiro.gateway.platform="teams" \ - --set agents.kiro.gateway.teams.appId="" \ - --set-literal agents.kiro.gateway.teams.appSecret="" \ - --set agents.kiro.gateway.teams.oauthEndpoint="https://login.microsoftonline.com//oauth2/v2.0/token" \ - --set-string agents.kiro.gateway.teams.allowedTenants[0]="" -``` +The Gateway is deployed separately from the OAB Helm chart. Use a standard Kubernetes Deployment: -> The gateway Service name follows the pattern `--gateway` (e.g. `openab-kiro-gateway`). The chart automatically creates the gateway Deployment + Service when `gateway.enabled=true`. +### Gateway Secret -> **Single Tenant bots must set `oauthEndpoint`** to the tenant-specific endpoint. The default (`botframework.com`) only works for Multi Tenant bots and will cause `401 Unauthorized` errors. +```yaml +apiVersion: v1 +kind: Secret +metadata: + name: openab-gateway-teams +type: Opaque +stringData: + TEAMS_APP_ID: "" + TEAMS_APP_SECRET: "" + TEAMS_OAUTH_ENDPOINT: "https://login.microsoftonline.com//oauth2/v2.0/token" +``` -> **Use `--set-literal` for `appSecret`** — the secret may contain `.` characters that Helm interprets as nested key separators. +> **⚠️ Single Tenant bots must set `TEAMS_OAUTH_ENDPOINT`** to the tenant-specific endpoint. The default (`botframework.com`) only works for Multi Tenant bots and will cause `401 Unauthorized` errors. This is the #1 setup pitfall. -### Helm Values Reference +### Gateway Deployment ```yaml -agents: - kiro: - gateway: - enabled: true - url: "ws://openab-kiro-gateway:8080/ws" - platform: "teams" - image: "ghcr.io/openabdev/openab-gateway" # gateway container image - tag: "" # defaults to Chart.AppVersion - teams: - appId: "" # Azure Entra ID application (client) ID - appSecret: "" # Azure Entra ID client secret - oauthEndpoint: "" # Required for Single Tenant - openidMetadata: "" # Override for sovereign clouds - allowedTenants: [] # List of tenant IDs - webhookPath: "" # Gateway default: /webhook/teams +apiVersion: apps/v1 +kind: Deployment +metadata: + name: openab-gateway +spec: + replicas: 1 + selector: + matchLabels: + app: openab-gateway + template: + metadata: + labels: + app: openab-gateway + spec: + containers: + - name: gateway + image: ghcr.io/openabdev/openab-gateway:latest + ports: + - containerPort: 8080 + envFrom: + - secretRef: + name: openab-gateway-teams + env: + - name: RUST_LOG + value: "info" + livenessProbe: + httpGet: + path: /health + port: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: openab-gateway +spec: + selector: + app: openab-gateway + ports: + - port: 8080 + targetPort: 8080 ``` -### Ingress Configuration +### Ingress -Route Bot Framework webhooks to the Gateway pod using your existing Ingress controller: +Route Bot Framework webhooks to the Gateway using your existing Ingress controller: ```yaml apiVersion: networking.k8s.io/v1 @@ -216,14 +247,27 @@ spec: pathType: Prefix backend: service: - name: openab-kiro-gateway # matches Helm-generated Service name + name: openab-gateway port: number: 8080 ``` > Bot Framework requires HTTPS. Your Ingress controller handles TLS termination — the Gateway pod listens on plain HTTP (:8080). -## Step 5: IT Admin — Approve the Teams App +## Step 5: Deploy OAB with Helm + +OAB connects outbound to the Gateway via WebSocket: + +```bash +helm install openab oci://ghcr.io/openabdev/charts/openab \ + --set agents.kiro.gateway.enabled=true \ + --set agents.kiro.gateway.url="ws://openab-gateway:8080/ws" \ + --set agents.kiro.gateway.platform="teams" +``` + +The OAB pod does not need any Teams credentials — it only needs the Gateway WebSocket URL. + +## Step 6: IT Admin — Approve the Teams App Enterprise tenants typically restrict custom app installation. An IT admin must approve the app. @@ -248,6 +292,14 @@ To pin the app for users automatically: 2. Edit the relevant policy → **Installed apps** → **Add apps** → select OpenAB 3. Optionally add to **Pinned apps** for sidebar visibility +### Bot Discovery Methods + +| Method | Who can find it | Best for | +|---|---|---| +| **Open in Teams link** | Only people with the link | Quick testing | +| **Teams Admin Center upload** | Everyone in the org | Enterprise deployment | +| **App Store publish** | Everyone worldwide | Commercial bots | + ### Verify After policy propagation (may take up to 24 hours): @@ -258,32 +310,26 @@ After policy propagation (may take up to 24 hours): ## Tenant Allowlist -Restrict which Azure AD tenants can interact with the bot: +Restrict which Azure AD tenants can interact with the bot by adding to the Gateway Secret: -```bash ---set-string agents.kiro.gateway.teams.allowedTenants[0]="" \ ---set-string agents.kiro.gateway.teams.allowedTenants[1]="" +```yaml +stringData: + TEAMS_ALLOWED_TENANTS: "" ``` -If not set, all tenants are allowed. +Multiple tenants: `","`. If not set, all tenants are allowed. ## Sovereign Cloud Configuration -For Azure Government or Azure China deployments: +For Azure Government or Azure China deployments, add to the Gateway Secret: -| Cloud | `oauthEndpoint` | `openidMetadata` | +| Cloud | `TEAMS_OAUTH_ENDPOINT` | `TEAMS_OPENID_METADATA` | |---|---|---| | Public (default) | `login.microsoftonline.com//...` | `login.botframework.com/...` | | Azure Government | `login.microsoftonline.us//...` | `login.botframework.azure.us/...` | | Azure China (21Vianet) | `login.partner.microsoftonline.cn//...` | `login.botframework.azure.cn/...` | -```bash -# Azure Government example ---set agents.kiro.gateway.teams.oauthEndpoint="https://login.microsoftonline.us//oauth2/v2.0/token" \ ---set agents.kiro.gateway.teams.openidMetadata="https://login.botframework.azure.us/v1/.well-known/openidconfiguration" -``` - -## Environment Variables Reference +## Environment Variables Reference (Gateway) | Variable | Required | Default | Description | |---|---|---|---| @@ -300,7 +346,13 @@ For Azure Government or Azure China deployments: OAuth endpoint mismatch. Single Tenant bots must use the tenant-specific endpoint. -**Fix**: Verify `oauthEndpoint` is set to `https://login.microsoftonline.com//oauth2/v2.0/token` +**Fix**: Verify `TEAMS_OAUTH_ENDPOINT` is set to `https://login.microsoftonline.com//oauth2/v2.0/token` + +### "Test in Web Chat" works but Teams doesn't reply + +Web Chat uses Direct Line (`webchat.botframework.com`), which has different auth than Teams (`smba.trafficmanager.net`). Web Chat may accept inbound but reject outbound for Single Tenant bots. + +**Fix**: Always test with a real Teams client. Do not rely on Web Chat for outbound reply testing. ### Bot doesn't appear in Teams @@ -329,9 +381,9 @@ kubectl exec deployment/openab-gateway -- curl -s https://login.botframework.com ## Security Considerations -- **Credentials in Kubernetes Secrets** — Helm chart stores `TEAMS_APP_SECRET` in a K8s Secret, not in ConfigMap +- **Credentials in Kubernetes Secrets** — never in ConfigMaps or Deployment manifests - **Rotate client secrets** before expiration — set a reminder based on the expiration chosen in Step 1 -- **Use tenant allowlist** in production — restrict to your organization's tenant ID +- **Use tenant allowlist** in production — restrict `TEAMS_ALLOWED_TENANTS` to your organization's tenant ID - **Network policies** — consider restricting Gateway pod egress to Bot Framework endpoints - **OAB pod has no inbound exposure** — connects outbound to Gateway only