diff --git a/server/authz/policy.rego b/server/authz/policy.rego index c5d742fba5..8e46820688 100644 --- a/server/authz/policy.rego +++ b/server/authz/policy.rego @@ -643,6 +643,36 @@ allow { action == read } +# Global admins, maintainers, observers, and observer_plus can read any software installer. +allow { + object.type == "software_installer" + subject.global_role == [admin, maintainer, observer, observer_plus][_] + action == read +} + +# Global admins, maintainers, and gitops can write any software installer. +allow { + object.type == "software_installer" + subject.global_role == [admin, maintainer, gitops][_] + action == write +} + +# Team admins, maintainers, observers, and observer_plus can read any software installer in their teams. +allow { + not is_null(object.team_id) + object.type == "software_installer" + team_role(subject, object.team_id) == [admin, maintainer, observer, observer_plus][_] + action == read +} + +# Team admins, maintainers, and gitops can write any software installer in their teams. +allow { + not is_null(object.team_id) + object.type == "software_installer" + team_role(subject, object.team_id) == [admin, maintainer, gitops][_] + action == write +} + ## # Apple and Windows MDM ## diff --git a/server/authz/policy_test.go b/server/authz/policy_test.go index e71a954d42..0f2ce9b340 100644 --- a/server/authz/policy_test.go +++ b/server/authz/policy_test.go @@ -500,6 +500,101 @@ func TestAuthorizeSoftwareInventory(t *testing.T) { }) } +func TestAuthorizeSoftwareInstaller(t *testing.T) { + t.Parallel() + + noTeamInstaller := &fleet.SoftwareInstaller{} + team1Installer := &fleet.SoftwareInstaller{TeamID: ptr.Uint(1)} + team2Installer := &fleet.SoftwareInstaller{TeamID: ptr.Uint(2)} + runTestCases(t, []authTestCase{ + {user: nil, object: noTeamInstaller, action: read, allow: false}, + {user: nil, object: noTeamInstaller, action: write, allow: false}, + {user: nil, object: team1Installer, action: read, allow: false}, + {user: nil, object: team1Installer, action: write, allow: false}, + {user: nil, object: team2Installer, action: read, allow: false}, + {user: nil, object: team2Installer, action: write, allow: false}, + + {user: test.UserNoRoles, object: noTeamInstaller, action: read, allow: false}, + {user: test.UserNoRoles, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserNoRoles, object: team1Installer, action: read, allow: false}, + {user: test.UserNoRoles, object: team1Installer, action: write, allow: false}, + {user: test.UserNoRoles, object: team2Installer, action: read, allow: false}, + {user: test.UserNoRoles, object: team2Installer, action: write, allow: false}, + + {user: test.UserAdmin, object: noTeamInstaller, action: read, allow: true}, + {user: test.UserAdmin, object: noTeamInstaller, action: write, allow: true}, + {user: test.UserAdmin, object: team1Installer, action: read, allow: true}, + {user: test.UserAdmin, object: team1Installer, action: write, allow: true}, + {user: test.UserAdmin, object: team2Installer, action: read, allow: true}, + {user: test.UserAdmin, object: team2Installer, action: write, allow: true}, + + {user: test.UserMaintainer, object: noTeamInstaller, action: read, allow: true}, + {user: test.UserMaintainer, object: noTeamInstaller, action: write, allow: true}, + {user: test.UserMaintainer, object: team1Installer, action: read, allow: true}, + {user: test.UserMaintainer, object: team1Installer, action: write, allow: true}, + {user: test.UserMaintainer, object: team2Installer, action: read, allow: true}, + {user: test.UserMaintainer, object: team2Installer, action: write, allow: true}, + + {user: test.UserObserver, object: noTeamInstaller, action: read, allow: true}, + {user: test.UserObserver, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserObserver, object: team1Installer, action: read, allow: true}, + {user: test.UserObserver, object: team1Installer, action: write, allow: false}, + {user: test.UserObserver, object: team2Installer, action: read, allow: true}, + {user: test.UserObserver, object: team2Installer, action: write, allow: false}, + + {user: test.UserObserverPlus, object: noTeamInstaller, action: read, allow: true}, + {user: test.UserObserverPlus, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserObserverPlus, object: team1Installer, action: read, allow: true}, + {user: test.UserObserverPlus, object: team1Installer, action: write, allow: false}, + {user: test.UserObserverPlus, object: team2Installer, action: read, allow: true}, + {user: test.UserObserverPlus, 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: write, allow: true}, + {user: test.UserGitOps, object: team1Installer, action: read, allow: false}, + {user: test.UserGitOps, object: team1Installer, action: write, allow: true}, + {user: test.UserGitOps, object: team2Installer, action: read, allow: false}, + {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: write, allow: true}, + {user: test.UserTeamGitOpsTeam1, object: team2Installer, action: read, allow: false}, + {user: test.UserTeamGitOpsTeam1, object: team2Installer, action: write, allow: false}, + + {user: test.UserTeamAdminTeam1, object: noTeamInstaller, action: read, allow: false}, + {user: test.UserTeamAdminTeam1, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserTeamAdminTeam1, object: team1Installer, action: read, allow: true}, + {user: test.UserTeamAdminTeam1, object: team1Installer, action: write, allow: true}, + {user: test.UserTeamAdminTeam1, object: team2Installer, action: read, allow: false}, + {user: test.UserTeamAdminTeam1, object: team2Installer, action: write, allow: false}, + + {user: test.UserTeamMaintainerTeam1, object: noTeamInstaller, action: read, allow: false}, + {user: test.UserTeamMaintainerTeam1, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserTeamMaintainerTeam1, object: team1Installer, action: read, allow: true}, + {user: test.UserTeamMaintainerTeam1, object: team1Installer, action: write, allow: true}, + {user: test.UserTeamMaintainerTeam1, object: team2Installer, action: read, allow: false}, + {user: test.UserTeamMaintainerTeam1, object: team2Installer, action: write, allow: false}, + + {user: test.UserTeamObserverTeam1, object: noTeamInstaller, action: read, allow: false}, + {user: test.UserTeamObserverTeam1, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserTeamObserverTeam1, object: team1Installer, action: read, allow: true}, + {user: test.UserTeamObserverTeam1, object: team1Installer, action: write, allow: false}, + {user: test.UserTeamObserverTeam1, object: team2Installer, action: read, allow: false}, + {user: test.UserTeamObserverTeam1, object: team2Installer, action: write, allow: false}, + + {user: test.UserTeamObserverPlusTeam1, object: noTeamInstaller, action: read, allow: false}, + {user: test.UserTeamObserverPlusTeam1, object: noTeamInstaller, action: write, allow: false}, + {user: test.UserTeamObserverPlusTeam1, object: team1Installer, action: read, allow: true}, + {user: test.UserTeamObserverPlusTeam1, object: team1Installer, action: write, allow: false}, + {user: test.UserTeamObserverPlusTeam1, object: team2Installer, action: read, allow: false}, + {user: test.UserTeamObserverPlusTeam1, object: team2Installer, action: write, allow: false}, + }) +} + func TestAuthorizeHost(t *testing.T) { t.Parallel() diff --git a/server/fleet/software_installer.go b/server/fleet/software_installer.go index 4b4e532511..07a7af571d 100644 --- a/server/fleet/software_installer.go +++ b/server/fleet/software_installer.go @@ -18,8 +18,7 @@ type SoftwareInstallerStore interface { // FailingSoftwareInstallerStore is an implementation of SoftwareInstallerStore // that fails all operations. It is used when S3 is not configured and the // local filesystem store could not be setup. -type FailingSoftwareInstallerStore struct { -} +type FailingSoftwareInstallerStore struct{} func (FailingSoftwareInstallerStore) Get(ctx context.Context, installerID string) (io.ReadCloser, int64, error) { return nil, 0, errors.New("software installer store not properly configured") @@ -32,3 +31,16 @@ func (FailingSoftwareInstallerStore) Put(ctx context.Context, installerID string func (FailingSoftwareInstallerStore) Exists(ctx context.Context, installerID string) (bool, error) { return false, errors.New("software installer store not properly configured") } + +// SoftwareInstaller represents a software installer package that can be used to install software on +// hosts in Fleet. +type SoftwareInstaller struct { + // TeamID is the ID of the team. A value of nil means it is scoped to hosts that are assigned to + // no team. + TeamID *uint `json:"team_id"` +} + +// AuthzType implements authz.AuthzTyper. +func (s *SoftwareInstaller) AuthzType() string { + return "software_installer" +}