-
Notifications
You must be signed in to change notification settings - Fork 40
🌱 improve ip address validation #1249
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?
Conversation
|
[APPROVALNOTIFIER] This PR is NOT APPROVED This pull-request has been approved by: The full list of commands accepted by this bot can be found here. DetailsNeeds approval from an approver in each of these files:Approvers can indicate their approval by writing |
|
/test metal3-centos-e2e-integration-test-main metal3-ubuntu-e2e-integration-test-main |
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.
Pull request overview
This PR improves IP address validation throughout the codebase by adding format validation for IP addresses when IPClaims and IPPools are created programmatically (complementing the existing kubebuilder validation for custom resources). The changes also enable proper comparison of canonical and non-canonical IPv6 addresses.
Key changes:
- Introduced IP address format validation using
net.ParseIPin webhook validators - Added
ipEqualfunction to properly compare canonical and non-canonical IPv6 addresses - Extended validation to cover IP addresses in annotations, pools, gateways, DNS servers, and pre-allocations
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| ipam/ippool_manager.go | Added ipEqual function and replaced string comparisons with semantic IP equality checks |
| ipam/ippool_manager_test.go | Added test cases for non-canonical IPv6 address handling |
| internal/webhooks/v1alpha1/ippool_webhook.go | Added validateIP and validatePoolRanges functions to validate IP format and pool ranges |
| internal/webhooks/v1alpha1/ippool_webhook_test.go | Added test cases for pool range validation (start <= end) |
| internal/webhooks/v1alpha1/ipclaim_webhook.go | Added validation for IP addresses in annotations |
| internal/webhooks/v1alpha1/ipaddress_webhook.go | Added IP address format validation for the address field and annotations |
| internal/webhooks/v1alpha1/ipaddress_webhook_test.go | Updated test data to use valid IP addresses and added test for invalid IP format |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| if err := validateIP(c.Spec.Address); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "address"), | ||
| c.Spec.Address, | ||
| "invalid IP address", | ||
| ), | ||
| ) | ||
| } | ||
|
|
Copilot
AI
Dec 18, 2025
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.
Duplicate validation of IP address detected. Lines 89-97 already validate the IP address using validateIP, but lines 112-120 perform the same validation again. This results in redundant error messages being added to the error list when an invalid IP address is provided. One of these validation blocks should be removed.
| if err := validateIP(c.Spec.Address); err != nil { | |
| allErrs = append(allErrs, | |
| field.Invalid( | |
| field.NewPath("spec", "address"), | |
| c.Spec.Address, | |
| "invalid IP address", | |
| ), | |
| ) | |
| } |
| if bytes.Compare(startIP, endIP) > 0 { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i), | ||
| fmt.Sprintf("start: %s, end: %s", *pool.Start, *pool.End), | ||
| "start address must be less than or equal to end address", | ||
| ), | ||
| ) |
Copilot
AI
Dec 18, 2025
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.
Potential nil pointer dereference when comparing IP addresses. If either the start or end IP address is invalid (fails validateIP check on lines 249 or 262), the net.ParseIP calls on lines 313-314 will return nil. The bytes.Compare function will then be called with nil slices, which will panic. The comparison should only be performed if both startIP and endIP are non-nil, or the function should skip comparison when validation errors were already detected for these IPs.
| if bytes.Compare(startIP, endIP) > 0 { | |
| allErrs = append(allErrs, | |
| field.Invalid( | |
| field.NewPath("spec", "pools").Index(i), | |
| fmt.Sprintf("start: %s, end: %s", *pool.Start, *pool.End), | |
| "start address must be less than or equal to end address", | |
| ), | |
| ) | |
| // Only compare if both IPs parsed successfully. | |
| if startIP != nil && endIP != nil { | |
| if bytes.Compare(startIP, endIP) > 0 { | |
| allErrs = append(allErrs, | |
| field.Invalid( | |
| field.NewPath("spec", "pools").Index(i), | |
| fmt.Sprintf("start: %s, end: %s", *pool.Start, *pool.End), | |
| "start address must be less than or equal to end address", | |
| ), | |
| ) | |
| } |
| // Validate requested IP address if present in annotations | ||
| if requestedIP, ok := c.ObjectMeta.Annotations["ipAddress"]; ok && requestedIP != "" { | ||
| if err := validateIP(ipamv1.IPAddressStr(requestedIP)); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("metadata", "annotations", "ipAddress"), | ||
| requestedIP, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } |
Copilot
AI
Dec 18, 2025
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.
Missing test coverage for the new IP address validation in annotations. The validation logic added for the "ipAddress" annotation (lines 72-82) should be tested to ensure invalid IP addresses in annotations are properly rejected. Consider adding test cases that verify both valid and invalid IP addresses in the annotation are handled correctly.
| // Validate requested IP address if present in annotations | ||
| if requestedIP, ok := newIPClaim.ObjectMeta.Annotations["ipAddress"]; ok && requestedIP != "" { | ||
| if validateIP(ipamv1.IPAddressStr(requestedIP)) != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("metadata", "annotations", "ipAddress"), | ||
| requestedIP, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } |
Copilot
AI
Dec 18, 2025
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.
Missing test coverage for the new IP address validation in annotations during updates. The validation logic added for the "ipAddress" annotation (lines 130-140) should be tested to ensure invalid IP addresses in annotations are properly rejected during update operations. Consider adding test cases that verify both valid and invalid IP addresses in the annotation are handled correctly.
| // validatePoolRanges validates that start <= end for each pool and validates all IP addresses. | ||
| func (webhook *IPPool) validatePoolRanges(pool *ipamv1.IPPool) field.ErrorList { | ||
| allErrs := field.ErrorList{} | ||
|
|
||
| // Validate gateway IP if present | ||
| if pool.Spec.Gateway != nil { | ||
| if err := validateIP(*pool.Spec.Gateway); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "gateway"), | ||
| *pool.Spec.Gateway, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate DNS server IPs | ||
| for i, dnsServer := range pool.Spec.DNSServers { | ||
| if err := validateIP(dnsServer); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "dnsServers").Index(i), | ||
| dnsServer, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate preAllocations IPs | ||
| for name, ipAddr := range pool.Spec.PreAllocations { | ||
| if err := validateIP(ipAddr); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "preAllocations", name), | ||
| ipAddr, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| for i, pool := range pool.Spec.Pools { | ||
| // Validate Start IP | ||
| if pool.Start != nil { | ||
| if err := validateIP(*pool.Start); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i).Child("start"), | ||
| *pool.Start, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate End IP | ||
| if pool.End != nil { | ||
| if err := validateIP(*pool.End); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i).Child("end"), | ||
| *pool.End, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate Subnet CIDR | ||
| if pool.Subnet != nil { | ||
| if _, _, err := net.ParseCIDR(string(*pool.Subnet)); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i).Child("subnet"), | ||
| *pool.Subnet, | ||
| "is not a valid CIDR", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate pool-specific gateway IP | ||
| if pool.Gateway != nil { | ||
| if err := validateIP(*pool.Gateway); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i).Child("gateway"), | ||
| *pool.Gateway, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
|
|
||
| // Validate pool-specific DNS server IPs | ||
| for j, dnsServer := range pool.DNSServers { | ||
| if err := validateIP(dnsServer); err != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i).Child("dnsServers").Index(j), | ||
| dnsServer, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
| // Validate start <= end if both are present and valid | ||
| if pool.Start != nil && pool.End != nil { | ||
| startIP := net.ParseIP(string(*pool.Start)) | ||
| endIP := net.ParseIP(string(*pool.End)) | ||
|
|
||
| if bytes.Compare(startIP, endIP) > 0 { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("spec", "pools").Index(i), | ||
| fmt.Sprintf("start: %s, end: %s", *pool.Start, *pool.End), | ||
| "start address must be less than or equal to end address", | ||
| ), | ||
| ) | ||
| } | ||
| } | ||
| } | ||
| return allErrs | ||
| } |
Copilot
AI
Dec 18, 2025
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.
Missing test coverage for IP address format validation in IPPool. While tests verify that start > end is rejected, there are no test cases that verify invalid IP address formats (e.g., malformed IPs like "not-an-ip", "256.256.256.256") are rejected for the Start, End, Gateway, DNSServers, and PreAllocations fields. Consider adding test cases that verify the validateIP function correctly rejects malformed IP addresses.
| // Validate requested IP address if present in annotations (for CAPI claims) | ||
| if requestedIP, ok := c.ObjectMeta.Annotations["ipAddress"]; ok && requestedIP != "" { | ||
| if validateIP(ipamv1.IPAddressStr(requestedIP)) != nil { | ||
| allErrs = append(allErrs, | ||
| field.Invalid( | ||
| field.NewPath("metadata", "annotations", "ipAddress"), | ||
| requestedIP, | ||
| "is not a valid IP address", | ||
| ), | ||
| ) | ||
| } | ||
| } |
Copilot
AI
Dec 18, 2025
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.
Missing test coverage for the new IP address validation in annotations for IPAddress resources. The validation logic added for the "ipAddress" annotation (lines 100-110) should be tested to ensure invalid IP addresses in annotations are properly rejected. Consider adding test cases that verify both valid and invalid IP addresses in the annotation are handled correctly.
|
/retitle 🐛 improve ip address validation |
tuminoid
left a comment
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.
ecf9886 to
7580343
Compare
Signed-off-by: peppi-lotta <[email protected]>
7580343 to
da6a292
Compare
| } | ||
|
|
||
| // Validate requested IP address if present in annotations (for CAPI claims) | ||
| if requestedIP, ok := c.ObjectMeta.Annotations["ipAddress"]; ok && requestedIP != "" { |
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.
Is user allowed to add annotation after creation?
if yes lets add this validation in Update also?
What this PR does / why we need it: Improve ip address validation. IP address string format is forced with kubebuilder when creating custom resources. No validation is done when IPClaims and IPPools are create with code. I suggest adding more rigorous validation to force ip addresses to be the correct format.
Fixes #1244
Checklist: