-
Notifications
You must be signed in to change notification settings - Fork 14
allowing CIDRs, wildcards and Plural in IP and DNS #324
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
49476af
4bf869e
62d95c0
46f37d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| package v1beta1 | ||
|
|
||
| import ( | ||
| "reflect" | ||
| "testing" | ||
| ) | ||
|
|
||
| // TestNetworkNeighbor_IPAddresses_ProtobufRoundtrip pins the v0.0.2 | ||
| // protobuf wire contract for the new IPAddresses field. Storage persists | ||
| // NetworkNeighborhood objects to etcd via this protobuf encoding; if | ||
| // the field is dropped on round-trip, the spec field is silently lost | ||
| // and runtime matchers see an empty list. | ||
| // | ||
| // Protobuf field number 9 (declared on the struct tag) MUST be preserved | ||
| // across Marshal → Unmarshal. | ||
| func TestNetworkNeighbor_IPAddresses_ProtobufRoundtrip(t *testing.T) { | ||
| original := &NetworkNeighbor{ | ||
| Identifier: "test-entry", | ||
| Type: "external", | ||
| IPAddress: "10.1.2.3", // deprecated singular still works | ||
| IPAddresses: []string{"10.0.0.0/8", "192.168.0.0/16", "*", "2001:db8::/32"}, | ||
| DNSNames: []string{"api.stripe.com.", "*.stripe.com."}, | ||
| } | ||
|
|
||
| wire, err := original.Marshal() | ||
| if err != nil { | ||
| t.Fatalf("Marshal: %v", err) | ||
| } | ||
|
|
||
| decoded := &NetworkNeighbor{} | ||
| if err := decoded.Unmarshal(wire); err != nil { | ||
| t.Fatalf("Unmarshal: %v", err) | ||
| } | ||
|
|
||
| if !reflect.DeepEqual(decoded.IPAddresses, original.IPAddresses) { | ||
| t.Errorf("IPAddresses roundtrip mismatch:\n got: %v\n want: %v", | ||
| decoded.IPAddresses, original.IPAddresses) | ||
| } | ||
|
|
||
| // Sanity: existing fields still survive (no regression). | ||
| if decoded.IPAddress != original.IPAddress { | ||
| t.Errorf("deprecated IPAddress lost: got %q want %q", decoded.IPAddress, original.IPAddress) | ||
| } | ||
| if !reflect.DeepEqual(decoded.DNSNames, original.DNSNames) { | ||
| t.Errorf("DNSNames lost: got %v want %v", decoded.DNSNames, original.DNSNames) | ||
| } | ||
| } | ||
|
|
||
| // TestNetworkNeighbor_IPAddresses_EmptyOmitted confirms that an empty | ||
| // IPAddresses slice is not encoded on the wire (zero overhead for | ||
| // existing profiles that don't use the new field). | ||
| func TestNetworkNeighbor_IPAddresses_EmptyOmitted(t *testing.T) { | ||
| withField := &NetworkNeighbor{ | ||
| Identifier: "id", | ||
| Type: "external", | ||
| IPAddresses: nil, | ||
| } | ||
| withoutField := &NetworkNeighbor{ | ||
| Identifier: "id", | ||
| Type: "external", | ||
| } | ||
| a, err := withField.Marshal() | ||
| if err != nil { | ||
| t.Fatalf("Marshal(withField): %v", err) | ||
| } | ||
| b, err := withoutField.Marshal() | ||
| if err != nil { | ||
| t.Fatalf("Marshal(withoutField): %v", err) | ||
| } | ||
| if !reflect.DeepEqual(a, b) { | ||
| t.Errorf("nil IPAddresses must encode identically to absent field;\n got %d bytes vs %d bytes", | ||
| len(a), len(b)) | ||
| } | ||
| } |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| # networkmatch | ||
|
|
||
| Wildcard-aware matchers for the `NetworkNeighbor.IPAddresses` and | ||
| `NetworkNeighbor.DNSNames` fields, used by node-agent's CEL functions | ||
| `nn.was_address_in_{egress,ingress}` and `nn.is_domain_in_{egress,ingress}`. | ||
|
|
||
| This package is the runtime counterpart to the spec sections §5.7 (IP) | ||
| and §5.8 (DNS) at <https://billofbehavior.fusioncore.ai/bob/docs/drafts/spec-v0.0.2/>. | ||
|
|
||
| ## Wildcard token vocabulary | ||
|
|
||
| Same tokens as the path / argv matchers in `dynamicpathdetector` — see | ||
| that package's `coverage_test.go` for the contract. | ||
|
|
||
| | Token | IP semantics | DNS semantics | | ||
| |---|---|---| | ||
| | Literal | byte-equality after canonicalization (net.IP) | byte-equality after trailing-dot normalization | | ||
| | CIDR (`a.b.c.d/n`) | `net.IPNet.Contains(observed)` | — | | ||
| | `*` as full entry | sugar for `0.0.0.0/0` ∪ `::/0` (any IP) | — | | ||
| | `*.<suffix>` (leading) | — | RFC 4592 — exactly one DNS label before `<suffix>` | | ||
| | `<a>.⋯.<b>` (mid) | — | DynamicIdentifier — exactly one DNS label between `<a>` and `<b>` | | ||
| | `<prefix>.*` (trailing) | — | one or more DNS labels after `<prefix>` (never zero) | | ||
| | `**` | reserved (rejected at admission) | reserved (rejected at admission) | | ||
|
|
||
| ## API | ||
|
|
||
| ```go | ||
| // MatchIP reports whether observedIP matches any of the profile entries. | ||
| // Each entry MAY be: a literal IP, a CIDR, or the "*" sentinel. | ||
| // | ||
| // observedIP is matched as text (the function calls net.ParseIP internally | ||
| // so the caller does not need to pre-parse it). Empty profile slice | ||
| // returns false (no entries → nothing to match against). Empty observedIP | ||
| // returns false (no observation to match). | ||
| // | ||
| // Compile-once contract: callers running this in a hot path SHOULD wrap | ||
| // it in a closure that captures pre-compiled *IPNet values across calls | ||
| // (the caller knows the profile's lifecycle, this function does not). | ||
| func MatchIP(profileEntries []string, observedIP string) bool | ||
|
|
||
| // MatchDNS reports whether observedName matches any of the profile entries. | ||
| // Each entry MAY use the wildcard tokens above. | ||
| // | ||
| // Both profile entries and observedName are normalized before | ||
| // comparison: a trailing dot is stripped if present, and labels are | ||
| // lowercased for case-insensitive equality. | ||
| func MatchDNS(profileEntries []string, observedName string) bool | ||
| ``` | ||
|
|
||
| ## Performance contract | ||
|
|
||
| Both functions are called per network event from R0005 / R0011 / R1003 / | ||
| R1009. The benchmarks in `bench_test.go` track: | ||
|
|
||
| - `BenchmarkMatchIP_Literal` — baseline byte-equality | ||
| - `BenchmarkMatchIP_CIDR` — single CIDR match | ||
| - `BenchmarkMatchIP_LongMixedList` — 10-entry mixed list, observed IP not in list (worst case) | ||
| - `BenchmarkMatchDNS_Literal` — baseline | ||
| - `BenchmarkMatchDNS_LeadingWildcard` — RFC 4592 | ||
| - `BenchmarkMatchDNS_DeepName` — 10-label observed name against a leading-`*` profile | ||
|
|
||
| Targets (CI runner reference): | ||
| - IP literal / CIDR: < 200 ns per call | ||
| - DNS literal: < 300 ns per call | ||
| - DNS wildcard: < 600 ns per call | ||
|
|
||
| Beat or hold these on every change; the matcher fires on every network | ||
| event captured by the eBPF tracers. | ||
|
|
||
| ## Testing | ||
|
|
||
| `match_ip_test.go` and `match_dns_test.go` are the contract pinning. The | ||
| fixtures in `node-agent/tests/resources/network-wildcards/` are the | ||
| end-to-end examples; both layers MUST agree. |
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.
Blocking:
IPAddressesis added here, but the generated protobuf/conversion/deepcopy code is unchanged, so the field is silently dropped on real storage paths. The newTestNetworkNeighbor_IPAddresses_ProtobufRoundtripalready fails (go test ./pkg/apis/softwarecomposition/v1beta1). Please regenerategenerated.pb.go, conversion code, and deepcopy code for this field before merge.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.
This has now been addressed and the PR compiles standalone