Skip to content

Commit

Permalink
[CLD-3125] remove explicit scope (#361)
Browse files Browse the repository at this point in the history
  • Loading branch information
shakeelrao authored Jul 4, 2024
1 parent 72cd259 commit 01242f2
Show file tree
Hide file tree
Showing 5 changed files with 649 additions and 599 deletions.
3 changes: 3 additions & 0 deletions app/role.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package app
import (
"context"
"fmt"
"slices"
"strings"

"github.com/temporalio/tcld/protogen/api/auth/v1"
Expand All @@ -21,6 +22,7 @@ func getAccountActionGroups() []string {
rv = append(rv, n)
}
}
slices.Sort(rv)
return rv
}

Expand All @@ -31,6 +33,7 @@ func getNamespaceActionGroups() []string {
rv = append(rv, n)
}
}
slices.Sort(rv)
return rv
}

Expand Down
126 changes: 64 additions & 62 deletions app/serviceaccount.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,8 @@ const (
serviceAccountIDFlagName = "service-account-id"
serviceAccountNameFlagName = "name"
serviceAccountDescriptionFlagName = "description"
serviceAccountScopeTypeFlagName = "scope-type"
serviceAccountScopeIDFlagName = "scope-id"
)

var (
scopeTypes = map[string]auth.ServiceAccountScopeType{
"namespace": auth.SERVICE_ACCOUNT_SCOPE_TYPE_NAMESPACE,
}
)

func getScopeTypes() []string {
var types []string
for st := range scopeTypes {
types = append(types, st)
}
return types
}

var (
serviceAccountIDFlag = &cli.StringFlag{
Name: serviceAccountIDFlagName,
Expand Down Expand Up @@ -226,73 +210,40 @@ func NewServiceAccountCommand(getServiceAccountClientFn GetServiceAccountClientF
serviceAccountNameFlag,
RequestIDFlag,
&cli.StringFlag{
Name: accountRoleFlagName,
Usage: fmt.Sprintf("The account role to set on the service account; valid types are: %v", accountActionGroups),
Aliases: []string{"ar"},
Name: accountRoleFlagName,
Usage: fmt.Sprintf("The account role to set on the service account; valid types are: %v", accountActionGroups),
Required: true,
Aliases: []string{"ar"},
},
&cli.StringSliceFlag{
Name: namespacePermissionFlagName,
Usage: fmt.Sprintf("Flag can be used multiple times; value must be \"namespace=permission\"; valid types are: %v", namespaceActionGroups),
Usage: fmt.Sprintf("Flag can be used multiple times; value must be \"<namespace>=<permission>\"; valid types are: %v", namespaceActionGroups),
Aliases: []string{"np"},
},
&cli.StringFlag{
Name: serviceAccountScopeTypeFlagName,
Usage: fmt.Sprintf("The service account scope type; valid types are: %v", getScopeTypes()),
Aliases: []string{"st"},
},
&cli.StringFlag{
Name: serviceAccountScopeIDFlagName,
Usage: "The entity ID that the service account is scoped to (e.g. namespace ID if creating a namespace scoped service account)",
Aliases: []string{"sid"},
},
},
Action: func(ctx *cli.Context) error {
if len(ctx.String(serviceAccountNameFlagName)) == 0 {
return fmt.Errorf("service account name must be provided with '--%s'", serviceAccountNameFlagName)
}

scopeType := ctx.String(serviceAccountScopeTypeFlagName)
scopeID := ctx.String(serviceAccountScopeIDFlagName)
if (len(scopeType) > 0) != (len(scopeID) > 0) {
return fmt.Errorf("both scope type and scope ID must be provided")
if len(ctx.String(accountRoleFlagName)) == 0 {
return fmt.Errorf("account role must be specified; valid types are %v", accountActionGroups)
}

var scope *auth.ServiceAccountScope
if len(scopeType) > 0 {
t, ok := scopeTypes[scopeType]
if !ok {
return fmt.Errorf("invalid scope type: %s", scopeType)
}
scope = &auth.ServiceAccountScope{
Type: t,
Id: scopeID,
}
}

var access *auth.AccountAccess
if scope == nil {
if len(ctx.String(accountRoleFlagName)) == 0 {
return fmt.Errorf("account role must be specified; valid types are %v", accountActionGroups)
}
ag, err := toAccountActionGroup(ctx.String(accountRoleFlagName))
if err != nil {
return fmt.Errorf("failed to parse account role: %w", err)
}
access = &auth.AccountAccess{
Role: ag,
}
} else if scope.Type == auth.SERVICE_ACCOUNT_SCOPE_TYPE_NAMESPACE && len(ctx.String(accountRoleFlagName)) > 0 {
return fmt.Errorf("namespace scoped service accounts may not have an account role")
ag, err := toAccountActionGroup(ctx.String(accountRoleFlagName))
if err != nil {
return fmt.Errorf("failed to parse account role: %w", err)
}

spec := &auth.ServiceAccountSpec{
Name: ctx.String(serviceAccountNameFlagName),
Access: &auth.Access{
AccountAccess: access,
AccountAccess: &auth.AccountAccess{
Role: ag,
},
NamespaceAccesses: map[string]*auth.NamespaceAccess{},
},
Description: ctx.String(serviceAccountDescriptionFlagName),
Scope: scope,
}

isAccountAdmin := ctx.String(accountRoleFlagName) == auth.AccountActionGroup_name[int32(auth.ACCOUNT_ACTION_GROUP_ADMIN)]
Expand Down Expand Up @@ -329,6 +280,57 @@ func NewServiceAccountCommand(getServiceAccountClientFn GetServiceAccountClientF
return c.createServiceAccount(ctx, spec, ctx.String(RequestIDFlagName))
},
},
{
Name: "create-scoped",
Usage: "Create a scoped service account (service account restricted to a single namespace)",
Aliases: []string{"cs"},
Flags: []cli.Flag{
serviceAccountDescriptionFlag,
serviceAccountNameFlag,
RequestIDFlag,
&cli.StringFlag{
Name: namespacePermissionFlagName,
Usage: fmt.Sprintf("Value must be \"<namespace>=<permission>\"; valid types are: %v", namespaceActionGroups),
Aliases: []string{"np"},
},
},
Action: func(ctx *cli.Context) error {
if len(ctx.String(serviceAccountNameFlagName)) == 0 {
return fmt.Errorf("service account name must be provided with '--%s'", serviceAccountNameFlagName)
}

scopedNamespace := ctx.String(namespacePermissionFlagName)
if len(scopedNamespace) == 0 {
return fmt.Errorf("namespace permission must be specified")
}

spec := &auth.ServiceAccountSpec{
Name: ctx.String(serviceAccountNameFlagName),
Access: &auth.Access{
NamespaceAccesses: map[string]*auth.NamespaceAccess{},
},
Description: ctx.String(serviceAccountDescriptionFlagName),
}

nsMap, err := toNamespacePermissionsMap([]string{scopedNamespace})
if err != nil {
return fmt.Errorf("failed to read namespace permissions: %w", err)
}

for ns, perm := range nsMap {
nsActionGroup, err := toNamespaceActionGroup(perm)
if err != nil {
return fmt.Errorf("failed to parse %q namespace permission: %w", ns, err)
}

spec.Access.NamespaceAccesses[ns] = &auth.NamespaceAccess{
Permission: nsActionGroup,
}
}

return c.createServiceAccount(ctx, spec, ctx.String(RequestIDFlagName))
},
},
{
Name: "list",
Usage: "List service accounts",
Expand Down
19 changes: 13 additions & 6 deletions app/serviceaccount_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,12 +105,6 @@ func (s *ServiceAccountTestSuite) TestList() {
func (s *ServiceAccountTestSuite) TestCreateServiceAccount() {
s.mockAuthService.EXPECT().CreateServiceAccount(gomock.Any(), gomock.Any()).Return(nil, errors.New("create service account error")).Times(1)
s.EqualError(s.RunCmd("service-account", "create", "--description", "test description", "--name", "test name", "--account-role", "Read"), "unable to create service account: create service account error")
s.EqualError(s.RunCmd("service-account", "create", "--name", "test name", "--scope-type", "namespace"), "both scope type and scope ID must be provided")
s.EqualError(s.RunCmd("service-account", "create", "--name", "test name", "--scope-id", "test.ns"), "both scope type and scope ID must be provided")
s.EqualError(s.RunCmd("service-account", "create", "--name", "test name", "--scope-type", "invalid", "--scope-id", "test.ns"), "invalid scope type: invalid")
s.EqualError(s.RunCmd("service-account", "create", "--name", "test name", "--scope-type", "namespace", "--scope-id", "test.ns", "--account-role", "Read"), "namespace scoped service accounts may not have an account role")
s.ErrorContains(s.RunCmd("service-account", "create", "--name", "test name"), "account role must be specified")

s.mockAuthService.EXPECT().CreateServiceAccount(gomock.Any(), gomock.Any()).Return(&authservice.CreateServiceAccountResponse{
RequestStatus: &request.RequestStatus{
State: request.STATE_FULFILLED,
Expand All @@ -131,6 +125,19 @@ func (s *ServiceAccountTestSuite) TestCreateServiceAccount() {
s.NoError(s.RunCmd("service-account", "create", "--description", "test description", "--name", "test name", "--account-role", "Read", "--namespace-permission", "test-namespace=Read"))
}

func (s *ServiceAccountTestSuite) TestCreateScopedServiceAccount() {
s.mockAuthService.EXPECT().CreateServiceAccount(gomock.Any(), gomock.Any()).Return(nil, errors.New("create service account error")).Times(1)
s.EqualError(s.RunCmd("service-account", "create-scoped", "--description", "test description", "--name", "test name", "--namespace-permission", "test-namespace=Read"), "unable to create service account: create service account error")
s.EqualError(s.RunCmd("service-account", "create-scoped", "--description", "test description", "--name", "test name"), "namespace permission must be specified")

s.mockAuthService.EXPECT().CreateServiceAccount(gomock.Any(), gomock.Any()).Return(&authservice.CreateServiceAccountResponse{
RequestStatus: &request.RequestStatus{
State: request.STATE_FULFILLED,
},
}, nil).Times(1)
s.NoError(s.RunCmd("service-account", "create-scoped", "--description", "test description", "--name", "test name", "--namespace-permission", "test-namespace=Admin"))
}

func (s *ServiceAccountTestSuite) TestDeleteServiceAccount() {
s.mockAuthService.EXPECT().GetServiceAccount(gomock.Any(), gomock.Any()).Return(nil, errors.New("get service account error")).Times(1)
s.EqualError(s.RunCmd("service-account", "delete", "--service-account-id", "test-service-account-id"), "unable to get service account: get service account error")
Expand Down
Loading

0 comments on commit 01242f2

Please sign in to comment.