Skip to content

Commit

Permalink
Add OAuth session store secret to credential store implementations (#…
Browse files Browse the repository at this point in the history
…1221)

* Add get/set methods for OAuth session store secret

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add `GetOAuthSecretsBasePath` method to Vault implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add `(Get|Put)OAuthSessionStoreSecret` methods to Vault implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add tests for `(Get|Put)OAuthSessionStoreSecret` methods to Vault implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Delete session store secret when cleaning up OAuth secrets in PostgreSQL implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add `(Get|Put)OAuthSessionStoreSecret` methods to PostgreSQL implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add tests for `(Get|Put)OAuthSessionStoreSecret` methods to PostgreSQL implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Add `(Get|Put)OAuthSessionStoreSecret` methods to in-memory implementation

Signed-off-by: Babak K. Shandiz <[email protected]>

* Update mock with new credential store methods

Signed-off-by: Babak K. Shandiz <[email protected]>

* Use credential store to fetch OAuth session store secret

Signed-off-by: Babak K. Shandiz <[email protected]>

* Generate OAuth session store secret on start if not exist

Signed-off-by: Babak K. Shandiz <[email protected]>

* Abort service setup when credential store is not set

Signed-off-by: Babak K. Shandiz <[email protected]>

* Update test to verify setup fails when credential store is not set

Signed-off-by: Babak K. Shandiz <[email protected]>

* Apply PostgreSQL credential store for tests

Signed-off-by: Babak K. Shandiz <[email protected]>

* Fix cleanup by calling `Delete` multiple times

Signed-off-by: Babak K. Shandiz <[email protected]>

* Run `go mod tidy`

Signed-off-by: Babak K. Shandiz <[email protected]>

* Fix early stopping of session clean ups

Signed-off-by: Babak K. Shandiz <[email protected]>

---------

Signed-off-by: Babak K. Shandiz <[email protected]>
  • Loading branch information
babakks authored May 30, 2024
1 parent bba595e commit 6d1cc50
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 63 deletions.
7 changes: 0 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -104,10 +104,7 @@ require (
github.com/aws/smithy-go v1.19.0 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f // indirect
github.com/canonical/go-flags v0.0.0-20230403090104-105d09a091b8 // indirect
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a // indirect
github.com/canonical/pebble v1.10.2 // indirect
github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b // indirect
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
Expand Down Expand Up @@ -147,7 +144,6 @@ require (
github.com/google/s2a-go v0.1.7 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect
github.com/googleapis/gax-go/v2 v2.12.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/schema v1.2.1 // indirect
github.com/gorilla/securecookie v1.1.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
Expand Down Expand Up @@ -264,7 +260,6 @@ require (
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pkg/sftp v1.13.6 // indirect
github.com/pkg/term v1.1.0 // indirect
github.com/pkg/xattr v0.4.9 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_model v0.5.0 // indirect
Expand Down Expand Up @@ -304,12 +299,10 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.22.0 // indirect
golang.org/x/exp v0.0.0-20231127185646-65229373498e // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/term v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.1 // indirect
google.golang.org/api v0.154.0 // indirect
google.golang.org/appengine v1.6.8 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20231127180814-3a041ad873d4 // indirect
Expand Down
11 changes: 0 additions & 11 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -145,18 +145,12 @@ github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f h1:gOO/tNZMjjvTKZWpY
github.com/bmizerany/pat v0.0.0-20210406213842-e4b6760bdd6f/go.mod h1:8rLXio+WjiTceGBHIoTvn60HIbs7Hm7bcHjyrSqYB9c=
github.com/canonical/go-dqlite v1.21.0 h1:4gLDdV2GF+vg0yv9Ff+mfZZNQ1JGhnQ3GnS2GeZPHfA=
github.com/canonical/go-dqlite v1.21.0/go.mod h1:Uvy943N8R4CFUAs59A1NVaziWY9nJ686lScY7ywurfg=
github.com/canonical/go-flags v0.0.0-20230403090104-105d09a091b8 h1:zGaJEJI9qPVyM+QKFJagiyrM91Ke5S9htoL1D470g6E=
github.com/canonical/go-flags v0.0.0-20230403090104-105d09a091b8/go.mod h1:ZZFeR9K9iGgpwOaLYF9PdT44/+lfSJ9sQz3B+SsGsYU=
github.com/canonical/go-service v1.0.0 h1:TF6TsEp04xAoI5pPoWjTYmEwLjbPATSnHEyeJCvzElg=
github.com/canonical/go-service v1.0.0/go.mod h1:GzNLXpkGdglL0kjREXoLXj2rB2Qx+EvAGncRDqCENYQ=
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a h1:Tfo/MzXK5GeG7gzSHqxGeY/669Mhh5ea43dn1mRDnk8=
github.com/canonical/lxd v0.0.0-20231214113525-e676fc63c50a/go.mod h1:UxfHGKFoRjgu1NUA9EFiR++dKvyAiT0h9HT0ffMlzjc=
github.com/canonical/ofga v0.10.0 h1:DHXhG/DAXWWQT/I+2jzr4qm0uTIYrILmtMxd6ZqmEzE=
github.com/canonical/ofga v0.10.0/go.mod h1:u4Ou8dbIhO7FmVlT7W3rX2roD9AOGz/CqmGh7AdF0Lo=
github.com/canonical/pebble v1.10.2 h1:TG0RYLqH+WEjnxsTB1JbaW0wzeygG0/dPHEEFQKn2JE=
github.com/canonical/pebble v1.10.2/go.mod h1:BXpt85cFqrBgACeVRrTQ7JxZIdnGILv32V7mAfDcGFc=
github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b h1:Da2fardddn+JDlVEYtrzBLTtyzoyU3nIS0Cf0GvjmwU=
github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b/go.mod h1:upTK9n6rlqITN9rCN69hdreI37dRDFUk2thlGGD5Cg8=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v3 v3.2.2 h1:cfUAAO3yvKMYKPrvhDuHSwQnhZNk/RMHKdZqKTxfm6M=
github.com/cenkalti/backoff/v3 v3.2.2/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
Expand Down Expand Up @@ -934,8 +928,6 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo=
github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk=
github.com/pkg/term v1.1.0 h1:xIAAdCMh3QIAy+5FrE8Ad8XoDhEU4ufwbaSozViP9kk=
github.com/pkg/term v1.1.0/go.mod h1:E25nymQcrSllhX42Ok8MRm1+hyBdHY0dCeiKZ9jpNGw=
github.com/pkg/xattr v0.4.9 h1:5883YPCtkSd8LFbs13nXplj9g9tlrwoJRjgpgMu1/fE=
github.com/pkg/xattr v0.4.9/go.mod h1:di8WF84zAKk8jzR1UBTEWh9AUlIZZ7M/JNt8e9B6ktU=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
Expand Down Expand Up @@ -1186,8 +1178,6 @@ golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0=
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/net v0.0.0-20150829230318-ea47fc708ee3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180406214816-61147c48b25b/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
Expand Down Expand Up @@ -1310,7 +1300,6 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand Down
15 changes: 8 additions & 7 deletions internal/db/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
package db

var (
JwksKind = jwksKind
JwksPublicKeyTag = jwksPublicKeyTag
JwksPrivateKeyTag = jwksPrivateKeyTag
JwksExpiryTag = jwksExpiryTag
OAuthKind = oauthKind
OAuthKeyTag = oauthKeyTag
NewUUID = &newUUID
JwksKind = jwksKind
JwksPublicKeyTag = jwksPublicKeyTag
JwksPrivateKeyTag = jwksPrivateKeyTag
JwksExpiryTag = jwksExpiryTag
OAuthKind = oauthKind
OAuthKeyTag = oauthKeyTag
OAuthSessionStoreSecretTag = oauthSessionStoreSecretTag
NewUUID = &newUUID
)
55 changes: 45 additions & 10 deletions internal/db/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@ const (
passwordKey = "password"

// These constants are used to create the appropriate identifiers for JWKS related data.
jwksKind = "jwks"
jwksPublicKeyTag = "jwksPublicKey"
jwksPrivateKeyTag = "jwksPrivateKey"
jwksExpiryTag = "jwksExpiry"
oauthKind = "oauth"
oauthKeyTag = "oauthKey"
jwksKind = "jwks"
jwksPublicKeyTag = "jwksPublicKey"
jwksPrivateKeyTag = "jwksPrivateKey"
jwksExpiryTag = "jwksExpiry"
oauthKind = "oauth"
oauthKeyTag = "oauthKey"
oauthSessionStoreSecretTag = "oauthSessionStoreSecret"
)

// UpsertSecret stores secret information.
Expand Down Expand Up @@ -287,10 +288,14 @@ func (d *Database) PutJWKSExpiry(ctx context.Context, expiry time.Time) error {
func (d *Database) CleanupOAuthSecrets(ctx context.Context) error {
const op = errors.Op("database.CleanupOAuthSecrets")
secret := dbmodel.NewSecret(oauthKind, oauthKeyTag, nil)
err := d.DeleteSecret(ctx, &secret)
if err != nil {
zapctx.Error(ctx, "failed to cleanup OAUth key", zap.Error(err))
return errors.E(op, err, "failed to cleanup OAUth key")
if err := d.DeleteSecret(ctx, &secret); err != nil {
zapctx.Error(ctx, "failed to cleanup OAuth key", zap.Error(err))
return errors.E(op, err, "failed to cleanup OAuth key")
}
secret = dbmodel.NewSecret(oauthKind, oauthSessionStoreSecretTag, nil)
if err := d.DeleteSecret(ctx, &secret); err != nil {
zapctx.Error(ctx, "failed to cleanup OAuth session store secret", zap.Error(err))
return errors.E(op, err, "failed to cleanup OAuth session store secret")
}
return nil
}
Expand Down Expand Up @@ -324,3 +329,33 @@ func (d *Database) PutOAuthSecret(ctx context.Context, raw []byte) error {
secret := dbmodel.NewSecret(oauthKind, oauthKeyTag, oauthKey)
return d.UpsertSecret(ctx, &secret)
}

// GetOAuthSessionStoreSecret returns the current secret used to store session tokens.
func (d *Database) GetOAuthSessionStoreSecret(ctx context.Context) ([]byte, error) {
const op = errors.Op("database.GetOAuthSessionStoreSecret")
secret := dbmodel.NewSecret(oauthKind, oauthSessionStoreSecretTag, nil)
err := d.GetSecret(ctx, &secret)
if err != nil {
zapctx.Error(ctx, "failed to get oauth session store secret", zap.Error(err))
return nil, errors.E(op, err)
}
var pem []byte
err = json.Unmarshal(secret.Data, &pem)
if err != nil {
zapctx.Error(ctx, "failed to unmarshal pem data", zap.Error(err))
return nil, errors.E(op, err)
}
return pem, nil
}

// PutOAuthSessionStoreSecret puts a secret into the credentials store for secure storage of session tokens.
func (d *Database) PutOAuthSessionStoreSecret(ctx context.Context, raw []byte) error {
const op = errors.Op("database.PutOAuthSessionStoreSecret")
oauthSessionStoreSecret, err := json.Marshal(raw)
if err != nil {
zapctx.Error(ctx, "failed to marshal pem data", zap.Error(err))
return errors.E(op, err, "failed to marshal oauth session store secret")
}
secret := dbmodel.NewSecret(oauthKind, oauthSessionStoreSecretTag, oauthSessionStoreSecret)
return d.UpsertSecret(ctx, &secret)
}
28 changes: 28 additions & 0 deletions internal/db/secrets_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,3 +307,31 @@ func (s *dbSuite) TestGetOAuthSecretFailsIfNotFound(c *qt.C) {
c.Assert(err, qt.ErrorMatches, "secret not found")
c.Assert(retrieved, qt.IsNil)
}

func (s *dbSuite) TestPutAndGetOAuthSessionStoreSecret(c *qt.C) {
err := s.Database.Migrate(context.Background(), true)
c.Assert(err, qt.Equals, nil)
ctx := context.Background()
key := []byte(uuid.NewString())
c.Assert(s.Database.PutOAuthSessionStoreSecret(ctx, key), qt.IsNil)

secret := dbmodel.Secret{}
tx := s.Database.DB.First(&secret)
c.Assert(tx.Error, qt.IsNil)
c.Assert(secret.Type, qt.Equals, db.OAuthKind)
c.Assert(secret.Tag, qt.Equals, db.OAuthSessionStoreSecretTag)

retrievedKey, err := s.Database.GetOAuthSessionStoreSecret(ctx)
c.Assert(err, qt.IsNil)
c.Assert(retrievedKey, qt.DeepEquals, key)
}

func (s *dbSuite) TestGetOAuthSessionStoreSecretFailsIfNotFound(c *qt.C) {
err := s.Database.Migrate(context.Background(), true)
c.Assert(err, qt.Equals, nil)
ctx := context.Background()

retrieved, err := s.Database.GetOAuthSessionStoreSecret(ctx)
c.Assert(err, qt.ErrorMatches, "secret not found")
c.Assert(retrieved, qt.IsNil)
}
8 changes: 8 additions & 0 deletions internal/jimm/cloudcredential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1781,3 +1781,11 @@ func (s testCloudCredentialAttributeStore) GetOAuthSecret(ctx context.Context) (
func (s testCloudCredentialAttributeStore) PutOAuthSecret(ctx context.Context, raw []byte) error {
return errors.E(errors.CodeNotImplemented)
}

func (s testCloudCredentialAttributeStore) GetOAuthSessionStoreSecret(ctx context.Context) ([]byte, error) {
return nil, errors.E(errors.CodeNotImplemented)
}

func (s testCloudCredentialAttributeStore) PutOAuthSessionStoreSecret(ctx context.Context, raw []byte) error {
return errors.E(errors.CodeNotImplemented)
}
6 changes: 6 additions & 0 deletions internal/jimm/credentials/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,10 @@ type CredentialStore interface {

// PutOAuthSecret puts a HS256 (symmetric encryption) secret into the credentials store for signing OAuth session tokens.
PutOAuthSecret(ctx context.Context, raw []byte) error

// GetOAuthSessionStoreSecret returns the current secret used to store session tokens.
GetOAuthSessionStoreSecret(ctx context.Context) ([]byte, error)

// PutOAuthSessionStoreSecret puts a secret into the credentials store for secure storage of session tokens.
PutOAuthSessionStoreSecret(ctx context.Context, raw []byte) error
}
5 changes: 4 additions & 1 deletion internal/jimmtest/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ import (
)

const (
JWTTestSecret = "test-secret"
// Note that these values are deliberately different to make sure we're not
// reusing/misusing them.
JWTTestSecret = "test-secret"
SessionStoreSecret = "another-test-secret"
)

// A SimpleTester is a simple version of the test interface
Expand Down
31 changes: 30 additions & 1 deletion internal/jimmtest/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ type InMemoryCredentialStore struct {
privateKey []byte
expiry time.Time
oauthKey []byte
oauthSessionStoreSecret []byte
controllerCredentials map[string]controllerCredentials
cloudCredentialAttributes map[string]map[string]string
}
Expand All @@ -33,7 +34,8 @@ type InMemoryCredentialStore struct {
// with some secrets/keys being populated.
func NewInMemoryCredentialStore() *InMemoryCredentialStore {
return &InMemoryCredentialStore{
oauthKey: []byte(JWTTestSecret),
oauthKey: []byte(JWTTestSecret),
oauthSessionStoreSecret: []byte(SessionStoreSecret),
}
}

Expand Down Expand Up @@ -193,6 +195,7 @@ func (s *InMemoryCredentialStore) CleanupOAuthSecrets(ctx context.Context) error
defer s.mu.Unlock()

s.oauthKey = nil
s.oauthSessionStoreSecret = nil
return nil
}

Expand Down Expand Up @@ -221,3 +224,29 @@ func (s *InMemoryCredentialStore) PutOAuthSecret(ctx context.Context, raw []byte

return nil
}

// GetOAuthSessionStoreSecret returns the current secret used to store session tokens.
func (s *InMemoryCredentialStore) GetOAuthSessionStoreSecret(ctx context.Context) ([]byte, error) {
s.mu.RLock()
defer s.mu.RUnlock()

if s.oauthSessionStoreSecret == nil || len(s.oauthSessionStoreSecret) == 0 {
return nil, errors.E(errors.CodeNotFound)
}

key := make([]byte, len(s.oauthSessionStoreSecret))
copy(key, s.oauthSessionStoreSecret)

return key, nil
}

// PutOAuthSessionStoreSecret puts a secret into the credentials store for secure storage of session tokens.
func (s *InMemoryCredentialStore) PutOAuthSessionStoreSecret(ctx context.Context, raw []byte) error {
s.mu.Lock()
defer s.mu.Unlock()

s.oauthSessionStoreSecret = make([]byte, len(raw))
copy(s.oauthSessionStoreSecret, raw)

return nil
}
Loading

0 comments on commit 6d1cc50

Please sign in to comment.