Skip to content

Latest commit

 

History

History
148 lines (114 loc) · 38.8 KB

File metadata and controls

148 lines (114 loc) · 38.8 KB

Milestone 3 — AlbMeshedHttpEntrypoint

Parent runbook: docs/slo/completed/RUNBOOK-hulumi-k8s.md. Read the runbook's Global Execution Rules + Global Entry Rules + M2's lessons before starting.

Goal: After M3, @hulumi/k8s-baseline.AlbMeshedHttpEntrypoint ships as a pulumi.ComponentResource that emits four resources for one workload: a K8s Ingress (ALB target-type=ip, scheme, group.name, healthcheck-port=15021, healthcheck-path=/healthz/ready), an Istio Gateway in the istio-system namespace, an Istio VirtualService in the workload namespace cross-referencing the gateway, and an AuthorizationPolicy in the workload namespace with from.principals matching the ingress gateway's ServiceAccount principal — automatically wired from the IstioFoundation ref. Closes #41.

Context: The four-resource bundle is what every meshed workload re-derives by hand. The principal-name linkage between AuthorizationPolicy.from.principals and the gateway's SA is the bug class that the issue's repro flagged (~90 minutes per workload to debug); this milestone's load-bearing value is collapsing that linkage to a single mesh: IstioFoundation ref. The ALB-specific bits (target-type=ip, scheme, healthcheck on the gateway-native readiness path) are well-understood and stable across ALB Controller versions.

Important design rule: The AuthorizationPolicy.from.principals array is computed from mesh.ingressGatewayServiceAccountName, never from a consumer-supplied string. Hand-typing the principal is the bug source the abstraction exists to prevent. The extraPrincipals arg is allowed for additional SPIFFE IDs (e.g., a sister-service that should also be allowed in), but the gateway principal itself is non-overridable and is asserted by the principal_linkage_load_bearing BDD row.

Refactor budget: Surgical addition only. New packages/k8s-baseline/src/alb-meshed-http-entrypoint* files + tests. Modifies packages/k8s-baseline/src/index.ts (re-export), docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker.

Contract Block

Field Value
Inputs new AlbMeshedHttpEntrypoint(name, args) where args: AlbMeshedHttpEntrypointArgs requires: mesh: IstioFoundation (concrete type at v1; the four output names are read from this ref), host: string (FQDN; refused if it doesn't match a basic FQDN regex), serviceRef: { namespace: string; name: string; port: number } (workload service to route to). Optional: scheme?: "internal" | "internet-facing" (default "internal"), mTLS?: "STRICT" | "PERMISSIVE" (default falls back to mesh's defaultMTLS at construction time), authorizationPolicy?: { allowFromGateway?: boolean; extraPrincipals?: string[] } (default { allowFromGateway: true, extraPrincipals: [] }), alb?: { healthcheckPath?: string; healthcheckPort?: number; groupName?: string; certificateArn?: string; sslPolicy?: string } (defaults: /healthz/ready, 15021, "default", no cert/policy).
Outputs AlbMeshedHttpEntrypointOutputs exposes ingressName, ingressNamespace, gatewayName, gatewayNamespace, virtualServiceName, virtualServiceNamespace, authorizationPolicyName, authorizationPolicyNamespace (each pulumi.Output<string>), albAddress: pulumi.Output<string> (computed from the Ingress status — eventual, may be empty until ALB Controller provisions).
Interfaces touched New stable surface: @hulumi/k8s-baseline#{AlbMeshedHttpEntrypoint,AlbMeshedHttpEntrypointArgs,AlbMeshedHttpEntrypointOutputs,ALB_MESHED_HTTP_ENTRYPOINT_COMPONENT_TYPE}. Component type: "hulumi:k8s:AlbMeshedHttpEntrypoint". Consumes IstioFoundation from M2.
Data classification Internal — provisions K8s manifests in a kind cluster (or sandbox EKS) during integration tests. The component does not handle workload data at runtime; it shapes how the ALB and the mesh route it.
Proactive controls in play (a) C1 Define Security Requirements — design record + the issue (#41) record the requirement: bundle the four resources with consistent SA-principal linkage. (b) C5 Validate All Inputshost regex-validated; serviceRef requires non-empty namespace/name and a positive port; scheme and mTLS constrained to enums. (c) C7 AuthorizationAuthorizationPolicy with from.principals matching the gateway SA is the security control; consumer's extraPrincipals are appended, never substituted. (d) C9 Implement Security Logging and Monitoring — emits pulumi.log.warn if authorizationPolicy.allowFromGateway: false (loud opt-out on the security-positive default — equivalent to "no AuthZ in front of this entrypoint"). (e) C10 Handle All Errors and Exceptions — refuses construction with clear errors for invalid host / serviceRef / mTLS / scheme.
Abuse acceptance scenarios Four BDD rows in the table below cite tm-hulumi-k8s-abuse-N. Slug-keyed: tm-hulumi-k8s-abuse-principal-linkage-broken (asserts from.principals is computed from the mesh ref, not a consumer string), tm-hulumi-k8s-abuse-allow-everyone (allowFromGateway: false with no extraPrincipals is a no-AuthZ posture; refuses construction unless the consumer explicitly opts in via a separate acknowledgeNoAuthZ: true flag — borrowed from SecureRepository's public-visibility opt-in pattern), tm-hulumi-k8s-abuse-host-regex-bypass (refuses host: "*" and host: ".internal" and host: ""), tm-hulumi-k8s-abuse-cross-ns-virtualservice (VirtualService references the gateway via <istio-system-ns>/<gateway-name> cross-namespace form; this is the standard Istio shape — the test asserts the full ref, not a bare name).
Files allowed to change New: packages/k8s-baseline/src/{alb-meshed-http-entrypoint.ts,alb-meshed-http-entrypoint.args.ts,alb-meshed-http-entrypoint.outputs.ts}; packages/k8s-baseline/tests/alb-meshed-http-entrypoint.test.ts; packages/k8s-baseline/tests/integration/kind/alb-meshed-http-entrypoint.kind.test.ts (kind cannot fully exercise ALB; the kind test asserts the four K8s manifests render correctly — ALB-side assertions live in the M5 real-EKS smoke); docs/slo/lessons/hulumi-k8s-m3.md; docs/slo/completion/hulumi-k8s-m3.md; docs/components/alb-meshed-http-entrypoint.md (one-line stub). Modified: packages/k8s-baseline/src/index.ts (re-export); docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker. Files outside this milestone's allow-list — REFUSE TO TOUCH including any packages/baseline/, packages/policies/, packages/drift/, M1 + M2 K8s files.
Files to read before changing anything docs/slo/completed/RUNBOOK-hulumi-k8s.md; docs/slo/design/hulumi-k8s-surface.md (§ Decision: MeshedHttpEntrypoint — ALB-first); docs/slo/runbook-milestones/hulumi-k8s-m2.md; docs/slo/lessons/hulumi-k8s-m2.md; packages/k8s-baseline/src/istio-foundation.ts and .outputs.ts; AWS Load Balancer Controller's IngressClass and annotation reference.
New files allowed All "New" entries above.
New dependencies allowed none.
Migration allowed no.
Compatibility commitments AlbMeshedHttpEntrypoint, args, outputs, component-type constant — stable from M3. The shape of serviceRef ({ namespace, name, port }) is load-bearing; renaming any field is a breaking change.
Forbidden shortcuts (a) NEVER allow from.principals to be set from a consumer string. The mesh ref's SA name is the single source of truth. (b) NEVER silently default allowFromGateway: false — the constructor refuses unless the consumer opts in via authorizationPolicy: { allowFromGateway: false, acknowledgeNoAuthZ: true }. (c) NEVER ship the ALB healthcheck pointing at the workload service (port 80, path /) as a default — the gateway-native readiness on port 15021 is the documented correct path. (d) NEVER allow scheme: "internet-facing" to be the default; the default is "internal" because the dominant deployment shape on EKS is internal-only. Internet-facing is an explicit opt-in. (e) NEVER infer the gateway's namespace from a "well-known" string; always read mesh.ingressGatewayNamespace (added to IstioFoundationOutputs if not already exposed — verify in M2 review).

Out of Scope / Must Not Do

  • No NLB / Cloudflare Tunnel / NodePort / nginx-ingress / traefik bundles. ALB only at v1 (Rule 0).
  • No mTLS-PERMISSIVE-on-the-entrypoint convenience; the entrypoint inherits mesh-level mTLS by default.
  • No multi-host fan-out (one VirtualService routing N hosts to N services). One entrypoint = one host = one service. Workloads needing fan-out compose multiple entrypoints.
  • No automatic ACM certificate management. alb.certificateArn is consumer-supplied.
  • No DNS record creation. Routes 53 / Cloudflare DNS is consumer-side.
  • No WAF / Shield association. Consumer-side ALB attachment.
  • No HTTP-to-HTTPS redirect annotation magic; if the consumer supplies a certificateArn, the redirect annotation is added; otherwise not.

Pre-Flight

  1. Complete the Global Entry Rules.
  2. Read M2's lessons; particularly any surprises around the ingressGatewayServiceAccountName output computation.
  3. Read the AWS Load Balancer Controller ingress annotation reference; the four annotations this milestone uses (alb.ingress.kubernetes.io/target-type, …/scheme, …/healthcheck-port, …/healthcheck-path, …/group.name) are the load-bearing ones.
  4. Copy the Evidence Log template.
  5. Re-state the load-bearing constraints: (i) principal linkage from mesh ref, never from consumer string; (ii) allowFromGateway: false requires explicit acknowledgeNoAuthZ: true; (iii) ALB healthcheck defaults to gateway-native (port 15021, path /healthz/ready); (iv) scheme: "internal" is the default.
  6. Verify M2's IstioFoundationOutputs exposes ingressGatewayNamespace. If not, add it to M2 in a small follow-up before starting M3 (allowed because M2 outputs are load-bearing for M3 — bump it via a M2 patch + lesson).

Files Allowed To Change

File Planned Change
packages/k8s-baseline/src/alb-meshed-http-entrypoint.ts NEW: ComponentResource emitting 4 children
packages/k8s-baseline/src/alb-meshed-http-entrypoint.args.ts NEW
packages/k8s-baseline/src/alb-meshed-http-entrypoint.outputs.ts NEW
packages/k8s-baseline/tests/alb-meshed-http-entrypoint.test.ts NEW: BDD covering the 4-resource emission, principal linkage, abuse rows
packages/k8s-baseline/tests/integration/kind/alb-meshed-http-entrypoint.kind.test.ts NEW: kind test asserting the 4 K8s objects render correctly (ALB itself is not exercised in kind)
packages/k8s-baseline/src/index.ts MODIFY: re-export
docs/components/alb-meshed-http-entrypoint.md NEW (one-line stub)
docs/slo/completed/RUNBOOK-hulumi-k8s.md Milestone Tracker MODIFY
docs/slo/runbook-milestones/hulumi-k8s-m3.md MODIFY (Evidence Log only, in execution)
docs/slo/lessons/hulumi-k8s-m3.md NEW (during exit)
docs/slo/completion/hulumi-k8s-m3.md NEW (during exit)

Step-by-Step

  1. Verify M2's IstioFoundationOutputs exposes ingressGatewayNamespace; patch M2 if not.
  2. Write packages/k8s-baseline/tests/alb-meshed-http-entrypoint.test.ts BDD: happy path emits 4 children with correct kinds + namespaces; principal computed from mesh ref; missing acknowledgeNoAuthZ refused; invalid host refused; cross-ns VirtualService gateway ref correct; abuse rows.
  3. Implement args, outputs, component. The constructor: validates inputs; reads mesh.ingressGatewayServiceAccountName and ingressGatewayNamespace; emits kubernetes.networking.v1.Ingress (workload namespace, with the four ALB annotations + gateway service backend), kubernetes.apiextensions.CustomResource for Gateway (istio-system), VirtualService (workload namespace, gateway ref <istio-system>/<gateway-name>), AuthorizationPolicy (workload namespace, principals = [mesh-sa-principal, ...extraPrincipals]).
  4. Wire re-export. Run pnpm --filter @hulumi/k8s-baseline build && test && typecheck && lint — green.
  5. Write the kind integration test asserting the four objects exist with correct shape; deploy a tiny workload service for the entrypoint to point at.
  6. Run full repo regression sweep. Update Tracker, lessons, completion.

BDD Acceptance Scenarios

Feature: AlbMeshedHttpEntrypoint emits ALB Ingress + Istio Gateway/VirtualService/AuthorizationPolicy with principal linkage automatically wired from the mesh ref

Scenario Category Given When Then Threat-model row Control
Happy path — minimal args happy path mock-runtime; mesh is a constructed IstioFoundation; host: "api.example.internal"; serviceRef: { namespace: "prod", name: "api", port: 9090 } construction 4 children registered: Ingress in prod (with annotations: target-type: ip, scheme: internal, healthcheck-port: "15021", healthcheck-path: /healthz/ready, group.name: default, backend service is the istio-ingressgateway); Gateway in istio-system (selector matches gateway pods, host api.example.internal); VirtualService in prod (gateways [istio-system/<gateway-name>], host api.example.internal); AuthorizationPolicy in prod (from.principals = [<gateway SA principal>]) n/a n/a
Happy path — extraPrincipals appended happy path mock-runtime; authorizationPolicy: { allowFromGateway: true, extraPrincipals: ["spiffe://example.org/sa/sister-svc"] } construction AuthorizationPolicy from.principals = [<gateway-sa-principal>, "spiffe://example.org/sa/sister-svc"] (gateway always first; extra appended) n/a n/a
Happy path — scheme: "internet-facing" opt-in happy path mock-runtime; scheme: "internet-facing" construction Ingress annotation scheme: internet-facing n/a C5
Invalid input — invalid host invalid input mock-runtime; host: "*.example" construction constructor throws Error('AlbMeshedHttpEntrypoint: host must be a valid FQDN'); no children registered tm-hulumi-k8s-abuse-host-regex-bypass C5
Invalid input — invalid serviceRef.port invalid input mock-runtime; serviceRef.port: -1 construction constructor throws Error mentioning port must be 1-65535 n/a C5
Invalid input — invalid scheme invalid input mock-runtime; scheme: "external" (typo) construction constructor throws Error mentioning enum n/a C5
Empty state — mesh.ingressGateway: { enabled: false } empty state mock-runtime; mesh constructed with ingressGateway: { enabled: false } construction constructor throws Error('AlbMeshedHttpEntrypoint requires mesh.ingressGateway.enabled === true') n/a C5
Dependency failure — kubernetes.Provider unconfigured partial failure mock-runtime where the implicit kubernetes.Provider lacks credentials construction mock-runtime test passes; kind test gracefully skips when binary absent n/a env-var-gating
Abuse case — principal linkage load-bearing abuse case mock-runtime; valid args construction the AuthorizationPolicy from.principals[0] is identity-equal to mesh.ingressGatewayServiceAccountName's SPIFFE form (asserted directly against the mesh output, not against a string literal) tm-hulumi-k8s-abuse-principal-linkage-broken Forbidden shortcut (a)
Abuse case — allowFromGateway: false without acknowledgement refused abuse case mock-runtime; authorizationPolicy: { allowFromGateway: false } (no acknowledgeNoAuthZ) construction constructor throws Error('AlbMeshedHttpEntrypoint: allowFromGateway: false requires acknowledgeNoAuthZ: true and a non-empty extraPrincipals list') tm-hulumi-k8s-abuse-allow-everyone Forbidden shortcut (b)
Abuse case — host wildcard refused abuse case mock-runtime; host: "*" construction constructor throws — no AuthorizationPolicy without a real host tm-hulumi-k8s-abuse-host-regex-bypass C5
Abuse case — VirtualService cross-namespace ref present abuse case mock-runtime; mesh in istio-system, workload in prod construction VirtualService.spec.gateways = ["istio-system/<gateway-name>"] (cross-namespace form), NOT just ["<gateway-name>"] tm-hulumi-k8s-abuse-cross-ns-virtualservice n/a (correct-by-construction)
Compatibility — outputs lock schema / compatibility construction succeeds inspect outputs the 9 documented outputs are present (ingressName, ingressNamespace, gatewayName, gatewayNamespace, virtualServiceName, virtualServiceNamespace, authorizationPolicyName, authorizationPolicyNamespace, albAddress); none renamed n/a type-layer schema lock

Regression Tests

  • All M1 + M2 BDD scenarios continue to pass.
  • AWS + GitHub regression suites continue to pass.

Compatibility Checklist

  • Component + args + outputs + type constant exported.
  • pnpm install --frozen-lockfile && pnpm -r build && pnpm -r test && pnpm -r typecheck && pnpm -r lint && pnpm run lint:license-boundary && pnpm run lint:exact-pin-guard green.
  • License header on every new file.
  • serviceRef.{namespace,name,port} shape is load-bearing — documented in docs/components/alb-meshed-http-entrypoint.md stub.

E2E Runtime Validation

| E2E Test | What It Proves | Pass Criteria | | ------------------------------------------------ | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------- | | emits_four_objects_against_kind | The four-resource bundle renders correctly | Kind test: install M2 + this; kubectl get ingress,gateway,virtualservice,authorizationpolicy -A returns the 4 expected objects with correct names | | principal_present_in_authorization_policy_yaml | The principal linkage actually reaches the rendered manifest | kubectl get authorizationpolicy <name> -n <ns> -o yaml | grep principals shows the gateway SA principal | | cross_namespace_gateway_ref_present | The cross-ns ref is correctly rendered | kubectl get virtualservice <name> -n <ns> -o yaml | yq '.spec.gateways[0]'returnsistio-system/<gateway-name> |

Smoke Tests

  • Full sweep green.
  • Kind test green or skipped cleanly.
  • In a Pulumi program: new AlbMeshedHttpEntrypoint("e", { mesh, host: "*", serviceRef: { ... } } as any) causes preview to fail with the host-regex error.
  • git status clean.

Evidence Log

Step Command / Check Expected Result Actual Result Pass/Fail Notes
Baseline pnpm -r build && pnpm -r test green pre-M3 filled during execution pending
M2 outputs verification grep ingressGatewayNamespace in istio-foundation.outputs.ts present (or patched in this milestone) filled during execution pending
BDD tests created pnpm --filter @hulumi/k8s-baseline test -- alb-meshed-http-entrypoint fail for module-not-found filled during execution pending
Component impl filesystem source files present + re-exported filled during execution pending
Mock-runtime BDD pnpm --filter @hulumi/k8s-baseline test all rows pass filled during execution pending
Build / typecheck / lint pnpm -r build && pnpm -r typecheck && pnpm -r lint green filled during execution pending
Kind integration KIND_E2E=1 pnpm --filter @hulumi/k8s-baseline test:integration:kind green or skipped filled during execution pending
Smoke full sweep green filled during execution pending

Definition of Done

Same as M2 standard. + docs/issue-candidates.md strikes #41.

Post-Flight

  • Tracker M3 → done.
  • docs/components/alb-meshed-http-entrypoint.md stub.
  • docs/issue-candidates.md — strike #41.

Notes

  • The mesh.ingressGatewayServiceAccountNamefrom.principals linkage is the abstraction's load-bearing contract.
  • ALB-side runtime behavior (target health, connection draining, idle timeout) is NOT exercised in kind; M5's real-EKS smoke covers it.