Skip to content

fix: use /128 CIDR for single IPv6 peers instead of /32 - #353

Merged
matthyx merged 1 commit into
kubescape:mainfrom
ArneshBanerjee:fix/ipv6-single-ip-cidr
Aug 6, 2026
Merged

fix: use /128 CIDR for single IPv6 peers instead of /32#353
matthyx merged 1 commit into
kubescape:mainfrom
ArneshBanerjee:fix/ipv6-single-ip-cidr

Conversation

@ArneshBanerjee

@ArneshBanerjee ArneshBanerjee commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

Part of #334 (AC-2)

getSingleIP always added /32 to the address it was given, no matter what kind of address it was. That is correct for IPv4 but wrong for IPv6, where a single host needs a /128 prefix. A single IPv6 peer such as 2001:db8::1 was ending up as 2001:db8::1/32, which is not a valid single host CIDR for IPv6 and corrupts the generated NetworkPolicy.

This checks the address family and uses /128 for IPv6 and /32 for everything else, including IPv4-mapped addresses and input that fails to parse, which both keep the old /32 behavior.

I converted the existing single-case test into a table test covering IPv4, IPv6, IPv4-mapped IPv6, and malformed input.

Ran go test on the package and gofmt, both clean.

Summary by CodeRabbit

  • Bug Fixes

    • Corrected single-IP network policy handling for IPv6 addresses by generating /128 CIDRs.
    • Preserved /32 CIDRs for IPv4 addresses and other supported inputs.
  • Tests

    • Expanded coverage for IPv4, IPv6, IPv4-mapped IPv6, and malformed addresses.

getSingleIP always appended /32 to the address it was given, no matter
what kind of address it was. That is correct for IPv4 but wrong for
IPv6, where a single host needs a /128 prefix. A single IPv6 peer such
as 2001:db8::1 was ending up as 2001:db8::1/32, which is not a valid
single-host CIDR for IPv6 and corrupts the generated NetworkPolicy.

This checks the address family and picks /128 for IPv6 and /32 for
everything else, including IPv4-mapped addresses and unparseable
input, which both keep the old /32 behavior.

Converted the existing single-case test into a table test covering
IPv4, IPv6, IPv4-mapped IPv6, and malformed input.

Fixes AC-2 in kubescape#334

Signed-off-by: Arnesh Banerjee <linkrinku13@gmail.com>
@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f19d3a1b-f75c-4875-9c1d-9aa61bd47af1

📥 Commits

Reviewing files that changed from the base of the PR and between 72c61d4 and 1fb702d.

📒 Files selected for processing (2)
  • pkg/apis/softwarecomposition/networkpolicy/v2/networkpolicy.go
  • pkg/apis/softwarecomposition/networkpolicy/v2/networkpolicy_test.go

📝 Walkthrough

Walkthrough

getSingleIP now generates /128 CIDRs for IPv6 addresses and retains /32 behavior for IPv4, IPv4-mapped IPv6, and malformed input. Tests now cover all cases.

Changes

Network policy IP CIDR handling

Layer / File(s) Summary
Single-IP CIDR generation and validation
pkg/apis/softwarecomposition/networkpolicy/v2/networkpolicy.go, pkg/apis/softwarecomposition/networkpolicy/v2/networkpolicy_test.go
getSingleIP detects IPv6 addresses and uses /128. Table-driven tests verify IPv4, IPv6, IPv4-mapped IPv6, and malformed input behavior.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: matthyx

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: using /128 CIDRs for single IPv6 peers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@matthyx matthyx moved this to Needs Reviewer in KS PRs tracking Aug 6, 2026

@matthyx matthyx left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Reviewed at 1fb702d. Checked out locally: go build ./... and go test ./pkg/apis/softwarecomposition/networkpolicy/... both pass.

Approving. The change is exactly AC-2 of #334, minimal, and can't regress the IPv4/hostname paths: net.ParseIP returns nil for hostnames, malformed input and zone-bearing addresses, and every To4() != nil address keeps /32 — so only true IPv6 changes. Nice that one fix covers all three call sites (networkpolicy.go:392, :482, :583), and the table test conversion reads well.

Two notes, neither blocking merge.

1. ::ffff:1.2.3.4/32 is a valid string that means ::/32networkpolicy.go:601, pinned by the test row at networkpolicy_test.go:2296.

This is pre-existing behavior and what the ticket asked for, so it's not a regression from this PR. But the new test now records it as intended, and it isn't benign: Go parses that string as an IPv6 prefix, not an IPv4 one, and masks it down to a /32 of the IPv6 space.

net.ParseCIDR("::ffff:1.2.3.4/32")   -> ip=1.2.3.4  net=::/32  err=<nil>
netip.ParsePrefix(...).Masked()      -> ::/32

It parses cleanly, so nothing rejects it as malformed — the peer just silently widens from one host to 2^96 addresses. Reachability is low (NetworkNeighbor.IPAddress is filled by node-agent, and Go's IP.String() renders 4-in-6 as a dotted quad), which is why I'm not holding the PR for it. If you'd like it closed off, unmapping first collapses both families into one rule:

func getSingleIP(ipAddress string) *softwarecomposition.IPBlock {
	if addr, err := netip.ParseAddr(ipAddress); err == nil {
		addr = addr.Unmap().WithZone("") // zones are not valid inside a CIDR
		if addr.Is4() {
			return &softwarecomposition.IPBlock{CIDR: addr.String() + "/32"}
		}
		return &softwarecomposition.IPBlock{CIDR: addr.String() + "/128"}
	}
	return &softwarecomposition.IPBlock{CIDR: ipAddress + "/32"} // unparseable: unchanged
}

The mapped test row would then expect 1.2.3.4/32. Note netip.ParseAddr is more permissive than net.ParseIP — it accepts fe80::1%eth0, hence the WithZone(""); today that input yields fe80::1%eth0/32, which is invalid either way. Follow-up PR is fine too; the minimum I'd ask for is an // intentional comment on the mapped test row so the next reader doesn't take ::ffff:…/32 for a host route.

2. Consistency, for tracking rather than for this PR: the plural path still drops every non-IPv4 entry (networkpolicy.go:561, continue // IPv6 or unparseable, out of scope (AC9), same at :528). After this merges, a single IPv6 peer gets a correct /128 while a batched one is silently discarded. Deliberate per AC-9, but worth keeping visible so IPv6 doesn't settle at half-done.

CI: the pull_request_created workflow is parked in action_required (run 31058071159) — it needs a maintainer to approve the run before build/test reports here. Only CodeRabbit, DCO and GitGuardian have posted so far; I ran the build and the package tests locally in the meantime.

@matthyx
matthyx merged commit 780c6d0 into kubescape:main Aug 6, 2026
3 checks passed
@matthyx matthyx moved this from Needs Reviewer to To Archive in KS PRs tracking Aug 6, 2026
@ArneshBanerjee

Copy link
Copy Markdown
Contributor Author

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Archived in project

Development

Successfully merging this pull request may close these issues.

2 participants