mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-07 11:13:48 +00:00
Merge pull request #8158 from thockin/label-validation
Tighten label validation
This commit is contained in:
commit
f49d2649c8
@ -32,10 +32,10 @@ These are just examples; you are free to develop your own conventions.
|
||||
|
||||
## Syntax and character set
|
||||
|
||||
As already mentioned, well formed _labels_ are key value pairs. Valid label keys have two segments - prefix and name - separated by a slash (`/`). The name segment is required and must be a DNS label: 63 characters or less, all lowercase, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`), with dashes (`-`) and alphanumerics between. The prefix and slash are optional. If specified, the prefix must be a DNS subdomain (a series of DNS labels separated by dots (`.`), not longer than 253 characters in total.
|
||||
If the prefix is omitted, the label key is presumed to be private to the user. System components which use labels must specify a prefix. The `kubernetes.io` prefix is reserved for kubernetes core components.
|
||||
_Labels_ are key value pairs. Valid label keys have two segments: an optional prefix and name, separated by a slash (`/`). The name segment is required and must be 63 characters or less, beginning and ending with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between. The prefix is optional. If specified, the prefix must be a DNS subdomain: a series of DNS labels separated by dots (`.`), not longer than 253 characters in total, followed by a slash (`/`).
|
||||
If the prefix is omitted, the label key is presumed to be private to the user. System components which use labels must specify a prefix. The `kubernetes.io/` prefix is reserved for kubernetes core components.
|
||||
|
||||
Valid label values must be shorter than 64 characters, accepted characters are (`[-A-Za-z0-9_.]`) but the first character must be (`[A-Za-z0-9]`).
|
||||
Valid label values must be 63 characters or less and must be empty or begin and end with an alphanumeric character (`[a-z0-9A-Z]`) with dashes (`-`), underscores (`_`), dots (`.`), and alphanumerics between.
|
||||
|
||||
## Label selectors
|
||||
|
||||
|
@ -32,18 +32,18 @@ import (
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const cIdentifierErrorMsg string = "must match regex " + util.CIdentifierFmt
|
||||
const isNegativeErrorMsg string = "value must not be negative"
|
||||
const cIdentifierErrorMsg string = `must be a C identifier (matching regex ` + util.CIdentifierFmt + `): e.g. "my_name" or "MyName"`
|
||||
const isNegativeErrorMsg string = `must be non-negative`
|
||||
|
||||
func intervalErrorMsg(lo, hi int) string {
|
||||
return fmt.Sprintf("must be greater than %d and less than %d", lo, hi)
|
||||
return fmt.Sprintf(`must be greater than %d and less than %d`, lo, hi)
|
||||
}
|
||||
|
||||
var labelValueErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.LabelValueMaxLength, util.LabelValueFmt)
|
||||
var qualifiedNameErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.QualifiedNameMaxLength, util.QualifiedNameFmt)
|
||||
var dnsSubdomainErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS1123SubdomainMaxLength, util.DNS1123SubdomainFmt)
|
||||
var dns1123LabelErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS1123LabelMaxLength, util.DNS1123LabelFmt)
|
||||
var dns952LabelErrorMsg string = fmt.Sprintf("must have at most %d characters and match regex %s", util.DNS952LabelMaxLength, util.DNS952LabelFmt)
|
||||
var labelValueErrorMsg string = fmt.Sprintf(`must have at most %d characters, matching regex %s: e.g. "MyValue" or ""`, util.LabelValueMaxLength, util.LabelValueFmt)
|
||||
var qualifiedNameErrorMsg string = fmt.Sprintf(`must be a qualified name (at most %d characters, matching regex %s), with an optional DNS subdomain prefix (at most %d characters, matching regex %s) and slash (/): e.g. "MyName" or "example.com/MyName"`, util.QualifiedNameMaxLength, util.QualifiedNameFmt, util.DNS1123SubdomainMaxLength, util.DNS1123SubdomainFmt)
|
||||
var dnsSubdomainErrorMsg string = fmt.Sprintf(`must be a DNS subdomain (at most %d characters, matching regex %s): e.g. "example.com"`, util.DNS1123SubdomainMaxLength, util.DNS1123SubdomainFmt)
|
||||
var dns1123LabelErrorMsg string = fmt.Sprintf(`must be a DNS label (at most %d characters, matching regex %s): e.g. "my-name"`, util.DNS1123LabelMaxLength, util.DNS1123LabelFmt)
|
||||
var dns952LabelErrorMsg string = fmt.Sprintf(`must be a DNS 952 label (at most %d characters, matching regex %s): e.g. "my-name"`, util.DNS952LabelMaxLength, util.DNS952LabelFmt)
|
||||
var pdPartitionErrorMsg string = intervalErrorMsg(0, 255)
|
||||
var portRangeErrorMsg string = intervalErrorMsg(0, 65536)
|
||||
|
||||
|
@ -544,7 +544,7 @@ func TestValidateEnv(t *testing.T) {
|
||||
{
|
||||
name: "name not a C identifier",
|
||||
envs: []api.EnvVar{{Name: "a.b.c"}},
|
||||
expectedError: "[0].name: invalid value 'a.b.c': must match regex [A-Za-z_][A-Za-z0-9_]*",
|
||||
expectedError: `[0].name: invalid value 'a.b.c': must be a C identifier (matching regex [A-Za-z_][A-Za-z0-9_]*): e.g. "my_name" or "MyName"`,
|
||||
},
|
||||
{
|
||||
name: "value and valueFrom specified",
|
||||
@ -2413,10 +2413,6 @@ func TestValidateServiceUpdate(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateResourceNames(t *testing.T) {
|
||||
longString := "a"
|
||||
for i := 0; i < 6; i++ {
|
||||
longString += longString
|
||||
}
|
||||
table := []struct {
|
||||
input string
|
||||
success bool
|
||||
@ -2432,7 +2428,8 @@ func TestValidateResourceNames(t *testing.T) {
|
||||
{"my.favorite.app.co/_12345", false},
|
||||
{"my.favorite.app.co/12345_", false},
|
||||
{"kubernetes.io/..", false},
|
||||
{"kubernetes.io/" + longString, true},
|
||||
{"kubernetes.io/" + strings.Repeat("a", 63), true},
|
||||
{"kubernetes.io/" + strings.Repeat("a", 64), false},
|
||||
{"kubernetes.io//", false},
|
||||
{"kubernetes.io", false},
|
||||
{"kubernetes.io/will/not/work/", false},
|
||||
|
@ -19,13 +19,35 @@ package util
|
||||
import (
|
||||
"net"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const qnameCharFmt string = "[A-Za-z0-9]"
|
||||
const qnameExtCharFmt string = "[-A-Za-z0-9_.]"
|
||||
const qnameTokenFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
|
||||
const QualifiedNameFmt string = "(" + qnameCharFmt + qnameExtCharFmt + "*)?" + qnameCharFmt
|
||||
const QualifiedNameMaxLength int = 63
|
||||
|
||||
const LabelValueFmt string = "(" + qnameTokenFmt + ")?"
|
||||
var qualifiedNameRegexp = regexp.MustCompile("^" + QualifiedNameFmt + "$")
|
||||
|
||||
func IsQualifiedName(value string) bool {
|
||||
parts := strings.Split(value, "/")
|
||||
var left, right string
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
left, right = "", parts[0]
|
||||
case 2:
|
||||
left, right = parts[0], parts[1]
|
||||
default:
|
||||
return false
|
||||
}
|
||||
|
||||
if left != "" && !IsDNS1123Subdomain(left) {
|
||||
return false
|
||||
}
|
||||
return right != "" && len(right) <= QualifiedNameMaxLength && qualifiedNameRegexp.MatchString(right)
|
||||
}
|
||||
|
||||
const LabelValueFmt string = "(" + QualifiedNameFmt + ")?"
|
||||
const LabelValueMaxLength int = 63
|
||||
|
||||
var labelValueRegexp = regexp.MustCompile("^" + LabelValueFmt + "$")
|
||||
@ -34,15 +56,6 @@ func IsValidLabelValue(value string) bool {
|
||||
return (len(value) <= LabelValueMaxLength && labelValueRegexp.MatchString(value))
|
||||
}
|
||||
|
||||
const QualifiedNameFmt string = "(" + qnameTokenFmt + "/)?" + qnameTokenFmt
|
||||
const QualifiedNameMaxLength int = 253
|
||||
|
||||
var qualifiedNameRegexp = regexp.MustCompile("^" + QualifiedNameFmt + "$")
|
||||
|
||||
func IsQualifiedName(value string) bool {
|
||||
return (len(value) <= QualifiedNameMaxLength && qualifiedNameRegexp.MatchString(value))
|
||||
}
|
||||
|
||||
const DNS1123LabelFmt string = "[a-z0-9]([-a-z0-9]*[a-z0-9])?"
|
||||
const DNS1123LabelMaxLength int = 63
|
||||
|
||||
|
@ -168,24 +168,30 @@ func TestIsQualifiedName(t *testing.T) {
|
||||
"1-num.2-num/3-num",
|
||||
"1234/5678",
|
||||
"1.2.3.4/5678",
|
||||
"UppercaseIsOK123",
|
||||
"Uppercase_Is_OK_123",
|
||||
"example.com/Uppercase_Is_OK_123",
|
||||
strings.Repeat("a", 63),
|
||||
strings.Repeat("a", 253) + "/" + strings.Repeat("b", 63),
|
||||
}
|
||||
for i := range successCases {
|
||||
if !IsQualifiedName(successCases[i]) {
|
||||
t.Errorf("case[%d] expected success", i)
|
||||
t.Errorf("case[%d]: %q: expected success", i, successCases[i])
|
||||
}
|
||||
}
|
||||
|
||||
errorCases := []string{
|
||||
"nospecialchars%^=@",
|
||||
"cantendwithadash-",
|
||||
"-cantstartwithadash-",
|
||||
"only/one/slash",
|
||||
strings.Repeat("a", 254),
|
||||
"-cantstartwithadash",
|
||||
"Example.com/abc",
|
||||
"example_com/abc",
|
||||
strings.Repeat("a", 64),
|
||||
strings.Repeat("a", 254) + "/abc",
|
||||
}
|
||||
for i := range errorCases {
|
||||
if IsQualifiedName(errorCases[i]) {
|
||||
t.Errorf("case[%d] expected failure", i)
|
||||
t.Errorf("case[%d]: %q: expected failure", i, errorCases[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user