k8s.io/registry.k8s.io@v0.3.1/pkg/net/clientip/clientip.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package clientip
    18  
    19  import (
    20  	"fmt"
    21  	"net"
    22  	"net/http"
    23  	"net/netip"
    24  	"strings"
    25  )
    26  
    27  // Get gets the client IP for an http.Request
    28  //
    29  // NOTE: currently only two scenarios are supported:
    30  // 1. no loadbalancer, local testing
    31  // 2. behind Google Cloud LoadBalancer (as in cloudrun)
    32  //
    33  // Note that in particular we do not support hitting the CloudRun endpoint
    34  // directly (though we could easily do so here). Cloud Armor is on the GCLB,
    35  // so directly accessing the CloudRun endpoint would bypass that.
    36  //
    37  // At this time we have no need to complicate it further.
    38  func Get(r *http.Request) (netip.Addr, error) {
    39  	// Upstream docs:
    40  	// https://cloud.google.com/load-balancing/docs/https#x-forwarded-for_header
    41  	//
    42  	// If there is no X-Forwarded-For header on the incoming request,
    43  	// these two IP addresses are the entire header value:
    44  	// X-Forwarded-For: <client-ip>,<load-balancer-ip>
    45  	//
    46  	// If the request includes an X-Forwarded-For header, the load balancer
    47  	// preserves the supplied value before the <client-ip>,<load-balancer-ip>:
    48  	// X-Forwarded-For: [<supplied-value>,]<client-ip>,<load-balancer-ip>
    49  	//
    50  	// Caution: The load balancer does not verify any IP addresses that
    51  	// precede <client-ip>,<load-balancer-ip> in this header.
    52  	// The preceding IP addresses might contain other characters, including spaces.
    53  	rawXFwdFor := r.Header.Get("X-Forwarded-For")
    54  	// clearly we are not in cloud if this header is not set, we can use
    55  	// r.RemoteAddr in that case to support local testing
    56  	// Go http server will always set this value for us
    57  	if rawXFwdFor == "" {
    58  		host, _, err := net.SplitHostPort(r.RemoteAddr)
    59  		if err != nil {
    60  			return netip.Addr{}, err
    61  		}
    62  		return netip.ParseAddr(host)
    63  	}
    64  	// assume we are in cloud run, get <client-ip> from load balancer header
    65  	// local tests with direct connection to the server can also fake this
    66  	// header which is fine + useful
    67  	//
    68  	// we want the contents between the second to last comma and the last comma
    69  	// or if only one comma between the start of the string and the last comma
    70  	keys := strings.FieldsFunc(rawXFwdFor, func(r rune) bool {
    71  		return r == ',' || r == ' '
    72  	})
    73  	// there should be at least two values: <client-ip>,<load-balancer-ip>
    74  	if len(keys) < 2 {
    75  		return netip.Addr{}, fmt.Errorf("invalid X-Forwarded-For value: %s", rawXFwdFor)
    76  	}
    77  	// normal case, we expect the client-ip to be 2 from the end
    78  	return netip.ParseAddr(keys[len(keys)-2])
    79  }