// DynamicApisDiscovery returns a webservice serving api group discovery. // Note: during the server runtime apiGroupsForDiscovery might change. func (s *GenericAPIServer) DynamicApisDiscovery() *restful.WebService { return genericapi.NewApisWebService(s.Serializer, APIGroupPrefix, func(req *restful.Request) []metav1.APIGroup { s.apiGroupsForDiscoveryLock.RLock() defer s.apiGroupsForDiscoveryLock.RUnlock() // sort to have a deterministic order sortedGroups := []metav1.APIGroup{} groupNames := make([]string, 0, len(s.apiGroupsForDiscovery)) for groupName := range s.apiGroupsForDiscovery { groupNames = append(groupNames, groupName) } sort.Strings(groupNames) for _, groupName := range groupNames { sortedGroups = append(sortedGroups, s.apiGroupsForDiscovery[groupName]) } clientIP := utilnet.GetClientIP(req.Request) serverCIDR := s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP) groups := make([]metav1.APIGroup, len(sortedGroups)) for i := range sortedGroups { groups[i] = sortedGroups[i] groups[i].ServerAddressByClientCIDRs = serverCIDR } return groups }) }
func (s *GenericAPIServer) InstallLegacyAPIGroup(apiPrefix string, apiGroupInfo *APIGroupInfo) error { if !s.legacyAPIGroupPrefixes.Has(apiPrefix) { return fmt.Errorf("%q is not in the allowed legacy API prefixes: %v", apiPrefix, s.legacyAPIGroupPrefixes.List()) } if err := s.installAPIResources(apiPrefix, apiGroupInfo); err != nil { return err } // setup discovery apiVersions := []string{} for _, groupVersion := range apiGroupInfo.GroupMeta.GroupVersions { apiVersions = append(apiVersions, groupVersion.Version) } // Install the version handler. // Add a handler at /<apiPrefix> to enumerate the supported api versions. genericapi.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *metav1.APIVersions { clientIP := utilnet.GetClientIP(req.Request) apiVersionsForDiscovery := metav1.APIVersions{ ServerAddressByClientCIDRs: s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP), Versions: apiVersions, } return &apiVersionsForDiscovery }) return nil }
func TestDiscoveryAtAPIS(t *testing.T) { master, etcdserver, _, assert := newMaster(t) defer etcdserver.Terminate(t) server := httptest.NewServer(master.InsecureHandler) groupList, err := getGroupList(server) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(0, len(groupList.Groups)) // Add a Group. extensionsVersions := []metav1.GroupVersionForDiscovery{ { GroupVersion: testapi.Extensions.GroupVersion().String(), Version: testapi.Extensions.GroupVersion().Version, }, } extensionsPreferredVersion := metav1.GroupVersionForDiscovery{ GroupVersion: extensions.GroupName + "/preferred", Version: "preferred", } master.AddAPIGroupForDiscovery(metav1.APIGroup{ Name: extensions.GroupName, 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(extensions.GroupName, groupListGroup.Name) assert.Equal(extensionsVersions, groupListGroup.Versions) assert.Equal(extensionsPreferredVersion, groupListGroup.PreferredVersion) assert.Equal(master.discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&http.Request{})), groupListGroup.ServerAddressByClientCIDRs) // Remove the group. master.RemoveAPIGroupForDiscovery(extensions.GroupName) groupList, err = getGroupList(server) if err != nil { t.Fatalf("unexpected error: %v", err) } assert.Equal(0, len(groupList.Groups)) }
// WithAudit decorates a http.Handler with audit logging information for all the // requests coming to the server. If out is nil, no decoration takes place. // Each audit log contains two entries: // 1. the request line containing: // - unique id allowing to match the response line (see 2) // - source ip of the request // - HTTP method being invoked // - original user invoking the operation // - impersonated user for the operation // - namespace of the request or <none> // - uri is the full URI as requested // 2. the response line containing: // - the unique id from 1 // - response code func WithAudit(handler http.Handler, requestContextMapper request.RequestContextMapper, out io.Writer) http.Handler { if out == nil { return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, ok := requestContextMapper.Get(req) if !ok { responsewriters.InternalError(w, req, errors.New("no context found for request")) return } attribs, err := GetAuthorizerAttributes(ctx) if err != nil { responsewriters.InternalError(w, req, err) return } username := "******" groups := "<none>" if attribs.GetUser() != nil { username = attribs.GetUser().GetName() if userGroups := attribs.GetUser().GetGroups(); len(userGroups) > 0 { groups = auditStringSlice(userGroups) } } asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(asuser) == 0 { asuser = "******" } asgroups := "<lookup>" requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader] if len(requestedGroups) > 0 { asgroups = auditStringSlice(requestedGroups) } namespace := attribs.GetNamespace() if len(namespace) == 0 { namespace = "<none>" } id := uuid.NewRandom().String() line := fmt.Sprintf("%s AUDIT: id=%q ip=%q method=%q user=%q groups=%q as=%q asgroups=%q namespace=%q uri=%q\n", time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, username, groups, asuser, asgroups, namespace, req.URL) if _, err := fmt.Fprint(out, line); err != nil { glog.Errorf("Unable to write audit log: %s, the error is: %v", line, err) } respWriter := decorateResponseWriter(w, out, id) handler.ServeHTTP(respWriter, req) }) }
func TestGetServerAddressByClientCIDRs(t *testing.T) { publicAddressCIDRMap := []metav1.ServerAddressByClientCIDR{ { ClientCIDR: "0.0.0.0/0", ServerAddress: "ExternalAddress", }, } internalAddressCIDRMap := []metav1.ServerAddressByClientCIDR{ publicAddressCIDRMap[0], { ClientCIDR: "10.0.0.0/24", ServerAddress: "serviceIP", }, } internalIP := "10.0.0.1" publicIP := "1.1.1.1" testCases := []struct { Request http.Request ExpectedMap []metav1.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, }, } _, ipRange, _ := net.ParseCIDR("10.0.0.0/24") discoveryAddresses := DefaultDiscoveryAddresses{DefaultAddress: "ExternalAddress"} discoveryAddresses.DiscoveryCIDRRules = append(discoveryAddresses.DiscoveryCIDRRules, DiscoveryCIDRRule{IPRange: *ipRange, Address: "serviceIP"}) for i, test := range testCases { if a, e := discoveryAddresses.ServerAddressByClientCIDRs(utilnet.GetClientIP(&test.Request)), test.ExpectedMap; reflect.DeepEqual(e, a) != true { t.Fatalf("test case %d failed. expected: %v, actual: %v", i+1, e, a) } } }