Skip to content

Commit

Permalink
CSS-7081 Add OAuth-specific methods to secrets store (#1175)
Browse files Browse the repository at this point in the history
* Add `Get/Put` OAuth key methods to Vault store

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

* Add `Get/Put` OAuth key methods to Postgres store

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

* Add `Get/Put` OAuth key methods to in-memory store

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

* Add `Get/Put` OAuth key methods to mock store

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

* Add `Get/Put` OAuth key methods to credential store interface

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

* Expose underlying `CredentialStore` via a `JIMM` interface method

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

* Use `New*` method to instantiate in-memory credential store

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

* Use credential store to retrieve OAuth session JWT secret key

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

* Update `CredentialStore` godoc

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

* Add test to verify `GetOAuthKey` returns not found error

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

* Add test to verify `GetOAuthKey` returns not found error

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

* Add `CheckOrGenerateOAuthKey` method

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

* Generate OAuth key on the leader unit

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

* Update suite to generate OAuth key as well

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

* Add package godoc

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

* Reuse shared `JWTTestSecret` in `JimmCmdSuite`

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

* Fix godoc

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

* Add `CleanupOAuth` to Postgres store

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

* Add `CleanupOAuth` to Vault store

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

* Add `CleanupOAuth` to mock store

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

* Add `CleanupOAuth` to in-memory store

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

* Add `CleanupOAuth` to credential store interface

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

* Use same const secret for in-memory store

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

* fix tests with populating OAuth key secrets in store

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

* Use `*WithContext` variants for read/write methods

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

* Use `net.Listen` to find an available TCP port

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

* Rename `CleanupOAuth` to `CleanupOAuthSecrets`

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

* Rename credential store `*OAuthKey` methods to `*OAuthSecret`

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

* Run `go mod tidy`

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

---------

Signed-off-by: Babak K. Shandiz <[email protected]>
  • Loading branch information
babakks authored Mar 19, 2024
1 parent cbea413 commit ef3e14f
Show file tree
Hide file tree
Showing 22 changed files with 382 additions and 79 deletions.
12 changes: 8 additions & 4 deletions cmd/jimmsrv/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,17 @@ func start(ctx context.Context, s *service.Service) error {
s.Go(func() error { return jimmsvc.WatchModelSummaries(ctx) })

if os.Getenv("JIMM_ENABLE_JWKS_ROTATOR") != "" {
zapctx.Info(ctx, "attempting to start JWKS rotator")
zapctx.Info(ctx, "attempting to start JWKS rotator and generate OAuth secret key")
s.Go(func() error {
err := jimmsvc.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0))
if err != nil {
if err := jimmsvc.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0)); err != nil {
zapctx.Error(ctx, "failed to start JWKS rotator", zap.Error(err))
return err
}
return err
if err := jimmsvc.CheckOrGenerateOAuthKey(ctx); err != nil {
zapctx.Error(ctx, "failed to check/generate OAuth secret key", zap.Error(err))
return err
}
return nil
})
}

Expand Down
7 changes: 0 additions & 7 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,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.9.0 // indirect
github.com/canonical/x-go v0.0.0-20230522092633-7947a7587f5b // indirect
github.com/cenkalti/backoff/v3 v3.0.0 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/cjlapao/common-go v0.0.39 // indirect
Expand Down Expand Up @@ -144,7 +141,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/gorilla/sessions v1.2.1 // indirect
Expand Down Expand Up @@ -262,7 +258,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 @@ -302,12 +297,10 @@ require (
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.19.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.17.0 // indirect
golang.org/x/term v0.17.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
21 changes: 0 additions & 21 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.9.0 h1:FWVEh1fg3aaW2HNue2Z2eYMwkJEQT8mgMFW3R5Iocn4=
github.com/canonical/pebble v1.9.0/go.mod h1:9Qkjmq298g0+9SvM2E5eekkEN4pjHDWhgg9eB2I0tjk=
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 h1:ske+9nBpD9qZsTBoF41nW5L+AIuFBKMeze18XQ3eG1c=
github.com/cenkalti/backoff/v3 v3.0.0/go.mod h1:cIeZDE3IrqwwJl6VUwCN6trj1oXrTS4rc0ij+ULvLYs=
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
Expand Down Expand Up @@ -727,8 +721,6 @@ github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4d
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kian99/juju v0.0.0-20240301094235-2688d7cd925e h1:MnSWbm0Th+V7YI61C4ledtaxg564bzwBdh665fj/MeM=
github.com/kian99/juju v0.0.0-20240301094235-2688d7cd925e/go.mod h1:V5eSJgiG7Evs4ejKhI7na7olYzHR1rxZXwx1/27Sa18=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
Expand Down Expand Up @@ -922,8 +914,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 @@ -1128,8 +1118,6 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k=
golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
Expand Down Expand Up @@ -1170,8 +1158,6 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
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.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 @@ -1220,8 +1206,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
golang.org/x/net v0.0.0-20211216030914-fe4d6282115f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
golang.org/x/net v0.19.0 h1:zTwKpTd2XuCqf8huc7Fo2iSy+4RHPd10s4KzeTnVr1c=
golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
Expand Down Expand Up @@ -1291,7 +1275,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 Expand Up @@ -1320,8 +1303,6 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
Expand All @@ -1330,8 +1311,6 @@ golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4=
golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0=
golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
3 changes: 3 additions & 0 deletions internal/cmdtest/jimmsuite.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,9 @@ func (s *JimmCmdSuite) SetUpTest(c *gc.C) {
err = s.Service.StartJWKSRotator(ctx, time.NewTicker(time.Hour).C, time.Now().UTC().AddDate(0, 3, 0))
c.Assert(err, gc.Equals, nil)

err = s.JIMM.GetCredentialStore().PutOAuthSecret(ctx, []byte(jimmtest.JWTTestSecret))
c.Assert(err, gc.Equals, nil)

s.HTTP.StartTLS()

// NOW we can set up the juju conn suites
Expand Down
2 changes: 2 additions & 0 deletions internal/db/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ var (
JwksPublicKeyTag = jwksPublicKeyTag
JwksPrivateKeyTag = jwksPrivateKeyTag
JwksExpiryTag = jwksExpiryTag
OAuthKind = oauthKind
OAuthKeyTag = oauthKeyTag
)
44 changes: 44 additions & 0 deletions internal/db/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ const (
jwksPublicKeyTag = "jwksPublicKey"
jwksPrivateKeyTag = "jwksPrivateKey"
jwksExpiryTag = "jwksExpiry"
oauthKind = "oauth"
oauthKeyTag = "oauthKey"
)

// UpsertSecret stores secret information.
Expand Down Expand Up @@ -280,3 +282,45 @@ func (d *Database) PutJWKSExpiry(ctx context.Context, expiry time.Time) error {
secret := dbmodel.NewSecret(jwksKind, jwksExpiryTag, expiryJson)
return d.UpsertSecret(ctx, &secret)
}

// CleanupOAuthSecrets removes all secrets associated with OAuth.
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")
}
return nil
}

// GetOAuthSecret returns the current HS256 (symmetric encryption) secret used to sign OAuth session tokens.
func (d *Database) GetOAuthSecret(ctx context.Context) ([]byte, error) {
const op = errors.Op("database.GetOAuthSecret")
secret := dbmodel.NewSecret(oauthKind, oauthKeyTag, nil)
err := d.GetSecret(ctx, &secret)
if err != nil {
zapctx.Error(ctx, "failed to get oauth key", 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
}

// PutOAuthSecret puts a HS256 (symmetric encryption) secret into the credentials store for signing OAuth session tokens.
func (d *Database) PutOAuthSecret(ctx context.Context, raw []byte) error {
const op = errors.Op("database.PutOAuthSecret")
oauthKey, 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 key")
}
secret := dbmodel.NewSecret(oauthKind, oauthKeyTag, oauthKey)
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 @@ -279,3 +279,31 @@ func (s *dbSuite) TestCleanupJWKS(c *qt.C) {
c.Assert(s.Database.DB.Model(&dbmodel.Secret{}).Count(&count).Error, qt.IsNil)
c.Assert(count, qt.Equals, int64(0))
}

func (s *dbSuite) TestPutAndGetOAuthSecret(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.PutOAuthSecret(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.OAuthKeyTag)

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

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

retrieved, err := s.Database.GetOAuthSecret(ctx)
c.Assert(err, qt.ErrorMatches, "secret not found")
c.Assert(retrieved, qt.IsNil)
}
12 changes: 12 additions & 0 deletions internal/jimm/cloudcredential_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1770,3 +1770,15 @@ func (s testCloudCredentialAttributeStore) PutJWKSExpiry(ctx context.Context, ex
func (s testCloudCredentialAttributeStore) CleanupJWKS(ctx context.Context) error {
return errors.E(errors.CodeNotImplemented)
}

func (s testCloudCredentialAttributeStore) CleanupOAuthSecrets(ctx context.Context) error {
return errors.E(errors.CodeNotImplemented)
}

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

func (s testCloudCredentialAttributeStore) PutOAuthSecret(ctx context.Context, raw []byte) error {
return errors.E(errors.CodeNotImplemented)
}
12 changes: 12 additions & 0 deletions internal/jimm/credentials/credentials.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
// Copyright 2023 canonical.

// Package credentials provides abstractions/definitions for credential storage
// backends and caching mechanisms.
package credentials

import (
Expand All @@ -16,6 +18,7 @@ import (
// - JWK Set
// - JWK expiry
// - JWK private key
// - OAuth session signing secret
type CredentialStore interface {
// Get retrieves the stored attributes of a cloud credential.
Get(context.Context, names.CloudCredentialTag) (map[string]string, error)
Expand Down Expand Up @@ -51,4 +54,13 @@ type CredentialStore interface {

// PutJWKSExpiry sets the expiry time for the current JWKS within the store.
PutJWKSExpiry(ctx context.Context, expiry time.Time) error

// CleanupOAuthSecrets removes all secrets associated with OAuth.
CleanupOAuthSecrets(ctx context.Context) error

// GetOAuthSecret returns the current HS256 (symmetric encryption) secret used to sign OAuth session tokens.
GetOAuthSecret(ctx context.Context) ([]byte, error)

// PutOAuthSecret puts a HS256 (symmetric encryption) secret into the credentials store for signing OAuth session tokens.
PutOAuthSecret(ctx context.Context, raw []byte) error
}
5 changes: 5 additions & 0 deletions internal/jimm/jimm.go
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,11 @@ type OAuthAuthenticator interface {
VerifyClientCredentials(ctx context.Context, clientID string, clientSecret string) error
}

// GetCredentialStore returns the credential store used by JIMM.
func (j *JIMM) GetCredentialStore() credentials.CredentialStore {
return j.CredentialStore
}

type permission struct {
resource string
relation string
Expand Down
4 changes: 2 additions & 2 deletions internal/jimm/jimm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,7 +696,7 @@ func TestFillMigrationTarget(t *testing.T) {
err := db.Migrate(ctx, false)
c.Assert(err, qt.IsNil)

store := &jimmtest.InMemoryCredentialStore{}
store := jimmtest.NewInMemoryCredentialStore()
err = store.PutControllerCredentials(context.Background(), test.controllerName, "admin", "test-secret")
c.Assert(err, qt.IsNil)

Expand Down Expand Up @@ -775,7 +775,7 @@ func TestInitiateInternalMigration(t *testing.T) {
c.Patch(jimm.InitiateMigration, func(ctx context.Context, j *jimm.JIMM, user *openfga.User, spec jujuparams.MigrationSpec, targetID uint) (jujuparams.InitiateMigrationResult, error) {
return jujuparams.InitiateMigrationResult{}, nil
})
store := &jimmtest.InMemoryCredentialStore{}
store := jimmtest.NewInMemoryCredentialStore()
err := store.PutControllerCredentials(context.Background(), test.migrateInfo.TargetController, "admin", "test-secret")
c.Assert(err, qt.IsNil)

Expand Down
17 changes: 6 additions & 11 deletions internal/jimmhttp/auth_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ import (
"context"
"fmt"
"io"
"math/rand"
"net"
"net/http"
"net/http/cookiejar"
"net/http/httptest"
"net/url"
"regexp"
"strconv"
"testing"
"time"

Expand Down Expand Up @@ -42,17 +40,14 @@ func setupDbAndSessionStore(c *qt.C) (*db.Database, *pgstore.PGStore) {
}

func setupTestServer(c *qt.C, dashboardURL string, db *db.Database, sessionStore *pgstore.PGStore) *httptest.Server {
// Find a random free TCP port.
listener, err := net.Listen("tcp", "127.0.0.1:0")
c.Assert(err, qt.IsNil)
port := fmt.Sprintf("%d", listener.Addr().(*net.TCPAddr).Port)

// Create unstarted server to enable auth service
s := httptest.NewUnstartedServer(nil)
// Setup random port listener
minPort := 30000
maxPort := 50000

port := strconv.Itoa(rand.Intn(maxPort-minPort+1) + minPort)
l, err := net.Listen("tcp", "localhost:"+port)
c.Assert(err, qt.IsNil)
// Set the listener with a random port
s.Listener = l
s.Listener = listener

// Remember redirect url to check it matches after test server starts
redirectURL := "http://127.0.0.1:" + port + "/callback"
Expand Down
6 changes: 3 additions & 3 deletions internal/jimmtest/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
"github.com/canonical/jimm/internal/openfga"
)

var (
jwtTestSecret = "test-secret"
const (
JWTTestSecret = "test-secret"
)

// A SimpleTester is a simple version of the test interface
Expand Down Expand Up @@ -70,7 +70,7 @@ func NewUserSessionLogin(c SimpleTester, username string) api.LoginProvider {
c.Fatalf("failed to generate test session token")
}

freshToken, err := jwt.Sign(token, jwt.WithKey(jwa.HS256, []byte(jwtTestSecret)))
freshToken, err := jwt.Sign(token, jwt.WithKey(jwa.HS256, []byte(JWTTestSecret)))
if err != nil {
c.Fatalf("failed to sign test session token")
}
Expand Down
Loading

0 comments on commit ef3e14f

Please sign in to comment.