feat: enable key rotation (#15066)

This PR contains the remaining logic necessary to hook up key rotation
to the product.
This commit is contained in:
Jon Ayers
2024-10-25 17:14:35 +01:00
committed by GitHub
parent ccfffc6911
commit cd890aa3a0
54 changed files with 1409 additions and 1126 deletions
+46
View File
@@ -228,6 +228,42 @@ var (
Scope: rbac.ScopeAll,
}.WithCachedASTValue()
// See cryptokeys package.
subjectCryptoKeyRotator = rbac.Subject{
FriendlyName: "Crypto Key Rotator",
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Identifier: rbac.RoleIdentifier{Name: "keyrotator"},
DisplayName: "Key Rotator",
Site: rbac.Permissions(map[string][]policy.Action{
rbac.ResourceCryptoKey.Type: {policy.WildcardSymbol},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
},
}),
Scope: rbac.ScopeAll,
}.WithCachedASTValue()
// See cryptokeys package.
subjectCryptoKeyReader = rbac.Subject{
FriendlyName: "Crypto Key Reader",
ID: uuid.Nil.String(),
Roles: rbac.Roles([]rbac.Role{
{
Identifier: rbac.RoleIdentifier{Name: "keyrotator"},
DisplayName: "Key Rotator",
Site: rbac.Permissions(map[string][]policy.Action{
rbac.ResourceCryptoKey.Type: {policy.WildcardSymbol},
}),
Org: map[string][]rbac.Permission{},
User: []rbac.Permission{},
},
}),
Scope: rbac.ScopeAll,
}.WithCachedASTValue()
subjectSystemRestricted = rbac.Subject{
FriendlyName: "System",
ID: uuid.Nil.String(),
@@ -281,6 +317,16 @@ func AsHangDetector(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectHangDetector)
}
// AsKeyRotator returns a context with an actor that has permissions required for rotating crypto keys.
func AsKeyRotator(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectCryptoKeyRotator)
}
// AsKeyReader returns a context with an actor that has permissions required for reading crypto keys.
func AsKeyReader(ctx context.Context) context.Context {
return context.WithValue(ctx, authContextKey{}, subjectCryptoKeyReader)
}
// AsSystemRestricted returns a context with an actor that has permissions
// required for various system operations (login, logout, metrics cache).
func AsSystemRestricted(ctx context.Context) context.Context {
+7 -7
View File
@@ -2243,13 +2243,13 @@ func (s *MethodTestSuite) TestCryptoKeys() {
}))
s.Run("InsertCryptoKey", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.InsertCryptoKeyParams{
Feature: database.CryptoKeyFeatureWorkspaceApps,
Feature: database.CryptoKeyFeatureWorkspaceAppsAPIKey,
}).
Asserts(rbac.ResourceCryptoKey, policy.ActionCreate)
}))
s.Run("DeleteCryptoKey", s.Subtest(func(db database.Store, check *expects) {
key := dbgen.CryptoKey(s.T(), db, database.CryptoKey{
Feature: database.CryptoKeyFeatureWorkspaceApps,
Feature: database.CryptoKeyFeatureWorkspaceAppsAPIKey,
Sequence: 4,
})
check.Args(database.DeleteCryptoKeyParams{
@@ -2259,7 +2259,7 @@ func (s *MethodTestSuite) TestCryptoKeys() {
}))
s.Run("GetCryptoKeyByFeatureAndSequence", s.Subtest(func(db database.Store, check *expects) {
key := dbgen.CryptoKey(s.T(), db, database.CryptoKey{
Feature: database.CryptoKeyFeatureWorkspaceApps,
Feature: database.CryptoKeyFeatureWorkspaceAppsAPIKey,
Sequence: 4,
})
check.Args(database.GetCryptoKeyByFeatureAndSequenceParams{
@@ -2269,14 +2269,14 @@ func (s *MethodTestSuite) TestCryptoKeys() {
}))
s.Run("GetLatestCryptoKeyByFeature", s.Subtest(func(db database.Store, check *expects) {
dbgen.CryptoKey(s.T(), db, database.CryptoKey{
Feature: database.CryptoKeyFeatureWorkspaceApps,
Feature: database.CryptoKeyFeatureWorkspaceAppsAPIKey,
Sequence: 4,
})
check.Args(database.CryptoKeyFeatureWorkspaceApps).Asserts(rbac.ResourceCryptoKey, policy.ActionRead)
check.Args(database.CryptoKeyFeatureWorkspaceAppsAPIKey).Asserts(rbac.ResourceCryptoKey, policy.ActionRead)
}))
s.Run("UpdateCryptoKeyDeletesAt", s.Subtest(func(db database.Store, check *expects) {
key := dbgen.CryptoKey(s.T(), db, database.CryptoKey{
Feature: database.CryptoKeyFeatureWorkspaceApps,
Feature: database.CryptoKeyFeatureWorkspaceAppsAPIKey,
Sequence: 4,
})
check.Args(database.UpdateCryptoKeyDeletesAtParams{
@@ -2286,7 +2286,7 @@ func (s *MethodTestSuite) TestCryptoKeys() {
}).Asserts(rbac.ResourceCryptoKey, policy.ActionUpdate)
}))
s.Run("GetCryptoKeysByFeature", s.Subtest(func(db database.Store, check *expects) {
check.Args(database.CryptoKeyFeatureWorkspaceApps).
check.Args(database.CryptoKeyFeatureWorkspaceAppsAPIKey).
Asserts(rbac.ResourceCryptoKey, policy.ActionRead)
}))
}
+5 -3
View File
@@ -943,7 +943,7 @@ func CustomRole(t testing.TB, db database.Store, seed database.CustomRole) datab
func CryptoKey(t testing.TB, db database.Store, seed database.CryptoKey) database.CryptoKey {
t.Helper()
seed.Feature = takeFirst(seed.Feature, database.CryptoKeyFeatureWorkspaceApps)
seed.Feature = takeFirst(seed.Feature, database.CryptoKeyFeatureWorkspaceAppsAPIKey)
// An empty string for the secret is interpreted as
// a caller wanting a new secret to be generated.
@@ -1048,9 +1048,11 @@ func takeFirst[Value comparable](values ...Value) Value {
func newCryptoKeySecret(feature database.CryptoKeyFeature) (string, error) {
switch feature {
case database.CryptoKeyFeatureWorkspaceApps:
case database.CryptoKeyFeatureWorkspaceAppsAPIKey:
return generateCryptoKey(32)
case database.CryptoKeyFeatureOidcConvert:
case database.CryptoKeyFeatureWorkspaceAppsToken:
return generateCryptoKey(64)
case database.CryptoKeyFeatureOIDCConvert:
return generateCryptoKey(64)
case database.CryptoKeyFeatureTailnetResume:
return generateCryptoKey(64)
+2 -1
View File
@@ -38,7 +38,8 @@ CREATE TYPE build_reason AS ENUM (
);
CREATE TYPE crypto_key_feature AS ENUM (
'workspace_apps',
'workspace_apps_token',
'workspace_apps_api_key',
'oidc_convert',
'tailnet_resume'
);
@@ -0,0 +1,18 @@
-- Step 1: Remove the new entries from crypto_keys table
DELETE FROM crypto_keys
WHERE feature IN ('workspace_apps_token', 'workspace_apps_api_key');
CREATE TYPE old_crypto_key_feature AS ENUM (
'workspace_apps',
'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;
@@ -0,0 +1,18 @@
-- Create a new enum type with the desired values
CREATE TYPE new_crypto_key_feature AS ENUM (
'workspace_apps_token',
'workspace_apps_api_key',
'oidc_convert',
'tailnet_resume'
);
DELETE FROM crypto_keys WHERE feature = 'workspace_apps';
-- Drop the old type and rename the new one
ALTER TABLE crypto_keys
ALTER COLUMN feature TYPE new_crypto_key_feature
USING (feature::text::new_crypto_key_feature);
DROP TYPE crypto_key_feature;
ALTER TYPE new_crypto_key_feature RENAME TO crypto_key_feature;
@@ -0,0 +1,40 @@
INSERT INTO crypto_keys (feature, sequence, secret, secret_key_id, starts_at, deletes_at)
VALUES (
'workspace_apps_token',
1,
'abc',
NULL,
'1970-01-01 00:00:00 UTC'::timestamptz,
'2100-01-01 00:00:00 UTC'::timestamptz
);
INSERT INTO crypto_keys (feature, sequence, secret, secret_key_id, starts_at, deletes_at)
VALUES (
'workspace_apps_api_key',
1,
'def',
NULL,
'1970-01-01 00:00:00 UTC'::timestamptz,
'2100-01-01 00:00:00 UTC'::timestamptz
);
INSERT INTO crypto_keys (feature, sequence, secret, secret_key_id, starts_at, deletes_at)
VALUES (
'oidc_convert',
2,
'ghi',
NULL,
'1970-01-01 00:00:00 UTC'::timestamptz,
'2100-01-01 00:00:00 UTC'::timestamptz
);
INSERT INTO crypto_keys (feature, sequence, secret, secret_key_id, starts_at, deletes_at)
VALUES (
'tailnet_resume',
2,
'jkl',
NULL,
'1970-01-01 00:00:00 UTC'::timestamptz,
'2100-01-01 00:00:00 UTC'::timestamptz
);
+10 -7
View File
@@ -345,9 +345,10 @@ func AllBuildReasonValues() []BuildReason {
type CryptoKeyFeature string
const (
CryptoKeyFeatureWorkspaceApps CryptoKeyFeature = "workspace_apps"
CryptoKeyFeatureOidcConvert CryptoKeyFeature = "oidc_convert"
CryptoKeyFeatureTailnetResume CryptoKeyFeature = "tailnet_resume"
CryptoKeyFeatureWorkspaceAppsToken CryptoKeyFeature = "workspace_apps_token"
CryptoKeyFeatureWorkspaceAppsAPIKey CryptoKeyFeature = "workspace_apps_api_key"
CryptoKeyFeatureOIDCConvert CryptoKeyFeature = "oidc_convert"
CryptoKeyFeatureTailnetResume CryptoKeyFeature = "tailnet_resume"
)
func (e *CryptoKeyFeature) Scan(src interface{}) error {
@@ -387,8 +388,9 @@ func (ns NullCryptoKeyFeature) Value() (driver.Value, error) {
func (e CryptoKeyFeature) Valid() bool {
switch e {
case CryptoKeyFeatureWorkspaceApps,
CryptoKeyFeatureOidcConvert,
case CryptoKeyFeatureWorkspaceAppsToken,
CryptoKeyFeatureWorkspaceAppsAPIKey,
CryptoKeyFeatureOIDCConvert,
CryptoKeyFeatureTailnetResume:
return true
}
@@ -397,8 +399,9 @@ func (e CryptoKeyFeature) Valid() bool {
func AllCryptoKeyFeatureValues() []CryptoKeyFeature {
return []CryptoKeyFeature{
CryptoKeyFeatureWorkspaceApps,
CryptoKeyFeatureOidcConvert,
CryptoKeyFeatureWorkspaceAppsToken,
CryptoKeyFeatureWorkspaceAppsAPIKey,
CryptoKeyFeatureOIDCConvert,
CryptoKeyFeatureTailnetResume,
}
}
+2
View File
@@ -135,6 +135,8 @@ sql:
api_key_id: APIKeyID
callback_url: CallbackURL
login_type_oauth2_provider_app: LoginTypeOAuth2ProviderApp
crypto_key_feature_workspace_apps_api_key: CryptoKeyFeatureWorkspaceAppsAPIKey
crypto_key_feature_oidc_convert: CryptoKeyFeatureOIDCConvert
rules:
- name: do-not-use-public-schema-in-queries
message: "do not use public schema in queries"