Skip to content

Commit 4d28746

Browse files
committed
make the test pass
1 parent a457307 commit 4d28746

File tree

4 files changed

+123
-6
lines changed

4 files changed

+123
-6
lines changed

cli/root.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,8 @@ func (r *RootCmd) Command(subcommands []*clibase.Cmd) (*clibase.Cmd, error) {
243243
for _, opt := range cmd.Options {
244244
// Verify that every option is configurable.
245245
if opt.Flag == "" && opt.Env == "" {
246-
if cmd.Name() == "server" {
246+
// TODO(Cian): maybe move dbcrypt-rotate under server to work around this?
247+
if cmd.Name() == "server" || cmd.Name() == "dbcrypt-rotate" {
247248
// The server command is funky and has YAML-only options, e.g.
248249
// support links.
249250
return

enterprise/cli/dbcrypt_rotate.go

Lines changed: 119 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,18 +3,134 @@
33
package cli
44

55
import (
6+
"bytes"
7+
"cdr.dev/slog"
8+
"context"
9+
"encoding/base64"
10+
"sync/atomic"
11+
12+
"github.com/coder/coder/v2/cli"
613
"github.com/coder/coder/v2/cli/clibase"
14+
"github.com/coder/coder/v2/coderd/database"
15+
"github.com/coder/coder/v2/codersdk"
16+
"github.com/coder/coder/v2/enterprise/dbcrypt"
17+
18+
"golang.org/x/xerrors"
719
)
820

921
func (r *RootCmd) dbcryptRotate() *clibase.Cmd {
22+
var (
23+
vals = new(codersdk.DeploymentValues)
24+
opts = vals.Options()
25+
)
1026
cmd := &clibase.Cmd{
11-
Use: "dbcrypt-rotate",
12-
Short: "Rotate database encryption keys",
27+
Use: "dbcrypt-rotate --postgres-url <postgres_url> --external-token-encryption-keys <new-key>,<old-key>",
28+
Short: "Rotate database encryption keys",
29+
Options: opts,
1330
Middleware: clibase.Chain(
1431
clibase.RequireNArgs(0),
1532
),
1633
Handler: func(inv *clibase.Invocation) error {
17-
// TODO: implement
34+
ctx, cancel := context.WithCancel(inv.Context())
35+
defer cancel()
36+
logger, closeLogger, err := cli.BuildLogger(inv, vals)
37+
if err != nil {
38+
return xerrors.Errorf("set up logging: %w", err)
39+
}
40+
defer closeLogger()
41+
42+
if vals.PostgresURL == "" {
43+
return xerrors.Errorf("no database configured")
44+
}
45+
46+
if vals.ExternalTokenEncryptionKeys == nil || len(vals.ExternalTokenEncryptionKeys) != 2 {
47+
return xerrors.Errorf("dbcrypt-rotate requires exactly two external token encryption keys")
48+
}
49+
50+
newKey, err := base64.StdEncoding.DecodeString(vals.ExternalTokenEncryptionKeys[0])
51+
if err != nil {
52+
return xerrors.Errorf("new key must be base64-encoded")
53+
}
54+
oldKey, err := base64.StdEncoding.DecodeString(vals.ExternalTokenEncryptionKeys[1])
55+
if err != nil {
56+
return xerrors.Errorf("old key must be base64-encoded")
57+
}
58+
if bytes.Compare(newKey, oldKey) == 0 {
59+
return xerrors.Errorf("old and new keys must be different")
60+
}
61+
62+
primaryCipherPtr := &atomic.Pointer[dbcrypt.Cipher]{}
63+
secondaryCipherPtr := &atomic.Pointer[dbcrypt.Cipher]{}
64+
primaryCipher, err := dbcrypt.CipherAES256(newKey)
65+
if err != nil {
66+
return xerrors.Errorf("create primary cipher: %w", err)
67+
}
68+
secondaryCipher, err := dbcrypt.CipherAES256(oldKey)
69+
if err != nil {
70+
return xerrors.Errorf("create secondary cipher: %w", err)
71+
}
72+
primaryCipherPtr.Store(&primaryCipher)
73+
secondaryCipherPtr.Store(&secondaryCipher)
74+
75+
sqlDB, err := cli.ConnectToPostgres(inv.Context(), logger, "postgres", vals.PostgresURL.Value())
76+
if err != nil {
77+
return xerrors.Errorf("connect to postgres: %w", err)
78+
}
79+
defer func() {
80+
_ = sqlDB.Close()
81+
}()
82+
logger.Info(ctx, "connected to postgres")
83+
84+
db := database.New(sqlDB)
85+
86+
cryptDB, err := dbcrypt.New(ctx, db, &dbcrypt.Options{
87+
PrimaryCipher: primaryCipherPtr,
88+
SecondaryCipher: secondaryCipherPtr,
89+
Logger: logger.Named("cryptdb"),
90+
})
91+
if err != nil {
92+
return xerrors.Errorf("create cryptdb: %w", err)
93+
}
94+
95+
users, err := cryptDB.GetUsers(ctx, database.GetUsersParams{})
96+
if err != nil {
97+
return xerrors.Errorf("get users: %w", err)
98+
}
99+
for idx, usr := range users {
100+
userLinks, err := cryptDB.GetUserLinksByUserID(ctx, usr.ID)
101+
if err != nil {
102+
return xerrors.Errorf("get user links for user: %w", err)
103+
}
104+
for _, userLink := range userLinks {
105+
if _, err := cryptDB.UpdateUserLink(ctx, database.UpdateUserLinkParams{
106+
OAuthAccessToken: userLink.OAuthAccessToken,
107+
OAuthRefreshToken: userLink.OAuthRefreshToken,
108+
OAuthExpiry: userLink.OAuthExpiry,
109+
UserID: usr.ID,
110+
LoginType: usr.LoginType,
111+
}); err != nil {
112+
return xerrors.Errorf("update user link: %w", err)
113+
}
114+
}
115+
gitAuthLinks, err := cryptDB.GetGitAuthLinksByUserID(ctx, usr.ID)
116+
if err != nil {
117+
return xerrors.Errorf("get git auth links for user: %w", err)
118+
}
119+
for _, gitAuthLink := range gitAuthLinks {
120+
if _, err := cryptDB.UpdateGitAuthLink(ctx, database.UpdateGitAuthLinkParams{
121+
ProviderID: gitAuthLink.ProviderID,
122+
UserID: usr.ID,
123+
UpdatedAt: gitAuthLink.UpdatedAt,
124+
OAuthAccessToken: gitAuthLink.OAuthAccessToken,
125+
OAuthRefreshToken: gitAuthLink.OAuthRefreshToken,
126+
OAuthExpiry: gitAuthLink.OAuthExpiry,
127+
}); err != nil {
128+
return xerrors.Errorf("update git auth link: %w", err)
129+
}
130+
}
131+
logger.Info(ctx, "encrypted user tokens", slog.F("current", idx+1), slog.F("of", len(users)))
132+
}
133+
logger.Info(ctx, "operation completed successfully!")
18134
return nil
19135
},
20136
}

enterprise/cli/root.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ func (r *RootCmd) enterpriseOnly() []*clibase.Cmd {
1717
r.licenses(),
1818
r.groups(),
1919
r.provisionerDaemons(),
20+
r.dbcryptRotate(),
2021
}
2122
}
2223

enterprise/dbcrypt/dbcrypt.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,11 @@ import (
2727
"database/sql"
2828
"encoding/base64"
2929
"errors"
30-
"github.com/google/uuid"
3130
"strings"
3231
"sync/atomic"
3332

33+
"github.com/google/uuid"
3434
"github.com/hashicorp/go-multierror"
35-
3635
"golang.org/x/xerrors"
3736

3837
"cdr.dev/slog"

0 commit comments

Comments
 (0)