Skip to content

Commit

Permalink
add support for customer SA namespace in snap meta lister tool
Browse files Browse the repository at this point in the history
Signed-off-by: Rakshith R <[email protected]>
  • Loading branch information
Rakshith-R committed Dec 3, 2024
1 parent 695f797 commit 9c56b79
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 39 deletions.
3 changes: 2 additions & 1 deletion examples/snapshot-metadata-lister/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func parseFlags() {
flag.StringVar(&kubeConfig, "kubeconfig", "", "Path to the kubeconfig file.")
}

flag.StringVar(&args.ServiceAccount, "service-account", "", "ServiceAccount used to create a security token. If unspecified the ServiceAccount of the Pod in which the command is invoked will be used.")
flag.StringVar(&args.SAName, "service-account", "", "ServiceAccount used to create a security token. If unspecified the ServiceAccount of the Pod in which the command is invoked will be used.")
flag.StringVar(&args.SANamespace, "service-account-namespace", "", "Namespace of the ServiceAccount used to create a security token. If unspecified the Namespace of the Pod in which the command is invoked will be used.")

flag.Int64Var(&args.TokenExpirySecs, "token-expiry", 600, "Expiry time in seconds for the security token.")
flag.Int64Var(&args.StartingOffset, "starting-offset", 0, "The starting byte offset.")
Expand Down
29 changes: 17 additions & 12 deletions pkg/iterator/common_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ type testHarness struct {
Namespace string
PrevSnapshotName string
SecurityToken string
ServiceAccount string
SAName string
SANamespace string
SnapshotName string
StartingOffset int64

Expand All @@ -62,7 +63,8 @@ type testHarness struct {

// fake helpers
CalledGetDefaultServiceAccount bool
RetGetDefaultServiceAccount string
RetGetDefaultSAName string
RetGetDefaultSANamespace string
RetGetDefaultServiceAccountErr error

CalledGetCSIDriverFromPrimarySnapshot bool
Expand All @@ -73,10 +75,11 @@ type testHarness struct {
RetGetSnapshotMetadataServiceCRService *smsCRv1alpha1.SnapshotMetadataService
RetGetSnapshotMetadataServiceCRErr error

InCreateSecurityTokenSA string
InCreateSecurityTokenAudience string
RetCreateSecurityToken string
RetCreateSecurityTokenErr error
InCreateSecurityTokenSA string
InCreateSecurityTokenSANamespace string
InCreateSecurityTokenAudience string
RetCreateSecurityToken string
RetCreateSecurityTokenErr error

InGetGRPCClientCA []byte
InGetGRPCClientURL string
Expand All @@ -99,7 +102,8 @@ func newTestHarness() *testHarness {
Namespace: "namespace",
SnapshotName: "snapshotName",
PrevSnapshotName: "prevSnapshotName",
ServiceAccount: "serviceAccount",
SAName: "serviceAccount",
SANamespace: "serviceAccountNamespace",
CSIDriver: "csiDriver",
Audience: "audience",
Address: "sidecar.csiDriver.k8s.local", // invalid
Expand Down Expand Up @@ -132,7 +136,7 @@ func (th *testHarness) Args() Args {
Namespace: th.Namespace,
SnapshotName: th.SnapshotName,
PrevSnapshotName: th.PrevSnapshotName,
ServiceAccount: th.ServiceAccount,
SAName: th.SAName,
StartingOffset: th.StartingOffset,
MaxResults: th.MaxResults,
Emitter: th,
Expand Down Expand Up @@ -178,7 +182,7 @@ func (th *testHarness) FakeAuthSelfSubjectReview() *authv1.SelfSubjectReview {
return &authv1.SelfSubjectReview{
Status: authv1.SelfSubjectReviewStatus{
UserInfo: authv1.UserInfo{
Username: K8sServiceAccountUserNamePrefix + th.Namespace + ":" + th.ServiceAccount,
Username: K8sServiceAccountUserNamePrefix + th.SANamespace + ":" + th.SAName,
},
},
}
Expand Down Expand Up @@ -251,9 +255,9 @@ func (th *testHarness) SnapshotMetadataIteratorDone(numberRecords int) {
}

// fake helpers
func (th *testHarness) getDefaultServiceAccount(ctx context.Context) (string, error) {
func (th *testHarness) getDefaultServiceAccount(ctx context.Context) (string, string, error) {
th.CalledGetDefaultServiceAccount = true
return th.RetGetDefaultServiceAccount, th.RetGetDefaultServiceAccountErr
return th.RetGetDefaultSAName, th.RetGetDefaultSANamespace, th.RetGetDefaultServiceAccountErr
}

func (th *testHarness) getCSIDriverFromPrimarySnapshot(ctx context.Context) (string, error) {
Expand All @@ -266,8 +270,9 @@ func (th *testHarness) getSnapshotMetadataServiceCR(ctx context.Context, csiDriv
return th.RetGetSnapshotMetadataServiceCRService, th.RetGetSnapshotMetadataServiceCRErr
}

func (th *testHarness) createSecurityToken(ctx context.Context, serviceAccount, audience string) (string, error) {
func (th *testHarness) createSecurityToken(ctx context.Context, serviceAccount, serviceAccountNamespace, audience string) (string, error) {
th.InCreateSecurityTokenSA = serviceAccount
th.InCreateSecurityTokenSANamespace = serviceAccountNamespace
th.InCreateSecurityTokenAudience = audience
return th.RetCreateSecurityToken, th.RetCreateSecurityTokenErr
}
Expand Down
43 changes: 26 additions & 17 deletions pkg/iterator/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,11 @@ type Args struct {
// the VolumeSnapshot specified by the SnapshotName field.
CSIDriver string

// ServiceAccount is used to construct a security token
// Specify the ServiceAccount object used to construct a security token
// with the audience string from the SnapshotMetadataService CR.
// If unspecified the default for the given client will be used.
ServiceAccount string
// If either of the following fields are unspecified, the default for the given client will be used.
SANamespace string
SAName string

// TokenExpirySecs specifies the time in seconds after which the
// security token will expire.
Expand All @@ -122,6 +123,10 @@ func (a Args) Validate() error {
return fmt.Errorf("%w: invalid TokenExpirySecs", ErrInvalidArgs)
case a.MaxResults < 0:
return fmt.Errorf("%w: invalid MaxResults", ErrInvalidArgs)
case a.SANamespace == "" && a.SAName != "":
return fmt.Errorf("%w: ServiceAccountName provided but ServiceAccountNamespace missing", ErrInvalidArgs)
case a.SANamespace != "" && a.SAName == "":
return fmt.Errorf("%w: ServiceAccountNamespace provided but ServiceAccountName missing", ErrInvalidArgs)
}

if err := a.Clients.Validate(); err != nil {
Expand Down Expand Up @@ -162,9 +167,9 @@ type iterator struct {

type iteratorHelpers interface {
getCSIDriverFromPrimarySnapshot(ctx context.Context) (string, error)
getDefaultServiceAccount(ctx context.Context) (string, error)
getDefaultServiceAccount(ctx context.Context) (string, string, error)
getSnapshotMetadataServiceCR(ctx context.Context, csiDriver string) (*smsCRv1alpha1.SnapshotMetadataService, error)
createSecurityToken(ctx context.Context, serviceAccount, audience string) (string, error)
createSecurityToken(ctx context.Context, saNamespace, saName, audience string) (string, error)
getGRPCClient(caCert []byte, URL string) (api.SnapshotMetadataClient, error)
getAllocatedBlocks(ctx context.Context, grpcClient api.SnapshotMetadataClient, securityToken string) error
getChangedBlocks(ctx context.Context, grpcClient api.SnapshotMetadataClient, securityToken string) error
Expand All @@ -191,9 +196,10 @@ func newIterator(args Args) *iterator {
func (iter *iterator) run(ctx context.Context) error {
var err error

serviceAccount := iter.ServiceAccount // optional field
if serviceAccount == "" {
serviceAccount, err = iter.h.getDefaultServiceAccount(ctx)
saName := iter.SAName // optional field
saNamespace := iter.SANamespace // optional field
if saName == "" || saNamespace == "" {
saNamespace, saName, err = iter.h.getDefaultServiceAccount(ctx)
if err != nil {
return err
}
Expand All @@ -213,7 +219,7 @@ func (iter *iterator) run(ctx context.Context) error {
}

// get the security token to use in the API
securityToken, err := iter.h.createSecurityToken(ctx, serviceAccount, smsCR.Spec.Audience)
securityToken, err := iter.h.createSecurityToken(ctx, saNamespace, saName, smsCR.Spec.Audience)
if err != nil {
return err
}
Expand Down Expand Up @@ -242,20 +248,19 @@ func (iter *iterator) run(ctx context.Context) error {
return err
}

func (iter *iterator) getDefaultServiceAccount(ctx context.Context) (string, error) {
func (iter *iterator) getDefaultServiceAccount(ctx context.Context) (string, string, error) {
ssr, err := iter.KubeClient.AuthenticationV1().SelfSubjectReviews().Create(ctx, &authv1.SelfSubjectReview{}, apimetav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("SelfSubjectReviews.Create(): %w", err)
return "", "", fmt.Errorf("SelfSubjectReviews.Create(): %w", err)
}

if strings.HasPrefix(ssr.Status.UserInfo.Username, K8sServiceAccountUserNamePrefix) {
fields := strings.Split(ssr.Status.UserInfo.Username, ":")
if len(fields) == 4 {
return fields[3], nil
return fields[3], fields[2], nil
}
}

return "", fmt.Errorf("%w: ServiceAccount unspecified and default cannot be determined", ErrInvalidArgs)
return "", "", fmt.Errorf("%w: ServiceAccount unspecified and default cannot be determined", ErrInvalidArgs)
}

// getCSIDriverFromPrimarySnapshot loads the bound VolumeSnapshotContent
Expand Down Expand Up @@ -291,17 +296,21 @@ func (iter *iterator) getSnapshotMetadataServiceCR(ctx context.Context, csiDrive

// createSecurityToken will create a security token for the specified storage
// account using the audience string from the SnapshotMetadataService CR.
func (iter *iterator) createSecurityToken(ctx context.Context, serviceAccount, audience string) (string, error) {
func (iter *iterator) createSecurityToken(ctx context.Context,
sa,
saNamespace,
audience string) (string, error) {
tokenRequest := authv1.TokenRequest{
Spec: authv1.TokenRequestSpec{
Audiences: []string{audience},
ExpirationSeconds: &iter.TokenExpirySecs,
},
}

tokenResp, err := iter.KubeClient.CoreV1().ServiceAccounts(iter.Namespace).CreateToken(ctx, serviceAccount, &tokenRequest, apimetav1.CreateOptions{})
tokenResp, err := iter.KubeClient.CoreV1().ServiceAccounts(saNamespace).
CreateToken(ctx, sa, &tokenRequest, apimetav1.CreateOptions{})
if err != nil {
return "", fmt.Errorf("ServiceAccounts.CreateToken(%s): %v", serviceAccount, err)
return "", fmt.Errorf("ServiceAccounts.CreateToken(%s/%s): %v", saNamespace, sa, err)
}

return tokenResp.Status.Token, nil
Expand Down
39 changes: 30 additions & 9 deletions pkg/iterator/iter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,24 @@ func TestValidateArgs(t *testing.T) {
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidArgs)
assert.ErrorContains(t, err, "MaxResults")

args.MaxResults = 5
args.SAName = "serviceAccount"
err = args.Validate()
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidArgs)
assert.ErrorContains(t, err, "ServiceAccountName provided")

args.SAName = ""
args.SANamespace = "serviceAccountNamespace"
err = args.Validate()
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidArgs)
assert.ErrorContains(t, err, "ServiceAccountNamespace provided")

args.SAName = "serviceAccount"
err = args.Validate()
assert.NoError(t, err)
}

func TestNewIterator(t *testing.T) {
Expand Down Expand Up @@ -124,11 +142,12 @@ func TestRun(t *testing.T) {
th.RetGetSnapshotMetadataServiceCRService = th.FakeCR()
th.RetGetGRPCClient = th.GRPCSnapshotMetadataClient(t)
th.RetCreateSecurityToken = "security-token"
th.RetGetDefaultServiceAccount = th.ServiceAccount
th.RetGetDefaultSAName = th.SAName
th.RetGetDefaultSANamespace = th.SANamespace

iter := th.NewTestIterator()
iter.recordNum = 100
iter.ServiceAccount = ""
iter.SAName = ""
assert.NotEmpty(t, iter.PrevSnapshotName) // changed block flow

err := iter.run(context.Background())
Expand All @@ -138,7 +157,7 @@ func TestRun(t *testing.T) {
assert.True(t, th.CalledGetDefaultServiceAccount)
assert.True(t, th.CalledGetCSIDriverFromPrimarySnapshot)
assert.Equal(t, th.CSIDriver, th.InGetSnapshotMetadataServiceCRCSIDriver)
assert.Equal(t, th.ServiceAccount, th.InCreateSecurityTokenSA)
assert.Equal(t, th.SAName, th.InCreateSecurityTokenSA)
assert.Equal(t, th.Audience, th.InCreateSecurityTokenAudience)
assert.Equal(t, th.CACert, th.InGetGRPCClientCA)
assert.Equal(t, th.Address, th.InGetGRPCClientURL)
Expand Down Expand Up @@ -301,7 +320,7 @@ func TestGetDefaultServiceAccount(t *testing.T) {
t.Run("self-subject-review-err", func(t *testing.T) {
th := newTestHarness()
args := th.Args()
args.ServiceAccount = ""
args.SAName = ""

// invoke via GetSnapshotMetadata directly to cover that code path
err := GetSnapshotMetadata(context.Background(), args)
Expand All @@ -319,10 +338,11 @@ func TestGetDefaultServiceAccount(t *testing.T) {
return true, ssr, nil
})

sa, err := iter.getDefaultServiceAccount(context.Background())
sa, saNS, err := iter.getDefaultServiceAccount(context.Background())
assert.Error(t, err)
assert.ErrorIs(t, err, ErrInvalidArgs)
assert.Empty(t, sa)
assert.Empty(t, saNS)
})

t.Run("success", func(t *testing.T) {
Expand All @@ -334,9 +354,10 @@ func TestGetDefaultServiceAccount(t *testing.T) {
return true, ssr, nil
})

sa, err := iter.getDefaultServiceAccount(context.Background())
sa, saNS, err := iter.getDefaultServiceAccount(context.Background())
assert.NoError(t, err)
assert.Equal(t, th.ServiceAccount, sa)
assert.Equal(t, th.SAName, sa)
assert.Equal(t, th.SANamespace, saNS)
})
}

Expand Down Expand Up @@ -467,7 +488,7 @@ func TestCreateSecurityToken(t *testing.T) {
th := newTestHarness()
iter := th.NewTestIterator()

securityToken, err := iter.createSecurityToken(context.Background(), th.ServiceAccount, th.Audience)
securityToken, err := iter.createSecurityToken(context.Background(), th.SAName, th.SANamespace, th.Audience)
assert.Error(t, err)
assert.ErrorContains(t, err, "ServiceAccounts.CreateToken")
assert.Empty(t, securityToken)
Expand All @@ -481,7 +502,7 @@ func TestCreateSecurityToken(t *testing.T) {
return true, th.FakeTokenRequest(), nil
})

securityToken, err := iter.createSecurityToken(context.Background(), th.ServiceAccount, th.Audience)
securityToken, err := iter.createSecurityToken(context.Background(), th.SAName, th.SANamespace, th.Audience)
assert.NoError(t, err)
assert.Equal(t, th.SecurityToken, securityToken)
})
Expand Down

0 comments on commit 9c56b79

Please sign in to comment.