Skip to content
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

Make impersonation play nice with subdomains. #5004

Merged
merged 1 commit into from
Oct 12, 2023
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
5 changes: 1 addition & 4 deletions app/auth/auth_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,6 @@ export class AuthService {
}

private getUser(request: user.GetUserRequest) {
if (rpcService.requestContext.impersonatingGroupId) {
return rpcService.service.getImpersonatedUser(request);
}
return rpcService.service.getUser(request);
}

Expand Down Expand Up @@ -178,7 +175,7 @@ export class AuthService {
(name) => (name[0].toLowerCase() + name.substring(1)) as BuildBuddyServiceRpcName
)
),
isImpersonating: Boolean(rpcService.requestContext.impersonatingGroupId),
isImpersonating: response.isImpersonating,
subdomainGroupID: response.subdomainGroupId,
});
}
Expand Down
3 changes: 3 additions & 0 deletions enterprise/server/backends/userdb/userdb.go
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,9 @@ func (d *UserDB) GetUser(ctx context.Context) (*tables.User, error) {
if err != nil {
return nil, err
}
if u.IsImpersonating() {
return d.GetImpersonatedUser(ctx)
}
var user *tables.User
err = d.h.Transaction(ctx, func(tx *db.DB) error {
user, err = d.getUser(tx, u.GetUserID())
Expand Down
1 change: 0 additions & 1 deletion proto/buildbuddy_service.proto
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ service BuildBuddyService {
// User API
rpc CreateUser(user.CreateUserRequest) returns (user.CreateUserResponse);
rpc GetUser(user.GetUserRequest) returns (user.GetUserResponse);
rpc GetImpersonatedUser(user.GetUserRequest) returns (user.GetUserResponse);

// Groups API
rpc GetGroup(grp.GetGroupRequest) returns (grp.GetGroupResponse);
Expand Down
3 changes: 3 additions & 0 deletions proto/user.proto
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,9 @@ message GetUserResponse {
// group ID that owns the subdomain, but only if the user is allowed to
// impersonate.
string subdomain_group_id = 8;

// True if the user is using impersonation to access a group.
bool is_impersonating = 10;
}

message CreateUserRequest {
Expand Down
34 changes: 15 additions & 19 deletions server/buildbuddy_server/buildbuddy_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -274,22 +274,6 @@ func makeGroups(groupRoles []*tables.GroupRole) []*grpb.Group {
return r
}

func (s *BuildBuddyServer) GetUser(ctx context.Context, req *uspb.GetUserRequest) (*uspb.GetUserResponse, error) {
userDB := s.env.GetUserDB()
if userDB == nil {
return nil, status.UnimplementedError("Not Implemented")
}
return s.getUser(ctx, req, userDB.GetUser)
}

func (s *BuildBuddyServer) GetImpersonatedUser(ctx context.Context, req *uspb.GetUserRequest) (*uspb.GetUserResponse, error) {
userDB := s.env.GetUserDB()
if userDB == nil {
return nil, status.UnimplementedError("Not Implemented")
}
return s.getUser(ctx, req, userDB.GetImpersonatedUser)
}

func (s *BuildBuddyServer) getGroupIDForSubdomain(ctx context.Context) (string, error) {
sd := subdomain.Get(ctx)
if sd == "" {
Expand All @@ -308,10 +292,21 @@ func (s *BuildBuddyServer) getGroupIDForSubdomain(ctx context.Context) (string,
return g.GroupID, nil
}

type userLookup func(ctx context.Context) (*tables.User, error)
func (s *BuildBuddyServer) GetUser(ctx context.Context, req *uspb.GetUserRequest) (*uspb.GetUserResponse, error) {
userDB := s.env.GetUserDB()
if userDB == nil {
return nil, status.UnimplementedError("Not Implemented")
}

func (s *BuildBuddyServer) getUser(ctx context.Context, req *uspb.GetUserRequest, dbLookup userLookup) (*uspb.GetUserResponse, error) {
tu, err := dbLookup(ctx)
auth := s.env.GetAuthenticator()
if auth == nil {
return nil, status.InternalError("No auth configured on this BuildBuddy instance")
}
u, err := s.env.GetAuthenticator().AuthenticatedUser(ctx)
if err != nil {
return nil, err
}
tu, err := s.env.GetUserDB().GetUser(ctx)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -370,6 +365,7 @@ func (s *BuildBuddyServer) getUser(ctx context.Context, req *uspb.GetUserRequest
AllowedRpc: allowedRPCs,
GithubLinked: tu.GithubToken != "",
SubdomainGroupId: subdomainGroupID,
IsImpersonating: u.IsImpersonating(),
}, nil
}

Expand Down
1 change: 0 additions & 1 deletion server/role_filter/role_filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ var (
roleIndependentRPCs = []string{
// RPCs that happen pre-login and don't require group membership.
"GetUser",
"GetImpersonatedUser",
"CreateUser",
"GetGroup",
// Invocations can be shared publicly, so authorization for these RPCs is
Expand Down
1 change: 1 addition & 0 deletions server/util/claims/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ go_library(
"//server/util/request_context",
"//server/util/role",
"//server/util/status",
"//server/util/subdomain",
"@com_github_golang_jwt_jwt//:jwt",
],
)
15 changes: 14 additions & 1 deletion server/util/claims/claims.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/buildbuddy-io/buildbuddy/server/util/lru"
"github.com/buildbuddy-io/buildbuddy/server/util/role"
"github.com/buildbuddy-io/buildbuddy/server/util/status"
"github.com/buildbuddy-io/buildbuddy/server/util/subdomain"
"github.com/golang-jwt/jwt"

akpb "github.com/buildbuddy-io/buildbuddy/proto/api_key"
Expand Down Expand Up @@ -161,8 +162,20 @@ func ClaimsFromSubID(ctx context.Context, env environment.Env, subID string) (*C
if membership.GroupID != env.GetAuthenticator().AdminGroupID() || membership.Role != role.Admin {
continue
}

ig, err := env.GetUserDB().GetGroupByID(ctx, c.GetImpersonatingGroupId())
if err != nil {
return nil, err
}

// If the user requested impersonation but the subdomain doesn't
// match the impersonation target then don't enable impersonation.
if sd := subdomain.Get(ctx); sd != "" && ig.URLIdentifier != nil && sd != *ig.URLIdentifier {
return claims, nil
}

u.Groups = []*tables.GroupRole{{
Group: tables.Group{GroupID: c.GetImpersonatingGroupId()},
Group: *ig,
Role: uint32(role.Admin),
}}
claims := userClaims(u, c.GetImpersonatingGroupId())
Expand Down