mirror of
https://github.com/coder/coder.git
synced 2025-07-03 16:13:58 +00:00
feat: enable key rotation (#15066)
This PR contains the remaining logic necessary to hook up key rotation to the product.
This commit is contained in:
@ -40,6 +40,7 @@ import (
|
||||
"github.com/coder/quartz"
|
||||
"github.com/coder/serpent"
|
||||
|
||||
"github.com/coder/coder/v2/coderd/cryptokeys"
|
||||
"github.com/coder/coder/v2/coderd/entitlements"
|
||||
"github.com/coder/coder/v2/coderd/idpsync"
|
||||
"github.com/coder/coder/v2/coderd/runtimeconfig"
|
||||
@ -185,9 +186,6 @@ type Options struct {
|
||||
TemplateScheduleStore *atomic.Pointer[schedule.TemplateScheduleStore]
|
||||
UserQuietHoursScheduleStore *atomic.Pointer[schedule.UserQuietHoursScheduleStore]
|
||||
AccessControlStore *atomic.Pointer[dbauthz.AccessControlStore]
|
||||
// AppSecurityKey is the crypto key used to sign and encrypt tokens related to
|
||||
// workspace applications. It consists of both a signing and encryption key.
|
||||
AppSecurityKey workspaceapps.SecurityKey
|
||||
// CoordinatorResumeTokenProvider is used to provide and validate resume
|
||||
// tokens issued by and passed to the coordinator DRPC API.
|
||||
CoordinatorResumeTokenProvider tailnet.ResumeTokenProvider
|
||||
@ -251,6 +249,12 @@ type Options struct {
|
||||
|
||||
// OneTimePasscodeValidityPeriod specifies how long a one time passcode should be valid for.
|
||||
OneTimePasscodeValidityPeriod time.Duration
|
||||
|
||||
// Keycaches
|
||||
AppSigningKeyCache cryptokeys.SigningKeycache
|
||||
AppEncryptionKeyCache cryptokeys.EncryptionKeycache
|
||||
OIDCConvertKeyCache cryptokeys.SigningKeycache
|
||||
Clock quartz.Clock
|
||||
}
|
||||
|
||||
// @title Coder API
|
||||
@ -352,6 +356,9 @@ func New(options *Options) *API {
|
||||
if options.PrometheusRegistry == nil {
|
||||
options.PrometheusRegistry = prometheus.NewRegistry()
|
||||
}
|
||||
if options.Clock == nil {
|
||||
options.Clock = quartz.NewReal()
|
||||
}
|
||||
if options.DERPServer == nil && options.DeploymentValues.DERP.Server.Enable {
|
||||
options.DERPServer = derp.NewServer(key.NewNode(), tailnet.Logger(options.Logger.Named("derp")))
|
||||
}
|
||||
@ -444,6 +451,49 @@ func New(options *Options) *API {
|
||||
if err != nil {
|
||||
panic(xerrors.Errorf("get deployment ID: %w", err))
|
||||
}
|
||||
|
||||
fetcher := &cryptokeys.DBFetcher{
|
||||
DB: options.Database,
|
||||
}
|
||||
|
||||
if options.OIDCConvertKeyCache == nil {
|
||||
options.OIDCConvertKeyCache, err = cryptokeys.NewSigningCache(ctx,
|
||||
options.Logger.Named("oidc_convert_keycache"),
|
||||
fetcher,
|
||||
codersdk.CryptoKeyFeatureOIDCConvert,
|
||||
)
|
||||
if err != nil {
|
||||
options.Logger.Critical(ctx, "failed to properly instantiate oidc convert signing cache", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if options.AppSigningKeyCache == nil {
|
||||
options.AppSigningKeyCache, err = cryptokeys.NewSigningCache(ctx,
|
||||
options.Logger.Named("app_signing_keycache"),
|
||||
fetcher,
|
||||
codersdk.CryptoKeyFeatureWorkspaceAppsToken,
|
||||
)
|
||||
if err != nil {
|
||||
options.Logger.Critical(ctx, "failed to properly instantiate app signing key cache", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
if options.AppEncryptionKeyCache == nil {
|
||||
options.AppEncryptionKeyCache, err = cryptokeys.NewEncryptionCache(ctx,
|
||||
options.Logger,
|
||||
fetcher,
|
||||
codersdk.CryptoKeyFeatureWorkspaceAppsAPIKey,
|
||||
)
|
||||
if err != nil {
|
||||
options.Logger.Critical(ctx, "failed to properly instantiate app encryption key cache", slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
// Start a background process that rotates keys. We intentionally start this after the caches
|
||||
// are created to force initial requests for a key to populate the caches. This helps catch
|
||||
// bugs that may only occur when a key isn't precached in tests and the latency cost is minimal.
|
||||
cryptokeys.StartRotator(ctx, options.Logger, options.Database)
|
||||
|
||||
api := &API{
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
@ -464,7 +514,7 @@ func New(options *Options) *API {
|
||||
options.DeploymentValues,
|
||||
oauthConfigs,
|
||||
options.AgentInactiveDisconnectTimeout,
|
||||
options.AppSecurityKey,
|
||||
options.AppSigningKeyCache,
|
||||
),
|
||||
metricsCache: metricsCache,
|
||||
Auditor: atomic.Pointer[audit.Auditor]{},
|
||||
@ -606,7 +656,7 @@ func New(options *Options) *API {
|
||||
ResumeTokenProvider: api.Options.CoordinatorResumeTokenProvider,
|
||||
})
|
||||
if err != nil {
|
||||
api.Logger.Fatal(api.ctx, "failed to initialize tailnet client service", slog.Error(err))
|
||||
api.Logger.Fatal(context.Background(), "failed to initialize tailnet client service", slog.Error(err))
|
||||
}
|
||||
|
||||
api.statsReporter = workspacestats.NewReporter(workspacestats.ReporterOptions{
|
||||
@ -628,9 +678,6 @@ func New(options *Options) *API {
|
||||
options.WorkspaceAppsStatsCollectorOptions.Reporter = api.statsReporter
|
||||
}
|
||||
|
||||
if options.AppSecurityKey.IsZero() {
|
||||
api.Logger.Fatal(api.ctx, "app security key cannot be zero")
|
||||
}
|
||||
api.workspaceAppServer = &workspaceapps.Server{
|
||||
Logger: workspaceAppsLogger,
|
||||
|
||||
@ -642,11 +689,11 @@ func New(options *Options) *API {
|
||||
|
||||
SignedTokenProvider: api.WorkspaceAppsProvider,
|
||||
AgentProvider: api.agentProvider,
|
||||
AppSecurityKey: options.AppSecurityKey,
|
||||
StatsCollector: workspaceapps.NewStatsCollector(options.WorkspaceAppsStatsCollectorOptions),
|
||||
|
||||
DisablePathApps: options.DeploymentValues.DisablePathApps.Value(),
|
||||
SecureAuthCookie: options.DeploymentValues.SecureAuthCookie.Value(),
|
||||
DisablePathApps: options.DeploymentValues.DisablePathApps.Value(),
|
||||
SecureAuthCookie: options.DeploymentValues.SecureAuthCookie.Value(),
|
||||
APIKeyEncryptionKeycache: options.AppEncryptionKeyCache,
|
||||
}
|
||||
|
||||
apiKeyMiddleware := httpmw.ExtractAPIKeyMW(httpmw.ExtractAPIKeyConfig{
|
||||
@ -1434,6 +1481,9 @@ func (api *API) Close() error {
|
||||
_ = api.agentProvider.Close()
|
||||
_ = api.statsReporter.Close()
|
||||
_ = api.NetworkTelemetryBatcher.Close()
|
||||
_ = api.OIDCConvertKeyCache.Close()
|
||||
_ = api.AppSigningKeyCache.Close()
|
||||
_ = api.AppEncryptionKeyCache.Close()
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user