mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: report SCIM configuration on Deployment (#26628)
Adds two nullable booleans to `telemetry.Deployment`: - `SCIMEnabled`: `true` when `CODER_SCIM_AUTH_HEADER` is set. - `SCIMUseLegacy`: `true` when `CODER_SCIM_USE_LEGACY` is set. Both mirror `Deployment.IDPOrgSync`: nullable for backward compatibility, and report configuration state rather than license entitlement (#16323). Lives on `Deployment` rather than `Snapshot` so the existing `bqDeployment` table on `coder/coder-telemetry-server` gets two columns instead of a new table. `SCIMAPIKey` is annotated as a secret and is scrubbed by `WithoutSecrets` before the config reaches telemetry, so `DeploymentConfig.SCIMAPIKey` is always empty in production. The booleans are pre-computed from the pre-scrub `DeploymentValues` in `cli/server.go` and passed in via `telemetry.Options.SCIMEnabled` / `SCIMUseLegacy`. Pairs with [coder/coder-telemetry-server#43](https://github.com/coder/coder-telemetry-server/pull/43), which adds the matching `bqDeployment` columns and the manual BigQuery `ALTER TABLE` step. --- Generated by Coder Agents on behalf of @Emyrk.
This commit is contained in:
@@ -988,6 +988,10 @@ func (r *RootCmd) Server(newAPI func(context.Context, *coderd.Options) (*coderd.
|
||||
URL: vals.Telemetry.URL.Value(),
|
||||
Tunnel: tunnel != nil,
|
||||
DeploymentConfig: deploymentConfigWithoutSecrets,
|
||||
// SCIMAPIKey is a secret and is scrubbed by WithoutSecrets above,
|
||||
// so we derive SCIMEnabled from vals (pre-scrub) instead.
|
||||
SCIMEnabled: vals.SCIMAPIKey != "",
|
||||
SCIMUseLegacy: vals.UseLegacySCIM.Value(),
|
||||
ParseLicenseJWT: func(lic *telemetry.License) error {
|
||||
// This will be nil when running in AGPL-only mode.
|
||||
if options.ParseLicenseClaims == nil {
|
||||
|
||||
@@ -61,6 +61,16 @@ type Options struct {
|
||||
BuiltinPostgres bool
|
||||
Tunnel bool
|
||||
|
||||
// SCIMEnabled is true when CODER_SCIM_AUTH_HEADER is set on the server.
|
||||
// Must be derived from the pre-WithoutSecrets DeploymentValues because the
|
||||
// SCIM API key is annotated as a secret and is cleared before the config
|
||||
// is handed to the telemetry reporter.
|
||||
SCIMEnabled bool
|
||||
// SCIMUseLegacy is true when the legacy SCIM handler is selected via
|
||||
// CODER_SCIM_USE_LEGACY. Not secret-scrubbed, but accepted alongside
|
||||
// SCIMEnabled so both come from the same source.
|
||||
SCIMUseLegacy bool
|
||||
|
||||
SnapshotFrequency time.Duration
|
||||
ParseLicenseJWT func(lic *License) error
|
||||
}
|
||||
@@ -332,6 +342,9 @@ func (r *remoteReporter) deployment() error {
|
||||
r.options.Logger.Debug(r.ctx, "check IDP org sync", slog.Error(err))
|
||||
}
|
||||
|
||||
scimEnabled := r.options.SCIMEnabled
|
||||
scimUseLegacy := r.options.SCIMUseLegacy
|
||||
|
||||
data, err := json.Marshal(&Deployment{
|
||||
ID: r.options.DeploymentID,
|
||||
Architecture: sysInfo.Architecture,
|
||||
@@ -352,6 +365,8 @@ func (r *remoteReporter) deployment() error {
|
||||
StartedAt: r.startedAt,
|
||||
ShutdownAt: r.shutdownAt,
|
||||
IDPOrgSync: &idpOrgSync,
|
||||
SCIMEnabled: &scimEnabled,
|
||||
SCIMUseLegacy: &scimUseLegacy,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("marshal deployment: %w", err)
|
||||
@@ -1636,6 +1651,15 @@ type Deployment struct {
|
||||
// While IDPOrgSync will always be set, it's nullable to make
|
||||
// the struct backwards compatible with older coder versions.
|
||||
IDPOrgSync *bool `json:"idp_org_sync"`
|
||||
// SCIMEnabled is true when CODER_SCIM_AUTH_HEADER is set on the deployment.
|
||||
// Reports configuration state, not license entitlement. Nullable so older
|
||||
// Coder versions that do not emit the field decode as nil.
|
||||
SCIMEnabled *bool `json:"scim_enabled"`
|
||||
// SCIMUseLegacy is true when the legacy SCIM handler is selected via
|
||||
// CODER_SCIM_USE_LEGACY instead of the SCIM 2.0 handler in
|
||||
// enterprise/coderd/scim. Nullable for the same backward compatibility
|
||||
// reason as SCIMEnabled.
|
||||
SCIMUseLegacy *bool `json:"scim_use_legacy"`
|
||||
}
|
||||
|
||||
type APIKey struct {
|
||||
|
||||
@@ -585,6 +585,46 @@ func TestTelemetry(t *testing.T) {
|
||||
deployment, _ = collectSnapshot(ctx, t, db, nil)
|
||||
require.True(t, *deployment.IDPOrgSync)
|
||||
})
|
||||
t.Run("SCIM", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
ctx := testutil.Context(t, testutil.WaitMedium)
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
// 1. Default Options: both flags false (and reported as such).
|
||||
deployment, _ := collectSnapshot(ctx, t, db, nil)
|
||||
require.NotNil(t, deployment.SCIMEnabled)
|
||||
require.False(t, *deployment.SCIMEnabled)
|
||||
require.NotNil(t, deployment.SCIMUseLegacy)
|
||||
require.False(t, *deployment.SCIMUseLegacy)
|
||||
|
||||
// 2. Both Options flags true: both reported true.
|
||||
deployment, _ = collectSnapshot(ctx, t, db, func(opts telemetry.Options) telemetry.Options {
|
||||
opts.SCIMEnabled = true
|
||||
opts.SCIMUseLegacy = true
|
||||
return opts
|
||||
})
|
||||
require.True(t, *deployment.SCIMEnabled)
|
||||
require.True(t, *deployment.SCIMUseLegacy)
|
||||
|
||||
// 3. Enabled only: enabled true, legacy false.
|
||||
deployment, _ = collectSnapshot(ctx, t, db, func(opts telemetry.Options) telemetry.Options {
|
||||
opts.SCIMEnabled = true
|
||||
return opts
|
||||
})
|
||||
require.True(t, *deployment.SCIMEnabled)
|
||||
require.False(t, *deployment.SCIMUseLegacy)
|
||||
|
||||
// 4. The reporter never reads DeploymentConfig.SCIMAPIKey directly:
|
||||
// even if a non-empty key sneaks through (it would not in production
|
||||
// because of WithoutSecrets), SCIMEnabled reflects only Options.
|
||||
deployment, _ = collectSnapshot(ctx, t, db, func(opts telemetry.Options) telemetry.Options {
|
||||
opts.DeploymentConfig = &codersdk.DeploymentValues{
|
||||
SCIMAPIKey: "a-secret-bearer-token",
|
||||
}
|
||||
return opts
|
||||
})
|
||||
require.False(t, *deployment.SCIMEnabled)
|
||||
})
|
||||
}
|
||||
|
||||
// nolint:paralleltest
|
||||
|
||||
Reference in New Issue
Block a user