CSAH: appconfig/gitops/DB migration to add preserve_host_activities_on_reenrollment field (#44212)
<!-- Add the related story/sub-task/bug number, like Resolves #123, or remove if NA --> **Related issue:** Resolves #43943 # Checklist for submitter - [x] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters. ## Testing - [x] Added/updated automated tests - [x] Where appropriate, [automated tests simulate multiple hosts and test for host isolation](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/reference/patterns-backend.md#unit-testing) (updates to one hosts's records do not affect another) - [x] QA'd all new/changed functionality manually See https://github.com/fleetdm/fleet/issues/43943#issuecomment-4329658412 ## Database migrations - [x] Checked schema for all modified table for columns that will auto-update timestamps during migration. ## New Fleet configuration settings - [x] Verified that the setting is exported via `fleetctl generate-gitops` - [x] Verified the setting is documented in a separate PR to [the GitOps documentation](https://github.com/fleetdm/fleet/blob/main/docs/Configuration/yaml-files.md#L485) (see https://github.com/fleetdm/fleet/pull/43877/changes) - [x] Verified that the setting is cleared on the server if it is not supplied in a YAML file (or that it is documented as being optional) - [ ] Verified that any relevant UI is disabled when GitOps mode is enabled (should be done by https://github.com/fleetdm/fleet/issues/43947) <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added a configuration option to preserve host activities during host re-enrollment, letting admins choose whether activity history is retained when hosts re-enroll. * **Chores** * Updated defaults and database migration state so the new setting is present in stored and generated configs and in GitOps outputs. * **Tests** * Added unit, integration, migration, and GitOps fixtures to validate behavior, serialization, and upgrade semantics. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -793,12 +793,13 @@ func (cmd *GenerateGitopsCommand) generateOrgSettings() (orgSettings map[string]
|
||||
}
|
||||
|
||||
orgSettings = map[string]interface{}{
|
||||
jsonFieldName(t, "Features"): cmd.AppConfig.Features,
|
||||
jsonFieldName(t, "FleetDesktop"): cmd.AppConfig.FleetDesktop,
|
||||
jsonFieldName(t, "HostExpirySettings"): cmd.AppConfig.HostExpirySettings,
|
||||
jsonFieldName(t, "OrgInfo"): cmd.AppConfig.OrgInfo,
|
||||
jsonFieldName(t, "ServerSettings"): cmd.AppConfig.ServerSettings,
|
||||
jsonFieldName(t, "WebhookSettings"): webhookSettings,
|
||||
jsonFieldName(t, "ActivityExpirySettings"): cmd.AppConfig.ActivityExpirySettings,
|
||||
jsonFieldName(t, "Features"): cmd.AppConfig.Features,
|
||||
jsonFieldName(t, "FleetDesktop"): cmd.AppConfig.FleetDesktop,
|
||||
jsonFieldName(t, "HostExpirySettings"): cmd.AppConfig.HostExpirySettings,
|
||||
jsonFieldName(t, "OrgInfo"): cmd.AppConfig.OrgInfo,
|
||||
jsonFieldName(t, "ServerSettings"): cmd.AppConfig.ServerSettings,
|
||||
jsonFieldName(t, "WebhookSettings"): webhookSettings,
|
||||
}
|
||||
|
||||
integrations, err := cmd.generateIntegrations("default.yml", &GlobalOrTeamIntegrations{GlobalIntegrations: &cmd.AppConfig.Integrations})
|
||||
|
||||
@@ -1033,6 +1033,39 @@ func TestGenerateGitopsFree(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGenerateGitopsPreserveHostActivitiesOnReenrollment(t *testing.T) {
|
||||
// Verifies that fleetctl generate-gitops emits
|
||||
// activity_expiry_settings.preserve_host_activities_on_reenrollment so
|
||||
// existing customers can roundtrip the setting via GitOps.
|
||||
fleetClient := &MockClient{}
|
||||
appConfig, err := fleetClient.GetAppConfig()
|
||||
require.NoError(t, err)
|
||||
// Sanity-check the source config matches what the generated output should
|
||||
// expose.
|
||||
require.True(t, appConfig.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
|
||||
cmd := &GenerateGitopsCommand{
|
||||
Client: fleetClient,
|
||||
CLI: cli.NewContext(&cli.App{}, nil, nil),
|
||||
Messages: Messages{},
|
||||
FilesToWrite: make(map[string]any),
|
||||
AppConfig: appConfig,
|
||||
}
|
||||
|
||||
orgSettingsRaw, err := cmd.generateOrgSettings()
|
||||
require.NoError(t, err)
|
||||
|
||||
b, err := yamlMarshalRenamed(orgSettingsRaw)
|
||||
require.NoError(t, err)
|
||||
|
||||
var orgSettings map[string]any
|
||||
require.NoError(t, yaml.Unmarshal(b, &orgSettings))
|
||||
|
||||
aes, ok := orgSettings["activity_expiry_settings"].(map[string]any)
|
||||
require.True(t, ok, "activity_expiry_settings should be present in generated org settings")
|
||||
require.Equal(t, true, aes["preserve_host_activities_on_reenrollment"])
|
||||
}
|
||||
|
||||
func TestGenerateOrgSettings(t *testing.T) {
|
||||
// Get the test app config.
|
||||
fleetClient := &MockClient{}
|
||||
|
||||
@@ -3844,6 +3844,39 @@ func TestGitOpsWindowsMigration(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGitOpsPreserveHostActivitiesOnReenrollment(t *testing.T) {
|
||||
t.Run("explicit true", func(t *testing.T) {
|
||||
_, appConfig, _ := testing_utils.SetupFullGitOpsPremiumServer(t)
|
||||
|
||||
_, err := RunAppNoChecks([]string{"gitops", "-f", "testdata/gitops/global_config_preserve_host_activities_true.yml"})
|
||||
require.NoError(t, err)
|
||||
require.True(t, (*appConfig).ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
})
|
||||
|
||||
t.Run("explicit false", func(t *testing.T) {
|
||||
_, appConfig, _ := testing_utils.SetupFullGitOpsPremiumServer(t)
|
||||
|
||||
// Seed the AppConfig with true so we can confirm gitops actually flips it.
|
||||
(*appConfig).ActivityExpirySettings.PreserveHostActivitiesOnReenrollment = true
|
||||
|
||||
_, err := RunAppNoChecks([]string{"gitops", "-f", "testdata/gitops/global_config_preserve_host_activities_false.yml"})
|
||||
require.NoError(t, err)
|
||||
require.False(t, (*appConfig).ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
})
|
||||
|
||||
t.Run("omitted preserves prior value", func(t *testing.T) {
|
||||
_, appConfig, _ := testing_utils.SetupFullGitOpsPremiumServer(t)
|
||||
|
||||
// Seed the AppConfig with true so we can confirm gitops does not clobber
|
||||
// the value when the field is absent from the YAML.
|
||||
(*appConfig).ActivityExpirySettings.PreserveHostActivitiesOnReenrollment = true
|
||||
|
||||
_, err := RunAppNoChecks([]string{"gitops", "-f", "testdata/gitops/global_config_preserve_host_activities_omitted.yml"})
|
||||
require.NoError(t, err)
|
||||
require.True(t, (*appConfig).ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
})
|
||||
}
|
||||
|
||||
func TestGitOpsGlobalWebhooksDisable(t *testing.T) {
|
||||
_, appConfig, _ := testing_utils.SetupFullGitOpsPremiumServer(t)
|
||||
|
||||
|
||||
@@ -42,7 +42,8 @@
|
||||
},
|
||||
"activity_expiry_settings": {
|
||||
"activity_expiry_enabled": false,
|
||||
"activity_expiry_window": 0
|
||||
"activity_expiry_window": 0,
|
||||
"preserve_host_activities_on_reenrollment": false
|
||||
},
|
||||
"conditional_access": {
|
||||
"microsoft_entra_tenant_id": "",
|
||||
|
||||
+2
-1
@@ -27,7 +27,8 @@
|
||||
},
|
||||
"activity_expiry_settings": {
|
||||
"activity_expiry_enabled": false,
|
||||
"activity_expiry_window": 0
|
||||
"activity_expiry_window": 0,
|
||||
"preserve_host_activities_on_reenrollment": false
|
||||
},
|
||||
"conditional_access": {
|
||||
"microsoft_entra_tenant_id": "",
|
||||
|
||||
+1
@@ -11,6 +11,7 @@ spec:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 0
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
conditional_access:
|
||||
microsoft_entra_tenant_id: ""
|
||||
microsoft_entra_connection_configured: false
|
||||
|
||||
@@ -11,6 +11,7 @@ spec:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 0
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
conditional_access:
|
||||
microsoft_entra_tenant_id: ""
|
||||
microsoft_entra_connection_configured: false
|
||||
|
||||
+2
-1
@@ -42,7 +42,8 @@
|
||||
},
|
||||
"activity_expiry_settings": {
|
||||
"activity_expiry_enabled": false,
|
||||
"activity_expiry_window": 0
|
||||
"activity_expiry_window": 0,
|
||||
"preserve_host_activities_on_reenrollment": false
|
||||
},
|
||||
"conditional_access": {
|
||||
"microsoft_entra_tenant_id": "",
|
||||
|
||||
@@ -11,6 +11,7 @@ spec:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 0
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
conditional_access:
|
||||
microsoft_entra_tenant_id: ""
|
||||
microsoft_entra_connection_configured: false
|
||||
|
||||
@@ -100,7 +100,8 @@
|
||||
},
|
||||
"activity_expiry_settings": {
|
||||
"activity_expiry_enabled": false,
|
||||
"activity_expiry_window": 30
|
||||
"activity_expiry_window": 30,
|
||||
"preserve_host_activities_on_reenrollment": true
|
||||
},
|
||||
"features": {
|
||||
"enable_host_users": true,
|
||||
|
||||
+4
@@ -1,3 +1,7 @@
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 30
|
||||
preserve_host_activities_on_reenrollment: true
|
||||
certificate_authorities:
|
||||
custom_est_proxy:
|
||||
- name: some-est-name
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 30
|
||||
preserve_host_activities_on_reenrollment: true
|
||||
certificate_authorities:
|
||||
custom_est_proxy:
|
||||
- name: some-est-name
|
||||
|
||||
@@ -49,6 +49,10 @@ labels:
|
||||
label_membership_type: host_vitals
|
||||
name: Label C
|
||||
org_settings:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 30
|
||||
preserve_host_activities_on_reenrollment: true
|
||||
certificate_authorities:
|
||||
custom_est_proxy:
|
||||
- name: some-est-name
|
||||
|
||||
@@ -31,6 +31,10 @@ labels:
|
||||
label_membership_type: host_vitals
|
||||
name: Label C
|
||||
org_settings:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 30
|
||||
preserve_host_activities_on_reenrollment: true
|
||||
certificate_authorities:
|
||||
custom_est_proxy:
|
||||
- name: some-est-name
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
controls:
|
||||
macos_settings:
|
||||
windows_settings:
|
||||
scripts:
|
||||
enable_disk_encryption: false
|
||||
macos_migration:
|
||||
enable: false
|
||||
mode: ""
|
||||
webhook_url: ""
|
||||
macos_setup:
|
||||
bootstrap_package: null
|
||||
enable_end_user_authentication: false
|
||||
macos_setup_assistant: null
|
||||
macos_updates:
|
||||
deadline: null
|
||||
minimum_version: null
|
||||
windows_enabled_and_configured: true
|
||||
windows_migration_enabled: false
|
||||
enable_turn_on_windows_mdm_manually: false
|
||||
apple_require_hardware_attestation: false
|
||||
windows_updates:
|
||||
deadline_days: null
|
||||
grace_period_days: null
|
||||
queries:
|
||||
policies:
|
||||
agent_options:
|
||||
command_line_flags:
|
||||
distributed_denylist_duration: 0
|
||||
config:
|
||||
decorators:
|
||||
load:
|
||||
- SELECT uuid AS host_uuid FROM system_info;
|
||||
- SELECT hostname AS hostname FROM system_info;
|
||||
options:
|
||||
disable_distributed: false
|
||||
distributed_interval: 10
|
||||
distributed_plugin: tls
|
||||
distributed_tls_max_attempts: 3
|
||||
logger_tls_endpoint: /api/v1/osquery/log
|
||||
pack_delimiter: /
|
||||
org_settings:
|
||||
server_settings:
|
||||
deferred_save_host: false
|
||||
enable_analytics: true
|
||||
live_query_disabled: false
|
||||
query_report_cap: 2000
|
||||
query_reports_disabled: false
|
||||
scripts_disabled: false
|
||||
server_url: $FLEET_SERVER_URL
|
||||
ai_features_disabled: true
|
||||
org_info:
|
||||
contact_url: https://fleetdm.com/company/contact
|
||||
org_logo_url: ""
|
||||
org_logo_url_light_background: ""
|
||||
org_name: $ORG_NAME
|
||||
smtp_settings:
|
||||
sso_settings:
|
||||
integrations:
|
||||
mdm:
|
||||
end_user_authentication:
|
||||
webhook_settings:
|
||||
fleet_desktop:
|
||||
transparency_url: https://fleetdm.com/transparency
|
||||
host_expiry_settings:
|
||||
host_expiry_enabled: false
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: true
|
||||
activity_expiry_window: 60
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
vulnerability_settings:
|
||||
databases_path: ""
|
||||
secrets:
|
||||
- secret: SampleSecret123
|
||||
- secret: ABC
|
||||
software:
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
controls:
|
||||
macos_settings:
|
||||
windows_settings:
|
||||
scripts:
|
||||
enable_disk_encryption: false
|
||||
macos_migration:
|
||||
enable: false
|
||||
mode: ""
|
||||
webhook_url: ""
|
||||
macos_setup:
|
||||
bootstrap_package: null
|
||||
enable_end_user_authentication: false
|
||||
macos_setup_assistant: null
|
||||
macos_updates:
|
||||
deadline: null
|
||||
minimum_version: null
|
||||
windows_enabled_and_configured: true
|
||||
windows_migration_enabled: false
|
||||
enable_turn_on_windows_mdm_manually: false
|
||||
apple_require_hardware_attestation: false
|
||||
windows_updates:
|
||||
deadline_days: null
|
||||
grace_period_days: null
|
||||
queries:
|
||||
policies:
|
||||
agent_options:
|
||||
command_line_flags:
|
||||
distributed_denylist_duration: 0
|
||||
config:
|
||||
decorators:
|
||||
load:
|
||||
- SELECT uuid AS host_uuid FROM system_info;
|
||||
- SELECT hostname AS hostname FROM system_info;
|
||||
options:
|
||||
disable_distributed: false
|
||||
distributed_interval: 10
|
||||
distributed_plugin: tls
|
||||
distributed_tls_max_attempts: 3
|
||||
logger_tls_endpoint: /api/v1/osquery/log
|
||||
pack_delimiter: /
|
||||
org_settings:
|
||||
server_settings:
|
||||
deferred_save_host: false
|
||||
enable_analytics: true
|
||||
live_query_disabled: false
|
||||
query_report_cap: 2000
|
||||
query_reports_disabled: false
|
||||
scripts_disabled: false
|
||||
server_url: $FLEET_SERVER_URL
|
||||
ai_features_disabled: true
|
||||
org_info:
|
||||
contact_url: https://fleetdm.com/company/contact
|
||||
org_logo_url: ""
|
||||
org_logo_url_light_background: ""
|
||||
org_name: $ORG_NAME
|
||||
smtp_settings:
|
||||
sso_settings:
|
||||
integrations:
|
||||
mdm:
|
||||
end_user_authentication:
|
||||
webhook_settings:
|
||||
fleet_desktop:
|
||||
transparency_url: https://fleetdm.com/transparency
|
||||
host_expiry_settings:
|
||||
host_expiry_enabled: false
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: true
|
||||
activity_expiry_window: 60
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
vulnerability_settings:
|
||||
databases_path: ""
|
||||
secrets:
|
||||
- secret: SampleSecret123
|
||||
- secret: ABC
|
||||
software:
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
controls:
|
||||
macos_settings:
|
||||
windows_settings:
|
||||
scripts:
|
||||
enable_disk_encryption: false
|
||||
macos_migration:
|
||||
enable: false
|
||||
mode: ""
|
||||
webhook_url: ""
|
||||
macos_setup:
|
||||
bootstrap_package: null
|
||||
enable_end_user_authentication: false
|
||||
macos_setup_assistant: null
|
||||
macos_updates:
|
||||
deadline: null
|
||||
minimum_version: null
|
||||
windows_enabled_and_configured: true
|
||||
windows_migration_enabled: false
|
||||
enable_turn_on_windows_mdm_manually: false
|
||||
apple_require_hardware_attestation: false
|
||||
windows_updates:
|
||||
deadline_days: null
|
||||
grace_period_days: null
|
||||
queries:
|
||||
policies:
|
||||
agent_options:
|
||||
command_line_flags:
|
||||
distributed_denylist_duration: 0
|
||||
config:
|
||||
decorators:
|
||||
load:
|
||||
- SELECT uuid AS host_uuid FROM system_info;
|
||||
- SELECT hostname AS hostname FROM system_info;
|
||||
options:
|
||||
disable_distributed: false
|
||||
distributed_interval: 10
|
||||
distributed_plugin: tls
|
||||
distributed_tls_max_attempts: 3
|
||||
logger_tls_endpoint: /api/v1/osquery/log
|
||||
pack_delimiter: /
|
||||
org_settings:
|
||||
server_settings:
|
||||
deferred_save_host: false
|
||||
enable_analytics: true
|
||||
live_query_disabled: false
|
||||
query_report_cap: 2000
|
||||
query_reports_disabled: false
|
||||
scripts_disabled: false
|
||||
server_url: $FLEET_SERVER_URL
|
||||
ai_features_disabled: true
|
||||
org_info:
|
||||
contact_url: https://fleetdm.com/company/contact
|
||||
org_logo_url: ""
|
||||
org_logo_url_light_background: ""
|
||||
org_name: $ORG_NAME
|
||||
smtp_settings:
|
||||
sso_settings:
|
||||
integrations:
|
||||
mdm:
|
||||
end_user_authentication:
|
||||
webhook_settings:
|
||||
fleet_desktop:
|
||||
transparency_url: https://fleetdm.com/transparency
|
||||
host_expiry_settings:
|
||||
host_expiry_enabled: false
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: true
|
||||
activity_expiry_window: 60
|
||||
preserve_host_activities_on_reenrollment: true
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
vulnerability_settings:
|
||||
databases_path: ""
|
||||
secrets:
|
||||
- secret: SampleSecret123
|
||||
- secret: ABC
|
||||
software:
|
||||
@@ -14,6 +14,7 @@ spec:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 0
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
conditional_access:
|
||||
microsoft_entra_tenant_id: ""
|
||||
microsoft_entra_connection_configured: false
|
||||
|
||||
@@ -14,6 +14,7 @@ spec:
|
||||
activity_expiry_settings:
|
||||
activity_expiry_enabled: false
|
||||
activity_expiry_window: 0
|
||||
preserve_host_activities_on_reenrollment: false
|
||||
conditional_access:
|
||||
microsoft_entra_tenant_id: ""
|
||||
microsoft_entra_connection_configured: false
|
||||
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func init() {
|
||||
MigrationClient.AddMigration(Up_20260427134220, Down_20260427134220)
|
||||
}
|
||||
|
||||
func Up_20260427134220(tx *sql.Tx) error {
|
||||
// Defaults to true for upgraded installations (where users already exist) so
|
||||
// that prior behavior is preserved, and false for fresh installations.
|
||||
var usersCount int
|
||||
if err := tx.QueryRow(`SELECT COUNT(*) FROM users;`).Scan(&usersCount); err != nil {
|
||||
return errors.Wrap(err, "select count users")
|
||||
}
|
||||
preserve := usersCount > 0
|
||||
|
||||
if err := updateAppConfigJSON(tx, func(config *fleet.AppConfig) error {
|
||||
if config != nil {
|
||||
config.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment = preserve
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "set PreserveHostActivitiesOnReenrollment in AppConfig")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func Down_20260427134220(tx *sql.Tx) error {
|
||||
return nil
|
||||
}
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
package tables
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestUp_20260427134220_FreshInstall(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
|
||||
// No users inserted: this represents a fresh installation.
|
||||
|
||||
applyNext(t, db)
|
||||
|
||||
var raw json.RawMessage
|
||||
require.NoError(t, sqlx.Get(db, &raw, `SELECT json_value FROM app_config_json LIMIT 1;`))
|
||||
|
||||
var cfg map[string]any
|
||||
require.NoError(t, json.Unmarshal(raw, &cfg))
|
||||
|
||||
aes, ok := cfg["activity_expiry_settings"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
v, ok := aes["preserve_host_activities_on_reenrollment"].(bool)
|
||||
require.True(t, ok)
|
||||
require.False(t, v)
|
||||
}
|
||||
|
||||
func TestUp_20260427134220_UpgradedInstall(t *testing.T) {
|
||||
db := applyUpToPrev(t)
|
||||
|
||||
// At least one user exists: this represents an upgrade.
|
||||
execNoErr(t, db,
|
||||
`INSERT INTO users (name, email, password, salt) VALUES (?, ?, ?, ?);`,
|
||||
"admin", "admin@example.com", "p", "s",
|
||||
)
|
||||
|
||||
applyNext(t, db)
|
||||
|
||||
var raw json.RawMessage
|
||||
require.NoError(t, sqlx.Get(db, &raw, `SELECT json_value FROM app_config_json LIMIT 1;`))
|
||||
|
||||
var cfg map[string]any
|
||||
require.NoError(t, json.Unmarshal(raw, &cfg))
|
||||
|
||||
aes, ok := cfg["activity_expiry_settings"].(map[string]any)
|
||||
require.True(t, ok)
|
||||
v, ok := aes["preserve_host_activities_on_reenrollment"].(bool)
|
||||
require.True(t, ok)
|
||||
require.True(t, v)
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1239,6 +1239,12 @@ type HostExpirySettings struct {
|
||||
type ActivityExpirySettings struct {
|
||||
ActivityExpiryEnabled bool `json:"activity_expiry_enabled"`
|
||||
ActivityExpiryWindow int `json:"activity_expiry_window"`
|
||||
|
||||
// PreserveHostActivitiesOnReenrollment controls whether existing host
|
||||
// activities, MDM commands, etc. are kept when a managed host re-enrolls.
|
||||
// Defaults to true for upgraded installs (preserves prior behavior) and
|
||||
// false for fresh installs.
|
||||
PreserveHostActivitiesOnReenrollment bool `json:"preserve_host_activities_on_reenrollment"`
|
||||
}
|
||||
|
||||
type Features struct {
|
||||
|
||||
@@ -8066,6 +8066,35 @@ func (s *integrationTestSuite) TestAppConfig() {
|
||||
require.True(t, acResp.ActivityExpirySettings.ActivityExpiryEnabled)
|
||||
require.Equal(t, 42, acResp.ActivityExpirySettings.ActivityExpiryWindow)
|
||||
|
||||
// preserve_host_activities_on_reenrollment round-trip.
|
||||
initialPreserve := acResp.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment
|
||||
acResp = appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"activity_expiry_settings": {
|
||||
"preserve_host_activities_on_reenrollment": true
|
||||
}
|
||||
}`), http.StatusOK, &acResp)
|
||||
require.True(t, acResp.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
require.True(t, acResp.ActivityExpirySettings.ActivityExpiryEnabled)
|
||||
require.Equal(t, 42, acResp.ActivityExpirySettings.ActivityExpiryWindow)
|
||||
|
||||
acResp = appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"activity_expiry_settings": {
|
||||
"preserve_host_activities_on_reenrollment": false
|
||||
}
|
||||
}`), http.StatusOK, &acResp)
|
||||
require.False(t, acResp.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
|
||||
// Restore initial value to keep subsequent tests order-independent.
|
||||
acResp = appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(fmt.Sprintf(`{
|
||||
"activity_expiry_settings": {
|
||||
"preserve_host_activities_on_reenrollment": %t
|
||||
}
|
||||
}`, initialPreserve)), http.StatusOK, &acResp)
|
||||
require.Equal(t, initialPreserve, acResp.ActivityExpirySettings.PreserveHostActivitiesOnReenrollment)
|
||||
|
||||
// Disable AI features.
|
||||
acResp = appConfigResponse{}
|
||||
s.DoJSON(
|
||||
|
||||
@@ -33,6 +33,7 @@ github.com/fleetdm/fleet/v4/server/fleet/HostExpirySettings HostExpiryWindow int
|
||||
github.com/fleetdm/fleet/v4/server/fleet/AppConfig ActivityExpirySettings fleet.ActivityExpirySettings
|
||||
github.com/fleetdm/fleet/v4/server/fleet/ActivityExpirySettings ActivityExpiryEnabled bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/ActivityExpirySettings ActivityExpiryWindow int
|
||||
github.com/fleetdm/fleet/v4/server/fleet/ActivityExpirySettings PreserveHostActivitiesOnReenrollment bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/AppConfig Features fleet.Features
|
||||
github.com/fleetdm/fleet/v4/server/fleet/Features EnableHostUsers bool
|
||||
github.com/fleetdm/fleet/v4/server/fleet/Features EnableSoftwareInventory bool
|
||||
|
||||
Reference in New Issue
Block a user