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:
Tim Möhlmann
2025-06-26 19:17:45 +03:00
committed by GitHub
parent 1ebbe275b9
commit 016676e1dc
59 changed files with 203 additions and 1614 deletions

View File

@@ -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),

View File

@@ -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,

View File

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

View File

@@ -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,

View File

@@ -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()

View File

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