Skip to content

Fix the shell-less cron-suspend image and five audit findings - #54

Open
henzigo wants to merge 2 commits into
mainfrom
audit-fixes-cron-suspend-and-s3-guard
Open

Fix the shell-less cron-suspend image and five audit findings#54
henzigo wants to merge 2 commits into
mainfrom
audit-fixes-cron-suspend-and-s3-guard

Conversation

@henzigo

@henzigo henzigo commented Aug 20, 2026

Copy link
Copy Markdown
Member

What

Six audit fixes. The headline is a hard blocker: every continuous deploy currently fails
at the first pre-upgrade hook.

Changes

1. cron-suspend hook: the kubectl image has no shell (critical)

rancher/kubectl:v1.33.13 contains only /bin/kubectl — no /bin/sh, entrypoint
/bin/kubectl. The hook runs command: [/bin/sh, -c, ...], so the container can never
start; with backoffLimit: 0 the Job fails immediately and every pre-upgrade with
cron.enabled (i.e. every continuous deploy) dies at hook weight 0.

The shell is genuinely required — the script relies on 2>/dev/null || true tolerance
(the cron deployment may not exist after a failed first install, and a kubectl wait
timeout must not fail the deploy). Splitting it into shell-less kubectl invocations
cannot preserve that, so the image was replaced, not the script:

kubectlImage: line/kubectl-kustomize:1.34.3-5.8.0   # was rancher/kubectl:v1.33.13

Verified locally with docker:

$ docker run --rm --entrypoint /bin/sh rancher/kubectl:v1.33.13 -c 'echo hi'
docker: Error response from daemon: ... exec: "/bin/sh": stat /bin/sh: no such file or directory

$ docker run --rm --entrypoint /bin/sh line/kubectl-kustomize:1.34.3-5.8.0 -c 'command -v kubectl && kubectl version --client'
/usr/local/bin/kubectl
Client Version: v1.34.3

Image choice (candidates compared by size, maintenance and tag retention):
line/kubectl-kustomize (LINE Corp) is actively maintained (last release 2026-08), is
~100 MB uncompressed and has never pruned a tag (history back to 1.16/2020), so a pinned
tag cannot disappear. The bundled kustomize is unused but harmless; the tag encodes
<kubectl>-<kustomize> versions (kubectl 1.33 was skipped upstream, 1.34.3 is the closest
to the previous pin). Rejected: alpine/kubectl (95 MB but keeps only ~15 tags — pinned
tags vanish after about a year), alpine/k8s (never prunes but ~1.2 GB), official
registry.k8s.io/kubectl (distroless, no shell), bitnami/kubectl (moved to the
unmaintained bitnamilegacy distribution, now publishes only latest), d3fk/kubectl
(versioned tags stale since 2024). A new unittest case pins command[0] == /bin/sh and
the image so this cannot regress silently.

2. app.s3Endpoint is now enforced by the default nginx vhost (high)

With the default empty value the vhost renders proxy_pass /<project>-<env>/web....
nginx -t passes (the URL contains variables, so it is only validated per request) but
every /content/* request fails at runtime with "invalid URL prefix" → HTTP 500.

A fail guard was added at the top of charts/shopsys-app/files/nginx/project-nginx.conf
(rendered through tpl). It is scoped to that file, so projects supplying
webserver.nginx.projectConfig are not affected — which is also why
values.schema.json was deliberately left alone. environments/base.yaml and
charts/shopsys-app/tests/values/required.yaml (the lint/unittest baseline) now set a
placeholder endpoint.

3. kubeVersion vs the HPA ContainerResource metric (low)

hpa-webserver.yaml uses metric type ContainerResource, enabled by default only since
Kubernetes 1.27 (GA 1.30), while the chart declared >=1.23.0-0. Bumped shopsys-app to
>=1.27.0-0; shopsys-infra is unchanged.

4. Infra pull-secret chicken-and-egg (docs only, low)

The dockerregistry Secret is created by the shopsys-app release, but shopsys-infra
installs before it with wait: true — a private redis/rabbitmq image would hang the very
first deploy in ImagePullBackOff. Documented in charts/shopsys-infra/values.yaml and
docs/values.md, pointing at a pre-created registry.existingSecret. No template changes.

5. Removed the no-op ingress.kubernetes.io/ssl-redirect: "true" (low)

It lacks the nginx. prefix, so ingress-nginx ignored it entirely — the https redirect is
actually done by the configuration-snippet and by the controller's default ssl-redirect
for TLS ingresses. Removed from ingress-domains.yaml, ingress-mcp.yaml and
ingress-rabbitmq.yaml (51 lines across the snapshots), with a notExists regression
assertion. As a deliberate departure from the 1:1 port it is recorded as deviation 21
in docs/migrating-from-shopsys-deployment.md.

6. Storefront Deployment metadata label (low)

deployment-storefront.yaml was the only Deployment missing app: storefront in
metadata.labels. Added under the shopsys.labels include, matching the other
deployments. The pod template and the (immutable) selector were not touched.

Notes

Golden snapshots regenerated in the same commit. The diff is exactly the three expected
kinds of change and nothing else:

  51 -    ingress.kubernetes.io/ssl-redirect: "true"
  15 -          image: "rancher/kubectl:v1.33.13"
  15 +          image: "line/kubectl-kustomize:1.34.3-5.8.0"
  15 +    app: storefront

Verification suite, all green:

Check Result
helm unittest (both charts) 9 suites, 63 tests passed (3 new: shell-capable image, s3Endpoint guard fires, custom vhost exempt + ssl-redirect notExists)
./tests/run-golden-tests.sh 15/15 passed
helm lint (infra + app) 0 failed
kubeconform -strict (k8s 1.31.0) 531 resources, 531 valid
shellcheck (4 scripts) clean
helmfile -e devel template renders

Negative tests for the s3Endpoint guard:

$ helm template charts/shopsys-app -f .../required.yaml --set app.s3Endpoint=""
Error: ... app.s3Endpoint is required by the default nginx vhost (content images are proxied to S3); ...

$ helm template charts/shopsys-app -f .../required.yaml --set app.s3Endpoint="" \
    --set webserver.nginx.projectConfig="server { listen 8080; }"
RENDER OK with custom projectConfig

Out of scope by design (a separate PR covers the probes): ingress snippet
annotations / annotations-risk-level, hook and cron timeout tuning, http-auth htpasswd
determinism, security.mcp defaults, RabbitMQ probes.

🤖 Generated with Claude Code

henzigo and others added 2 commits August 20, 2026 14:01
The cron-suspend hook ran `/bin/sh -c ...` on rancher/kubectl:v1.33.13, an image
that ships only /bin/kubectl. The container could never start, and with
backoffLimit: 0 the Job failed immediately — so every pre-upgrade with
cron.enabled (i.e. every continuous deploy) died at hook weight 0. The script's
`2>/dev/null || true` tolerance genuinely needs a shell, so the fix is the image,
not the script: alpine/k8s:1.33.13 keeps the same pinned kubectl version and adds
/bin/sh (verified with `docker run --entrypoint /bin/sh`).

Also in this commit:

- app.s3Endpoint is now enforced by the default nginx vhost. Empty renders passed
  `nginx -t` (the proxy_pass URL contains variables, so it is validated per
  request) but every /content/* request failed with "invalid URL prefix". The
  guard lives in files/nginx/project-nginx.conf only, so projects with their own
  webserver.nginx.projectConfig are unaffected; the schema is untouched for the
  same reason.
- shopsys-app kubeVersion >=1.27.0-0: hpa-webserver.yaml uses the
  ContainerResource metric type, enabled by default only since 1.27 (GA 1.30).
- Documented the infra pull-secret ordering: the dockerregistry Secret comes from
  the app release, which installs after shopsys-infra — private infra images need
  registry.existingSecret pre-created out of band.
- Dropped `ingress.kubernetes.io/ssl-redirect: "true"` from all three ingresses.
  Without the `nginx.` prefix ingress-nginx never read it; the https redirect
  comes from the configuration-snippet and the controller's TLS default.
- Added the missing `app: storefront` metadata label to the storefront
  Deployment (pod template and selector already had it, untouched).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
alpine/k8s ships a full toolchain (~1.2 GB). line/kubectl-kustomize is
actively maintained, keeps all historical tags (unlike alpine/kubectl,
which prunes pinned tags after ~a year) and is ~100 MB. Verified with
docker: /bin/sh present, kubectl v1.34.3, shell tolerance works.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant