Skip to content

Commit

Permalink
Clear bootstrap package and enrollment profile with GitOps (#26095)
Browse files Browse the repository at this point in the history
For #25648 

Fixed issue where `fleetctl gitops` was NOT deleting macOS setup
experience bootstrap package and enrollment profile. GitOps should clear
all settings that are not explicitly set in YAML config files.

# Checklist for submitter

- [x] Changes file added for user-visible changes in `changes/`,
`orbit/changes/` or `ee/fleetd-chrome/changes`.
See [Changes
files](https://github.com/fleetdm/fleet/blob/main/docs/Contributing/Committing-Changes.md#changes-files)
for more information.
- [x] Added/updated automated tests
- [x] A detailed QA plan exists on the associated ticket (if it isn't
there, work with the product group's QA engineer to add it)
- [x] Manual QA for all new/changed functionality
  • Loading branch information
getvictor authored Feb 7, 2025
1 parent 7fce39b commit 2eb5119
Show file tree
Hide file tree
Showing 9 changed files with 361 additions and 128 deletions.
1 change: 1 addition & 0 deletions changes/25648-gitops-bootstrap-profile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fixed issue where `fleetctl gitops` was NOT deleting macOS setup experience bootstrap package and enrollment profile. GitOps should clear all settings that are not explicitly set in YAML config files.
6 changes: 3 additions & 3 deletions cmd/fleetctl/gitops.go
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ func gitopsCommand() *cli.Command {
}
}

func extractControlsForNoTeam(flFilenames cli.StringSlice, appConfig *fleet.EnrichedAppConfig) (spec.Controls, bool, error) {
func extractControlsForNoTeam(flFilenames cli.StringSlice, appConfig *fleet.EnrichedAppConfig) (spec.GitOpsControls, bool, error) {
for _, flFilename := range flFilenames.Value() {
if filepath.Base(flFilename) == "no-team.yml" {
if !appConfig.License.IsPremium() {
Expand All @@ -283,12 +283,12 @@ func extractControlsForNoTeam(flFilenames cli.StringSlice, appConfig *fleet.Enri
baseDir := filepath.Dir(flFilename)
config, err := spec.GitOpsFromFile(flFilename, baseDir, appConfig, func(format string, a ...interface{}) {})
if err != nil {
return spec.Controls{}, false, err
return spec.GitOpsControls{}, false, err
}
return config.Controls, true, nil
}
}
return spec.Controls{}, false, nil
return spec.GitOpsControls{}, false, nil
}

// checkABMTeamAssignments validates the spec, and finds if:
Expand Down
181 changes: 155 additions & 26 deletions cmd/fleetctl/gitops_enterprise_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"testing"

"github.com/fleetdm/fleet/v4/server/config"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
"github.com/fleetdm/fleet/v4/server/datastore/redis/redistest"
"github.com/fleetdm/fleet/v4/server/fleet"
appleMdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
Expand All @@ -18,6 +19,8 @@ import (
"github.com/fleetdm/fleet/v4/server/service"
"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"
"github.com/stretchr/testify/suite"
Expand Down Expand Up @@ -112,35 +115,12 @@ func (s *enterpriseIntegrationGitopsTestSuite) TestFleetGitops() {
t := s.T()
const fleetGitopsRepo = "https://github.com/fleetdm/fleet-gitops"

// Create GitOps user
user := fleet.User{
Name: "GitOps User",
Email: "[email protected]",
GlobalRole: ptr.String(fleet.RoleGitOps),
}
require.NoError(t, user.SetPassword(test.GoodPassword, 10, 10))
_, err := s.ds.NewUser(context.Background(), &user)
require.NoError(t, err)

// Create a temporary fleetctl config file
fleetctlConfig, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
token := s.getTestToken(user.Email, test.GoodPassword)
configStr := fmt.Sprintf(
`
contexts:
default:
address: %s
tls-skip-verify: true
token: %s
`, s.server.URL, token,
)
_, err = fleetctlConfig.WriteString(configStr)
require.NoError(t, err)
user := s.createGitOpsUser(t)
fleetctlConfig := s.createFleetctlConfig(t, user)

// Clone git repo
repoDir := t.TempDir()
_, err = git.PlainClone(
_, err := git.PlainClone(
repoDir, false, &git.CloneOptions{
ReferenceName: "main",
SingleBranch: true,
Expand Down Expand Up @@ -235,3 +215,152 @@ team_settings:
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", fileName})
}
}

func (s *enterpriseIntegrationGitopsTestSuite) createFleetctlConfig(t *testing.T, user fleet.User) *os.File {
fleetctlConfig, err := os.CreateTemp(t.TempDir(), "*.yml")
require.NoError(t, err)
token := s.getTestToken(user.Email, test.GoodPassword)
configStr := fmt.Sprintf(
`
contexts:
default:
address: %s
tls-skip-verify: true
token: %s
`, s.server.URL, token,
)
_, err = fleetctlConfig.WriteString(configStr)
require.NoError(t, err)
return fleetctlConfig
}

func (s *enterpriseIntegrationGitopsTestSuite) createGitOpsUser(t *testing.T) fleet.User {
user := fleet.User{
Name: "GitOps User",
Email: uuid.NewString() + "@example.com",
GlobalRole: ptr.String(fleet.RoleGitOps),
}
require.NoError(t, user.SetPassword(test.GoodPassword, 10, 10))
_, err := s.ds.NewUser(context.Background(), &user)
require.NoError(t, err)
return user
}

// TestDeleteMacOSSetup tests the deletion of macOS setup assets by `fleetctl gitops` command.
func (s *enterpriseIntegrationGitopsTestSuite) TestDeleteMacOSSetup() {
t := s.T()

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
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"})
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()})

// 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
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name(), "--dry-run"})
_ = runAppForTest(t, []string{"gitops", "--config", fleetctlConfig.Name(), "-f", globalFile.Name(), "-f", teamFile.Name()})

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
})

}
24 changes: 24 additions & 0 deletions cmd/fleetctl/gitops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,6 +793,18 @@ func TestGitOpsFullTeam(t *testing.T) {
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) {
return &fleet.MDMAppleBootstrapPackage{}, nil
}
ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error {
return nil
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, nil
}
ds.DeleteMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) error {
return nil
}

// Team
var savedTeam *fleet.Team
Expand Down Expand Up @@ -2729,6 +2741,18 @@ func setupFullGitOpsPremiumServer(t *testing.T) (*mock.Store, **fleet.AppConfig,
ds.DeleteMDMAppleDeclarationByNameFunc = func(ctx context.Context, teamID *uint, name string) error {
return nil
}
ds.GetMDMAppleBootstrapPackageMetaFunc = func(ctx context.Context, teamID uint) (*fleet.MDMAppleBootstrapPackage, error) {
return &fleet.MDMAppleBootstrapPackage{}, nil
}
ds.DeleteMDMAppleBootstrapPackageFunc = func(ctx context.Context, teamID uint) error {
return nil
}
ds.GetMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) (*fleet.MDMAppleSetupAssistant, error) {
return nil, nil
}
ds.DeleteMDMAppleSetupAssistantFunc = func(ctx context.Context, teamID *uint) error {
return nil
}
ds.IsEnrollSecretAvailableFunc = func(ctx context.Context, secret string, new bool, teamID *uint) (bool, error) {
return true, nil
}
Expand Down
Loading

0 comments on commit 2eb5119

Please sign in to comment.