mirror of
https://github.com/empayre/fleet.git
synced 2024-11-06 00:45:19 +00:00
New observer_plus
role (#10675)
#8593 This PR adds a new role `observer_plus` to Fleet. (The `GitOps` role will be added on a separate PR.) - [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
1516caad46
commit
a756614c1a
1
changes/8593-observer_plus
Normal file
1
changes/8593-observer_plus
Normal file
@ -0,0 +1 @@
|
||||
* Add `observer_plus` user role to Fleet. Observers+ are observers that can run any live query.
|
@ -3056,7 +3056,7 @@ Fleet will attempt to parse SAML custom attributes with the following format:
|
||||
- `FLEET_JIT_USER_ROLE_GLOBAL`: Specifies the global role to use when creating the user.
|
||||
- `FLEET_JIT_USER_ROLE_TEAM_<TEAM_ID>`: Specifies team role for team with ID `<TEAM_ID>` to use when creating the user.
|
||||
|
||||
Currently supported values for the above attributes are: `admin`, `maintainer` and `observer`.
|
||||
Currently supported values for the above attributes are: `admin`, `maintainer`, `observer` and `observer_plus`.
|
||||
SAML supports multi-valued attributes, Fleet will always use the last value.
|
||||
|
||||
NOTE: Setting both `FLEET_JIT_USER_ROLE_GLOBAL` and `FLEET_JIT_USER_ROLE_TEAM_<TEAM_ID>` will cause an error during login as Fleet users cannot be Global users and belong to teams.
|
||||
|
@ -6,49 +6,49 @@ Users with the Admin role receive all permissions.
|
||||
|
||||
## User permissions
|
||||
|
||||
| **Action** | 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 |
|
||||
| ------------------------------------------------------------------------------------------------------------------------------------------ | -------- | --------- | ---------- | ----- |
|
||||
| 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 +71,34 @@ 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 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 |
|
||||
| -------------------------------------------------------------------------------------------------------------------------------- | ------------- | -------------- | --------------- | ---------- |
|
||||
| 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 | ✅ | ✅ | ✅ | ✅ |
|
||||
|
||||
\* Applies only to [Fleet REST API](https://fleetdm.com/docs/using-fleet/rest-api)
|
||||
|
||||
|
@ -4303,7 +4303,7 @@ Returns a list of all queries in the Fleet instance.
|
||||
| name | string | body | **Required**. The name of the query. |
|
||||
| query | string | body | **Required**. The query in SQL syntax. |
|
||||
| description | string | body | The query's description. |
|
||||
| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). |
|
||||
| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -4356,7 +4356,7 @@ Returns the query specified by ID.
|
||||
| name | string | body | The name of the query. |
|
||||
| query | string | body | The query in SQL syntax. |
|
||||
| description | string | body | The query's description. |
|
||||
| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). |
|
||||
| observer_can_run | bool | body | Whether or not users with the `observer` role can run the query. In Fleet 4.0.0, 3 user roles were introduced (`admin`, `maintainer`, and `observer`). This field is only relevant for the `observer` role. The `observer_plus` role can run any query and is not limited by this flag (`observer_plus` role was added in Fleet 4.30.0). |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -6000,8 +6000,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`). 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`). 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, 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. |
|
||||
|
||||
#### Example
|
||||
|
||||
@ -6117,9 +6117,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`). 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, the `observer_plus` role was introduced. If `global_role` is specified, `teams` cannot be specified. |
|
||||
| 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`). 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, the `observer_plus` role was introduced. If `teams` is specified, `global_role` cannot be specified. |
|
||||
|
||||
#### Example
|
||||
|
||||
|
@ -18,8 +18,9 @@ write := "write"
|
||||
write_role := "write_role"
|
||||
change_password := "change_password"
|
||||
|
||||
# Query specific actions
|
||||
# Action used on object "targeted_query" used for running live queries.
|
||||
run := "run"
|
||||
# Action used on object "query" used for running "new" live queries.
|
||||
run_new := "run_new"
|
||||
|
||||
# MDM specific actions
|
||||
@ -29,6 +30,7 @@ mdm_command := "mdm_command"
|
||||
admin := "admin"
|
||||
maintainer := "maintainer"
|
||||
observer := "observer"
|
||||
observer_plus := "observer_plus"
|
||||
|
||||
# Default deny
|
||||
default allow = false
|
||||
@ -75,14 +77,14 @@ allow {
|
||||
allow {
|
||||
object.type == "team"
|
||||
object.id != 0
|
||||
team_role(subject, object.id) == [admin,maintainer,observer][_]
|
||||
team_role(subject, object.id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
# Global users can read all teams.
|
||||
allow {
|
||||
object.type == "team"
|
||||
object.id != 0
|
||||
subject.global_role == [admin, maintainer, observer][_]
|
||||
subject.global_role == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -212,24 +214,24 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Allow read for global observer
|
||||
# Allow read for global observer and observer_plus.
|
||||
allow {
|
||||
object.type == "host"
|
||||
subject.global_role = observer
|
||||
subject.global_role == [observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Allow read for matching team admin/maintainer/observer
|
||||
# Allow read for matching team admin/maintainer/observer/observer_plus.
|
||||
allow {
|
||||
object.type == "host"
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins and maintainers can write to hosts of their own team
|
||||
allow {
|
||||
object.type == "host"
|
||||
team_role(subject, object.team_id) == [admin,maintainer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -270,12 +272,7 @@ allow {
|
||||
# Global admins and maintainers can write queries
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == admin
|
||||
action == write
|
||||
}
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == maintainer
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
@ -295,29 +292,19 @@ allow {
|
||||
action == write
|
||||
}
|
||||
|
||||
# Global admins and maintainers can run any
|
||||
# Global admins, maintainers and observer_plus can run any query (saved and new).
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
subject.global_role == admin
|
||||
action = run
|
||||
}
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
subject.global_role == maintainer
|
||||
subject.global_role == [admin, maintainer, observer_plus][_]
|
||||
action = run
|
||||
}
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == admin
|
||||
action = run_new
|
||||
}
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == maintainer
|
||||
subject.global_role == [admin, maintainer, observer_plus][_]
|
||||
action = run_new
|
||||
}
|
||||
|
||||
# Team admin and maintainer running a non-observers_can_run query must have the targets
|
||||
# Team admin, maintainer and observer_plus running a non-observers_can_run query must have the targets
|
||||
# filtered to only teams that they maintain.
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
@ -326,34 +313,33 @@ allow {
|
||||
action == run
|
||||
|
||||
not is_null(object.host_targets.teams)
|
||||
ok_teams := { tmid | tmid := object.host_targets.teams[_]; team_role(subject, tmid) == [admin,maintainer][_] }
|
||||
ok_teams := { tmid | tmid := object.host_targets.teams[_]; team_role(subject, tmid) == [admin, maintainer, observer_plus][_] }
|
||||
count(ok_teams) == count(object.host_targets.teams)
|
||||
}
|
||||
|
||||
# Team admin and maintainer running a non-observers_can_run query when no target teams
|
||||
# are specified.
|
||||
# Team admin, maintainer and observer_plus running a non-observers_can_run query when no target teams are specified.
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
object.observer_can_run == false
|
||||
is_null(subject.global_role)
|
||||
action == run
|
||||
|
||||
# If role is admin or maintainer on any team
|
||||
team_role(subject, subject.teams[_].id) == [admin,maintainer][_]
|
||||
# If role is admin, maintainer or observer_plus on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus][_]
|
||||
|
||||
# and there are no team targets
|
||||
is_null(object.host_targets.teams)
|
||||
}
|
||||
|
||||
# Team admin and maintainer can run a new query
|
||||
# Team admin, maintainer and observer_plus can run a new query.
|
||||
allow {
|
||||
object.type == "query"
|
||||
# If role is admin or maintainer on any team
|
||||
team_role(subject, subject.teams[_].id) == [admin,maintainer][_]
|
||||
# If role is admin, maintainer or observer_plus on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus][_]
|
||||
action == run_new
|
||||
}
|
||||
|
||||
# Observers can run only if observers_can_run
|
||||
# Global observers can run only if observers_can_run.
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
object.observer_can_run == true
|
||||
@ -361,7 +347,7 @@ allow {
|
||||
action = run
|
||||
}
|
||||
|
||||
# Team observer running a observers_can_run query must have the targets
|
||||
# Team admin, maintainer, observer_plus and observer running a observers_can_run query must have the targets
|
||||
# filtered to only teams that they observe.
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
@ -370,20 +356,19 @@ allow {
|
||||
action == run
|
||||
|
||||
not is_null(object.host_targets.teams)
|
||||
ok_teams := { tmid | tmid := object.host_targets.teams[_]; team_role(subject, tmid) == [admin,maintainer,observer][_] }
|
||||
ok_teams := { tmid | tmid := object.host_targets.teams[_]; team_role(subject, tmid) == [admin, maintainer, observer_plus, observer][_] }
|
||||
count(ok_teams) == count(object.host_targets.teams)
|
||||
}
|
||||
|
||||
# Team observer running a observers_can_run query and there are no
|
||||
# target teams.
|
||||
# Team admin, maintainer, observer_plus and observer running a observers_can_run query and there are no target teams.
|
||||
allow {
|
||||
object.type == "targeted_query"
|
||||
object.observer_can_run == true
|
||||
is_null(subject.global_role)
|
||||
action == run
|
||||
|
||||
# If role is admin, maintainer or observer on any team
|
||||
team_role(subject, subject.teams[_].id) == [admin,maintainer,observer][_]
|
||||
# If role is admin, maintainer, observer_plus or observer on any team.
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer_plus, observer][_]
|
||||
|
||||
# and there are no team targets
|
||||
is_null(object.host_targets.teams)
|
||||
@ -420,13 +405,13 @@ allow {
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins, maintainers and observers can read their team's pack.
|
||||
# Team admins, maintainers, observers and observer_plus can read their team's pack.
|
||||
#
|
||||
# NOTE: Action "read" on a team's pack includes listing its scheduled queries.
|
||||
allow {
|
||||
object.type == "pack"
|
||||
not is_null(object.pack_team_id)
|
||||
team_role(subject, object.pack_team_id) == [admin, maintainer, observer][_]
|
||||
team_role(subject, object.pack_team_id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -464,10 +449,10 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Global Observer can read any policies
|
||||
# Global observer and observer_plus can read any policies
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == observer
|
||||
subject.global_role == [observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -479,19 +464,19 @@ allow {
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# Team admin, maintainers and observers can read global policies
|
||||
# Team admin, maintainers, observers and observer_plus can read global policies.
|
||||
allow {
|
||||
is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
team_role(subject, subject.teams[_].id) == [admin,maintainer,observer][_]
|
||||
team_role(subject, subject.teams[_].id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team Observer can read policies for their teams
|
||||
# Team observer and observer_plus can read policies for their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "policy"
|
||||
team_role(subject, object.team_id) == observer
|
||||
team_role(subject, object.team_id) == [observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -502,7 +487,7 @@ allow {
|
||||
# Global users can read all software.
|
||||
allow {
|
||||
object.type == "software_inventory"
|
||||
subject.global_role == [admin, maintainer, observer][_]
|
||||
subject.global_role == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -510,7 +495,7 @@ allow {
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "software_inventory"
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
@ -578,18 +563,18 @@ allow {
|
||||
action == write
|
||||
}
|
||||
|
||||
# Admin, maintainer and observer can read MDM Apple commands.
|
||||
# Global admins, maintainers, observers and observer_plus can read MDM Apple commands.
|
||||
allow {
|
||||
object.type == "mdm_apple_command"
|
||||
subject.global_role == [admin, maintainer, observer][_]
|
||||
subject.global_role == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
# Team admins, maintainers and observers can read MDM Apple commands on hosts of their teams.
|
||||
# Team admins, maintainers, observers and observer_plus can read MDM Apple commands on hosts of their teams.
|
||||
allow {
|
||||
not is_null(object.team_id)
|
||||
object.type == "mdm_apple_command"
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer, observer_plus][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,9 @@ func TestAuthorizeAppConfig(t *testing.T) {
|
||||
|
||||
{user: test.UserObserver, object: config, action: read, allow: true},
|
||||
{user: test.UserObserver, object: config, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: config, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: config, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -90,6 +93,11 @@ func TestAuthorizeSession(t *testing.T) {
|
||||
{user: test.UserObserver, object: session, action: write, allow: false},
|
||||
{user: test.UserObserver, object: &fleet.Session{UserID: test.UserObserver.ID}, action: read, allow: true},
|
||||
{user: test.UserObserver, object: &fleet.Session{UserID: test.UserObserver.ID}, action: write, allow: true},
|
||||
|
||||
{user: test.UserObserverPlus, object: session, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: session, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: &fleet.Session{UserID: test.UserObserverPlus.ID}, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: &fleet.Session{UserID: test.UserObserverPlus.ID}, action: write, allow: true},
|
||||
})
|
||||
}
|
||||
|
||||
@ -167,6 +175,19 @@ func TestAuthorizeUser(t *testing.T) {
|
||||
{user: test.UserObserver, object: test.UserObserver, action: writeRole, allow: false},
|
||||
{user: test.UserObserver, object: test.UserObserver, action: changePwd, allow: true},
|
||||
|
||||
// Global observers+ cannot read/write users.
|
||||
{user: test.UserObserverPlus, object: user, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: user, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: user, action: writeRole, allow: false},
|
||||
{user: test.UserObserverPlus, object: user, action: changePwd, allow: false},
|
||||
// Global observers+ cannot create users.
|
||||
{user: test.UserObserverPlus, object: newUser, action: write, allow: false},
|
||||
// Global observers+ can read/write itself (besides roles).
|
||||
{user: test.UserObserverPlus, object: test.UserObserverPlus, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: test.UserObserverPlus, action: write, allow: true},
|
||||
{user: test.UserObserverPlus, object: test.UserObserverPlus, action: writeRole, allow: false},
|
||||
{user: test.UserObserverPlus, object: test.UserObserverPlus, action: changePwd, allow: true},
|
||||
|
||||
// Team admins cannot read/write global users.
|
||||
{user: teamAdmin, object: user, action: read, allow: false},
|
||||
{user: teamAdmin, object: user, action: write, allow: false},
|
||||
@ -208,6 +229,9 @@ func TestAuthorizeInvite(t *testing.T) {
|
||||
|
||||
{user: test.UserObserver, object: invite, action: read, allow: false},
|
||||
{user: test.UserObserver, object: invite, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: invite, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: invite, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -246,6 +270,10 @@ func TestAuthorizeEnrollSecret(t *testing.T) {
|
||||
{user: test.UserObserver, object: globalSecret, action: write, allow: false},
|
||||
{user: test.UserObserver, object: teamSecret, action: read, allow: false},
|
||||
{user: test.UserObserver, object: teamSecret, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: globalSecret, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: globalSecret, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: teamSecret, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: teamSecret, action: write, allow: false},
|
||||
{user: teamObserver, object: globalSecret, action: read, allow: false},
|
||||
{user: teamObserver, object: globalSecret, action: write, allow: false},
|
||||
{user: teamObserver, object: teamSecret, action: read, allow: false},
|
||||
@ -296,6 +324,9 @@ func TestAuthorizeTeam(t *testing.T) {
|
||||
|
||||
{user: test.UserObserver, object: team, action: read, allow: true},
|
||||
{user: test.UserObserver, object: team, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: team, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: team, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -318,6 +349,9 @@ func TestAuthorizeLabel(t *testing.T) {
|
||||
|
||||
{user: test.UserObserver, object: label, action: read, allow: true},
|
||||
{user: test.UserObserver, object: label, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: label, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: label, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -379,6 +413,18 @@ func TestAuthorizeHost(t *testing.T) {
|
||||
{user: test.UserObserver, object: hostTeam2, action: write, allow: false},
|
||||
{user: test.UserObserver, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Global observer+ can read all
|
||||
{user: test.UserObserverPlus, object: host, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: host, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: host, action: list, allow: true},
|
||||
{user: test.UserObserverPlus, object: host, action: mdmCommand, allow: false},
|
||||
{user: test.UserObserverPlus, object: hostTeam1, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: hostTeam1, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: hostTeam1, action: mdmCommand, allow: false},
|
||||
{user: test.UserObserverPlus, object: hostTeam2, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: hostTeam2, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: hostTeam2, action: mdmCommand, allow: false},
|
||||
|
||||
// Global admin can read/write all
|
||||
{user: test.UserAdmin, object: host, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: host, action: write, allow: true},
|
||||
@ -469,6 +515,12 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{Team: fleet.Team{ID: 2}, Role: fleet.RoleObserver},
|
||||
},
|
||||
}
|
||||
teamObserverPlus := &fleet.User{
|
||||
ID: 104,
|
||||
Teams: []fleet.UserTeam{
|
||||
{Team: fleet.Team{ID: 1}, Role: fleet.RoleObserverPlus},
|
||||
},
|
||||
}
|
||||
|
||||
query := &fleet.Query{ObserverCanRun: false}
|
||||
emptyTquery := &fleet.TargetedQuery{Query: query}
|
||||
@ -529,7 +581,21 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{user: test.UserObserver, object: team12ObsQuery, action: run, allow: true}, // can run observer query
|
||||
{user: test.UserObserver, object: observerQuery, action: runNew, allow: false},
|
||||
|
||||
// Global maintainer can read/write (even not authored by them)/run any
|
||||
// Global observer+ can read all queries, not write them, and can run any query.
|
||||
{user: test.UserObserverPlus, object: query, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: query, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: teamAdminQuery, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: emptyTquery, action: run, allow: true},
|
||||
{user: test.UserObserverPlus, object: team1Query, action: run, allow: true},
|
||||
{user: test.UserObserverPlus, object: query, action: runNew, allow: true},
|
||||
{user: test.UserObserverPlus, object: observerQuery, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: observerQuery, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: emptyTobsQuery, action: run, allow: true}, // can run observer query
|
||||
{user: test.UserObserverPlus, object: team1ObsQuery, action: run, allow: true}, // can run observer query
|
||||
{user: test.UserObserverPlus, object: team12ObsQuery, action: run, allow: true}, // can run observer query
|
||||
{user: test.UserObserverPlus, object: observerQuery, action: runNew, allow: true},
|
||||
|
||||
// Global maintainer can read/write (even not authored by them)/run any.
|
||||
{user: test.UserMaintainer, object: query, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: query, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: teamMaintQuery, action: write, allow: true},
|
||||
@ -572,6 +638,21 @@ func TestAuthorizeQuery(t *testing.T) {
|
||||
{user: teamObserver, object: team2ObsQuery, action: run, allow: false}, // not filtered only to observed teams
|
||||
{user: teamObserver, object: observerQuery, action: runNew, allow: false},
|
||||
|
||||
// Team observer+ can read all queries, not write them, and can run any query.
|
||||
{user: teamObserverPlus, object: query, action: read, allow: true},
|
||||
{user: teamObserverPlus, object: query, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: teamAdminQuery, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: emptyTquery, action: run, allow: true},
|
||||
{user: teamObserverPlus, object: team1Query, action: run, allow: true},
|
||||
{user: teamObserverPlus, object: query, action: runNew, allow: true},
|
||||
{user: teamObserverPlus, object: observerQuery, action: read, allow: true},
|
||||
{user: teamObserverPlus, object: observerQuery, action: write, allow: false},
|
||||
{user: teamObserverPlus, object: emptyTobsQuery, action: run, allow: true}, // can run observer query with no targeted team
|
||||
{user: teamObserverPlus, object: team1ObsQuery, action: run, allow: true}, // can run observer query filtered to observed team
|
||||
{user: teamObserverPlus, object: team12ObsQuery, action: run, allow: false}, // not filtered only to observed teams
|
||||
{user: teamObserverPlus, object: team2ObsQuery, action: run, allow: false}, // not filtered only to observed teams
|
||||
{user: teamObserverPlus, object: observerQuery, action: runNew, allow: true},
|
||||
|
||||
// Team maintainer can read/write their own queries/run queries filtered on their team(s)
|
||||
{user: teamMaintainer, object: query, action: read, allow: true},
|
||||
{user: teamMaintainer, object: query, action: write, allow: true},
|
||||
@ -645,6 +726,7 @@ func TestAuthorizeTargets(t *testing.T) {
|
||||
{user: test.UserAdmin, object: target, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: target, action: read, allow: true},
|
||||
{user: test.UserObserver, object: target, action: read, allow: true},
|
||||
{user: test.UserObserverPlus, object: target, action: read, allow: true},
|
||||
})
|
||||
}
|
||||
|
||||
@ -667,6 +749,9 @@ func TestAuthorizePacks(t *testing.T) {
|
||||
|
||||
{user: test.UserObserver, object: pack, action: read, allow: false},
|
||||
{user: test.UserObserver, object: pack, action: write, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: pack, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: pack, action: write, allow: false},
|
||||
})
|
||||
}
|
||||
|
||||
@ -756,6 +841,8 @@ func TestAuthorizeCarves(t *testing.T) {
|
||||
{user: test.UserMaintainer, object: carve, action: write, allow: false},
|
||||
{user: test.UserObserver, object: carve, action: read, allow: false},
|
||||
{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},
|
||||
|
||||
// Only admins allowed
|
||||
{user: test.UserAdmin, object: carve, action: read, allow: true},
|
||||
@ -767,7 +854,7 @@ func TestAuthorizePolicies(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
globalPolicy := &fleet.Policy{}
|
||||
teamPolicy := &fleet.Policy{
|
||||
team1Policy := &fleet.Policy{
|
||||
PolicyData: fleet.PolicyData{
|
||||
TeamID: ptr.Uint(1),
|
||||
},
|
||||
@ -782,27 +869,34 @@ func TestAuthorizePolicies(t *testing.T) {
|
||||
{user: test.UserObserver, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserAdmin, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserObserver, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserObserver, object: teamPolicy, action: read, allow: true},
|
||||
{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.UserTeamAdminTeam1, object: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamPolicy, 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: teamPolicy, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamPolicy, 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: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamPolicy, action: read, allow: true},
|
||||
{user: test.UserTeamObserverTeam2, object: teamPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: teamPolicy, 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},
|
||||
@ -815,59 +909,74 @@ func TestAuthorizeMDMAppleConfigProfile(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
globalProfile := &fleet.MDMAppleConfigProfile{}
|
||||
teamProfile := &fleet.MDMAppleConfigProfile{
|
||||
team1Profile := &fleet.MDMAppleConfigProfile{
|
||||
TeamID: ptr.Uint(1),
|
||||
}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: test.UserNoRoles, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: teamProfile, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: teamProfile, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: globalProfile, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: globalProfile, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: teamProfile, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: teamProfile, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: team1Profile, action: read, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: globalProfile, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: globalProfile, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: teamProfile, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: teamProfile, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Profile, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserObserver, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserObserver, object: teamProfile, action: write, allow: false},
|
||||
{user: test.UserObserver, object: teamProfile, action: read, allow: false},
|
||||
{user: test.UserObserver, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserObserver, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, 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: teamProfile, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: teamProfile, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Profile, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam2, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamProfile, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamProfile, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamProfile, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamProfile, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Profile, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Profile, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam2, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamProfile, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamProfile, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Profile, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: globalProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: globalProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamProfile, action: read, allow: false},
|
||||
{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: teamProfile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: teamProfile, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Profile, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, 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},
|
||||
})
|
||||
}
|
||||
|
||||
@ -875,59 +984,74 @@ func TestAuthorizeMDMAppleSettings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
globalSettings := &fleet.MDMAppleSettingsPayload{}
|
||||
teamSettings := &fleet.MDMAppleSettingsPayload{
|
||||
team1Settings := &fleet.MDMAppleSettingsPayload{
|
||||
TeamID: ptr.Uint(1),
|
||||
}
|
||||
runTestCases(t, []authTestCase{
|
||||
{user: test.UserNoRoles, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: teamSettings, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: teamSettings, action: read, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserNoRoles, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserAdmin, object: globalSettings, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: globalSettings, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: teamSettings, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: teamSettings, action: read, allow: true},
|
||||
{user: test.UserAdmin, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserAdmin, object: team1Settings, action: read, allow: true},
|
||||
|
||||
{user: test.UserMaintainer, object: globalSettings, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: globalSettings, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: teamSettings, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: teamSettings, action: read, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserMaintainer, object: team1Settings, action: read, allow: true},
|
||||
|
||||
{user: test.UserObserver, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserObserver, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserObserver, object: teamSettings, action: write, allow: false},
|
||||
{user: test.UserObserver, object: teamSettings, action: read, allow: false},
|
||||
{user: test.UserObserver, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserObserver, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserObserverPlus, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, 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: teamSettings, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: teamSettings, action: read, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Settings, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam2, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamSettings, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: teamSettings, action: read, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam2, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamSettings, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: teamSettings, action: read, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Settings, action: write, allow: true},
|
||||
{user: test.UserTeamMaintainerTeam1, object: team1Settings, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamMaintainerTeam2, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamSettings, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: teamSettings, action: read, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamMaintainerTeam2, object: team1Settings, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamObserverTeam1, object: globalSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: globalSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam1, object: teamSettings, action: read, allow: false},
|
||||
{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: teamSettings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: teamSettings, action: read, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, object: team1Settings, action: write, allow: false},
|
||||
{user: test.UserTeamObserverTeam2, 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},
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -859,15 +859,13 @@ func (ds *Datastore) whereFilterHostsByTeams(filter fleet.TeamFilter, hostKey st
|
||||
|
||||
if filter.User.GlobalRole != nil {
|
||||
switch *filter.User.GlobalRole {
|
||||
case fleet.RoleAdmin, fleet.RoleMaintainer:
|
||||
case fleet.RoleAdmin, fleet.RoleMaintainer, fleet.RoleObserverPlus:
|
||||
return defaultAllowClause
|
||||
|
||||
case fleet.RoleObserver:
|
||||
if filter.IncludeObserver {
|
||||
return defaultAllowClause
|
||||
}
|
||||
return "FALSE"
|
||||
|
||||
default:
|
||||
// Fall through to specific teams
|
||||
}
|
||||
@ -877,7 +875,9 @@ func (ds *Datastore) whereFilterHostsByTeams(filter fleet.TeamFilter, hostKey st
|
||||
var idStrs []string
|
||||
var teamIDSeen bool
|
||||
for _, team := range filter.User.Teams {
|
||||
if team.Role == fleet.RoleAdmin || team.Role == fleet.RoleMaintainer ||
|
||||
if team.Role == fleet.RoleAdmin ||
|
||||
team.Role == fleet.RoleMaintainer ||
|
||||
team.Role == fleet.RoleObserverPlus ||
|
||||
(team.Role == fleet.RoleObserver && filter.IncludeObserver) {
|
||||
idStrs = append(idStrs, strconv.Itoa(int(team.ID)))
|
||||
if filter.TeamID != nil && *filter.TeamID == team.ID {
|
||||
@ -918,16 +918,13 @@ func (ds *Datastore) whereFilterTeams(filter fleet.TeamFilter, teamKey string) s
|
||||
|
||||
if filter.User.GlobalRole != nil {
|
||||
switch *filter.User.GlobalRole {
|
||||
|
||||
case fleet.RoleAdmin, fleet.RoleMaintainer:
|
||||
case fleet.RoleAdmin, fleet.RoleMaintainer, fleet.RoleObserverPlus:
|
||||
return "TRUE"
|
||||
|
||||
case fleet.RoleObserver:
|
||||
if filter.IncludeObserver {
|
||||
return "TRUE"
|
||||
}
|
||||
return "FALSE"
|
||||
|
||||
default:
|
||||
// Fall through to specific teams
|
||||
}
|
||||
@ -936,7 +933,9 @@ func (ds *Datastore) whereFilterTeams(filter fleet.TeamFilter, teamKey string) s
|
||||
// Collect matching teams
|
||||
var idStrs []string
|
||||
for _, team := range filter.User.Teams {
|
||||
if team.Role == fleet.RoleAdmin || team.Role == fleet.RoleMaintainer ||
|
||||
if team.Role == fleet.RoleAdmin ||
|
||||
team.Role == fleet.RoleMaintainer ||
|
||||
team.Role == fleet.RoleObserverPlus ||
|
||||
(team.Role == fleet.RoleObserver && filter.IncludeObserver) {
|
||||
idStrs = append(idStrs, strconv.Itoa(int(team.ID)))
|
||||
}
|
||||
|
@ -167,7 +167,10 @@ func parseRole(values []SAMLAttributeValue) (string, error) {
|
||||
}
|
||||
// Using last value by default.
|
||||
value := values[len(values)-1].Value
|
||||
if value != RoleAdmin && value != RoleMaintainer && value != RoleObserver {
|
||||
if value != RoleAdmin &&
|
||||
value != RoleMaintainer &&
|
||||
value != RoleObserver &&
|
||||
value != RoleObserverPlus {
|
||||
return "", fmt.Errorf("invalid role: %s", value)
|
||||
}
|
||||
return value, nil
|
||||
|
@ -11,6 +11,7 @@ const (
|
||||
RoleAdmin = "admin"
|
||||
RoleMaintainer = "maintainer"
|
||||
RoleObserver = "observer"
|
||||
RoleObserverPlus = "observer_plus"
|
||||
)
|
||||
|
||||
type TeamPayload struct {
|
||||
@ -195,15 +196,21 @@ type TeamUser struct {
|
||||
Role string `json:"role" db:"role"`
|
||||
}
|
||||
|
||||
var teamRoles = map[string]bool{
|
||||
RoleAdmin: true,
|
||||
RoleObserver: true,
|
||||
RoleMaintainer: true,
|
||||
var teamRoles = map[string]struct{}{
|
||||
RoleAdmin: {},
|
||||
RoleObserver: {},
|
||||
RoleMaintainer: {},
|
||||
RoleObserverPlus: {},
|
||||
}
|
||||
|
||||
var premiumTeamRoles = map[string]struct{}{
|
||||
RoleObserverPlus: {},
|
||||
}
|
||||
|
||||
// ValidTeamRole returns whether the role provided is valid for a team user.
|
||||
func ValidTeamRole(role string) bool {
|
||||
return teamRoles[role]
|
||||
_, ok := teamRoles[role]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidTeamRoles returns the list of valid roles for a team user.
|
||||
@ -215,15 +222,21 @@ func ValidTeamRoles() []string {
|
||||
return roles
|
||||
}
|
||||
|
||||
var globalRoles = map[string]bool{
|
||||
RoleObserver: true,
|
||||
RoleMaintainer: true,
|
||||
RoleAdmin: true,
|
||||
var globalRoles = map[string]struct{}{
|
||||
RoleObserver: {},
|
||||
RoleMaintainer: {},
|
||||
RoleAdmin: {},
|
||||
RoleObserverPlus: {},
|
||||
}
|
||||
|
||||
var premiumGlobalRoles = map[string]struct{}{
|
||||
RoleObserverPlus: {},
|
||||
}
|
||||
|
||||
// ValidGlobalRole returns whether the role provided is valid for a global user.
|
||||
func ValidGlobalRole(role string) bool {
|
||||
return globalRoles[role]
|
||||
_, ok := globalRoles[role]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ValidGlobalRoles returns the list of valid roles for a global user.
|
||||
@ -244,7 +257,7 @@ func ValidateRole(globalRole *string, teamUsers []UserTeam) error {
|
||||
}
|
||||
for _, t := range teamUsers {
|
||||
if !ValidTeamRole(t.Role) {
|
||||
return NewError(ErrNoRoleNeeded, "Team roles can be observer or maintainer")
|
||||
return NewErrorf(ErrNoRoleNeeded, "invalid team role: %s", t.Role)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -255,12 +268,37 @@ func ValidateRole(globalRole *string, teamUsers []UserTeam) error {
|
||||
}
|
||||
|
||||
if !ValidGlobalRole(*globalRole) {
|
||||
return NewError(ErrNoRoleNeeded, "GlobalRole role can only be admin, observer, or maintainer.")
|
||||
return NewErrorf(ErrNoRoleNeeded, "invalid global role: %s", *globalRole)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ValidateRoleForLicense(globalRole *string, teamUsers *[]UserTeam, license LicenseInfo) error {
|
||||
var teamUsers_ []UserTeam
|
||||
if teamUsers != nil {
|
||||
teamUsers_ = *teamUsers
|
||||
}
|
||||
if err := ValidateRole(globalRole, teamUsers_); err != nil {
|
||||
return err
|
||||
}
|
||||
premiumRolesPresent := false
|
||||
if globalRole != nil {
|
||||
if _, ok := premiumGlobalRoles[*globalRole]; ok {
|
||||
premiumRolesPresent = true
|
||||
}
|
||||
}
|
||||
for _, teamUser := range teamUsers_ {
|
||||
if _, ok := premiumTeamRoles[teamUser.Role]; ok {
|
||||
premiumRolesPresent = true
|
||||
}
|
||||
}
|
||||
if !license.IsPremium() && premiumRolesPresent {
|
||||
return ErrMissingLicense
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TeamFilter is the filtering information passed to the datastore for queries
|
||||
// that may be filtered by team.
|
||||
type TeamFilter struct {
|
||||
|
@ -16,7 +16,7 @@ func TestListActivities(t *testing.T) {
|
||||
ds := new(mock.Store)
|
||||
svc, ctx := newTestService(t, ds, nil, nil)
|
||||
|
||||
globalUsers := []*fleet.User{test.UserAdmin, test.UserMaintainer, test.UserObserver}
|
||||
globalUsers := []*fleet.User{test.UserAdmin, test.UserMaintainer, test.UserObserver, test.UserObserverPlus}
|
||||
teamUsers := []*fleet.User{test.UserTeamAdminTeam1, test.UserTeamMaintainerTeam1, test.UserTeamObserverTeam1}
|
||||
|
||||
ds.ListActivitiesFunc = func(ctx context.Context, opts fleet.ListActivitiesOptions) ([]*fleet.Activity, *fleet.PaginationMetadata, error) {
|
||||
|
@ -203,6 +203,7 @@ func TestAppleMDMAuthorization(t *testing.T) {
|
||||
test.UserNoRoles,
|
||||
test.UserMaintainer,
|
||||
test.UserObserver,
|
||||
test.UserObserverPlus,
|
||||
test.UserTeamAdminTeam1,
|
||||
} {
|
||||
testAuthdMethods(t, user, true)
|
||||
@ -339,6 +340,10 @@ func TestAppleMDMAuthorization(t *testing.T) {
|
||||
{"observer can view", test.UserObserver, "uuidTm2", false},
|
||||
{"observer can view", test.UserObserver, "uuidNoTm", false},
|
||||
{"observer can view", test.UserObserver, "uuidMixTm1Tm2", false},
|
||||
{"observer+ can view", test.UserObserverPlus, "uuidTm1", false},
|
||||
{"observer+ can view", test.UserObserverPlus, "uuidTm2", false},
|
||||
{"observer+ can view", test.UserObserverPlus, "uuidNoTm", false},
|
||||
{"observer+ can view", test.UserObserverPlus, "uuidMixTm1Tm2", false},
|
||||
{"admin can view", test.UserAdmin, "uuidTm1", false},
|
||||
{"admin can view", test.UserAdmin, "uuidTm2", false},
|
||||
{"admin can view", test.UserAdmin, "uuidNoTm", false},
|
||||
@ -351,6 +356,10 @@ func TestAppleMDMAuthorization(t *testing.T) {
|
||||
{"tm1 observer cannot view tm2", test.UserTeamObserverTeam1, "uuidTm2", true},
|
||||
{"tm1 observer cannot view no team", test.UserTeamObserverTeam1, "uuidNoTm", true},
|
||||
{"tm1 observer cannot view mix", test.UserTeamObserverTeam1, "uuidMixTm1Tm2", true},
|
||||
{"tm1 observer+ can view tm1", test.UserTeamObserverPlusTeam1, "uuidTm1", false},
|
||||
{"tm1 observer+ cannot view tm2", test.UserTeamObserverPlusTeam1, "uuidTm2", true},
|
||||
{"tm1 observer+ cannot view no team", test.UserTeamObserverPlusTeam1, "uuidNoTm", true},
|
||||
{"tm1 observer+ cannot view mix", test.UserTeamObserverPlusTeam1, "uuidMixTm1Tm2", true},
|
||||
{"tm1 admin can view tm1", test.UserTeamAdminTeam1, "uuidTm1", false},
|
||||
{"tm1 admin cannot view tm2", test.UserTeamAdminTeam1, "uuidTm2", true},
|
||||
{"tm1 admin cannot view no team", test.UserTeamAdminTeam1, "uuidNoTm", true},
|
||||
@ -774,6 +783,7 @@ func TestAppleMDMEnrollmentProfile(t *testing.T) {
|
||||
test.UserNoRoles,
|
||||
test.UserMaintainer,
|
||||
test.UserObserver,
|
||||
test.UserObserverPlus,
|
||||
test.UserTeamAdminTeam1,
|
||||
} {
|
||||
ctx := test.UserContext(ctx, user)
|
||||
|
@ -689,6 +689,7 @@ func TestRefetchHost(t *testing.T) {
|
||||
|
||||
require.NoError(t, svc.RefetchHost(test.UserContext(ctx, test.UserAdmin), host.ID))
|
||||
require.NoError(t, svc.RefetchHost(test.UserContext(ctx, test.UserObserver), host.ID))
|
||||
require.NoError(t, svc.RefetchHost(test.UserContext(ctx, test.UserObserverPlus), host.ID))
|
||||
require.NoError(t, svc.RefetchHost(test.UserContext(ctx, test.UserMaintainer), host.ID))
|
||||
assert.True(t, ds.HostLiteFuncInvoked)
|
||||
assert.True(t, ds.UpdateHostRefetchRequestedFuncInvoked)
|
||||
@ -809,6 +810,7 @@ func TestHostEncryptionKey(t *testing.T) {
|
||||
test.UserAdmin,
|
||||
test.UserMaintainer,
|
||||
test.UserObserver,
|
||||
test.UserObserverPlus,
|
||||
},
|
||||
disallowedUsers: []*fleet.User{
|
||||
test.UserTeamAdminTeam1,
|
||||
@ -831,14 +833,17 @@ func TestHostEncryptionKey(t *testing.T) {
|
||||
test.UserAdmin,
|
||||
test.UserMaintainer,
|
||||
test.UserObserver,
|
||||
test.UserObserverPlus,
|
||||
test.UserTeamAdminTeam1,
|
||||
test.UserTeamMaintainerTeam1,
|
||||
test.UserTeamObserverTeam1,
|
||||
test.UserTeamObserverPlusTeam1,
|
||||
},
|
||||
disallowedUsers: []*fleet.User{
|
||||
test.UserTeamAdminTeam2,
|
||||
test.UserTeamMaintainerTeam2,
|
||||
test.UserTeamObserverTeam2,
|
||||
test.UserTeamObserverPlusTeam2,
|
||||
test.UserNoRoles,
|
||||
},
|
||||
},
|
||||
|
@ -204,7 +204,7 @@ func (s *integrationTestSuite) TestUserWithWrongRoleErrors() {
|
||||
GlobalRole: ptr.String("wrongrole"),
|
||||
}
|
||||
resp := s.Do("POST", "/api/latest/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity)
|
||||
assertErrorCodeAndMessage(t, resp, fleet.ErrNoRoleNeeded, "GlobalRole role can only be admin, observer, or maintainer.")
|
||||
assertErrorCodeAndMessage(t, resp, fleet.ErrNoRoleNeeded, "invalid global role: wrongrole")
|
||||
}
|
||||
|
||||
func (s *integrationTestSuite) TestUserCreationWrongTeamErrors() {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
@ -20,6 +21,7 @@ import (
|
||||
"github.com/fleetdm/fleet/v4/server/live_query/live_query_mock"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
"github.com/fleetdm/fleet/v4/server/test"
|
||||
"github.com/go-kit/log"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
@ -53,6 +55,7 @@ func (s *integrationEnterpriseTestSuite) SetupSuite() {
|
||||
},
|
||||
Pool: s.redisPool,
|
||||
Lq: s.lq,
|
||||
Logger: log.NewLogfmtLogger(os.Stdout),
|
||||
}
|
||||
users, server := RunServerForTestsWithDS(s.T(), s.ds, &config)
|
||||
s.server = server
|
||||
|
@ -79,6 +79,7 @@ func TestMDMAppleAuthorization(t *testing.T) {
|
||||
test.UserNoRoles,
|
||||
test.UserMaintainer,
|
||||
test.UserObserver,
|
||||
test.UserObserverPlus,
|
||||
test.UserTeamAdminTeam1,
|
||||
} {
|
||||
testAuthdMethods(t, user, true)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
|
||||
"github.com/fleetdm/fleet/v4/server/authz"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/ctxerr"
|
||||
"github.com/fleetdm/fleet/v4/server/contexts/license"
|
||||
"github.com/fleetdm/fleet/v4/server/fleet"
|
||||
"github.com/fleetdm/fleet/v4/server/ptr"
|
||||
)
|
||||
@ -30,6 +31,14 @@ func (svc *Service) CreateInitialUser(ctx context.Context, p fleet.UserPayload)
|
||||
}
|
||||
|
||||
func (svc *Service) NewUser(ctx context.Context, p fleet.UserPayload) (*fleet.User, error) {
|
||||
license, _ := license.FromContext(ctx)
|
||||
if license == nil {
|
||||
return nil, ctxerr.New(ctx, "license not found")
|
||||
}
|
||||
if err := fleet.ValidateRoleForLicense(p.GlobalRole, p.Teams, *license); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate role")
|
||||
}
|
||||
|
||||
user, err := p.User(svc.config.Auth.SaltKeySize, svc.config.Auth.BcryptCost)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -13,6 +13,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/fleetdm/fleet/v4/server/mail"
|
||||
@ -297,6 +298,13 @@ func (svc *Service) ModifyUser(ctx context.Context, userID uint, p fleet.UserPay
|
||||
if err := svc.authz.Authorize(ctx, user, fleet.ActionWriteRole); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
license, _ := license.FromContext(ctx)
|
||||
if license == nil {
|
||||
return nil, ctxerr.New(ctx, "license not found")
|
||||
}
|
||||
if err := fleet.ValidateRoleForLicense(p.GlobalRole, p.Teams, *license); err != nil {
|
||||
return nil, ctxerr.Wrap(ctx, err, "validate role")
|
||||
}
|
||||
}
|
||||
|
||||
if p.NewPassword != nil {
|
||||
|
@ -91,4 +91,26 @@ var (
|
||||
},
|
||||
},
|
||||
}
|
||||
UserObserverPlus = &fleet.User{
|
||||
ID: 12,
|
||||
GlobalRole: ptr.String(fleet.RoleObserverPlus),
|
||||
}
|
||||
UserTeamObserverPlusTeam1 = &fleet.User{
|
||||
ID: 13,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 1},
|
||||
Role: fleet.RoleObserverPlus,
|
||||
},
|
||||
},
|
||||
}
|
||||
UserTeamObserverPlusTeam2 = &fleet.User{
|
||||
ID: 14,
|
||||
Teams: []fleet.UserTeam{
|
||||
{
|
||||
Team: fleet.Team{ID: 2},
|
||||
Role: fleet.RoleObserverPlus,
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user