Reject unsafe chart metadata integers (#4679)

This commit is contained in:
Nikola Jokic
2026-09-24 12:32:24 +02:00
committed by GitHub
parent 79ff8945d3
commit 00cb8e7e02
9 changed files with 399 additions and 70 deletions
@@ -1,8 +1,8 @@
{{/*
Render a single label or annotation value as a string.
Values from a values file arrive as float64, so "%v" would turn large integers into
scientific notation (12345678901234 -> 1.2345678901234e+13) and silently write a value the
user never asked for. Integral floats are therefore formatted without an exponent.
Values from a values file arrive as float64. Integral values in the IEEE 754 safe integer
range are formatted without an exponent. Unsafe integral values are rejected by assert-scalar
before reaching this helper, because their original value may already have been rounded.
*/}}
{{- define "metadata-value" -}}
{{- if eq . nil -}}
@@ -50,6 +50,8 @@ Expects a dict with "value", "key", "kind" and "path".
{{- $value := .value -}}
{{- if or (kindIs "map" $value) (kindIs "slice" $value) (kindIs "invalid" $value) -}}
{{- fail (printf "%s: invalid value for %s %q: must be a scalar, got %s. Quote the value if it is meant to be a string" .path .kind .key (kindOf $value)) -}}
{{- else if and (or (kindIs "int" $value) (kindIs "int64" $value) (and (kindIs "float64" $value) (eq $value (floor $value)))) (or (ge (float64 $value) 9007199254740992.0) (le (float64 $value) -9007199254740992.0)) -}}
{{- fail (printf "%s: invalid value for %s %q: unquoted integers outside the IEEE 754 safe range must be quoted to preserve their exact value" .path .kind .key) -}}
{{- end -}}
{{- end }}
@@ -72,6 +74,11 @@ Expects a dict with "key", "kind" (label|annotation) and "path" (the values path
{{- if gt (len $prefix) 253 -}}
{{- fail (printf "%s: invalid %s key %q: the prefix %q must be a DNS subdomain of no more than 253 characters" $path $kind $key $prefix) -}}
{{- end -}}
{{- range $segment := splitList "." $prefix -}}
{{- if gt (len $segment) 63 -}}
{{- fail (printf "%s: invalid %s key %q: the prefix segment %q must be no more than 63 characters" $path $kind $key $segment) -}}
{{- end -}}
{{- end -}}
{{- if not (regexMatch "^[a-z0-9]([-a-z0-9]*[a-z0-9])?([.][a-z0-9]([-a-z0-9]*[a-z0-9])?)*$" $prefix) -}}
{{- fail (printf "%s: invalid %s key %q: the prefix %q must be a DNS subdomain, so it must consist of dot-separated segments of lowercase alphanumeric characters or '-', each starting and ending with an alphanumeric character" $path $kind $key $prefix) -}}
{{- end -}}
@@ -318,5 +325,3 @@ Behavior:
path: {{ $key | quote }}
{{ end }}
{{ end }}
@@ -92,6 +92,7 @@ tests:
sync-wave: 1
annotations:
enabled: true
quoted-unsafe-integer: "9007199254740993"
runner:
pod:
metadata:
@@ -110,6 +111,95 @@ tests:
- equal:
path: spec.ephemeralRunnerMetadata.annotations["enabled"]
value: "true"
- equal:
path: spec.ephemeralRunnerMetadata.annotations["quoted-unsafe-integer"]
value: "9007199254740993"
- it: should render the inclusive safe integer boundaries without an exponent
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
labels:
maximum-safe-integer: 9007199254740991
annotations:
maximum-safe-integer: 9007199254740991
minimum-safe-integer: -9007199254740991
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: metadata.labels["maximum-safe-integer"]
value: "9007199254740991"
- equal:
path: metadata.annotations["maximum-safe-integer"]
value: "9007199254740991"
- equal:
path: metadata.annotations["minimum-safe-integer"]
value: "-9007199254740991"
- it: should fail at the first unsafe positive integer
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
unsafe-integer: 9007199254740992
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: '.Values.resource.all.metadata.annotations: invalid value for annotation "unsafe-integer": unquoted integers outside the IEEE 754 safe range must be quoted to preserve their exact value'
- it: should fail at the first unsafe negative integer
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
unsafe-integer: -9007199254740992
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: '.Values.resource.all.metadata.annotations: invalid value for annotation "unsafe-integer": unquoted integers outside the IEEE 754 safe range must be quoted to preserve their exact value'
- it: should fail when an unquoted metadata integer is outside the IEEE 754 safe range
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
resource:
all:
metadata:
annotations:
unsafe-integer: 9007199254740993
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- failedTemplate:
errorMessage: '.Values.resource.all.metadata.annotations: invalid value for annotation "unsafe-integer": unquoted integers outside the IEEE 754 safe range must be quoted to preserve their exact value'
- it: should fail when a metadata block is not a mapping
set:
@@ -201,7 +291,28 @@ tests:
- failedTemplate:
errorMessage: '.Values.runner.pod.metadata.annotations: invalid value for annotation "nested": must be a scalar, got map. Quote the value if it is meant to be a string'
- it: should accept a prefix segment longer than 63 characters, matching Kubernetes
- it: should render a metadata prefix segment of exactly 63 characters
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
auth.githubToken: "gh_token12345"
controllerServiceAccount.name: "arc"
controllerServiceAccount.namespace: "arc-system"
runner:
pod:
metadata:
labels:
? "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com/purpose"
: "yes"
release:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.metadata.labels["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com/purpose"]
value: "yes"
- it: should fail when a metadata prefix segment is longer than 63 characters
set:
scaleset.name: "test"
auth.url: "https://github.com/org"
@@ -218,9 +329,8 @@ tests:
name: "test-name"
namespace: "test-namespace"
asserts:
- equal:
path: spec.template.metadata.labels["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com/purpose"]
value: "yes"
- failedTemplate:
errorMessage: '.Values.runner.pod.metadata.labels: invalid label key "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.example.com/purpose": the prefix segment "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" must be no more than 63 characters'
# A listener pod template carrying both metadata and spec used to render "true" and the
# following "spec:" key onto the same line, producing invalid YAML.
@@ -0,0 +1,55 @@
package tests
import (
"os"
"path/filepath"
"strings"
"testing"
)
func TestValuesExamplesAreTopLevel(t *testing.T) {
valuesPath, err := filepath.Abs("../values.yaml")
if err != nil {
t.Fatal(err)
}
values, err := os.ReadFile(valuesPath)
if err != nil {
t.Fatal(err)
}
for _, test := range []struct {
name string
marker string
example string
}{
{
name: "proxy",
marker: "## Proxy can be used to define proxy settings",
example: "# proxy:",
},
{
name: "github server TLS",
marker: "## A self-signed CA certificate for communication with the GitHub server",
example: "# githubServerTLS:",
},
} {
t.Run(test.name, func(t *testing.T) {
markerIndex := strings.Index(string(values), test.marker)
if markerIndex == -1 {
t.Fatalf("could not find example marker %q", test.marker)
}
exampleIndex := markerIndex + strings.Index(string(values[markerIndex:]), test.example)
if exampleIndex < markerIndex {
t.Fatalf("could not find example %q after marker %q", test.example, test.marker)
}
lineStart := strings.LastIndex(string(values[:exampleIndex]), "\n") + 1
lineEnd := exampleIndex + strings.Index(string(values[exampleIndex:]), "\n")
if got := string(values[lineStart:lineEnd]); got != test.example {
t.Errorf("example must be top-level: got %q, want %q", got, test.example)
}
})
}
}
@@ -62,20 +62,41 @@ secretResolution:
# tenant_id: ""
# certificate_path: ""
## Proxy can be used to define proxy settings that will be used by the
## controller, the listener and the runner of this scale set.
# proxy:
# http:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# https:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# noProxy:
# - example.com
# - example.org
## Proxy can be used to define proxy settings that will be used by the
## controller, the listener and the runner of this scale set.
# proxy:
# http:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# https:
# url: http://proxy.com:1234
# credentialSecretRef: proxy-auth # a secret with `username` and `password` keys
# noProxy:
# - example.com
# - example.org
## Resource object allows modifying resources created by the chart itself
## A self-signed CA certificate for communication with the GitHub server can be
## provided using a config map key selector. If `runnerMountPath` is set, for
## each runner pod ARC will:
## - create a `github-server-tls-cert` volume containing the certificate
## specified in `certificateFrom`
## - mount that volume on path `runnerMountPath`/{certificate name}
## - set NODE_EXTRA_CA_CERTS environment variable to that same path
## - set RUNNER_UPDATE_CA_CERTS environment variable to "1" (as of version
## 2.303.0 this will instruct the runner to reload certificates on the host)
##
## If any of the above had already been set by the user in the runner pod
## template, ARC will observe those and not overwrite them.
## Example configuration:
#
# githubServerTLS:
# certificateFrom:
# configMapKeyRef:
# name: config-map-name
# key: ca.crt
# runnerMountPath: /usr/local/share/ca-certificates/
## Resource object allows modifying resources created by the chart itself
resource:
# Specifies metadata that will be applied to all resources managed by ARC
all:
@@ -269,27 +290,6 @@ runner:
# spec:
# containers: []
## A self-signed CA certificate for communication with the GitHub server can be
## provided using a config map key selector. If `runnerMountPath` is set, for
## each runner pod ARC will:
## - create a `github-server-tls-cert` volume containing the certificate
## specified in `certificateFrom`
## - mount that volume on path `runnerMountPath`/{certificate name}
## - set NODE_EXTRA_CA_CERTS environment variable to that same path
## - set RUNNER_UPDATE_CA_CERTS environment variable to "1" (as of version
## 2.303.0 this will instruct the runner to reload certificates on the host)
##
## If any of the above had already been set by the user in the runner pod
## template, ARC will observe those and not overwrite them.
## Example configuration:
#
# githubServerTLS:
# certificateFrom:
# configMapKeyRef:
# name: config-map-name
# key: ca.crt
# runnerMountPath: /usr/local/share/ca-certificates/
## controllerServiceAccount is the service account of the controller
controllerServiceAccount:
namespace: ""