mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: encrypt gitsshkeys.private_key at rest via dbcrypt (#25872)
Adds an optional dbcrypt wrapper around gitsshkeys.private_key. The column is encrypted on insert and update through enterprise/dbcrypt when external token encryption is configured, and decrypted on read. A new private_key_key_id column references dbcrypt_keys(active_key_digest) so revocation safety is enforced by the existing foreign key. Rows with a NULL key_id stay plaintext and remain readable. Existing plaintext rows can be backfilled by running `coder server dbcrypt rotate`. Generated with assistance from Coder Agents.
This commit is contained in:
@@ -1021,11 +1021,12 @@ func User(t testing.TB, db database.Store, orig database.User) database.User {
|
||||
|
||||
func GitSSHKey(t testing.TB, db database.Store, orig database.GitSSHKey) database.GitSSHKey {
|
||||
key, err := db.InsertGitSSHKey(genCtx, database.InsertGitSSHKeyParams{
|
||||
UserID: takeFirst(orig.UserID, uuid.New()),
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
|
||||
PrivateKey: takeFirst(orig.PrivateKey, ""),
|
||||
PublicKey: takeFirst(orig.PublicKey, ""),
|
||||
UserID: takeFirst(orig.UserID, uuid.New()),
|
||||
CreatedAt: takeFirst(orig.CreatedAt, dbtime.Now()),
|
||||
UpdatedAt: takeFirst(orig.UpdatedAt, dbtime.Now()),
|
||||
PrivateKey: takeFirst(orig.PrivateKey, ""),
|
||||
PrivateKeyKeyID: takeFirst(orig.PrivateKeyKeyID, sql.NullString{}),
|
||||
PublicKey: takeFirst(orig.PublicKey, ""),
|
||||
})
|
||||
require.NoError(t, err, "insert ssh key")
|
||||
return key
|
||||
|
||||
Generated
+7
-1
@@ -2013,9 +2013,12 @@ CREATE TABLE gitsshkeys (
|
||||
created_at timestamp with time zone NOT NULL,
|
||||
updated_at timestamp with time zone NOT NULL,
|
||||
private_key text NOT NULL,
|
||||
public_key text NOT NULL
|
||||
public_key text NOT NULL,
|
||||
private_key_key_id text
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN gitsshkeys.private_key_key_id IS 'The ID of the key used to encrypt the private key. If this is NULL, the private key is not encrypted.';
|
||||
|
||||
CREATE TABLE group_ai_budgets (
|
||||
group_id uuid NOT NULL,
|
||||
spend_limit_micros bigint NOT NULL,
|
||||
@@ -4701,6 +4704,9 @@ ALTER TABLE ONLY external_auth_links
|
||||
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);
|
||||
|
||||
ALTER TABLE ONLY gitsshkeys
|
||||
ADD CONSTRAINT gitsshkeys_private_key_key_id_fkey FOREIGN KEY (private_key_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
ALTER TABLE ONLY gitsshkeys
|
||||
ADD CONSTRAINT gitsshkeys_user_id_fkey FOREIGN KEY (user_id) REFERENCES users(id);
|
||||
|
||||
|
||||
+1
@@ -46,6 +46,7 @@ const (
|
||||
ForeignKeyFkOauth2ProviderAppTokensUserID ForeignKeyConstraint = "fk_oauth2_provider_app_tokens_user_id" // ALTER TABLE ONLY oauth2_provider_app_tokens ADD CONSTRAINT fk_oauth2_provider_app_tokens_user_id FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
|
||||
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);
|
||||
ForeignKeyGitSSHKeysPrivateKeyKeyID ForeignKeyConstraint = "gitsshkeys_private_key_key_id_fkey" // ALTER TABLE ONLY gitsshkeys ADD CONSTRAINT gitsshkeys_private_key_key_id_fkey FOREIGN KEY (private_key_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);
|
||||
ForeignKeyGroupAiBudgetsGroupID ForeignKeyConstraint = "group_ai_budgets_group_id_fkey" // ALTER TABLE ONLY group_ai_budgets ADD CONSTRAINT group_ai_budgets_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE;
|
||||
ForeignKeyGroupMembersGroupID ForeignKeyConstraint = "group_members_group_id_fkey" // ALTER TABLE ONLY group_members ADD CONSTRAINT group_members_group_id_fkey FOREIGN KEY (group_id) REFERENCES groups(id) ON DELETE CASCADE;
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
ALTER TABLE gitsshkeys
|
||||
DROP CONSTRAINT gitsshkeys_private_key_key_id_fkey,
|
||||
DROP COLUMN private_key_key_id;
|
||||
@@ -0,0 +1,7 @@
|
||||
ALTER TABLE gitsshkeys
|
||||
ADD COLUMN private_key_key_id TEXT;
|
||||
|
||||
ALTER TABLE ONLY gitsshkeys
|
||||
ADD CONSTRAINT gitsshkeys_private_key_key_id_fkey FOREIGN KEY (private_key_key_id) REFERENCES dbcrypt_keys(active_key_digest);
|
||||
|
||||
COMMENT ON COLUMN gitsshkeys.private_key_key_id IS 'The ID of the key used to encrypt the private key. If this is NULL, the private key is not encrypted.';
|
||||
Generated
+2
@@ -4914,6 +4914,8 @@ type GitSSHKey struct {
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
PrivateKey string `db:"private_key" json:"private_key"`
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
// The ID of the key used to encrypt the private key. If this is NULL, the private key is not encrypted.
|
||||
PrivateKeyKeyID sql.NullString `db:"private_key_key_id" json:"private_key_key_id"`
|
||||
}
|
||||
|
||||
type Group struct {
|
||||
|
||||
Generated
+22
-13
@@ -12572,7 +12572,7 @@ func (q *sqlQuerier) InsertFile(ctx context.Context, arg InsertFileParams) (File
|
||||
|
||||
const getGitSSHKey = `-- name: GetGitSSHKey :one
|
||||
SELECT
|
||||
user_id, created_at, updated_at, private_key, public_key
|
||||
user_id, created_at, updated_at, private_key, public_key, private_key_key_id
|
||||
FROM
|
||||
gitsshkeys
|
||||
WHERE
|
||||
@@ -12588,6 +12588,7 @@ func (q *sqlQuerier) GetGitSSHKey(ctx context.Context, userID uuid.UUID) (GitSSH
|
||||
&i.UpdatedAt,
|
||||
&i.PrivateKey,
|
||||
&i.PublicKey,
|
||||
&i.PrivateKeyKeyID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -12599,18 +12600,20 @@ INSERT INTO
|
||||
created_at,
|
||||
updated_at,
|
||||
private_key,
|
||||
private_key_key_id,
|
||||
public_key
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5) RETURNING user_id, created_at, updated_at, private_key, public_key
|
||||
($1, $2, $3, $4, $5, $6) RETURNING user_id, created_at, updated_at, private_key, public_key, private_key_key_id
|
||||
`
|
||||
|
||||
type InsertGitSSHKeyParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
PrivateKey string `db:"private_key" json:"private_key"`
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
CreatedAt time.Time `db:"created_at" json:"created_at"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
PrivateKey string `db:"private_key" json:"private_key"`
|
||||
PrivateKeyKeyID sql.NullString `db:"private_key_key_id" json:"private_key_key_id"`
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyParams) (GitSSHKey, error) {
|
||||
@@ -12619,6 +12622,7 @@ func (q *sqlQuerier) InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyPar
|
||||
arg.CreatedAt,
|
||||
arg.UpdatedAt,
|
||||
arg.PrivateKey,
|
||||
arg.PrivateKeyKeyID,
|
||||
arg.PublicKey,
|
||||
)
|
||||
var i GitSSHKey
|
||||
@@ -12628,6 +12632,7 @@ func (q *sqlQuerier) InsertGitSSHKey(ctx context.Context, arg InsertGitSSHKeyPar
|
||||
&i.UpdatedAt,
|
||||
&i.PrivateKey,
|
||||
&i.PublicKey,
|
||||
&i.PrivateKeyKeyID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
@@ -12638,18 +12643,20 @@ UPDATE
|
||||
SET
|
||||
updated_at = $2,
|
||||
private_key = $3,
|
||||
public_key = $4
|
||||
private_key_key_id = $4,
|
||||
public_key = $5
|
||||
WHERE
|
||||
user_id = $1
|
||||
RETURNING
|
||||
user_id, created_at, updated_at, private_key, public_key
|
||||
user_id, created_at, updated_at, private_key, public_key, private_key_key_id
|
||||
`
|
||||
|
||||
type UpdateGitSSHKeyParams struct {
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
PrivateKey string `db:"private_key" json:"private_key"`
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
UserID uuid.UUID `db:"user_id" json:"user_id"`
|
||||
UpdatedAt time.Time `db:"updated_at" json:"updated_at"`
|
||||
PrivateKey string `db:"private_key" json:"private_key"`
|
||||
PrivateKeyKeyID sql.NullString `db:"private_key_key_id" json:"private_key_key_id"`
|
||||
PublicKey string `db:"public_key" json:"public_key"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyParams) (GitSSHKey, error) {
|
||||
@@ -12657,6 +12664,7 @@ func (q *sqlQuerier) UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyPar
|
||||
arg.UserID,
|
||||
arg.UpdatedAt,
|
||||
arg.PrivateKey,
|
||||
arg.PrivateKeyKeyID,
|
||||
arg.PublicKey,
|
||||
)
|
||||
var i GitSSHKey
|
||||
@@ -12666,6 +12674,7 @@ func (q *sqlQuerier) UpdateGitSSHKey(ctx context.Context, arg UpdateGitSSHKeyPar
|
||||
&i.UpdatedAt,
|
||||
&i.PrivateKey,
|
||||
&i.PublicKey,
|
||||
&i.PrivateKeyKeyID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
@@ -5,10 +5,11 @@ INSERT INTO
|
||||
created_at,
|
||||
updated_at,
|
||||
private_key,
|
||||
private_key_key_id,
|
||||
public_key
|
||||
)
|
||||
VALUES
|
||||
($1, $2, $3, $4, $5) RETURNING *;
|
||||
($1, $2, $3, $4, $5, $6) RETURNING *;
|
||||
|
||||
-- name: GetGitSSHKey :one
|
||||
SELECT
|
||||
@@ -24,9 +25,9 @@ UPDATE
|
||||
SET
|
||||
updated_at = $2,
|
||||
private_key = $3,
|
||||
public_key = $4
|
||||
private_key_key_id = $4,
|
||||
public_key = $5
|
||||
WHERE
|
||||
user_id = $1
|
||||
RETURNING
|
||||
*;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user