mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
New gitops
role (#10850)
#8593 This PR adds a new role `gitops` to Fleet. MDM capabilities for the role coming on a separate PR. We need this merged ASAP so that we can unblock the UI work for this. - [X] 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. - [x] 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:
parent
3c177aa928
commit
1ebfbb14eb
1
changes/8593-gitops
Normal file
1
changes/8593-gitops
Normal file
@ -0,0 +1 @@
|
||||
* Add `gitops` user role to Fleet. GitOps users are users that can manage configuration.
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/service"
|
||||
kithttp "github.com/go-kit/kit/transport/http"
|
||||
"github.com/kolide/kit/version"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
@ -81,14 +82,18 @@ func clientFromCLI(c *cli.Context) (*service.Client, error) {
|
||||
}
|
||||
|
||||
// check that AppConfig's Apple BM terms are not expired.
|
||||
appCfg, err := fleetClient.GetAppConfig()
|
||||
if err != nil {
|
||||
var sce kithttp.StatusCoder
|
||||
switch appCfg, err := fleetClient.GetAppConfig(); {
|
||||
case err == nil:
|
||||
if appCfg.MDM.AppleBMTermsExpired {
|
||||
fleet.WriteAppleBMTermsExpiredBanner(os.Stderr)
|
||||
// This is just a warning, continue ...
|
||||
}
|
||||
case errors.As(err, &sce) && sce.StatusCode() == http.StatusForbidden:
|
||||
// OK, could be a user without permissions to read app config (e.g. gitops).
|
||||
default:
|
||||
return nil, err
|
||||
}
|
||||
if appCfg.MDM.AppleBMTermsExpired {
|
||||
fleet.WriteAppleBMTermsExpiredBanner(os.Stderr)
|
||||
// This is just a warning, continue ...
|
||||
}
|
||||
|
||||
return fleetClient, nil
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
@ -537,25 +538,8 @@ spec:
|
||||
assert.True(t, savedAppConfig.Features.EnableSoftwareInventory)
|
||||
}
|
||||
|
||||
func TestApplyPolicies(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
var appliedPolicySpecs []*fleet.PolicySpec
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, authorID uint, specs []*fleet.PolicySpec) error {
|
||||
appliedPolicySpecs = specs
|
||||
return nil
|
||||
}
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
if name == "Team1" {
|
||||
return &fleet.Team{ID: 123}, nil
|
||||
}
|
||||
return nil, errors.New("unexpected team name!")
|
||||
}
|
||||
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
const (
|
||||
policySpec = `---
|
||||
apiVersion: v1
|
||||
kind: policy
|
||||
spec:
|
||||
@ -583,7 +567,68 @@ spec:
|
||||
description: Checks to make sure that the Filevault feature is enabled on macOS devices.
|
||||
resolution: "Choose Apple menu > System Preferences, then click Security & Privacy. Click the FileVault tab. Click the Lock icon, then enter an administrator name and password. Click Turn On FileVault."
|
||||
platform: darwin
|
||||
`)
|
||||
`
|
||||
enrollSecretsSpec = `---
|
||||
apiVersion: v1
|
||||
kind: enroll_secret
|
||||
spec:
|
||||
secrets:
|
||||
- secret: RzTlxPvugG4o4O5IKS/HqEDJUmI1hwBoffff
|
||||
- secret: reallyworks
|
||||
- secret: thissecretwontwork!
|
||||
`
|
||||
labelsSpec = `---
|
||||
apiVersion: v1
|
||||
kind: label
|
||||
spec:
|
||||
name: pending_updates
|
||||
query: select 1;
|
||||
platforms:
|
||||
- darwin
|
||||
`
|
||||
packsSpec = `---
|
||||
apiVersion: v1
|
||||
kind: pack
|
||||
spec:
|
||||
name: osquery_monitoring
|
||||
queries:
|
||||
- query: osquery_version
|
||||
name: osquery_version_snapshot
|
||||
interval: 7200
|
||||
snapshot: true
|
||||
- query: osquery_version
|
||||
name: osquery_version_differential
|
||||
interval: 7200
|
||||
`
|
||||
queriesSpec = `---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
spec:
|
||||
description: Retrieves the list of application scheme/protocol-based IPC handlers.
|
||||
name: app_schemes
|
||||
query: select * from app_schemes;
|
||||
`
|
||||
)
|
||||
|
||||
func TestApplyPolicies(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
var appliedPolicySpecs []*fleet.PolicySpec
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, authorID uint, specs []*fleet.PolicySpec) error {
|
||||
appliedPolicySpecs = specs
|
||||
return nil
|
||||
}
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
if name == "Team1" {
|
||||
return &fleet.Team{ID: 123}, nil
|
||||
}
|
||||
return nil, errors.New("unexpected team name!")
|
||||
}
|
||||
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, policySpec)
|
||||
|
||||
assert.Equal(t, "[+] applied 3 policies\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyPolicySpecsFuncInvoked)
|
||||
@ -594,6 +639,201 @@ spec:
|
||||
assert.True(t, ds.TeamByNameFuncInvoked)
|
||||
}
|
||||
|
||||
func TestApplyAsGitOps(t *testing.T) {
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
_, ds := runServerWithMockedDS(t, &service.TestServerOpts{License: license})
|
||||
|
||||
gitOps := &fleet.User{
|
||||
Name: "GitOps",
|
||||
Password: []byte("p4ssw0rd.123"),
|
||||
Email: "gitops1@example.com",
|
||||
GlobalRole: ptr.String(fleet.RoleGitOps),
|
||||
}
|
||||
gitOps, err := ds.NewUser(context.Background(), gitOps)
|
||||
require.NoError(t, err)
|
||||
ds.SessionByKeyFunc = func(ctx context.Context, key string) (*fleet.Session, error) {
|
||||
return &fleet.Session{
|
||||
CreateTimestamp: fleet.CreateTimestamp{CreatedAt: time.Now()},
|
||||
ID: 1,
|
||||
AccessedAt: time.Now(),
|
||||
UserID: gitOps.ID,
|
||||
Key: key,
|
||||
}, nil
|
||||
}
|
||||
ds.UserByIDFunc = func(ctx context.Context, id uint) (*fleet.User, error) {
|
||||
return gitOps, nil
|
||||
}
|
||||
ds.NewActivityFunc = func(ctx context.Context, user *fleet.User, activity fleet.ActivityDetails) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Apply global config.
|
||||
currentAppConfig := &fleet.AppConfig{
|
||||
OrgInfo: fleet.OrgInfo{OrgName: "Fleet"}, ServerSettings: fleet.ServerSettings{ServerURL: "https://example.org"},
|
||||
}
|
||||
ds.AppConfigFunc = func(ctx context.Context) (*fleet.AppConfig, error) {
|
||||
return currentAppConfig, nil
|
||||
}
|
||||
|
||||
ds.SaveAppConfigFunc = func(ctx context.Context, config *fleet.AppConfig) error {
|
||||
currentAppConfig = config
|
||||
return nil
|
||||
}
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: config
|
||||
spec:
|
||||
features:
|
||||
enable_host_users: true
|
||||
enable_software_inventory: true
|
||||
agent_options:
|
||||
config:
|
||||
decorators:
|
||||
load:
|
||||
- SELECT uuid AS host_uuid FROM system_info;
|
||||
- SELECT hostname AS hostname FROM system_info;
|
||||
options:
|
||||
disable_distributed: false
|
||||
distributed_interval: 10
|
||||
distributed_plugin: tls
|
||||
distributed_tls_max_attempts: 3
|
||||
logger_tls_endpoint: /api/osquery/log
|
||||
logger_tls_period: 10
|
||||
pack_delimiter: /
|
||||
overrides: {}
|
||||
`)
|
||||
assert.Equal(t, "[+] applied fleet config\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, currentAppConfig.Features.EnableHostUsers)
|
||||
|
||||
// Apply team config.
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
if name == "Team1" {
|
||||
return &fleet.Team{ID: 123}, nil
|
||||
}
|
||||
return nil, errors.New("unexpected team name!")
|
||||
}
|
||||
var savedTeam *fleet.Team
|
||||
ds.SaveTeamFunc = func(ctx context.Context, team *fleet.Team) (*fleet.Team, error) {
|
||||
savedTeam = team
|
||||
return team, nil
|
||||
}
|
||||
var teamEnrollSecrets []*fleet.EnrollSecret
|
||||
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
|
||||
if teamID == nil || *teamID != 123 {
|
||||
return fmt.Errorf("unexpected data: %+v", teamID)
|
||||
}
|
||||
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, `
|
||||
apiVersion: v1
|
||||
kind: team
|
||||
spec:
|
||||
team:
|
||||
agent_options:
|
||||
config:
|
||||
views:
|
||||
foo: qux
|
||||
name: Team1
|
||||
secrets:
|
||||
- secret: BBB
|
||||
`)
|
||||
|
||||
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.EnrollSecret{{Secret: "BBB"}}, teamEnrollSecrets)
|
||||
assert.True(t, ds.ApplyEnrollSecretsFuncInvoked)
|
||||
|
||||
// Apply policies.
|
||||
var appliedPolicySpecs []*fleet.PolicySpec
|
||||
ds.ApplyPolicySpecsFunc = func(ctx context.Context, authorID uint, specs []*fleet.PolicySpec) error {
|
||||
appliedPolicySpecs = specs
|
||||
return nil
|
||||
}
|
||||
name = writeTmpYml(t, policySpec)
|
||||
assert.Equal(t, "[+] applied 3 policies\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyPolicySpecsFuncInvoked)
|
||||
assert.Len(t, appliedPolicySpecs, 3)
|
||||
for _, p := range appliedPolicySpecs {
|
||||
assert.NotEmpty(t, p.Platform)
|
||||
}
|
||||
assert.True(t, ds.TeamByNameFuncInvoked)
|
||||
|
||||
// Apply enroll secrets.
|
||||
var appliedSecrets []*fleet.EnrollSecret
|
||||
ds.ApplyEnrollSecretsFunc = func(ctx context.Context, teamID *uint, secrets []*fleet.EnrollSecret) error {
|
||||
appliedSecrets = secrets
|
||||
return nil
|
||||
}
|
||||
name = writeTmpYml(t, enrollSecretsSpec)
|
||||
assert.Equal(t, "[+] applied enroll secrets\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyEnrollSecretsFuncInvoked)
|
||||
assert.Len(t, appliedSecrets, 3)
|
||||
for _, s := range appliedSecrets {
|
||||
assert.NotEmpty(t, s.Secret)
|
||||
}
|
||||
|
||||
// Apply labels.
|
||||
var appliedLabels []*fleet.LabelSpec
|
||||
ds.ApplyLabelSpecsFunc = func(ctx context.Context, specs []*fleet.LabelSpec) error {
|
||||
appliedLabels = specs
|
||||
return nil
|
||||
}
|
||||
name = writeTmpYml(t, labelsSpec)
|
||||
assert.Equal(t, "[+] applied 1 labels\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyLabelSpecsFuncInvoked)
|
||||
require.Len(t, appliedLabels, 1)
|
||||
assert.Equal(t, "pending_updates", appliedLabels[0].Name)
|
||||
assert.Equal(t, "select 1;", appliedLabels[0].Query)
|
||||
|
||||
// Apply packs.
|
||||
var appliedPacks []*fleet.PackSpec
|
||||
ds.ApplyPackSpecsFunc = func(ctx context.Context, specs []*fleet.PackSpec) error {
|
||||
appliedPacks = specs
|
||||
return nil
|
||||
}
|
||||
ds.ListPacksFunc = func(ctx context.Context, opt fleet.PackListOptions) ([]*fleet.Pack, error) {
|
||||
return nil, nil
|
||||
}
|
||||
name = writeTmpYml(t, packsSpec)
|
||||
assert.Equal(t, "[+] applied 1 packs\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyPackSpecsFuncInvoked)
|
||||
require.Len(t, appliedPacks, 1)
|
||||
assert.Equal(t, "osquery_monitoring", appliedPacks[0].Name)
|
||||
require.Len(t, appliedPacks[0].Queries, 2)
|
||||
|
||||
// Apply queries.
|
||||
var appliedQueries []*fleet.Query
|
||||
ds.QueryByNameFunc = func(ctx context.Context, name string, opts ...fleet.OptionalArg) (*fleet.Query, error) {
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
ds.ApplyQueriesFunc = func(ctx context.Context, authorID uint, queries []*fleet.Query) error {
|
||||
appliedQueries = queries
|
||||
return nil
|
||||
}
|
||||
name = writeTmpYml(t, queriesSpec)
|
||||
assert.Equal(t, "[+] applied 1 queries\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyQueriesFuncInvoked)
|
||||
require.Len(t, appliedQueries, 1)
|
||||
assert.Equal(t, "app_schemes", appliedQueries[0].Name)
|
||||
assert.Equal(t, "select * from app_schemes;", appliedQueries[0].Query)
|
||||
}
|
||||
|
||||
func TestApplyEnrollSecrets(t *testing.T) {
|
||||
_, ds := runServerWithMockedDS(t)
|
||||
|
||||
@ -603,15 +843,7 @@ func TestApplyEnrollSecrets(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: enroll_secret
|
||||
spec:
|
||||
secrets:
|
||||
- secret: RzTlxPvugG4o4O5IKS/HqEDJUmI1hwBoffff
|
||||
- secret: reallyworks
|
||||
- secret: thissecretwontwork!
|
||||
`)
|
||||
name := writeTmpYml(t, enrollSecretsSpec)
|
||||
|
||||
assert.Equal(t, "[+] applied enroll secrets\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyEnrollSecretsFuncInvoked)
|
||||
@ -630,15 +862,7 @@ func TestApplyLabels(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: label
|
||||
spec:
|
||||
name: pending_updates
|
||||
query: select 1;
|
||||
platforms:
|
||||
- darwin
|
||||
`)
|
||||
name := writeTmpYml(t, labelsSpec)
|
||||
|
||||
assert.Equal(t, "[+] applied 1 labels\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyLabelSpecsFuncInvoked)
|
||||
@ -663,20 +887,7 @@ func TestApplyPacks(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: pack
|
||||
spec:
|
||||
name: osquery_monitoring
|
||||
queries:
|
||||
- query: osquery_version
|
||||
name: osquery_version_snapshot
|
||||
interval: 7200
|
||||
snapshot: true
|
||||
- query: osquery_version
|
||||
name: osquery_version_differential
|
||||
interval: 7200
|
||||
`)
|
||||
name := writeTmpYml(t, packsSpec)
|
||||
|
||||
assert.Equal(t, "[+] applied 1 packs\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyPackSpecsFuncInvoked)
|
||||
@ -720,14 +931,7 @@ func TestApplyQueries(t *testing.T) {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := writeTmpYml(t, `---
|
||||
apiVersion: v1
|
||||
kind: query
|
||||
spec:
|
||||
description: Retrieves the list of application scheme/protocol-based IPC handlers.
|
||||
name: app_schemes
|
||||
query: select * from app_schemes;
|
||||
`)
|
||||
name := writeTmpYml(t, queriesSpec)
|
||||
|
||||
assert.Equal(t, "[+] applied 1 queries\n", runAppForTest(t, []string{"apply", "-f", name}))
|
||||
assert.True(t, ds.ApplyQueriesFuncInvoked)
|
||||
|
@ -2,53 +2,76 @@
|
||||
|
||||
Users have different abilities depending on the access level they have.
|
||||
|
||||
Users with the Admin role receive all permissions.
|
||||
## Roles
|
||||
|
||||
### Admin
|
||||
|
||||
Users with the admin role receive all permissions.
|
||||
|
||||
### Maintainer
|
||||
|
||||
Maintainers can manage most entities in Fleet, like queries, policies, labels and schedules.
|
||||
Unlike admins, maintainers cannot edit higher level settings like application configuration, teams or users.
|
||||
|
||||
### Observer
|
||||
|
||||
The Observer role is a read-only role. It can access most entities in Fleet, like queries, policies, labels, schedules, application configuration, teams, etc.
|
||||
They can also run queries configured with the `observer_can_run` flag set to `true`.
|
||||
|
||||
### Observer+
|
||||
|
||||
Observer+ is an Observer with the added ability to run *any* query.
|
||||
|
||||
### GitOps
|
||||
|
||||
GitOps is a modern approach to Continuous Deployment (CD) that uses Git as the single source of truth for declarative infrastructure and application configurations.
|
||||
GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
|
||||
|
||||
## User permissions
|
||||
|
||||
| **Action** | Observer | Observer+ | Maintainer | Admin |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------- | ---------- | ----- |
|
||||
| View all [activity](https://fleetdm.com/docs/using-fleet/rest-api#activities) | ✅ | ✅ | ✅ | ✅ |
|
||||
| View all hosts | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts using [labels](https://fleetdm.com/docs/using-fleet/rest-api#labels) | ✅ | ✅ | ✅ | ✅ |
|
||||
| Target hosts using labels | ✅ | ✅ | ✅ | ✅ |
|
||||
| Add and delete hosts | | | ✅ | ✅ |
|
||||
| Transfer hosts between teams\* | | | ✅ | ✅ |
|
||||
| Create, edit, and delete labels | | | ✅ | ✅ |
|
||||
| View all software | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter software by [vulnerabilities](https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing) | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts by software | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter software by team\* | ✅ | ✅ | ✅ | ✅ |
|
||||
| Manage [vulnerability automations](https://fleetdm.com/docs/using-fleet/automations#vulnerability-automations) | | | | ✅ |
|
||||
| Run only designated, **observer can run** ,queries as live queries against all hosts | ✅ | ✅ | ✅ | ✅ |
|
||||
| Run any query as [live query](https://fleetdm.com/docs/using-fleet/fleet-ui#run-a-query) against all hosts | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete queries | | | ✅ | ✅ |
|
||||
| View all queries | ✅ | ✅ | ✅ | ✅ |
|
||||
| Add, edit, and remove queries from all schedules | | | ✅ | ✅ |
|
||||
| Create, edit, view, and delete packs | | | ✅ | ✅ |
|
||||
| View all policies | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts using policies | ✅ | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete policies for all hosts | | | ✅ | ✅ |
|
||||
| Create, edit, and delete policies for all hosts assigned to team\* | | | ✅ | ✅ |
|
||||
| Manage [policy automations](https://fleetdm.com/docs/using-fleet/automations#policy-automations) | | | | ✅ |
|
||||
| Create, edit, view, and delete users | | | | ✅ |
|
||||
| Add and remove team members\* | | | | ✅ |
|
||||
| Create, edit, and delete teams\* | | | | ✅ |
|
||||
| Create, edit, and delete [enroll secrets](https://fleetdm.com/docs/deploying/faq#when-do-i-need-to-deploy-a-new-enroll-secret-to-my-hosts) | | | ✅ | ✅ |
|
||||
| Create, edit, and delete [enroll secrets for teams](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team)\* | | | ✅ | ✅ |
|
||||
| Read organization settings and agent options\** | ✅ | ✅ | ✅ | ✅ |
|
||||
| Edit [organization settings](https://fleetdm.com/docs/using-fleet/configuration-files#organization-settings) | | | | ✅ |
|
||||
| Edit [agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) | | | | ✅ |
|
||||
| Edit [agent options for hosts assigned to teams](https://fleetdm.com/docs/using-fleet/configuration-files#team-agent-options)\* | | | | ✅ |
|
||||
| Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ |
|
||||
| Retrieve contents from file carving | | | | ✅ |
|
||||
| View Apple mobile device management (MDM) certificate information | | | | ✅ |
|
||||
| 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 | | | ✅ | ✅ |
|
||||
| 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 | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Action** | Observer | Observer+ | Maintainer | Admin | GitOps |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------- | ---------- | ----- | ------ |
|
||||
| View all [activity](https://fleetdm.com/docs/using-fleet/rest-api#activities) | ✅ | ✅ | ✅ | ✅ | |
|
||||
| View all hosts | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts using [labels](https://fleetdm.com/docs/using-fleet/rest-api#labels) | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Target hosts using labels | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Add and delete hosts | | | ✅ | ✅ | |
|
||||
| Transfer hosts between teams\* | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete labels | | | ✅ | ✅ | ✅ |
|
||||
| View all software | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter software by [vulnerabilities](https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing) | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts by software | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter software by team\* | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Manage [vulnerability automations](https://fleetdm.com/docs/using-fleet/automations#vulnerability-automations) | | | | ✅ | ✅ |
|
||||
| Run only designated, **observer can run** ,queries as live queries against all hosts | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Run any query as [live query](https://fleetdm.com/docs/using-fleet/fleet-ui#run-a-query) against all hosts | | ✅ | ✅ | ✅ | |
|
||||
| Create, edit, and delete queries | | | ✅ | ✅ | ✅ |
|
||||
| View all queries | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Add, edit, and remove queries from all schedules | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, view, and delete packs | | | ✅ | ✅ | ✅ |
|
||||
| View all policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts using policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Create, edit, and delete policies for all hosts | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete policies for all hosts assigned to team\* | | | ✅ | ✅ | ✅ |
|
||||
| Manage [policy automations](https://fleetdm.com/docs/using-fleet/automations#policy-automations) | | | | ✅ | ✅ |
|
||||
| Create, edit, view, and delete users | | | | ✅ | |
|
||||
| Add and remove team members\* | | | | ✅ | ✅ |
|
||||
| Create, edit, and delete teams\* | | | | ✅ | ✅ |
|
||||
| Create, edit, and delete [enroll secrets](https://fleetdm.com/docs/deploying/faq#when-do-i-need-to-deploy-a-new-enroll-secret-to-my-hosts) | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete [enroll secrets for teams](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team)\* | | | ✅ | ✅ | |
|
||||
| Read organization settings and agent options\** | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Edit [organization settings](https://fleetdm.com/docs/using-fleet/configuration-files#organization-settings) | | | | ✅ | ✅ |
|
||||
| Edit [agent options](https://fleetdm.com/docs/using-fleet/configuration-files#agent-options) | | | | ✅ | ✅ |
|
||||
| Edit [agent options for hosts assigned to teams](https://fleetdm.com/docs/using-fleet/configuration-files#team-agent-options)\* | | | | ✅ | ✅ |
|
||||
| Initiate [file carving](https://fleetdm.com/docs/using-fleet/rest-api#file-carving) | | | ✅ | ✅ | |
|
||||
| Retrieve contents from file carving | | | | ✅ | |
|
||||
| View Apple mobile device management (MDM) certificate information | | | | ✅ | |
|
||||
| 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 | | | ✅ | ✅ | |
|
||||
| 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 | ✅ | ✅ | ✅ | ✅ | |
|
||||
|
||||
\*Applies only to Fleet Premium
|
||||
|
||||
@ -71,34 +94,35 @@ Users can be a member of multiple teams in Fleet.
|
||||
|
||||
Users that are members of multiple teams can be assigned different roles for each team. For example, a user can be given access to the "Workstations" team and assigned the "Observer" role. This same user can be given access to the "Servers" team and assigned the "Maintainer" role.
|
||||
|
||||
| **Action** | Team observer | Team observer+ | Team maintainer | Team admin |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------- | --------------- | ---------- |
|
||||
| View hosts | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts using [labels](https://fleetdm.com/docs/using-fleet/rest-api#labels) | ✅ | ✅ | ✅ | ✅ |
|
||||
| Target hosts using labels | ✅ | ✅ | ✅ | ✅ |
|
||||
| Add and delete hosts | | | ✅ | ✅ |
|
||||
| Filter software by [vulnerabilities](<(https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing)>) | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts by software | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter software | ✅ | ✅ | ✅ | ✅ |
|
||||
| Run only designated, **observer can run** ,queries as live queries against all hosts | ✅ | ✅ | ✅ | ✅ |
|
||||
| Run any query as [live query](https://fleetdm.com/docs/using-fleet/fleet-ui#run-a-query) | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete only **self authored** queries | | | ✅ | ✅ |
|
||||
| Add, edit, and remove queries from the schedule | | | ✅ | ✅ |
|
||||
| View policies | ✅ | ✅ | ✅ | ✅ |
|
||||
| View global (inherited) policies | ✅ | ✅ | ✅ | ✅ |
|
||||
| Filter hosts using policies | ✅ | ✅ | ✅ | ✅ |
|
||||
| Create, edit, and delete policies | | | ✅ | ✅ |
|
||||
| Manage [policy automations](https://fleetdm.com/docs/using-fleet/automations#policy-automations) | | | | ✅ |
|
||||
| Add and remove team members | | | | ✅ |
|
||||
| Edit team name | | | | ✅ |
|
||||
| Create, edit, and delete [team enroll secrets](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team) | | | ✅ | ✅ |
|
||||
| Read agent options\* | ✅ | ✅ | ✅ | ✅ |
|
||||
| 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 | | | ✅ | ✅ |
|
||||
| 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 | ✅ | ✅ | ✅ | ✅ |
|
||||
| **Action** | Team observer | Team observer+ | Team maintainer | Team admin | Team GitOps |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------- | --------------- | ---------- | ----------- |
|
||||
| View hosts | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts using [labels](https://fleetdm.com/docs/using-fleet/rest-api#labels) | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Target hosts using labels | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Add and delete hosts | | | ✅ | ✅ | |
|
||||
| Filter software by [vulnerabilities](<(https://fleetdm.com/docs/using-fleet/vulnerability-processing#vulnerability-processing)>) | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts by software | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter software | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Run only designated, **observer can run** ,queries as live queries against all hosts | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Run any query as [live query](https://fleetdm.com/docs/using-fleet/fleet-ui#run-a-query) | | ✅ | ✅ | ✅ | |
|
||||
| Create, edit, and delete only **self authored** queries | | | ✅ | ✅ | ✅ |
|
||||
| Add, edit, and remove queries from the schedule | | | ✅ | ✅ | ✅ |
|
||||
| View policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| View global (inherited) policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts using policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Create, edit, and delete policies | | | ✅ | ✅ | ✅ |
|
||||
| Manage [policy automations](https://fleetdm.com/docs/using-fleet/automations#policy-automations) | | | | ✅ | ✅ |
|
||||
| Add and remove team members | | | | ✅ | ✅ |
|
||||
| Edit team name | | | | ✅ | ✅ |
|
||||
| Create, edit, and delete [team enroll secrets](https://fleetdm.com/docs/using-fleet/rest-api#get-enroll-secrets-for-a-team) | | | ✅ | ✅ | |
|
||||
| Read agent options\* | ✅ | ✅ | ✅ | ✅ | |
|
||||
| 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 | | | ✅ | ✅ | |
|
||||
| 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 | ✅ | ✅ | ✅ | ✅ | |
|
||||
|
||||
\* Applies only to [Fleet REST API](https://fleetdm.com/docs/using-fleet/rest-api)
|
||||
|
||||
|
@ -6431,8 +6431,8 @@ Creates a user account after an invited user provides registration information a
|
||||
| name | string | body | **Required**. The name of the user. |
|
||||
| password | string | body | The password chosen by the user (if not SSO user). |
|
||||
| password_confirmation | string | body | Confirmation of the password chosen by the user. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0, the `observer_plus` role was introduced. If `global_role` is specified, `teams` cannot be specified. |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0, the `observer_plus` role was introduced. If `teams` is specified, `global_role` cannot be specified. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `global_role` is specified, `teams` cannot be specified. For more information, see [Permissions](Permissions.md). |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [Permissions](Permissions.md). |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -6548,9 +6548,9 @@ By default, the user will be forced to reset its password upon first login.
|
||||
| password | string | body | The user's password (required for non-SSO users). |
|
||||
| sso_enabled | boolean | body | Whether or not SSO is enabled for the user. |
|
||||
| api_only | boolean | body | User is an "API-only" user (cannot use web UI) if true. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0, the `observer_plus` role was introduced. If `global_role` is specified, `teams` cannot be specified. |
|
||||
| global_role | string | body | The role assigned to the user. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `global_role` is specified, `teams` cannot be specified. For more information, see [Permissions](Permissions.md). |
|
||||
| admin_forced_password_reset | boolean | body | Sets whether the user will be forced to reset its password upon first login (default=true) |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0, the `observer_plus` role was introduced. If `teams` is specified, `global_role` cannot be specified. |
|
||||
| teams | array | body | _Available in Fleet Premium_ The teams and respective roles assigned to the user. Should contain an array of objects in which each object includes the team's `id` and the user's `role` on each team. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). In Fleet 4.30.0 and 4.31.0, the `observer_plus` and `gitops` roles were introduced respectively. If `teams` is specified, `global_role` cannot be specified. For more information, see [Permissions](Permissions.md). |
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server"
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/logging"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
@ -499,23 +500,30 @@ func (svc *Service) teamByIDOrName(ctx context.Context, id *uint, name *string)
|
||||
|
||||
var jsonNull = json.RawMessage(`null`)
|
||||
|
||||
func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec, applyOpts fleet.ApplySpecOptions) error {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
// setAuthCheckedOnPreAuthErr can be used to set the authentication as checked
|
||||
// in case of errors that happened before an auth check can be performed.
|
||||
// Otherwise the endpoints return a "authentication skipped" error instead of
|
||||
// the actual returned error.
|
||||
func setAuthCheckedOnPreAuthErr(ctx context.Context) {
|
||||
if az, ok := authz_ctx.FromContext(ctx); ok {
|
||||
az.SetChecked()
|
||||
}
|
||||
}
|
||||
|
||||
// check auth for all teams specified first
|
||||
func (svc *Service) checkAuthorizationForTeams(ctx context.Context, specs []*fleet.TeamSpec) error {
|
||||
for _, spec := range specs {
|
||||
team, err := svc.ds.TeamByName(ctx, spec.Name)
|
||||
if err != nil {
|
||||
if err := ctxerr.Cause(err); err == sql.ErrNoRows {
|
||||
// can the user create a new team?
|
||||
// Can the user create a new team?
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Team{}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Set authorization as checked to return a proper error.
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
@ -524,11 +532,25 @@ func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec,
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
appConfig, err := svc.AppConfigObfuscated(ctx)
|
||||
func (svc *Service) ApplyTeamSpecs(ctx context.Context, specs []*fleet.TeamSpec, applyOpts fleet.ApplySpecOptions) error {
|
||||
if len(specs) == 0 {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
// Nothing to do.
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := svc.checkAuthorizationForTeams(ctx, specs); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
appConfig, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
appConfig.Obfuscate()
|
||||
|
||||
var details []fleet.TeamActivityDetail
|
||||
|
||||
|
@ -31,6 +31,7 @@ admin := "admin"
|
||||
maintainer := "maintainer"
|
||||
observer := "observer"
|
||||
observer_plus := "observer_plus"
|
||||
gitops := "gitops"
|
||||
|
||||
# Default deny
|
||||
default allow = false
|
||||
@ -47,17 +48,25 @@ team_role(subject, team_id) = role {
|
||||
# Global config
|
||||
##
|
||||
|
||||
# Any logged in user can read global config
|
||||
# Global admin, maintainer, observer_plus and observer can read global config.
|
||||
allow {
|
||||
object.type == "app_config"
|
||||
not is_null(subject)
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Admin can write global config
|
||||
# Team admin, maintainer, observer_plus and observer can read global config.
|
||||
allow {
|
||||
object.type == "app_config"
|
||||
subject.global_role == admin
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Global admins and gitops can write global config.
|
||||
allow {
|
||||
object.type == "app_config"
|
||||
subject.global_role == [admin, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -73,14 +82,8 @@ allow {
|
||||
not is_null(subject)
|
||||
action == read
|
||||
}
|
||||
# For specific teams, only members can read.
|
||||
allow {
|
||||
object.type == "team"
|
||||
object.id != 0
|
||||
team_role(subject, object.id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
# Global users can read all teams.
|
||||
|
||||
# Global admins, maintainers, observer_plus and observers can read teams.
|
||||
allow {
|
||||
object.type == "team"
|
||||
object.id != 0
|
||||
@ -88,17 +91,25 @@ allow {
|
||||
action == read
|
||||
}
|
||||
|
||||
# Admin can write teams
|
||||
# Team admins, maintainers, observer_plus and observers can read their team.
|
||||
allow {
|
||||
object.type == "team"
|
||||
subject.global_role == admin
|
||||
object.id != 0
|
||||
team_role(subject, object.id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Global admins and gitops can write teams.
|
||||
allow {
|
||||
object.type == "team"
|
||||
subject.global_role == [admin, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admin can write teams
|
||||
# Team admins and gitops can write their teams.
|
||||
allow {
|
||||
object.type == "team"
|
||||
team_role(subject, object.id) == admin
|
||||
team_role(subject, object.id) == [admin, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -146,10 +157,10 @@ allow {
|
||||
# Activities
|
||||
##
|
||||
|
||||
# Only global users can read activities
|
||||
# Global admins, maintainers, observer_plus and observers can read activities.
|
||||
allow {
|
||||
not is_null(subject.global_role)
|
||||
object.type == "activity"
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -175,14 +186,22 @@ allow {
|
||||
# Enroll Secrets
|
||||
##
|
||||
|
||||
# Global admins and maintainers can read/write all
|
||||
# Global admins and maintainers can read/write enroll secrets.
|
||||
allow {
|
||||
object.type == "enroll_secret"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Team admins and maintainers can read/write for appropriate teams
|
||||
# Global gitops can write global enroll secrets.
|
||||
allow {
|
||||
object.type == "enroll_secret"
|
||||
object.is_global_secret
|
||||
subject.global_role == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admins and maintainers can read/write for appropriate teams.
|
||||
allow {
|
||||
object.type == "enroll_secret"
|
||||
team_role(subject, object.team_id) == [admin, maintainer][_]
|
||||
@ -195,23 +214,33 @@ allow {
|
||||
# Hosts
|
||||
##
|
||||
|
||||
# Allow anyone to list (must be filtered appropriately by the service).
|
||||
# Global admins, maintainers, observer_plus and observers can list hosts.
|
||||
allow {
|
||||
object.type == "host"
|
||||
not is_null(subject)
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
action == list
|
||||
}
|
||||
|
||||
# Allow read/write for global admin/maintainer
|
||||
# Team admins, maintainers, observer_plus and observers can list hosts.
|
||||
allow {
|
||||
object.type == "host"
|
||||
subject.global_role = admin
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
action == list
|
||||
}
|
||||
|
||||
# Allow read/write for global admin/maintainer.
|
||||
allow {
|
||||
object.type == "host"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global gitops can write hosts.
|
||||
allow {
|
||||
object.type == "host"
|
||||
subject.global_role = maintainer
|
||||
action == [read, write][_]
|
||||
subject.global_role == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Allow read for global observer and observer_plus.
|
||||
@ -239,22 +268,25 @@ allow {
|
||||
# Labels
|
||||
##
|
||||
|
||||
# All users can read labels
|
||||
# Global admins, maintainers, observer_plus and observers can read labels.
|
||||
allow {
|
||||
object.type == "label"
|
||||
not is_null(subject)
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Only global admins and maintainers can write labels
|
||||
# Team admins, maintainers, observer_plus and observers can read labels.
|
||||
allow {
|
||||
object.type == "label"
|
||||
subject.global_role == admin
|
||||
action == write
|
||||
object.type == "label"
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Only global admins, maintainers and gitops can write labels
|
||||
allow {
|
||||
object.type == "label"
|
||||
subject.global_role == maintainer
|
||||
subject.global_role == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -262,33 +294,41 @@ allow {
|
||||
# Queries
|
||||
##
|
||||
|
||||
# All users can read queries
|
||||
# Global admins, maintainers, observer_plus and observers can read queries.
|
||||
allow {
|
||||
not is_null(subject)
|
||||
object.type == "query"
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Global admins and maintainers can write queries
|
||||
# Team admins, maintainers, observer_plus and observers can read queries.
|
||||
allow {
|
||||
object.type == "query"
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Global admins, maintainers and gitops can write queries.
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
subject.global_role == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admins and maintainers can create new queries
|
||||
# Team admins, maintainers and gitops can create new queries
|
||||
allow {
|
||||
object.id == 0 # new queries have ID zero
|
||||
object.type == "query"
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer][_]
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admins and maintainers can edit and delete only their own queries
|
||||
# Team admins, maintainers and gitops can edit and delete only their own queries
|
||||
allow {
|
||||
object.author_id == subject.id
|
||||
object.type == "query"
|
||||
team_role(subject, subject.teams[_].id) == [admin,maintainer][_]
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -390,22 +430,31 @@ allow {
|
||||
# Packs
|
||||
##
|
||||
|
||||
# Global admins and maintainers can read/write all packs.
|
||||
# Global admins, maintainers and gitops can read/write all types of packs.
|
||||
allow {
|
||||
object.type == "pack"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
subject.global_role == [admin, maintainer, gitops][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# All users can read the global pack.
|
||||
# Global admins, maintainers, observers and observer_plus can read the global pack.
|
||||
allow {
|
||||
object.type == "pack"
|
||||
not is_null(subject)
|
||||
object.is_global_pack == true
|
||||
subject.global_role == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins, maintainers, observers and observer_plus can read their team's pack.
|
||||
# Team admins, maintainers, observer_plus and observers can read the global pack.
|
||||
allow {
|
||||
object.type == "pack"
|
||||
object.is_global_pack == true
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins, maintainers, observers, observer_plus can read their team's pack.
|
||||
#
|
||||
# NOTE: Action "read" on a team's pack includes listing its scheduled queries.
|
||||
allow {
|
||||
@ -415,7 +464,7 @@ allow {
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins and maintainers can add/remove scheduled queries from/to their team's pack.
|
||||
# Team admins, maintainers and gitops can add/remove scheduled queries from/to their team's pack.
|
||||
#
|
||||
# NOTE: The team's pack is not editable per-se, it's a special pack to group
|
||||
# all the team's scheduled queries. So the "write" operation only covers
|
||||
@ -423,7 +472,7 @@ allow {
|
||||
allow {
|
||||
object.type == "pack"
|
||||
not is_null(object.pack_team_id)
|
||||
team_role(subject, object.pack_team_id) == [admin, maintainer][_]
|
||||
team_role(subject, object.pack_team_id) == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -442,29 +491,44 @@ allow {
|
||||
# Policies
|
||||
##
|
||||
|
||||
# Global Admin and Maintainer can read and write policies
|
||||
# Global admins and maintainers can read and write policies.
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == [admin,maintainer][_]
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global observer and observer_plus can read any policies
|
||||
# Global gitops can write policies.
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Global observer and observer_plus can read any policies.
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == [observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admin and maintainers can read and write policies for their teams
|
||||
# Team admin and maintainers can read and write policies for their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
team_role(subject, object.team_id) == [admin,maintainer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Team admin, maintainers, observers and observer_plus can read global policies.
|
||||
# Team gitops can write policies for their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
team_role(subject, object.team_id) == gitops
|
||||
action == write
|
||||
}
|
||||
|
||||
# Team admin, maintainers, observers and observers_plus can read global policies
|
||||
allow {
|
||||
is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
@ -484,14 +548,14 @@ allow {
|
||||
# Software
|
||||
##
|
||||
|
||||
# Global users can read all software.
|
||||
# Global admins, maintainers, observers and observer_plus can read all software.
|
||||
allow {
|
||||
object.type == "software_inventory"
|
||||
subject.global_role == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team users can read all software in their teams.
|
||||
# Team admins, maintainers, observers and observer_plus can read all software in their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "software_inventory"
|
||||
@ -637,3 +701,13 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
##
|
||||
# Version
|
||||
##
|
||||
|
||||
# Any logged in user can read Fleet's version
|
||||
allow {
|
||||
object.type == "version"
|
||||
not is_null(subject)
|
||||
action == read
|
||||
}
|
@ -49,7 +49,7 @@ func TestAuthorizeAppConfig(t *testing.T) {
|
||||
{user: nil, object: config, action: read, allow: false},
|
||||
{user: nil, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: config, action: read, allow: true},
|
||||
{user: test.UserNoRoles, object: config, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: config, action: read, allow: true},
|
||||
@ -63,6 +63,24 @@ func TestAuthorizeAppConfig(t *testing.T) {
|
||||
|
||||
{user: test.UserObserverPlus, object: config, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserGitOps, object: config, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: config, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: config, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: config, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: config, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam1, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: config, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: config, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: config, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -101,6 +119,29 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeActivity(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
activity := &fleet.Activity{}
|
||||
|
||||
runTestCases(t, []authTestCase{
|
||||
// All global roles except GitOps can read activities.
|
||||
{user: nil, object: activity, action: read, allow: false},
|
||||
{user: test.UserAdmin, object: activity, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: activity, action: read, allow: true},
|
||||
{user: test.UserObserver, object: activity, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: activity, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: activity, action: read, allow: false},
|
||||
|
||||
// Team roles cannot read activites.
|
||||
{user: test.UserTeamAdminTeam1, object: activity, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: activity, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: activity, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: activity, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: activity, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeUser(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@ -243,7 +284,6 @@ func TestAuthorizeEnrollSecret(t *testing.T) {
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin},
|
||||
},
|
||||
}
|
||||
|
||||
teamMaintainer := &fleet.User{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer},
|
||||
@ -254,7 +294,17 @@ func TestAuthorizeEnrollSecret(t *testing.T) {
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver},
|
||||
},
|
||||
}
|
||||
globalSecret := &fleet.EnrollSecret{}
|
||||
teamObserverPlus := &fleet.User{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus},
|
||||
},
|
||||
}
|
||||
teamGitOps := &fleet.User{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps},
|
||||
},
|
||||
}
|
||||
globalSecret := &fleet.EnrollSecret{TeamID: nil}
|
||||
teamSecret := &fleet.EnrollSecret{TeamID: ptr.Uint(1)}
|
||||
runTestCases(t, []authTestCase{
|
||||
// No access
|
||||
@ -278,28 +328,45 @@ func TestAuthorizeEnrollSecret(t *testing.T) {
|
||||
{user: teamObserver, object: globalSecret, action: write, allow: false},
|
||||
{user: teamObserver, object: teamSecret, action: read, allow: false},
|
||||
{user: teamObserver, object: teamSecret, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: globalSecret, action: read, allow: false},
|
||||
{user: teamObserverPlus, object: globalSecret, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: teamSecret, action: read, allow: false},
|
||||
{user: teamObserverPlus, object: teamSecret, action: write, allow: false},
|
||||
{user: teamGitOps, object: globalSecret, action: read, allow: false},
|
||||
{user: teamGitOps, object: globalSecret, action: write, allow: false},
|
||||
{user: teamGitOps, object: teamSecret, action: read, allow: false},
|
||||
{user: teamGitOps, object: teamSecret, action: write, allow: false},
|
||||
|
||||
// Admin can read/write all
|
||||
// Global admin can read/write all.
|
||||
{user: test.UserAdmin, object: globalSecret, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: globalSecret, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: teamSecret, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: teamSecret, action: write, allow: true},
|
||||
|
||||
// Maintainer can read all
|
||||
// Global maintainer can read/write all.
|
||||
{user: test.UserMaintainer, object: globalSecret, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: globalSecret, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: teamSecret, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: teamSecret, action: write, allow: true},
|
||||
|
||||
// Team admin can read/write team secret
|
||||
// Global GitOps can write global secret but not read it.
|
||||
{user: test.UserGitOps, object: globalSecret, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: globalSecret, action: write, allow: true},
|
||||
// Global GitOps cannot read/write team secrets.
|
||||
{user: test.UserGitOps, object: teamSecret, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: teamSecret, action: write, allow: false},
|
||||
|
||||
// Team admin cannot read/write global secret.
|
||||
{user: teamAdmin, object: globalSecret, action: read, allow: false},
|
||||
{user: teamAdmin, object: globalSecret, action: write, allow: false},
|
||||
// Team admin can read/write team secret.
|
||||
{user: teamAdmin, object: teamSecret, action: read, allow: true},
|
||||
{user: teamAdmin, object: teamSecret, action: write, allow: true},
|
||||
|
||||
// Team maintainer can read/write team secret
|
||||
// Team maintainer cannot read/write global secret.
|
||||
{user: teamMaintainer, object: globalSecret, action: read, allow: false},
|
||||
{user: teamMaintainer, object: globalSecret, action: write, allow: false},
|
||||
// Team maintainer can read/write team secret.
|
||||
{user: teamMaintainer, object: teamSecret, action: read, allow: true},
|
||||
{user: teamMaintainer, object: teamSecret, action: write, allow: true},
|
||||
})
|
||||
@ -308,25 +375,78 @@ func TestAuthorizeEnrollSecret(t *testing.T) {
|
||||
func TestAuthorizeTeam(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
team := &fleet.Team{}
|
||||
team := &fleet.Team{} // Empty team is used to "list teams"
|
||||
team1 := &fleet.Team{ID: 1}
|
||||
team2 := &fleet.Team{ID: 2}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: nil, object: team, action: read, allow: false},
|
||||
{user: nil, object: team, action: write, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: team, action: read, allow: true},
|
||||
{user: test.UserNoRoles, object: team, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: team1, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: team1, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: team, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: team, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: team1, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: team1, action: write, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: team, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: team, action: write, allow: false},
|
||||
{user: test.UserMaintainer, object: team1, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: team1, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserver, object: team, action: read, allow: true},
|
||||
{user: test.UserObserver, object: team, action: write, allow: false},
|
||||
{user: test.UserObserver, object: team1, action: read, allow: true},
|
||||
{user: test.UserObserver, object: team1, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: team, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: team, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: team1, action: write, allow: false},
|
||||
|
||||
{user: test.UserGitOps, object: team, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: team, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: team1, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team1, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team2, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team2, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: team, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: team, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam1, object: team, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team1, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam1, object: team1, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team2, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team2, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: team, action: read, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team, action: write, allow: false},
|
||||
// Team GitOps cannot read its team but can write it.
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -338,7 +458,7 @@ func TestAuthorizeLabel(t *testing.T) {
|
||||
{user: nil, object: label, action: read, allow: false},
|
||||
{user: nil, object: label, action: write, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: label, action: read, allow: true},
|
||||
{user: test.UserNoRoles, object: label, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: label, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: label, action: read, allow: true},
|
||||
@ -352,6 +472,30 @@ func TestAuthorizeLabel(t *testing.T) {
|
||||
|
||||
{user: test.UserObserverPlus, object: label, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: label, action: write, allow: false},
|
||||
|
||||
// Global GitOps can write, but not read labels.
|
||||
{user: test.UserGitOps, object: label, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: label, action: write, allow: true},
|
||||
|
||||
// Team GitOps cannot read or write labels.
|
||||
{user: test.UserTeamGitOpsTeam1, object: label, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: label, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeSoftwareInventory(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
softwareInventory := &fleet.AuthzSoftwareInventory{}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: nil, object: softwareInventory, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: softwareInventory, action: read, allow: false},
|
||||
{user: test.UserAdmin, object: softwareInventory, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: softwareInventory, action: read, allow: true},
|
||||
{user: test.UserObserver, object: softwareInventory, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: softwareInventory, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: softwareInventory, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: softwareInventory, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -373,6 +517,16 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver},
|
||||
},
|
||||
}
|
||||
teamObserverPlus := &fleet.User{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus},
|
||||
},
|
||||
}
|
||||
teamGitOps := &fleet.User{
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps},
|
||||
},
|
||||
}
|
||||
host := &fleet.Host{}
|
||||
hostTeam1 := &fleet.Host{TeamID: ptr.Uint(1)}
|
||||
hostTeam2 := &fleet.Host{TeamID: ptr.Uint(2)}
|
||||
@ -389,10 +543,10 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{user: nil, object: hostTeam2, action: write, allow: false},
|
||||
{user: nil, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// List but no specific host access
|
||||
// No host access if the user has no roles.
|
||||
{user: test.UserNoRoles, object: host, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: host, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: host, action: list, allow: true},
|
||||
{user: test.UserNoRoles, object: host, action: list, allow: false},
|
||||
{user: test.UserNoRoles, object: host, action: mdmCommand, allow: false},
|
||||
{user: test.UserNoRoles, object: hostTeam1, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: hostTeam1, action: write, allow: false},
|
||||
@ -449,6 +603,18 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{user: test.UserMaintainer, object: hostTeam2, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: hostTeam2, action: mdmCommand, allow: true},
|
||||
|
||||
// Global GitOps can write (not read) all.
|
||||
{user: test.UserGitOps, object: host, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: host, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: host, action: list, allow: false},
|
||||
{user: test.UserGitOps, object: host, action: mdmCommand, allow: false},
|
||||
{user: test.UserGitOps, object: hostTeam1, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: hostTeam1, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: hostTeam1, action: mdmCommand, allow: false},
|
||||
{user: test.UserGitOps, object: hostTeam2, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: hostTeam2, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Team observer can read only on appropriate team
|
||||
{user: teamObserver, object: host, action: read, allow: false},
|
||||
{user: teamObserver, object: host, action: write, allow: false},
|
||||
@ -461,6 +627,18 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{user: teamObserver, object: hostTeam2, action: write, allow: false},
|
||||
{user: teamObserver, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Team observer+ can read only on appropriate team
|
||||
{user: teamObserverPlus, object: host, action: read, allow: false},
|
||||
{user: teamObserverPlus, object: host, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: host, action: list, allow: true},
|
||||
{user: teamObserverPlus, object: host, action: mdmCommand, allow: false},
|
||||
{user: teamObserverPlus, object: hostTeam1, action: read, allow: true},
|
||||
{user: teamObserverPlus, object: hostTeam1, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: hostTeam1, action: mdmCommand, allow: false},
|
||||
{user: teamObserverPlus, object: hostTeam2, action: read, allow: false},
|
||||
{user: teamObserverPlus, object: hostTeam2, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Team maintainer can read/write only on appropriate team
|
||||
{user: teamMaintainer, object: host, action: read, allow: false},
|
||||
{user: teamMaintainer, object: host, action: write, allow: false},
|
||||
@ -484,6 +662,18 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{user: teamAdmin, object: hostTeam2, action: read, allow: false},
|
||||
{user: teamAdmin, object: hostTeam2, action: write, allow: false},
|
||||
{user: teamAdmin, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Team GitOps can cannot read/write hosts.
|
||||
{user: teamGitOps, object: host, action: read, allow: false},
|
||||
{user: teamGitOps, object: host, action: write, allow: false},
|
||||
{user: teamGitOps, object: host, action: list, allow: false},
|
||||
{user: teamGitOps, object: host, action: mdmCommand, allow: false},
|
||||
{user: teamGitOps, object: hostTeam1, action: read, allow: false},
|
||||
{user: teamGitOps, object: hostTeam1, action: write, allow: false},
|
||||
{user: teamGitOps, object: hostTeam1, action: mdmCommand, allow: false},
|
||||
{user: teamGitOps, object: hostTeam2, action: read, allow: false},
|
||||
{user: teamGitOps, object: hostTeam2, action: write, allow: false},
|
||||
{user: teamGitOps, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -521,6 +711,12 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus},
|
||||
},
|
||||
}
|
||||
teamGitOps := &fleet.User{
|
||||
ID: 105,
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps},
|
||||
},
|
||||
}
|
||||
|
||||
query := &fleet.Query{ObserverCanRun: false}
|
||||
emptyTquery := &fleet.TargetedQuery{Query: query}
|
||||
@ -539,6 +735,8 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
teamAdminQuery := &fleet.Query{ID: 1, AuthorID: ptr.Uint(teamAdmin.ID), ObserverCanRun: false}
|
||||
teamMaintQuery := &fleet.Query{ID: 2, AuthorID: ptr.Uint(teamMaintainer.ID), ObserverCanRun: false}
|
||||
globalAdminQuery := &fleet.Query{ID: 3, AuthorID: ptr.Uint(test.UserAdmin.ID), ObserverCanRun: false}
|
||||
globalGitOpsQuery := &fleet.Query{ID: 4, AuthorID: ptr.Uint(test.UserGitOps.ID), ObserverCanRun: false}
|
||||
teamGitOpsQuery := &fleet.Query{ID: 5, AuthorID: ptr.Uint(teamGitOps.ID), ObserverCanRun: false}
|
||||
|
||||
runTestCases(t, []authTestCase{
|
||||
// No access
|
||||
@ -554,14 +752,14 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{user: nil, object: team1ObsQuery, action: run, allow: false},
|
||||
{user: nil, object: observerQuery, action: runNew, allow: false},
|
||||
|
||||
// User can still read queries with no roles
|
||||
{user: test.UserNoRoles, object: query, action: read, allow: true},
|
||||
// User with no roles cannot access queries.
|
||||
{user: test.UserNoRoles, object: query, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: query, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: teamAdminQuery, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: emptyTquery, action: run, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Query, action: run, allow: false},
|
||||
{user: test.UserNoRoles, object: query, action: runNew, allow: false},
|
||||
{user: test.UserNoRoles, object: observerQuery, action: read, allow: true},
|
||||
{user: test.UserNoRoles, object: observerQuery, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: observerQuery, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: emptyTobsQuery, action: run, allow: false},
|
||||
{user: test.UserNoRoles, object: team1ObsQuery, action: run, allow: false},
|
||||
@ -623,6 +821,20 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{user: test.UserAdmin, object: team1ObsQuery, action: run, allow: true},
|
||||
{user: test.UserAdmin, object: observerQuery, action: runNew, allow: true},
|
||||
|
||||
// Global GitOps cannot read, or run any query, but can write.
|
||||
{user: test.UserGitOps, object: query, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: query, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: teamAdminQuery, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: emptyTquery, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: team1Query, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: query, action: runNew, allow: false},
|
||||
{user: test.UserGitOps, object: observerQuery, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: observerQuery, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: emptyTobsQuery, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: team1ObsQuery, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: team12ObsQuery, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: observerQuery, action: runNew, allow: false},
|
||||
|
||||
// Team observer can read and run observer_can_run only
|
||||
{user: teamObserver, object: query, action: read, allow: true},
|
||||
{user: teamObserver, object: query, action: write, allow: false},
|
||||
@ -690,6 +902,21 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{user: teamAdmin, object: team2ObsQuery, action: run, allow: false},
|
||||
{user: teamAdmin, object: observerQuery, action: runNew, allow: true},
|
||||
|
||||
// Team GitOps cannot read or run any query, but can create new or edit (write) queries authored by it.
|
||||
{user: teamGitOps, object: query, action: read, allow: false},
|
||||
{user: teamGitOps, object: query, action: write, allow: true}, // create new
|
||||
{user: teamGitOps, object: teamAdminQuery, action: write, allow: false}, // not the author
|
||||
{user: teamGitOps, object: teamGitOpsQuery, action: write, allow: true}, // author
|
||||
{user: teamGitOps, object: globalGitOpsQuery, action: write, allow: false}, // not the author
|
||||
{user: teamGitOps, object: emptyTquery, action: run, allow: false},
|
||||
{user: teamGitOps, object: team1Query, action: run, allow: false},
|
||||
{user: teamGitOps, object: query, action: runNew, allow: false},
|
||||
{user: teamGitOps, object: emptyTobsQuery, action: run, allow: false},
|
||||
{user: teamGitOps, object: team1ObsQuery, action: run, allow: false},
|
||||
{user: teamGitOps, object: team12ObsQuery, action: run, allow: false},
|
||||
{user: teamGitOps, object: team2ObsQuery, action: run, allow: false},
|
||||
{user: teamGitOps, object: observerQuery, action: runNew, allow: false},
|
||||
|
||||
// User admin on team 1, observer on team 2
|
||||
{user: twoTeamsAdminObs, object: query, action: read, allow: true},
|
||||
{user: twoTeamsAdminObs, object: query, action: write, allow: true},
|
||||
@ -713,7 +940,7 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeTargets(t *testing.T) {
|
||||
func TestAuthorizeTarget(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
target := &fleet.Target{}
|
||||
@ -730,105 +957,158 @@ func TestAuthorizeTargets(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizePacks(t *testing.T) {
|
||||
func TestAuthorizeUserCreatedPack(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pack := &fleet.Pack{}
|
||||
userCreatedPack := &fleet.Pack{
|
||||
// Type nil is the type for user-created packs.
|
||||
Type: nil,
|
||||
}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: nil, object: pack, action: read, allow: false},
|
||||
{user: nil, object: pack, action: write, allow: false},
|
||||
{user: nil, object: userCreatedPack, action: read, allow: false},
|
||||
{user: nil, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: pack, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: pack, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: pack, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: pack, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: userCreatedPack, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: userCreatedPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: pack, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: pack, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: userCreatedPack, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: userCreatedPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: pack, action: read, allow: false},
|
||||
{user: test.UserObserver, object: pack, action: write, allow: false},
|
||||
{user: test.UserObserver, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserObserver, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: pack, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: pack, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
// This is one exception to the "write only" nature of gitops. To be able to create
|
||||
// and edit packs currently it needs read access too.
|
||||
{user: test.UserGitOps, object: userCreatedPack, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: userCreatedPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: userCreatedPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: userCreatedPack, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: userCreatedPack, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeTeamPacks(t *testing.T) {
|
||||
func TestAuthorizeGlobalPack(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
globalPack := &fleet.Pack{
|
||||
// Type "global" is the type for the one global pack.
|
||||
Type: ptr.String("global"),
|
||||
}
|
||||
runTestCases(t, []authTestCase{
|
||||
// Team maintainer can read packs of the team.
|
||||
{
|
||||
user: test.UserTeamMaintainerTeam1,
|
||||
object: &fleet.Pack{
|
||||
Type: ptr.String("team-1"),
|
||||
},
|
||||
action: read,
|
||||
allow: true,
|
||||
},
|
||||
// Team observer can read packs of the team.
|
||||
{
|
||||
user: test.UserTeamObserverTeam1TeamAdminTeam2,
|
||||
object: &fleet.Pack{
|
||||
Type: ptr.String("team-1"),
|
||||
},
|
||||
action: read,
|
||||
allow: true,
|
||||
},
|
||||
// Team observer cannot write packs of the team.
|
||||
{
|
||||
user: test.UserTeamObserverTeam1TeamAdminTeam2,
|
||||
object: &fleet.Pack{
|
||||
Type: ptr.String("team-1"),
|
||||
},
|
||||
action: write,
|
||||
allow: false,
|
||||
},
|
||||
// Members of a team cannot read packs of another team.
|
||||
{
|
||||
user: test.UserTeamAdminTeam1,
|
||||
object: &fleet.Pack{
|
||||
Type: ptr.String("team-2"),
|
||||
},
|
||||
action: read,
|
||||
allow: false,
|
||||
},
|
||||
// Members of a team cannot read packs of another team.
|
||||
{
|
||||
user: test.UserTeamAdminTeam1,
|
||||
object: &fleet.Pack{
|
||||
Type: ptr.String("team-2"),
|
||||
},
|
||||
action: read,
|
||||
allow: false,
|
||||
},
|
||||
// Team maintainers cannot read global packs.
|
||||
{
|
||||
user: test.UserTeamMaintainerTeam1,
|
||||
object: &fleet.Pack{},
|
||||
action: read,
|
||||
allow: false,
|
||||
},
|
||||
// Team admins cannot read global packs.
|
||||
{
|
||||
user: test.UserTeamAdminTeam1,
|
||||
object: &fleet.Pack{},
|
||||
action: read,
|
||||
allow: false,
|
||||
},
|
||||
// Team admins cannot write global packs.
|
||||
{
|
||||
user: test.UserTeamAdminTeam1,
|
||||
object: &fleet.Pack{},
|
||||
action: write,
|
||||
allow: false,
|
||||
},
|
||||
{user: nil, object: globalPack, action: read, allow: false},
|
||||
{user: nil, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: globalPack, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: globalPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: globalPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserObserver, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: globalPack, action: write, allow: false},
|
||||
|
||||
// This is one exception to the "write only" nature of gitops. To be able to create
|
||||
// and edit packs currently it needs read access too.
|
||||
{user: test.UserGitOps, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: globalPack, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam1, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalPack, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalPack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalPack, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalPack, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeCarves(t *testing.T) {
|
||||
func TestAuthorizeTeamPack(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
team1Pack := &fleet.Pack{Type: ptr.String("team-1")}
|
||||
team2Pack := &fleet.Pack{Type: ptr.String("team-2")}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: test.UserAdmin, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: team1Pack, action: write, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Pack, action: write, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: team1Pack, action: read, allow: false},
|
||||
{user: test.UserObserver, object: team1Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: team1Pack, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Pack, action: write, allow: false},
|
||||
|
||||
// This is one exception to the "write only" nature of gitops. To be able to create
|
||||
// and edit packs currently it needs read access too.
|
||||
{user: test.UserGitOps, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: team1Pack, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Pack, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Pack, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam1, object: team1Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Pack, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Pack, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Pack, action: write, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team2Pack, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team2Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2Pack, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: team2Pack, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team2Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2Pack, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2Pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2Pack, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2Pack, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeCarve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
carve := &fleet.CarveMetadata{}
|
||||
@ -843,6 +1123,8 @@ func TestAuthorizeCarves(t *testing.T) {
|
||||
{user: test.UserObserver, object: carve, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: carve, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: carve, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: carve, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: carve, action: write, allow: false},
|
||||
|
||||
// Only admins allowed
|
||||
{user: test.UserAdmin, object: carve, action: read, allow: true},
|
||||
@ -850,58 +1132,113 @@ func TestAuthorizeCarves(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizePolicies(t *testing.T) {
|
||||
func TestAuthorizeGlobalPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
globalPolicy := &fleet.Policy{}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: nil, object: globalPolicy, action: write, allow: false},
|
||||
{user: nil, object: globalPolicy, action: read, allow: false},
|
||||
|
||||
{user: test.UserNoRoles, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: globalPolicy, action: read, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: globalPolicy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: globalPolicy, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserverPlus, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserGitOps, object: globalPolicy, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalPolicy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalPolicy, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
func TestAuthorizeTeamPolicy(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
team1Policy := &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: ptr.Uint(1),
|
||||
},
|
||||
}
|
||||
team2Policy := &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: ptr.Uint(2),
|
||||
},
|
||||
}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: test.UserNoRoles, object: globalPolicy, action: write, allow: false},
|
||||
{user: nil, object: team1Policy, action: write, allow: false},
|
||||
{user: nil, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: globalPolicy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: globalPolicy, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: globalPolicy, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: globalPolicy, action: read, allow: true},
|
||||
{user: test.UserObserver, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: globalPolicy, action: read, allow: true},
|
||||
{user: test.UserNoRoles, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserverPlus, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserGitOps, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Policy, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Policy, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team1Policy, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Policy, action: read, allow: true},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Policy, action: read, allow: false},
|
||||
|
||||
// Team observers cannot write global policies.
|
||||
{user: test.UserTeamObserverTeam1, object: globalPolicy, action: write, allow: false},
|
||||
// Team observers can read global policies.
|
||||
{user: test.UserTeamObserverTeam1, object: globalPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team2Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team2Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team2Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team2Policy, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team2Policy, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -938,6 +1275,11 @@ 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: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Profile, action: write, allow: true},
|
||||
@ -963,20 +1305,30 @@ func TestAuthorizeMDMAppleConfigProfile(t *testing.T) {
|
||||
{user: test.UserTeamObserverTeam1, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam2, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam2, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{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: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: team1Profile, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -1013,6 +1365,11 @@ 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: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Settings, action: write, allow: true},
|
||||
@ -1038,20 +1395,30 @@ func TestAuthorizeMDMAppleSettings(t *testing.T) {
|
||||
{user: test.UserTeamObserverTeam1, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam2, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam1, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverPlusTeam2, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverPlusTeam2, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{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: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam2, object: team1Settings, action: read, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -277,6 +277,19 @@ type AppConfig struct {
|
||||
/////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
// Obfuscate overrides credentials with obfuscated characters.
|
||||
func (c *AppConfig) Obfuscate() {
|
||||
if c.SMTPSettings.SMTPPassword != "" {
|
||||
c.SMTPSettings.SMTPPassword = MaskedPassword
|
||||
}
|
||||
for _, jiraIntegration := range c.Integrations.Jira {
|
||||
jiraIntegration.APIToken = MaskedPassword
|
||||
}
|
||||
for _, zdIntegration := range c.Integrations.Zendesk {
|
||||
zdIntegration.APIToken = MaskedPassword
|
||||
}
|
||||
}
|
||||
|
||||
// legacyConfig holds settings that have been replaced, superceded or
|
||||
// deprecated by other AppConfig settings.
|
||||
type legacyConfig struct {
|
||||
@ -678,6 +691,19 @@ func (e *EnrollSecret) AuthzType() string {
|
||||
return "enroll_secret"
|
||||
}
|
||||
|
||||
// ExtraAuthz implements authz.ExtraAuthzer.
|
||||
func (e *EnrollSecret) ExtraAuthz() (map[string]interface{}, error) {
|
||||
return map[string]interface{}{
|
||||
"is_global_secret": e.TeamID == nil,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// IsGlobalSecret returns whether the secret is global.
|
||||
// This method is defined for the Policy Rego code (is_global_secret).
|
||||
func (e *EnrollSecret) IsGlobalSecret() bool {
|
||||
return e.TeamID == nil
|
||||
}
|
||||
|
||||
const (
|
||||
EnrollSecretKind = "enroll_secret"
|
||||
EnrollSecretDefaultLength = 24
|
||||
@ -835,3 +861,11 @@ type DeviceGlobalConfig struct {
|
||||
type DeviceGlobalMDMConfig struct {
|
||||
EnabledAndConfigured bool `json:"enabled_and_configured"`
|
||||
}
|
||||
|
||||
// Version is the authz type used to check access control to the version endpoint.
|
||||
type Version struct{}
|
||||
|
||||
// AuthzType implements authz.AuthzTyper.
|
||||
func (v *Version) AuthzType() string {
|
||||
return "version"
|
||||
}
|
||||
|
@ -321,6 +321,8 @@ const (
|
||||
ErrNoOneAdminNeeded = 2
|
||||
// ErrNoUnknownTranslate is returned when an item type in the translate payload is unknown
|
||||
ErrNoUnknownTranslate = 3
|
||||
// ErrAPIOnlyRole is returned when a selected role for a user is for API only users.
|
||||
ErrAPIOnlyRole = 4
|
||||
)
|
||||
|
||||
// NewError returns a fleet error with the code and message specified
|
||||
|
@ -12,6 +12,7 @@ const (
|
||||
RoleMaintainer = "maintainer"
|
||||
RoleObserver = "observer"
|
||||
RoleObserverPlus = "observer_plus"
|
||||
RoleGitOps = "gitops"
|
||||
)
|
||||
|
||||
type TeamPayload struct {
|
||||
@ -204,10 +205,12 @@ var teamRoles = map[string]struct{}{
|
||||
RoleObserver: {},
|
||||
RoleMaintainer: {},
|
||||
RoleObserverPlus: {},
|
||||
RoleGitOps: {},
|
||||
}
|
||||
|
||||
var premiumTeamRoles = map[string]struct{}{
|
||||
RoleObserverPlus: {},
|
||||
RoleGitOps: {},
|
||||
}
|
||||
|
||||
// ValidTeamRole returns whether the role provided is valid for a team user.
|
||||
@ -216,24 +219,17 @@ func ValidTeamRole(role string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidTeamRoles returns the list of valid roles for a team user.
|
||||
func ValidTeamRoles() []string {
|
||||
var roles []string
|
||||
for role := range teamRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
var globalRoles = map[string]struct{}{
|
||||
RoleObserver: {},
|
||||
RoleMaintainer: {},
|
||||
RoleAdmin: {},
|
||||
RoleObserverPlus: {},
|
||||
RoleGitOps: {},
|
||||
}
|
||||
|
||||
var premiumGlobalRoles = map[string]struct{}{
|
||||
RoleObserverPlus: {},
|
||||
RoleGitOps: {},
|
||||
}
|
||||
|
||||
// ValidGlobalRole returns whether the role provided is valid for a global user.
|
||||
@ -242,15 +238,6 @@ func ValidGlobalRole(role string) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidGlobalRoles returns the list of valid roles for a global user.
|
||||
func ValidGlobalRoles() []string {
|
||||
var roles []string
|
||||
for role := range globalRoles {
|
||||
roles = append(roles, role)
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
// ValidateRole returns nil if the global and team roles combination is a valid
|
||||
// one within fleet, or a fleet Error otherwise.
|
||||
func ValidateRole(globalRole *string, teamUsers []UserTeam) error {
|
||||
@ -277,21 +264,31 @@ func ValidateRole(globalRole *string, teamUsers []UserTeam) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateRoleForLicense(globalRole *string, teamUsers *[]UserTeam, license LicenseInfo) error {
|
||||
// ValidateUserRoles verifies the roles to be applied to a new or existing user.
|
||||
//
|
||||
// Argument createNew sets whether the user is being created (true) or is being modified (false).
|
||||
func ValidateUserRoles(createNew bool, payload UserPayload, license LicenseInfo) error {
|
||||
var teamUsers_ []UserTeam
|
||||
if teamUsers != nil {
|
||||
teamUsers_ = *teamUsers
|
||||
if payload.Teams != nil {
|
||||
teamUsers_ = *payload.Teams
|
||||
}
|
||||
if err := ValidateRole(globalRole, teamUsers_); err != nil {
|
||||
if err := ValidateRole(payload.GlobalRole, teamUsers_); err != nil {
|
||||
return err
|
||||
}
|
||||
premiumRolesPresent := false
|
||||
if globalRole != nil {
|
||||
if _, ok := premiumGlobalRoles[*globalRole]; ok {
|
||||
gitOpsRolePresent := false
|
||||
if payload.GlobalRole != nil {
|
||||
if *payload.GlobalRole == RoleGitOps {
|
||||
gitOpsRolePresent = true
|
||||
}
|
||||
if _, ok := premiumGlobalRoles[*payload.GlobalRole]; ok {
|
||||
premiumRolesPresent = true
|
||||
}
|
||||
}
|
||||
for _, teamUser := range teamUsers_ {
|
||||
if teamUser.Role == RoleGitOps {
|
||||
gitOpsRolePresent = true
|
||||
}
|
||||
if _, ok := premiumTeamRoles[teamUser.Role]; ok {
|
||||
premiumRolesPresent = true
|
||||
}
|
||||
@ -299,6 +296,14 @@ func ValidateRoleForLicense(globalRole *string, teamUsers *[]UserTeam, license L
|
||||
if !license.IsPremium() && premiumRolesPresent {
|
||||
return ErrMissingLicense
|
||||
}
|
||||
if gitOpsRolePresent &&
|
||||
// New user is not API only.
|
||||
((createNew && (payload.APIOnly == nil || !*payload.APIOnly)) ||
|
||||
// Removing API only status from existing user.
|
||||
(!createNew && payload.APIOnly != nil && !*payload.APIOnly)) {
|
||||
return NewErrorf(ErrAPIOnlyRole, "role GitOps can only be set for API only users")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
260
server/fleet/teams_test.go
Normal file
260
server/fleet/teams_test.go
Normal file
@ -0,0 +1,260 @@
|
||||
package fleet
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestValidateUserRoles(t *testing.T) {
|
||||
checkErrCode := func(code int) func(error) bool {
|
||||
return func(err error) bool {
|
||||
errError, ok := err.(*Error)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return errError.Code == code
|
||||
}
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
create bool
|
||||
payload UserPayload
|
||||
license LicenseInfo
|
||||
checkErr func(err error) bool
|
||||
}{
|
||||
{
|
||||
name: "global-gitops-create-not-premium",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: ptr.Bool(true),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: func(err error) bool {
|
||||
return err == ErrMissingLicense
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "global-gitops-create-api-only",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: ptr.Bool(true),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "global-gitops-create-not-api-only",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: ptr.Bool(false),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "global-gitops-create-api-only-not-set",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: nil,
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "global-gitops-create-api-only-not-set",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: nil,
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "global-gitops-modify-not-api-only",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: ptr.Bool(false),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "global-gitops-modify-api-only-not-set",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleGitOps),
|
||||
APIOnly: nil,
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "team-gitops-create-mixed-with-other-roles",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}, {Role: RoleMaintainer}},
|
||||
APIOnly: ptr.Bool(true),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "team-gitops-modify-mixed-with-other-roles",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}, {Role: RoleMaintainer}},
|
||||
APIOnly: ptr.Bool(true),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "team-gitops-create-api-only-false",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}},
|
||||
APIOnly: ptr.Bool(false),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "team-gitops-create-api-only-not-set",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}},
|
||||
APIOnly: nil,
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "team-gitops-modify-to-not-api-only",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}},
|
||||
APIOnly: ptr.Bool(false),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: checkErrCode(ErrAPIOnlyRole),
|
||||
},
|
||||
{
|
||||
name: "team-gitops-modify-api-only-not-set-should-succeed",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: RoleGitOps}},
|
||||
APIOnly: nil, // not updating the APIOnly status.
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierPremium,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "global-observer-modify-to-not-api-only",
|
||||
create: false,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleObserver),
|
||||
APIOnly: ptr.Bool(false),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: nil,
|
||||
},
|
||||
{
|
||||
name: "global-invalid-role",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String("foobar"),
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: checkErrCode(ErrNoRoleNeeded),
|
||||
},
|
||||
{
|
||||
name: "team-invalid-role",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
Teams: &[]UserTeam{{Role: "foobar"}},
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: checkErrCode(ErrNoRoleNeeded),
|
||||
},
|
||||
{
|
||||
name: "global-and-team-role-set",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: ptr.String(RoleObserver),
|
||||
Teams: &[]UserTeam{{Role: RoleObserver}},
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: checkErrCode(ErrNoRoleNeeded),
|
||||
},
|
||||
{
|
||||
name: "no-roles-set",
|
||||
create: true,
|
||||
payload: UserPayload{
|
||||
GlobalRole: nil,
|
||||
Teams: &[]UserTeam{},
|
||||
},
|
||||
license: LicenseInfo{
|
||||
Tier: TierFree,
|
||||
},
|
||||
checkErr: checkErrCode(ErrNoRoleNeeded),
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := ValidateUserRoles(tc.create, tc.payload, tc.license)
|
||||
if err == nil {
|
||||
if tc.checkErr != nil {
|
||||
t.Errorf("expected an error: %+v", tc)
|
||||
}
|
||||
} else { // err != nil
|
||||
if tc.checkErr == nil {
|
||||
t.Errorf("unexpected error: %s %+v", err, tc)
|
||||
} else {
|
||||
require.True(t, tc.checkErr(err), "err_type=%T, err=%s", err, err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/go-kit/kit/log/level"
|
||||
@ -163,17 +164,7 @@ func (svc *Service) AppConfigObfuscated(ctx context.Context) (*fleet.AppConfig,
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ac.SMTPSettings.SMTPPassword != "" {
|
||||
ac.SMTPSettings.SMTPPassword = fleet.MaskedPassword
|
||||
}
|
||||
|
||||
for _, jiraIntegration := range ac.Integrations.Jira {
|
||||
jiraIntegration.APIToken = fleet.MaskedPassword
|
||||
}
|
||||
|
||||
for _, zdIntegration := range ac.Integrations.Zendesk {
|
||||
zdIntegration.APIToken = fleet.MaskedPassword
|
||||
}
|
||||
ac.Obfuscate()
|
||||
|
||||
return ac, nil
|
||||
}
|
||||
@ -198,10 +189,9 @@ func modifyAppConfigEndpoint(ctx context.Context, request interface{}, svc fleet
|
||||
return appConfigResponse{appConfigResponseFields: appConfigResponseFields{Err: err}}, nil
|
||||
}
|
||||
|
||||
license, err := svc.License(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We do not use svc.License(ctx) to allow roles (like GitOps) write but not read access to AppConfig.
|
||||
license, _ := license.FromContext(ctx)
|
||||
|
||||
loggingConfig, err := svc.LoggingConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -238,10 +228,8 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
||||
}
|
||||
oldAppConfig := appConfig.Copy()
|
||||
|
||||
license, err := svc.License(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// We do not use svc.License(ctx) to allow roles (like GitOps) write but not read access to AppConfig.
|
||||
license, _ := license.FromContext(ctx)
|
||||
|
||||
oldSmtpSettings := appConfig.SMTPSettings
|
||||
oldAgentOptions := ""
|
||||
@ -405,15 +393,16 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
||||
}
|
||||
|
||||
// retrieve new app config with obfuscated secrets
|
||||
obfuscatedConfig, err := svc.AppConfigObfuscated(ctx)
|
||||
obfuscatedAppConfig, err := svc.ds.AppConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
obfuscatedAppConfig.Obfuscate()
|
||||
|
||||
// if the agent options changed, create the corresponding activity
|
||||
newAgentOptions := ""
|
||||
if obfuscatedConfig.AgentOptions != nil {
|
||||
newAgentOptions = string(*obfuscatedConfig.AgentOptions)
|
||||
if obfuscatedAppConfig.AgentOptions != nil {
|
||||
newAgentOptions = string(*obfuscatedAppConfig.AgentOptions)
|
||||
}
|
||||
if oldAgentOptions != newAgentOptions {
|
||||
if err := svc.ds.NewActivity(
|
||||
@ -460,7 +449,7 @@ func (svc *Service) ModifyAppConfig(ctx context.Context, p []byte, applyOpts fle
|
||||
}
|
||||
}
|
||||
|
||||
return obfuscatedConfig, nil
|
||||
return obfuscatedAppConfig, nil
|
||||
}
|
||||
|
||||
func (svc *Service) validateMDM(
|
||||
@ -645,7 +634,7 @@ func versionEndpoint(ctx context.Context, request interface{}, svc fleet.Service
|
||||
}
|
||||
|
||||
func (svc *Service) Version(ctx context.Context) (*version.Info, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Version{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
@ -69,6 +69,18 @@ func TestAppConfigAuth(t *testing.T) {
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global observer+",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserverPlus)},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"global gitops",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
false,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"team admin",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
||||
@ -88,11 +100,23 @@ func TestAppConfigAuth(t *testing.T) {
|
||||
false,
|
||||
},
|
||||
{
|
||||
"user",
|
||||
&fleet.User{ID: 777},
|
||||
"team observer+",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus}}},
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team gitops",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
true,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"user without roles",
|
||||
&fleet.User{ID: 777},
|
||||
true,
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
@ -104,15 +128,75 @@ func TestAppConfigAuth(t *testing.T) {
|
||||
_, err = svc.ModifyAppConfig(ctx, []byte(`{}`), fleet.ApplySpecOptions{})
|
||||
checkAuthErr(t, tt.shouldFailWrite, err)
|
||||
|
||||
_, err = svc.Version(ctx)
|
||||
checkAuthErr(t, tt.shouldFailRead, err)
|
||||
|
||||
_, err = svc.CertificateChain(ctx)
|
||||
checkAuthErr(t, tt.shouldFailRead, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestVersion tests that all users can access the version endpoint.
|
||||
func TestVersion(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *fleet.User
|
||||
}{
|
||||
{
|
||||
"global admin",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)},
|
||||
},
|
||||
{
|
||||
"global maintainer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleMaintainer)},
|
||||
},
|
||||
{
|
||||
"global observer",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserver)},
|
||||
},
|
||||
{
|
||||
"global observer+",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleObserverPlus)},
|
||||
},
|
||||
{
|
||||
"global gitops",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
},
|
||||
{
|
||||
"team admin",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
||||
},
|
||||
{
|
||||
"team maintainer",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
|
||||
},
|
||||
{
|
||||
"team observer",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
||||
},
|
||||
{
|
||||
"team observer+",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus}}},
|
||||
},
|
||||
{
|
||||
"team gitops",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
},
|
||||
{
|
||||
"user without roles",
|
||||
&fleet.User{ID: 777},
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
||||
_, err := svc.Version(ctx)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnrollSecretAuth(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
@ -387,36 +471,64 @@ func TestAppConfigSecretsObfuscated(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
user *fleet.User
|
||||
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",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleAdmin}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team maintainer",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleMaintainer}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team observer",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserver}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"user",
|
||||
"team observer+",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus}}},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team gitops",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
true,
|
||||
},
|
||||
{
|
||||
"user without roles",
|
||||
&fleet.User{ID: 777},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range testCases {
|
||||
@ -424,10 +536,14 @@ func TestAppConfigSecretsObfuscated(t *testing.T) {
|
||||
ctx := viewer.NewContext(ctx, viewer.Viewer{User: tt.user})
|
||||
|
||||
ac, err := svc.AppConfigObfuscated(ctx)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ac.SMTPSettings.SMTPPassword, fleet.MaskedPassword)
|
||||
require.Equal(t, ac.Integrations.Jira[0].APIToken, fleet.MaskedPassword)
|
||||
require.Equal(t, ac.Integrations.Zendesk[0].APIToken, fleet.MaskedPassword)
|
||||
if tt.shouldFail {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ac.SMTPSettings.SMTPPassword, fleet.MaskedPassword)
|
||||
require.Equal(t, ac.Integrations.Jira[0].APIToken, fleet.MaskedPassword)
|
||||
require.Equal(t, ac.Integrations.Zendesk[0].APIToken, fleet.MaskedPassword)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -174,10 +174,6 @@ func deleteGlobalPoliciesEndpoint(ctx context.Context, request interface{}, svc
|
||||
// DeleteGlobalPolicies deletes the given policies from the database.
|
||||
// It also deletes the given ids from the failing policies webhook configuration.
|
||||
func (svc Service) DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint, error) {
|
||||
// First check if authorized to read policies
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -185,7 +181,6 @@ func (svc Service) DeleteGlobalPolicies(ctx context.Context, ids []uint) ([]uint
|
||||
if err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "getting policies by ID")
|
||||
}
|
||||
// Then check if authorized to write policies
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -525,11 +525,6 @@ func TestListHosts(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 1)
|
||||
|
||||
// anyone can list hosts
|
||||
hosts, err = svc.ListHosts(test.UserContext(ctx, test.UserNoRoles), fleet.HostListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, hosts, 1)
|
||||
|
||||
// a user is required
|
||||
_, err = svc.ListHosts(ctx, fleet.HostListOptions{})
|
||||
require.Error(t, err)
|
||||
@ -568,9 +563,6 @@ func TestGetHostSummary(t *testing.T) {
|
||||
require.Len(t, summary.BuiltinLabels, 1)
|
||||
require.Equal(t, "All hosts", summary.BuiltinLabels[0].Name)
|
||||
|
||||
_, err = svc.GetHostSummary(test.UserContext(ctx, test.UserNoRoles), nil, nil, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
// a user is required
|
||||
_, err = svc.GetHostSummary(ctx, nil, nil, nil)
|
||||
require.Error(t, err)
|
||||
|
@ -61,6 +61,7 @@ func (s *integrationEnterpriseTestSuite) SetupSuite() {
|
||||
s.server = server
|
||||
s.users = users
|
||||
s.token = s.getTestAdminToken()
|
||||
s.cachedTokens = make(map[string]string)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TearDownTest() {
|
||||
@ -271,6 +272,77 @@ func (s *integrationEnterpriseTestSuite) TestTeamSpecs() {
|
||||
assert.Equal(t, "ABC", team.Secrets[0].Secret)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestTeamSpecsPermissions() {
|
||||
t := s.T()
|
||||
|
||||
//
|
||||
// Setup test
|
||||
//
|
||||
|
||||
// Create two teams, team1 and team2.
|
||||
team1, err := s.ds.NewTeam(context.Background(), &fleet.Team{
|
||||
ID: 42,
|
||||
Name: "team1",
|
||||
Description: "desc team1",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
team2, err := s.ds.NewTeam(context.Background(), &fleet.Team{
|
||||
ID: 43,
|
||||
Name: "team2",
|
||||
Description: "desc team2",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create a new admin for team1.
|
||||
password := test.GoodPassword
|
||||
email := "admin-team1@example.com"
|
||||
u := &fleet.User{
|
||||
Name: "admin team1",
|
||||
Email: email,
|
||||
GlobalRole: nil,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: *team1,
|
||||
Role: fleet.RoleAdmin,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, u.SetPassword(password, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// Start testing team specs with admin of team1.
|
||||
//
|
||||
|
||||
s.setTokenForTest(t, "admin-team1@example.com", test.GoodPassword)
|
||||
|
||||
// Should allow editing own team.
|
||||
agentOpts := json.RawMessage(`{"config": {"views": {"foo": "bar2"}}, "overrides": {"platforms": {"darwin": {"views": {"bar": "qux"}}}}}`)
|
||||
editTeam1Spec := applyTeamSpecsRequest{
|
||||
Specs: []*fleet.TeamSpec{
|
||||
{
|
||||
Name: team1.Name,
|
||||
AgentOptions: agentOpts,
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Do("POST", "/api/latest/fleet/spec/teams", editTeam1Spec, http.StatusOK)
|
||||
team1b, err := s.ds.Team(context.Background(), team1.ID)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, *team1b.Config.AgentOptions, agentOpts)
|
||||
|
||||
// Should not allow editing other teams.
|
||||
editTeam2Spec := applyTeamSpecsRequest{
|
||||
Specs: []*fleet.TeamSpec{
|
||||
{
|
||||
Name: team2.Name,
|
||||
AgentOptions: agentOpts,
|
||||
},
|
||||
},
|
||||
}
|
||||
s.Do("POST", "/api/latest/fleet/spec/teams", editTeam2Spec, http.StatusForbidden)
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) TestTeamSchedule() {
|
||||
t := s.T()
|
||||
|
||||
@ -2512,3 +2584,659 @@ func (s *integrationEnterpriseTestSuite) TestListSoftware() {
|
||||
require.NotNil(t, barPayload.Vulnerabilities[0].CISAKnownExploit, ptr.BoolPtr(true))
|
||||
require.Equal(t, barPayload.Vulnerabilities[0].CVEPublished, ptr.TimePtr(now))
|
||||
}
|
||||
|
||||
// TestGitOpsUserActions tests the permissions listed in ../../docs/Using-Fleet/Permissions.md.
|
||||
func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
//
|
||||
// Setup test data.
|
||||
// All actions are authored by a global admin.
|
||||
//
|
||||
|
||||
admin, err := s.ds.UserByEmail(ctx, "admin1@example.com")
|
||||
require.NoError(t, err)
|
||||
h1, err := s.ds.NewHost(ctx, &fleet.Host{
|
||||
NodeKey: ptr.String(t.Name() + "1"),
|
||||
UUID: t.Name() + "1",
|
||||
Hostname: t.Name() + "foo.local",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
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)
|
||||
acr := appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"webhook_settings": {
|
||||
"vulnerabilities_webhook": {
|
||||
"enable_vulnerabilities_webhook": false
|
||||
}
|
||||
}
|
||||
}`), http.StatusOK, &acr)
|
||||
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusOK, &acr)
|
||||
require.False(t, acr.WebhookSettings.VulnerabilitiesWebhook.Enable)
|
||||
q1, err := s.ds.NewQuery(ctx, &fleet.Query{
|
||||
Name: "Foo",
|
||||
Query: "SELECT * from time;",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
ggsr := getGlobalScheduleResponse{}
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusOK, &ggsr)
|
||||
require.NoError(t, ggsr.Err)
|
||||
var globalPackID uint
|
||||
mysql.ExecAdhocSQL(t, s.ds, func(q sqlx.ExtContext) error {
|
||||
return sqlx.GetContext(context.Background(), q, &globalPackID,
|
||||
`SELECT id FROM packs WHERE pack_type = 'global'`)
|
||||
})
|
||||
require.NotZero(t, globalPackID)
|
||||
cur := createUserResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/users/admin", createUserRequest{
|
||||
UserPayload: fleet.UserPayload{
|
||||
Email: ptr.String("foo42@example.com"),
|
||||
Password: ptr.String("p4ssw0rd.123"),
|
||||
Name: ptr.String("foo42"),
|
||||
GlobalRole: ptr.String("maintainer"),
|
||||
},
|
||||
}, http.StatusOK, &cur)
|
||||
maintainer := cur.User
|
||||
var carveBeginResp carveBeginResponse
|
||||
s.DoJSON("POST", "/api/osquery/carve/begin", carveBeginRequest{
|
||||
NodeKey: *h1.NodeKey,
|
||||
BlockCount: 3,
|
||||
BlockSize: 3,
|
||||
CarveSize: 8,
|
||||
CarveId: "c1",
|
||||
RequestId: "r1",
|
||||
}, http.StatusOK, &carveBeginResp)
|
||||
require.NotEmpty(t, carveBeginResp.SessionId)
|
||||
lcr := listCarvesResponse{}
|
||||
s.DoJSON("GET", "/api/latest/fleet/carves", listCarvesRequest{}, http.StatusOK, &lcr)
|
||||
require.NotEmpty(t, lcr.Carves)
|
||||
carveID := lcr.Carves[0].ID
|
||||
// Create the global GitOps user we'll use in tests.
|
||||
u := &fleet.User{
|
||||
Name: "GitOps",
|
||||
Email: "gitops1@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@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)
|
||||
gp2, err := s.ds.NewGlobalPolicy(ctx, &admin.ID, fleet.PolicyPayload{
|
||||
Name: "Zoo",
|
||||
Query: "SELECT 0;",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
t2p, err := s.ds.NewTeamPolicy(ctx, t2.ID, &admin.ID, fleet.PolicyPayload{
|
||||
Name: "Zoo2",
|
||||
Query: "SELECT 2;",
|
||||
})
|
||||
require.NoError(t, err)
|
||||
// Create some test user to test moving from/to teams.
|
||||
u3 := &fleet.User{
|
||||
Name: "Test Foo Observer",
|
||||
Email: "test-foo-observer@example.com",
|
||||
GlobalRole: nil,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: *t1,
|
||||
Role: fleet.RoleObserver,
|
||||
},
|
||||
},
|
||||
}
|
||||
require.NoError(t, u3.SetPassword(test.GoodPassword, 10, 10))
|
||||
_, err = s.ds.NewUser(context.Background(), u3)
|
||||
require.NoError(t, err)
|
||||
|
||||
//
|
||||
// Start running permission tests with user gitops1.
|
||||
//
|
||||
s.setTokenForTest(t, "gitops1@example.com", test.GoodPassword)
|
||||
|
||||
// Attempt to retrieve activities, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/activities", nil, http.StatusForbidden, &listActivitiesResponse{})
|
||||
|
||||
// Attempt to retrieve hosts, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/hosts", nil, http.StatusForbidden, &listHostsResponse{})
|
||||
|
||||
// Attempt to filter hosts using labels, should fail (label ID 6 is the builtin label "All Hosts")
|
||||
s.DoJSON("GET", "/api/latest/fleet/labels/6/hosts", nil, http.StatusForbidden, &listHostsResponse{})
|
||||
|
||||
// Attempt to delete hosts, should fail.
|
||||
s.DoJSON("DELETE", "/api/latest/fleet/hosts/1", nil, http.StatusForbidden, &deleteHostResponse{})
|
||||
|
||||
// Attempt to transfer host from global to a team, should allow.
|
||||
s.DoJSON("POST", "/api/latest/fleet/hosts/transfer", addHostsToTeamRequest{
|
||||
TeamID: &t1.ID,
|
||||
HostIDs: []uint{h1.ID},
|
||||
}, http.StatusOK, &addHostsToTeamResponse{})
|
||||
|
||||
// Attempt to create a label, should allow.
|
||||
clr := createLabelResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/labels", createLabelRequest{
|
||||
LabelPayload: fleet.LabelPayload{
|
||||
Name: ptr.String("foo"),
|
||||
Query: ptr.String("SELECT 1;"),
|
||||
},
|
||||
}, http.StatusOK, &clr)
|
||||
|
||||
// Attempt to modify a label, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/labels/%d", clr.Label.ID), modifyLabelRequest{
|
||||
ModifyLabelPayload: fleet.ModifyLabelPayload{
|
||||
Name: ptr.String("foo2"),
|
||||
},
|
||||
}, http.StatusOK, &modifyLabelResponse{})
|
||||
|
||||
// Attempt to get a label, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/labels/%d", clr.Label.ID), getLabelRequest{}, http.StatusForbidden, &getLabelResponse{})
|
||||
|
||||
// Attempt to list all labels, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/labels", listLabelsRequest{}, http.StatusForbidden, &listLabelsResponse{})
|
||||
|
||||
// Attempt to delete a label, should allow.
|
||||
s.DoJSON("DELETE", "/api/latest/fleet/labels/foo2", deleteLabelRequest{}, http.StatusOK, &deleteLabelResponse{})
|
||||
|
||||
// Attempt to list all software, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/software", listSoftwareRequest{}, http.StatusForbidden, &listSoftwareResponse{})
|
||||
s.DoJSON("GET", "/api/latest/fleet/software/count", countSoftwareRequest{}, http.StatusForbidden, &countSoftwareResponse{})
|
||||
|
||||
// Attempt to list a software, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/software/1", getSoftwareRequest{}, http.StatusForbidden, &getSoftwareResponse{})
|
||||
|
||||
// Attempt to read app config, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/config", nil, http.StatusForbidden, &appConfigResponse{})
|
||||
|
||||
// Attempt to write app config, should allow.
|
||||
acr = appConfigResponse{}
|
||||
s.DoJSON("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"webhook_settings": {
|
||||
"vulnerabilities_webhook": {
|
||||
"enable_vulnerabilities_webhook": true,
|
||||
"destination_url": "https://foobar.example.com"
|
||||
}
|
||||
}
|
||||
}`), http.StatusOK, &acr)
|
||||
require.True(t, acr.AppConfig.WebhookSettings.VulnerabilitiesWebhook.Enable)
|
||||
require.Equal(t, "https://foobar.example.com", acr.AppConfig.WebhookSettings.VulnerabilitiesWebhook.DestinationURL)
|
||||
|
||||
// Attempt to run live queries synchronously, should fail.
|
||||
// TODO(lucas): This is a bug, the synchronous live query API should return 403 but currently returns 200.
|
||||
// It doesn't run the query but incorrectly returns a 200.
|
||||
s.DoJSON("GET", "/api/latest/fleet/queries/run", runLiveQueryRequest{
|
||||
HostIDs: []uint{h1.ID},
|
||||
QueryIDs: []uint{q1.ID},
|
||||
}, http.StatusOK, &runLiveQueryResponse{})
|
||||
|
||||
// Attempt to run live queries asynchronously (new unsaved query), should fail.
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run", createDistributedQueryCampaignRequest{
|
||||
QuerySQL: "SELECT * FROM time;",
|
||||
Selected: fleet.HostTargets{
|
||||
HostIDs: []uint{h1.ID},
|
||||
},
|
||||
}, http.StatusForbidden, &runLiveQueryResponse{})
|
||||
|
||||
// Attempt to run live queries asynchronously (saved query), should fail.
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/run", createDistributedQueryCampaignRequest{
|
||||
QueryID: ptr.Uint(q1.ID),
|
||||
Selected: fleet.HostTargets{
|
||||
HostIDs: []uint{h1.ID},
|
||||
},
|
||||
}, http.StatusForbidden, &runLiveQueryResponse{})
|
||||
|
||||
// Attempt to create queries, should allow.
|
||||
cqr := createQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", createQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo4"),
|
||||
Query: ptr.String("SELECT * from osquery_info;"),
|
||||
},
|
||||
}, http.StatusOK, &cqr)
|
||||
cqr2 := createQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", createQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo5"),
|
||||
Query: ptr.String("SELECT * from os_version;"),
|
||||
},
|
||||
}, http.StatusOK, &cqr2)
|
||||
cqr3 := createQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", createQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo6"),
|
||||
Query: ptr.String("SELECT * from processes;"),
|
||||
},
|
||||
}, http.StatusOK, &cqr3)
|
||||
cqr4 := createQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", createQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo7"),
|
||||
Query: ptr.String("SELECT * from managed_policies;"),
|
||||
},
|
||||
}, http.StatusOK, &cqr4)
|
||||
|
||||
// Attempt to edit queries, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", cqr.Query.ID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo4"),
|
||||
Query: ptr.String("SELECT * FROM system_info;"),
|
||||
},
|
||||
}, http.StatusOK, &modifyQueryResponse{})
|
||||
|
||||
// Attempt to view a query, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d", cqr.Query.ID), getQueryRequest{}, http.StatusForbidden, &getQueryResponse{})
|
||||
|
||||
// Attempt to list all queries, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/queries", listQueriesRequest{}, http.StatusForbidden, &listQueriesResponse{})
|
||||
|
||||
// Attempt to delete queries, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/queries/id/%d", cqr.Query.ID), deleteQueryByIDRequest{}, http.StatusOK, &deleteQueryByIDResponse{})
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries/delete", deleteQueriesRequest{IDs: []uint{cqr2.Query.ID}}, http.StatusOK, &deleteQueriesResponse{})
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/queries/%s", cqr3.Query.Name), deleteQueryRequest{}, http.StatusOK, &deleteQueryResponse{})
|
||||
|
||||
// Attempt to add a query to the global schedule, should allow.
|
||||
sqr := scheduleQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/packs/schedule", scheduleQueryRequest{
|
||||
PackID: globalPackID,
|
||||
QueryID: cqr4.Query.ID,
|
||||
Interval: 60,
|
||||
}, http.StatusOK, &sqr)
|
||||
|
||||
// Attempt to edit a scheduled query in the global schedule, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/packs/schedule/%d", sqr.Scheduled.ID), modifyScheduledQueryRequest{
|
||||
ScheduledQueryPayload: fleet.ScheduledQueryPayload{
|
||||
Interval: ptr.Uint(30),
|
||||
},
|
||||
}, http.StatusOK, &scheduleQueryResponse{})
|
||||
|
||||
// Attempt to remove a query from the global schedule, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/packs/schedule/%d", sqr.Scheduled.ID), deleteScheduledQueryRequest{}, http.StatusOK, &scheduleQueryResponse{})
|
||||
|
||||
// Attempt to read the global schedule, should allow.
|
||||
// This is an exception to the "write only" nature of gitops (packs can be viewed by gitops).
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusOK, &getGlobalScheduleResponse{})
|
||||
|
||||
// Attempt to create a pack, should allow.
|
||||
cpr := createPackResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/packs", createPackRequest{
|
||||
PackPayload: fleet.PackPayload{
|
||||
Name: ptr.String("foo8"),
|
||||
},
|
||||
}, http.StatusOK, &cpr)
|
||||
|
||||
// Attempt to edit a pack, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/packs/%d", cpr.Pack.ID), modifyPackRequest{
|
||||
PackPayload: fleet.PackPayload{
|
||||
Name: ptr.String("foo9"),
|
||||
},
|
||||
}, http.StatusOK, &modifyPackResponse{})
|
||||
|
||||
// Attempt to read a pack, should allow.
|
||||
// This is an exception to the "write only" nature of gitops (packs can be viewed by gitops).
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/packs/%d", cpr.Pack.ID), nil, http.StatusOK, &getPackResponse{})
|
||||
|
||||
// Attempt to delete a pack, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/packs/id/%d", cpr.Pack.ID), deletePackRequest{}, http.StatusOK, &deletePackResponse{})
|
||||
|
||||
// Attempt to create a global policy, should allow.
|
||||
gplr := globalPolicyResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies", globalPolicyRequest{
|
||||
Name: "foo9",
|
||||
Query: "SELECT * from plist;",
|
||||
}, http.StatusOK, &gplr)
|
||||
|
||||
// Attempt to edit a global policy, should allow.
|
||||
mgplr := modifyGlobalPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/policies/%d", gplr.Policy.ID), modifyGlobalPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Query: ptr.String("SELECT * from plist WHERE path = 'foo';"),
|
||||
},
|
||||
}, http.StatusOK, &mgplr)
|
||||
|
||||
// Attempt to read a global policy, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/policies/%d", gplr.Policy.ID), getPolicyByIDRequest{}, http.StatusForbidden, &getPolicyByIDResponse{})
|
||||
|
||||
// Attempt to delete a global policy, should allow.
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies/delete", deleteGlobalPoliciesRequest{
|
||||
IDs: []uint{gplr.Policy.ID},
|
||||
}, http.StatusOK, &deleteGlobalPoliciesResponse{})
|
||||
|
||||
// Attempt to create a team policy, should allow.
|
||||
tplr := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/team/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "foo10",
|
||||
Query: "SELECT * from file;",
|
||||
}, http.StatusOK, &tplr)
|
||||
|
||||
// Attempt to edit a team policy, should allow.
|
||||
mtplr := modifyTeamPolicyResponse{}
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, tplr.Policy.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Query: ptr.String("SELECT * from file WHERE path = 'foo';"),
|
||||
},
|
||||
}, http.StatusOK, &mtplr)
|
||||
|
||||
// Attempt to view a team policy, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/team/%d/policies/%d", t1.ID, tplr.Policy.ID), getTeamPolicyByIDRequest{}, http.StatusForbidden, &getTeamPolicyByIDResponse{})
|
||||
|
||||
// Attempt to delete a team policy, should allow.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/delete", t1.ID), deleteTeamPoliciesRequest{
|
||||
IDs: []uint{tplr.Policy.ID},
|
||||
}, http.StatusOK, &deleteTeamPoliciesResponse{})
|
||||
|
||||
// Attempt to create a user, should fail.
|
||||
s.DoJSON("POST", "/api/latest/fleet/users/admin", createUserRequest{
|
||||
UserPayload: fleet.UserPayload{
|
||||
Email: ptr.String("foo10@example.com"),
|
||||
Name: ptr.String("foo10"),
|
||||
GlobalRole: ptr.String("admin"),
|
||||
},
|
||||
}, http.StatusForbidden, &createUserResponse{})
|
||||
|
||||
// Attempt to modify a user, should fail.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/users/%d", admin.ID), modifyUserRequest{
|
||||
UserPayload: fleet.UserPayload{
|
||||
GlobalRole: ptr.String("observer"),
|
||||
},
|
||||
}, http.StatusForbidden, &modifyUserResponse{})
|
||||
|
||||
// Attempt to view a user, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/users/%d", admin.ID), getUserRequest{}, http.StatusForbidden, &getUserResponse{})
|
||||
|
||||
// Attempt to delete a user, should fail.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/users/%d", admin.ID), deleteUserRequest{}, http.StatusForbidden, &deleteUserResponse{})
|
||||
|
||||
// Attempt to add users to team, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t1.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *maintainer,
|
||||
Role: "admin",
|
||||
},
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to delete users from team, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t1.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *maintainer,
|
||||
Role: "admin",
|
||||
},
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to create a team, should allow.
|
||||
tr := teamResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/teams", createTeamRequest{
|
||||
TeamPayload: fleet.TeamPayload{
|
||||
Name: ptr.String("foo11"),
|
||||
},
|
||||
}, http.StatusOK, &tr)
|
||||
|
||||
// Attempt to edit a team, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", tr.Team.ID), modifyTeamRequest{
|
||||
TeamPayload: fleet.TeamPayload{
|
||||
Name: ptr.String("foo12"),
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to edit a team's agent options, should allow.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", tr.Team.ID), json.RawMessage(`{
|
||||
"config": {
|
||||
"options": {
|
||||
"aws_debug": true
|
||||
}
|
||||
}
|
||||
}`), http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to view a team, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d", tr.Team.ID), getTeamRequest{}, http.StatusForbidden, &teamResponse{})
|
||||
|
||||
// Attempt to delete a team, should allow.
|
||||
dtr := deleteTeamResponse{}
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d", tr.Team.ID), deleteTeamRequest{}, http.StatusOK, &dtr)
|
||||
|
||||
// Attempt to create/edit enroll secrets, should allow.
|
||||
s.DoJSON("POST", "/api/latest/fleet/spec/enroll_secret", applyEnrollSecretSpecRequest{
|
||||
Spec: &fleet.EnrollSecretSpec{
|
||||
Secrets: []*fleet.EnrollSecret{
|
||||
{
|
||||
Secret: "foo400",
|
||||
TeamID: nil,
|
||||
},
|
||||
{
|
||||
Secret: "foo500",
|
||||
TeamID: ptr.Uint(t1.ID),
|
||||
},
|
||||
},
|
||||
},
|
||||
}, http.StatusOK, &applyEnrollSecretSpecResponse{})
|
||||
|
||||
// Attempt to get enroll secrets, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/spec/enroll_secret", nil, http.StatusForbidden, &getEnrollSecretSpecResponse{})
|
||||
|
||||
// Attempt to get team enroll secret, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/secrets", t1.ID), teamEnrollSecretsRequest{}, http.StatusForbidden, &teamEnrollSecretsResponse{})
|
||||
|
||||
// Attempt to list carved files, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/carves", listCarvesRequest{}, http.StatusForbidden, &listCarvesResponse{})
|
||||
|
||||
// Attempt to get a carved file, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/carves/%d", carveID), listCarvesRequest{}, http.StatusForbidden, &listCarvesResponse{})
|
||||
|
||||
//
|
||||
// Start running permission tests with user gitops2 (which is a GitOps use for team t1).
|
||||
//
|
||||
|
||||
s.setTokenForTest(t, "gitops2@example.com", test.GoodPassword)
|
||||
|
||||
// Attempt to create queries, should allow.
|
||||
tcqr := createQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/queries", createQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo600"),
|
||||
Query: ptr.String("SELECT * from orbit_info;"),
|
||||
},
|
||||
}, http.StatusOK, &tcqr)
|
||||
|
||||
// Attempt to edit own query, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", tcqr.Query.ID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo4"),
|
||||
Query: ptr.String("SELECT * FROM system_info;"),
|
||||
},
|
||||
}, http.StatusOK, &modifyQueryResponse{})
|
||||
|
||||
// Attempt to delete own query, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/queries/id/%d", tcqr.Query.ID), deleteQueryByIDRequest{}, http.StatusOK, &deleteQueryByIDResponse{})
|
||||
|
||||
// Attempt to edit query created by somebody else, should fail.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/queries/%d", cqr4.Query.ID), modifyQueryRequest{
|
||||
QueryPayload: fleet.QueryPayload{
|
||||
Name: ptr.String("foo4"),
|
||||
Query: ptr.String("SELECT * FROM system_info;"),
|
||||
},
|
||||
}, http.StatusForbidden, &modifyQueryResponse{})
|
||||
|
||||
// Attempt to delete query created by somebody else, should fail.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/queries/id/%d", cqr4.Query.ID), deleteQueryByIDRequest{}, http.StatusForbidden, &deleteQueryByIDResponse{})
|
||||
|
||||
// Attempt to read the global schedule, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusForbidden, &getGlobalScheduleResponse{})
|
||||
|
||||
// Attempt to read the team's schedule, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/schedule", t1.ID), getTeamScheduleRequest{}, http.StatusForbidden, &getTeamScheduleResponse{})
|
||||
|
||||
// Attempt to read other team's schedule, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/teams/%d/schedule", t2.ID), getTeamScheduleRequest{}, http.StatusForbidden, &getTeamScheduleResponse{})
|
||||
|
||||
// Attempt to add a query to the global schedule, should fail.
|
||||
tsqr := scheduleQueryResponse{}
|
||||
s.DoJSON("POST", "/api/latest/fleet/packs/schedule", scheduleQueryRequest{
|
||||
PackID: globalPackID,
|
||||
QueryID: cqr4.Query.ID,
|
||||
Interval: 60,
|
||||
}, http.StatusForbidden, &tsqr)
|
||||
|
||||
// Attempt to add a query to the team's schedule, should allow.
|
||||
ttsqr := teamScheduleQueryResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/schedule", t1.ID), teamScheduleQueryRequest{
|
||||
ScheduledQueryPayload: fleet.ScheduledQueryPayload{
|
||||
QueryID: ptr.Uint(cqr4.Query.ID),
|
||||
Interval: ptr.Uint(60),
|
||||
},
|
||||
}, http.StatusOK, &ttsqr)
|
||||
|
||||
// Attempt to remove a query from the team's schedule, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d/schedule/%d", t1.ID, ttsqr.Scheduled.ID), deleteTeamScheduleRequest{}, http.StatusOK, &deleteTeamScheduleResponse{})
|
||||
|
||||
// Attempt to read the global schedule, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusForbidden, &getGlobalScheduleResponse{})
|
||||
|
||||
// Attempt to read a global policy, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/policies/%d", gp2.ID), getPolicyByIDRequest{}, http.StatusForbidden, &getPolicyByIDResponse{})
|
||||
|
||||
// Attempt to delete a global policy, should fail.
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies/delete", deleteGlobalPoliciesRequest{
|
||||
IDs: []uint{gp2.ID},
|
||||
}, http.StatusForbidden, &deleteGlobalPoliciesResponse{})
|
||||
|
||||
// Attempt to create a team policy, should allow.
|
||||
ttplr := teamPolicyResponse{}
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/team/%d/policies", t1.ID), teamPolicyRequest{
|
||||
Name: "foo1000",
|
||||
Query: "SELECT * from file;",
|
||||
}, http.StatusOK, &ttplr)
|
||||
|
||||
// Attempt to edit a team policy, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t1.ID, ttplr.Policy.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Query: ptr.String("SELECT * from file WHERE path = 'foobar';"),
|
||||
},
|
||||
}, http.StatusOK, &modifyTeamPolicyResponse{})
|
||||
|
||||
// Attempt to edit another team's policy, should fail.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/%d", t2.ID, t2p.ID), modifyTeamPolicyRequest{
|
||||
ModifyPolicyPayload: fleet.ModifyPolicyPayload{
|
||||
Query: ptr.String("SELECT * from file WHERE path = 'foobar';"),
|
||||
},
|
||||
}, http.StatusForbidden, &modifyTeamPolicyResponse{})
|
||||
|
||||
// Attempt to view a team policy, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/team/%d/policies/%d", t1.ID, ttplr.Policy.ID), getTeamPolicyByIDRequest{}, http.StatusForbidden, &getTeamPolicyByIDResponse{})
|
||||
|
||||
// Attempt to view another team's policy, should fail.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/team/%d/policies/%d", t2.ID, t2p.ID), getTeamPolicyByIDRequest{}, http.StatusForbidden, &getTeamPolicyByIDResponse{})
|
||||
|
||||
// Attempt to delete a team policy, should allow.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/delete", t1.ID), deleteTeamPoliciesRequest{
|
||||
IDs: []uint{ttplr.Policy.ID},
|
||||
}, http.StatusOK, &deleteTeamPoliciesResponse{})
|
||||
|
||||
// Attempt to edit own team, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", t1.ID), modifyTeamRequest{
|
||||
TeamPayload: fleet.TeamPayload{
|
||||
Name: ptr.String("foo123456"),
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to edit another team, should fail.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d", t2.ID), modifyTeamRequest{
|
||||
TeamPayload: fleet.TeamPayload{
|
||||
Name: ptr.String("foo123456"),
|
||||
},
|
||||
}, http.StatusForbidden, &teamResponse{})
|
||||
|
||||
// Attempt to edit own team's agent options, should allow.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", t1.ID), json.RawMessage(`{
|
||||
"config": {
|
||||
"options": {
|
||||
"aws_debug": true
|
||||
}
|
||||
}
|
||||
}`), http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to edit another team's agent options, should fail.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/agent_options", t2.ID), json.RawMessage(`{
|
||||
"config": {
|
||||
"options": {
|
||||
"aws_debug": true
|
||||
}
|
||||
}
|
||||
}`), http.StatusForbidden, &teamResponse{})
|
||||
|
||||
// Attempt to add users from team it owns to another team it owns, should allow.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t3.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *u3,
|
||||
Role: "maintainer",
|
||||
},
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to delete users from team it owns, should allow.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t3.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *u3,
|
||||
},
|
||||
},
|
||||
}, http.StatusOK, &teamResponse{})
|
||||
|
||||
// Attempt to add users to another team it doesn't own, should fail.
|
||||
s.DoJSON("PATCH", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t2.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *u3,
|
||||
Role: "maintainer",
|
||||
},
|
||||
},
|
||||
}, http.StatusForbidden, &teamResponse{})
|
||||
|
||||
// Attempt to delete users from team it doesn't own, should fail.
|
||||
s.DoJSON("DELETE", fmt.Sprintf("/api/latest/fleet/teams/%d/users", t2.ID), modifyTeamUsersRequest{
|
||||
Users: []fleet.TeamUser{
|
||||
{
|
||||
User: *u2,
|
||||
},
|
||||
},
|
||||
}, http.StatusForbidden, &teamResponse{})
|
||||
}
|
||||
|
||||
func (s *integrationEnterpriseTestSuite) setTokenForTest(t *testing.T, email, password string) {
|
||||
oldToken := s.token
|
||||
t.Cleanup(func() {
|
||||
s.token = oldToken
|
||||
})
|
||||
|
||||
s.token = s.getCachedUserToken(email, password)
|
||||
}
|
||||
|
@ -198,9 +198,9 @@ func (s *integrationMDMTestSuite) TearDownTest() {
|
||||
t := s.T()
|
||||
ctx := context.Background()
|
||||
|
||||
s.token = s.getTestAdminToken()
|
||||
appCfg := s.getConfig()
|
||||
if appCfg.MDM.MacOSSettings.EnableDiskEncryption {
|
||||
s.token = s.getTestAdminToken()
|
||||
// ensure global disk encryption is disabled on exit
|
||||
s.Do("PATCH", "/api/latest/fleet/config", json.RawMessage(`{
|
||||
"mdm": { "macos_settings": { "enable_disk_encryption": false } }
|
||||
@ -2739,7 +2739,8 @@ func (s *integrationMDMTestSuite) assertConfigProfilesByIdentifier(teamID *uint,
|
||||
// generates the body and headers part of a multipart request ready to be
|
||||
// used via s.DoRawWithHeaders to POST /api/_version_/fleet/mdm/apple/profiles.
|
||||
func generateNewProfileMultipartRequest(t *testing.T, tmID *uint,
|
||||
fileName string, fileContent []byte, token string) (*bytes.Buffer, map[string]string) {
|
||||
fileName string, fileContent []byte, token string,
|
||||
) (*bytes.Buffer, map[string]string) {
|
||||
var body bytes.Buffer
|
||||
|
||||
writer := multipart.NewWriter(&body)
|
||||
|
@ -222,8 +222,13 @@ func modifyQueryEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
|
||||
}
|
||||
|
||||
func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPayload) (*fleet.Query, error) {
|
||||
// First make sure the user can read queries
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRead); err != nil {
|
||||
query, err := svc.ds.Query(ctx, id)
|
||||
if err != nil {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := svc.authz.Authorize(ctx, query, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@ -233,16 +238,6 @@ func (svc *Service) ModifyQuery(ctx context.Context, id uint, p fleet.QueryPaylo
|
||||
})
|
||||
}
|
||||
|
||||
query, err := svc.ds.Query(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Then we make sure they can modify them
|
||||
if err := svc.authz.Authorize(ctx, query, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if p.Name != nil {
|
||||
query.Name = *p.Name
|
||||
}
|
||||
@ -303,17 +298,12 @@ func deleteQueryEndpoint(ctx context.Context, request interface{}, svc fleet.Ser
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteQuery(ctx context.Context, name string) error {
|
||||
// First make sure the user can read queries
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query, err := svc.ds.QueryByName(ctx, name)
|
||||
if err != nil {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return err
|
||||
}
|
||||
|
||||
// Then we make sure they can modify them
|
||||
if err := svc.authz.Authorize(ctx, query, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -358,17 +348,12 @@ func deleteQueryByIDEndpoint(ctx context.Context, request interface{}, svc fleet
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteQueryByID(ctx context.Context, id uint) error {
|
||||
// First make sure the user can read queries
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRead); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
query, err := svc.ds.Query(ctx, id)
|
||||
if err != nil {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return ctxerr.Wrap(ctx, err, "lookup query by ID")
|
||||
}
|
||||
|
||||
// Then we make sure they can modify them
|
||||
if err := svc.authz.Authorize(ctx, query, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -414,18 +399,13 @@ func deleteQueriesEndpoint(ctx context.Context, request interface{}, svc fleet.S
|
||||
}
|
||||
|
||||
func (svc *Service) DeleteQueries(ctx context.Context, ids []uint) (uint, error) {
|
||||
// First make sure the user can read queries
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionRead); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
for _, id := range ids {
|
||||
query, err := svc.ds.Query(ctx, id)
|
||||
if err != nil {
|
||||
setAuthCheckedOnPreAuthErr(ctx)
|
||||
return 0, ctxerr.Wrap(ctx, err, "lookup query by ID")
|
||||
}
|
||||
|
||||
// Then we make sure they can modify them
|
||||
if err := svc.authz.Authorize(ctx, query, fleet.ActionWrite); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -472,7 +452,6 @@ func applyQuerySpecsEndpoint(ctx context.Context, request interface{}, svc fleet
|
||||
}
|
||||
|
||||
func (svc *Service) ApplyQuerySpecs(ctx context.Context, specs []*fleet.QuerySpec) error {
|
||||
// check that the user can create queries
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Query{}, fleet.ActionWrite); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -133,10 +133,6 @@ func (svc *Service) VulnerabilitiesConfig(ctx context.Context) (*fleet.Vulnerabi
|
||||
}
|
||||
|
||||
func (svc *Service) LoggingConfig(ctx context.Context) (*fleet.Logging, error) {
|
||||
if err := svc.authz.Authorize(ctx, &fleet.AppConfig{}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conf := svc.config
|
||||
logging := &fleet.Logging{
|
||||
Debug: conf.Logging.Debug,
|
||||
|
@ -414,7 +414,8 @@ func TestService_EmailConfig(t *testing.T) {
|
||||
Config: fleet.SESConfig{
|
||||
Region: "us-east-1",
|
||||
SourceARN: "qux",
|
||||
}},
|
||||
},
|
||||
},
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
@ -429,15 +430,17 @@ func TestService_EmailConfig(t *testing.T) {
|
||||
wantErr: assert.NoError,
|
||||
},
|
||||
{
|
||||
name: "no configured email backend should return nil",
|
||||
name: "accessing without roles should return forbidden",
|
||||
fields: fields{
|
||||
config: config.TestConfig(),
|
||||
config: testSESPluginConfig(),
|
||||
},
|
||||
args: args{
|
||||
ctx: test.UserContext(context.Background(), test.UserNoRoles),
|
||||
},
|
||||
want: nil,
|
||||
wantErr: assert.NoError,
|
||||
want: nil,
|
||||
wantErr: func(tt assert.TestingT, err error, i ...interface{}) bool {
|
||||
return assert.EqualError(tt, err, "forbidden")
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
|
@ -35,7 +35,7 @@ func (svc *Service) NewUser(ctx context.Context, p fleet.UserPayload) (*fleet.Us
|
||||
if license == nil {
|
||||
return nil, ctxerr.New(ctx, "license not found")
|
||||
}
|
||||
if err := fleet.ValidateRoleForLicense(p.GlobalRole, p.Teams, *license); err != nil {
|
||||
if err := fleet.ValidateUserRoles(true, p, *license); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate role")
|
||||
}
|
||||
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
@ -201,14 +202,6 @@ func deleteTeamPoliciesEndpoint(ctx context.Context, request interface{}, svc fl
|
||||
}
|
||||
|
||||
func (svc Service) DeleteTeamPolicies(ctx context.Context, teamID uint, ids []uint) ([]uint, error) {
|
||||
// First check if authorized to read policies
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: ptr.Uint(teamID),
|
||||
},
|
||||
}, fleet.ActionRead); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(ids) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
@ -217,7 +210,6 @@ func (svc Service) DeleteTeamPolicies(ctx context.Context, teamID uint, ids []ui
|
||||
return nil, ctxerr.Wrap(ctx, err, "getting policies by ID")
|
||||
}
|
||||
|
||||
// Then check if authorized to write policies
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: ptr.Uint(teamID),
|
||||
@ -289,22 +281,29 @@ func (svc *Service) ModifyTeamPolicy(ctx context.Context, teamID uint, id uint,
|
||||
return svc.modifyPolicy(ctx, &teamID, id, p)
|
||||
}
|
||||
|
||||
func checkTeamID(teamID *uint, policy *fleet.Policy) bool {
|
||||
return policy != nil && reflect.DeepEqual(teamID, policy.TeamID)
|
||||
}
|
||||
|
||||
func (svc *Service) modifyPolicy(ctx context.Context, teamID *uint, id uint, p fleet.ModifyPolicyPayload) (*fleet.Policy, error) {
|
||||
// First make sure the user can read the policies.
|
||||
if err := svc.authz.Authorize(ctx, &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: teamID,
|
||||
},
|
||||
}, fleet.ActionRead); err != nil {
|
||||
}, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
policy, err := svc.ds.Policy(ctx, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Then we make sure they can modify the team's policies.
|
||||
if err := svc.authz.Authorize(ctx, policy, fleet.ActionWrite); err != nil {
|
||||
return nil, err
|
||||
|
||||
if ok := checkTeamID(teamID, policy); !ok {
|
||||
return nil, ctxerr.Wrap(ctx, &fleet.BadRequestError{
|
||||
Message: "policy does not belong to team/global",
|
||||
InternalErr: fmt.Errorf("teamID: %+v, policy: %+v", teamID, policy),
|
||||
})
|
||||
}
|
||||
|
||||
if err := p.Verify(); err != nil {
|
||||
|
@ -4,9 +4,11 @@ import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
authz_ctx "github.com/fleetdm/fleet/v4/server/contexts/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/mock"
|
||||
@ -367,3 +369,23 @@ func TestApplyTeamSpecs(t *testing.T) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestApplyTeamSpecsErrorInTeamByName tests that an error in ds.TeamByName will
|
||||
// result in a proper error returned (instead of the authorization check missing error).
|
||||
func TestApplyTeamSpecsErrorInTeamByName(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
license := &fleet.LicenseInfo{Tier: fleet.TierPremium, Expiration: time.Now().Add(24 * time.Hour)}
|
||||
svc, ctx := newTestService(t, ds, nil, nil, &TestServerOpts{License: license, SkipCreateTestUsers: true})
|
||||
user := &fleet.User{GlobalRole: ptr.String(fleet.RoleAdmin)}
|
||||
ctx = viewer.NewContext(ctx, viewer.Viewer{User: user})
|
||||
ds.TeamByNameFunc = func(ctx context.Context, name string) (*fleet.Team, error) {
|
||||
return nil, errors.New("unknown error")
|
||||
}
|
||||
authzctx := &authz_ctx.AuthorizationContext{}
|
||||
ctx = authz_ctx.NewContext(ctx, authzctx)
|
||||
err := svc.ApplyTeamSpecs(ctx, []*fleet.TeamSpec{{Name: "Foo"}}, fleet.ApplySpecOptions{})
|
||||
require.Error(t, err)
|
||||
az, ok := authz_ctx.FromContext(ctx)
|
||||
require.True(t, ok)
|
||||
require.True(t, az.Checked())
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -59,7 +60,11 @@ type withServer struct {
|
||||
users map[string]fleet.User
|
||||
token string
|
||||
cachedAdminToken string
|
||||
lq *live_query_mock.MockLiveQuery
|
||||
|
||||
cachedTokensMu sync.Mutex
|
||||
cachedTokens map[string]string // email -> auth token
|
||||
|
||||
lq *live_query_mock.MockLiveQuery
|
||||
}
|
||||
|
||||
func (ts *withServer) SetupSuite(dbName string) {
|
||||
@ -75,6 +80,7 @@ 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
|
||||
}
|
||||
@ -215,6 +221,20 @@ func (ts *withServer) getTestAdminToken() string {
|
||||
return ts.cachedAdminToken
|
||||
}
|
||||
|
||||
// getCachedUserToken returns the cached auth token for the given test user email.
|
||||
// If it's not found, then a login request is performed and the token cached.
|
||||
func (ts *withServer) getCachedUserToken(email, password string) string {
|
||||
ts.cachedTokensMu.Lock()
|
||||
defer ts.cachedTokensMu.Unlock()
|
||||
|
||||
token, ok := ts.cachedTokens[email]
|
||||
if !ok {
|
||||
token = ts.getTestToken(email, password)
|
||||
ts.cachedTokens[email] = token
|
||||
}
|
||||
return token
|
||||
}
|
||||
|
||||
func (ts *withServer) getTestToken(email string, password string) string {
|
||||
params := loginRequest{
|
||||
Email: email,
|
||||
|
@ -303,7 +303,7 @@ func (svc *Service) ModifyUser(ctx context.Context, userID uint, p fleet.UserPay
|
||||
if license == nil {
|
||||
return nil, ctxerr.New(ctx, "license not found")
|
||||
}
|
||||
if err := fleet.ValidateRoleForLicense(p.GlobalRole, p.Teams, *license); err != nil {
|
||||
if err := fleet.ValidateUserRoles(false, p, *license); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate role")
|
||||
}
|
||||
}
|
||||
|
@ -113,4 +113,26 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
UserGitOps = &fleet.User{
|
||||
ID: 15,
|
||||
GlobalRole: ptr.String(fleet.RoleGitOps),
|
||||
}
|
||||
UserTeamGitOpsTeam1 = &fleet.User{
|
||||
ID: 16,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleGitOps,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamGitOpsTeam2 = &fleet.User{
|
||||
ID: 17,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleGitOps,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user