Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/44696-add-list-software-perms-for-gitops
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Added permissions for GitOps user to list software titles
28 changes: 28 additions & 0 deletions ee/server/service/maintained_apps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,20 @@ func TestListMaintainedAppsAuth(t *testing.T) {
true,
true,
},
{
"global gitops",
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
false,
false,
false,
},
{
"team gitops",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
false,
false,
true,
},
}

var forbiddenError *authz.Forbidden
Expand Down Expand Up @@ -224,6 +238,20 @@ func TestGetMaintainedAppAuth(t *testing.T) {
true,
true,
},
{
"global gitops",
&fleet.User{GlobalRole: ptr.String(fleet.RoleGitOps)},
false,
false,
false,
},
{
"team gitops",
&fleet.User{Teams: []fleet.UserTeam{{Team: fleet.Team{ID: 1}, Role: fleet.RoleGitOps}}},
false,
false,
true,
},
}

var forbiddenError *authz.Forbidden
Expand Down
34 changes: 17 additions & 17 deletions server/authz/policy.rego
Original file line number Diff line number Diff line change
Expand Up @@ -740,10 +740,10 @@ allow {
# Software
##

# Global admins, maintainers, technician, observers, and observer_plus can read all software.
# Global admins, maintainers, technician, observers, observer_plus and gitops can read all software.
allow {
object.type == "software_inventory"
subject.global_role == [admin, maintainer, technician, observer, observer_plus][_]
subject.global_role == [admin, maintainer, technician, observer, observer_plus, gitops][_]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the global role updates might be overkill, but they don't hurt and could be needed in the future 🤷

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be able to set software for any team (so it will need to get the software titles for any team) so I think this makes sense.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the moment it always passes a team ID and gets software for a single fleet, but I don't see a downside to leaving this.

action == read
}

Expand All @@ -754,32 +754,32 @@ allow {
action == write
}

# Team admins, maintainers, technician, observers and observer_plus can read all software in their teams.
# Team admins, maintainers, technician, observers, observer_plus and gitops can read all software in their teams.
allow {
not is_null(object.team_id)
object.type == "software_inventory"
team_role(subject, object.team_id) == [admin, maintainer, technician, observer, observer_plus][_]
team_role(subject, object.team_id) == [admin, maintainer, technician, observer, observer_plus, gitops][_]
action == read
}

# Global admins and maintainers can read all maintained apps.
# Global admins and maintainers and gitops can read all maintained apps.
allow {
object.type == "maintained_app"
subject.global_role == [admin, maintainer][_]
subject.global_role == [admin, maintainer, gitops][_]
action == read
}

# Team admins and maintainers can read all maintained apps (no team constraint, unlike installers)
# Team admins and maintainers and gitops can read all maintained apps (no team constraint, unlike installers)
allow {
object.type == "maintained_app"
team_role(subject, subject.teams[_].id) == [admin, maintainer][_]
team_role(subject, subject.teams[_].id) == [admin, maintainer, gitops][_]
action == read
}

# Global admins, maintainers, and technicians can read any installable entity (software installer or VPP app)
# Global admins, maintainers, technicians and gitops can read any installable entity (software installer or VPP app)
allow {
object.type == "installable_entity"
subject.global_role == [admin, maintainer, technician][_]
subject.global_role == [admin, maintainer, technician, gitops][_]
action == read
}

Expand All @@ -790,11 +790,11 @@ allow {
action == write
}

# Team admins, maintainers, and technicians can read any installable entity (software installer or VPP app) in their teams.
# Team admins, maintainers, technicians and gitops can read any installable entity (software installer or VPP app) in their teams.
allow {
not is_null(object.team_id)
object.type == "installable_entity"
team_role(subject, object.team_id) == [admin, maintainer, technician][_]
team_role(subject, object.team_id) == [admin, maintainer, technician, gitops][_]
action == read
}

Expand Down Expand Up @@ -865,9 +865,9 @@ allow {

# Global admins, maintainers, technicians, and gitops can resend MDM config profiles.
#
# GitOps doesn't really need permissions to resend to specific hosts,
# gitops doesn't really need permissions to resend to specific hosts,
# but we will keep this as-is to not break any workflows that might be using a
# GitOps token to do a resend.
# gitops token to do a resend.
allow {
object.type == "mdm_config_profile"
subject.global_role == [admin, maintainer, technician, gitops][_]
Expand All @@ -894,9 +894,9 @@ allow {

# Team admins, maintainers, technicians, and gitops can resend MDM config profiles on their teams.
#
# GitOps doesn't really need permissions to resend to specific hosts,
# gitops doesn't really need permissions to resend to specific hosts,
# but we will keep this as-is to not break any workflows that might be using a
# GitOps token to do a resend.
# gitops token to do a resend.
allow {
not is_null(object.team_id)
object.team_id != 0
Expand Down Expand Up @@ -1250,7 +1250,7 @@ allow {
##
# Certificate Authorities
##
# Global admins and GitOps can configure, read, list, and read secrets of certificate authorities.
# Global admins and gitops can configure, read, list, and read secrets of certificate authorities.
allow {
object.type == "certificate_authority"
subject.global_role == [admin, gitops][_]
Expand Down
41 changes: 34 additions & 7 deletions server/authz/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -643,6 +643,8 @@ func TestAuthorizeSoftwareInventory(t *testing.T) {
t.Parallel()

softwareInventory := &fleet.AuthzSoftwareInventory{}
team1SoftwareInventory := &fleet.AuthzSoftwareInventory{TeamID: ptr.Uint(1)}
team2SoftwareInventory := &fleet.AuthzSoftwareInventory{TeamID: ptr.Uint(2)}
runTestCases(t, []authTestCase{
{user: nil, object: softwareInventory, action: read, allow: false},
{user: test.UserNoRoles, object: softwareInventory, action: read, allow: false},
Expand All @@ -651,8 +653,11 @@ func TestAuthorizeSoftwareInventory(t *testing.T) {
{user: test.UserObserver, object: softwareInventory, action: read, allow: true},
{user: test.UserObserverPlus, object: softwareInventory, action: read, allow: true},
{user: test.UserTechnician, object: softwareInventory, action: read, allow: true},
{user: test.UserGitOps, object: softwareInventory, action: read, allow: false},
{user: test.UserGitOps, object: softwareInventory, action: read, allow: true},
{user: test.UserGitOps, object: team1SoftwareInventory, action: read, allow: true},
{user: test.UserTeamGitOpsTeam1, object: softwareInventory, action: read, allow: false},
{user: test.UserTeamGitOpsTeam1, object: team1SoftwareInventory, action: read, allow: true},
{user: test.UserTeamGitOpsTeam1, object: team2SoftwareInventory, action: read, allow: false},
{user: test.UserTeamTechnicianTeam1, object: softwareInventory, action: read, allow: false},
})
}
Expand Down Expand Up @@ -713,18 +718,16 @@ func TestAuthorizeSoftwareInstaller(t *testing.T) {
{user: test.UserTechnician, object: team2Installer, action: read, allow: true},
{user: test.UserTechnician, object: team2Installer, action: write, allow: false},

// TODO: confirm gitops permissions
{user: test.UserGitOps, object: noTeamInstaller, action: read, allow: false},
{user: test.UserGitOps, object: noTeamInstaller, action: read, allow: true},
{user: test.UserGitOps, object: noTeamInstaller, action: write, allow: true},
{user: test.UserGitOps, object: team1Installer, action: read, allow: false},
{user: test.UserGitOps, object: team1Installer, action: read, allow: true},
{user: test.UserGitOps, object: team1Installer, action: write, allow: true},
{user: test.UserGitOps, object: team2Installer, action: read, allow: false},
{user: test.UserGitOps, object: team2Installer, action: read, allow: true},
{user: test.UserGitOps, object: team2Installer, action: write, allow: true},

// TODO: confirm gitops permissions
{user: test.UserTeamGitOpsTeam1, object: noTeamInstaller, action: read, allow: false},
{user: test.UserTeamGitOpsTeam1, object: noTeamInstaller, action: write, allow: false},
{user: test.UserTeamGitOpsTeam1, object: team1Installer, action: read, allow: false},
{user: test.UserTeamGitOpsTeam1, object: team1Installer, action: read, allow: true},
{user: test.UserTeamGitOpsTeam1, object: team1Installer, action: write, allow: true},
{user: test.UserTeamGitOpsTeam1, object: team2Installer, action: read, allow: false},
{user: test.UserTeamGitOpsTeam1, object: team2Installer, action: write, allow: false},
Expand Down Expand Up @@ -766,6 +769,30 @@ func TestAuthorizeSoftwareInstaller(t *testing.T) {
})
}

func TestAuthorizeMaintainedApp(t *testing.T) {
t.Parallel()

maintainedApp := &fleet.MaintainedApp{}
runTestCases(t, []authTestCase{
{user: nil, object: maintainedApp, action: read, allow: false},
{user: test.UserNoRoles, object: maintainedApp, action: read, allow: false},

{user: test.UserAdmin, object: maintainedApp, action: read, allow: true},
{user: test.UserMaintainer, object: maintainedApp, action: read, allow: true},
{user: test.UserObserver, object: maintainedApp, action: read, allow: false},
{user: test.UserObserverPlus, object: maintainedApp, action: read, allow: false},
{user: test.UserTechnician, object: maintainedApp, action: read, allow: false},
{user: test.UserGitOps, object: maintainedApp, action: read, allow: true},

{user: test.UserTeamAdminTeam1, object: maintainedApp, action: read, allow: true},
{user: test.UserTeamMaintainerTeam1, object: maintainedApp, action: read, allow: true},
{user: test.UserTeamObserverTeam1, object: maintainedApp, action: read, allow: false},
{user: test.UserTeamObserverPlusTeam1, object: maintainedApp, action: read, allow: false},
{user: test.UserTeamTechnicianTeam1, object: maintainedApp, action: read, allow: false},
{user: test.UserTeamGitOpsTeam1, object: maintainedApp, action: read, allow: true},
})
}

func TestAuthorizeHostSoftwareInstallerResult(t *testing.T) {
t.Parallel()

Expand Down
20 changes: 13 additions & 7 deletions server/service/integration_enterprise_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6917,14 +6917,13 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
// Attempt to delete a label, should allow.
s.DoJSON("DELETE", "/api/latest/fleet/labels/foo2", fleet.DeleteLabelRequest{}, http.StatusOK, &fleet.DeleteLabelResponse{})

// Attempt to list all software, should fail.
s.DoJSON("GET", "/api/latest/fleet/software/versions", listSoftwareRequest{}, http.StatusForbidden, &listSoftwareVersionsResponse{})
s.DoJSON("GET", "/api/latest/fleet/software", listSoftwareRequest{}, http.StatusForbidden, &listSoftwareResponse{})
s.DoJSON("GET", "/api/latest/fleet/software/count", countSoftwareRequest{}, http.StatusForbidden, &countSoftwareResponse{})
s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusForbidden, &listSoftwareTitlesResponse{})
// Listing software is allowed for gitops so they can reconcile software state.
s.DoJSON("GET", "/api/latest/fleet/software/versions", listSoftwareRequest{}, http.StatusOK, &listSoftwareVersionsResponse{})
s.DoJSON("GET", "/api/latest/fleet/software", listSoftwareRequest{}, http.StatusOK, &listSoftwareResponse{})
s.DoJSON("GET", "/api/latest/fleet/software/count", countSoftwareRequest{}, http.StatusOK, &countSoftwareResponse{})
s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusOK, &listSoftwareTitlesResponse{})
// Getting a single software title or version still requires Host:list, which gitops doesn't have.
s.DoJSON("GET", "/api/latest/fleet/software/titles/1", getSoftwareTitleRequest{}, http.StatusForbidden, &getSoftwareTitleResponse{})

// Attempt to list a software, should fail.
s.DoJSON("GET", "/api/latest/fleet/software/1", getSoftwareRequest{}, http.StatusForbidden, &getSoftwareResponse{})
s.DoJSON("GET", "/api/latest/fleet/software/versions/1", getSoftwareRequest{}, http.StatusForbidden, &getSoftwareResponse{})

Expand Down Expand Up @@ -7473,6 +7472,13 @@ func (s *integrationEnterpriseTestSuite) TestGitOpsUserActions() {
},
QueryID: &q1.ID,
}, http.StatusForbidden, &countTargetsResponse{})

// Listing software titles for the team it owns is allowed.
s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusOK, &listSoftwareTitlesResponse{}, "team_id", fmt.Sprint(t1.ID))
// Listing software titles globally (no team) is forbidden for team gitops.
s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusForbidden, &listSoftwareTitlesResponse{})
// Listing software titles for a team it does not own is forbidden.
s.DoJSON("GET", "/api/latest/fleet/software/titles", listSoftwareTitlesRequest{}, http.StatusForbidden, &listSoftwareTitlesResponse{}, "team_id", fmt.Sprint(t2.ID))
}

func (s *integrationEnterpriseTestSuite) TestDesktopEndpointWithInvalidPolicy() {
Expand Down
6 changes: 3 additions & 3 deletions server/service/software_installers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,8 @@ func TestSoftwareInstallersAuth(t *testing.T) {
{"global observer+ team 0", test.UserObserverPlus, ptr.Uint(0), true, true},
{"global observer+ team", test.UserObserverPlus, ptr.Uint(1), true, true},
{"global gitops no team", test.UserGitOps, nil, true, false},
{"global gitops team 0", test.UserGitOps, ptr.Uint(0), true, false},
{"global gitops team", test.UserGitOps, ptr.Uint(1), true, false},
{"global gitops team 0", test.UserGitOps, ptr.Uint(0), false, false},
{"global gitops team", test.UserGitOps, ptr.Uint(1), false, false},
{"team admin no team", test.UserTeamAdminTeam1, nil, true, true},
{"team admin team 0", test.UserTeamAdminTeam1, ptr.Uint(0), true, true},
{"team admin team", test.UserTeamAdminTeam1, ptr.Uint(1), false, false},
Expand All @@ -77,7 +77,7 @@ func TestSoftwareInstallersAuth(t *testing.T) {
{"team observer+ other team", test.UserTeamObserverPlusTeam2, ptr.Uint(1), true, true},
{"team gitops no team", test.UserTeamGitOpsTeam1, nil, true, true},
{"team gitops team 0", test.UserTeamGitOpsTeam1, ptr.Uint(0), true, true},
{"team gitops team", test.UserTeamGitOpsTeam1, ptr.Uint(1), true, false},
{"team gitops team", test.UserTeamGitOpsTeam1, ptr.Uint(1), false, false},
{"team gitops other team", test.UserTeamGitOpsTeam2, ptr.Uint(1), true, true},
}

Expand Down
Loading
Loading