From 44e740c34282b7f97ef53aba650cf7910e31a5b3 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 09:53:33 -0400 Subject: [PATCH 1/7] feat(workbench): add Audit Database support Adds spec.workbench.experimentalFeatures.auditDatabaseEnabled, which provisions a second Postgres database (distinct from the internal database) via the same EnsureDatabaseExists/PostgresDatabase machinery already used for the internal database, and renders audit-database.conf pointing Workbench at it. Also sets package-audit=1 in rserver.conf. The audit role's password is resolved server-side (via the existing product.FetchSecret helper, keyed by a new "dev-audit-db-password" Site secret entry) and written directly into audit-database.conf's Password field, since that file -- like database.conf -- is rendered into a Kubernetes Secret rather than a ConfigMap. A dedicated role/password is required because WORKBENCH_POSTGRES_PASSWORD is shared between database.conf and audit-database.conf and cannot carry two different passwords for two different roles. workbench-api-admin-enabled/-super-admin-enabled need no changes here: they're already set whenever ApiSettings.Enabled is true. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html --- api/core/v1beta1/site_types.go | 8 ++++ api/core/v1beta1/workbench_config.go | 22 ++++++++++ api/core/v1beta1/workbench_config_test.go | 41 ++++++++++++++++++- api/core/v1beta1/workbench_types.go | 6 +++ api/core/v1beta1/zz_generated.deepcopy.go | 20 +++++++++ config/crd/bases/core.posit.team_sites.yaml | 14 +++++++ .../bases/core.posit.team_workbenches.yaml | 38 +++++++++++++++++ .../core/site_controller_workbench.go | 5 +++ internal/controller/core/workbench.go | 37 +++++++++++++++++ .../crdapply/bases/core.posit.team_sites.yaml | 14 +++++++ .../bases/core.posit.team_workbenches.yaml | 38 +++++++++++++++++ 11 files changed, 242 insertions(+), 1 deletion(-) diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index 1373ffc..aeb6d77 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -632,6 +632,14 @@ type InternalWorkbenchExperimentalFeatures struct { // ForceAdminUiEnabled forces the configuration manager UI to be enabled even when Workbench has it disabled // by default when running on Kubernetes ForceAdminUiEnabled bool `json:"forceAdminUiEnabled,omitempty"` + + // AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, + // distinct from the internal database, that stores historical session and package usage + // data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires + // the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set + // whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + // See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + AuditDatabaseEnabled bool `json:"auditDatabaseEnabled,omitempty"` } type InternalChronicleSpec struct { diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index 24de445..d89013e 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -30,6 +30,7 @@ type WorkbenchSecretConfig struct { type WorkbenchSecretIniConfig struct { Database *WorkbenchDatabaseConfig `json:"database.conf,omitempty"` + AuditDatabase *WorkbenchAuditDatabaseConfig `json:"audit-database.conf,omitempty"` OpenidClientSecret *WorkbenchOpenidClientSecret `json:"openid-client-secret,omitempty"` Databricks map[string]*WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` } @@ -909,6 +910,22 @@ type WorkbenchDatabaseConfig struct { Password string `json:"password,omitempty"` } +// WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike +// WorkbenchDatabaseConfig (the internal database), Password is populated directly +// here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var +// applies to both database.conf and audit-database.conf identically, so it cannot +// carry two different passwords for two distinct database roles. +// See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html +type WorkbenchAuditDatabaseConfig struct { + Provider WorkbenchDatabaseProvider `json:"provider,omitempty"` + Database string `json:"database,omitempty"` + Port string `json:"port,omitempty"` + Host string `json:"host,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + AutoCreate string `json:"auto-create,omitempty"` +} + type WorkbenchVsCodeConfig struct { Enabled int `json:"enabled,omitempty"` Exe string `json:"exe,omitempty"` @@ -1091,6 +1108,11 @@ type WorkbenchRServerConfig struct { WorkbenchApiAdminEnabled int `json:"workbench-api-admin-enabled,omitempty"` WorkbenchApiSuperAdminEnabled int `json:"workbench-api-super-admin-enabled,omitempty"` ForceAdminUiEnabled int `json:"force-admin-ui-enabled,omitempty"` + // PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, + // defaults to 0. Also requires the Advanced license tier, audit-database.conf to be + // configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the + // calling token's scope. + PackageAudit int `json:"package-audit,omitempty"` // Audited Jobs Configuration // See: https://docs.posit.co/ide/server-pro/admin/auditing_and_monitoring/audited_workbench_jobs.html AuditedJobs *int `json:"audited-jobs,omitempty"` diff --git a/api/core/v1beta1/workbench_config_test.go b/api/core/v1beta1/workbench_config_test.go index 7f3f638..80265c9 100644 --- a/api/core/v1beta1/workbench_config_test.go +++ b/api/core/v1beta1/workbench_config_test.go @@ -22,6 +22,14 @@ func TestWorkbenchSecretConfig_GenerateSecretData(t *testing.T) { Host: "myhost.com", Username: "user", }, + AuditDatabase: &WorkbenchAuditDatabaseConfig{ + Provider: WorkbenchDatabaseProviderPostgres, + Database: "chicken_audit", + Port: "5432", + Host: "myhost.com", + Username: "chicken_audit", + Password: "audit-secret", + }, OpenidClientSecret: &WorkbenchOpenidClientSecret{ ClientId: "your-client-id-test", }, @@ -40,9 +48,15 @@ func TestWorkbenchSecretConfig_GenerateSecretData(t *testing.T) { require.Contains(t, res["database.conf"], "port=5432") require.Contains(t, res["database.conf"], "host=myhost.com") require.Contains(t, res["database.conf"], "username=user") + require.Contains(t, res["audit-database.conf"], "provider=postgresql") + require.Contains(t, res["audit-database.conf"], "database=chicken_audit") + require.Contains(t, res["audit-database.conf"], "port=5432") + require.Contains(t, res["audit-database.conf"], "host=myhost.com") + require.Contains(t, res["audit-database.conf"], "username=chicken_audit") + require.Contains(t, res["audit-database.conf"], "password=audit-secret") require.Contains(t, res["openid-client-secret"], "client-id=your-client-id-test") require.Contains(t, res["databricks.conf"], "name=posit-test") - require.Len(t, res, 3) + require.Len(t, res, 4) } func TestWorkbenchConfig_GenerateConfigmap(t *testing.T) { @@ -1199,3 +1213,28 @@ func TestWorkbenchConfig_ForceAdminUiEnabled(t *testing.T) { require.Nil(t, err) require.Contains(t, res["rserver.conf"], "force-admin-ui-enabled=0\n") } + +// TestWorkbenchConfig_PackageAudit tests package-audit behavior +func TestWorkbenchConfig_PackageAudit(t *testing.T) { + wbEnabled := WorkbenchConfig{ + WorkbenchIniConfig: WorkbenchIniConfig{ + RServer: &WorkbenchRServerConfig{ + PackageAudit: 1, + }, + }, + } + + res, err := wbEnabled.GenerateConfigmap() + require.Nil(t, err) + require.Contains(t, res["rserver.conf"], "package-audit=1\n") + + wbDisabled := WorkbenchConfig{ + WorkbenchIniConfig: WorkbenchIniConfig{ + RServer: &WorkbenchRServerConfig{}, + }, + } + + res, err = wbDisabled.GenerateConfigmap() + require.Nil(t, err) + require.Contains(t, res["rserver.conf"], "package-audit=0\n") +} diff --git a/api/core/v1beta1/workbench_types.go b/api/core/v1beta1/workbench_types.go index 51cb0b0..fb413fd 100644 --- a/api/core/v1beta1/workbench_types.go +++ b/api/core/v1beta1/workbench_types.go @@ -110,6 +110,12 @@ type WorkbenchSpec struct { // MainDatabaseCredentialSecret configures the secret used for storing the main database credentials MainDatabaseCredentialSecret SecretConfig `json:"mainDatabaseCredentialSecret,omitempty"` + // AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal + // database) and role, then renders audit-database.conf pointing Workbench at it. Requires + // the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. + // See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + AuditDatabaseEnabled bool `json:"auditDatabaseEnabled,omitempty"` + Replicas int `json:"replicas,omitempty"` // DsnSecret is the name of the secret that contains the DSN to include with all Workbench sessions diff --git a/api/core/v1beta1/zz_generated.deepcopy.go b/api/core/v1beta1/zz_generated.deepcopy.go index 633b0c4..83aeffc 100644 --- a/api/core/v1beta1/zz_generated.deepcopy.go +++ b/api/core/v1beta1/zz_generated.deepcopy.go @@ -2670,6 +2670,21 @@ func (in *Workbench) DeepCopyObject() runtime.Object { return nil } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *WorkbenchAuditDatabaseConfig) DeepCopyInto(out *WorkbenchAuditDatabaseConfig) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new WorkbenchAuditDatabaseConfig. +func (in *WorkbenchAuditDatabaseConfig) DeepCopy() *WorkbenchAuditDatabaseConfig { + if in == nil { + return nil + } + out := new(WorkbenchAuditDatabaseConfig) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WorkbenchConfig) DeepCopyInto(out *WorkbenchConfig) { *out = *in @@ -3271,6 +3286,11 @@ func (in *WorkbenchSecretIniConfig) DeepCopyInto(out *WorkbenchSecretIniConfig) *out = new(WorkbenchDatabaseConfig) **out = **in } + if in.AuditDatabase != nil { + in, out := &in.AuditDatabase, &out.AuditDatabase + *out = new(WorkbenchAuditDatabaseConfig) + **out = **in + } if in.OpenidClientSecret != nil { in, out := &in.OpenidClientSecret, &out.OpenidClientSecret *out = new(WorkbenchOpenidClientSecret) diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 5296908..86155a3 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -1884,6 +1884,20 @@ spec: description: ExperimentalFeatures allows enabling miscellaneous experimental features for workbench properties: + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, + distinct from the internal database, that stores historical session and package usage + data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires + the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set + whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean + chronicleBuiltinEnabled: + description: |- + ChronicleBuiltinEnabled enables Workbench's embedded Chronicle observability service. + Sets chronicle-enabled=1 in rserver.conf. Requires metrics-enabled=1, which is always set by the operator. + type: boolean chronicleSidecarProductApiKeyEnabled: description: |- ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index 79839b1..344306a 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -106,6 +106,13 @@ spec: type: string type: object type: array + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal + database) and role, then renders audit-database.conf pointing Workbench at it. Requires + the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean auth: properties: administratorRoleMapping: @@ -461,6 +468,13 @@ spec: type: integer metrics-port: type: integer + package-audit: + description: |- + PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, + defaults to 0. Also requires the Advanced license tier, audit-database.conf to be + configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the + calling token's scope. + type: integer secure-cookie-key-file: type: string server-health-check-enabled: @@ -975,6 +989,30 @@ spec: properties: workbench-secret-ini-config: properties: + audit-database.conf: + description: |- + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike + WorkbenchDatabaseConfig (the internal database), Password is populated directly + here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var + applies to both database.conf and audit-database.conf identically, so it cannot + carry two different passwords for two distinct database roles. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + properties: + auto-create: + type: string + database: + type: string + host: + type: string + password: + type: string + port: + type: string + provider: + type: string + username: + type: string + type: object database.conf: properties: database: diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index 892ee13..ba00ee5 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -388,6 +388,11 @@ func (r *SiteReconciler) reconcileWorkbench( targetWorkbench.Spec.Config.RServer.ForceAdminUiEnabled = 1 } + if site.Spec.Workbench.ExperimentalFeatures.AuditDatabaseEnabled { + targetWorkbench.Spec.AuditDatabaseEnabled = true + targetWorkbench.Spec.Config.RServer.PackageAudit = 1 + } + if site.Spec.Workbench.ExperimentalFeatures.ReadinessProbePath != nil { targetWorkbench.Spec.ReadinessProbePath = site.Spec.Workbench.ExperimentalFeatures.ReadinessProbePath } diff --git a/internal/controller/core/workbench.go b/internal/controller/core/workbench.go index b0bca23..083dda6 100644 --- a/internal/controller/core/workbench.go +++ b/internal/controller/core/workbench.go @@ -179,6 +179,43 @@ func (r *WorkbenchReconciler) ReconcileWorkbench(ctx context.Context, req ctrl.R // FYI: Password is set via env var in the CreateSecretVolumeFactory } + // Audit Database: a second, distinct database + role from the internal database above. + // Its own role (rather than reusing dbName) is required because WORKBENCH_POSTGRES_PASSWORD + // is shared by database.conf and audit-database.conf, so the two cannot carry different + // passwords for two different roles — the audit role's password is instead resolved here and + // written directly into audit-database.conf's Password field, which is safe because this file + // is rendered into a Kubernetes Secret, not a ConfigMap. + if w.Spec.AuditDatabaseEnabled { + auditComponentName := fmt.Sprintf("%s-audit", w.ComponentName()) + auditSecretKey := "dev-audit-db-password" + if err := db.EnsureDatabaseExists(ctx, r, req, w, w.Spec.DatabaseConfig, auditComponentName, "", []string{}, w.Spec.Secret, w.Spec.WorkloadSecret, w.Spec.MainDatabaseCredentialSecret, auditSecretKey); err != nil { + l.Error(err, "error creating database", "database", auditComponentName) + if patchErr := status.PatchErrorStatus(ctx, r.Status(), w, patchBase, &w.Status.Conditions, w.Generation, err); patchErr != nil { + l.Error(patchErr, "Error patching error status") + } + return ctrl.Result{}, err + } + + auditDbName := invalidCharacters.ReplaceAllString(auditComponentName, "_") + auditPassword, err := product.FetchSecret(ctx, r, req, w.Spec.Secret.Type, w.Spec.Secret.VaultName, auditSecretKey) + if err != nil { + l.Error(err, "error fetching audit database password") + if patchErr := status.PatchErrorStatus(ctx, r.Status(), w, patchBase, &w.Status.Conditions, w.Generation, err); patchErr != nil { + l.Error(patchErr, "Error patching error status") + } + return ctrl.Result{}, err + } + + w.Spec.SecretConfig.AuditDatabase = &positcov1beta1.WorkbenchAuditDatabaseConfig{ + Provider: positcov1beta1.WorkbenchDatabaseProviderPostgres, + Database: auditDbName, + Port: justPort, + Host: justHost, + Username: auditDbName, + Password: auditPassword, + } + } + // fetch azure secret, if databricks is involved if err := r.FetchAndSetClientSecretForAzureDatabricks(ctx, req, w); err != nil { l.Error(err, "error fetching client secret for databricks azure. Not fatal") diff --git a/internal/crdapply/bases/core.posit.team_sites.yaml b/internal/crdapply/bases/core.posit.team_sites.yaml index 5296908..86155a3 100644 --- a/internal/crdapply/bases/core.posit.team_sites.yaml +++ b/internal/crdapply/bases/core.posit.team_sites.yaml @@ -1884,6 +1884,20 @@ spec: description: ExperimentalFeatures allows enabling miscellaneous experimental features for workbench properties: + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, + distinct from the internal database, that stores historical session and package usage + data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires + the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set + whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean + chronicleBuiltinEnabled: + description: |- + ChronicleBuiltinEnabled enables Workbench's embedded Chronicle observability service. + Sets chronicle-enabled=1 in rserver.conf. Requires metrics-enabled=1, which is always set by the operator. + type: boolean chronicleSidecarProductApiKeyEnabled: description: |- ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and diff --git a/internal/crdapply/bases/core.posit.team_workbenches.yaml b/internal/crdapply/bases/core.posit.team_workbenches.yaml index 79839b1..344306a 100644 --- a/internal/crdapply/bases/core.posit.team_workbenches.yaml +++ b/internal/crdapply/bases/core.posit.team_workbenches.yaml @@ -106,6 +106,13 @@ spec: type: string type: object type: array + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal + database) and role, then renders audit-database.conf pointing Workbench at it. Requires + the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean auth: properties: administratorRoleMapping: @@ -461,6 +468,13 @@ spec: type: integer metrics-port: type: integer + package-audit: + description: |- + PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, + defaults to 0. Also requires the Advanced license tier, audit-database.conf to be + configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the + calling token's scope. + type: integer secure-cookie-key-file: type: string server-health-check-enabled: @@ -975,6 +989,30 @@ spec: properties: workbench-secret-ini-config: properties: + audit-database.conf: + description: |- + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike + WorkbenchDatabaseConfig (the internal database), Password is populated directly + here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var + applies to both database.conf and audit-database.conf identically, so it cannot + carry two different passwords for two distinct database roles. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + properties: + auto-create: + type: string + database: + type: string + host: + type: string + password: + type: string + port: + type: string + provider: + type: string + username: + type: string + type: object database.conf: properties: database: From 24968a0941b985382521d9f2590bb96f73c2ed60 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 10:13:24 -0400 Subject: [PATCH 2/7] address review feedback on Audit Database support - Truncate the audit component name to Postgres's 63-byte identifier limit before deriving the database/role name from it, so a long Workbench name plus the "-audit" suffix can't silently overflow. - Remove WorkbenchAuditDatabaseConfig.AutoCreate: dead field, never set by the controller (the operator always provisions the audit database itself via EnsureDatabaseExists). - Add TestSiteAuditDatabaseEnabled / TestSiteAuditDatabaseDisabledByDefault covering the Site -> Workbench propagation path, mirroring the existing AuditedJobs/ForceAdminUiEnabled test pattern. - Regenerate CRDs: also picks up a stale chronicleBuiltinEnabled entry in core.posit.team_sites.yaml left over from the earlier branch rebase (removed from the Go types, but not re-synced to the CRD YAML at the time). --- api/core/v1beta1/workbench_config.go | 13 ++++--- config/crd/bases/core.posit.team_sites.yaml | 5 --- .../bases/core.posit.team_workbenches.yaml | 2 -- internal/controller/core/site_test.go | 35 +++++++++++++++++++ internal/controller/core/workbench.go | 19 +++++++++- .../crdapply/bases/core.posit.team_sites.yaml | 5 --- .../bases/core.posit.team_workbenches.yaml | 2 -- 7 files changed, 59 insertions(+), 22 deletions(-) diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index d89013e..953ea88 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -917,13 +917,12 @@ type WorkbenchDatabaseConfig struct { // carry two different passwords for two distinct database roles. // See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html type WorkbenchAuditDatabaseConfig struct { - Provider WorkbenchDatabaseProvider `json:"provider,omitempty"` - Database string `json:"database,omitempty"` - Port string `json:"port,omitempty"` - Host string `json:"host,omitempty"` - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - AutoCreate string `json:"auto-create,omitempty"` + Provider WorkbenchDatabaseProvider `json:"provider,omitempty"` + Database string `json:"database,omitempty"` + Port string `json:"port,omitempty"` + Host string `json:"host,omitempty"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` } type WorkbenchVsCodeConfig struct { diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 86155a3..7ae0a39 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -1893,11 +1893,6 @@ spec: whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean - chronicleBuiltinEnabled: - description: |- - ChronicleBuiltinEnabled enables Workbench's embedded Chronicle observability service. - Sets chronicle-enabled=1 in rserver.conf. Requires metrics-enabled=1, which is always set by the operator. - type: boolean chronicleSidecarProductApiKeyEnabled: description: |- ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index 344306a..885fde5 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -998,8 +998,6 @@ spec: carry two different passwords for two distinct database roles. See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html properties: - auto-create: - type: string database: type: string host: diff --git a/internal/controller/core/site_test.go b/internal/controller/core/site_test.go index d55f0b4..361e556 100644 --- a/internal/controller/core/site_test.go +++ b/internal/controller/core/site_test.go @@ -562,6 +562,41 @@ func TestSiteAuditedJobsConfiguration(t *testing.T) { assert.Equal(t, intPtr(0), testWorkbench.Spec.Config.RServer.AuditedJobsDetailsUserDefined) } +func TestSiteAuditDatabaseEnabled(t *testing.T) { + siteName := "audit-database-site" + siteNamespace := "posit-team" + + site := defaultSite(siteName) + site.Spec.Workbench.ExperimentalFeatures = &v1beta1.InternalWorkbenchExperimentalFeatures{ + AuditDatabaseEnabled: true, + } + + cli, _, err := runFakeSiteReconciler(t, siteNamespace, siteName, site) + assert.Nil(t, err) + + testWorkbench := getWorkbench(t, cli, siteNamespace, siteName) + + assert.True(t, testWorkbench.Spec.AuditDatabaseEnabled) + require.NotNil(t, testWorkbench.Spec.Config.RServer) + assert.Equal(t, 1, testWorkbench.Spec.Config.RServer.PackageAudit) +} + +func TestSiteAuditDatabaseDisabledByDefault(t *testing.T) { + siteName := "audit-database-default-site" + siteNamespace := "posit-team" + + site := defaultSite(siteName) + + cli, _, err := runFakeSiteReconciler(t, siteNamespace, siteName, site) + assert.Nil(t, err) + + testWorkbench := getWorkbench(t, cli, siteNamespace, siteName) + + assert.False(t, testWorkbench.Spec.AuditDatabaseEnabled) + require.NotNil(t, testWorkbench.Spec.Config.RServer) + assert.Equal(t, 0, testWorkbench.Spec.Config.RServer.PackageAudit) +} + func TestSiteAuditedJobsPartialConfiguration(t *testing.T) { siteName := "audited-jobs-partial-site" siteNamespace := "posit-team" diff --git a/internal/controller/core/workbench.go b/internal/controller/core/workbench.go index 083dda6..21f8538 100644 --- a/internal/controller/core/workbench.go +++ b/internal/controller/core/workbench.go @@ -7,6 +7,7 @@ import ( "fmt" "regexp" "strconv" + "strings" "github.com/go-logr/logr" "github.com/pkg/errors" @@ -50,6 +51,22 @@ var invalidCharacters = regexp.MustCompile("[^a-z0-9]") // do not glob, lest we var azureDatabricksRegexp = regexp.MustCompile("azuredatabricks\\.net") +// postgresMaxIdentifierLength is Postgres's NAMEDATALEN-1 limit for unquoted identifiers +// (database and role names). Names longer than this are silently truncated by Postgres +// itself, which could collide with the primary database's own (already-shorter) name. +const postgresMaxIdentifierLength = 63 + +// truncateForPostgresIdentifier bounds name to postgresMaxIdentifierLength, trimming any +// trailing separator left dangling by the cut so it also remains a valid Kubernetes object +// name (name is reused as both a PostgresDatabase CR name and, after sanitization, the +// Postgres database/role name derived from it). +func truncateForPostgresIdentifier(name string) string { + if len(name) <= postgresMaxIdentifierLength { + return name + } + return strings.TrimRight(name[:postgresMaxIdentifierLength], "-_") +} + const defaultWorkbenchReadinessProbePath = "/health-check" const ( @@ -186,7 +203,7 @@ func (r *WorkbenchReconciler) ReconcileWorkbench(ctx context.Context, req ctrl.R // written directly into audit-database.conf's Password field, which is safe because this file // is rendered into a Kubernetes Secret, not a ConfigMap. if w.Spec.AuditDatabaseEnabled { - auditComponentName := fmt.Sprintf("%s-audit", w.ComponentName()) + auditComponentName := truncateForPostgresIdentifier(fmt.Sprintf("%s-audit", w.ComponentName())) auditSecretKey := "dev-audit-db-password" if err := db.EnsureDatabaseExists(ctx, r, req, w, w.Spec.DatabaseConfig, auditComponentName, "", []string{}, w.Spec.Secret, w.Spec.WorkloadSecret, w.Spec.MainDatabaseCredentialSecret, auditSecretKey); err != nil { l.Error(err, "error creating database", "database", auditComponentName) diff --git a/internal/crdapply/bases/core.posit.team_sites.yaml b/internal/crdapply/bases/core.posit.team_sites.yaml index 86155a3..7ae0a39 100644 --- a/internal/crdapply/bases/core.posit.team_sites.yaml +++ b/internal/crdapply/bases/core.posit.team_sites.yaml @@ -1893,11 +1893,6 @@ spec: whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean - chronicleBuiltinEnabled: - description: |- - ChronicleBuiltinEnabled enables Workbench's embedded Chronicle observability service. - Sets chronicle-enabled=1 in rserver.conf. Requires metrics-enabled=1, which is always set by the operator. - type: boolean chronicleSidecarProductApiKeyEnabled: description: |- ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and diff --git a/internal/crdapply/bases/core.posit.team_workbenches.yaml b/internal/crdapply/bases/core.posit.team_workbenches.yaml index 344306a..885fde5 100644 --- a/internal/crdapply/bases/core.posit.team_workbenches.yaml +++ b/internal/crdapply/bases/core.posit.team_workbenches.yaml @@ -998,8 +998,6 @@ spec: carry two different passwords for two distinct database roles. See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html properties: - auto-create: - type: string database: type: string host: From 9b9d651dd81430945e0897ce52e948ce3a2d7d89 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 10:21:37 -0400 Subject: [PATCH 3/7] sync dist/chart Helm CRDs with kustomize make helm-generate regenerates dist/chart/templates/crd/*.yaml from config/crd/bases/*.yaml; the CI "Verify Helm chart is in sync with kustomize" check caught that this hadn't been run after the site/ workbench CRD changes for Audit Database support. --- .../templates/crd/core.posit.team_sites.yaml | 9 +++++ .../crd/core.posit.team_workbenches.yaml | 36 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index 14d65f7..b089c80 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -1905,6 +1905,15 @@ spec: description: ExperimentalFeatures allows enabling miscellaneous experimental features for workbench properties: + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, + distinct from the internal database, that stores historical session and package usage + data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires + the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set + whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean chronicleSidecarProductApiKeyEnabled: description: |- ChronicleSidecarProductApiKeyEnabled assumes the api key for this product has been added to a secret and diff --git a/dist/chart/templates/crd/core.posit.team_workbenches.yaml b/dist/chart/templates/crd/core.posit.team_workbenches.yaml index 1fd5626..67aac32 100755 --- a/dist/chart/templates/crd/core.posit.team_workbenches.yaml +++ b/dist/chart/templates/crd/core.posit.team_workbenches.yaml @@ -127,6 +127,13 @@ spec: type: string type: object type: array + auditDatabaseEnabled: + description: |- + AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal + database) and role, then renders audit-database.conf pointing Workbench at it. Requires + the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html + type: boolean auth: properties: administratorRoleMapping: @@ -482,6 +489,13 @@ spec: type: integer metrics-port: type: integer + package-audit: + description: |- + PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, + defaults to 0. Also requires the Advanced license tier, audit-database.conf to be + configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the + calling token's scope. + type: integer secure-cookie-key-file: type: string server-health-check-enabled: @@ -996,6 +1010,28 @@ spec: properties: workbench-secret-ini-config: properties: + audit-database.conf: + description: |- + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike + WorkbenchDatabaseConfig (the internal database), Password is populated directly + here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var + applies to both database.conf and audit-database.conf identically, so it cannot + carry two different passwords for two distinct database roles. + See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + properties: + database: + type: string + host: + type: string + password: + type: string + port: + type: string + provider: + type: string + username: + type: string + type: object database.conf: properties: database: From ed668aad241bef0ed95f7c9b9f3a5cef84af9f36 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 10:45:20 -0400 Subject: [PATCH 4/7] regenerate client-go applyconfigurations make generate-client (kube_codegen) hadn't been run after the API changes for Audit Database support; CI's "Assert no diff" check caught the drift in client-go/applyconfiguration/. --- .../internalworkbenchexperimentalfeatures.go | 9 +++ .../v1beta1/workbenchauditdatabaseconfig.go | 75 +++++++++++++++++++ .../core/v1beta1/workbenchrserverconfig.go | 9 +++ .../core/v1beta1/workbenchsecretconfig.go | 9 +++ .../core/v1beta1/workbenchsecretiniconfig.go | 9 +++ .../core/v1beta1/workbenchspec.go | 9 +++ client-go/applyconfiguration/utils.go | 2 + 7 files changed, 122 insertions(+) create mode 100644 client-go/applyconfiguration/core/v1beta1/workbenchauditdatabaseconfig.go diff --git a/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go b/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go index 21d0973..61d6383 100644 --- a/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go +++ b/client-go/applyconfiguration/core/v1beta1/internalworkbenchexperimentalfeatures.go @@ -34,6 +34,7 @@ type InternalWorkbenchExperimentalFeaturesApplyConfiguration struct { LauncherEnvPath *string `json:"launcherEnvPath,omitempty"` ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` ForceAdminUiEnabled *bool `json:"forceAdminUiEnabled,omitempty"` + AuditDatabaseEnabled *bool `json:"auditDatabaseEnabled,omitempty"` } // InternalWorkbenchExperimentalFeaturesApplyConfiguration constructs a declarative configuration of the InternalWorkbenchExperimentalFeatures type for use with @@ -217,3 +218,11 @@ func (b *InternalWorkbenchExperimentalFeaturesApplyConfiguration) WithForceAdmin b.ForceAdminUiEnabled = &value return b } + +// WithAuditDatabaseEnabled sets the AuditDatabaseEnabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuditDatabaseEnabled field is set to the value of the last call. +func (b *InternalWorkbenchExperimentalFeaturesApplyConfiguration) WithAuditDatabaseEnabled(value bool) *InternalWorkbenchExperimentalFeaturesApplyConfiguration { + b.AuditDatabaseEnabled = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchauditdatabaseconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchauditdatabaseconfig.go new file mode 100644 index 0000000..548d671 --- /dev/null +++ b/client-go/applyconfiguration/core/v1beta1/workbenchauditdatabaseconfig.go @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +// Copyright (c) 2023-2026 Posit Software, PBC + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v1beta1 + +import ( + corev1beta1 "github.com/posit-dev/team-operator/api/core/v1beta1" +) + +// WorkbenchAuditDatabaseConfigApplyConfiguration represents a declarative configuration of the WorkbenchAuditDatabaseConfig type for use +// with apply. +type WorkbenchAuditDatabaseConfigApplyConfiguration struct { + Provider *corev1beta1.WorkbenchDatabaseProvider `json:"provider,omitempty"` + Database *string `json:"database,omitempty"` + Port *string `json:"port,omitempty"` + Host *string `json:"host,omitempty"` + Username *string `json:"username,omitempty"` + Password *string `json:"password,omitempty"` +} + +// WorkbenchAuditDatabaseConfigApplyConfiguration constructs a declarative configuration of the WorkbenchAuditDatabaseConfig type for use with +// apply. +func WorkbenchAuditDatabaseConfig() *WorkbenchAuditDatabaseConfigApplyConfiguration { + return &WorkbenchAuditDatabaseConfigApplyConfiguration{} +} + +// WithProvider sets the Provider field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Provider field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithProvider(value corev1beta1.WorkbenchDatabaseProvider) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Provider = &value + return b +} + +// WithDatabase sets the Database field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Database field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithDatabase(value string) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Database = &value + return b +} + +// WithPort sets the Port field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Port field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithPort(value string) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Port = &value + return b +} + +// WithHost sets the Host field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Host field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithHost(value string) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Host = &value + return b +} + +// WithUsername sets the Username field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Username field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithUsername(value string) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Username = &value + return b +} + +// WithPassword sets the Password field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the Password field is set to the value of the last call. +func (b *WorkbenchAuditDatabaseConfigApplyConfiguration) WithPassword(value string) *WorkbenchAuditDatabaseConfigApplyConfiguration { + b.Password = &value + return b +} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go index 225cb3d..21a330f 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go @@ -48,6 +48,7 @@ type WorkbenchRServerConfigApplyConfiguration struct { WorkbenchApiAdminEnabled *int `json:"workbench-api-admin-enabled,omitempty"` WorkbenchApiSuperAdminEnabled *int `json:"workbench-api-super-admin-enabled,omitempty"` ForceAdminUiEnabled *int `json:"force-admin-ui-enabled,omitempty"` + PackageAudit *int `json:"package-audit,omitempty"` AuditedJobs *int `json:"audited-jobs,omitempty"` AuditedJobsStoragePath *string `json:"audited-jobs-storage-path,omitempty"` AuditedJobsPrivateKeyPath *string `json:"audited-jobs-private-key-path,omitempty"` @@ -387,6 +388,14 @@ func (b *WorkbenchRServerConfigApplyConfiguration) WithForceAdminUiEnabled(value return b } +// WithPackageAudit sets the PackageAudit field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the PackageAudit field is set to the value of the last call. +func (b *WorkbenchRServerConfigApplyConfiguration) WithPackageAudit(value int) *WorkbenchRServerConfigApplyConfiguration { + b.PackageAudit = &value + return b +} + // WithAuditedJobs sets the AuditedJobs field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AuditedJobs field is set to the value of the last call. diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchsecretconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchsecretconfig.go index 589e881..c72acdf 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchsecretconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchsecretconfig.go @@ -30,6 +30,15 @@ func (b *WorkbenchSecretConfigApplyConfiguration) WithDatabase(value *WorkbenchD return b } +// WithAuditDatabase sets the AuditDatabase field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuditDatabase field is set to the value of the last call. +func (b *WorkbenchSecretConfigApplyConfiguration) WithAuditDatabase(value *WorkbenchAuditDatabaseConfigApplyConfiguration) *WorkbenchSecretConfigApplyConfiguration { + b.ensureWorkbenchSecretIniConfigApplyConfigurationExists() + b.WorkbenchSecretIniConfigApplyConfiguration.AuditDatabase = value + return b +} + // WithOpenidClientSecret sets the OpenidClientSecret field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the OpenidClientSecret field is set to the value of the last call. diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchsecretiniconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchsecretiniconfig.go index 3de039c..62abf02 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchsecretiniconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchsecretiniconfig.go @@ -13,6 +13,7 @@ import ( // with apply. type WorkbenchSecretIniConfigApplyConfiguration struct { Database *WorkbenchDatabaseConfigApplyConfiguration `json:"database.conf,omitempty"` + AuditDatabase *WorkbenchAuditDatabaseConfigApplyConfiguration `json:"audit-database.conf,omitempty"` OpenidClientSecret *WorkbenchOpenidClientSecretApplyConfiguration `json:"openid-client-secret,omitempty"` Databricks map[string]*corev1beta1.WorkbenchDatabricksConfig `json:"databricks.conf,omitempty"` } @@ -31,6 +32,14 @@ func (b *WorkbenchSecretIniConfigApplyConfiguration) WithDatabase(value *Workben return b } +// WithAuditDatabase sets the AuditDatabase field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuditDatabase field is set to the value of the last call. +func (b *WorkbenchSecretIniConfigApplyConfiguration) WithAuditDatabase(value *WorkbenchAuditDatabaseConfigApplyConfiguration) *WorkbenchSecretIniConfigApplyConfiguration { + b.AuditDatabase = value + return b +} + // WithOpenidClientSecret sets the OpenidClientSecret field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the OpenidClientSecret field is set to the value of the last call. diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchspec.go b/client-go/applyconfiguration/core/v1beta1/workbenchspec.go index 82c9072..f20af98 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchspec.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchspec.go @@ -46,6 +46,7 @@ type WorkbenchSpecApplyConfiguration struct { Secret *SecretConfigApplyConfiguration `json:"secret,omitempty"` WorkloadSecret *SecretConfigApplyConfiguration `json:"workloadSecret,omitempty"` MainDatabaseCredentialSecret *SecretConfigApplyConfiguration `json:"mainDatabaseCredentialSecret,omitempty"` + AuditDatabaseEnabled *bool `json:"auditDatabaseEnabled,omitempty"` Replicas *int `json:"replicas,omitempty"` DsnSecret *string `json:"dsnSecret,omitempty"` ChronicleSidecarProductApiKeyEnabled *bool `json:"chronicleSidecarProductApiKeyEnabled,omitempty"` @@ -351,6 +352,14 @@ func (b *WorkbenchSpecApplyConfiguration) WithMainDatabaseCredentialSecret(value return b } +// WithAuditDatabaseEnabled sets the AuditDatabaseEnabled field in the declarative configuration to the given value +// and returns the receiver, so that objects can be built by chaining "With" function invocations. +// If called multiple times, the AuditDatabaseEnabled field is set to the value of the last call. +func (b *WorkbenchSpecApplyConfiguration) WithAuditDatabaseEnabled(value bool) *WorkbenchSpecApplyConfiguration { + b.AuditDatabaseEnabled = &value + return b +} + // WithReplicas sets the Replicas field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the Replicas field is set to the value of the last call. diff --git a/client-go/applyconfiguration/utils.go b/client-go/applyconfiguration/utils.go index 3618f64..e5a4c3b 100644 --- a/client-go/applyconfiguration/utils.go +++ b/client-go/applyconfiguration/utils.go @@ -201,6 +201,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} { return &corev1beta1.VSCodeConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("Workbench"): return &corev1beta1.WorkbenchApplyConfiguration{} + case v1beta1.SchemeGroupVersion.WithKind("WorkbenchAuditDatabaseConfig"): + return &corev1beta1.WorkbenchAuditDatabaseConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("WorkbenchConfig"): return &corev1beta1.WorkbenchConfigApplyConfiguration{} case v1beta1.SchemeGroupVersion.WithKind("WorkbenchDatabaseConfig"): From b290fc256bd0e831539d6a03248c99763ab11deb Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 11:26:10 -0400 Subject: [PATCH 5/7] descope: decouple package-audit from AuditDatabaseEnabled package-audit=1 and "audit database configured" are two independent prerequisites for the Package Audit Read API (see rstudio-pro's wiki/features/package-audit-read-api.qmd) -- not the same feature. AuditDatabaseEnabled provisioning the audit database should not also silently flip on package scanning; other consumers of the audit database (general session/usage history) don't want that side effect. Removes WorkbenchRServerConfig.PackageAudit and the auto-set on AuditDatabaseEnabled. package-audit=1 can be set independently via the existing free-form rserver.conf additionalConfigs escape hatch on consumers that want it -- it doesn't need a dedicated CRD field. --- api/core/v1beta1/site_types.go | 9 +++---- api/core/v1beta1/workbench_config.go | 5 ---- api/core/v1beta1/workbench_config_test.go | 24 ------------------- .../core/v1beta1/workbenchrserverconfig.go | 9 ------- config/crd/bases/core.posit.team_sites.yaml | 9 +++---- .../bases/core.posit.team_workbenches.yaml | 7 ------ .../templates/crd/core.posit.team_sites.yaml | 9 +++---- .../crd/core.posit.team_workbenches.yaml | 7 ------ .../core/site_controller_workbench.go | 1 - internal/controller/core/site_test.go | 4 ---- .../crdapply/bases/core.posit.team_sites.yaml | 9 +++---- .../bases/core.posit.team_workbenches.yaml | 7 ------ 12 files changed, 20 insertions(+), 80 deletions(-) diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index aeb6d77..fd92451 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -634,10 +634,11 @@ type InternalWorkbenchExperimentalFeatures struct { ForceAdminUiEnabled bool `json:"forceAdminUiEnabled,omitempty"` // AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, - // distinct from the internal database, that stores historical session and package usage - // data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires - // the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set - // whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + // distinct from the internal database, that stores historical session and usage data. + // Renders audit-database.conf. This is one of several independent prerequisites for the + // Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the + // Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not + // specific to it -- other consumers of the audit database do not need those. // See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html AuditDatabaseEnabled bool `json:"auditDatabaseEnabled,omitempty"` } diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index 953ea88..4804bbb 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -1107,11 +1107,6 @@ type WorkbenchRServerConfig struct { WorkbenchApiAdminEnabled int `json:"workbench-api-admin-enabled,omitempty"` WorkbenchApiSuperAdminEnabled int `json:"workbench-api-super-admin-enabled,omitempty"` ForceAdminUiEnabled int `json:"force-admin-ui-enabled,omitempty"` - // PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, - // defaults to 0. Also requires the Advanced license tier, audit-database.conf to be - // configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the - // calling token's scope. - PackageAudit int `json:"package-audit,omitempty"` // Audited Jobs Configuration // See: https://docs.posit.co/ide/server-pro/admin/auditing_and_monitoring/audited_workbench_jobs.html AuditedJobs *int `json:"audited-jobs,omitempty"` diff --git a/api/core/v1beta1/workbench_config_test.go b/api/core/v1beta1/workbench_config_test.go index 80265c9..d592e0d 100644 --- a/api/core/v1beta1/workbench_config_test.go +++ b/api/core/v1beta1/workbench_config_test.go @@ -1214,27 +1214,3 @@ func TestWorkbenchConfig_ForceAdminUiEnabled(t *testing.T) { require.Contains(t, res["rserver.conf"], "force-admin-ui-enabled=0\n") } -// TestWorkbenchConfig_PackageAudit tests package-audit behavior -func TestWorkbenchConfig_PackageAudit(t *testing.T) { - wbEnabled := WorkbenchConfig{ - WorkbenchIniConfig: WorkbenchIniConfig{ - RServer: &WorkbenchRServerConfig{ - PackageAudit: 1, - }, - }, - } - - res, err := wbEnabled.GenerateConfigmap() - require.Nil(t, err) - require.Contains(t, res["rserver.conf"], "package-audit=1\n") - - wbDisabled := WorkbenchConfig{ - WorkbenchIniConfig: WorkbenchIniConfig{ - RServer: &WorkbenchRServerConfig{}, - }, - } - - res, err = wbDisabled.GenerateConfigmap() - require.Nil(t, err) - require.Contains(t, res["rserver.conf"], "package-audit=0\n") -} diff --git a/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go b/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go index 21a330f..225cb3d 100644 --- a/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go +++ b/client-go/applyconfiguration/core/v1beta1/workbenchrserverconfig.go @@ -48,7 +48,6 @@ type WorkbenchRServerConfigApplyConfiguration struct { WorkbenchApiAdminEnabled *int `json:"workbench-api-admin-enabled,omitempty"` WorkbenchApiSuperAdminEnabled *int `json:"workbench-api-super-admin-enabled,omitempty"` ForceAdminUiEnabled *int `json:"force-admin-ui-enabled,omitempty"` - PackageAudit *int `json:"package-audit,omitempty"` AuditedJobs *int `json:"audited-jobs,omitempty"` AuditedJobsStoragePath *string `json:"audited-jobs-storage-path,omitempty"` AuditedJobsPrivateKeyPath *string `json:"audited-jobs-private-key-path,omitempty"` @@ -388,14 +387,6 @@ func (b *WorkbenchRServerConfigApplyConfiguration) WithForceAdminUiEnabled(value return b } -// WithPackageAudit sets the PackageAudit field in the declarative configuration to the given value -// and returns the receiver, so that objects can be built by chaining "With" function invocations. -// If called multiple times, the PackageAudit field is set to the value of the last call. -func (b *WorkbenchRServerConfigApplyConfiguration) WithPackageAudit(value int) *WorkbenchRServerConfigApplyConfiguration { - b.PackageAudit = &value - return b -} - // WithAuditedJobs sets the AuditedJobs field in the declarative configuration to the given value // and returns the receiver, so that objects can be built by chaining "With" function invocations. // If called multiple times, the AuditedJobs field is set to the value of the last call. diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 7ae0a39..71a3a6c 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -1887,10 +1887,11 @@ spec: auditDatabaseEnabled: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, - distinct from the internal database, that stores historical session and package usage - data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires - the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set - whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + distinct from the internal database, that stores historical session and usage data. + Renders audit-database.conf. This is one of several independent prerequisites for the + Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the + Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not + specific to it -- other consumers of the audit database do not need those. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index 885fde5..e1a7be7 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -468,13 +468,6 @@ spec: type: integer metrics-port: type: integer - package-audit: - description: |- - PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, - defaults to 0. Also requires the Advanced license tier, audit-database.conf to be - configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the - calling token's scope. - type: integer secure-cookie-key-file: type: string server-health-check-enabled: diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index b089c80..239339e 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -1908,10 +1908,11 @@ spec: auditDatabaseEnabled: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, - distinct from the internal database, that stores historical session and package usage - data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires - the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set - whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + distinct from the internal database, that stores historical session and usage data. + Renders audit-database.conf. This is one of several independent prerequisites for the + Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the + Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not + specific to it -- other consumers of the audit database do not need those. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: diff --git a/dist/chart/templates/crd/core.posit.team_workbenches.yaml b/dist/chart/templates/crd/core.posit.team_workbenches.yaml index 67aac32..d400704 100755 --- a/dist/chart/templates/crd/core.posit.team_workbenches.yaml +++ b/dist/chart/templates/crd/core.posit.team_workbenches.yaml @@ -489,13 +489,6 @@ spec: type: integer metrics-port: type: integer - package-audit: - description: |- - PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, - defaults to 0. Also requires the Advanced license tier, audit-database.conf to be - configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the - calling token's scope. - type: integer secure-cookie-key-file: type: string server-health-check-enabled: diff --git a/internal/controller/core/site_controller_workbench.go b/internal/controller/core/site_controller_workbench.go index ba00ee5..88eae4d 100644 --- a/internal/controller/core/site_controller_workbench.go +++ b/internal/controller/core/site_controller_workbench.go @@ -390,7 +390,6 @@ func (r *SiteReconciler) reconcileWorkbench( if site.Spec.Workbench.ExperimentalFeatures.AuditDatabaseEnabled { targetWorkbench.Spec.AuditDatabaseEnabled = true - targetWorkbench.Spec.Config.RServer.PackageAudit = 1 } if site.Spec.Workbench.ExperimentalFeatures.ReadinessProbePath != nil { diff --git a/internal/controller/core/site_test.go b/internal/controller/core/site_test.go index 361e556..7931056 100644 --- a/internal/controller/core/site_test.go +++ b/internal/controller/core/site_test.go @@ -577,8 +577,6 @@ func TestSiteAuditDatabaseEnabled(t *testing.T) { testWorkbench := getWorkbench(t, cli, siteNamespace, siteName) assert.True(t, testWorkbench.Spec.AuditDatabaseEnabled) - require.NotNil(t, testWorkbench.Spec.Config.RServer) - assert.Equal(t, 1, testWorkbench.Spec.Config.RServer.PackageAudit) } func TestSiteAuditDatabaseDisabledByDefault(t *testing.T) { @@ -593,8 +591,6 @@ func TestSiteAuditDatabaseDisabledByDefault(t *testing.T) { testWorkbench := getWorkbench(t, cli, siteNamespace, siteName) assert.False(t, testWorkbench.Spec.AuditDatabaseEnabled) - require.NotNil(t, testWorkbench.Spec.Config.RServer) - assert.Equal(t, 0, testWorkbench.Spec.Config.RServer.PackageAudit) } func TestSiteAuditedJobsPartialConfiguration(t *testing.T) { diff --git a/internal/crdapply/bases/core.posit.team_sites.yaml b/internal/crdapply/bases/core.posit.team_sites.yaml index 7ae0a39..71a3a6c 100644 --- a/internal/crdapply/bases/core.posit.team_sites.yaml +++ b/internal/crdapply/bases/core.posit.team_sites.yaml @@ -1887,10 +1887,11 @@ spec: auditDatabaseEnabled: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, - distinct from the internal database, that stores historical session and package usage - data. Sets package-audit=1 in rserver.conf and renders audit-database.conf. Also requires - the Advanced license tier and workbench-api-admin-enabled/-super-admin-enabled (already set - whenever ApiSettings.Enabled is true) for the Package Audit Read API to be usable. + distinct from the internal database, that stores historical session and usage data. + Renders audit-database.conf. This is one of several independent prerequisites for the + Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the + Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not + specific to it -- other consumers of the audit database do not need those. See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: diff --git a/internal/crdapply/bases/core.posit.team_workbenches.yaml b/internal/crdapply/bases/core.posit.team_workbenches.yaml index 885fde5..e1a7be7 100644 --- a/internal/crdapply/bases/core.posit.team_workbenches.yaml +++ b/internal/crdapply/bases/core.posit.team_workbenches.yaml @@ -468,13 +468,6 @@ spec: type: integer metrics-port: type: integer - package-audit: - description: |- - PackageAudit enables the Package Audit Read API (POST /api/packages). Hidden option, - defaults to 0. Also requires the Advanced license tier, audit-database.conf to be - configured, and workbench-api-admin-enabled/workbench-api-super-admin-enabled for the - calling token's scope. - type: integer secure-cookie-key-file: type: string server-health-check-enabled: From 6b56788348c71c0302dc9b67344b60e9fd736983 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 11:43:18 -0400 Subject: [PATCH 6/7] trim comments --- api/core/v1beta1/site_types.go | 5 ----- api/core/v1beta1/workbench_config.go | 9 +++------ api/core/v1beta1/workbench_types.go | 1 - config/crd/bases/core.posit.team_sites.yaml | 5 ----- .../crd/bases/core.posit.team_workbenches.yaml | 10 +++------- .../templates/crd/core.posit.team_sites.yaml | 5 ----- .../crd/core.posit.team_workbenches.yaml | 10 +++------- internal/controller/core/workbench.go | 16 ++++------------ .../crdapply/bases/core.posit.team_sites.yaml | 5 ----- .../bases/core.posit.team_workbenches.yaml | 10 +++------- 10 files changed, 16 insertions(+), 60 deletions(-) diff --git a/api/core/v1beta1/site_types.go b/api/core/v1beta1/site_types.go index fd92451..9024ce1 100644 --- a/api/core/v1beta1/site_types.go +++ b/api/core/v1beta1/site_types.go @@ -635,11 +635,6 @@ type InternalWorkbenchExperimentalFeatures struct { // AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, // distinct from the internal database, that stores historical session and usage data. - // Renders audit-database.conf. This is one of several independent prerequisites for the - // Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the - // Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not - // specific to it -- other consumers of the audit database do not need those. - // See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html AuditDatabaseEnabled bool `json:"auditDatabaseEnabled,omitempty"` } diff --git a/api/core/v1beta1/workbench_config.go b/api/core/v1beta1/workbench_config.go index 4804bbb..7dfa0c3 100644 --- a/api/core/v1beta1/workbench_config.go +++ b/api/core/v1beta1/workbench_config.go @@ -910,12 +910,9 @@ type WorkbenchDatabaseConfig struct { Password string `json:"password,omitempty"` } -// WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike -// WorkbenchDatabaseConfig (the internal database), Password is populated directly -// here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var -// applies to both database.conf and audit-database.conf identically, so it cannot -// carry two different passwords for two distinct database roles. -// See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html +// WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Password is +// populated directly here rather than via WORKBENCH_POSTGRES_PASSWORD, since that env +// var is shared with database.conf and can't carry two different roles' passwords. type WorkbenchAuditDatabaseConfig struct { Provider WorkbenchDatabaseProvider `json:"provider,omitempty"` Database string `json:"database,omitempty"` diff --git a/api/core/v1beta1/workbench_types.go b/api/core/v1beta1/workbench_types.go index fb413fd..8426d96 100644 --- a/api/core/v1beta1/workbench_types.go +++ b/api/core/v1beta1/workbench_types.go @@ -113,7 +113,6 @@ type WorkbenchSpec struct { // AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal // database) and role, then renders audit-database.conf pointing Workbench at it. Requires // the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. - // See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html AuditDatabaseEnabled bool `json:"auditDatabaseEnabled,omitempty"` Replicas int `json:"replicas,omitempty"` diff --git a/config/crd/bases/core.posit.team_sites.yaml b/config/crd/bases/core.posit.team_sites.yaml index 71a3a6c..3659032 100644 --- a/config/crd/bases/core.posit.team_sites.yaml +++ b/config/crd/bases/core.posit.team_sites.yaml @@ -1888,11 +1888,6 @@ spec: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, distinct from the internal database, that stores historical session and usage data. - Renders audit-database.conf. This is one of several independent prerequisites for the - Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the - Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not - specific to it -- other consumers of the audit database do not need those. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: description: |- diff --git a/config/crd/bases/core.posit.team_workbenches.yaml b/config/crd/bases/core.posit.team_workbenches.yaml index e1a7be7..dd49d61 100644 --- a/config/crd/bases/core.posit.team_workbenches.yaml +++ b/config/crd/bases/core.posit.team_workbenches.yaml @@ -111,7 +111,6 @@ spec: AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal database) and role, then renders audit-database.conf pointing Workbench at it. Requires the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean auth: properties: @@ -984,12 +983,9 @@ spec: properties: audit-database.conf: description: |- - WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike - WorkbenchDatabaseConfig (the internal database), Password is populated directly - here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var - applies to both database.conf and audit-database.conf identically, so it cannot - carry two different passwords for two distinct database roles. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Password is + populated directly here rather than via WORKBENCH_POSTGRES_PASSWORD, since that env + var is shared with database.conf and can't carry two different roles' passwords. properties: database: type: string diff --git a/dist/chart/templates/crd/core.posit.team_sites.yaml b/dist/chart/templates/crd/core.posit.team_sites.yaml index 239339e..5e85781 100755 --- a/dist/chart/templates/crd/core.posit.team_sites.yaml +++ b/dist/chart/templates/crd/core.posit.team_sites.yaml @@ -1909,11 +1909,6 @@ spec: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, distinct from the internal database, that stores historical session and usage data. - Renders audit-database.conf. This is one of several independent prerequisites for the - Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the - Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not - specific to it -- other consumers of the audit database do not need those. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: description: |- diff --git a/dist/chart/templates/crd/core.posit.team_workbenches.yaml b/dist/chart/templates/crd/core.posit.team_workbenches.yaml index d400704..e093678 100755 --- a/dist/chart/templates/crd/core.posit.team_workbenches.yaml +++ b/dist/chart/templates/crd/core.posit.team_workbenches.yaml @@ -132,7 +132,6 @@ spec: AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal database) and role, then renders audit-database.conf pointing Workbench at it. Requires the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean auth: properties: @@ -1005,12 +1004,9 @@ spec: properties: audit-database.conf: description: |- - WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike - WorkbenchDatabaseConfig (the internal database), Password is populated directly - here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var - applies to both database.conf and audit-database.conf identically, so it cannot - carry two different passwords for two distinct database roles. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Password is + populated directly here rather than via WORKBENCH_POSTGRES_PASSWORD, since that env + var is shared with database.conf and can't carry two different roles' passwords. properties: database: type: string diff --git a/internal/controller/core/workbench.go b/internal/controller/core/workbench.go index 21f8538..2cd08e8 100644 --- a/internal/controller/core/workbench.go +++ b/internal/controller/core/workbench.go @@ -51,15 +51,11 @@ var invalidCharacters = regexp.MustCompile("[^a-z0-9]") // do not glob, lest we var azureDatabricksRegexp = regexp.MustCompile("azuredatabricks\\.net") -// postgresMaxIdentifierLength is Postgres's NAMEDATALEN-1 limit for unquoted identifiers -// (database and role names). Names longer than this are silently truncated by Postgres -// itself, which could collide with the primary database's own (already-shorter) name. +// postgresMaxIdentifierLength is Postgres's NAMEDATALEN-1 limit for database/role names. const postgresMaxIdentifierLength = 63 // truncateForPostgresIdentifier bounds name to postgresMaxIdentifierLength, trimming any -// trailing separator left dangling by the cut so it also remains a valid Kubernetes object -// name (name is reused as both a PostgresDatabase CR name and, after sanitization, the -// Postgres database/role name derived from it). +// trailing separator left dangling by the cut. func truncateForPostgresIdentifier(name string) string { if len(name) <= postgresMaxIdentifierLength { return name @@ -196,12 +192,8 @@ func (r *WorkbenchReconciler) ReconcileWorkbench(ctx context.Context, req ctrl.R // FYI: Password is set via env var in the CreateSecretVolumeFactory } - // Audit Database: a second, distinct database + role from the internal database above. - // Its own role (rather than reusing dbName) is required because WORKBENCH_POSTGRES_PASSWORD - // is shared by database.conf and audit-database.conf, so the two cannot carry different - // passwords for two different roles — the audit role's password is instead resolved here and - // written directly into audit-database.conf's Password field, which is safe because this file - // is rendered into a Kubernetes Secret, not a ConfigMap. + // A separate role from dbName, since WORKBENCH_POSTGRES_PASSWORD is shared by + // database.conf and audit-database.conf and can't carry two different passwords. if w.Spec.AuditDatabaseEnabled { auditComponentName := truncateForPostgresIdentifier(fmt.Sprintf("%s-audit", w.ComponentName())) auditSecretKey := "dev-audit-db-password" diff --git a/internal/crdapply/bases/core.posit.team_sites.yaml b/internal/crdapply/bases/core.posit.team_sites.yaml index 71a3a6c..3659032 100644 --- a/internal/crdapply/bases/core.posit.team_sites.yaml +++ b/internal/crdapply/bases/core.posit.team_sites.yaml @@ -1888,11 +1888,6 @@ spec: description: |- AuditDatabaseEnabled provisions Workbench's Audit Database: a second Postgres database, distinct from the internal database, that stores historical session and usage data. - Renders audit-database.conf. This is one of several independent prerequisites for the - Package Audit Read API (which additionally needs package-audit=1 in rserver.conf, the - Advanced license tier, and workbench-api-admin-enabled/-super-admin-enabled) but is not - specific to it -- other consumers of the audit database do not need those. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean chronicleSidecarProductApiKeyEnabled: description: |- diff --git a/internal/crdapply/bases/core.posit.team_workbenches.yaml b/internal/crdapply/bases/core.posit.team_workbenches.yaml index e1a7be7..dd49d61 100644 --- a/internal/crdapply/bases/core.posit.team_workbenches.yaml +++ b/internal/crdapply/bases/core.posit.team_workbenches.yaml @@ -111,7 +111,6 @@ spec: AuditDatabaseEnabled provisions a second Postgres database (distinct from the internal database) and role, then renders audit-database.conf pointing Workbench at it. Requires the Secret referenced by Secret.VaultName to already contain a "dev-audit-db-password" key. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/database.html type: boolean auth: properties: @@ -984,12 +983,9 @@ spec: properties: audit-database.conf: description: |- - WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Unlike - WorkbenchDatabaseConfig (the internal database), Password is populated directly - here rather than via the shared WORKBENCH_POSTGRES_PASSWORD env var: that env var - applies to both database.conf and audit-database.conf identically, so it cannot - carry two different passwords for two distinct database roles. - See: https://docs.posit.co/ide/server-pro/admin/database/audit/configuration.html + WorkbenchAuditDatabaseConfig renders /etc/rstudio/audit-database.conf. Password is + populated directly here rather than via WORKBENCH_POSTGRES_PASSWORD, since that env + var is shared with database.conf and can't carry two different roles' passwords. properties: database: type: string From ceb1ca5472ed8d231f7372c73be262205929b2c7 Mon Sep 17 00:00:00 2001 From: Zach Hannum Date: Wed, 26 Aug 2026 19:55:21 -0400 Subject: [PATCH 7/7] gofmt: drop trailing blank line left by test removal --- api/core/v1beta1/workbench_config_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api/core/v1beta1/workbench_config_test.go b/api/core/v1beta1/workbench_config_test.go index d592e0d..d190275 100644 --- a/api/core/v1beta1/workbench_config_test.go +++ b/api/core/v1beta1/workbench_config_test.go @@ -1213,4 +1213,3 @@ func TestWorkbenchConfig_ForceAdminUiEnabled(t *testing.T) { require.Nil(t, err) require.Contains(t, res["rserver.conf"], "force-admin-ui-enabled=0\n") } -