feat(coderd): connect dbcrypt package implementation (#9523)

See also: https://github.com/coder/coder/pull/9522

- Adds commands `server dbcrypt {rotate,decrypt,delete}` to re-encrypt, decrypt, or delete encrypted data, respectively.
- Plumbs through dbcrypt in enterprise/coderd (including unit tests).
- Adds documentation in admin/encryption.md.

This enables dbcrypt by default, but the feature is soft-enforced on supplying external token encryption keys. Without specifying any keys, encryption/decryption is a no-op.
This commit is contained in:
Cian Johnston
2023-09-07 15:49:49 +01:00
committed by GitHub
parent ed7f682fd1
commit 7d7c84bb4d
36 changed files with 1600 additions and 36 deletions

6
coderd/apidoc/docs.go generated
View File

@ -7956,6 +7956,12 @@ const docTemplate = `{
"type": "string"
}
},
"external_token_encryption_keys": {
"type": "array",
"items": {
"type": "string"
}
},
"git_auth": {
"$ref": "#/definitions/clibase.Struct-array_codersdk_GitAuthConfig"
},

View File

@ -7112,6 +7112,12 @@
"type": "string"
}
},
"external_token_encryption_keys": {
"type": "array",
"items": {
"type": "string"
}
},
"git_auth": {
"$ref": "#/definitions/clibase.Struct-array_codersdk_GitAuthConfig"
},

View File

@ -26,6 +26,7 @@ func TestDeploymentValues(t *testing.T) {
cfg.OIDC.EmailField.Set("some_random_field_you_never_expected")
cfg.PostgresURL.Set(hi)
cfg.SCIMAPIKey.Set(hi)
cfg.ExternalTokenEncryptionKeys.Set("the_random_key_we_never_expected,an_other_key_we_never_unexpected")
client := coderdtest.New(t, &coderdtest.Options{
DeploymentValues: cfg,
@ -44,6 +45,7 @@ func TestDeploymentValues(t *testing.T) {
require.Empty(t, scrubbed.Values.OIDC.ClientSecret.Value())
require.Empty(t, scrubbed.Values.PostgresURL.Value())
require.Empty(t, scrubbed.Values.SCIMAPIKey.Value())
require.Empty(t, scrubbed.Values.ExternalTokenEncryptionKeys.Value())
}
func TestDeploymentStats(t *testing.T) {

View File

@ -248,6 +248,12 @@ func ExtractAPIKey(rw http.ResponseWriter, r *http.Request, cfg ExtractAPIKeyCon
UserID: key.UserID,
LoginType: key.LoginType,
})
if errors.Is(err, sql.ErrNoRows) {
return optionalWrite(http.StatusUnauthorized, codersdk.Response{
Message: SignedOutErrorMessage,
Detail: "You must re-authenticate with the login provider.",
})
}
if err != nil {
return write(http.StatusInternalServerError, codersdk.Response{
Message: "A database error occurred",

View File

@ -153,6 +153,34 @@ func TestAPIKey(t *testing.T) {
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
})
t.Run("UserLinkNotFound", func(t *testing.T) {
t.Parallel()
var (
db = dbfake.New()
r = httptest.NewRequest("GET", "/", nil)
rw = httptest.NewRecorder()
user = dbgen.User(t, db, database.User{
LoginType: database.LoginTypeGithub,
})
// Intentionally not inserting any user link
_, token = dbgen.APIKey(t, db, database.APIKey{
UserID: user.ID,
LoginType: user.LoginType,
})
)
r.Header.Set(codersdk.SessionTokenHeader, token)
httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
DB: db,
RedirectToLogin: false,
})(successHandler).ServeHTTP(rw, r)
res := rw.Result()
defer res.Body.Close()
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
var resp codersdk.Response
require.NoError(t, json.NewDecoder(res.Body).Decode(&resp))
require.Equal(t, resp.Message, httpmw.SignedOutErrorMessage)
})
t.Run("InvalidSecret", func(t *testing.T) {
t.Parallel()
var (