mirror of
https://github.com/coder/coder.git
synced 2025-07-06 15:41:45 +00:00
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:
@ -33,6 +33,7 @@ import (
|
||||
"github.com/coder/coder/v2/enterprise/coderd/license"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/proxyhealth"
|
||||
"github.com/coder/coder/v2/enterprise/coderd/schedule"
|
||||
"github.com/coder/coder/v2/enterprise/dbcrypt"
|
||||
"github.com/coder/coder/v2/enterprise/derpmesh"
|
||||
"github.com/coder/coder/v2/enterprise/replicasync"
|
||||
"github.com/coder/coder/v2/enterprise/tailnet"
|
||||
@ -47,8 +48,8 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
if options.EntitlementsUpdateInterval == 0 {
|
||||
options.EntitlementsUpdateInterval = 10 * time.Minute
|
||||
}
|
||||
if options.Keys == nil {
|
||||
options.Keys = Keys
|
||||
if options.LicenseKeys == nil {
|
||||
options.LicenseKeys = Keys
|
||||
}
|
||||
if options.Options == nil {
|
||||
options.Options = &coderd.Options{}
|
||||
@ -61,10 +62,38 @@ func New(ctx context.Context, options *Options) (_ *API, err error) {
|
||||
}
|
||||
|
||||
ctx, cancelFunc := context.WithCancel(ctx)
|
||||
api := &API{
|
||||
ctx: ctx,
|
||||
cancel: cancelFunc,
|
||||
|
||||
if options.ExternalTokenEncryption == nil {
|
||||
options.ExternalTokenEncryption = make([]dbcrypt.Cipher, 0)
|
||||
}
|
||||
// Database encryption is an enterprise feature, but as checking license entitlements
|
||||
// depends on the database, we end up in a chicken-and-egg situation. To avoid this,
|
||||
// we always enable it but only soft-enforce it.
|
||||
if len(options.ExternalTokenEncryption) > 0 {
|
||||
var keyDigests []string
|
||||
for _, cipher := range options.ExternalTokenEncryption {
|
||||
keyDigests = append(keyDigests, cipher.HexDigest())
|
||||
}
|
||||
options.Logger.Info(ctx, "database encryption enabled", slog.F("keys", keyDigests))
|
||||
}
|
||||
|
||||
cryptDB, err := dbcrypt.New(ctx, options.Database, options.ExternalTokenEncryption...)
|
||||
if err != nil {
|
||||
cancelFunc()
|
||||
// If we fail to initialize the database, it's likely that the
|
||||
// database is encrypted with an unknown external token encryption key.
|
||||
// This is a fatal error.
|
||||
var derr *dbcrypt.DecryptFailedError
|
||||
if xerrors.As(err, &derr) {
|
||||
return nil, xerrors.Errorf("database encrypted with unknown key, either add the key or see https://coder.com/docs/v2/latest/admin/encryption#disabling-encryption: %w", derr)
|
||||
}
|
||||
return nil, xerrors.Errorf("init database encryption: %w", err)
|
||||
}
|
||||
options.Database = cryptDB
|
||||
|
||||
api := &API{
|
||||
ctx: ctx,
|
||||
cancel: cancelFunc,
|
||||
AGPL: coderd.New(options.Options),
|
||||
Options: options,
|
||||
provisionerDaemonAuth: &provisionerDaemonAuth{
|
||||
@ -364,6 +393,8 @@ type Options struct {
|
||||
BrowserOnly bool
|
||||
SCIMAPIKey []byte
|
||||
|
||||
ExternalTokenEncryption []dbcrypt.Cipher
|
||||
|
||||
// Used for high availability.
|
||||
ReplicaSyncUpdateInterval time.Duration
|
||||
DERPServerRelayAddress string
|
||||
@ -374,7 +405,7 @@ type Options struct {
|
||||
|
||||
EntitlementsUpdateInterval time.Duration
|
||||
ProxyHealthInterval time.Duration
|
||||
Keys map[string]ed25519.PublicKey
|
||||
LicenseKeys map[string]ed25519.PublicKey
|
||||
|
||||
// optional pre-shared key for authentication of external provisioner daemons
|
||||
ProvisionerDaemonPSK string
|
||||
@ -429,13 +460,14 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
|
||||
entitlements, err := license.Entitlements(
|
||||
ctx, api.Database,
|
||||
api.Logger, len(api.replicaManager.AllPrimary()), len(api.GitAuthConfigs), api.Keys, map[codersdk.FeatureName]bool{
|
||||
api.Logger, len(api.replicaManager.AllPrimary()), len(api.GitAuthConfigs), api.LicenseKeys, map[codersdk.FeatureName]bool{
|
||||
codersdk.FeatureAuditLog: api.AuditLogging,
|
||||
codersdk.FeatureBrowserOnly: api.BrowserOnly,
|
||||
codersdk.FeatureSCIM: len(api.SCIMAPIKey) != 0,
|
||||
codersdk.FeatureHighAvailability: api.DERPServerRelayAddress != "",
|
||||
codersdk.FeatureMultipleGitAuth: len(api.GitAuthConfigs) > 1,
|
||||
codersdk.FeatureTemplateRBAC: api.RBAC,
|
||||
codersdk.FeatureExternalTokenEncryption: len(api.ExternalTokenEncryption) > 0,
|
||||
codersdk.FeatureExternalProvisionerDaemons: true,
|
||||
codersdk.FeatureAdvancedTemplateScheduling: true,
|
||||
// FeatureTemplateAutostopRequirement depends on
|
||||
@ -615,6 +647,16 @@ func (api *API) updateEntitlements(ctx context.Context) error {
|
||||
}
|
||||
}
|
||||
|
||||
// External token encryption is soft-enforced
|
||||
featureExternalTokenEncryption := entitlements.Features[codersdk.FeatureExternalTokenEncryption]
|
||||
featureExternalTokenEncryption.Enabled = len(api.ExternalTokenEncryption) > 0
|
||||
if featureExternalTokenEncryption.Enabled && featureExternalTokenEncryption.Entitlement != codersdk.EntitlementEntitled {
|
||||
msg := fmt.Sprintf("%s is enabled (due to setting external token encryption keys) but your license is not entitled to this feature.", codersdk.FeatureExternalTokenEncryption.Humanize())
|
||||
api.Logger.Warn(ctx, msg)
|
||||
entitlements.Warnings = append(entitlements.Warnings, msg)
|
||||
}
|
||||
entitlements.Features[codersdk.FeatureExternalTokenEncryption] = featureExternalTokenEncryption
|
||||
|
||||
api.entitlementsMu.Lock()
|
||||
defer api.entitlementsMu.Unlock()
|
||||
api.entitlements = entitlements
|
||||
|
Reference in New Issue
Block a user