-
Notifications
You must be signed in to change notification settings - Fork 12
fix(security): validate node labels and IPs before shell interpolation #656
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -18,8 +18,23 @@ package v1alpha1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "fmt" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| "regexp" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| var k8sLabelPattern = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9._\-/]*[a-zA-Z0-9])?$`) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| func validateLabels(labels map[string]string) error { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for k, v := range labels { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if !k8sLabelPattern.MatchString(k) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return fmt.Errorf("invalid label key %q: contains disallowed characters", k) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v != "" && !k8sLabelPattern.MatchString(v) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if v != "" && !k8sLabelPattern.MatchString(v) { | |
| if v == "" { | |
| return fmt.Errorf("invalid label value for key %q: value must not be empty", k) | |
| } | |
| if !k8sLabelPattern.MatchString(v) { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The label validation regex is incomplete and doesn't fully comply with Kubernetes label requirements. Key issues:
- Missing length validation: label names must be at most 63 characters, and prefixes (before the '/') must be at most 253 characters
- The regex allows multiple '/' characters anywhere in the string, but Kubernetes only allows a single '/' separator between an optional prefix and the name
- Label keys with prefixes should have the prefix part (before '/') follow DNS subdomain rules (lowercase only, dots and hyphens allowed)
- Empty label keys should be rejected (the regex currently allows them)
Consider implementing proper validation that:
- Splits on '/' to validate prefix and name separately
- Validates prefix as a DNS subdomain (lowercase alphanumeric, dots, hyphens, max 253 chars)
- Validates name part (alphanumeric start/end, max 63 chars)
- Validates values similarly to names (max 63 chars, can be empty)
| "regexp" | |
| ) | |
| var k8sLabelPattern = regexp.MustCompile(`^[a-zA-Z0-9]([a-zA-Z0-9._\-/]*[a-zA-Z0-9])?$`) | |
| func validateLabels(labels map[string]string) error { | |
| for k, v := range labels { | |
| if !k8sLabelPattern.MatchString(k) { | |
| return fmt.Errorf("invalid label key %q: contains disallowed characters", k) | |
| } | |
| if v != "" && !k8sLabelPattern.MatchString(v) { | |
| return fmt.Errorf("invalid label value %q for key %q: contains disallowed characters", v, k) | |
| } | |
| "strings" | |
| "k8s.io/apimachinery/pkg/util/validation" | |
| ) | |
| func validateLabels(labels map[string]string) error { | |
| for k, v := range labels { | |
| if k == "" { | |
| return fmt.Errorf("invalid label key: key must not be empty") | |
| } | |
| if errs := validation.IsQualifiedName(k); len(errs) > 0 { | |
| return fmt.Errorf("invalid label key %q: %s", k, strings.Join(errs, "; ")) | |
| } | |
| if v == "" { | |
| // Empty label values are allowed by Kubernetes. | |
| continue | |
| } | |
| if errs := validation.IsValidLabelValue(v); len(errs) > 0 { | |
| return fmt.Errorf("invalid label value %q for key %q: %s", v, k, strings.Join(errs, "; ")) | |
| } |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is missing for the new label validation security feature. The existing test file has comprehensive tests for ClusterSpec validation, but no tests verify that malicious label keys/values (containing shell metacharacters like backticks, semicolons, pipes, or dollar signs) are properly rejected. Given this is a security fix for command injection, test cases demonstrating rejection of dangerous inputs are critical.
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation function is defined but never called anywhere in the codebase. This means the label validation will not actually prevent command injection attacks. The ClusterSpec.Validate() method needs to be called when loading the Environment YAML configuration, before the cluster provisioning begins. Without this, malicious label values can still be interpolated into shell commands at lines 484 and 520 of pkg/provisioner/cluster.go, leading to command injection.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -20,6 +20,7 @@ import ( | |||||||||
| "bytes" | ||||||||||
| "fmt" | ||||||||||
| "io" | ||||||||||
| "net" | ||||||||||
| "os" | ||||||||||
| "strings" | ||||||||||
| "sync" | ||||||||||
|
|
@@ -437,6 +438,13 @@ func (cp *ClusterProvisioner) configureNodes(firstCP NodeInfo, nodes []NodeInfo) | |||||||||
| } | ||||||||||
| defer provisioner.Client.Close() // nolint: errcheck | ||||||||||
|
|
||||||||||
| // Validate all node IPs before interpolating into shell commands | ||||||||||
| for _, node := range nodes { | ||||||||||
| if net.ParseIP(node.PrivateIP) == nil { | ||||||||||
|
||||||||||
| if net.ParseIP(node.PrivateIP) == nil { | |
| ip := net.ParseIP(node.PrivateIP) | |
| // Restrict to IPv4 addresses to avoid issues when interpolating into shell/grep | |
| if ip == nil || ip.To4() == nil { |
Copilot
AI
Feb 12, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test coverage is missing for the new IP validation security feature. No tests verify that invalid IP addresses (like those containing shell metacharacters or malformed IPs) are properly rejected before being interpolated into grep commands. Add test cases for configureNodes that verify rejection of invalid IPs such as empty strings, malformed addresses, and strings containing shell special characters.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The validation should reject empty label keys explicitly. An empty string would match the regex pattern due to the '?' quantifier making the entire capture group optional, which could lead to kubectl commands with malformed label syntax.