mirror of
https://github.com/zitadel/zitadel.git
synced 2025-02-28 23:27:23 +00:00
fix: improve key rotation (#1328)
* fix: improve key rotation * update oidc pkg version
This commit is contained in:
parent
428ef4acdb
commit
57b277bc7c
@ -137,4 +137,5 @@ SystemDefaults:
|
||||
PublicKeyLifetime: 30h
|
||||
EncryptionConfig:
|
||||
EncryptionKeyID: $ZITADEL_OIDC_KEYS_ID
|
||||
SigningKeyRotation: 10s
|
||||
SigningKeyRotationCheck: 10s
|
||||
SigningKeyGracefulPeriod: 10m
|
4
go.mod
4
go.mod
@ -16,14 +16,14 @@ require (
|
||||
github.com/allegro/bigcache v1.2.1
|
||||
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc
|
||||
github.com/caos/logging v0.0.2
|
||||
github.com/caos/oidc v0.14.0
|
||||
github.com/caos/oidc v0.14.1
|
||||
github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0
|
||||
github.com/cockroachdb/cockroach-go/v2 v2.1.0
|
||||
github.com/duo-labs/webauthn v0.0.0-20200714211715-1daaee874e43
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0
|
||||
github.com/ghodss/yaml v1.0.0
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/golang/mock v1.4.4
|
||||
github.com/golang/mock v1.5.0
|
||||
github.com/golang/protobuf v1.4.3
|
||||
github.com/golang/snappy v0.0.2 // indirect
|
||||
github.com/google/go-cmp v0.5.3 // indirect
|
||||
|
12
go.sum
12
go.sum
@ -140,8 +140,8 @@ github.com/caos/logging v0.0.0-20191210002624-b3260f690a6a/go.mod h1:9LKiDE2ChuG
|
||||
github.com/caos/logging v0.0.2 h1:ebg5C/HN0ludYR+WkvnFjwSExF4wvyiWPyWGcKMYsoo=
|
||||
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
|
||||
github.com/caos/oidc v0.6.2/go.mod h1:ozoi3b+aY33gzdvjz4w90VZShIHGsmDa0goruuV0arQ=
|
||||
github.com/caos/oidc v0.14.0 h1:l7mTqYDpqNRZF9Vwzq5KAQd1wQCThdceL5HpsEMGoao=
|
||||
github.com/caos/oidc v0.14.0/go.mod h1:CPsubVrA110OyLnCKwVZjTdsAVwq67DTbYIvux7UgbY=
|
||||
github.com/caos/oidc v0.14.1 h1:MZ1wKomUnGeOjNWClf3dtcxqSKd2o37WXf0fpshSb0k=
|
||||
github.com/caos/oidc v0.14.1/go.mod h1:fSLPGlxZhjSMP2LYKZ5QMaM/YYmLHfj/Fce+ji48kYY=
|
||||
github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0 h1:N+KYBwuQO3QPr/nTUaNwjAetjp3NU4MP8Nv9Iue53UE=
|
||||
github.com/caos/orbos v1.5.14-0.20210205131708-6dc812182dc0/go.mod h1:ZLxNgPuYIlSvr80trezGGUIXng9gY2hHEdky/m0B/P0=
|
||||
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
|
||||
@ -338,6 +338,8 @@ github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt
|
||||
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
|
||||
github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc=
|
||||
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
|
||||
github.com/golang/mock v1.5.0 h1:jlYHihg//f7RRwuPfptm04yp4s7O6Kw8EZiVYIGcH0g=
|
||||
github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8=
|
||||
github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
@ -598,6 +600,8 @@ github.com/lithammer/dedent v1.1.0/go.mod h1:jrXYCQtgg0nJiN+StA2KgR7w6CiQNv9Fd/Z
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw=
|
||||
github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI=
|
||||
github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ=
|
||||
github.com/magefile/mage v1.10.0 h1:3HiXzCUY12kh9bIuyXShaVe529fJfyqoVM42o/uom2g=
|
||||
github.com/magefile/mage v1.10.0/go.mod h1:z5UZb/iS3GoOSn0JgWuiw7dxlurVYTu+/jHXqQg881A=
|
||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
github.com/mailru/easyjson v0.0.0-20180823135443-60711f1a8329/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||
@ -783,8 +787,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
|
||||
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM=
|
||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.8.0 h1:nfhvjKcUMhBMVqbKHJlk5RPrrfYr/NMo3692g0dwfWU=
|
||||
github.com/sirupsen/logrus v1.8.0/go.mod h1:4GuYW9TZmE769R5STWrRakJc4UqQ3+QQ95fyz7ENv1A=
|
||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||
|
@ -120,8 +120,8 @@ func (o *OPStorage) TerminateSession(ctx context.Context, userID, clientID strin
|
||||
return o.command.HumansSignOut(ctx, userAgentID, userIDs)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, timer <-chan time.Time) {
|
||||
o.repo.GetSigningKey(ctx, keyCh, errCh, timer)
|
||||
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
|
||||
o.repo.GetSigningKey(ctx, keyCh, o.signingKeyAlgorithm)
|
||||
}
|
||||
|
||||
func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err error) {
|
||||
@ -130,10 +130,6 @@ func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err e
|
||||
return o.repo.GetKeySet(ctx)
|
||||
}
|
||||
|
||||
func (o *OPStorage) SaveNewKeyPair(ctx context.Context) error {
|
||||
return o.command.GenerateSigningKeyPair(ctx, o.signingKeyAlgorithm)
|
||||
}
|
||||
|
||||
func (o *OPStorage) assertProjectRoleScopes(app *proj_model.ApplicationView, scopes []string) ([]string, error) {
|
||||
if !app.ProjectRoleAssertion {
|
||||
return scopes, nil
|
||||
|
@ -79,7 +79,6 @@ func NewProvider(ctx context.Context, config OPHandlerConfig, command *command.C
|
||||
op.WithCustomUserinfoEndpoint(op.NewEndpointWithURL(config.Endpoints.Userinfo.Path, config.Endpoints.Userinfo.URL)),
|
||||
op.WithCustomEndSessionEndpoint(op.NewEndpointWithURL(config.Endpoints.EndSession.Path, config.Endpoints.EndSession.URL)),
|
||||
op.WithCustomKeysEndpoint(op.NewEndpointWithURL(config.Endpoints.Keys.Path, config.Endpoints.Keys.URL)),
|
||||
op.WithRetry(3, time.Duration(30*time.Second)),
|
||||
)
|
||||
logging.Log("OIDC-asf13").OnError(err).WithField("traceID", tracing.TraceIDFromCtx(ctx)).Panic("cannot create provider")
|
||||
return provider
|
||||
|
@ -2,27 +2,58 @@ package eventstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
"github.com/caos/zitadel/internal/crypto"
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
"github.com/caos/zitadel/internal/key/model"
|
||||
key_view "github.com/caos/zitadel/internal/key/repository/view"
|
||||
"github.com/caos/zitadel/internal/v2/command"
|
||||
)
|
||||
|
||||
type KeyRepository struct {
|
||||
Commands *command.CommandSide
|
||||
Eventstore *eventstore.Eventstore
|
||||
View *view.View
|
||||
SigningKeyRotation time.Duration
|
||||
SigningKeyRotationCheck time.Duration
|
||||
SigningKeyGracefulPeriod time.Duration
|
||||
KeyAlgorithm crypto.EncryptionAlgorithm
|
||||
KeyChan <-chan *model.KeyView
|
||||
Locker spooler.Locker
|
||||
lockID string
|
||||
currentKeyID string
|
||||
currentKeyExpiration time.Time
|
||||
}
|
||||
|
||||
func (k *KeyRepository) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, renewTimer <-chan time.Time) {
|
||||
const (
|
||||
signingKey = "signing_key"
|
||||
)
|
||||
|
||||
func (k *KeyRepository) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, algorithm string) {
|
||||
renewTimer := time.After(0)
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case key := <-k.KeyChan:
|
||||
refreshed, err := k.refreshSigningKey(ctx, key, keyCh, algorithm)
|
||||
logging.Log("KEY-asd5g").OnError(err).Error("could not refresh signing key on key channel push")
|
||||
k.setRenewTimer(renewTimer, refreshed)
|
||||
case <-renewTimer:
|
||||
k.refreshSigningKey(keyCh, errCh)
|
||||
renewTimer = time.After(k.SigningKeyRotation)
|
||||
key, err := k.latestSigningKey()
|
||||
logging.Log("KEY-DAfh4").OnError(err).Error("could not check for latest signing key")
|
||||
refreshed, err := k.refreshSigningKey(ctx, key, keyCh, algorithm)
|
||||
logging.Log("KEY-DAfh4").OnError(err).Error("could not refresh signing key when ensuring key")
|
||||
k.setRenewTimer(renewTimer, refreshed)
|
||||
}
|
||||
}
|
||||
}()
|
||||
@ -40,17 +71,98 @@ func (k *KeyRepository) GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, err
|
||||
return &jose.JSONWebKeySet{Keys: webKeys}, nil
|
||||
}
|
||||
|
||||
func (k *KeyRepository) refreshSigningKey(keyCh chan<- jose.SigningKey, errCh chan<- error) {
|
||||
key, err := k.View.GetSigningKey()
|
||||
if err != nil {
|
||||
errCh <- err
|
||||
return
|
||||
func (k *KeyRepository) setRenewTimer(timer <-chan time.Time, refreshed bool) {
|
||||
duration := k.SigningKeyRotationCheck
|
||||
if refreshed {
|
||||
duration = k.currentKeyExpiration.Sub(time.Now().Add(k.SigningKeyGracefulPeriod + k.SigningKeyRotationCheck*2))
|
||||
}
|
||||
timer = time.After(duration)
|
||||
}
|
||||
|
||||
func (k *KeyRepository) latestSigningKey() (shortRefresh *model.KeyView, err error) {
|
||||
key, errView := k.View.GetActivePrivateKeyForSigning(time.Now().UTC().Add(k.SigningKeyGracefulPeriod))
|
||||
if errView != nil && !errors.IsNotFound(errView) {
|
||||
logging.Log("EVENT-GEd4h").WithError(errView).Warn("could not get signing key")
|
||||
return nil, errView
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (k *KeyRepository) ensureIsLatestKey(ctx context.Context) (bool, error) {
|
||||
sequence, err := k.View.GetLatestKeySequence()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
events, err := k.getKeyEvents(ctx, sequence.CurrentSequence)
|
||||
if err != nil {
|
||||
logging.Log("EVENT-der5g").WithError(err).Warn("error retrieving new events")
|
||||
return false, err
|
||||
}
|
||||
if len(events) > 0 {
|
||||
logging.Log("EVENT-GBD23").Warn("view not up to date, retrying later")
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (k *KeyRepository) refreshSigningKey(ctx context.Context, key *model.KeyView, keyCh chan<- jose.SigningKey, algorithm string) (refreshed bool, err error) {
|
||||
if key == nil {
|
||||
if k.currentKeyExpiration.Before(time.Now().UTC()) {
|
||||
keyCh <- jose.SigningKey{}
|
||||
}
|
||||
if ok, err := k.ensureIsLatestKey(ctx); !ok && err == nil {
|
||||
return false, err
|
||||
}
|
||||
err = k.lockAndGenerateSigningKeyPair(ctx, algorithm)
|
||||
logging.Log("EVENT-B4d21").OnError(err).Warn("could not create signing key")
|
||||
return false, err
|
||||
}
|
||||
if k.currentKeyID == key.ID {
|
||||
return false, nil
|
||||
}
|
||||
if ok, err := k.ensureIsLatestKey(ctx); !ok && err == nil {
|
||||
return false, err
|
||||
}
|
||||
signingKey, err := model.SigningKeyFromKeyView(key, k.KeyAlgorithm)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
k.currentKeyID = signingKey.ID
|
||||
k.currentKeyExpiration = key.Expiry
|
||||
keyCh <- jose.SigningKey{
|
||||
Algorithm: jose.SignatureAlgorithm(key.Algorithm),
|
||||
Algorithm: jose.SignatureAlgorithm(signingKey.Algorithm),
|
||||
Key: jose.JSONWebKey{
|
||||
KeyID: key.ID,
|
||||
Key: key.Key,
|
||||
KeyID: signingKey.ID,
|
||||
Key: signingKey.Key,
|
||||
},
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (k *KeyRepository) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error {
|
||||
err := k.Locker.Renew(k.lockerID(), signingKey, k.SigningKeyRotationCheck*2)
|
||||
if err != nil {
|
||||
if errors.IsErrorAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
return k.Commands.GenerateSigningKeyPair(ctx, algorithm)
|
||||
}
|
||||
|
||||
func (k *KeyRepository) lockerID() string {
|
||||
if k.lockID == "" {
|
||||
var err error
|
||||
k.lockID, err = os.Hostname()
|
||||
if err != nil || k.lockID == "" {
|
||||
k.lockID, err = id.SonyFlakeGenerator.Next()
|
||||
logging.Log("EVENT-bsdf6").OnError(err).Panic("unable to generate lockID")
|
||||
}
|
||||
context.TODO()
|
||||
}
|
||||
return k.lockID
|
||||
}
|
||||
|
||||
func (k *KeyRepository) getKeyEvents(ctx context.Context, sequence uint64) ([]eventstore.EventReader, error) {
|
||||
return k.Eventstore.FilterEvents(ctx, key_view.KeyPairQuery(sequence))
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/caos/zitadel/internal/config/types"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/query"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
)
|
||||
|
||||
type Configs map[string]*Config
|
||||
@ -29,7 +30,7 @@ func (h *handler) Eventstore() eventstore.Eventstore {
|
||||
return h.es
|
||||
}
|
||||
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es eventstore.Eventstore, systemDefaults sd.SystemDefaults) []query.Handler {
|
||||
func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es eventstore.Eventstore, systemDefaults sd.SystemDefaults, keyChan chan<- *key_model.KeyView) []query.Handler {
|
||||
return []query.Handler{
|
||||
newUser(
|
||||
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
|
||||
@ -41,7 +42,8 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
|
||||
newToken(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Token"), errorCount, es}),
|
||||
newKey(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount, es}),
|
||||
handler{view, bulkLimit, configs.cycleDuration("Key"), errorCount, es},
|
||||
keyChan),
|
||||
newApplication(handler{view, bulkLimit, configs.cycleDuration("Application"), errorCount, es}),
|
||||
newOrg(
|
||||
handler{view, bulkLimit, configs.cycleDuration("Org"), errorCount, es}),
|
||||
|
@ -4,10 +4,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
"github.com/caos/zitadel/internal/eventstore/query"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/key/model"
|
||||
"github.com/caos/zitadel/internal/key/repository/eventsourcing"
|
||||
es_model "github.com/caos/zitadel/internal/key/repository/eventsourcing/model"
|
||||
view_model "github.com/caos/zitadel/internal/key/repository/view/model"
|
||||
@ -20,11 +22,13 @@ const (
|
||||
type Key struct {
|
||||
handler
|
||||
subscription *eventstore.Subscription
|
||||
keyChan chan<- *model.KeyView
|
||||
}
|
||||
|
||||
func newKey(handler handler) *Key {
|
||||
func newKey(handler handler, keyChan chan<- *model.KeyView) *Key {
|
||||
h := &Key{
|
||||
handler: handler,
|
||||
keyChan: keyChan,
|
||||
}
|
||||
|
||||
h.subscribe()
|
||||
@ -75,7 +79,12 @@ func (k *Key) Reduce(event *models.Event) error {
|
||||
if privateKey.Expiry.Before(time.Now()) && publicKey.Expiry.Before(time.Now()) {
|
||||
return k.view.ProcessedKeySequence(event)
|
||||
}
|
||||
return k.view.PutKeys(privateKey, publicKey, event)
|
||||
err = k.view.PutKeys(privateKey, publicKey, event)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
k.keyChan <- view_model.KeyViewToModel(privateKey)
|
||||
return nil
|
||||
default:
|
||||
return k.view.ProcessedKeySequence(event)
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
es_int "github.com/caos/zitadel/internal/eventstore"
|
||||
es_spol "github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
"github.com/caos/zitadel/internal/id"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
"github.com/caos/zitadel/internal/v2/command"
|
||||
"github.com/caos/zitadel/internal/v2/query"
|
||||
)
|
||||
@ -75,7 +76,9 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
return nil, err
|
||||
}
|
||||
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults)
|
||||
keyChan := make(chan *key_model.KeyView)
|
||||
spool := spooler.StartSpooler(conf.Spooler, es, view, sqlClient, systemDefaults, keyChan)
|
||||
locker := spooler.NewLocker(sqlClient)
|
||||
|
||||
userRepo := eventstore.UserRepo{
|
||||
SearchLimit: conf.SearchLimit,
|
||||
@ -108,12 +111,15 @@ func Start(conf Config, authZ authz.Config, systemDefaults sd.SystemDefaults, co
|
||||
IAMID: systemDefaults.IamID,
|
||||
},
|
||||
eventstore.TokenRepo{
|
||||
Eventstore: es,
|
||||
View: view,
|
||||
},
|
||||
eventstore.KeyRepository{
|
||||
View: view,
|
||||
SigningKeyRotation: systemDefaults.KeyConfig.SigningKeyRotation.Duration,
|
||||
SigningKeyRotationCheck: systemDefaults.KeyConfig.SigningKeyRotationCheck.Duration,
|
||||
SigningKeyGracefulPeriod: systemDefaults.KeyConfig.SigningKeyGracefulPeriod.Duration,
|
||||
KeyAlgorithm: keyAlgorithm,
|
||||
Locker: locker,
|
||||
KeyChan: keyChan,
|
||||
},
|
||||
eventstore.ApplicationRepo{
|
||||
Commands: command,
|
||||
|
@ -15,6 +15,10 @@ type locker struct {
|
||||
dbClient *sql.DB
|
||||
}
|
||||
|
||||
func NewLocker(client *sql.DB) *locker {
|
||||
return &locker{dbClient: client}
|
||||
}
|
||||
|
||||
func (l *locker) Renew(lockerID, viewModel string, waitTime time.Duration) error {
|
||||
return es_locker.Renew(l.dbClient, lockTable, lockerID, viewModel, waitTime)
|
||||
}
|
||||
|
@ -3,13 +3,12 @@ package spooler
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/handler"
|
||||
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
|
||||
|
||||
sd "github.com/caos/zitadel/internal/config/systemdefaults"
|
||||
"github.com/caos/zitadel/internal/eventstore"
|
||||
"github.com/caos/zitadel/internal/eventstore/spooler"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
)
|
||||
|
||||
type SpoolerConfig struct {
|
||||
@ -19,12 +18,12 @@ type SpoolerConfig struct {
|
||||
Handlers handler.Configs
|
||||
}
|
||||
|
||||
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, client *sql.DB, systemDefaults sd.SystemDefaults) *spooler.Spooler {
|
||||
func StartSpooler(c SpoolerConfig, es eventstore.Eventstore, view *view.View, client *sql.DB, systemDefaults sd.SystemDefaults, keyChan chan<- *key_model.KeyView) *spooler.Spooler {
|
||||
spoolerConfig := spooler.Config{
|
||||
Eventstore: es,
|
||||
Locker: &locker{dbClient: client},
|
||||
ConcurrentWorkers: c.ConcurrentWorkers,
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, systemDefaults),
|
||||
ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, systemDefaults, keyChan),
|
||||
}
|
||||
spool := spoolerConfig.New()
|
||||
spool.Start()
|
||||
|
@ -1,6 +1,8 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/eventstore/models"
|
||||
key_model "github.com/caos/zitadel/internal/key/model"
|
||||
@ -17,12 +19,21 @@ func (v *View) KeyByIDAndType(keyID string, private bool) (*model.KeyView, error
|
||||
return view.KeyByIDAndType(v.Db, keyTable, keyID, private)
|
||||
}
|
||||
|
||||
func (v *View) GetSigningKey() (*key_model.SigningKey, error) {
|
||||
key, err := view.GetSigningKey(v.Db, keyTable)
|
||||
func (v *View) GetActivePrivateKeyForSigning(expiry time.Time) (*key_model.KeyView, error) {
|
||||
key, err := view.GetSigningKey(v.Db, keyTable, expiry)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return key_model.SigningKeyFromKeyView(model.KeyViewToModel(key), v.keyAlgorithm)
|
||||
return model.KeyViewToModel(key), nil
|
||||
}
|
||||
|
||||
func (v *View) GetSigningKey(expiry time.Time) (*key_model.SigningKey, time.Time, error) {
|
||||
key, err := view.GetSigningKey(v.Db, keyTable, expiry)
|
||||
if err != nil {
|
||||
return nil, time.Time{}, err
|
||||
}
|
||||
signingKey, err := key_model.SigningKeyFromKeyView(model.KeyViewToModel(key), v.keyAlgorithm)
|
||||
return signingKey, key.Expiry, err
|
||||
}
|
||||
|
||||
func (v *View) GetActiveKeySet() ([]*key_model.PublicKey, error) {
|
||||
|
@ -2,12 +2,11 @@ package repository
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
)
|
||||
|
||||
type KeyRepository interface {
|
||||
GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, errCh chan<- error, timer <-chan time.Time)
|
||||
GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, algorithm string)
|
||||
GetKeySet(ctx context.Context) (*jose.JSONWebKeySet, error)
|
||||
}
|
||||
|
@ -105,5 +105,6 @@ type KeyConfig struct {
|
||||
PrivateKeyLifetime types.Duration
|
||||
PublicKeyLifetime types.Duration
|
||||
EncryptionConfig *crypto.KeyConfig
|
||||
SigningKeyRotation types.Duration
|
||||
SigningKeyRotationCheck types.Duration
|
||||
SigningKeyGracefulPeriod types.Duration
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ type SigningKey struct {
|
||||
ID string
|
||||
Algorithm string
|
||||
Key interface{}
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
type PublicKey struct {
|
||||
@ -84,6 +85,7 @@ func SigningKeyFromKeyView(key *KeyView, alg crypto.EncryptionAlgorithm) (*Signi
|
||||
ID: key.ID,
|
||||
Algorithm: key.Algorithm,
|
||||
Key: privateKey,
|
||||
Sequence: key.Sequence,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -3,6 +3,7 @@ package view
|
||||
import (
|
||||
"time"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/view/repository"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
@ -22,15 +23,30 @@ func KeyByIDAndType(db *gorm.DB, table, keyID string, private bool) (*model.KeyV
|
||||
return key, err
|
||||
}
|
||||
|
||||
func GetSigningKey(db *gorm.DB, table string) (*model.KeyView, error) {
|
||||
key := new(model.KeyView)
|
||||
query := repository.PrepareGetByQuery(table,
|
||||
model.KeySearchQuery{Key: key_model.KeySearchKeyPrivate, Method: global_model.SearchMethodEquals, Value: true},
|
||||
model.KeySearchQuery{Key: key_model.KeySearchKeyUsage, Method: global_model.SearchMethodEquals, Value: key_model.KeyUsageSigning},
|
||||
model.KeySearchQuery{Key: key_model.KeySearchKeyExpiry, Method: global_model.SearchMethodGreaterThan, Value: time.Now().UTC()},
|
||||
func GetSigningKey(db *gorm.DB, table string, expiry time.Time) (*model.KeyView, error) {
|
||||
if expiry.IsZero() {
|
||||
expiry = time.Now().UTC()
|
||||
}
|
||||
keys := make([]*model.KeyView, 0)
|
||||
query := repository.PrepareSearchQuery(table,
|
||||
model.KeySearchRequest{
|
||||
Queries: []*key_model.KeySearchQuery{
|
||||
{Key: key_model.KeySearchKeyPrivate, Method: global_model.SearchMethodEquals, Value: true},
|
||||
{Key: key_model.KeySearchKeyUsage, Method: global_model.SearchMethodEquals, Value: key_model.KeyUsageSigning},
|
||||
{Key: key_model.KeySearchKeyExpiry, Method: global_model.SearchMethodGreaterThan, Value: time.Now().UTC()},
|
||||
},
|
||||
SortingColumn: key_model.KeySearchKeyExpiry,
|
||||
Limit: 1,
|
||||
},
|
||||
)
|
||||
err := query(db, key)
|
||||
return key, err
|
||||
_, err := query(db, &keys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keys) != 1 {
|
||||
return nil, caos_errs.ThrowNotFound(err, "VIEW-BGD41", "key not found")
|
||||
}
|
||||
return keys[0], nil
|
||||
}
|
||||
|
||||
func GetActivePublicKeys(db *gorm.DB, table string) ([]*model.KeyView, error) {
|
||||
|
@ -45,7 +45,7 @@ func KeysFromPairEvent(event *models.Event) (*KeyView, *KeyView, error) {
|
||||
Algorithm: pair.Algorithm,
|
||||
Usage: pair.Usage,
|
||||
Key: pair.PrivateKey.Key,
|
||||
Sequence: pair.Sequence,
|
||||
Sequence: event.Sequence,
|
||||
}
|
||||
publicKey := &KeyView{
|
||||
ID: event.AggregateID,
|
||||
@ -54,7 +54,7 @@ func KeysFromPairEvent(event *models.Event) (*KeyView, *KeyView, error) {
|
||||
Algorithm: pair.Algorithm,
|
||||
Usage: pair.Usage,
|
||||
Key: pair.PublicKey.Key,
|
||||
Sequence: pair.Sequence,
|
||||
Sequence: event.Sequence,
|
||||
}
|
||||
return privateKey, publicKey, nil
|
||||
}
|
||||
|
11
internal/key/repository/view/query.go
Normal file
11
internal/key/repository/view/query.go
Normal file
@ -0,0 +1,11 @@
|
||||
package view
|
||||
|
||||
import (
|
||||
"github.com/caos/zitadel/internal/eventstore/v2"
|
||||
"github.com/caos/zitadel/internal/v2/repository/iam"
|
||||
)
|
||||
|
||||
func KeyPairQuery(latestSequence uint64) *eventstore.SearchQueryBuilder {
|
||||
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent, iam.AggregateType).
|
||||
SequenceGreater(latestSequence)
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user