mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:47:32 +00:00
fix(setup): init projections (#7194)
Even though this is a feature it's released as fix so that we can back port to earlier revisions. As reported by multiple users startup of ZITADEL after leaded to downtime and worst case rollbacks to the previously deployed version. The problem starts rising when there are too many events to process after the start of ZITADEL. The root cause are changes on projections (database tables) which must be recomputed. This PR solves this problem by adding a new step to the setup phase which prefills the projections. The step can be enabled by adding the `--init-projections`-flag to `setup`, `start-from-init` and `start-from-setup`. Setting this flag results in potentially longer duration of the setup phase but reduces the risk of the problems mentioned in the paragraph above.
This commit is contained in:
@@ -1,14 +1,14 @@
|
||||
package start
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
"github.com/mitchellh/mapstructure"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/zitadel/logging"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/encryption"
|
||||
"github.com/zitadel/zitadel/cmd/systemapi"
|
||||
"github.com/zitadel/zitadel/internal/actions"
|
||||
admin_es "github.com/zitadel/zitadel/internal/admin/repository/eventsourcing"
|
||||
internal_authz "github.com/zitadel/zitadel/internal/api/authz"
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/config/hook"
|
||||
"github.com/zitadel/zitadel/internal/config/network"
|
||||
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
@@ -59,10 +58,10 @@ type Config struct {
|
||||
AssetStorage static_config.AssetStorageConfig
|
||||
InternalAuthZ internal_authz.Config
|
||||
SystemDefaults systemdefaults.SystemDefaults
|
||||
EncryptionKeys *encryptionKeyConfig
|
||||
EncryptionKeys *encryption.EncryptionKeyConfig
|
||||
DefaultInstance command.InstanceSetup
|
||||
AuditLogRetention time.Duration
|
||||
SystemAPIUsers SystemAPIUsers
|
||||
SystemAPIUsers systemapi.Users
|
||||
CustomerPortal string
|
||||
Machine *id.Config
|
||||
Actions *actions.Config
|
||||
@@ -92,7 +91,7 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
mapstructure.StringToSliceHookFunc(","),
|
||||
database.DecodeHook,
|
||||
actions.HTTPConfigDecodeHook,
|
||||
systemAPIUsersDecodeHook,
|
||||
systemapi.UsersDecodeHook,
|
||||
hook.EnumHookFunc(domain.FeatureString),
|
||||
hook.EnumHookFunc(internal_authz.MemberTypeString),
|
||||
)),
|
||||
@@ -113,35 +112,3 @@ func MustNewConfig(v *viper.Viper) *Config {
|
||||
|
||||
return config
|
||||
}
|
||||
|
||||
type encryptionKeyConfig struct {
|
||||
DomainVerification *crypto.KeyConfig
|
||||
IDPConfig *crypto.KeyConfig
|
||||
OIDC *crypto.KeyConfig
|
||||
SAML *crypto.KeyConfig
|
||||
OTP *crypto.KeyConfig
|
||||
SMS *crypto.KeyConfig
|
||||
SMTP *crypto.KeyConfig
|
||||
User *crypto.KeyConfig
|
||||
CSRFCookieKeyID string
|
||||
UserAgentCookieKeyID string
|
||||
}
|
||||
|
||||
type SystemAPIUsers map[string]*internal_authz.SystemAPIUser
|
||||
|
||||
func systemAPIUsersDecodeHook(from, to reflect.Value) (any, error) {
|
||||
if to.Type() != reflect.TypeOf(SystemAPIUsers{}) {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
|
||||
data, ok := from.Interface().(string)
|
||||
if !ok {
|
||||
return from.Interface(), nil
|
||||
}
|
||||
users := make(SystemAPIUsers)
|
||||
err := json.Unmarshal([]byte(data), &users)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return users, nil
|
||||
}
|
||||
|
@@ -1,114 +0,0 @@
|
||||
package start
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultKeyIDs = []string{
|
||||
"domainVerificationKey",
|
||||
"idpConfigKey",
|
||||
"oidcKey",
|
||||
"samlKey",
|
||||
"otpKey",
|
||||
"smsKey",
|
||||
"smtpKey",
|
||||
"userKey",
|
||||
"csrfCookieKey",
|
||||
"userAgentCookieKey",
|
||||
}
|
||||
)
|
||||
|
||||
type encryptionKeys struct {
|
||||
DomainVerification crypto.EncryptionAlgorithm
|
||||
IDPConfig crypto.EncryptionAlgorithm
|
||||
OIDC crypto.EncryptionAlgorithm
|
||||
SAML crypto.EncryptionAlgorithm
|
||||
OTP crypto.EncryptionAlgorithm
|
||||
SMS crypto.EncryptionAlgorithm
|
||||
SMTP crypto.EncryptionAlgorithm
|
||||
User crypto.EncryptionAlgorithm
|
||||
CSRFCookieKey []byte
|
||||
UserAgentCookieKey []byte
|
||||
OIDCKey []byte
|
||||
}
|
||||
|
||||
func ensureEncryptionKeys(ctx context.Context, keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (keys *encryptionKeys, err error) {
|
||||
if err := verifyDefaultKeys(ctx, keyStorage); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys = new(encryptionKeys)
|
||||
keys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.SAML, err = crypto.NewAESCrypto(keyConfig.SAML, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err := crypto.LoadKey(keyConfig.OIDC.EncryptionKeyID, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.OIDCKey = []byte(key)
|
||||
keys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.User, err = crypto.NewAESCrypto(keyConfig.User, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key, err = crypto.LoadKey(keyConfig.CSRFCookieKeyID, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.CSRFCookieKey = []byte(key)
|
||||
key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
keys.UserAgentCookieKey = []byte(key)
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func verifyDefaultKeys(ctx context.Context, keyStorage crypto.KeyStorage) (err error) {
|
||||
keys := make([]*crypto.Key, 0, len(defaultKeyIDs))
|
||||
for _, keyID := range defaultKeyIDs {
|
||||
_, err := crypto.LoadKey(keyID, keyStorage)
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
key, err := crypto.NewKey(keyID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
keys = append(keys, key)
|
||||
}
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
if err := keyStorage.CreateKeys(ctx, keys...); err != nil {
|
||||
return zerrors.ThrowInternal(err, "START-aGBq2", "cannot create default keys")
|
||||
}
|
||||
return nil
|
||||
}
|
@@ -25,6 +25,7 @@ import (
|
||||
"golang.org/x/net/http2/h2c"
|
||||
|
||||
"github.com/zitadel/zitadel/cmd/build"
|
||||
"github.com/zitadel/zitadel/cmd/encryption"
|
||||
"github.com/zitadel/zitadel/cmd/key"
|
||||
cmd_tls "github.com/zitadel/zitadel/cmd/tls"
|
||||
"github.com/zitadel/zitadel/feature"
|
||||
@@ -108,7 +109,7 @@ type Server struct {
|
||||
Config *Config
|
||||
DB *database.DB
|
||||
KeyStorage crypto.KeyStorage
|
||||
Keys *encryptionKeys
|
||||
Keys *encryption.EncryptionKeys
|
||||
Eventstore *eventstore.Eventstore
|
||||
Queries *query.Queries
|
||||
AuthzRepo authz_repo.Repository
|
||||
@@ -141,7 +142,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start key storage: %w", err)
|
||||
}
|
||||
keys, err := ensureEncryptionKeys(ctx, config.EncryptionKeys, keyStorage)
|
||||
keys, err := encryption.EnsureEncryptionKeys(ctx, config.EncryptionKeys, keyStorage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -172,6 +173,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
},
|
||||
config.AuditLogRetention,
|
||||
config.SystemAPIUsers,
|
||||
true,
|
||||
)
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot start queries: %w", err)
|
||||
@@ -236,7 +238,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
actionsLogstoreSvc := logstore.New(queries, actionsExecutionDBEmitter, actionsExecutionStdoutEmitter)
|
||||
actions.SetLogstoreService(actionsLogstoreSvc)
|
||||
|
||||
notification.Start(
|
||||
notification.Register(
|
||||
ctx,
|
||||
config.Projections.Customizations["notifications"],
|
||||
config.Projections.Customizations["notificationsquotas"],
|
||||
@@ -254,6 +256,7 @@ func startZitadel(ctx context.Context, config *Config, masterKey string, server
|
||||
keys.SMTP,
|
||||
keys.SMS,
|
||||
)
|
||||
notification.Start(ctx)
|
||||
|
||||
router := mux.NewRouter()
|
||||
tlsConfig, err := config.TLS.Config()
|
||||
@@ -313,7 +316,7 @@ func startAPIs(
|
||||
config *Config,
|
||||
store static.Storage,
|
||||
authZRepo authz_repo.Repository,
|
||||
keys *encryptionKeys,
|
||||
keys *encryption.EncryptionKeys,
|
||||
permissionCheck domain.PermissionCheck,
|
||||
) error {
|
||||
repo := struct {
|
||||
|
@@ -31,6 +31,9 @@ Requirements:
|
||||
|
||||
initialise.InitAll(cmd.Context(), initialise.MustNewConfig(viper.GetViper()))
|
||||
|
||||
err = setup.BindInitProjections(cmd)
|
||||
logging.OnError(err).Fatal("unable to bind \"init-projections\" flag")
|
||||
|
||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||
setupSteps := setup.MustNewSteps(viper.New())
|
||||
setup.Setup(setupConfig, setupSteps, masterKey)
|
||||
|
@@ -29,6 +29,9 @@ Requirements:
|
||||
masterKey, err := key.MasterKey(cmd)
|
||||
logging.OnError(err).Panic("No master key provided")
|
||||
|
||||
err = setup.BindInitProjections(cmd)
|
||||
logging.OnError(err).Fatal("unable to bind \"init-projections\" flag")
|
||||
|
||||
setupConfig := setup.MustNewConfig(viper.GetViper())
|
||||
setupSteps := setup.MustNewSteps(viper.New())
|
||||
setup.Setup(setupConfig, setupSteps, masterKey)
|
||||
|
Reference in New Issue
Block a user