mirror of
https://github.com/fleetdm/fleet
synced 2026-05-24 01:18:42 +00:00
gitops role authorization changes for fleetctl gitops (#16710)
To support `fleetctl gitops`, gitops role can now read policies/queries and write scripts. # Checklist for submitter If some of the following don't apply, delete the relevant line. <!-- Note that API documentation changes are now addressed by the product design team. --> - [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 permissions changes (docs/Using Fleet/manage-access.md) - [x] Added/updated tests - [x] Manual QA for all new/changed functionality
This commit is contained in:
parent
5a9f5d86a5
commit
95437f9044
8 changed files with 59 additions and 61 deletions
1
changes/13643-gitops-role
Normal file
1
changes/13643-gitops-role
Normal file
|
|
@ -0,0 +1 @@
|
|||
gitops role can now read queries/policies and write (but not execute) scripts
|
||||
|
|
@ -50,10 +50,10 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
|
|||
| Run queries designated "**observer can run**" 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 and their reports | ✅ | ✅ | ✅ | ✅ | |
|
||||
| View all queries and their reports | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Manage [query automations](https://fleetdm.com/docs/using-fleet/fleet-ui#schedule-a-query) | | | ✅ | ✅ | ✅ |
|
||||
| Create, edit, view, and delete packs | | | ✅ | ✅ | ✅ |
|
||||
| View all policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| View all policies | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| Run all policies | | ✅ | ✅ | ✅ | |
|
||||
| Filter hosts using policies | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Create, edit, and delete policies for all hosts | | | ✅ | ✅ | ✅ |
|
||||
|
|
@ -90,7 +90,7 @@ GitOps is an API-only and write-only role that can be used on CI/CD pipelines.
|
|||
| Enable/disable MDM macOS setup end user authentication\* | | | ✅ | ✅ | ✅ |
|
||||
| Run arbitrary scripts on hosts\* | | | ✅ | ✅ | |
|
||||
| View saved scripts\* | ✅ | ✅ | ✅ | ✅ | |
|
||||
| Edit/upload saved scripts\* | | | ✅ | ✅ | |
|
||||
| Edit/upload saved scripts\* | | | ✅ | ✅ | ✅ |
|
||||
| Run saved scripts on hosts\* | ✅ | ✅ | ✅ | ✅ | |
|
||||
|
||||
\* Applies only to Fleet Premium
|
||||
|
|
|
|||
|
|
@ -313,10 +313,10 @@ allow {
|
|||
action == write
|
||||
}
|
||||
|
||||
# Global admins, maintainers, observer_plus and observers can read queries.
|
||||
# Global admins, maintainers, gitops, observer_plus and observers can read queries.
|
||||
allow {
|
||||
object.type == "query"
|
||||
subject.global_role == [admin, maintainer, observer_plus, observer][_]
|
||||
subject.global_role == [admin, maintainer, gitops, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
|
|
@ -328,11 +328,11 @@ allow {
|
|||
action == write
|
||||
}
|
||||
|
||||
# Team admins, maintainers, observer_plus and observers can read queries for their teams.
|
||||
# Team admins, maintainers, gitops, observer_plus and observers can read queries for their teams.
|
||||
allow {
|
||||
object.type == "query"
|
||||
not is_null(object.team_id)
|
||||
team_role(subject, object.team_id) == [admin, maintainer, observer_plus, observer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer, gitops, observer_plus, observer][_]
|
||||
action == read
|
||||
}
|
||||
|
||||
|
|
@ -537,20 +537,13 @@ allow {
|
|||
# Policies
|
||||
##
|
||||
|
||||
# Global admins and maintainers can read and write policies.
|
||||
# Global admins, maintainers, and gitops can read and write policies.
|
||||
allow {
|
||||
object.type == "policy"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
subject.global_role == [admin, maintainer, gitops][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# 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"
|
||||
|
|
@ -558,22 +551,14 @@ allow {
|
|||
action == read
|
||||
}
|
||||
|
||||
# Team admin and maintainers can read and write policies for their teams.
|
||||
# Team admin, maintainers, and gitops 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, gitops][_]
|
||||
action == [read, write][_]
|
||||
}
|
||||
|
||||
# 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)
|
||||
|
|
@ -900,10 +885,10 @@ allow {
|
|||
# Scripts (saved script)
|
||||
##
|
||||
|
||||
# Global admins and maintainers can write (upload) saved scripts.
|
||||
# Global admins, maintainers, and gitops can write (upload) saved scripts.
|
||||
allow {
|
||||
object.type == "script"
|
||||
subject.global_role == [admin, maintainer][_]
|
||||
subject.global_role == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
|
|
@ -914,11 +899,11 @@ allow {
|
|||
action == read
|
||||
}
|
||||
|
||||
# Team admin and maintainers can write (upload) saved scripts for their teams.
|
||||
# Team admin, maintainers, and gitops can write (upload) saved scripts for their teams.
|
||||
allow {
|
||||
object.type == "script"
|
||||
not is_null(object.team_id)
|
||||
team_role(subject, object.team_id) == [admin, maintainer][_]
|
||||
team_role(subject, object.team_id) == [admin, maintainer, gitops][_]
|
||||
action == write
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -900,15 +900,15 @@ func TestAuthorizeQuery(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "Global GitOps cannot read, or run any query, but can write",
|
||||
name: "Global GitOps cannot run any query, but can read or write",
|
||||
testCases: []authTestCase{
|
||||
{user: test.UserGitOps, object: globalQuery, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: globalQuery, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: globalQuery, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: teamAdminQuery, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalQueryNoTargets, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: globalQueryTargetedToTeam1, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: globalQuery, action: runNew, allow: false},
|
||||
{user: test.UserGitOps, object: globalObserverQuery, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: globalObserverQuery, action: read, allow: true},
|
||||
{user: test.UserGitOps, object: globalObserverQuery, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalObserverQueryEmptyTargets, action: run, allow: false},
|
||||
{user: test.UserGitOps, object: globalObserverQueryTargetedToTeam1, action: run, allow: false},
|
||||
|
|
@ -1201,7 +1201,7 @@ func TestAuthorizeGlobalPolicy(t *testing.T) {
|
|||
{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.UserGitOps, object: globalPolicy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalPolicy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: globalPolicy, action: read, allow: true},
|
||||
|
|
@ -1253,7 +1253,7 @@ func TestAuthorizeTeamPolicy(t *testing.T) {
|
|||
{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.UserGitOps, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserTeamAdminTeam1, object: team1Policy, action: read, allow: true},
|
||||
|
|
@ -1268,7 +1268,7 @@ func TestAuthorizeTeamPolicy(t *testing.T) {
|
|||
{user: test.UserTeamObserverPlusTeam1, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Policy, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Policy, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Policy, action: read, allow: true},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: team2Policy, action: write, allow: false},
|
||||
{user: test.UserTeamAdminTeam1, object: team2Policy, action: read, allow: false},
|
||||
|
|
@ -2002,9 +2002,9 @@ func TestAuthorizeScript(t *testing.T) {
|
|||
{user: test.UserObserverPlus, object: team1Script, action: write, allow: false},
|
||||
{user: test.UserObserverPlus, object: team1Script, action: read, allow: true},
|
||||
|
||||
{user: test.UserGitOps, object: globalScript, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: globalScript, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: globalScript, action: read, allow: false},
|
||||
{user: test.UserGitOps, object: team1Script, action: write, allow: false},
|
||||
{user: test.UserGitOps, object: team1Script, action: write, allow: true},
|
||||
{user: test.UserGitOps, object: team1Script, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamAdminTeam1, object: globalScript, action: write, allow: false},
|
||||
|
|
@ -2049,7 +2049,7 @@ func TestAuthorizeScript(t *testing.T) {
|
|||
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalScript, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: globalScript, action: read, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Script, action: write, allow: false},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Script, action: write, allow: true},
|
||||
{user: test.UserTeamGitOpsTeam1, object: team1Script, action: read, allow: false},
|
||||
|
||||
{user: test.UserTeamGitOpsTeam2, object: globalScript, action: write, allow: false},
|
||||
|
|
|
|||
|
|
@ -3946,11 +3946,11 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
},
|
||||
}, 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 view a query, should work.
|
||||
s.DoJSON("GET", fmt.Sprintf("/api/latest/fleet/queries/%d", cqr.Query.ID), getQueryRequest{}, http.StatusOK, &getQueryResponse{})
|
||||
|
||||
// Attempt to list all queries, should fail.
|
||||
s.DoJSON("GET", "/api/latest/fleet/queries", listQueriesRequest{}, http.StatusForbidden, &listQueriesResponse{})
|
||||
// Attempt to list all queries, should work.
|
||||
s.DoJSON("GET", "/api/latest/fleet/queries", listQueriesRequest{}, http.StatusOK, &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{})
|
||||
|
|
@ -3975,8 +3975,8 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
// 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 disallow.
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusForbidden, &getGlobalScheduleResponse{})
|
||||
// Attempt to read the global schedule, should allow.
|
||||
s.DoJSON("GET", "/api/latest/fleet/schedule", nil, http.StatusOK, &getGlobalScheduleResponse{})
|
||||
|
||||
// Attempt to create a pack, should allow.
|
||||
cpr := createPackResponse{}
|
||||
|
|
@ -4015,8 +4015,11 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
},
|
||||
}, 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 read a global policy, should allow.
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/policies/%d", gplr.Policy.ID), getPolicyByIDRequest{}, http.StatusOK,
|
||||
&getPolicyByIDResponse{},
|
||||
)
|
||||
|
||||
// Attempt to delete a global policy, should allow.
|
||||
s.DoJSON("POST", "/api/latest/fleet/policies/delete", deleteGlobalPoliciesRequest{
|
||||
|
|
@ -4038,8 +4041,11 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
},
|
||||
}, 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 view a team policy, should allow.
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/team/%d/policies/%d", t1.ID, tplr.Policy.ID), getTeamPolicyByIDRequest{}, http.StatusOK,
|
||||
&getTeamPolicyByIDResponse{},
|
||||
)
|
||||
|
||||
// Attempt to delete a team policy, should allow.
|
||||
s.DoJSON("POST", fmt.Sprintf("/api/latest/fleet/teams/%d/policies/delete", t1.ID), deleteTeamPoliciesRequest{
|
||||
|
|
@ -4213,8 +4219,11 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
// 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 the team's schedule, should allow.
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/teams/%d/schedule", t1.ID), getTeamScheduleRequest{}, http.StatusOK,
|
||||
&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{})
|
||||
|
|
@ -4280,8 +4289,11 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
|
|||
},
|
||||
}, 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 a team policy, should allow.
|
||||
s.DoJSON(
|
||||
"GET", fmt.Sprintf("/api/latest/fleet/team/%d/policies/%d", t1.ID, ttplr.Policy.ID), getTeamPolicyByIDRequest{}, http.StatusOK,
|
||||
&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{})
|
||||
|
|
|
|||
|
|
@ -469,7 +469,7 @@ func TestQueryAuth(t *testing.T) {
|
|||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
globalQuery.ID,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
|
|
@ -477,7 +477,7 @@ func TestQueryAuth(t *testing.T) {
|
|||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
teamQuery.ID,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
|
|
@ -589,7 +589,7 @@ func TestQueryAuth(t *testing.T) {
|
|||
teamGitOps,
|
||||
teamQuery.ID,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -563,8 +563,8 @@ func TestSavedScripts(t *testing.T) {
|
|||
{
|
||||
name: "global gitops",
|
||||
user: &fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
shouldFailTeamWrite: true,
|
||||
shouldFailGlobalWrite: true,
|
||||
shouldFailTeamWrite: false,
|
||||
shouldFailGlobalWrite: false,
|
||||
shouldFailTeamRead: true,
|
||||
shouldFailGlobalRead: true,
|
||||
},
|
||||
|
|
@ -603,7 +603,7 @@ func TestSavedScripts(t *testing.T) {
|
|||
{
|
||||
name: "team gitops, belongs to team",
|
||||
user: &fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
shouldFailTeamWrite: true,
|
||||
shouldFailTeamWrite: false,
|
||||
shouldFailGlobalWrite: true,
|
||||
shouldFailTeamRead: true,
|
||||
shouldFailGlobalRead: true,
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ func TestTeamScheduleAuth(t *testing.T) {
|
|||
"global gitops",
|
||||
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team admin, belongs to team",
|
||||
|
|
@ -117,7 +117,7 @@ func TestTeamScheduleAuth(t *testing.T) {
|
|||
"team gitops, belongs to team",
|
||||
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"team maintainer, DOES NOT belong to team",
|
||||
|
|
|
|||
Loading…
Reference in a new issue