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

View File

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

View File

@ -13,8 +13,7 @@ import (
"time"
"github.com/caos/logging"
"github.com/caos/oidc/pkg/op"
"github.com/caos/zitadel/internal/api/grpc/system"
"github.com/caos/oidc/v2/pkg/op"
"github.com/gorilla/mux"
"github.com/spf13/cobra"
"github.com/spf13/viper"
@ -29,6 +28,7 @@ import (
"github.com/caos/zitadel/internal/api/grpc/admin"
"github.com/caos/zitadel/internal/api/grpc/auth"
"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"
"github.com/caos/zitadel/internal/api/http/middleware"
"github.com/caos/zitadel/internal/api/oidc"
@ -49,10 +49,6 @@ import (
"github.com/caos/zitadel/openapi"
)
const (
flagMasterKey = "masterkey"
)
func New() *cobra.Command {
start := &cobra.Command{
Use: "start",
@ -78,7 +74,6 @@ Requirements:
func startZitadel(config *Config, masterKey string) error {
ctx := context.Background()
keyChan := make(chan interface{})
dbClient, err := database.Connect(config.Database)
if err != nil {
@ -99,7 +94,7 @@ func startZitadel(config *Config, masterKey string) error {
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 {
return fmt.Errorf("cannot start queries: %w", err)
}
@ -113,10 +108,9 @@ func startZitadel(config *Config, masterKey string) error {
if err != nil {
return fmt.Errorf("cannot start asset storage client: %w", err)
}
webAuthNConfig := webauthn.Config{
ID: config.ExternalDomain,
Origin: http_util.BuildHTTP(config.ExternalDomain, config.ExternalPort, config.ExternalSecure),
DisplayName: "ZITADEL",
webAuthNConfig := &webauthn.Config{
DisplayName: config.WebAuthNName,
ExternalSecure: config.ExternalSecure,
}
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 {
@ -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)
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 {
return err
}
return listen(ctx, router, config.Port)
}
func startAPIs(ctx context.Context, router *mux.Router, commands *command.Commands, queries *query.Queries, eventstore *eventstore.Eventstore, dbClient *sql.DB, keyChan chan interface{}, config *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 {
authz_repo.Repository
*query.Queries
@ -168,13 +162,12 @@ func startAPIs(ctx context.Context, router *mux.Router, commands *command.Comman
instanceInterceptor := middleware.InstanceInterceptor(queries, config.HTTP1HostHeader)
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 {
return err
}
issuer := oidc.Issuer(config.ExternalDomain, config.ExternalPort, config.ExternalSecure)
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, issuer, login.DefaultLoggedOutPath, commands, queries, authRepo, config.SystemDefaults.KeyConfig, keys.OIDC, keys.OIDCKey, eventstore, dbClient, keyChan, userAgentInterceptor, instanceInterceptor.Handler)
oidcProvider, err := oidc.NewProvider(ctx, config.OIDC, login.DefaultLoggedOutPath, config.ExternalSecure, commands, queries, authRepo, keys.OIDC, keys.OIDCKey, eventstore, dbClient, userAgentInterceptor, instanceInterceptor.Handler)
if err != nil {
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)
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 {
return fmt.Errorf("unable to start console: %w", err)
}
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 {
return fmt.Errorf("unable to start login: %w", err)
}

View File

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

4
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/allegro/bigcache v1.2.1
github.com/boombuler/barcode v1.0.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/dop251/goja v0.0.0-20211129110639-4739a1d10a51
github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d
@ -47,7 +47,7 @@ require (
github.com/sony/sonyflake v1.0.0
github.com/spf13/cobra v1.3.0
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/ttacon/libphonenumber v1.2.1
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 h1:NDBbPmhS+EqABEs5Kg3n/5ZNjy73Pz7SIV+KCeqyXcs=
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/go.mod h1:B8QNS0WDmR2Keac52Fw+XN4ZJkzLDGrcRIPB2Ux4uRo=
github.com/caos/oidc v1.0.1 h1:8UHAPynCObwaqortppDtJFktjqLDLYSLidkNy0Num4o=
github.com/caos/oidc v1.0.1/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU=
github.com/caos/oidc v1.2.0 h1:dTy5bcT2WQbwPgytEZiG8SV1bCgHUXyDdaPDCNtRdEU=
github.com/caos/oidc v1.2.0/go.mod h1:4l0PPwdc6BbrdCFhNrRTUddsG292uHGa7gE2DSEIqoU=
github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1 h1:xJuhRlhJf9huGX/+c/mEN2QaFiYlSMHd4QfsWkDoCGI=
github.com/caos/oidc/v2 v2.0.0-dynamic-issuer.1/go.mod h1:bdIIx0WCNjbGqpi7o4PifypVoeCAD3yTncL03rogNKw=
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/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.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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
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-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-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-20191228213918-04cbcbbfeed8/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 (
"encoding/json"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/v2/pkg/oidc"
)
type Context map[string]interface{}

View File

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

View File

@ -2,6 +2,7 @@ package http
import (
"net/http"
"strings"
"github.com/gorilla/securecookie"
@ -20,7 +21,6 @@ type CookieHandler struct {
sameSite http.SameSite
path string
maxAge int
domain string
}
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 {
if !secureOnly {
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 {
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 {
return err
}
@ -111,11 +105,11 @@ func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, va
return c.securecookie.Decode(name, cookie.Value, value)
}
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
c.httpSet(w, name, value, c.maxAge)
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name, domain, value string) {
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 {
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 {
return err
}
c.httpSet(w, name, encoded, c.maxAge)
c.httpSet(w, name, domain, encoded, c.maxAge)
return nil
}
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
c.httpSet(w, name, "", -1)
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, r *http.Request, name string) {
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{
Name: SetCookiePrefix(name, c.domain, c.path, c.secureOnly),
Name: SetCookiePrefix(name, domain, c.path, c.secureOnly),
Value: value,
Domain: c.domain,
Domain: strings.Split(domain, ":")[0],
Path: c.path,
MaxAge: maxage,
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()
values := make([]string, 0, len(valuesMap))
@ -43,7 +43,7 @@ func (csp *CSP) Value(nonce string) string {
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, ";")
@ -99,24 +99,33 @@ func (srcOpts CSPSourceOptions) AddHost(h ...string) CSPSourceOptions {
return append(srcOpts, h...)
}
func (srcOpts CSPSourceOptions) AddOwnHost() CSPSourceOptions {
return append(srcOpts, placeHolderHost)
}
func (srcOpts CSPSourceOptions) AddScheme(s ...string) CSPSourceOptions {
return srcOpts.add(s, "%v:")
}
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 {
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, " ")
if !strings.Contains(value, "%v") {
if !strings.Contains(value, placeHolderNonce) && !strings.Contains(value, placeHolderHost) {
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 {

View File

@ -63,7 +63,7 @@ func (h *headers) ServeHTTP(w http.ResponseWriter, r *http.Request) {
r = saveContext(r, nonceKey, nonce)
}
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.StrictTransportSecurity, "max-age=31536000; includeSubDomains")
headers.Set(http_utils.XFrameOptions, "DENY")

View File

@ -21,10 +21,6 @@ func UserAgentIDFromCtx(ctx context.Context) (string, bool) {
return userAgentID, ok
}
func InstanceIDFromCtx(ctx context.Context) string {
return "" //TODO: implement
}
type UserAgent struct {
ID string
}
@ -41,10 +37,9 @@ type UserAgentCookieConfig struct {
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{
http_utils.WithEncryption(cookieKey, cookieKey),
http_utils.WithDomain(domain),
http_utils.WithMaxAge(int(config.MaxAge.Seconds())),
}
if !externalSecure {
@ -68,7 +63,7 @@ func (ua *userAgentHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if err == nil {
ctx := context.WithValue(r.Context(), userAgentKey, agent.ID)
r = r.WithContext(ctx)
ua.setUserAgent(w, agent)
ua.setUserAgent(w, r.Host, agent)
}
ua.nextHandler.ServeHTTP(w, r)
}
@ -90,8 +85,8 @@ func (ua *userAgentHandler) getUserAgent(r *http.Request) (*UserAgent, error) {
return userAgent, nil
}
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, agent *UserAgent) error {
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, agent)
func (ua *userAgentHandler) setUserAgent(w http.ResponseWriter, host string, agent *UserAgent) error {
err := ua.cookieHandler.SetEncryptedCookie(w, ua.cookieName, host, agent)
if err != nil {
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 {
return BuildOrigin(fmt.Sprintf("%s:%d", hostname, externalPort), secure)
}
func BuildOrigin(host string, secure bool) string {
schema := "https"
if !secure {
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"
"github.com/caos/logging"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/api/authz"
"github.com/caos/zitadel/internal/api/http/middleware"

View File

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

View File

@ -5,8 +5,8 @@ import (
"encoding/base64"
"strings"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/pkg/op"
"github.com/caos/oidc/v2/pkg/oidc"
"github.com/caos/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2"
"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 {
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 {
return nil, err
}

View File

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

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/caos/logging"
"github.com/caos/oidc/v2/pkg/op"
"gopkg.in/square/go-jose.v2"
"github.com/caos/zitadel/internal/api/authz"
@ -20,99 +21,121 @@ import (
const (
locksTable = "projections.locks"
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)
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 {
return nil, err
}
webKeys := make([]jose.JSONWebKey, len(keys.Keys))
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
return []jose.SignatureAlgorithm{key.SignatureAlgorithm()}, nil
}
func (o *OPStorage) GetSigningKey(ctx context.Context, keyCh chan<- jose.SigningKey) {
renewTimer := time.NewTimer(0)
go func() {
for {
select {
case <-ctx.Done():
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)
}
//SigningKey implements the op.Storage interface
func (o *OPStorage) SigningKey(ctx context.Context) (key op.SigningKey, err error) {
err = retry(func() error {
key, err = o.getSigningKey(ctx)
if err != nil {
return err
}
}()
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) {
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(o.signingKeyGracefulPeriod))
func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(gracefulPeriod))
if err != nil {
checkAfter := o.resetTimer(renewTimer, true)
logging.Infof("next signing key check in %s", checkAfter)
return
return nil, err
}
if len(keys.Keys) == 0 {
var sequence uint64
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
if len(keys.Keys) > 0 {
return o.privateKeyToSigningKey(selectSigningKey(keys.Keys))
}
err = o.exchangeSigningKey(selectSigningKey(keys.Keys), keyCh)
logging.OnError(err).Error("could not exchange signing key")
checkAfter := o.resetTimer(renewTimer, err != nil)
logging.Infof("next signing key check in %s", checkAfter)
var sequence uint64
if keys.LatestSequence != nil {
sequence = keys.LatestSequence.Sequence
}
return nil, o.refreshSigningKey(ctx, o.signingKeyAlgorithm, sequence)
}
func (o *OPStorage) resetTimer(timer *time.Timer, shortRefresh bool) (nextCheck time.Duration) {
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{}
}
func (o *OPStorage) refreshSigningKey(ctx context.Context, algorithm string, sequence uint64) error {
ok, err := o.ensureIsLatestKey(ctx, sequence)
if err != nil {
logging.New().WithError(err).Error("could not ensure latest key")
return
}
if !ok {
logging.Warn("view not up to date, retrying later")
return
if err != nil || !ok {
return errors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
}
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) {
@ -123,29 +146,20 @@ func (o *OPStorage) ensureIsLatestKey(ctx context.Context, sequence uint64) (boo
return sequence == maxSequence, nil
}
func (o *OPStorage) exchangeSigningKey(key query.PrivateKey, keyCh chan<- jose.SigningKey) (err error) {
if o.currentKey != nil && o.currentKey.ID() == key.ID() {
logging.Info("no new signing key")
return nil
}
func (o *OPStorage) privateKeyToSigningKey(key query.PrivateKey) (_ op.SigningKey, err error) {
keyData, err := crypto.Decrypt(key.Key(), o.encAlg)
if err != nil {
return err
return nil, err
}
privateKey, err := crypto.BytesToPrivateKey(keyData)
if err != nil {
return err
return nil, err
}
keyCh <- jose.SigningKey{
Algorithm: jose.SignatureAlgorithm(key.Algorithm()),
Key: jose.JSONWebKey{
KeyID: key.ID(),
Key: privateKey,
},
}
o.currentKey = key
logging.WithFields("keyID", key.ID()).Info("exchanged signing key")
return nil
return &SigningKey{
algorithm: jose.SignatureAlgorithm(key.Algorithm()),
key: privateKey,
id: key.ID(),
}, nil
}
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)
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
if err != nil || !ok {
if errors.IsErrorAlreadyExists(err) {
@ -164,13 +178,13 @@ func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context, algorithm
return err
}
return o.command.GenerateSigningKeyPair(ctx, algorithm)
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), algorithm)
}
func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
return o.eventstore.LatestSequence(ctx,
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxSequence).
ResourceOwner("system"). //TODO: change with multi issuer
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
AddQuery().
AggregateTypes(keypair.AggregateType).
Builder(),
@ -180,3 +194,18 @@ func (o *OPStorage) getMaxKeySequence(ctx context.Context) (uint64, error) {
func selectSigningKey(keys []query.PrivateKey) query.PrivateKey {
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"
"time"
"github.com/caos/oidc/pkg/op"
"github.com/caos/oidc/v2/pkg/op"
"github.com/rakyll/statik/fs"
"golang.org/x/text/language"
@ -17,7 +17,6 @@ import (
"github.com/caos/zitadel/internal/api/ui/login"
"github.com/caos/zitadel/internal/auth/repository"
"github.com/caos/zitadel/internal/command"
"github.com/caos/zitadel/internal/config/systemdefaults"
"github.com/caos/zitadel/internal/crypto"
caos_errs "github.com/caos/zitadel/internal/errors"
"github.com/caos/zitadel/internal/eventstore"
@ -74,26 +73,23 @@ type OPStorage struct {
defaultRefreshTokenIdleExpiration time.Duration
defaultRefreshTokenExpiration time.Duration
encAlg crypto.EncryptionAlgorithm
keyChan <-chan interface{}
currentKey query.PrivateKey
signingKeyRotationCheck time.Duration
signingKeyGracefulPeriod time.Duration
locker crdb.Locker
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) {
opConfig, err := createOPConfig(config, issuer, defaultLogoutRedirectURI, cryptoKey)
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, defaultLogoutRedirectURI, cryptoKey)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
}
storage := newStorage(config, command, query, repo, keyConfig, encryptionAlg, es, projections, keyChan)
options, err := createOptions(config, userAgentCookie, instanceHandler)
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections)
options, err := createOptions(config, externalSecure, userAgentCookie, instanceHandler)
if err != nil {
return nil, caos_errs.ThrowInternal(err, "OIDC-D3gq1", "cannot create options: %w")
}
provider, err := op.NewOpenIDProvider(
provider, err := op.NewDynamicOpenIDProvider(
ctx,
HandlerPrefix,
opConfig,
storage,
options...,
@ -104,17 +100,12 @@ func NewProvider(ctx context.Context, config Config, issuer, defaultLogoutRedire
return provider, nil
}
func Issuer(domain string, port uint16, externalSecure bool) string {
return http_utils.BuildHTTP(domain, port, externalSecure) + HandlerPrefix
}
func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
func createOPConfig(config Config, defaultLogoutRedirectURI string, cryptoKey []byte) (*op.Config, error) {
supportedLanguages, err := getSupportedLanguages()
if err != nil {
return nil, err
}
opConfig := &op.Config{
Issuer: issuer,
DefaultLogoutRedirectURI: defaultLogoutRedirectURI,
CodeMethodS256: config.CodeMethodS256,
AuthMethodPost: config.AuthMethodPost,
@ -130,21 +121,26 @@ func createOPConfig(config Config, issuer, defaultLogoutRedirectURI string, cryp
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}
interceptor := op.WithHttpInterceptors(
middleware.MetricsHandler(metricTypes),
middleware.TelemetryHandler(),
middleware.NoCacheInterceptor,
instanceHandler,
userAgentCookie,
http_utils.CopyHeadersToContext,
)
endpoints := customEndpoints(config.CustomEndpoints)
if len(endpoints) == 0 {
return []op.Option{interceptor}, nil
options := []op.Option{
op.WithHttpInterceptors(
middleware.MetricsHandler(metricTypes),
middleware.TelemetryHandler(),
middleware.NoCacheInterceptor,
instanceHandler,
userAgentCookie,
http_utils.CopyHeadersToContext,
),
}
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 {
@ -176,7 +172,7 @@ func customEndpoints(endpointConfig *EndpointConfig) []op.Option {
return options
}
func newStorage(config Config, command *command.Commands, query *query.Queries, repo repository.Repository, keyConfig systemdefaults.KeyConfig, 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{
repo: repo,
command: command,
@ -189,10 +185,7 @@ func newStorage(config Config, command *command.Commands, query *query.Queries,
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
encAlg: encAlg,
signingKeyGracefulPeriod: keyConfig.SigningKeyGracefulPeriod,
signingKeyRotationCheck: keyConfig.SigningKeyRotationCheck,
locker: crdb.NewLocker(projections, locksTable, signingKey),
keyChan: keyChan,
assetAPIPrefix: assets.HandlerPrefix,
}
}

View File

@ -8,13 +8,13 @@ import (
"net/http"
"os"
"path"
"strings"
"time"
"github.com/caos/logging"
"github.com/caos/oidc/v2/pkg/op"
"github.com/caos/zitadel/internal/api/authz"
http_util "github.com/caos/zitadel/internal/api/http"
"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")
}
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")
if err != nil {
return nil, err
@ -70,7 +70,7 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
config.LongCache.MaxAge,
config.LongCache.SharedMaxAge,
)
security := middleware.SecurityHeaders(csp(domain), nil)
security := middleware.SecurityHeaders(csp(), nil)
handler := &http.ServeMux{}
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)
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 {
http.Error(w, fmt.Sprintf("unable to marshal env for console: %v", err), http.StatusInternalServerError)
return
@ -91,15 +92,12 @@ func Start(config Config, domain, url, issuer string, instanceHandler func(http.
return handler, nil
}
func csp(zitadelDomain string) *middleware.CSP {
if !strings.HasPrefix(zitadelDomain, "*.") {
zitadelDomain = "*." + zitadelDomain
}
func csp() *middleware.CSP {
csp := middleware.DefaultSCP
csp.StyleSrc = csp.StyleSrc.AddInline()
csp.ScriptSrc = csp.ScriptSrc.AddEval()
csp.ConnectSrc = csp.ConnectSrc.AddHost(zitadelDomain)
csp.ImgSrc = csp.ImgSrc.AddHost(zitadelDomain).AddScheme("blob")
csp.ConnectSrc = csp.ConnectSrc.AddOwnHost()
csp.ImgSrc = csp.ImgSrc.AddOwnHost().AddScheme("blob")
return &csp
}

View File

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

View File

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

View File

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

View File

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

View File

@ -25,18 +25,17 @@ import (
)
type Login struct {
endpoint string
router http.Handler
renderer *Renderer
parser *form.Parser
command *command.Commands
query *query.Queries
staticStorage static.Storage
//staticCache cache.Cache //TODO: enable when storage is implemented again
endpoint string
router http.Handler
renderer *Renderer
parser *form.Parser
command *command.Commands
query *query.Queries
staticStorage static.Storage
authRepo auth_repository.Repository
baseURL string
consolePath string
oidcAuthCallbackURL func(string) string
oidcAuthCallbackURL func(context.Context, string) string
idpConfigAlg crypto.EncryptionAlgorithm
userCodeAlg crypto.EncryptionAlgorithm
iamDomain string
@ -46,7 +45,6 @@ type Config struct {
LanguageCookieName string
CSRFCookieName string
Cache middleware.CacheConfig
//StaticCache cache_config.CacheConfig //TODO: enable when storage is implemented again
}
const (
@ -64,9 +62,10 @@ func CreateLogin(config Config,
consolePath,
domain,
baseURL string,
oidcAuthCallbackURL func(string) string,
oidcAuthCallbackURL func(context.Context, string) string,
externalSecure bool,
userAgentCookie,
issuerInterceptor,
instanceHandler mux.MiddlewareFunc,
userCodeAlg crypto.EncryptionAlgorithm,
idpConfigAlg crypto.EncryptionAlgorithm,
@ -85,12 +84,6 @@ func CreateLogin(config Config,
idpConfigAlg: idpConfigAlg,
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")
if err != nil {
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)
}
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.parser = form.NewParser()
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),
}
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)
}
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
multifactors domain.MultifactorConfigs
webauthn *webauthn_helper.WebAuthN
webauthnConfig *webauthn_helper.Config
keySize int
keyAlgorithm crypto.EncryptionAlgorithm
privateKeyLifetime time.Duration
@ -60,7 +60,7 @@ func StartCommands(es *eventstore.Eventstore,
zitadelRoles []authz.RoleMapping,
staticStore static.Storage,
authZRepo authz_repo.Repository,
webAuthN webauthn_helper.Config,
webAuthN *webauthn_helper.Config,
idpConfigEncryption,
otpEncryption,
smtpEncryption,
@ -84,6 +84,7 @@ func StartCommands(es *eventstore.Eventstore,
userEncryption: userEncryption,
domainVerificationAlg: domainVerificationEncryption,
keyAlgorithm: oidcEncryption,
webauthnConfig: webAuthN,
}
instance_repo.RegisterEventMappers(repo.eventstore)
@ -107,11 +108,6 @@ func StartCommands(es *eventstore.Eventstore,
repo.domainVerificationGenerator = crypto.NewEncryptionGenerator(defaults.DomainVerification.VerificationGenerator, repo.domainVerificationAlg)
repo.domainVerificationValidator = http.ValidateDomain
web, err := webauthn_helper.StartServer(webAuthN)
if err != nil {
return nil, err
}
repo.webauthn = web
repo.tokenVerifier = authZRepo
return repo, nil

View File

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

View File

@ -15,7 +15,7 @@ import (
func (c *Commands) AddInstanceDomain(ctx context.Context, instanceDomain string) (*domain.ObjectDetails, error) {
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)
if err != nil {
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) {
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)
if err != nil {
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) {
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)
if err != nil {
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 {
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) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-28nlD", "Errors.Invalid.Argument")
}
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 {
return nil, err
}
@ -88,7 +88,7 @@ func (c *Commands) addInstanceDomain(a *instance.Aggregate, instanceDomain strin
events := []eventstore.Command{
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 {
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) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-9mWjf", "Errors.Invalid.Argument")
}
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 {
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) {
if instanceDomain = strings.TrimSpace(instanceDomain); instanceDomain == "" {
return nil, errors.ThrowInvalidArgument(nil, "INST-39nls", "Errors.Invalid.Argument")
}
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 {
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)
err := c.eventstore.FilterToQueryReducer(ctx, domainWriteModel)
events, err := filter(ctx, domainWriteModel.Query())
if err != nil {
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"
)
const (
oidcUser = "OIDC"
)
func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string) error {
ctx = setOIDCCtx(ctx)
privateCrypto, publicCrypto, err := crypto.GenerateEncryptedKeyPair(c.keySize, c.keyAlgorithm)
if err != nil {
return err
@ -28,8 +23,7 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
privateKeyExp := time.Now().UTC().Add(c.privateKeyLifetime)
publicKeyExp := time.Now().UTC().Add(c.publicKeyLifetime)
//TODO: InstanceID not available here?
keyPairWriteModel := NewKeyPairWriteModel(keyID, "system") //TODO: change with multi issuer
keyPairWriteModel := NewKeyPairWriteModel(keyID, authz.GetInstance(ctx).InstanceID())
keyAgg := KeyPairAggregateFromWriteModel(&keyPairWriteModel.WriteModel)
_, err = c.eventstore.Push(ctx, keypair.NewAddedEvent(
ctx,
@ -40,8 +34,3 @@ func (c *Commands) GenerateSigningKeyPair(ctx context.Context, algorithm string)
privateKeyExp, publicKeyExp))
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 (
"github.com/caos/zitadel/internal/domain"
"github.com/caos/zitadel/internal/eventstore"
keypair "github.com/caos/zitadel/internal/repository/keypair"
"github.com/caos/zitadel/internal/repository/project"
"github.com/caos/zitadel/internal/repository/keypair"
)
type KeyPairWriteModel struct {
@ -52,7 +51,7 @@ func (wm *KeyPairWriteModel) Query() *eventstore.SearchQueryBuilder {
return eventstore.NewSearchQueryBuilder(eventstore.ColumnsEvent).
ResourceOwner(wm.ResourceOwner).
AddQuery().
AggregateTypes(project.AggregateType).
AggregateTypes(keypair.AggregateType).
AggregateIDs(wm.AggregateID).
EventTypes(keypair.AddedEventType).
Builder()

View File

@ -321,3 +321,17 @@ func (c *Commands) getOIDCAppWriteModel(ctx context.Context, projectID, appID, r
}
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"
"time"
"github.com/caos/oidc/pkg/oidc"
"github.com/caos/oidc/v2/pkg/oidc"
"github.com/golang/mock/gomock"
"github.com/stretchr/testify/assert"

View File

@ -157,7 +157,7 @@ func (c *Commands) addHumanWebAuthN(ctx context.Context, userID, resourceowner s
if accountName == "" {
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 {
return nil, nil, nil, err
}
@ -272,7 +272,7 @@ func (c *Commands) verifyHumanWebAuthN(ctx context.Context, userID, resourceowne
return nil, nil, nil, err
}
_, 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 {
return nil, nil, nil, err
}
@ -343,7 +343,7 @@ func (c *Commands) beginWebAuthNLogin(ctx context.Context, userID, resourceOwner
if err != nil {
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 {
return nil, nil, err
}
@ -454,7 +454,7 @@ func (c *Commands) finishWebAuthNLogin(ctx context.Context, userID, resourceOwne
if err != nil {
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 {
return nil, nil, 0, err
}

View File

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

View File

@ -145,8 +145,8 @@ func (t *Translator) Lang(r *http.Request) language.Tag {
return tag
}
func (t *Translator) SetLangCookie(w http.ResponseWriter, lang language.Tag) {
t.cookieHandler.SetCookie(w, t.cookieName, lang.String())
func (t *Translator) SetLangCookie(w http.ResponseWriter, r *http.Request, lang language.Tag) {
t.cookieHandler.SetCookie(w, t.cookieName, r.Host, lang.String())
}
func (t *Translator) localizerFromRequest(r *http.Request) *i18n.Localizer {
@ -177,7 +177,7 @@ func (t *Translator) langsFromCtx(ctx context.Context) []string {
langs := t.preferredLanguages
if ctx != nil {
ctxData := authz.GetCtxData(ctx)
if ctxData.PreferredLanguage != "" {
if ctxData.PreferredLanguage != language.Und.String() {
langs = append(langs, authz.GetCtxData(ctx).PreferredLanguage)
}
langs = append(langs, getAcceptLanguageHeader(ctx))
@ -190,6 +190,10 @@ func (t *Translator) SetPreferredLanguages(langs ...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")
}
@ -199,7 +203,7 @@ func localize(localizer *i18n.Localizer, id string, args map[string]interface{})
TemplateData: args,
})
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 s

View File

@ -4,6 +4,7 @@ import (
"context"
"database/sql"
errs "errors"
"strings"
"time"
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) {
stmt, scan := prepareInstanceQuery(host)
stmt, scan := prepareInstanceDomainQuery(host)
query, args, err := stmt.Where(sq.Eq{
InstanceColumnID.identifier(): "system", //TODO: change column to domain when available
InstanceDomainDomainCol.identifier(): strings.Split(host, ":")[0],
}).ToSql()
if err != nil {
return nil, errors.ThrowInternal(err, "QUERY-SAfg2", "Errors.Query.SQLStatement")
@ -279,3 +280,47 @@ func prepareInstancesQuery() (sq.SelectBuilder, func(*sql.Rows) (*Instances, err
}, 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 {
crdb.StatementHandler
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)
config.ProjectionName = KeyProjectionTable
config.Reducers = p.reducers()
@ -56,7 +55,7 @@ func NewKeyProjection(ctx context.Context, config crdb.StatementHandlerConfig, k
crdb.NewColumn(KeyColumnInstanceID, crdb.ColumnTypeText),
crdb.NewColumn(KeyColumnSequence, crdb.ColumnTypeInt64),
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.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.keyChan = keyChan
p.encryptionAlgorithm = keyEncryptionAlgorithm
return p
@ -130,9 +128,6 @@ func (p *KeyProjection) reduceKeyPairAdded(event eventstore.Event) (*handler.Sta
},
crdb.WithTableSuffix(privateKeyTableSuffix),
))
if p.keyChan != nil {
p.keyChan <- true
}
}
if e.PublicKey.Expiry.After(time.Now()) {
publicKey, err := crypto.Decrypt(e.PublicKey.Key, p.encryptionAlgorithm)

View File

@ -17,7 +17,7 @@ const (
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{
ProjectionHandlerConfig: handler.ProjectionHandlerConfig{
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"]))
NewOIDCSettingsProjection(ctx, applyCustomConfig(projectionConfig, config.Customizations["oidc_settings"]))
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
}

View File

@ -37,7 +37,7 @@ type Queries struct {
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")
if err != nil {
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)
usergrant.RegisterEventMappers(repo.eventstore)
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm, keyChan)
err = projection.Start(ctx, sqlClient, es, projections, keyEncryptionAlgorithm)
if err != nil {
return nil, err
}

View File

@ -2,38 +2,22 @@ package webauthn
import (
"bytes"
"context"
"encoding/json"
"github.com/caos/logging"
"github.com/duo-labs/webauthn/protocol"
"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"
caos_errs "github.com/caos/zitadel/internal/errors"
)
type WebAuthN struct {
webAuthN *webauthn.WebAuthn
}
type Config struct {
ID string
Origin string
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
DisplayName string
ExternalSecure bool
}
type webUser struct {
@ -54,7 +38,10 @@ func (u *webUser) WebAuthnName() string {
}
func (u *webUser) WebAuthnDisplayName() string {
return u.DisplayName
if u.DisplayName != "" {
return u.DisplayName
}
return u.GetUsername()
}
func (u *webUser) WebAuthnIcon() string {
@ -65,7 +52,11 @@ func (u *webUser) WebAuthnCredentials() []webauthn.Credential {
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)
existing := make([]protocol.CredentialDescriptor, len(creds))
for i, cred := range creds {
@ -74,7 +65,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
CredentialID: cred.ID,
}
}
credentialOptions, sessionData, err := w.webAuthN.BeginRegistration(
credentialOptions, sessionData, err := webAuthNServer.BeginRegistration(
&webUser{
Human: user,
accountName: accountName,
@ -102,7 +93,7 @@ func (w *WebAuthN) BeginRegistration(user *domain.Human, accountName string, aut
}, 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 {
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")
}
sessionData := WebAuthNToSessionData(webAuthN)
credential, err := w.webAuthN.CreateCredential(
webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, err
}
credential, err := webAuthNServer.CreateCredential(
&webUser{
Human: user,
},
@ -132,8 +127,12 @@ func (w *WebAuthN) FinishRegistration(user *domain.Human, webAuthN *domain.WebAu
return webAuthN, nil
}
func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
assertion, sessionData, err := w.webAuthN.BeginLogin(&webUser{
func (w *Config) BeginLogin(ctx context.Context, user *domain.Human, userVerification domain.UserVerificationRequirement, isLoginUI bool, webAuthNs ...*domain.WebAuthNToken) (*domain.WebAuthNLogin, error) {
webAuthNServer, err := w.serverFromContext(ctx)
if err != nil {
return nil, err
}
assertion, sessionData, err := webAuthNServer.BeginLogin(&webUser{
Human: user,
credentials: WebAuthNsToCredentials(webAuthNs),
}, webauthn.WithUserVerification(UserVerificationFromDomain(userVerification)))
@ -152,7 +151,7 @@ func (w *WebAuthN) BeginLogin(user *domain.Human, userVerification domain.UserVe
}, 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))
if err != nil {
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,
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 {
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
}
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),
})
}