a7871ed7c6
The hook's container was alpine:3 and its first line was `apk add --no-cache curl`. That writes the binary into the container's writable upper layer, and every exec of it afterwards is, correctly, a dropped binary: Falco's `Drop and execute new binary in container` (PCI_DSS_11.5.1, MITRE TA0003) fired twice at Critical on the upgrade to chart 0.9.3, 65ms after the container started, with evt.arg.flags=EXE_WRITABLE|EXE_UPPER_LAYER. Ryuvia/charts#100 has the event lines. A true positive of the rule and a false positive of intent, and it is not a one-off: the hook is post-install,post-upgrade, so it recurred on every release. The cluster is still in the Falco burn-in with detections routed to a null receiver, which is the only reason nobody was paged for it. Fixed here rather than with a Falco exception on purpose. An exception would have to name this container and would then stay in the rule set forever, blinding it for the one workload that already runs as root with create-secret RBAC, and it would leave the second problem untouched: this runs as a post-upgrade hook, a failed hook fails the release, so every `helm upgrade` of terdut-server depended on dl-cdn.alpinelinux.org answering. That dependency is now gone. alpine/curl is still a full Alpine, so sh, cat, sleep, grep, cut, head and tail are all present -- verified in-cluster before the swap rather than assumed, since a missing utility would surface as a failed post-upgrade hook and not as anything visible here. Digest-pinned, as the wrapper chart's own sidecar images are. The image declares an ENTRYPOINT, which the Job's `command:` overrides; a comment says so, because rewriting that to `args:` would silently run curl's entrypoint instead of the script. No change to the script's logic, to the RBAC, or to when the hook runs. Nothing on the terdut-tui side of the API moves, and no terdut-tui version is required or excluded by this. Worth recording while it is in view, and deliberately not acted on here: there is no terdut-server-admin-key secret in the namespace, so the POST returns 403, the hook logs "Server already bootstrapped, nothing to do" and exits before the secret-creating branch. On an upgrade this hook currently achieves nothing at all. Narrowing it to post-install would remove the detection outright, but that changes what the hook is for and belongs in its own change. Claude-Session: https://claude.ai/code/session_014m2pJdpCTv3mvvUUuBM54Y
102 lines
4.3 KiB
YAML
102 lines
4.3 KiB
YAML
{{- if .Values.bootstrap.enabled }}
|
|
---
|
|
apiVersion: batch/v1
|
|
kind: Job
|
|
metadata:
|
|
name: {{ include "terdut-server.fullname" . }}-bootstrap
|
|
namespace: {{ .Release.Namespace }}
|
|
labels:
|
|
{{- include "terdut-server.labels" . | nindent 4 }}
|
|
annotations:
|
|
helm.sh/hook: post-install,post-upgrade
|
|
helm.sh/hook-weight: "0"
|
|
helm.sh/hook-delete-policy: before-hook-creation,hook-succeeded
|
|
spec:
|
|
backoffLimit: 3
|
|
template:
|
|
metadata:
|
|
labels:
|
|
{{- include "terdut-server.selectorLabels" . | nindent 8 }}
|
|
app.kubernetes.io/component: bootstrap
|
|
spec:
|
|
restartPolicy: OnFailure
|
|
serviceAccountName: {{ include "terdut-server.fullname" . }}-bootstrap
|
|
containers:
|
|
- name: bootstrap
|
|
# alpine/curl, not alpine:3 + `apk add curl`. Installing the binary at run time
|
|
# writes it into the container's writable upper layer, which is exactly the
|
|
# signature Falco's `Drop and execute new binary in container` (MITRE TA0003)
|
|
# exists to catch -- this hook emitted two Critical events on every single
|
|
# upgrade. See Ryuvia/charts#100. It also made `helm upgrade` depend on the
|
|
# Alpine CDN answering, since this runs as a post-upgrade hook and a failed
|
|
# hook fails the release.
|
|
#
|
|
# Still a full Alpine underneath, so sh, cat, sleep, grep, cut, head and tail
|
|
# are all present (verified in-cluster 2026-09-04). The image declares
|
|
# ENTRYPOINT ["/entrypoint.sh"], which `command:` below overrides -- do not
|
|
# change `command:` to `args:`.
|
|
image: alpine/curl:8.21.0@sha256:a1c44bab54d88e18ea9a6a4ecefab7f2d230b968567b78960fcaff8d51b7f067
|
|
command:
|
|
- /bin/sh
|
|
- -c
|
|
- |
|
|
SERVICE_URL="http://{{ include "terdut-server.fullname" . }}:{{ .Values.service.port }}"
|
|
SECRET_NAME="{{ include "terdut-server.bootstrapSecretName" . }}"
|
|
K8S_API="https://kubernetes.default.svc"
|
|
SA_TOKEN="$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)"
|
|
CA_CERT="/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
|
|
NAMESPACE="$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)"
|
|
|
|
echo "Waiting for terdut-server to be ready..."
|
|
RETRIES=60
|
|
while [ "$RETRIES" -gt 0 ]; do
|
|
curl -sf "$SERVICE_URL/healthz" > /dev/null 2>&1 && break
|
|
RETRIES=$((RETRIES - 1))
|
|
sleep 2
|
|
done
|
|
if [ "$RETRIES" -eq 0 ]; then
|
|
echo "Timed out waiting for server to be ready."
|
|
exit 1
|
|
fi
|
|
echo "Server is ready."
|
|
|
|
RESPONSE=$(curl -s -w "\n%{http_code}" -X POST "$SERVICE_URL/api/bootstrap" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"username":"{{ .Values.bootstrap.username }}","email":"{{ .Values.bootstrap.email }}"}')
|
|
|
|
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
|
|
BODY=$(echo "$RESPONSE" | head -1)
|
|
|
|
if [ "$HTTP_CODE" = "403" ]; then
|
|
echo "Server already bootstrapped, nothing to do."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "Bootstrap failed (HTTP $HTTP_CODE): $BODY"
|
|
exit 1
|
|
fi
|
|
|
|
API_KEY=$(echo "$BODY" | grep -o '"key":"[^"]*"' | cut -d'"' -f4)
|
|
if [ -z "$API_KEY" ]; then
|
|
echo "Failed to extract API key from response."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Bootstrap succeeded. Storing API key in secret '$SECRET_NAME'."
|
|
|
|
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" \
|
|
-X POST "$K8S_API/api/v1/namespaces/$NAMESPACE/secrets" \
|
|
--cacert "$CA_CERT" \
|
|
-H "Authorization: Bearer $SA_TOKEN" \
|
|
-H "Content-Type: application/json" \
|
|
-d "$(printf '{"apiVersion":"v1","kind":"Secret","metadata":{"name":"%s"},"stringData":{"api-key":"%s"}}' "$SECRET_NAME" "$API_KEY")")
|
|
|
|
if [ "$HTTP_CODE" != "201" ]; then
|
|
echo "Failed to create secret (HTTP $HTTP_CODE)."
|
|
exit 1
|
|
fi
|
|
|
|
echo "Secret '$SECRET_NAME' created successfully."
|
|
{{- end }}
|