mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 21:27:42 +00:00
chore(oidc): graduate webkey to stable (#10122)
# Which Problems Are Solved Stabilize the usage of webkeys. # How the Problems Are Solved - Remove all legacy signing key code from the OIDC API - Remove the webkey feature flag from proto - Remove the webkey feature flag from console - Cleanup documentation # Additional Changes - Resolved some canonical header linter errors in OIDC - Use the constant for `projections.lock` in the saml package. # Additional Context - Closes #10029 - After #10105 - After #10061
This commit is contained in:
@@ -58,7 +58,6 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) (*com
|
||||
UserSchema: req.UserSchema,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
DisableUserTokenEvent: req.DisableUserTokenEvent,
|
||||
@@ -77,7 +76,6 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
UserSchema: featureSourceToFlagPb(&f.UserSchema),
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
DisableUserTokenEvent: featureSourceToFlagPb(&f.DisableUserTokenEvent),
|
||||
|
@@ -153,7 +153,6 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
UserSchema: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOidcParentError: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
@@ -169,7 +168,6 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
UserSchema: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
DebugOIDCParentError: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
EnableBackChannelLogout: gu.Ptr(true),
|
||||
@@ -211,10 +209,6 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
WebKey: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
@@ -265,10 +259,6 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
WebKey: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
DebugOidcParentError: &feature_pb.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
|
@@ -38,7 +38,6 @@ func instanceFeaturesToCommand(req *feature_pb.SetInstanceFeaturesRequest) *comm
|
||||
UserSchema: req.UserSchema,
|
||||
TokenExchange: req.OidcTokenExchange,
|
||||
ImprovedPerformance: improvedPerformanceListToDomain(req.ImprovedPerformance),
|
||||
WebKey: req.WebKey,
|
||||
DebugOIDCParentError: req.DebugOidcParentError,
|
||||
OIDCSingleV1SessionTermination: req.OidcSingleV1SessionTermination,
|
||||
}
|
||||
@@ -52,7 +51,6 @@ func instanceFeaturesToPb(f *query.InstanceFeatures) *feature_pb.GetInstanceFeat
|
||||
UserSchema: featureSourceToFlagPb(&f.UserSchema),
|
||||
OidcTokenExchange: featureSourceToFlagPb(&f.TokenExchange),
|
||||
ImprovedPerformance: featureSourceToImprovedPerformanceFlagPb(&f.ImprovedPerformance),
|
||||
WebKey: featureSourceToFlagPb(&f.WebKey),
|
||||
DebugOidcParentError: featureSourceToFlagPb(&f.DebugOIDCParentError),
|
||||
OidcSingleV1SessionTermination: featureSourceToFlagPb(&f.OIDCSingleV1SessionTermination),
|
||||
}
|
||||
|
@@ -111,7 +111,6 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
UserSchema: gu.Ptr(true),
|
||||
OidcTokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OidcSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
want := &command.InstanceFeatures{
|
||||
@@ -120,7 +119,6 @@ func Test_instanceFeaturesToCommand(t *testing.T) {
|
||||
UserSchema: gu.Ptr(true),
|
||||
TokenExchange: gu.Ptr(true),
|
||||
ImprovedPerformance: nil,
|
||||
WebKey: gu.Ptr(true),
|
||||
OIDCSingleV1SessionTermination: gu.Ptr(true),
|
||||
}
|
||||
got := instanceFeaturesToCommand(arg)
|
||||
@@ -154,10 +152,6 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
Level: feature.LevelSystem,
|
||||
Value: []feature.ImprovedPerformanceType{feature.ImprovedPerformanceTypeOrgByID},
|
||||
},
|
||||
WebKey: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
},
|
||||
OIDCSingleV1SessionTermination: query.FeatureSource[bool]{
|
||||
Level: feature.LevelInstance,
|
||||
Value: true,
|
||||
@@ -189,10 +183,6 @@ func Test_instanceFeaturesToPb(t *testing.T) {
|
||||
ExecutionPaths: []feature_pb.ImprovedPerformance{feature_pb.ImprovedPerformance_IMPROVED_PERFORMANCE_ORG_BY_ID},
|
||||
Source: feature_pb.Source_SOURCE_SYSTEM,
|
||||
},
|
||||
WebKey: &feature_pb.FeatureFlag{
|
||||
Enabled: true,
|
||||
Source: feature_pb.Source_SOURCE_INSTANCE,
|
||||
},
|
||||
DebugOidcParentError: &feature_pb.FeatureFlag{
|
||||
Enabled: false,
|
||||
Source: feature_pb.Source_SOURCE_UNSPECIFIED,
|
||||
|
@@ -12,11 +12,9 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
"google.golang.org/protobuf/proto"
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
@@ -33,34 +31,8 @@ func TestMain(m *testing.M) {
|
||||
}())
|
||||
}
|
||||
|
||||
func TestServer_Feature_Disabled(t *testing.T) {
|
||||
instance, iamCtx, _ := createInstance(t, false)
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
t.Run("CreateWebKey", func(t *testing.T) {
|
||||
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{})
|
||||
assertFeatureDisabledError(t, err)
|
||||
})
|
||||
t.Run("ActivateWebKey", func(t *testing.T) {
|
||||
_, err := client.ActivateWebKey(iamCtx, &webkey.ActivateWebKeyRequest{
|
||||
Id: "1",
|
||||
})
|
||||
assertFeatureDisabledError(t, err)
|
||||
})
|
||||
t.Run("DeleteWebKey", func(t *testing.T) {
|
||||
_, err := client.DeleteWebKey(iamCtx, &webkey.DeleteWebKeyRequest{
|
||||
Id: "1",
|
||||
})
|
||||
assertFeatureDisabledError(t, err)
|
||||
})
|
||||
t.Run("ListWebKeys", func(t *testing.T) {
|
||||
_, err := client.ListWebKeys(iamCtx, &webkey.ListWebKeysRequest{})
|
||||
assertFeatureDisabledError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestServer_ListWebKeys(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
instance, iamCtx, creationDate := createInstance(t)
|
||||
// After the feature is first enabled, we can expect 2 generated keys with the default config.
|
||||
checkWebKeyListState(iamCtx, t, instance, 2, "", &webkey.WebKey_Rsa{
|
||||
Rsa: &webkey.RSA{
|
||||
@@ -71,7 +43,7 @@ func TestServer_ListWebKeys(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_CreateWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
instance, iamCtx, creationDate := createInstance(t)
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||
@@ -93,7 +65,7 @@ func TestServer_CreateWebKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_ActivateWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
instance, iamCtx, creationDate := createInstance(t)
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
resp, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||
@@ -120,7 +92,7 @@ func TestServer_ActivateWebKey(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestServer_DeleteWebKey(t *testing.T) {
|
||||
instance, iamCtx, creationDate := createInstance(t, true)
|
||||
instance, iamCtx, creationDate := createInstance(t)
|
||||
client := instance.Client.WebKeyV2Beta
|
||||
|
||||
keyIDs := make([]string, 2)
|
||||
@@ -197,40 +169,22 @@ func TestServer_DeleteWebKey(t *testing.T) {
|
||||
}, creationDate)
|
||||
}
|
||||
|
||||
func createInstance(t *testing.T, enableFeature bool) (*integration.Instance, context.Context, *timestamppb.Timestamp) {
|
||||
func createInstance(t *testing.T) (*integration.Instance, context.Context, *timestamppb.Timestamp) {
|
||||
instance := integration.NewInstance(CTX)
|
||||
creationDate := timestamppb.Now()
|
||||
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
if enableFeature {
|
||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(iamCTX, &feature.SetInstanceFeaturesRequest{
|
||||
WebKey: proto.Bool(true),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamCTX, time.Minute)
|
||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||
resp, err := instance.Client.WebKeyV2Beta.ListWebKeys(iamCTX, &webkey.ListWebKeysRequest{})
|
||||
if enableFeature {
|
||||
assert.NoError(collect, err)
|
||||
assert.Len(collect, resp.GetWebKeys(), 2)
|
||||
} else {
|
||||
assert.Error(collect, err)
|
||||
}
|
||||
assert.NoError(collect, err)
|
||||
assert.Len(collect, resp.GetWebKeys(), 2)
|
||||
|
||||
}, retryDuration, tick)
|
||||
|
||||
return instance, iamCTX, creationDate
|
||||
}
|
||||
|
||||
func assertFeatureDisabledError(t *testing.T, err error) {
|
||||
t.Helper()
|
||||
require.Error(t, err)
|
||||
s := status.Convert(err)
|
||||
assert.Equal(t, codes.FailedPrecondition, s.Code())
|
||||
assert.Contains(t, s.Message(), "WEBKEY-Ohx6E")
|
||||
}
|
||||
|
||||
func checkWebKeyListState(ctx context.Context, t *testing.T, instance *integration.Instance, nKeys int, expectActiveKeyID string, config any, creationDate *timestamppb.Timestamp) {
|
||||
t.Helper()
|
||||
|
||||
|
@@ -5,9 +5,7 @@ import (
|
||||
|
||||
"google.golang.org/protobuf/types/known/timestamppb"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
webkey "github.com/zitadel/zitadel/pkg/grpc/webkey/v2beta"
|
||||
)
|
||||
|
||||
@@ -15,9 +13,6 @@ func (s *Server) CreateWebKey(ctx context.Context, req *webkey.CreateWebKeyReque
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err = checkWebKeyFeature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
webKey, err := s.command.CreateWebKey(ctx, createWebKeyRequestToConfig(req))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -33,9 +28,6 @@ func (s *Server) ActivateWebKey(ctx context.Context, req *webkey.ActivateWebKeyR
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err = checkWebKeyFeature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
details, err := s.command.ActivateWebKey(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -50,9 +42,6 @@ func (s *Server) DeleteWebKey(ctx context.Context, req *webkey.DeleteWebKeyReque
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err = checkWebKeyFeature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
deletedAt, err := s.command.DeleteWebKey(ctx, req.GetId())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -71,9 +60,6 @@ func (s *Server) ListWebKeys(ctx context.Context, _ *webkey.ListWebKeysRequest)
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if err = checkWebKeyFeature(ctx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list, err := s.query.ListWebKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -83,10 +69,3 @@ func (s *Server) ListWebKeys(ctx context.Context, _ *webkey.ListWebKeysRequest)
|
||||
WebKeys: webKeyDetailsListToPb(list),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func checkWebKeyFeature(ctx context.Context) error {
|
||||
if !authz.GetFeatures(ctx).WebKey {
|
||||
return zerrors.ThrowPreconditionFailed(nil, "WEBKEY-Ohx6E", "Errors.WebKey.FeatureDisabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@@ -53,7 +53,7 @@ func (s *Server) verifyAccessToken(ctx context.Context, tkn string) (_ *accessTo
|
||||
tokenID, subject = split[0], split[1]
|
||||
} else {
|
||||
verifier := op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), s.accessTokenKeySet,
|
||||
op.WithSupportedAccessTokenSigningAlgorithms(supportedSigningAlgs(ctx)...),
|
||||
op.WithSupportedAccessTokenSigningAlgorithms(supportedSigningAlgs()...),
|
||||
)
|
||||
claims, err := op.VerifyAccessToken[*oidc.AccessTokenClaims](ctx, tkn, verifier)
|
||||
if err != nil {
|
||||
|
@@ -140,13 +140,8 @@ func HttpHeadersFromContext(ctx context.Context) (userAgent, acceptLang string)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if agents, ok := ctxHeaders[http_utils.UserAgentHeader]; ok {
|
||||
userAgent = agents[0]
|
||||
}
|
||||
if langs, ok := ctxHeaders[http_utils.AcceptLanguage]; ok {
|
||||
acceptLang = langs[0]
|
||||
}
|
||||
return userAgent, acceptLang
|
||||
return ctxHeaders.Get(http_utils.UserAgentHeader),
|
||||
ctxHeaders.Get(http_utils.AcceptLanguage)
|
||||
}
|
||||
|
||||
func IpFromContext(ctx context.Context) net.IP {
|
||||
|
@@ -14,12 +14,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/zitadel/oidc/v3/pkg/client"
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"google.golang.org/protobuf/proto"
|
||||
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/integration"
|
||||
"github.com/zitadel/zitadel/pkg/grpc/feature/v2"
|
||||
oidc_pb "github.com/zitadel/zitadel/pkg/grpc/oidc/v2"
|
||||
)
|
||||
|
||||
@@ -53,25 +51,16 @@ func TestServer_Keys(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
webKeyFeature bool
|
||||
wantLen int
|
||||
name string
|
||||
wantLen int
|
||||
}{
|
||||
{
|
||||
name: "legacy only",
|
||||
webKeyFeature: false,
|
||||
wantLen: 1,
|
||||
},
|
||||
{
|
||||
name: "webkeys with legacy",
|
||||
webKeyFeature: true,
|
||||
wantLen: 3, // 1 legacy + 2 created by enabling feature flag
|
||||
name: "webkeys",
|
||||
wantLen: 2, // 2 from instance creation.
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ensureWebKeyFeature(t, instance, tt.webKeyFeature)
|
||||
|
||||
assert.EventuallyWithT(t, func(ttt *assert.CollectT) {
|
||||
resp, err := http.Get(discovery.JwksURI)
|
||||
require.NoError(ttt, err)
|
||||
@@ -92,30 +81,10 @@ func TestServer_Keys(t *testing.T) {
|
||||
}
|
||||
|
||||
cacheControl := resp.Header.Get("cache-control")
|
||||
if tt.webKeyFeature {
|
||||
require.Equal(ttt, "max-age=300, must-revalidate", cacheControl)
|
||||
return
|
||||
}
|
||||
require.Equal(ttt, "no-store", cacheControl)
|
||||
require.Equal(ttt, "max-age=300, must-revalidate", cacheControl)
|
||||
|
||||
}, time.Minute, time.Second/10)
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func ensureWebKeyFeature(t *testing.T, instance *integration.Instance, set bool) {
|
||||
ctxIam := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||
|
||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(ctxIam, &feature.SetInstanceFeaturesRequest{
|
||||
WebKey: proto.Bool(set),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Cleanup(func() {
|
||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(ctxIam, &feature.SetInstanceFeaturesRequest{
|
||||
WebKey: proto.Bool(false),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
@@ -35,21 +35,14 @@ func TestServer_UserInfo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
trigger bool
|
||||
webKey bool
|
||||
}{
|
||||
{
|
||||
name: "trigger enabled",
|
||||
trigger: true,
|
||||
},
|
||||
|
||||
// This is the only functional test we need to cover web keys.
|
||||
// - By creating tokens the signer is tested
|
||||
// - When obtaining the tokens, the RP verifies the ID Token using the key set from the jwks endpoint.
|
||||
// - By calling userinfo with the access token as JWT, the Token Verifier with the public key cache is tested.
|
||||
{
|
||||
name: "web keys",
|
||||
name: "trigger disabled",
|
||||
trigger: false,
|
||||
webKey: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -57,7 +50,6 @@ func TestServer_UserInfo(t *testing.T) {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := Instance.Client.FeatureV2.SetInstanceFeatures(iamOwnerCTX, &feature.SetInstanceFeaturesRequest{
|
||||
OidcTriggerIntrospectionProjections: &tt.trigger,
|
||||
WebKey: &tt.webKey,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
testServer_UserInfo(t)
|
||||
|
@@ -10,18 +10,12 @@ import (
|
||||
|
||||
"github.com/go-jose/go-jose/v4"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/muhlemmer/gu"
|
||||
"github.com/shopspring/decimal"
|
||||
"github.com/zitadel/logging"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
http_util "github.com/zitadel/zitadel/internal/api/http"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/keypair"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
@@ -36,11 +30,8 @@ var supportedWebKeyAlgs = []string{
|
||||
string(jose.ES512),
|
||||
}
|
||||
|
||||
func supportedSigningAlgs(ctx context.Context) []string {
|
||||
if authz.GetFeatures(ctx).WebKey {
|
||||
return supportedWebKeyAlgs
|
||||
}
|
||||
return []string{string(jose.RS256)}
|
||||
func supportedSigningAlgs() []string {
|
||||
return supportedWebKeyAlgs
|
||||
}
|
||||
|
||||
type cachedPublicKey struct {
|
||||
@@ -211,15 +202,6 @@ func withKeyExpiryCheck(check bool) keySetOption {
|
||||
}
|
||||
}
|
||||
|
||||
func jsonWebkey(key query.PublicKey) *jose.JSONWebKey {
|
||||
return &jose.JSONWebKey{
|
||||
KeyID: key.ID(),
|
||||
Algorithm: key.Algorithm(),
|
||||
Use: key.Use().String(),
|
||||
Key: key.Key(),
|
||||
}
|
||||
}
|
||||
|
||||
// keySetMap is a mapping of key IDs to public key data.
|
||||
type keySetMap map[string][]byte
|
||||
|
||||
@@ -250,7 +232,6 @@ func (k keySetMap) VerifySignature(ctx context.Context, jws *jose.JSONWebSignatu
|
||||
}
|
||||
|
||||
const (
|
||||
locksTable = "projections.locks"
|
||||
signingKey = "signing_key"
|
||||
oidcUser = "OIDC"
|
||||
|
||||
@@ -279,203 +260,36 @@ 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) }()
|
||||
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
|
||||
panic(o.panicErr("KeySet"))
|
||||
}
|
||||
|
||||
// SignatureAlgorithms implements the op.Storage interface
|
||||
func (o *OPStorage) SignatureAlgorithms(ctx context.Context) ([]jose.SignatureAlgorithm, error) {
|
||||
key, err := o.SigningKey(ctx)
|
||||
if err != nil {
|
||||
logging.WithError(err).Warn("unable to fetch signing key")
|
||||
return nil, err
|
||||
}
|
||||
return []jose.SignatureAlgorithm{key.SignatureAlgorithm()}, nil
|
||||
panic(o.panicErr("SignatureAlgorithms"))
|
||||
}
|
||||
|
||||
// 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 zerrors.ThrowNotFound(nil, "OIDC-ve4Qu", "Errors.Internal")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (o *OPStorage) getSigningKey(ctx context.Context) (op.SigningKey, error) {
|
||||
keys, err := o.query.ActivePrivateSigningKey(ctx, time.Now().Add(gracefulPeriod))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(keys.Keys) > 0 {
|
||||
return PrivateKeyToSigningKey(SelectSigningKey(keys.Keys), o.encAlg)
|
||||
}
|
||||
var position decimal.Decimal
|
||||
if keys.State != nil {
|
||||
position = keys.State.Position
|
||||
}
|
||||
return nil, o.refreshSigningKey(ctx, position)
|
||||
}
|
||||
|
||||
func (o *OPStorage) refreshSigningKey(ctx context.Context, position decimal.Decimal) error {
|
||||
ok, err := o.ensureIsLatestKey(ctx, position)
|
||||
if err != nil || !ok {
|
||||
return zerrors.ThrowInternal(err, "OIDC-ASfh3", "cannot ensure that projection is up to date")
|
||||
}
|
||||
err = o.lockAndGenerateSigningKeyPair(ctx)
|
||||
if err != nil {
|
||||
return zerrors.ThrowInternal(err, "OIDC-ADh31", "could not create signing key")
|
||||
}
|
||||
return zerrors.ThrowInternal(nil, "OIDC-Df1bh", "")
|
||||
}
|
||||
|
||||
func (o *OPStorage) ensureIsLatestKey(ctx context.Context, position decimal.Decimal) (bool, error) {
|
||||
maxSequence, err := o.getMaxKeyPosition(ctx)
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("error retrieving new events: %w", err)
|
||||
}
|
||||
return position.GreaterThanOrEqual(maxSequence), nil
|
||||
}
|
||||
|
||||
func PrivateKeyToSigningKey(key query.PrivateKey, algorithm crypto.EncryptionAlgorithm) (_ op.SigningKey, err error) {
|
||||
keyData, err := crypto.Decrypt(key.Key(), algorithm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
privateKey, err := crypto.BytesToPrivateKey(keyData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &SigningKey{
|
||||
algorithm: jose.SignatureAlgorithm(key.Algorithm()),
|
||||
key: privateKey,
|
||||
id: key.ID(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (o *OPStorage) lockAndGenerateSigningKeyPair(ctx context.Context) error {
|
||||
logging.Info("lock and generate signing key pair")
|
||||
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
|
||||
errs := o.locker.Lock(ctx, lockDuration, authz.GetInstance(ctx).InstanceID())
|
||||
err, ok := <-errs
|
||||
if err != nil || !ok {
|
||||
if zerrors.IsErrorAlreadyExists(err) {
|
||||
return nil
|
||||
}
|
||||
logging.OnError(err).Debug("initial lock failed")
|
||||
return err
|
||||
}
|
||||
|
||||
return o.command.GenerateSigningKeyPair(setOIDCCtx(ctx), "RS256")
|
||||
}
|
||||
|
||||
func (o *OPStorage) getMaxKeyPosition(ctx context.Context) (decimal.Decimal, error) {
|
||||
return o.eventstore.LatestPosition(ctx,
|
||||
eventstore.NewSearchQueryBuilder(eventstore.ColumnsMaxPosition).
|
||||
ResourceOwner(authz.GetInstance(ctx).InstanceID()).
|
||||
AwaitOpenTransactions().
|
||||
AddQuery().
|
||||
AggregateTypes(
|
||||
keypair.AggregateType,
|
||||
instance.AggregateType,
|
||||
).
|
||||
EventTypes(
|
||||
keypair.AddedEventType,
|
||||
instance.InstanceRemovedEventType,
|
||||
).
|
||||
Builder(),
|
||||
)
|
||||
}
|
||||
|
||||
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++ {
|
||||
err = retryable()
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(retryBackoff)
|
||||
}
|
||||
return err
|
||||
panic(o.panicErr("SigningKey"))
|
||||
}
|
||||
|
||||
func (s *Server) Keys(ctx context.Context, r *op.Request[struct{}]) (_ *op.Response, err error) {
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if !authz.GetFeatures(ctx).WebKey {
|
||||
return s.LegacyServer.Keys(ctx, r)
|
||||
}
|
||||
|
||||
keyset, err := s.query.GetWebKeySet(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Return legacy keys, so we do not invalidate all tokens
|
||||
// once the feature flag is enabled.
|
||||
legacyKeys, err := s.query.ActivePublicKeys(ctx, time.Now())
|
||||
logging.OnError(err).Error("oidc server: active public keys (legacy)")
|
||||
appendPublicKeysToWebKeySet(keyset, legacyKeys)
|
||||
|
||||
resp := op.NewResponse(keyset)
|
||||
if s.jwksCacheControlMaxAge != 0 {
|
||||
resp.Header.Set(http_util.CacheControl,
|
||||
fmt.Sprintf("max-age=%d, must-revalidate", int(s.jwksCacheControlMaxAge/time.Second)),
|
||||
)
|
||||
}
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
@@ -497,20 +311,10 @@ func appendPublicKeysToWebKeySet(keyset *jose.JSONWebKeySet, pubkeys *query.Publ
|
||||
|
||||
func queryKeyFunc(q *query.Queries) func(ctx context.Context, keyID string) (*jose.JSONWebKey, *time.Time, error) {
|
||||
return func(ctx context.Context, keyID string) (*jose.JSONWebKey, *time.Time, error) {
|
||||
if authz.GetFeatures(ctx).WebKey {
|
||||
webKey, err := q.GetPublicWebKeyByID(ctx, keyID)
|
||||
if err == nil {
|
||||
return webKey, nil, nil
|
||||
}
|
||||
if !zerrors.IsNotFound(err) {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
pubKey, err := q.GetPublicKeyByID(ctx, keyID)
|
||||
webKey, err := q.GetPublicWebKeyByID(ctx, keyID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return jsonWebkey(pubKey), gu.Ptr(pubKey.Expiry()), nil
|
||||
return webKey, nil, nil
|
||||
}
|
||||
}
|
||||
|
@@ -18,10 +18,8 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/cache"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/database"
|
||||
"github.com/zitadel/zitadel/internal/domain/federatedlogout"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/eventstore/handler/crdb"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/metrics"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
@@ -75,7 +73,6 @@ type OPStorage struct {
|
||||
defaultRefreshTokenIdleExpiration time.Duration
|
||||
defaultRefreshTokenExpiration time.Duration
|
||||
encAlg crypto.EncryptionAlgorithm
|
||||
locker crdb.Locker
|
||||
assetAPIPrefix func(ctx context.Context) string
|
||||
contextToIssuer func(context.Context) string
|
||||
federateLogoutCache cache.Cache[federatedlogout.Index, string, *federatedlogout.FederatedLogout]
|
||||
@@ -91,14 +88,14 @@ type Provider struct {
|
||||
// IDTokenHintVerifier configures a Verifier and supported signing algorithms based on the Web Key feature in the context.
|
||||
func (o *Provider) IDTokenHintVerifier(ctx context.Context) *op.IDTokenHintVerifier {
|
||||
return op.NewIDTokenHintVerifier(op.IssuerFromContext(ctx), o.idTokenHintKeySet, op.WithSupportedIDTokenHintSigningAlgorithms(
|
||||
supportedSigningAlgs(ctx)...,
|
||||
supportedSigningAlgs()...,
|
||||
))
|
||||
}
|
||||
|
||||
// AccessTokenVerifier configures a Verifier and supported signing algorithms based on the Web Key feature in the context.
|
||||
func (o *Provider) AccessTokenVerifier(ctx context.Context) *op.AccessTokenVerifier {
|
||||
return op.NewAccessTokenVerifier(op.IssuerFromContext(ctx), o.accessTokenKeySet, op.WithSupportedAccessTokenSigningAlgorithms(
|
||||
supportedSigningAlgs(ctx)...,
|
||||
supportedSigningAlgs()...,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -113,7 +110,6 @@ func NewServer(
|
||||
encryptionAlg crypto.EncryptionAlgorithm,
|
||||
cryptoKey []byte,
|
||||
es *eventstore.Eventstore,
|
||||
projections *database.DB,
|
||||
userAgentCookie, instanceHandler func(http.Handler) http.Handler,
|
||||
accessHandler *middleware.AccessInterceptor,
|
||||
fallbackLogger *slog.Logger,
|
||||
@@ -124,7 +120,7 @@ func NewServer(
|
||||
if err != nil {
|
||||
return nil, zerrors.ThrowInternal(err, "OIDC-EGrqd", "cannot create op config: %w")
|
||||
}
|
||||
storage := newStorage(config, command, query, repo, encryptionAlg, es, projections, ContextToIssuer, federatedLogoutCache)
|
||||
storage := newStorage(config, command, query, repo, encryptionAlg, es, ContextToIssuer, federatedLogoutCache)
|
||||
keyCache := newPublicKeyCache(ctx, config.PublicKeyCacheMaxAge, queryKeyFunc(query))
|
||||
accessTokenKeySet := newOidcKeySet(keyCache, withKeyExpiryCheck(true))
|
||||
idTokenHintKeySet := newOidcKeySet(keyCache)
|
||||
@@ -236,7 +232,6 @@ func newStorage(
|
||||
repo repository.Repository,
|
||||
encAlg crypto.EncryptionAlgorithm,
|
||||
es *eventstore.Eventstore,
|
||||
db *database.DB,
|
||||
contextToIssuer func(context.Context) string,
|
||||
federateLogoutCache cache.Cache[federatedlogout.Index, string, *federatedlogout.FederatedLogout],
|
||||
) *OPStorage {
|
||||
@@ -253,7 +248,6 @@ func newStorage(
|
||||
defaultRefreshTokenIdleExpiration: config.DefaultRefreshTokenIdleExpiration,
|
||||
defaultRefreshTokenExpiration: config.DefaultRefreshTokenExpiration,
|
||||
encAlg: encAlg,
|
||||
locker: crdb.NewLocker(db.DB, locksTable, signingKey),
|
||||
assetAPIPrefix: assets.AssetAPI(),
|
||||
contextToIssuer: contextToIssuer,
|
||||
federateLogoutCache: federateLogoutCache,
|
||||
|
@@ -188,7 +188,7 @@ func (s *Server) createDiscoveryConfig(ctx context.Context, supportedUILocales o
|
||||
},
|
||||
GrantTypesSupported: op.GrantTypes(s.Provider()),
|
||||
SubjectTypesSupported: op.SubjectTypes(s.Provider()),
|
||||
IDTokenSigningAlgValuesSupported: supportedSigningAlgs(ctx),
|
||||
IDTokenSigningAlgValuesSupported: supportedSigningAlgs(),
|
||||
RequestObjectSigningAlgValuesSupported: op.RequestObjectSigAlgorithms(s.Provider()),
|
||||
TokenEndpointAuthMethodsSupported: op.AuthMethodsTokenEndpoint(s.Provider()),
|
||||
TokenEndpointAuthSigningAlgValuesSupported: op.TokenSigAlgorithms(s.Provider()),
|
||||
|
@@ -8,9 +8,6 @@ import (
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
"golang.org/x/text/language"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/feature"
|
||||
)
|
||||
|
||||
func TestServer_createDiscoveryConfig(t *testing.T) {
|
||||
@@ -63,92 +60,6 @@ func TestServer_createDiscoveryConfig(t *testing.T) {
|
||||
ctx: op.ContextWithIssuer(context.Background(), "https://issuer.com"),
|
||||
supportedUILocales: []language.Tag{language.English, language.German},
|
||||
},
|
||||
&oidc.DiscoveryConfiguration{
|
||||
Issuer: "https://issuer.com",
|
||||
AuthorizationEndpoint: "https://issuer.com/auth",
|
||||
TokenEndpoint: "https://issuer.com/token",
|
||||
IntrospectionEndpoint: "https://issuer.com/introspect",
|
||||
UserinfoEndpoint: "https://issuer.com/userinfo",
|
||||
RevocationEndpoint: "https://issuer.com/revoke",
|
||||
EndSessionEndpoint: "https://issuer.com/logout",
|
||||
DeviceAuthorizationEndpoint: "https://issuer.com/device",
|
||||
CheckSessionIframe: "",
|
||||
JwksURI: "https://issuer.com/keys",
|
||||
RegistrationEndpoint: "",
|
||||
ScopesSupported: []string{oidc.ScopeOpenID, oidc.ScopeProfile, oidc.ScopeEmail, oidc.ScopePhone, oidc.ScopeAddress, oidc.ScopeOfflineAccess},
|
||||
ResponseTypesSupported: []string{string(oidc.ResponseTypeCode), string(oidc.ResponseTypeIDTokenOnly), string(oidc.ResponseTypeIDToken)},
|
||||
ResponseModesSupported: []string{string(oidc.ResponseModeQuery), string(oidc.ResponseModeFragment), string(oidc.ResponseModeFormPost)},
|
||||
GrantTypesSupported: []oidc.GrantType{oidc.GrantTypeCode, oidc.GrantTypeImplicit, oidc.GrantTypeRefreshToken, oidc.GrantTypeBearer},
|
||||
ACRValuesSupported: nil,
|
||||
SubjectTypesSupported: []string{"public"},
|
||||
IDTokenSigningAlgValuesSupported: []string{"RS256"},
|
||||
IDTokenEncryptionAlgValuesSupported: nil,
|
||||
IDTokenEncryptionEncValuesSupported: nil,
|
||||
UserinfoSigningAlgValuesSupported: nil,
|
||||
UserinfoEncryptionAlgValuesSupported: nil,
|
||||
UserinfoEncryptionEncValuesSupported: nil,
|
||||
RequestObjectSigningAlgValuesSupported: []string{"RS256"},
|
||||
RequestObjectEncryptionAlgValuesSupported: nil,
|
||||
RequestObjectEncryptionEncValuesSupported: nil,
|
||||
TokenEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
|
||||
TokenEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
|
||||
RevocationEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodNone, oidc.AuthMethodBasic, oidc.AuthMethodPost, oidc.AuthMethodPrivateKeyJWT},
|
||||
RevocationEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
|
||||
IntrospectionEndpointAuthMethodsSupported: []oidc.AuthMethod{oidc.AuthMethodBasic, oidc.AuthMethodPrivateKeyJWT},
|
||||
IntrospectionEndpointAuthSigningAlgValuesSupported: []string{"RS256"},
|
||||
DisplayValuesSupported: nil,
|
||||
ClaimTypesSupported: nil,
|
||||
ClaimsSupported: []string{"sub", "aud", "exp", "iat", "iss", "auth_time", "nonce", "acr", "amr", "c_hash", "at_hash", "act", "scopes", "client_id", "azp", "preferred_username", "name", "family_name", "given_name", "locale", "email", "email_verified", "phone_number", "phone_number_verified"},
|
||||
ClaimsParameterSupported: false,
|
||||
CodeChallengeMethodsSupported: []oidc.CodeChallengeMethod{"S256"},
|
||||
ServiceDocumentation: "",
|
||||
ClaimsLocalesSupported: nil,
|
||||
UILocalesSupported: []language.Tag{language.English, language.German},
|
||||
RequestParameterSupported: true,
|
||||
RequestURIParameterSupported: false,
|
||||
RequireRequestURIRegistration: false,
|
||||
OPPolicyURI: "",
|
||||
OPTermsOfServiceURI: "",
|
||||
},
|
||||
},
|
||||
{
|
||||
"web keys feature enabled",
|
||||
fields{
|
||||
LegacyServer: op.NewLegacyServer(
|
||||
func() *op.Provider {
|
||||
//nolint:staticcheck
|
||||
provider, _ := op.NewForwardedOpenIDProvider("path",
|
||||
&op.Config{
|
||||
CodeMethodS256: true,
|
||||
AuthMethodPost: true,
|
||||
AuthMethodPrivateKeyJWT: true,
|
||||
GrantTypeRefreshToken: true,
|
||||
RequestObjectSupported: true,
|
||||
},
|
||||
nil,
|
||||
)
|
||||
return provider
|
||||
}(),
|
||||
op.Endpoints{
|
||||
Authorization: op.NewEndpoint("auth"),
|
||||
Token: op.NewEndpoint("token"),
|
||||
Introspection: op.NewEndpoint("introspect"),
|
||||
Userinfo: op.NewEndpoint("userinfo"),
|
||||
Revocation: op.NewEndpoint("revoke"),
|
||||
EndSession: op.NewEndpoint("logout"),
|
||||
JwksURI: op.NewEndpoint("keys"),
|
||||
DeviceAuthorization: op.NewEndpoint("device"),
|
||||
},
|
||||
),
|
||||
signingKeyAlgorithm: "RS256",
|
||||
},
|
||||
args{
|
||||
ctx: authz.WithFeatures(
|
||||
op.ContextWithIssuer(context.Background(), "https://issuer.com"),
|
||||
feature.Features{WebKey: true},
|
||||
),
|
||||
supportedUILocales: []language.Tag{language.English, language.German},
|
||||
},
|
||||
&oidc.DiscoveryConfiguration{
|
||||
Issuer: "https://issuer.com",
|
||||
AuthorizationEndpoint: "https://issuer.com/auth",
|
||||
|
@@ -12,7 +12,6 @@ import (
|
||||
"github.com/zitadel/oidc/v3/pkg/oidc"
|
||||
"github.com/zitadel/oidc/v3/pkg/op"
|
||||
|
||||
"github.com/zitadel/zitadel/internal/api/authz"
|
||||
"github.com/zitadel/zitadel/internal/command"
|
||||
"github.com/zitadel/zitadel/internal/domain"
|
||||
"github.com/zitadel/zitadel/internal/telemetry/tracing"
|
||||
@@ -64,14 +63,13 @@ func (s *Server) accessTokenResponseFromSession(ctx context.Context, client op.C
|
||||
type SignerFunc func(ctx context.Context) (jose.Signer, jose.SignatureAlgorithm, error)
|
||||
|
||||
func (s *Server) getSignerOnce() SignerFunc {
|
||||
return GetSignerOnce(s.query.GetActiveSigningWebKey, s.Provider().Storage().SigningKey)
|
||||
return GetSignerOnce(s.query.GetActiveSigningWebKey)
|
||||
}
|
||||
|
||||
// GetSignerOnce returns a function which retrieves the instance's signer from the database once.
|
||||
// Repeated calls of the returned function return the same results.
|
||||
func GetSignerOnce(
|
||||
getActiveSigningWebKey func(ctx context.Context) (*jose.JSONWebKey, error),
|
||||
getSigningKey func(ctx context.Context) (op.SigningKey, error),
|
||||
) SignerFunc {
|
||||
var (
|
||||
once sync.Once
|
||||
@@ -84,23 +82,12 @@ func GetSignerOnce(
|
||||
ctx, span := tracing.NewSpan(ctx)
|
||||
defer func() { span.EndWithError(err) }()
|
||||
|
||||
if authz.GetFeatures(ctx).WebKey {
|
||||
var webKey *jose.JSONWebKey
|
||||
webKey, err = getActiveSigningWebKey(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signer, signAlg, err = signerFromWebKey(webKey)
|
||||
return
|
||||
}
|
||||
|
||||
var signingKey op.SigningKey
|
||||
signingKey, err = getSigningKey(ctx)
|
||||
var webKey *jose.JSONWebKey
|
||||
webKey, err = getActiveSigningWebKey(ctx)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
signAlg = signingKey.SignatureAlgorithm()
|
||||
signer, err = op.SignerFromKey(signingKey)
|
||||
signer, signAlg, err = signerFromWebKey(webKey)
|
||||
})
|
||||
return signer, signAlg, err
|
||||
}
|
||||
|
@@ -14,13 +14,14 @@ import (
|
||||
"github.com/zitadel/zitadel/internal/crypto"
|
||||
"github.com/zitadel/zitadel/internal/eventstore"
|
||||
"github.com/zitadel/zitadel/internal/query"
|
||||
"github.com/zitadel/zitadel/internal/query/projection"
|
||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||
"github.com/zitadel/zitadel/internal/repository/keypair"
|
||||
"github.com/zitadel/zitadel/internal/zerrors"
|
||||
)
|
||||
|
||||
const (
|
||||
locksTable = "projections.locks"
|
||||
locksTable = projection.LocksTable
|
||||
signingKey = "signing_key"
|
||||
samlUser = "SAML"
|
||||
|
||||
|
Reference in New Issue
Block a user