macOS managed local account foundations (#43381)
Implements both #42942 and #42943 Co-authored-by: jkatz01 <yehonatankatz@gmail.com>
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -705,3 +706,52 @@ var (
|
||||
</Item>
|
||||
</Exec>`
|
||||
)
|
||||
|
||||
func (svc *Service) GetHostManagedAccountPassword(ctx context.Context, hostID uint) (*fleet.HostManagedLocalAccountPassword, error) {
|
||||
// First ensure the user has access to list hosts, then check the specific
|
||||
// host once team_id is loaded.
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Host{}, fleet.ActionList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
host, err := svc.ds.HostLite(ctx, hostID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host lite")
|
||||
}
|
||||
if err := svc.authz.Authorize(ctx, host, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !fleet.IsMacOSPlatform(host.Platform) {
|
||||
return nil, &fleet.BadRequestError{
|
||||
Message: "Host is not a macOS device.",
|
||||
}
|
||||
}
|
||||
|
||||
acct, err := svc.ds.GetHostManagedLocalAccountStatus(ctx, host.UUID)
|
||||
if err != nil {
|
||||
if fleet.IsNotFound(err) {
|
||||
return nil, &fleet.BadRequestError{
|
||||
Message: "Host does not have a managed account.",
|
||||
}
|
||||
}
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host managed account status")
|
||||
}
|
||||
if acct.Status == nil || *acct.Status != string(fleet.MDMDeliveryVerified) {
|
||||
return nil, &fleet.BadRequestError{
|
||||
Message: "Host's managed account password is not yet verified.",
|
||||
}
|
||||
}
|
||||
|
||||
pwd, err := svc.ds.GetHostManagedLocalAccountPassword(ctx, host.UUID)
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "get host managed account password")
|
||||
}
|
||||
|
||||
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), fleet.ActivityTypeViewedManagedLocalAccount{
|
||||
HostID: host.ID,
|
||||
HostDisplayName: host.DisplayName(),
|
||||
}); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "create viewed managed local account activity")
|
||||
}
|
||||
|
||||
return pwd, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
)
|
||||
|
||||
func TestGetHostManagedAccountPasswordAuth(t *testing.T) {
|
||||
t.Parallel()
|
||||
ds := new(mock.Store)
|
||||
svc, baseSvc := newTestServiceWithMock(t, ds)
|
||||
|
||||
teamID := uint(1)
|
||||
|
||||
verified := string(fleet.MDMDeliveryVerified)
|
||||
|
||||
ds.HostLiteFunc = func(ctx context.Context, hostID uint) (*fleet.Host, error) {
|
||||
return &fleet.Host{ID: hostID, UUID: "test-uuid", Platform: "darwin", TeamID: &teamID}, nil
|
||||
}
|
||||
ds.GetHostManagedLocalAccountStatusFunc = func(ctx context.Context, hostUUID string) (*fleet.HostMDMManagedLocalAccount, error) {
|
||||
return &fleet.HostMDMManagedLocalAccount{Status: &verified, PasswordAvailable: true}, nil
|
||||
}
|
||||
ds.GetHostManagedLocalAccountPasswordFunc = func(ctx context.Context, hostUUID string) (*fleet.HostManagedLocalAccountPassword, error) {
|
||||
return &fleet.HostManagedLocalAccountPassword{}, nil
|
||||
}
|
||||
baseSvc.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *fleet.User
|
||||
shouldFail bool
|
||||
}{
|
||||
{
|
||||
"global admin",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global maintainer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global observer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global observer+",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserverPlus)},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global gitops",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"team admin, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team maintainer, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team observer, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team observer+, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team gitops, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"team admin, DOES NOT belong to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleAdmin}}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"team observer, DOES NOT belong to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 2}, Role: fleet.RoleObserver}}},
|
||||
true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(context.Background(), viewer.Viewer{User: tt.user})
|
||||
_, err := svc.GetHostManagedAccountPassword(ctx, 1)
|
||||
checkAuthErr(t, tt.shouldFail, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -203,7 +203,7 @@ func (svc *Service) updateAppConfigMDMAppleSetup(ctx context.Context, payload fl
|
||||
return err
|
||||
}
|
||||
|
||||
var didUpdate, didUpdateMacOSEndUserAuth bool
|
||||
var didUpdate, didUpdateMacOSEndUserAuth, didUpdateManagedLocalAccount bool
|
||||
if payload.EnableEndUserAuthentication != nil {
|
||||
if ac.MDM.MacOSSetup.EnableEndUserAuthentication != *payload.EnableEndUserAuthentication {
|
||||
ac.MDM.MacOSSetup.EnableEndUserAuthentication = *payload.EnableEndUserAuthentication
|
||||
@@ -272,6 +272,24 @@ func (svc *Service) updateAppConfigMDMAppleSetup(ctx context.Context, payload fl
|
||||
}
|
||||
}
|
||||
|
||||
if payload.EnableManagedLocalAccount != nil {
|
||||
if ac.MDM.MacOSSetup.EnableManagedLocalAccount == nil || *ac.MDM.MacOSSetup.EnableManagedLocalAccount != *payload.EnableManagedLocalAccount {
|
||||
ac.MDM.MacOSSetup.EnableManagedLocalAccount = payload.EnableManagedLocalAccount
|
||||
didUpdateManagedLocalAccount = true
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
if payload.EndUserLocalAccountType != nil {
|
||||
if *payload.EndUserLocalAccountType != "admin" {
|
||||
return fleet.NewInvalidArgumentError("end_user_local_account_type", `only "admin" is supported`)
|
||||
}
|
||||
if ac.MDM.MacOSSetup.EndUserLocalAccountType == nil || *ac.MDM.MacOSSetup.EndUserLocalAccountType != *payload.EndUserLocalAccountType {
|
||||
ac.MDM.MacOSSetup.EndUserLocalAccountType = payload.EndUserLocalAccountType
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
if didUpdate {
|
||||
if err := svc.ds.SaveAppConfig(ctx, ac); err != nil {
|
||||
return err
|
||||
@@ -281,6 +299,11 @@ func (svc *Service) updateAppConfigMDMAppleSetup(ctx context.Context, payload fl
|
||||
return err
|
||||
}
|
||||
}
|
||||
if didUpdateManagedLocalAccount {
|
||||
if err := svc.updateMacOSSetupEnableManagedLocalAccount(ctx, *ac.MDM.MacOSSetup.EnableManagedLocalAccount, nil, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -302,6 +325,19 @@ func (svc *Service) updateMacOSSetupEnableEndUserAuth(ctx context.Context, enabl
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) updateMacOSSetupEnableManagedLocalAccount(ctx context.Context, enable bool, teamID *uint, teamName *string) error {
|
||||
var act fleet.ActivityDetails
|
||||
if enable {
|
||||
act = fleet.ActivityTypeEnabledManagedLocalAccount{TeamID: teamID, TeamName: teamName}
|
||||
} else {
|
||||
act = fleet.ActivityTypeDisabledManagedLocalAccount{TeamID: teamID, TeamName: teamName}
|
||||
}
|
||||
if err := svc.NewActivity(ctx, authz.UserFromContext(ctx), act); err != nil {
|
||||
return ctxerr.Wrap(ctx, err, "create activity for macos enable managed local account change")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (svc *Service) validateMDMAppleSetupPayload(ctx context.Context, payload fleet.MDMAppleSetupPayload) error {
|
||||
// appconfig is only used internally, it's fine to read it unobfuscated
|
||||
// (svc.AppConfigObfuscated must not be used because the write-only users
|
||||
@@ -312,7 +348,8 @@ func (svc *Service) validateMDMAppleSetupPayload(ctx context.Context, payload fl
|
||||
}
|
||||
|
||||
// If anything besides enable_end_user_authentication is being updated, ensure MDM is on.
|
||||
if (payload.RequireAllSoftware != nil || payload.EnableReleaseDeviceManually != nil || payload.ManualAgentInstall != nil) && !ac.MDM.EnabledAndConfigured {
|
||||
if (payload.RequireAllSoftware != nil || payload.EnableReleaseDeviceManually != nil || payload.ManualAgentInstall != nil ||
|
||||
payload.EnableManagedLocalAccount != nil || payload.EndUserLocalAccountType != nil) && !ac.MDM.EnabledAndConfigured {
|
||||
return fleet.ErrMDMNotConfigured
|
||||
}
|
||||
|
||||
|
||||
@@ -2012,7 +2012,7 @@ func (svc *Service) updateTeamMDMDiskEncryption(ctx context.Context, tm *fleet.T
|
||||
}
|
||||
|
||||
func (svc *Service) updateTeamMDMAppleSetup(ctx context.Context, tm *fleet.Team, payload fleet.MDMAppleSetupPayload) error {
|
||||
var didUpdate, didUpdateMacOSEndUserAuth bool
|
||||
var didUpdate, didUpdateMacOSEndUserAuth, didUpdateManagedLocalAccount bool
|
||||
|
||||
if payload.EnableEndUserAuthentication != nil {
|
||||
if tm.Config.MDM.MacOSSetup.EnableEndUserAuthentication != *payload.EnableEndUserAuthentication {
|
||||
@@ -2082,6 +2082,24 @@ func (svc *Service) updateTeamMDMAppleSetup(ctx context.Context, tm *fleet.Team,
|
||||
}
|
||||
}
|
||||
|
||||
if payload.EnableManagedLocalAccount != nil {
|
||||
if tm.Config.MDM.MacOSSetup.EnableManagedLocalAccount == nil || *tm.Config.MDM.MacOSSetup.EnableManagedLocalAccount != *payload.EnableManagedLocalAccount {
|
||||
tm.Config.MDM.MacOSSetup.EnableManagedLocalAccount = payload.EnableManagedLocalAccount
|
||||
didUpdateManagedLocalAccount = true
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
if payload.EndUserLocalAccountType != nil {
|
||||
if *payload.EndUserLocalAccountType != "admin" {
|
||||
return fleet.NewInvalidArgumentError("end_user_local_account_type", `only "admin" is supported`)
|
||||
}
|
||||
if tm.Config.MDM.MacOSSetup.EndUserLocalAccountType == nil || *tm.Config.MDM.MacOSSetup.EndUserLocalAccountType != *payload.EndUserLocalAccountType {
|
||||
tm.Config.MDM.MacOSSetup.EndUserLocalAccountType = payload.EndUserLocalAccountType
|
||||
didUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
if didUpdate {
|
||||
if _, err := svc.ds.SaveTeam(ctx, tm); err != nil {
|
||||
return err
|
||||
@@ -2091,6 +2109,11 @@ func (svc *Service) updateTeamMDMAppleSetup(ctx context.Context, tm *fleet.Team,
|
||||
return err
|
||||
}
|
||||
}
|
||||
if didUpdateManagedLocalAccount {
|
||||
if err := svc.updateMacOSSetupEnableManagedLocalAccount(ctx, *tm.Config.MDM.MacOSSetup.EnableManagedLocalAccount, &tm.ID, &tm.Name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user