// NewRequestAuthenticator creates an http handler that tries to authenticate the given request as a user, and then // stores any such user found onto the provided context for the request. If authentication fails or returns an error // the failed handler is used. On success, handler is invoked to serve the request. func NewRequestAuthenticator(mapper api.RequestContextMapper, auth authenticator.Request, failed http.Handler, handler http.Handler) (http.Handler, error) { return api.NewRequestContextFilter( mapper, http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { user, ok, err := auth.AuthenticateRequest(req) if err != nil || !ok { if err != nil { glog.Errorf("Unable to authenticate the request due to an error: %v", err) } failed.ServeHTTP(w, req) return } if ctx, ok := mapper.Get(req); ok { mapper.Update(req, api.WithUser(ctx, user)) } authenticatedUserCounter.WithLabelValues(compressUsername(user.GetName())).Inc() handler.ServeHTTP(w, req) }), ) }
func TestAppliesTo(t *testing.T) { tests := []struct { subjects []rbac.Subject ctx api.Context appliesTo bool testCase string }{ { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), appliesTo: true, testCase: "single subject that matches username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), appliesTo: true, testCase: "multiple subjects, one that matches username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.UserKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam"}), appliesTo: false, testCase: "multiple subjects, none that match username", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, }, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}}), appliesTo: true, testCase: "multiple subjects, one that match group", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "zimzam", Groups: []string{"foobar"}}), "namespace1", ), appliesTo: true, testCase: "multiple subjects, one that match group, should ignore namespace", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "barfoo"}, {Kind: rbac.GroupKind, Name: "foobar"}, {Kind: rbac.ServiceAccountKind, Name: "kube-system", Namespace: "default"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "system:serviceaccount:kube-system:default"}), "default", ), appliesTo: true, testCase: "multiple subjects with a service account that matches", }, { subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "*"}, }, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "default", ), appliesTo: true, testCase: "multiple subjects with a service account that matches", }, } for _, tc := range tests { got, err := appliesTo(tc.ctx, tc.subjects) if err != nil { t.Errorf("case %q %v", tc.testCase, err) continue } if got != tc.appliesTo { t.Errorf("case %q want appliesTo=%t, got appliesTo=%t", tc.testCase, tc.appliesTo, got) } } }
func TestDefaultRuleResolver(t *testing.T) { ruleReadPods := rbac.PolicyRule{ Verbs: []string{"GET", "WATCH"}, APIGroups: []string{"v1"}, Resources: []string{"pods"}, } ruleReadServices := rbac.PolicyRule{ Verbs: []string{"GET", "WATCH"}, APIGroups: []string{"v1"}, Resources: []string{"services"}, } ruleWriteNodes := rbac.PolicyRule{ Verbs: []string{"PUT", "CREATE", "UPDATE"}, APIGroups: []string{"v1"}, Resources: []string{"nodes"}, } ruleAdmin := rbac.PolicyRule{ Verbs: []string{"*"}, APIGroups: []string{"*"}, Resources: []string{"*"}, } staticRoles1 := staticRoles{ roles: []rbac.Role{ { ObjectMeta: api.ObjectMeta{Namespace: "namespace1", Name: "readthings"}, Rules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, }, clusterRoles: []rbac.ClusterRole{ { ObjectMeta: api.ObjectMeta{Name: "cluster-admin"}, Rules: []rbac.PolicyRule{ruleAdmin}, }, { ObjectMeta: api.ObjectMeta{Name: "write-nodes"}, Rules: []rbac.PolicyRule{ruleWriteNodes}, }, }, roleBindings: []rbac.RoleBinding{ { ObjectMeta: api.ObjectMeta{Namespace: "namespace1"}, Subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "foobar"}, {Kind: rbac.GroupKind, Name: "group1"}, }, RoleRef: api.ObjectReference{Kind: "Role", Namespace: "namespace1", Name: "readthings"}, }, }, clusterRoleBindings: []rbac.ClusterRoleBinding{ { Subjects: []rbac.Subject{ {Kind: rbac.UserKind, Name: "admin"}, {Kind: rbac.GroupKind, Name: "admin"}, }, RoleRef: api.ObjectReference{Kind: "ClusterRole", Name: "cluster-admin"}, }, }, } tests := []struct { staticRoles // For a given context, what are the rules that apply? ctx api.Context effectiveRules []rbac.PolicyRule }{ { staticRoles: staticRoles1, ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "namespace1", ), effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, { staticRoles: staticRoles1, ctx: api.WithNamespace( // Same as above but diffrerent namespace. Should return no rules. api.WithUser(api.NewContext(), &user.DefaultInfo{Name: "foobar"}), "namespace2", ), effectiveRules: []rbac.PolicyRule{}, }, { staticRoles: staticRoles1, // GetEffectivePolicyRules only returns the policies for the namespace, not the master namespace. ctx: api.WithNamespace( api.WithUser(api.NewContext(), &user.DefaultInfo{ Name: "foobar", Groups: []string{"admin"}, }), "namespace1", ), effectiveRules: []rbac.PolicyRule{ruleReadPods, ruleReadServices}, }, { staticRoles: staticRoles1, // Same as above but without a namespace. Only cluster rules should apply. ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{ Name: "foobar", Groups: []string{"admin"}, }), effectiveRules: []rbac.PolicyRule{ruleAdmin}, }, { staticRoles: staticRoles1, ctx: api.WithUser(api.NewContext(), &user.DefaultInfo{}), effectiveRules: []rbac.PolicyRule{}, }, } for i, tc := range tests { ruleResolver := newMockRuleResolver(&tc.staticRoles) rules, err := ruleResolver.GetEffectivePolicyRules(tc.ctx) if err != nil { t.Errorf("case %d: GetEffectivePolicyRules(context)=%v", i, err) continue } // Sort for deep equals sort.Sort(byHash(rules)) sort.Sort(byHash(tc.effectiveRules)) if !reflect.DeepEqual(rules, tc.effectiveRules) { ruleDiff := diff.ObjectDiff(rules, tc.effectiveRules) t.Errorf("case %d: %s", i, ruleDiff) } } }
func WithImpersonation(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get("Impersonate-User") if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { forbidden(w, req) return } requestor, exists := api.UserFrom(ctx) if !exists { forbidden(w, req) return } actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: api.GroupName, Resource: "users", Name: requestedSubject, ResourceRequest: true, } if namespace, name, err := serviceaccount.SplitUsername(requestedSubject); err == nil { actingAsAttributes.Resource = "serviceaccounts" actingAsAttributes.Namespace = namespace actingAsAttributes.Name = name } err := a.Authorize(actingAsAttributes) if err != nil { forbidden(w, req) return } switch { case strings.HasPrefix(requestedSubject, serviceaccount.ServiceAccountUsernamePrefix): namespace, name, err := serviceaccount.SplitUsername(requestedSubject) if err != nil { forbidden(w, req) return } requestContextMapper.Update(req, api.WithUser(ctx, serviceaccount.UserInfo(namespace, name, ""))) default: newUser := &user.DefaultInfo{ Name: requestedSubject, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) } newCtx, _ := requestContextMapper.Get(req) oldUser, _ := api.UserFrom(ctx) newUser, _ := api.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }