Skip to content

feat: add allowICMP annotation for port-filter mode#12

Merged
kvaps merged 1 commit into
mainfrom
feat/allow-icmp
Apr 27, 2026
Merged

feat: add allowICMP annotation for port-filter mode#12
kvaps merged 1 commit into
mainfrom
feat/allow-icmp

Conversation

@kvaps
Copy link
Copy Markdown
Member

@kvaps kvaps commented Apr 27, 2026

Summary

Follow-up to #11. In port-filter mode the previous PR drops all ICMP toward the backend pod IP, which breaks ping, PMTU discovery (ICMP "fragmentation needed"), and ICMP unreachable signalling. This adds an opt-in annotation to accept ICMP for services that need it.

  • New annotation networking.cozystack.io/allowICMP: "true" (default false). Only meaningful in port-filter mode (wholeIP: "false").
  • New nft set icmp_allowed_pods and a rule in port_filter placed between the ct established,related accept and the per-port drop: ip daddr @icmp_allowed_pods meta l4proto icmp accept.
  • New ProxyProcessor methods EnsureICMPAllow / DeleteICMPAllow / CleanupICMPAllow, mirroring the port-filter API shape.
  • Controller wiring consolidated into reconcilePortFilter / clearPortFilter helpers — fewer scattered if/else blocks for both port-filter and ICMP state.
  • Unit test for the new allowICMP predicate.
  • README documents the annotation and updates the Datapath section.

Why per-pod set, not per-rule

Same reason as filtered_pods/allowed_ports: the chain runs at priority filter (0), after ingress_dnat has rewritten daddr from LB IP to pod IP. Keying on pod IP keeps the chain rule count constant and the per-service state in nft sets, which is consistent with the rest of the design.

Test plan

  • wholeIP: "false" + allowICMP: "true": ping VM works, port filter still in effect for TCP/UDP
  • wholeIP: "false" without allowICMP: ICMP dropped (default)
  • wholeIP: "true": passthrough, ICMP works
  • Toggle allowICMP true→false: pod removed from icmp_allowed_pods
  • Service deletion cleans up icmp_allowed_pods entry

Summary by CodeRabbit

Release Notes

  • New Features

    • Added optional networking.cozystack.io/allowICMP service annotation to selectively allow ICMP traffic to managed pods in port-filter mode.
  • Documentation

    • Updated documentation explaining default ICMP drop behavior in port-filter mode and how the new annotation enables ICMP acceptance.

Introduce networking.cozystack.io/allowICMP. When set to "true" on a
service in port-filter mode (wholeIP: "false"), ICMP traffic toward the
backend pod IP is accepted instead of being dropped by the per-port
filter. Without this opt-in, port-filter mode silently drops all ICMP,
which breaks ping, PMTU discovery (ICMP "fragmentation needed") and
ICMP unreachable signalling.

Implementation:
- New nft set icmp_allowed_pods (key: ipv4 pod IP).
- New rule in port_filter, between the ct established,related accept
  and the per-port drop: ip daddr @icmp_allowed_pods meta l4proto icmp
  accept.
- New ProxyProcessor methods EnsureICMPAllow / DeleteICMPAllow /
  CleanupICMPAllow, mirroring the port-filter API.
- Controller refactored to consolidate port-filter + ICMP-allow handling
  into reconcilePortFilter / clearPortFilter helpers.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Andrei Kvapil <kvapss@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ad28621d-6bb7-4a9a-86a6-d92e89b60c31

📥 Commits

Reviewing files that changed from the base of the PR and between 4a8d111 and 2131467.

📒 Files selected for processing (6)
  • README.md
  • pkg/controllers/services_controller.go
  • pkg/controllers/services_controller_test.go
  • pkg/proxy/dummy.go
  • pkg/proxy/interface.go
  • pkg/proxy/nft.go

📝 Walkthrough

Walkthrough

This change introduces an optional ICMP allowlist feature for port-filter networking mode. The implementation adds service annotation support (networking.cozystack.io/allowICMP), refactors service controller logic into centralized helpers, extends the ProxyProcessor interface with ICMP management methods, and implements ICMP allow/deny functionality in both dummy and nftables proxy processors.

Changes

Cohort / File(s) Summary
Documentation
README.md
Added documentation for optional networking.cozystack.io/allowICMP annotation in port-filter mode; describes ICMP drop behavior and conditional acceptance flow via icmp_allowed_pods.
Service Controller Refactor
pkg/controllers/services_controller.go, pkg/controllers/services_controller_test.go
Refactored port-filter logic into centralized helpers (reconcilePortFilter, clearPortFilter); integrated allowICMP annotation parsing and conditional ICMP-allow rule management; added unit test validating annotation parsing across multiple scenarios.
Proxy Interface
pkg/proxy/interface.go
Extended ProxyProcessor interface with three methods: EnsureICMPAllow, DeleteICMPAllow, and CleanupICMPAllow to manage ICMP allowlist entries.
Proxy Implementations
pkg/proxy/dummy.go, pkg/proxy/nft.go
Dummy processor provides no-op implementations with diagnostic output; nftables processor creates icmp_allowed_pods set, installs ICMP accept rules, and implements reconciliation logic with batched set operations.

Sequence Diagram

sequenceDiagram
    participant Svc as Service with<br/>allowICMP annotation
    participant Ctrl as Services<br/>Controller
    participant PP as ProxyProcessor
    participant NFT as nftables<br/>port_filter chain

    Svc->>Ctrl: Add/Update Service
    Ctrl->>Ctrl: Extract allowICMP<br/>annotation value
    
    alt allowICMP: "true" & port-filter mode
        Ctrl->>PP: EnsureICMPAllow(svcIP, podIP)
        PP->>NFT: Add podIP to<br/>icmp_allowed_pods set
        NFT->>NFT: ICMP packets to podIP<br/>match accept rule
    else allowICMP absent/false & port-filter mode
        Ctrl->>PP: DeleteICMPAllow(svcIP, podIP)
        PP->>NFT: Remove podIP from<br/>icmp_allowed_pods set
        NFT->>NFT: ICMP packets to podIP<br/>hit drop rule
    end

    Svc->>Ctrl: Delete Service
    Ctrl->>PP: CleanupICMPAllow(keep map)
    PP->>NFT: Reconcile set:<br/>remove unlisted entries
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A fluffy bouncing through the nets,
Where ICMP pings now pass begets,
With annotations marking the way,
Allowed pods get to ICMP-play,
The port-filter chain, no longer gray!

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/allow-icmp

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 and usage tips.

@kvaps kvaps merged commit e54181f into main Apr 27, 2026
1 of 2 checks passed
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces the networking.cozystack.io/allowICMP annotation, enabling ICMP traffic for services in port-filter mode to support ping and Path MTU Discovery. The ServicesController has been refactored to use reconcilePortFilter and clearPortFilter helpers for managing port-filtering and ICMP rules. Additionally, the NFTProxyProcessor now includes an icmp_allowed_pods nftables set and a rule to accept ICMP traffic for authorized pods. I have no feedback to provide.

kvaps added a commit to cozystack/cozystack that referenced this pull request Apr 28, 2026
## What this PR does

The `vm-instance` chart now drives the cozy-proxy `wholeIP` and
`allowICMP` annotations explicitly so that `externalMethod: PortList`
actually filters ingress traffic to declared ports while keeping
ping/PMTU functional.

- Render `networking.cozystack.io/wholeIP: "false"` when
`externalMethod: PortList` (was always `"true"`, which silently disabled
the PortList semantics in cozy-proxy).
- Add `externalAllowICMP` value (default `true`) propagated as
`networking.cozystack.io/allowICMP` when `externalMethod: PortList`.
Without this, cozy-proxy drops ICMP in port-filter mode (ping/PMTU
broken). Operators can set `externalAllowICMP: false` to opt out.

The changelog entry is intentionally **not** part of this PR — it will
be added in a dedicated `docs: add changelog for vX.Y.Z` commit at
release time, per project convention.

## Why

`externalMethod: PortList` is documented as filtering ingress traffic to
declared ports but has been non-functional on Cozystack v1.3.0 —
verified empirically on a 3-node Talos lab. Root cause was twofold:
chart always set `wholeIP: "true"`, and cozy-proxy v0.2.0 had no
port-aware logic. The cozy-proxy side was fixed in
cozystack/cozy-proxy#11 (merged) and cozystack/cozy-proxy#12 (allowICMP
follow-up); this PR completes the user-visible fix on the chart side.

## Companion PRs

- cozystack/cozy-proxy#11 (merged) — per-service ingress port filtering
- cozystack/cozy-proxy#12 (merged) — `allowICMP` annotation for
port-filter mode

## Test plan

- [x] Built cozy-proxy with the companion fix locally, deployed on a
3-node Talos lab (Cozystack v1.3.0)
- [x] `wholeIP: "false"` Service with `spec.ports: [22]`: only port 22
reachable from outside; ports 80/443/8080/9999 filtered
- [x] WholeIP-annotated Service unchanged: all listening ports reachable
- [x] Egress IP preservation works in both modes (TCP curl + UDP DNS)
- [x] `nft list table ip cozy_proxy` confirms expected ruleset
- [x] `helm template` renders the expected annotation matrix: `PortList`
default → `wholeIP=false, allowICMP=true`; `PortList` opt-out →
`allowICMP=false`; `WholeIP` → only `wholeIP=true`
- [x] `make unit-tests` passes locally
- [ ] CI unit tests
- [ ] CI E2E

## Backport

Suggesting `backport-v1.3` once merged.

## Release note

```release-note
[vm-instance] Make `externalMethod: PortList` actually filter ingress traffic to ports listed in `externalPorts`. New `externalAllowICMP` knob (default true) propagates the cozy-proxy `allowICMP` annotation to keep ping/PMTU functional in port-filter mode. Combined with cozy-proxy v0.3.0+, only listed ports plus ICMP are reachable from the VM's LoadBalancer IP.
```


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Added `externalAllowICMP` configuration option to control ICMP traffic
acceptance for VM external access in PortList mode (enabled by default).

* **Documentation**
* Updated parameter documentation to include the new ICMP traffic
control setting.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant