Skip to content

Commit

Permalink
Add support for rotating credentials.
Browse files Browse the repository at this point in the history
  • Loading branch information
ggreer committed Jan 9, 2024
1 parent e553f4d commit d79e1e0
Show file tree
Hide file tree
Showing 4 changed files with 191 additions and 35 deletions.
25 changes: 23 additions & 2 deletions pkg/cli/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,14 @@ func NewCmd[T any, PtrT *T](
v.GetString("delete-resource"),
v.GetString("delete-resource-type"),
))
case v.GetString("rotate-credentials") != "":
opts = append(opts,
connectorrunner.WithProvisioningEnabled(),
connectorrunner.WithOnDemandRotateCredentials(
v.GetString("file"),
v.GetString("rotate-credentials"),
v.GetString("rotate-credentials-type"),
))
default:
opts = append(opts, connectorrunner.WithOnDemandSync(v.GetString("file")))
}
Expand Down Expand Up @@ -195,6 +203,8 @@ func NewCmd[T any, PtrT *T](
copts = append(copts, connector.WithProvisioningEnabled())
case v.GetString("delete-resource") != "" || v.GetString("delete-resource-type") != "":
copts = append(copts, connector.WithProvisioningEnabled())
case v.GetString("rotate-credentials") != "" || v.GetString("rotate-credentials-type") != "":
copts = append(copts, connector.WithProvisioningEnabled())
case v.GetBool("provisioning"):
copts = append(copts, connector.WithProvisioningEnabled())
}
Expand Down Expand Up @@ -317,8 +327,11 @@ func NewCmd[T any, PtrT *T](
cmd.PersistentFlags().String("delete-resource", "", "The id of the resource to delete ($BATON_DELETE_RESOURCE)")
cmd.PersistentFlags().String("delete-resource-type", "", "The type of the resource to delete ($BATON_DELETE_RESOURCE_TYPE)")

cmd.MarkFlagsMutuallyExclusive("grant-entitlement", "revoke-grant", "create-account-login", "delete-resource")
cmd.MarkFlagsMutuallyExclusive("grant-entitlement", "revoke-grant", "create-account-email", "delete-resource-type")
cmd.PersistentFlags().String("rotate-credentials", "", "The id of the resource to rotate credentials on ($BATON_ROTATE_CREDENTIALS)")
cmd.PersistentFlags().String("rotate-credentials-type", "", "The type of the resource to rotate credentials on ($BATON_ROTATE_CREDENTIALS_TYPE)")

cmd.MarkFlagsMutuallyExclusive("grant-entitlement", "revoke-grant", "create-account-login", "delete-resource", "rotate-credentials")
cmd.MarkFlagsMutuallyExclusive("grant-entitlement", "revoke-grant", "create-account-email", "delete-resource-type", "rotate-credentials-type")
err = cmd.PersistentFlags().MarkHidden("grant-entitlement")
if err != nil {
return nil, err
Expand Down Expand Up @@ -351,6 +364,14 @@ func NewCmd[T any, PtrT *T](
if err != nil {
return nil, err
}
err = cmd.PersistentFlags().MarkHidden("rotate-credentials")
if err != nil {
return nil, err
}
err = cmd.PersistentFlags().MarkHidden("rotate-credentials-type")
if err != nil {
return nil, err
}

// Flags for daemon mode
cmd.PersistentFlags().String("client-id", "", "The client ID used to authenticate with ConductorOne ($BATON_CLIENT_ID)")
Expand Down
48 changes: 35 additions & 13 deletions pkg/connectorrunner/runner.go
Original file line number Diff line number Diff line change
Expand Up @@ -211,20 +211,26 @@ type deleteResourceConfig struct {
resourceType string
}

type rotateCredentialsConfig struct {
resourceId string
resourceType string
}

type runnerConfig struct {
rlCfg *ratelimitV1.RateLimiterConfig
rlDescriptors []*ratelimitV1.RateLimitDescriptors_Entry
onDemand bool
c1zPath string
clientAuth bool
clientID string
clientSecret string
provisioningEnabled bool
grantConfig *grantConfig
revokeConfig *revokeConfig
tempDir string
createAccountConfig *createAccountConfig
deleteResourceConfig *deleteResourceConfig
rlCfg *ratelimitV1.RateLimiterConfig
rlDescriptors []*ratelimitV1.RateLimitDescriptors_Entry
onDemand bool
c1zPath string
clientAuth bool
clientID string
clientSecret string
provisioningEnabled bool
grantConfig *grantConfig
revokeConfig *revokeConfig
tempDir string
createAccountConfig *createAccountConfig
deleteResourceConfig *deleteResourceConfig
rotateCredentialsConfig *rotateCredentialsConfig
}

// WithRateLimiterConfig sets the RateLimiterConfig for a runner.
Expand Down Expand Up @@ -357,6 +363,19 @@ func WithOnDemandDeleteResource(c1zPath string, resourceId string, resourceType
return nil
}
}

func WithOnDemandRotateCredentials(c1zPath string, resourceId string, resourceType string) Option {
return func(ctx context.Context, cfg *runnerConfig) error {
cfg.onDemand = true
cfg.c1zPath = c1zPath
cfg.rotateCredentialsConfig = &rotateCredentialsConfig{
resourceId: resourceId,
resourceType: resourceType,
}
return nil
}
}

func WithOnDemandSync(c1zPath string) Option {
return func(ctx context.Context, cfg *runnerConfig) error {
cfg.onDemand = true
Expand Down Expand Up @@ -434,6 +453,9 @@ func NewConnectorRunner(ctx context.Context, c types.ConnectorServer, opts ...Op
case cfg.deleteResourceConfig != nil:
tm = local.NewResourceDeleter(ctx, cfg.c1zPath, cfg.deleteResourceConfig.resourceId, cfg.deleteResourceConfig.resourceType)

case cfg.rotateCredentialsConfig != nil:
tm = local.NewCredentialRotator(ctx, cfg.c1zPath, cfg.rotateCredentialsConfig.resourceId, cfg.rotateCredentialsConfig.resourceType)

default:
tm, err = local.NewSyncer(ctx, cfg.c1zPath, local.WithTmpDir(cfg.tempDir))
if err != nil {
Expand Down
98 changes: 78 additions & 20 deletions pkg/provisioner/provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package provisioner

import (
"context"
"crypto/ecdsa"
"errors"

v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
Expand Down Expand Up @@ -33,6 +34,37 @@ type Provisioner struct {

deleteResourceID string
deleteResourceType string

rotateCredentialsId string
rotateCredentialsType string
}

// makeCrypto is used by rotateCredentials and createAccount.
func makeCrypto(ctx context.Context) (*ecdsa.PrivateKey, *v2.CredentialOptions, []*v2.EncryptionConfig, error) {
// Default to generating a random key and random password that is 12 characters long
privKey, pubKey := crypto.GenKey()
pubKeyJWKBytes, err := pubKey.MarshalJSON()
if err != nil {
return nil, nil, nil, err
}
opts := &v2.CredentialOptions{
Create: true,
Options: &v2.CredentialOptions_RandomPassword_{
RandomPassword: &v2.CredentialOptions_RandomPassword{
Length: 12,
},
},
}
config := []*v2.EncryptionConfig{
{
Config: &v2.EncryptionConfig_PublicKeyConfig_{
PublicKeyConfig: &v2.EncryptionConfig_PublicKeyConfig{
PubKey: pubKeyJWKBytes,
},
},
},
}
return privKey, opts, config, nil
}

func (p *Provisioner) Run(ctx context.Context) error {
Expand All @@ -45,6 +77,8 @@ func (p *Provisioner) Run(ctx context.Context) error {
return p.createAccount(ctx)
case p.deleteResourceID != "" && p.deleteResourceType != "":
return p.deleteResource(ctx)
case p.rotateCredentialsId != "" && p.rotateCredentialsType != "":
return p.rotateCredentials(ctx)
default:
return errors.New("unknown provisioning action")
}
Expand Down Expand Up @@ -183,29 +217,10 @@ func (p *Provisioner) createAccount(ctx context.Context) error {
})
}

// Default to generating a random key and random password that is 12 characters long
privKey, pubKey := crypto.GenKey()
pubKeyJWKBytes, err := pubKey.MarshalJSON()
privKey, opts, config, err := makeCrypto(ctx)
if err != nil {
return err
}
opts := &v2.CredentialOptions{
Create: true,
Options: &v2.CredentialOptions_RandomPassword_{
RandomPassword: &v2.CredentialOptions_RandomPassword{
Length: 12,
},
},
}
config := []*v2.EncryptionConfig{
{
Config: &v2.EncryptionConfig_PublicKeyConfig_{
PublicKeyConfig: &v2.EncryptionConfig_PublicKeyConfig{
PubKey: pubKeyJWKBytes,
},
},
},
}

result, err := p.connector.CreateAccount(ctx, &v2.CreateAccountRequest{
AccountInfo: &v2.AccountInfo{
Expand Down Expand Up @@ -246,6 +261,40 @@ func (p *Provisioner) deleteResource(ctx context.Context) error {
return nil
}

func (p *Provisioner) rotateCredentials(ctx context.Context) error {
l := ctxzap.Extract(ctx)

privKey, opts, config, err := makeCrypto(ctx)
if err != nil {
return err
}

result, err := p.connector.RotateCredential(ctx, &v2.RotateCredentialRequest{
ResourceId: &v2.ResourceId{
Resource: p.rotateCredentialsId,
ResourceType: p.rotateCredentialsType,
},
CredentialOptions: opts,
EncryptionConfigs: config,
})
if err != nil {
return err
}

jwe, err := jose.ParseEncrypted(string(result.EncryptedData[0].EncryptedBytes))
if err != nil {
return err
}
plaintext, err := jwe.Decrypt(privKey)
if err != nil {
return err
}
// TODO FIXME: do better
l.Info("credentials rotated", zap.String("resource", p.rotateCredentialsId), zap.String("resource type", p.rotateCredentialsType), zap.String("password", string(plaintext)))

return nil
}

func NewGranter(c types.ConnectorClient, dbPath string, entitlementID string, principalID string, principalType string) *Provisioner {
return &Provisioner{
dbPath: dbPath,
Expand Down Expand Up @@ -281,3 +330,12 @@ func NewCreateAccountManager(c types.ConnectorClient, dbPath string, login strin
createAccountEmail: email,
}
}

func NewCredentialRotator(c types.ConnectorClient, dbPath string, resourceId string, resourceType string) *Provisioner {
return &Provisioner{
dbPath: dbPath,
connector: c,
rotateCredentialsId: resourceId,
rotateCredentialsType: resourceType,
}
}
55 changes: 55 additions & 0 deletions pkg/tasks/local/rotator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package local

import (
"context"
"sync"
"time"

v1 "github.com/conductorone/baton-sdk/pb/c1/connectorapi/baton/v1"
"github.com/conductorone/baton-sdk/pkg/provisioner"
"github.com/conductorone/baton-sdk/pkg/tasks"
"github.com/conductorone/baton-sdk/pkg/types"
)

type localCredentialRotator struct {
dbPath string
o sync.Once

resourceId string
resourceType string
}

func (m *localCredentialRotator) Next(ctx context.Context) (*v1.Task, time.Duration, error) {
var task *v1.Task
m.o.Do(func() {
task = &v1.Task{
TaskType: &v1.Task_CreateAccount{},
}
})
return task, 0, nil
}

func (m *localCredentialRotator) Process(ctx context.Context, task *v1.Task, cc types.ConnectorClient) error {
accountManager := provisioner.NewCredentialRotator(cc, m.dbPath, m.resourceId, m.resourceType)

err := accountManager.Run(ctx)
if err != nil {
return err
}

err = accountManager.Close(ctx)
if err != nil {
return err
}

return nil
}

// NewGranter returns a task manager that queues a sync task.
func NewCredentialRotator(ctx context.Context, dbPath string, resourceId string, resourceType string) tasks.Manager {
return &localCredentialRotator{
dbPath: dbPath,
resourceId: resourceId,
resourceType: resourceType,
}
}

0 comments on commit d79e1e0

Please sign in to comment.