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
41 changed files with 403 additions and 348 deletions

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