mirror of
https://github.com/zitadel/zitadel.git
synced 2024-12-12 11:04:25 +00:00
17953e9040
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.
190 lines
5.2 KiB
Go
190 lines
5.2 KiB
Go
package setup
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
"strings"
|
|
|
|
"golang.org/x/text/language"
|
|
|
|
"github.com/zitadel/zitadel/internal/api/authz"
|
|
"github.com/zitadel/zitadel/internal/command"
|
|
"github.com/zitadel/zitadel/internal/config/systemdefaults"
|
|
"github.com/zitadel/zitadel/internal/crypto"
|
|
crypto_db "github.com/zitadel/zitadel/internal/crypto/database"
|
|
"github.com/zitadel/zitadel/internal/database"
|
|
"github.com/zitadel/zitadel/internal/domain"
|
|
"github.com/zitadel/zitadel/internal/eventstore"
|
|
)
|
|
|
|
type FirstInstance struct {
|
|
InstanceName string
|
|
DefaultLanguage language.Tag
|
|
Org command.InstanceOrgSetup
|
|
MachineKeyPath string
|
|
PatPath string
|
|
Features map[domain.Feature]any
|
|
|
|
instanceSetup command.InstanceSetup
|
|
userEncryptionKey *crypto.KeyConfig
|
|
smtpEncryptionKey *crypto.KeyConfig
|
|
oidcEncryptionKey *crypto.KeyConfig
|
|
masterKey string
|
|
db *database.DB
|
|
es *eventstore.Eventstore
|
|
defaults systemdefaults.SystemDefaults
|
|
zitadelRoles []authz.RoleMapping
|
|
externalDomain string
|
|
externalSecure bool
|
|
externalPort uint16
|
|
domain string
|
|
}
|
|
|
|
func (mig *FirstInstance) Execute(ctx context.Context, _ eventstore.Event) error {
|
|
keyStorage, err := mig.verifyEncryptionKeys(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
userAlg, err := crypto.NewAESCrypto(mig.userEncryptionKey, keyStorage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
smtpEncryption, err := crypto.NewAESCrypto(mig.smtpEncryptionKey, keyStorage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
oidcEncryption, err := crypto.NewAESCrypto(mig.oidcEncryptionKey, keyStorage)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
cmd, err := command.StartCommands(mig.es,
|
|
mig.defaults,
|
|
mig.zitadelRoles,
|
|
nil,
|
|
nil,
|
|
mig.externalDomain,
|
|
mig.externalSecure,
|
|
mig.externalPort,
|
|
nil,
|
|
nil,
|
|
smtpEncryption,
|
|
nil,
|
|
userAlg,
|
|
nil,
|
|
oidcEncryption,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
nil,
|
|
0,
|
|
0,
|
|
0,
|
|
nil,
|
|
)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mig.instanceSetup.InstanceName = mig.InstanceName
|
|
mig.instanceSetup.CustomDomain = mig.externalDomain
|
|
mig.instanceSetup.DefaultLanguage = mig.DefaultLanguage
|
|
mig.instanceSetup.Org = mig.Org
|
|
// check if username is email style or else append @<orgname>.<custom-domain>
|
|
//this way we have the same value as before changing `UserLoginMustBeDomain` to false
|
|
if !mig.instanceSetup.DomainPolicy.UserLoginMustBeDomain && !strings.Contains(mig.instanceSetup.Org.Human.Username, "@") {
|
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mig.instanceSetup.Org.Human.Username = mig.instanceSetup.Org.Human.Username + "@" + orgDomain
|
|
}
|
|
mig.instanceSetup.Org.Human.Email.Address = mig.instanceSetup.Org.Human.Email.Address.Normalize()
|
|
if mig.instanceSetup.Org.Human.Email.Address == "" {
|
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username)
|
|
if !strings.Contains(string(mig.instanceSetup.Org.Human.Email.Address), "@") {
|
|
orgDomain, err := domain.NewIAMDomainName(mig.instanceSetup.Org.Name, mig.instanceSetup.CustomDomain)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
mig.instanceSetup.Org.Human.Email.Address = domain.EmailAddress(mig.instanceSetup.Org.Human.Username + "@" + orgDomain)
|
|
}
|
|
}
|
|
|
|
_, token, key, _, err := cmd.SetUpInstance(ctx, &mig.instanceSetup)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if mig.instanceSetup.Org.Machine != nil &&
|
|
((mig.instanceSetup.Org.Machine.Pat != nil && token == "") ||
|
|
(mig.instanceSetup.Org.Machine.MachineKey != nil && key == nil)) {
|
|
return err
|
|
}
|
|
return mig.outputMachineAuthentication(key, token)
|
|
}
|
|
|
|
func (mig *FirstInstance) verifyEncryptionKeys(ctx context.Context) (*crypto_db.Database, error) {
|
|
keyStorage, err := crypto_db.NewKeyStorage(mig.db, mig.masterKey)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("cannot start key storage: %w", err)
|
|
}
|
|
if err = verifyKey(ctx, mig.userEncryptionKey, keyStorage); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = verifyKey(ctx, mig.smtpEncryptionKey, keyStorage); err != nil {
|
|
return nil, err
|
|
}
|
|
if err = verifyKey(ctx, mig.oidcEncryptionKey, keyStorage); err != nil {
|
|
return nil, err
|
|
}
|
|
return keyStorage, nil
|
|
}
|
|
|
|
func (mig *FirstInstance) outputMachineAuthentication(key *command.MachineKey, token string) error {
|
|
if key != nil {
|
|
keyDetails, err := key.Detail()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := outputStdoutOrPath(mig.MachineKeyPath, string(keyDetails)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
if token != "" {
|
|
if err := outputStdoutOrPath(mig.PatPath, token); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func outputStdoutOrPath(path string, content string) (err error) {
|
|
f := os.Stdout
|
|
if path != "" {
|
|
f, err = os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer f.Close()
|
|
}
|
|
_, err = fmt.Fprintln(f, content)
|
|
return err
|
|
}
|
|
|
|
func (mig *FirstInstance) String() string {
|
|
return "03_default_instance"
|
|
}
|
|
|
|
func verifyKey(ctx context.Context, key *crypto.KeyConfig, storage crypto.KeyStorage) (err error) {
|
|
_, err = crypto.LoadKey(key.EncryptionKeyID, storage)
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
k, err := crypto.NewKey(key.EncryptionKeyID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return storage.CreateKeys(ctx, k)
|
|
}
|