Skip to content

fix(gke): plumb UseSSL through GKE Autopilot deploy path#302

Merged
Cre-eD merged 1 commit into
mainfrom
fix/gke-autopilot-usessl-default
May 30, 2026
Merged

fix(gke): plumb UseSSL through GKE Autopilot deploy path#302
Cre-eD merged 1 commit into
mainfrom
fix/gke-autopilot-usessl-default

Conversation

@Cre-eD

@Cre-eD Cre-eD commented May 29, 2026

Copy link
Copy Markdown
Contributor

Summary

The GKE Autopilot cloud-compose deploy path silently omits import hsts from every per-stack Caddyfile entry because kubernetes.Args.UseSSL is never set — it lands at the Go bool zero value (false), and simple_container.go gates import hsts on args.UseSSL. Result: the parent Caddy's (hsts) snippet never fires for GKE Autopilot deploys, no matter what lbConfig.https the consumer declares.

Asymmetric with the Cloudrun deploy path (kube_run.go:102), which already wires args.UseSSL correctly.

Purpose

Make HSTS work out of the box on the GKE Autopilot deploy path for any stack that declares it's HTTPS-fronted. Today, consumers who set lbConfig.https: true in their stack yaml get the https:// site-block protocol (and TLS termination on Caddy), but not the corresponding import hsts — they have to add header_down Strict-Transport-Security manually via lbConfig.extraHelpers to get any HSTS, and even then it only fires on proxied responses (not static assets / error pages).

After this fix, lbConfig.https: true is the single user-facing signal that turns on both the HTTPS site block and the HSTS snippet — same intent, same knob.

Root cause

kubernetes.Args in pkg/clouds/pulumi/gcp/gke_autopilot_stack.go was constructed without UseSSL:

kubeArgs := kubernetes.Args{
    Input:                  input,
    Deployment:             gkeAutopilotInput.Deployment,
    Images:                 images,
    Params:                 params,
    KubeProvider:           kubeProvider,
    ComputeContext:         params.ComputeContext,
    // UseSSL: unset, defaults to false
    GenerateCaddyfileEntry: domain != "",
    ...
}

if args.UseSSL { imports = append(imports, "import hsts") } → never executes for GKE → (hsts) snippet inert → no HSTS header on responses.

Verified by reading a live Service annotation: import gzip + import handle_static present, import hsts missing.

Fix

In gke_autopilot_stack.go, derive useSSL from the consumer's lbConfig.https setting (the same per-stack yaml field that already controls the site-block protocol in the same rendering pass) and pass it through to kubernetes.Args:

var useSSL bool
if sc := gkeAutopilotInput.Deployment.StackConfig; sc != nil {
    useSSL = lo.FromPtr(sc.LBConfig).Https
}

One signal in, two aligned outputs (site-block protocol + import hsts).

Approach choice

Considered adding a separate UseSSL *bool field on GkeAutopilotTemplate (mirror CloudrunTemplate.UseSSL exactly). Rejected because:

  • lbConfig.https is already the consumer-facing declaration of HTTPS intent — adding a second knob at the template level would duplicate the signal.
  • Per-stack granularity is more useful than per-template (different consumers may want different defaults).
  • Smaller diff, less API surface, no new schema or test scaffolding.

The CloudrunTemplate path keeps its independent UseSSL knob for now (out of scope for this fix); future work could harmonize, but no behaviour change there.

Compatibility

  • Stacks with lbConfig.https: true now get import hsts automatically. The parent (hsts) snippet (in embed/caddy/Caddyfile) sets Strict-Transport-Security: max-age=31536000; includeSubDomains; preload site-level and redirects HTTP→HTTPS on X-Forwarded-Proto: http.
  • Stacks without lbConfig.https: true continue to get no HSTS — byte-identical behaviour to today's broken state. No silent default flip; no risk to plain-HTTP GKE deployments.
  • The (hsts) snippet's redir is gated on X-Forwarded-Proto: http (matched only when an upstream proxy sets it), so HTTP-only origins that aren't fronted by such a proxy see no redirect loop. The always-on Strict-Transport-Security header is the sticky bit — only fires on stacks that explicitly opted into HTTPS via lbConfig.https: true, which is exactly the right consent model.

Test plan

  • Existing kubernetes test suite green (pkg/clouds/pulumi/kubernetes/...).
  • SC preview build (branch-preview.yaml) tagged — see linked verification comment.
  • After pinning a downstream consumer's staging deploy to the preview SC version, the rendered caddyfile-entry annotation on the K8s Service now contains import hsts alongside import gzip + import handle_static. Pre-fix that line was missing on the same Service.

Reviewer feedback addressed

Original revision of this PR added a UseSSL *bool field to GkeAutopilotTemplate (mirroring CloudrunTemplate). Reviewer feedback flagged this as adding unnecessary API surface when lbConfig.https already carried the HTTPS-intent signal. This revision drops the new field and derives directly from the existing per-stack flag.

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown

Semgrep Scan Results

Repository: api | Commit: 885b6fb

Check Status Details
✅ Semgrep Pass 0 total findings (no error/warning)

Scanned at 2026-05-30 08:13 UTC

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown

Security Scan Results

Repository: api | Commit: 885b6fb

Check Status Details
✅ Secret Scan Pass No secrets detected
✅ Dependencies (Trivy) Pass 0 total (no critical/high)
✅ Dependencies (Grype) Pass 0 total (no critical/high)
📦 SBOM Generated 527 components (CycloneDX)

Scanned at 2026-05-30 09:55 UTC

@Cre-eD

Cre-eD commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end verification on a preview build — PASSED

Built SC preview v2026.5.42-pre.71077b9-preview.71077b9 from this branch, pinned a downstream consumer's deploy workflow to it on a throwaway test branch, deployed to staging.

Before/after the Service annotation

Pre-fix (live state of a long-running GKE Autopilot deploy, queried via kubectl get svc -o jsonpath):

http://<host> {
  reverse_proxy http://<svc>.<ns>.svc.cluster.local:3000 {
    header_down Server nginx
    import handle_server_error
  }
  import gzip
  import handle_static
}

Post-fix (same query, after deploying via the preview SC):

http://<host> {
  reverse_proxy http://<svc>.<ns>.svc.cluster.local:3000 {
    header_down Server nginx
    import handle_server_error
  }
  import gzip
  import handle_static
  import hsts          ← NEW
}

import hsts is now correctly emitted on the GKE Autopilot deploy path. The parent Caddy's (hsts) snippet now fires for these stacks (sets Strict-Transport-Security site-level, redirects HTTP→HTTPS on X-Forwarded-Proto: http), bringing GKE behavior into parity with Cloudrun.

Ready to merge.

The GKE Autopilot cloud-compose deploy path in
`pkg/clouds/pulumi/gcp/gke_autopilot_stack.go` was building a
`kubernetes.Args{}` struct without setting `UseSSL`, so it landed at
the Go bool zero value (`false`). The per-stack Caddyfile rendering in
`simple_container.go:707-708` then never emitted `import hsts`:

    if args.UseSSL {
        imports = append(imports, "import hsts")
    }

Effect: every GKE Autopilot deploy silently omitted `import hsts` from
its rendered Caddyfile entry, so the parent Caddy's `(hsts)` snippet
never fired — no HSTS site-level header, no HTTP→HTTPS redirect — even
for stacks declaring `lbConfig.https: true` in their stack yaml.

The Cloudrun deploy path (`kube_run.go:102`) was already correctly
setting `args.UseSSL`, so this is a GKE-specific asymmetry.

Verified by reading a live K8s Service annotation pre-fix: `import gzip`
+ `import handle_static` were present, `import hsts` missing. After
fix (validated via a preview build of this branch), `import hsts`
appears on the same Service.

Fix: derive `useSSL` from the consumer's `lbConfig.https` setting
(the existing per-stack yaml field that already declares "this stack
is HTTPS-fronted") and pass it to `kubernetes.Args.UseSSL`. Same field
also determines the site-block protocol in the same rendering pass
(`proto = lo.If(lbConfig.Https, "https").Else("http")`), so the two
signals stay aligned. Stacks not setting `lbConfig.https: true`
continue to get no HSTS — matching today's behaviour exactly for the
non-HTTPS case.

No new schema fields; no new template-level knobs. One signal, one
behaviour: declare HTTPS at the consumer level, get HSTS automatically.

Two SC outputs gated by `args.UseSSL` in the kubernetes.Args contract
that this PR therefore turns on for HTTPS GKE stacks:
1. `import hsts` in the per-stack Caddyfile entry → the parent Caddy's
   `(hsts)` snippet sets Strict-Transport-Security site-level (covers
   ALL response paths including static assets + error pages, not just
   proxied responses) AND redirects HTTP→HTTPS on
   X-Forwarded-Proto: http.
2. `ingress.kubernetes.io/ssl-redirect: false` annotation on the
   Ingress (Caddy owns the redirect, so the ingress shouldn't).

Both already correctly fire on the Cloudrun path; this fix brings GKE
to parity.

No tests added: the existing `TestCaddyfileEntry_*` suite covers the
rendering once `args.UseSSL` is true. The derivation itself is a
single expression too small to need its own unit test — end-to-end via
SC preview pipeline is the meaningful verification.

Signed-off-by: Dmitrii Creed <creeed22@gmail.com>
@Cre-eD
Cre-eD force-pushed the fix/gke-autopilot-usessl-default branch from 71077b9 to 8203458 Compare May 30, 2026 08:12
@Cre-eD

Cre-eD commented May 30, 2026

Copy link
Copy Markdown
Contributor Author

End-to-end verification of the simplified fix — PASSED

Built SC preview v2026.5.42-pre.8203458-preview.8203458 from this branch, pinned a downstream consumer's deploy workflow to it on a throwaway test branch, set lbConfig.https: true on the staging stack, and deployed.

Rendered caddyfile-entry annotation (verbatim from kubectl)

https://<host> {
  reverse_proxy http://<svc>.<ns>.svc.cluster.local:3000 {
    header_down Server nginx
    import handle_server_error
  }
  import gzip
    import handle_static
    import hsts
}

Three properties confirmed:

  1. Site block declares https:// — driven by lbConfig.https: true (existing field).
  2. import hsts lands at site level alongside import gzip + import handle_static.
  3. Single signal drives bothlbConfig.https: true is the sole knob the consumer sets; no new template field required.

Compared to the previous revision (template-field approach)

The earlier revision of this PR added a UseSSL *bool field to GkeAutopilotTemplate mirroring CloudrunTemplate.UseSSL (default true). It also worked end-to-end (verified earlier), but added template-level API surface and decoupled the HSTS signal from the existing HTTPS-intent signal. The current revision drops that field and derives useSSL from lbConfig.https directly:

var useSSL bool
if sc := gkeAutopilotInput.Deployment.StackConfig; sc != nil {
    useSSL = lo.FromPtr(sc.LBConfig).Https
}

Diff is now 1 file / 25 lines (down from 4 files / 156 lines on the previous rev).

Ready to merge.

@Cre-eD
Cre-eD merged commit d5ab1c1 into main May 30, 2026
36 of 38 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants