Match pre-assigned profiles to a team (or create one) and assign host to team (#12127)
This commit is contained in:
+116
-2
@@ -7,10 +7,13 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/pkg/file"
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
@@ -742,7 +745,118 @@ func (svc *Service) MDMAppleMatchPreassignment(ctx context.Context, externalHost
|
||||
return ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
// TODO(mna): TBD the exact signature, must return the list of profiles for matching
|
||||
//profs, err := svc.profileMatcher.RetrieveProfiles(ctx, externalHostIdentifier)
|
||||
profs, err := svc.profileMatcher.RetrieveProfiles(ctx, externalHostIdentifier)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(profs.Profiles) == 0 || profs.HostUUID == "" {
|
||||
return nil // nothing to do
|
||||
}
|
||||
|
||||
// load the host and ensure it is enrolled in Fleet MDM
|
||||
host, err := svc.ds.HostByIdentifier(ctx, profs.HostUUID)
|
||||
if err != nil {
|
||||
return err // will return a not found error if host does not exist
|
||||
}
|
||||
|
||||
hostMDM, err := svc.ds.GetHostMDM(ctx, host.ID)
|
||||
if err != nil || !hostMDM.IsFleetEnrolled() {
|
||||
if err == nil || fleet.IsNotFound(err) {
|
||||
err = errors.New("host is not enrolled in Fleet MDM")
|
||||
return ctxerr.Wrap(ctx, &fleet.BadRequestError{
|
||||
Message: err.Error(),
|
||||
InternalErr: err,
|
||||
})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Collect the profiles' hashes and look for a team with exactly that set.
|
||||
// Also collect the profiles' groups in case we need to create a new team,
|
||||
// and the list of raw profiles bytes.
|
||||
hashes, groups, rawProfiles := make([]string, 0, len(profs.Profiles)),
|
||||
make([]string, 0, len(profs.Profiles)),
|
||||
make([][]byte, 0, len(profs.Profiles))
|
||||
for _, prof := range profs.Profiles {
|
||||
hashes = append(hashes, prof.HexMD5Hash)
|
||||
rawProfiles = append(rawProfiles, prof.Profile)
|
||||
if prof.Group != "" {
|
||||
groups = append(groups, prof.Group)
|
||||
}
|
||||
}
|
||||
|
||||
// find a team with exactly that set of profiles
|
||||
teamIDs, err := svc.ds.MatchMDMAppleConfigProfiles(ctx, hashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var targetTeamID uint
|
||||
if len(teamIDs) > 0 {
|
||||
// if the host is already in one of those valid teams, nothing to do.
|
||||
if host.TeamID != nil {
|
||||
for _, tmID := range teamIDs {
|
||||
if *host.TeamID == tmID {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
// else assign the host to the first valid team
|
||||
targetTeamID = teamIDs[0]
|
||||
|
||||
} else {
|
||||
// Create a new team with this set of profiles. Creating via the service
|
||||
// call so that it properly assigns the agent options and creates audit
|
||||
// activities, etc.
|
||||
teamName := teamNameFromPreassignGroups(groups)
|
||||
tm, err := svc.NewTeam(ctx, fleet.TeamPayload{Name: &teamName})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create profiles for that team via the service call, so that uniqueness
|
||||
// of profile identifier/name is verified, activity created, etc.
|
||||
// NOTE: this will use the read replica to load the team, which was just
|
||||
// created above, could lead to not found issues with slow replication.
|
||||
if err := svc.BatchSetMDMAppleProfiles(ctx, &tm.ID, nil, rawProfiles, false); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
targetTeamID = tm.ID
|
||||
}
|
||||
|
||||
// assign host to that team via the service call, which will trigger
|
||||
// deployment of the profiles.
|
||||
if err := svc.AddHostsToTeam(ctx, &targetTeamID, []uint{host.ID}); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// teamNameFromPreassignGroups returns the team name to use for a new team
|
||||
// created to match the set of profiles preassigned to a host. The team name is
|
||||
// derived from the "group" field provided with each request to pre-assign a
|
||||
// profile to a host (in fleet.MDMApplePreassignProfilePayload). That field is
|
||||
// optional, and empty groups are ignored. The current timestamp is appended to
|
||||
// the team's name to help avoid duplicates.
|
||||
func teamNameFromPreassignGroups(groups []string) string {
|
||||
const defaultName = "default"
|
||||
|
||||
dedupeGroups := make(map[string]struct{}, len(groups))
|
||||
for _, group := range groups {
|
||||
if group != "" {
|
||||
dedupeGroups[group] = struct{}{}
|
||||
}
|
||||
}
|
||||
groups = groups[:0]
|
||||
for group := range dedupeGroups {
|
||||
groups = append(groups, group)
|
||||
}
|
||||
sort.Strings(groups)
|
||||
|
||||
if len(groups) == 0 {
|
||||
groups = []string{defaultName}
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s (%s)", strings.Join(groups, " - "), time.Now().UTC().Format("2006-01-02:15:04:05"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user