Skip to content

add impersonate-group #9062

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 31, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pkg/auth/api/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const (
IdentityPreferredUsernameKey = "preferred_username"

ImpersonateUserHeader = "Impersonate-User"
ImpersonateGroupHeader = "Impersonate-Group"
ImpersonateUserScopeHeader = "Impersonate-User-Scope"
)

Expand Down
18 changes: 16 additions & 2 deletions pkg/cmd/server/origin/audit.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package origin

import (
"fmt"
"net/http"

"github.com/golang/glog"
Expand Down Expand Up @@ -45,14 +46,27 @@ func (c *MasterConfig) auditHandler(handler http.Handler) http.Handler {
if len(asuser) == 0 {
asuser = "<self>"
}
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 namespace=%q uri=%q",
id, net.GetClientIP(req), req.Method, user.GetName(), asuser, namespace, req.URL)
glog.Infof("AUDIT: id=%q ip=%q method=%q user=%q as=%q asgroups=%q namespace=%q uri=%q",
id, net.GetClientIP(req), req.Method, user.GetName(), asuser, asgroups, namespace, req.URL)
handler.ServeHTTP(&auditResponseWriter{w, id}, req)
})
}
180 changes: 87 additions & 93 deletions pkg/cmd/server/origin/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,125 +302,119 @@ func assetServerRedirect(handler http.Handler, assetPublicURL string) http.Handl

func (c *MasterConfig) impersonationFilter(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
requestedSubject := req.Header.Get(authenticationapi.ImpersonateUserHeader)
if len(requestedSubject) == 0 {
requestedUser := req.Header.Get(authenticationapi.ImpersonateUserHeader)
if len(requestedUser) == 0 {
handler.ServeHTTP(w, req)
return
}

resource, namespace, name, err := parseRequestedSubject(requestedSubject)
if err != nil {
forbidden(err.Error(), nil, w, req)
return
}
subjects := authorizationapi.BuildSubjects([]string{requestedUser}, req.Header[authenticationapi.ImpersonateGroupHeader],
// validates whether the usernames are regular users or system users
uservalidation.ValidateUserName,
// validates group names are regular groups or system groups
uservalidation.ValidateGroupName)

ctx, exists := c.RequestContextMapper.Get(req)
if !exists {
forbidden("context not found", nil, w, req)
return
}

actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{
Verb: "impersonate",
APIGroup: resource.Group,
Resource: resource.Resource,
ResourceName: name,
}
authCheckCtx := kapi.WithNamespace(ctx, namespace)

allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes)
if err != nil {
forbidden(err.Error(), actingAsAttributes, w, req)
return
}
if !allowed {
forbidden(reason, actingAsAttributes, w, req)
return
}
// if groups are not specified, then we need to look them up differently depending on the type of user
// if they are specified, then they are the authority
groupsSpecified := len(req.Header[authenticationapi.ImpersonateGroupHeader]) > 0

// make sure we're allowed to impersonate each subject. While we're iterating through, start building username
// and group information
username := ""
groups := []string{}
for _, subject := range subjects {
actingAsAttributes := &authorizer.DefaultAuthorizationAttributes{
Verb: "impersonate",
}

var extra map[string][]string
if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok {
extra = map[string][]string{authorizationapi.ScopesKey: requestScopes}
}
switch subject.GetObjectKind().GroupVersionKind().GroupKind() {
case userapi.Kind(authorizationapi.GroupKind):
actingAsAttributes.APIGroup = userapi.GroupName
actingAsAttributes.Resource = authorizationapi.GroupResource
actingAsAttributes.ResourceName = subject.Name
groups = append(groups, subject.Name)

case userapi.Kind(authorizationapi.SystemGroupKind):
actingAsAttributes.APIGroup = userapi.GroupName
actingAsAttributes.Resource = authorizationapi.SystemGroupResource
actingAsAttributes.ResourceName = subject.Name
groups = append(groups, subject.Name)

case userapi.Kind(authorizationapi.UserKind):
actingAsAttributes.APIGroup = userapi.GroupName
actingAsAttributes.Resource = authorizationapi.UserResource
actingAsAttributes.ResourceName = subject.Name
username = subject.Name
if !groupsSpecified {
if actualGroups, err := c.GroupCache.GroupsFor(subject.Name); err == nil {
for _, group := range actualGroups {
groups = append(groups, group.Name)
}
}
groups = append(groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup)
}

switch resource {
case kapi.Resource(authorizationapi.ServiceAccountResource):
newUser := &user.DefaultInfo{
Name: serviceaccount.MakeUsername(namespace, name),
Groups: serviceaccount.MakeGroupNames(namespace, name),
Extra: extra,
}
newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup)
c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser))
case userapi.Kind(authorizationapi.SystemUserKind):
actingAsAttributes.APIGroup = userapi.GroupName
actingAsAttributes.Resource = authorizationapi.SystemUserResource
actingAsAttributes.ResourceName = subject.Name
username = subject.Name
if !groupsSpecified {
if subject.Name == bootstrappolicy.UnauthenticatedUsername {
groups = append(groups, bootstrappolicy.UnauthenticatedGroup)
} else {
groups = append(groups, bootstrappolicy.AuthenticatedGroup)
}
}

case userapi.Resource(authorizationapi.UserResource):
newUser := &user.DefaultInfo{
Name: name,
Extra: extra,
}
groups, err := c.GroupCache.GroupsFor(name)
if err == nil {
for _, group := range groups {
newUser.Groups = append(newUser.Groups, group.Name)
case kapi.Kind(authorizationapi.ServiceAccountKind):
actingAsAttributes.APIGroup = kapi.GroupName
actingAsAttributes.Resource = authorizationapi.ServiceAccountResource
actingAsAttributes.ResourceName = subject.Name
username = serviceaccount.MakeUsername(subject.Namespace, subject.Name)
if !groupsSpecified {
groups = append(serviceaccount.MakeGroupNames(subject.Namespace, subject.Name), bootstrappolicy.AuthenticatedGroup)
}

default:
forbidden(fmt.Sprintf("unknown subject type: %v", subject), actingAsAttributes, w, req)
return
}

newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup, bootstrappolicy.AuthenticatedOAuthGroup)
c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser))
authCheckCtx := kapi.WithNamespace(ctx, subject.Namespace)

case userapi.Resource(authorizationapi.SystemUserResource):
newUser := &user.DefaultInfo{
Name: name,
Extra: extra,
allowed, reason, err := c.Authorizer.Authorize(authCheckCtx, actingAsAttributes)
if err != nil {
forbidden(err.Error(), actingAsAttributes, w, req)
return
}

if name == bootstrappolicy.UnauthenticatedUsername {
newUser.Groups = append(newUser.Groups, bootstrappolicy.UnauthenticatedGroup)
} else {
newUser.Groups = append(newUser.Groups, bootstrappolicy.AuthenticatedGroup)
if !allowed {
forbidden(reason, actingAsAttributes, w, req)
return
}
c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser))
}

default:
forbidden(fmt.Sprintf("%v is an unhandled resource for acting-as", resource), nil, w, req)
return
var extra map[string][]string
if requestScopes, ok := req.Header[authenticationapi.ImpersonateUserScopeHeader]; ok {
extra = map[string][]string{authorizationapi.ScopesKey: requestScopes}
}

newUser := &user.DefaultInfo{
Name: username,
Groups: groups,
Extra: extra,
}
c.RequestContextMapper.Update(req, kapi.WithUser(ctx, newUser))

newCtx, _ := c.RequestContextMapper.Get(req)
oldUser, _ := kapi.UserFrom(ctx)
newUser, _ := kapi.UserFrom(newCtx)
httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser)

handler.ServeHTTP(w, req)
})
}

func parseRequestedSubject(requestedSubject string) (unversioned.GroupResource, string, string, error) {
subjects := authorizationapi.BuildSubjects([]string{requestedSubject}, nil,
// validates whether the usernames are regular users or system users
uservalidation.ValidateUserName,
// validates group names, but we never pass any groups
func(s string, b bool) (bool, string) { return true, "" })

if len(subjects) == 0 {
return unversioned.GroupResource{}, "", "", fmt.Errorf("subject must be in the form of a username, not %v", requestedSubject)

}

resource := unversioned.GroupResource{}
switch subjects[0].GetObjectKind().GroupVersionKind().GroupKind() {
case userapi.Kind(authorizationapi.UserKind):
resource = userapi.Resource(authorizationapi.UserResource)

case userapi.Kind(authorizationapi.SystemUserKind):
resource = userapi.Resource(authorizationapi.SystemUserResource)

case kapi.Kind(authorizationapi.ServiceAccountKind):
resource = kapi.Resource(authorizationapi.ServiceAccountResource)

default:
return unversioned.GroupResource{}, "", "", fmt.Errorf("unknown subject type: %v", subjects[0])
}

return resource, subjects[0].Namespace, subjects[0].Name, nil
}
87 changes: 84 additions & 3 deletions pkg/cmd/server/origin/handlers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,22 @@ func (impersonateAuthorizer) Authorize(ctx kapi.Context, a authorizer.Authorizat
return false, "denied", nil
}

if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "systemusers" {
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "wheel" && a.GetVerb() == "impersonate" && a.GetResource() == "systemusers" {
return true, "", nil
}

if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "sa-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "serviceaccounts" {
return true, "", nil
}

if len(user.GetGroups()) == 1 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
if len(user.GetGroups()) > 0 && user.GetGroups()[0] == "regular-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "users" {
return true, "", nil
}

if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "groups" {
return true, "", nil
}
if len(user.GetGroups()) > 1 && user.GetGroups()[1] == "system-group-impersonater" && a.GetVerb() == "impersonate" && a.GetResource() == "systemgroups" {
return true, "", nil
}

Expand Down Expand Up @@ -87,6 +94,7 @@ func TestImpersonationFilter(t *testing.T) {
name string
user user.Info
impersonationString string
impersonationGroups []string
expectedUser user.Info
expectedCode int
}{
Expand All @@ -111,6 +119,76 @@ func TestImpersonationFilter(t *testing.T) {
},
expectedCode: http.StatusForbidden,
},
{
name: "disallowed-group",
user: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel"},
},
impersonationString: "system:admin",
impersonationGroups: []string{"some-group"},
expectedUser: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel"},
},
expectedCode: http.StatusForbidden,
},
{
name: "disallowed-system-group",
user: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "group-impersonater"},
},
impersonationString: "system:admin",
impersonationGroups: []string{"some-group", "system:group"},
expectedUser: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "group-impersonater"},
},
expectedCode: http.StatusForbidden,
},
{
name: "disallowed-group-2",
user: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "system-group-impersonater"},
},
impersonationString: "system:admin",
impersonationGroups: []string{"some-group", "system:group"},
expectedUser: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "system-group-impersonater"},
},
expectedCode: http.StatusForbidden,
},
{
name: "allowed-group",
user: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "group-impersonater"},
},
impersonationString: "system:admin",
impersonationGroups: []string{"some-group"},
expectedUser: &user.DefaultInfo{
Name: "system:admin",
Groups: []string{"some-group"},
},
expectedCode: http.StatusOK,
},
{
name: "allowed-system-group",
user: &user.DefaultInfo{
Name: "dev",
Groups: []string{"wheel", "system-group-impersonater"},
},
impersonationString: "system:admin",
impersonationGroups: []string{"some-system:group"},
expectedUser: &user.DefaultInfo{
Name: "system:admin",
Groups: []string{"some-system:group"},
},
expectedCode: http.StatusOK,
},
{
name: "allowed-systemusers-impersonation",
user: &user.DefaultInfo{
Expand Down Expand Up @@ -224,6 +302,9 @@ func TestImpersonationFilter(t *testing.T) {
continue
}
req.Header.Add(authenticationapi.ImpersonateUserHeader, tc.impersonationString)
for _, group := range tc.impersonationGroups {
req.Header.Add(authenticationapi.ImpersonateGroupHeader, group)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
t.Errorf("%s: unexpected error: %v", tc.name, err)
Expand Down