Merge pull request #20626 from nikhiljindal/serverIPInDiscovery

Auto commit by PR queue bot
This commit is contained in:
k8s-merge-robot 2016-02-18 21:53:24 -08:00
commit 5d7d0eea2e
14 changed files with 438 additions and 48 deletions

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/api",
"apis": [
{

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis",
"apis": [
{

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis/extensions",
"apis": [
{

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "extensions/v1beta1",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/apis/extensions/v1beta1",
"apis": [
{

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "v1",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/api/v1",
"apis": [
{

View File

@ -1,7 +1,7 @@
{
"swaggerVersion": "1.2",
"apiVersion": "",
"basePath": "https://10.10.10.10:6443",
"basePath": "https://10.10.10.10:443",
"resourcePath": "/version",
"apis": [
{

View File

@ -308,6 +308,14 @@ type APIVersions struct {
TypeMeta `json:",inline"`
// versions are the api versions that are available.
Versions []string `json:"versions"`
// a map of client CIDR to server address that is serving this group.
// This is to help clients reach servers in the most network-efficient way possible.
// Clients can use the appropriate server address as per the CIDR that they match.
// In case of multiple matches, clients should use the longest matching CIDR.
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"`
}
// APIGroupList is a list of APIGroup, to allow clients to discover the API at
@ -329,6 +337,23 @@ type APIGroup struct {
// preferredVersion is the version preferred by the API server, which
// probably is the storage version.
PreferredVersion GroupVersionForDiscovery `json:"preferredVersion,omitempty"`
// a map of client CIDR to server address that is serving this group.
// This is to help clients reach servers in the most network-efficient way possible.
// Clients can use the appropriate server address as per the CIDR that they match.
// In case of multiple matches, clients should use the longest matching CIDR.
// The server returns only those CIDRs that it thinks that the client can match.
// For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP.
// Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.
ServerAddressByClientCIDRs []ServerAddressByClientCIDR `json:"serverAddressByClientCIDRs"`
}
// ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.
type ServerAddressByClientCIDR struct {
// The CIDR with which clients can match their IP to figure out the server address that they should use.
ClientCIDR string `json:"clientCIDR"`
// Address of this server, suitable for a client that matches the above CIDR.
// This can be a hostname, hostname:port, IP or IP:port.
ServerAddress string `json:"serverAddress"`
}
// GroupVersion contains the "group/version" and "version" string of a version.

View File

@ -28,10 +28,11 @@ package unversioned
// AUTO-GENERATED FUNCTIONS START HERE
var map_APIGroup = map[string]string{
"": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
"name": "name is the name of the group.",
"versions": "versions are the versions supported in this group.",
"preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.",
"": "APIGroup contains the name, the supported versions, and the preferred version of a group.",
"name": "name is the name of the group.",
"versions": "versions are the versions supported in this group.",
"preferredVersion": "preferredVersion is the version preferred by the API server, which probably is the storage version.",
"serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
}
func (APIGroup) SwaggerDoc() map[string]string {
@ -69,8 +70,9 @@ func (APIResourceList) SwaggerDoc() map[string]string {
}
var map_APIVersions = map[string]string{
"": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
"versions": "versions are the api versions that are available.",
"": "APIVersions lists the versions that are available, to allow clients to discover the API at /api, which is the root path of the legacy v1 API.",
"versions": "versions are the api versions that are available.",
"serverAddressByClientCIDRs": "a map of client CIDR to server address that is serving this group. This is to help clients reach servers in the most network-efficient way possible. Clients can use the appropriate server address as per the CIDR that they match. In case of multiple matches, clients should use the longest matching CIDR. The server returns only those CIDRs that it thinks that the client can match. For example: the master will return an internal IP CIDR only, if the client reaches the server using an internal IP. Server looks at X-Forwarded-For header or X-Real-Ip header or request.RemoteAddr (in that order) to get the client IP.",
}
func (APIVersions) SwaggerDoc() map[string]string {
@ -145,6 +147,16 @@ func (RootPaths) SwaggerDoc() map[string]string {
return map_RootPaths
}
var map_ServerAddressByClientCIDR = map[string]string{
"": "ServerAddressByClientCIDR helps the client to determine the server address that they should use, depending on the clientCIDR that they match.",
"clientCIDR": "The CIDR with which clients can match their IP to figure out the server address that they should use.",
"serverAddress": "Address of this server, suitable for a client that matches the above CIDR. This can be a hostname, hostname:port, IP or IP:port.",
}
func (ServerAddressByClientCIDR) SwaggerDoc() map[string]string {
return map_ServerAddressByClientCIDR
}
var map_Status = map[string]string{
"": "Status is a return value for calls that don't return other objects.",
"metadata": "Standard list metadata. More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds",

View File

@ -218,10 +218,10 @@ func serviceErrorHandler(s runtime.NegotiatedSerializer, requestResolver *Reques
}
// Adds a service to return the supported api versions at the legacy /api.
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, versions []string) {
func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) {
// TODO: InstallREST should register each version automatically
versionHandler := APIVersionHandler(s, versions[:]...)
versionHandler := APIVersionHandler(s, getAPIVersionsFunc)
ws := new(restful.WebService)
ws.Path(apiPrefix)
ws.Doc("get available API versions")
@ -234,7 +234,7 @@ func AddApiWebService(s runtime.NegotiatedSerializer, container *restful.Contain
}
// Adds a service to return the supported api versions at /apis.
func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func() []unversioned.APIGroup) {
func AddApisWebService(s runtime.NegotiatedSerializer, container *restful.Container, apiPrefix string, f func(req *restful.Request) []unversioned.APIGroup) {
rootAPIHandler := RootAPIHandler(s, f)
ws := new(restful.WebService)
ws.Path(apiPrefix)
@ -279,16 +279,16 @@ func handleVersion(req *restful.Request, resp *restful.Response) {
}
// APIVersionHandler returns a handler which will list the provided versions as available.
func APIVersionHandler(s runtime.NegotiatedSerializer, versions ...string) restful.RouteFunction {
func APIVersionHandler(s runtime.NegotiatedSerializer, getAPIVersionsFunc func(req *restful.Request) *unversioned.APIVersions) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIVersions{Versions: versions})
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, getAPIVersionsFunc(req))
}
}
// RootAPIHandler returns a handler which will list the provided groups and versions as available.
func RootAPIHandler(s runtime.NegotiatedSerializer, f func() []unversioned.APIGroup) restful.RouteFunction {
func RootAPIHandler(s runtime.NegotiatedSerializer, f func(req *restful.Request) []unversioned.APIGroup) restful.RouteFunction {
return func(req *restful.Request, resp *restful.Response) {
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f()})
writeNegotiated(s, unversioned.GroupVersion{}, resp.ResponseWriter, req.Request, http.StatusOK, &unversioned.APIGroupList{Groups: f(req)})
}
}

View File

@ -256,9 +256,12 @@ type Config struct {
// The range of IPs to be assigned to services with type=ClusterIP or greater
ServiceClusterIPRange *net.IPNet
// The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange
// The IP address for the GenericAPIServer service (must be inside ServiceClusterIPRange)
ServiceReadWriteIP net.IP
// Port for the apiserver service.
ServiceReadWritePort int
// The range of ports to be assigned to services with type=NodePort or greater
ServiceNodePortRange utilnet.PortRange
@ -308,8 +311,9 @@ type GenericAPIServer struct {
ApiGroupVersionOverrides map[string]APIGroupVersionOverride
RequestContextMapper api.RequestContextMapper
// External host is the name that should be used in external (public internet) URLs for this GenericAPIServer
externalHost string
// ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer.
ExternalAddress string
// ClusterIP is the IP address of the GenericAPIServer within the cluster.
ClusterIP net.IP
PublicReadWritePort int
@ -370,6 +374,9 @@ func setDefaults(c *Config) {
glog.V(4).Infof("Setting GenericAPIServer service IP to %q (read-write).", serviceReadWriteIP)
c.ServiceReadWriteIP = serviceReadWriteIP
}
if c.ServiceReadWritePort == 0 {
c.ServiceReadWritePort = 443
}
if c.ServiceNodePortRange.Size == 0 {
// TODO: Currently no way to specify an empty range (do we need to allow this?)
// We should probably allow this for clouds that don't require NodePort to do load-balancing (GCE)
@ -392,6 +399,13 @@ func setDefaults(c *Config) {
if c.RequestContextMapper == nil {
c.RequestContextMapper = api.NewRequestContextMapper()
}
if len(c.ExternalHost) == 0 && c.PublicAddress != nil {
hostAndPort := c.PublicAddress.String()
if c.ReadWritePort != 0 {
hostAndPort = net.JoinHostPort(hostAndPort, strconv.Itoa(c.ServiceReadWritePort))
}
c.ExternalHost = hostAndPort
}
}
// New returns a new instance of GenericAPIServer from the given config.
@ -444,13 +458,12 @@ func New(c *Config) (*GenericAPIServer, error) {
cacheTimeout: c.CacheTimeout,
MinRequestTimeout: time.Duration(c.MinRequestTimeout) * time.Second,
MasterCount: c.MasterCount,
externalHost: c.ExternalHost,
ClusterIP: c.PublicAddress,
PublicReadWritePort: c.ReadWritePort,
ServiceReadWriteIP: c.ServiceReadWriteIP,
// TODO: ServiceReadWritePort should be passed in as an argument, it may not always be 443
ServiceReadWritePort: 443,
MasterCount: c.MasterCount,
ExternalAddress: c.ExternalHost,
ClusterIP: c.PublicAddress,
PublicReadWritePort: c.ReadWritePort,
ServiceReadWriteIP: c.ServiceReadWriteIP,
ServiceReadWritePort: c.ServiceReadWritePort,
ExtraServicePorts: c.ExtraServicePorts,
ExtraEndpointPorts: c.ExtraEndpointPorts,
@ -603,7 +616,7 @@ func (s *GenericAPIServer) InstallAPIGroups(groupsInfo []APIGroupInfo) error {
// Installs handler at /apis to list all group versions for discovery
func (s *GenericAPIServer) installGroupsDiscoveryHandler() {
apiserver.AddApisWebService(s.Serializer, s.HandlerContainer, s.APIGroupPrefix, func() []unversioned.APIGroup {
apiserver.AddApisWebService(s.Serializer, s.HandlerContainer, s.APIGroupPrefix, func(req *restful.Request) []unversioned.APIGroup {
// Return the list of supported groups in sorted order (to have a deterministic order).
groups := []unversioned.APIGroup{}
groupNames := make([]string, len(s.apiGroupsForDiscovery))
@ -614,7 +627,10 @@ func (s *GenericAPIServer) installGroupsDiscoveryHandler() {
}
sort.Strings(groupNames)
for _, groupName := range groupNames {
groups = append(groups, s.apiGroupsForDiscovery[groupName])
apiGroup := s.apiGroupsForDiscovery[groupName]
// Add ServerAddressByClientCIDRs.
apiGroup.ServerAddressByClientCIDRs = s.getServerAddressByClientCIDRs(req.Request)
groups = append(groups, apiGroup)
}
return groups
})
@ -738,7 +754,13 @@ func (s *GenericAPIServer) installAPIGroup(apiGroupInfo *APIGroupInfo) error {
// Install the version handler.
if apiGroupInfo.IsLegacyGroup {
// Add a handler at /api to enumerate the supported api versions.
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, apiVersions)
apiserver.AddApiWebService(s.Serializer, s.HandlerContainer, apiPrefix, func(req *restful.Request) *unversioned.APIVersions {
apiVersionsForDiscovery := unversioned.APIVersions{
ServerAddressByClientCIDRs: s.getServerAddressByClientCIDRs(req.Request),
Versions: apiVersions,
}
return &apiVersionsForDiscovery
})
} else {
// Do not register empty group or empty version. Doing so claims /apis/ for the wrong entity to be returned.
// Catching these here places the error much closer to its origin
@ -781,6 +803,27 @@ func (s *GenericAPIServer) RemoveAPIGroupForDiscovery(groupName string) {
delete(s.apiGroupsForDiscovery, groupName)
}
func (s *GenericAPIServer) getServerAddressByClientCIDRs(req *http.Request) []unversioned.ServerAddressByClientCIDR {
addressCIDRMap := []unversioned.ServerAddressByClientCIDR{
{
ClientCIDR: "0.0.0.0/0",
ServerAddress: s.ExternalAddress,
},
}
// Add internal CIDR if the request came from internal IP.
clientIP := utilnet.GetClientIP(req)
clusterCIDR := s.ServiceClusterIPRange
if clusterCIDR.Contains(clientIP) {
addressCIDRMap = append(addressCIDRMap, unversioned.ServerAddressByClientCIDR{
ClientCIDR: clusterCIDR.String(),
ServerAddress: net.JoinHostPort(s.ServiceReadWriteIP.String(), strconv.Itoa(s.ServiceReadWritePort)),
})
}
return addressCIDRMap
}
func (s *GenericAPIServer) getAPIGroupVersion(apiGroupInfo *APIGroupInfo, groupVersion unversioned.GroupVersion, apiPrefix string) (*apiserver.APIGroupVersion, error) {
storage := make(map[string]rest.Storage)
for k, v := range apiGroupInfo.VersionedResourcesStorageMap[groupVersion.Version] {
@ -817,17 +860,8 @@ func (s *GenericAPIServer) newAPIGroupVersion(groupMeta apimachinery.GroupMeta,
// register their own web services into the Kubernetes mux prior to initialization
// of swagger, so that other resource types show up in the documentation.
func (s *GenericAPIServer) InstallSwaggerAPI() {
hostAndPort := s.externalHost
hostAndPort := s.ExternalAddress
protocol := "https://"
// TODO: this is kind of messed up, we should just pipe in the full URL from the outside, rather
// than guessing at it.
if len(s.externalHost) == 0 && s.ClusterIP != nil {
host := s.ClusterIP.String()
if s.PublicReadWritePort != 0 {
hostAndPort = net.JoinHostPort(host, strconv.Itoa(s.PublicReadWritePort))
}
}
webServicesUrl := protocol + hostAndPort
// Enable swagger UI and discovery API

View File

@ -18,14 +18,22 @@ package genericapiserver
import (
"crypto/tls"
"encoding/json"
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"reflect"
"strconv"
"testing"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/rest"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
apiutil "k8s.io/kubernetes/pkg/api/util"
"k8s.io/kubernetes/pkg/apimachinery/registered"
"k8s.io/kubernetes/pkg/apis/extensions"
"k8s.io/kubernetes/pkg/apiserver"
@ -46,20 +54,27 @@ func setUp(t *testing.T) (GenericAPIServer, *etcdtesting.EtcdTestServer, Config,
return genericapiserver, etcdServer, config, assert.New(t)
}
// TestNew verifies that the New function returns a GenericAPIServer
// using the configuration properly.
func TestNew(t *testing.T) {
func newMaster(t *testing.T) (*GenericAPIServer, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
_, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
config.Serializer = api.Codecs
config.APIPrefix = "/api"
config.APIGroupPrefix = "/apis"
s, err := New(&config)
if err != nil {
t.Fatalf("Error in bringing up the server: %v", err)
}
return s, etcdserver, config, assert
}
// TestNew verifies that the New function returns a GenericAPIServer
// using the configuration properly.
func TestNew(t *testing.T) {
s, etcdserver, config, assert := newMaster(t)
defer etcdserver.Terminate(t)
// Verify many of the variables match their config counterparts
assert.Equal(s.enableLogsSupport, config.EnableLogsSupport)
@ -75,7 +90,7 @@ func TestNew(t *testing.T) {
assert.Equal(s.ApiGroupVersionOverrides, config.APIGroupVersionOverrides)
assert.Equal(s.RequestContextMapper, config.RequestContextMapper)
assert.Equal(s.cacheTimeout, config.CacheTimeout)
assert.Equal(s.externalHost, config.ExternalHost)
assert.Equal(s.ExternalAddress, config.ExternalHost)
assert.Equal(s.ClusterIP, config.PublicAddress)
assert.Equal(s.PublicReadWritePort, config.ReadWritePort)
assert.Equal(s.ServiceReadWriteIP, config.ServiceReadWriteIP)
@ -211,7 +226,7 @@ func TestInstallSwaggerAPI(t *testing.T) {
// Empty externalHost verification
mux = http.NewServeMux()
server.HandlerContainer = NewHandlerContainer(mux, nil)
server.externalHost = ""
server.ExternalAddress = ""
server.ClusterIP = net.IPv4(10, 10, 10, 10)
server.PublicReadWritePort = 1010
server.InstallSwaggerAPI()
@ -219,3 +234,170 @@ func TestInstallSwaggerAPI(t *testing.T) {
assert.Equal("/swaggerapi/", ws[0].RootPath(), "SwaggerAPI did not install to the proper path. %s != /swaggerapi", ws[0].RootPath())
}
}
func decodeResponse(resp *http.Response, obj interface{}) error {
defer resp.Body.Close()
data, err := ioutil.ReadAll(resp.Body)
if err != nil {
return err
}
if err := json.Unmarshal(data, obj); err != nil {
return err
}
return nil
}
func getGroupList(server *httptest.Server) (*unversioned.APIGroupList, error) {
resp, err := http.Get(server.URL + "/apis")
if err != nil {
return nil, err
}
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("unexpected server response, expected %d, actual: %d", http.StatusOK, resp.StatusCode)
}
groupList := unversioned.APIGroupList{}
err = decodeResponse(resp, &groupList)
return &groupList, err
}
func TestDiscoveryAtAPIS(t *testing.T) {
master, etcdserver, config, assert := newMaster(t)
defer etcdserver.Terminate(t)
server := httptest.NewServer(master.HandlerContainer.ServeMux)
groupList, err := getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(0, len(groupList.Groups))
// Add a Group.
extensionsGroupName := extensions.GroupName
extensionsVersions := []unversioned.GroupVersionForDiscovery{
{
GroupVersion: testapi.Extensions.GroupVersion().String(),
Version: testapi.Extensions.GroupVersion().Version,
},
}
extensionsPreferredVersion := unversioned.GroupVersionForDiscovery{
GroupVersion: config.StorageVersions[extensions.GroupName],
Version: apiutil.GetVersion(config.StorageVersions[extensions.GroupName]),
}
master.AddAPIGroupForDiscovery(unversioned.APIGroup{
Name: extensionsGroupName,
Versions: extensionsVersions,
PreferredVersion: extensionsPreferredVersion,
})
groupList, err = getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(1, len(groupList.Groups))
groupListGroup := groupList.Groups[0]
assert.Equal(extensionsGroupName, groupListGroup.Name)
assert.Equal(extensionsVersions, groupListGroup.Versions)
assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion)
assert.Equal(master.getServerAddressByClientCIDRs(&http.Request{}), groupListGroup.ServerAddressByClientCIDRs)
// Remove the group.
master.RemoveAPIGroupForDiscovery(extensionsGroupName)
groupList, err = getGroupList(server)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(0, len(groupList.Groups))
}
func TestGetServerAddressByClientCIDRs(t *testing.T) {
s, etcdserver, _, _ := newMaster(t)
defer etcdserver.Terminate(t)
publicAddressCIDRMap := []unversioned.ServerAddressByClientCIDR{
{
ClientCIDR: "0.0.0.0/0",
ServerAddress: s.ExternalAddress,
},
}
internalAddressCIDRMap := []unversioned.ServerAddressByClientCIDR{
publicAddressCIDRMap[0],
{
ClientCIDR: s.ServiceClusterIPRange.String(),
ServerAddress: net.JoinHostPort(s.ServiceReadWriteIP.String(), strconv.Itoa(s.ServiceReadWritePort)),
},
}
internalIP := "10.0.0.1"
publicIP := "1.1.1.1"
testCases := []struct {
Request http.Request
ExpectedMap []unversioned.ServerAddressByClientCIDR
}{
{
Request: http.Request{},
ExpectedMap: publicAddressCIDRMap,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {internalIP},
},
},
ExpectedMap: internalAddressCIDRMap,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {publicIP},
},
},
ExpectedMap: publicAddressCIDRMap,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {internalIP},
},
},
ExpectedMap: internalAddressCIDRMap,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {publicIP},
},
},
ExpectedMap: publicAddressCIDRMap,
},
{
Request: http.Request{
RemoteAddr: internalIP,
},
ExpectedMap: internalAddressCIDRMap,
},
{
Request: http.Request{
RemoteAddr: publicIP,
},
ExpectedMap: publicAddressCIDRMap,
},
{
Request: http.Request{
RemoteAddr: "invalidIP",
},
ExpectedMap: publicAddressCIDRMap,
},
}
for i, test := range testCases {
if a, e := s.getServerAddressByClientCIDRs(&test.Request), test.ExpectedMap; reflect.DeepEqual(e, a) != true {
t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a)
}
}
}

View File

@ -304,6 +304,7 @@ func TestDiscoveryAtAPIS(t *testing.T) {
},
}
assert.Equal(2, len(groupList.Groups))
assert.Equal(expectGroupNames[0], groupList.Groups[0].Name)
assert.Equal(expectGroupNames[1], groupList.Groups[1].Name)
@ -383,6 +384,7 @@ func initThirdParty(t *testing.T, version string) (*Master, *etcdtesting.EtcdTes
},
}
master.thirdPartyStorage = etcdstorage.NewEtcdStorage(etcdserver.Client, testapi.Extensions.Codec(), etcdtest.PathPrefix(), false)
_, master.ServiceClusterIPRange, _ = net.ParseCIDR("10.0.0.0/24")
if !assert.NoError(master.InstallThirdPartyResource(api)) {
t.FailNow()

View File

@ -120,3 +120,36 @@ func GetHTTPClient(req *http.Request) string {
}
return "unknown"
}
// Extracts and returns the clients IP from the given request.
// Looks at X-Forwarded-For header, X-Real-Ip header and request.RemoteAddr in that order.
// Returns nil if none of them are set or is set to an invalid value.
func GetClientIP(req *http.Request) net.IP {
hdr := req.Header
// First check the X-Forwarded-For header for requests via proxy.
hdrForwardedFor := hdr.Get("X-Forwarded-For")
if hdrForwardedFor != "" {
// X-Forwarded-For can be a csv of IPs in case of multiple proxies.
// Use the first valid one.
parts := strings.Split(hdrForwardedFor, ",")
for _, part := range parts {
ip := net.ParseIP(strings.TrimSpace(part))
if ip != nil {
return ip
}
}
}
// Try the X-Real-Ip header.
hdrRealIp := hdr.Get("X-Real-Ip")
if hdrRealIp != "" {
ip := net.ParseIP(hdrRealIp)
if ip != nil {
return ip
}
}
// Fallback to Remote Address in request, which will give the correct client IP when there is no proxy.
ip := net.ParseIP(req.RemoteAddr)
return ip
}

102
pkg/util/net/http_test.go Normal file
View File

@ -0,0 +1,102 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package net
import (
"net"
"net/http"
"reflect"
"testing"
)
func TestGetClientIP(t *testing.T) {
ipString := "10.0.0.1"
ip := net.ParseIP(ipString)
invalidIPString := "invalidIPString"
testCases := []struct {
Request http.Request
ExpectedIP net.IP
}{
{
Request: http.Request{},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Real-Ip": {invalidIPString},
},
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString},
},
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString + "," + ipString},
},
},
ExpectedIP: ip,
},
{
Request: http.Request{
RemoteAddr: ipString,
},
ExpectedIP: ip,
},
{
Request: http.Request{
RemoteAddr: invalidIPString,
},
},
{
Request: http.Request{
Header: map[string][]string{
"X-Forwarded-For": {invalidIPString},
},
RemoteAddr: ipString,
},
ExpectedIP: ip,
},
}
for i, test := range testCases {
if a, e := GetClientIP(&test.Request), test.ExpectedIP; reflect.DeepEqual(e, a) != true {
t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a)
}
}
}