From 5463244376aa56e475a1080ef88edf7002800401 Mon Sep 17 00:00:00 2001 From: Livio Amstutz Date: Mon, 14 Mar 2022 07:55:09 +0100 Subject: [PATCH] feat: encryption keys in database (#3265) * enable overwrite of adminUser fields in defaults.yaml * create schema and table * cli: create keys * cli: create keys * read encryptionkey from db * merge v2 * file names * cleanup defaults.yaml * remove custom errors * load encryptionKeys on start * cleanup * fix merge * update system defaults * fix error message --- cmd/admin/admin.go | 5 +- cmd/admin/initialise/sql/06_system.sql | 1 + .../sql/07_encryption_keys_table.sql | 6 + ...sql => 08_enable_hash_sharded_indexes.sql} | 0 ...7_events_table.sql => 09_events_table.sql} | 0 cmd/admin/initialise/sql/README.md | 8 +- cmd/admin/initialise/verify_zitadel.go | 55 +- cmd/admin/initialise/verify_zitadel_test.go | 53 ++ cmd/admin/key/key.go | 130 +++++ cmd/admin/key/key_test.go | 161 ++++++ cmd/admin/start/encryption_keys.go | 105 ++++ cmd/admin/start/start.go | 80 +-- cmd/defaults.yaml | 135 ++--- docs/docs/apis/proto/admin.md | 48 +- internal/api/grpc/admin/org.go | 4 +- internal/api/grpc/admin/server.go | 15 +- internal/api/grpc/auth/email.go | 6 +- internal/api/grpc/auth/passwordless.go | 6 +- internal/api/grpc/auth/phone.go | 6 +- internal/api/grpc/auth/server.go | 14 +- .../grpc/management/project_application.go | 8 +- internal/api/grpc/management/server.go | 17 +- internal/api/grpc/management/user.go | 26 +- .../api/http/middleware/user_agent_cookie.go | 9 +- internal/api/oidc/op.go | 45 +- .../api/ui/login/external_login_handler.go | 6 +- .../api/ui/login/external_register_handler.go | 10 +- .../api/ui/login/init_password_handler.go | 4 +- internal/api/ui/login/init_user_handler.go | 4 +- internal/api/ui/login/jwt_handler.go | 6 +- internal/api/ui/login/login.go | 55 +- internal/api/ui/login/login_handler.go | 2 +- internal/api/ui/login/mail_verify_handler.go | 4 +- .../api/ui/login/password_reset_handler.go | 2 +- internal/api/ui/login/register_handler.go | 6 +- internal/api/ui/login/register_org_handler.go | 6 +- .../eventsourcing/eventstore/auth_request.go | 25 +- .../repository/eventsourcing/repository.go | 13 +- internal/authz/authz.go | 4 +- .../repository/eventsourcing/repository.go | 9 +- internal/command/command.go | 56 +- .../config/systemdefaults/system_defaults.go | 44 +- internal/crypto/aes.go | 4 +- internal/crypto/database/database.go | 133 +++++ internal/crypto/database/database_test.go | 524 ++++++++++++++++++ internal/crypto/file/file.go | 44 ++ internal/crypto/key.go | 46 +- internal/crypto/key_storage.go | 7 + internal/notification/notification.go | 16 +- .../eventsourcing/handler/handler.go | 26 +- .../eventsourcing/handler/notification.go | 31 +- .../repository/eventsourcing/repository.go | 14 +- .../eventsourcing/spooler/spooler.go | 16 +- internal/query/projection/key.go | 10 +- internal/query/projection/projection.go | 7 +- internal/query/query.go | 5 +- migrations/cockroach/V1.111__settings.sql | 7 + 57 files changed, 1618 insertions(+), 471 deletions(-) create mode 100644 cmd/admin/initialise/sql/06_system.sql create mode 100644 cmd/admin/initialise/sql/07_encryption_keys_table.sql rename cmd/admin/initialise/sql/{06_enable_hash_sharded_indexes.sql => 08_enable_hash_sharded_indexes.sql} (100%) rename cmd/admin/initialise/sql/{07_events_table.sql => 09_events_table.sql} (100%) create mode 100644 cmd/admin/key/key.go create mode 100644 cmd/admin/key/key_test.go create mode 100644 cmd/admin/start/encryption_keys.go create mode 100644 internal/crypto/database/database.go create mode 100644 internal/crypto/database/database_test.go create mode 100644 internal/crypto/file/file.go create mode 100644 internal/crypto/key_storage.go diff --git a/cmd/admin/admin.go b/cmd/admin/admin.go index c293959d04..07747dbd3a 100644 --- a/cmd/admin/admin.go +++ b/cmd/admin/admin.go @@ -4,10 +4,12 @@ import ( _ "embed" "github.com/caos/logging" + "github.com/spf13/cobra" + "github.com/caos/zitadel/cmd/admin/initialise" + "github.com/caos/zitadel/cmd/admin/key" "github.com/caos/zitadel/cmd/admin/setup" "github.com/caos/zitadel/cmd/admin/start" - "github.com/spf13/cobra" ) func New() *cobra.Command { @@ -24,6 +26,7 @@ func New() *cobra.Command { initialise.New(), setup.New(), start.New(), + key.New(), ) return adminCMD diff --git a/cmd/admin/initialise/sql/06_system.sql b/cmd/admin/initialise/sql/06_system.sql new file mode 100644 index 0000000000..2f0c100cc7 --- /dev/null +++ b/cmd/admin/initialise/sql/06_system.sql @@ -0,0 +1 @@ +CREATE SCHEMA system; diff --git a/cmd/admin/initialise/sql/07_encryption_keys_table.sql b/cmd/admin/initialise/sql/07_encryption_keys_table.sql new file mode 100644 index 0000000000..77b7e6cd91 --- /dev/null +++ b/cmd/admin/initialise/sql/07_encryption_keys_table.sql @@ -0,0 +1,6 @@ +CREATE TABLE system.encryption_keys ( + id TEXT NOT NULL + , key TEXT NOT NULL + + , PRIMARY KEY (id) +); diff --git a/cmd/admin/initialise/sql/06_enable_hash_sharded_indexes.sql b/cmd/admin/initialise/sql/08_enable_hash_sharded_indexes.sql similarity index 100% rename from cmd/admin/initialise/sql/06_enable_hash_sharded_indexes.sql rename to cmd/admin/initialise/sql/08_enable_hash_sharded_indexes.sql diff --git a/cmd/admin/initialise/sql/07_events_table.sql b/cmd/admin/initialise/sql/09_events_table.sql similarity index 100% rename from cmd/admin/initialise/sql/07_events_table.sql rename to cmd/admin/initialise/sql/09_events_table.sql diff --git a/cmd/admin/initialise/sql/README.md b/cmd/admin/initialise/sql/README.md index 26e5113a6e..b477c0fb73 100644 --- a/cmd/admin/initialise/sql/README.md +++ b/cmd/admin/initialise/sql/README.md @@ -9,6 +9,8 @@ The sql-files in this folder initialize the ZITADEL database and user. These obj - 03_grant_user.sql: grants the user created before to have full access to its database. The user needs full access to the database because zitadel makes ddl/dml on runtime - 04_eventstore.sql: creates the schema needed for eventsourcing - 05_projections.sql: creates the schema needed to read the data -- files 06_enable_hash_sharded_indexes.sql and 07_events_table.sql must run in the same session - - 06_enable_hash_sharded_indexes.sql enables the [hash sharded index](https://www.cockroachlabs.com/docs/stable/hash-sharded-indexes.html) feature for this session - - 07_events_table.sql creates the table for eventsourcing +- 06_system.sql: creates the schema needed for ZITADEL itself +- 07_encryption_keys_table.sql: creates the table for encryption keys (for event data) +- files 08_enable_hash_sharded_indexes.sql and 09_events_table.sql must run in the same session + - 08_enable_hash_sharded_indexes.sql enables the [hash sharded index](https://www.cockroachlabs.com/docs/stable/hash-sharded-indexes.html) feature for this session + - 09_events_table.sql creates the table for eventsourcing diff --git a/cmd/admin/initialise/verify_zitadel.go b/cmd/admin/initialise/verify_zitadel.go index 51c5c7b43c..b1702830c8 100644 --- a/cmd/admin/initialise/verify_zitadel.go +++ b/cmd/admin/initialise/verify_zitadel.go @@ -4,27 +4,36 @@ import ( "database/sql" _ "embed" - "github.com/caos/zitadel/internal/database" + "github.com/caos/logging" "github.com/spf13/cobra" "github.com/spf13/viper" + + "github.com/caos/zitadel/internal/database" ) const ( - eventstoreSchema = "eventstore" - projectionsSchema = "projections" + eventstoreSchema = "eventstore" + eventsTable = "events" + projectionsSchema = "projections" + systemSchema = "system" + encryptionKeysTable = "encryption_key" ) var ( - searchEventsTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = 'events'" - searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1" - //go:embed sql/06_enable_hash_sharded_indexes.sql - enableHashShardedIdx string - //go:embed sql/07_events_table.sql - createEventsStmt string - //go:embed sql/05_projections.sql - createProjectionsStmt string + searchTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = $1" + searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1" //go:embed sql/04_eventstore.sql createEventstoreStmt string + //go:embed sql/05_projections.sql + createProjectionsStmt string + //go:embed sql/06_system.sql + createSystemStmt string + //go:embed sql/07_encryption_keys_table.sql + createEncryptionKeysStmt string + //go:embed sql/08_enable_hash_sharded_indexes.sql + enableHashShardedIdx string + //go:embed sql/09_events_table.sql + createEventsStmt string ) func newZitadel() *cobra.Command { @@ -47,11 +56,20 @@ Prereqesits: } func verifyZitadel(config database.Config) error { + logging.WithFields("database", config.Database).Info("verify database") db, err := database.Connect(config) if err != nil { return err } + if err := verify(db, exists(searchSchema, systemSchema), exec(createSystemStmt)); err != nil { + return err + } + + if err := verify(db, exists(searchTable, encryptionKeysTable), createEncryptionKeys); err != nil { + return err + } + if err := verify(db, exists(searchSchema, projectionsSchema), exec(createProjectionsStmt)); err != nil { return err } @@ -60,13 +78,26 @@ func verifyZitadel(config database.Config) error { return err } - if err := verify(db, exists(searchSchema, projectionsSchema), createEvents); err != nil { + if err := verify(db, exists(searchTable, eventsTable), createEvents); err != nil { return err } return db.Close() } +func createEncryptionKeys(db *sql.DB) error { + tx, err := db.Begin() + if err != nil { + return err + } + if _, err = tx.Exec(createEncryptionKeysStmt); err != nil { + tx.Rollback() + return err + } + + return tx.Commit() +} + func createEvents(db *sql.DB) error { tx, err := db.Begin() if err != nil { diff --git a/cmd/admin/initialise/verify_zitadel_test.go b/cmd/admin/initialise/verify_zitadel_test.go index 6f6c73a49a..fbd476a35b 100644 --- a/cmd/admin/initialise/verify_zitadel_test.go +++ b/cmd/admin/initialise/verify_zitadel_test.go @@ -71,3 +71,56 @@ func Test_verifyEvents(t *testing.T) { }) } } + +func Test_verifyEncryptionKeys(t *testing.T) { + type args struct { + db db + } + tests := []struct { + name string + args args + targetErr error + }{ + { + name: "unable to begin", + args: args{ + db: prepareDB(t, + expectBegin(sql.ErrConnDone), + ), + }, + targetErr: sql.ErrConnDone, + }, + { + name: "create table fails", + args: args{ + db: prepareDB(t, + expectBegin(nil), + expectExec(createEncryptionKeysStmt, sql.ErrNoRows), + expectRollback(nil), + ), + }, + targetErr: sql.ErrNoRows, + }, + { + name: "correct", + args: args{ + db: prepareDB(t, + expectBegin(nil), + expectExec(createEncryptionKeysStmt, nil), + expectCommit(nil), + ), + }, + targetErr: nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if err := createEncryptionKeys(tt.args.db.db); !errors.Is(err, tt.targetErr) { + t.Errorf("createEvents() error = %v, want: %v", err, tt.targetErr) + } + if err := tt.args.db.mock.ExpectationsWereMet(); err != nil { + t.Error(err) + } + }) + } +} diff --git a/cmd/admin/key/key.go b/cmd/admin/key/key.go new file mode 100644 index 0000000000..5b0793b39c --- /dev/null +++ b/cmd/admin/key/key.go @@ -0,0 +1,130 @@ +package key + +import ( + "io" + "os" + "strings" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + "sigs.k8s.io/yaml" + + caos_errs "github.com/caos/zitadel/internal/errors" + + "github.com/caos/zitadel/internal/crypto" + cryptoDB "github.com/caos/zitadel/internal/crypto/database" + "github.com/caos/zitadel/internal/database" +) + +const ( + flagMasterKey = "masterkey" + flagKeyFile = "file" +) + +type Config struct { + Database database.Config +} + +func New() *cobra.Command { + cmd := &cobra.Command{ + Use: "keys", + Short: "manage encryption keys", + } + cmd.PersistentFlags().String(flagMasterKey, "", "masterkey for en/decryption keys") + cmd.AddCommand(newKey()) + return cmd +} + +func newKey() *cobra.Command { + cmd := &cobra.Command{ + Use: "new [keyID=key]... [-f file]", + Short: "create new encryption key(s)", + Long: `create new encryption key(s) (encrypted by the provided master key) +provide key(s) by YAML file and/or by argument +Requirements: +- cockroachdb`, + Example: `new -f keys.yaml +new key1=somekey key2=anotherkey +new -f keys.yaml key2=anotherkey`, + RunE: func(cmd *cobra.Command, args []string) error { + keys, err := keysFromArgs(args) + if err != nil { + return err + } + filePath, _ := cmd.Flags().GetString(flagKeyFile) + if filePath != "" { + file, err := openFile(filePath) + if err != nil { + return err + } + yamlKeys, err := keysFromYAML(file) + if err != nil { + return err + } + keys = append(keys, yamlKeys...) + } + config := new(Config) + if err := viper.Unmarshal(config); err != nil { + return err + } + masterKey, _ := cmd.Flags().GetString(flagMasterKey) + storage, err := keyStorage(config.Database, masterKey) + if err != nil { + return err + } + return storage.CreateKeys(keys...) + }, + } + cmd.PersistentFlags().StringP(flagKeyFile, "f", "", "path to keys file") + return cmd +} + +func keysFromArgs(args []string) ([]*crypto.Key, error) { + keys := make([]*crypto.Key, len(args)) + for i, arg := range args { + key := strings.Split(arg, "=") + if len(key) != 2 { + return nil, caos_errs.ThrowInternal(nil, "KEY-JKd82", "argument is not in the valid format [keyID=key]") + } + keys[i] = &crypto.Key{ + ID: key[0], + Value: key[1], + } + } + return keys, nil +} + +func keysFromYAML(file io.Reader) ([]*crypto.Key, error) { + data, err := io.ReadAll(file) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "KEY-ajGFr", "unable to extract keys from file") + } + keysYAML := make(map[string]string) + if err = yaml.Unmarshal(data, &keysYAML); err != nil { + return nil, caos_errs.ThrowInternal(err, "KEY-sd34K", "unable to extract keys from file") + } + keys := make([]*crypto.Key, 0, len(keysYAML)) + for id, key := range keysYAML { + keys = append(keys, &crypto.Key{ + ID: id, + Value: key, + }) + } + return keys, nil +} + +func openFile(fileName string) (*os.File, error) { + file, err := os.Open(fileName) + if err != nil { + return nil, caos_errs.ThrowInternalf(err, "KEY-asGr2", "failed to open file: %s", fileName) + } + return file, nil +} + +func keyStorage(config database.Config, masterKey string) (crypto.KeyStorage, error) { + db, err := database.Connect(config) + if err != nil { + return nil, err + } + return cryptoDB.NewKeyStorage(db, masterKey) +} diff --git a/cmd/admin/key/key_test.go b/cmd/admin/key/key_test.go new file mode 100644 index 0000000000..b1019deed1 --- /dev/null +++ b/cmd/admin/key/key_test.go @@ -0,0 +1,161 @@ +package key + +import ( + "bytes" + "io" + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + caos_errors "github.com/caos/zitadel/internal/errors" + + "github.com/caos/zitadel/internal/crypto" +) + +func Test_keysFromArgs(t *testing.T) { + type args struct { + args []string + } + type res struct { + keys []*crypto.Key + err func(error) bool + } + tests := []struct { + name string + args args + res res + }{ + { + "no args", + args{}, + res{ + keys: []*crypto.Key{}, + }, + }, + { + "invalid arg", + args{ + args: []string{"keyID", "value"}, + }, + res{ + err: caos_errors.IsInternal, + }, + }, + { + "single arg", + args{ + args: []string{"keyID=value"}, + }, + res{ + keys: []*crypto.Key{ + { + ID: "keyID", + Value: "value", + }, + }, + }, + }, + { + "multiple args", + args{ + args: []string{"keyID=value", "keyID2=value2"}, + }, + res{ + keys: []*crypto.Key{ + { + ID: "keyID", + Value: "value", + }, + { + ID: "keyID2", + Value: "value2", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := keysFromArgs(tt.args.args) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + if !reflect.DeepEqual(got, tt.res.keys) { + t.Errorf("keysFromArgs() got = %v, want %v", got, tt.res.keys) + } + }) + } +} + +func Test_keysFromYAML(t *testing.T) { + type args struct { + file io.Reader + } + type res struct { + keys []*crypto.Key + err func(error) bool + } + tests := []struct { + name string + args args + res res + }{ + { + "invalid yaml", + args{ + file: bytes.NewReader([]byte("keyID=ds")), + }, + res{ + err: caos_errors.IsInternal, + }, + }, + { + "single key", + args{ + file: bytes.NewReader([]byte("keyID: value")), + }, + res{ + keys: []*crypto.Key{ + { + ID: "keyID", + Value: "value", + }, + }, + }, + }, + { + "multiple keys", + args{ + file: bytes.NewReader([]byte("keyID: value\nkeyID2: value2")), + }, + res{ + keys: []*crypto.Key{ + { + ID: "keyID", + Value: "value", + }, + { + ID: "keyID2", + Value: "value2", + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := keysFromYAML(tt.args.file) + if tt.res.err == nil { + assert.NoError(t, err) + } + if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v ", err) + } + assert.EqualValues(t, got, tt.res.keys) + }) + } +} diff --git a/cmd/admin/start/encryption_keys.go b/cmd/admin/start/encryption_keys.go new file mode 100644 index 0000000000..433881589e --- /dev/null +++ b/cmd/admin/start/encryption_keys.go @@ -0,0 +1,105 @@ +package start + +import ( + "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" +) + +var ( + defaultKeyIDs = []string{ + "domainVerificationKey", + "idpConfigKey", + "oidcKey", + "otpKey", + "smsKey", + "smtpKey", + "userKey", + "csrfCookieKey", + "userAgentCookieKey", + } +) + +type encryptionKeys struct { + DomainVerification crypto.EncryptionAlgorithm + IDPConfig crypto.EncryptionAlgorithm + OIDC crypto.EncryptionAlgorithm + OTP crypto.EncryptionAlgorithm + SMS crypto.EncryptionAlgorithm + SMTP crypto.EncryptionAlgorithm + User crypto.EncryptionAlgorithm + CSRFCookieKey []byte + UserAgentCookieKey []byte + OIDCKey []byte +} + +func ensureEncryptionKeys(keyConfig *encryptionKeyConfig, keyStorage crypto.KeyStorage) (*encryptionKeys, error) { + keys, err := keyStorage.ReadKeys() + if err != nil { + return nil, nil + } + if len(keys) == 0 { + if err := createDefaultKeys(keyStorage); err != nil { + return nil, err + } + } + encryptionKeys := new(encryptionKeys) + encryptionKeys.DomainVerification, err = crypto.NewAESCrypto(keyConfig.DomainVerification, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.IDPConfig, err = crypto.NewAESCrypto(keyConfig.IDPConfig, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.OIDC, err = crypto.NewAESCrypto(keyConfig.OIDC, keyStorage) + if err != nil { + return nil, err + } + key, err := crypto.LoadKey(keyConfig.OIDC.EncryptionKeyID, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.OIDCKey = []byte(key) + encryptionKeys.OTP, err = crypto.NewAESCrypto(keyConfig.OTP, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.SMS, err = crypto.NewAESCrypto(keyConfig.SMS, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.SMTP, err = crypto.NewAESCrypto(keyConfig.SMTP, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.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 + } + encryptionKeys.CSRFCookieKey = []byte(key) + key, err = crypto.LoadKey(keyConfig.UserAgentCookieKeyID, keyStorage) + if err != nil { + return nil, err + } + encryptionKeys.UserAgentCookieKey = []byte(key) + return encryptionKeys, nil +} + +func createDefaultKeys(keyStorage crypto.KeyStorage) error { + keys := make([]*crypto.Key, len(defaultKeyIDs)) + for i, keyID := range defaultKeyIDs { + key, err := crypto.NewKey(keyID) + if err != nil { + return err + } + keys[i] = key + } + if err := keyStorage.CreateKeys(keys...); err != nil { + return caos_errs.ThrowInternal(err, "START-aGBq2", "cannot create default keys") + } + return nil +} diff --git a/cmd/admin/start/start.go b/cmd/admin/start/start.go index 35bf2db1ca..bdf20a36a1 100644 --- a/cmd/admin/start/start.go +++ b/cmd/admin/start/start.go @@ -39,6 +39,7 @@ import ( "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" + cryptoDB "github.com/caos/zitadel/internal/crypto/database" "github.com/caos/zitadel/internal/database" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/eventstore" @@ -52,6 +53,10 @@ import ( "github.com/caos/zitadel/openapi" ) +const ( + flagMasterKey = "masterkey" +) + func New() *cobra.Command { start := &cobra.Command{ Use: "start", @@ -72,7 +77,8 @@ Requirements: if err != nil { return err } - return startZitadel(config) + masterKey, _ := cmd.Flags().GetString("masterkey") + return startZitadel(config, masterKey) }, } bindUint16Flag(start, "port", "port to run ZITADEL on") @@ -80,6 +86,8 @@ Requirements: bindStringFlag(start, "externalPort", "port ZITADEL will be exposed on") bindBoolFlag(start, "externalSecure", "if ZITADEL will be served on HTTPS") + start.PersistentFlags().String(flagMasterKey, "", "masterkey for en/decryption keys") + return start } @@ -105,7 +113,7 @@ type startConfig struct { ExternalDomain string ExternalSecure bool Database database.Config - Projections projectionConfig + Projections projection.Config AuthZ authz.Config Auth auth_es.Config Admin admin_es.Config @@ -117,14 +125,22 @@ type startConfig struct { AssetStorage static_config.AssetStorageConfig InternalAuthZ internal_authz.Config SystemDefaults systemdefaults.SystemDefaults + EncryptionKeys *encryptionKeyConfig } -type projectionConfig struct { - projection.Config - KeyConfig *crypto.KeyConfig +type encryptionKeyConfig struct { + DomainVerification *crypto.KeyConfig + IDPConfig *crypto.KeyConfig + OIDC *crypto.KeyConfig + OTP *crypto.KeyConfig + SMS *crypto.KeyConfig + SMTP *crypto.KeyConfig + User *crypto.KeyConfig + CSRFCookieKeyID string + UserAgentCookieKeyID string } -func startZitadel(config *startConfig) error { +func startZitadel(config *startConfig, masterKey string) error { ctx := context.Background() keyChan := make(chan interface{}) @@ -132,6 +148,16 @@ func startZitadel(config *startConfig) error { if err != nil { return fmt.Errorf("cannot start client for projection: %w", err) } + + keyStorage, err := cryptoDB.NewKeyStorage(dbClient, masterKey) + if err != nil { + return fmt.Errorf("cannot start key storage: %w", err) + } + keys, err := ensureEncryptionKeys(config.EncryptionKeys, keyStorage) + if err != nil { + return err + } + var storage static.Storage //TODO: enable when storage is implemented again //if *assetsEnabled { @@ -142,22 +168,13 @@ func startZitadel(config *startConfig) error { if err != nil { return fmt.Errorf("cannot start eventstore for queries: %w", err) } - smtpPasswordCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.SMTPPasswordVerificationKey) - if err != nil { - return fmt.Errorf("cannot create smtp crypto: %w", err) - } - smsCrypto, err := crypto.NewAESCrypto(config.SystemDefaults.SMSVerificationKey) - if err != nil { - return fmt.Errorf("cannot create smtp crypto: %w", err) - } - - queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections.Config, config.SystemDefaults, config.Projections.KeyConfig, keyChan, config.InternalAuthZ.RolePermissionMappings) + queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, keyChan, config.InternalAuthZ.RolePermissionMappings) if err != nil { return fmt.Errorf("cannot start queries: %w", err) } - authZRepo, err := authz.Start(config.AuthZ, config.SystemDefaults, queries, dbClient, config.OIDC.KeyConfig) + authZRepo, err := authz.Start(config.AuthZ, config.SystemDefaults, queries, dbClient, keys.OIDC) if err != nil { return fmt.Errorf("error starting authz repo: %w", err) } @@ -166,22 +183,22 @@ func startZitadel(config *startConfig) error { Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), DisplayName: "ZITADEL", } - commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, config.OIDC.KeyConfig, webAuthNConfig, smtpPasswordCrypto, smsCrypto) + commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.DomainVerification, keys.OIDC) if err != nil { return fmt.Errorf("cannot start commands: %w", err) } - notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, smtpPasswordCrypto, smsCrypto) + notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.User, keys.SMTP, keys.SMS) router := mux.NewRouter() - err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo) + err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, keyChan, config, storage, authZRepo, keys) if err != nil { return err } return listen(ctx, router, config.Port) } -func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *startConfig, store static.Storage, authZRepo authz_repo.Repository) error { +func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *startConfig, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error { repo := struct { authz_repo.Repository *query.Queries @@ -192,11 +209,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman verifier := internal_authz.Start(repo) apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure) - userEncryptionAlgorithm, err := crypto.NewAESCrypto(config.SystemDefaults.UserVerificationKey) - if err != nil { - return nil - } - authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, config.OIDC.KeyConfig, assets.HandlerPrefix, userEncryptionAlgorithm) + authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.OIDC, keys.User) if err != nil { return fmt.Errorf("error starting auth repo: %w", err) } @@ -204,25 +217,25 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman if err != nil { return fmt.Errorf("error starting admin repo: %w", err) } - if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil { + if err := apis.RegisterServer(ctx, admin.CreateServer(commands, queries, adminRepo, config.SystemDefaults.Domain, assets.HandlerPrefix, keys.User)); err != nil { return err } - if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil { + if err := apis.RegisterServer(ctx, management.CreateServer(commands, queries, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil { return err } - if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, userEncryptionAlgorithm)); err != nil { + if err := apis.RegisterServer(ctx, auth.CreateServer(commands, queries, authRepo, config.SystemDefaults, assets.HandlerPrefix, keys.User)); err != nil { return err } apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries)) - userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure) + userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure) if err != nil { return err } issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) - oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, eventstore, dbClient, keyChan, userAgentInterceptor) + oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, keys.OIDC, keys.OIDCKey, eventstore, dbClient, keyChan, userAgentInterceptor) if err != nil { return fmt.Errorf("unable to start oidc provider: %w", err) } @@ -238,13 +251,14 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman if err != nil { return fmt.Errorf("unable to get client_id for console: %w", err) } - c, err := console.Start(config.Console, config.ExternalDomain, http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), issuer, consoleID) + baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) + c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, consoleID) if err != nil { return fmt.Errorf("unable to start console: %w", err) } apis.RegisterHandler(console.HandlerPrefix, c) - l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix, config.ExternalDomain, oidc.AuthCallback, config.ExternalSecure, userAgentInterceptor, userEncryptionAlgorithm) + l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix, config.ExternalDomain, baseURL, oidc.AuthCallback, config.ExternalSecure, userAgentInterceptor, keys.User, keys.IDPConfig, keys.CSRFCookieKey) if err != nil { return fmt.Errorf("unable to start login: %w", err) } diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index 62870f3da7..ebcb750c77 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -20,13 +20,19 @@ Database: Username: zitadel Password: "" SSL: - Mode: diabled + Mode: disable RootCert: "" Cert: "" Key: "" AdminUser: Username: root + Password: "" + SSL: + Mode: disable + RootCert: "" + Cert: "" + Key: "" Projections: Config: @@ -38,10 +44,6 @@ Projections: Customizations: projects: BulkLimit: 2000 - KeyConfig: - # We don't need an EncryptionKey but DecryptionKeys (and load them via env) - DecryptionKeyIDs: - Path: "" AuthZ: Repository: @@ -66,8 +68,6 @@ Admin: UserAgentCookie: Name: zitadel.useragent - Key: - EncryptionKeyID: MaxAge: 8760h #365*24h (1 year) OIDC: @@ -84,19 +84,11 @@ OIDC: Cache: MaxAge: 12h SharedMaxAge: 168h #7d - KeyConfig: - EncryptionKeyID: "" - DecryptionKeyIDs: - Path: "" CustomEndpoints: Login: LanguageCookieName: zitadel.login.lang - CSRF: - CookieName: zitadel.login.csrf - Development: true - Key: - EncryptionKeyID: + CSRFCookieName: zitadel.login.csrf Cache: MaxAge: 12h SharedMaxAge: 168h #7d @@ -118,6 +110,31 @@ Notification: FailureCountUntilSkip: 5 Handlers: +EncryptionKeys: + DomainVerification: + EncryptionKeyID: "domainVerificationKey" + DecryptionKeyIDs: + IDPConfig: + EncryptionKeyID: "idpConfigKey" + DecryptionKeyIDs: + OIDC: + EncryptionKeyID: "oidcKey" + DecryptionKeyIDs: + OTP: + EncryptionKeyID: "otpKey" + DecryptionKeyIDs: + SMS: + EncryptionKeyID: "smsKey" + DecryptionKeyIDs: + SMTP: + EncryptionKeyID: "smtpKey" + DecryptionKeyIDs: + User: + EncryptionKeyID: "userKey" + DecryptionKeyIDs: + CSRFCookieKeyID: "csrfCookieKey" + UserAgentCookieKeyID: "userAgentCookieKey" + #TODO: configure as soon as possible #AssetStorage: # Type: $ZITADEL_ASSET_STORAGE_TYPE @@ -137,73 +154,14 @@ SystemDefaults: ZitadelDocs: Issuer: $ZITADEL_ISSUER DiscoveryEndpoint: '$ZITADEL_ISSUER/.well-known/openid-configuration' - UserVerificationKey: - EncryptionKeyID: $ZITADEL_USER_VERIFICATION_KEY - IDPConfigVerificationKey: - EncryptionKeyID: $ZITADEL_IDP_CONFIG_VERIFICATION_KEY - SMTPPasswordVerificationKey: - EncryptionKeyID: $ZITADEL_SMTP_PASSWORD_VERIFICATION_KEY - SMSVerificationKey: - EncryptionKeyID: $ZITADEL_SMS_VERIFICATION_KEY SecretGenerators: PasswordSaltCost: 14 - ClientSecretGenerator: - Length: 64 - IncludeLowerLetters: true - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false - InitializeUserCode: - Length: 6 - Expiry: '72h' - IncludeLowerLetters: false - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false - EmailVerificationCode: - Length: 6 - Expiry: '1h' - IncludeLowerLetters: false - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false - PhoneVerificationCode: - Length: 6 - Expiry: '1h' - IncludeLowerLetters: false - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false - PasswordVerificationCode: - Length: 6 - Expiry: '1h' - IncludeLowerLetters: false - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false - PasswordlessInitCode: - Length: 12 - Expiry: '1h' - IncludeLowerLetters: true - IncludeUpperLetters: true - IncludeDigits: true - IncludeSymbols: false MachineKeySize: 2048 ApplicationKeySize: 2048 Multifactors: OTP: Issuer: 'ZITADEL' - VerificationKey: - EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY - VerificationLifetimes: - PasswordCheck: 240h #10d - ExternalLoginCheck: 240h #10d - MFAInitSkip: 720h #30d - SecondFactorCheck: 18h - MultiFactorCheck: 12h DomainVerification: - VerificationKey: - EncryptionKeyID: $ZITADEL_DOMAIN_VERIFICATION_KEY VerificationGenerator: Length: 32 IncludeLowerLetters: true @@ -211,38 +169,13 @@ SystemDefaults: IncludeDigits: true IncludeSymbols: false Notifications: - # DebugMode: $DEBUG_MODE Endpoints: InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}&passwordset={{.PasswordSet}}' PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}' VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}' DomainClaimed: '$ZITADEL_ACCOUNTS/login' PasswordlessRegistration: '$ZITADEL_ACCOUNTS/login/passwordless/init' - Providers: - Email: - SMTP: - Host: $SMTP_HOST - User: $SMTP_USER - Password: $SMTP_PASSWORD - From: $EMAIL_SENDER_ADDRESS - FromName: $EMAIL_SENDER_NAME - # Tls: $SMTP_TLS - Twilio: - SID: $TWILIO_SERVICE_SID - Token: $TWILIO_TOKEN - From: $TWILIO_SENDER_NAME - FileSystem: - # Enabled: $FS_NOTIFICATIONS_ENABLED - Path: $FS_NOTIFICATIONS_PATH - # Compact: $FS_NOTIFICATIONS_COMPACT - Log: - # Enabled: $LOG_NOTIFICATIONS_ENABLED - # Compact: $LOG_NOTIFICATIONS_COMPACT - Chat: - # Enabled: $CHAT_ENABLED - Url: $CHAT_URL - # Compact: $CHAT_COMPACT - SplitCount: 4000 + FileSystemPath: '.notifications/' KeyConfig: Size: 2048 PrivateKeyLifetime: 6h diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 748c211d51..5c28614c35 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -188,30 +188,6 @@ Update twilio sms provider token PUT: /sms/twilio/{id}/token -### GetFileSystemNotificationProvider - -> **rpc** GetFileSystemNotificationProvider([GetFileSystemNotificationProviderRequest](#getfilesystemnotificationproviderrequest)) -[GetFileSystemNotificationProviderResponse](#getfilesystemnotificationproviderresponse) - -Get file system notification provider - - - - GET: /notification/provider/file - - -### GetLogNotificationProvider - -> **rpc** GetLogNotificationProvider([GetLogNotificationProviderRequest](#getlognotificationproviderrequest)) -[GetLogNotificationProviderResponse](#getlognotificationproviderresponse) - -Get log notification provider - - - - GET: /notification/provider/log - - ### GetOIDCSettings > **rpc** GetOIDCSettings([GetOIDCSettingsRequest](#getoidcsettingsrequest)) @@ -236,6 +212,30 @@ Update oidc settings (e.g token lifetimes, etc) PUT: /settings/oidc +### GetFileSystemNotificationProvider + +> **rpc** GetFileSystemNotificationProvider([GetFileSystemNotificationProviderRequest](#getfilesystemnotificationproviderrequest)) +[GetFileSystemNotificationProviderResponse](#getfilesystemnotificationproviderresponse) + +Get file system notification provider + + + + GET: /notification/provider/file + + +### GetLogNotificationProvider + +> **rpc** GetLogNotificationProvider([GetLogNotificationProviderRequest](#getlognotificationproviderrequest)) +[GetLogNotificationProviderResponse](#getlognotificationproviderresponse) + +Get log notification provider + + + + GET: /notification/provider/log + + ### GetOrgByID > **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest)) diff --git a/internal/api/grpc/admin/org.go b/internal/api/grpc/admin/org.go index a660d2aa58..6b3f9a2ea1 100644 --- a/internal/api/grpc/admin/org.go +++ b/internal/api/grpc/admin/org.go @@ -53,11 +53,11 @@ func (s *Server) SetUpOrg(ctx context.Context, req *admin_pb.SetUpOrgRequest) (* human := setUpOrgHumanToDomain(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine org := setUpOrgOrgToDomain(req.Org) - initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg) + initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, err } - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } diff --git a/internal/api/grpc/admin/server.go b/internal/api/grpc/admin/server.go index dfcdc23e6d..38bc6041d9 100644 --- a/internal/api/grpc/admin/server.go +++ b/internal/api/grpc/admin/server.go @@ -1,7 +1,6 @@ package admin import ( - "github.com/caos/zitadel/internal/crypto" "google.golang.org/grpc" "github.com/caos/zitadel/internal/admin/repository" @@ -9,6 +8,7 @@ import ( "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/server" "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/pkg/grpc/admin" ) @@ -26,22 +26,27 @@ type Server struct { administrator repository.AdministratorRepository iamDomain string assetsAPIDomain string - - UserCodeAlg crypto.EncryptionAlgorithm + userCodeAlg crypto.EncryptionAlgorithm } type Config struct { Repository eventsourcing.Config } -func CreateServer(command *command.Commands, query *query.Queries, repo repository.Repository, iamDomain, assetsAPIDomain string, userCrypto *crypto.AESCrypto) *Server { +func CreateServer(command *command.Commands, + query *query.Queries, + repo repository.Repository, + iamDomain, + assetsAPIDomain string, + userCodeAlg crypto.EncryptionAlgorithm, +) *Server { return &Server{ command: command, query: query, administrator: repo, iamDomain: iamDomain, assetsAPIDomain: assetsAPIDomain, - UserCodeAlg: userCrypto, + userCodeAlg: userCodeAlg, } } diff --git a/internal/api/grpc/auth/email.go b/internal/api/grpc/auth/email.go index 465f14ac29..6fa3f2ce84 100644 --- a/internal/api/grpc/auth/email.go +++ b/internal/api/grpc/auth/email.go @@ -27,7 +27,7 @@ func (s *Server) GetMyEmail(ctx context.Context, _ *auth_pb.GetMyEmailRequest) ( } func (s *Server) SetMyEmail(ctx context.Context, req *auth_pb.SetMyEmailRequest) (*auth_pb.SetMyEmailResponse, error) { - emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg) + emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg) if err != nil { return nil, err } @@ -45,7 +45,7 @@ func (s *Server) SetMyEmail(ctx context.Context, req *auth_pb.SetMyEmailRequest) } func (s *Server) VerifyMyEmail(ctx context.Context, req *auth_pb.VerifyMyEmailRequest) (*auth_pb.VerifyMyEmailResponse, error) { - emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg) + emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg) if err != nil { return nil, err } @@ -61,7 +61,7 @@ func (s *Server) VerifyMyEmail(ctx context.Context, req *auth_pb.VerifyMyEmailRe func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.ResendMyEmailVerificationRequest) (*auth_pb.ResendMyEmailVerificationResponse, error) { ctxData := authz.GetCtxData(ctx) - emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg) + emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/passwordless.go b/internal/api/grpc/auth/passwordless.go index fee5c0c7e6..ffe9a2e275 100644 --- a/internal/api/grpc/auth/passwordless.go +++ b/internal/api/grpc/auth/passwordless.go @@ -3,13 +3,13 @@ package auth import ( "context" - "github.com/caos/zitadel/internal/query" "google.golang.org/protobuf/types/known/durationpb" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/object" user_grpc "github.com/caos/zitadel/internal/api/grpc/user" "github.com/caos/zitadel/internal/domain" + "github.com/caos/zitadel/internal/query" auth_pb "github.com/caos/zitadel/pkg/grpc/auth" user_pb "github.com/caos/zitadel/pkg/grpc/user" ) @@ -57,7 +57,7 @@ func (s *Server) AddMyPasswordless(ctx context.Context, _ *auth_pb.AddMyPassword func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPasswordlessLinkRequest) (*auth_pb.AddMyPasswordlessLinkResponse, error) { ctxData := authz.GetCtxData(ctx) - passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg) + passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg) if err != nil { return nil, err } @@ -74,7 +74,7 @@ func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPass func (s *Server) SendMyPasswordlessLink(ctx context.Context, _ *auth_pb.SendMyPasswordlessLinkRequest) (*auth_pb.SendMyPasswordlessLinkResponse, error) { ctxData := authz.GetCtxData(ctx) - passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg) + passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/phone.go b/internal/api/grpc/auth/phone.go index 60efca999d..f620d725fd 100644 --- a/internal/api/grpc/auth/phone.go +++ b/internal/api/grpc/auth/phone.go @@ -27,7 +27,7 @@ func (s *Server) GetMyPhone(ctx context.Context, _ *auth_pb.GetMyPhoneRequest) ( } func (s *Server) SetMyPhone(ctx context.Context, req *auth_pb.SetMyPhoneRequest) (*auth_pb.SetMyPhoneResponse, error) { - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } @@ -46,7 +46,7 @@ func (s *Server) SetMyPhone(ctx context.Context, req *auth_pb.SetMyPhoneRequest) func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRequest) (*auth_pb.VerifyMyPhoneResponse, error) { ctxData := authz.GetCtxData(ctx) - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } @@ -62,7 +62,7 @@ func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRe func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) { ctxData := authz.GetCtxData(ctx) - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } diff --git a/internal/api/grpc/auth/server.go b/internal/api/grpc/auth/server.go index c40295ac8b..f2e1f49f27 100644 --- a/internal/api/grpc/auth/server.go +++ b/internal/api/grpc/auth/server.go @@ -1,7 +1,6 @@ package auth import ( - "github.com/caos/zitadel/internal/crypto" "google.golang.org/grpc" "github.com/caos/zitadel/internal/api/authz" @@ -10,6 +9,7 @@ import ( "github.com/caos/zitadel/internal/auth/repository/eventsourcing" "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/pkg/grpc/auth" ) @@ -27,21 +27,27 @@ type Server struct { repo repository.Repository defaults systemdefaults.SystemDefaults assetsAPIDomain string - UserCodeAlg crypto.EncryptionAlgorithm + userCodeAlg crypto.EncryptionAlgorithm } type Config struct { Repository eventsourcing.Config } -func CreateServer(command *command.Commands, query *query.Queries, authRepo repository.Repository, defaults systemdefaults.SystemDefaults, assetsAPIDomain string, userCrypto *crypto.AESCrypto) *Server { +func CreateServer(command *command.Commands, + query *query.Queries, + authRepo repository.Repository, + defaults systemdefaults.SystemDefaults, + assetsAPIDomain string, + userCodeAlg crypto.EncryptionAlgorithm, +) *Server { return &Server{ command: command, query: query, repo: authRepo, defaults: defaults, assetsAPIDomain: assetsAPIDomain, - UserCodeAlg: userCrypto, + userCodeAlg: userCodeAlg, } } diff --git a/internal/api/grpc/management/project_application.go b/internal/api/grpc/management/project_application.go index 11886acbf0..c5148a6bbc 100644 --- a/internal/api/grpc/management/project_application.go +++ b/internal/api/grpc/management/project_application.go @@ -58,7 +58,7 @@ func (s *Server) ListAppChanges(ctx context.Context, req *mgmt_pb.ListAppChanges } func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) (*mgmt_pb.AddOIDCAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.PasswordHashAlg) + appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) if err != nil { return nil, err } @@ -77,7 +77,7 @@ func (s *Server) AddOIDCApp(ctx context.Context, req *mgmt_pb.AddOIDCAppRequest) } func (s *Server) AddAPIApp(ctx context.Context, req *mgmt_pb.AddAPIAppRequest) (*mgmt_pb.AddAPIAppResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.PasswordHashAlg) + appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) if err != nil { return nil, err } @@ -162,7 +162,7 @@ func (s *Server) RemoveApp(ctx context.Context, req *mgmt_pb.RemoveAppRequest) ( } func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.RegenerateOIDCClientSecretRequest) (*mgmt_pb.RegenerateOIDCClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.PasswordHashAlg) + appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) if err != nil { return nil, err } @@ -181,7 +181,7 @@ func (s *Server) RegenerateOIDCClientSecret(ctx context.Context, req *mgmt_pb.Re } func (s *Server) RegenerateAPIClientSecret(ctx context.Context, req *mgmt_pb.RegenerateAPIClientSecretRequest) (*mgmt_pb.RegenerateAPIClientSecretResponse, error) { - appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.PasswordHashAlg) + appSecretGenerator, err := s.query.InitHashGenerator(ctx, domain.SecretGeneratorTypeAppSecret, s.passwordHashAlg) if err != nil { return nil, err } diff --git a/internal/api/grpc/management/server.go b/internal/api/grpc/management/server.go index 56778acb74..2bccf2a57f 100644 --- a/internal/api/grpc/management/server.go +++ b/internal/api/grpc/management/server.go @@ -1,13 +1,13 @@ package management import ( - "github.com/caos/zitadel/internal/crypto" "google.golang.org/grpc" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/grpc/server" "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/config/systemdefaults" + "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/pkg/grpc/management" ) @@ -24,18 +24,23 @@ type Server struct { query *query.Queries systemDefaults systemdefaults.SystemDefaults assetAPIPrefix string - PasswordHashAlg crypto.HashAlgorithm - UserCodeAlg crypto.EncryptionAlgorithm + passwordHashAlg crypto.HashAlgorithm + userCodeAlg crypto.EncryptionAlgorithm } -func CreateServer(command *command.Commands, query *query.Queries, sd systemdefaults.SystemDefaults, assetAPIPrefix string, userCrypto *crypto.AESCrypto) *Server { +func CreateServer(command *command.Commands, + query *query.Queries, + sd systemdefaults.SystemDefaults, + assetAPIPrefix string, + userCodeAlg crypto.EncryptionAlgorithm, +) *Server { return &Server{ command: command, query: query, systemDefaults: sd, assetAPIPrefix: assetAPIPrefix, - PasswordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), - UserCodeAlg: userCrypto, + passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), + userCodeAlg: userCodeAlg, } } diff --git a/internal/api/grpc/management/user.go b/internal/api/grpc/management/user.go index a9cd0161ee..149ec8f7c2 100644 --- a/internal/api/grpc/management/user.go +++ b/internal/api/grpc/management/user.go @@ -192,11 +192,11 @@ func (s *Server) BulkRemoveUserMetadata(ctx context.Context, req *mgmt_pb.BulkRe } func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequest) (*mgmt_pb.AddHumanUserResponse, error) { - initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg) + initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, err } - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } @@ -216,15 +216,15 @@ func (s *Server) AddHumanUser(ctx context.Context, req *mgmt_pb.AddHumanUserRequ func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) { human, passwordless := ImportHumanUserRequestToDomain(req) - initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg) + initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, err } - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } - passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg) + passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg) if err != nil { return nil, err } @@ -408,7 +408,7 @@ func (s *Server) GetHumanEmail(ctx context.Context, req *mgmt_pb.GetHumanEmailRe } func (s *Server) UpdateHumanEmail(ctx context.Context, req *mgmt_pb.UpdateHumanEmailRequest) (*mgmt_pb.UpdateHumanEmailResponse, error) { - emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg) + emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg) if err != nil { return nil, err } @@ -426,7 +426,7 @@ func (s *Server) UpdateHumanEmail(ctx context.Context, req *mgmt_pb.UpdateHumanE } func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.ResendHumanInitializationRequest) (*mgmt_pb.ResendHumanInitializationResponse, error) { - initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.UserCodeAlg) + initCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeInitCode, s.userCodeAlg) if err != nil { return nil, err } @@ -440,7 +440,7 @@ func (s *Server) ResendHumanInitialization(ctx context.Context, req *mgmt_pb.Res } func (s *Server) ResendHumanEmailVerification(ctx context.Context, req *mgmt_pb.ResendHumanEmailVerificationRequest) (*mgmt_pb.ResendHumanEmailVerificationResponse, error) { - emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.UserCodeAlg) + emailCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyEmailCode, s.userCodeAlg) if err != nil { return nil, err } @@ -474,7 +474,7 @@ func (s *Server) GetHumanPhone(ctx context.Context, req *mgmt_pb.GetHumanPhoneRe } func (s *Server) UpdateHumanPhone(ctx context.Context, req *mgmt_pb.UpdateHumanPhoneRequest) (*mgmt_pb.UpdateHumanPhoneResponse, error) { - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } @@ -502,7 +502,7 @@ func (s *Server) RemoveHumanPhone(ctx context.Context, req *mgmt_pb.RemoveHumanP } func (s *Server) ResendHumanPhoneVerification(ctx context.Context, req *mgmt_pb.ResendHumanPhoneVerificationRequest) (*mgmt_pb.ResendHumanPhoneVerificationResponse, error) { - phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.UserCodeAlg) + phoneCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypeVerifyPhoneCode, s.userCodeAlg) if err != nil { return nil, err } @@ -547,7 +547,7 @@ func (s *Server) SetHumanPassword(ctx context.Context, req *mgmt_pb.SetHumanPass } func (s *Server) SendHumanResetPasswordNotification(ctx context.Context, req *mgmt_pb.SendHumanResetPasswordNotificationRequest) (*mgmt_pb.SendHumanResetPasswordNotificationResponse, error) { - passwordCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordResetCode, s.UserCodeAlg) + passwordCodeGenerator, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordResetCode, s.userCodeAlg) if err != nil { return nil, err } @@ -628,7 +628,7 @@ func (s *Server) ListHumanPasswordless(ctx context.Context, req *mgmt_pb.ListHum func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.AddPasswordlessRegistrationRequest) (*mgmt_pb.AddPasswordlessRegistrationResponse, error) { ctxData := authz.GetCtxData(ctx) - passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg) + passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg) if err != nil { return nil, err } @@ -645,7 +645,7 @@ func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.A func (s *Server) SendPasswordlessRegistration(ctx context.Context, req *mgmt_pb.SendPasswordlessRegistrationRequest) (*mgmt_pb.SendPasswordlessRegistrationResponse, error) { ctxData := authz.GetCtxData(ctx) - passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.UserCodeAlg) + passwordlessInitCode, err := s.query.InitEncryptionGenerator(ctx, domain.SecretGeneratorTypePasswordlessInitCode, s.userCodeAlg) if err != nil { return nil, err } diff --git a/internal/api/http/middleware/user_agent_cookie.go b/internal/api/http/middleware/user_agent_cookie.go index 8df99fcc4c..90caf559d9 100644 --- a/internal/api/http/middleware/user_agent_cookie.go +++ b/internal/api/http/middleware/user_agent_cookie.go @@ -6,7 +6,6 @@ import ( "time" http_utils "github.com/caos/zitadel/internal/api/http" - "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/id" ) @@ -35,16 +34,10 @@ type userAgentHandler struct { type UserAgentCookieConfig struct { Name string - Key *crypto.KeyConfig MaxAge time.Duration } -func NewUserAgentHandler(config *UserAgentCookieConfig, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) { - key, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID) - if err != nil { - return nil, err - } - cookieKey := []byte(key) +func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) { opts := []http_utils.CookieHandlerOpt{ http_utils.WithEncryption(cookieKey, cookieKey), http_utils.WithDomain(domain), diff --git a/internal/api/oidc/op.go b/internal/api/oidc/op.go index 702bcff461..c5153824cd 100644 --- a/internal/api/oidc/op.go +++ b/internal/api/oidc/op.go @@ -19,6 +19,7 @@ import ( "github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/i18n" @@ -44,7 +45,6 @@ type Config struct { DefaultRefreshTokenExpiration time.Duration UserAgentCookieConfig *middleware.UserAgentCookieConfig Cache *middleware.CacheConfig - KeyConfig *crypto.KeyConfig CustomEndpoints *EndpointConfig } @@ -83,18 +83,15 @@ type OPStorage struct { assetAPIPrefix string } -func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie func(http.Handler) http.Handler) (op.OpenIDProvider, error) { - opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI) +func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedirectURI string, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie func(http.Handler) http.Handler) (op.OpenIDProvider, error) { + opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey) if err != nil { - return nil, fmt.Errorf("cannot create op config: %w", err) - } - storage, err := newStorage(config, command, query, repo, keyConfig, config.KeyConfig, es, projections, keyChan) - if err != nil { - return nil, fmt.Errorf("cannot create storage: %w", err) + return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w") } + storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan) options, err := createOptions(config, userAgentCookie) if err != nil { - return nil, fmt.Errorf("cannot create options: %w", err) + return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w") } provider, err := op.NewOpenIDProvider( ctx, @@ -103,7 +100,7 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire options..., ) if err != nil { - return nil, fmt.Errorf("cannot create provider: %w", err) + return nil, caos_errs.ThrowInternal(err, "OIDC-DAtg3", "cannot create provider: %w") } return provider, nil } @@ -112,7 +109,7 @@ func Issuer(domain string, port uint16, externalSecure bool) string { return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix } -func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string) (*op.Config, error) { +func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) { supportedLanguages, err := getSupportedLanguages() if err != nil { return nil, err @@ -127,25 +124,13 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string) (*op RequestObjectSupported: config.RequestObjectSupported, SupportedUILocales: supportedLanguages, } - if err := cryptoKey(opConfig, config.KeyConfig); err != nil { - return nil, err + if cryptoLength := len(cryptoKey); cryptoLength != 32 { + return nil, caos_errs.ThrowInternalf(nil, "OIDC-D43gf", "crypto key must be 32 bytes, but is %d", cryptoLength) } + copy(opConfig.CryptoKey[:], cryptoKey) return opConfig, nil } -func cryptoKey(config *op.Config, keyConfig *crypto.KeyConfig) error { - tokenKey, err := crypto.LoadKey(keyConfig, keyConfig.EncryptionKeyID) - if err != nil { - return fmt.Errorf("cannot load OP crypto key: %w", err) - } - cryptoKey := []byte(tokenKey) - if len(cryptoKey) != 32 { - return fmt.Errorf("OP crypto key must be exactly 32 bytes") - } - copy(config.CryptoKey[:], cryptoKey) - return nil -} - func createOptions(config Config, userAgentCookie func(http.Handler) http.Handler) ([]op.Option, error) { metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} interceptor := op.WithHttpInterceptors( @@ -191,11 +176,7 @@ func customEndpoints(endpointConfig *EndpointConfig) []op.Option { return options } -func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, c *crypto.KeyConfig, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) (*OPStorage, error) { - encAlg, err := crypto.NewAESCrypto(c) - if err != nil { - return nil, err - } +func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) *OPStorage { return &OPStorage{ repo: repo, command: command, @@ -213,7 +194,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries, locker: crdb.NewLocker(projections, locksTable, signingKey), keyChan: keyChan, assetAPIPrefix: assets.HandlerPrefix, - }, nil + } } func (o *OPStorage) Health(ctx context.Context) error { diff --git a/internal/api/ui/login/external_login_handler.go b/internal/api/ui/login/external_login_handler.go index 4e9b52171d..13c5c672e9 100644 --- a/internal/api/ui/login/external_login_handler.go +++ b/internal/api/ui/login/external_login_handler.go @@ -74,7 +74,7 @@ func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) { return } if authReq == nil { - http.Redirect(w, r, l.zitadelURL, http.StatusFound) + http.Redirect(w, r, l.consolePath, http.StatusFound) return } l.handleIDP(w, r, authReq, data.IDPConfigID) @@ -121,7 +121,7 @@ func (l *Login) handleJWTAuthorize(w http.ResponseWriter, r *http.Request, authR l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound")) return } - nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(userAgentID)) + nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID)) if err != nil { l.renderLogin(w, r, authReq, err) return @@ -167,7 +167,7 @@ func (l *Login) handleExternalLoginCallback(w http.ResponseWriter, r *http.Reque } func (l *Login) getRPConfig(idpConfig *iam_model.IDPConfigView, callbackEndpoint string) (rp.RelyingParty, error) { - oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.IDPConfigAesCrypto) + oidcClientSecret, err := crypto.DecryptString(idpConfig.OIDCClientSecret, l.idpConfigAlg) if err != nil { return nil, err } diff --git a/internal/api/ui/login/external_register_handler.go b/internal/api/ui/login/external_register_handler.go index dd4a844839..550a160e7a 100644 --- a/internal/api/ui/login/external_register_handler.go +++ b/internal/api/ui/login/external_register_handler.go @@ -58,7 +58,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) { return } if authReq == nil { - http.Redirect(w, r, l.zitadelURL, http.StatusFound) + http.Redirect(w, r, l.consolePath, http.StatusFound) return } idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID) @@ -145,12 +145,12 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut memberRoles = nil resourceOwner = authReq.RequestedOrgID } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg) if err != nil { l.renderRegisterOption(w, r, authReq, err) return } - phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg) + phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg) if err != nil { l.renderRegisterOption(w, r, authReq, err) return @@ -226,12 +226,12 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque l.renderRegisterOption(w, r, authReq, err) return } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg) if err != nil { l.renderRegisterOption(w, r, authReq, err) return } - phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg) + phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg) if err != nil { l.renderRegisterOption(w, r, authReq, err) return diff --git a/internal/api/ui/login/init_password_handler.go b/internal/api/ui/login/init_password_handler.go index 238bf46431..97f2aa8467 100644 --- a/internal/api/ui/login/init_password_handler.go +++ b/internal/api/ui/login/init_password_handler.go @@ -70,7 +70,7 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom userOrg = authReq.UserOrgID } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) - passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg) + passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.userCodeAlg) if err != nil { l.renderInitPassword(w, r, authReq, data.UserID, "", err) return @@ -97,7 +97,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) return } - passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg) + passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.userCodeAlg) if err != nil { l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) return diff --git a/internal/api/ui/login/init_user_handler.go b/internal/api/ui/login/init_user_handler.go index 31fd70eac3..2ad369d995 100644 --- a/internal/api/ui/login/init_user_handler.go +++ b/internal/api/ui/login/init_user_handler.go @@ -73,7 +73,7 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe if authReq != nil { userOrgID = authReq.UserOrgID } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg) if err != nil { l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err) return @@ -91,7 +91,7 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq * if authReq != nil { userOrgID = authReq.UserOrgID } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg) if err != nil { l.renderInitUser(w, r, authReq, userID, "", showPassword, err) return diff --git a/internal/api/ui/login/jwt_handler.go b/internal/api/ui/login/jwt_handler.go index da8b69afae..b007a670e2 100644 --- a/internal/api/ui/login/jwt_handler.go +++ b/internal/api/ui/login/jwt_handler.go @@ -39,7 +39,7 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, nil, err) return } - userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID()) + userAgentID, err := l.idpConfigAlg.DecryptString(id, l.idpConfigAlg.EncryptionKeyID()) if err != nil { l.renderError(w, r, nil, err) return @@ -181,7 +181,7 @@ func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, erro } q := redirect.Query() q.Set(QueryAuthRequestID, authReq.ID) - nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(authReq.AgentID)) + nonce, err := l.idpConfigAlg.Encrypt([]byte(authReq.AgentID)) if err != nil { return "", err } @@ -202,7 +202,7 @@ func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) { l.renderError(w, r, nil, err) return } - userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID()) + userAgentID, err := l.idpConfigAlg.DecryptString(id, l.idpConfigAlg.EncryptionKeyID()) if err != nil { l.renderError(w, r, nil, err) return diff --git a/internal/api/ui/login/login.go b/internal/api/ui/login/login.go index 057f3bed2d..5f68288305 100644 --- a/internal/api/ui/login/login.go +++ b/internal/api/ui/login/login.go @@ -35,47 +35,54 @@ type Login struct { //staticCache cache.Cache //TODO: enable when storage is implemented again authRepo auth_repository.Repository baseURL string - zitadelURL string + consolePath string oidcAuthCallbackURL string - IDPConfigAesCrypto crypto.EncryptionAlgorithm - UserCodeAlg crypto.EncryptionAlgorithm + idpConfigAlg crypto.EncryptionAlgorithm + userCodeAlg crypto.EncryptionAlgorithm iamDomain string } type Config struct { LanguageCookieName string - CSRF CSRF + CSRFCookieName string Cache middleware.CacheConfig //StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again } -type CSRF struct { - CookieName string - Key *crypto.KeyConfig -} - const ( login = "LOGIN" HandlerPrefix = "/ui/login" DefaultLoggedOutPath = HandlerPrefix + EndpointLogoutDone ) -func CreateLogin(config Config, command *command.Commands, query *query.Queries, authRepo *eventsourcing.EsRepository, staticStorage static.Storage, systemDefaults systemdefaults.SystemDefaults, zitadelURL, domain, oidcAuthCallbackURL string, externalSecure bool, userAgentCookie mux.MiddlewareFunc, userCrypto *crypto.AESCrypto) (*Login, error) { - aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey) - if err != nil { - return nil, fmt.Errorf("error create new aes crypto: %w", err) - } +func CreateLogin(config Config, + command *command.Commands, + query *query.Queries, + authRepo *eventsourcing.EsRepository, + staticStorage static.Storage, + systemDefaults systemdefaults.SystemDefaults, + consolePath, + domain, + baseURL, + oidcAuthCallbackURL string, + externalSecure bool, + userAgentCookie mux.MiddlewareFunc, + userCodeAlg crypto.EncryptionAlgorithm, + idpConfigAlg crypto.EncryptionAlgorithm, + csrfCookieKey []byte, +) (*Login, error) { + login := &Login{ oidcAuthCallbackURL: oidcAuthCallbackURL, - baseURL: HandlerPrefix, - zitadelURL: zitadelURL, + baseURL: baseURL + HandlerPrefix, + consolePath: consolePath, command: command, query: query, staticStorage: staticStorage, authRepo: authRepo, - IDPConfigAesCrypto: aesCrypto, iamDomain: domain, - UserCodeAlg: userCrypto, + idpConfigAlg: idpConfigAlg, + userCodeAlg: userCodeAlg, } //TODO: enable when storage is implemented again //login.staticCache, err = config.StaticCache.Config.NewCache() @@ -88,7 +95,7 @@ func CreateLogin(config Config, command *command.Commands, query *query.Queries, return nil, fmt.Errorf("unable to create filesystem: %w", err) } - csrfInterceptor, err := createCSRFInterceptor(config.CSRF, externalSecure, login.csrfErrorHandler()) + csrfInterceptor, err := createCSRFInterceptor(config.CSRFCookieName, csrfCookieKey, externalSecure, login.csrfErrorHandler()) if err != nil { return nil, fmt.Errorf("unable to create csrfInterceptor: %w", err) } @@ -111,15 +118,11 @@ func csp() *middleware.CSP { return &csp } -func createCSRFInterceptor(config CSRF, externalSecure bool, errorHandler http.Handler) (func(http.Handler) http.Handler, error) { - csrfKey, err := crypto.LoadKey(config.Key, config.Key.EncryptionKeyID) - if err != nil { - return nil, err - } +func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, externalSecure bool, errorHandler http.Handler) (func(http.Handler) http.Handler, error) { path := "/" - return csrf.Protect([]byte(csrfKey), + return csrf.Protect(csrfCookieKey, csrf.Secure(externalSecure), - csrf.CookieName(http_utils.SetCookiePrefix(config.CookieName, "", path, externalSecure)), + csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)), csrf.Path(path), csrf.ErrorHandler(errorHandler), ), nil diff --git a/internal/api/ui/login/login_handler.go b/internal/api/ui/login/login_handler.go index 489e795989..c1400cadcf 100644 --- a/internal/api/ui/login/login_handler.go +++ b/internal/api/ui/login/login_handler.go @@ -24,7 +24,7 @@ func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) { return } if authReq == nil { - http.Redirect(w, r, l.zitadelURL, http.StatusFound) + http.Redirect(w, r, l.consolePath, http.StatusFound) return } l.renderNextStep(w, r, authReq) diff --git a/internal/api/ui/login/mail_verify_handler.go b/internal/api/ui/login/mail_verify_handler.go index fdfb80e69a..08c7f03730 100644 --- a/internal/api/ui/login/mail_verify_handler.go +++ b/internal/api/ui/login/mail_verify_handler.go @@ -51,7 +51,7 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque if authReq != nil { userOrg = authReq.UserOrgID } - emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.UserCodeAlg) + emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg) if err != nil { l.checkMailCode(w, r, authReq, data.UserID, data.Code) return @@ -66,7 +66,7 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d userID = authReq.UserID userOrg = authReq.UserOrgID } - emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.UserCodeAlg) + emailCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyEmailCode, l.userCodeAlg) if err != nil { l.renderMailVerification(w, r, authReq, userID, err) return diff --git a/internal/api/ui/login/password_reset_handler.go b/internal/api/ui/login/password_reset_handler.go index 8f28ff914e..cc1ef0560a 100644 --- a/internal/api/ui/login/password_reset_handler.go +++ b/internal/api/ui/login/password_reset_handler.go @@ -27,7 +27,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) { l.renderPasswordResetDone(w, r, authReq, err) return } - passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.UserCodeAlg) + passwordCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordResetCode, l.userCodeAlg) if err != nil { l.renderPasswordResetDone(w, r, authReq, err) return diff --git a/internal/api/ui/login/register_handler.go b/internal/api/ui/login/register_handler.go index 546663378a..7b2057e06c 100644 --- a/internal/api/ui/login/register_handler.go +++ b/internal/api/ui/login/register_handler.go @@ -74,12 +74,12 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { memberRoles = nil resourceOwner = authRequest.RequestedOrgID } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeInitCode, l.userCodeAlg) if err != nil { l.renderRegister(w, r, authRequest, data, err) return } - phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg) + phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg) if err != nil { l.renderRegister(w, r, authRequest, data, err) return @@ -90,7 +90,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) { return } if authRequest == nil { - http.Redirect(w, r, l.zitadelURL, http.StatusFound) + http.Redirect(w, r, l.consolePath, http.StatusFound) return } userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) diff --git a/internal/api/ui/login/register_org_handler.go b/internal/api/ui/login/register_org_handler.go index 2e0854f7c6..800171dfd7 100644 --- a/internal/api/ui/login/register_org_handler.go +++ b/internal/api/ui/login/register_org_handler.go @@ -65,12 +65,12 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) { l.renderRegisterOrg(w, r, authRequest, data, err) return } - initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordlessInitCode, l.UserCodeAlg) + initCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypePasswordlessInitCode, l.userCodeAlg) if err != nil { l.renderRegisterOrg(w, r, authRequest, data, err) return } - phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.UserCodeAlg) + phoneCodeGenerator, err := l.query.InitEncryptionGenerator(r.Context(), domain.SecretGeneratorTypeVerifyPhoneCode, l.userCodeAlg) if err != nil { l.renderRegisterOrg(w, r, authRequest, data, err) return @@ -81,7 +81,7 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) { return } if authRequest == nil { - http.Redirect(w, r, l.zitadelURL, http.StatusFound) + http.Redirect(w, r, l.consolePath, http.StatusFound) return } l.renderNextStep(w, r, authRequest) diff --git a/internal/auth/repository/eventsourcing/eventstore/auth_request.go b/internal/auth/repository/eventsourcing/eventstore/auth_request.go index 82b1099160..71a5ad4068 100644 --- a/internal/auth/repository/eventsourcing/eventstore/auth_request.go +++ b/internal/auth/repository/eventsourcing/eventstore/auth_request.go @@ -5,13 +5,13 @@ import ( "time" "github.com/caos/logging" - "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" "github.com/caos/zitadel/internal/auth_request/model" cache "github.com/caos/zitadel/internal/auth_request/repository" "github.com/caos/zitadel/internal/command" + "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/errors" v1 "github.com/caos/zitadel/internal/eventstore/v1" @@ -666,15 +666,20 @@ func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy { CreationDate: policy.CreationDate, ChangeDate: policy.ChangeDate, }, - Default: policy.IsDefault, - AllowUsernamePassword: policy.AllowUsernamePassword, - AllowRegister: policy.AllowRegister, - AllowExternalIDP: policy.AllowExternalIDPs, - ForceMFA: policy.ForceMFA, - SecondFactors: policy.SecondFactors, - MultiFactors: policy.MultiFactors, - PasswordlessType: policy.PasswordlessType, - HidePasswordReset: policy.HidePasswordReset, + Default: policy.IsDefault, + AllowUsernamePassword: policy.AllowUsernamePassword, + AllowRegister: policy.AllowRegister, + AllowExternalIDP: policy.AllowExternalIDPs, + ForceMFA: policy.ForceMFA, + SecondFactors: policy.SecondFactors, + MultiFactors: policy.MultiFactors, + PasswordlessType: policy.PasswordlessType, + HidePasswordReset: policy.HidePasswordReset, + PasswordCheckLifetime: policy.PasswordCheckLifetime, + ExternalLoginCheckLifetime: policy.ExternalLoginCheckLifetime, + MFAInitSkipLifetime: policy.MFAInitSkipLifetime, + SecondFactorCheckLifetime: policy.SecondFactorCheckLifetime, + MultiFactorCheckLifetime: policy.MultiFactorCheckLifetime, } } diff --git a/internal/auth/repository/eventsourcing/repository.go b/internal/auth/repository/eventsourcing/repository.go index 3ebb4d1b98..ebb6305236 100644 --- a/internal/auth/repository/eventsourcing/repository.go +++ b/internal/auth/repository/eventsourcing/repository.go @@ -33,19 +33,14 @@ type EsRepository struct { eventstore.OrgRepository } -func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig, assetsPrefix string, userCrypto *crypto.AESCrypto) (*EsRepository, error) { +func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, oidcEncryption crypto.EncryptionAlgorithm, userEncryption crypto.EncryptionAlgorithm) (*EsRepository, error) { es, err := v1.Start(dbClient) if err != nil { return nil, err } - - keyAlgorithm, err := crypto.NewAESCrypto(keyConfig) - if err != nil { - return nil, err - } idGenerator := id.SonyFlakeGenerator - view, err := auth_view.StartView(dbClient, keyAlgorithm, queries, idGenerator, assetsPrefix) + view, err := auth_view.StartView(dbClient, oidcEncryption, queries, idGenerator, assetsPrefix) if err != nil { return nil, err } @@ -80,7 +75,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma AuthRequests: authReq, View: view, Eventstore: es, - UserCodeAlg: userCrypto, + UserCodeAlg: userEncryption, UserSessionViewProvider: view, UserViewProvider: view, UserCommandProvider: command, @@ -101,7 +96,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma View: view, Eventstore: es, SearchLimit: conf.SearchLimit, - KeyAlgorithm: keyAlgorithm, + KeyAlgorithm: oidcEncryption, }, eventstore.UserSessionRepo{ View: view, diff --git a/internal/authz/authz.go b/internal/authz/authz.go index e77e674d51..f9c9c3db49 100644 --- a/internal/authz/authz.go +++ b/internal/authz/authz.go @@ -14,6 +14,6 @@ type Config struct { Repository eventsourcing.Config } -func Start(config Config, systemDefaults sd.SystemDefaults, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig) (repository.Repository, error) { - return eventsourcing.Start(config.Repository, systemDefaults, queries, dbClient, keyConfig) +func Start(config Config, systemDefaults sd.SystemDefaults, queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) (repository.Repository, error) { + return eventsourcing.Start(config.Repository, systemDefaults, queries, dbClient, keyEncryptionAlgorithm) } diff --git a/internal/authz/repository/eventsourcing/repository.go b/internal/authz/repository/eventsourcing/repository.go index 6df3e066b3..a46a3209a7 100644 --- a/internal/authz/repository/eventsourcing/repository.go +++ b/internal/authz/repository/eventsourcing/repository.go @@ -26,7 +26,7 @@ type EsRepository struct { eventstore.TokenVerifierRepo } -func Start(conf Config, systemDefaults sd.SystemDefaults, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig) (repository.Repository, error) { +func Start(conf Config, systemDefaults sd.SystemDefaults, queries *query.Queries, dbClient *sql.DB, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) (repository.Repository, error) { es, err := v1.Start(dbClient) if err != nil { return nil, err @@ -40,18 +40,13 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, queries *query.Queries spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, systemDefaults) - keyAlgorithm, err := crypto.NewAESCrypto(keyConfig) - if err != nil { - return nil, err - } - return &EsRepository{ spool, eventstore.UserMembershipRepo{ View: view, }, eventstore.TokenVerifierRepo{ - TokenVerificationKey: keyAlgorithm, + TokenVerificationKey: keyEncryptionAlgorithm, Eventstore: es, View: view, Query: queries, diff --git a/internal/command/command.go b/internal/command/command.go index f2d27dadf8..7de69a216d 100644 --- a/internal/command/command.go +++ b/internal/command/command.go @@ -54,28 +54,33 @@ type orgFeatureChecker interface { CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error } -func StartCommands( - es *eventstore.Eventstore, +func StartCommands(es *eventstore.Eventstore, defaults sd.SystemDefaults, authZConfig authz.Config, staticStore static.Storage, authZRepo authz_repo.Repository, - keyConfig *crypto.KeyConfig, webAuthN webauthn_helper.Config, - smtpPasswordEncAlg crypto.EncryptionAlgorithm, - smsHashAlg crypto.EncryptionAlgorithm, + idpConfigEncryption, + otpEncryption, + smtpEncryption, + smsEncryption, + domainVerificationEncryption, + oidcEncryption crypto.EncryptionAlgorithm, ) (repo *Commands, err error) { repo = &Commands{ - eventstore: es, - static: staticStore, - idGenerator: id.SonyFlakeGenerator, - iamDomain: defaults.Domain, - zitadelRoles: authZConfig.RolePermissionMappings, - keySize: defaults.KeyConfig.Size, - privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime, - publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime, - smtpPasswordCrypto: smtpPasswordEncAlg, - smsCrypto: smsHashAlg, + eventstore: es, + static: staticStore, + idGenerator: id.SonyFlakeGenerator, + iamDomain: defaults.Domain, + zitadelRoles: authZConfig.RolePermissionMappings, + keySize: defaults.KeyConfig.Size, + privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime, + publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime, + idpConfigSecretCrypto: idpConfigEncryption, + smtpPasswordCrypto: smtpEncryption, + smsCrypto: smsEncryption, + domainVerificationAlg: domainVerificationEncryption, + keyAlgorithm: oidcEncryption, } iam_repo.RegisterEventMappers(repo.eventstore) org.RegisterEventMappers(repo.eventstore) @@ -85,30 +90,17 @@ func StartCommands( keypair.RegisterEventMappers(repo.eventstore) action.RegisterEventMappers(repo.eventstore) - repo.idpConfigSecretCrypto, err = crypto.NewAESCrypto(defaults.IDPConfigVerificationKey) - if err != nil { - return nil, err - } - repo.userPasswordAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost) repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize) repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize) - aesOTPCrypto, err := crypto.NewAESCrypto(defaults.Multifactors.OTP.VerificationKey) - if err != nil { - return nil, err - } repo.multifactors = domain.MultifactorConfigs{ OTP: domain.OTPConfig{ - CryptoMFA: aesOTPCrypto, + CryptoMFA: otpEncryption, Issuer: defaults.Multifactors.OTP.Issuer, }, } - repo.domainVerificationAlg, err = crypto.NewAESCrypto(defaults.DomainVerification.VerificationKey) - if err != nil { - return nil, err - } repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg) repo.domainVerificationValidator = http.ValidateDomain web, err := webauthn_helper.StartServer(webAuthN) @@ -117,12 +109,6 @@ func StartCommands( } repo.webauthn = web - keyAlgorithm, err := crypto.NewAESCrypto(keyConfig) - if err != nil { - return nil, err - } - repo.keyAlgorithm = keyAlgorithm - repo.tokenVerifier = authZRepo return repo, nil } diff --git a/internal/config/systemdefaults/system_defaults.go b/internal/config/systemdefaults/system_defaults.go index 76f9d5d0e5..003aacd2c4 100644 --- a/internal/config/systemdefaults/system_defaults.go +++ b/internal/config/systemdefaults/system_defaults.go @@ -6,25 +6,17 @@ import ( "golang.org/x/text/language" "github.com/caos/zitadel/internal/crypto" - "github.com/caos/zitadel/internal/notification/channels/fs" - "github.com/caos/zitadel/internal/notification/channels/log" - "github.com/caos/zitadel/internal/notification/channels/twilio" - "github.com/caos/zitadel/internal/notification/templates" ) type SystemDefaults struct { - DefaultLanguage language.Tag - Domain string - ZitadelDocs ZitadelDocs - SecretGenerators SecretGenerators - UserVerificationKey *crypto.KeyConfig - IDPConfigVerificationKey *crypto.KeyConfig - SMTPPasswordVerificationKey *crypto.KeyConfig - SMSVerificationKey *crypto.KeyConfig - Multifactors MultifactorConfig - DomainVerification DomainVerification - Notifications Notifications - KeyConfig KeyConfig + DefaultLanguage language.Tag + Domain string + ZitadelDocs ZitadelDocs + SecretGenerators SecretGenerators + Multifactors MultifactorConfig + DomainVerification DomainVerification + Notifications Notifications + KeyConfig KeyConfig } type ZitadelDocs struct { @@ -43,20 +35,16 @@ type MultifactorConfig struct { } type OTPConfig struct { - Issuer string - VerificationKey *crypto.KeyConfig + Issuer string } type DomainVerification struct { - VerificationKey *crypto.KeyConfig VerificationGenerator crypto.GeneratorConfig } type Notifications struct { - DebugMode bool Endpoints Endpoints FileSystemPath string - //Providers Channels } type Endpoints struct { @@ -67,20 +55,6 @@ type Endpoints struct { PasswordlessRegistration string } -type Channels struct { - Twilio twilio.TwilioConfig - FileSystem fs.FSConfig - Log log.LogConfig -} - -type TemplateData struct { - InitCode templates.TemplateData - PasswordReset templates.TemplateData - VerifyEmail templates.TemplateData - VerifyPhone templates.TemplateData - DomainClaimed templates.TemplateData -} - type KeyConfig struct { Size int PrivateKeyLifetime time.Duration diff --git a/internal/crypto/aes.go b/internal/crypto/aes.go index 892b098278..afdb545748 100644 --- a/internal/crypto/aes.go +++ b/internal/crypto/aes.go @@ -18,8 +18,8 @@ type AESCrypto struct { keyIDs []string } -func NewAESCrypto(config *KeyConfig) (*AESCrypto, error) { - keys, ids, err := LoadKeys(config) +func NewAESCrypto(config *KeyConfig, keyStorage KeyStorage) (*AESCrypto, error) { + keys, ids, err := LoadKeys(config, keyStorage) if err != nil { return nil, err } diff --git a/internal/crypto/database/database.go b/internal/crypto/database/database.go new file mode 100644 index 0000000000..b7f28e1e54 --- /dev/null +++ b/internal/crypto/database/database.go @@ -0,0 +1,133 @@ +package database + +import ( + "database/sql" + + sq "github.com/Masterminds/squirrel" + + "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" +) + +type database struct { + client *sql.DB + masterKey string + encrypt func(key, masterKey string) (encryptedKey string, err error) + decrypt func(encryptedKey, masterKey string) (key string, err error) +} + +const ( + EncryptionKeysTable = "system.encryption_keys" + encryptionKeysIDCol = "id" + encryptionKeysKeyCol = "key" +) + +func NewKeyStorage(client *sql.DB, masterKey string) (*database, error) { + if err := checkMasterKeyLength(masterKey); err != nil { + return nil, err + } + return &database{ + client: client, + masterKey: masterKey, + encrypt: crypto.EncryptAESString, + decrypt: crypto.DecryptAESString, + }, nil +} + +func (d *database) ReadKeys() (crypto.Keys, error) { + keys := make(map[string]string) + stmt, args, err := sq.Select(encryptionKeysIDCol, encryptionKeysKeyCol). + From(EncryptionKeysTable). + ToSql() + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read keys") + } + rows, err := d.client.Query(stmt, args...) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read keys") + } + for rows.Next() { + var id, encryptionKey string + err = rows.Scan(&id, &encryptionKey) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read keys") + } + key, err := d.decrypt(encryptionKey, d.masterKey) + if err != nil { + if err := rows.Close(); err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to close rows") + } + return nil, caos_errs.ThrowInternal(err, "", "unable to decrypt key") + } + keys[id] = key + } + if err := rows.Close(); err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to close rows") + } + return keys, err +} + +func (d *database) ReadKey(id string) (*crypto.Key, error) { + stmt, args, err := sq.Select(encryptionKeysKeyCol). + From(EncryptionKeysTable). + Where(sq.Eq{encryptionKeysIDCol: id}). + PlaceholderFormat(sq.Dollar). + ToSql() + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read key") + } + row := d.client.QueryRow(stmt, args...) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read key") + } + var encryptionKey string + err = row.Scan(&encryptionKey) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to read key") + } + key, err := d.decrypt(encryptionKey, d.masterKey) + if err != nil { + return nil, caos_errs.ThrowInternal(err, "", "unable to decrypt key") + } + return &crypto.Key{ + ID: id, + Value: key, + }, nil +} + +func (d *database) CreateKeys(keys ...*crypto.Key) error { + insert := sq.Insert(EncryptionKeysTable). + Columns(encryptionKeysIDCol, encryptionKeysKeyCol).PlaceholderFormat(sq.Dollar) + for _, key := range keys { + encryptionKey, err := d.encrypt(key.Value, d.masterKey) + if err != nil { + return caos_errs.ThrowInternal(err, "", "unable to encrypt key") + } + insert = insert.Values(key.ID, encryptionKey) + } + stmt, args, err := insert.ToSql() + if err != nil { + return caos_errs.ThrowInternal(err, "", "unable to insert new keys") + } + tx, err := d.client.Begin() + if err != nil { + return caos_errs.ThrowInternal(err, "", "unable to insert new keys") + } + _, err = tx.Exec(stmt, args...) + if err != nil { + tx.Rollback() + return caos_errs.ThrowInternal(err, "", "unable to insert new keys") + } + err = tx.Commit() + if err != nil { + return caos_errs.ThrowInternal(err, "", "unable to insert new keys") + } + return nil +} + +func checkMasterKeyLength(masterKey string) error { + if length := len([]byte(masterKey)); length != 32 { + return caos_errs.ThrowInternalf(nil, "", "masterkey must be 32 bytes, but is %d", length) + } + return nil +} diff --git a/internal/crypto/database/database_test.go b/internal/crypto/database/database_test.go new file mode 100644 index 0000000000..d266a08cee --- /dev/null +++ b/internal/crypto/database/database_test.go @@ -0,0 +1,524 @@ +package database + +import ( + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "regexp" + "testing" + + "github.com/DATA-DOG/go-sqlmock" + "github.com/stretchr/testify/assert" + + "github.com/caos/zitadel/internal/crypto" + caos_errs "github.com/caos/zitadel/internal/errors" +) + +func Test_database_ReadKeys(t *testing.T) { + type fields struct { + client db + masterKey string + decrypt func(encryptedKey, masterKey string) (key string, err error) + } + type res struct { + keys crypto.Keys + err func(error) bool + } + tests := []struct { + name string + fields fields + res res + }{ + { + "query fails, error", + fields{ + client: dbMock(t, expectQueryErr("SELECT id, key FROM system.encryption_keys", sql.ErrConnDone)), + masterKey: "", + decrypt: nil, + }, + res{ + err: func(err error) bool { + return errors.Is(err, sql.ErrConnDone) + }, + }, + }, + { + "decryption error", + fields{ + client: dbMock(t, expectQuery( + "SELECT id, key FROM system.encryption_keys", + []string{"id", "key"}, + [][]driver.Value{ + { + "id1", + "key1", + }, + })), + masterKey: "wrong key", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return "", fmt.Errorf("wrong masterkey") + }, + }, + res{ + err: caos_errs.IsInternal, + }, + }, + { + "single key ok", + fields{ + client: dbMock(t, expectQuery( + "SELECT id, key FROM system.encryption_keys", + []string{"id", "key"}, + [][]driver.Value{ + { + "id1", + "key1", + }, + })), + masterKey: "masterKey", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return encryptedKey, nil + }, + }, + res{ + keys: crypto.Keys(map[string]string{"id1": "key1"}), + }, + }, + { + "multiple keys ok", + fields{ + client: dbMock(t, expectQuery( + "SELECT id, key FROM system.encryption_keys", + []string{"id", "key"}, + [][]driver.Value{ + { + "id1", + "key1", + }, + { + "id2", + "key2", + }, + })), + masterKey: "masterKey", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return encryptedKey, nil + }, + }, + res{ + keys: crypto.Keys(map[string]string{"id1": "key1", "id2": "key2"}), + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &database{ + client: tt.fields.client.db, + masterKey: tt.fields.masterKey, + decrypt: tt.fields.decrypt, + } + got, err := d.ReadKeys() + if tt.res.err == nil { + assert.NoError(t, err) + } else if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.keys, got) + } + if err := tt.fields.client.mock.ExpectationsWereMet(); err != nil { + t.Error(err) + } + }) + } +} + +func Test_database_ReadKey(t *testing.T) { + type fields struct { + client db + masterKey string + decrypt func(encryptedKey, masterKey string) (key string, err error) + } + type args struct { + id string + } + type res struct { + key *crypto.Key + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "query fails, error", + fields{ + client: dbMock(t, expectQueryErr("SELECT key FROM system.encryption_keys WHERE id = $1", sql.ErrConnDone)), + masterKey: "", + decrypt: nil, + }, + args{ + id: "id1", + }, + res{ + err: func(err error) bool { + return errors.Is(err, sql.ErrConnDone) + }, + }, + }, + { + "key not found err", + fields{ + client: dbMock(t, expectQuery( + "SELECT key FROM system.encryption_keys WHERE id = $1", + nil, + nil, + "id1")), + masterKey: "masterKey", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return encryptedKey, nil + }, + }, + args{ + id: "id1", + }, + res{ + err: caos_errs.IsInternal, + }, + }, + { + "decryption error", + fields{ + client: dbMock(t, expectQuery( + "SELECT key FROM system.encryption_keys WHERE id = $1", + []string{"key"}, + [][]driver.Value{ + { + "key1", + }, + }, + "id1", + )), + masterKey: "wrong key", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return "", fmt.Errorf("wrong masterkey") + }, + }, + args{ + id: "id1", + }, + res{ + err: caos_errs.IsInternal, + }, + }, + { + "key ok", + fields{ + client: dbMock(t, expectQuery( + "SELECT key FROM system.encryption_keys WHERE id = $1", + []string{"key"}, + [][]driver.Value{ + { + "key1", + }, + }, + "id1", + )), + masterKey: "masterKey", + decrypt: func(encryptedKey, masterKey string) (key string, err error) { + return encryptedKey, nil + }, + }, + args{ + id: "id1", + }, + res{ + key: &crypto.Key{ + ID: "id1", + Value: "key1", + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &database{ + client: tt.fields.client.db, + masterKey: tt.fields.masterKey, + decrypt: tt.fields.decrypt, + } + got, err := d.ReadKey(tt.args.id) + if tt.res.err == nil { + assert.NoError(t, err) + } else if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v", err) + } + if tt.res.err == nil { + assert.Equal(t, tt.res.key, got) + } + if err := tt.fields.client.mock.ExpectationsWereMet(); err != nil { + t.Error(err) + } + }) + } +} + +func Test_database_CreateKeys(t *testing.T) { + type fields struct { + client db + masterKey string + encrypt func(key, masterKey string) (encryptedKey string, err error) + } + type args struct { + keys []*crypto.Key + } + type res struct { + err func(error) bool + } + tests := []struct { + name string + fields fields + args args + res res + }{ + { + "encryption fails, error", + fields{ + client: dbMock(t), + masterKey: "", + encrypt: func(key, masterKey string) (encryptedKey string, err error) { + return "", fmt.Errorf("encryption failed") + }, + }, + args{ + keys: []*crypto.Key{ + { + "id1", + "key1", + }, + }, + }, + res{ + err: caos_errs.IsInternal, + }, + }, + { + "insert fails, error", + fields{ + client: dbMock(t, + expectBegin(nil), + expectExec("INSERT INTO system.encryption_keys (id,key) VALUES ($1,$2)", sql.ErrTxDone), + expectRollback(nil), + ), + masterKey: "masterkey", + encrypt: func(key, masterKey string) (encryptedKey string, err error) { + return key, nil + }, + }, + args{ + keys: []*crypto.Key{ + { + "id1", + "key1", + }, + }, + }, + res{ + err: func(err error) bool { + return errors.Is(err, sql.ErrTxDone) + }, + }, + }, + { + "single insert ok", + fields{ + client: dbMock(t, + expectBegin(nil), + expectExec("INSERT INTO system.encryption_keys (id,key) VALUES ($1,$2)", nil, "id1", "key1"), + expectCommit(nil), + ), + masterKey: "masterkey", + encrypt: func(key, masterKey string) (encryptedKey string, err error) { + return key, nil + }, + }, + args{ + keys: []*crypto.Key{ + { + "id1", + "key1", + }, + }, + }, + res{ + err: nil, + }, + }, + { + "multiple insert ok", + fields{ + client: dbMock(t, + expectBegin(nil), + expectExec("INSERT INTO system.encryption_keys (id,key) VALUES ($1,$2)", nil, "id1", "key1", "id2", "key2"), + expectCommit(nil), + ), + masterKey: "masterkey", + encrypt: func(key, masterKey string) (encryptedKey string, err error) { + return key, nil + }, + }, + args{ + keys: []*crypto.Key{ + { + "id1", + "key1", + }, + { + "id2", + "key2", + }, + }, + }, + res{ + err: nil, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + d := &database{ + client: tt.fields.client.db, + masterKey: tt.fields.masterKey, + encrypt: tt.fields.encrypt, + } + err := d.CreateKeys(tt.args.keys...) + if tt.res.err == nil { + assert.NoError(t, err) + } else if tt.res.err != nil && !tt.res.err(err) { + t.Errorf("got wrong err: %v", err) + } + if err := tt.fields.client.mock.ExpectationsWereMet(); err != nil { + t.Error(err) + } + }) + } +} + +func Test_checkMasterKeyLength(t *testing.T) { + type args struct { + masterKey string + } + tests := []struct { + name string + args args + err func(error) bool + }{ + { + "invalid length", + args{ + masterKey: "", + }, + caos_errs.IsInternal, + }, + { + "valid length", + args{ + masterKey: "!themasterkeywhichis32byteslong!", + }, + nil, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := checkMasterKeyLength(tt.args.masterKey) + if tt.err == nil { + assert.NoError(t, err) + } else if tt.err != nil && !tt.err(err) { + t.Errorf("got wrong err: %v", err) + } + }) + } +} + +type db struct { + mock sqlmock.Sqlmock + db *sql.DB +} + +func dbMock(t *testing.T, expectations ...func(m sqlmock.Sqlmock)) db { + t.Helper() + client, mock, err := sqlmock.New() + if err != nil { + t.Fatalf("unable to create sql mock: %v", err) + } + for _, expectation := range expectations { + expectation(mock) + } + return db{ + mock: mock, + db: client, + } +} + +func expectQueryErr(query string, err error, args ...driver.Value) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + m.ExpectQuery(regexp.QuoteMeta(query)).WithArgs(args...).WillReturnError(err) + } +} + +func expectQuery(stmt string, cols []string, rows [][]driver.Value, args ...driver.Value) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + q := m.ExpectQuery(regexp.QuoteMeta(stmt)).WithArgs(args...) + result := sqlmock.NewRows(cols) + count := uint64(len(rows)) + for _, row := range rows { + if cols[len(cols)-1] == "count" { + row = append(row, count) + } + result.AddRow(row...) + } + q.WillReturnRows(result) + q.RowsWillBeClosed() + } +} + +func expectExec(stmt string, err error, args ...driver.Value) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + query := m.ExpectExec(regexp.QuoteMeta(stmt)).WithArgs(args...) + if err != nil { + query.WillReturnError(err) + return + } + query.WillReturnResult(sqlmock.NewResult(1, 1)) + } +} + +func expectBegin(err error) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + query := m.ExpectBegin() + if err != nil { + query.WillReturnError(err) + } + } +} + +func expectCommit(err error) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + query := m.ExpectCommit() + if err != nil { + query.WillReturnError(err) + } + } +} + +func expectRollback(err error) func(m sqlmock.Sqlmock) { + return func(m sqlmock.Sqlmock) { + query := m.ExpectRollback() + if err != nil { + query.WillReturnError(err) + } + } +} diff --git a/internal/crypto/file/file.go b/internal/crypto/file/file.go new file mode 100644 index 0000000000..05469f6576 --- /dev/null +++ b/internal/crypto/file/file.go @@ -0,0 +1,44 @@ +package file + +import ( + "fmt" + "os" + + "github.com/caos/zitadel/internal/config" + "github.com/caos/zitadel/internal/crypto" +) + +const ( + ZitadelKeyPath = "ZITADEL_KEY_PATH" +) + +type Storage struct{} + +func (d *Storage) ReadKeys() (crypto.Keys, error) { + path := os.Getenv(ZitadelKeyPath) + if path == "" { + return nil, fmt.Errorf("no path set, %s is empty", ZitadelKeyPath) + } + keys := new(crypto.Keys) + err := config.Read(keys, path) + return *keys, err +} + +func (d *Storage) ReadKey(id string) (*crypto.Key, error) { + keys, err := d.ReadKeys() + if err != nil { + return nil, err + } + key, ok := keys[id] + if !ok { + return nil, fmt.Errorf("key no found") + } + return &crypto.Key{ + ID: id, + Value: key, + }, nil +} + +func (d *Storage) CreateKeys(keys ...*crypto.Key) error { + return fmt.Errorf("this provider is not able to store new keys") +} diff --git a/internal/crypto/key.go b/internal/crypto/key.go index 6cae38170b..b19dc7fb98 100644 --- a/internal/crypto/key.go +++ b/internal/crypto/key.go @@ -1,51 +1,49 @@ package crypto import ( - "os" + "crypto/rand" "github.com/caos/logging" - "github.com/caos/zitadel/internal/config" "github.com/caos/zitadel/internal/errors" ) -const ( - ZitadelKeyPath = "ZITADEL_KEY_PATH" -) - type KeyConfig struct { EncryptionKeyID string DecryptionKeyIDs []string - Path string } type Keys map[string]string -func ReadKeys(path string) (Keys, error) { - if path == "" { - path = os.Getenv(ZitadelKeyPath) - if path == "" { - return nil, errors.ThrowInvalidArgument(nil, "CRYPT-56lka", "no path set") - } - } - keys := new(Keys) - err := config.Read(keys, path) - return *keys, err +type Key struct { + ID string + Value string } -func LoadKey(config *KeyConfig, id string) (string, error) { - keys, _, err := LoadKeys(config) +func NewKey(id string) (*Key, error) { + randBytes := make([]byte, 32) + if _, err := rand.Read(randBytes); err != nil { + return nil, err + } + return &Key{ + ID: id, + Value: string(randBytes), + }, nil +} + +func LoadKey(id string, keyStorage KeyStorage) (string, error) { + key, err := keyStorage.ReadKey(id) if err != nil { return "", err } - return keys[id], nil + return key.Value, nil } -func LoadKeys(config *KeyConfig) (map[string]string, []string, error) { +func LoadKeys(config *KeyConfig, keyStorage KeyStorage) (map[string]string, []string, error) { if config == nil { return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be nil") } - readKeys, err := ReadKeys(config.Path) + readKeys, err := keyStorage.ReadKeys() if err != nil { return nil, nil, err } @@ -54,7 +52,7 @@ func LoadKeys(config *KeyConfig) (map[string]string, []string, error) { if config.EncryptionKeyID != "" { key, ok := readKeys[config.EncryptionKeyID] if !ok { - return nil, nil, errors.ThrowInternalf(nil, "CRYPT-v2Kas", "encryption key not found") + return nil, nil, errors.ThrowInternalf(nil, "CRYPT-v2Kas", "encryption key %s not found", config.EncryptionKeyID) } keys[config.EncryptionKeyID] = key ids = append(ids, config.EncryptionKeyID) @@ -62,7 +60,7 @@ func LoadKeys(config *KeyConfig) (map[string]string, []string, error) { for _, id := range config.DecryptionKeyIDs { key, ok := readKeys[id] if !ok { - logging.Log("CRYPT-s23rf").Warnf("description key %s not found", id) + logging.Errorf("description key %s not found", id) continue } keys[id] = key diff --git a/internal/crypto/key_storage.go b/internal/crypto/key_storage.go new file mode 100644 index 0000000000..523818a0c0 --- /dev/null +++ b/internal/crypto/key_storage.go @@ -0,0 +1,7 @@ +package crypto + +type KeyStorage interface { + ReadKeys() (Keys, error) + ReadKey(id string) (*Key, error) + CreateKeys(...*Key) error +} diff --git a/internal/notification/notification.go b/internal/notification/notification.go index 5ff3bfab53..9aafd65b2e 100644 --- a/internal/notification/notification.go +++ b/internal/notification/notification.go @@ -4,9 +4,10 @@ import ( "database/sql" "github.com/caos/logging" - "github.com/caos/zitadel/internal/crypto" "github.com/rakyll/statik/fs" + "github.com/caos/zitadel/internal/crypto" + "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/notification/repository/eventsourcing" @@ -18,10 +19,19 @@ type Config struct { Repository eventsourcing.Config } -func Start(config Config, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) { +func Start(config Config, + systemDefaults sd.SystemDefaults, + command *command.Commands, + queries *query.Queries, + dbClient *sql.DB, + assetsPrefix string, + userEncryption crypto.EncryptionAlgorithm, + smtpEncryption crypto.EncryptionAlgorithm, + smsEncryption crypto.EncryptionAlgorithm, +) { statikFS, err := fs.NewWithNamespace("notification") logging.OnError(err).Panic("unable to start listener") - _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix, smtpPasswordEncAlg, smsCrypto) + _, err = eventsourcing.Start(config.Repository, statikFS, systemDefaults, command, queries, dbClient, assetsPrefix, userEncryption, smtpEncryption, smsEncryption) logging.OnError(err).Panic("unable to start app") } diff --git a/internal/notification/repository/eventsourcing/handler/handler.go b/internal/notification/repository/eventsourcing/handler/handler.go index bb967f8780..36792bae81 100644 --- a/internal/notification/repository/eventsourcing/handler/handler.go +++ b/internal/notification/repository/eventsourcing/handler/handler.go @@ -4,8 +4,6 @@ import ( "net/http" "time" - "github.com/caos/logging" - "github.com/caos/zitadel/internal/command" sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" @@ -34,10 +32,20 @@ func (h *handler) Eventstore() v1.Eventstore { return h.es } -func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es v1.Eventstore, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) []queryv1.Handler { - aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) - logging.OnError(err).Fatal("error create new aes crypto") - +func Register(configs Configs, + bulkLimit, + errorCount uint64, + view *view.View, + es v1.Eventstore, + command *command.Commands, + queries *query.Queries, + systemDefaults sd.SystemDefaults, + dir http.FileSystem, + assetsPrefix string, + userEncryption crypto.EncryptionAlgorithm, + smtpEncryption crypto.EncryptionAlgorithm, + smsEncryption crypto.EncryptionAlgorithm, +) []queryv1.Handler { return []queryv1.Handler{ newNotifyUser( handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, @@ -48,11 +56,11 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es command, queries, systemDefaults, - aesCrypto, dir, assetsPrefix, - smtpPasswordEncAlg, - smsCrypto, + userEncryption, + smtpEncryption, + smsEncryption, ), } } diff --git a/internal/notification/repository/eventsourcing/handler/notification.go b/internal/notification/repository/eventsourcing/handler/notification.go index b44681ca9f..c3224b1520 100644 --- a/internal/notification/repository/eventsourcing/handler/notification.go +++ b/internal/notification/repository/eventsourcing/handler/notification.go @@ -7,6 +7,7 @@ import ( "time" "github.com/caos/logging" + "github.com/caos/zitadel/internal/notification/channels/fs" "github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/twilio" @@ -40,26 +41,36 @@ type Notification struct { handler command *command.Commands systemDefaults sd.SystemDefaults - AesCrypto crypto.EncryptionAlgorithm statikDir http.FileSystem subscription *v1.Subscription assetsPrefix string queries *query.Queries + userDataCrypto crypto.EncryptionAlgorithm smtpPasswordCrypto crypto.EncryptionAlgorithm smsTokenCrypto crypto.EncryptionAlgorithm } -func newNotification(handler handler, command *command.Commands, query *query.Queries, defaults sd.SystemDefaults, aesCrypto crypto.EncryptionAlgorithm, statikDir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) *Notification { +func newNotification( + handler handler, + command *command.Commands, + query *query.Queries, + defaults sd.SystemDefaults, + statikDir http.FileSystem, + assetsPrefix string, + userEncryption crypto.EncryptionAlgorithm, + smtpEncryption crypto.EncryptionAlgorithm, + smsEncryption crypto.EncryptionAlgorithm, +) *Notification { h := &Notification{ handler: handler, command: command, systemDefaults: defaults, statikDir: statikDir, - AesCrypto: aesCrypto, assetsPrefix: assetsPrefix, queries: query, - smtpPasswordCrypto: smtpPasswordEncAlg, - smsTokenCrypto: smsCrypto, + userDataCrypto: userEncryption, + smtpPasswordCrypto: smtpEncryption, + smsTokenCrypto: smsEncryption, } h.subscribe() @@ -161,7 +172,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) { return err } - err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendUserInitCode(ctx, string(template.Template), translator, user, initCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -199,7 +210,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) { if err != nil { return err } - err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.systemDefaults, n.getSMTPConfig, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendPasswordCode(ctx, string(template.Template), translator, user, pwCode, n.systemDefaults, n.getSMTPConfig, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -238,7 +249,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err return err } - err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendEmailVerificationCode(ctx, string(template.Template), translator, user, emailCode, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix) if err != nil { return err } @@ -264,7 +275,7 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err if err != nil { return err } - err = types.SendPhoneVerificationCode(context.Background(), translator, user, phoneCode, n.systemDefaults, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto) + err = types.SendPhoneVerificationCode(context.Background(), translator, user, phoneCode, n.systemDefaults, n.getTwilioConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto) if err != nil { return err } @@ -351,7 +362,7 @@ func (n *Notification) handlePasswordlessRegistrationLink(event *models.Event) ( return err } - err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.AesCrypto, colors, n.assetsPrefix) + err = types.SendPasswordlessRegistrationLink(ctx, string(template.Template), translator, user, addedEvent, n.systemDefaults, n.getSMTPConfig, n.getFileSystemProvider, n.getLogProvider, n.userDataCrypto, colors, n.assetsPrefix) if err != nil { return err } diff --git a/internal/notification/repository/eventsourcing/repository.go b/internal/notification/repository/eventsourcing/repository.go index 76ba54e946..a2ca812313 100644 --- a/internal/notification/repository/eventsourcing/repository.go +++ b/internal/notification/repository/eventsourcing/repository.go @@ -22,7 +22,17 @@ type EsRepository struct { spooler *es_spol.Spooler } -func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, command *command.Commands, queries *query.Queries, dbClient *sql.DB, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) (*EsRepository, error) { +func Start(conf Config, + dir http.FileSystem, + systemDefaults sd.SystemDefaults, + command *command.Commands, + queries *query.Queries, + dbClient *sql.DB, + assetsPrefix string, + userEncryption crypto.EncryptionAlgorithm, + smtpEncryption crypto.EncryptionAlgorithm, + smsEncryption crypto.EncryptionAlgorithm, +) (*EsRepository, error) { es, err := v1.Start(dbClient) if err != nil { return nil, err @@ -33,7 +43,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c return nil, err } - spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg, smsCrypto) + spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, command, queries, systemDefaults, dir, assetsPrefix, userEncryption, smtpEncryption, smsEncryption) return &EsRepository{ spool, diff --git a/internal/notification/repository/eventsourcing/spooler/spooler.go b/internal/notification/repository/eventsourcing/spooler/spooler.go index 67dec942b4..656f81f156 100644 --- a/internal/notification/repository/eventsourcing/spooler/spooler.go +++ b/internal/notification/repository/eventsourcing/spooler/spooler.go @@ -21,12 +21,24 @@ type SpoolerConfig struct { Handlers handler.Configs } -func StartSpooler(c SpoolerConfig, es v1.Eventstore, view *view.View, sql *sql.DB, command *command.Commands, queries *query.Queries, systemDefaults sd.SystemDefaults, dir http.FileSystem, assetsPrefix string, smtpPasswordEncAlg crypto.EncryptionAlgorithm, smsCrypto *crypto.AESCrypto) *spooler.Spooler { +func StartSpooler(c SpoolerConfig, + es v1.Eventstore, + view *view.View, + sql *sql.DB, + command *command.Commands, + queries *query.Queries, + systemDefaults sd.SystemDefaults, + dir http.FileSystem, + assetsPrefix string, + userEncryption crypto.EncryptionAlgorithm, + smtpEncryption crypto.EncryptionAlgorithm, + smsEncryption crypto.EncryptionAlgorithm, +) *spooler.Spooler { spoolerConfig := spooler.Config{ Eventstore: es, Locker: &locker{dbClient: sql}, ConcurrentWorkers: c.ConcurrentWorkers, - ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix, smtpPasswordEncAlg, smsCrypto), + ViewHandlers: handler.Register(c.Handlers, c.BulkLimit, c.FailureCountUntilSkip, view, es, command, queries, systemDefaults, dir, assetsPrefix, userEncryption, smtpEncryption, smsEncryption), } spool := spoolerConfig.New() spool.Start() diff --git a/internal/query/projection/key.go b/internal/query/projection/key.go index 67673400d5..0aaa756ddf 100644 --- a/internal/query/projection/key.go +++ b/internal/query/projection/key.go @@ -26,17 +26,15 @@ const ( KeyPublicTable = KeyProjectionTable + "_" + publicKeyTableSuffix ) -func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyConfig *crypto.KeyConfig, keyChan chan<- interface{}) (_ *KeyProjection, err error) { +func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) *KeyProjection { p := new(KeyProjection) config.ProjectionName = KeyProjectionTable config.Reducers = p.reducers() p.StatementHandler = crdb.NewStatementHandler(ctx, config) p.keyChan = keyChan - p.encryptionAlgorithm, err = crypto.NewAESCrypto(keyConfig) - if err != nil { - return nil, err - } - return p, nil + p.encryptionAlgorithm = keyEncryptionAlgorithm + + return p } func (p *KeyProjection) reducers() []handler.AggregateReducer { diff --git a/internal/query/projection/projection.go b/internal/query/projection/projection.go index 8c322b906a..d87e2a2485 100644 --- a/internal/query/projection/projection.go +++ b/internal/query/projection/projection.go @@ -17,7 +17,7 @@ const ( FailedEventsTable = "projections.failed_events" ) -func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyConfig *crypto.KeyConfig, keyChan chan<- interface{}) error { +func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) error { projectionConfig := crdb.StatementHandlerConfig{ ProjectionHandlerConfig: handler.ProjectionHandlerConfig{ HandlerConfig: handler.HandlerConfig{ @@ -73,10 +73,9 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co NewSMTPConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["smtp_configs"])) NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"])) NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"])) - _, err := NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyConfig, keyChan) NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"])) - - return err + NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, keyChan) + return nil } func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomConfig) crdb.StatementHandlerConfig { diff --git a/internal/query/query.go b/internal/query/query.go index 99f29c20d9..efb0680d48 100644 --- a/internal/query/query.go +++ b/internal/query/query.go @@ -11,7 +11,6 @@ import ( "golang.org/x/text/language" "github.com/caos/zitadel/internal/api/authz" - sd "github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/query/projection" @@ -38,7 +37,7 @@ type Queries struct { zitadelRoles []authz.RoleMapping } -func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, defaults sd.SystemDefaults, keyConfig *crypto.KeyConfig, keyChan chan<- interface{}, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) { +func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql.DB, projections projection.Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}, zitadelRoles []authz.RoleMapping) (repo *Queries, err error) { statikLoginFS, err := fs.NewWithNamespace("login") if err != nil { return nil, fmt.Errorf("unable to start login statik dir") @@ -67,7 +66,7 @@ func StartQueries(ctx context.Context, es *eventstore.Eventstore, sqlClient *sql keypair.RegisterEventMappers(repo.eventstore) usergrant.RegisterEventMappers(repo.eventstore) - err = projection.Start(ctx, sqlClient, es, projections, keyConfig, keyChan) + err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, keyChan) if err != nil { return nil, err } diff --git a/migrations/cockroach/V1.111__settings.sql b/migrations/cockroach/V1.111__settings.sql index ec486960d3..aa015cd552 100644 --- a/migrations/cockroach/V1.111__settings.sql +++ b/migrations/cockroach/V1.111__settings.sql @@ -55,3 +55,10 @@ CREATE TABLE zitadel.projections.sms_configs_twilio ( , PRIMARY KEY (sms_id) ); + +ALTER TABLE zitadel.projections.login_policies + ADD COLUMN password_check_lifetime BIGINT NOT NULL DEFAULT 0 + , ADD COLUMN external_login_check_lifetime BIGINT NOT NULL DEFAULT 0 + , ADD COLUMN mfa_init_skip_lifetime BIGINT NOT NULL DEFAULT 0 + , ADD COLUMN second_factor_check_lifetime BIGINT NOT NULL DEFAULT 0 + , ADD COLUMN multi_factor_check_lifetime BIGINT NOT NULL DEFAULT 0;