Skip to content

Commit

Permalink
feat: add service account manager (#1510)
Browse files Browse the repository at this point in the history
* feat: add service account manager

* chore: add godoc

* chore: add godoc
  • Loading branch information
kian99 authored Jan 13, 2025
1 parent eadc412 commit 334456d
Show file tree
Hide file tree
Showing 15 changed files with 392 additions and 277 deletions.
2 changes: 1 addition & 1 deletion cmd/jaas/cmd/listserviceaccountcredentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func (s *listServiceAccountCredentialsSuite) TestListServiceAccountCredentials(c
user, err := dbmodel.NewIdentity("alice@canonical.com")
c.Assert(err, gc.IsNil)
u := openfga.NewUser(user, s.OFGAClient)
err = s.JIMM.AddServiceAccount(ctx, u, clientIDWithDomain)
err = s.JIMM.ServiceAccountManager().AddServiceAccount(ctx, u, clientIDWithDomain)
c.Assert(err, gc.IsNil)
svcAcc, err := dbmodel.NewIdentity(clientIDWithDomain)
c.Assert(err, gc.IsNil)
Expand Down
2 changes: 1 addition & 1 deletion cmd/jaas/cmd/updatecredentials_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ func (s *updateCredentialsSuite) TestUpdateServiceAccountCredentialFromControlle
user, err := dbmodel.NewIdentity("alice@canonical.com")
c.Assert(err, gc.IsNil)
u := openfga.NewUser(user, s.OFGAClient)
err = s.JIMM.AddServiceAccount(ctx, u, clientIDWithDomain)
err = s.JIMM.ServiceAccountManager().AddServiceAccount(ctx, u, clientIDWithDomain)
c.Assert(err, gc.IsNil)

// Create cloud and cloud-credential for Alice.
Expand Down
35 changes: 35 additions & 0 deletions internal/jimm/cloudcredential.go
Original file line number Diff line number Diff line change
Expand Up @@ -350,3 +350,38 @@ func (j *JIMM) getCloudCredentialAttributes(ctx context.Context, cred *dbmodel.C
}
return attr, nil
}

// CopyCredential copies a cloud credential from one user to another.
func (j *JIMM) CopyCredential(ctx context.Context, originalUser *openfga.User, newUser *openfga.User, cred names.CloudCredentialTag) (names.CloudCredentialTag, []jujuparams.UpdateCredentialModelResult, error) {
op := errors.Op("jimm.CopyCredential")

credential, err := j.GetCloudCredential(ctx, originalUser, cred)
if err != nil {
return names.CloudCredentialTag{}, nil, errors.E(op, err)
}

attr, err := j.getCloudCredentialAttributes(ctx, credential)
if err != nil {
return names.CloudCredentialTag{}, nil, errors.E(op, err)
}

newCredID := fmt.Sprintf("%s/%s/%s", cred.Cloud().Id(), newUser.Name, cred.Name())
if !names.IsValidCloudCredential(newCredID) {
return names.CloudCredentialTag{}, nil, errors.E(op, fmt.Sprintf("new credential ID %s is not a valid cloud credential tag", newCredID))
}

newCredential := jujuparams.CloudCredential{
AuthType: credential.AuthType,
Attributes: attr,
}
newTag := names.NewCloudCredentialTag(newCredID)

modelRes, err := j.UpdateCloudCredential(ctx, newUser, UpdateCloudCredentialArgs{
CredentialTag: newTag,
Credential: newCredential,
SkipCheck: false,
SkipUpdate: false,
})

return newTag, modelRes, err
}
107 changes: 107 additions & 0 deletions internal/jimm/cloudcredential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1740,3 +1740,110 @@ func (s testCloudCredentialAttributeStore) GetOAuthSecret(ctx context.Context) (
func (s testCloudCredentialAttributeStore) PutOAuthSecret(ctx context.Context, raw []byte) error {
return errors.E(errors.CodeNotImplemented)
}

func TestCopyCredential(t *testing.T) {
c := qt.New(t)

ctx := context.Background()

api := &jimmtest.API{
CheckCredentialModels_: func(context.Context, jujuparams.TaggedCredential) ([]jujuparams.UpdateCredentialModelResult, error) {
return []jujuparams.UpdateCredentialModelResult{}, nil
},
UpdateCredential_: func(context.Context, jujuparams.TaggedCredential) ([]jujuparams.UpdateCredentialModelResult, error) {
return []jujuparams.UpdateCredentialModelResult{}, nil
},
}

j := jimmtest.NewJIMM(c, &jimm.Parameters{
Dialer: &jimmtest.Dialer{
API: api,
},
})

svcAccId, err := dbmodel.NewIdentity("39caae91-b914-41ae-83f8-c7b86ca5ad5a@serviceaccount")
c.Assert(err, qt.IsNil)
c.Assert(j.Database.DB.Create(&svcAccId).Error, qt.IsNil)
svcAcc := openfga.NewUser(svcAccId, j.OpenFGAClient)
u, err := dbmodel.NewIdentity("alice@canonical.com")
c.Assert(err, qt.IsNil)

c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil)

user := openfga.NewUser(u, j.OpenFGAClient)

err = user.SetControllerAccess(context.Background(), j.ResourceTag(), ofganames.AdministratorRelation)
c.Assert(err, qt.IsNil)

// Create cloud, controller and cloud-credential as setup for test.
cloud := dbmodel.Cloud{
Name: "test-cloud",
Type: "test-provider",
Regions: []dbmodel.CloudRegion{{
Name: "test-region-1",
}},
}
c.Assert(j.Database.DB.Create(&cloud).Error, qt.IsNil)

err = user.SetCloudAccess(context.Background(), cloud.ResourceTag(), ofganames.AdministratorRelation)
c.Assert(err, qt.IsNil)

controller1 := dbmodel.Controller{
Name: "test-controller-1",
UUID: "00000000-0000-0000-0000-0000-0000000000001",
CloudName: "test-cloud",
CloudRegion: "test-region-1",
CloudRegions: []dbmodel.CloudRegionControllerPriority{{
Priority: 0,
CloudRegionID: cloud.Regions[0].ID,
}},
}
err = j.Database.AddController(context.Background(), &controller1)
c.Assert(err, qt.Equals, nil)

cred := dbmodel.CloudCredential{
Name: "test-credential-1",
CloudName: cloud.Name,
OwnerIdentityName: u.Name,
AuthType: "empty",
}
err = j.Database.SetCloudCredential(context.Background(), &cred)
c.Assert(err, qt.Equals, nil)

_, res, err := j.CopyCredential(ctx, user, svcAcc, cred.ResourceTag())
c.Assert(err, qt.Equals, nil)
newCred := dbmodel.CloudCredential{
Name: "test-credential-1",
CloudName: cloud.Name,
OwnerIdentityName: svcAcc.Name,
}
c.Assert(len(res), qt.Equals, 0)
err = j.Database.GetCloudCredential(context.Background(), &newCred)
c.Assert(err, qt.Equals, nil)
}

func TestCopyCredentialWithMissingCredential(t *testing.T) {
c := qt.New(t)

ctx := context.Background()

j := jimmtest.NewJIMM(c, nil)

svcAccId, err := dbmodel.NewIdentity("39caae91-b914-41ae-83f8-c7b86ca5ad5a@serviceaccount")
c.Assert(err, qt.IsNil)
c.Assert(j.Database.DB.Create(&svcAccId).Error, qt.IsNil)
svcAcc := openfga.NewUser(svcAccId, j.OpenFGAClient)
u, err := dbmodel.NewIdentity("alice@canonical.com")
c.Assert(err, qt.IsNil)
c.Assert(j.Database.DB.Create(&u).Error, qt.IsNil)
user := openfga.NewUser(u, j.OpenFGAClient)

cred := dbmodel.CloudCredential{
Name: "test-credential-1",
CloudName: "fake-cloud",
OwnerIdentityName: u.Name,
AuthType: "empty",
}
_, _, err = j.CopyCredential(ctx, user, svcAcc, cred.ResourceTag())
c.Assert(err, qt.ErrorMatches, "cloudcredential .* not found")
}
24 changes: 24 additions & 0 deletions internal/jimm/jimm.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (
"github.com/canonical/jimm/v3/internal/jimm/login"
"github.com/canonical/jimm/v3/internal/jimm/permissions"
"github.com/canonical/jimm/v3/internal/jimm/role"
"github.com/canonical/jimm/v3/internal/jimm/serviceaccount"
"github.com/canonical/jimm/v3/internal/jimmjwx"
"github.com/canonical/jimm/v3/internal/openfga"
ofganames "github.com/canonical/jimm/v3/internal/openfga/names"
Expand Down Expand Up @@ -231,6 +232,14 @@ type AuditLogManager interface {
StartCleanup(ctx context.Context)
}

// ServiceAccountManager provides methods to assign ownerhsip and credentials to service accounts.
type ServiceAccountManager interface {
// AddServiceAccount assigns an unowned service account to the provided user.
AddServiceAccount(ctx context.Context, u *openfga.User, clientId string) error
// CopyServiceAccountCredential copies a cloud-credential from a user to a service account.
CopyServiceAccountCredential(ctx context.Context, u *openfga.User, svcAcc *openfga.User, cred names.CloudCredentialTag) (names.CloudCredentialTag, []jujuparams.UpdateCredentialModelResult, error)
}

// Parameters holds the services and static fields passed to the jimm.New() constructor.
// You can provide mock implementations of certain services where necessary for dependency injection.
type Parameters struct {
Expand Down Expand Up @@ -366,6 +375,12 @@ func New(p Parameters) (*JIMM, error) {
}
j.auditLogManager = auditLogManager

svcAccManager, err := serviceaccount.NewServiceAccountManager(j.Database, j.OpenFGAClient, j)
if err != nil {
return nil, err
}
j.serviceAccountManager = svcAccManager

return j, nil
}

Expand Down Expand Up @@ -394,6 +409,9 @@ type JIMM struct {

// auditLogManager provides a means to manage audit logs within JIMM.
auditLogManager AuditLogManager

// serviceAccountManager provides a means to manage service accounts within JIMM.
serviceAccountManager ServiceAccountManager
}

// ResourceTag returns JIMM's controller tag stating its UUID.
Expand Down Expand Up @@ -443,6 +461,12 @@ func (j *JIMM) AuditLogManager() AuditLogManager {
return j.auditLogManager
}

// ServiceAccountManager returns a manager that enables operations
// related to service accounts.
func (j *JIMM) ServiceAccountManager() ServiceAccountManager {
return j.serviceAccountManager
}

type permission struct {
resource string
relation string
Expand Down
89 changes: 0 additions & 89 deletions internal/jimm/service_account.go

This file was deleted.

Loading

0 comments on commit 334456d

Please sign in to comment.