Skip to content

Commit

Permalink
Bugfix and SE: support VPP apps and (install during setup of VPP apps…
Browse files Browse the repository at this point in the history
…) in `gitops` for no-team (#23160)
  • Loading branch information
mna authored Oct 28, 2024
1 parent d66a2a7 commit d5c9a16
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 41 deletions.
1 change: 1 addition & 0 deletions changes/22970-support-vpp-apps-for-no-team-gitops
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Fixed `fleetctl gitops` to support VPP apps, along with setting the VPP apps to install during the setup experience.
3 changes: 1 addition & 2 deletions cmd/fleetctl/gitops_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1968,8 +1968,7 @@ func TestGitOpsNoTeamSoftwareInstallers(t *testing.T) {
{"testdata/gitops/no_team_setup_software_valid.yml", ""},
{"testdata/gitops/no_team_setup_software_invalid_script.yml", "no_such_script.sh: no such file"},
{"testdata/gitops/no_team_setup_software_invalid_software_package.yml", "no_such_software.yml\" does not exist for that team"},
// VPP apps for No Team is unsupported at the moment : https://github.com/fleetdm/fleet/issues/22970
// {"testdata/gitops/no_team_setup_software_invalid_vpp_app.yml", "\"no_such_app\" does not exist for that team"},
{"testdata/gitops/no_team_setup_software_invalid_vpp_app.yml", "\"no_such_app\" does not exist for that team"},
}
for _, c := range cases {
t.Run(filepath.Base(c.noTeamFile), func(t *testing.T) {
Expand Down
4 changes: 4 additions & 0 deletions cmd/fleetctl/testdata/gitops/no_team_setup_software_valid.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ controls:
macos_setup:
software:
- package_path: lib/software_ruby.yml
- app_store_id: "1"
script: lib/setup_script.sh
policies:
software:
app_store_apps:
- app_store_id: "1"
- app_store_id: "2"
packages:
- path: lib/software_ruby.yml
- path: lib/software_other.yml
27 changes: 13 additions & 14 deletions ee/server/service/vpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,25 +40,24 @@ func (svc *Service) getVPPToken(ctx context.Context, teamID *uint) (string, erro
}

func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string, payloads []fleet.VPPBatchPayload, dryRun bool) error {
if teamName == "" {
svc.authz.SkipAuthorization(ctx) // so that the error message is not replaced by "forbidden"
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("team_name", "must not be empty"))
}

if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
return err
}

team, err := svc.ds.TeamByName(ctx, teamName)
if err != nil {
// If this is a dry run, the team may not have been created yet
if dryRun && fleet.IsNotFound(err) {
return nil
var teamID *uint
if teamName != "" {
tm, err := svc.ds.TeamByName(ctx, teamName)
if err != nil {
// If this is a dry run, the team may not have been created yet
if dryRun && fleet.IsNotFound(err) {
return nil
}
return err
}
return err
teamID = &tm.ID
}

if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: &team.ID}, fleet.ActionWrite); err != nil {
if err := svc.authz.Authorize(ctx, &fleet.SoftwareInstaller{TeamID: teamID}, fleet.ActionWrite); err != nil {
return ctxerr.Wrap(ctx, err, "validating authorization")
}

Expand Down Expand Up @@ -95,7 +94,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
var vppAppTeams []fleet.VPPAppTeam
// Don't check for token if we're only disassociating assets
if len(payloads) > 0 {
token, err := svc.getVPPToken(ctx, &team.ID)
token, err := svc.getVPPToken(ctx, teamID)
if err != nil {
return fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "could not retrieve vpp token"), http.StatusUnprocessableEntity)
}
Expand Down Expand Up @@ -164,7 +163,7 @@ func (svc *Service) BatchAssociateVPPApps(ctx context.Context, teamName string,
}

}
if err := svc.ds.SetTeamVPPApps(ctx, &team.ID, vppAppTeams); err != nil {
if err := svc.ds.SetTeamVPPApps(ctx, teamID, vppAppTeams); err != nil {
if errors.Is(err, sql.ErrNoRows) {
return fleet.NewUserMessageError(ctxerr.Wrap(ctx, err, "no vpp token to set team vpp assets"), http.StatusUnprocessableEntity)
}
Expand Down
60 changes: 40 additions & 20 deletions server/service/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -1689,20 +1689,6 @@ func (c *Client) doGitOpsNoTeamSoftware(
return nil, nil
}

var softwareInstallers []fleet.SoftwarePackageResponse

packages := make([]fleet.SoftwarePackageSpec, 0, len(config.Software.Packages))
packagesByPath := make(map[string]fleet.SoftwarePackageSpec, len(config.Software.Packages))
for _, software := range config.Software.Packages {
if software != nil {
packages = append(packages, *software)
if software.ReferencedYamlPath != "" {
// can be referenced by macos_setup.software
packagesByPath[software.ReferencedYamlPath] = *software
}
}
}

// marshaling dance to get the macos_setup data - config.Controls.MacOSSetup
// is of type any and contains a generic map[string]any. By
// marshal-unmarshaling it into a properly typed struct, we avoid having to
Expand Down Expand Up @@ -1732,12 +1718,42 @@ func (c *Client) doGitOpsNoTeamSoftware(
return nil, err
}

// TODO: note that VPP apps are not validated nor taken into account at the moment,
// tracked with issue https://github.com/fleetdm/fleet/issues/22970
if err := validateTeamOrNoTeamMacOSSetupSoftware(*config.TeamName, macOSSetup.Software.Value, packagesByPath, nil); err != nil {
var softwareInstallers []fleet.SoftwarePackageResponse

packages := make([]fleet.SoftwarePackageSpec, 0, len(config.Software.Packages))
packagesByPath := make(map[string]fleet.SoftwarePackageSpec, len(config.Software.Packages))
for _, software := range config.Software.Packages {
if software != nil {
packages = append(packages, *software)
if software.ReferencedYamlPath != "" {
// can be referenced by macos_setup.software
packagesByPath[software.ReferencedYamlPath] = *software
}
}
}

var appsPayload []fleet.VPPBatchPayload
vppApps := make([]fleet.TeamSpecAppStoreApp, 0, len(config.Software.AppStoreApps))
appsByAppID := make(map[string]fleet.TeamSpecAppStoreApp, len(config.Software.AppStoreApps))
for _, vppApp := range config.Software.AppStoreApps {
if vppApp != nil {
vppApps = append(vppApps, *vppApp)
// can be referenced by macos_setup.software
appsByAppID[vppApp.AppStoreID] = *vppApp

_, installDuringSetup := noTeamSoftwareMacOSSetup[fleet.MacOSSetupSoftware{AppStoreID: vppApp.AppStoreID}]
appsPayload = append(appsPayload, fleet.VPPBatchPayload{
AppStoreID: vppApp.AppStoreID,
SelfService: vppApp.SelfService,
InstallDuringSetup: &installDuringSetup,
})
}
}

if err := validateTeamOrNoTeamMacOSSetupSoftware(*config.TeamName, macOSSetup.Software.Value, packagesByPath, appsByAppID); err != nil {
return nil, err
}
payload, err := buildSoftwarePackagesPayload(baseDir, packages, noTeamSoftwareMacOSSetup)
swPkgPayload, err := buildSoftwarePackagesPayload(baseDir, packages, noTeamSoftwareMacOSSetup)
if err != nil {
return nil, fmt.Errorf("applying software installers: %w", err)
}
Expand All @@ -1751,11 +1767,15 @@ func (c *Client) doGitOpsNoTeamSoftware(
return nil, fmt.Errorf("deleting setup experience script for No team: %w", err)
}

logFn("[+] applying %d software packages for 'No team'\n", len(payload))
softwareInstallers, err = c.ApplyNoTeamSoftwareInstallers(payload, fleet.ApplySpecOptions{DryRun: dryRun})
logFn("[+] applying %d software packages for 'No team'\n", len(swPkgPayload))
softwareInstallers, err = c.ApplyNoTeamSoftwareInstallers(swPkgPayload, fleet.ApplySpecOptions{DryRun: dryRun})
if err != nil {
return nil, fmt.Errorf("applying software installers: %w", err)
}
logFn("[+] applying %d app store apps for 'No team'\n", len(appsPayload))
if err := c.ApplyNoTeamAppStoreAppsAssociation(appsPayload, fleet.ApplySpecOptions{DryRun: dryRun}); err != nil {
return nil, fmt.Errorf("applying app store apps: %w", err)
}

if dryRun {
logFn("[+] would've applied 'No Team' software packages\n")
Expand Down
14 changes: 13 additions & 1 deletion server/service/client_teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,23 @@ func (c *Client) ApplyTeamSoftwareInstallers(tmName string, softwareInstallers [
}

func (c *Client) ApplyTeamAppStoreAppsAssociation(tmName string, vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) error {
verb, path := "POST", "/api/latest/fleet/software/app_store_apps/batch"
query, err := url.ParseQuery(opts.RawQuery())
if err != nil {
return err
}
query.Add("team_name", tmName)
return c.applyAppStoreAppsAssociation(vppBatchPayload, query)
}

func (c *Client) ApplyNoTeamAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, opts fleet.ApplySpecOptions) error {
query, err := url.ParseQuery(opts.RawQuery())
if err != nil {
return err
}
return c.applyAppStoreAppsAssociation(vppBatchPayload, query)
}

func (c *Client) applyAppStoreAppsAssociation(vppBatchPayload []fleet.VPPBatchPayload, query url.Values) error {
verb, path := "POST", "/api/latest/fleet/software/app_store_apps/batch"
return c.authenticatedRequestWithQuery(map[string]interface{}{"app_store_apps": vppBatchPayload}, verb, path, nil, query.Encode())
}
3 changes: 0 additions & 3 deletions server/service/integration_mdm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10058,9 +10058,6 @@ func (s *integrationMDMTestSuite) TestBatchAssociateAppStoreApps() {
t := s.T()
batchURL := "/api/latest/fleet/software/app_store_apps/batch"

// a team name is required (we don't allow installers for "no team")
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusBadRequest)

// non-existent team
s.Do("POST", batchURL, batchAssociateAppStoreAppsRequest{}, http.StatusNotFound, "team_name", "foo")

Expand Down
2 changes: 1 addition & 1 deletion server/service/software_installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -661,7 +661,7 @@ func (svc *Service) HasSelfServiceSoftwareInstallers(ctx context.Context, host *
//////////////////////////////////////////////////////////////////////////////

type batchAssociateAppStoreAppsRequest struct {
TeamName string `json:"-" query:"team_name"`
TeamName string `json:"-" query:"team_name,optional"`
DryRun bool `json:"-" query:"dry_run,optional"`
Apps []fleet.VPPBatchPayload `json:"app_store_apps"`
}
Expand Down

0 comments on commit d5c9a16

Please sign in to comment.