Move gRPC server to Unix socket and scrub introspection data#3739
Move gRPC server to Unix socket and scrub introspection data#3739oliviassss wants to merge 3 commits into
Conversation
There was a problem hiding this comment.
Pull request overview
This PR hardens IPAMD-to-CNI communication and introspection outputs to mitigate local reconnaissance and IP hijack vectors by moving the primary gRPC transport to a Unix domain socket and scrubbing sensitive fields from /v1/enis.
Changes:
- IPAMD gRPC server now listens on a Unix socket (
/var/run/aws-node/ipamd.sock) with restrictive permissions, while also starting a TCP listener for upgrade compatibility. - CNI plugin now dials IPAMD via Unix socket first and falls back to TCP if the socket is unavailable.
/v1/enisintrospection response now clearsContainerIDandIfNamefrom returned IP assignment data; added tests for IPv4/IPv6 scrubbing.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| pkg/ipamd/rpc_handler.go | Switch gRPC server to a Unix socket and add a TCP fallback listener. |
| pkg/ipamd/rpc_handler_test.go | Add a unit test validating Unix socket creation/permissions. |
| pkg/ipamd/introspect.go | Scrub ContainerID/IfName from /v1/enis response payload. |
| pkg/ipamd/introspect_test.go | Add tests verifying sensitive-field scrubbing for IPv4/IPv6. |
| cmd/routed-eni-cni-plugin/cni.go | Prefer dialing IPAMD via Unix socket with TCP fallback for upgrades. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
7c0763e to
de4370d
Compare
…ss-wide umask - Make TCP listener bind mandatory at startup (matching pre-existing behavior) since health probes and waitForIPAM depend on port 50051. If TCP fails to bind, the process exits so kubelet can restart it. - Extract dialIPAMDWithSocketPath to allow test injection of the socket path. Fix TestDialIPAMD_FallsBackToTCPWhenSocketDialFails to actually exercise the "socket exists but Dial fails" branch. Add TestDialIPAMD_ConnectsViaUnixSocket for the happy path. - Remove process-wide syscall.Umask manipulation that could affect concurrent goroutines; rely on os.Chmod immediately after socket creation instead.
viveksb007
left a comment
There was a problem hiding this comment.
we should get rid of the TCP fallback which might hide the issues if there is anything with unix socket based implementation.
|
|
||
| // TODO: Remove TCP fallback once all nodes run the socket-based IPAMD. | ||
| log.Debugf("Falling back to TCP connection: %s", ipamdAddress) | ||
| conn, err := grpcClient.Dial(ipamdAddress, grpc.WithTransportCredentials(insecure.NewCredentials())) |
There was a problem hiding this comment.
CNI to IPAMD is node local communication, why do we need this fallback?
There was a problem hiding this comment.
This is majorly for upgrade safety.
During a DS rolling update, kubelet may still invoke the old CNI binary while the new IPAMD has started with the Unix socket. TCP fallback ensures those in-flight calls succeed.
Additionally, health probe still use TCP :50051 so the server need to bind to TCP anyway. The fallback is a cheap safeguard for backward compatibility.
There was a problem hiding this comment.
i don't think the old CNI binary while the new IPAMD has started will ever happen on a Node. check this out https://kubernetes.io/docs/tasks/manage-daemon/update-daemon-set/
some old ref about DS update mechanism of delete then create, so DS poses some un-availability for short window as the DS pod is always deleted first and then created with updated manifest. (kubernetes/kubernetes#48841)
when a new pod will come, IPAMD won't start unless old CNI binary isn't replaced as init-container will run first.
Nice catch on health probe, we need to change that also to dial on unix socket that IPAMD is creating.
There was a problem hiding this comment.
You're right that ds updates and the upgrade race shouldn't happen in practice. But I'd prefer to keep the TCP fallback just in case the socket is unavailable for reasons like file deletion or permission issues. We can evaluate removal after 1-2 versions.
For health probes - agree we should ideally migrate to the Unix socket, but it requires Helm chart and addon config changes plus evaluation on the rollout/rollback strategy. That's irrelevant to the security fix so better to decouple it.
There was a problem hiding this comment.
We can evaluate removal after 1-2 versions.
what data will support this?
how would we know if Unix socket is being exercised or TCP fallback is being used by CNI binary?
For health probes, I think we just need to change our grpc health probe, can you double check what changes are needed to release this change via AddOns?
| assert.True(t, resp.MultiNICEnabled) | ||
| } | ||
|
|
||
| func TestRunRPCHandler_UnixSocket(t *testing.T) { |
There was a problem hiding this comment.
Can you add other test cases here ? Removing existing sockets, change permission failing
There was a problem hiding this comment.
Added tests for stale socket removal and directory creation. The os perm failure need an OS abstraction layer (not used elsewhere in the codebase), may not worth adding it as the failure handling is straightforward: close listener -> remove socket -> return error.
| // TCP must bind successfully — health probes and waitForIPAM depend on it. | ||
| tcpListener, err := net.Listen("tcp", ipamdgRPCaddress) | ||
| if err != nil { | ||
| listener.Close() |
There was a problem hiding this comment.
Why do we have to close the socket connection if tcp one fails ?
There was a problem hiding this comment.
TCP fail -> health probe fail -> container restart. just to handle the restart gracefully.
|
govuln failure is irrelevant to the change, and no fixed version yet |
What type of PR is this?
bug
Which issue does this PR fix?:
IPAMD IP Hijack issue, more detail in internal docs.
What does this PR do / Why do we need it?:
hostNetwork pods cannot access the socket due to filesystem permissions and container mount namespace isolation.
addresses, pod names) but no longer exposes the fields required to construct a valid DelNetwork call.
Testing done on this change:
Unit tests
New tests added
Integration tests (IPAMD suite, EKS v1.35.5, m5.xlarge nodes)
Manual verification on live cluster (VPC CNI sec-fix.v0.1)
details in internal doc
Node logs confirming socket listener:
Will this PR introduce any new dependencies?:
No
Will this break upgrades or downgrades? Has updating a running cluster been tested?:
No. Backward compatibility is maintained via TCP fallback:
Rolling upgrade tested on live cluster — pods continued to receive IPs throughout the daemonset rollout.
Does this change require updates to the CNI daemonset config files to work?:
Does this PR introduce any user-facing change?:
no
By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.