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
This commit is contained in:
Livio Amstutz 2022-03-14 07:55:09 +01:00 committed by GitHub
parent 7899a0b851
commit 5463244376
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
57 changed files with 1618 additions and 471 deletions

View File

@ -4,10 +4,12 @@ import (
_ "embed" _ "embed"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/spf13/cobra"
"github.com/caos/zitadel/cmd/admin/initialise" "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/setup"
"github.com/caos/zitadel/cmd/admin/start" "github.com/caos/zitadel/cmd/admin/start"
"github.com/spf13/cobra"
) )
func New() *cobra.Command { func New() *cobra.Command {
@ -24,6 +26,7 @@ func New() *cobra.Command {
initialise.New(), initialise.New(),
setup.New(), setup.New(),
start.New(), start.New(),
key.New(),
) )
return adminCMD return adminCMD

View File

@ -0,0 +1 @@
CREATE SCHEMA system;

View File

@ -0,0 +1,6 @@
CREATE TABLE system.encryption_keys (
id TEXT NOT NULL
, key TEXT NOT NULL
, PRIMARY KEY (id)
);

View File

@ -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 - 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 - 04_eventstore.sql: creates the schema needed for eventsourcing
- 05_projections.sql: creates the schema needed to read the data - 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_system.sql: creates the schema needed for ZITADEL itself
- 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_encryption_keys_table.sql: creates the table for encryption keys (for event data)
- 07_events_table.sql creates the table for eventsourcing - 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

View File

@ -4,27 +4,36 @@ import (
"database/sql" "database/sql"
_ "embed" _ "embed"
"github.com/caos/zitadel/internal/database" "github.com/caos/logging"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/caos/zitadel/internal/database"
) )
const ( const (
eventstoreSchema = "eventstore" eventstoreSchema = "eventstore"
projectionsSchema = "projections" eventsTable = "events"
projectionsSchema = "projections"
systemSchema = "system"
encryptionKeysTable = "encryption_key"
) )
var ( var (
searchEventsTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = 'events'" searchTable = "SELECT table_name FROM [SHOW TABLES] WHERE table_name = $1"
searchSchema = "SELECT schema_name FROM [SHOW SCHEMAS] WHERE schema_name = $1" 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
//go:embed sql/04_eventstore.sql //go:embed sql/04_eventstore.sql
createEventstoreStmt string 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 { func newZitadel() *cobra.Command {
@ -47,11 +56,20 @@ Prereqesits:
} }
func verifyZitadel(config database.Config) error { func verifyZitadel(config database.Config) error {
logging.WithFields("database", config.Database).Info("verify database")
db, err := database.Connect(config) db, err := database.Connect(config)
if err != nil { if err != nil {
return err 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 { if err := verify(db, exists(searchSchema, projectionsSchema), exec(createProjectionsStmt)); err != nil {
return err return err
} }
@ -60,13 +78,26 @@ func verifyZitadel(config database.Config) error {
return err 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 err
} }
return db.Close() 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 { func createEvents(db *sql.DB) error {
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {

View File

@ -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)
}
})
}
}

130
cmd/admin/key/key.go Normal file
View File

@ -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)
}

161
cmd/admin/key/key_test.go Normal file
View File

@ -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)
})
}
}

View File

@ -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
}

View File

@ -39,6 +39,7 @@ import (
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "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/database"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -52,6 +53,10 @@ import (
"github.com/caos/zitadel/openapi" "github.com/caos/zitadel/openapi"
) )
const (
flagMasterKey = "masterkey"
)
func New() *cobra.Command { func New() *cobra.Command {
start := &cobra.Command{ start := &cobra.Command{
Use: "start", Use: "start",
@ -72,7 +77,8 @@ Requirements:
if err != nil { if err != nil {
return err return err
} }
return startZitadel(config) masterKey, _ := cmd.Flags().GetString("masterkey")
return startZitadel(config, masterKey)
}, },
} }
bindUint16Flag(start, "port", "port to run ZITADEL on") bindUint16Flag(start, "port", "port to run ZITADEL on")
@ -80,6 +86,8 @@ Requirements:
bindStringFlag(start, "externalPort", "port ZITADEL will be exposed on") bindStringFlag(start, "externalPort", "port ZITADEL will be exposed on")
bindBoolFlag(start, "externalSecure", "if ZITADEL will be served on HTTPS") bindBoolFlag(start, "externalSecure", "if ZITADEL will be served on HTTPS")
start.PersistentFlags().String(flagMasterKey, "", "masterkey for en/decryption keys")
return start return start
} }
@ -105,7 +113,7 @@ type startConfig struct {
ExternalDomain string ExternalDomain string
ExternalSecure bool ExternalSecure bool
Database database.Config Database database.Config
Projections projectionConfig Projections projection.Config
AuthZ authz.Config AuthZ authz.Config
Auth auth_es.Config Auth auth_es.Config
Admin admin_es.Config Admin admin_es.Config
@ -117,14 +125,22 @@ type startConfig struct {
AssetStorage static_config.AssetStorageConfig AssetStorage static_config.AssetStorageConfig
InternalAuthZ internal_authz.Config InternalAuthZ internal_authz.Config
SystemDefaults systemdefaults.SystemDefaults SystemDefaults systemdefaults.SystemDefaults
EncryptionKeys *encryptionKeyConfig
} }
type projectionConfig struct { type encryptionKeyConfig struct {
projection.Config DomainVerification *crypto.KeyConfig
KeyConfig *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() ctx := context.Background()
keyChan := make(chan interface{}) keyChan := make(chan interface{})
@ -132,6 +148,16 @@ func startZitadel(config *startConfig) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot start client for projection: %w", err) 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 var storage static.Storage
//TODO: enable when storage is implemented again //TODO: enable when storage is implemented again
//if *assetsEnabled { //if *assetsEnabled {
@ -142,22 +168,13 @@ func startZitadel(config *startConfig) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot start eventstore for queries: %w", err) 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) queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, keyChan, config.InternalAuthZ.RolePermissionMappings)
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)
if err != nil { if err != nil {
return fmt.Errorf("cannot start queries: %w", err) 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 { if err != nil {
return fmt.Errorf("error starting authz repo: %w", err) 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), Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
DisplayName: "ZITADEL", 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 { if err != nil {
return fmt.Errorf("cannot start commands: %w", err) 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() 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 { if err != nil {
return err return err
} }
return listen(ctx, router, config.Port) 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 { repo := struct {
authz_repo.Repository authz_repo.Repository
*query.Queries *query.Queries
@ -192,11 +209,7 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
verifier := internal_authz.Start(repo) verifier := internal_authz.Start(repo)
apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure) apis := api.New(config.Port, router, &repo, config.InternalAuthZ, config.ExternalSecure)
userEncryptionAlgorithm, err := crypto.NewAESCrypto(config.SystemDefaults.UserVerificationKey) authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.OIDC, keys.User)
if err != nil {
return nil
}
authRepo, err := auth_es.Start(config.Auth, config.SystemDefaults, commands, queries, dbClient, config.OIDC.KeyConfig, assets.HandlerPrefix, userEncryptionAlgorithm)
if err != nil { if err != nil {
return fmt.Errorf("error starting auth repo: %w", err) 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 { if err != nil {
return fmt.Errorf("error starting admin repo: %w", err) 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 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 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 return err
} }
apis.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries)) 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 { if err != nil {
return err return err
} }
issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) 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 { if err != nil {
return fmt.Errorf("unable to start oidc provider: %w", err) 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 { if err != nil {
return fmt.Errorf("unable to get client_id for console: %w", err) 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 { if err != nil {
return fmt.Errorf("unable to start console: %w", err) return fmt.Errorf("unable to start console: %w", err)
} }
apis.RegisterHandler(console.HandlerPrefix, c) 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 { if err != nil {
return fmt.Errorf("unable to start login: %w", err) return fmt.Errorf("unable to start login: %w", err)
} }

View File

@ -20,13 +20,19 @@ Database:
Username: zitadel Username: zitadel
Password: "" Password: ""
SSL: SSL:
Mode: diabled Mode: disable
RootCert: "" RootCert: ""
Cert: "" Cert: ""
Key: "" Key: ""
AdminUser: AdminUser:
Username: root Username: root
Password: ""
SSL:
Mode: disable
RootCert: ""
Cert: ""
Key: ""
Projections: Projections:
Config: Config:
@ -38,10 +44,6 @@ Projections:
Customizations: Customizations:
projects: projects:
BulkLimit: 2000 BulkLimit: 2000
KeyConfig:
# We don't need an EncryptionKey but DecryptionKeys (and load them via env)
DecryptionKeyIDs:
Path: ""
AuthZ: AuthZ:
Repository: Repository:
@ -66,8 +68,6 @@ Admin:
UserAgentCookie: UserAgentCookie:
Name: zitadel.useragent Name: zitadel.useragent
Key:
EncryptionKeyID:
MaxAge: 8760h #365*24h (1 year) MaxAge: 8760h #365*24h (1 year)
OIDC: OIDC:
@ -84,19 +84,11 @@ OIDC:
Cache: Cache:
MaxAge: 12h MaxAge: 12h
SharedMaxAge: 168h #7d SharedMaxAge: 168h #7d
KeyConfig:
EncryptionKeyID: ""
DecryptionKeyIDs:
Path: ""
CustomEndpoints: CustomEndpoints:
Login: Login:
LanguageCookieName: zitadel.login.lang LanguageCookieName: zitadel.login.lang
CSRF: CSRFCookieName: zitadel.login.csrf
CookieName: zitadel.login.csrf
Development: true
Key:
EncryptionKeyID:
Cache: Cache:
MaxAge: 12h MaxAge: 12h
SharedMaxAge: 168h #7d SharedMaxAge: 168h #7d
@ -118,6 +110,31 @@ Notification:
FailureCountUntilSkip: 5 FailureCountUntilSkip: 5
Handlers: 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 #TODO: configure as soon as possible
#AssetStorage: #AssetStorage:
# Type: $ZITADEL_ASSET_STORAGE_TYPE # Type: $ZITADEL_ASSET_STORAGE_TYPE
@ -137,73 +154,14 @@ SystemDefaults:
ZitadelDocs: ZitadelDocs:
Issuer: $ZITADEL_ISSUER Issuer: $ZITADEL_ISSUER
DiscoveryEndpoint: '$ZITADEL_ISSUER/.well-known/openid-configuration' 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: SecretGenerators:
PasswordSaltCost: 14 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 MachineKeySize: 2048
ApplicationKeySize: 2048 ApplicationKeySize: 2048
Multifactors: Multifactors:
OTP: OTP:
Issuer: 'ZITADEL' Issuer: 'ZITADEL'
VerificationKey:
EncryptionKeyID: $ZITADEL_OTP_VERIFICATION_KEY
VerificationLifetimes:
PasswordCheck: 240h #10d
ExternalLoginCheck: 240h #10d
MFAInitSkip: 720h #30d
SecondFactorCheck: 18h
MultiFactorCheck: 12h
DomainVerification: DomainVerification:
VerificationKey:
EncryptionKeyID: $ZITADEL_DOMAIN_VERIFICATION_KEY
VerificationGenerator: VerificationGenerator:
Length: 32 Length: 32
IncludeLowerLetters: true IncludeLowerLetters: true
@ -211,38 +169,13 @@ SystemDefaults:
IncludeDigits: true IncludeDigits: true
IncludeSymbols: false IncludeSymbols: false
Notifications: Notifications:
# DebugMode: $DEBUG_MODE
Endpoints: Endpoints:
InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}&passwordset={{.PasswordSet}}' InitCode: '$ZITADEL_ACCOUNTS/user/init?userID={{.UserID}}&code={{.Code}}&passwordset={{.PasswordSet}}'
PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}' PasswordReset: '$ZITADEL_ACCOUNTS/password/init?userID={{.UserID}}&code={{.Code}}'
VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}' VerifyEmail: '$ZITADEL_ACCOUNTS/mail/verification?userID={{.UserID}}&code={{.Code}}'
DomainClaimed: '$ZITADEL_ACCOUNTS/login' DomainClaimed: '$ZITADEL_ACCOUNTS/login'
PasswordlessRegistration: '$ZITADEL_ACCOUNTS/login/passwordless/init' PasswordlessRegistration: '$ZITADEL_ACCOUNTS/login/passwordless/init'
Providers: FileSystemPath: '.notifications/'
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
KeyConfig: KeyConfig:
Size: 2048 Size: 2048
PrivateKeyLifetime: 6h PrivateKeyLifetime: 6h

View File

@ -188,30 +188,6 @@ Update twilio sms provider token
PUT: /sms/twilio/{id}/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 ### GetOIDCSettings
> **rpc** GetOIDCSettings([GetOIDCSettingsRequest](#getoidcsettingsrequest)) > **rpc** GetOIDCSettings([GetOIDCSettingsRequest](#getoidcsettingsrequest))
@ -236,6 +212,30 @@ Update oidc settings (e.g token lifetimes, etc)
PUT: /settings/oidc 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 ### GetOrgByID
> **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest)) > **rpc** GetOrgByID([GetOrgByIDRequest](#getorgbyidrequest))

View File

@ -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 human := setUpOrgHumanToDomain(req.User.(*admin_pb.SetUpOrgRequest_Human_).Human) //TODO: handle machine
org := setUpOrgOrgToDomain(req.Org) 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,7 +1,6 @@
package admin package admin
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/admin/repository" "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/authz"
"github.com/caos/zitadel/internal/api/grpc/server" "github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/admin" "github.com/caos/zitadel/pkg/grpc/admin"
) )
@ -26,22 +26,27 @@ type Server struct {
administrator repository.AdministratorRepository administrator repository.AdministratorRepository
iamDomain string iamDomain string
assetsAPIDomain string assetsAPIDomain string
userCodeAlg crypto.EncryptionAlgorithm
UserCodeAlg crypto.EncryptionAlgorithm
} }
type Config struct { type Config struct {
Repository eventsourcing.Config 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{ return &Server{
command: command, command: command,
query: query, query: query,
administrator: repo, administrator: repo,
iamDomain: iamDomain, iamDomain: iamDomain,
assetsAPIDomain: assetsAPIDomain, assetsAPIDomain: assetsAPIDomain,
UserCodeAlg: userCrypto, userCodeAlg: userCodeAlg,
} }
} }

View File

@ -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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { func (s *Server) ResendMyEmailVerification(ctx context.Context, _ *auth_pb.ResendMyEmailVerificationRequest) (*auth_pb.ResendMyEmailVerificationResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,13 +3,13 @@ package auth
import ( import (
"context" "context"
"github.com/caos/zitadel/internal/query"
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/object" "github.com/caos/zitadel/internal/api/grpc/object"
user_grpc "github.com/caos/zitadel/internal/api/grpc/user" user_grpc "github.com/caos/zitadel/internal/api/grpc/user"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/query"
auth_pb "github.com/caos/zitadel/pkg/grpc/auth" auth_pb "github.com/caos/zitadel/pkg/grpc/auth"
user_pb "github.com/caos/zitadel/pkg/grpc/user" 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) { func (s *Server) AddMyPasswordlessLink(ctx context.Context, _ *auth_pb.AddMyPasswordlessLinkRequest) (*auth_pb.AddMyPasswordlessLinkResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err 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) { func (s *Server) SendMyPasswordlessLink(ctx context.Context, _ *auth_pb.SendMyPasswordlessLinkRequest) (*auth_pb.SendMyPasswordlessLinkResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -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) { 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 { if err != nil {
return nil, err 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) { func (s *Server) VerifyMyPhone(ctx context.Context, req *auth_pb.VerifyMyPhoneRequest) (*auth_pb.VerifyMyPhoneResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err 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) { func (s *Server) ResendMyPhoneVerification(ctx context.Context, _ *auth_pb.ResendMyPhoneVerificationRequest) (*auth_pb.ResendMyPhoneVerificationResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,7 +1,6 @@
package auth package auth
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz" "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/auth/repository/eventsourcing"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/auth" "github.com/caos/zitadel/pkg/grpc/auth"
) )
@ -27,21 +27,27 @@ type Server struct {
repo repository.Repository repo repository.Repository
defaults systemdefaults.SystemDefaults defaults systemdefaults.SystemDefaults
assetsAPIDomain string assetsAPIDomain string
UserCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
} }
type Config struct { type Config struct {
Repository eventsourcing.Config 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{ return &Server{
command: command, command: command,
query: query, query: query,
repo: authRepo, repo: authRepo,
defaults: defaults, defaults: defaults,
assetsAPIDomain: assetsAPIDomain, assetsAPIDomain: assetsAPIDomain,
UserCodeAlg: userCrypto, userCodeAlg: userCodeAlg,
} }
} }

View File

@ -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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -1,13 +1,13 @@
package management package management
import ( import (
"github.com/caos/zitadel/internal/crypto"
"google.golang.org/grpc" "google.golang.org/grpc"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/grpc/server" "github.com/caos/zitadel/internal/api/grpc/server"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/query" "github.com/caos/zitadel/internal/query"
"github.com/caos/zitadel/pkg/grpc/management" "github.com/caos/zitadel/pkg/grpc/management"
) )
@ -24,18 +24,23 @@ type Server struct {
query *query.Queries query *query.Queries
systemDefaults systemdefaults.SystemDefaults systemDefaults systemdefaults.SystemDefaults
assetAPIPrefix string assetAPIPrefix string
PasswordHashAlg crypto.HashAlgorithm passwordHashAlg crypto.HashAlgorithm
UserCodeAlg crypto.EncryptionAlgorithm 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{ return &Server{
command: command, command: command,
query: query, query: query,
systemDefaults: sd, systemDefaults: sd,
assetAPIPrefix: assetAPIPrefix, assetAPIPrefix: assetAPIPrefix,
PasswordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost), passwordHashAlg: crypto.NewBCrypt(sd.SecretGenerators.PasswordSaltCost),
UserCodeAlg: userCrypto, userCodeAlg: userCodeAlg,
} }
} }

View File

@ -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) { 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err 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) { func (s *Server) ImportHumanUser(ctx context.Context, req *mgmt_pb.ImportHumanUserRequest) (*mgmt_pb.ImportHumanUserResponse, error) {
human, passwordless := ImportHumanUserRequestToDomain(req) 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { 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 { if err != nil {
return nil, err 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) { func (s *Server) AddPasswordlessRegistration(ctx context.Context, req *mgmt_pb.AddPasswordlessRegistrationRequest) (*mgmt_pb.AddPasswordlessRegistrationResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err 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) { func (s *Server) SendPasswordlessRegistration(ctx context.Context, req *mgmt_pb.SendPasswordlessRegistrationRequest) (*mgmt_pb.SendPasswordlessRegistrationResponse, error) {
ctxData := authz.GetCtxData(ctx) 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -6,7 +6,6 @@ import (
"time" "time"
http_utils "github.com/caos/zitadel/internal/api/http" 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/errors"
"github.com/caos/zitadel/internal/id" "github.com/caos/zitadel/internal/id"
) )
@ -35,16 +34,10 @@ type userAgentHandler struct {
type UserAgentCookieConfig struct { type UserAgentCookieConfig struct {
Name string Name string
Key *crypto.KeyConfig
MaxAge time.Duration MaxAge time.Duration
} }
func NewUserAgentHandler(config *UserAgentCookieConfig, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) { func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, 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)
opts := []http_utils.CookieHandlerOpt{ opts := []http_utils.CookieHandlerOpt{
http_utils.WithEncryption(cookieKey, cookieKey), http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(domain), http_utils.WithDomain(domain),

View File

@ -19,6 +19,7 @@ import (
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults" "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "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"
"github.com/caos/zitadel/internal/eventstore/handler/crdb" "github.com/caos/zitadel/internal/eventstore/handler/crdb"
"github.com/caos/zitadel/internal/i18n" "github.com/caos/zitadel/internal/i18n"
@ -44,7 +45,6 @@ type Config struct {
DefaultRefreshTokenExpiration time.Duration DefaultRefreshTokenExpiration time.Duration
UserAgentCookieConfig *middleware.UserAgentCookieConfig UserAgentCookieConfig *middleware.UserAgentCookieConfig
Cache *middleware.CacheConfig Cache *middleware.CacheConfig
KeyConfig *crypto.KeyConfig
CustomEndpoints *EndpointConfig CustomEndpoints *EndpointConfig
} }
@ -83,18 +83,15 @@ type OPStorage struct {
assetAPIPrefix string 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) { 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) opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey)
if err != nil { if err != nil {
return nil, fmt.Errorf("cannot create op config: %w", err) return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
}
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)
} }
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan)
options, err := createOptions(config, userAgentCookie) options, err := createOptions(config, userAgentCookie)
if err != nil { 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( provider, err := op.NewOpenIDProvider(
ctx, ctx,
@ -103,7 +100,7 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire
options..., options...,
) )
if err != nil { 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 return provider, nil
} }
@ -112,7 +109,7 @@ func Issuer(domain string, port uint16, externalSecure bool) string {
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix 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() supportedLanguages, err := getSupportedLanguages()
if err != nil { if err != nil {
return nil, err return nil, err
@ -127,25 +124,13 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string) (*op
RequestObjectSupported: config.RequestObjectSupported, RequestObjectSupported: config.RequestObjectSupported,
SupportedUILocales: supportedLanguages, SupportedUILocales: supportedLanguages,
} }
if err := cryptoKey(opConfig, config.KeyConfig); err != nil { if cryptoLength := len(cryptoKey); cryptoLength != 32 {
return nil, err 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 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) { func createOptions(config Config, userAgentCookie func(http.Handler) http.Handler) ([]op.Option, error) {
metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount} metricTypes := []metrics.MetricType{metrics.MetricTypeRequestCount, metrics.MetricTypeStatusCode, metrics.MetricTypeTotalCount}
interceptor := op.WithHttpInterceptors( interceptor := op.WithHttpInterceptors(
@ -191,11 +176,7 @@ func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
return options 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) { 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 {
encAlg, err := crypto.NewAESCrypto(c)
if err != nil {
return nil, err
}
return &OPStorage{ return &OPStorage{
repo: repo, repo: repo,
command: command, command: command,
@ -213,7 +194,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
locker: crdb.NewLocker(projections, locksTable, signingKey), locker: crdb.NewLocker(projections, locksTable, signingKey),
keyChan: keyChan, keyChan: keyChan,
assetAPIPrefix: assets.HandlerPrefix, assetAPIPrefix: assets.HandlerPrefix,
}, nil }
} }
func (o *OPStorage) Health(ctx context.Context) error { func (o *OPStorage) Health(ctx context.Context) error {

View File

@ -74,7 +74,7 @@ func (l *Login) handleExternalLogin(w http.ResponseWriter, r *http.Request) {
return return
} }
if authReq == nil { if authReq == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.consolePath, http.StatusFound)
return return
} }
l.handleIDP(w, r, authReq, data.IDPConfigID) 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")) l.renderLogin(w, r, authReq, caos_errors.ThrowPreconditionFailed(nil, "LOGIN-dsgg3", "Errors.AuthRequest.UserAgentNotFound"))
return return
} }
nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(userAgentID)) nonce, err := l.idpConfigAlg.Encrypt([]byte(userAgentID))
if err != nil { if err != nil {
l.renderLogin(w, r, authReq, err) l.renderLogin(w, r, authReq, err)
return 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) { 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -58,7 +58,7 @@ func (l *Login) handleExternalRegister(w http.ResponseWriter, r *http.Request) {
return return
} }
if authReq == nil { if authReq == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.consolePath, http.StatusFound)
return return
} }
idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID) idpConfig, err := l.getIDPConfigByID(r, data.IDPConfigID)
@ -145,12 +145,12 @@ func (l *Login) registerExternalUser(w http.ResponseWriter, r *http.Request, aut
memberRoles = nil memberRoles = nil
resourceOwner = authReq.RequestedOrgID 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 { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return 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 { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return return
@ -226,12 +226,12 @@ func (l *Login) handleExternalRegisterCheck(w http.ResponseWriter, r *http.Reque
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return 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 { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return 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 { if err != nil {
l.renderRegisterOption(w, r, authReq, err) l.renderRegisterOption(w, r, authReq, err)
return return

View File

@ -70,7 +70,7 @@ func (l *Login) checkPWCode(w http.ResponseWriter, r *http.Request, authReq *dom
userOrg = authReq.UserOrgID userOrg = authReq.UserOrgID
} }
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) 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 { if err != nil {
l.renderInitPassword(w, r, authReq, data.UserID, "", err) l.renderInitPassword(w, r, authReq, data.UserID, "", err)
return return
@ -97,7 +97,7 @@ func (l *Login) resendPasswordSet(w http.ResponseWriter, r *http.Request, authRe
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return 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 { if err != nil {
l.renderInitPassword(w, r, authReq, authReq.UserID, "", err) l.renderInitPassword(w, r, authReq, authReq.UserID, "", err)
return return

View File

@ -73,7 +73,7 @@ func (l *Login) checkUserInitCode(w http.ResponseWriter, r *http.Request, authRe
if authReq != nil { if authReq != nil {
userOrgID = authReq.UserOrgID 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 { if err != nil {
l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err) l.renderInitUser(w, r, authReq, data.UserID, "", data.PasswordSet, err)
return return
@ -91,7 +91,7 @@ func (l *Login) resendUserInit(w http.ResponseWriter, r *http.Request, authReq *
if authReq != nil { if authReq != nil {
userOrgID = authReq.UserOrgID 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 { if err != nil {
l.renderInitUser(w, r, authReq, userID, "", showPassword, err) l.renderInitUser(w, r, authReq, userID, "", showPassword, err)
return return

View File

@ -39,7 +39,7 @@ func (l *Login) handleJWTRequest(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, nil, err) l.renderError(w, r, nil, err)
return return
} }
userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID()) userAgentID, err := l.idpConfigAlg.DecryptString(id, l.idpConfigAlg.EncryptionKeyID())
if err != nil { if err != nil {
l.renderError(w, r, nil, err) l.renderError(w, r, nil, err)
return return
@ -181,7 +181,7 @@ func (l *Login) redirectToJWTCallback(authReq *domain.AuthRequest) (string, erro
} }
q := redirect.Query() q := redirect.Query()
q.Set(QueryAuthRequestID, authReq.ID) q.Set(QueryAuthRequestID, authReq.ID)
nonce, err := l.IDPConfigAesCrypto.Encrypt([]byte(authReq.AgentID)) nonce, err := l.idpConfigAlg.Encrypt([]byte(authReq.AgentID))
if err != nil { if err != nil {
return "", err return "", err
} }
@ -202,7 +202,7 @@ func (l *Login) handleJWTCallback(w http.ResponseWriter, r *http.Request) {
l.renderError(w, r, nil, err) l.renderError(w, r, nil, err)
return return
} }
userAgentID, err := l.IDPConfigAesCrypto.DecryptString(id, l.IDPConfigAesCrypto.EncryptionKeyID()) userAgentID, err := l.idpConfigAlg.DecryptString(id, l.idpConfigAlg.EncryptionKeyID())
if err != nil { if err != nil {
l.renderError(w, r, nil, err) l.renderError(w, r, nil, err)
return return

View File

@ -35,47 +35,54 @@ type Login struct {
//staticCache cache.Cache //TODO: enable when storage is implemented again //staticCache cache.Cache //TODO: enable when storage is implemented again
authRepo auth_repository.Repository authRepo auth_repository.Repository
baseURL string baseURL string
zitadelURL string consolePath string
oidcAuthCallbackURL string oidcAuthCallbackURL string
IDPConfigAesCrypto crypto.EncryptionAlgorithm idpConfigAlg crypto.EncryptionAlgorithm
UserCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
iamDomain string iamDomain string
} }
type Config struct { type Config struct {
LanguageCookieName string LanguageCookieName string
CSRF CSRF CSRFCookieName string
Cache middleware.CacheConfig Cache middleware.CacheConfig
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again //StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
} }
type CSRF struct {
CookieName string
Key *crypto.KeyConfig
}
const ( const (
login = "LOGIN" login = "LOGIN"
HandlerPrefix = "/ui/login" HandlerPrefix = "/ui/login"
DefaultLoggedOutPath = HandlerPrefix + EndpointLogoutDone 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) { func CreateLogin(config Config,
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.IDPConfigVerificationKey) command *command.Commands,
if err != nil { query *query.Queries,
return nil, fmt.Errorf("error create new aes crypto: %w", err) 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{ login := &Login{
oidcAuthCallbackURL: oidcAuthCallbackURL, oidcAuthCallbackURL: oidcAuthCallbackURL,
baseURL: HandlerPrefix, baseURL: baseURL + HandlerPrefix,
zitadelURL: zitadelURL, consolePath: consolePath,
command: command, command: command,
query: query, query: query,
staticStorage: staticStorage, staticStorage: staticStorage,
authRepo: authRepo, authRepo: authRepo,
IDPConfigAesCrypto: aesCrypto,
iamDomain: domain, iamDomain: domain,
UserCodeAlg: userCrypto, idpConfigAlg: idpConfigAlg,
userCodeAlg: userCodeAlg,
} }
//TODO: enable when storage is implemented again //TODO: enable when storage is implemented again
//login.staticCache, err = config.StaticCache.Config.NewCache() //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) 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 { if err != nil {
return nil, fmt.Errorf("unable to create csrfInterceptor: %w", err) return nil, fmt.Errorf("unable to create csrfInterceptor: %w", err)
} }
@ -111,15 +118,11 @@ func csp() *middleware.CSP {
return &csp return &csp
} }
func createCSRFInterceptor(config CSRF, externalSecure bool, errorHandler http.Handler) (func(http.Handler) http.Handler, error) { func createCSRFInterceptor(cookieName string, csrfCookieKey []byte, 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
}
path := "/" path := "/"
return csrf.Protect([]byte(csrfKey), return csrf.Protect(csrfCookieKey,
csrf.Secure(externalSecure), csrf.Secure(externalSecure),
csrf.CookieName(http_utils.SetCookiePrefix(config.CookieName, "", path, externalSecure)), csrf.CookieName(http_utils.SetCookiePrefix(cookieName, "", path, externalSecure)),
csrf.Path(path), csrf.Path(path),
csrf.ErrorHandler(errorHandler), csrf.ErrorHandler(errorHandler),
), nil ), nil

View File

@ -24,7 +24,7 @@ func (l *Login) handleLogin(w http.ResponseWriter, r *http.Request) {
return return
} }
if authReq == nil { if authReq == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.consolePath, http.StatusFound)
return return
} }
l.renderNextStep(w, r, authReq) l.renderNextStep(w, r, authReq)

View File

@ -51,7 +51,7 @@ func (l *Login) handleMailVerificationCheck(w http.ResponseWriter, r *http.Reque
if authReq != nil { if authReq != nil {
userOrg = authReq.UserOrgID 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 { if err != nil {
l.checkMailCode(w, r, authReq, data.UserID, data.Code) l.checkMailCode(w, r, authReq, data.UserID, data.Code)
return return
@ -66,7 +66,7 @@ func (l *Login) checkMailCode(w http.ResponseWriter, r *http.Request, authReq *d
userID = authReq.UserID userID = authReq.UserID
userOrg = authReq.UserOrgID 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 { if err != nil {
l.renderMailVerification(w, r, authReq, userID, err) l.renderMailVerification(w, r, authReq, userID, err)
return return

View File

@ -27,7 +27,7 @@ func (l *Login) handlePasswordReset(w http.ResponseWriter, r *http.Request) {
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
return 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 { if err != nil {
l.renderPasswordResetDone(w, r, authReq, err) l.renderPasswordResetDone(w, r, authReq, err)
return return

View File

@ -74,12 +74,12 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
memberRoles = nil memberRoles = nil
resourceOwner = authRequest.RequestedOrgID 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 { if err != nil {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return 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 { if err != nil {
l.renderRegister(w, r, authRequest, data, err) l.renderRegister(w, r, authRequest, data, err)
return return
@ -90,7 +90,7 @@ func (l *Login) handleRegisterCheck(w http.ResponseWriter, r *http.Request) {
return return
} }
if authRequest == nil { if authRequest == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.consolePath, http.StatusFound)
return return
} }
userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context()) userAgentID, _ := http_mw.UserAgentIDFromCtx(r.Context())

View File

@ -65,12 +65,12 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) {
l.renderRegisterOrg(w, r, authRequest, data, err) l.renderRegisterOrg(w, r, authRequest, data, err)
return 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 { if err != nil {
l.renderRegisterOrg(w, r, authRequest, data, err) l.renderRegisterOrg(w, r, authRequest, data, err)
return 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 { if err != nil {
l.renderRegisterOrg(w, r, authRequest, data, err) l.renderRegisterOrg(w, r, authRequest, data, err)
return return
@ -81,7 +81,7 @@ func (l *Login) handleRegisterOrgCheck(w http.ResponseWriter, r *http.Request) {
return return
} }
if authRequest == nil { if authRequest == nil {
http.Redirect(w, r, l.zitadelURL, http.StatusFound) http.Redirect(w, r, l.consolePath, http.StatusFound)
return return
} }
l.renderNextStep(w, r, authRequest) l.renderNextStep(w, r, authRequest)

View File

@ -5,13 +5,13 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/auth/repository/eventsourcing/view" "github.com/caos/zitadel/internal/auth/repository/eventsourcing/view"
"github.com/caos/zitadel/internal/auth_request/model" "github.com/caos/zitadel/internal/auth_request/model"
cache "github.com/caos/zitadel/internal/auth_request/repository" cache "github.com/caos/zitadel/internal/auth_request/repository"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
v1 "github.com/caos/zitadel/internal/eventstore/v1" v1 "github.com/caos/zitadel/internal/eventstore/v1"
@ -666,15 +666,20 @@ func queryLoginPolicyToDomain(policy *query.LoginPolicy) *domain.LoginPolicy {
CreationDate: policy.CreationDate, CreationDate: policy.CreationDate,
ChangeDate: policy.ChangeDate, ChangeDate: policy.ChangeDate,
}, },
Default: policy.IsDefault, Default: policy.IsDefault,
AllowUsernamePassword: policy.AllowUsernamePassword, AllowUsernamePassword: policy.AllowUsernamePassword,
AllowRegister: policy.AllowRegister, AllowRegister: policy.AllowRegister,
AllowExternalIDP: policy.AllowExternalIDPs, AllowExternalIDP: policy.AllowExternalIDPs,
ForceMFA: policy.ForceMFA, ForceMFA: policy.ForceMFA,
SecondFactors: policy.SecondFactors, SecondFactors: policy.SecondFactors,
MultiFactors: policy.MultiFactors, MultiFactors: policy.MultiFactors,
PasswordlessType: policy.PasswordlessType, PasswordlessType: policy.PasswordlessType,
HidePasswordReset: policy.HidePasswordReset, HidePasswordReset: policy.HidePasswordReset,
PasswordCheckLifetime: policy.PasswordCheckLifetime,
ExternalLoginCheckLifetime: policy.ExternalLoginCheckLifetime,
MFAInitSkipLifetime: policy.MFAInitSkipLifetime,
SecondFactorCheckLifetime: policy.SecondFactorCheckLifetime,
MultiFactorCheckLifetime: policy.MultiFactorCheckLifetime,
} }
} }

View File

@ -33,19 +33,14 @@ type EsRepository struct {
eventstore.OrgRepository 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) es, err := v1.Start(dbClient)
if err != nil { if err != nil {
return nil, err return nil, err
} }
keyAlgorithm, err := crypto.NewAESCrypto(keyConfig)
if err != nil {
return nil, err
}
idGenerator := id.SonyFlakeGenerator 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 { if err != nil {
return nil, err return nil, err
} }
@ -80,7 +75,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
AuthRequests: authReq, AuthRequests: authReq,
View: view, View: view,
Eventstore: es, Eventstore: es,
UserCodeAlg: userCrypto, UserCodeAlg: userEncryption,
UserSessionViewProvider: view, UserSessionViewProvider: view,
UserViewProvider: view, UserViewProvider: view,
UserCommandProvider: command, UserCommandProvider: command,
@ -101,7 +96,7 @@ func Start(conf Config, systemDefaults sd.SystemDefaults, command *command.Comma
View: view, View: view,
Eventstore: es, Eventstore: es,
SearchLimit: conf.SearchLimit, SearchLimit: conf.SearchLimit,
KeyAlgorithm: keyAlgorithm, KeyAlgorithm: oidcEncryption,
}, },
eventstore.UserSessionRepo{ eventstore.UserSessionRepo{
View: view, View: view,

View File

@ -14,6 +14,6 @@ type Config struct {
Repository eventsourcing.Config Repository eventsourcing.Config
} }
func Start(config Config, systemDefaults sd.SystemDefaults, queries *query.Queries, dbClient *sql.DB, keyConfig *crypto.KeyConfig) (repository.Repository, error) { 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, keyConfig) return eventsourcing.Start(config.Repository, systemDefaults, queries, dbClient, keyEncryptionAlgorithm)
} }

View File

@ -26,7 +26,7 @@ type EsRepository struct {
eventstore.TokenVerifierRepo 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) es, err := v1.Start(dbClient)
if err != nil { if err != nil {
return nil, err 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) spool := spooler.StartSpooler(conf.Spooler, es, view, dbClient, systemDefaults)
keyAlgorithm, err := crypto.NewAESCrypto(keyConfig)
if err != nil {
return nil, err
}
return &EsRepository{ return &EsRepository{
spool, spool,
eventstore.UserMembershipRepo{ eventstore.UserMembershipRepo{
View: view, View: view,
}, },
eventstore.TokenVerifierRepo{ eventstore.TokenVerifierRepo{
TokenVerificationKey: keyAlgorithm, TokenVerificationKey: keyEncryptionAlgorithm,
Eventstore: es, Eventstore: es,
View: view, View: view,
Query: queries, Query: queries,

View File

@ -54,28 +54,33 @@ type orgFeatureChecker interface {
CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error CheckOrgFeatures(ctx context.Context, orgID string, requiredFeatures ...string) error
} }
func StartCommands( func StartCommands(es *eventstore.Eventstore,
es *eventstore.Eventstore,
defaults sd.SystemDefaults, defaults sd.SystemDefaults,
authZConfig authz.Config, authZConfig authz.Config,
staticStore static.Storage, staticStore static.Storage,
authZRepo authz_repo.Repository, authZRepo authz_repo.Repository,
keyConfig *crypto.KeyConfig,
webAuthN webauthn_helper.Config, webAuthN webauthn_helper.Config,
smtpPasswordEncAlg crypto.EncryptionAlgorithm, idpConfigEncryption,
smsHashAlg crypto.EncryptionAlgorithm, otpEncryption,
smtpEncryption,
smsEncryption,
domainVerificationEncryption,
oidcEncryption crypto.EncryptionAlgorithm,
) (repo *Commands, err error) { ) (repo *Commands, err error) {
repo = &Commands{ repo = &Commands{
eventstore: es, eventstore: es,
static: staticStore, static: staticStore,
idGenerator: id.SonyFlakeGenerator, idGenerator: id.SonyFlakeGenerator,
iamDomain: defaults.Domain, iamDomain: defaults.Domain,
zitadelRoles: authZConfig.RolePermissionMappings, zitadelRoles: authZConfig.RolePermissionMappings,
keySize: defaults.KeyConfig.Size, keySize: defaults.KeyConfig.Size,
privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime, privateKeyLifetime: defaults.KeyConfig.PrivateKeyLifetime,
publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime, publicKeyLifetime: defaults.KeyConfig.PublicKeyLifetime,
smtpPasswordCrypto: smtpPasswordEncAlg, idpConfigSecretCrypto: idpConfigEncryption,
smsCrypto: smsHashAlg, smtpPasswordCrypto: smtpEncryption,
smsCrypto: smsEncryption,
domainVerificationAlg: domainVerificationEncryption,
keyAlgorithm: oidcEncryption,
} }
iam_repo.RegisterEventMappers(repo.eventstore) iam_repo.RegisterEventMappers(repo.eventstore)
org.RegisterEventMappers(repo.eventstore) org.RegisterEventMappers(repo.eventstore)
@ -85,30 +90,17 @@ func StartCommands(
keypair.RegisterEventMappers(repo.eventstore) keypair.RegisterEventMappers(repo.eventstore)
action.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.userPasswordAlg = crypto.NewBCrypt(defaults.SecretGenerators.PasswordSaltCost)
repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize) repo.machineKeySize = int(defaults.SecretGenerators.MachineKeySize)
repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize) repo.applicationKeySize = int(defaults.SecretGenerators.ApplicationKeySize)
aesOTPCrypto, err := crypto.NewAESCrypto(defaults.Multifactors.OTP.VerificationKey)
if err != nil {
return nil, err
}
repo.multifactors = domain.MultifactorConfigs{ repo.multifactors = domain.MultifactorConfigs{
OTP: domain.OTPConfig{ OTP: domain.OTPConfig{
CryptoMFA: aesOTPCrypto, CryptoMFA: otpEncryption,
Issuer: defaults.Multifactors.OTP.Issuer, 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.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg)
repo.domainVerificationValidator = http.ValidateDomain repo.domainVerificationValidator = http.ValidateDomain
web, err := webauthn_helper.StartServer(webAuthN) web, err := webauthn_helper.StartServer(webAuthN)
@ -117,12 +109,6 @@ func StartCommands(
} }
repo.webauthn = web repo.webauthn = web
keyAlgorithm, err := crypto.NewAESCrypto(keyConfig)
if err != nil {
return nil, err
}
repo.keyAlgorithm = keyAlgorithm
repo.tokenVerifier = authZRepo repo.tokenVerifier = authZRepo
return repo, nil return repo, nil
} }

View File

@ -6,25 +6,17 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/caos/zitadel/internal/crypto" "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 { type SystemDefaults struct {
DefaultLanguage language.Tag DefaultLanguage language.Tag
Domain string Domain string
ZitadelDocs ZitadelDocs ZitadelDocs ZitadelDocs
SecretGenerators SecretGenerators SecretGenerators SecretGenerators
UserVerificationKey *crypto.KeyConfig Multifactors MultifactorConfig
IDPConfigVerificationKey *crypto.KeyConfig DomainVerification DomainVerification
SMTPPasswordVerificationKey *crypto.KeyConfig Notifications Notifications
SMSVerificationKey *crypto.KeyConfig KeyConfig KeyConfig
Multifactors MultifactorConfig
DomainVerification DomainVerification
Notifications Notifications
KeyConfig KeyConfig
} }
type ZitadelDocs struct { type ZitadelDocs struct {
@ -43,20 +35,16 @@ type MultifactorConfig struct {
} }
type OTPConfig struct { type OTPConfig struct {
Issuer string Issuer string
VerificationKey *crypto.KeyConfig
} }
type DomainVerification struct { type DomainVerification struct {
VerificationKey *crypto.KeyConfig
VerificationGenerator crypto.GeneratorConfig VerificationGenerator crypto.GeneratorConfig
} }
type Notifications struct { type Notifications struct {
DebugMode bool
Endpoints Endpoints Endpoints Endpoints
FileSystemPath string FileSystemPath string
//Providers Channels
} }
type Endpoints struct { type Endpoints struct {
@ -67,20 +55,6 @@ type Endpoints struct {
PasswordlessRegistration string 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 { type KeyConfig struct {
Size int Size int
PrivateKeyLifetime time.Duration PrivateKeyLifetime time.Duration

View File

@ -18,8 +18,8 @@ type AESCrypto struct {
keyIDs []string keyIDs []string
} }
func NewAESCrypto(config *KeyConfig) (*AESCrypto, error) { func NewAESCrypto(config *KeyConfig, keyStorage KeyStorage) (*AESCrypto, error) {
keys, ids, err := LoadKeys(config) keys, ids, err := LoadKeys(config, keyStorage)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -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
}

View File

@ -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)
}
}
}

View File

@ -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")
}

View File

@ -1,51 +1,49 @@
package crypto package crypto
import ( import (
"os" "crypto/rand"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/config"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"
) )
const (
ZitadelKeyPath = "ZITADEL_KEY_PATH"
)
type KeyConfig struct { type KeyConfig struct {
EncryptionKeyID string EncryptionKeyID string
DecryptionKeyIDs []string DecryptionKeyIDs []string
Path string
} }
type Keys map[string]string type Keys map[string]string
func ReadKeys(path string) (Keys, error) { type Key struct {
if path == "" { ID string
path = os.Getenv(ZitadelKeyPath) Value string
if path == "" {
return nil, errors.ThrowInvalidArgument(nil, "CRYPT-56lka", "no path set")
}
}
keys := new(Keys)
err := config.Read(keys, path)
return *keys, err
} }
func LoadKey(config *KeyConfig, id string) (string, error) { func NewKey(id string) (*Key, error) {
keys, _, err := LoadKeys(config) 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 { if err != nil {
return "", err 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 { if config == nil {
return nil, nil, errors.ThrowInvalidArgument(nil, "CRYPT-dJK8s", "config must not be 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -54,7 +52,7 @@ func LoadKeys(config *KeyConfig) (map[string]string, []string, error) {
if config.EncryptionKeyID != "" { if config.EncryptionKeyID != "" {
key, ok := readKeys[config.EncryptionKeyID] key, ok := readKeys[config.EncryptionKeyID]
if !ok { 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 keys[config.EncryptionKeyID] = key
ids = append(ids, config.EncryptionKeyID) ids = append(ids, config.EncryptionKeyID)
@ -62,7 +60,7 @@ func LoadKeys(config *KeyConfig) (map[string]string, []string, error) {
for _, id := range config.DecryptionKeyIDs { for _, id := range config.DecryptionKeyIDs {
key, ok := readKeys[id] key, ok := readKeys[id]
if !ok { if !ok {
logging.Log("CRYPT-s23rf").Warnf("description key %s not found", id) logging.Errorf("description key %s not found", id)
continue continue
} }
keys[id] = key keys[id] = key

View File

@ -0,0 +1,7 @@
package crypto
type KeyStorage interface {
ReadKeys() (Keys, error)
ReadKey(id string) (*Key, error)
CreateKeys(...*Key) error
}

View File

@ -4,9 +4,10 @@ import (
"database/sql" "database/sql"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/crypto"
"github.com/rakyll/statik/fs" "github.com/rakyll/statik/fs"
"github.com/caos/zitadel/internal/crypto"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/notification/repository/eventsourcing" "github.com/caos/zitadel/internal/notification/repository/eventsourcing"
@ -18,10 +19,19 @@ type Config struct {
Repository eventsourcing.Config 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") statikFS, err := fs.NewWithNamespace("notification")
logging.OnError(err).Panic("unable to start listener") 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") logging.OnError(err).Panic("unable to start app")
} }

View File

@ -4,8 +4,6 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/caos/logging"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
sd "github.com/caos/zitadel/internal/config/systemdefaults" sd "github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
@ -34,10 +32,20 @@ func (h *handler) Eventstore() v1.Eventstore {
return h.es 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 { func Register(configs Configs,
aesCrypto, err := crypto.NewAESCrypto(systemDefaults.UserVerificationKey) bulkLimit,
logging.OnError(err).Fatal("error create new aes crypto") 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{ return []queryv1.Handler{
newNotifyUser( newNotifyUser(
handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es}, handler{view, bulkLimit, configs.cycleDuration("User"), errorCount, es},
@ -48,11 +56,11 @@ func Register(configs Configs, bulkLimit, errorCount uint64, view *view.View, es
command, command,
queries, queries,
systemDefaults, systemDefaults,
aesCrypto,
dir, dir,
assetsPrefix, assetsPrefix,
smtpPasswordEncAlg, userEncryption,
smsCrypto, smtpEncryption,
smsEncryption,
), ),
} }
} }

View File

@ -7,6 +7,7 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/notification/channels/fs" "github.com/caos/zitadel/internal/notification/channels/fs"
"github.com/caos/zitadel/internal/notification/channels/log" "github.com/caos/zitadel/internal/notification/channels/log"
"github.com/caos/zitadel/internal/notification/channels/twilio" "github.com/caos/zitadel/internal/notification/channels/twilio"
@ -40,26 +41,36 @@ type Notification struct {
handler handler
command *command.Commands command *command.Commands
systemDefaults sd.SystemDefaults systemDefaults sd.SystemDefaults
AesCrypto crypto.EncryptionAlgorithm
statikDir http.FileSystem statikDir http.FileSystem
subscription *v1.Subscription subscription *v1.Subscription
assetsPrefix string assetsPrefix string
queries *query.Queries queries *query.Queries
userDataCrypto crypto.EncryptionAlgorithm
smtpPasswordCrypto crypto.EncryptionAlgorithm smtpPasswordCrypto crypto.EncryptionAlgorithm
smsTokenCrypto 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{ h := &Notification{
handler: handler, handler: handler,
command: command, command: command,
systemDefaults: defaults, systemDefaults: defaults,
statikDir: statikDir, statikDir: statikDir,
AesCrypto: aesCrypto,
assetsPrefix: assetsPrefix, assetsPrefix: assetsPrefix,
queries: query, queries: query,
smtpPasswordCrypto: smtpPasswordEncAlg, userDataCrypto: userEncryption,
smsTokenCrypto: smsCrypto, smtpPasswordCrypto: smtpEncryption,
smsTokenCrypto: smsEncryption,
} }
h.subscribe() h.subscribe()
@ -161,7 +172,7 @@ func (n *Notification) handleInitUserCode(event *models.Event) (err error) {
return err 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 { if err != nil {
return err return err
} }
@ -199,7 +210,7 @@ func (n *Notification) handlePasswordCode(event *models.Event) (err error) {
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -238,7 +249,7 @@ func (n *Notification) handleEmailVerificationCode(event *models.Event) (err err
return 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 { if err != nil {
return err return err
} }
@ -264,7 +275,7 @@ func (n *Notification) handlePhoneVerificationCode(event *models.Event) (err err
if err != nil { if err != nil {
return err 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 { if err != nil {
return err return err
} }
@ -351,7 +362,7 @@ func (n *Notification) handlePasswordlessRegistrationLink(event *models.Event) (
return err 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 { if err != nil {
return err return err
} }

View File

@ -22,7 +22,17 @@ type EsRepository struct {
spooler *es_spol.Spooler 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) es, err := v1.Start(dbClient)
if err != nil { if err != nil {
return nil, err return nil, err
@ -33,7 +43,7 @@ func Start(conf Config, dir http.FileSystem, systemDefaults sd.SystemDefaults, c
return nil, err 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{ return &EsRepository{
spool, spool,

View File

@ -21,12 +21,24 @@ type SpoolerConfig struct {
Handlers handler.Configs 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{ spoolerConfig := spooler.Config{
Eventstore: es, Eventstore: es,
Locker: &locker{dbClient: sql}, Locker: &locker{dbClient: sql},
ConcurrentWorkers: c.ConcurrentWorkers, 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 := spoolerConfig.New()
spool.Start() spool.Start()

View File

@ -26,17 +26,15 @@ const (
KeyPublicTable = KeyProjectionTable + "_" + publicKeyTableSuffix 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) p := new(KeyProjection)
config.ProjectionName = KeyProjectionTable config.ProjectionName = KeyProjectionTable
config.Reducers = p.reducers() config.Reducers = p.reducers()
p.StatementHandler = crdb.NewStatementHandler(ctx, config) p.StatementHandler = crdb.NewStatementHandler(ctx, config)
p.keyChan = keyChan p.keyChan = keyChan
p.encryptionAlgorithm, err = crypto.NewAESCrypto(keyConfig) p.encryptionAlgorithm = keyEncryptionAlgorithm
if err != nil {
return nil, err return p
}
return p, nil
} }
func (p *KeyProjection) reducers() []handler.AggregateReducer { func (p *KeyProjection) reducers() []handler.AggregateReducer {

View File

@ -17,7 +17,7 @@ const (
FailedEventsTable = "projections.failed_events" 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{ projectionConfig := crdb.StatementHandlerConfig{
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{ ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
HandlerConfig: handler.HandlerConfig{ 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"])) NewSMTPConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["smtp_configs"]))
NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"])) NewSMSConfigProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["sms_config"]))
NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"])) 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"])) NewDebugNotificationProviderProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["debug_notification_provider"]))
NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm, keyChan)
return err return nil
} }
func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomConfig) crdb.StatementHandlerConfig { func applyCustomConfig(config crdb.StatementHandlerConfig, customConfig CustomConfig) crdb.StatementHandlerConfig {

View File

@ -11,7 +11,6 @@ import (
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz" "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/crypto"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
"github.com/caos/zitadel/internal/query/projection" "github.com/caos/zitadel/internal/query/projection"
@ -38,7 +37,7 @@ type Queries struct {
zitadelRoles []authz.RoleMapping 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") statikLoginFS, err := fs.NewWithNamespace("login")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to start login statik dir") 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) keypair.RegisterEventMappers(repo.eventstore)
usergrant.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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -55,3 +55,10 @@ CREATE TABLE zitadel.projections.sms_configs_twilio (
, PRIMARY KEY (sms_id) , 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;