mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add schema for key rotation (#14662)
This commit is contained in:
Generated
+2
@@ -12027,6 +12027,7 @@ const docTemplate = `{
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
"deployment_config",
|
||||
"deployment_stats",
|
||||
@@ -12059,6 +12060,7 @@ const docTemplate = `{
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
"ResourceDeploymentConfig",
|
||||
"ResourceDeploymentStats",
|
||||
|
||||
Generated
+2
@@ -10859,6 +10859,7 @@
|
||||
"assign_org_role",
|
||||
"assign_role",
|
||||
"audit_log",
|
||||
"crypto_key",
|
||||
"debug_info",
|
||||
"deployment_config",
|
||||
"deployment_stats",
|
||||
@@ -10891,6 +10892,7 @@
|
||||
"ResourceAssignOrgRole",
|
||||
"ResourceAssignRole",
|
||||
"ResourceAuditLog",
|
||||
"ResourceCryptoKey",
|
||||
"ResourceDebugInfo",
|
||||
"ResourceDeploymentConfig",
|
||||
"ResourceDeploymentStats",
|
||||
|
||||
@@ -253,6 +253,7 @@ var (
|
||||
rbac.ResourceDeploymentConfig.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationPreference.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceNotificationTemplate.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
rbac.ResourceCryptoKey.Type: {policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete},
|
||||
}),
|
||||
Org: map[string][]rbac.Permission{},
|
||||
User: []rbac.Permission{},
|
||||
@@ -1041,6 +1042,13 @@ func (q *querier) DeleteCoordinator(ctx context.Context, id uuid.UUID) error {
|
||||
return q.db.DeleteCoordinator(ctx, id)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return q.db.DeleteCryptoKey(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) DeleteCustomRole(ctx context.Context, arg database.DeleteCustomRoleParams) error {
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionDelete, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
@@ -1383,6 +1391,20 @@ func (q *querier) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (stri
|
||||
return q.db.GetCoordinatorResumeTokenSigningKey(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return q.db.GetCryptoKeyByFeatureAndSequence(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) GetCryptoKeys(ctx context.Context) ([]database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceCryptoKey); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.db.GetCryptoKeys(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetDBCryptKeys(ctx context.Context) ([]database.DBCryptKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceSystem); err != nil {
|
||||
return nil, err
|
||||
@@ -1549,6 +1571,13 @@ func (q *querier) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
||||
return q.db.GetLastUpdateCheck(ctx)
|
||||
}
|
||||
|
||||
func (q *querier) GetLatestCryptoKeyByFeature(ctx context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionRead, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return q.db.GetLatestCryptoKeyByFeature(ctx, feature)
|
||||
}
|
||||
|
||||
func (q *querier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
if _, err := q.GetWorkspaceByID(ctx, workspaceID); err != nil {
|
||||
return database.WorkspaceBuild{}, err
|
||||
@@ -2662,6 +2691,13 @@ func (q *querier) InsertAuditLog(ctx context.Context, arg database.InsertAuditLo
|
||||
return insert(q.log, q.auth, rbac.ResourceAuditLog, q.db.InsertAuditLog)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionCreate, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return q.db.InsertCryptoKey(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) InsertCustomRole(ctx context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
// Org and site role upsert share the same query. So switch the assertion based on the org uuid.
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
@@ -3169,6 +3205,13 @@ func (q *querier) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateAPIKe
|
||||
return update(q.log, q.auth, fetch, q.db.UpdateAPIKeyByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceCryptoKey); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return q.db.UpdateCryptoKeyDeletesAt(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateCustomRole(ctx context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
if arg.OrganizationID.UUID != uuid.Nil {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceAssignOrgRole.InOrg(arg.OrganizationID.UUID)); err != nil {
|
||||
|
||||
@@ -2253,6 +2253,57 @@ func (s *MethodTestSuite) TestDBCrypt() {
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestCryptoKeys() {
|
||||
s.Run("GetCryptoKeys", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args().
|
||||
Asserts(rbac.ResourceCryptoKey, policy.ActionRead)
|
||||
}))
|
||||
s.Run("InsertCryptoKey", s.Subtest(func(db database.Store, check *expects) {
|
||||
check.Args(database.InsertCryptoKeyParams{
|
||||
Feature: database.CryptoKeyFeatureWorkspaceApps,
|
||||
}).
|
||||
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,
|
||||
Sequence: 4,
|
||||
})
|
||||
check.Args(database.DeleteCryptoKeyParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
}).Asserts(rbac.ResourceCryptoKey, policy.ActionDelete)
|
||||
}))
|
||||
s.Run("GetCryptoKeyByFeatureAndSequence", s.Subtest(func(db database.Store, check *expects) {
|
||||
key := dbgen.CryptoKey(s.T(), db, database.CryptoKey{
|
||||
Feature: database.CryptoKeyFeatureWorkspaceApps,
|
||||
Sequence: 4,
|
||||
})
|
||||
check.Args(database.GetCryptoKeyByFeatureAndSequenceParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
}).Asserts(rbac.ResourceCryptoKey, policy.ActionRead).Returns(key)
|
||||
}))
|
||||
s.Run("GetLatestCryptoKeyByFeature", s.Subtest(func(db database.Store, check *expects) {
|
||||
dbgen.CryptoKey(s.T(), db, database.CryptoKey{
|
||||
Feature: database.CryptoKeyFeatureWorkspaceApps,
|
||||
Sequence: 4,
|
||||
})
|
||||
check.Args(database.CryptoKeyFeatureWorkspaceApps).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,
|
||||
Sequence: 4,
|
||||
})
|
||||
check.Args(database.UpdateCryptoKeyDeletesAtParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
DeletesAt: sql.NullTime{Time: time.Now(), Valid: true},
|
||||
}).Asserts(rbac.ResourceCryptoKey, policy.ActionUpdate)
|
||||
}))
|
||||
}
|
||||
|
||||
func (s *MethodTestSuite) TestSystemFunctions() {
|
||||
s.Run("UpdateUserLinkedID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
|
||||
@@ -2,6 +2,7 @@ package dbgen
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
@@ -16,6 +17,7 @@ import (
|
||||
"github.com/google/uuid"
|
||||
"github.com/sqlc-dev/pqtype"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/database"
|
||||
"github.com/coder/coder/v2/coderd/database/dbauthz"
|
||||
@@ -893,6 +895,40 @@ func CustomRole(t testing.TB, db database.Store, seed database.CustomRole) datab
|
||||
return role
|
||||
}
|
||||
|
||||
func CryptoKey(t testing.TB, db database.Store, seed database.CryptoKey) database.CryptoKey {
|
||||
t.Helper()
|
||||
|
||||
seed.Feature = takeFirst(seed.Feature, database.CryptoKeyFeatureWorkspaceApps)
|
||||
|
||||
if !seed.Secret.Valid {
|
||||
secret, err := newCryptoKeySecret(seed.Feature)
|
||||
require.NoError(t, err, "generate secret")
|
||||
seed.Secret = sql.NullString{
|
||||
String: secret,
|
||||
Valid: true,
|
||||
}
|
||||
}
|
||||
|
||||
key, err := db.InsertCryptoKey(genCtx, database.InsertCryptoKeyParams{
|
||||
Sequence: takeFirst(seed.Sequence, 123),
|
||||
Secret: seed.Secret,
|
||||
SecretKeyID: takeFirst(seed.SecretKeyID, sql.NullString{}),
|
||||
Feature: seed.Feature,
|
||||
StartsAt: takeFirst(seed.StartsAt, time.Now()),
|
||||
})
|
||||
require.NoError(t, err, "insert crypto key")
|
||||
|
||||
if seed.DeletesAt.Valid {
|
||||
key, err = db.UpdateCryptoKeyDeletesAt(genCtx, database.UpdateCryptoKeyDeletesAtParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
DeletesAt: sql.NullTime{Time: seed.DeletesAt.Time, Valid: true},
|
||||
})
|
||||
require.NoError(t, err, "update crypto key deletes_at")
|
||||
}
|
||||
return key
|
||||
}
|
||||
|
||||
func ProvisionerJobTimings(t testing.TB, db database.Store, seed database.InsertProvisionerJobTimingsParams) []database.ProvisionerJobTiming {
|
||||
timings, err := db.InsertProvisionerJobTimings(genCtx, seed)
|
||||
require.NoError(t, err, "insert provisioner job timings")
|
||||
@@ -942,3 +978,24 @@ func takeFirst[Value comparable](values ...Value) Value {
|
||||
return v != empty
|
||||
})
|
||||
}
|
||||
|
||||
func newCryptoKeySecret(feature database.CryptoKeyFeature) (string, error) {
|
||||
switch feature {
|
||||
case database.CryptoKeyFeatureWorkspaceApps:
|
||||
return generateCryptoKey(96)
|
||||
case database.CryptoKeyFeatureOidcConvert:
|
||||
return generateCryptoKey(32)
|
||||
case database.CryptoKeyFeatureTailnetResume:
|
||||
return generateCryptoKey(64)
|
||||
}
|
||||
return "", xerrors.Errorf("unknown feature: %s", feature)
|
||||
}
|
||||
|
||||
func generateCryptoKey(length int) (string, error) {
|
||||
b := make([]byte, length)
|
||||
_, err := rand.Read(b)
|
||||
if err != nil {
|
||||
return "", xerrors.Errorf("rand read: %w", err)
|
||||
}
|
||||
return hex.EncodeToString(b), nil
|
||||
}
|
||||
|
||||
@@ -189,6 +189,7 @@ type data struct {
|
||||
// New tables
|
||||
workspaceAgentStats []database.WorkspaceAgentStat
|
||||
auditLogs []database.AuditLog
|
||||
cryptoKeys []database.CryptoKey
|
||||
dbcryptKeys []database.DBCryptKey
|
||||
files []database.File
|
||||
externalAuthLinks []database.ExternalAuthLink
|
||||
@@ -1471,6 +1472,27 @@ func (*FakeQuerier) DeleteCoordinator(context.Context, uuid.UUID) error {
|
||||
return ErrUnimplemented
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteCryptoKey(_ context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, key := range q.cryptoKeys {
|
||||
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
|
||||
q.cryptoKeys[i].Secret.String = ""
|
||||
q.cryptoKeys[i].Secret.Valid = false
|
||||
q.cryptoKeys[i].SecretKeyID.String = ""
|
||||
q.cryptoKeys[i].SecretKeyID.Valid = false
|
||||
return q.cryptoKeys[i], nil
|
||||
}
|
||||
}
|
||||
return database.CryptoKey{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) DeleteCustomRole(_ context.Context, arg database.DeleteCustomRoleParams) error {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@@ -2346,6 +2368,41 @@ func (q *FakeQuerier) GetCoordinatorResumeTokenSigningKey(_ context.Context) (st
|
||||
return q.coordinatorResumeTokenSigningKey, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetCryptoKeyByFeatureAndSequence(_ context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
for _, key := range q.cryptoKeys {
|
||||
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
|
||||
// Keys with NULL secrets are considered deleted.
|
||||
if key.Secret.Valid {
|
||||
return key, nil
|
||||
}
|
||||
return database.CryptoKey{}, sql.ErrNoRows
|
||||
}
|
||||
}
|
||||
|
||||
return database.CryptoKey{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetCryptoKeys(_ context.Context) ([]database.CryptoKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
keys := make([]database.CryptoKey, 0)
|
||||
for _, key := range q.cryptoKeys {
|
||||
if key.Secret.Valid {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetDBCryptKeys(_ context.Context) ([]database.DBCryptKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -2849,6 +2906,22 @@ func (q *FakeQuerier) GetLastUpdateCheck(_ context.Context) (string, error) {
|
||||
return string(q.lastUpdateCheck), nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetLatestCryptoKeyByFeature(_ context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
|
||||
var latestKey database.CryptoKey
|
||||
for _, key := range q.cryptoKeys {
|
||||
if key.Feature == feature && latestKey.Sequence < key.Sequence {
|
||||
latestKey = key
|
||||
}
|
||||
}
|
||||
if latestKey.StartsAt.IsZero() {
|
||||
return database.CryptoKey{}, sql.ErrNoRows
|
||||
}
|
||||
return latestKey, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
q.mutex.RLock()
|
||||
defer q.mutex.RUnlock()
|
||||
@@ -6368,6 +6441,28 @@ func (q *FakeQuerier) InsertAuditLog(_ context.Context, arg database.InsertAudit
|
||||
return alog, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertCryptoKey(_ context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
key := database.CryptoKey{
|
||||
Feature: arg.Feature,
|
||||
Sequence: arg.Sequence,
|
||||
Secret: arg.Secret,
|
||||
SecretKeyID: arg.SecretKeyID,
|
||||
StartsAt: arg.StartsAt,
|
||||
}
|
||||
|
||||
q.cryptoKeys = append(q.cryptoKeys, key)
|
||||
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) InsertCustomRole(_ context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
@@ -7874,6 +7969,25 @@ func (q *FakeQuerier) UpdateAPIKeyByID(_ context.Context, arg database.UpdateAPI
|
||||
return sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateCryptoKeyDeletesAt(_ context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
q.mutex.Lock()
|
||||
defer q.mutex.Unlock()
|
||||
|
||||
for i, key := range q.cryptoKeys {
|
||||
if key.Feature == arg.Feature && key.Sequence == arg.Sequence {
|
||||
key.DeletesAt = arg.DeletesAt
|
||||
q.cryptoKeys[i] = key
|
||||
return key, nil
|
||||
}
|
||||
}
|
||||
|
||||
return database.CryptoKey{}, sql.ErrNoRows
|
||||
}
|
||||
|
||||
func (q *FakeQuerier) UpdateCustomRole(_ context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
err := validateDatabaseType(arg)
|
||||
if err != nil {
|
||||
|
||||
@@ -214,6 +214,13 @@ func (m metricsStore) DeleteCoordinator(ctx context.Context, id uuid.UUID) error
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteCryptoKey(ctx context.Context, arg database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.DeleteCryptoKey(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("DeleteCryptoKey").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) DeleteCustomRole(ctx context.Context, arg database.DeleteCustomRoleParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.DeleteCustomRole(ctx, arg)
|
||||
@@ -543,6 +550,20 @@ func (m metricsStore) GetCoordinatorResumeTokenSigningKey(ctx context.Context) (
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetCryptoKeyByFeatureAndSequence(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("GetCryptoKeyByFeatureAndSequence").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetCryptoKeys(ctx context.Context) ([]database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetCryptoKeys(ctx)
|
||||
m.queryLatencies.WithLabelValues("GetCryptoKeys").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetDBCryptKeys(ctx context.Context) ([]database.DBCryptKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetDBCryptKeys(ctx)
|
||||
@@ -711,6 +732,13 @@ func (m metricsStore) GetLastUpdateCheck(ctx context.Context) (string, error) {
|
||||
return version, err
|
||||
}
|
||||
|
||||
func (m metricsStore) GetLatestCryptoKeyByFeature(ctx context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.GetLatestCryptoKeyByFeature(ctx, feature)
|
||||
m.queryLatencies.WithLabelValues("GetLatestCryptoKeyByFeature").Observe(time.Since(start).Seconds())
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
func (m metricsStore) GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
start := time.Now()
|
||||
build, err := m.s.GetLatestWorkspaceBuildByWorkspaceID(ctx, workspaceID)
|
||||
@@ -1600,6 +1628,13 @@ func (m metricsStore) InsertAuditLog(ctx context.Context, arg database.InsertAud
|
||||
return log, err
|
||||
}
|
||||
|
||||
func (m metricsStore) InsertCryptoKey(ctx context.Context, arg database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
key, err := m.s.InsertCryptoKey(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("InsertCryptoKey").Observe(time.Since(start).Seconds())
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (m metricsStore) InsertCustomRole(ctx context.Context, arg database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.InsertCustomRole(ctx, arg)
|
||||
@@ -2006,6 +2041,13 @@ func (m metricsStore) UpdateAPIKeyByID(ctx context.Context, arg database.UpdateA
|
||||
return err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
start := time.Now()
|
||||
key, err := m.s.UpdateCryptoKeyDeletesAt(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateCryptoKeyDeletesAt").Observe(time.Since(start).Seconds())
|
||||
return key, err
|
||||
}
|
||||
|
||||
func (m metricsStore) UpdateCustomRole(ctx context.Context, arg database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
start := time.Now()
|
||||
r0, r1 := m.s.UpdateCustomRole(ctx, arg)
|
||||
|
||||
@@ -317,6 +317,21 @@ func (mr *MockStoreMockRecorder) DeleteCoordinator(arg0, arg1 any) *gomock.Call
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCoordinator", reflect.TypeOf((*MockStore)(nil).DeleteCoordinator), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteCryptoKey mocks base method.
|
||||
func (m *MockStore) DeleteCryptoKey(arg0 context.Context, arg1 database.DeleteCryptoKeyParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "DeleteCryptoKey", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// DeleteCryptoKey indicates an expected call of DeleteCryptoKey.
|
||||
func (mr *MockStoreMockRecorder) DeleteCryptoKey(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteCryptoKey", reflect.TypeOf((*MockStore)(nil).DeleteCryptoKey), arg0, arg1)
|
||||
}
|
||||
|
||||
// DeleteCustomRole mocks base method.
|
||||
func (m *MockStore) DeleteCustomRole(arg0 context.Context, arg1 database.DeleteCustomRoleParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1058,6 +1073,36 @@ func (mr *MockStoreMockRecorder) GetCoordinatorResumeTokenSigningKey(arg0 any) *
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCoordinatorResumeTokenSigningKey", reflect.TypeOf((*MockStore)(nil).GetCoordinatorResumeTokenSigningKey), arg0)
|
||||
}
|
||||
|
||||
// GetCryptoKeyByFeatureAndSequence mocks base method.
|
||||
func (m *MockStore) GetCryptoKeyByFeatureAndSequence(arg0 context.Context, arg1 database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCryptoKeyByFeatureAndSequence", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCryptoKeyByFeatureAndSequence indicates an expected call of GetCryptoKeyByFeatureAndSequence.
|
||||
func (mr *MockStoreMockRecorder) GetCryptoKeyByFeatureAndSequence(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCryptoKeyByFeatureAndSequence", reflect.TypeOf((*MockStore)(nil).GetCryptoKeyByFeatureAndSequence), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetCryptoKeys mocks base method.
|
||||
func (m *MockStore) GetCryptoKeys(arg0 context.Context) ([]database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetCryptoKeys", arg0)
|
||||
ret0, _ := ret[0].([]database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetCryptoKeys indicates an expected call of GetCryptoKeys.
|
||||
func (mr *MockStoreMockRecorder) GetCryptoKeys(arg0 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCryptoKeys", reflect.TypeOf((*MockStore)(nil).GetCryptoKeys), arg0)
|
||||
}
|
||||
|
||||
// GetDBCryptKeys mocks base method.
|
||||
func (m *MockStore) GetDBCryptKeys(arg0 context.Context) ([]database.DBCryptKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -1418,6 +1463,21 @@ func (mr *MockStoreMockRecorder) GetLastUpdateCheck(arg0 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLastUpdateCheck", reflect.TypeOf((*MockStore)(nil).GetLastUpdateCheck), arg0)
|
||||
}
|
||||
|
||||
// GetLatestCryptoKeyByFeature mocks base method.
|
||||
func (m *MockStore) GetLatestCryptoKeyByFeature(arg0 context.Context, arg1 database.CryptoKeyFeature) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "GetLatestCryptoKeyByFeature", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// GetLatestCryptoKeyByFeature indicates an expected call of GetLatestCryptoKeyByFeature.
|
||||
func (mr *MockStoreMockRecorder) GetLatestCryptoKeyByFeature(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLatestCryptoKeyByFeature", reflect.TypeOf((*MockStore)(nil).GetLatestCryptoKeyByFeature), arg0, arg1)
|
||||
}
|
||||
|
||||
// GetLatestWorkspaceBuildByWorkspaceID mocks base method.
|
||||
func (m *MockStore) GetLatestWorkspaceBuildByWorkspaceID(arg0 context.Context, arg1 uuid.UUID) (database.WorkspaceBuild, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -3367,6 +3427,21 @@ func (mr *MockStoreMockRecorder) InsertAuditLog(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertAuditLog", reflect.TypeOf((*MockStore)(nil).InsertAuditLog), arg0, arg1)
|
||||
}
|
||||
|
||||
// InsertCryptoKey mocks base method.
|
||||
func (m *MockStore) InsertCryptoKey(arg0 context.Context, arg1 database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "InsertCryptoKey", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// InsertCryptoKey indicates an expected call of InsertCryptoKey.
|
||||
func (mr *MockStoreMockRecorder) InsertCryptoKey(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertCryptoKey", reflect.TypeOf((*MockStore)(nil).InsertCryptoKey), arg0, arg1)
|
||||
}
|
||||
|
||||
// InsertCustomRole mocks base method.
|
||||
func (m *MockStore) InsertCustomRole(arg0 context.Context, arg1 database.InsertCustomRoleParams) (database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
@@ -4234,6 +4309,21 @@ func (mr *MockStoreMockRecorder) UpdateAPIKeyByID(arg0, arg1 any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateAPIKeyByID", reflect.TypeOf((*MockStore)(nil).UpdateAPIKeyByID), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateCryptoKeyDeletesAt mocks base method.
|
||||
func (m *MockStore) UpdateCryptoKeyDeletesAt(arg0 context.Context, arg1 database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateCryptoKeyDeletesAt", arg0, arg1)
|
||||
ret0, _ := ret[0].(database.CryptoKey)
|
||||
ret1, _ := ret[1].(error)
|
||||
return ret0, ret1
|
||||
}
|
||||
|
||||
// UpdateCryptoKeyDeletesAt indicates an expected call of UpdateCryptoKeyDeletesAt.
|
||||
func (mr *MockStoreMockRecorder) UpdateCryptoKeyDeletesAt(arg0, arg1 any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateCryptoKeyDeletesAt", reflect.TypeOf((*MockStore)(nil).UpdateCryptoKeyDeletesAt), arg0, arg1)
|
||||
}
|
||||
|
||||
// UpdateCustomRole mocks base method.
|
||||
func (m *MockStore) UpdateCustomRole(arg0 context.Context, arg1 database.UpdateCustomRoleParams) (database.CustomRole, error) {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
Generated
+21
@@ -36,6 +36,12 @@ CREATE TYPE build_reason AS ENUM (
|
||||
'autodelete'
|
||||
);
|
||||
|
||||
CREATE TYPE crypto_key_feature AS ENUM (
|
||||
'workspace_apps',
|
||||
'oidc_convert',
|
||||
'tailnet_resume'
|
||||
);
|
||||
|
||||
CREATE TYPE display_app AS ENUM (
|
||||
'vscode',
|
||||
'vscode_insiders',
|
||||
@@ -494,6 +500,15 @@ CREATE TABLE audit_logs (
|
||||
resource_icon text NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE crypto_keys (
|
||||
feature crypto_key_feature NOT NULL,
|
||||
sequence integer NOT NULL,
|
||||
secret text,
|
||||
secret_key_id text,
|
||||
starts_at timestamp with time zone NOT NULL,
|
||||
deletes_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE custom_roles (
|
||||
name text NOT NULL,
|
||||
display_name text NOT NULL,
|
||||
@@ -1641,6 +1656,9 @@ ALTER TABLE ONLY api_keys
|
||||
ALTER TABLE ONLY audit_logs
|
||||
ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
|
||||
ALTER TABLE ONLY crypto_keys
|
||||
ADD CONSTRAINT crypto_keys_pkey PRIMARY KEY (feature, sequence);
|
||||
|
||||
ALTER TABLE ONLY custom_roles
|
||||
ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
||||
|
||||
@@ -2036,6 +2054,9 @@ CREATE TRIGGER update_notification_message_dedupe_hash BEFORE INSERT OR UPDATE O
|
||||
ALTER TABLE ONLY api_keys
|
||||
ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
|
||||
ALTER TABLE ONLY crypto_keys
|
||||
ADD CONSTRAINT crypto_keys_secret_key_id_fkey FOREIGN KEY (secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
ALTER TABLE ONLY external_auth_links
|
||||
ADD CONSTRAINT git_auth_links_oauth_access_token_key_id_fkey FOREIGN KEY (oauth_access_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ type ForeignKeyConstraint string
|
||||
// ForeignKeyConstraint enums.
|
||||
const (
|
||||
ForeignKeyAPIKeysUserIDUUID ForeignKeyConstraint = "api_keys_user_id_uuid_fkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_user_id_uuid_fkey FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
ForeignKeyCryptoKeysSecretKeyID ForeignKeyConstraint = "crypto_keys_secret_key_id_fkey" // ALTER TABLE ONLY crypto_keys ADD CONSTRAINT crypto_keys_secret_key_id_fkey FOREIGN KEY (secret_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyGitAuthLinksOauthAccessTokenKeyID ForeignKeyConstraint = "git_auth_links_oauth_access_token_key_id_fkey" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_oauth_access_token_key_id_fkey FOREIGN KEY (oauth_access_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyGitAuthLinksOauthRefreshTokenKeyID ForeignKeyConstraint = "git_auth_links_oauth_refresh_token_key_id_fkey" // ALTER TABLE ONLY external_auth_links ADD CONSTRAINT git_auth_links_oauth_refresh_token_key_id_fkey FOREIGN KEY (oauth_refresh_token_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
ForeignKeyGitSSHKeysUserID ForeignKeyConstraint = "gitsshkeys_user_id_fkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
DROP TABLE "crypto_keys";
|
||||
DROP TYPE "crypto_key_feature";
|
||||
@@ -0,0 +1,16 @@
|
||||
CREATE TYPE crypto_key_feature AS ENUM (
|
||||
'workspace_apps',
|
||||
'oidc_convert',
|
||||
'tailnet_resume'
|
||||
);
|
||||
|
||||
CREATE TABLE crypto_keys (
|
||||
feature crypto_key_feature NOT NULL,
|
||||
sequence integer NOT NULL,
|
||||
secret text NULL,
|
||||
secret_key_id text NULL REFERENCES dbcrypt_keys(active_key_digest),
|
||||
starts_at timestamptz NOT NULL,
|
||||
deletes_at timestamptz NULL,
|
||||
PRIMARY KEY (feature, sequence)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
INSERT INTO crypto_keys (feature, sequence, starts_at, secret) VALUES
|
||||
('workspace_apps', 1, now(), 'abc'),
|
||||
('oidc_convert', 1, now(), 'def'),
|
||||
('tailnet_resume', 1, now(), 'ghi');
|
||||
@@ -447,3 +447,7 @@ func (r GetAuthorizationUserRolesRow) RoleNames() ([]rbac.RoleIdentifier, error)
|
||||
}
|
||||
return names, nil
|
||||
}
|
||||
|
||||
func (k CryptoKey) ExpiresAt(keyDuration time.Duration) time.Time {
|
||||
return k.StartsAt.Add(keyDuration).UTC()
|
||||
}
|
||||
|
||||
@@ -339,6 +339,67 @@ func AllBuildReasonValues() []BuildReason {
|
||||
}
|
||||
}
|
||||
|
||||
type CryptoKeyFeature string
|
||||
|
||||
const (
|
||||
CryptoKeyFeatureWorkspaceApps CryptoKeyFeature = "workspace_apps"
|
||||
CryptoKeyFeatureOidcConvert CryptoKeyFeature = "oidc_convert"
|
||||
CryptoKeyFeatureTailnetResume CryptoKeyFeature = "tailnet_resume"
|
||||
)
|
||||
|
||||
func (e *CryptoKeyFeature) Scan(src interface{}) error {
|
||||
switch s := src.(type) {
|
||||
case []byte:
|
||||
*e = CryptoKeyFeature(s)
|
||||
case string:
|
||||
*e = CryptoKeyFeature(s)
|
||||
default:
|
||||
return fmt.Errorf("unsupported scan type for CryptoKeyFeature: %T", src)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type NullCryptoKeyFeature struct {
|
||||
CryptoKeyFeature CryptoKeyFeature `json:"crypto_key_feature"`
|
||||
Valid bool `json:"valid"` // Valid is true if CryptoKeyFeature is not NULL
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface.
|
||||
func (ns *NullCryptoKeyFeature) Scan(value interface{}) error {
|
||||
if value == nil {
|
||||
ns.CryptoKeyFeature, ns.Valid = "", false
|
||||
return nil
|
||||
}
|
||||
ns.Valid = true
|
||||
return ns.CryptoKeyFeature.Scan(value)
|
||||
}
|
||||
|
||||
// Value implements the driver Valuer interface.
|
||||
func (ns NullCryptoKeyFeature) Value() (driver.Value, error) {
|
||||
if !ns.Valid {
|
||||
return nil, nil
|
||||
}
|
||||
return string(ns.CryptoKeyFeature), nil
|
||||
}
|
||||
|
||||
func (e CryptoKeyFeature) Valid() bool {
|
||||
switch e {
|
||||
case CryptoKeyFeatureWorkspaceApps,
|
||||
CryptoKeyFeatureOidcConvert,
|
||||
CryptoKeyFeatureTailnetResume:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func AllCryptoKeyFeatureValues() []CryptoKeyFeature {
|
||||
return []CryptoKeyFeature{
|
||||
CryptoKeyFeatureWorkspaceApps,
|
||||
CryptoKeyFeatureOidcConvert,
|
||||
CryptoKeyFeatureTailnetResume,
|
||||
}
|
||||
}
|
||||
|
||||
type DisplayApp string
|
||||
|
||||
const (
|
||||
@@ -2043,6 +2104,15 @@ type AuditLog struct {
|
||||
ResourceIcon string `db:"resource_icon" json:"resource_icon"`
|
||||
}
|
||||
|
||||
type CryptoKey struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
Secret sql.NullString `db:"secret" json:"secret"`
|
||||
SecretKeyID sql.NullString `db:"secret_key_id" json:"secret_key_id"`
|
||||
StartsAt time.Time `db:"starts_at" json:"starts_at"`
|
||||
DeletesAt sql.NullTime `db:"deletes_at" json:"deletes_at"`
|
||||
}
|
||||
|
||||
// Custom roles allow dynamic roles expanded at runtime
|
||||
type CustomRole struct {
|
||||
Name string `db:"name" json:"name"`
|
||||
|
||||
@@ -69,6 +69,7 @@ type sqlcQuerier interface {
|
||||
DeleteAllTailnetTunnels(ctx context.Context, arg DeleteAllTailnetTunnelsParams) error
|
||||
DeleteApplicationConnectAPIKeysByUserID(ctx context.Context, userID uuid.UUID) error
|
||||
DeleteCoordinator(ctx context.Context, id uuid.UUID) error
|
||||
DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error)
|
||||
DeleteCustomRole(ctx context.Context, arg DeleteCustomRoleParams) error
|
||||
DeleteExternalAuthLink(ctx context.Context, arg DeleteExternalAuthLinkParams) error
|
||||
DeleteGitSSHKey(ctx context.Context, userID uuid.UUID) error
|
||||
@@ -131,6 +132,8 @@ type sqlcQuerier interface {
|
||||
// are included.
|
||||
GetAuthorizationUserRoles(ctx context.Context, userID uuid.UUID) (GetAuthorizationUserRolesRow, error)
|
||||
GetCoordinatorResumeTokenSigningKey(ctx context.Context) (string, error)
|
||||
GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error)
|
||||
GetCryptoKeys(ctx context.Context) ([]CryptoKey, error)
|
||||
GetDBCryptKeys(ctx context.Context) ([]DBCryptKey, error)
|
||||
GetDERPMeshKey(ctx context.Context) (string, error)
|
||||
GetDefaultOrganization(ctx context.Context) (Organization, error)
|
||||
@@ -159,6 +162,7 @@ type sqlcQuerier interface {
|
||||
GetHungProvisionerJobs(ctx context.Context, updatedAt time.Time) ([]ProvisionerJob, error)
|
||||
GetJFrogXrayScanByWorkspaceAndAgentID(ctx context.Context, arg GetJFrogXrayScanByWorkspaceAndAgentIDParams) (JfrogXrayScan, error)
|
||||
GetLastUpdateCheck(ctx context.Context) (string, error)
|
||||
GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error)
|
||||
GetLatestWorkspaceBuildByWorkspaceID(ctx context.Context, workspaceID uuid.UUID) (WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuilds(ctx context.Context) ([]WorkspaceBuild, error)
|
||||
GetLatestWorkspaceBuildsByWorkspaceIDs(ctx context.Context, ids []uuid.UUID) ([]WorkspaceBuild, error)
|
||||
@@ -338,6 +342,7 @@ type sqlcQuerier interface {
|
||||
// every member of the org.
|
||||
InsertAllUsersGroup(ctx context.Context, organizationID uuid.UUID) (Group, error)
|
||||
InsertAuditLog(ctx context.Context, arg InsertAuditLogParams) (AuditLog, error)
|
||||
InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error)
|
||||
InsertCustomRole(ctx context.Context, arg InsertCustomRoleParams) (CustomRole, error)
|
||||
InsertDBCryptKey(ctx context.Context, arg InsertDBCryptKeyParams) error
|
||||
InsertDERPMeshKey(ctx context.Context, value string) error
|
||||
@@ -412,6 +417,7 @@ type sqlcQuerier interface {
|
||||
UnarchiveTemplateVersion(ctx context.Context, arg UnarchiveTemplateVersionParams) error
|
||||
UnfavoriteWorkspace(ctx context.Context, id uuid.UUID) error
|
||||
UpdateAPIKeyByID(ctx context.Context, arg UpdateAPIKeyByIDParams) error
|
||||
UpdateCryptoKeyDeletesAt(ctx context.Context, arg UpdateCryptoKeyDeletesAtParams) (CryptoKey, error)
|
||||
UpdateCustomRole(ctx context.Context, arg UpdateCustomRoleParams) (CustomRole, error)
|
||||
UpdateExternalAuthLink(ctx context.Context, arg UpdateExternalAuthLinkParams) (ExternalAuthLink, error)
|
||||
UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error)
|
||||
|
||||
@@ -761,6 +761,186 @@ func (q *sqlQuerier) InsertAuditLog(ctx context.Context, arg InsertAuditLogParam
|
||||
return i, err
|
||||
}
|
||||
|
||||
const deleteCryptoKey = `-- name: DeleteCryptoKey :one
|
||||
UPDATE crypto_keys
|
||||
SET secret = NULL, secret_key_id = NULL
|
||||
WHERE feature = $1 AND sequence = $2 RETURNING feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
`
|
||||
|
||||
type DeleteCryptoKeyParams struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) DeleteCryptoKey(ctx context.Context, arg DeleteCryptoKeyParams) (CryptoKey, error) {
|
||||
row := q.db.QueryRowContext(ctx, deleteCryptoKey, arg.Feature, arg.Sequence)
|
||||
var i CryptoKey
|
||||
err := row.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getCryptoKeyByFeatureAndSequence = `-- name: GetCryptoKeyByFeatureAndSequence :one
|
||||
SELECT feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
FROM crypto_keys
|
||||
WHERE feature = $1
|
||||
AND sequence = $2
|
||||
AND secret IS NOT NULL
|
||||
`
|
||||
|
||||
type GetCryptoKeyByFeatureAndSequenceParams struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) GetCryptoKeyByFeatureAndSequence(ctx context.Context, arg GetCryptoKeyByFeatureAndSequenceParams) (CryptoKey, error) {
|
||||
row := q.db.QueryRowContext(ctx, getCryptoKeyByFeatureAndSequence, arg.Feature, arg.Sequence)
|
||||
var i CryptoKey
|
||||
err := row.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getCryptoKeys = `-- name: GetCryptoKeys :many
|
||||
SELECT feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
FROM crypto_keys
|
||||
WHERE secret IS NOT NULL
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetCryptoKeys(ctx context.Context) ([]CryptoKey, error) {
|
||||
rows, err := q.db.QueryContext(ctx, getCryptoKeys)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var items []CryptoKey
|
||||
for rows.Next() {
|
||||
var i CryptoKey
|
||||
if err := rows.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getLatestCryptoKeyByFeature = `-- name: GetLatestCryptoKeyByFeature :one
|
||||
SELECT feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
FROM crypto_keys
|
||||
WHERE feature = $1
|
||||
ORDER BY sequence DESC
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
func (q *sqlQuerier) GetLatestCryptoKeyByFeature(ctx context.Context, feature CryptoKeyFeature) (CryptoKey, error) {
|
||||
row := q.db.QueryRowContext(ctx, getLatestCryptoKeyByFeature, feature)
|
||||
var i CryptoKey
|
||||
err := row.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const insertCryptoKey = `-- name: InsertCryptoKey :one
|
||||
INSERT INTO crypto_keys (
|
||||
feature,
|
||||
sequence,
|
||||
secret,
|
||||
starts_at,
|
||||
secret_key_id
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
) RETURNING feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
`
|
||||
|
||||
type InsertCryptoKeyParams struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
Secret sql.NullString `db:"secret" json:"secret"`
|
||||
StartsAt time.Time `db:"starts_at" json:"starts_at"`
|
||||
SecretKeyID sql.NullString `db:"secret_key_id" json:"secret_key_id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertCryptoKey(ctx context.Context, arg InsertCryptoKeyParams) (CryptoKey, error) {
|
||||
row := q.db.QueryRowContext(ctx, insertCryptoKey,
|
||||
arg.Feature,
|
||||
arg.Sequence,
|
||||
arg.Secret,
|
||||
arg.StartsAt,
|
||||
arg.SecretKeyID,
|
||||
)
|
||||
var i CryptoKey
|
||||
err := row.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateCryptoKeyDeletesAt = `-- name: UpdateCryptoKeyDeletesAt :one
|
||||
UPDATE crypto_keys
|
||||
SET deletes_at = $3
|
||||
WHERE feature = $1 AND sequence = $2 RETURNING feature, sequence, secret, secret_key_id, starts_at, deletes_at
|
||||
`
|
||||
|
||||
type UpdateCryptoKeyDeletesAtParams struct {
|
||||
Feature CryptoKeyFeature `db:"feature" json:"feature"`
|
||||
Sequence int32 `db:"sequence" json:"sequence"`
|
||||
DeletesAt sql.NullTime `db:"deletes_at" json:"deletes_at"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateCryptoKeyDeletesAt(ctx context.Context, arg UpdateCryptoKeyDeletesAtParams) (CryptoKey, error) {
|
||||
row := q.db.QueryRowContext(ctx, updateCryptoKeyDeletesAt, arg.Feature, arg.Sequence, arg.DeletesAt)
|
||||
var i CryptoKey
|
||||
err := row.Scan(
|
||||
&i.Feature,
|
||||
&i.Sequence,
|
||||
&i.Secret,
|
||||
&i.SecretKeyID,
|
||||
&i.StartsAt,
|
||||
&i.DeletesAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getDBCryptKeys = `-- name: GetDBCryptKeys :many
|
||||
SELECT number, active_key_digest, revoked_key_digest, created_at, revoked_at, test FROM dbcrypt_keys ORDER BY number ASC
|
||||
`
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
-- name: GetCryptoKeys :many
|
||||
SELECT *
|
||||
FROM crypto_keys
|
||||
WHERE secret IS NOT NULL;
|
||||
|
||||
-- name: GetLatestCryptoKeyByFeature :one
|
||||
SELECT *
|
||||
FROM crypto_keys
|
||||
WHERE feature = $1
|
||||
ORDER BY sequence DESC
|
||||
LIMIT 1;
|
||||
|
||||
-- name: GetCryptoKeyByFeatureAndSequence :one
|
||||
SELECT *
|
||||
FROM crypto_keys
|
||||
WHERE feature = $1
|
||||
AND sequence = $2
|
||||
AND secret IS NOT NULL;
|
||||
|
||||
-- name: DeleteCryptoKey :one
|
||||
UPDATE crypto_keys
|
||||
SET secret = NULL, secret_key_id = NULL
|
||||
WHERE feature = $1 AND sequence = $2 RETURNING *;
|
||||
|
||||
-- name: InsertCryptoKey :one
|
||||
INSERT INTO crypto_keys (
|
||||
feature,
|
||||
sequence,
|
||||
secret,
|
||||
starts_at,
|
||||
secret_key_id
|
||||
) VALUES (
|
||||
$1,
|
||||
$2,
|
||||
$3,
|
||||
$4,
|
||||
$5
|
||||
) RETURNING *;
|
||||
|
||||
-- name: UpdateCryptoKeyDeletesAt :one
|
||||
UPDATE crypto_keys
|
||||
SET deletes_at = $3
|
||||
WHERE feature = $1 AND sequence = $2 RETURNING *;
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
UniqueAgentStatsPkey UniqueConstraint = "agent_stats_pkey" // ALTER TABLE ONLY workspace_agent_stats ADD CONSTRAINT agent_stats_pkey PRIMARY KEY (id);
|
||||
UniqueAPIKeysPkey UniqueConstraint = "api_keys_pkey" // ALTER TABLE ONLY api_keys ADD CONSTRAINT api_keys_pkey PRIMARY KEY (id);
|
||||
UniqueAuditLogsPkey UniqueConstraint = "audit_logs_pkey" // ALTER TABLE ONLY audit_logs ADD CONSTRAINT audit_logs_pkey PRIMARY KEY (id);
|
||||
UniqueCryptoKeysPkey UniqueConstraint = "crypto_keys_pkey" // ALTER TABLE ONLY crypto_keys ADD CONSTRAINT crypto_keys_pkey PRIMARY KEY (feature, sequence);
|
||||
UniqueCustomRolesUniqueKey UniqueConstraint = "custom_roles_unique_key" // ALTER TABLE ONLY custom_roles ADD CONSTRAINT custom_roles_unique_key UNIQUE (name, organization_id);
|
||||
UniqueDbcryptKeysActiveKeyDigestKey UniqueConstraint = "dbcrypt_keys_active_key_digest_key" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_active_key_digest_key UNIQUE (active_key_digest);
|
||||
UniqueDbcryptKeysPkey UniqueConstraint = "dbcrypt_keys_pkey" // ALTER TABLE ONLY dbcrypt_keys ADD CONSTRAINT dbcrypt_keys_pkey PRIMARY KEY (number);
|
||||
|
||||
@@ -55,6 +55,16 @@ var (
|
||||
Type: "audit_log",
|
||||
}
|
||||
|
||||
// ResourceCryptoKey
|
||||
// Valid Actions
|
||||
// - "ActionCreate" :: create crypto keys
|
||||
// - "ActionDelete" :: delete crypto keys
|
||||
// - "ActionRead" :: read crypto keys
|
||||
// - "ActionUpdate" :: update crypto keys
|
||||
ResourceCryptoKey = Object{
|
||||
Type: "crypto_key",
|
||||
}
|
||||
|
||||
// ResourceDebugInfo
|
||||
// Valid Actions
|
||||
// - "ActionRead" :: access to debug routes
|
||||
@@ -299,6 +309,7 @@ func AllResources() []Objecter {
|
||||
ResourceAssignOrgRole,
|
||||
ResourceAssignRole,
|
||||
ResourceAuditLog,
|
||||
ResourceCryptoKey,
|
||||
ResourceDebugInfo,
|
||||
ResourceDeploymentConfig,
|
||||
ResourceDeploymentStats,
|
||||
|
||||
@@ -274,6 +274,14 @@ var RBACPermissions = map[string]PermissionDefinition{
|
||||
ActionUpdate: actDef("update notification preferences"),
|
||||
},
|
||||
},
|
||||
"crypto_key": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
ActionRead: actDef("read crypto keys"),
|
||||
ActionUpdate: actDef("update crypto keys"),
|
||||
ActionDelete: actDef("delete crypto keys"),
|
||||
ActionCreate: actDef("create crypto keys"),
|
||||
},
|
||||
},
|
||||
// idpsync_settings should always be org scoped
|
||||
"idpsync_settings": {
|
||||
Actions: map[Action]ActionDefinition{
|
||||
|
||||
@@ -705,6 +705,15 @@ func TestRolePermissions(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "CryptoKeys",
|
||||
Actions: []policy.Action{policy.ActionCreate, policy.ActionUpdate, policy.ActionDelete, policy.ActionRead},
|
||||
Resource: rbac.ResourceCryptoKey,
|
||||
AuthorizeMap: map[bool][]hasAuthSubjects{
|
||||
true: {owner},
|
||||
false: {setOtherOrg, setOrgNotMe, memberMe, orgMemberMe, templateAdmin, userAdmin},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "IDPSyncSettings",
|
||||
Actions: []policy.Action{policy.ActionRead, policy.ActionUpdate},
|
||||
|
||||
@@ -9,6 +9,7 @@ const (
|
||||
ResourceAssignOrgRole RBACResource = "assign_org_role"
|
||||
ResourceAssignRole RBACResource = "assign_role"
|
||||
ResourceAuditLog RBACResource = "audit_log"
|
||||
ResourceCryptoKey RBACResource = "crypto_key"
|
||||
ResourceDebugInfo RBACResource = "debug_info"
|
||||
ResourceDeploymentConfig RBACResource = "deployment_config"
|
||||
ResourceDeploymentStats RBACResource = "deployment_stats"
|
||||
@@ -62,6 +63,7 @@ var RBACResourceActions = map[RBACResource][]RBACAction{
|
||||
ResourceAssignOrgRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceAssignRole: {ActionAssign, ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceAuditLog: {ActionCreate, ActionRead},
|
||||
ResourceCryptoKey: {ActionCreate, ActionDelete, ActionRead, ActionUpdate},
|
||||
ResourceDebugInfo: {ActionRead},
|
||||
ResourceDeploymentConfig: {ActionRead, ActionUpdate},
|
||||
ResourceDeploymentStats: {ActionRead},
|
||||
|
||||
Generated
+5
@@ -184,6 +184,7 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
@@ -343,6 +344,7 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
@@ -502,6 +504,7 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
@@ -630,6 +633,7 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
@@ -888,6 +892,7 @@ Status Code **200**
|
||||
| `resource_type` | `assign_org_role` |
|
||||
| `resource_type` | `assign_role` |
|
||||
| `resource_type` | `audit_log` |
|
||||
| `resource_type` | `crypto_key` |
|
||||
| `resource_type` | `debug_info` |
|
||||
| `resource_type` | `deployment_config` |
|
||||
| `resource_type` | `deployment_stats` |
|
||||
|
||||
Generated
+1
@@ -4384,6 +4384,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o
|
||||
| `assign_org_role` |
|
||||
| `assign_role` |
|
||||
| `audit_log` |
|
||||
| `crypto_key` |
|
||||
| `debug_info` |
|
||||
| `deployment_config` |
|
||||
| `deployment_stats` |
|
||||
|
||||
@@ -261,6 +261,66 @@ func (db *dbCrypt) UpdateExternalAuthLink(ctx context.Context, params database.U
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetCryptoKeys(ctx context.Context) ([]database.CryptoKey, error) {
|
||||
keys, err := db.Store.GetCryptoKeys(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range keys {
|
||||
if err := db.decryptField(&keys[i].Secret.String, keys[i].SecretKeyID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return keys, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetLatestCryptoKeyByFeature(ctx context.Context, feature database.CryptoKeyFeature) (database.CryptoKey, error) {
|
||||
key, err := db.Store.GetLatestCryptoKeyByFeature(ctx, feature)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
if err := db.decryptField(&key.Secret.String, key.SecretKeyID); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) GetCryptoKeyByFeatureAndSequence(ctx context.Context, params database.GetCryptoKeyByFeatureAndSequenceParams) (database.CryptoKey, error) {
|
||||
key, err := db.Store.GetCryptoKeyByFeatureAndSequence(ctx, params)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
if err := db.decryptField(&key.Secret.String, key.SecretKeyID); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) InsertCryptoKey(ctx context.Context, params database.InsertCryptoKeyParams) (database.CryptoKey, error) {
|
||||
if err := db.encryptField(¶ms.Secret.String, ¶ms.SecretKeyID); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
key, err := db.Store.InsertCryptoKey(ctx, params)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
if err := db.decryptField(&key.Secret.String, key.SecretKeyID); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) UpdateCryptoKeyDeletesAt(ctx context.Context, arg database.UpdateCryptoKeyDeletesAtParams) (database.CryptoKey, error) {
|
||||
key, err := db.Store.UpdateCryptoKeyDeletesAt(ctx, arg)
|
||||
if err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
if err := db.decryptField(&key.Secret.String, key.SecretKeyID); err != nil {
|
||||
return database.CryptoKey{}, err
|
||||
}
|
||||
return key, nil
|
||||
}
|
||||
|
||||
func (db *dbCrypt) encryptField(field *string, digest *sql.NullString) error {
|
||||
// If no cipher is loaded, then we can't encrypt anything!
|
||||
if db.ciphers == nil || db.primaryCipherDigest == "" {
|
||||
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lib/pq"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -349,6 +350,129 @@ func TestExternalAuthLinks(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCryptoKeys(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("InsertCryptoKey", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
db, crypt, ciphers := setup(t)
|
||||
key := dbgen.CryptoKey(t, crypt, database.CryptoKey{
|
||||
Secret: sql.NullString{String: "test", Valid: true},
|
||||
})
|
||||
require.Equal(t, "test", key.Secret.String)
|
||||
|
||||
key, err := db.GetCryptoKeyByFeatureAndSequence(ctx, database.GetCryptoKeyByFeatureAndSequenceParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
requireEncryptedEquals(t, ciphers[0], key.Secret.String, "test")
|
||||
})
|
||||
|
||||
t.Run("GetCryptoKeys", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
_ = dbgen.CryptoKey(t, crypt, database.CryptoKey{
|
||||
Secret: sql.NullString{String: "test", Valid: true},
|
||||
})
|
||||
keys, err := crypt.GetCryptoKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
require.Equal(t, "test", keys[0].Secret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), keys[0].SecretKeyID.String)
|
||||
|
||||
keys, err = db.GetCryptoKeys(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, keys, 1)
|
||||
requireEncryptedEquals(t, ciphers[0], keys[0].Secret.String, "test")
|
||||
require.Equal(t, ciphers[0].HexDigest(), keys[0].SecretKeyID.String)
|
||||
})
|
||||
|
||||
t.Run("GetLatestCryptoKeyByFeature", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
_ = dbgen.CryptoKey(t, crypt, database.CryptoKey{
|
||||
Secret: sql.NullString{String: "test", Valid: true},
|
||||
})
|
||||
key, err := crypt.GetLatestCryptoKeyByFeature(ctx, database.CryptoKeyFeatureWorkspaceApps)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", key.Secret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
|
||||
key, err = db.GetLatestCryptoKeyByFeature(ctx, database.CryptoKeyFeatureWorkspaceApps)
|
||||
require.NoError(t, err)
|
||||
requireEncryptedEquals(t, ciphers[0], key.Secret.String, "test")
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
})
|
||||
|
||||
t.Run("GetCryptoKeyByFeatureAndSequence", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
key := dbgen.CryptoKey(t, crypt, database.CryptoKey{
|
||||
Secret: sql.NullString{String: "test", Valid: true},
|
||||
})
|
||||
key, err := crypt.GetCryptoKeyByFeatureAndSequence(ctx, database.GetCryptoKeyByFeatureAndSequenceParams{
|
||||
Feature: database.CryptoKeyFeatureWorkspaceApps,
|
||||
Sequence: key.Sequence,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", key.Secret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
|
||||
key, err = db.GetCryptoKeyByFeatureAndSequence(ctx, database.GetCryptoKeyByFeatureAndSequenceParams{
|
||||
Feature: database.CryptoKeyFeatureWorkspaceApps,
|
||||
Sequence: key.Sequence,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
requireEncryptedEquals(t, ciphers[0], key.Secret.String, "test")
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
})
|
||||
|
||||
t.Run("UpdateCryptoKeyDeletesAt", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
_, crypt, ciphers := setup(t)
|
||||
key := dbgen.CryptoKey(t, crypt, database.CryptoKey{
|
||||
Secret: sql.NullString{String: "test", Valid: true},
|
||||
})
|
||||
key, err := crypt.UpdateCryptoKeyDeletesAt(ctx, database.UpdateCryptoKeyDeletesAtParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
DeletesAt: sql.NullTime{
|
||||
Time: time.Now().Add(time.Hour),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "test", key.Secret.String)
|
||||
require.Equal(t, ciphers[0].HexDigest(), key.SecretKeyID.String)
|
||||
})
|
||||
|
||||
t.Run("DecryptErr", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, crypt, ciphers := setup(t)
|
||||
key := dbgen.CryptoKey(t, db, database.CryptoKey{
|
||||
Secret: sql.NullString{
|
||||
String: fakeBase64RandomData(t, 32),
|
||||
Valid: true,
|
||||
},
|
||||
SecretKeyID: sql.NullString{
|
||||
String: ciphers[0].HexDigest(),
|
||||
Valid: true,
|
||||
},
|
||||
})
|
||||
_, err := crypt.GetCryptoKeyByFeatureAndSequence(ctx, database.GetCryptoKeyByFeatureAndSequenceParams{
|
||||
Feature: key.Feature,
|
||||
Sequence: key.Sequence,
|
||||
})
|
||||
require.Error(t, err, "expected an error")
|
||||
var derr *DecryptFailedError
|
||||
require.ErrorAs(t, err, &derr, "expected a decrypt error")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
@@ -32,6 +32,12 @@ export const RBACResourceActions: Partial<
|
||||
create: "create new audit log entries",
|
||||
read: "read audit logs",
|
||||
},
|
||||
crypto_key: {
|
||||
create: "create crypto keys",
|
||||
delete: "delete crypto keys",
|
||||
read: "read crypto keys",
|
||||
update: "update crypto keys",
|
||||
},
|
||||
debug_info: {
|
||||
read: "access to debug routes",
|
||||
},
|
||||
|
||||
Generated
+2
-2
@@ -2155,8 +2155,8 @@ export type RBACAction = "application_connect" | "assign" | "create" | "delete"
|
||||
export const RBACActions: RBACAction[] = ["application_connect", "assign", "create", "delete", "read", "read_personal", "ssh", "start", "stop", "update", "update_personal", "use", "view_insights"]
|
||||
|
||||
// From codersdk/rbacresources_gen.go
|
||||
export type RBACResource = "*" | "api_key" | "assign_org_role" | "assign_role" | "audit_log" | "debug_info" | "deployment_config" | "deployment_stats" | "file" | "group" | "group_member" | "idpsync_settings" | "license" | "notification_preference" | "notification_template" | "oauth2_app" | "oauth2_app_code_token" | "oauth2_app_secret" | "organization" | "organization_member" | "provisioner_daemon" | "provisioner_keys" | "replicas" | "system" | "tailnet_coordinator" | "template" | "user" | "workspace" | "workspace_dormant" | "workspace_proxy"
|
||||
export const RBACResources: RBACResource[] = ["*", "api_key", "assign_org_role", "assign_role", "audit_log", "debug_info", "deployment_config", "deployment_stats", "file", "group", "group_member", "idpsync_settings", "license", "notification_preference", "notification_template", "oauth2_app", "oauth2_app_code_token", "oauth2_app_secret", "organization", "organization_member", "provisioner_daemon", "provisioner_keys", "replicas", "system", "tailnet_coordinator", "template", "user", "workspace", "workspace_dormant", "workspace_proxy"]
|
||||
export type RBACResource = "*" | "api_key" | "assign_org_role" | "assign_role" | "audit_log" | "crypto_key" | "debug_info" | "deployment_config" | "deployment_stats" | "file" | "group" | "group_member" | "idpsync_settings" | "license" | "notification_preference" | "notification_template" | "oauth2_app" | "oauth2_app_code_token" | "oauth2_app_secret" | "organization" | "organization_member" | "provisioner_daemon" | "provisioner_keys" | "replicas" | "system" | "tailnet_coordinator" | "template" | "user" | "workspace" | "workspace_dormant" | "workspace_proxy"
|
||||
export const RBACResources: RBACResource[] = ["*", "api_key", "assign_org_role", "assign_role", "audit_log", "crypto_key", "debug_info", "deployment_config", "deployment_stats", "file", "group", "group_member", "idpsync_settings", "license", "notification_preference", "notification_template", "oauth2_app", "oauth2_app_code_token", "oauth2_app_secret", "organization", "organization_member", "provisioner_daemon", "provisioner_keys", "replicas", "system", "tailnet_coordinator", "template", "user", "workspace", "workspace_dormant", "workspace_proxy"]
|
||||
|
||||
// From codersdk/audit.go
|
||||
export type ResourceType = "api_key" | "convert_login" | "custom_role" | "git_ssh_key" | "group" | "health_settings" | "license" | "notifications_settings" | "oauth2_provider_app" | "oauth2_provider_app_secret" | "organization" | "template" | "template_version" | "user" | "workspace" | "workspace_build" | "workspace_proxy"
|
||||
|
||||
Reference in New Issue
Block a user