Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: match LabelDomainExceptions correctly as label domains #808

Merged
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion hack/validation/labels.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
## checking for restricted labels while filtering out well-known labels
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.template.properties.metadata.properties.labels.maxProperties = 100' -i pkg/apis/crds/karpenter.sh_nodepools.yaml
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.template.properties.metadata.properties.labels.x-kubernetes-validations += [
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self.all(x, x in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || x.startsWith(\"node.kubernetes.io\") || x.startsWith(\"node-restriction.kubernetes.io\") || !x.find(\"^([^/]+)\").endsWith(\"kubernetes.io\"))"},
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self.all(x, x in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || x.startsWith(\"node.kubernetes.io\") || x.find(\"^([^/]+)\").endsWith(\"node-restriction.kubernetes.io\") || !x.find(\"^([^/]+)\").endsWith(\"kubernetes.io\"))"},
{"message": "label domain \"k8s.io\" is restricted", "rule": "self.all(x, x.startsWith(\"kops.k8s.io\") || !x.find(\"^([^/]+)\").endsWith(\"k8s.io\"))"},
{"message": "label domain \"karpenter.sh\" is restricted", "rule": "self.all(x, x in [\"karpenter.sh/capacity-type\", \"karpenter.sh/nodepool\"] || !x.find(\"^([^/]+)\").endsWith(\"karpenter.sh\"))"},
{"message": "label \"karpenter.sh/nodepool\" is restricted", "rule": "self.all(x, x != \"karpenter.sh/nodepool\")"},
Expand Down
4 changes: 2 additions & 2 deletions hack/validation/requirements.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.req
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.requirements.items.properties.key.pattern = "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(\/))?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$"' -i pkg/apis/crds/karpenter.sh_nodeclaims.yaml
## checking for restricted labels while filtering out well-known labels
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.requirements.items.properties.key.x-kubernetes-validations += [
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"node.kubernetes.io/instance-type\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || self.startsWith(\"node.kubernetes.io/\") || self.startsWith(\"node-restriction.kubernetes.io/\") || !self.find(\"^([^/]+)\").endsWith(\"kubernetes.io\")"},
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"node.kubernetes.io/instance-type\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || self.startsWith(\"node.kubernetes.io/\") || self.find(\"^([^/]+)\").endsWith(\"node-restriction.kubernetes.io\") || !self.find(\"^([^/]+)\").endsWith(\"kubernetes.io\")"},
{"message": "label domain \"k8s.io\" is restricted", "rule": "self.startsWith(\"kops.k8s.io/\") || !self.find(\"^([^/]+)\").endsWith(\"k8s.io\")"},
{"message": "label domain \"karpenter.sh\" is restricted", "rule": "self in [\"karpenter.sh/capacity-type\", \"karpenter.sh/nodepool\"] || !self.find(\"^([^/]+)\").endsWith(\"karpenter.sh\")"},
{"message": "label \"kubernetes.io/hostname\" is restricted", "rule": "self != \"kubernetes.io/hostname\""}]' -i pkg/apis/crds/karpenter.sh_nodeclaims.yaml
Expand All @@ -24,7 +24,7 @@ yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.tem
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.template.properties.spec.properties.requirements.items.properties.key.pattern = "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(\/))?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$"' -i pkg/apis/crds/karpenter.sh_nodepools.yaml
## checking for restricted labels while filtering out well-known labels
yq eval '.spec.versions[0].schema.openAPIV3Schema.properties.spec.properties.template.properties.spec.properties.requirements.items.properties.key.x-kubernetes-validations += [
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"node.kubernetes.io/instance-type\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || self.startsWith(\"node.kubernetes.io/\") || self.startsWith(\"node-restriction.kubernetes.io/\") || !self.find(\"^([^/]+)\").endsWith(\"kubernetes.io\")"},
{"message": "label domain \"kubernetes.io\" is restricted", "rule": "self in [\"beta.kubernetes.io/instance-type\", \"failure-domain.beta.kubernetes.io/region\", \"beta.kubernetes.io/os\", \"beta.kubernetes.io/arch\", \"failure-domain.beta.kubernetes.io/zone\", \"topology.kubernetes.io/zone\", \"topology.kubernetes.io/region\", \"node.kubernetes.io/instance-type\", \"kubernetes.io/arch\", \"kubernetes.io/os\", \"node.kubernetes.io/windows-build\"] || self.startsWith(\"node.kubernetes.io/\") || self.find(\"^([^/]+)\").endsWith(\"node-restriction.kubernetes.io\") || !self.find(\"^([^/]+)\").endsWith(\"kubernetes.io\")"},
{"message": "label domain \"k8s.io\" is restricted", "rule": "self.startsWith(\"kops.k8s.io/\") || !self.find(\"^([^/]+)\").endsWith(\"k8s.io\")"},
{"message": "label domain \"karpenter.sh\" is restricted", "rule": "self in [\"karpenter.sh/capacity-type\", \"karpenter.sh/nodepool\"] || !self.find(\"^([^/]+)\").endsWith(\"karpenter.sh\")"},
{"message": "label \"karpenter.sh/nodepool\" is restricted", "rule": "self != \"karpenter.sh/nodepool\""},
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/crds/karpenter.sh_nodeclaims.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ spec:
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(\/))?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$
x-kubernetes-validations:
- message: label domain "kubernetes.io" is restricted
rule: self in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "node.kubernetes.io/instance-type", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || self.startsWith("node.kubernetes.io/") || self.startsWith("node-restriction.kubernetes.io/") || !self.find("^([^/]+)").endsWith("kubernetes.io")
rule: self in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "node.kubernetes.io/instance-type", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || self.find("^([^/]+)").endsWith("node.kubernetes.io") || self.find("^([^/]+)").endsWith("node-restriction.kubernetes.io") || !self.find("^([^/]+)").endsWith("kubernetes.io")
- message: label domain "k8s.io" is restricted
rule: self.startsWith("kops.k8s.io/") || !self.find("^([^/]+)").endsWith("k8s.io")
rule: self.find("^([^/]+)").endsWith("kops.k8s.io") || !self.find("^([^/]+)").endsWith("k8s.io")
- message: label domain "karpenter.sh" is restricted
rule: self in ["karpenter.sh/capacity-type", "karpenter.sh/nodepool"] || !self.find("^([^/]+)").endsWith("karpenter.sh")
- message: label "kubernetes.io/hostname" is restricted
Expand Down
8 changes: 4 additions & 4 deletions pkg/apis/crds/karpenter.sh_nodepools.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ spec:
maxProperties: 100
x-kubernetes-validations:
- message: label domain "kubernetes.io" is restricted
rule: self.all(x, x in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || x.startsWith("node.kubernetes.io") || x.startsWith("node-restriction.kubernetes.io") || !x.find("^([^/]+)").endsWith("kubernetes.io"))
rule: self.all(x, x in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || x.find("^([^/]+)").endsWith("node.kubernetes.io") || x.find("^([^/]+)").endsWith("node-restriction.kubernetes.io") || !x.find("^([^/]+)").endsWith("kubernetes.io"))
- message: label domain "k8s.io" is restricted
rule: self.all(x, x.startsWith("kops.k8s.io") || !x.find("^([^/]+)").endsWith("k8s.io"))
rule: self.all(x, x.find("^([^/]+)").endsWith("kops.k8s.io") || !x.find("^([^/]+)").endsWith("k8s.io"))
- message: label domain "karpenter.sh" is restricted
rule: self.all(x, x in ["karpenter.sh/capacity-type", "karpenter.sh/nodepool"] || !x.find("^([^/]+)").endsWith("karpenter.sh"))
- message: label "karpenter.sh/nodepool" is restricted
Expand Down Expand Up @@ -236,9 +236,9 @@ spec:
pattern: ^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*(\/))?([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]$
x-kubernetes-validations:
- message: label domain "kubernetes.io" is restricted
rule: self in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "node.kubernetes.io/instance-type", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || self.startsWith("node.kubernetes.io/") || self.startsWith("node-restriction.kubernetes.io/") || !self.find("^([^/]+)").endsWith("kubernetes.io")
rule: self in ["beta.kubernetes.io/instance-type", "failure-domain.beta.kubernetes.io/region", "beta.kubernetes.io/os", "beta.kubernetes.io/arch", "failure-domain.beta.kubernetes.io/zone", "topology.kubernetes.io/zone", "topology.kubernetes.io/region", "node.kubernetes.io/instance-type", "kubernetes.io/arch", "kubernetes.io/os", "node.kubernetes.io/windows-build"] || self.find("^([^/]+)").endsWith("node.kubernetes.io") || self.find("^([^/]+)").endsWith("node-restriction.kubernetes.io") || !self.find("^([^/]+)").endsWith("kubernetes.io")
- message: label domain "k8s.io" is restricted
rule: self.startsWith("kops.k8s.io/") || !self.find("^([^/]+)").endsWith("k8s.io")
rule: self.find("^([^/]+)").endsWith("kops.k8s.io") || !self.find("^([^/]+)").endsWith("k8s.io")
- message: label domain "karpenter.sh" is restricted
rule: self in ["karpenter.sh/capacity-type", "karpenter.sh/nodepool"] || !self.find("^([^/]+)").endsWith("karpenter.sh")
- message: label "karpenter.sh/nodepool" is restricted
Expand Down
10 changes: 6 additions & 4 deletions pkg/apis/v1alpha5/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,11 @@ func IsRestrictedNodeLabel(key string) bool {
if WellKnownLabels.Has(key) {
return true
}
labelDomain := getLabelDomain(key)
if LabelDomainExceptions.Has(labelDomain) {
return false
labelDomain := GetLabelDomain(key)
for exceptionLabelDomain := range LabelDomainExceptions {
if strings.HasSuffix(labelDomain, exceptionLabelDomain) {
return false
}
}
for restrictedLabelDomain := range RestrictedLabelDomains {
if strings.HasSuffix(labelDomain, restrictedLabelDomain) {
Expand All @@ -132,7 +134,7 @@ func IsRestrictedNodeLabel(key string) bool {
return RestrictedLabels.Has(key)
}

func getLabelDomain(key string) string {
func GetLabelDomain(key string) string {
if parts := strings.SplitN(key, "/", 2); len(parts) == 2 {
return parts[0]
}
Expand Down
10 changes: 6 additions & 4 deletions pkg/apis/v1beta1/labels.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ func IsRestrictedNodeLabel(key string) bool {
if WellKnownLabels.Has(key) {
return true
}
labelDomain := getLabelDomain(key)
if LabelDomainExceptions.Has(labelDomain) {
return false
labelDomain := GetLabelDomain(key)
for exceptionLabelDomain := range LabelDomainExceptions {
if strings.HasSuffix(labelDomain, exceptionLabelDomain) {
return false
}
}
for restrictedLabelDomain := range RestrictedLabelDomains {
if strings.HasSuffix(labelDomain, restrictedLabelDomain) {
Expand All @@ -128,7 +130,7 @@ func IsRestrictedNodeLabel(key string) bool {
return RestrictedLabels.Has(key)
}

func getLabelDomain(key string) string {
func GetLabelDomain(key string) string {
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
if parts := strings.SplitN(key, "/", 2); len(parts) == 2 {
return parts[0]
}
Expand Down
25 changes: 25 additions & 0 deletions pkg/apis/v1beta1/nodepool_validation_cel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -632,5 +632,30 @@ var _ = Describe("CEL/Validation", func() {
nodePool = oldNodePool.DeepCopy()
}
})
It("should allow subdomain labels in restricted domains exceptions list", func() {
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
oldNodePool := nodePool.DeepCopy()
for label := range LabelDomainExceptions {
fmt.Println(label)
jonathan-innis marked this conversation as resolved.
Show resolved Hide resolved
nodePool.Spec.Template.Labels = map[string]string{
fmt.Sprintf("subdomain.%s", label): "test-value",
}
Expect(env.Client.Create(ctx, nodePool)).To(Succeed())
Expect(env.Client.Delete(ctx, nodePool)).To(Succeed())
Expect(nodePool.RuntimeValidate()).To(Succeed())
nodePool = oldNodePool.DeepCopy()
}
})
It("should allow subdomain labels prefixed with the restricted domain exceptions", func() {
oldNodePool := nodePool.DeepCopy()
for label := range LabelDomainExceptions {
nodePool.Spec.Template.Labels = map[string]string{
fmt.Sprintf("subdomain.%s/key", label): "test-value",
}
Expect(env.Client.Create(ctx, nodePool)).To(Succeed())
Expect(env.Client.Delete(ctx, nodePool)).To(Succeed())
Expect(nodePool.RuntimeValidate()).To(Succeed())
nodePool = oldNodePool.DeepCopy()
}
})
})
})
16 changes: 16 additions & 0 deletions pkg/apis/v1beta1/nodepool_validation_webhook_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,22 @@ var _ = Describe("Webhook/Validation", func() {
Expect(nodePool.Validate(ctx)).To(Succeed())
}
})
It("should allow subdomain labels in restricted domains exceptions list", func() {
for label := range LabelDomainExceptions {
nodePool.Spec.Template.Labels = map[string]string{
fmt.Sprintf("subdomain.%s", label): "test-value",
}
Expect(nodePool.Validate(ctx)).To(Succeed())
}
})
It("should allow subdomain labels prefixed with the restricted domain exceptions", func() {
for label := range LabelDomainExceptions {
nodePool.Spec.Template.Labels = map[string]string{
fmt.Sprintf("subdomain.%s/key", label): "test-value",
}
Expect(nodePool.Validate(ctx)).To(Succeed())
}
})
})
Context("Taints", func() {
It("should succeed for valid taints", func() {
Expand Down