Skip to content

npep-187: More protocols support#297

Merged
k8s-ci-robot merged 1 commit into
kubernetes-sigs:mainfrom
npinaeva:npep-187
Dec 16, 2025
Merged

npep-187: More protocols support#297
k8s-ci-robot merged 1 commit into
kubernetes-sigs:mainfrom
npinaeva:npep-187

Conversation

@npinaeva
Copy link
Copy Markdown
Member

@npinaeva npinaeva commented Apr 22, 2025

Issue: #187
Also fixes #247 - no more pointers to lists
We have a plan for #340 so this npep can move on

Option3 has won the design discussion.

protocols:
  - tcp:
      destinationPort:
        number: 8080
      flags: [syn] # future extension example
  - tcp:
      destinationPort:
        range: 
          start: 8080
          end: 9090
  - udp:
      destinationPort:
        number: 8080
  - udp:
      destinationPort:
        number: 9090
  - namedPort: http
  - namedPort: monitoring
  - icmp: # that doesn't exist yet, but may be added
      type: 7
      code: 3

@k8s-ci-robot k8s-ci-robot requested a review from aojea April 22, 2025 14:37
@k8s-ci-robot k8s-ci-robot added the cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. label Apr 22, 2025
@k8s-ci-robot k8s-ci-robot requested a review from astoycos April 22, 2025 14:37
@k8s-ci-robot k8s-ci-robot added the size/L Denotes a PR that changes 100-499 lines, ignoring generated files. label Apr 22, 2025
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 22, 2025

Deploy Preview for kubernetes-sigs-network-policy-api ready!

Name Link
🔨 Latest commit 940a1f4
🔍 Latest deploy log https://app.netlify.com/projects/kubernetes-sigs-network-policy-api/deploys/6936edea428436000876d65d
😎 Deploy Preview https://deploy-preview-297--kubernetes-sigs-network-policy-api.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@bowei
Copy link
Copy Markdown
Contributor

bowei commented May 14, 2025

High level looks ok to me plus aligns with what was discussed.

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label May 14, 2025
```

with the validation like so:
- empty `protocols` list is not allowed
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.

Clarification: this is having just protocols: but no list?

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.

This needs to work the same way as whatever we eventually decided for ports.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

it should be a list, minItems=1, so you can't define an empty list and empty list should work the same as unset

with the validation like so:
- empty `protocols` list is not allowed
- at least 1 field for each `protocol` element must be set
- if `namedPort` is set, `protocol` must be unset
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.

I think we were thinking to not have namedPort for the first pass?

Copy link
Copy Markdown
Contributor

@danwinship danwinship Oct 23, 2025

Choose a reason for hiding this comment

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

I think we were thinking to not have namedPort for the first pass?

We should at least account for it in the design.

(Hm... are we going to have "experimental" fields in v1beta1? That seems kind of contradictory...)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think we just agreed no one likes them, but we can't get rid of them?

@bowei
Copy link
Copy Markdown
Contributor

bowei commented Jun 2, 2025

How do we make progress on this?

@k8s-triage-robot
Copy link
Copy Markdown

The Kubernetes project currently lacks enough contributors to adequately respond to all PRs.

This bot triages PRs according to the following rules:

  • After 90d of inactivity, lifecycle/stale is applied
  • After 30d of inactivity since lifecycle/stale was applied, lifecycle/rotten is applied
  • After 30d of inactivity since lifecycle/rotten was applied, the PR is closed

You can:

  • Mark this PR as fresh with /remove-lifecycle stale
  • Close this PR with /close
  • Offer to help out with Issue Triage

Please send feedback to sig-contributor-experience at kubernetes/community.

/lifecycle stale

@k8s-ci-robot k8s-ci-robot added the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Aug 31, 2025
@npinaeva
Copy link
Copy Markdown
Member Author

npinaeva commented Sep 2, 2025

/remove-lifecycle stale
/lifecycle freeze

@k8s-ci-robot k8s-ci-robot removed the lifecycle/stale Denotes an issue or PR has remained open with no activity and has become stale. label Sep 2, 2025
@k8s-ci-robot k8s-ci-robot removed the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Oct 23, 2025
@k8s-ci-robot
Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: npinaeva

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 23, 2025
@npinaeva npinaeva force-pushed the npep-187 branch 2 times, most recently from b3dee7e to becac76 Compare October 23, 2025 09:14
Comment thread npeps/npep-187-ports-and-protocols.md Outdated
Comment on lines +107 to +109
namedPorts:
- http
- monitoring
Copy link
Copy Markdown
Contributor

@fasaxc fasaxc Oct 23, 2025

Choose a reason for hiding this comment

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

Feels like a type mismatch that namedPorts is a peer of tcp; how about any for this case; it makes it explicit that named ports bring their own protocol with them:

Suggested change
namedPorts:
- http
- monitoring
any:
- namedPort: http
- namedPort: monitoring

also opens up options like

  any:
  - all: true 

to explicitly match absolutely anything

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

named port is a protocol+port reference, so it kinda makes sense to me to be on the same level as tcp?
I guess I would agree with any if we had more cases like named ports in mind (but I do hope there are none :D)

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.

Once it becomes an either/or, don't we need a way to say "match anything at all", so I think we'd need something like

  any:
  - all: true 

Copy link
Copy Markdown
Contributor

@fasaxc fasaxc Oct 23, 2025

Choose a reason for hiding this comment

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

We should have an explicit list of use cases to test each of these approaches against:

  • Rule that matches all traffic (how do you write your "default deny" for the baseline tier, or the end of your Admin tier if your policy set needs that)
  • Rule that allows DNS to TCP and UDP port 53 (Tim's example of a common need)
  • Rule that allows TCP port 80, 8080, 443
  • Rule that allows dozens of ports (I've seen this with some customers monolith apps, good to make sure it doesn't get awful)
  • Rule that allows a range of UDP ports (e.g. for telephony use cases).

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

We are talking about protocols field, so

  any:
  - all: true

is the same as not having a protocol match at all? Same way, it just should not be set for "deny all"?

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.

"any all" is confusing. Also, what would this mean:

any:
- all: false

Comment thread npeps/npep-187-ports-and-protocols.md Outdated
@npinaeva npinaeva force-pushed the npep-187 branch 2 times, most recently from 6deacbe to be8ca48 Compare October 23, 2025 09:42
Comment thread npeps/npep-187-ports-and-protocols.md Outdated
with the validation like so:
- empty `protocols` list is not allowed
- at least 1 field for each `protocol` element must be set
- if `namedPort` is set, `protocol` must be unset
Copy link
Copy Markdown
Contributor

@danwinship danwinship Oct 23, 2025

Choose a reason for hiding this comment

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

I think we were thinking to not have namedPort for the first pass?

We should at least account for it in the design.

(Hm... are we going to have "experimental" fields in v1beta1? That seems kind of contradictory...)

protocols:
tcp:
- port:
number: 8080
Copy link
Copy Markdown
Contributor

@danwinship danwinship Oct 23, 2025

Choose a reason for hiding this comment

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

So this loses the possibility of defaulting the protocol, and thus makes the most common case ("allow to specific TCP ports") more complicated. This is not awful, but it's a data point.

The fact that you can specify multiple ports under tcp helps with that though.

(EDIT: oh, you make both of these points later)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IMO, defaulting of port is sort of wart, anyway. It kind of hurts readability to leave it out.

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.

In the era of generated configuration, I think trying to make configuration extremely concise seems to be less critical IMO

Comment thread npeps/npep-187-ports-and-protocols.md Outdated
protocols:
tcp:
- port: 8080
- portRange:
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.

maybe ports?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

so you mean convert this to list? good point to discuss when we decide on the option (I think it only applies to option2) , but also we need to consider if having a nested list is a good idea (it will require more maxItems and more some extra validations)

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.

No, I just meant rename portRange to ports to make it a little simpler, since the start and end makes it clear it's a "range" anyway. (I guess this is another briefness/clarity trade-off.)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ah well I guess if I was confused by the name, others will be too :D
I kinda like portRange it is very clear and not too long?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If port is int, I would expect ports to be []int. I would stay with portRange if the goal is to elide the extra nesting level.

THAT SAID...

We generally want one-of groups to exist in a struct by themselves, if we can. Something like:

type PortSpec struct {
    // exactly one of these must be set

    // +optional
    Port *int

    // +optional
    PortRange *PortRange

    //... maybe more over time ...
}

The way this is described in YAML would be:

type Protocols struct {
    TCP []TCPPortSpec
     // ...
}

type TCPPortSpect struct {
    // exactly one of these must be set

    // +optional
    Port *int

    // +optional
    PortRange *PortRange

    //... maybe more over time ...
}

Are we 100% sure that we will never want to express something about the TCP protocol that doesn't fit into a repeated port-spec? E.g. "all" doesn't really fit. Might we ever want to express something like a timeout config or something which applies across "all ports in a proto" or "all ICMP codes"? If so, you need the extra nesting, and even if we can confidently say we will never need that, I bet we are wrong :)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

You could add allow-all with one-of like

type TCPSpec struct {
    // exactly one of these must be set

    // +optional
    Port *int

    // +optional
    PortRange *PortRange

    // +optional
    AllowAll *bool
}

?
If we need to do "block port=80 AND flag(PSH)" I assume we would have to remove the one-of validation and add more CEL to restrict compatible fields.
So you wouldn't do

type TCPPortAndFlag {
   Port int
   Flag int
}

type TCPSpec struct {
    // exactly one of these must be set
    // +optional
    Port *int
    // +optional
    PortRange *PortRange
    // +optional
    TCPPortAndFlag *TCPPortAndFlag
}

you would probably do

// CEL: Port and PortRange can't be set at the same time
type TCPSpec struct {
    // +optional
    Port *int
    // +optional
    PortRange *PortRange
    // +optional
    Flag *int
}

?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

We're trying to find more declarative ways to express validation with less traps. Your allowAll means I am able to specify:

tcp:
  - portNumber: 80
  - allowAll: true
  - portNumber: 443
  - allowAll: false

What does it mean? Should allowAll: false even be allowed? It is a *bool so violates the "exactly one" rule (another reason bool is frowned on).

Doing what you suggest for AND means that a) validation is much more complex to reason about (see service API) and b) a back-rev client might see portNumber: 80, flags: [ PSH ] but not know about flags, and so interpret it as just portNumber: 80 -- potentially failing open.

Copy link
Copy Markdown
Member Author

@npinaeva npinaeva Oct 27, 2025

Choose a reason for hiding this comment

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

well allowAll is a wrong field name, it should be called something like any and only be allowed to set to true. Even better to make it non-bool, like match: all, where all is the only supported value for now (which is also not a good field name, but I don't have anything better from the top of my head)
It could be nice to make sure protocol filters don't intersect, but it may be hard to validate, since we have never had a way to make sure

tcp:
  - port: 5
  - portRange:
      start: 1
      end: 10

don't intersect. And the behaviour is well-defined: it is OR-ed, so anything that has at least one match: all match is equivalent to having only that match.
So in your example (considering allowAll: false is not possible) the result is equivalent to simply allow all.

The compatibility with the older clients is a "well-known" problem of this API as discussed here #297 (comment) (which could affect the whole design, but it is kinda separate discussion)
Extra validaiton is needed, as I mentioned in the example, but we need to compare its complexity (which is not too high) with the disadvantages of the alternative solutions. Not sure if you thinks that TCPPortAndFlag is better or if you have other ideas?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

so anything that has at least one match: all match is equivalent to having only that match

That works but is a little awkward, IMO. At the bottom it is a data normalization exercise.

Extra validaiton is needed, as I mentioned in the example, but we need to compare its complexity (which is not too high) with the disadvantages of the alternative solutions.

I am trying to drive validation away from "here's some code" and into "here's the schema". In that regard...

Not sure if you thinks that TCPPortAndFlag is better or if you have other ideas?

...this is superior. Schematically it is an alternative to TCPPort and less likely to be mistaken

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not sure how this strategy should work with more fields, AFAIU every struct have to have a one-of limitation and add all possible fields combinations as separate fields? See Option3 that I added on the enhancement and lmk if I am missing something

Comment thread npeps/npep-187-ports-and-protocols.md Outdated
Comment thread npeps/npep-187-ports-and-protocols.md Outdated
Comment on lines +107 to +109
namedPorts:
- http
- monitoring
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.

"any all" is confusing. Also, what would this mean:

any:
- all: false

```

with the validation like so:
- empty `protocols` list is not allowed
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.

This needs to work the same way as whatever we eventually decided for ports.

Comment thread npeps/npep-187-ports-and-protocols.md
code: 3
```

A likely option is that we will start with the `ICMP: match all` option, as more details matching is not very popular,
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.

i think a lot of people want to block pings but not host unreachable

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think all use cases I have heard of only want to allow ICMP(v6), but if someone does want to drop it, more options make sense

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.

I think v6 would be icmp6. I'm looking at the codes and type and they don't line up:

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.

i think a lot of people want to block pings but not host unreachable

Not that we need to figure it out now, but ICMP host unreachable is typically a response packet to an outgoing UDP/TCP packet. It's best handled as part of the UDP/TCP session from a firewall point of view (I think this is what Linux conntrack does, for example).

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.

yeah, conntrack will mark it as being related to the UDP/TCP connection; I talked about this a little in the allow/deny all bug too (that "deny all" should probably not deny related ICMP replies by default)

protocols:
tcp:
- port:
number: 8080
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

IMO, defaulting of port is sort of wart, anyway. It kind of hurts readability to leave it out.

number: 8080
- port:
number: 9090
namedPort:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This threw me for a minute - not having a protocol here is surprising. Of course a given pod has exactly 1 instance of a named port and that includes protocol, so this is strictly correct, but it is a) a little clunky; and b ) REALLY clunky under a struct named "protocols".

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.

+1 to this comment, I think this definitely will confuse users.

The presence of namedPort kind of pushes us to have Port-like things by themselves.

Copy link
Copy Markdown
Contributor

@danwinship danwinship Oct 25, 2025

Choose a reason for hiding this comment

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

@thockin note that NetworkPolicy allows you to specify:

ports:
- protocol: TCP
  port: dns-udp

(which can't be ruled out at validation time)

and we're trying to avoid that here...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Understood, it's still awkward.

Comment thread npeps/npep-187-ports-and-protocols.md Outdated
protocols:
tcp:
- port: 8080
- portRange:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If port is int, I would expect ports to be []int. I would stay with portRange if the goal is to elide the extra nesting level.

THAT SAID...

We generally want one-of groups to exist in a struct by themselves, if we can. Something like:

type PortSpec struct {
    // exactly one of these must be set

    // +optional
    Port *int

    // +optional
    PortRange *PortRange

    //... maybe more over time ...
}

The way this is described in YAML would be:

type Protocols struct {
    TCP []TCPPortSpec
     // ...
}

type TCPPortSpect struct {
    // exactly one of these must be set

    // +optional
    Port *int

    // +optional
    PortRange *PortRange

    //... maybe more over time ...
}

Are we 100% sure that we will never want to express something about the TCP protocol that doesn't fit into a repeated port-spec? E.g. "all" doesn't really fit. Might we ever want to express something like a timeout config or something which applies across "all ports in a proto" or "all ICMP codes"? If so, you need the extra nesting, and even if we can confidently say we will never need that, I bet we are wrong :)

Comment on lines +176 to +180
```yaml
protocols:
tcp:
- port: 53
udp:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

or the one you considered and evolved past:

protocols:
  tcp:
    ports:
      - number: 53
  udp:
    ports:
      - number: 53

What none of these do is make some forms of evolution more visible. NOTE - I am not saying we should do this but it's worth considering.

Suppose one day we add GRE support. The API understands an object with gre specified but we have a back-rev client which does not. The client will deserialize the gre field and discard it. One approach to defending that is to elevate the list, like:

type Protocols {
    // exactly one should be set, if you see this empty it's somethign you don't know how to handle

    TCP *TCPProtocol
    UDP *UDPProtocol
    GRE *GREProtocol
}

Then rather than the parent having a single Protocols Protocols it would have a list: Protocols []Protocols. Rendering as YAML like:

protocols
  - tcp:
    ports:
      - number : 53
  - udp:
    ports:
      - number: 53
  - gre:
    somethingHere: true

and an older client would see:

protocols
  - tcp:
    ports:
      - number : 53
  - udp:
    ports:
      - number: 53
  - {}  // <- something is obviously wrong!

Again, not sure we should do it, but it's a pattern used in some other places like DRA, where evolution is highly likely. I am not sure it matters here, and anyway it does not handle other kinds of new-field evolutions.

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.

Do we consistently consider the old clients issue? I think inconsistent application of this rule doesn't give us much in terms of guarantees? Specifically, we should define what a client should do when they get something invalid. In the case above, this would be an API object that seemingly violates validation rules that is being sent be the server -- which would unexpected...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is not a rule, it's an opportunity for indicating something went wrong. As you point out, an empty struct have failed at the server, so clearly there's something here but I don't understand it. Compare to the model where we just drop an unknown field, there might be other fields which I do know so I can't tell that you dropped an unknown one.

This matters most in places where we are likely to add more options over time, and where clients really need to know that they don't know something.

The reason I bring it up in this context is to ask, what should an implementation of CNP do if it finds a. "Protocol) that it doesn't understand? How dangerous is it to get that wrong?

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.

Do we consistently consider the old clients issue?

We try to (eg #252).

This matters most in places where we are likely to add more options over time, and where clients really need to know that they don't know something.

Note that (as we eventually decided in the PR above), this does not currently apply to ANP/CNP, because the implementation will be installing the CRD itself, so it should know all of the fields that exist in the CRD (though if they're installing the experimental CRD, they might not implement every field in the CRD, but in that case they are responsible for noticing that one of the fields they don't implement is set).

How dangerous is it to get that wrong?

Per the PR above, if the goal is to ensure that no traffic that is supposed to be denied gets accepted instead, then:

  • If there's something you don't recognize in an action: Accept rule, then you must assume that the rule does not match anything (ie, it neither Accepts nor Denies, but allows further rule processing).
  • If there's something you don't recognize in an action: Deny rule, then you must assume that the rule matches everything (so, "deny all").
  • If there's something you don't recognize in an action: Pass rule, then theoretically you could split evaluation in two and follow both paths, but we currently recommend you just treat it as "deny all" as in the previous case.

Of course this is still "dangerous" in the "could totally break your cluster by denying too much" sense, just not in the "accidentally allows forbidden traffic" sense.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I added a section on this to not lose the nice examples.
So if we can't recognize that an unknown protocol is set (so it is just omitted), then we also can't implement the second rule, so something that should have been denied won't be.
Is it a good enough reason to add this requirement? I have added a third option that takes care of the old clients, let's discuss if it makes sense.
Basically supporting old clients means you can only have one-of types of structs, and no combination of fields, which is a pretty strong limitation on the API and makes it somewhat ugly

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.

If we assume it will always be the case that the NP impl installs the CRDs by itself, then we can argue that it's impossible for there to be unrecognized fields. But I feel like that's a fragile assumption and it would probably be better for us to design the APIs in such a way that unrecognized fields won't mess us up. (see also kubernetes-sigs/gateway-api#3624)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I did put a word "misconfiguration" in there, so should we claim it as such in more details? Saying that we don't think it is reasonable to run older clients with newer APIs, and especially using newer API fields?
Should we try to join forces with gateway api (as it is a common problem) but do not design the API based on this requirement?

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I think it could be reasonable to have different parts of AND implemented in different ways. Regardless, any time we make assumptions about "this can't happen" we end up regretting it -- if it is possible to be correct by construction, that's almost always better.

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.

I think the best policy for a controller when it encounters configuration it can't understand is to fail static and alert. This is already a failure scenario that needs to be handled (control plane outage) and we don't have to make guesses about future features that don't yet exist...

number: 8080
- port:
number: 9090
namedPort:
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.

+1 to this comment, I think this definitely will confuse users.

The presence of namedPort kind of pushes us to have Port-like things by themselves.

Comment on lines +176 to +180
```yaml
protocols:
tcp:
- port: 53
udp:
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.

Do we consistently consider the old clients issue? I think inconsistent application of this rule doesn't give us much in terms of guarantees? Specifically, we should define what a client should do when they get something invalid. In the case above, this would be an API object that seemingly violates validation rules that is being sent be the server -- which would unexpected...

protocols:
- protocol: ICMP
icmp:
matchAll: true
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.

Is icmp: {} an option (was discussed on the Slack thread).

This comment is meant for Option 2 (below)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Using an empty struct to represent something means that you can never ever add another way of selecting. This is the same problem with old clients. If a new user specifies one of the newer selection modes, and an older implementation reads it and drops that field, it will assume that means "all", which, for Network policy, could mean failing open.

It's better for "all" to be an explicit thing.

In an ideal world, you could perhaps make "all" be the default, applied by the server. If nothing else is specified. Then clients are not subject to the empty structure. That requires defaulting logic to look at multiple Fields, which isn't very well supported right now, unless you hand code the defaulting.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I am not even sure the defaulting is a good idea, how do I distinguish "I went to have some coffee and forgot to finish writing a rule" vs "I wanted to deny/allow ALL".

I don't see a single advantage of allowing icmp: {}, it is only saving one line?

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.

Yes, I agree! Having an explicit enum type field description is IMO the most explicit and least error prone path.

In general, I think we should bias towards being more consistent and easy to understand with less emphasis on conciseness because most users will be generating their configuration using an (AI) tool.

code: 3
```

A likely option is that we will start with the `ICMP: match all` option, as more details matching is not very popular,
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.

I think v6 would be icmp6. I'm looking at the codes and type and they don't line up:

```

with the validation like so:
- `protocols` must have at least 1 protocol set
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.

Doesn't this break our "exactly one field set" rule? A back-level client that doesn't know about icmp would see

protocols:
  tcp: ...
  icmp: ...

as

protocols:
  tcp:

and do the wrong thing (unless we have another plan for how to prevent that).

Should this just be "protocol" and allow only one protocol per rule (at cost of a little verbosity for the TCP+UDP case)

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.

(unless we have another plan for how to prevent that)

we do! Nadia talked to Jordan and he came up with a pretty easy way for all CNP clients to detect when fields have gotten dropped (which we need to document now...)

Comment thread npeps/npep-187-ports-and-protocols.md Outdated
```yaml
protocols:
tcp:
- port:
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.

we made a last-second decision in Atlanta to change this to destinationPort

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

huh true, let me update

Copy link
Copy Markdown
Member Author

@npinaeva npinaeva Nov 17, 2025

Choose a reason for hiding this comment

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

should we do destinationNamedPort then? i guess not, but it kinda also applies :)

bowei added a commit to bowei/network-policy-api that referenced this pull request Nov 24, 2025
kubernetes-sigs#297

protocols:
  tcp:
    - destinationPort:
        number: 8080
    - destinationPort:
        range:
          start: 8080
          end: 9090
  udp:
    - destinationPort:
        number: 8080
    - destinationPort:
        number: 9090
  destinationNamedPort:
    - name: http
    - name: monitoring
@danwinship
Copy link
Copy Markdown
Contributor

Option2: Join by protocol has won the design discussion.

As discussed in the last meeting, this ends up being slightly weird because it means the fields of the protocols struct have "OR" semantics rather than "AND" semantics.

Option 3 fixes this, but we were only talking about option 3 as being the "better compatibility for future changes" option, and we eventually decided we didn't need to worry about that, so we discarded 3 in favor of 2. But I don't think we ever noticed that 2 had kinda weird semantics. Given that, I don't think there was strong opposition to 3 over 2.

Signed-off-by: Nadia Pinaeva <npinaeva@redhat.com>
@npinaeva
Copy link
Copy Markdown
Member Author

npinaeva commented Dec 8, 2025

@danwinship @bowei I agree that option 3 makes more sense, update the proposal and yaml, ptal

@bowei
Copy link
Copy Markdown
Contributor

bowei commented Dec 8, 2025

Looks ok to me

@bowei
Copy link
Copy Markdown
Contributor

bowei commented Dec 16, 2025

/lgtm

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Dec 16, 2025
@k8s-ci-robot k8s-ci-robot merged commit 399040a into kubernetes-sigs:main Dec 16, 2025
10 checks passed
@danwinship
Copy link
Copy Markdown
Contributor

belated lgtm

@npinaeva npinaeva deleted the npep-187 branch December 17, 2025 11:14
bowei added a commit to bowei/network-policy-api that referenced this pull request Dec 18, 2025
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
@bowei
Copy link
Copy Markdown
Contributor

bowei commented Dec 18, 2025

#347

bowei added a commit to bowei/network-policy-api that referenced this pull request Dec 18, 2025
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Dec 18, 2025
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Dec 18, 2025
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 22, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 22, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 23, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 26, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 28, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 28, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Jan 29, 2026
Example (from kubernetes-sigs#297)

apiVersion: policy.networking.k8s.io/v1alpha2
kind: ClusterNetworkPolicy
metadata:
  name: cluster-wide-deny
spec:
  tier: Admin
  priority: 0
  subject:
    namespaces:
      matchLabels:
        kubernetes.io/metadata.name: sensitive-ns
  ingress:
    - action: Deny
      name: select-all-deny-all
      from:
      - pods:
          namespaceSelector:
            matchLabels: {}
          podSelector:
            matchLabels: {}
  protocols:
    - tcp:
        destinationPort:
          number: 8080
        flags: [syn] # future extension example
    - tcp:
        destinationPort:
          range:
            start: 8080
            end: 9090
    - udp:
        destinationPort:
          number: 8080
    - udp:
        destinationPort:
          number: 9090
    - namedPort: http
    - namedPort: monitoring
    - icmp: # that doesn't exist yet, but may be added
        type: 7
        code: 3
bowei added a commit to bowei/network-policy-api that referenced this pull request Feb 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] AdminNetworkPolicyIngressRule.Ports has two nil values?

7 participants