feat: dynamic issuer (#3481)

* feat: dynamic issuer

* dynamic domain handling

* key rotation durations

* feat: dynamic issuer

* make webauthn displayname dynamic
This commit is contained in:
Livio Amstutz 2022-04-25 10:01:17 +02:00 committed by GitHub
parent 3d5891eb11
commit 75ec73ca4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 403 additions and 348 deletions

View File

@ -11,7 +11,6 @@ import (
"github.com/caos/zitadel/internal/crypto" "github.com/caos/zitadel/internal/crypto"
crypto_db "github.com/caos/zitadel/internal/crypto/database" crypto_db "github.com/caos/zitadel/internal/crypto/database"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
webauthn_helper "github.com/caos/zitadel/internal/webauthn"
) )
type DefaultInstance struct { type DefaultInstance struct {
@ -47,8 +46,7 @@ func (mig *DefaultInstance) Execute(ctx context.Context) error {
mig.zitadelRoles, mig.zitadelRoles,
nil, nil,
nil, nil,
//TODO: Livio will fix this, but it ZITADEL doesn't run without this nil,
webauthn_helper.Config{DisplayName: "HELLO LIVIO", ID: "RPID"},
nil, nil,
nil, nil,
nil, nil,

View File

@ -2,11 +2,12 @@ package start
import ( import (
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/hook"
"github.com/mitchellh/mapstructure" "github.com/mitchellh/mapstructure"
"github.com/spf13/viper" "github.com/spf13/viper"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/hook"
admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing" admin_es "github.com/caos/zitadel/internal/admin/repository/eventsourcing"
internal_authz "github.com/caos/zitadel/internal/api/authz" internal_authz "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/api/http/middleware"
@ -31,6 +32,7 @@ type Config struct {
ExternalSecure bool ExternalSecure bool
HTTP2HostHeader string HTTP2HostHeader string
HTTP1HostHeader string HTTP1HostHeader string
WebAuthNName string
Database database.Config Database database.Config
Projections projection.Config Projections projection.Config
AuthZ authz.Config AuthZ authz.Config

View File

@ -13,8 +13,7 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/api/grpc/system"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
@ -29,6 +28,7 @@ import (
"github.com/caos/zitadel/internal/api/grpc/admin" "github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/api/grpc/auth" "github.com/caos/zitadel/internal/api/grpc/auth"
"github.com/caos/zitadel/internal/api/grpc/management" "github.com/caos/zitadel/internal/api/grpc/management"
"github.com/caos/zitadel/internal/api/grpc/system"
http_util "github.com/caos/zitadel/internal/api/http" http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/api/oidc" "github.com/caos/zitadel/internal/api/oidc"
@ -49,10 +49,6 @@ 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",
@ -78,7 +74,6 @@ Requirements:
func startZitadel(config *Config, masterKey string) error { func startZitadel(config *Config, masterKey string) error {
ctx := context.Background() ctx := context.Background()
keyChan := make(chan interface{})
dbClient, err := database.Connect(config.Database) dbClient, err := database.Connect(config.Database)
if err != nil { if err != nil {
@ -99,7 +94,7 @@ func startZitadel(config *Config, masterKey string) error {
return fmt.Errorf("cannot start eventstore for queries: %w", err) return fmt.Errorf("cannot start eventstore for queries: %w", err)
} }
queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, keyChan, config.InternalAuthZ.RolePermissionMappings) queries, err := query.StartQueries(ctx, eventstoreClient, dbClient, config.Projections, keys.OIDC, 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)
} }
@ -113,10 +108,9 @@ func startZitadel(config *Config, masterKey string) error {
if err != nil { if err != nil {
return fmt.Errorf("cannot start asset storage client: %w", err) return fmt.Errorf("cannot start asset storage client: %w", err)
} }
webAuthNConfig := webauthn.Config{ webAuthNConfig := &webauthn.Config{
ID: config.ExternalDomain, DisplayName: config.WebAuthNName,
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure), ExternalSecure: config.ExternalSecure,
DisplayName: "ZITADEL",
} }
commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ.RolePermissionMappings, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC) commands, err := command.StartCommands(eventstoreClient, config.SystemDefaults, config.InternalAuthZ.RolePermissionMappings, storage, authZRepo, webAuthNConfig, keys.IDPConfig, keys.OTP, keys.SMTP, keys.SMS, keys.User, keys.DomainVerification, keys.OIDC)
if err != nil { if err != nil {
@ -126,14 +120,14 @@ func startZitadel(config *Config, masterKey string) error {
notification.Start(config.Notification, config.SystemDefaults, commands, queries, dbClient, assets.HandlerPrefix, keys.User, keys.SMTP, keys.SMS) 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, keys) err = startAPIs(ctx, router, commands, queries, eventstoreClient, dbClient, 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 *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error { func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, config *Config, store static.Storage, authZRepo authz_repo.Repository, keys *encryptionKeys) error {
repo := struct { repo := struct {
authz_repo.Repository authz_repo.Repository
*query.Queries *query.Queries
@ -168,13 +162,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader) instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler)) authenticatedAPIs.RegisterHandler(assets.HandlerPrefix, assets.NewHandler(commands, verifier, config.InternalAuthZ, id.SonyFlakeGenerator, store, queries, instanceInterceptor.Handler))
userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, config.ExternalDomain, id.SonyFlakeGenerator, config.ExternalSecure) userAgentInterceptor, err := middleware.NewUserAgentHandler(config.UserAgentCookie, keys.UserAgentCookieKey, id.SonyFlakeGenerator, config.ExternalSecure)
if err != nil { if err != nil {
return err return err
} }
issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler)
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, keys.OIDC, keys.OIDCKey, eventstore, dbClient, keyChan, userAgentInterceptor, instanceInterceptor.Handler)
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)
} }
@ -187,13 +180,13 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler) authenticatedAPIs.RegisterHandler(openapi.HandlerPrefix, openAPIHandler)
baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure) baseURL := http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
c, err := console.Start(config.Console, config.ExternalDomain, baseURL, issuer, instanceInterceptor.Handler) c, err := console.Start(config.Console, config.ExternalSecure, oidcProvider.IssuerFromRequest, instanceInterceptor.Handler)
if err != nil { if err != nil {
return fmt.Errorf("unable to start console: %w", err) return fmt.Errorf("unable to start console: %w", err)
} }
authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c) authenticatedAPIs.RegisterHandler(console.HandlerPrefix, c)
l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, instanceInterceptor.Handler, keys.User, keys.IDPConfig, keys.CSRFCookieKey) l, err := login.CreateLogin(config.Login, commands, queries, authRepo, store, config.SystemDefaults, console.HandlerPrefix+"/", config.ExternalDomain, baseURL, op.AuthCallbackURL(oidcProvider), config.ExternalSecure, userAgentInterceptor, op.NewIssuerInterceptor(oidcProvider.IssuerFromRequest).Handler, instanceInterceptor.Handler, 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

@ -10,6 +10,8 @@ ExternalSecure: true
HTTP2HostHeader: ":authority" HTTP2HostHeader: ":authority"
HTTP1HostHeader: "host" HTTP1HostHeader: "host"
WebAuthNName: ZITADEL
Database: Database:
Host: localhost Host: localhost
Port: 26257 Port: 26257

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/allegro/bigcache v1.2.1 github.com/allegro/bigcache v1.2.1
github.com/boombuler/barcode v1.0.1 github.com/boombuler/barcode v1.0.1
github.com/caos/logging v0.3.1 github.com/caos/logging v0.3.1
github.com/caos/oidc v1.2.0 github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1
github.com/cockroachdb/cockroach-go/v2 v2.2.4 github.com/cockroachdb/cockroach-go/v2 v2.2.4
github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51 github.com/dop251/goja v0.0.0-20211129110639-4739a1d10a51
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
@ -47,7 +47,7 @@ require (
github.com/sony/sonyflake v1.0.0 github.com/sony/sonyflake v1.0.0
github.com/spf13/cobra v1.3.0 github.com/spf13/cobra v1.3.0
github.com/spf13/viper v1.10.1 github.com/spf13/viper v1.10.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.1
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203
github.com/ttacon/libphonenumber v1.2.1 github.com/ttacon/libphonenumber v1.2.1
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.27.0

11
go.sum
View File

@ -123,13 +123,10 @@ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kB
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs= github.com/boombuler/barcode v1.0.1 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= github.com/boombuler/barcode v1.0.1/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8=
github.com/caos/logging v0.0.2/go.mod h1:9LKiDE2ChuGv6CHYif/kiugrfEXu9AwDiFWSreX7Wp0=
github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc= github.com/caos/logging v0.3.1 h1:892AMeHs09D0e3ZcGB+QDRsZ5+2xtPAsAhOy8eKfztc=
github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo= github.com/caos/logging v0.3.1/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
github.com/caos/oidc v1.0.1 h1:8UHAPynCObwaqortppDtJFktjqLDLYSLidkNy0Num4o= github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1 h1:xJuhRlhJf9huGX/+c/mEN2QaFiYlSMHd4QfsWkDoCGI=
github.com/caos/oidc v1.0.1/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU= github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1/go.mod h1:bdIIx0WCNjbGqpi7o4PifypVoeCAD3yTncL03rogNKw=
github.com/caos/oidc v1.2.0 h1:dTy5bcT2WQbwPgytEZiG8SV1bCgHUXyDdaPDCNtRdEU=
github.com/caos/oidc v1.2.0/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
@ -895,8 +892,9 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 h1:1SWXcTphBQjYGWRRxLFIAR1LVtQEj4eR7xPtyeOVM/c= github.com/superseriousbusiness/exifremove v0.0.0-20210330092427-6acd27eac203 h1:1SWXcTphBQjYGWRRxLFIAR1LVtQEj4eR7xPtyeOVM/c=
@ -1192,7 +1190,6 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191206220618-eeba5f6aabab/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=

View File

@ -3,7 +3,7 @@ package actions
import ( import (
"encoding/json" "encoding/json"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
) )
type Context map[string]interface{} type Context map[string]interface{}

View File

@ -5,7 +5,7 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"golang.org/x/text/language" "golang.org/x/text/language"
"google.golang.org/protobuf/types/known/durationpb" "google.golang.org/protobuf/types/known/durationpb"

View File

@ -2,6 +2,7 @@ package http
import ( import (
"net/http" "net/http"
"strings"
"github.com/gorilla/securecookie" "github.com/gorilla/securecookie"
@ -20,7 +21,6 @@ type CookieHandler struct {
sameSite http.SameSite sameSite http.SameSite
path string path string
maxAge int maxAge int
domain string
} }
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler { func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
@ -76,12 +76,6 @@ func WithMaxAge(maxAge int) CookieHandlerOpt {
} }
} }
func WithDomain(domain string) CookieHandlerOpt {
return func(c *CookieHandler) {
c.domain = domain
}
}
func SetCookiePrefix(name, domain, path string, secureOnly bool) string { func SetCookiePrefix(name, domain, path string, secureOnly bool) string {
if !secureOnly { if !secureOnly {
return name return name
@ -101,7 +95,7 @@ func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, er
} }
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error { func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
cookie, err := r.Cookie(SetCookiePrefix(name, c.domain, c.path, c.secureOnly)) cookie, err := r.Cookie(SetCookiePrefix(name, r.Host, c.path, c.secureOnly))
if err != nil { if err != nil {
return err return err
} }
@ -111,11 +105,11 @@ func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, va
return c.securecookie.Decode(name, cookie.Value, value) return c.securecookie.Decode(name, cookie.Value, value)
} }
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) { func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, domain, value string) {
c.httpSet(w, name, value, c.maxAge) c.httpSet(w, name, domain, value, c.maxAge)
} }
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error { func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name, domain string, value interface{}) error {
if c.securecookie == nil { if c.securecookie == nil {
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured") return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
} }
@ -123,19 +117,19 @@ func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, v
if err != nil { if err != nil {
return err return err
} }
c.httpSet(w, name, encoded, c.maxAge) c.httpSet(w, name, domain, encoded, c.maxAge)
return nil return nil
} }
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) { func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, r *http.Request, name string) {
c.httpSet(w, name, "", -1) c.httpSet(w, name, r.Host, "", -1)
} }
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) { func (c *CookieHandler) httpSet(w http.ResponseWriter, name, domain, value string, maxage int) {
http.SetCookie(w, &http.Cookie{ http.SetCookie(w, &http.Cookie{
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly), Name: SetCookiePrefix(name, domain, c.path, c.secureOnly),
Value: value, Value: value,
Domain: c.domain, Domain: strings.Split(domain, ":")[0],
Path: c.path, Path: c.path,
MaxAge: maxage, MaxAge: maxage,
HttpOnly: c.httpOnly, HttpOnly: c.httpOnly,

View File

@ -34,7 +34,7 @@ var (
} }
) )
func (csp *CSP) Value(nonce string) string { func (csp *CSP) Value(nonce string, host string) string {
valuesMap := csp.asMap() valuesMap := csp.asMap()
values := make([]string, 0, len(valuesMap)) values := make([]string, 0, len(valuesMap))
@ -43,7 +43,7 @@ func (csp *CSP) Value(nonce string) string {
continue continue
} }
values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce))) values = append(values, fmt.Sprintf("%v %v", k, v.String(nonce, host)))
} }
return strings.Join(values, ";") return strings.Join(values, ";")
@ -99,24 +99,33 @@ func (srcOpts CSPSourceOptions) AddHost(h ...string) CSPSourceOptions {
return append(srcOpts, h...) return append(srcOpts, h...)
} }
func (srcOpts CSPSourceOptions) AddOwnHost() CSPSourceOptions {
return append(srcOpts, placeHolderHost)
}
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions { func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
return srcOpts.add(s, "%v:") return srcOpts.add(s, "%v:")
} }
func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions { func (srcOpts CSPSourceOptions) AddNonce() CSPSourceOptions {
return append(srcOpts, "'nonce-%v'") return append(srcOpts, fmt.Sprintf("'nonce-%s'", placeHolderNonce))
} }
const (
placeHolderNonce = "{{nonce}}"
placeHolderHost = "{{host}}"
)
func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions { func (srcOpts CSPSourceOptions) AddHash(alg, b64v string) CSPSourceOptions {
return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v)) return append(srcOpts, fmt.Sprintf("'%v-%v'", alg, b64v))
} }
func (srcOpts CSPSourceOptions) String(nonce string) string { func (srcOpts CSPSourceOptions) String(nonce, host string) string {
value := strings.Join(srcOpts, " ") value := strings.Join(srcOpts, " ")
if !strings.Contains(value, "%v") { if !strings.Contains(value, placeHolderNonce) && !strings.Contains(value, placeHolderHost) {
return value return value
} }
return fmt.Sprintf(value, nonce) return strings.ReplaceAll(strings.ReplaceAll(value, placeHolderHost, host), placeHolderNonce, nonce)
} }
func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions { func (srcOpts CSPSourceOptions) add(values []string, format string) CSPSourceOptions {

View File

@ -63,7 +63,7 @@ func (h *headers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r = saveContext(r, nonceKey, nonce) r = saveContext(r, nonceKey, nonce)
} }
headers := w.Header() headers := w.Header()
headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce)) headers.Set(http_utils.ContentSecurityPolicy, h.csp.Value(nonce, r.Host))
headers.Set(http_utils.XXSSProtection, "1; mode=block") headers.Set(http_utils.XXSSProtection, "1; mode=block")
headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains") headers.Set(http_utils.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
headers.Set(http_utils.XFrameOptions, "DENY") headers.Set(http_utils.XFrameOptions, "DENY")

View File

@ -21,10 +21,6 @@ func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
return userAgentID, ok return userAgentID, ok
} }
func InstanceIDFromCtx(ctx context.Context) string {
return "" //TODO: implement
}
type UserAgent struct { type UserAgent struct {
ID string ID string
} }
@ -41,10 +37,9 @@ type UserAgentCookieConfig struct {
MaxAge time.Duration MaxAge time.Duration
} }
func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, domain string, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) { func NewUserAgentHandler(config *UserAgentCookieConfig, cookieKey []byte, idGenerator id.Generator, externalSecure bool) (func(http.Handler) http.Handler, error) {
opts := []http_utils.CookieHandlerOpt{ opts := []http_utils.CookieHandlerOpt{
http_utils.WithEncryption(cookieKey, cookieKey), http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(domain),
http_utils.WithMaxAge(int(config.MaxAge.Seconds())), http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
} }
if !externalSecure { if !externalSecure {
@ -68,7 +63,7 @@ func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err == nil { if err == nil {
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID) ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
r = r.WithContext(ctx) r = r.WithContext(ctx)
ua.setUserAgent(w, agent) ua.setUserAgent(w, r.Host, agent)
} }
ua.nextHandler.ServeHTTP(w, r) ua.nextHandler.ServeHTTP(w, r)
} }
@ -90,8 +85,8 @@ func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
return userAgent, nil return userAgent, nil
} }
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error { func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, host string, agent *UserAgent) error {
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent) err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, host, agent)
if err != nil { if err != nil {
return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie") return errors.ThrowPermissionDenied(err, "HTTP-AqgqdA", "cannot set user agent cookie")
} }

View File

@ -32,9 +32,13 @@ func IsOrigin(rawOrigin string) bool {
} }
func BuildHTTP(hostname string, externalPort uint16, secure bool) string { func BuildHTTP(hostname string, externalPort uint16, secure bool) string {
return BuildOrigin(fmt.Sprintf("%s:%d", hostname, externalPort), secure)
}
func BuildOrigin(host string, secure bool) string {
schema := "https" schema := "https"
if !secure { if !secure {
schema = "http" schema = "http"
} }
return fmt.Sprintf("%s://%s:%d", schema, hostname, externalPort) return fmt.Sprintf("%s://%s", schema, host)
} }

View File

@ -6,8 +6,8 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/api/http/middleware"

View File

@ -6,8 +6,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"golang.org/x/text/language" "golang.org/x/text/language"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"

View File

@ -5,8 +5,8 @@ import (
"encoding/base64" "encoding/base64"
"strings" "strings"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
@ -43,7 +43,7 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl
if err != nil { if err != nil {
return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal") return nil, errors.ThrowInternal(err, "OIDC-mPxqP", "Errors.Internal")
} }
projectRoles, err := o.query.SearchProjectRoles(context.TODO(), &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}}) projectRoles, err := o.query.SearchProjectRoles(ctx, &query.ProjectRoleSearchQueries{Queries: []query.SearchQuery{projectIDQuery}})
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -4,8 +4,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/errors" "github.com/caos/zitadel/internal/errors"

View File

@ -6,6 +6,7 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2" "gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
@ -20,99 +21,121 @@ import (
const ( const (
locksTable = "projections.locks" locksTable = "projections.locks"
signingKey = "signing_key" signingKey = "signing_key"
oidcUser = "OIDC"
retryBackoff = 500 * time.Millisecond
retryCount = 3
lockDuration = retryCount * retryBackoff * 5
gracefulPeriod = 10 * time.Minute
) )
func (o *OPStorage) GetKeySet(ctx context.Context) (_ *jose.JSONWebKeySet, err error) { //SigningKey wraps the query.PrivateKey to implement the op.SigningKey interface
type SigningKey struct {
algorithm jose.SignatureAlgorithm
id string
key interface{}
}
func (s *SigningKey) SignatureAlgorithm() jose.SignatureAlgorithm {
return s.algorithm
}
func (s *SigningKey) Key() interface{} {
return s.key
}
func (s *SigningKey) ID() string {
return s.id
}
//PublicKey wraps the query.PublicKey to implement the op.Key interface
type PublicKey struct {
key query.PublicKey
}
func (s *PublicKey) Algorithm() jose.SignatureAlgorithm {
return jose.SignatureAlgorithm(s.key.Algorithm())
}
func (s *PublicKey) Use() string {
return s.key.Use().String()
}
func (s *PublicKey) Key() interface{} {
return s.key.Key()
}
func (s *PublicKey) ID() string {
return s.key.ID()
}
//KeySet implements the op.Storage interface
func (o *OPStorage) KeySet(ctx context.Context) (keys []op.Key, err error) {
ctx, span := tracing.NewSpan(ctx) ctx, span := tracing.NewSpan(ctx)
defer func() { span.EndWithError(err) }() defer func() { span.EndWithError(err) }()
keys, err := o.query.ActivePublicKeys(ctx, time.Now()) err = retry(func() error {
publicKeys, err := o.query.ActivePublicKeys(ctx, time.Now())
if err != nil {
return err
}
keys = make([]op.Key, len(publicKeys.Keys))
for i, key := range publicKeys.Keys {
keys[i] = &PublicKey{key}
}
return nil
})
return keys, err
}
//SignatureAlgorithms implements the op.Storage interface
func (o *OPStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
key, err := o.SigningKey(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
webKeys := make([]jose.JSONWebKey, len(keys.Keys)) return []jose.SignatureAlgorithm{key.SignatureAlgorithm()}, nil
for i, key := range keys.Keys {
webKeys[i] = jose.JSONWebKey{
KeyID: key.ID(),
Algorithm: key.Algorithm(),
Use: key.Use().String(),
Key: key.Key(),
}
}
return &jose.JSONWebKeySet{Keys: webKeys}, nil
} }
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) { //SigningKey implements the op.Storage interface
renewTimer := time.NewTimer(0) func (o *OPStorage) SigningKey(ctx context.Context) (key op.SigningKey, err error) {
go func() { err = retry(func() error {
for { key, err = o.getSigningKey(ctx)
select { if err != nil {
case <-ctx.Done(): return err
return
case <-o.keyChan:
if !renewTimer.Stop() {
<-renewTimer.C
}
checkAfter := o.resetTimer(renewTimer, true)
logging.Infof("requested next signing key check in %s", checkAfter)
case <-renewTimer.C:
o.getSigningKey(ctx, renewTimer, keyCh)
}
} }
}() if key == nil {
return errors.ThrowInternal(nil, "test", "test")
}
return nil
})
return key, err
} }
func (o *OPStorage) getSigningKey(ctx context.Context, renewTimer *time.Timer, keyCh chan<- jose.SigningKey) { func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(o.signingKeyGracefulPeriod)) keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(gracefulPeriod))
if err != nil { if err != nil {
checkAfter := o.resetTimer(renewTimer, true) return nil, err
logging.Infof("next signing key check in %s", checkAfter)
return
} }
if len(keys.Keys) == 0 { if len(keys.Keys) > 0 {
var sequence uint64 return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
if keys.LatestSequence != nil {
sequence = keys.LatestSequence.Sequence
}
o.refreshSigningKey(ctx, keyCh, o.signingKeyAlgorithm, sequence)
checkAfter := o.resetTimer(renewTimer, true)
logging.Infof("next signing key check in %s", checkAfter)
return
} }
err = o.exchangeSigningKey(selectSigningKey(keys.Keys), keyCh) var sequence uint64
logging.OnError(err).Error("could not exchange signing key") if keys.LatestSequence != nil {
checkAfter := o.resetTimer(renewTimer, err != nil) sequence = keys.LatestSequence.Sequence
logging.Infof("next signing key check in %s", checkAfter) }
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, sequence)
} }
func (o *OPStorage) resetTimer(timer *time.Timer, shortRefresh bool) (nextCheck time.Duration) { func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, sequence uint64) error {
nextCheck = o.signingKeyRotationCheck
defer func() { timer.Reset(nextCheck) }()
if shortRefresh || o.currentKey == nil {
return nextCheck
}
maxLifetime := time.Until(o.currentKey.Expiry())
if maxLifetime < o.signingKeyGracefulPeriod+2*o.signingKeyRotationCheck {
return nextCheck
}
return maxLifetime - o.signingKeyGracefulPeriod - o.signingKeyRotationCheck
}
func (o *OPStorage) refreshSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey, algorithm string, sequence uint64) {
if o.currentKey != nil && o.currentKey.Expiry().Before(time.Now().UTC()) {
logging.Info("unset current signing key")
keyCh <- jose.SigningKey{}
}
ok, err := o.ensureIsLatestKey(ctx, sequence) ok, err := o.ensureIsLatestKey(ctx, sequence)
if err != nil { if err != nil || !ok {
logging.New().WithError(err).Error("could not ensure latest key") return errors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
return
}
if !ok {
logging.Warn("view not up to date, retrying later")
return
} }
err = o.lockAndGenerateSigningKeyPair(ctx, algorithm) err = o.lockAndGenerateSigningKeyPair(ctx, algorithm)
logging.OnError(err).Warn("could not create signing key") if err != nil {
return errors.ThrowInternal(err, "OIDC-ADh31", "could not create signing key")
}
return errors.ThrowInternal(nil, "OIDC-Df1bh", "")
} }
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (bool, error) { func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (bool, error) {
@ -123,29 +146,20 @@ func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (boo
return sequence == maxSequence, nil return sequence == maxSequence, nil
} }
func (o *OPStorage) exchangeSigningKey(key query.PrivateKey, keyCh chan<- jose.SigningKey) (err error) { func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
if o.currentKey != nil && o.currentKey.ID() == key.ID() {
logging.Info("no new signing key")
return nil
}
keyData, err := crypto.Decrypt(key.Key(), o.encAlg) keyData, err := crypto.Decrypt(key.Key(), o.encAlg)
if err != nil { if err != nil {
return err return nil, err
} }
privateKey, err := crypto.BytesToPrivateKey(keyData) privateKey, err := crypto.BytesToPrivateKey(keyData)
if err != nil { if err != nil {
return err return nil, err
} }
keyCh <- jose.SigningKey{ return &SigningKey{
Algorithm: jose.SignatureAlgorithm(key.Algorithm()), algorithm: jose.SignatureAlgorithm(key.Algorithm()),
Key: jose.JSONWebKey{ key: privateKey,
KeyID: key.ID(), id: key.ID(),
Key: privateKey, }, nil
},
}
o.currentKey = key
logging.WithFields("keyID", key.ID()).Info("exchanged signing key")
return nil
} }
func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error { func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm string) error {
@ -154,7 +168,7 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
ctx, cancel := context.WithCancel(ctx) ctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
errs := o.locker.Lock(ctx, o.signingKeyRotationCheck*2, authz.GetInstance(ctx).InstanceID()) errs := o.locker.Lock(ctx, lockDuration, authz.GetInstance(ctx).InstanceID())
err, ok := <-errs err, ok := <-errs
if err != nil || !ok { if err != nil || !ok {
if errors.IsErrorAlreadyExists(err) { if errors.IsErrorAlreadyExists(err) {
@ -164,13 +178,13 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
return err return err
} }
return o.command.GenerateSigningKeyPair(ctx, algorithm) return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
} }
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) { func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
return o.eventstore.LatestSequence(ctx, return o.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence). eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner("system"). //TODO: change with multi issuer ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AddQuery(). AddQuery().
AggregateTypes(keypair.AggregateType). AggregateTypes(keypair.AggregateType).
Builder(), Builder(),
@ -180,3 +194,18 @@ func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
func selectSigningKey(keys []query.PrivateKey) query.PrivateKey { func selectSigningKey(keys []query.PrivateKey) query.PrivateKey {
return keys[len(keys)-1] return keys[len(keys)-1]
} }
func setOIDCCtx(ctx context.Context) context.Context {
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
}
func retry(retryable func() error) (err error) {
for i := 0; i < retryCount; i++ {
time.Sleep(retryBackoff)
err = retryable()
if err == nil {
return nil
}
}
return err
}

View File

@ -7,7 +7,7 @@ import (
"net/http" "net/http"
"time" "time"
"github.com/caos/oidc/pkg/op" "github.com/caos/oidc/v2/pkg/op"
"github.com/rakyll/statik/fs" "github.com/rakyll/statik/fs"
"golang.org/x/text/language" "golang.org/x/text/language"
@ -17,7 +17,6 @@ import (
"github.com/caos/zitadel/internal/api/ui/login" "github.com/caos/zitadel/internal/api/ui/login"
"github.com/caos/zitadel/internal/auth/repository" "github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/internal/command" "github.com/caos/zitadel/internal/command"
"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" caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
@ -74,26 +73,23 @@ type OPStorage struct {
defaultRefreshTokenIdleExpiration time.Duration defaultRefreshTokenIdleExpiration time.Duration
defaultRefreshTokenExpiration time.Duration defaultRefreshTokenExpiration time.Duration
encAlg crypto.EncryptionAlgorithm encAlg crypto.EncryptionAlgorithm
keyChan <-chan interface{}
currentKey query.PrivateKey
signingKeyRotationCheck time.Duration
signingKeyGracefulPeriod time.Duration
locker crdb.Locker locker crdb.Locker
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, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) { func NewProvider(ctx context.Context, config Config, defaultLogoutRedirectURI string, externalSecure bool, command *command.Commands, query *query.Queries, repo repository.Repository, encryptionAlg crypto.EncryptionAlgorithm, cryptoKey []byte, es *eventstore.Eventstore, projections *sql.DB, userAgentCookie, instanceHandler func(http.Handler) http.Handler) (op.OpenIDProvider, error) {
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey) opConfig, err := createOPConfig(config, defaultLogoutRedirectURI, cryptoKey)
if err != nil { if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w") return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
} }
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan) storage := newStorage(config, command, query, repo, encryptionAlg, es, projections)
options, err := createOptions(config, userAgentCookie, instanceHandler) options, err := createOptions(config, externalSecure, userAgentCookie, instanceHandler)
if err != nil { if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w") return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
} }
provider, err := op.NewOpenIDProvider( provider, err := op.NewDynamicOpenIDProvider(
ctx, ctx,
HandlerPrefix,
opConfig, opConfig,
storage, storage,
options..., options...,
@ -104,17 +100,12 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire
return provider, nil return provider, nil
} }
func Issuer(domain string, port uint16, externalSecure bool) string { func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix
}
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
} }
opConfig := &op.Config{ opConfig := &op.Config{
Issuer: issuer,
DefaultLogoutRedirectURI: defaultLogoutRedirectURI, DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
CodeMethodS256: config.CodeMethodS256, CodeMethodS256: config.CodeMethodS256,
AuthMethodPost: config.AuthMethodPost, AuthMethodPost: config.AuthMethodPost,
@ -130,21 +121,26 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryp
return opConfig, nil return opConfig, nil
} }
func createOptions(config Config, userAgentCookie, instanceHandler func(http.Handler) http.Handler) ([]op.Option, error) { func createOptions(config Config, externalSecure bool, userAgentCookie, instanceHandler 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( options := []op.Option{
middleware.MetricsHandler(metricTypes), op.WithHttpInterceptors(
middleware.TelemetryHandler(), middleware.MetricsHandler(metricTypes),
middleware.NoCacheInterceptor, middleware.TelemetryHandler(),
instanceHandler, middleware.NoCacheInterceptor,
userAgentCookie, instanceHandler,
http_utils.CopyHeadersToContext, userAgentCookie,
) http_utils.CopyHeadersToContext,
endpoints := customEndpoints(config.CustomEndpoints) ),
if len(endpoints) == 0 {
return []op.Option{interceptor}, nil
} }
return append(endpoints, interceptor), nil if !externalSecure {
options = append(options, op.WithAllowInsecure())
}
endpoints := customEndpoints(config.CustomEndpoints)
if len(endpoints) != 0 {
options = append(options, endpoints...)
}
return options, nil
} }
func customEndpoints(endpointConfig *EndpointConfig) []op.Option { func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
@ -176,7 +172,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, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB, keyChan <-chan interface{}) *OPStorage { func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, encAlg crypto.EncryptionAlgorithm, es *eventstore.Eventstore, projections *sql.DB) *OPStorage {
return &OPStorage{ return &OPStorage{
repo: repo, repo: repo,
command: command, command: command,
@ -189,10 +185,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration, defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration, defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
encAlg: encAlg, encAlg: encAlg,
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod,
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck,
locker: crdb.NewLocker(projections, locksTable, signingKey), locker: crdb.NewLocker(projections, locksTable, signingKey),
keyChan: keyChan,
assetAPIPrefix: assets.HandlerPrefix, assetAPIPrefix: assets.HandlerPrefix,
} }
} }

View File

@ -8,13 +8,13 @@ import (
"net/http" "net/http"
"os" "os"
"path" "path"
"strings"
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/api/authz" "github.com/caos/zitadel/internal/api/authz"
http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/api/http/middleware" "github.com/caos/zitadel/internal/api/http/middleware"
) )
@ -59,7 +59,7 @@ func (i *spaHandler) Open(name string) (http.File, error) {
return i.fileSystem.Open("/index.html") return i.fileSystem.Open("/index.html")
} }
func Start(config Config, domain, url, issuer string, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) { func Start(config Config, externalSecure bool, issuer op.IssuerFromRequest, instanceHandler func(http.Handler) http.Handler) (http.Handler, error) {
fSys, err := fs.Sub(static, "static") fSys, err := fs.Sub(static, "static")
if err != nil { if err != nil {
return nil, err return nil, err
@ -70,7 +70,7 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
config.LongCache.MaxAge, config.LongCache.MaxAge,
config.LongCache.SharedMaxAge, config.LongCache.SharedMaxAge,
) )
security := middleware.SecurityHeaders(csp(domain), nil) security := middleware.SecurityHeaders(csp(), nil)
handler := &http.ServeMux{} handler := &http.ServeMux{}
handler.Handle("/", cache(security(http.FileServer(&spaHandler{http.FS(fSys)})))) handler.Handle("/", cache(security(http.FileServer(&spaHandler{http.FS(fSys)}))))
@ -80,7 +80,8 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
http.Error(w, "empty instanceID", http.StatusInternalServerError) http.Error(w, "empty instanceID", http.StatusInternalServerError)
return return
} }
environmentJSON, err := createEnvironmentJSON(url, issuer, instance.ConsoleClientID()) url := http_util.BuildOrigin(r.Host, externalSecure)
environmentJSON, err := createEnvironmentJSON(url, issuer(r), instance.ConsoleClientID())
if err != nil { if err != nil {
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError) http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
return return
@ -91,15 +92,12 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
return handler, nil return handler, nil
} }
func csp(zitadelDomain string) *middleware.CSP { func csp() *middleware.CSP {
if !strings.HasPrefix(zitadelDomain, "*.") {
zitadelDomain = "*." + zitadelDomain
}
csp := middleware.DefaultSCP csp := middleware.DefaultSCP
csp.StyleSrc = csp.StyleSrc.AddInline() csp.StyleSrc = csp.StyleSrc.AddInline()
csp.ScriptSrc = csp.ScriptSrc.AddEval() csp.ScriptSrc = csp.ScriptSrc.AddEval()
csp.ConnectSrc = csp.ConnectSrc.AddHost(zitadelDomain) csp.ConnectSrc = csp.ConnectSrc.AddOwnHost()
csp.ImgSrc = csp.ImgSrc.AddHost(zitadelDomain).AddScheme("blob") csp.ImgSrc = csp.ImgSrc.AddOwnHost().AddScheme("blob")
return &csp return &csp
} }

View File

@ -3,10 +3,10 @@ package login
import ( import (
"context" "context"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/actions" "github.com/caos/zitadel/internal/actions"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
iam_model "github.com/caos/zitadel/internal/iam/model" iam_model "github.com/caos/zitadel/internal/iam/model"
) )

View File

@ -7,8 +7,8 @@ import (
"strings" "strings"
"time" "time"
"github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/v2/pkg/client/rp"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"golang.org/x/oauth2" "golang.org/x/oauth2"
http_mw "github.com/caos/zitadel/internal/api/http/middleware" http_mw "github.com/caos/zitadel/internal/api/http/middleware"

View File

@ -4,8 +4,8 @@ import (
"net/http" "net/http"
"strings" "strings"
"github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/v2/pkg/client/rp"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"golang.org/x/text/language" "golang.org/x/text/language"
http_mw "github.com/caos/zitadel/internal/api/http/middleware" http_mw "github.com/caos/zitadel/internal/api/http/middleware"

View File

@ -9,8 +9,8 @@ import (
"time" "time"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/caos/oidc/pkg/client/rp" "github.com/caos/oidc/v2/pkg/client/rp"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
http_util "github.com/caos/zitadel/internal/api/http" http_util "github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"

View File

@ -25,18 +25,17 @@ import (
) )
type Login struct { type Login struct {
endpoint string endpoint string
router http.Handler router http.Handler
renderer *Renderer renderer *Renderer
parser *form.Parser parser *form.Parser
command *command.Commands command *command.Commands
query *query.Queries query *query.Queries
staticStorage static.Storage staticStorage static.Storage
//staticCache cache.Cache //TODO: enable when storage is implemented again
authRepo auth_repository.Repository authRepo auth_repository.Repository
baseURL string baseURL string
consolePath string consolePath string
oidcAuthCallbackURL func(string) string oidcAuthCallbackURL func(context.Context, string) string
idpConfigAlg crypto.EncryptionAlgorithm idpConfigAlg crypto.EncryptionAlgorithm
userCodeAlg crypto.EncryptionAlgorithm userCodeAlg crypto.EncryptionAlgorithm
iamDomain string iamDomain string
@ -46,7 +45,6 @@ type Config struct {
LanguageCookieName string LanguageCookieName string
CSRFCookieName string CSRFCookieName string
Cache middleware.CacheConfig Cache middleware.CacheConfig
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
} }
const ( const (
@ -64,9 +62,10 @@ func CreateLogin(config Config,
consolePath, consolePath,
domain, domain,
baseURL string, baseURL string,
oidcAuthCallbackURL func(string) string, oidcAuthCallbackURL func(context.Context, string) string,
externalSecure bool, externalSecure bool,
userAgentCookie, userAgentCookie,
issuerInterceptor,
instanceHandler mux.MiddlewareFunc, instanceHandler mux.MiddlewareFunc,
userCodeAlg crypto.EncryptionAlgorithm, userCodeAlg crypto.EncryptionAlgorithm,
idpConfigAlg crypto.EncryptionAlgorithm, idpConfigAlg crypto.EncryptionAlgorithm,
@ -85,12 +84,6 @@ func CreateLogin(config Config,
idpConfigAlg: idpConfigAlg, idpConfigAlg: idpConfigAlg,
userCodeAlg: userCodeAlg, userCodeAlg: userCodeAlg,
} }
//TODO: enable when storage is implemented again
//login.staticCache, err = config.StaticCache.Config.NewCache()
//if err != nil {
// return nil, fmt.Errorf("unable to create storage cache: %w", err)
//}
statikFS, err := fs.NewWithNamespace("login") statikFS, err := fs.NewWithNamespace("login")
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to create filesystem: %w", err) return nil, fmt.Errorf("unable to create filesystem: %w", err)
@ -105,7 +98,8 @@ func CreateLogin(config Config,
return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err) return nil, fmt.Errorf("unable to create cacheInterceptor: %w", err)
} }
security := middleware.SecurityHeaders(csp(), login.cspErrorHandler) security := middleware.SecurityHeaders(csp(), login.cspErrorHandler)
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources))
login.router = CreateRouter(login, statikFS, instanceHandler, csrfInterceptor, cacheInterceptor, security, userAgentCookie, middleware.TelemetryHandler(EndpointResources), issuerInterceptor)
login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage) login.renderer = CreateRenderer(HandlerPrefix, statikFS, staticStorage, config.LanguageCookieName, systemDefaults.DefaultLanguage)
login.parser = form.NewParser() login.parser = form.NewParser()
return login, nil return login, nil

View File

@ -43,11 +43,11 @@ func (l *Login) renderSuccessAndCallback(w http.ResponseWriter, r *http.Request,
userData: l.getUserData(r, authReq, "Login Successful", errID, errMessage), userData: l.getUserData(r, authReq, "Login Successful", errID, errMessage),
} }
if authReq != nil { if authReq != nil {
data.RedirectURI = l.oidcAuthCallbackURL("") //the id will be set via the html (maybe change this with the login refactoring) data.RedirectURI = l.oidcAuthCallbackURL(r.Context(), "") //the id will be set via the html (maybe change this with the login refactoring)
} }
l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplLoginSuccess], data, nil) l.renderer.RenderTemplate(w, r, l.getTranslator(authReq), l.renderer.Templates[tmplLoginSuccess], data, nil)
} }
func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) { func (l *Login) redirectToCallback(w http.ResponseWriter, r *http.Request, authReq *domain.AuthRequest) {
http.Redirect(w, r, l.oidcAuthCallbackURL(authReq.ID), http.StatusFound) http.Redirect(w, r, l.oidcAuthCallbackURL(r.Context(), authReq.ID), http.StatusFound)
} }

View File

@ -42,7 +42,7 @@ type Commands struct {
domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error domainVerificationValidator func(domain, token, verifier string, checkType http.CheckType) error
multifactors domain.MultifactorConfigs multifactors domain.MultifactorConfigs
webauthn *webauthn_helper.WebAuthN webauthnConfig *webauthn_helper.Config
keySize int keySize int
keyAlgorithm crypto.EncryptionAlgorithm keyAlgorithm crypto.EncryptionAlgorithm
privateKeyLifetime time.Duration privateKeyLifetime time.Duration
@ -60,7 +60,7 @@ func StartCommands(es *eventstore.Eventstore,
zitadelRoles []authz.RoleMapping, zitadelRoles []authz.RoleMapping,
staticStore static.Storage, staticStore static.Storage,
authZRepo authz_repo.Repository, authZRepo authz_repo.Repository,
webAuthN webauthn_helper.Config, webAuthN *webauthn_helper.Config,
idpConfigEncryption, idpConfigEncryption,
otpEncryption, otpEncryption,
smtpEncryption, smtpEncryption,
@ -84,6 +84,7 @@ func StartCommands(es *eventstore.Eventstore,
userEncryption: userEncryption, userEncryption: userEncryption,
domainVerificationAlg: domainVerificationEncryption, domainVerificationAlg: domainVerificationEncryption,
keyAlgorithm: oidcEncryption, keyAlgorithm: oidcEncryption,
webauthnConfig: webAuthN,
} }
instance_repo.RegisterEventMappers(repo.eventstore) instance_repo.RegisterEventMappers(repo.eventstore)
@ -107,11 +108,6 @@ func StartCommands(es *eventstore.Eventstore,
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)
if err != nil {
return nil, err
}
repo.webauthn = web
repo.tokenVerifier = authZRepo repo.tokenVerifier = authZRepo
return repo, nil return repo, nil

View File

@ -28,16 +28,6 @@ const (
consolePostLogoutPath = console.HandlerPrefix + "/signedout" consolePostLogoutPath = console.HandlerPrefix + "/signedout"
) )
type AddInstance struct {
InstanceName string
CustomDomain string
FirstOrgName string
OwnerEmail string
OwnerUsername string
OwnerFirstName string
OwnerLastName string
}
type InstanceSetup struct { type InstanceSetup struct {
zitadel ZitadelConfig zitadel ZitadelConfig
InstanceName string InstanceName string
@ -301,7 +291,7 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup, exte
} }
if setup.CustomDomain != "" { if setup.CustomDomain != "" {
validations = append(validations, c.addInstanceDomain(instanceAgg, setup.CustomDomain, false)) validations = append(validations, addInstanceDomain(instanceAgg, setup.CustomDomain, false))
} }
console := &addOIDCApp{ console := &addOIDCApp{

View File

@ -15,7 +15,7 @@ import (
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) { func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.addInstanceDomain(instanceAgg, instanceDomain, false) validation := addInstanceDomain(instanceAgg, instanceDomain, false)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil { if err != nil {
return nil, err return nil, err
@ -33,7 +33,7 @@ func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string)
func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) { func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.setPrimaryInstanceDomain(instanceAgg, instanceDomain) validation := setPrimaryInstanceDomain(instanceAgg, instanceDomain)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil { if err != nil {
return nil, err return nil, err
@ -51,7 +51,7 @@ func (c *Commands) SetPrimaryInstanceDomain(ctx context.Context, instanceDomain
func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) { func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID())
validation := c.removeInstanceDomain(instanceAgg, instanceDomain) validation := removeInstanceDomain(instanceAgg, instanceDomain)
cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation)
if err != nil { if err != nil {
return nil, err return nil, err
@ -69,16 +69,16 @@ func (c *Commands) RemoveInstanceDomain(ctx context.Context, instanceDomain stri
func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation { func (c *Commands) addGeneratedInstanceDomain(a *instance.Aggregate, instanceName string) preparation.Validation {
domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain) domain := domain.NewGeneratedInstanceDomain(instanceName, c.iamDomain)
return c.addInstanceDomain(a, domain, true) return addInstanceDomain(a, domain, true)
} }
func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation { func addInstanceDomain(a *instance.Aggregate, instanceDomain string, generated bool) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" { if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
} }
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain) domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -88,7 +88,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
events := []eventstore.Command{ events := []eventstore.Command{
instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated), instance.NewDomainAddedEvent(ctx, &a.Aggregate, instanceDomain, generated),
} }
appWriteModel, err := c.getOIDCAppWriteModel(ctx, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "") appWriteModel, err := getOIDCAppWriteModel(ctx, filter, authz.GetInstance(ctx).ProjectID(), authz.GetInstance(ctx).ConsoleApplicationID(), "")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -115,13 +115,13 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
} }
} }
func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation { func setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" { if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
} }
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain) domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -133,13 +133,13 @@ func (c *Commands) setPrimaryInstanceDomain(a *instance.Aggregate, instanceDomai
} }
} }
func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation { func removeInstanceDomain(a *instance.Aggregate, instanceDomain string) preparation.Validation {
return func() (preparation.CreateCommands, error) { return func() (preparation.CreateCommands, error) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" { if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument") return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
} }
return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) {
domainWriteModel, err := c.getInstanceDomainWriteModel(ctx, instanceDomain) domainWriteModel, err := getInstanceDomainWriteModel(ctx, filter, instanceDomain)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -154,11 +154,16 @@ func (c *Commands) removeInstanceDomain(a *instance.Aggregate, instanceDomain st
} }
} }
func (c *Commands) getInstanceDomainWriteModel(ctx context.Context, domain string) (*InstanceDomainWriteModel, error) { func getInstanceDomainWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, domain string) (*InstanceDomainWriteModel, error) {
domainWriteModel := NewInstanceDomainWriteModel(ctx, domain) domainWriteModel := NewInstanceDomainWriteModel(ctx, domain)
err := c.eventstore.FilterToQueryReducer(ctx, domainWriteModel) events, err := filter(ctx, domainWriteModel.Query())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return domainWriteModel, nil if len(events) == 0 {
return domainWriteModel, nil
}
domainWriteModel.AppendEvents(events...)
err = domainWriteModel.Reduce()
return domainWriteModel, err
} }

View File

@ -10,12 +10,7 @@ import (
"github.com/caos/zitadel/internal/repository/keypair" "github.com/caos/zitadel/internal/repository/keypair"
) )
const (
oidcUser = "OIDC"
)
func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error { func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
ctx = setOIDCCtx(ctx)
privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm) privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm)
if err != nil { if err != nil {
return err return err
@ -28,8 +23,7 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime) privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime)
publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime) publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime)
//TODO: InstanceID not available here? keyPairWriteModel := NewKeyPairWriteModel(keyID, authz.GetInstance(ctx).InstanceID())
keyPairWriteModel := NewKeyPairWriteModel(keyID, "system") //TODO: change with multi issuer
keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel) keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel)
_, err = c.eventstore.Push(ctx, keypair.NewAddedEvent( _, err = c.eventstore.Push(ctx, keypair.NewAddedEvent(
ctx, ctx,
@ -40,8 +34,3 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
privateKeyExp, publicKeyExp)) privateKeyExp, publicKeyExp))
return err return err
} }
func setOIDCCtx(ctx context.Context) context.Context {
//TODO: InstanceID not available here?
return authz.SetCtxData(ctx, authz.CtxData{UserID: oidcUser, OrgID: authz.GetInstance(ctx).InstanceID()})
}

View File

@ -3,8 +3,7 @@ package command
import ( import (
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore" "github.com/caos/zitadel/internal/eventstore"
keypair "github.com/caos/zitadel/internal/repository/keypair" "github.com/caos/zitadel/internal/repository/keypair"
"github.com/caos/zitadel/internal/repository/project"
) )
type KeyPairWriteModel struct { type KeyPairWriteModel struct {
@ -52,7 +51,7 @@ func (wm *KeyPairWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent). return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner). ResourceOwner(wm.ResourceOwner).
AddQuery(). AddQuery().
AggregateTypes(project.AggregateType). AggregateTypes(keypair.AggregateType).
AggregateIDs(wm.AggregateID). AggregateIDs(wm.AggregateID).
EventTypes(keypair.AddedEventType). EventTypes(keypair.AddedEventType).
Builder() Builder()

View File

@ -321,3 +321,17 @@ func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, r
} }
return appWriteModel, nil return appWriteModel, nil
} }
func getOIDCAppWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer, projectID, appID, resourceOwner string) (*OIDCApplicationWriteModel, error) {
appWriteModel := NewOIDCApplicationWriteModelWithAppID(projectID, appID, resourceOwner)
events, err := filter(ctx, appWriteModel.Query())
if err != nil {
return nil, err
}
if len(events) == 0 {
return appWriteModel, nil
}
appWriteModel.AppendEvents(events...)
err = appWriteModel.Reduce()
return appWriteModel, err
}

View File

@ -6,7 +6,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/caos/oidc/pkg/oidc" "github.com/caos/oidc/v2/pkg/oidc"
"github.com/golang/mock/gomock" "github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"

View File

@ -157,7 +157,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
if accountName == "" { if accountName == "" {
accountName = user.EmailAddress accountName = user.EmailAddress
} }
webAuthN, err := c.webauthn.BeginRegistration(user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...) webAuthN, err := c.webauthnConfig.BeginRegistration(ctx, user, accountName, authenticatorPlatform, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -272,7 +272,7 @@ func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowne
return nil, nil, nil, err return nil, nil, nil, err
} }
_, token := domain.GetTokenToVerify(tokens) _, token := domain.GetTokenToVerify(tokens)
webAuthN, err := c.webauthn.FinishRegistration(user, token, tokenName, credentialData, userAgentID != "") webAuthN, err := c.webauthnConfig.FinishRegistration(ctx, user, token, tokenName, credentialData, userAgentID != "")
if err != nil { if err != nil {
return nil, nil, nil, err return nil, nil, nil, err
} }
@ -343,7 +343,7 @@ func (c *Commands) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
webAuthNLogin, err := c.webauthn.BeginLogin(human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...) webAuthNLogin, err := c.webauthnConfig.BeginLogin(ctx, human, domain.UserVerificationRequirementDiscouraged, isLoginUI, tokens...)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@ -454,7 +454,7 @@ func (c *Commands) finishWebAuthNLogin(ctx context.Context, userID, resourceOwne
if err != nil { if err != nil {
return nil, nil, 0, err return nil, nil, 0, err
} }
keyID, signCount, err := c.webauthn.FinishLogin(human, webAuthN, credentialData, isLoginUI, tokens...) keyID, signCount, err := c.webauthnConfig.FinishLogin(ctx, human, webAuthN, credentialData, isLoginUI, tokens...)
if err != nil && keyID == nil { if err != nil && keyID == nil {
return nil, nil, 0, err return nil, nil, 0, err
} }

View File

@ -56,9 +56,7 @@ type Endpoints struct {
} }
type KeyConfig struct { type KeyConfig struct {
Size int Size int
PrivateKeyLifetime time.Duration PrivateKeyLifetime time.Duration
PublicKeyLifetime time.Duration PublicKeyLifetime time.Duration
SigningKeyRotationCheck time.Duration
SigningKeyGracefulPeriod time.Duration
} }

View File

@ -145,8 +145,8 @@ func (t *Translator) Lang(r *http.Request) language.Tag {
return tag return tag
} }
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) { func (t *Translator) SetLangCookie(w http.ResponseWriter, r *http.Request, lang language.Tag) {
t.cookieHandler.SetCookie(w, t.cookieName, lang.String()) t.cookieHandler.SetCookie(w, t.cookieName, r.Host, lang.String())
} }
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer { func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
@ -177,7 +177,7 @@ func (t *Translator) langsFromCtx(ctx context.Context) []string {
langs := t.preferredLanguages langs := t.preferredLanguages
if ctx != nil { if ctx != nil {
ctxData := authz.GetCtxData(ctx) ctxData := authz.GetCtxData(ctx)
if ctxData.PreferredLanguage != "" { if ctxData.PreferredLanguage != language.Und.String() {
langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage) langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage)
} }
langs = append(langs, getAcceptLanguageHeader(ctx)) langs = append(langs, getAcceptLanguageHeader(ctx))
@ -190,6 +190,10 @@ func (t *Translator) SetPreferredLanguages(langs ...string) {
} }
func getAcceptLanguageHeader(ctx context.Context) string { func getAcceptLanguageHeader(ctx context.Context) string {
acceptLanguage := metautils.ExtractIncoming(ctx).Get("accept-language")
if acceptLanguage != "" {
return acceptLanguage
}
return metautils.ExtractIncoming(ctx).Get("grpcgateway-accept-language") return metautils.ExtractIncoming(ctx).Get("grpcgateway-accept-language")
} }
@ -199,7 +203,7 @@ func localize(localizer *i18n.Localizer, id string, args map[string]interface{})
TemplateData: args, TemplateData: args,
}) })
if err != nil { if err != nil {
logging.LogWithFields("I18N-MsF5sx", "id", id, "args", args).WithError(err).Warnf("missing translation") logging.WithFields("id", id, "args", args).WithError(err).Warnf("missing translation")
return id return id
} }
return s return s

View File

@ -4,6 +4,7 @@ import (
"context" "context"
"database/sql" "database/sql"
errs "errors" errs "errors"
"strings"
"time" "time"
sq "github.com/Masterminds/squirrel" sq "github.com/Masterminds/squirrel"
@ -163,9 +164,9 @@ func (q *Queries) Instance(ctx context.Context) (*Instance, error) {
} }
func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) { func (q *Queries) InstanceByHost(ctx context.Context, host string) (authz.Instance, error) {
stmt, scan := prepareInstanceQuery(host) stmt, scan := prepareInstanceDomainQuery(host)
query, args, err := stmt.Where(sq.Eq{ query, args, err := stmt.Where(sq.Eq{
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available InstanceDomainDomainCol.identifier(): strings.Split(host, ":")[0],
}).ToSql() }).ToSql()
if err != nil { if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement") return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
@ -279,3 +280,47 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
}, nil }, nil
} }
} }
func prepareInstanceDomainQuery(host string) (sq.SelectBuilder, func(*sql.Row) (*Instance, error)) {
return sq.Select(
InstanceColumnID.identifier(),
InstanceColumnCreationDate.identifier(),
InstanceColumnChangeDate.identifier(),
InstanceColumnSequence.identifier(),
InstanceColumnGlobalOrgID.identifier(),
InstanceColumnProjectID.identifier(),
InstanceColumnConsoleID.identifier(),
InstanceColumnConsoleAppID.identifier(),
InstanceColumnSetupStarted.identifier(),
InstanceColumnSetupDone.identifier(),
InstanceColumnDefaultLanguage.identifier(),
).
From(instanceTable.identifier()).
LeftJoin(join(InstanceDomainInstanceIDCol, InstanceColumnID)).
PlaceholderFormat(sq.Dollar),
func(row *sql.Row) (*Instance, error) {
instance := &Instance{Host: host}
lang := ""
err := row.Scan(
&instance.ID,
&instance.CreationDate,
&instance.ChangeDate,
&instance.Sequence,
&instance.GlobalOrgID,
&instance.IAMProjectID,
&instance.ConsoleID,
&instance.ConsoleAppID,
&instance.SetupStarted,
&instance.SetupDone,
&lang,
)
if err != nil {
if errs.Is(err, sql.ErrNoRows) {
return nil, errors.ThrowNotFound(err, "QUERY-n0wng", "Errors.IAM.NotFound")
}
return nil, errors.ThrowInternal(err, "QUERY-d9nw", "Errors.Internal")
}
instance.DefaultLanguage = language.Make(lang)
return instance, nil
}
}

View File

@ -40,10 +40,9 @@ const (
type KeyProjection struct { type KeyProjection struct {
crdb.StatementHandler crdb.StatementHandler
encryptionAlgorithm crypto.EncryptionAlgorithm encryptionAlgorithm crypto.EncryptionAlgorithm
keyChan chan<- interface{}
} }
func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) *KeyProjection { func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) *KeyProjection {
p := new(KeyProjection) p := new(KeyProjection)
config.ProjectionName = KeyProjectionTable config.ProjectionName = KeyProjectionTable
config.Reducers = p.reducers() config.Reducers = p.reducers()
@ -56,7 +55,7 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText), crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText),
crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64), crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64),
crdb.NewColumn(KeyColumnAlgorithm, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(KeyColumnAlgorithm, crdb.ColumnTypeText, crdb.Default("")),
crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeText, crdb.Default("")), crdb.NewColumn(KeyColumnUse, crdb.ColumnTypeEnum, crdb.Default(0)),
}, },
crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID), crdb.NewPrimaryKey(KeyColumnInstanceID, KeyColumnID),
crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})), crdb.WithConstraint(crdb.NewConstraint("id_unique", []string{KeyColumnID})),
@ -79,7 +78,6 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
), ),
) )
p.StatementHandler = crdb.NewStatementHandler(ctx, config) p.StatementHandler = crdb.NewStatementHandler(ctx, config)
p.keyChan = keyChan
p.encryptionAlgorithm = keyEncryptionAlgorithm p.encryptionAlgorithm = keyEncryptionAlgorithm
return p return p
@ -130,9 +128,6 @@ func (p *KeyProjection) reduceKeyPairAdded(event eventstore.Event) (*handler.Sta
}, },
crdb.WithTableSuffix(privateKeyTableSuffix), crdb.WithTableSuffix(privateKeyTableSuffix),
)) ))
if p.keyChan != nil {
p.keyChan <- true
}
} }
if e.PublicKey.Expiry.After(time.Now()) { if e.PublicKey.Expiry.After(time.Now()) {
publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm) publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm)

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, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, keyChan chan<- interface{}) error { func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, config Config, keyEncryptionAlgorithm crypto.EncryptionAlgorithm) error {
projectionConfig := crdb.StatementHandlerConfig{ projectionConfig := crdb.StatementHandlerConfig{
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{ ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
HandlerConfig: handler.HandlerConfig{ HandlerConfig: handler.HandlerConfig{
@ -74,7 +74,7 @@ func Start(ctx context.Context, sqlClient *sql.DB, es *eventstore.Eventstore, co
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"]))
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) NewKeyProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["keys"]), keyEncryptionAlgorithm)
return nil return nil
} }

View File

@ -37,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, keyEncryptionAlgorithm crypto.EncryptionAlgorithm, 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, 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")
@ -66,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, keyEncryptionAlgorithm, keyChan) err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -2,38 +2,22 @@ package webauthn
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"github.com/caos/logging" "github.com/caos/logging"
"github.com/duo-labs/webauthn/protocol" "github.com/duo-labs/webauthn/protocol"
"github.com/duo-labs/webauthn/webauthn" "github.com/duo-labs/webauthn/webauthn"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http"
"github.com/caos/zitadel/internal/domain" "github.com/caos/zitadel/internal/domain"
caos_errs "github.com/caos/zitadel/internal/errors" caos_errs "github.com/caos/zitadel/internal/errors"
) )
type WebAuthN struct {
webAuthN *webauthn.WebAuthn
}
type Config struct { type Config struct {
ID string DisplayName string
Origin string ExternalSecure bool
DisplayName string
}
func StartServer(config Config) (*WebAuthN, error) {
webAuthN, err := webauthn.New(&webauthn.Config{
RPDisplayName: config.DisplayName,
RPID: config.ID,
RPOrigin: config.Origin,
})
if err != nil {
return nil, err
}
return &WebAuthN{
webAuthN: webAuthN,
}, err
} }
type webUser struct { type webUser struct {
@ -54,7 +38,10 @@ func (u *webUser) WebAuthnName() string {
} }
func (u *webUser) WebAuthnDisplayName() string { func (u *webUser) WebAuthnDisplayName() string {
return u.DisplayName if u.DisplayName != "" {
return u.DisplayName
}
return u.GetUsername()
} }
func (u *webUser) WebAuthnIcon() string { func (u *webUser) WebAuthnIcon() string {
@ -65,7 +52,11 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
return u.credentials return u.credentials
} }
func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) { func (w *Config) BeginRegistration(ctx context.Context, user *domain.Human, accountName string, authType domain.AuthenticatorAttachment, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNToken, error) {
webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, err
}
creds := WebAuthNsToCredentials(webAuthNs) creds := WebAuthNsToCredentials(webAuthNs)
existing := make([]protocol.CredentialDescriptor, len(creds)) existing := make([]protocol.CredentialDescriptor, len(creds))
for i, cred := range creds { for i, cred := range creds {
@ -74,7 +65,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
CredentialID: cred.ID, CredentialID: cred.ID,
} }
} }
credentialOptions, sessionData, err := w.webAuthN.BeginRegistration( credentialOptions, sessionData, err := webAuthNServer.BeginRegistration(
&webUser{ &webUser{
Human: user, Human: user,
accountName: accountName, accountName: accountName,
@ -102,7 +93,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
}, nil }, nil
} }
func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) { func (w *Config) FinishRegistration(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNToken, tokenName string, credData []byte, isLoginUI bool) (*domain.WebAuthNToken, error) {
if webAuthN == nil { if webAuthN == nil {
return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound") return nil, caos_errs.ThrowInternal(nil, "WEBAU-5M9so", "Errors.User.WebAuthN.NotFound")
} }
@ -113,7 +104,11 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential") return nil, caos_errs.ThrowInternal(err, "WEBAU-sEr8c", "Errors.User.WebAuthN.ErrorOnParseCredential")
} }
sessionData := WebAuthNToSessionData(webAuthN) sessionData := WebAuthNToSessionData(webAuthN)
credential, err := w.webAuthN.CreateCredential( webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, err
}
credential, err := webAuthNServer.CreateCredential(
&webUser{ &webUser{
Human: user, Human: user,
}, },
@ -132,8 +127,12 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
return webAuthN, nil return webAuthN, nil
} }
func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) { func (w *Config) BeginLogin(ctx context.Context, user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
assertion, sessionData, err := w.webAuthN.BeginLogin(&webUser{ webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, err
}
assertion, sessionData, err := webAuthNServer.BeginLogin(&webUser{
Human: user, Human: user,
credentials: WebAuthNsToCredentials(webAuthNs), credentials: WebAuthNsToCredentials(webAuthNs),
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification))) }, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
@ -152,7 +151,7 @@ func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVe
}, nil }, nil
} }
func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) { func (w *Config) FinishLogin(ctx context.Context, user *domain.Human, webAuthN *domain.WebAuthNLogin, credData []byte, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) ([]byte, uint32, error) {
assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData)) assertionData, err := protocol.ParseCredentialRequestResponseBody(bytes.NewReader(credData))
if err != nil { if err != nil {
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed") return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-ADgv4", "Errors.User.WebAuthN.ValidateLoginFailed")
@ -161,7 +160,11 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
Human: user, Human: user,
credentials: WebAuthNsToCredentials(webAuthNs), credentials: WebAuthNsToCredentials(webAuthNs),
} }
credential, err := w.webAuthN.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData) webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, 0, err
}
credential, err := webAuthNServer.ValidateLogin(webUser, WebAuthNLoginToSessionData(webAuthN), assertionData)
if err != nil { if err != nil {
return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed") return nil, 0, caos_errs.ThrowInternal(err, "WEBAU-3M9si", "Errors.User.WebAuthN.ValidateLoginFailed")
} }
@ -171,3 +174,12 @@ func (w *WebAuthN) FinishLogin(user *domain.Human, webAuthN *domain.WebAuthNLogi
} }
return credential.ID, credential.Authenticator.SignCount, nil return credential.ID, credential.Authenticator.SignCount, nil
} }
func (w *Config) serverFromContext(ctx context.Context) (*webauthn.WebAuthn, error) {
host := authz.GetInstance(ctx).RequestedDomain()
return webauthn.New(&webauthn.Config{
RPDisplayName: w.DisplayName,
RPID: host,
RPOrigin: http.BuildOrigin(host, w.ExternalSecure),
})
}