// 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, attributeGetter RequestAttributeGetter, out io.Writer) http.Handler { if out == nil { return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { attribs, err := attributeGetter.GetAttribs(req) if err != nil { internalError(w, req, err) return } asuser := req.Header.Get("Impersonate-User") if len(asuser) == 0 { asuser = "******" } 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 as=%q namespace=%q uri=%q\n", time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, attribs.GetUser().GetName(), asuser, 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) }) }
// DynamicApisDiscovery returns a webservice serving api group discovery. // Note: during the server runtime apiGroupsForDiscovery might change. func (s *GenericAPIServer) DynamicApisDiscovery() *restful.WebService { return apiserver.NewApisWebService(s.Serializer, APIGroupPrefix, func(req *restful.Request) []unversioned.APIGroup { s.apiGroupsForDiscoveryLock.RLock() defer s.apiGroupsForDiscoveryLock.RUnlock() // sort to have a deterministic order sortedGroups := []unversioned.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([]unversioned.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. apiserver.AddApiWebService(s.Serializer, s.HandlerContainer.Container, apiPrefix, func(req *restful.Request) *unversioned.APIVersions { clientIP := utilnet.GetClientIP(req.Request) apiVersionsForDiscovery := unversioned.APIVersions{ ServerAddressByClientCIDRs: s.discoveryAddresses.ServerAddressByClientCIDRs(clientIP), Versions: apiVersions, } return &apiVersionsForDiscovery }) return nil }
// WithAudit decorates a http.Handler with audit logging information for all the // requests coming to the server. 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, attributeGetter apiserver.RequestAttributeGetter, out io.Writer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { attribs := attributeGetter.GetAttribs(req) asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(asuser) == 0 { asuser = "******" } asgroups := "<lookup>" requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader] if len(requestedGroups) > 0 { quotedGroups := make([]string, len(requestedGroups)) for i, group := range requestedGroups { quotedGroups[i] = fmt.Sprintf("%q", group) } asgroups = strings.Join(quotedGroups, ", ") } 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 as=%q asgroups=%q namespace=%q uri=%q\n", time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, attribs.GetUser().GetName(), 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 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 := []unversioned.GroupVersionForDiscovery{ { GroupVersion: testapi.Extensions.GroupVersion().String(), Version: testapi.Extensions.GroupVersion().Version, }, } extensionsPreferredVersion := unversioned.GroupVersionForDiscovery{ GroupVersion: extensions.GroupName + "/preferred", Version: "preferred", } master.AddAPIGroupForDiscovery(unversioned.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 api.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 { internalError(w, req, errors.New("no context found for request")) return } attribs, err := GetAuthorizerAttributes(ctx) if err != nil { 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 (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 }
// WithAudit decorates a http.Handler with audit logging information for all the // requests coming to the server. 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 api.RequestContextMapper, out io.Writer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, _ := requestContextMapper.Get(req) user, _ := api.UserFrom(ctx) asuser := req.Header.Get("Impersonate-User") if len(asuser) == 0 { asuser = "******" } namespace := api.NamespaceValue(ctx) if len(namespace) == 0 { namespace = "<none>" } id := uuid.NewRandom().String() fmt.Fprintf(out, "%s AUDIT: id=%q ip=%q method=%q user=%q as=%q namespace=%q uri=%q\n", time.Now().Format(time.RFC3339Nano), id, utilnet.GetClientIP(req), req.Method, user.GetName(), asuser, namespace, req.URL) respWriter := decorateResponseWriter(w, out, id) handler.ServeHTTP(respWriter, req) }) }
// auditHandler is responsible for logging audit information for all the // request coming to server. 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 and response code func (c *MasterConfig) auditHandler(handler http.Handler) http.Handler { if !c.Options.AuditConfig.Enabled { return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, _ := c.RequestContextMapper.Get(req) user, _ := kapi.UserFrom(ctx) asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(asuser) == 0 { asuser = "******" } namespace := kapi.NamespaceValue(ctx) if len(namespace) == 0 { namespace = "<none>" } id := uuid.NewRandom().String() glog.Infof("AUDIT: id=%q ip=%q method=%q user=%q as=%q namespace=%q uri=%q", id, net.GetClientIP(req), req.Method, user.GetName(), asuser, namespace, req.URL) handler.ServeHTTP(&auditResponseWriter{w, id}, req) }) }
// auditHandler is responsible for logging audit information for all the // request coming to server. 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 and response code func (c *MasterConfig) auditHandler(handler http.Handler) http.Handler { if !c.Options.AuditConfig.Enabled { return handler } return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { ctx, _ := c.RequestContextMapper.Get(req) user, _ := kapi.UserFrom(ctx) asuser := req.Header.Get(authenticationapi.ImpersonateUserHeader) if len(asuser) == 0 { asuser = "******" } requestedGroups := req.Header[authenticationapi.ImpersonateGroupHeader] asgroups := "<lookup>" if len(requestedGroups) == 0 { asgroups = "" first := true for _, group := range requestedGroups { if !first { asgroups = asgroups + "," } asgroups = asgroups + fmt.Sprintf("%q", group) first = false } } namespace := kapi.NamespaceValue(ctx) if len(namespace) == 0 { namespace = "<none>" } id := uuid.NewRandom().String() glog.Infof("AUDIT: id=%q ip=%q method=%q user=%q as=%q asgroups=%q namespace=%q uri=%q", id, utilnet.GetClientIP(req), req.Method, user.GetName(), asuser, asgroups, namespace, req.URL) handler.ServeHTTP(constructResponseWriter(w, id), req) }) }
func TestGetServerAddressByClientCIDRs(t *testing.T) { publicAddressCIDRMap := []unversioned.ServerAddressByClientCIDR{ { ClientCIDR: "0.0.0.0/0", ServerAddress: "ExternalAddress", }, } internalAddressCIDRMap := []unversioned.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 []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, }, } _, 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) } } }