Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
150 changes: 150 additions & 0 deletions pkg/webhook/ipam_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package webhook

import (
"encoding/json"
"fmt"
"strings"

netutils "k8s.io/utils/net"
)

const whereaboutsIPAMType = "whereabouts"

func validateIPAMConfigs(config []byte) error {
var c map[string]interface{}
if err := json.Unmarshal(config, &c); err != nil {
return fmt.Errorf("invalid json: %w", err)
}

if plugins, ok := c["plugins"].([]interface{}); ok {
for _, plugin := range plugins {
pluginConfig, ok := plugin.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid plugin config")
}
if err := validatePluginIPAM(pluginConfig); err != nil {
return err
}
}
return nil
}

return validatePluginIPAM(c)
}

func validatePluginIPAM(plugin map[string]interface{}) error {
ipamRaw, ok := plugin["ipam"]
if !ok {
return nil
}

ipam, ok := ipamRaw.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid ipam config")
}

ipamType, _ := ipam["type"].(string)
if ipamType != whereaboutsIPAMType {
return nil
}

return validateWhereaboutsIPAM(ipam)
}

func validateWhereaboutsIPAM(ipam map[string]interface{}) error {
if rangeStr, ok := ipam["range"].(string); ok && rangeStr != "" {
if err := validateWhereaboutsRange(rangeStr); err != nil {
return fmt.Errorf("invalid whereabouts ipam range: %w", err)
}
}

if err := validateWhereaboutsStringIP(ipam, "range_start"); err != nil {
return err
}
if err := validateWhereaboutsStringIP(ipam, "range_end"); err != nil {
return err
}
if err := validateWhereaboutsStringIP(ipam, "gateway"); err != nil {
return err
}

if err := validateWhereaboutsExcludeList(ipam["exclude"]); err != nil {
return err
}

ipRangesRaw, ok := ipam["ipRanges"].([]interface{})
if !ok {
return nil
Comment on lines +55 to +77

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject wrong JSON types for Whereabouts IPAM fields instead of silently skipping

Several validators treat type mismatch as “not present” and return nil (e.g., exclude, ipRanges, gateway/range_start/range_end, range). This lets malformed Whereabouts IPAM objects pass admission when fields exist but are non-string/non-array.

Suggested fix
 func validateWhereaboutsIPAM(ipam map[string]interface{}) error {
-	if rangeStr, ok := ipam["range"].(string); ok && rangeStr != "" {
-		if err := validateWhereaboutsRange(rangeStr); err != nil {
-			return fmt.Errorf("invalid whereabouts ipam range: %w", err)
-		}
-	}
+	if raw, exists := ipam["range"]; exists {
+		rangeStr, ok := raw.(string)
+		if !ok {
+			return fmt.Errorf("invalid whereabouts ipam range: must be a string")
+		}
+		if rangeStr != "" {
+			if err := validateWhereaboutsRange(rangeStr); err != nil {
+				return fmt.Errorf("invalid whereabouts ipam range: %w", err)
+			}
+		}
+	}
@@
-	ipRangesRaw, ok := ipam["ipRanges"].([]interface{})
-	if !ok {
-		return nil
-	}
+	rawIPRanges, exists := ipam["ipRanges"]
+	if !exists {
+		return nil
+	}
+	ipRangesRaw, ok := rawIPRanges.([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid whereabouts ipam ipRanges: must be an array")
+	}
@@
 func validateWhereaboutsExcludeList(excludeRaw interface{}) error {
-	excludeList, ok := excludeRaw.([]interface{})
-	if !ok || len(excludeList) == 0 {
+	if excludeRaw == nil {
+		return nil
+	}
+	excludeList, ok := excludeRaw.([]interface{})
+	if !ok {
+		return fmt.Errorf("invalid exclude list: must be an array")
+	}
+	if len(excludeList) == 0 {
 		return nil
 	}
@@
 func validateWhereaboutsStringIP(ipam map[string]interface{}, field string) error {
-	value, ok := ipam[field].(string)
-	if !ok || value == "" {
+	raw, exists := ipam[field]
+	if !exists {
+		return nil
+	}
+	value, ok := raw.(string)
+	if !ok {
+		return fmt.Errorf("invalid whereabouts ipam %s: must be a string", field)
+	}
+	if value == "" {
 		return nil
 	}

Also applies to: 101-123

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/webhook/ipam_validation.go` around lines 55 - 77, The validators
currently treat type mismatches as absent and silently accept malformed
Whereabouts IPAM fields; update the validation in the ipam validation path to
explicitly reject wrong JSON types instead of returning nil. For each key
referenced (range -> validateWhereaboutsRange, range_start/range_end/gateway ->
validateWhereaboutsStringIP, exclude -> validateWhereaboutsExcludeList, and
ipRanges -> the ipRangesRaw handling) detect if the key exists but is the wrong
type and return a descriptive error (e.g., "field X must be a string" or "field
ipRanges must be an array") rather than treating it as missing; keep calling the
existing helper functions when types are correct and only short-circuit to an
error on type mismatches.

}

for idx, ipRangeRaw := range ipRangesRaw {
ipRange, ok := ipRangeRaw.(map[string]interface{})
if !ok {
return fmt.Errorf("invalid whereabouts ipam ipRanges entry at index %d", idx)
}

rangeStr, _ := ipRange["range"].(string)
if rangeStr != "" {
if err := validateWhereaboutsRange(rangeStr); err != nil {
return fmt.Errorf("invalid whereabouts ipam ipRanges[%d].range: %w", idx, err)
}
}

if err := validateWhereaboutsExcludeList(ipRange["exclude"]); err != nil {
return fmt.Errorf("invalid whereabouts ipam ipRanges[%d].exclude: %w", idx, err)
}
}

return nil
}

func validateWhereaboutsExcludeList(excludeRaw interface{}) error {
excludeList, ok := excludeRaw.([]interface{})
if !ok || len(excludeList) == 0 {
return nil
}

for idx, excludeEntry := range excludeList {
excludeStr, ok := excludeEntry.(string)
if !ok {
return fmt.Errorf("invalid exclude entry at index %d", idx)
}
if err := validateWhereaboutsRange(excludeStr); err != nil {
return fmt.Errorf("invalid CIDR in exclude list %s: %w", excludeStr, err)
}
}

return nil
}

func validateWhereaboutsStringIP(ipam map[string]interface{}, field string) error {
value, ok := ipam[field].(string)
if !ok || value == "" {
return nil
}

if netutils.ParseIPSloppy(value) == nil {
return fmt.Errorf("invalid whereabouts ipam %s: %s", field, value)
}

return nil
}

func validateWhereaboutsRange(rangeStr string) error {
parts := strings.SplitN(rangeStr, "-", 2)
if len(parts) == 2 {
if netutils.ParseIPSloppy(strings.TrimSpace(parts[0])) == nil {
return fmt.Errorf("invalid range start IP: %s", parts[0])
}
if _, _, err := netutils.ParseCIDRSloppy(strings.TrimSpace(parts[1])); err != nil {
return fmt.Errorf("invalid CIDR '%s': %w", parts[1], err)
}
return nil
}

if _, _, err := netutils.ParseCIDRSloppy(rangeStr); err != nil {
return fmt.Errorf("invalid CIDR %s: %w", rangeStr, err)
}

return nil
}
Loading