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 }