From 8ff2109298694c93b7935c1a38eed8195cc9ba63 Mon Sep 17 00:00:00 2001 From: Callum Styan Date: Fri, 26 Jun 2026 11:59:00 -0700 Subject: [PATCH] feat: add nats_ca crypto_key_feature enum value (#26761) --- coderd/cryptokeys/rotate.go | 21 ++++++++++++++++++- coderd/cryptokeys/rotate_internal_test.go | 4 ++-- coderd/cryptokeys/rotate_test.go | 6 +++--- coderd/database/dump.sql | 3 ++- ...000533_nats_ca_crypto_key_feature.down.sql | 16 ++++++++++++++ .../000533_nats_ca_crypto_key_feature.up.sql | 1 + coderd/database/models.go | 5 ++++- coderd/database/sqlc.yaml | 1 + 8 files changed, 49 insertions(+), 8 deletions(-) create mode 100644 coderd/database/migrations/000533_nats_ca_crypto_key_feature.down.sql create mode 100644 coderd/database/migrations/000533_nats_ca_crypto_key_feature.up.sql diff --git a/coderd/cryptokeys/rotate.go b/coderd/cryptokeys/rotate.go index e768d53273..4f30f84259 100644 --- a/coderd/cryptokeys/rotate.go +++ b/coderd/cryptokeys/rotate.go @@ -5,6 +5,7 @@ import ( "crypto/rand" "database/sql" "encoding/hex" + "slices" "time" "golang.org/x/xerrors" @@ -27,6 +28,24 @@ const ( DefaultKeyDuration = time.Hour * 24 * 30 ) +// defaultRotatedFeatures are the crypto key features the rotator manages. It +// intentionally excludes features that are gated behind an experiment or +// deployment flag so that a dormant feature's enum value does not cause the +// rotator to mint keys it has no generator for. Gated features are opted in by +// the caller that owns their generator. +var defaultRotatedFeatures = []database.CryptoKeyFeature{ + database.CryptoKeyFeatureWorkspaceAppsToken, + database.CryptoKeyFeatureWorkspaceAppsAPIKey, + database.CryptoKeyFeatureOIDCConvert, + database.CryptoKeyFeatureTailnetResume, +} + +// DefaultRotatedFeatures returns the crypto key features the rotator manages by +// default. It excludes experiment-gated features such as the NATS CA. +func DefaultRotatedFeatures() []database.CryptoKeyFeature { + return slices.Clone(defaultRotatedFeatures) +} + // rotator is responsible for rotating keys in the database. type rotator struct { db database.Store @@ -62,7 +81,7 @@ func StartRotator(ctx context.Context, logger slog.Logger, db database.Store, op logger: logger.Named("keyrotator"), clock: quartz.NewReal(), keyDuration: DefaultKeyDuration, - features: database.AllCryptoKeyFeatureValues(), + features: defaultRotatedFeatures, } for _, opt := range opts { diff --git a/coderd/cryptokeys/rotate_internal_test.go b/coderd/cryptokeys/rotate_internal_test.go index a8202320ae..4d43f24e18 100644 --- a/coderd/cryptokeys/rotate_internal_test.go +++ b/coderd/cryptokeys/rotate_internal_test.go @@ -358,7 +358,7 @@ func Test_rotateKeys(t *testing.T) { keyDuration: keyDuration, clock: clock, logger: logger, - features: database.AllCryptoKeyFeatureValues(), + features: defaultRotatedFeatures, } now := dbnow(clock) @@ -409,7 +409,7 @@ func Test_rotateKeys(t *testing.T) { require.NoError(t, err) require.Len(t, keys, 5) - kbf, err := keysByFeature(keys, database.AllCryptoKeyFeatureValues()) + kbf, err := keysByFeature(keys, defaultRotatedFeatures) require.NoError(t, err) // No actions on OIDC convert. diff --git a/coderd/cryptokeys/rotate_test.go b/coderd/cryptokeys/rotate_test.go index 4a5c458772..df5db4413e 100644 --- a/coderd/cryptokeys/rotate_test.go +++ b/coderd/cryptokeys/rotate_test.go @@ -37,7 +37,7 @@ func TestRotator(t *testing.T) { // are as expected. dbkeys, err = db.GetCryptoKeys(ctx) require.NoError(t, err) - require.Len(t, dbkeys, len(database.AllCryptoKeyFeatureValues())) + require.Len(t, dbkeys, len(cryptokeys.DefaultRotatedFeatures())) requireContainsAllFeatures(t, dbkeys) }) @@ -64,7 +64,7 @@ func TestRotator(t *testing.T) { cryptokeys.StartRotator(ctx, logger, db, cryptokeys.WithClock(clock)) - initialKeyLen := len(database.AllCryptoKeyFeatureValues()) + initialKeyLen := len(cryptokeys.DefaultRotatedFeatures()) // Fetch the keys from the database and ensure they // are as expected. dbkeys, err := db.GetCryptoKeys(ctx) @@ -113,7 +113,7 @@ func requireContainsAllFeatures(t *testing.T, keys []database.CryptoKey) { for _, key := range keys { features[key.Feature] = true } - for _, feature := range database.AllCryptoKeyFeatureValues() { + for _, feature := range cryptokeys.DefaultRotatedFeatures() { require.True(t, features[feature]) } } diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index c9a8eb03b6..a984421d82 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -374,7 +374,8 @@ CREATE TYPE crypto_key_feature AS ENUM ( 'workspace_apps_token', 'workspace_apps_api_key', 'oidc_convert', - 'tailnet_resume' + 'tailnet_resume', + 'nats_ca' ); CREATE TYPE display_app AS ENUM ( diff --git a/coderd/database/migrations/000533_nats_ca_crypto_key_feature.down.sql b/coderd/database/migrations/000533_nats_ca_crypto_key_feature.down.sql new file mode 100644 index 0000000000..ec36128a51 --- /dev/null +++ b/coderd/database/migrations/000533_nats_ca_crypto_key_feature.down.sql @@ -0,0 +1,16 @@ +DELETE FROM crypto_keys WHERE feature = 'nats_ca'; + +CREATE TYPE old_crypto_key_feature AS ENUM ( + 'workspace_apps_token', + 'workspace_apps_api_key', + 'oidc_convert', + 'tailnet_resume' +); + +ALTER TABLE crypto_keys + ALTER COLUMN feature TYPE old_crypto_key_feature + USING (feature::text::old_crypto_key_feature); + +DROP TYPE crypto_key_feature; + +ALTER TYPE old_crypto_key_feature RENAME TO crypto_key_feature; diff --git a/coderd/database/migrations/000533_nats_ca_crypto_key_feature.up.sql b/coderd/database/migrations/000533_nats_ca_crypto_key_feature.up.sql new file mode 100644 index 0000000000..c37227451d --- /dev/null +++ b/coderd/database/migrations/000533_nats_ca_crypto_key_feature.up.sql @@ -0,0 +1 @@ +ALTER TYPE crypto_key_feature ADD VALUE IF NOT EXISTS 'nats_ca'; diff --git a/coderd/database/models.go b/coderd/database/models.go index 0d08e74ede..93ae445581 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -1887,6 +1887,7 @@ const ( CryptoKeyFeatureWorkspaceAppsAPIKey CryptoKeyFeature = "workspace_apps_api_key" CryptoKeyFeatureOIDCConvert CryptoKeyFeature = "oidc_convert" CryptoKeyFeatureTailnetResume CryptoKeyFeature = "tailnet_resume" + CryptoKeyFeatureNATSCA CryptoKeyFeature = "nats_ca" ) func (e *CryptoKeyFeature) Scan(src interface{}) error { @@ -1929,7 +1930,8 @@ func (e CryptoKeyFeature) Valid() bool { case CryptoKeyFeatureWorkspaceAppsToken, CryptoKeyFeatureWorkspaceAppsAPIKey, CryptoKeyFeatureOIDCConvert, - CryptoKeyFeatureTailnetResume: + CryptoKeyFeatureTailnetResume, + CryptoKeyFeatureNATSCA: return true } return false @@ -1941,6 +1943,7 @@ func AllCryptoKeyFeatureValues() []CryptoKeyFeature { CryptoKeyFeatureWorkspaceAppsAPIKey, CryptoKeyFeatureOIDCConvert, CryptoKeyFeatureTailnetResume, + CryptoKeyFeatureNATSCA, } } diff --git a/coderd/database/sqlc.yaml b/coderd/database/sqlc.yaml index abc19ee1ae..42ba273df3 100644 --- a/coderd/database/sqlc.yaml +++ b/coderd/database/sqlc.yaml @@ -254,6 +254,7 @@ sql: login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp crypto_key_feature_workspace_apps_api_key: CryptoKeyFeatureWorkspaceAppsAPIKey crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert + crypto_key_feature_nats_ca: CryptoKeyFeatureNATSCA stale_interval_ms: StaleIntervalMS has_ai_task: HasAITask ai_task_sidebar_app_id: AITaskSidebarAppID