diff --git a/changes/40015-activate-deprecation-warnings b/changes/40015-activate-deprecation-warnings new file mode 100644 index 0000000000..89dce54da5 --- /dev/null +++ b/changes/40015-activate-deprecation-warnings @@ -0,0 +1 @@ +- Activated warnings for deprecated API parameters, API URLs, fleetctl commands and fleetctl command options. diff --git a/cmd/fleet/serve.go b/cmd/fleet/serve.go index 02bf96584b..0553998716 100644 --- a/cmd/fleet/serve.go +++ b/cmd/fleet/serve.go @@ -294,7 +294,6 @@ func runServeCmd(cmd *cobra.Command, configManager configpkg.Manager, debug, dev // // For example: // platform_logging.DisableTopic("deprecated-api-keys") - platform_logging.DisableTopic(platform_logging.DeprecatedFieldTopic) // Apply log topic overrides from config. Enables run first, then // disables, so disable wins on conflict. diff --git a/cmd/fleetctl/fleetctl/apply.go b/cmd/fleetctl/fleetctl/apply.go index 8266ec8d26..c55fd6da95 100644 --- a/cmd/fleetctl/fleetctl/apply.go +++ b/cmd/fleetctl/fleetctl/apply.go @@ -9,7 +9,6 @@ import ( "github.com/fleetdm/fleet/v4/pkg/spec" "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/logging" "github.com/urfave/cli/v2" ) @@ -59,10 +58,6 @@ func applyCommand() *cli.Command { return nil }, Action: func(c *cli.Context) error { - // Disable field deprecation warnings for now. - // TODO - remove this in future release to unleash warnings. - logging.DisableTopic(logging.DeprecatedFieldTopic) - // Apply log topic overrides from flags/env vars. applyLogTopicFlags(c) diff --git a/cmd/fleetctl/fleetctl/apply_deprecated_test.go b/cmd/fleetctl/fleetctl/apply_deprecated_test.go new file mode 100644 index 0000000000..1fd59f44ec --- /dev/null +++ b/cmd/fleetctl/fleetctl/apply_deprecated_test.go @@ -0,0 +1,2605 @@ +package fleetctl + +// Tests using deprecated key names to ensure backward compatibility. +// These are copies of the corresponding tests in apply_test.go but using +// the old (deprecated) key names instead of the new ones. + +import ( + "bytes" + "context" + "crypto/sha256" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/http/httptest" + "os" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "testing" + "time" + + "github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl/testing_utils" + "github.com/fleetdm/fleet/v4/pkg/optjson" + "github.com/fleetdm/fleet/v4/server/config" + "github.com/fleetdm/fleet/v4/server/fleet" + apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple" + "github.com/fleetdm/fleet/v4/server/mdm/nanodep/tokenpki" + mdmtest "github.com/fleetdm/fleet/v4/server/mdm/testing_utils" + "github.com/fleetdm/fleet/v4/server/mock" + mdmmock "github.com/fleetdm/fleet/v4/server/mock/mdm" + "github.com/fleetdm/fleet/v4/server/ptr" + "github.com/fleetdm/fleet/v4/server/service" + "github.com/google/uuid" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +// runAppForTestDeprecated wraps RunAppForTest and strips deprecation warning +// lines from the output, since these tests use old key names that now trigger +// deprecation warnings. +func runAppForTestDeprecated(t *testing.T, args []string) string { + t.Helper() + out := RunAppForTest(t, args) + return stripDeprecationWarnings(out) +} + +// runAppNoChecksDeprecated wraps RunAppNoChecks and strips deprecation warning +// lines from the output buffer. +func runAppNoChecksDeprecated(args []string) (*bytes.Buffer, error) { + buf, err := RunAppNoChecks(args) + if buf != nil { + buf = bytes.NewBufferString(stripDeprecationWarnings(buf.String())) + } + return buf, err +} + +// stripDeprecationWarnings removes "[!] ... is deprecated ..." lines from +// output while preserving other [!] warnings (e.g. "[!] ignoring labels"). +func stripDeprecationWarnings(s string) string { + var lines []string + for line := range strings.SplitSeq(s, "\n") { + if strings.HasPrefix(line, "[!]") && strings.Contains(line, "is deprecated") { + continue + } + lines = append(lines, line) + } + return strings.Join(lines, "\n") +} + +func TestApplyAsGitOpsDeprecatedKeys(t *testing.T) { + enqueuer := new(mdmmock.MDMAppleStore) + license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} + + // mdm test configuration must be set so that activating windows MDM works. + testCert, testKey, err := apple_mdm.NewSCEPCACertKey() + require.NoError(t, err) + testCertPEM := tokenpki.PEMCertificate(testCert.Raw) + testKeyPEM := tokenpki.PEMRSAPrivateKey(testKey) + fleetCfg := config.TestConfig() + // Mock Apple DEP API + depStorage := SetupMockDEPStorageAndMockDEPServer(t) + + // Mock Apple GDMF API (required for validating OS update minimum version settings) + mdmtest.StartNewAppleGDMFTestServer(t) + + config.SetTestMDMConfig(t, &fleetCfg, testCertPEM, testKeyPEM, "../../../server/service/testdata") + + _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{ + License: license, + MDMStorage: enqueuer, + MDMPusher: testing_utils.MockPusher{}, + FleetConfig: &fleetCfg, + DEPStorage: depStorage, + }) + + gitOps := &fleet.User{ + Name: "GitOps", + Password: []byte("p4ssw0rd.123"), + Email: "gitops1@example.com", + GlobalRole: ptr.String(fleet.RoleGitOps), + } + gitOps, err = ds.NewUser(context.Background(), gitOps) + require.NoError(t, err) + ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) { + return &fleet.Session{ + CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Now()}, + ID: 1, + AccessedAt: time.Now(), + UserID: gitOps.ID, + Key: key, + }, nil + } + ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) { + return gitOps, nil + } + currentAppConfig := &fleet.AppConfig{ + OrgInfo: fleet.OrgInfo{ + OrgName: "Fleet", + }, + ServerSettings: fleet.ServerSettings{ + ServerURL: "https://example.org", + }, + MDM: fleet.MDM{ + EnabledAndConfigured: true, + }, + } + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return currentAppConfig, nil + } + + ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error { + currentAppConfig = config + return nil + } + + savedTeam := &fleet.Team{ID: 123} + ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) { + if name == "Team1" { + return savedTeam, nil + } + return nil, errors.New("unexpected team name!") + } + ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { + savedTeam = team + return team, nil + } + ds.TeamWithExtrasFunc = func(ctx context.Context, tid uint) (*fleet.Team, error) { + return savedTeam, nil + } + + ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, isNew bool, teamID *uint) (bool, error) { + assert.False(t, isNew) + assert.Equal(t, uint(123), *teamID) + return true, nil + } + var teamEnrollSecrets []*fleet.EnrollSecret + ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error { + if teamID == nil || *teamID != 123 { + return fmt.Errorf("unexpected data: %+v", teamID) + } + teamEnrollSecrets = secrets + return nil + } + ds.BatchSetMDMProfilesFunc = func(ctx context.Context, tmID *uint, macProfiles []*fleet.MDMAppleConfigProfile, + winProfiles []*fleet.MDMWindowsConfigProfile, macDecls []*fleet.MDMAppleDeclaration, androidProfiles []*fleet.MDMAndroidConfigProfile, vars []fleet.MDMProfileIdentifierFleetVariables, + ) (updates fleet.MDMProfilesUpdates, err error) { + return fleet.MDMProfilesUpdates{}, nil + } + ds.BulkSetPendingMDMHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs []uint, profileUUIDs, hostUUIDs []string, + ) (updates fleet.MDMProfilesUpdates, err error) { + return fleet.MDMProfilesUpdates{}, nil + } + ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) { + return nil, ¬FoundError{} + } + ds.SetOrUpdateMDMAppleSetupAssistantFunc = func(ctx context.Context, asst *fleet.MDMAppleSetupAssistant) (*fleet.MDMAppleSetupAssistant, error) { + return asst, nil + } + ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) { + return job, nil + } + ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) { + return nil, ¬FoundError{} + } + ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error { + return nil + } + ds.SetOrUpdateMDMWindowsConfigProfileFunc = func(ctx context.Context, cp fleet.MDMWindowsConfigProfile) error { + return nil + } + ds.DeleteMDMWindowsConfigProfileByTeamAndNameFunc = func(ctx context.Context, teamID *uint, profileName string) error { + return nil + } + ds.LabelIDsByNameFunc = func(ctx context.Context, names []string, filter fleet.TeamFilter) (map[string]uint, error) { + require.ElementsMatch(t, names, []string{fleet.BuiltinLabelMacOS14Plus}) + return map[string]uint{fleet.BuiltinLabelMacOS14Plus: 1}, nil + } + ds.SetAsideLabelsFunc = func(ctx context.Context, notOnTeamID *uint, names []string, user fleet.User) error { + return nil + } + ds.SetOrUpdateMDMAppleDeclarationFunc = func(ctx context.Context, declaration *fleet.MDMAppleDeclaration) (*fleet.MDMAppleDeclaration, error) { + declaration.DeclarationUUID = uuid.NewString() + return declaration, nil + } + ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error { + return nil + } + + ds.GetMDMAppleEnrollmentProfileByTypeFunc = func(ctx context.Context, typ fleet.MDMAppleEnrollmentType) (*fleet.MDMAppleEnrollmentProfile, error) { + return &fleet.MDMAppleEnrollmentProfile{Token: "foobar"}, nil + } + ds.CountABMTokensWithTermsExpiredFunc = func(ctx context.Context) (int, error) { + return 0, nil + } + + ds.GetABMTokenOrgNamesAssociatedWithTeamFunc = func(ctx context.Context, teamID *uint) ([]string, error) { + return []string{"foobar"}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{{ID: 1}}, nil + } + + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ExpandEmbeddedSecretsAndUpdatedAtFunc = func(ctx context.Context, document string) (string, *time.Time, error) { + return document, nil, nil + } + ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) { + return nil, ¬FoundError{} + } + + // Apply global config. + name := writeTmpYml(t, `--- +apiVersion: v1 +kind: config +spec: + features: + enable_host_users: true + enable_software_inventory: true + agent_options: + 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/osquery/log + logger_tls_period: 10 + pack_delimiter: / + overrides: {} +`) + + // test applying with dry-run flag + assert.Equal(t, "[+] would've applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"})) + + // test applying for real + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, currentAppConfig.Features.EnableHostUsers) + + mobileConfig := mobileconfigForTest("foo", "bar") + mobileConfigPath := filepath.Join(t.TempDir(), "foo.mobileconfig") + err = os.WriteFile(mobileConfigPath, mobileConfig, 0o644) + require.NoError(t, err) + + emptySetupAsst := writeTmpJSON(t, map[string]any{}) + + // Apply global config with custom setting and macos setup assistant, and enable + // Windows MDM. + name = writeTmpYml(t, fmt.Sprintf(`--- +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: 14.6.1 + deadline: 2020-02-02 + windows_updates: + deadline_days: 1 + grace_period_days: 0 + macos_settings: + custom_settings: + - %s + macos_setup: + macos_setup_assistant: %s + windows_enabled_and_configured: true +`, mobileConfigPath, emptySetupAsst)) + + // first apply with dry-run + assert.Equal(t, "[+] would've applied fleet config\n[+] would've applied MDM profiles\n", + runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"})) + + // then apply for real + assert.Equal(t, "[+] applied fleet config\n[+] applied MDM profiles\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + // features left untouched, not provided + assert.True(t, currentAppConfig.Features.EnableHostUsers) + assert.Equal(t, fleet.MDM{ + EnabledAndConfigured: true, + MacOSSetup: fleet.MacOSSetup{ + MacOSSetupAssistant: optjson.SetString(emptySetupAsst), + EnableReleaseDeviceManually: optjson.SetBool(false), + LockEndUserInfo: optjson.SetBool(false), + }, + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.6.1"), + Deadline: optjson.SetString("2020-02-02"), + }, + WindowsUpdates: fleet.WindowsUpdates{ + DeadlineDays: optjson.SetInt(1), + GracePeriodDays: optjson.SetInt(0), + }, + MacOSSettings: fleet.MacOSSettings{ + CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, + }, + WindowsEnabledAndConfigured: true, + }, currentAppConfig.MDM) + + // start a server to return the bootstrap package + srv, _ := testing_utils.ServeMDMBootstrapPackage(t, "../../../server/service/testdata/bootstrap-packages/signed.pkg", "signed.pkg") + + // Apply global config with bootstrap package + bootstrapURL := srv.URL + "/signed.pkg" + name = writeTmpYml(t, fmt.Sprintf(`--- +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: + bootstrap_package: %s +`, bootstrapURL)) + + // first apply with dry-run + assert.Equal(t, "[+] would've applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"})) + + // then apply for real + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + // features left untouched, not provided + assert.True(t, currentAppConfig.Features.EnableHostUsers) + // MDM settings left untouched except for the bootstrap package + assert.Equal(t, fleet.MDM{ + EnabledAndConfigured: true, + MacOSSetup: fleet.MacOSSetup{ + MacOSSetupAssistant: optjson.SetString(emptySetupAsst), + BootstrapPackage: optjson.SetString(bootstrapURL), + EnableReleaseDeviceManually: optjson.SetBool(false), + LockEndUserInfo: optjson.SetBool(false), + }, + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.6.1"), + Deadline: optjson.SetString("2020-02-02"), + }, + WindowsUpdates: fleet.WindowsUpdates{ + DeadlineDays: optjson.SetInt(1), + GracePeriodDays: optjson.SetInt(0), + }, + MacOSSettings: fleet.MacOSSettings{ + CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, + }, + WindowsEnabledAndConfigured: true, + }, currentAppConfig.MDM) + + // Apply team config. + name = writeTmpYml(t, fmt.Sprintf(` +apiVersion: v1 +kind: fleet +spec: + team: + agent_options: + config: + views: + foo: qux + name: Team1 + mdm: + enable_disk_encryption: false + macos_updates: + minimum_version: 14.6.1 + deadline: 1992-03-01 + windows_updates: + deadline_days: 0 + grace_period_days: 1 + macos_settings: + custom_settings: + - %s + secrets: + - secret: BBB +`, mobileConfigPath)) + + // first apply with dry-run + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"}), "[+] would've applied 1 fleet\n") + + // then apply for real + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name}), "[+] applied 1 fleet\n") + assert.JSONEq(t, string(json.RawMessage(`{"config":{"views":{"foo":"qux"}}}`)), string(*savedTeam.Config.AgentOptions)) + assert.Equal(t, fleet.TeamMDM{ + EnableDiskEncryption: false, + MacOSSettings: fleet.MacOSSettings{ + CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, + }, + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.6.1"), + Deadline: optjson.SetString("1992-03-01"), + }, + MacOSSetup: fleet.MacOSSetup{ + EnableReleaseDeviceManually: optjson.SetBool(false), + LockEndUserInfo: optjson.SetBool(false), + }, + WindowsUpdates: fleet.WindowsUpdates{ + DeadlineDays: optjson.SetInt(0), + GracePeriodDays: optjson.SetInt(1), + }, + }, savedTeam.Config.MDM) + assert.Equal(t, []*fleet.EnrollSecret{{Secret: "BBB"}}, teamEnrollSecrets) + assert.True(t, ds.ApplyEnrollSecretsFuncInvoked) + assert.True(t, ds.BatchSetMDMProfilesFuncInvoked) + + // add macos setup assistant to team + name = writeTmpYml(t, fmt.Sprintf(` +apiVersion: v1 +kind: fleet +spec: + team: + name: Team1 + mdm: + macos_setup: + macos_setup_assistant: %s +`, emptySetupAsst)) + + // first apply with dry-run + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"}), "[+] would've applied 1 fleet\n") + + // then apply for real + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name}), "[+] applied 1 fleet\n") + require.True(t, ds.GetMDMAppleSetupAssistantFuncInvoked) + require.True(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + require.True(t, ds.NewJobFuncInvoked) + // all left untouched, only setup assistant added + assert.Equal(t, fleet.TeamMDM{ + EnableDiskEncryption: false, + MacOSSettings: fleet.MacOSSettings{ + CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, + }, + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.6.1"), + Deadline: optjson.SetString("1992-03-01"), + }, + WindowsUpdates: fleet.WindowsUpdates{ + DeadlineDays: optjson.SetInt(0), + GracePeriodDays: optjson.SetInt(1), + }, + MacOSSetup: fleet.MacOSSetup{ + MacOSSetupAssistant: optjson.SetString(emptySetupAsst), + EnableReleaseDeviceManually: optjson.SetBool(false), + LockEndUserInfo: optjson.SetBool(false), + }, + }, savedTeam.Config.MDM) + + // add bootstrap package to team + name = writeTmpYml(t, fmt.Sprintf(` +apiVersion: v1 +kind: fleet +spec: + team: + name: Team1 + mdm: + macos_setup: + bootstrap_package: %s +`, bootstrapURL)) + + // first apply with dry-run + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name, "--dry-run"}), "[+] would've applied 1 fleet\n") + + // then apply for real + assert.Contains(t, runAppForTestDeprecated(t, []string{"apply", "-f", name}), "[+] applied 1 fleet\n") + // all left untouched, only bootstrap package added + assert.Equal(t, fleet.TeamMDM{ + EnableDiskEncryption: false, + MacOSSettings: fleet.MacOSSettings{ + CustomSettings: []fleet.MDMProfileSpec{{Path: mobileConfigPath}}, + }, + MacOSUpdates: fleet.AppleOSUpdateSettings{ + MinimumVersion: optjson.SetString("14.6.1"), + Deadline: optjson.SetString("1992-03-01"), + }, + WindowsUpdates: fleet.WindowsUpdates{ + DeadlineDays: optjson.SetInt(0), + GracePeriodDays: optjson.SetInt(1), + }, + MacOSSetup: fleet.MacOSSetup{ + MacOSSetupAssistant: optjson.SetString(emptySetupAsst), + BootstrapPackage: optjson.SetString(bootstrapURL), + EnableReleaseDeviceManually: optjson.SetBool(false), + LockEndUserInfo: optjson.SetBool(false), + }, + }, savedTeam.Config.MDM) + + // Apply policies. + var appliedPolicySpecs []*fleet.PolicySpec + ds.ApplyPolicySpecsFunc = func(ctx context.Context, authorID uint, specs []*fleet.PolicySpec) error { + appliedPolicySpecs = specs + return nil + } + name = writeTmpYml(t, policySpec) + assert.Equal(t, "[+] applied 3 policies\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyPolicySpecsFuncInvoked) + assert.Len(t, appliedPolicySpecs, 3) + for _, p := range appliedPolicySpecs { + assert.NotEmpty(t, p.Platform) + } + assert.True(t, ds.TeamByNameFuncInvoked) + + // Apply enroll secrets. + var appliedSecrets []*fleet.EnrollSecret + ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error { + appliedSecrets = secrets + return nil + } + name = writeTmpYml(t, enrollSecretsSpec) + assert.Equal(t, "[+] applied enroll secrets\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyEnrollSecretsFuncInvoked) + assert.Len(t, appliedSecrets, 3) + for _, s := range appliedSecrets { + assert.NotEmpty(t, s.Secret) + } + + // Apply labels. + var appliedLabels []*fleet.LabelSpec + ds.ApplyLabelSpecsWithAuthorFunc = func(ctx context.Context, specs []*fleet.LabelSpec, authorId *uint) error { + appliedLabels = specs + return nil + } + name = writeTmpYml(t, labelsSpec) + assert.Equal(t, "[+] applied 1 label\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyLabelSpecsWithAuthorFuncInvoked) + require.Len(t, appliedLabels, 1) + assert.Equal(t, "pending_updates", appliedLabels[0].Name) + assert.Equal(t, "select 1;", appliedLabels[0].Query) + + // Apply packs. + var appliedPacks []*fleet.PackSpec + ds.ApplyPackSpecsFunc = func(ctx context.Context, specs []*fleet.PackSpec) error { + appliedPacks = specs + return nil + } + ds.ListPacksFunc = func(ctx context.Context, opt fleet.PackListOptions) ([]*fleet.Pack, error) { + return nil, nil + } + name = writeTmpYml(t, packsSpec) + assert.Equal(t, "[+] applied 1 pack\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyPackSpecsFuncInvoked) + require.Len(t, appliedPacks, 1) + assert.Equal(t, "osquery_monitoring", appliedPacks[0].Name) + require.Len(t, appliedPacks[0].Queries, 2) + + // Apply queries. + var appliedQueries []*fleet.Query + ds.QueryByNameFunc = func(ctx context.Context, teamID *uint, name string) (*fleet.Query, error) { + return nil, ¬FoundError{} + } + ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query, queriesToDiscardResults map[uint]struct{}) error { + appliedQueries = queries + return nil + } + name = writeTmpYml(t, queriesSpec) + assert.Equal(t, "[+] applied 1 report\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.ApplyQueriesFuncInvoked) + require.Len(t, appliedQueries, 1) + assert.Equal(t, "app_schemes", appliedQueries[0].Name) + assert.Equal(t, "select * from app_schemes;", appliedQueries[0].Query) +} + +func TestApplyMacosSetupDeprecatedKeys(t *testing.T) { + mockStore := struct { + sync.Mutex + appConfig *fleet.AppConfig + metaHash []byte + }{} + + setupServer := func(t *testing.T, premium bool) *mock.Store { + tier := fleet.TierFree + if premium { + tier = fleet.TierPremium + } + license := &fleet.LicenseInfo{Tier: tier, Expiration: time.Now().Add(24 * time.Hour)} + depStorage := SetupMockDEPStorageAndMockDEPServer(t) + _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{License: license, DEPStorage: depStorage}) + + tm1 := &fleet.Team{ID: 1, Name: "tm1"} + teamsByName := map[string]*fleet.Team{ + "tm1": tm1, + } + teamsByID := map[uint]*fleet.Team{ + tm1.ID: tm1, + } + ds.NewJobFunc = func(ctx context.Context, job *fleet.Job) (*fleet.Job, error) { + return job, nil + } + ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) { + team, ok := teamsByName[name] + if !ok { + return nil, ¬FoundError{} + } + clone := *team + return &clone, nil + } + + tmID := 1 // new teams will start at 2 + ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { + tmID++ + team.ID = uint(tmID) //nolint:gosec // dismiss G115 + clone := *team + teamsByName[team.Name] = &clone + teamsByID[team.ID] = &clone + return team, nil + } + + ds.TeamWithExtrasFunc = func(ctx context.Context, id uint) (*fleet.Team, error) { + tm, ok := teamsByID[id] + if !ok { + return nil, ¬FoundError{} + } + clone := *tm + return &clone, nil + } + + ds.ListTeamsFunc = func(ctx context.Context, filter fleet.TeamFilter, opt fleet.ListOptions) ([]*fleet.Team, error) { + tms := make([]*fleet.Team, 0, len(teamsByName)) + for _, tm := range teamsByName { + clone := *tm + tms = append(tms, &clone) + } + sort.Slice(tms, func(i, j int) bool { + l, r := tms[i], tms[j] + return l.Name < r.Name + }) + return tms, nil + } + + // initialize mockConfig + mockStore.Lock() + mockStore.appConfig = &fleet.AppConfig{ + OrgInfo: fleet.OrgInfo{OrgName: "Fleet"}, + ServerSettings: fleet.ServerSettings{ServerURL: "https://example.org"}, + MDM: fleet.MDM{EnabledAndConfigured: true}, + SMTPSettings: &fleet.SMTPSettings{}, + SSOSettings: &fleet.SSOSettings{}, + } + if premium { + mockStore.appConfig.ServerSettings.EnableAnalytics = true + } + mockStore.Unlock() + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + mockStore.Lock() + defer mockStore.Unlock() + clone, err := mockStore.appConfig.Clone() + return clone.(*fleet.AppConfig), err + } + + ds.SaveAppConfigFunc = func(ctx context.Context, info *fleet.AppConfig) error { + mockStore.Lock() + defer mockStore.Unlock() + clone, err := info.Clone() + if err != nil { + return err + } + mockStore.appConfig = clone.(*fleet.AppConfig) + return nil + } + + ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, isNew bool, teamID *uint) (bool, error) { + return true, nil + } + ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { + teamsByName[team.Name] = team + teamsByID[team.ID] = team + return team, nil + } + + asstsByTeam := make(map[uint]*fleet.MDMAppleSetupAssistant) + asstID := 0 + ds.SetOrUpdateMDMAppleSetupAssistantFunc = func(ctx context.Context, asst *fleet.MDMAppleSetupAssistant) (*fleet.MDMAppleSetupAssistant, error) { + asstID++ + asst.ID = uint(asstID) //nolint:gosec // dismiss G115 + asst.UploadedAt = time.Now() + + var tmID uint + if asst.TeamID != nil { + tmID = *asst.TeamID + } + asstsByTeam[tmID] = asst + + return asst, nil + } + + ds.DeleteMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) error { + var tmID uint + if teamID != nil { + tmID = *teamID + } + delete(asstsByTeam, tmID) + return nil + } + + ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) { + var tmID uint + if teamID != nil { + tmID = *teamID + } + if asst, ok := asstsByTeam[tmID]; ok { + return asst, nil + } + return nil, ¬FoundError{} + } + ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error { + return nil + } + ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error { + return nil + } + ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) { + return nil, nil + } + + ds.GetMDMAppleEnrollmentProfileByTypeFunc = func(ctx context.Context, typ fleet.MDMAppleEnrollmentType) (*fleet.MDMAppleEnrollmentProfile, error) { + return &fleet.MDMAppleEnrollmentProfile{Token: "foobar"}, nil + } + ds.CountABMTokensWithTermsExpiredFunc = func(ctx context.Context) (int, error) { + return 0, nil + } + + ds.GetABMTokenOrgNamesAssociatedWithTeamFunc = func(ctx context.Context, teamID *uint) ([]string, error) { + return []string{"foobar"}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{{ID: 1}}, nil + } + + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) { + return nil, ¬FoundError{} + } + + return ds + } + + emptyMacosSetup := writeTmpJSON(t, map[string]any{}) + invalidURLMacosSetup := writeTmpJSON(t, map[string]any{ + "url": "https://example.com", + }) + invalidAwaitMacosSetup := writeTmpJSON(t, map[string]any{ + "await_device_configured": true, + }) + + const ( + appConfigSpec = ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: + bootstrap_package: %s + macos_setup_assistant: %s +` + appConfigEnableReleaseSpec = appConfigSpec + ` + enable_release_device_manually: %s +` + appConfigNoKeySpec = ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: +` + appConfigSpecEnableEndUserAuth = ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: + enable_end_user_authentication: %s +` + team1Spec = ` +apiVersion: v1 +kind: fleet +spec: + team: + name: tm1 + mdm: + macos_setup: + bootstrap_package: %s + macos_setup_assistant: %s +` + team1EnableReleaseSpec = team1Spec + ` + enable_release_device_manually: %s + require_all_software_macos: %s +` + team1NoKeySpec = ` +apiVersion: v1 +kind: fleet +spec: + team: + name: tm1 + mdm: + macos_setup: +` + team1And2Spec = ` +apiVersion: v1 +kind: fleet +spec: + team: + name: tm1 + mdm: + macos_setup: + bootstrap_package: %s + macos_setup_assistant: %s +--- +apiVersion: v1 +kind: fleet +spec: + team: + name: tm2 + mdm: + macos_setup: + bootstrap_package: %s + macos_setup_assistant: %s +` + team1SpecEnableEndUserAuth = ` +apiVersion: v1 +kind: fleet +spec: + team: + name: tm1 + mdm: + macos_setup: + enable_end_user_authentication: %s +` + ) + + t.Run("free license", func(t *testing.T) { + ds := setupServer(t, false) + + // appconfig macos setup assistant + name := writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", emptyMacosSetup)) + RunAppCheckErr(t, []string{"apply", "-f", name}, `applying fleet config: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "https://example.com", "")) + RunAppCheckErr(t, []string{"apply", "-f", name}, `applying fleet config: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // team macos setup assistant + name = writeTmpYml(t, fmt.Sprintf(team1Spec, "", emptyMacosSetup)) + RunAppCheckErr(t, []string{"apply", "-f", name}, `applying teams: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + name = writeTmpYml(t, fmt.Sprintf(team1Spec, "https://example.com", "")) + RunAppCheckErr(t, []string{"apply", "-f", name}, `applying fleets: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + // enable_end_user_authentication is premium only + name = writeTmpYml(t, fmt.Sprintf(appConfigSpecEnableEndUserAuth, "true")) + RunAppCheckErr(t, []string{"apply", "-f", name}, + `applying fleet config: PATCH /api/latest/fleet/config received status 422 Validation Failed: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + name = writeTmpYml(t, fmt.Sprintf(team1SpecEnableEndUserAuth, "true")) + RunAppCheckErr(t, []string{"apply", "-f", name}, `applying teams: missing or invalid license`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + }) + + t.Run("setup assistant invalid file, not json, invalid json", func(t *testing.T) { + ds := setupServer(t, true) + + // create invalid json file + tmpFile, err := os.CreateTemp(t.TempDir(), "*.json") + require.NoError(t, err) + _, err = tmpFile.WriteString(`not json`) + require.NoError(t, err) + invalidJSON := tmpFile.Name() + + // appconfig invalid file + name := writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", "no_such_file.json")) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `no such file`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // appconfig not .json + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", "no_such_file.txt")) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `Couldn’t edit apple_setup_assistant. The file should be a .json file.`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // appconfig invalid json + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", invalidJSON)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `The file should include valid JSON`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // team invalid file + name = writeTmpYml(t, fmt.Sprintf(team1Spec, "", "no_such_file.json")) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `no such file`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + // team not .json + name = writeTmpYml(t, fmt.Sprintf(team1Spec, "", "no_such_file.txt")) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `Couldn’t edit apple_setup_assistant. The file should be a .json file.`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + // team invalid json + name = writeTmpYml(t, fmt.Sprintf(team1Spec, "", invalidJSON)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `The file should include valid JSON`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + }) + + t.Run("setup assistant get and apply roundtrip", func(t *testing.T) { + ds := setupServer(t, true) + + b, err := os.ReadFile(filepath.Join("testdata", "macosSetupExpectedAppConfigEmpty.yml")) + require.NoError(t, err) + expectedEmptyAppCfg := string(b) + + b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedAppConfigSet.yml")) + require.NoError(t, err) + expectedAppCfgSet := fmt.Sprintf(string(b), "", emptyMacosSetup, "", emptyMacosSetup) + expectedAppCfgSetReleaseEnabled := strings.ReplaceAll(expectedAppCfgSet, `enable_release_device_manually: false`, `enable_release_device_manually: true`) + + b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1Empty.yml")) + require.NoError(t, err) + expectedEmptyTm1 := string(b) + + b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1Set.yml")) + require.NoError(t, err) + expectedTm1Set := fmt.Sprintf(string(b), "", "", "", "") + expectedTm1SetReleaseAndRequireEnabled := strings.ReplaceAll(expectedTm1Set, `enable_release_device_manually: false`, `enable_release_device_manually: true`) + expectedTm1SetReleaseAndRequireEnabled = strings.ReplaceAll(expectedTm1SetReleaseAndRequireEnabled, `require_all_software_macos: false`, `require_all_software_macos: true`) + + b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1And2Empty.yml")) + require.NoError(t, err) + expectedEmptyTm1And2 := string(b) + + b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1And2Set.yml")) + require.NoError(t, err) + expectedTm1And2Set := fmt.Sprintf(string(b), "", emptyMacosSetup, "", emptyMacosSetup, "", emptyMacosSetup, "", emptyMacosSetup) + + // get without setup assistant set + assert.YAMLEq(t, expectedEmptyAppCfg, runAppForTestDeprecated(t, []string{"get", "config", "--yaml"})) + assert.YAMLEq(t, expectedEmptyTm1, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // apply with dry-run, appconfig + name := writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", emptyMacosSetup)) + assert.Equal(t, "[+] would've applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "--dry-run", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // apply with dry-run, teams + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", emptyMacosSetup, "", emptyMacosSetup)) + assert.Equal(t, "[+] would've applied 2 fleets\n", runAppForTestDeprecated(t, []string{"apply", "--dry-run", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + // get, setup assistant still not set + assert.YAMLEq(t, expectedEmptyAppCfg, runAppForTestDeprecated(t, []string{"get", "config", "--yaml"})) + assert.YAMLEq(t, expectedEmptyTm1, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // apply appconfig for real, and enable release device + name = writeTmpYml(t, fmt.Sprintf(appConfigEnableReleaseSpec, "", emptyMacosSetup, "true")) + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + + // apply teams for real + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", emptyMacosSetup, "", emptyMacosSetup)) + ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false + assert.Equal(t, "[+] applied 2 fleets\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.True(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveTeamFuncInvoked) + + // get, setup assistant is now set + assert.YAMLEq(t, expectedAppCfgSetReleaseEnabled, runAppForTestDeprecated(t, []string{"get", "config", "--yaml"})) + assert.YAMLEq(t, expectedTm1And2Set, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // clear with dry-run, appconfig + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", "")) + ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false + ds.SaveAppConfigFuncInvoked = false + assert.Equal(t, "[+] would've applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "--dry-run", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + + // clear with dry-run, teams + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", "", "", "")) + ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false + ds.SaveTeamFuncInvoked = false + assert.Equal(t, "[+] would've applied 2 fleets\n", runAppForTestDeprecated(t, []string{"apply", "--dry-run", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.SaveTeamFuncInvoked) + + // apply appconfig without the setup assistant key + name = writeTmpYml(t, appConfigNoKeySpec) + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + + // apply team 1 without the setup assistant key + name = writeTmpYml(t, team1NoKeySpec) + assert.Equal(t, "[+] applied 1 fleet\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveTeamFuncInvoked) + + // get, results unchanged + assert.YAMLEq(t, expectedAppCfgSetReleaseEnabled, runAppForTestDeprecated(t, []string{"get", "config", "--yaml"})) + assert.YAMLEq(t, expectedTm1And2Set, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // clear appconfig for real + name = writeTmpYml(t, fmt.Sprintf(appConfigEnableReleaseSpec, "", "", "false")) + ds.SaveAppConfigFuncInvoked = false + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + + // clear teams for real + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", "", "", "")) + ds.SaveTeamFuncInvoked = false + assert.Equal(t, "[+] applied 2 fleets\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveTeamFuncInvoked) + + // get, results now empty + assert.YAMLEq(t, expectedEmptyAppCfg, runAppForTestDeprecated(t, []string{"get", "config", "--yaml"})) + assert.YAMLEq(t, expectedEmptyTm1And2, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // apply team 1 without the setup assistant key but enable device release + name = writeTmpYml(t, fmt.Sprintf(team1EnableReleaseSpec, "", "", "true", "true")) + ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked = false + ds.DeleteMDMAppleSetupAssistantFuncInvoked = false + ds.SaveTeamFuncInvoked = false + assert.Equal(t, "[+] applied 1 fleet\n", runAppForTestDeprecated(t, []string{"apply", "-f", name})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.SaveTeamFuncInvoked) + + assert.YAMLEq(t, expectedTm1SetReleaseAndRequireEnabled, runAppForTestDeprecated(t, []string{"get", "teams", "--yaml"})) + + // apply appconfig with invalid URL key + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", invalidURLMacosSetup)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, "The automatic enrollment profile can't include url.") + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + + // apply teams with invalid URL key + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", invalidURLMacosSetup, "", invalidURLMacosSetup)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, "The automatic enrollment profile can't include url.") + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + + // apply appconfig with invalid await_device_configured key + name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", invalidAwaitMacosSetup)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `The profile can't include "await_device_configured" option.`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + + // apply teams with invalid await_device_configured key + name = writeTmpYml(t, fmt.Sprintf(team1And2Spec, "", invalidAwaitMacosSetup, "", invalidAwaitMacosSetup)) + _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + require.ErrorContains(t, err, `The profile can't include "await_device_configured" option.`) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + }) + + t.Run("new bootstrap package", func(t *testing.T) { + cases := []struct { + pkgName string + expectedErr error + }{ + {"signed.pkg", nil}, + {"unsigned.pkg", errors.New("applying fleet config: Couldn’t edit macos_bootstrap_package. The macos_bootstrap_package must be signed. Learn how to sign the package in the Fleet documentation: https://fleetdm.com/learn-more-about/setup-experience/bootstrap-package")}, + {"invalid.tar.gz", errors.New("applying fleet config: Couldn’t edit macos_bootstrap_package. The file must be a package (.pkg).")}, + {"wrong-toc.pkg", errors.New("applying fleet config: checking package signature: decompressing TOC: unexpected EOF")}, + } + + for _, c := range cases { + t.Run(c.pkgName, func(t *testing.T) { + srv, pkgLen := testing_utils.ServeMDMBootstrapPackage(t, + filepath.Join("../../../server/service/testdata/bootstrap-packages", c.pkgName), c.pkgName) + ds := setupServer(t, true) + ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error { + require.Equal(t, len(bp.Bytes), pkgLen) + return nil + } + ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) { + return nil, ¬FoundError{} + } + + mockStore.Lock() + assert.Empty(t, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + + // create the app config yaml with server url for bootstrap package + tmpFilename := writeTmpYml(t, fmt.Sprintf(appConfigSpec, srv.URL, "")) + + if c.expectedErr != nil { + RunAppCheckErr(t, []string{"apply", "-f", tmpFilename}, c.expectedErr.Error()) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Empty(t, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + } else { + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", tmpFilename})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.True(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Equal(t, srv.URL, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + } + }) + } + }) + + t.Run("bootstrap package with manual agent install", func(t *testing.T) { + pkgName := "signed.pkg" + srv, pkgLen := testing_utils.ServeMDMBootstrapPackage(t, filepath.Join("../../../server/service/testdata/bootstrap-packages", pkgName), + pkgName) + ds := setupServer(t, true) + ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error { + require.Equal(t, len(bp.Bytes), pkgLen) + return nil + } + ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) { + return nil, ¬FoundError{} + } + + mockStore.Lock() + assert.Empty(t, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + + spec := ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: + bootstrap_package: %s + manual_agent_install: true +` + + // create the app config yaml with server url for bootstrap package + tmpFilename := writeTmpYml(t, fmt.Sprintf(spec, srv.URL)) + + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", tmpFilename})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.True(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Equal(t, srv.URL, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + assert.True(t, mockStore.appConfig.MDM.MacOSSetup.ManualAgentInstall.Value) + mockStore.Unlock() + }) + + t.Run("replace bootstrap package", func(t *testing.T) { + pkgName := "signed.pkg" + pkgBytes, err := os.ReadFile(filepath.Join("../../../server/service/testdata/bootstrap-packages", pkgName)) + require.NoError(t, err) + pkgHash := sha256.New() + n, err := io.Copy(pkgHash, bytes.NewReader(pkgBytes)) + require.NoError(t, err) + require.Equal(t, int64(len(pkgBytes)), n) + + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Content-Length", strconv.Itoa(len(pkgBytes))) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment;filename="%s"`, pkgName)) + if n, err := w.Write(pkgBytes); err != nil { + assert.NoError(t, err) + assert.Equal(t, len(pkgBytes), n) + } + })) + defer srv.Close() + + ds := setupServer(t, true) + ds.InsertMDMAppleBootstrapPackageFunc = func(ctx context.Context, bp *fleet.MDMAppleBootstrapPackage, pkgStore fleet.MDMBootstrapPackageStore) error { + mockStore.Lock() + defer mockStore.Unlock() + require.Equal(t, pkgName, bp.Name) + require.Len(t, bp.Bytes, len(pkgBytes)) + require.Equal(t, pkgHash.Sum(nil), bp.Sha256) + mockStore.metaHash = bp.Sha256 + return nil + } + ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error { + require.Equal(t, uint(0), teamID) + return nil + } + ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) { + mockStore.Lock() + defer mockStore.Unlock() + return &fleet.MDMAppleBootstrapPackage{ + TeamID: 0, + Name: pkgName, + Sha256: mockStore.metaHash, + Token: "token", + CreatedAt: time.Now().Add(-1 * time.Hour), + UpdatedAt: time.Now().Add(-1 * time.Hour), + }, nil + } + + mockStore.Lock() + mockStore.metaHash = []byte("foobar") // initial hash is a throwaway + mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage = optjson.SetString("https://example.com") // initial value is a throwaway + mockStore.Unlock() + + // upload a new package + tmpFilename := writeTmpYml(t, fmt.Sprintf(appConfigSpec, srv.URL, "")) + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", tmpFilename})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.True(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Equal(t, srv.URL, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + + ds.GetMDMAppleBootstrapPackageMetaFuncInvoked = false + ds.InsertMDMAppleBootstrapPackageFuncInvoked = false + ds.DeleteMDMAppleBootstrapPackageFuncInvoked = false + ds.SaveAppConfigFuncInvoked = false + + // running again should not re-upload + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", tmpFilename})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Equal(t, srv.URL, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + + ds.GetMDMAppleBootstrapPackageMetaFuncInvoked = false + ds.InsertMDMAppleBootstrapPackageFuncInvoked = false + ds.DeleteMDMAppleBootstrapPackageFuncInvoked = false + ds.SaveAppConfigFuncInvoked = false + + // empty server url should delete the package + tmpFilename = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", "")) + assert.Equal(t, "[+] applied fleet config\n", runAppForTestDeprecated(t, []string{"apply", "-f", tmpFilename})) + assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + assert.True(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + assert.True(t, ds.SaveAppConfigFuncInvoked) + mockStore.Lock() + assert.Empty(t, mockStore.appConfig.MDM.MacOSSetup.BootstrapPackage.Value) + mockStore.Unlock() + }) + + // // TODO: restore this test when we have a way to mock the Apple Business Manager API in + // // fleetctl tests + // t.Run("enable end user authentication", func(t *testing.T) { + // ds := setupServer(t, true) + + // // setup app config + // b, err := os.ReadFile(filepath.Join("testdata", "macosSetupExpectedAppConfigEmpty.yml")) + // require.NoError(t, err) + // expectedNotSetAppConfg := string(b) + // assert.YAMLEq(t, expectedNotSetAppConfg, runAppForTest(t, []string{"get", "config", "--yaml"})) + + // // enable end user auth in app config + // name := writeTmpYml(t, fmt.Sprintf(appConfigSpecEnableEndUserAuth, "true")) + // _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + // require.NoError(t, err) + // assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + // assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + // assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + // assert.True(t, ds.SaveAppConfigFuncInvoked) + // expectedSetAppCfg := strings.ReplaceAll(expectedNotSetAppConfg, "enable_end_user_authentication: false", "enable_end_user_authentication: true") + // assert.YAMLEq(t, expectedSetAppCfg, runAppForTest(t, []string{"get", "config", "--yaml"})) + // ds.SaveAppConfigFuncInvoked = false + + // // disable end user auth in app config + // name = writeTmpYml(t, fmt.Sprintf(appConfigSpecEnableEndUserAuth, "false")) + // _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + // require.NoError(t, err) + // assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + // assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + // assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + // assert.True(t, ds.SaveAppConfigFuncInvoked) + // assert.YAMLEq(t, expectedNotSetAppConfg, runAppForTest(t, []string{"get", "config", "--yaml"})) + // ds.SaveAppConfigFuncInvoked = false + + // // setup team config + // assert.False(t, ds.SaveTeamFuncInvoked) + // b, err = os.ReadFile(filepath.Join("testdata", "macosSetupExpectedTeam1Empty.yml")) + // require.NoError(t, err) + // expectedNotSetTeam1 := string(b) + // assert.YAMLEq(t, expectedNotSetTeam1, runAppForTest(t, []string{"get", "teams", "--yaml"})) + + // // enable end user auth in team config + // name = writeTmpYml(t, fmt.Sprintf(team1SpecEnableEndUserAuth, "true")) + // _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + // require.NoError(t, err) + // assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + // assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + // assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.SaveAppConfigFuncInvoked) + // assert.True(t, ds.SaveTeamFuncInvoked) + // expectedSetTeam1 := strings.ReplaceAll(expectedNotSetTeam1, "enable_end_user_authentication: false", "enable_end_user_authentication: true") + // expectedSetTeam1 = strings.ReplaceAll(expectedSetTeam1, "enable_host_users: false", "enable_host_users: true") + // expectedSetTeam1 = strings.ReplaceAll(expectedSetTeam1, "enable_software_inventory: false", "enable_software_inventory: true") + // assert.YAMLEq(t, expectedSetTeam1, runAppForTest(t, []string{"get", "teams", "--yaml"})) + // ds.SaveTeamFuncInvoked = false + + // // disable end user auth in team config + // name = writeTmpYml(t, fmt.Sprintf(team1SpecEnableEndUserAuth, "false")) + // _, err = runAppNoChecksDeprecated([]string{"apply", "-f", name}) + // require.NoError(t, err) + // assert.False(t, ds.SetOrUpdateMDMAppleSetupAssistantFuncInvoked) + // assert.False(t, ds.GetMDMAppleBootstrapPackageMetaFuncInvoked) + // assert.False(t, ds.InsertMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.DeleteMDMAppleBootstrapPackageFuncInvoked) + // assert.False(t, ds.SaveAppConfigFuncInvoked) + // assert.True(t, ds.SaveTeamFuncInvoked) + // expectedSetTeam1 = strings.ReplaceAll(expectedSetTeam1, "enable_end_user_authentication: true", "enable_end_user_authentication: false") + // assert.YAMLEq(t, expectedSetTeam1, runAppForTest(t, []string{"get", "teams", "--yaml"})) + // ds.SaveTeamFuncInvoked = false + // }) +} + +func TestApplySpecsDeprecatedKeys(t *testing.T) { + // create a macos setup json file (content not important) + macSetupFile := writeTmpJSON(t, map[string]any{}) + + setupDS := func(ds *mock.Store) { + // labels + ds.ApplyLabelSpecsWithAuthorFunc = func(ctx context.Context, specs []*fleet.LabelSpec, authorId *uint) error { + return nil + } + + ds.ConditionalAccessMicrosoftGetFunc = func(ctx context.Context) (*fleet.ConditionalAccessMicrosoftIntegration, error) { + return &fleet.ConditionalAccessMicrosoftIntegration{}, nil + } + + // teams - team ID 1 already exists + teamsByName := map[string]*fleet.Team{ + "team1": { + ID: 1, + Name: "team1", + Description: "team1 description", + }, + } + + ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) { + team, ok := teamsByName[name] + if !ok { + return nil, ¬FoundError{} + } + return team, nil + } + + i := 1 // new teams will start at 2 + ds.NewTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { + i++ + team.ID = uint(i) //nolint:gosec // dismiss G115 + teamsByName[team.Name] = team + return team, nil + } + + agentOpts := json.RawMessage(`{"config":{}}`) + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return &fleet.AppConfig{AgentOptions: &agentOpts}, nil + } + + ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) { + teamsByName[team.Name] = team + return team, nil + } + + ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, isNew bool, teamID *uint) (bool, error) { + return true, nil + } + ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error { + return nil + } + + // app config + ds.ListUsersFunc = func(ctx context.Context, opt fleet.UserListOptions) ([]*fleet.User, error) { + return userRoleSpecList, nil + } + + ds.UserByEmailFunc = func(ctx context.Context, email string) (*fleet.User, error) { + if email == "admin1@example.com" { + return userRoleSpecList[0], nil + } + return userRoleSpecList[1], nil + } + + ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) { + return &fleet.AppConfig{OrgInfo: fleet.OrgInfo{OrgName: "Fleet"}, ServerSettings: fleet.ServerSettings{ServerURL: "https://example.org"}}, nil + } + + ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error { + return nil + } + ds.SetOrUpdateMDMWindowsConfigProfileFunc = func(ctx context.Context, cp fleet.MDMWindowsConfigProfile) error { + return nil + } + ds.DeleteMDMWindowsConfigProfileByTeamAndNameFunc = func(ctx context.Context, teamID *uint, profileName string) error { + return nil + } + + // VPP/AMB + ds.SaveABMTokenFunc = func(ctx context.Context, tok *fleet.ABMToken) error { + return nil + } + ds.ListVPPTokensFunc = func(ctx context.Context) ([]*fleet.VPPTokenDB, error) { + return []*fleet.VPPTokenDB{}, nil + } + ds.ListABMTokensFunc = func(ctx context.Context) ([]*fleet.ABMToken, error) { + return []*fleet.ABMToken{}, nil + } + } + + cases := []struct { + desc string + flags []string + spec string + wantOutput string + wantErr string + }{ + { + desc: "empty team spec", + spec: ` +apiVersion: v1 +kind: fleet +spec: +`, + wantOutput: "[+] applied 1 fleet", + }, + { + desc: "empty team name", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: "" +`, + wantErr: `422 Validation Failed: name may not be empty`, + }, + { + desc: "invalid agent options for existing team", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + agent_options: + config: + blah: nope +`, + wantErr: `400 Bad Request: unsupported key provided: "blah"`, + }, + { + desc: "invalid top-level key for team", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + blah: nope +`, + wantErr: `400 Bad Request: unsupported key provided: "blah"`, + }, + { + desc: "invalid known key's value type for team cannot be forced", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: 123 +`, + flags: []string{"--force"}, + wantErr: `400 Bad Request: invalid value type at 'specs.name': expected string but got number`, + }, + { + desc: "unknown key for team can be forced", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + blah: true +`, + flags: []string{"--force"}, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "invalid agent options for new team", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + config: + blah: nope +`, + wantErr: `400 Bad Request: unsupported key provided: "blah"`, + }, + { + desc: "invalid agent options dry-run", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + config: + blah: nope +`, + flags: []string{"--dry-run"}, + wantErr: `400 Bad Request: unsupported key provided: "blah"`, + }, + { + desc: "invalid agent options force", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + config: + blah: nope +`, + flags: []string{"--force"}, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "invalid agent options field type", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + config: + options: + aws_debug: 123 +`, + flags: []string{"--dry-run"}, + wantErr: `400 Bad Request: invalid value type at 'options.aws_debug': expected bool but got number`, + }, + { + desc: "invalid team agent options command-line flag", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + command_line_flags: + no_such_flag: 123 +`, + wantErr: `400 Bad Request: unsupported key provided: "no_such_flag"`, + }, + { + desc: "valid team agent options command-line flag", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + command_line_flags: + enable_tables: "abc" +`, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "invalid agent options field type in overrides", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW + agent_options: + config: + options: + aws_debug: true + overrides: + platforms: + darwin: + options: + aws_debug: 123 +`, + wantErr: `400 Bad Request: invalid value type at 'options.aws_debug': expected bool but got number`, + }, + { + desc: "empty config", + spec: ` +apiVersion: v1 +kind: config +spec: +`, + wantOutput: ``, // no output for empty config + }, + { + desc: "config with blank required org name", + spec: ` +apiVersion: v1 +kind: config +spec: + org_info: + org_name: "" +`, + wantErr: `422 Validation Failed: organization name must be present`, + }, + { + desc: "config with blank required server url", + spec: ` +apiVersion: v1 +kind: config +spec: + server_settings: + server_url: "" +`, + wantErr: `422 Validation Failed: Fleet server URL must be present`, + }, + { + desc: "config with unknown key", + spec: ` +apiVersion: v1 +kind: config +spec: + server_settings: + foo: bar +`, + wantErr: `400 Bad Request: unsupported key provided: "foo"`, + }, + { + desc: "config with invalid key type", + spec: ` +apiVersion: v1 +kind: config +spec: + server_settings: + server_url: 123 +`, + wantErr: `400 Bad request: failed to decode app config`, + }, + { + desc: "config with invalid agent options in dry-run", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + foo: bar +`, + flags: []string{"--dry-run"}, + wantErr: `400 Bad Request: unsupported key provided: "foo"`, + }, + { + desc: "config with invalid agent options data type in dry-run", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + aws_debug: 123 +`, + flags: []string{"--dry-run"}, + wantErr: `400 Bad Request: invalid value type at 'options.aws_debug': expected bool but got number`, + }, + { + desc: "config with invalid agent options data type with force", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + config: + options: + aws_debug: 123 +`, + flags: []string{"--force"}, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "config with invalid agent options command-line flags", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + command_line_flags: + enable_tables: "foo" + no_such_flag: false +`, + wantErr: `400 Bad Request: unsupported key provided: "no_such_flag"`, + }, + { + desc: "config with invalid value for agent options command-line flags", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + command_line_flags: + enable_tables: 123 +`, + wantErr: `400 Bad Request: invalid value type at 'enable_tables': expected string but got number`, + }, + { + desc: "config with valid agent options command-line flags", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + command_line_flags: + enable_tables: "abc" +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "dry-run set with unsupported spec", + spec: ` +apiVersion: v1 +kind: label +spec: + name: label1 + query: SELECT 1 +`, + flags: []string{"--dry-run"}, + wantOutput: `[!] ignoring labels, dry run mode only supported for 'config' and 'fleet' specs`, + }, + { + desc: "dry-run set with various specs, appconfig warning for legacy", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW +--- +apiVersion: v1 +kind: label +spec: + name: label1 + query: SELECT 1 +--- +apiVersion: v1 +kind: config +spec: + host_settings: + enable_software_inventory: true +`, + flags: []string{"--dry-run"}, + wantErr: `400 Bad request: warning: deprecated settings were used in the configuration: [host_settings]`, + wantOutput: `[!] ignoring labels, dry run mode only supported for 'config' and 'fleet' spec`, + }, + { + desc: "dry-run set with various specs, no errors", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: teamNEW +--- +apiVersion: v1 +kind: label +spec: + name: label1 + query: SELECT 1 +--- +apiVersion: v1 +kind: config +spec: + features: + enable_software_inventory: true +`, + flags: []string{"--dry-run"}, + wantOutput: `[!] ignoring labels, dry run mode only supported for 'config' and 'fleet' specs +[+] would've applied fleet config +[+] would've applied 1 fleet`, + }, + { + desc: "macos_updates deadline set but minimum_version empty", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + deadline: 2022-01-04 +`, + wantErr: `422 Validation Failed: minimum_version is required when deadline is provided`, + }, + { + desc: "macos_updates minimum_version set but deadline empty", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + minimum_version: "12.2" +`, + wantErr: `422 Validation Failed: deadline is required when minimum_version is provided`, + }, + { + desc: "macos_updates.minimum_version with build version", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + minimum_version: "12.2 (ABCD)" + deadline: 1892-01-01 +`, + wantErr: `422 Validation Failed: minimum_version accepts version numbers only. (E.g., "13.0.1.") NOT "Ventura 13" or "13.0.1 (22A400)"`, + }, + { + desc: "macos_updates.deadline with timestamp", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "1892-01-01T00:00:00Z" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "macos_updates.deadline with invalid date", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "18-01-01" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "macos_updates.deadline with incomplete date", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "2022-01" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "windows_updates.deadline_days but grace period empty", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: 5 +`, + wantErr: `422 Validation Failed: grace_period_days is required when deadline_days is provided`, + }, + { + desc: "windows_updates.grace_period_days but deadline empty", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + grace_period_days: 5 +`, + wantErr: `422 Validation Failed: deadline_days is required when grace_period_days is provided`, + }, + { + desc: "windows_updates.deadline_days out of range", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: 9999 + grace_period_days: 1 +`, + wantErr: `422 Validation Failed: deadline_days must be an integer between 0 and 30`, + }, + { + desc: "windows_updates.grace_period_days out of range", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: 1 + grace_period_days: 9999 +`, + wantErr: `422 Validation Failed: grace_period_days must be an integer between 0 and 7`, + }, + { + desc: "windows_updates.deadline_days not a number", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: abc + grace_period_days: 1 +`, + wantErr: `400 Bad Request: invalid value type at 'specs.mdm.windows_updates.deadline_days': expected int but got string`, + }, + { + desc: "windows_updates.grace_period_days not a number", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: 1 + grace_period_days: true +`, + wantErr: `400 Bad Request: invalid value type at 'specs.mdm.windows_updates.grace_period_days': expected int but got bool`, + }, + { + desc: "windows_updates valid", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: 5 + grace_period_days: 1 +`, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "windows_updates unset valid", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + windows_updates: + deadline_days: + grace_period_days: +`, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "missing required sso entity_id", + spec: fmt.Sprintf(` +apiVersion: v1 +kind: config +spec: + sso_settings: + enable_sso: true + entity_id: "" + issuer_uri: "http://localhost:8080/simplesaml/saml2/idp/SSOService.php" + idp_name: "SimpleSAML" + metadata_url: "%s" +`, testSAMLIDPMetadataURL), + wantErr: `422 Validation Failed: required`, + }, + { + desc: "missing required sso idp_name", + spec: fmt.Sprintf(` +apiVersion: v1 +kind: config +spec: + sso_settings: + enable_sso: true + entity_id: "https://localhost:8080" + issuer_uri: "http://localhost:8080/simplesaml/saml2/idp/SSOService.php" + idp_name: "" + metadata_url: "%s" +`, testSAMLIDPMetadataURL), + wantErr: `422 Validation Failed: required`, + }, + { + desc: "missing required failing policies destination_url", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + failing_policies_webhook: + enable_failing_policies_webhook: true + destination_url: "" + policy_ids: + - 1 + host_batch_size: 1000 + interval: 1h +`, + wantErr: `422 Validation Failed: destination_url is required to enable the failing policies webhook`, + }, + { + desc: "missing required vulnerabilities destination_url", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + vulnerabilities_webhook: + enable_vulnerabilities_webhook: true + destination_url: "" + host_batch_size: 1000 + interval: 1h +`, + wantErr: `422 Validation Failed: destination_url is required to enable the vulnerabilities webhook`, + }, + { + desc: "missing required host status destination_url", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + host_status_webhook: + enable_host_status_webhook: true + destination_url: "" + days_count: 10 + host_percentage: 10 + interval: 1h +`, + wantErr: `422 Validation Failed: destination_url is required to enable the host status webhook`, + }, + { + desc: "missing required host status days_count", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + host_status_webhook: + enable_host_status_webhook: true + destination_url: "http://some/url" + days_count: 0 + host_percentage: 10 + interval: 1h +`, + wantErr: `422 Validation Failed: days_count must be > 0 to enable the host status webhook`, + }, + { + desc: "missing required host status host_percentage", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + host_status_webhook: + enable_host_status_webhook: true + destination_url: "http://some/url" + days_count: 10 + host_percentage: -1 + interval: 1h +`, + wantErr: `422 Validation Failed: host_percentage must be > 0 to enable the host status webhook`, + }, + { + desc: "config with FIM values for agent options (#8699)", + spec: ` +apiVersion: v1 +kind: config +spec: + agent_options: + config: + file_paths: + ssh: + - /home/%/.ssh/authorized_keys + exclude_paths: + ssh: + - /home/ubuntu/.ssh/authorized_keys +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "app config macos_updates deadline set but minimum_version empty", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + deadline: 2022-01-04 +`, + wantErr: `422 Validation Failed: minimum_version is required when deadline is provided`, + }, + { + desc: "app config macos_updates minimum_version set but deadline empty", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: "12.2" +`, + wantErr: `422 Validation Failed: deadline is required when minimum_version is provided`, + }, + { + desc: "app config macos_updates.minimum_version with build version", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: "12.2 (ABCD)" + deadline: 1892-01-01 +`, + wantErr: `422 Validation Failed: minimum_version accepts version numbers only. (E.g., "13.0.1.") NOT "Ventura 13" or "13.0.1 (22A400)"`, + }, + { + desc: "app config macos_updates.deadline with timestamp", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "1892-01-01T00:00:00Z" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "app config macos_updates.deadline with invalid date", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "18-01-01" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "app config macos_updates.deadline with incomplete date", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_updates: + minimum_version: "12.2" + deadline: "2022-01" +`, + wantErr: fmt.Sprintf(`422 Validation Failed: %s`, fleet.AppleOSVersionDeadlineInvalidMessage), + }, + { + desc: "app config windows_updates.deadline_days but grace period empty", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: 5 +`, + wantErr: `422 Validation Failed: grace_period_days is required when deadline_days is provided`, + }, + { + desc: "app config windows_updates.grace_period_days but deadline empty", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + grace_period_days: 5 +`, + wantErr: `422 Validation Failed: deadline_days is required when grace_period_days is provided`, + }, + { + desc: "app config windows_updates.deadline_days out of range", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: 9999 + grace_period_days: 1 +`, + wantErr: `422 Validation Failed: deadline_days must be an integer between 0 and 30`, + }, + { + desc: "app config windows_updates.grace_period_days out of range", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: 1 + grace_period_days: 9999 +`, + wantErr: `422 Validation Failed: grace_period_days must be an integer between 0 and 7`, + }, + { + desc: "app config windows_updates.deadline_days not a number", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: abc + grace_period_days: 1 +`, + wantErr: `400 Bad request: failed to decode app config`, + }, + { + desc: "app config windows_updates.grace_period_days not a number", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: 1 + grace_period_days: true +`, + wantErr: `400 Bad request: failed to decode app config`, + }, + { + desc: "app config windows_updates valid", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: 5 + grace_period_days: 1 +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "app config windows_updates unset valid", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_updates: + deadline_days: + grace_period_days: +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "app config macos_settings.enable_disk_encryption without a value", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_settings: + enable_disk_encryption: +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "app config macos_settings.enable_disk_encryption with invalid value type", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_settings: + enable_disk_encryption: 123 +`, + wantErr: `400 Bad request: failed to decode app config`, + }, + { + desc: "app config macos_settings.enable_disk_encryption true", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_settings: + enable_disk_encryption: true +`, + + // Since Linux disk encryption does not use MDM, we allow enabling it even without MDM enabled and configured + wantOutput: `[+] applied fleet config`, + }, + { + desc: "app config macos_settings.enable_disk_encryption false", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + macos_settings: + enable_disk_encryption: false +`, + wantOutput: `[+] applied fleet config`, + }, + { + desc: "team config macos_settings.enable_disk_encryption without a value", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_settings: + enable_disk_encryption: +`, + wantErr: `400 Bad Request: invalid value type at 'macos_settings.enable_disk_encryption': expected bool but got `, + }, + { + desc: "team config macos_settings.enable_disk_encryption with invalid value type", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_settings: + enable_disk_encryption: 123 +`, + wantErr: `400 Bad Request: invalid value type at 'macos_settings.enable_disk_encryption': expected bool but got float64`, + }, + { + desc: "team config macos_settings.enable_disk_encryption true", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_settings: + enable_disk_encryption: true +`, + wantErr: `Couldn't update apple_settings because MDM features aren't turned on in Fleet.`, + }, + { + desc: "team config macos_settings.enable_disk_encryption false", + spec: ` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_settings: + enable_disk_encryption: false +`, + wantOutput: `[+] applied 1 fleet`, + }, + { + desc: "team config mac setup assistant", + spec: fmt.Sprintf(` +apiVersion: v1 +kind: fleet +spec: + team: + name: team1 + mdm: + macos_setup: + macos_setup_assistant: %s +`, macSetupFile), + wantErr: `macOS MDM isn't turned on.`, + }, + { + desc: "app config macos setup assistant", + spec: fmt.Sprintf(` +apiVersion: v1 +kind: config +spec: + mdm: + macos_setup: + macos_setup_assistant: %s +`, macSetupFile), + wantErr: `macOS MDM isn't turned on.`, + }, + { + desc: "app config enable windows mdm without WSTEP", + spec: ` +apiVersion: v1 +kind: config +spec: + mdm: + windows_enabled_and_configured: true +`, + wantErr: `422 Validation Failed: Couldn't turn on Windows MDM. Please configure Fleet with a certificate and key pair first.`, + }, + { + desc: "activities_webhook empty destination_url", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + activities_webhook: + enable_activities_webhook: true + destination_url: "" +`, + wantErr: `422 Validation Failed: destination_url is required`, + }, + { + desc: "activities_webhook bad destination_url 1", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + activities_webhook: + enable_activities_webhook: true + destination_url: ftp://host +`, + wantErr: `422 Validation Failed: destination_url must be http`, + }, + { + desc: "activities_webhook bad destination_url 2", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + activities_webhook: + enable_activities_webhook: true + destination_url: /foo +`, + wantErr: `422 Validation Failed: destination_url must be http`, + }, + { + desc: "activities_webhook bad destination_url 3", + spec: ` +apiVersion: v1 +kind: config +spec: + webhook_settings: + activities_webhook: + enable_activities_webhook: true + destination_url: foo +`, + wantErr: `422 Validation Failed: parse "foo": invalid URI`, + }, + } + // NOTE: Integrations required fields are not tested (Jira/Zendesk) because + // they require a complex setup to mock the client that would communicate + // with the external API. However, we make a test API call when enabling an + // integration, ensuring that any missing configuration field results in an + // error. Same for smtp_settings (a test email is sent when enabling). + + license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)} + for _, c := range cases { + t.Run(c.desc, func(t *testing.T) { + _, ds := testing_utils.RunServerWithMockedDS(t, &service.TestServerOpts{License: license}) + setupDS(ds) + filename := writeTmpYml(t, c.spec) + + var got string + if c.wantErr == "" { + got = runAppForTestDeprecated(t, append([]string{"apply", "-f", filename}, c.flags...)) + } else { + buf, err := runAppNoChecksDeprecated(append([]string{"apply", "-f", filename}, c.flags...)) + require.ErrorContains(t, err, c.wantErr) + got = buf.String() + } + if c.wantOutput == "" { + require.Empty(t, got) + } else { + require.Contains(t, got, c.wantOutput) + } + }) + } +} diff --git a/cmd/fleetctl/fleetctl/apply_test.go b/cmd/fleetctl/fleetctl/apply_test.go index 0b845d7725..68955d9254 100644 --- a/cmd/fleetctl/fleetctl/apply_test.go +++ b/cmd/fleetctl/fleetctl/apply_test.go @@ -1574,11 +1574,11 @@ spec: windows_updates: deadline_days: 1 grace_period_days: 0 - macos_settings: - custom_settings: + apple_settings: + configuration_profiles: - %s - macos_setup: - macos_setup_assistant: %s + setup_experience: + apple_setup_assistant: %s windows_enabled_and_configured: true `, mobileConfigPath, emptySetupAsst)) @@ -1621,8 +1621,8 @@ apiVersion: v1 kind: config spec: mdm: - macos_setup: - bootstrap_package: %s + setup_experience: + macos_bootstrap_package: %s `, bootstrapURL)) // first apply with dry-run @@ -1759,7 +1759,7 @@ spec: name: Team1 mdm: macos_setup: - bootstrap_package: %s + macos_bootstrap_package: %s `, bootstrapURL)) // first apply with dry-run @@ -2357,26 +2357,26 @@ apiVersion: v1 kind: config spec: mdm: - macos_setup: - bootstrap_package: %s - macos_setup_assistant: %s + setup_experience: + macos_bootstrap_package: %s + apple_setup_assistant: %s ` appConfigEnableReleaseSpec = appConfigSpec + ` - enable_release_device_manually: %s + apple_enable_release_device_manually: %s ` appConfigNoKeySpec = ` apiVersion: v1 kind: config spec: mdm: - macos_setup: + setup_experience: ` appConfigSpecEnableEndUserAuth = ` apiVersion: v1 kind: config spec: mdm: - macos_setup: + setup_experience: enable_end_user_authentication: %s ` team1Spec = ` @@ -2386,12 +2386,12 @@ spec: team: name: tm1 mdm: - macos_setup: - bootstrap_package: %s - macos_setup_assistant: %s + setup_experience: + macos_bootstrap_package: %s + apple_setup_assistant: %s ` team1EnableReleaseSpec = team1Spec + ` - enable_release_device_manually: %s + apple_enable_release_device_manually: %s require_all_software_macos: %s ` team1NoKeySpec = ` @@ -2401,7 +2401,7 @@ spec: team: name: tm1 mdm: - macos_setup: + setup_experience: ` team1And2Spec = ` apiVersion: v1 @@ -2410,9 +2410,9 @@ spec: team: name: tm1 mdm: - macos_setup: - bootstrap_package: %s - macos_setup_assistant: %s + setup_experience: + macos_bootstrap_package: %s + apple_setup_assistant: %s --- apiVersion: v1 kind: fleet @@ -2420,9 +2420,9 @@ spec: team: name: tm2 mdm: - macos_setup: - bootstrap_package: %s - macos_setup_assistant: %s + setup_experience: + macos_bootstrap_package: %s + apple_setup_assistant: %s ` team1SpecEnableEndUserAuth = ` apiVersion: v1 @@ -2431,7 +2431,7 @@ spec: team: name: tm1 mdm: - macos_setup: + setup_experience: enable_end_user_authentication: %s ` ) @@ -2593,7 +2593,7 @@ spec: // get, setup assistant still not set assert.YAMLEq(t, expectedEmptyAppCfg, RunAppForTest(t, []string{"get", "config", "--yaml"})) - assert.YAMLEq(t, expectedEmptyTm1, RunAppForTest(t, []string{"get", "teams", "--yaml"})) + assert.YAMLEq(t, expectedEmptyTm1, RunAppForTest(t, []string{"get", "fleets", "--yaml"})) // apply appconfig for real, and enable release device name = writeTmpYml(t, fmt.Sprintf(appConfigEnableReleaseSpec, "", emptyMacosSetup, "true")) @@ -2610,7 +2610,7 @@ spec: // get, setup assistant is now set assert.YAMLEq(t, expectedAppCfgSetReleaseEnabled, RunAppForTest(t, []string{"get", "config", "--yaml"})) - assert.YAMLEq(t, expectedTm1And2Set, RunAppForTest(t, []string{"get", "teams", "--yaml"})) + assert.YAMLEq(t, expectedTm1And2Set, RunAppForTest(t, []string{"get", "fleets", "--yaml"})) // clear with dry-run, appconfig name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", "")) @@ -2646,7 +2646,7 @@ spec: // get, results unchanged assert.YAMLEq(t, expectedAppCfgSetReleaseEnabled, RunAppForTest(t, []string{"get", "config", "--yaml"})) - assert.YAMLEq(t, expectedTm1And2Set, RunAppForTest(t, []string{"get", "teams", "--yaml"})) + assert.YAMLEq(t, expectedTm1And2Set, RunAppForTest(t, []string{"get", "fleets", "--yaml"})) // clear appconfig for real name = writeTmpYml(t, fmt.Sprintf(appConfigEnableReleaseSpec, "", "", "false")) @@ -2666,7 +2666,7 @@ spec: // get, results now empty assert.YAMLEq(t, expectedEmptyAppCfg, RunAppForTest(t, []string{"get", "config", "--yaml"})) - assert.YAMLEq(t, expectedEmptyTm1And2, RunAppForTest(t, []string{"get", "teams", "--yaml"})) + assert.YAMLEq(t, expectedEmptyTm1And2, RunAppForTest(t, []string{"get", "fleets", "--yaml"})) // apply team 1 without the setup assistant key but enable device release name = writeTmpYml(t, fmt.Sprintf(team1EnableReleaseSpec, "", "", "true", "true")) @@ -2678,7 +2678,7 @@ spec: assert.False(t, ds.DeleteMDMAppleSetupAssistantFuncInvoked) assert.True(t, ds.SaveTeamFuncInvoked) - assert.YAMLEq(t, expectedTm1SetReleaseAndRequireEnabled, RunAppForTest(t, []string{"get", "teams", "--yaml"})) + assert.YAMLEq(t, expectedTm1SetReleaseAndRequireEnabled, RunAppForTest(t, []string{"get", "fleets", "--yaml"})) // apply appconfig with invalid URL key name = writeTmpYml(t, fmt.Sprintf(appConfigSpec, "", invalidURLMacosSetup)) @@ -2781,9 +2781,9 @@ apiVersion: v1 kind: config spec: mdm: - macos_setup: - bootstrap_package: %s - manual_agent_install: true + setup_experience: + macos_bootstrap_package: %s + macos_manual_agent_install: true ` // create the app config yaml with server url for bootstrap package @@ -3964,7 +3964,7 @@ apiVersion: v1 kind: config spec: mdm: - macos_settings: + apple_settings: enable_disk_encryption: `, wantOutput: `[+] applied fleet config`, @@ -3976,7 +3976,7 @@ apiVersion: v1 kind: config spec: mdm: - macos_settings: + apple_settings: enable_disk_encryption: 123 `, wantErr: `400 Bad request: failed to decode app config`, @@ -3988,7 +3988,7 @@ apiVersion: v1 kind: config spec: mdm: - macos_settings: + apple_settings: enable_disk_encryption: true `, @@ -4002,7 +4002,7 @@ apiVersion: v1 kind: config spec: mdm: - macos_settings: + apple_settings: enable_disk_encryption: false `, wantOutput: `[+] applied fleet config`, @@ -4016,7 +4016,7 @@ spec: team: name: team1 mdm: - macos_settings: + apple_settings: enable_disk_encryption: `, wantErr: `400 Bad Request: invalid value type at 'macos_settings.enable_disk_encryption': expected bool but got `, @@ -4030,7 +4030,7 @@ spec: team: name: team1 mdm: - macos_settings: + apple_settings: enable_disk_encryption: 123 `, wantErr: `400 Bad Request: invalid value type at 'macos_settings.enable_disk_encryption': expected bool but got float64`, @@ -4044,7 +4044,7 @@ spec: team: name: team1 mdm: - macos_settings: + apple_settings: enable_disk_encryption: true `, wantErr: `Couldn't update apple_settings because MDM features aren't turned on in Fleet.`, @@ -4058,7 +4058,7 @@ spec: team: name: team1 mdm: - macos_settings: + apple_settings: enable_disk_encryption: false `, wantOutput: `[+] applied 1 fleet`, @@ -4072,8 +4072,8 @@ spec: team: name: team1 mdm: - macos_setup: - macos_setup_assistant: %s + setup_experience: + apple_setup_assistant: %s `, macSetupFile), wantErr: `macOS MDM isn't turned on.`, }, @@ -4084,8 +4084,8 @@ apiVersion: v1 kind: config spec: mdm: - macos_setup: - macos_setup_assistant: %s + setup_experience: + apple_setup_assistant: %s `, macSetupFile), wantErr: `macOS MDM isn't turned on.`, }, diff --git a/cmd/fleetctl/fleetctl/get.go b/cmd/fleetctl/fleetctl/get.go index 21384cb464..5a03f73576 100644 --- a/cmd/fleetctl/fleetctl/get.go +++ b/cmd/fleetctl/fleetctl/get.go @@ -18,7 +18,6 @@ import ( "github.com/fleetdm/fleet/v4/pkg/rawjson" "github.com/fleetdm/fleet/v4/pkg/secure" "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/logging" "github.com/fleetdm/fleet/v4/server/service" "github.com/ghodss/yaml" kithttp "github.com/go-kit/kit/transport/http" @@ -304,10 +303,6 @@ func getCommand() *cli.Command { return &cli.Command{ Name: "get", Usage: "Get/list resources", - Before: func(c *cli.Context) error { - logging.DisableTopic(logging.DeprecatedFieldTopic) - return nil - }, Subcommands: withLogTopicFlags([]*cli.Command{ getReportsCommand(), getPacksCommand(), diff --git a/cmd/fleetctl/fleetctl/gitops.go b/cmd/fleetctl/fleetctl/gitops.go index 22f50c0ef5..b97eac5621 100644 --- a/cmd/fleetctl/fleetctl/gitops.go +++ b/cmd/fleetctl/fleetctl/gitops.go @@ -90,10 +90,6 @@ func gitopsCommand() *cli.Command { disableLogTopicsFlag(), }, Action: func(c *cli.Context) error { - // Disable field deprecation warnings for now. - // TODO - remove this in future release to unleash warnings. - logging.DisableTopic(logging.DeprecatedFieldTopic) - // Apply log topic overrides from CLI flags. applyLogTopicFlags(c) diff --git a/cmd/fleetctl/fleetctl/query.go b/cmd/fleetctl/fleetctl/query.go index de4bbf6241..58398972d1 100644 --- a/cmd/fleetctl/fleetctl/query.go +++ b/cmd/fleetctl/fleetctl/query.go @@ -11,7 +11,6 @@ import ( "github.com/briandowns/spinner" "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/logging" "github.com/urfave/cli/v2" ) @@ -27,7 +26,6 @@ func queryCommand() *cli.Command { Usage: "Run a live report", UsageText: `fleetctl report [options]`, Before: func(c *cli.Context) error { - logging.DisableTopic(logging.DeprecatedFieldTopic) applyLogTopicFlags(c) logDeprecatedCommandName(c, []string{"query"}, "report") logDeprecatedFlagName(c, "query-name", "report-name") diff --git a/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_deprecated_test.go b/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_deprecated_test.go deleted file mode 100644 index 57f53e59a9..0000000000 --- a/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_deprecated_test.go +++ /dev/null @@ -1,1752 +0,0 @@ -package gitops - -import ( - "context" - "encoding/json" - "fmt" - "maps" - "net/http" - "net/http/httptest" - "os" - "path" - "path/filepath" - "runtime" - "text/template" - - "github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl" - "github.com/fleetdm/fleet/v4/cmd/fleetctl/fleetctl/testing_utils" - ma "github.com/fleetdm/fleet/v4/ee/maintained-apps" - "github.com/fleetdm/fleet/v4/server/datastore/mysql" - "github.com/fleetdm/fleet/v4/server/dev_mode" - "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/logging" - "github.com/fleetdm/fleet/v4/server/ptr" - "github.com/fleetdm/fleet/v4/server/test" - "github.com/go-git/go-git/v5" - "github.com/google/uuid" - "github.com/jmoiron/sqlx" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" -) - -func (s *enterpriseIntegrationGitopsTestSuite) TestDeleteMacOSSetupDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(` -agent_options: -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -`) - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString( - fmt.Sprintf( - ` -controls: -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -`, teamName, - ), - ) - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - // Add bootstrap packages - require.NoError(t, s.DS.InsertMDMAppleBootstrapPackage(context.Background(), &fleet.MDMAppleBootstrapPackage{ - Name: "bootstrap.pkg", - TeamID: 0, - Bytes: []byte("bootstrap package"), - Token: uuid.NewString(), - Sha256: []byte("sha256"), - }, nil)) - team, err := s.DS.TeamByName(context.Background(), teamName) - require.NoError(t, err) - t.Cleanup(func() { - _ = s.DS.DeleteTeam(context.Background(), team.ID) - }) - require.NoError(t, s.DS.InsertMDMAppleBootstrapPackage(context.Background(), &fleet.MDMAppleBootstrapPackage{ - Name: "bootstrap.pkg", - TeamID: team.ID, - Bytes: []byte("bootstrap package"), - Token: uuid.NewString(), - Sha256: []byte("sha256"), - }, nil)) - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt := "SELECT COUNT(*) FROM mdm_apple_bootstrap_packages WHERE team_id IN (?, ?)" - var result int - require.NoError(t, sqlx.GetContext(context.Background(), q, &result, stmt, 0, team.ID)) - assert.Equal(t, 2, result) - return nil - }) - - // Add enrollment profiles - _, err = s.DS.SetOrUpdateMDMAppleSetupAssistant(context.Background(), &fleet.MDMAppleSetupAssistant{ - TeamID: nil, - Name: "enrollment_profile.json", - Profile: []byte(`{"foo":"bar"}`), - }) - require.NoError(t, err) - _, err = s.DS.SetOrUpdateMDMAppleSetupAssistant(context.Background(), &fleet.MDMAppleSetupAssistant{ - TeamID: &team.ID, - Name: "enrollment_profile.json", - Profile: []byte(`{"foo":"bar"}`), - }) - require.NoError(t, err) - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt := "SELECT COUNT(*) FROM mdm_apple_setup_assistants WHERE global_or_team_id IN (?, ?)" - var result int - require.NoError(t, sqlx.GetContext(context.Background(), q, &result, stmt, 0, team.ID)) - assert.Equal(t, 2, result) - return nil - }) - - // Re-apply configs and expect the macOS setup assets to be cleared - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt := "SELECT COUNT(*) FROM mdm_apple_bootstrap_packages WHERE team_id IN (?, ?)" - var result int - require.NoError(t, sqlx.GetContext(context.Background(), q, &result, stmt, 0, team.ID)) - assert.Equal(t, 0, result) - return nil - }) - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt := "SELECT COUNT(*) FROM mdm_apple_setup_assistants WHERE global_or_team_id IN (?, ?)" - var result int - require.NoError(t, sqlx.GetContext(context.Background(), q, &result, stmt, 0, team.ID)) - assert.Equal(t, 0, result) - return nil - }) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestUnsetConfigurationProfileLabelsDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - profileFile, err := os.CreateTemp(t.TempDir(), "*.mobileconfig") - require.NoError(t, err) - _, err = profileFile.WriteString(test.GenerateMDMAppleProfile("test", "test", uuid.NewString())) - require.NoError(t, err) - err = profileFile.Close() - require.NoError(t, err) - - const ( - globalTemplate = ` -agent_options: -labels: - - name: Label1 - query: select 1 -controls: - macos_settings: - custom_settings: - - path: %s -%s -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - withLabelsIncludeAny = ` - labels_include_any: - - Label1 -` - emptyLabelsIncludeAny = ` - labels_include_any: -` - teamTemplate = ` -controls: - macos_settings: - custom_settings: - - path: %s -%s -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - withLabelsIncludeAll = ` - labels_include_all: - - Label1 -` - ) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(fmt.Sprintf(globalTemplate, profileFile.Name(), withLabelsIncludeAny)) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString(fmt.Sprintf(teamTemplate, profileFile.Name(), withLabelsIncludeAll, teamName)) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - // get the team ID - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - - // the custom setting is scoped by the label for no team - profs, _, err := s.DS.ListMDMConfigProfiles(ctx, nil, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 1) - require.Len(t, profs[0].LabelsIncludeAny, 1) - require.Equal(t, "Label1", profs[0].LabelsIncludeAny[0].LabelName) - - // the custom setting is scoped by the label for team - profs, _, err = s.DS.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 1) - require.Len(t, profs[0].LabelsIncludeAll, 1) - require.Equal(t, "Label1", profs[0].LabelsIncludeAll[0].LabelName) - - // remove the label conditions - err = os.WriteFile(globalFile.Name(), fmt.Appendf(nil, globalTemplate, profileFile.Name(), emptyLabelsIncludeAny), 0o644) - require.NoError(t, err) - err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, profileFile.Name(), "", teamName), 0o644) - require.NoError(t, err) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - // the custom setting is not scoped by label anymore - profs, _, err = s.DS.ListMDMConfigProfiles(ctx, nil, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 1) - require.Len(t, profs[0].LabelsIncludeAny, 0) - - profs, _, err = s.DS.ListMDMConfigProfiles(ctx, &team.ID, fleet.ListOptions{}) - require.NoError(t, err) - require.Len(t, profs, 1) - require.Len(t, profs[0].LabelsIncludeAll, 0) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestUnsetSoftwareInstallerLabelsDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - const ( - globalTemplate = ` -agent_options: -labels: - - name: Label1 - query: select 1 -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - - noTeamTemplate = `name: No team -controls: -policies: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb -%s -` - withLabelsIncludeAny = ` - labels_include_any: - - Label1 -` - emptyLabelsIncludeAny = ` - labels_include_any: -` - teamTemplate = ` -controls: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb -%s -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - withLabelsExcludeAny = ` - labels_exclude_any: - - Label1 -` - withLabelsIncludeAll = ` - labels_include_all: - - Label1 -` - ) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalTemplate) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFile.WriteString(fmt.Sprintf(noTeamTemplate, withLabelsIncludeAny)) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString(fmt.Sprintf(teamTemplate, withLabelsExcludeAny, teamName)) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - testing_utils.StartSoftwareInstallerServer(t) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - // get the team ID - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - - // the installer is scoped by the label for no team - titles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: ptr.Uint(0)}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, titles, 1) - require.NotNil(t, titles[0].SoftwarePackage) - noTeamTitleID := titles[0].ID - meta, err := s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false) - require.NoError(t, err) - require.Len(t, meta.LabelsIncludeAny, 1) - require.Equal(t, "Label1", meta.LabelsIncludeAny[0].LabelName) - - // the installer is scoped by the label for team - titles, _, _, err = s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{TeamID: &team.ID}, fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, titles, 1) - require.NotNil(t, titles[0].SoftwarePackage) - teamTitleID := titles[0].ID - meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false) - require.NoError(t, err) - require.Len(t, meta.LabelsExcludeAny, 1) - require.Equal(t, "Label1", meta.LabelsExcludeAny[0].LabelName) - - // switch both to labels_include_all - err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, withLabelsIncludeAll), 0o644) - require.NoError(t, err) - err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, withLabelsIncludeAll, teamName), 0o644) - require.NoError(t, err) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - // the installer is now scoped by labels_include_all for no team - meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false) - require.NoError(t, err) - require.Empty(t, meta.LabelsIncludeAny) - require.Empty(t, meta.LabelsExcludeAny) - require.Len(t, meta.LabelsIncludeAll, 1) - require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName) - - // the installer is now scoped by labels_include_all for team - meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false) - require.NoError(t, err) - require.Empty(t, meta.LabelsIncludeAny) - require.Empty(t, meta.LabelsExcludeAny) - require.Len(t, meta.LabelsIncludeAll, 1) - require.Equal(t, "Label1", meta.LabelsIncludeAll[0].LabelName) - - // remove the label conditions - err = os.WriteFile(noTeamFilePath, fmt.Appendf(nil, noTeamTemplate, emptyLabelsIncludeAny), 0o644) - require.NoError(t, err) - err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, "", teamName), 0o644) - require.NoError(t, err) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - // the installer is not scoped by label anymore - meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, nil, noTeamTitleID, false) - require.NoError(t, err) - require.NotNil(t, meta.TitleID) - require.Equal(t, noTeamTitleID, *meta.TitleID) - require.Len(t, meta.LabelsExcludeAny, 0) - require.Len(t, meta.LabelsIncludeAny, 0) - require.Len(t, meta.LabelsIncludeAll, 0) - - meta, err = s.DS.GetSoftwareInstallerMetadataByTeamAndTitleID(ctx, &team.ID, teamTitleID, false) - require.NoError(t, err) - require.NotNil(t, meta.TitleID) - require.Equal(t, teamTitleID, *meta.TitleID) - require.Len(t, meta.LabelsExcludeAny, 0) - require.Len(t, meta.LabelsIncludeAny, 0) - require.Len(t, meta.LabelsIncludeAll, 0) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestNoTeamWebhookSettingsDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := t.Context() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - var webhookSettings fleet.FailingPoliciesWebhookSettings - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - - // Create a global config file - const globalTemplate = ` -agent_options: -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: - - secret: global_secret -policies: -queries: -` - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalTemplate) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - // Create a no-team.yml file with webhook settings - const noTeamTemplateWithWebhook = ` -name: No team -policies: - - name: No Team Test Policy - query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; - description: Test policy for no team - resolution: This is a test -controls: -software: -team_settings: - webhook_settings: - failing_policies_webhook: - enable_failing_policies_webhook: true - destination_url: https://example.com/no-team-webhook - host_batch_size: 50 - policy_ids: - - 1 - - 2 - - 3 -` - - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFile.WriteString(noTeamTemplateWithWebhook) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - // Test dry-run first - output := fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "--dry-run"}) - s.assertDryRunOutputWithDeprecation(t, output, true) - - // Check that webhook settings are mentioned in the output - require.Contains(t, output, "would've applied webhook settings for unassigned hosts") - - // Apply the configuration (non-dry-run) - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath}) - s.assertRealRunOutputWithDeprecation(t, output, true) - - // Verify the output mentions webhook settings were applied - require.Contains(t, output, "applying webhook settings for unassigned hosts") - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook settings were actually applied by checking the database - verifyNoTeamWebhookSettings(ctx, t, s.DS, fleet.FailingPoliciesWebhookSettings{ - Enable: true, - DestinationURL: "https://example.com/no-team-webhook", - HostBatchSize: 50, - PolicyIDs: []uint{1, 2, 3}, - }) - - // Test updating webhook settings - const noTeamTemplateUpdatedWebhook = ` -name: No team -policies: - - name: No Team Test Policy - query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; - description: Test policy for no team - resolution: This is a test -controls: -software: -team_settings: - webhook_settings: - failing_policies_webhook: - enable_failing_policies_webhook: false - destination_url: https://updated.example.com/webhook - host_batch_size: 100 - policy_ids: - - 4 - - 5 -` - - noTeamFileUpdated, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFileUpdated.WriteString(noTeamTemplateUpdatedWebhook) - require.NoError(t, err) - err = noTeamFileUpdated.Close() - require.NoError(t, err) - noTeamFilePathUpdated := filepath.Join(filepath.Dir(noTeamFileUpdated.Name()), "no-team.yml") - err = os.Rename(noTeamFileUpdated.Name(), noTeamFilePathUpdated) - require.NoError(t, err) - - // Apply the updated configuration - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePathUpdated}) - - // Verify the output still mentions webhook settings were applied - require.Contains(t, output, "applying webhook settings for unassigned hosts") - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook settings were updated - verifyNoTeamWebhookSettings(ctx, t, s.DS, fleet.FailingPoliciesWebhookSettings{ - Enable: false, - DestinationURL: "https://updated.example.com/webhook", - HostBatchSize: 100, - PolicyIDs: []uint{4, 5}, - }) - - // Test removing webhook settings entirely - const noTeamTemplateNoWebhook = ` -name: No team -policies: - - name: No Team Test Policy - query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; - description: Test policy for no team - resolution: This is a test -controls: -software: -` - - noTeamFileNoWebhook, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFileNoWebhook.WriteString(noTeamTemplateNoWebhook) - require.NoError(t, err) - err = noTeamFileNoWebhook.Close() - require.NoError(t, err) - noTeamFilePathNoWebhook := filepath.Join(filepath.Dir(noTeamFileNoWebhook.Name()), "no-team.yml") - err = os.Rename(noTeamFileNoWebhook.Name(), noTeamFilePathNoWebhook) - require.NoError(t, err) - - // Apply configuration without webhook settings - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePathNoWebhook}) - - // Verify webhook settings are mentioned as being applied (they're applied as nil to clear) - require.Contains(t, output, "applying webhook settings for unassigned hosts") - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook settings were cleared - verifyNoTeamWebhookSettings(ctx, t, s.DS, fleet.FailingPoliciesWebhookSettings{ - Enable: false, - }) - - // Test case: team_settings exists but webhook_settings is nil - // First, set webhook settings again - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath}) - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook was set - webhookSettings = getNoTeamWebhookSettings(ctx, t, s.DS) - require.True(t, webhookSettings.Enable) - - // Now apply config with team_settings but no webhook_settings - const noTeamTemplateTeamSettingsNoWebhook = ` -name: No team -policies: - - name: No Team Test Policy - query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; - description: Test policy for no team - resolution: This is a test -controls: -software: -team_settings: -` - noTeamFileTeamNoWebhook, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFileTeamNoWebhook.WriteString(noTeamTemplateTeamSettingsNoWebhook) - require.NoError(t, err) - err = noTeamFileTeamNoWebhook.Close() - require.NoError(t, err) - noTeamFilePathTeamNoWebhook := filepath.Join(filepath.Dir(noTeamFileTeamNoWebhook.Name()), "no-team.yml") - err = os.Rename(noTeamFileTeamNoWebhook.Name(), noTeamFilePathTeamNoWebhook) - require.NoError(t, err) - - // Apply configuration with team_settings but no webhook_settings - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePathTeamNoWebhook}) - - // Verify webhook settings are cleared - require.Contains(t, output, "applying webhook settings for unassigned hosts") - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook settings are disabled - verifyNoTeamWebhookSettings(ctx, t, s.DS, fleet.FailingPoliciesWebhookSettings{ - Enable: false, - }) - - // Test case: webhook_settings exists but failing_policies_webhook is nil - // First, set webhook settings again - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath}) - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook was set - webhookSettings = getNoTeamWebhookSettings(ctx, t, s.DS) - require.True(t, webhookSettings.Enable) - - // Now apply config with webhook_settings but no failing_policies_webhook - const noTeamTemplateWebhookNoFailing = ` -name: No team -policies: - - name: No Team Test Policy - query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; - description: Test policy for no team - resolution: This is a test -controls: -software: -team_settings: - webhook_settings: -` - noTeamFileWebhookNoFailing, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFileWebhookNoFailing.WriteString(noTeamTemplateWebhookNoFailing) - require.NoError(t, err) - err = noTeamFileWebhookNoFailing.Close() - require.NoError(t, err) - noTeamFilePathWebhookNoFailing := filepath.Join(filepath.Dir(noTeamFileWebhookNoFailing.Name()), "no-team.yml") - err = os.Rename(noTeamFileWebhookNoFailing.Name(), noTeamFilePathWebhookNoFailing) - require.NoError(t, err) - - // Apply configuration with webhook_settings but no failing_policies_webhook - output = fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePathWebhookNoFailing}) - - // Verify webhook settings are cleared - require.Contains(t, output, "applying webhook settings for unassigned hosts") - require.Contains(t, output, "applied webhook settings for unassigned hosts") - - // Verify webhook settings are disabled - verifyNoTeamWebhookSettings(ctx, t, s.DS, fleet.FailingPoliciesWebhookSettings{ - Enable: false, - }) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestMacOSSetupDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - originalAppConfig, err := s.DS.AppConfig(ctx) - require.NoError(t, err) - t.Cleanup(func() { - err := s.DS.SaveAppConfig(ctx, originalAppConfig) - require.NoError(t, err) - }) - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - bootstrapServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.ServeFile(w, r, "testdata/signed.pkg") - })) - defer bootstrapServer.Close() - - const ( - globalConfig = ` -agent_options: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - - globalConfigOnly = ` -agent_options: -controls: - macos_setup: - bootstrap_package: %s - manual_agent_install: %t -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - - noTeamConfig = `name: No team -controls: - macos_setup: - bootstrap_package: %s - manual_agent_install: true -policies: -software: -` - - teamConfig = ` -controls: - macos_setup: - bootstrap_package: %s - manual_agent_install: %t -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - ) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalConfig) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFile.WriteString(fmt.Sprintf(noTeamConfig, bootstrapServer.URL)) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString(fmt.Sprintf(teamConfig, bootstrapServer.URL, true, teamName)) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - teamFileClear, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFileClear.WriteString(fmt.Sprintf(teamConfig, bootstrapServer.URL, false, teamName)) - require.NoError(t, err) - err = teamFileClear.Close() - require.NoError(t, err) - - globalFileOnlySet, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFileOnlySet.WriteString(fmt.Sprintf(globalConfigOnly, bootstrapServer.URL, true)) - require.NoError(t, err) - err = globalFileOnlySet.Close() - require.NoError(t, err) - globalFileOnlyClear, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFileOnlyClear.WriteString(fmt.Sprintf(globalConfigOnly, bootstrapServer.URL, false)) - require.NoError(t, err) - err = globalFileOnlyClear.Close() - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - appConfig, err := s.DS.AppConfig(ctx) - require.NoError(t, err) - assert.True(t, appConfig.MDM.MacOSSetup.ManualAgentInstall.Value) - - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - assert.True(t, team.Config.MDM.MacOSSetup.ManualAgentInstall.Value) - - // Apply global configs without no-team - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFileOnlyClear.Name(), "-f", teamFileClear.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFileOnlyClear.Name(), "-f", teamFileClear.Name()}), true) - appConfig, err = s.DS.AppConfig(ctx) - require.NoError(t, err) - assert.False(t, appConfig.MDM.MacOSSetup.ManualAgentInstall.Value) - team, err = s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - assert.False(t, team.Config.MDM.MacOSSetup.ManualAgentInstall.Value) - - // Apply global configs only - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, - []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFileOnlySet.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFileOnlySet.Name()}), true) - appConfig, err = s.DS.AppConfig(ctx) - require.NoError(t, err) - assert.True(t, appConfig.MDM.MacOSSetup.ManualAgentInstall.Value) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestIPASoftwareInstallersDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - lbl, err := s.DS.NewLabel(ctx, &fleet.Label{Name: "Label1", Query: "SELECT 1"}) - require.NoError(t, err) - require.NotZero(t, lbl.ID) - - const ( - globalTemplate = ` -agent_options: -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -labels: - - name: Label1 - label_membership_type: dynamic - query: SELECT 1 -` - - noTeamTemplate = `name: No team -controls: -policies: -software: - packages: -%s -` - teamTemplate = ` -controls: -software: - packages: -%s -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - ) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalTemplate) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - // create an .ipa software for the no-team config - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFile.WriteString(fmt.Sprintf(noTeamTemplate, ` - - url: ${SOFTWARE_INSTALLER_URL}/ipa_test.ipa - self_service: true -`)) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - testing_utils.StartSoftwareInstallerServer(t) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath}), true) - - // the ipa installer was created for no team - titles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: ptr.Uint(0)}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - - require.Len(t, titles, 2) - var sources, platforms []string - for _, title := range titles { - require.Equal(t, "ipa_test", title.Name) - require.NotNil(t, title.BundleIdentifier) - require.Equal(t, "com.ipa-test.ipa-test", *title.BundleIdentifier) - sources = append(sources, title.Source) - - require.NotNil(t, title.SoftwarePackage) - platforms = append(platforms, title.SoftwarePackage.Platform) - require.Equal(t, "ipa_test.ipa", title.SoftwarePackage.Name) - - meta, err := s.DS.GetInHouseAppMetadataByTeamAndTitleID(ctx, nil, title.ID) - require.NoError(t, err) - require.True(t, meta.SelfService) - require.Empty(t, meta.LabelsExcludeAny) - require.Empty(t, meta.LabelsIncludeAny) - require.Empty(t, meta.LabelsIncludeAll) - } - require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources) - require.ElementsMatch(t, []string{"ios", "ipados"}, platforms) - - // create a dummy install script, should be ignored for ipa apps - scriptFile, err := os.CreateTemp(t.TempDir(), "*.sh") - require.NoError(t, err) - _, err = scriptFile.WriteString(`echo "dummy install script"`) - require.NoError(t, err) - err = scriptFile.Close() - require.NoError(t, err) - - // create an .ipa software for the team config - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString(fmt.Sprintf(teamTemplate, ` - - url: ${SOFTWARE_INSTALLER_URL}/ipa_test.ipa - self_service: false - install_script: - path: `+scriptFile.Name()+` - labels_include_any: - - Label1 -`, teamName)) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - // get the team ID - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - - // the ipa installer was created for the team - titles, _, _, err = s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: &team.ID}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - - require.Len(t, titles, 2) - sources, platforms = []string{}, []string{} - for _, title := range titles { - require.Equal(t, "ipa_test", title.Name) - require.NotNil(t, title.BundleIdentifier) - require.Equal(t, "com.ipa-test.ipa-test", *title.BundleIdentifier) - sources = append(sources, title.Source) - - require.NotNil(t, title.SoftwarePackage) - platforms = append(platforms, title.SoftwarePackage.Platform) - require.Equal(t, "ipa_test.ipa", title.SoftwarePackage.Name) - - meta, err := s.DS.GetInHouseAppMetadataByTeamAndTitleID(ctx, &team.ID, title.ID) - require.NoError(t, err) - require.False(t, meta.SelfService) - require.Empty(t, meta.LabelsExcludeAny) - require.Empty(t, meta.LabelsIncludeAll) - require.Len(t, meta.LabelsIncludeAny, 1) - require.Equal(t, lbl.ID, meta.LabelsIncludeAny[0].LabelID) - require.Empty(t, meta.InstallScript) // install script should be ignored for ipa apps - } - require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources) - require.ElementsMatch(t, []string{"ios", "ipados"}, platforms) - - // update the team config to clear the label condition - err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, ` - - url: ${SOFTWARE_INSTALLER_URL}/ipa_test.ipa - labels_include_any: -`, teamName), 0o644) - require.NoError(t, err) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - // the ipa installer was created for the team - titles, _, _, err = s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: &team.ID}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - - require.Len(t, titles, 2) - sources, platforms = []string{}, []string{} - for _, title := range titles { - require.Equal(t, "ipa_test", title.Name) - require.NotNil(t, title.BundleIdentifier) - require.Equal(t, "com.ipa-test.ipa-test", *title.BundleIdentifier) - sources = append(sources, title.Source) - - require.NotNil(t, title.SoftwarePackage) - platforms = append(platforms, title.SoftwarePackage.Platform) - require.Equal(t, "ipa_test.ipa", title.SoftwarePackage.Name) - - meta, err := s.DS.GetInHouseAppMetadataByTeamAndTitleID(ctx, &team.ID, title.ID) - require.NoError(t, err) - require.False(t, meta.SelfService) - require.Empty(t, meta.LabelsExcludeAny) - require.Empty(t, meta.LabelsIncludeAny) - require.Empty(t, meta.LabelsIncludeAll) - } - require.ElementsMatch(t, []string{"ios_apps", "ipados_apps"}, sources) - require.ElementsMatch(t, []string{"ios", "ipados"}, platforms) - - // update the team config to clear all installers - err = os.WriteFile(teamFile.Name(), fmt.Appendf(nil, teamTemplate, "", teamName), 0o644) - require.NoError(t, err) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()}), true) - - titles, _, _, err = s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: &team.ID}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, titles, 0) -} - -// TestGitOpsSoftwareDisplayNameDeprecated tests that display names for software packages and VPP apps -// are properly applied via GitOps. -func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsSoftwareDisplayNameDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - const ( - globalTemplate = ` -agent_options: -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - - noTeamTemplate = `name: No team -controls: -policies: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb - display_name: Custom Ruby Name -` - - teamTemplate = ` -controls: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb - display_name: Team Custom Ruby -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - ) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalTemplate) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = noTeamFile.WriteString(noTeamTemplate) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = teamFile.WriteString(fmt.Sprintf(teamTemplate, teamName)) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - testing_utils.StartSoftwareInstallerServer(t) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - // get the team ID - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - - // Verify display name for no team - noTeamTitles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: ptr.Uint(0)}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, noTeamTitles, 1) - require.NotNil(t, noTeamTitles[0].SoftwarePackage) - noTeamTitleID := noTeamTitles[0].ID - - // Verify the display name is stored in the database for no team - var noTeamDisplayName string - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - return sqlx.GetContext(ctx, q, &noTeamDisplayName, - "SELECT display_name FROM software_title_display_names WHERE team_id = ? AND software_title_id = ?", - 0, noTeamTitleID) - }) - require.Equal(t, "Custom Ruby Name", noTeamDisplayName) - - // Verify display name for team - teamTitles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{TeamID: &team.ID}, fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, teamTitles, 1) - require.NotNil(t, teamTitles[0].SoftwarePackage) - teamTitleID := teamTitles[0].ID - - // Verify the display name is stored in the database for team - var teamDisplayName string - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - return sqlx.GetContext(ctx, q, &teamDisplayName, - "SELECT display_name FROM software_title_display_names WHERE team_id = ? AND software_title_id = ?", - team.ID, teamTitleID) - }) - require.Equal(t, "Team Custom Ruby", teamDisplayName) -} - -// TestGitOpsSoftwareIconsDeprecated tests that custom icons for software packages -// and fleet maintained apps are properly applied via GitOps. -func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsSoftwareIconsDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetctlConfig := s.createFleetctlConfig(t, user) - - const ( - globalTemplate = ` -agent_options: -controls: -org_settings: - server_settings: - server_url: $FLEET_URL - org_info: - org_name: Fleet - secrets: -policies: -queries: -` - - noTeamTemplate = `name: No team -controls: -policies: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb - icon: - path: %s/testdata/gitops/lib/icon.png - fleet_maintained_apps: - - slug: foodeprecated/darwin - icon: - path: %s/testdata/gitops/lib/icon.png -` - - teamTemplate = ` -controls: -software: - packages: - - url: ${SOFTWARE_INSTALLER_URL}/ruby.deb - icon: - path: %s/testdata/gitops/lib/icon.png - fleet_maintained_apps: - - slug: foodeprecated/darwin - icon: - path: %s/testdata/gitops/lib/icon.png -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -` - ) - - // Get the absolute path to the directory of this test file - _, currentFile, _, ok := runtime.Caller(0) - require.True(t, ok, "failed to get runtime caller info") - dirPath := filepath.Dir(currentFile) - dirPath = filepath.Join(dirPath, "../../fleetctl") - dirPath, err := filepath.Abs(filepath.Clean(dirPath)) - require.NoError(t, err) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = globalFile.WriteString(globalTemplate) - require.NoError(t, err) - err = globalFile.Close() - require.NoError(t, err) - - noTeamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = fmt.Fprintf(noTeamFile, noTeamTemplate, dirPath, dirPath) - require.NoError(t, err) - err = noTeamFile.Close() - require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") - err = os.Rename(noTeamFile.Name(), noTeamFilePath) - require.NoError(t, err) - - teamName := uuid.NewString() - teamFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - _, err = fmt.Fprintf(teamFile, teamTemplate, dirPath, dirPath, teamName) - require.NoError(t, err) - err = teamFile.Close() - require.NoError(t, err) - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - testing_utils.StartSoftwareInstallerServer(t) - - // Mock server to serve fleet maintained app installer - installerBytes := []byte("foo") - installerServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - _, _ = w.Write(installerBytes) - })) - defer installerServer.Close() - - // Mock server to serve fleet maintained app manifest - manifestServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - var versions []*ma.FMAManifestApp - versions = append(versions, &ma.FMAManifestApp{ - Version: "6.0", - Queries: ma.FMAQueries{ - Exists: "SELECT 1 FROM osquery_info;", - }, - InstallerURL: installerServer.URL + "/foo.pkg", - InstallScriptRef: "foobaz", - UninstallScriptRef: "foobaz", - SHA256: "no_check", // See ma.noCheckHash - }) - - manifest := ma.FMAManifestFile{ - Versions: versions, - Refs: map[string]string{ - "foobaz": "Hello World!", - }, - } - - err := json.NewEncoder(w).Encode(manifest) - require.NoError(t, err) - })) - - t.Cleanup(manifestServer.Close) - dev_mode.SetOverride("FLEET_DEV_MAINTAINED_APPS_BASE_URL", manifestServer.URL, t) - - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - _, err := q.ExecContext(ctx, `INSERT INTO fleet_maintained_apps (name, slug, platform, unique_identifier) - VALUES ('foodeprecated', 'foodeprecated/darwin', 'darwin', 'com.example.foodeprecated')`) - return err - }) - - // Apply configs - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", noTeamFilePath, "-f", teamFile.Name()}), true) - - // Get the team ID - team, err := s.DS.TeamByName(ctx, teamName) - require.NoError(t, err) - - // Verify titles were added for no team - noTeamTitles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{AvailableForInstall: true, TeamID: ptr.Uint(0)}, - fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, noTeamTitles, 2) - require.NotNil(t, noTeamTitles[0].SoftwarePackage) - require.NotNil(t, noTeamTitles[1].SoftwarePackage) - noTeamTitleIDs := []uint{noTeamTitles[0].ID, noTeamTitles[1].ID} - - // Verify the custom icon is stored in the database for no team - var noTeamIconFilenames []string - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt, args, err := sqlx.In("SELECT filename FROM software_title_icons WHERE team_id = ? AND software_title_id IN (?)", 0, noTeamTitleIDs) - if err != nil { - return err - } - return sqlx.SelectContext(ctx, q, &noTeamIconFilenames, stmt, args...) - }) - require.Len(t, noTeamIconFilenames, 2) - require.Equal(t, "icon.png", noTeamIconFilenames[0]) - require.Equal(t, "icon.png", noTeamIconFilenames[1]) - - // Verify titles were added for team - teamTitles, _, _, err := s.DS.ListSoftwareTitles(ctx, fleet.SoftwareTitleListOptions{TeamID: &team.ID}, fleet.TeamFilter{User: test.UserAdmin}) - require.NoError(t, err) - require.Len(t, teamTitles, 2) - require.NotNil(t, teamTitles[0].SoftwarePackage) - require.NotNil(t, teamTitles[1].SoftwarePackage) - teamTitleIDs := []uint{teamTitles[0].ID, teamTitles[1].ID} - - // Verify the custom icon is stored in the database for team - var teamIconFilenames []string - mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { - stmt, args, err := sqlx.In("SELECT filename FROM software_title_icons WHERE team_id = ? AND software_title_id IN (?)", team.ID, teamTitleIDs) - if err != nil { - return err - } - return sqlx.SelectContext(ctx, q, &teamIconFilenames, stmt, args...) - }) - require.Len(t, teamIconFilenames, 2) - require.Equal(t, "icon.png", teamIconFilenames[0]) - require.Equal(t, "icon.png", teamIconFilenames[1]) -} - -func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsTeamLabelsDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - user := s.createGitOpsUser(t) - fleetCfg := s.createFleetctlConfig(t, user) - - globalFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - - // ----------------------------------------------------------------- - // First, let's validate that we can add labels to the global scope - // ----------------------------------------------------------------- - require.NoError(t, os.WriteFile(globalFile.Name(), []byte(` -agent_options: -controls: -org_settings: - secrets: - - secret: test_secret -policies: -queries: -labels: - - name: global-label-one - label_membership_type: dynamic - query: SELECT 1 - - name: global-label-two - label_membership_type: dynamic - query: SELECT 1 -`), 0o644)) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name()}), true) - - expected := make(map[string]uint) - expected["global-label-one"] = 0 - expected["global-label-two"] = 0 - - got := labelTeamIDResult(t, s, ctx) - - require.True(t, maps.Equal(expected, got)) - - // --------------------------------------------------------------- - // Now, let's validate that we can add and remove labels in a team - // --------------------------------------------------------------- - // TeamOne already exists - teamOneName := uuid.NewString() - teamOne, err := s.DS.NewTeam(context.Background(), &fleet.Team{Name: teamOneName}) - require.NoError(t, err) - - teamOneFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - - require.NoError(t, os.WriteFile(teamOneFile.Name(), fmt.Appendf(nil, - ` -controls: -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -labels: - - name: team-one-label-one - label_membership_type: dynamic - query: SELECT 2 - - name: team-one-label-two - label_membership_type: dynamic - query: SELECT 3 -`, teamOneName), 0o644)) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", teamOneFile.Name(), "--dry-run"}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", teamOneFile.Name()}), true) - - got = labelTeamIDResult(t, s, ctx) - - expected = make(map[string]uint) - expected["global-label-one"] = 0 - expected["global-label-two"] = 0 - expected["team-one-label-one"] = teamOne.ID - expected["team-one-label-two"] = teamOne.ID - - require.True(t, maps.Equal(expected, got)) - - // Try removing one label from teamOne - require.NoError(t, os.WriteFile(teamOneFile.Name(), fmt.Appendf(nil, - ` -controls: -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -labels: - - name: team-one-label-one - label_membership_type: dynamic - query: SELECT 2 -`, teamOneName), 0o644)) - - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name(), "-f", teamOneFile.Name()}), true) - - expected = make(map[string]uint) - expected["global-label-one"] = 0 - expected["global-label-two"] = 0 - expected["team-one-label-one"] = teamOne.ID - - got = labelTeamIDResult(t, s, ctx) - - require.True(t, maps.Equal(expected, got)) - - // ------------------------------------------------ - // Finally, let's validate that we can move labels around - // ------------------------------------------------ - require.NoError(t, os.WriteFile(globalFile.Name(), []byte(` -agent_options: -controls: -org_settings: - secrets: - - secret: test_secret -policies: -queries: -labels: - - name: global-label-one - label_membership_type: dynamic - query: SELECT 1 - -`), 0o644)) - - require.NoError(t, os.WriteFile(teamOneFile.Name(), fmt.Appendf(nil, - - ` -controls: -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret"}] -labels: - - name: team-one-label-two - label_membership_type: dynamic - query: SELECT 3 - - name: global-label-two - label_membership_type: dynamic - query: SELECT 1 -`, teamOneName), 0o644)) - - teamTwoName := uuid.NewString() - teamTwoFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - - require.NoError(t, os.WriteFile(teamTwoFile.Name(), fmt.Appendf(nil, ` -controls: -software: -queries: -policies: -agent_options: -name: %s -team_settings: - secrets: [{"secret":"enroll_secret2"}] -labels: - - name: team-one-label-one - label_membership_type: dynamic - query: SELECT 2 -`, teamTwoName), 0o644)) - - s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name(), "-f", teamOneFile.Name(), "-f", teamTwoFile.Name(), "--dry-run"}), true) - - // TODO: Seems like we require two passes to achieve equilibrium? - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name(), "-f", teamOneFile.Name(), "-f", teamTwoFile.Name()}), true) - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetCfg.Name(), "-f", globalFile.Name(), "-f", teamOneFile.Name(), "-f", teamTwoFile.Name()}), true) - - teamTwo, err := s.DS.TeamByName(ctx, teamTwoName) - require.NoError(t, err) - - got = labelTeamIDResult(t, s, ctx) - - expected = make(map[string]uint) - expected["global-label-one"] = 0 - expected["team-one-label-two"] = teamOne.ID - expected["global-label-two"] = teamOne.ID - expected["team-one-label-one"] = teamTwo.ID - - require.True(t, maps.Equal(expected, got)) -} - -// TestGitOpsTeamLabelsMultipleReposDeprecated tests a gitops setup where every team runs from an independent repo. -// Multiple repos are simulated by copying over the example repository multiple times. -func (s *enterpriseIntegrationGitopsTestSuite) TestGitOpsTeamLabelsMultipleReposDeprecated() { - t := s.T() - t.Setenv("FLEET_ENABLE_LOG_TOPICS", logging.DeprecatedFieldTopic) - - ctx := context.Background() - - type repoSetup struct { - user fleet.User - cfg *os.File - repoDir string - } - setups := make([]repoSetup, 0, 2) - - for range 2 { - user := s.createGitOpsUser(t) - cfg := s.createFleetctlConfig(t, user) - - repoDir := t.TempDir() - _, err := git.PlainClone( - repoDir, false, &git.CloneOptions{ - ReferenceName: "main", - SingleBranch: true, - Depth: 1, - URL: fleetGitopsRepo, - Progress: os.Stdout, - }, - ) - require.NoError(t, err) - setups = append(setups, repoSetup{ - user: user, - cfg: cfg, - repoDir: repoDir, - }) - } - - // Set the required environment variables - t.Setenv("FLEET_URL", s.Server.URL) - t.Setenv("FLEET_GLOBAL_ENROLL_SECRET", "global_enroll_secret") - t.Setenv("FLEET_WORKSTATIONS_ENROLL_SECRET", "workstations_enroll_secret") - t.Setenv("FLEET_WORKSTATIONS_CANARY_ENROLL_SECRET", "workstations_canary_enroll_secret") - - type tmplParams struct { - Name string - Queries string - Labels string - } - teamCfgTmpl, err := template.New("t1").Parse(` -controls: -software: -queries:{{ .Queries }} -policies: -labels:{{ .Labels }} -agent_options: -name:{{ .Name }} -team_settings: - secrets: [{"secret":"{{ .Name}}_secret"}] -`) - require.NoError(t, err) - - // -------------------------------------------------- - // First, lets simulate adding a new team per repo - // -------------------------------------------------- - for i, setup := range setups { - globalFile := path.Join(setup.repoDir, "default.yml") - - newTeamCfgFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - - require.NoError(t, teamCfgTmpl.Execute(newTeamCfgFile, tmplParams{ - Name: fmt.Sprintf(" team-%d", i), - Queries: fmt.Sprintf("\n - name: query-%d\n query: SELECT 1", i), - Labels: fmt.Sprintf("\n - name: label-%d\n label_membership_type: dynamic\n query: SELECT 1", i), - })) - - args := []string{"gitops", "--config", setup.cfg.Name(), "-f", globalFile, "-f", newTeamCfgFile.Name()} - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, args), true) - } - - for i, setup := range setups { - user := setup.user - team, err := s.DS.TeamByName(ctx, fmt.Sprintf("team-%d", i)) - require.NoError(t, err) - require.NotNil(t, team) - - queries, _, _, _, err := s.DS.ListQueries(ctx, fleet.ListQueryOptions{TeamID: &team.ID}) - require.NoError(t, err) - require.Len(t, queries, 1) - require.Equal(t, fmt.Sprintf("query-%d", i), queries[0].Name) - require.Equal(t, "SELECT 1", queries[0].Query) - require.NotNil(t, queries[0].TeamID) - require.Equal(t, *queries[0].TeamID, team.ID) - require.NotNil(t, queries[0].AuthorID) - require.Equal(t, *queries[0].AuthorID, user.ID) - - label, err := s.DS.LabelByName(ctx, fmt.Sprintf("label-%d", i), fleet.TeamFilter{User: &fleet.User{ID: user.ID}}) - require.NoError(t, err) - require.NotNil(t, label) - require.NotNil(t, label.TeamID) - require.Equal(t, *label.TeamID, team.ID) - require.NotNil(t, label.AuthorID) - require.Equal(t, *label.AuthorID, user.ID) - } - - // ----------------------------------------------------------------- - // Then, lets simulate a mutation by dropping the labels on team one - // ----------------------------------------------------------------- - for i, setup := range setups { - globalFile := path.Join(setup.repoDir, "default.yml") - - newTeamCfgFile, err := os.CreateTemp(t.TempDir(), "*.yml") - require.NoError(t, err) - - params := tmplParams{ - Name: fmt.Sprintf(" team-%d", i), - Queries: fmt.Sprintf("\n - name: query-%d\n query: SELECT 1", i), - } - if i != 0 { - params.Labels = fmt.Sprintf("\n - name: label-%d\n label_membership_type: dynamic\n query: SELECT 1", i) - } - - require.NoError(t, teamCfgTmpl.Execute(newTeamCfgFile, params)) - - args := []string{"gitops", "--config", setup.cfg.Name(), "-f", globalFile, "-f", newTeamCfgFile.Name()} - s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, args), true) - } - - for i, setup := range setups { - user := setup.user - team, err := s.DS.TeamByName(ctx, fmt.Sprintf("team-%d", i)) - require.NoError(t, err) - require.NotNil(t, team) - - queries, _, _, _, err := s.DS.ListQueries(ctx, fleet.ListQueryOptions{TeamID: &team.ID}) - require.NoError(t, err) - require.Len(t, queries, 1) - require.Equal(t, fmt.Sprintf("query-%d", i), queries[0].Name) - require.Equal(t, "SELECT 1", queries[0].Query) - require.NotNil(t, queries[0].TeamID) - require.Equal(t, *queries[0].TeamID, team.ID) - require.NotNil(t, queries[0].AuthorID) - require.Equal(t, *queries[0].AuthorID, user.ID) - - label, err := s.DS.LabelByName(ctx, fmt.Sprintf("label-%d", i), fleet.TeamFilter{User: &fleet.User{ID: user.ID}}) - if i == 0 { - require.Error(t, err) - require.Nil(t, label) - } else { - require.NoError(t, err) - require.NotNil(t, label) - require.NotNil(t, label.TeamID) - require.Equal(t, *label.TeamID, team.ID) - require.NotNil(t, label.AuthorID) - require.Equal(t, *label.AuthorID, user.ID) - } - } -} diff --git a/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go b/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go index 6795059abe..a19bd1ca57 100644 --- a/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go +++ b/cmd/fleetctl/integrationtest/gitops/gitops_enterprise_integration_test.go @@ -167,12 +167,12 @@ func (s *enterpriseIntegrationGitopsTestSuite) TearDownTest() { require.NoError(t, err) } - // Delete policies in "No team" (the others are deleted in ts.DS.DeleteTeam above). + // Delete policies in "Unassigned" (the others are deleted in ts.DS.DeleteTeam above). mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { _, err := q.ExecContext(ctx, `DELETE FROM policies WHERE team_id = 0;`) return err }) - // Clean software installers in "No team" (the others are deleted in ts.DS.DeleteTeam above). + // Clean software installers in "Unassigned" (the others are deleted in ts.DS.DeleteTeam above). mysql.ExecAdhocSQL(t, s.DS, func(q sqlx.ExtContext) error { _, err := q.ExecContext(ctx, `DELETE FROM software_installers WHERE global_or_team_id = 0;`) return err @@ -338,7 +338,7 @@ settings: s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile, "--dry-run"}), true) for _, fileName := range teamFileNames { // When running no-teams, global config must also be provided ... - if strings.Contains(fileName, "no-team.yml") { + if strings.Contains(fileName, "unassigned.yml") { s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName, "-f", globalFile, "--dry-run"}), true) } else { s.assertDryRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName, "--dry-run"}), true) @@ -379,7 +379,7 @@ settings: s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile}), true) for _, fileName := range teamFileNames { // When running no-teams, global config must also be provided ... - if strings.Contains(fileName, "no-team.yml") { + if strings.Contains(fileName, "unassigned.yml") { s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName, "-f", globalFile}), true) } else { s.assertRealRunOutputWithDeprecation(t, fleetctl.RunAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName}), true) @@ -1061,7 +1061,7 @@ policies: reports: ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: software: @@ -1112,7 +1112,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -1262,7 +1262,7 @@ reports: // no team file setup const ( - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned policies: controls: macos_setup: @@ -1277,7 +1277,7 @@ software: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -1354,7 +1354,7 @@ reports: // Create a no-team.yml file with webhook settings const noTeamTemplateWithWebhook = ` -name: No team +name: Unassigned policies: - name: No Team Test Policy query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; @@ -1380,7 +1380,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -1411,7 +1411,7 @@ settings: // Test updating webhook settings const noTeamTemplateUpdatedWebhook = ` -name: No team +name: Unassigned policies: - name: No Team Test Policy query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; @@ -1436,7 +1436,7 @@ settings: require.NoError(t, err) err = noTeamFileUpdated.Close() require.NoError(t, err) - noTeamFilePathUpdated := filepath.Join(filepath.Dir(noTeamFileUpdated.Name()), "no-team.yml") + noTeamFilePathUpdated := filepath.Join(filepath.Dir(noTeamFileUpdated.Name()), "unassigned.yml") err = os.Rename(noTeamFileUpdated.Name(), noTeamFilePathUpdated) require.NoError(t, err) @@ -1458,7 +1458,7 @@ settings: // Test removing webhook settings entirely const noTeamTemplateNoWebhook = ` -name: No team +name: Unassigned policies: - name: No Team Test Policy query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; @@ -1474,7 +1474,7 @@ software: require.NoError(t, err) err = noTeamFileNoWebhook.Close() require.NoError(t, err) - noTeamFilePathNoWebhook := filepath.Join(filepath.Dir(noTeamFileNoWebhook.Name()), "no-team.yml") + noTeamFilePathNoWebhook := filepath.Join(filepath.Dir(noTeamFileNoWebhook.Name()), "unassigned.yml") err = os.Rename(noTeamFileNoWebhook.Name(), noTeamFilePathNoWebhook) require.NoError(t, err) @@ -1503,7 +1503,7 @@ software: // Now apply config with team_settings but no webhook_settings const noTeamTemplateTeamSettingsNoWebhook = ` -name: No team +name: Unassigned policies: - name: No Team Test Policy query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; @@ -1519,7 +1519,7 @@ settings: require.NoError(t, err) err = noTeamFileTeamNoWebhook.Close() require.NoError(t, err) - noTeamFilePathTeamNoWebhook := filepath.Join(filepath.Dir(noTeamFileTeamNoWebhook.Name()), "no-team.yml") + noTeamFilePathTeamNoWebhook := filepath.Join(filepath.Dir(noTeamFileTeamNoWebhook.Name()), "unassigned.yml") err = os.Rename(noTeamFileTeamNoWebhook.Name(), noTeamFilePathTeamNoWebhook) require.NoError(t, err) @@ -1548,7 +1548,7 @@ settings: // Now apply config with webhook_settings but no failing_policies_webhook const noTeamTemplateWebhookNoFailing = ` -name: No team +name: Unassigned policies: - name: No Team Test Policy query: SELECT 1 FROM osquery_info WHERE version = '0.0.0'; @@ -1565,7 +1565,7 @@ settings: require.NoError(t, err) err = noTeamFileWebhookNoFailing.Close() require.NoError(t, err) - noTeamFilePathWebhookNoFailing := filepath.Join(filepath.Dir(noTeamFileWebhookNoFailing.Name()), "no-team.yml") + noTeamFilePathWebhookNoFailing := filepath.Join(filepath.Dir(noTeamFileWebhookNoFailing.Name()), "unassigned.yml") err = os.Rename(noTeamFileWebhookNoFailing.Name(), noTeamFilePathWebhookNoFailing) require.NoError(t, err) @@ -1714,7 +1714,7 @@ policies: reports: ` - noTeamConfig = `name: No team + noTeamConfig = `name: Unassigned controls: macos_setup: bootstrap_package: %s @@ -1751,7 +1751,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -1899,7 +1899,7 @@ func (s *enterpriseIntegrationGitopsTestSuite) TestMacOSSetupScriptWithFleetSecr require.NoError(t, err) // Create a no-team file with the script - const noTeamTemplate = `name: No team + const noTeamTemplate = `name: Unassigned policies: controls: macos_setup: @@ -1912,7 +1912,7 @@ software: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -2426,7 +2426,7 @@ labels: query: SELECT 1 ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: software: @@ -2464,7 +2464,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -2635,7 +2635,7 @@ policies: reports: ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: software: @@ -2672,7 +2672,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -2753,7 +2753,7 @@ policies: reports: ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: software: @@ -2808,7 +2808,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -3635,7 +3635,7 @@ settings: }, { specialCase: "No team", - teamName: "No team", + teamName: "Unassigned", }, } for _, tc := range testCases { @@ -3657,7 +3657,7 @@ settings: teamFileName := teamFile.Name() if tc.specialCase == "No team" { - noTeamFilePath := filepath.Join(filepath.Dir(teamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(teamFile.Name()), "unassigned.yml") err = os.Rename(teamFile.Name(), noTeamFilePath) require.NoError(t, err) @@ -3801,14 +3801,14 @@ team_settings: { testName: "No team VPP", VPPTeam: "No team", - teamName: "No team", + teamName: "Unassigned", teamTemplate: testVPP, errContains: ptr.String("Couldn't edit software."), }, { testName: "No team Installers", VPPTeam: "No team", - teamName: "No team", + teamName: "Unassigned", teamTemplate: testPackages, errContains: ptr.String("Couldn't edit software."), }, @@ -3833,7 +3833,7 @@ team_settings: teamFileName := teamFile.Name() if tc.VPPTeam == "No team" { - noTeamFilePath := filepath.Join(filepath.Dir(teamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(teamFile.Name()), "unassigned.yml") err = os.Rename(teamFile.Name(), noTeamFilePath) require.NoError(t, err) teamFileName = noTeamFilePath @@ -3952,7 +3952,7 @@ policies: reports: ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: - description: Test policy. @@ -3996,7 +3996,7 @@ settings: err := os.WriteFile(globalFile, []byte(globalTemplate), 0o644) //nolint:gosec require.NoError(t, err) - noTeamFile := filepath.Join(tempDir, "no-team.yml") + noTeamFile := filepath.Join(tempDir, "unassigned.yml") err = os.WriteFile(noTeamFile, []byte(noTeamTemplate), 0o644) require.NoError(t, err) @@ -4346,7 +4346,7 @@ org_settings: policies: reports: ` - noTeamTemplate = `name: No team + noTeamTemplate = `name: Unassigned controls: policies: software: @@ -4388,7 +4388,7 @@ settings: require.NoError(t, err) err = noTeamFile.Close() require.NoError(t, err) - noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "no-team.yml") + noTeamFilePath := filepath.Join(filepath.Dir(noTeamFile.Name()), "unassigned.yml") err = os.Rename(noTeamFile.Name(), noTeamFilePath) require.NoError(t, err) diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index a25af252a9..0a6521dc7b 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -234,7 +234,13 @@ func GroupFromBytes(b []byte, options ...GroupFromBytesOpts) (*Group, error) { if err := yaml.Unmarshal(s.Spec, &rawTeam); err != nil { return nil, fmt.Errorf("unmarshaling %s spec: %w", kind, err) } - specs.Teams = append(specs.Teams, rawTeam["team"]) + teamRaw := rawTeam["team"] + var err error + teamRaw, deprecatedKeysMap, err = rewriteNewToOldKeys(teamRaw, fleet.TeamSpec{}) + if err != nil { + return nil, fmt.Errorf("in %s spec: %w", kind, err) + } + specs.Teams = append(specs.Teams, teamRaw) default: return nil, fmt.Errorf("unknown kind %q", s.Kind) diff --git a/server/service/client.go b/server/service/client.go index edc7538b56..1d2e6bab35 100644 --- a/server/service/client.go +++ b/server/service/client.go @@ -27,6 +27,7 @@ import ( "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/mdm" "github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig" + "github.com/fleetdm/fleet/v4/server/platform/endpointer" "github.com/fleetdm/fleet/v4/server/ptr" kithttp "github.com/go-kit/kit/transport/http" ) @@ -161,6 +162,12 @@ func (c *Client) doContextWithHeaders(ctx context.Context, verb, path, rawQuery if err != nil { return nil, ctxerr.Wrap(ctx, err, "marshaling json") } + if rules := endpointer.ExtractAliasRules(params); len(rules) > 0 { + bodyBytes, err = endpointer.RewriteOldToNewKeys(bodyBytes, rules) + if err != nil { + return nil, ctxerr.Wrap(ctx, err, "rewriting deprecated keys") + } + } } } return c.doContextWithBodyAndHeaders(ctx, verb, path, rawQuery, bodyBytes, headers) diff --git a/server/service/client_policies.go b/server/service/client_policies.go index 858ca00c73..5bdbd12561 100644 --- a/server/service/client_policies.go +++ b/server/service/client_policies.go @@ -1,11 +1,9 @@ package service import ( - "encoding/json" "fmt" "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/endpointer" ) func (c *Client) CreateGlobalPolicy(name, query, description, resolution, platform string) error { @@ -27,15 +25,7 @@ func (c *Client) ApplyPolicies(specs []*fleet.PolicySpec) error { req := applyPolicySpecsRequest{Specs: specs} verb, path := "POST", "/api/latest/fleet/spec/policies" var responseBody applyPolicySpecsResponse - data, err := json.Marshal(req) - if err != nil { - return err - } - data, err = endpointer.RewriteOldToNewKeys(data, endpointer.ExtractAliasRules(req)) - if err != nil { - return err - } - return c.authenticatedRequest(data, verb, path, &responseBody) + return c.authenticatedRequest(req, verb, path, &responseBody) } // GetPolicies retrieves the list of Policies. Inherited policies are excluded. diff --git a/server/service/client_queries.go b/server/service/client_queries.go index 502bc33560..9746b3b94c 100644 --- a/server/service/client_queries.go +++ b/server/service/client_queries.go @@ -1,12 +1,10 @@ package service import ( - "encoding/json" "fmt" "net/url" "github.com/fleetdm/fleet/v4/server/fleet" - "github.com/fleetdm/fleet/v4/server/platform/endpointer" ) // ApplyQueries sends the list of Queries to be applied (upserted) to the @@ -15,15 +13,7 @@ func (c *Client) ApplyQueries(specs []*fleet.QuerySpec) error { req := applyQuerySpecsRequest{Specs: specs} verb, path := "POST", "/api/latest/fleet/spec/reports" var responseBody applyQuerySpecsResponse - data, err := json.Marshal(req) - if err != nil { - return err - } - data, err = endpointer.RewriteOldToNewKeys(data, endpointer.ExtractAliasRules(req)) - if err != nil { - return err - } - return c.authenticatedRequest(data, verb, path, &responseBody) + return c.authenticatedRequest(req, verb, path, &responseBody) } // GetQuerySpec returns the query spec of a query by its team+name. diff --git a/server/service/client_targets.go b/server/service/client_targets.go index 16c124831c..1786afb784 100644 --- a/server/service/client_targets.go +++ b/server/service/client_targets.go @@ -1,8 +1,6 @@ package service import ( - "fmt" - "github.com/fleetdm/fleet/v4/server/fleet" ) @@ -20,7 +18,7 @@ func (c *Client) SearchTargets(query string, hostIDs, labelIDs []uint) (*fleet.T var responseBody searchTargetsResponse err := c.authenticatedRequest(req, verb, path, &responseBody) if err != nil { - return nil, fmt.Errorf("SearchTargets: %s", err) + return nil, err } hosts := make([]*fleet.Host, len(responseBody.Targets.Hosts))