Skip to content

Commit dc3fda1

Browse files
author
Michel Jansen
committed
test(kubernetes): group sysctl safety checks with heredoc tests
The heredoc suite owns escaped-input and render-time rejection coverage. Keeping sysctl safety checks there leaves bootstrap-name agreement tests focused on the shared content hash. Assisted-by: LLM Signed-off-by: Michel Jansen <[email protected]>
1 parent 359c082 commit dc3fda1

2 files changed

Lines changed: 105 additions & 88 deletions

File tree

‎hack/check-talosconfigtemplate-name-agreement.bats‎

Lines changed: 0 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -185,85 +185,3 @@ assert_agreement() {
185185
[ "$(job_tct_name "$TMP/r.yaml")" = "$baseline" ]
186186
rm -rf "$TMP"
187187
}
188-
189-
@test "sysctl names and values survive the actual Job heredoc literally without shell execution" {
190-
TMP=$(mktemp -d)
191-
cat > "$TMP/values.yaml" <<'VALUES'
192-
talos:
193-
sysctls:
194-
user.max_user_namespaces: "11255"
195-
net.ipv4.ip_local_port_range: "1024\t65535"
196-
kernel.panic: "0"
197-
'$(touch "$SYSCTL_MARKER")': '$(touch "$SYSCTL_MARKER")'
198-
'`touch "$SYSCTL_MARKER"`': '`touch "$SYSCTL_MARKER"`'
199-
'${SYSCTL_MARKER}': '${SYSCTL_MARKER}'
200-
'backslash\$(touch "$SYSCTL_MARKER")': 'backslash\$(touch "$SYSCTL_MARKER")'
201-
"quotes\"\\\nEOF\n$(touch \"$SYSCTL_MARKER\")": "quotes\"\\\nEOF\n$(touch \"$SYSCTL_MARKER\")"
202-
"unicode\u0085EOF\u2028EOF\u2029": "unicode\u0085EOF\u2028EOF\u2029"
203-
"empty": ""
204-
VALUES
205-
render_pool "$TMP/r.yaml" -f "$TMP/values.yaml"
206-
yq -r 'select(.kind == "Job") | .spec.template.spec.containers[0].command[2]' "$TMP/r.yaml" > "$TMP/job.sh"
207-
# Execute only the real manifest-producing heredoc, never the surrounding
208-
# kubectl/Talos operations. YAML is parsed on both sides of the shell so
209-
# escaping that prevents execution but corrupts the machine config also fails.
210-
awk '/^TCT_MANIFEST=/{emit=1} emit{print} emit && /^\)"$/{exit}' "$TMP/job.sh" > "$TMP/heredoc.sh"
211-
[ -s "$TMP/heredoc.sh" ]
212-
printf '\nprintf "%%s\\n" "$TCT_MANIFEST"\n' >> "$TMP/heredoc.sh"
213-
yq -o=json '.talos.sysctls | sort_keys(.)' "$TMP/values.yaml" > "$TMP/expected.json"
214-
for shell in bash dash; do
215-
SYSCTL_MARKER="$TMP/executed" TALOS_TOKEN=runtime-token "$shell" "$TMP/heredoc.sh" > "$TMP/tct.yaml"
216-
[ ! -e "$TMP/executed" ]
217-
yq -o=json '.spec.template.spec.data | from_yaml | .machine.sysctls | sort_keys(.)' "$TMP/tct.yaml" > "$TMP/actual.json"
218-
diff -u "$TMP/expected.json" "$TMP/actual.json"
219-
[ "$(yq '.spec.template.spec.data | from_yaml | .machine.token' "$TMP/tct.yaml")" = "runtime-token" ]
220-
done
221-
rm -rf "$TMP"
222-
}
223-
224-
@test "sysctl types are rejected by both the schema and the render-time guard" {
225-
TMP=$(mktemp -d)
226-
for value in '11255' 'false' 'null' '["1"]' '{"evil":"$(id)"}'; do
227-
input="talos.sysctls={\"kernel.panic\":$value}"
228-
if render_pool "$TMP/r.yaml" --set-json "$input"; then
229-
echo "FAIL: schema accepted a non-string sysctl: $value"
230-
false
231-
fi
232-
grep -q 'values don.t meet the specifications' "$TMP/r.yaml.err"
233-
if render_pool "$TMP/r.yaml" --skip-schema-validation --set-json "$input"; then
234-
echo "FAIL: template accepted a non-string sysctl: $value"
235-
false
236-
fi
237-
grep -Fq 'talos.sysctls["kernel.panic"] must be a string' "$TMP/r.yaml.err"
238-
done
239-
for value in '"$(id)"' 'false' '0' '[]' '["1"]'; do
240-
input="talos.sysctls=$value"
241-
if render_pool "$TMP/r.yaml" --set-json "$input"; then
242-
echo "FAIL: schema accepted a non-map sysctls: $value"
243-
false
244-
fi
245-
grep -q 'values don.t meet the specifications' "$TMP/r.yaml.err"
246-
if render_pool "$TMP/r.yaml" --skip-schema-validation --set-json "$input"; then
247-
echo "FAIL: template accepted a non-map sysctls: $value"
248-
false
249-
fi
250-
grep -Fq 'talos.sysctls must be a map of strings' "$TMP/r.yaml.err"
251-
done
252-
rm -rf "$TMP"
253-
}
254-
255-
@test "sysctl file values reject invalid UTF-8 rather than changing their bytes" {
256-
TMP=$(mktemp -d)
257-
# --set-file reads raw bytes, unlike the YAML/JSON-facing application API.
258-
for bytes in '\377' '\300\257' '\342\202'; do
259-
printf '%b' "$bytes" > "$TMP/value"
260-
if render_pool "$TMP/r.yaml" --skip-schema-validation --set-file "talos.sysctls.kernel\\.panic=$TMP/value"; then
261-
echo "FAIL: accepted invalid UTF-8 sysctl bytes: $bytes"
262-
false
263-
fi
264-
grep -Fq 'must be valid UTF-8' "$TMP/r.yaml.err"
265-
done
266-
printf '\357\277\275' > "$TMP/value"
267-
render_pool "$TMP/r.yaml" --skip-schema-validation --set-file "talos.sysctls.kernel\\.panic=$TMP/value"
268-
rm -rf "$TMP"
269-
}

‎hack/talos-reconcile-heredoc_test.bats‎

Lines changed: 105 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
# -----------------------------------------------------------------------------
33
# Execution-level regression test for the worker TalosConfigTemplate heredoc:
44
# it pins that the reconcile Job's unquoted heredoc still emits a valid config
5-
# when talos.registryMirrors (and the Talos image coordinates) carry hostile
6-
# free-form input.
5+
# when talos.registryMirrors, talos.sysctls and the Talos image coordinates
6+
# carry hostile free-form input.
77
#
88
# Since the Phase 2 split the worker pool, its talos-reconcile Job, and the
99
# TalosConfigTemplate it applies live only in the kubernetes-nodes chart; the
@@ -13,8 +13,8 @@
1313
# The reconcile Job applies the TalosConfigTemplate via an UNQUOTED `cat <<EOF`
1414
# heredoc, captured into a variable and then applied, so every line of its body
1515
# is subject to shell parameter expansion and command substitution at Job
16-
# runtime. The `talos.registryMirrors` knob and the Talos image coordinates
17-
# render free-form tenant-facing input into that heredoc. A helm-unittest
16+
# runtime. The `talos.registryMirrors` and `talos.sysctls` knobs and the Talos
17+
# image coordinates render free-form tenant-facing input into that heredoc. A helm-unittest
1818
# string `matchRegex` cannot catch a heredoc that the shell refuses to emit
1919
# (e.g. an unbalanced backtick from an un-escaped value): it never runs the
2020
# shell. This test does.
@@ -26,23 +26,40 @@
2626
# escaped the value reaches the heredoc and is escaped for backslash,
2727
# dollar and backtick. Assert it survives a real shell as a
2828
# LITERAL. This covers talos.installerRepository and
29-
# talos.registryMirrors, both genuinely free-form.
29+
# talos.registryMirrors and talos.sysctls (both keys and values).
3030
# validated the value is refused at render before it can reach the heredoc.
3131
# Assert the refusal. This covers talos.schematicID and
3232
# talos.version, which also land in the worker DataVolume name and
3333
# the image URL, where an unquoted YAML scalar makes escaping the
3434
# wrong tool. templates/nodegroup.yaml pattern-checks them. It also
3535
# covers kernelModules, whose names and parameters
3636
# templates/_helpers.tpl checks before rebuilding the list.
37+
# Sysctl map/value types and UTF-8 are validated before escaping.
3738
#
3839
# A field that is in neither arm is the regression this file exists to catch.
3940
#
40-
# Needs `helm` + `yq` and must run from the repo root.
41+
# Needs `helm`, `yq`, Bash and Dash and must run from the repo root.
4142
# Run with: bats hack/talos-reconcile-heredoc_test.bats
4243
# -----------------------------------------------------------------------------
4344

4445
load test_helper
4546

47+
render_sysctl_pool() {
48+
out="$1"; shift
49+
helm template kubernetes-nodes-myk8s-md0 packages/apps/kubernetes-nodes \
50+
--namespace tenant-test \
51+
--set cluster=myk8s \
52+
--set _cluster.cluster-domain=cozy.local \
53+
--set version=v1.35 \
54+
--set minReplicas=0 --set maxReplicas=3 \
55+
--set instanceType="" \
56+
--set diskSize=20Gi --set storageClass=replicated \
57+
--set 'roles[0]=ingress-nginx' \
58+
--set resources.cpu=2 --set resources.memory=4Gi \
59+
"$@" > "$out" 2>"$out.err"
60+
[ -s "$out" ] || { cat "$out.err" >&2; return 1; }
61+
}
62+
4663
@test "kubernetes-nodes refuses hostile Talos image coordinates at render" {
4764
work=$(mktemp -d)
4865
cat > "$work/vals.yaml" <<'VALS'
@@ -334,3 +351,85 @@ VALS
334351
|| { echo "kernelModules parameter was not preserved literally" >&2; printf '%s\n' "$out" | grep -A6 'kernel:' >&2; rm -rf "$work"; exit 1; }
335352
rm -rf "$work"
336353
}
354+
355+
@test "sysctl names and values survive the actual Job heredoc literally without shell execution" {
356+
TMP=$(mktemp -d)
357+
cat > "$TMP/values.yaml" <<'VALUES'
358+
talos:
359+
sysctls:
360+
user.max_user_namespaces: "11255"
361+
net.ipv4.ip_local_port_range: "1024\t65535"
362+
kernel.panic: "0"
363+
'$(touch "$SYSCTL_MARKER")': '$(touch "$SYSCTL_MARKER")'
364+
'`touch "$SYSCTL_MARKER"`': '`touch "$SYSCTL_MARKER"`'
365+
'${SYSCTL_MARKER}': '${SYSCTL_MARKER}'
366+
'backslash\$(touch "$SYSCTL_MARKER")': 'backslash\$(touch "$SYSCTL_MARKER")'
367+
"quotes\"\\\nEOF\n$(touch \"$SYSCTL_MARKER\")": "quotes\"\\\nEOF\n$(touch \"$SYSCTL_MARKER\")"
368+
"unicode\u0085EOF\u2028EOF\u2029": "unicode\u0085EOF\u2028EOF\u2029"
369+
"empty": ""
370+
VALUES
371+
render_sysctl_pool "$TMP/r.yaml" -f "$TMP/values.yaml"
372+
yq -r 'select(.kind == "Job") | .spec.template.spec.containers[0].command[2]' "$TMP/r.yaml" > "$TMP/job.sh"
373+
# Execute only the real manifest-producing heredoc, never the surrounding
374+
# kubectl/Talos operations. YAML is parsed on both sides of the shell so
375+
# escaping that prevents execution but corrupts the machine config also fails.
376+
awk '/^TCT_MANIFEST=/{emit=1} emit{print} emit && /^\)"$/{exit}' "$TMP/job.sh" > "$TMP/heredoc.sh"
377+
[ -s "$TMP/heredoc.sh" ]
378+
printf '\nprintf "%%s\\n" "$TCT_MANIFEST"\n' >> "$TMP/heredoc.sh"
379+
yq -o=json '.talos.sysctls | sort_keys(.)' "$TMP/values.yaml" > "$TMP/expected.json"
380+
for shell in bash dash; do
381+
SYSCTL_MARKER="$TMP/executed" TALOS_TOKEN=runtime-token "$shell" "$TMP/heredoc.sh" > "$TMP/tct.yaml"
382+
[ ! -e "$TMP/executed" ]
383+
yq -o=json '.spec.template.spec.data | from_yaml | .machine.sysctls | sort_keys(.)' "$TMP/tct.yaml" > "$TMP/actual.json"
384+
diff -u "$TMP/expected.json" "$TMP/actual.json"
385+
[ "$(yq '.spec.template.spec.data | from_yaml | .machine.token' "$TMP/tct.yaml")" = "runtime-token" ]
386+
done
387+
rm -rf "$TMP"
388+
}
389+
390+
@test "sysctl types are rejected by both the schema and the render-time guard" {
391+
TMP=$(mktemp -d)
392+
for value in '11255' 'false' 'null' '["1"]' '{"evil":"$(id)"}'; do
393+
input="talos.sysctls={\"kernel.panic\":$value}"
394+
if render_sysctl_pool "$TMP/r.yaml" --set-json "$input"; then
395+
echo "FAIL: schema accepted a non-string sysctl: $value"
396+
false
397+
fi
398+
grep -q 'values don.t meet the specifications' "$TMP/r.yaml.err"
399+
if render_sysctl_pool "$TMP/r.yaml" --skip-schema-validation --set-json "$input"; then
400+
echo "FAIL: template accepted a non-string sysctl: $value"
401+
false
402+
fi
403+
grep -Fq 'talos.sysctls["kernel.panic"] must be a string' "$TMP/r.yaml.err"
404+
done
405+
for value in '"$(id)"' 'false' '0' '[]' '["1"]'; do
406+
input="talos.sysctls=$value"
407+
if render_sysctl_pool "$TMP/r.yaml" --set-json "$input"; then
408+
echo "FAIL: schema accepted a non-map sysctls: $value"
409+
false
410+
fi
411+
grep -q 'values don.t meet the specifications' "$TMP/r.yaml.err"
412+
if render_sysctl_pool "$TMP/r.yaml" --skip-schema-validation --set-json "$input"; then
413+
echo "FAIL: template accepted a non-map sysctls: $value"
414+
false
415+
fi
416+
grep -Fq 'talos.sysctls must be a map of strings' "$TMP/r.yaml.err"
417+
done
418+
rm -rf "$TMP"
419+
}
420+
421+
@test "sysctl file values reject invalid UTF-8 rather than changing their bytes" {
422+
TMP=$(mktemp -d)
423+
# --set-file reads raw bytes, unlike the YAML/JSON-facing application API.
424+
for bytes in '\377' '\300\257' '\342\202'; do
425+
printf '%b' "$bytes" > "$TMP/value"
426+
if render_sysctl_pool "$TMP/r.yaml" --skip-schema-validation --set-file "talos.sysctls.kernel\\.panic=$TMP/value"; then
427+
echo "FAIL: accepted invalid UTF-8 sysctl bytes: $bytes"
428+
false
429+
fi
430+
grep -Fq 'must be valid UTF-8' "$TMP/r.yaml.err"
431+
done
432+
printf '\357\277\275' > "$TMP/value"
433+
render_sysctl_pool "$TMP/r.yaml" --skip-schema-validation --set-file "talos.sysctls.kernel\\.panic=$TMP/value"
434+
rm -rf "$TMP"
435+
}

0 commit comments

Comments
 (0)