kilo icon indicating copy to clipboard operation
kilo copied to clipboard

Connection to K8S Service - SourceIP is not preserved (Source NAT)

Open ch9hn opened this issue 2 years ago • 3 comments

Hello together, we are using a managed K8S cluster with the following Kilo image: docker.io/squat/kilo:bcb722b0b90271a6643d6360e69e702d578d6a06

What we have done so far: Added a Kilo peer via the following manifest:

apiVersion: kilo.squat.ai/v1alpha1
kind: Peer
metadata:
  name: gw-peer
spec:
  allowedIPs:
    - 10.4.0.99/32
    - 192.168.0.0/24 # Test Client Network (
  publicKey: <PUBKEY>
  persistentKeepalive: 10

On the side of the VPN peer we have the following config, which was generated by kgctl:

[Interface]
Address = 10.4.0.99/24
SaveConfig = true
PrivateKey = <PRIVATEKEY>

[Peer]
PublicKey = <PUBKEY>
AllowedIPs = 100.64.2.0/24, 10.4.0.1/32
Endpoint = 51.15.70.238:51820
PersistentKeepalive = 10

[Peer]
PublicKey = <PUBKEY>
AllowedIPs = 100.64.4.0/24, 10.4.0.2/32, 10.32.0.0/12 # Service CIDR is added here.
Endpoint = 51.15.100.213:51820
PersistentKeepalive = 10

[Peer]
PublicKey = <PUBKEY>
AllowedIPs = 100.64.3.0/24, 10.4.0.3/32
Endpoint = 51.158.232.221:51820
PersistentKeepalive = 10

[Peer]
PublicKey = <PUBKEY>
AllowedIPs = 100.64.1.0/24, 10.4.0.4/32
Endpoint = 51.158.241.235:51820
PersistentKeepalive = 10

Now to the issue:

When we are sending UDP packets to our pod, the client IP needs to be preserved. This is no problem, when we are sending on one Pod IP like 100.64.4.4.

But when we are sending the UDP packets to a K8S Service like this:

apiVersion: v1
kind: Service
metadata:
  name: svc-connector
  namespace: <MYSPACE>
spec:
  type: ClusterIP
  clusterIP: 10.40.40.40 # We set the clusterIP field, to identify and hardcode the IP.
  ports:
    - name: connector-udp
      protocol: UDP
      port: 5684
      targetPort: 5684
  selector:
    app: <MYAPP>
    component: test-connector

Then the Source IP is not preserved and we are seeing the IP address of the node.

UDP server message sent to 100.64.4.1:59778
processed data for xxxxx from 100.64.4.1`

We need to preserve the source IP.

How we can do this? Which options we need to switch on ?

ch9hn avatar Aug 28 '22 21:08 ch9hn

Hi @chfxr I did some digging and found the cause of this SNAT. Kilo masquerades packets when they come from peers [0] and go to IPs that Kilo doesn't know about. In other words, Kilo does not masquerade packets that are destined for known IPs [1]. The main goal here is to ensure that packets that are leaving the cluster can find their way back to private IPs under control of Kilo, like the IPs of peers. Today, service IPs are IPs that Kilo doesn't know about! This is rather something under the control of kube-proxy and similar projects. Nevertheless, service IPs are internal to the cluster and the pods that ultimately receive the packet would be able to respond. Furthermore, if it's internal to the cluster, then this masquerading doesn't provide any benefit and only adds overhead, so I would argue that Kilo should allow packets destined for service IPs to go through without being masqueraded.

The main challenge is that Kilo doesn't know the Service CIDR for the cluster. One solution that occurs to me is to make the preservation of source IPs an opt-in feature that is enabled if you deploy Kilo with a --service-cidr flag. We could then maybe use this value for additional features. Would an opt-in solution like this work for you?

[0] https://github.com/squat/kilo/blob/main/pkg/mesh/routes.go#L368 [1] https://github.com/squat/kilo/blob/main/pkg/mesh/routes.go#L352

squat avatar Sep 20 '22 07:09 squat

Hello @squat, thank you very much for your explanation. The Service CIDR range of a cluster can be determined via the following script:

SVCRANGE=$(echo '{"apiVersion":"v1","kind":"Service","metadata":{"name":"tst"},"spec":{"clusterIP":"1.1.1.1","ports":[{"port":443}]}}' | kubectl apply -f - 2>&1 | sed 's/.*valid IPs is //') echo $SVCRANGE Therefore the range is known then and can obtained and loaded into Kilo.

A --service-cidr flag would be sufficient to use this feature.

ch9hn avatar Sep 20 '22 07:09 ch9hn

A --service-cidr flag would be sufficient to use this feature.

Excellent I'll put something together.

squat avatar Sep 20 '22 08:09 squat

Hi @squat. First thing first let me thank you for your awesome work :)

Are there any news about this?

I'm using Kilo on top of Cilium to allow my team to have a VPN access to cluster services. Of course we would not want to provide indiscriminate access to all services and we'd like to leverage CiliumNetworkPolicies to segment traffic.

However without this feature it would be useless to enumerate peers IP in network policies ingress rules, because all traffic to services backing pods will appear as coming from the cilium host. Moreover, in CNP, we need to allow ingress from host nodes which we also would like to avoid.

gtriggiano avatar Feb 24 '23 13:02 gtriggiano

hi @gtriggiano sorry I forgot about this issue! I just pushed a PR that implements the suggested --service-cidr flag: #351. Please take a look. I'll merge this soon so that you and @ch9hn can give it a test drive.

squat avatar Feb 24 '23 19:02 squat

Thank you very much @squat! Looking forward for the merged feature. Do you plan to craft a new versioned release soon?

gtriggiano avatar Feb 25 '23 00:02 gtriggiano

@gtriggiano @ch9hn this feature is in Kilo 0.6.0 🎉🎉

squat avatar Jul 03 '23 23:07 squat