Add MDM configuration permissions to GitOps (#11207)
#8593 Adding new MDM functionality to GitOps. - ~[ ] Changes file added for user-visible changes in `changes/` or `orbit/changes/`. See [Changes files](https://fleetdm.com/docs/contributing/committing-changes#changes-files) for more information.~ - ~[ ] Documented any API changes (docs/Using-Fleet/REST-API.md or docs/Contributing/API-for-contributors.md)~ - [X] Documented any permissions changes - ~[ ] Input data is properly validated, `SELECT *` is avoided, SQL injection is prevented (using placeholders for values in statements)~ - ~[ ] Added support on fleet's osquery simulator `cmd/osquery-perf` for new osquery data ingestion features.~ - [x] Added/updated tests - [X] Manual QA for all new/changed functionality - ~For Orbit and Fleet Desktop changes:~ - ~[ ] Manual QA must be performed in the three main OSs, macOS, Windows and Linux.~ - ~[ ] Auto-update manual QA, from released version of component to new version (see [tools/tuf/test](../tools/tuf/test/README.md)).~
This commit is contained in:
+2
-3
@@ -11,9 +11,6 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
|
||||
eewebhooks "github.com/fleetdm/fleet/v4/ee/server/webhooks"
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
@@ -21,8 +18,10 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
|
||||
"github.com/fleetdm/fleet/v4/server/policies"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
"github.com/fleetdm/fleet/v4/server/service/externalsvc"
|
||||
"github.com/fleetdm/fleet/v4/server/service/schedule"
|
||||
"github.com/fleetdm/fleet/v4/server/vulnerabilities/macoffice"
|
||||
|
||||
+73
-19
@@ -7,13 +7,16 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
nanomdm_mock "github.com/fleetdm/fleet/v4/server/mock/nanomdm"
|
||||
"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"
|
||||
)
|
||||
@@ -639,9 +642,36 @@ func TestApplyPolicies(t *testing.T) {
|
||||
assert.True(t, ds.TeamByNameFuncInvoked)
|
||||
}
|
||||
|
||||
func mobileconfigForTest(name, identifier string) []byte {
|
||||
return []byte(fmt.Sprintf(`<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>PayloadContent</key>
|
||||
<array/>
|
||||
<key>PayloadDisplayName</key>
|
||||
<string>%s</string>
|
||||
<key>PayloadIdentifier</key>
|
||||
<string>%s</string>
|
||||
<key>PayloadType</key>
|
||||
<string>Configuration</string>
|
||||
<key>PayloadUUID</key>
|
||||
<string>%s</string>
|
||||
<key>PayloadVersion</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
`, name, identifier, uuid.New().String()))
|
||||
}
|
||||
|
||||
func TestApplyAsGitOps(t *testing.T) {
|
||||
enqueuer := new(nanomdm_mock.Storage)
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
_, ds := runServerWithMockedDS(t, &service.TestServerOpts{License: license})
|
||||
_, ds := runServerWithMockedDS(t, &service.TestServerOpts{
|
||||
License: license,
|
||||
MDMStorage: enqueuer,
|
||||
MDMPusher: mockPusher{},
|
||||
})
|
||||
|
||||
gitOps := &fleet.User{
|
||||
Name: "GitOps",
|
||||
@@ -669,7 +699,15 @@ func TestApplyAsGitOps(t *testing.T) {
|
||||
|
||||
// Apply global config.
|
||||
currentAppConfig := &fleet.AppConfig{
|
||||
OrgInfo: fleet.OrgInfo{OrgName: "Fleet"}, ServerSettings: fleet.ServerSettings{ServerURL: "https://example.org"},
|
||||
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
|
||||
@@ -725,14 +763,19 @@ spec:
|
||||
teamEnrollSecrets = secrets
|
||||
return nil
|
||||
}
|
||||
/*
|
||||
# TODO(lucas): MDM still not defined.
|
||||
# mdm:
|
||||
# macos_updates:
|
||||
# minimum_version: 10.10.10
|
||||
# deadline: 1992-03-01
|
||||
*/
|
||||
name = writeTmpYml(t, `
|
||||
ds.BatchSetMDMAppleProfilesFunc = func(ctx context.Context, teamID *uint, profiles []*fleet.MDMAppleConfigProfile) error {
|
||||
return nil
|
||||
}
|
||||
ds.BulkSetPendingMDMAppleHostProfilesFunc = func(ctx context.Context, hostIDs, teamIDs, profileIDs []uint, hostUUIDs []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
mobileConfig := mobileconfigForTest("foo", "bar")
|
||||
mobileConfigPath := filepath.Join(t.TempDir(), "foo.mobileconfig")
|
||||
err = os.WriteFile(mobileConfigPath, mobileConfig, 0o644)
|
||||
require.NoError(t, err)
|
||||
|
||||
name = writeTmpYml(t, fmt.Sprintf(`
|
||||
apiVersion: v1
|
||||
kind: team
|
||||
spec:
|
||||
@@ -742,22 +785,33 @@ spec:
|
||||
views:
|
||||
foo: qux
|
||||
name: Team1
|
||||
mdm:
|
||||
macos_updates:
|
||||
minimum_version: 10.10.10
|
||||
deadline: 1992-03-01
|
||||
macos_settings:
|
||||
custom_settings:
|
||||
- %s
|
||||
enable_disk_encryption: false
|
||||
secrets:
|
||||
- secret: BBB
|
||||
`)
|
||||
`, mobileConfigPath))
|
||||
|
||||
require.Equal(t, "[+] applied 1 teams\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.JSONEq(t, string(json.RawMessage(`{"config":{"views":{"foo":"qux"}}}`)), string(*savedTeam.Config.AgentOptions))
|
||||
/*
|
||||
assert.Equal(t, fleet.TeamMDM{
|
||||
MacOSUpdates: fleet.MacOSUpdates{
|
||||
MinimumVersion: "10.10.10",
|
||||
Deadline: "1992-03-01",
|
||||
},
|
||||
}, savedTeam.Config.MDM)
|
||||
*/
|
||||
assert.Equal(t, fleet.TeamMDM{
|
||||
MacOSSettings: fleet.MacOSSettings{
|
||||
CustomSettings: []string{mobileConfigPath},
|
||||
EnableDiskEncryption: false,
|
||||
},
|
||||
MacOSUpdates: fleet.MacOSUpdates{
|
||||
MinimumVersion: "10.10.10",
|
||||
Deadline: "1992-03-01",
|
||||
},
|
||||
}, savedTeam.Config.MDM)
|
||||
assert.Equal(t, []*fleet.EnrollSecret{{Secret: "BBB"}}, teamEnrollSecrets)
|
||||
assert.True(t, ds.ApplyEnrollSecretsFuncInvoked)
|
||||
assert.True(t, ds.BatchSetMDMAppleProfilesFuncInvoked)
|
||||
|
||||
// Apply policies.
|
||||
var appliedPolicySpecs []*fleet.PolicySpec
|
||||
|
||||
@@ -69,9 +69,11 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
|
||||
| View Apple business manager (BM) information | | | | ✅ | |
|
||||
| Generate Apple mobile device management (MDM) certificate signing request (CSR) | | | | ✅ | |
|
||||
| View disk encryption key for macOS hosts enrolled in Fleet's MDM | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Create edit and delete configuration profiles for macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | |
|
||||
| Create edit and delete configuration profiles for macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | ✅ |
|
||||
| Execute MDM commands on macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | |
|
||||
| View results of MDM commands executed on macOS hosts enrolled in Fleet's MDM | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Edit [MDM settings](https://fleetdm.com/docs/using-fleet/mdm-macos-settings) | | | | ✅ | ✅ |
|
||||
| Edit [MDM settings for teams](https://fleetdm.com/docs/using-fleet/mdm-macos-settings) | | | | ✅ | ✅ |
|
||||
|
||||
\*Applies only to Fleet Premium
|
||||
|
||||
@@ -119,10 +121,11 @@ Users that are members of multiple teams can be assigned different roles for eac
|
||||
| Edit [agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) | | | | ✅ | ✅ |
|
||||
| Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ | |
|
||||
| View disk encryption key for macOS hosts enrolled in Fleet's MDM | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Create edit and delete configuration profiles for macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | |
|
||||
| Create edit and delete configuration profiles for macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | ✅ |
|
||||
| Execute MDM commands on macOS hosts enrolled in Fleet's MDM, and read command results | | | ✅ | ✅ | |
|
||||
| Execute MDM commands on macOS hosts enrolled in Fleet's MDM | | | ✅ | ✅ | |
|
||||
| View results of MDM commands executed on macOS hosts enrolled in Fleet's MDM | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Edit [team MDM settings](https://fleetdm.com/docs/using-fleet/mdm-macos-settings) | | | | ✅ | ✅ |
|
||||
|
||||
\* Applies only to [Fleet REST API](https://fleetdm.com/docs/using-fleet/rest-api)
|
||||
|
||||
|
||||
@@ -574,6 +574,13 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global gitops can write Apple MDM config profiles.
|
||||
allow {
|
||||
object.type == "mdm_apple_config_profile"
|
||||
subject.global_role == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admins and maintainers can read and write Apple MDM config profiles on their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
@@ -583,6 +590,15 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Team gitops can write Apple MDM config profiles on their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.team_id != 0
|
||||
object.type == "mdm_apple_config_profile"
|
||||
team_role(subject, object.team_id) == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Global admins and maintainers can issue MDM commands to all hosts.
|
||||
allow {
|
||||
object.type == "host"
|
||||
@@ -663,13 +679,6 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global admins can read and write (i.e. trigger) cron schedules.
|
||||
allow {
|
||||
object.type == "cron_schedules"
|
||||
subject.global_role == admin
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global admins and maintainers can read and write MDM Apple settings.
|
||||
allow {
|
||||
object.type == "mdm_apple_settings"
|
||||
@@ -677,6 +686,13 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global gitops can write MDM Apple settings.
|
||||
allow {
|
||||
object.type == "mdm_apple_settings"
|
||||
subject.global_role == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admins and maintainers can read and write MDM Apple Settings of their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
@@ -685,6 +701,14 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Team gitops can write MDM Apple Settings of their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "mdm_apple_settings"
|
||||
team_role(subject, object.team_id) == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Global admins and maintainers can read and write bootstrap packages.
|
||||
allow {
|
||||
object.type == "mdm_apple_bootstrap_package"
|
||||
@@ -701,6 +725,17 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
##
|
||||
# Cron schedules
|
||||
##
|
||||
|
||||
# Global admins can read and write (i.e. trigger) cron schedules.
|
||||
allow {
|
||||
object.type == "cron_schedules"
|
||||
subject.global_role == admin
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
##
|
||||
# Version
|
||||
##
|
||||
|
||||
@@ -1275,9 +1275,9 @@ func TestAuthorizeMDMAppleConfigProfile(t *testing.T) {
|
||||
{user: test.UserObserverPlus, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserGitOps, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: globalProfile, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalProfile, action: write, allow: false},
|
||||
@@ -1322,7 +1322,7 @@ func TestAuthorizeMDMAppleConfigProfile(t *testing.T) {
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalProfile, action: write, allow: false},
|
||||
@@ -1365,9 +1365,9 @@ func TestAuthorizeMDMAppleSettings(t *testing.T) {
|
||||
{user: test.UserObserverPlus, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserGitOps, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: globalSettings, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalSettings, action: write, allow: false},
|
||||
@@ -1412,7 +1412,7 @@ func TestAuthorizeMDMAppleSettings(t *testing.T) {
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalSettings, action: write, allow: false},
|
||||
|
||||
+8
-2
@@ -172,8 +172,14 @@ func (m MacOSUpdates) Validate() error {
|
||||
|
||||
// MacOSSettings contains settings specific to macOS.
|
||||
type MacOSSettings struct {
|
||||
CustomSettings []string `json:"custom_settings"`
|
||||
EnableDiskEncryption bool `json:"enable_disk_encryption"`
|
||||
// CustomSettings is a slice of configuration profile file paths.
|
||||
//
|
||||
// NOTE: These are only present here for informational purposes.
|
||||
// (The source of truth for profiles is in MySQL.)
|
||||
CustomSettings []string `json:"custom_settings"`
|
||||
// EnableDiskEncryption enables disk encryption on hosts such that the hosts'
|
||||
// disk encryption keys will be stored in Fleet.
|
||||
EnableDiskEncryption bool `json:"enable_disk_encryption"`
|
||||
|
||||
// NOTE: make sure to update the ToMap/FromMap methods when adding/updating fields.
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"github.com/docker/go-units"
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -970,7 +971,7 @@ func (svc *Service) EnqueueMDMAppleCommand(
|
||||
deviceIDs []string,
|
||||
noPush bool,
|
||||
) (status int, result *fleet.CommandEnqueueResult, err error) {
|
||||
var premiumCommands = map[string]bool{
|
||||
premiumCommands := map[string]bool{
|
||||
"EraseDevice": true,
|
||||
"DeviceLock": true,
|
||||
}
|
||||
@@ -1464,11 +1465,7 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
|
||||
return ctxerr.Wrap(ctx, fleet.NewInvalidArgumentError("team_name", "cannot specify both team_id and team_name"))
|
||||
}
|
||||
if tmID != nil || tmName != nil {
|
||||
license, err := svc.License(ctx)
|
||||
if err != nil {
|
||||
svc.authz.SkipAuthorization(ctx) // so that the error message is not replaced by "forbidden"
|
||||
return err
|
||||
}
|
||||
license, _ := license.FromContext(ctx)
|
||||
if !license.IsPremium() {
|
||||
field := "team_id"
|
||||
if tmName != nil {
|
||||
@@ -1498,7 +1495,7 @@ func (svc *Service) BatchSetMDMAppleProfiles(ctx context.Context, tmID *uint, tm
|
||||
return ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
appCfg, err := svc.AppConfigObfuscated(ctx)
|
||||
appCfg, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return ctxerr.Wrap(ctx, err)
|
||||
}
|
||||
|
||||
@@ -2592,7 +2592,7 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
||||
|
||||
//
|
||||
// Setup test data.
|
||||
// All actions are authored by a global admin.
|
||||
// All setup actions are authored by a global admin.
|
||||
//
|
||||
|
||||
admin, err := s.ds.UserByEmail(ctx, "admin1@example.com")
|
||||
|
||||
@@ -27,10 +27,6 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/micromdm/nanomdm/mdm"
|
||||
"github.com/micromdm/nanomdm/push"
|
||||
nanomdm_pushsvc "github.com/micromdm/nanomdm/push/service"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/config"
|
||||
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
@@ -51,6 +47,9 @@ import (
|
||||
"github.com/micromdm/nanodep/godep"
|
||||
nanodep_storage "github.com/micromdm/nanodep/storage"
|
||||
"github.com/micromdm/nanodep/tokenpki"
|
||||
"github.com/micromdm/nanomdm/mdm"
|
||||
"github.com/micromdm/nanomdm/push"
|
||||
nanomdm_pushsvc "github.com/micromdm/nanomdm/push/service"
|
||||
scepclient "github.com/micromdm/scep/v2/client"
|
||||
"github.com/micromdm/scep/v2/cryptoutil/x509util"
|
||||
"github.com/micromdm/scep/v2/scep"
|
||||
@@ -2521,7 +2520,6 @@ func (s *integrationMDMTestSuite) TestFleetdConfiguration() {
|
||||
|
||||
// the old configuration profile is kept
|
||||
s.assertConfigProfilesByIdentifier(nil, mobileconfig.FleetdConfigPayloadIdentifier, true)
|
||||
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestEnqueueMDMCommand() {
|
||||
@@ -2625,7 +2623,6 @@ func (s *integrationMDMTestSuite) TestEnqueueMDMCommand() {
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestBootstrapPackage() {
|
||||
//ctx := context.Background()
|
||||
t := s.T()
|
||||
|
||||
read := func(name string) []byte {
|
||||
@@ -2678,7 +2675,6 @@ func (s *integrationMDMTestSuite) TestBootstrapPackage() {
|
||||
s.DoJSON("GET", "/api/latest/fleet/mdm/apple/bootstrap/0/metadata", nil, http.StatusNotFound, &metadataResp)
|
||||
// trying to delete again is a bad request
|
||||
s.DoJSON("DELETE", "/api/latest/fleet/mdm/apple/bootstrap/0", nil, http.StatusNotFound, &deleteResp)
|
||||
|
||||
}
|
||||
|
||||
// only asserts the profile identifier, status and operation (per host)
|
||||
@@ -3029,3 +3025,140 @@ var testBMToken = &nanodep_client.OAuth1Tokens{
|
||||
AccessSecret: "test_access_secret",
|
||||
AccessTokenExpiry: time.Date(2999, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
}
|
||||
|
||||
// TestGitOpsUserActions tests the MDM permissions listed in ../../docs/Using-Fleet/Permissions.md.
|
||||
func (s *integrationMDMTestSuite) TestGitOpsUserActions() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
//
|
||||
// Setup test data.
|
||||
// All setup actions are authored by a global admin.
|
||||
//
|
||||
|
||||
t1, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "Foo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t2, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "Bar",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t3, err := s.ds.NewTeam(ctx, &fleet.Team{
|
||||
Name: "Zoo",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create the global GitOps user we'll use in tests.
|
||||
u := &fleet.User{
|
||||
Name: "GitOps",
|
||||
Email: "gitops1-mdm@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleGitOps),
|
||||
}
|
||||
require.NoError(t, u.SetPassword(test.GoodPassword, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u)
|
||||
require.NoError(t, err)
|
||||
// Create a GitOps user for team t1 we'll use in tests.
|
||||
u2 := &fleet.User{
|
||||
Name: "GitOps 2",
|
||||
Email: "gitops2-mdm@example.com",
|
||||
GlobalRole: nil,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: *t1,
|
||||
Role: fleet.RoleGitOps,
|
||||
},
|
||||
{
|
||||
Team: *t3,
|
||||
Role: fleet.RoleGitOps,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, u2.SetPassword(test.GoodPassword, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u2)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// Start running permission tests with user gitops1-mdm.
|
||||
//
|
||||
s.setTokenForTest(t, "gitops1-mdm@example.com", test.GoodPassword)
|
||||
|
||||
// Attempt to edit global MDM settings, should allow.
|
||||
acResp := appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "macos_settings": { "enable_disk_encryption": true } }
|
||||
}`), http.StatusOK, &acResp)
|
||||
assert.True(t, acResp.MDM.MacOSSettings.EnableDiskEncryption)
|
||||
|
||||
// Attempt to set profile batch globally, should allow.
|
||||
globalProfiles := [][]byte{
|
||||
mobileconfigForTest("N1", "I1"),
|
||||
mobileconfigForTest("N2", "I2"),
|
||||
}
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{Profiles: globalProfiles}, http.StatusNoContent)
|
||||
|
||||
// Attempt to edit team MDM settings, should allow.
|
||||
teamSpecs := applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
|
||||
Name: t1.Name,
|
||||
MDM: fleet.TeamSpecMDM{
|
||||
MacOSSettings: map[string]interface{}{
|
||||
"enable_disk_encryption": true,
|
||||
"custom_settings": []interface{}{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
|
||||
|
||||
// Attempt to set profile batch for team t1, should allow.
|
||||
teamProfiles := [][]byte{
|
||||
mobileconfigForTest("N3", "I3"),
|
||||
mobileconfigForTest("N4", "I4"),
|
||||
}
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{
|
||||
Profiles: teamProfiles,
|
||||
}, http.StatusNoContent, "team_id", strconv.Itoa(int(t1.ID)))
|
||||
|
||||
//
|
||||
// Start running permission tests with user gitops2-mdm,
|
||||
// which is GitOps for teams t1 and t3.
|
||||
//
|
||||
s.setTokenForTest(t, "gitops2-mdm@example.com", test.GoodPassword)
|
||||
|
||||
// Attempt to edit team t1 MDM settings, should allow.
|
||||
teamSpecs = applyTeamSpecsRequest{Specs: []*fleet.TeamSpec{{
|
||||
Name: t1.Name,
|
||||
MDM: fleet.TeamSpecMDM{
|
||||
MacOSSettings: map[string]interface{}{
|
||||
"enable_disk_encryption": true,
|
||||
"custom_settings": []interface{}{"foo", "bar"},
|
||||
},
|
||||
},
|
||||
}}}
|
||||
s.Do("POST", "/api/latest/fleet/spec/teams", teamSpecs, http.StatusOK)
|
||||
|
||||
// Attempt to set profile batch for team t1, should allow.
|
||||
teamProfiles = [][]byte{
|
||||
mobileconfigForTest("N5", "I5"),
|
||||
mobileconfigForTest("N6", "I6"),
|
||||
}
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{
|
||||
Profiles: teamProfiles,
|
||||
}, http.StatusNoContent, "team_id", strconv.Itoa(int(t1.ID)))
|
||||
|
||||
// Attempt to set profile batch for team t2, should not allow.
|
||||
teamProfiles = [][]byte{
|
||||
mobileconfigForTest("N7", "I7"),
|
||||
mobileconfigForTest("N8", "I8"),
|
||||
}
|
||||
s.Do("POST", "/api/v1/fleet/mdm/apple/profiles/batch", batchSetMDMAppleProfilesRequest{
|
||||
Profiles: teamProfiles,
|
||||
}, http.StatusForbidden, "team_id", strconv.Itoa(int(t2.ID)))
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) setTokenForTest(t *testing.T, email, password string) {
|
||||
oldToken := s.token
|
||||
t.Cleanup(func() {
|
||||
s.token = oldToken
|
||||
})
|
||||
|
||||
s.token = s.getCachedUserToken(email, password)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,6 @@ func (ts *withServer) SetupSuite(dbName string) {
|
||||
})
|
||||
ts.server = server
|
||||
ts.users = users
|
||||
ts.cachedTokens = make(map[string]string)
|
||||
ts.token = ts.getTestAdminToken()
|
||||
ts.cachedAdminToken = ts.token
|
||||
}
|
||||
@@ -227,6 +226,10 @@ func (ts *withServer) getCachedUserToken(email, password string) string {
|
||||
ts.cachedTokensMu.Lock()
|
||||
defer ts.cachedTokensMu.Unlock()
|
||||
|
||||
if ts.cachedTokens == nil {
|
||||
ts.cachedTokens = make(map[string]string)
|
||||
}
|
||||
|
||||
token, ok := ts.cachedTokens[email]
|
||||
if !ok {
|
||||
token = ts.getTestToken(email, password)
|
||||
|
||||
Reference in New Issue
Block a user