Skip to content

Commit c72c86a

Browse files
authored
feat(operator): oabctl ingress for Telegram/LINE (API Gateway + VPC Link + Cloud Map) (#1275)
* feat(operator): add API Gateway ingress for webhook platforms Adds an optional `spec.ingress` block to the oab.dev/v2 manifest so `oabctl apply` can provision inbound HTTPS webhook ingress for Telegram/LINE bots in one shot — instead of the ~7 manual `aws` commands documented in the Telegram/LINE-on-AWS refarch (Option 1). When `ingress` is present (ECS runtime only), apply reconciles, all idempotently and reused by name: - Cloud Map private DNS namespace + per-service A record - ECS service registry wiring (attached at service *creation*) - VPC Link (shared `oab-vpc-link`), waited until AVAILABLE - API Gateway HTTP API (`oab-webhook`) + HTTP_PROXY integration - one route per path + a `prod` auto-deploy stage - a self-referencing security-group inbound rule on containerPort It then prints the stable webhook URL(s) to register with the platform. Backward compatible: `ingress` is optional and defaults to absent, so existing outbound-only (Discord) deployments create no ingress resources and behave exactly as before. Because ECS service registries can only be set at creation time, apply attaches the registry on create and, for a pre-existing service without service discovery, provisions the ingress resources and prints how to recreate the service (non-destructive — no automatic delete). - manifest.rs: Ingress struct, validation, fleet passthrough, 7 unit tests - ingress.rs: Cloud Map + VPC Link + API Gateway reconciliation - apply.rs: two-phase wiring (Cloud Map before create, gateway after) - schema/oabservice-v2.json: ingress def + refs - README: ingress section - Cargo.toml: aws-sdk-apigatewayv2, aws-sdk-servicediscovery Refs #1274 * build(operator): declare standalone [workspace] so CI cargo check works The ci.yml `operator` job runs `cargo check` with working-directory operator, but the crate is not listed in the repo-root workspace (members: openab-core, openab-gateway). Cargo then walks up to the root Cargo.toml and errors ("believes it's in a workspace when it's not"). This job only runs when operator/** changes, so it was latent until now. Adding an empty [workspace] table marks operator as its own workspace root, matching how it's built and released standalone (own Cargo.lock). * test(operator): unit-test ingress URL/route helpers; document shared VPC Link constraint Addresses PR review feedback: - Extract pure helpers (integration_uri, route_key, webhook_urls) from ensure_gateway and add 4 unit tests (F3 — ingress.rs had no coverage). - Warn at apply time when reusing the shared oab-vpc-link and document in README that all ingress bots in a VPC must share subnets/SGs, since a VPC Link's subnets/SGs are fixed at creation (F2). * fix(operator): correct recreate hint, add ingress teardown on delete, paginate API GW lists Addresses PR review round 3: - F1: recreate hint used `oabctl delete service` (invalid) and omitted --cluster; now prints `oabctl delete oabservice <name> --cluster oab --namespace <ns>` which matches delete.rs's contract and the hardcoded `oab` cluster. - F2: oabctl delete oabservice now tears down the bot's per-bot ingress resources (Cloud Map service + API Gateway routes/integration), best-effort, leaving shared VPC Link / HTTP API / SG rule intact. No-op for bots that never had ingress. Documented in README. - F3: apigatewayv2 GetApis/GetVpcLinks/GetIntegrations/GetRoutes/GetStages have no smithy paginator in this SDK, so paginate manually via next_token loops instead of reading only the first page. Verified: cargo build, clippy --all-targets -D warnings, cargo test (11 passed) in a nested layout mirroring the CI operator job. * fix(operator): per-bot HTTP API to prevent cross-bot webhook path collisions Round-4 review: - F1 (critical): routes on the shared oab-webhook API were keyed by path only, so two bots both declaring /webhook/telegram would collide — the second apply saw the first's route, skipped, and silently misrouted traffic. Give each bot its own HTTP API (oab-webhook-<ns>-<name>) so webhook paths can never clash across bots; each bot gets a distinct {api-id} endpoint URL. Simplifies teardown to a single delete_api (cascades routes/integration/stage). VPC Link + SG rule stay shared. - F2: removed duplicated comment line in ensure_vpc_link. - Added api_name() helper + unit test (12 tests total). Verified: build, clippy --all-targets -D warnings, cargo test (12 passed) in a nested layout mirroring the CI operator job. * fix(operator): match typed EC2 error code for duplicate SG rule Round-5 review F1: ensure_sg_ingress classified the "rule already exists" case by substring-matching the Debug-rendered error string, which breaks if the SDK changes error formatting. Match the typed AWS error code InvalidPermission.Duplicate via ProvideErrorMetadata::code() instead. (F2 — CI green — the operator job now passes on the prior commit.) Verified: build, clippy --all-targets -D warnings, cargo test (12 passed). * fix(operator): use Cloud Map SRV + service-ARN integration (fixes live 503/BadRequest) Live E2E against a real account revealed the documented Option-1 wiring does not work: an HTTP API VPC_LINK integration rejects a raw 'http://<dns>:<port>' URI with BadRequestException: For VpcLink VPC_LINK, integration uri should be a valid ELB listener ARN or a valid Cloud Map service ARN. Correct wiring (matches CDK's HttpServiceDiscoveryIntegration): - Cloud Map service uses an SRV record (not A) so the container port is captured; ECS registers the task IP + port into it. - ECS service registry sets containerName/containerPort and the task def exposes the container port, so ECS writes the SRV record. - API Gateway integration URI is the Cloud Map *service ARN* with connectionType=VPC_LINK and integrationMethod=ANY; the port is resolved from SRV. Verified end-to-end (POST through API Gateway → VPC Link → Cloud Map SRV → Fargate task returned HTTP 200 with the request echoed at /prod/webhook/telegram), then torn down cleanly. build + clippy -D warnings + cargo test (11 passed). * feat(operator): consolidated apply summary for services needing ingress recreation Review F2: the 'service exists without service discovery' warning was a mid-run eprintln that's easy to miss in non-interactive CI, so a partial apply could look like a clean success. apply_ecs now returns whether the service needs recreation; apply::run collects these and prints a single consolidated ⚠ summary block (with the exact recreate commands) after the 'N service(s) applied' line. build + clippy -D warnings + test (11 passed). * fix(operator): scope VPC Link/namespace per-VPC; teardown on ingress removal; prune stale routes Round-7 review: - F1 (critical): VPC Link was matched by a hardcoded name ('oab-vpc-link') with no VPC check. A VPC Link's ENIs live in one VPC and cannot route to another, so a second bot in a *different* VPC would silently reuse the first VPC's link and get unreachable integrations. Fixed by naming the link 'oab-vpc-link-<vpc-id>', scoped per-VPC. - F2: same collision class for the Cloud Map namespace — matched by configured name only. Fixed by scoping the actual namespace name to '<cloudMapNamespace>-<vpc-id>' (the DnsConfig VPC association makes namespace-per-VPC the correct AWS-native mental model anyway; the private DNS only resolves inside that VPC). - F3: apply only ever added ingress resources — editing a manifest to remove spec.ingress orphaned the per-bot HTTP API + Cloud Map service. apply now detects 'had ingress before, doesn't now' by comparing against the previously-stored S3 manifest and calls the same best-effort ingress::teardown used by . - F4: ensure_route only ever added routes; renaming/removing a webhook path left a dead route on the bot's API forever. Added prune_stale_routes, which deletes any route on the bot's API whose key isn't in the current ingress.paths after ensuring the desired ones. Added 3 unit tests (vpc_link_name, vpc_scoped_namespace) — 13 total. Updated README + module doc to describe per-VPC scoping and the new apply-time teardown/pruning behavior. Verified: build, clippy --all-targets -D warnings, cargo test (13 passed). * fix(operator): dead doc link, VPC Link race hardening, Cloud Map teardown retry Round-8 review: - F1 (blocking): README and code comments linked to docs/refarch/running-telegram-line-on-aws.md, which does not exist on main or this branch (its PR #1274 is still open). Repointed the README to the existing docs/refarch/telegram-cloudflare-tunnel.md as an interim cross-reference with a note that a dedicated AWS-native doc is tracked in #1274; ingress.rs/manifest.rs doc comments now point at operator/README.md instead of the nonexistent file. - F2: VPC Link names are not unique to the apigatewayv2 API (confirmed: AWS does not error on a duplicate name), so two 'oabctl apply' processes racing to create the same per-VPC link in a brand-new VPC could end up with two links. Within a single apply run this can't happen (manifests are processed sequentially), but the reconciler hardens against the cross-process race anyway: ensure_vpc_link now collects ALL matching links, deterministically picks the lexicographically-first ID (stable regardless of list ordering) rather than an arbitrary one, and warns with cleanup commands if more than one exists. - F3: Cloud Map service teardown gave up on the first 'still has registered instances' error, permanently orphaning the service if the caller didn't manually retry. ECS deregisters the instance asynchronously on scale-to-0/delete, so this is usually just a timing window, not a real conflict — teardown now retries delete_service up to 6 times over ~25s before falling back to a warning with the exact manual cleanup command. Verified: build, clippy --all-targets -D warnings, cargo test (13 passed). * fix(operator): preserve webhook URL on recreate; exact Cloud Map targeting; namespace-change + SG mismatch detection Round-10 review (full team pass on 838c220): - F1 (blocking): the documented recreate path (oabctl delete && apply) deleted the bot's HTTP API resource itself via delete_api(), so the next apply's ensure_api() found nothing and created a NEW api-id, silently rotating the webhook URL hostname and breaking any Telegram/ LINE registration made against the old URL. Fixed by splitting teardown into two functions: - ingress::teardown(): strips routes/integration/stage but keeps the HTTP API resource, so its api-id (and thus the public URL) survives an ECS-service recreate. Used by both apply's ingress-removed path and delete's ingress cleanup. - ingress::delete_api(): permanently deletes the HTTP API. Only called from oabctl delete's full-removal path, where there's no URL to keep stable since the bot itself is gone. - F2: Cloud Map service teardown matched by name via an account-wide list_services() scan, so two bots with the same namespace/name in different VPCs/environments could collide. delete.rs now captures the ECS service's actual service_registries ARN BEFORE deleting it and passes it to teardown(), which resolves the exact Cloud Map service ID from that ARN instead of searching by name. Falls back to the by-name scan only when no ARN is known (apply's ingress-removed path, where the service may already be gone). - F3: has_registries only checked whether SOME registry was attached, not whether it matched the currently-resolved registry for the manifest's ingress.cloudMapNamespace. Changing cloudMapNamespace on an existing service silently left it pointed at the old namespace (503s) without triggering the recreate warning. apply now compares the resolved registry ARN against the service's actual registry ARNs and flags a mismatch as needing recreate too, with a distinct message. - F4: VPC Link reuse only printed a reminder about subnet/SG matching, never validated it. ensure_vpc_link now calls GetVpcLink on the reused link and compares its actual security groups against the manifest's, warning loudly on mismatch. (Subnets aren't exposed by GetVpcLink, so those remain a documented reminder only.) - F5: VPC Link duplicate-name tie-breaker sorted candidates by ID only, ignoring status, so a Pending duplicate could be picked over an Available one (wasted wait time, not a correctness bug). Now sorts AVAILABLE first, ID as tiebreaker. - F6: Cargo.lock was stale (missing the two new SDK deps entirely). Regenerated and committed. Added 2 new unit tests (cloud_map_service_id_from_arn parsing) — 15 total. Rewrote the README ingress caveats section to describe the stable-URL guarantee, exact-ARN teardown targeting, namespace-change detection, and SG validation. Verified: build, clippy --all-targets -D warnings, cargo test (15 passed). * fix(operator): attach/fix service discovery via UpdateService, no recreate needed Round-11 review (full team pass): the PR's core operational caveat — "ECS service registries can only be set at creation time, so an existing service needs delete-and-recreate to get ingress" — is outdated and wrong. Confirmed directly against the AWS API_UpdateService reference: serviceRegistries has been a documented UpdateService parameter since March 2022. "When you add, update, or remove the service registries configuration, Amazon ECS starts new tasks with the updated service registries configuration, and then stops the old tasks when the new tasks are running" — a normal rolling replacement, no downtime gap, no delete needed. Requires the AWSServiceRoleForECS service-linked role, which ECS creates automatically the first time any service in the account uses service discovery. Removed the entire recreate code path: - apply_ecs's update_service branch now attaches or replaces the serviceRegistries directly when there's no registry or a mismatch (registry_mismatch detection from round-10 is kept — just resolved automatically instead of printed as a manual instruction). - Deleted the "needs_recreate" plumbing through apply_ecs/run (dead code once the update path handles it directly). - Corrected the "create-only" claim in ingress.rs's module doc and apply.rs's ensure_cloud_map-ordering comment. - Rewrote README's "Recreate caveat" into "Adding/fixing service discovery never requires recreating the service", describing the actual UpdateService rolling-replacement behavior and the service-linked role note. Also addressed the two non-blocking findings from the same review round: - Added an "Additional permissions for spec.ingress" table to the Prerequisites section (Cloud Map, API Gateway, EC2, ECS UpdateService actions) — this is new AWS API surface the caller's credentials need that wasn't documented anywhere. - Corrected the security note: Telegram validates the X-Telegram-Bot-Api-Secret-Token header + source-IP allowlist (not just "token in the path"), LINE does HMAC-SHA256 signature verification — verified against crates/openab-gateway/src/adapters/{telegram,line}.rs. Verified live end-to-end against a real AWS account: 1. Applied a manifest WITHOUT ingress -> plain ECS service created. 2. Added ingress, re-applied -> printed "updated (service discovery attached; rolling replacement, no downtime)", NOT a recreate warning. 3. Confirmed via describe-services: same serviceArn, same createdAt timestamp (service was never deleted), serviceRegistries now populated with the correct Cloud Map ARN. 4. Confirmed the task rolled (old task ARN -> new task ARN) rather than the service being replaced. 5. curl through the printed webhook URL end-to-end -> HTTP 200. 6. Deleted and cleaned up all test resources (service, HTTP API, Cloud Map service/namespace, VPC Link, SG rule) - account left clean. Verified: build, clippy --all-targets -D warnings, cargo test (15 passed, unchanged from round-10 since no new pure logic was added here). * fix(operator): detach registry + exact-ARN teardown on ingress removal Round-12 review (full team pass on 536e858): the ingress-removal path (manifest edited to drop spec.ingress) had two gaps left over from round-11's UpdateService redesign, both only reachable via that specific transition: - F1 (critical): the update_service branch only ever set `serviceRegistries` inside `if let Some(cm) = &cloud_map`. When ingress is removed, cloud_map is None, so the field was omitted from the request entirely. Per AWS's own UpdateService reference, an omitted serviceRegistries leaves the existing configuration unchanged — only an explicit empty list detaches it. Since ingress::teardown (running earlier in the same call) deletes the underlying Cloud Map service, the ECS service was left pointing at a registry ARN that no longer existed, which surfaces as registration failures on the next deployment. Fixed by tracking `needs_detach = cloud_map.is_none() && has_registries` and calling `update_req.set_service_registries(Some(vec![]))` in that case. - F2: the ingress-removal call site passed `None` for teardown's `known_registry_arn`, forcing a fallback to an account-wide `list_services()` scan matching only on `oab-{namespace}-{name}`. Two bots with the same namespace/name in different VPCs/environments sharing one account could collide there. Fixed by hoisting the `describe_services` call (previously done further down, purely for the service_active check) to the top of `apply_ecs`, before the ingress-removal branch, so the real registry ARN captured from the live ECS service can be threaded through instead of `None`. The service_active check later in the function now reuses this same response instead of issuing a second describe_services call. No new tests added — this only changes AWS SDK request-building logic in a code path (ingress removal) that isn't exercised by the existing unit tests (which cover manifest/schema parsing, not live apply_ecs behavior). Verified via `cargo fmt --check` (no new diffs beyond the changed lines) and manual review of the full diff; `cargo build`/`clippy`/`test` could not be run in this environment (no linker available), so CI is the first real compile/test signal for this commit — flagged explicitly in the accompanying PR comment. --------- Co-authored-by: chaodu-agent <chaodu-agent@users.noreply.github.com>
1 parent 709d03f commit c72c86a

9 files changed

Lines changed: 2585 additions & 44 deletions

File tree

operator/Cargo.lock

Lines changed: 1010 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

operator/Cargo.toml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ version = "0.1.0"
44
edition = "2021"
55
description = "CLI provisioner for OAB agents on ECS"
66

7+
# Standalone crate — not part of the repo-root [workspace]. Declaring an empty
8+
# workspace table stops cargo from attaching this crate to the root workspace
9+
# (which lists only crates/openab-core and crates/openab-gateway).
10+
[workspace]
11+
712
[[bin]]
813
name = "oabctl"
914
path = "src/main.rs"
@@ -18,6 +23,8 @@ aws-sdk-ssm = "1.52"
1823
aws-sdk-sts = "1"
1924
aws-sdk-cloudwatchlogs = "1"
2025
aws-sdk-secretsmanager = "1"
26+
aws-sdk-apigatewayv2 = "1"
27+
aws-sdk-servicediscovery = "1"
2128
ecsctl = { git = "https://github.com/oablab/ecsctl.git", rev = "90a6cd1" }
2229
clap = { version = "4.5", features = ["derive"] }
2330
chrono = "0.4"

operator/README.md

Lines changed: 122 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,110 @@ spec:
209209
securityGroups: [sg-xxx]
210210
```
211211
212+
### Ingress — inbound webhooks (Telegram / LINE)
213+
214+
Discord bots are outbound-only and need no ingress. Webhook platforms (Telegram,
215+
LINE, ...) POST *into* the task, so they need a public HTTPS endpoint. Adding an
216+
optional `spec.ingress` block makes `oabctl apply` provision the cheapest
217+
AWS-native path in one shot — API Gateway HTTP API → VPC Link → Cloud Map → the
218+
task — instead of running ~7 manual `aws` commands, replacing the manual steps
219+
implemented here. For a Kubernetes/Cloudflare-Tunnel alternative, see
220+
[`docs/refarch/telegram-cloudflare-tunnel.md`](../docs/refarch/telegram-cloudflare-tunnel.md).
221+
A dedicated AWS-native refarch doc covering this path in depth is tracked in
222+
[#1274](https://github.com/openabdev/openab/pull/1274); once merged it will be
223+
linked here.
224+
225+
```yaml
226+
spec:
227+
image: public.ecr.aws/oablab/kiro:beta
228+
resources: { cpu: "256", memory: "512" }
229+
configFrom: s3://.../config.toml
230+
runtime:
231+
type: ecs
232+
capacityProvider: FARGATE_SPOT
233+
networking:
234+
subnets: [subnet-aaa, subnet-bbb]
235+
securityGroups: [sg-xxx]
236+
ingress:
237+
type: apigateway # only supported type (default)
238+
cloudMapNamespace: oab # reused across bots in the VPC (default: oab)
239+
containerPort: 8080 # OpenAB listen port (default: 8080)
240+
paths:
241+
- /webhook/telegram
242+
- /webhook/line
243+
```
244+
245+
On `apply` this reconciles (idempotently, reused by name):
246+
247+
1. **Cloud Map** private DNS namespace (`<cloudMapNamespace>-<vpc-id>`, shared per-VPC) + a per-service **SRV** record (carries the container port; a plain A record does not work as a VPC-Link integration target)
248+
2. **ECS service registry** wiring (attached at service creation)
249+
3. **VPC Link** (`oab-vpc-link-<vpc-id>`, shared per-VPC), waits until `AVAILABLE`
250+
4. **API Gateway HTTP API** (`oab-webhook-<ns>-<name>`, one per bot) + `HTTP_PROXY` integration over the VPC Link
251+
5. One **route** per path + a `prod` auto-deploy **stage**
252+
6. A self-referencing **security-group** inbound rule on `containerPort`
253+
254+
`apply` then prints the stable webhook URL(s) to register with BotFather / the
255+
LINE console:
256+
257+
```
258+
🔗 Webhook URL(s) for my-bot:
259+
https://{api-id}.execute-api.{region}.amazonaws.com/prod/webhook/telegram
260+
https://{api-id}.execute-api.{region}.amazonaws.com/prod/webhook/line
261+
```
262+
263+
> **Security note:** the API Gateway endpoint itself is public and unauthenticated
264+
> at the transport layer (no IAM auth, no API key). OpenAB's webhook handlers add
265+
> their own app-layer verification on top: Telegram validates the
266+
> `X-Telegram-Bot-Api-Secret-Token` header (`TELEGRAM_SECRET_TOKEN`) and the
267+
> request's source IP against Telegram's published webhook subnets; LINE
268+
> verifies an HMAC-SHA256 signature using `LINE_CHANNEL_SECRET`. Set
269+
> `TELEGRAM_SECRET_TOKEN` when registering the webhook with BotFather to enable
270+
> that check.
271+
>
272+
> **Adding/fixing service discovery never requires recreating the service:**
273+
> if an existing ECS service has no Cloud Map registry, or has one pointing at a
274+
> different Cloud Map service than the one currently resolved for
275+
> `ingress.cloudMapNamespace` (e.g. the namespace was changed after the service
276+
> was created), `apply`'s `update_service` call attaches or replaces the
277+
> registry directly — ECS has supported adding/updating/removing
278+
> `serviceRegistries` on an existing service via a normal rolling replacement
279+
> (new tasks start with the new registry, old tasks stop once healthy — no
280+
> downtime gap) since March 2022. This requires the `AWSServiceRoleForECS`
281+
> service-linked role, which ECS creates automatically the first time any
282+
> service in the account uses service discovery — no setup needed.
283+
>
284+
> **Shared per-VPC (not per-account):** all ingress-enabled bots in the *same VPC*
285+
> share one VPC Link (`oab-vpc-link-<vpc-id>`) and one Cloud Map namespace
286+
> (`<cloudMapNamespace>-<vpc-id>`) — both are named by VPC ID so bots in different
287+
> VPCs never collide or reuse each other's link/namespace. A VPC Link's
288+
> subnets/security groups are fixed at creation and cannot be changed, so every
289+
> ingress bot in a given VPC must use the same `networking.subnets` /
290+
> `securityGroups` as whichever bot created that VPC's link first. `apply`
291+
> verifies the reused link's actual security groups match the manifest and warns
292+
> loudly on a mismatch (subnets aren't exposed by the API, so those can only be
293+
> reminded, not verified).
294+
>
295+
> **Teardown:** `oabctl delete oabservice <name>` permanently removes the bot's
296+
> per-bot ingress resources — its exact Cloud Map service (resolved by the ECS
297+
> service's own registry ARN, not a name search, so same-named bots in different
298+
> VPCs/environments can't collide) and its HTTP API (`oab-webhook-<ns>-<name>`,
299+
> including the API resource itself this time, since the bot is gone for good) —
300+
> on a best-effort basis (it never blocks service deletion). If you instead edit
301+
> a manifest to remove `spec.ingress` while keeping the bot, `apply` runs the same
302+
> Cloud Map + routes/integration/stage cleanup automatically, but **keeps the HTTP
303+
> API** in case ingress is re-added later. The **shared** VPC Link and the
304+
> security-group inbound rule are always left in place for other bots. If the
305+
> Cloud Map service still has registered instances, teardown retries for ~25s
306+
> before falling back to a warning with the manual cleanup command.
307+
>
308+
> **Changing `paths`:** `apply` prunes routes on the bot's API that are no longer
309+
> in the manifest's `ingress.paths`, so renaming or removing a webhook path never
310+
> leaves a dangling route.
311+
>
312+
> **Per-bot API, no path collisions:** each ingress bot gets its own HTTP API, so
313+
> two bots can both use `/webhook/telegram` without clashing — each has a distinct
314+
> `{api-id}` endpoint URL that stays stable across recreates.
315+
212316
### OABFleet — batch deploy
213317

214318
```yaml
@@ -234,7 +338,7 @@ spec:
234338
```
235339

236340
**Fleet features:**
237-
- Template inheritance with per-agent overrides (`image`, `resources`, `bootstrapFrom`, `secrets`)
341+
- Template inheritance with per-agent overrides (`image`, `resources`, `bootstrapFrom`, `secrets`, `ingress`)
238342
- `${name}` interpolation in `configFrom`, `bootstrapFrom`
239343
- Runtime shared across fleet (not overridable per-agent)
240344
- Validate-all-before-apply (no partial deploys)
@@ -328,3 +432,20 @@ With `oabctl bootstrap`, most prerequisites are handled automatically. You only
328432
1. **AWS credentials** — IAM user/role with permissions to create the above resources
329433
2. **Docker** — to build custom images (optional if using official images)
330434
435+
### Additional permissions for `spec.ingress`
436+
437+
The resources bootstrap creates cover outbound-only (Discord) deployments. If
438+
any manifest sets `spec.ingress`, the **caller of `oabctl apply`/`delete`**
439+
(not the task role) also needs:
440+
441+
| Service | Actions |
442+
|---------|---------|
443+
| Cloud Map | `servicediscovery:CreatePrivateDnsNamespace`, `CreateService`, `DeleteService`, `ListNamespaces`, `ListServices`, `GetOperation` |
444+
| API Gateway | `apigateway:CreateVpcLink`, `CreateApi`, `CreateIntegration`, `CreateRoute`, `CreateStage`, `DeleteRoute`, `DeleteIntegration`, `DeleteStage`, `DeleteApi`, `GetVpcLinks`, `GetVpcLink`, `GetApis`, `GetIntegrations`, `GetRoutes`, `GetStages` |
445+
| EC2 | `ec2:DescribeSubnets`, `AuthorizeSecurityGroupIngress` |
446+
| ECS | `ecs:UpdateService` with `serviceRegistries` (requires the `AWSServiceRoleForECS` service-linked role, which ECS creates automatically the first time any service in the account uses service discovery) |
447+
448+
`AdministratorAccess`-equivalent or a broad `servicediscovery:*`/`apigateway:*`
449+
during development is fine; the table above is for scoping a least-privilege
450+
policy.
451+

operator/schema/oabservice-v2.json

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,8 @@
5858
"configFrom": { "type": "string" },
5959
"bootstrapFrom": { "type": "string" },
6060
"secrets": { "type": "object", "additionalProperties": { "type": "string" } },
61-
"runtime": { "$ref": "#/$defs/runtime" }
61+
"runtime": { "$ref": "#/$defs/runtime" },
62+
"ingress": { "$ref": "#/$defs/ingress" }
6263
},
6364
"additionalProperties": false
6465
},
@@ -83,7 +84,8 @@
8384
"resources": { "$ref": "#/$defs/resources" },
8485
"bootstrapFrom": { "type": "string" },
8586
"secrets": { "type": "object", "additionalProperties": { "type": "string" } },
86-
"runtime": { "$ref": "#/$defs/runtime" }
87+
"runtime": { "$ref": "#/$defs/runtime" },
88+
"ingress": { "$ref": "#/$defs/ingress" }
8789
},
8890
"additionalProperties": false
8991
},
@@ -96,7 +98,8 @@
9698
"image": { "type": "string" },
9799
"resources": { "$ref": "#/$defs/resources" },
98100
"bootstrapFrom": { "type": "string" },
99-
"secrets": { "type": "object", "additionalProperties": { "type": "string" } }
101+
"secrets": { "type": "object", "additionalProperties": { "type": "string" } },
102+
"ingress": { "$ref": "#/$defs/ingress" }
100103
},
101104
"additionalProperties": false
102105
},
@@ -109,6 +112,22 @@
109112
},
110113
"additionalProperties": false
111114
},
115+
"ingress": {
116+
"type": "object",
117+
"description": "Inbound HTTPS webhook ingress (API Gateway HTTP API + VPC Link + Cloud Map). ECS runtime only.",
118+
"required": ["paths"],
119+
"properties": {
120+
"type": { "type": "string", "enum": ["apigateway"], "default": "apigateway" },
121+
"cloudMapNamespace": { "type": "string", "default": "oab" },
122+
"paths": {
123+
"type": "array",
124+
"minItems": 1,
125+
"items": { "type": "string", "pattern": "^/" }
126+
},
127+
"containerPort": { "type": "integer", "minimum": 1, "maximum": 65535, "default": 8080 }
128+
},
129+
"additionalProperties": false
130+
},
112131
"runtime": {
113132
"type": "object",
114133
"required": ["type"],

0 commit comments

Comments
 (0)