mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-23 05:56:53 +00:00
fix(oidc): enable webkey feature by default (#10683)
# Which Problems Are Solved When the webkey feature flag was not enabled before an upgrade to v4, all JWT tokens became invalid. This created a couple of issues: - All users with JWT access tokens are logged-out - Clients that are unable to refresh keys based on key ID break - id_token_hint could no longer be validated. # How the Problems Are Solved Force-enable the webkey feature on the v3 version, so that the upgrade path is cleaner. Sessions now have time to role-over to the new keys before initiating the upgrade to v4. # Additional Changes - none # Additional Context - Related https://github.com/zitadel/zitadel/issues/10673 --------- Co-authored-by: Livio Spring <livio.a@gmail.com>
This commit is contained in:
@@ -1116,7 +1116,7 @@ DefaultInstance:
|
|||||||
# - 3 # Project
|
# - 3 # Project
|
||||||
# - 4 # UserGrant
|
# - 4 # UserGrant
|
||||||
# - 5 # OrgDomainVerified
|
# - 5 # OrgDomainVerified
|
||||||
# WebKey: false # ZITADEL_DEFAULTINSTANCE_FEATURES_WEBKEY
|
WebKey: true # ZITADEL_DEFAULTINSTANCE_FEATURES_WEBKEY
|
||||||
# DebugOIDCParentError: false # ZITADEL_DEFAULTINSTANCE_FEATURES_DEBUGOIDCPARENTERROR
|
# DebugOIDCParentError: false # ZITADEL_DEFAULTINSTANCE_FEATURES_DEBUGOIDCPARENTERROR
|
||||||
# OIDCSingleV1SessionTermination: false # ZITADEL_DEFAULTINSTANCE_FEATURES_OIDCSINGLEV1SESSIONTERMINATION
|
# OIDCSingleV1SessionTermination: false # ZITADEL_DEFAULTINSTANCE_FEATURES_OIDCSINGLEV1SESSIONTERMINATION
|
||||||
# DisableUserTokenEvent: false # ZITADEL_DEFAULTINSTANCE_FEATURES_DISABLEUSERTOKENEVENT
|
# DisableUserTokenEvent: false # ZITADEL_DEFAULTINSTANCE_FEATURES_DISABLEUSERTOKENEVENT
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/muhlemmer/gu"
|
||||||
"github.com/zitadel/logging"
|
"github.com/zitadel/logging"
|
||||||
|
|
||||||
"github.com/zitadel/zitadel/internal/api/authz"
|
"github.com/zitadel/zitadel/internal/api/authz"
|
||||||
"github.com/zitadel/zitadel/internal/command"
|
"github.com/zitadel/zitadel/internal/command"
|
||||||
"github.com/zitadel/zitadel/internal/crypto"
|
|
||||||
"github.com/zitadel/zitadel/internal/eventstore"
|
"github.com/zitadel/zitadel/internal/eventstore"
|
||||||
"github.com/zitadel/zitadel/internal/repository/instance"
|
"github.com/zitadel/zitadel/internal/repository/instance"
|
||||||
)
|
)
|
||||||
@@ -34,21 +34,20 @@ func (mig *SetupWebkeys) Execute(ctx context.Context, _ eventstore.Event) error
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("%s get instance IDs: %w", mig, err)
|
return fmt.Errorf("%s get instance IDs: %w", mig, err)
|
||||||
}
|
}
|
||||||
conf := &crypto.WebKeyRSAConfig{
|
|
||||||
Bits: crypto.RSABits2048,
|
|
||||||
Hasher: crypto.RSAHasherSHA256,
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, instance := range instances {
|
for _, instance := range instances {
|
||||||
ctx := authz.WithInstanceID(ctx, instance)
|
ctx := authz.WithInstanceID(ctx, instance)
|
||||||
logging.Info("prepare initial webkeys for instance", "instance_id", instance, "migration", mig)
|
logging.Info("prepare initial webkeys for instance", "instance_id", instance, "migration", mig)
|
||||||
if err := mig.commands.GenerateInitialWebKeys(ctx, conf); err != nil {
|
_, err := mig.commands.SetInstanceFeatures(ctx, &command.InstanceFeatures{
|
||||||
return fmt.Errorf("%s generate initial webkeys: %w", mig, err)
|
WebKey: gu.Ptr(true),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("%s set webkey instance feature: %w", mig, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mig *SetupWebkeys) String() string {
|
func (mig *SetupWebkeys) String() string {
|
||||||
return "59_setup_webkeys"
|
return "59_setup_webkeys_2"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ func TestMain(m *testing.M) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_Feature_Disabled(t *testing.T) {
|
func TestServer_Feature_Disabled(t *testing.T) {
|
||||||
instance, iamCtx, _ := createInstance(t, false)
|
instance, iamCtx, _ := createInstance(t, true)
|
||||||
client := instance.Client.WebKeyV2Beta
|
client := instance.Client.WebKeyV2Beta
|
||||||
|
|
||||||
t.Run("CreateWebKey", func(t *testing.T) {
|
t.Run("CreateWebKey", func(t *testing.T) {
|
||||||
@@ -60,7 +60,7 @@ func TestServer_Feature_Disabled(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ListWebKeys(t *testing.T) {
|
func TestServer_ListWebKeys(t *testing.T) {
|
||||||
instance, iamCtx, creationDate := createInstance(t, true)
|
instance, iamCtx, creationDate := createInstance(t, false)
|
||||||
// After the feature is first enabled, we can expect 2 generated keys with the default config.
|
// After the feature is first enabled, we can expect 2 generated keys with the default config.
|
||||||
checkWebKeyListState(iamCtx, t, instance, 2, "", &webkey.WebKey_Rsa{
|
checkWebKeyListState(iamCtx, t, instance, 2, "", &webkey.WebKey_Rsa{
|
||||||
Rsa: &webkey.RSA{
|
Rsa: &webkey.RSA{
|
||||||
@@ -71,7 +71,7 @@ func TestServer_ListWebKeys(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_CreateWebKey(t *testing.T) {
|
func TestServer_CreateWebKey(t *testing.T) {
|
||||||
instance, iamCtx, creationDate := createInstance(t, true)
|
instance, iamCtx, creationDate := createInstance(t, false)
|
||||||
client := instance.Client.WebKeyV2Beta
|
client := instance.Client.WebKeyV2Beta
|
||||||
|
|
||||||
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
_, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||||
@@ -93,7 +93,7 @@ func TestServer_CreateWebKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_ActivateWebKey(t *testing.T) {
|
func TestServer_ActivateWebKey(t *testing.T) {
|
||||||
instance, iamCtx, creationDate := createInstance(t, true)
|
instance, iamCtx, creationDate := createInstance(t, false)
|
||||||
client := instance.Client.WebKeyV2Beta
|
client := instance.Client.WebKeyV2Beta
|
||||||
|
|
||||||
resp, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
resp, err := client.CreateWebKey(iamCtx, &webkey.CreateWebKeyRequest{
|
||||||
@@ -120,7 +120,7 @@ func TestServer_ActivateWebKey(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestServer_DeleteWebKey(t *testing.T) {
|
func TestServer_DeleteWebKey(t *testing.T) {
|
||||||
instance, iamCtx, creationDate := createInstance(t, true)
|
instance, iamCtx, creationDate := createInstance(t, false)
|
||||||
client := instance.Client.WebKeyV2Beta
|
client := instance.Client.WebKeyV2Beta
|
||||||
|
|
||||||
keyIDs := make([]string, 2)
|
keyIDs := make([]string, 2)
|
||||||
@@ -197,14 +197,14 @@ func TestServer_DeleteWebKey(t *testing.T) {
|
|||||||
}, creationDate)
|
}, creationDate)
|
||||||
}
|
}
|
||||||
|
|
||||||
func createInstance(t *testing.T, enableFeature bool) (*integration.Instance, context.Context, *timestamppb.Timestamp) {
|
func createInstance(t *testing.T, disableFeature bool) (*integration.Instance, context.Context, *timestamppb.Timestamp) {
|
||||||
instance := integration.NewInstance(CTX)
|
instance := integration.NewInstance(CTX)
|
||||||
creationDate := timestamppb.Now()
|
creationDate := instance.Instance.GetDetails().GetCreationDate()
|
||||||
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
iamCTX := instance.WithAuthorization(CTX, integration.UserTypeIAMOwner)
|
||||||
|
|
||||||
if enableFeature {
|
if disableFeature {
|
||||||
_, err := instance.Client.FeatureV2.SetInstanceFeatures(iamCTX, &feature.SetInstanceFeaturesRequest{
|
_, err := instance.Client.FeatureV2.SetInstanceFeatures(iamCTX, &feature.SetInstanceFeaturesRequest{
|
||||||
WebKey: proto.Bool(true),
|
WebKey: proto.Bool(false),
|
||||||
})
|
})
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
@@ -212,11 +212,11 @@ func createInstance(t *testing.T, enableFeature bool) (*integration.Instance, co
|
|||||||
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamCTX, time.Minute)
|
retryDuration, tick := integration.WaitForAndTickWithMaxDuration(iamCTX, time.Minute)
|
||||||
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
assert.EventuallyWithT(t, func(collect *assert.CollectT) {
|
||||||
resp, err := instance.Client.WebKeyV2Beta.ListWebKeys(iamCTX, &webkey.ListWebKeysRequest{})
|
resp, err := instance.Client.WebKeyV2Beta.ListWebKeys(iamCTX, &webkey.ListWebKeysRequest{})
|
||||||
if enableFeature {
|
if disableFeature {
|
||||||
|
assert.Error(collect, err)
|
||||||
|
} else {
|
||||||
assert.NoError(collect, err)
|
assert.NoError(collect, err)
|
||||||
assert.Len(collect, resp.GetWebKeys(), 2)
|
assert.Len(collect, resp.GetWebKeys(), 2)
|
||||||
} else {
|
|
||||||
assert.Error(collect, err)
|
|
||||||
}
|
}
|
||||||
}, retryDuration, tick)
|
}, retryDuration, tick)
|
||||||
|
|
||||||
@@ -244,8 +244,8 @@ func checkWebKeyListState(ctx context.Context, t *testing.T, instance *integrati
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
var gotActiveKeyID string
|
var gotActiveKeyID string
|
||||||
for _, key := range list {
|
for _, key := range list {
|
||||||
assert.WithinRange(collect, key.GetCreationDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute))
|
assert.WithinRange(collect, key.GetCreationDate().AsTime(), creationDate.AsTime(), now.Add(time.Minute))
|
||||||
assert.WithinRange(collect, key.GetChangeDate().AsTime(), now.Add(-time.Minute), now.Add(time.Minute))
|
assert.WithinRange(collect, key.GetChangeDate().AsTime(), creationDate.AsTime(), now.Add(time.Minute))
|
||||||
assert.NotEqual(collect, webkey.State_STATE_UNSPECIFIED, key.GetState())
|
assert.NotEqual(collect, webkey.State_STATE_UNSPECIFIED, key.GetState())
|
||||||
assert.NotEqual(collect, webkey.State_STATE_REMOVED, key.GetState())
|
assert.NotEqual(collect, webkey.State_STATE_REMOVED, key.GetState())
|
||||||
assert.Equal(collect, config, key.GetKey())
|
assert.Equal(collect, config, key.GetKey())
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ import (
|
|||||||
|
|
||||||
func TestServer_Keys(t *testing.T) {
|
func TestServer_Keys(t *testing.T) {
|
||||||
instance := integration.NewInstance(CTX)
|
instance := integration.NewInstance(CTX)
|
||||||
|
// As we want to test the legacy keys as well, we need to ensure the webkey feature is off
|
||||||
|
// at the beginning since the instance creation enables it by default.
|
||||||
|
ensureWebKeyFeature(t, instance, false)
|
||||||
|
|
||||||
ctxLogin := instance.WithAuthorization(CTX, integration.UserTypeLogin)
|
ctxLogin := instance.WithAuthorization(CTX, integration.UserTypeLogin)
|
||||||
|
|
||||||
clientID, _ := createClient(t, instance)
|
clientID, _ := createClient(t, instance)
|
||||||
|
|||||||
Reference in New Issue
Block a user