All Products
Search
Document Center

Alibaba Cloud Service Mesh:Preserve the source IP address of a client when the client accesses services in ASM

Last Updated:Mar 11, 2024

This topic describes how to preserve the source IP address of a client when the client accesses services in Service Mesh (ASM).

Prerequisites

Background information

Source IP addresses are widely used. Typical usage scenarios include:

  • Access control for applications: Many applications enforce additional identity verification by checking the source IP address when they detect that a user is trying to log on from a different location.

  • Session persistence: You can configure a load balancer to direct all the requests from a client to the same server instance based on the source IP address of the client.

  • Access logs and monitoring statistics: Access logs and monitoring statistics that contain real source IP addresses help developers analyze the information.

A load balancer on the cloud can transfer the source IP address of a client to the backend service. Istio is supposed to allow applications to obtain the original source IP addresses. However, when Envoy proxies are injected as sidecars in Istio, the proxies redirect all inbound traffic. Envoy sends the traffic to the applications that are bound to the localhost 127.0.0.1, so the applications cannot obtain the original source IP addresses.

Deploy sample applications

  1. Deploy a sleep application.

    1. Create a sleep.yaml file that contains the following content:

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: sleep
      ---
      apiVersion: v1
      kind: Service
      metadata:
        name: sleep
        labels:
          app: sleep
          service: sleep
      spec:
        ports:
        - port: 80
          name: http
        selector:
          app: sleep
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: sleep
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: sleep
        template:
          metadata:
            labels:
              app: sleep
          spec:
            terminationGracePeriodSeconds: 0
            serviceAccountName: sleep
            containers:
            - name: sleep
              image: curlimages/curl
              command: ["/bin/sleep", "3650d"]
              imagePullPolicy: IfNotPresent
              volumeMounts:
              - mountPath: /etc/sleep/tls
                name: secret-volume
            volumes:
            - name: secret-volume
              secret:
                secretName: sleep-secret
                optional: true
    2. Run the following command to deploy the sleep application:

      kubectl -n default apply -f  sleep.yaml
  2. Deploy an HTTPBin application.

    1. Create an httpbin.yaml file that contains the following content:

      apiVersion: v1
      kind: Service
      metadata:
        name: httpbin
        labels:
          app: httpbin
      spec:
        ports:
        - name: http
          port: 8000
        selector:
          app: httpbin
      ---
      apiVersion: apps/v1
      kind: Deployment
      metadata:
        name: httpbin
      spec:
        replicas: 1
        selector:
          matchLabels:
            app: httpbin
            version: v1
        template:
          metadata:
            labels:
              app: httpbin
              version: v1
          spec:
            containers:
            - image: docker.io/citizenstig/httpbin
              imagePullPolicy: IfNotPresent
              name: httpbin
              ports:
              - containerPort: 8000
    2. Run the following command to deploy the HTTPBin application:

      kubectl -n default apply -f  httpbin.yaml

Scenario 1: East-west traffic

TPROXY mode is not enabled

Envoy proxies are injected as sidecars in Istio. These proxies intercept all inbound and outbound traffic for services and forward the traffic to requested applications. Therefore, the IP addresses of the requests received by applications are the endpoint that is used to access Envoy 127.0.0.6.

  1. Run the following command to query the pod status:

    kubectl -n default get pods -o wide

    Expected output:

    NAME                            READY   STATUS        RESTARTS   AGE     IP             NODE                     NOMINATED NODE   READINESS GATES
    httpbin-c85bdb469-4ll2m         2/2     Running       0          3m22s   172.17.X.XXX   cn-hongkong.10.0.0.XX    <none>           <none>
    sleep-8f764df66-q7dr2           2/2     Running       0          3m9s    172.17.X.XXX   cn-hongkong.10.0.0.XX    <none>           <none>

    The output shows that the IP address of the sleep application is 172.17.X.XXX.

  2. Run the following command to initiate a request from the container where the sleep application resides:

    kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip

    Expected output:

    {
      "origin": "127.0.0.6"
    }

    The output shows that the IP address of the request received by the HTTPBin application is the endpoint that is used to access Envoy 127.0.0.6 instead of the IP address of the sleep application.

  3. Check whether the source IP address is 127.0.0.6 from the socket.

    1. Log on to the container where the HTTPBin application resides and run the following command to install netstat:

      apt update & apt install net-tools
    2. Log off from the container. Then, run the following command to connect to the HTTPBin container and view information about the connection that uses port 80:

      kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80

      Expected output:

      tcp        0      0 172.17.X.XXX:80         127.0.0.6:42691         TIME_WAIT   -

      The output shows that the source IP address is 127.0.0.6.

  4. View proxy logs of the pod where the HTTPBin application resides.

    The following example shows the formatted logs:

    {
      "trace_id":null,
      "bytes_received":0,
      "upstream_host":"172.17.X.XXX:80",
      "authority":"httpbin:8000",
      "downstream_remote_address":"172.17.X.XXX:56160",
      "upstream_service_time":"1",
      "upstream_transport_failure_reason":null,
      "istio_policy_status":null,
      "path":"/ip",
      "bytes_sent":28,
      "request_id":"4501a50a-dab0-44c9-b52c-2a4f425a****",
      "protocol":"HTTP/1.1",
      "method":"GET",
      "duration":1,
      "start_time":"2022-11-22T16:09:30.394Z",
      "user_agent":"curl/7.86.0-DEV",
      "upstream_local_address":"127.0.0.6:42169",
      "response_flags":"-",
      "route_name":"default",
      "response_code":200,
      "upstream_cluster":"inbound|80||",
      "x_forwarded_for":null,
      "downstream_local_address":"172.17.X.XXX:80",
      "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local"
    }

    The logs indicate the following information:

    • "downstream_remote_address":"172.17.X.XXX:56160": the IP address of the sleep application.

    • "downstream_local_address":"172.17.X.XXX:80": the IP address that the sleep application requests to access.

    • "upstream_local_address":"127.0.0.6:42169": the localhost IP address that the Envoy proxy for the HTTPBin application uses to connect to HTTPBin. In this case, the source IP address obtained is 127.0.0.6.

    • "upstream_host":"172.17.X.XXX:80": the IP address that the Envoy proxy for the HTTPBin application requests to access.

Enable TPROXY mode to preserve source IP addresses

Modify the deployment of the HTTPBin application to intercept inbound traffic by using TPROXY.

  1. Run the following command to modify the deployment of the HTTPBin application:

    kubectl patch deployment -n default httpbin -p '{"spec":{"template":{"metadata":{"annotations":{"sidecar.istio.io/interceptionMode":"TPROXY"}}}}}'                       
  2. Run the following command to initiate a request from the container where the sleep application resides:

    kubectl -n default exec -it deploy/sleep -c sleep -- curl http://httpbin:8000/ip

    Expected output:

    {
      "origin": "172.17.X.XXX"
    }

    The output shows that the HTTPBin application can get the real IP address of the sleep application.

  3. Run the following command to connect to the HTTPBin container and view information about the connection that uses port 80.

    Note

    After the pod is restarted, you must reinstall netstat.

    kubectl -n default exec -it deploy/httpbin -c httpbin -- netstat -ntp | grep 80

    Expected output:

    tcp        0      0 172.17.X.XXX:80         172.17.X.XXX:36728      ESTABLISHED -

    The output shows that the source IP address is 172.17.X.XXX.

  4. View proxy logs of the pod where the HTTPBin application resides.

    The following example shows the formatted logs:

    {
      "route_name":"default",
      "bytes_received":0,
      "trace_id":null,
      "request_id":"1ccabe60-63cf-469b-8565-99cac546****",
      "upstream_cluster":"inbound|80||",
      "response_flags":"-",
      "protocol":"HTTP/1.1",
      "upstream_transport_failure_reason":null,
      "requested_server_name":"outbound_.8000_._.httpbin.default.svc.cluster.local",
      "response_code":200,
      "user_agent":"curl/7.86.0-DEV",
      "start_time":"2022-11-22T16:03:32.803Z",
      "path":"/ip",
      "authority":"httpbin:8000",
      "bytes_sent":31,
      "downstream_remote_address":"172.17.X.XXX:39058",
      "upstream_service_time":"1",
      "method":"GET",
      "downstream_local_address":"172.17.X.XXX:80",
      "duration":1,
      "upstream_host":"172.17.X.XXX:80",
      "istio_policy_status":null,
      "upstream_local_address":"172.17.X.XXX:46129",
      "x_forwarded_for":null
    }

    The logs indicate the following information:

    • "downstream_remote_address":"172.17.X.XXX:39058": the IP address of the sleep application.

    • "downstream_local_address":"172.17.X.XXX:80": the IP address that the sleep application requests to access.

    • "upstream_local_address":"172.17.X.XXX:46129": the localhost IP address that the Envoy proxy for the HTTPBin application uses to connect to HTTPBin, that is, the IP address of the sleep application.

    • "upstream_host":"172.17.X.XXX:80": the IP address that the Envoy proxy for the HTTPBin application requests to access.

Scenario 2: North-south traffic

For north-south traffic, a client request is first sent to the load balancer, forwarded to the ingress gateway, and then forwarded to the backend service. Because the ingress gateway is added to the process, it becomes more complicated to obtain the source IP address of the client. The following sections describe how to configure and verify the preservation of source IP addresses for HTTP and HTTPS requests.

HTTP requests

Source IP addresses are not preserved

  1. Create a http-demo.yaml file that contains the following content to access the HTTPBin application over HTTP:

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gw-httpprotocol
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - '*'
          port:
            name: http
            number: 80
            protocol: HTTP
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin
      namespace: default
    spec:
      gateways:
        - httpbin-gw-httpprotocol
      hosts:
        - '*'
      http:
        - route:
            - destination:
                host: httpbin
                port:
                  number: 8000
  2. Run the following command to deploy the Istio gateway and virtual service:

    kubectl -n default apply -f  http-demo.yaml
  3. Run the following command to access the HTTPBin application by using the ingress gateway:

    export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    curl http://$GATEWAY_URL:80/ip

    Expected output:

    {
      "origin": "10.0.0.93"
    }

    The output shows that the returned IP address is the node address of the Kubernetes cluster.

  4. View the access logs of the ingress gateway.

    Sample logs:

    {
      "upstream_service_time":"1",
      "response_code":200,
      "protocol":"HTTP/1.1",
      "bytes_sent":28,
      "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local",
      "start_time":"2022-11-23T03:29:20.017Z",
      "istio_policy_status":null,
      "upstream_transport_failure_reason":null,
      "trace_id":null,
      "route_name":null,
      "request_id":"292903be-a889-4d5d-83a0-ab1f5d1a****",
      "method":"GET",
      "upstream_host":"172.17.X.XXX:80",
      "duration":1,
      "path":"/ip",
      "downstream_local_address":"172.17.X.XXX:80",
      "authority":"47.242.XXX.XX",
      "user_agent":"curl/7.79.1",
      "downstream_remote_address":"10.0.0.93:5899",
      "upstream_local_address":"172.17.X.XXX:54322",
      "requested_server_name":null,
      "x_forwarded_for":"10.0.0.93",
      "response_flags":"-",
      "bytes_received":0
    }

    The logs indicate the following information:

    • "downstream_remote_address":"10.0.0.93:5899": is not the source IP address of the client.

    • "downstream_local_address":"172.17.X.XXX:80": the IP address of the pod where the ingress gateway resides.

    • "upstream_local_address":"172.17.X.XXX:54322": retains the IP address of the pod where the ingress gateway resides but changes the port number.

    • "upstream_host":"172.17.X.XXX:80": the IP address of the pod where the HTTPBin application resides.

Preserve source IP addresses

  1. Configure an external traffic routing policy. If your cluster uses the Terway network plug-in, skip this step.

    1. Log on to the ASM console. In the left-side navigation pane, choose Service Mesh > Mesh Management.

    2. On the Mesh Management page, click the name of the ASM instance. In the left-side navigation pane, choose ASM Gateways > Ingress Gateway.

    3. On the Ingress Gateway page, find the ingress gateway that you want to use and click YAML.

    4. In the Edit dialog box, set the externalTrafficPolicy field in the spec section to Local and then click OK.设置外部流量策略为Local

  2. Run the following command to access the HTTPBin application by using the ingress gateway:

    curl http://$GATEWAY_URL:80/ip

    Expected output:

    {
      "origin": "120.244.xxx.xxx"
    }

    The output shows that the returned IP address is the source IP address of the client.

  3. View the access logs of the ingress gateway.

    Sample logs:

    {
      "istio_policy_status":null,
      "upstream_transport_failure_reason":null,
      "path":"/ip",
      "x_forwarded_for":"120.244.XXX.XXX",
      "route_name":null,
      "method":"GET",
      "duration":2,
      "downstream_remote_address":"120.244.XXX.XXX:28504",
      "bytes_received":0,
      "upstream_cluster":"outbound|8000||httpbin.default.svc.cluster.local",
      "bytes_sent":34,
      "protocol":"HTTP/1.1",
      "response_flags":"-",
      "upstream_local_address":"172.17.X.XXX:57498",
      "upstream_service_time":"2",
      "request_id":"9c0295d4-e77f-4a3a-b292-e5c58d92****",
      "start_time":"2022-11-23T03:24:04.413Z",
      "response_code":200,
      "trace_id":null,
      "authority":"47.242.XXX.XX",
      "user_agent":"curl/7.79.1",
      "downstream_local_address":"172.17.X.XXX:80",
      "upstream_host":"172.17.X.XXX:80",
      "requested_server_name":null
    }

    The logs indicate the following information:

    • "downstream_remote_address":"120.244.XXX.XXX:28504": the source IP address of the client, which meets expectations.

    • "downstream_local_address":"172.17.X.XXX:80": the IP address of the pod where the ingress gateway resides.

    • "upstream_local_address":"172.17.X.XXX:57498": retains the IP address of the pod where the ingress gateway resides and changes the port number.

    • "upstream_host":"172.17.X.XXX:80": the IP address of the pod where the HTTPBin application resides.

HTTPS requests

The preceding section compares the outcomes before and after the preservation of source IP addresses is configured. This section only describes how to configure and verify the preservation of source IP addresses for HTTPS requests.

  1. Configure the preservation of source IP addresses.

  2. Create an https-demo file that contains the following content to access the HTTPBin application over HTTPS:

    apiVersion: networking.istio.io/v1alpha3
    kind: Gateway
    metadata:
      name: httpbin-gw-https
      namespace: default
    spec:
      selector:
        istio: ingressgateway
      servers:
        - hosts:
            - '*'
          port:
            name: https
            number: 443
            protocol: HTTPS
          tls:
            credentialName: myexample-credential
            mode: SIMPLE
    ---
    apiVersion: networking.istio.io/v1alpha3
    kind: VirtualService
    metadata:
      name: httpbin-https
      namespace: default
    spec:
      gateways:
        - httpbin-gw-https
      hosts:
        - '*'
      http:
        - route:
            - destination:
                host: httpbin
                port:
                  number: 8000
  3. Run the following command to deploy the Istio gateway and virtual service:

    kubectl -n default apply -f  https-demo.yaml
  4. Run the following command to access the HTTPBin application by using the ingress gateway:

    export GATEWAY_URL=$(kubectl -n istio-system get service istio-ingressgateway -o jsonpath='{.status.loadBalancer.ingress[0].ip}')
    curl -k https://$GATEWAY_URL:443/ip

    Expected output:

    {
      "origin": "120.244.XXX.XXX"
    }

    The output shows that the returned IP address is the source IP address of the client.