Skip to content

Support generating Kubernetes NetworkPolicies - #138

Open
tombentley wants to merge 25 commits into
kroxylicious:mainfrom
tombentley:kubernetes-network-policies
Open

tombentley wants to merge 25 commits into
kroxylicious:mainfrom
tombentley:kubernetes-network-policies

Conversation

@tombentley

@tombentley tombentley commented Sep 22, 2026 •

Copy link
Copy Markdown
Member

This proposes allowing the operator to generate Kubernetes NetworkPolicies based on the KafkaProxyIngresses, KafkaServices and KafkaProtocolFilters which make up a KafkaProxy.

To enforce this correctly some changes will also be needed to the loadBalancer `Service` the operator generates.
`Service.spec.loadBalancerSourceRanges` is a best-effort declaration of the source IPs which should be allowed through the load balance.
In practice enforcement is cloud-provider-dependent, and this can be igored.
We will also need to set `Service.spec.externalTrafficPolicy: Local` to preserve the client's source IP address, so that it can be enforced by the

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That's already the subject of #134, so we need to watch for a conflict between the two proposals.

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.

Noted

@k-wall

k-wall commented Sep 22, 2026

Copy link
Copy Markdown
Member

Thanks @tombentley - on first reading this looks reasonable. I will take a deeper look.

@k-wall k-wall added the network label Sep 23, 2026
```


### Operator-level opt-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.

This KafkaProxyOperatorConfig looks like it wants to be its own thing rather than part of this proposal as it's a general mechanism for configuring the operator. Once it exists other features will want to hang their operator-level options off it too, so it feels like it deserves a proposal and a design discussion of its own rather than arriving as a side effect of NetworkPolicy generation.

Could it be split out, with a speculative reference kept here showing just the shape this proposal needs from it (the resourceGeneration toggle and the policyAttachment block) and a note that the exact schema is subject to that separate proposal?

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.

Yes, it is a general mechanism for configuring the proxy. A dedicated proposal would be a good idea. However, apart from this proposal, there's no immediate need to put anything in the spec. It seems odd to open a proposal for a CR with an empty spec.


When no `KafkaProxyNetworkPolicy` targets a `KafkaProtocolFilter` we will generate a default `NetworkPolicy`, such as we saw above, which allows access to and from anywhere.
This default `NetworkPolicy` will have a name like `default-allow-filter-${kafka-filter-name}`.
**TODO** this would be, without policies lots of access, is that right/defensible?

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.

On your TODO here ('this would be, without policies, lots of access, is that right/defensible ?'), I'd lean towards no for the filter case. The stated goal of the feature is auditability, and a default that silently allows a filter both ingress and egress to anywhere is the situation an audit is trying to catch. Defaulting a filter with no attached KafkaProxyNetworkPolicy to deny, or to requiring an explicit attachment to a well known policy via requiredAttachments would show the explicit decision to allow all and seems more in keeping with that goal than the automatic default-allow. The downside is that this stricter requirement could break existing filters if they need network access.

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've (consciously) been trying to have my cake and eat it too: On the one hand we're being asked to make it deny-by-default, but on the other hand doing that without a new major version, or migration period, will break existing setups when they upgrade.

The upgrade is a real problem because (without taking special measures) you can't write the policy (a CR) without the CRD, which arrives at the same time as the new operator.

I think we could do something a little nicer: Use deny-by-default for new installs and allow-by-default for existing installs. This would have to be implemented in the operator itself (not the installation marchinery/manifests/OLM whatever).

kind: NetworkPolicy
options:
generation: enabled|disabled
policyAttachment:

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.

The requiredAttachments / permittedAttachments / prohibitedAttachments trio is quite a lot of machinery. Could we start with required and the default now, and let the other two follow if a user asks for them? or because this isn't a new feature as such, but is attempting to retro-fit over existing deployments that we'll need all the options the three give us to allow users the ability to correctly configure everything?

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.

If we're going to make the distinction between new installs vs existing installs when deciding whether to deny-/allow-by-default, then this becomes the place where the behaviour would be spelled out.

So for an existing install, we'd use:

policyAttachment:
  policyTarget:
    - group: kroxylicious.io
      kind: KafkaProxyIngress
      permittedAttachments: # the KafkaProxyIngressNetworkPolicy need not exist
        - group: kroxylicious.io
           kind: KafkaProxyIngressNetworkPolicy
           whenAbsent: allow # when its missing we allow by default

And for a new install we'd use:

policyAttachment:
  policyTarget:
    - group: kroxylicious.io
      kind: KafkaProxyIngress
      requiredAttachments: # a KafkaProxyIngressNetworkPolicy must exist
        - group: kroxylicious.io
           kind: KafkaProxyIngressNetworkPolicy                                                                                                                                                  

But perhaps we can drop prohibitedAttachments for now, to try to reduce the complexity budget.

tombentley and others added 20 commits October 30, 2026 17:13
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Corrects misspellings (opererator, reqirements, simplfy, propertry,
igored, segration, korxyliciousio), inconsistent "targetting/targetted"
vs "targeting/targeted", inconsistent organisation/organization
spelling, and a couple of grammar slips.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
- Introduce both new CRDs (KafkaProxyNetworkPolicy, KafkaProxyOperatorConfig)
  up front in a summary table, instead of only naming one under a
  "New CRDs" heading and introducing the other much later.
- Extract the inline aside about a future per-SNI-hostname IP-allowlist
  enhancement into its own "Future work" section, consistent with how
  "Rejected alternatives" is already broken out, leaving a forward
  pointer at its original location.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
- Add a "Typical owner" column to the New CRDs table, making explicit
  which role holds KafkaProxyNetworkPolicy (per-namespace/tenant) vs
  KafkaProxyOperatorConfig (cluster-admin/platform), tying back to the
  RBAC separation-of-concerns rationale already given in Motivation.
- State explicitly that targetRef only resolves within the
  KafkaProxyNetworkPolicy's own namespace, so one tenant can never
  attach policy to another tenant's resources.
- Note in Current situation that NetworkPolicy enforcement is
  CNI-dependent, since the proposal's audit/compliance motivation
  could otherwise read as implying guaranteed enforcement.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Replace the single KafkaProxyNetworkPolicy kind with
KafkaProxyIngressNetworkPolicy (targets KafkaProxyIngress or
KafkaProtocolFilter) and KafkaProxyEgressNetworkPolicy (targets
KafkaService or KafkaProtocolFilter), following discussion of
alternative designs for the policy-attachment kind.

A single combined kind can only be granted via RBAC as a whole: a role
trusted to declare one filter's own egress need would, by the same
grant, also be able to declare sweeping ingress rules against the
proxy's shared KafkaProxyIngress. Splitting by direction closes that
gap and makes the two "may not target X" cross-validation rules
impossible to express rather than something the operator has to
reject at reconcile time. Splitting further, one kind per target CR,
was considered and rejected as more CRDs/RBAC surface than needed;
recorded as a rejected alternative alongside the single-kind case.

Incidental fixes made while rewriting every example: spec.allow is now
consistently an array (previously allowIngress/allowEgress examples
inconsistently showed a single rule object in some places and an array
in others), the generated NetworkPolicy ports field is consistently a
list, and targetRef.group now matches the actual API group
(kroxylicious.io, not io.kroxylicious).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Add a Status conditions table under New CRDs covering ResolvedRefs and
Accepted (types, statuses, reasons, and meaning), grounded in the
condition/reason vocabulary already implemented in the kroxy-np PoC
(ReferencedResourcesNotFound, TransitivelyReferencedResourcesNotFound,
Invalid, Disabled, ReferencedResourceNotReconciled), including the
PoC's cascade behaviour where Accepted can never be True while
ResolvedRefs is False, and where a Disabled NetworkPolicy generation
config leaves ResolvedRefs untouched but forces Accepted: False.
Previously the proposal only mentioned a bare "Accepted" condition.

Also add a sentence making explicit that allowing multiple
KafkaProxyIngressNetworkPolicy/KafkaProxyEgressNetworkPolicy to target
the same resource (unioned, not replacing) is a deliberate divergence
from the Gateway API's usual 1:1 direct policy attachment convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Replace the two TODOs about what internal DNS names Strimzi actually
produces. Strimzi's bootstrap Service for a listener is named
<cluster-name>-kafka-bootstrap (or <cluster-name>-kafka-<listener-name>-bootstrap
for a named listener) and is stable across broker scaling/KafkaNodePool
changes, since it's derived only from the Kafka resource's name. Its
headless per-broker Service DNS names, by contrast, embed a broker-id
that depends on node pool naming and can change as pools are scaled or
renamed, so they're called out as unsuitable for bootstrapServers.

Updated the internal-DNS bootstrapServers example to a realistic,
correctly-formed Strimzi bootstrap Service DNS name (also fixing a
"boostrap" typo), and fixed "entirely compose of" -> "entirely composed
of" in both the internal- and external-DNS sections for consistency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
A policy-attached NetworkPolicy is owned by the KafkaProxyIngressNetworkPolicy/
KafkaProxyEgressNetworkPolicy that produced it, so deleting the policy CR
only removes its own NetworkPolicy rather than affecting others targeting
the same resource. A default-allow NetworkPolicy (no policy CR exists) is
owned by the target CR itself, since there's nothing else to own it.
Previously this wasn't stated anywhere.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Validation of a KafkaProxyIngressNetworkPolicy/KafkaProxyEgressNetworkPolicy
happens asynchronously on the reconciler's schedule, not at kubectl apply
time, so a successful apply doesn't confirm the policy was accepted.
Add a sentence recommending kubectl wait --for=condition=Accepted for
pipelines that need that confirmation.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Generated NetworkPolicy names are a prefix plus a source CR's name, so
a sufficiently long source name could exceed Kubernetes' name length
limit. State that the operator will handle this with the standard
truncate-and-hash pattern already used elsewhere in Kubernetes (e.g.
ReplicaSet/ControllerRevision names), without spelling out the
algorithm here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
…g default

Replace the open TODO about whether default-allow is defensible for
KafkaProtocolFilter with a resolved design: a new
KafkaProxyOperatorConfig.spec.resourceGeneration[].options.filterDefaultPolicy
(allow|deny, schema default allow) governs what's generated when no
KafkaProxyIngressNetworkPolicy/KafkaProxyEgressNetworkPolicy targets a
filter. KafkaProxyIngress/KafkaService are unaffected since they always
need some access to function.

The schema default stays allow so upgrading an existing deployment never
silently cuts off a filter's access. Separately, the operator applies a
first-run heuristic: on startup, if no KafkaProxyOperatorConfig exists
and no KafkaProxy/KafkaProtocolFilter resources exist either, it seeds
one with filterDefaultPolicy: deny, giving fresh installs a secure
default with no configuration required. This lives in the operator
itself rather than in install tooling, because KafkaProxyOperatorConfig
being a brand-new CRD means its absence looks identical on a fresh
install and on an upgrade — no install mechanism (including OLM's
ClusterServiceVersion, which OpenShift's embedded OperatorHub also uses)
can tell those apart from that signal alone, unlike Helm's install-only
hooks which only solve it for Helm specifically.

Documents this in a new filterDefaultPolicy subsection, references it
from Compatibility, and records the install-tooling-seeding approach as
a rejected alternative with the reasoning above.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
…rgets

Replace the standalone resourceGeneration.options.filterDefaultPolicy
with policyAttachment.policyTarget[].permittedAttachments[].whenAbsent,
restructuring policyTarget into per-target-kind entries (each with its
own requiredAttachments/permittedAttachments/prohibitedAttachments)
instead of flat global lists. This reuses the existing required/
permitted/prohibited machinery for "what happens when no policy is
attached" rather than introducing a second, parallel configuration
concept, and incidentally fixes the previously-unclear pairing between
policyTarget kinds and attachment kinds.

Extend the allow/deny default choice from KafkaProtocolFilter to
KafkaProxyIngress and KafkaService as well, since the same reasoning
applies to all three, and fold in two behavioural decisions:
- When whenAbsent: deny applies, the target gets Accepted: False with a
  reason identifying the missing policy (not a silent failure), using
  the same treatment as a requiredAttachments violation, and propagates
  to dependents the same way existing ResolvedRefs/Accepted failures do.
- Note the consequence for docs: a deny-by-default fresh install won't
  pass traffic until the right policy CRs exist, so quickstarts and
  example manifests need to be reviewed and updated accordingly.

Update the KafkaProxyIngress/KafkaService/KafkaProtocolFilter sections
and Compatibility to reference the new mechanism.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Previously the precise, auto-derivable egress rule for a strimziKafkaRef-based
KafkaService still required an explicit (if empty) KafkaProxyEgressNetworkPolicy
to be created, otherwise falling back to the generic allow/deny default like
every other target. Since the operator already has everything it needs to
compute the exact rule (the Kafka cluster's namespace and Strimzi's published
broker pod labels), requiring a CR just to get the answer the system already
knows is an avoidable papercut — and one that would bite every fresh,
deny-by-default install proxying a same-cluster Strimzi target.

Generate the precise rule unconditionally instead, with no CR required;
policyAttachment's whenAbsent setting doesn't apply to this case, since
there's no "allow vs deny" choice to make when a correct, minimal answer
is already available. An explicit KafkaProxyEgressNetworkPolicy can still
be attached on top for additional egress needs, additive with the
auto-derived rule rather than replacing it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>


Resolves Keith's review comment flagging a potential conflict with
proposal kroxylicious#134 ("Configuring the LoadBalancer Service from
KafkaProxyIngress"), which has since been merged and implemented:
each loadBalancer-type KafkaProxyIngress now gets its own dedicated
Service (named after the ingress), rather than all loadBalancer
ingresses on a proxy sharing one `<proxy>-sni` Service.

This proposal's loadBalancer section was written against the old
shared-Service model and is now out of date:
- externalTrafficPolicy is already configurable via
  KafkaProxyIngress.spec.loadBalancer.service.externalTrafficPolicy
  (proposal kroxylicious#134); this proposal depends on it as a prerequisite
  rather than needing to introduce it, and should reject a
  KafkaProxyIngressNetworkPolicy with ipBlock rules against a
  loadBalancer ingress that doesn't have it set to Local, since
  otherwise the restriction would silently fail to match real
  client addresses.
- The CIDR-union mechanism for sharing one Service across multiple
  KafkaProxyIngresses is no longer needed, since each ingress already
  has its own Service post-kroxylicious#134. The remaining, still-valid sharing
  concern is narrower: VirtualKafkaClusters that share one ingress
  (via SNI) still can't get per-cluster CIDR isolation from a
  Service-level restriction, which is what the Future work section's
  proxy-side IP-allowlist idea would address.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Centralize the podSelector, ownerReferences, and naming explanation for
generated NetworkPolicies into one section instead of repeating it per
target kind. The previous naming scheme (plain string concatenation)
could collide: the strimziKafkaRef auto-derived rule and an attached
KafkaProxyEgressNetworkPolicy both produced allow-egress-${name}, and
the -filter infix was textually indistinguishable from a hyphen inside
a user-chosen name. Replace it with a {token}.{name} scheme that's
collision-free by construction, using . as a reserved delimiter, and
move the allow/deny distinction to a label so it can't collide at all.

Signed-off-by: Tom Bentley <tbentley@redhat.com>
Splitting by direction only partially separated RBAC concerns: both
resulting kinds still mixed a shared, proxy-wide target with per-filter
ownership (KafkaProxyIngress/filter ingress, KafkaService/filter
egress), so the two-kind split didn't justify its extra CRD/controller
surface. A single kind now covers both directions, with spec.ingress/
spec.egress validity narrowed by CEL instead of by kind, and orgs
needing finer-than-kind-level separation pointed at a policy engine
(Kyverno/OPA/ValidatingAdmissionPolicy) layered on top.

Signed-off-by: Tom Bentley <tbentley@redhat.com>
tombentley and others added 5 commits October 30, 2026 17:13
Move KafkaProxyOperatorConfig/policyAttachment earlier so it's defined
before the per-target sections that depend on it, consolidate the
allow/deny-when-absent explanation that was repeated near-verbatim
across KafkaProxyIngress/KafkaService/KafkaProtocolFilter, and trim
other repeated explanations (additive semantics, per-mechanism
rejection reporting, RBAC rationale) down to one canonical statement
plus cross-references. No technical content removed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
The full matchLabels block was repeated verbatim in every generated
NetworkPolicy example; it's already fully defined once in "Common
structure of generated NetworkPolicy resources", so later examples
now just show podSelector: ... with a pointer back to it, matching
the elision style already used for the KafkaProxyOperatorConfig
outline example.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Resolves the open TODO about whether whenAbsent should default to
deny for fresh installs. Decided: allow is the schema default for
every install, fresh or upgraded, so the bar to a working proxy stays
low. deny remains available, but only as an explicit administrator
opt-in via KafkaProxyOperatorConfig — never something the operator
decides on its own. The generated NetworkPolicy naming and
kroxylicious.io/default-policy label are the audit trail an admin
uses to find and tighten allow rules over time.

Drops the first-run heuristic that auto-created a deny-default
KafkaProxyOperatorConfig for installs with no existing KafkaProxy
resources, and updates Compatibility and Rejected alternatives to
match (folding the install-time-tooling-seeding alternative into a
single rejected "deny-for-fresh-installs" entry, regardless of
mechanism).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Documents why clusterCIDRs can't get the same autodetection fallback
clusterDomain has: no CNI-agnostic, low-privilege way to derive
pod/service CIDRs, and reading Node.spec.podCIDRs cluster-wide would
need a nodes RBAC grant disproportionate to the one check it enables
today. Also makes explicit that leaving it unset skips the
bootstrapServers internal-address check rather than enforcing it some
other way.

Notes proposal kroxylicious#148 (per-gateway source-address filtering in the
proxy) as a plausible future second consumer of clusterCIDRs, so the
field's value isn't pinned to a single narrow validation forever.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
Signed-off-by: Tom Bentley <tbentley@redhat.com>
@tombentley
tombentley force-pushed the kubernetes-network-policies branch from a5712c8 to 3fb8bd4 Compare October 30, 2026 04:13

@robobario robobario left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for the proposal @tombentley, getting my head around it slowly.

egress: ...
```

* The `spec.targetRef` references another proxy CR — restricted (via an enum on the `kind` property at the CRD schema level) to `KafkaProxyIngress`, `KafkaService`, or `KafkaProtocolFilter`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

https://github.com/kroxylicious/design/blob/main/proposals/142-kube-api-routing.md has been accepted now, should we add KafkaProtocolRouter to the list? A user's router could also want to make network calls.

policyTypes:
- Ingress
ingress:
- ports: # the ingress's own listening port, since spec.ingress[].ports was omitted

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Would user supplied ports completely replace our generated ones? Taking the user's specifications is less surprising for audit, but more likely to leave them without connectivity if they don't allow ingress to the listen port. Since KPI port-allocation is an operator responsibility I don't think it would really be practical for a user to declare these, so I'm wondering, is there any case we can imagine where a user would want to add additional ports to the ingress policy beyond our listen ports and should we support that? I can't think of any, so maybe we could use CEL to block that combination early (referencing a KPI + ingress ports declared) and prevent misconfiguration?

I can imagine usages for ingress ports for the other types, like for Filters/Routers it would be possible for users to start up their own listening servers and want to configure ingress.

Comment on lines +119 to +121
We will add a new cluster-scoped CRD for expressing options for the operator.
The operator `Deployment` itself will support a `CONFIG_NAME` env var which allows to select the `KafkaProxyOperatorConfig` to be used by that operator instance.
The value will default to `default`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we should state here that the new resource is optional and we don't try to explicitly install a default. It looks like there's rationale already in the rejected alternatives about it being painful to keep helm/OLM/manifests in sync if we tried to do this.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

(I later came upon the requirement in the goals to not manipulate existing NetworkPolicies, maybe that should be answered first, do we need to support this idea of different behaviour for new vs existing)

Comment on lines +54 to +56
* For new installations, generate `NetworkPolicy` resources by default, with the option to disable generation.
In the absence of ingress and egress policy resources, the generated `NetworkPolicy` will be allow-by-default.
* For existing installations, do **not** generate `NetworkPolicy` resources by default. Require the user to enable generation.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

How are we planning to implement this. In the rejected alternatives it mentions we can't discriminate between new/existing since the optional resources could be absent.

The resourceGeneration section of the operator config is out of sync with this saying generation is enabled by default with no distinction between new/existing.

Does this goal need to be walked back? Or something added to describe how we can discriminate between new/existing?

(claude spotted)


#### `podSelector`

The operator resolves `spec.targetRef` through to the `KafkaProxy` that ultimately owns the targeted resource, and copies that `KafkaProxy`'s own `Pod`-selector labels, unchanged, into every generated `NetworkPolicy`:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

KafkaProtocolFilter and KafkaService can be shared by multiple KafkaProxy, should this sentence be updated to say we generate NetworkPolicies per targeted KafkaProxy plural?

(claude spotted)

* `bootstrapServers` is a comma-separated list of the host addresses for some bootstrap servers of a Kafka cluster.


#### The `strimziKafkaRef` case

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

in the other bootstrapServers cases we nail down which are allowed to use namespace and pod selectors, vs ipBlock. Does the strimziKafkaRef have a similar restriction to selectors only because it's cluster local? We should add an equivalent statement about which types are allowed.

(claude spotted)


Most of the remaining peer-type validation described in the sections below — e.g. `ipBlock` vs. selector, which depends on the *targeted* `KafkaProxyIngress`'s own `clusterIP`/`loadBalancer`/`openShiftRoute` mechanism — depends on the state of a different object, which a CRD-schema CEL rule cannot look up. That validation happens in the reconciler instead, and is reported via `Accepted: False` (see [Status conditions](#status-conditions)); each mechanism-specific section below therefore states only which peer type it requires, not how a violation gets reported.

`targetRef` only resolves within the same namespace as the `KafkaProxyNetworkPolicy` itself; there is no cross-namespace form (unlike, for example, the Gateway API's `ReferenceGrant`-mediated cross-namespace policy attachment). This keeps the ownership split described above confined to a single namespace: `KafkaProxyNetworkPolicy` can never grant or imply network access on behalf of a `KafkaProxyIngress`, `KafkaService`, or `KafkaProtocolFilter` owned by a different namespace/tenant.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

this is fine for our proxy resources, but what about the pods/namespaces targeted by the ingress/egress rules?

should I, on the proxy team, be able to allow the proxy to egress to some other teams namespace without something like ReferenceGrant for the other team to allow access? Can I allow egress to kube-system namespace for example?

(claude discovered)


The `KafkaService` CR supports two ways to express a target cluster:

* `strimziKafkaRef` is a reference to a Strimzi `Kafka` resource which exists in the same cluster as the `KafkaService` CR.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

similar question about allowing egress to some other teams namespace containing Kafka. Is it okay to not have a ReferenceGrant type resource, which would give the Kafka team the ability to grant the policy


### Operator RBAC changes

The operator will need to be allowed to CRUD `NetworkPolicy` resources via its dependent resources `ClusterRole`.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might be worth a callout that we've increased the Proxy blast radius. A kroxy operator watching all namespaces would be able to touch other tenants NetworkPolicy. This new access to the firewall type rules should be called out as a new risk.

(claude found)

app.kubernetes.io/instance: my-proxy-cr
```

Because this `podSelector` is identical on every `NetworkPolicy` generated for a given `KafkaProxy` — regardless of which `KafkaProxyIngress`, `KafkaService`, or `KafkaProtocolFilter` drove its generation — they all select the same `Pods`, and `NetworkPolicy` rules are additive across policies selecting the same `Pod` (see [Current situation](#current-situation)). This means a broad rule generated for *any one* target on a proxy is unioned with every other target's rules for the same direction. For example, a `KafkaProxy` with one `KafkaService` using `strimziKafkaRef` (always getting the narrow, auto-derived `target-strimzi` rule) and a second `KafkaService` using `bootstrapServers` with no `KafkaProxyNetworkPolicy` attached (getting the allow-all `target-default` fallback) ends up, in practice, with unrestricted egress on that port from the proxy's `Pods`: the first `KafkaService`'s narrow rule is still generated correctly and still has naming/audit value, but it adds no enforcement once a sibling target's rule already allows everything. Restricting a proxy's effective ingress or egress for a given direction therefore means addressing *every* target attached to that proxy, not just the one under review.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

One risk is that there's no visibility into whether your restrictions are widened. If I create a deny all egress rule for my filter, it can be flipped to an allow all by some other team/role setting up some other Filter with a wider policy, how do I know my policy has been weakened?

Maybe for visibility you could include it in the KafkaProxyNetworkPolicy, status, like list off all the associated KafkaProxyNetworkPolicies and whether their policy is redundant/widened?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants