2021-10-11 14:37:48 +00:00
package service
import (
"context"
2023-02-08 23:20:23 +00:00
"crypto/x509"
"encoding/base64"
2022-06-10 21:52:24 +00:00
"errors"
2022-04-18 21:19:58 +00:00
"fmt"
2023-03-08 20:42:23 +00:00
"strconv"
2023-08-23 20:47:47 +00:00
"strings"
2021-10-11 14:37:48 +00:00
"testing"
2021-11-09 14:35:36 +00:00
"time"
2021-10-11 14:37:48 +00:00
2021-12-14 21:34:11 +00:00
"github.com/WatchBeam/clock"
2021-10-11 14:37:48 +00:00
"github.com/fleetdm/fleet/v4/server/authz"
2023-02-08 23:20:23 +00:00
"github.com/fleetdm/fleet/v4/server/config"
2021-12-14 21:34:11 +00:00
"github.com/fleetdm/fleet/v4/server/contexts/viewer"
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
2021-10-11 14:37:48 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
2023-02-08 23:20:23 +00:00
apple_mdm "github.com/fleetdm/fleet/v4/server/mdm/apple"
2023-03-17 21:52:30 +00:00
"github.com/fleetdm/fleet/v4/server/mdm/apple/mobileconfig"
2021-10-11 14:37:48 +00:00
"github.com/fleetdm/fleet/v4/server/mock"
2021-12-14 21:34:11 +00:00
"github.com/fleetdm/fleet/v4/server/ptr"
2021-10-11 14:37:48 +00:00
"github.com/fleetdm/fleet/v4/server/test"
2023-02-08 23:20:23 +00:00
nanodep_client "github.com/micromdm/nanodep/client"
"github.com/micromdm/nanodep/tokenpki"
2021-12-14 21:34:11 +00:00
"github.com/stretchr/testify/assert"
2021-10-11 14:37:48 +00:00
"github.com/stretchr/testify/require"
2023-02-08 23:20:23 +00:00
"go.mozilla.org/pkcs7"
2021-10-11 14:37:48 +00:00
)
2021-12-14 21:34:11 +00:00
func TestHostDetails ( t * testing . T ) {
ds := new ( mock . Store )
svc := & Service { ds : ds }
host := & fleet . Host { ID : 3 }
expectedLabels := [ ] * fleet . Label {
{
Name : "foobar" ,
Description : "the foobar label" ,
} ,
}
2023-02-22 22:26:06 +00:00
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig { } , nil
}
2021-12-14 21:34:11 +00:00
ds . ListLabelsForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Label , error ) {
return expectedLabels , nil
}
expectedPacks := [ ] * fleet . Pack {
{
Name : "pack1" ,
} ,
{
Name : "pack2" ,
} ,
}
ds . ListPacksForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Pack , error ) {
return expectedPacks , nil
}
2022-06-01 16:06:57 +00:00
ds . LoadHostSoftwareFunc = func ( ctx context . Context , host * fleet . Host , includeCVEScores bool ) error {
2021-12-14 21:34:11 +00:00
return nil
}
ds . ListPoliciesForHostFunc = func ( ctx context . Context , host * fleet . Host ) ( [ ] * fleet . HostPolicy , error ) {
return nil , nil
}
2022-07-21 02:16:03 +00:00
dsBats := [ ] * fleet . HostBattery { { HostID : host . ID , SerialNumber : "a" , CycleCount : 999 , Health : "Check Battery" } , { HostID : host . ID , SerialNumber : "b" , CycleCount : 1001 , Health : "Good" } }
2022-06-28 18:11:49 +00:00
ds . ListHostBatteriesFunc = func ( ctx context . Context , hostID uint ) ( [ ] * fleet . HostBattery , error ) {
2022-07-21 02:16:03 +00:00
return dsBats , nil
2022-06-28 18:11:49 +00:00
}
2022-07-21 02:16:03 +00:00
// Health should be replaced at the service layer with custom values determined by the cycle count. See https://github.com/fleetdm/fleet/issues/6763.
expectedBats := [ ] * fleet . HostBattery { { HostID : host . ID , SerialNumber : "a" , CycleCount : 999 , Health : "Normal" } , { HostID : host . ID , SerialNumber : "b" , CycleCount : 1001 , Health : "Replacement recommended" } }
2021-12-14 21:34:11 +00:00
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
opts := fleet . HostDetailOptions {
IncludeCVEScores : false ,
IncludePolicies : false ,
}
2022-11-15 14:08:05 +00:00
hostDetail , err := svc . getHostDetails ( test . UserContext ( context . Background ( ) , test . UserAdmin ) , host , opts )
2021-12-14 21:34:11 +00:00
require . NoError ( t , err )
assert . Equal ( t , expectedLabels , hostDetail . Labels )
assert . Equal ( t , expectedPacks , hostDetail . Packs )
2022-06-28 18:11:49 +00:00
require . NotNil ( t , hostDetail . Batteries )
assert . Equal ( t , expectedBats , * hostDetail . Batteries )
2023-03-08 20:42:23 +00:00
require . Nil ( t , hostDetail . MDM . MacOSSettings )
}
func TestHostDetailsMDMDiskEncryption ( t * testing . T ) {
ds := new ( mock . Store )
svc := & Service { ds : ds }
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig { MDM : fleet . MDM { EnabledAndConfigured : true } } , nil
}
ds . ListLabelsForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Label , error ) {
return nil , nil
}
ds . ListPacksForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Pack , error ) {
return nil , nil
}
ds . LoadHostSoftwareFunc = func ( ctx context . Context , host * fleet . Host , includeCVEScores bool ) error {
return nil
}
ds . ListPoliciesForHostFunc = func ( ctx context . Context , host * fleet . Host ) ( [ ] * fleet . HostPolicy , error ) {
return nil , nil
}
ds . ListHostBatteriesFunc = func ( ctx context . Context , hostID uint ) ( [ ] * fleet . HostBattery , error ) {
return nil , nil
}
cases := [ ] struct {
name string
rawDecrypt * int
fvProf * fleet . HostMDMAppleProfile
2023-04-24 21:27:15 +00:00
wantState fleet . DiskEncryptionStatus
2023-03-08 20:42:23 +00:00
wantAction fleet . ActionRequiredState
2023-03-20 20:22:57 +00:00
wantStatus * fleet . MDMAppleDeliveryStatus
2023-03-08 20:42:23 +00:00
} {
2023-03-20 20:22:57 +00:00
{ "no profile" , ptr . Int ( - 1 ) , nil , "" , "" , nil } ,
2023-03-08 20:42:23 +00:00
{
"installed profile, no key" ,
ptr . Int ( - 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-04-24 21:27:15 +00:00
Status : & fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionActionRequired ,
fleet . ActionRequiredLogOut ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"installed profile, unknown decryptable" ,
nil ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-04-24 21:27:15 +00:00
Status : & fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionEnforcing ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"installed profile, not decryptable" ,
ptr . Int ( 0 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-04-24 21:27:15 +00:00
Status : & fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionActionRequired ,
fleet . ActionRequiredRotateKey ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"installed profile, decryptable" ,
ptr . Int ( 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-04-24 21:27:15 +00:00
Status : & fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
2023-04-24 21:27:15 +00:00
fleet . DiskEncryptionVerifying ,
2023-03-08 20:42:23 +00:00
"" ,
2023-04-24 21:27:15 +00:00
& fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
} ,
2023-06-05 17:05:28 +00:00
{
"installed profile, decryptable, verified" ,
ptr . Int ( 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
Status : & fleet . MDMAppleDeliveryVerified ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionVerified ,
"" ,
& fleet . MDMAppleDeliveryVerified ,
} ,
2023-03-08 20:42:23 +00:00
{
"pending install, decryptable" ,
ptr . Int ( 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryPending ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionEnforcing ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"pending install, unknown decryptable" ,
nil ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryPending ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionEnforcing ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"pending install, no key" ,
ptr . Int ( - 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryPending ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionEnforcing ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"failed install, no key" ,
ptr . Int ( - 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryFailed ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionFailed ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryFailed ,
2023-03-08 20:42:23 +00:00
} ,
{
"failed install, not decryptable" ,
ptr . Int ( 0 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryFailed ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
} ,
fleet . DiskEncryptionFailed ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryFailed ,
2023-03-08 20:42:23 +00:00
} ,
{
"pending remove, decryptable" ,
ptr . Int ( 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryPending ,
OperationType : fleet . MDMAppleOperationTypeRemove ,
} ,
fleet . DiskEncryptionRemovingEnforcement ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"pending remove, no key" ,
ptr . Int ( - 1 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryPending ,
OperationType : fleet . MDMAppleOperationTypeRemove ,
} ,
fleet . DiskEncryptionRemovingEnforcement ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryPending ,
2023-03-08 20:42:23 +00:00
} ,
{
"failed remove, unknown decryptable" ,
nil ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-03-08 20:42:23 +00:00
Status : & fleet . MDMAppleDeliveryFailed ,
OperationType : fleet . MDMAppleOperationTypeRemove ,
} ,
fleet . DiskEncryptionFailed ,
"" ,
2023-03-20 20:22:57 +00:00
& fleet . MDMAppleDeliveryFailed ,
2023-03-08 20:42:23 +00:00
} ,
{
"removed profile, not decryptable" ,
ptr . Int ( 0 ) ,
& fleet . HostMDMAppleProfile {
HostUUID : "abc" ,
2023-03-17 21:52:30 +00:00
Identifier : mobileconfig . FleetFileVaultPayloadIdentifier ,
2023-04-24 21:27:15 +00:00
Status : & fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
OperationType : fleet . MDMAppleOperationTypeRemove ,
} ,
"" ,
"" ,
2023-04-24 21:27:15 +00:00
& fleet . MDMAppleDeliveryVerifying ,
2023-03-08 20:42:23 +00:00
} ,
}
for _ , c := range cases {
t . Run ( c . name , func ( t * testing . T ) {
var mdmData fleet . MDMHostData
rawDecrypt := "null"
if c . rawDecrypt != nil {
rawDecrypt = strconv . Itoa ( * c . rawDecrypt )
}
require . NoError ( t , mdmData . Scan ( [ ] byte ( fmt . Sprintf ( ` { "raw_decryptable": %s} ` , rawDecrypt ) ) ) )
host := & fleet . Host { ID : 3 , MDM : mdmData , UUID : "abc" }
opts := fleet . HostDetailOptions {
IncludeCVEScores : false ,
IncludePolicies : false ,
}
ds . GetHostMDMProfilesFunc = func ( ctx context . Context , uuid string ) ( [ ] fleet . HostMDMAppleProfile , error ) {
if c . fvProf == nil {
return nil , nil
}
return [ ] fleet . HostMDMAppleProfile { * c . fvProf } , nil
}
hostDetail , err := svc . getHostDetails ( test . UserContext ( context . Background ( ) , test . UserAdmin ) , host , opts )
require . NoError ( t , err )
2023-03-20 20:22:57 +00:00
if c . wantState == "" {
2023-03-08 20:42:23 +00:00
require . Nil ( t , hostDetail . MDM . MacOSSettings . DiskEncryption )
} else {
require . NotNil ( t , hostDetail . MDM . MacOSSettings . DiskEncryption )
2023-03-20 20:22:57 +00:00
require . Equal ( t , c . wantState , * hostDetail . MDM . MacOSSettings . DiskEncryption )
2023-03-08 20:42:23 +00:00
}
if c . wantAction == "" {
require . Nil ( t , hostDetail . MDM . MacOSSettings . ActionRequired )
} else {
require . NotNil ( t , hostDetail . MDM . MacOSSettings . ActionRequired )
require . Equal ( t , c . wantAction , * hostDetail . MDM . MacOSSettings . ActionRequired )
}
2023-03-20 20:22:57 +00:00
if c . wantStatus != nil {
require . NotNil ( t , hostDetail . MDM . Profiles )
profs := * hostDetail . MDM . Profiles
require . Equal ( t , c . wantStatus , profs [ 0 ] . Status )
} else {
require . Nil ( t , * hostDetail . MDM . Profiles )
}
2023-03-08 20:42:23 +00:00
} )
}
2021-12-14 21:34:11 +00:00
}
func TestHostAuth ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
teamHost := & fleet . Host { TeamID : ptr . Uint ( 1 ) }
globalHost := & fleet . Host { }
2023-02-22 22:26:06 +00:00
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig { } , nil
}
2021-12-14 21:34:11 +00:00
ds . DeleteHostFunc = func ( ctx context . Context , hid uint ) error {
return nil
}
2022-01-18 01:52:09 +00:00
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
if id == 1 {
return teamHost , nil
}
return globalHost , nil
}
2022-05-25 16:30:03 +00:00
ds . HostFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
2021-12-14 21:34:11 +00:00
if id == 1 {
return teamHost , nil
}
return globalHost , nil
}
ds . HostByIdentifierFunc = func ( ctx context . Context , identifier string ) ( * fleet . Host , error ) {
if identifier == "1" {
return teamHost , nil
}
return globalHost , nil
}
ds . ListHostsFunc = func ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
return nil , nil
}
2022-06-01 16:06:57 +00:00
ds . LoadHostSoftwareFunc = func ( ctx context . Context , host * fleet . Host , includeCVEScores bool ) error {
2021-12-14 21:34:11 +00:00
return nil
}
ds . ListLabelsForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Label , error ) {
return nil , nil
}
ds . ListPacksForHostFunc = func ( ctx context . Context , hid uint ) ( packs [ ] * fleet . Pack , err error ) {
return nil , nil
}
ds . AddHostsToTeamFunc = func ( ctx context . Context , teamID * uint , hostIDs [ ] uint ) error {
return nil
}
ds . ListPoliciesForHostFunc = func ( ctx context . Context , host * fleet . Host ) ( [ ] * fleet . HostPolicy , error ) {
return nil , nil
}
2022-06-28 18:11:49 +00:00
ds . ListHostBatteriesFunc = func ( ctx context . Context , hostID uint ) ( [ ] * fleet . HostBattery , error ) {
return nil , nil
}
2021-12-14 21:34:11 +00:00
ds . DeleteHostsFunc = func ( ctx context . Context , ids [ ] uint ) error {
return nil
}
2022-01-18 01:52:09 +00:00
ds . UpdateHostRefetchRequestedFunc = func ( ctx context . Context , id uint , value bool ) error {
if id == 1 {
teamHost . RefetchRequested = true
} else {
globalHost . RefetchRequested = true
}
return nil
}
2023-03-27 18:43:01 +00:00
ds . BulkSetPendingMDMAppleHostProfilesFunc = func ( ctx context . Context , hids , tids , pids [ ] uint , uuids [ ] string ) error {
return nil
}
2023-05-15 18:06:09 +00:00
ds . ListMDMAppleDEPSerialsInHostIDsFunc = func ( ctx context . Context , hids [ ] uint ) ( [ ] string , error ) {
return nil , nil
}
2023-06-14 12:15:05 +00:00
ds . TeamFunc = func ( ctx context . Context , id uint ) ( * fleet . Team , error ) {
return & fleet . Team { ID : id } , nil
}
ds . NewActivityFunc = func ( ctx context . Context , u * fleet . User , a fleet . ActivityDetails ) error {
return nil
}
ds . ListHostsLiteByIDsFunc = func ( ctx context . Context , ids [ ] uint ) ( [ ] * fleet . Host , error ) {
return nil , nil
}
2021-12-14 21:34:11 +00:00
testCases := [ ] struct {
name string
user * fleet . User
shouldFailGlobalWrite bool
shouldFailGlobalRead bool
shouldFailTeamWrite bool
shouldFailTeamRead bool
} {
{
"global admin" ,
& fleet . User { GlobalRole : ptr . String ( fleet . RoleAdmin ) } ,
false ,
false ,
false ,
false ,
} ,
{
"global maintainer" ,
& fleet . User { GlobalRole : ptr . String ( fleet . RoleMaintainer ) } ,
false ,
false ,
false ,
false ,
} ,
{
"global observer" ,
& fleet . User { GlobalRole : ptr . String ( fleet . RoleObserver ) } ,
true ,
false ,
true ,
false ,
} ,
{
"team maintainer, belongs to team" ,
& fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleMaintainer } } } ,
true ,
true ,
false ,
false ,
} ,
{
"team observer, belongs to team" ,
& fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleObserver } } } ,
true ,
true ,
true ,
false ,
} ,
{
"team maintainer, DOES NOT belong to team" ,
& fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleMaintainer } } } ,
true ,
true ,
true ,
true ,
} ,
{
"team observer, DOES NOT belong to team" ,
& fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleObserver } } } ,
true ,
true ,
true ,
true ,
} ,
}
for _ , tt := range testCases {
t . Run ( tt . name , func ( t * testing . T ) {
2022-11-15 14:08:05 +00:00
ctx := viewer . NewContext ( ctx , viewer . Viewer { User : tt . user } )
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
opts := fleet . HostDetailOptions {
IncludeCVEScores : false ,
IncludePolicies : false ,
}
2021-12-14 21:34:11 +00:00
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
_ , err := svc . GetHost ( ctx , 1 , opts )
2021-12-14 21:34:11 +00:00
checkAuthErr ( t , tt . shouldFailTeamRead , err )
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
_ , err = svc . HostByIdentifier ( ctx , "1" , opts )
2021-12-14 21:34:11 +00:00
checkAuthErr ( t , tt . shouldFailTeamRead , err )
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
_ , err = svc . GetHost ( ctx , 2 , opts )
2021-12-14 21:34:11 +00:00
checkAuthErr ( t , tt . shouldFailGlobalRead , err )
only include policies in device endpoints for premium users (#6077)
This removes policy information from `GET /api/_version_/fleet/device/{token}` from non-premium Fleet instances.
Starting the server with `./build/fleet serve --dev --dev_license`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
[
{
"id": 3,
"name": "Antivirus healthy (Linux)",
"query": "SELECT score FROM (SELECT case when COUNT(*) = 2 then 1 ELSE 0 END AS score FROM processes WHERE (name = 'clamd') OR (name = 'freshclam')) WHERE score == 1;",
"description": "Checks that both ClamAV's daemon and its updater service (freshclam) are running.",
"author_id": 1,
"author_name": "Roberto",
"author_email": "test@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-06-03T13:17:42Z",
"response": ""
}
]
```
Starting the server with `./build/fleet serve --dev`
```bash
$ curl -s https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a | jq '.host.policies // "not present"'
"not present"
```
2022-06-07 16:27:13 +00:00
_ , err = svc . HostByIdentifier ( ctx , "2" , opts )
2021-12-14 21:34:11 +00:00
checkAuthErr ( t , tt . shouldFailGlobalRead , err )
err = svc . DeleteHost ( ctx , 1 )
checkAuthErr ( t , tt . shouldFailTeamWrite , err )
err = svc . DeleteHost ( ctx , 2 )
checkAuthErr ( t , tt . shouldFailGlobalWrite , err )
err = svc . DeleteHosts ( ctx , [ ] uint { 1 } , fleet . HostListOptions { } , nil )
checkAuthErr ( t , tt . shouldFailTeamWrite , err )
err = svc . DeleteHosts ( ctx , [ ] uint { 2 } , fleet . HostListOptions { } , nil )
checkAuthErr ( t , tt . shouldFailGlobalWrite , err )
err = svc . AddHostsToTeam ( ctx , ptr . Uint ( 1 ) , [ ] uint { 1 } )
checkAuthErr ( t , tt . shouldFailTeamWrite , err )
err = svc . AddHostsToTeamByFilter ( ctx , ptr . Uint ( 1 ) , fleet . HostListOptions { } , nil )
checkAuthErr ( t , tt . shouldFailTeamWrite , err )
err = svc . RefetchHost ( ctx , 1 )
checkAuthErr ( t , tt . shouldFailTeamRead , err )
} )
}
2022-05-10 15:29:17 +00:00
// List, GetHostSummary work for all
2021-12-14 21:34:11 +00:00
}
2022-01-18 01:52:09 +00:00
2021-10-11 14:37:48 +00:00
func TestListHosts ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-10-11 14:37:48 +00:00
ds . ListHostsFunc = func ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
return [ ] * fleet . Host {
{ ID : 1 } ,
} , nil
}
2022-11-15 14:08:05 +00:00
hosts , err := svc . ListHosts ( test . UserContext ( ctx , test . UserAdmin ) , fleet . HostListOptions { } )
2021-10-11 14:37:48 +00:00
require . NoError ( t , err )
require . Len ( t , hosts , 1 )
// a user is required
2022-11-15 14:08:05 +00:00
_ , err = svc . ListHosts ( ctx , fleet . HostListOptions { } )
2021-10-11 14:37:48 +00:00
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , authz . ForbiddenErrorMessage )
}
2021-11-09 14:35:36 +00:00
func TestGetHostSummary ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-11-09 14:35:36 +00:00
2022-09-21 19:56:17 +00:00
ds . GenerateHostStatusStatisticsFunc = func ( ctx context . Context , filter fleet . TeamFilter , now time . Time , platform * string , lowDiskSpace * int ) ( * fleet . HostSummary , error ) {
2021-11-09 14:35:36 +00:00
return & fleet . HostSummary {
OnlineCount : 1 ,
2022-05-23 19:11:02 +00:00
OfflineCount : 5 , // offline hosts also includes mia hosts as of Fleet 4.15
2021-11-09 14:35:36 +00:00
MIACount : 3 ,
NewCount : 4 ,
TotalsHostsCount : 5 ,
2022-05-10 15:32:55 +00:00
Platforms : [ ] * fleet . HostSummaryPlatform { { Platform : "darwin" , HostsCount : 1 } , { Platform : "debian" , HostsCount : 2 } , { Platform : "centos" , HostsCount : 3 } , { Platform : "ubuntu" , HostsCount : 4 } } ,
2021-11-09 14:35:36 +00:00
} , nil
}
2022-05-10 15:32:55 +00:00
ds . LabelsSummaryFunc = func ( ctx context . Context ) ( [ ] * fleet . LabelSummary , error ) {
return [ ] * fleet . LabelSummary { { ID : 1 , Name : "All hosts" , Description : "All hosts enrolled in Fleet" , LabelType : fleet . LabelTypeBuiltIn } , { ID : 10 , Name : "Other label" , Description : "Not a builtin label" , LabelType : fleet . LabelTypeRegular } } , nil
}
2021-11-09 14:35:36 +00:00
2022-11-15 14:08:05 +00:00
summary , err := svc . GetHostSummary ( test . UserContext ( ctx , test . UserAdmin ) , nil , nil , nil )
2021-11-09 14:35:36 +00:00
require . NoError ( t , err )
2021-11-15 14:56:13 +00:00
require . Nil ( t , summary . TeamID )
2021-11-09 14:35:36 +00:00
require . Equal ( t , uint ( 1 ) , summary . OnlineCount )
2022-05-23 19:11:02 +00:00
require . Equal ( t , uint ( 5 ) , summary . OfflineCount )
2021-11-09 14:35:36 +00:00
require . Equal ( t , uint ( 3 ) , summary . MIACount )
require . Equal ( t , uint ( 4 ) , summary . NewCount )
require . Equal ( t , uint ( 5 ) , summary . TotalsHostsCount )
2022-05-10 15:32:55 +00:00
require . Len ( t , summary . Platforms , 4 )
require . Equal ( t , uint ( 9 ) , summary . AllLinuxCount )
2022-09-21 19:56:17 +00:00
require . Nil ( t , summary . LowDiskSpaceCount )
2022-05-10 15:32:55 +00:00
require . Len ( t , summary . BuiltinLabels , 1 )
require . Equal ( t , "All hosts" , summary . BuiltinLabels [ 0 ] . Name )
2021-11-09 14:35:36 +00:00
// a user is required
2022-11-15 14:08:05 +00:00
_ , err = svc . GetHostSummary ( ctx , nil , nil , nil )
2021-11-09 14:35:36 +00:00
require . Error ( t , err )
require . Contains ( t , err . Error ( ) , authz . ForbiddenErrorMessage )
}
2021-12-14 21:34:11 +00:00
func TestDeleteHost ( t * testing . T ) {
ds := mysql . CreateMySQLDS ( t )
defer ds . Close ( )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
mockClock := clock . NewMockClock ( )
host := test . NewHost ( t , ds , "foo" , "192.168.1.10" , "1" , "1" , mockClock . Now ( ) )
assert . NotZero ( t , host . ID )
2022-11-15 14:08:05 +00:00
err := svc . DeleteHost ( test . UserContext ( ctx , test . UserAdmin ) , host . ID )
2021-12-14 21:34:11 +00:00
assert . Nil ( t , err )
filter := fleet . TeamFilter { User : test . UserAdmin }
2022-11-15 14:08:05 +00:00
hosts , err := ds . ListHosts ( ctx , filter , fleet . HostListOptions { } )
2021-12-14 21:34:11 +00:00
assert . Nil ( t , err )
assert . Len ( t , hosts , 0 )
}
func TestAddHostsToTeamByFilter ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
expectedHostIDs := [ ] uint { 1 , 2 , 4 }
expectedTeam := ( * uint ) ( nil )
ds . ListHostsFunc = func ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
var hosts [ ] * fleet . Host
for _ , id := range expectedHostIDs {
hosts = append ( hosts , & fleet . Host { ID : id } )
}
return hosts , nil
}
ds . AddHostsToTeamFunc = func ( ctx context . Context , teamID * uint , hostIDs [ ] uint ) error {
assert . Equal ( t , expectedTeam , teamID )
assert . Equal ( t , expectedHostIDs , hostIDs )
return nil
}
2023-03-27 18:43:01 +00:00
ds . BulkSetPendingMDMAppleHostProfilesFunc = func ( ctx context . Context , hids , tids , pids [ ] uint , uuids [ ] string ) error {
return nil
}
2023-05-15 18:06:09 +00:00
ds . ListMDMAppleDEPSerialsInHostIDsFunc = func ( ctx context . Context , hids [ ] uint ) ( [ ] string , error ) {
return nil , nil
}
2023-06-14 12:15:05 +00:00
ds . NewActivityFunc = func ( ctx context . Context , user * fleet . User , activity fleet . ActivityDetails ) error {
return nil
}
2021-12-14 21:34:11 +00:00
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . AddHostsToTeamByFilter ( test . UserContext ( ctx , test . UserAdmin ) , expectedTeam , fleet . HostListOptions { } , nil ) )
2021-12-14 21:34:11 +00:00
assert . True ( t , ds . ListHostsFuncInvoked )
assert . True ( t , ds . AddHostsToTeamFuncInvoked )
}
func TestAddHostsToTeamByFilterLabel ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
expectedHostIDs := [ ] uint { 6 }
expectedTeam := ptr . Uint ( 1 )
expectedLabel := ptr . Uint ( 2 )
ds . ListHostsInLabelFunc = func ( ctx context . Context , filter fleet . TeamFilter , lid uint , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
assert . Equal ( t , * expectedLabel , lid )
var hosts [ ] * fleet . Host
for _ , id := range expectedHostIDs {
hosts = append ( hosts , & fleet . Host { ID : id } )
}
return hosts , nil
}
ds . AddHostsToTeamFunc = func ( ctx context . Context , teamID * uint , hostIDs [ ] uint ) error {
assert . Equal ( t , expectedHostIDs , hostIDs )
return nil
}
2023-03-27 18:43:01 +00:00
ds . BulkSetPendingMDMAppleHostProfilesFunc = func ( ctx context . Context , hids , tids , pids [ ] uint , uuids [ ] string ) error {
return nil
}
2023-05-15 18:06:09 +00:00
ds . ListMDMAppleDEPSerialsInHostIDsFunc = func ( ctx context . Context , hids [ ] uint ) ( [ ] string , error ) {
return nil , nil
}
2023-06-14 12:15:05 +00:00
ds . TeamFunc = func ( ctx context . Context , id uint ) ( * fleet . Team , error ) {
return & fleet . Team { ID : id } , nil
}
ds . NewActivityFunc = func ( ctx context . Context , user * fleet . User , activity fleet . ActivityDetails ) error {
return nil
}
2021-12-14 21:34:11 +00:00
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . AddHostsToTeamByFilter ( test . UserContext ( ctx , test . UserAdmin ) , expectedTeam , fleet . HostListOptions { } , expectedLabel ) )
2021-12-14 21:34:11 +00:00
assert . True ( t , ds . ListHostsInLabelFuncInvoked )
assert . True ( t , ds . AddHostsToTeamFuncInvoked )
}
func TestAddHostsToTeamByFilterEmptyHosts ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
ds . ListHostsFunc = func ( ctx context . Context , filter fleet . TeamFilter , opt fleet . HostListOptions ) ( [ ] * fleet . Host , error ) {
return [ ] * fleet . Host { } , nil
}
ds . AddHostsToTeamFunc = func ( ctx context . Context , teamID * uint , hostIDs [ ] uint ) error {
return nil
}
2023-03-27 18:43:01 +00:00
ds . BulkSetPendingMDMAppleHostProfilesFunc = func ( ctx context . Context , hids , tids , pids [ ] uint , uuids [ ] string ) error {
return nil
}
2021-12-14 21:34:11 +00:00
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . AddHostsToTeamByFilter ( test . UserContext ( ctx , test . UserAdmin ) , nil , fleet . HostListOptions { } , nil ) )
2021-12-14 21:34:11 +00:00
assert . True ( t , ds . ListHostsFuncInvoked )
assert . False ( t , ds . AddHostsToTeamFuncInvoked )
}
func TestRefetchHost ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
host := & fleet . Host { ID : 3 }
2022-01-18 01:52:09 +00:00
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
2021-12-14 21:34:11 +00:00
return host , nil
}
2022-01-18 01:52:09 +00:00
ds . UpdateHostRefetchRequestedFunc = func ( ctx context . Context , id uint , value bool ) error {
assert . Equal ( t , host . ID , id )
assert . True ( t , value )
2021-12-14 21:34:11 +00:00
return nil
}
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , test . UserAdmin ) , host . ID ) )
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , test . UserObserver ) , host . ID ) )
2023-04-05 18:23:49 +00:00
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , test . UserObserverPlus ) , host . ID ) )
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , test . UserMaintainer ) , host . ID ) )
2022-01-18 01:52:09 +00:00
assert . True ( t , ds . HostLiteFuncInvoked )
assert . True ( t , ds . UpdateHostRefetchRequestedFuncInvoked )
2021-12-14 21:34:11 +00:00
}
func TestRefetchHostUserInTeams ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2021-12-14 21:34:11 +00:00
host := & fleet . Host { ID : 3 , TeamID : ptr . Uint ( 4 ) }
2022-01-18 01:52:09 +00:00
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
2021-12-14 21:34:11 +00:00
return host , nil
}
2022-01-18 01:52:09 +00:00
ds . UpdateHostRefetchRequestedFunc = func ( ctx context . Context , id uint , value bool ) error {
assert . Equal ( t , host . ID , id )
assert . True ( t , value )
2021-12-14 21:34:11 +00:00
return nil
}
maintainer := & fleet . User {
Teams : [ ] fleet . UserTeam {
{
Team : fleet . Team { ID : 4 } ,
Role : fleet . RoleMaintainer ,
} ,
} ,
}
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , maintainer ) , host . ID ) )
2022-01-18 01:52:09 +00:00
assert . True ( t , ds . HostLiteFuncInvoked )
assert . True ( t , ds . UpdateHostRefetchRequestedFuncInvoked )
ds . HostLiteFuncInvoked , ds . UpdateHostRefetchRequestedFuncInvoked = false , false
2021-12-14 21:34:11 +00:00
observer := & fleet . User {
Teams : [ ] fleet . UserTeam {
{
Team : fleet . Team { ID : 4 } ,
Role : fleet . RoleObserver ,
} ,
} ,
}
2022-11-15 14:08:05 +00:00
require . NoError ( t , svc . RefetchHost ( test . UserContext ( ctx , observer ) , host . ID ) )
2022-01-18 01:52:09 +00:00
assert . True ( t , ds . HostLiteFuncInvoked )
assert . True ( t , ds . UpdateHostRefetchRequestedFuncInvoked )
2021-12-14 21:34:11 +00:00
}
2022-04-18 21:19:58 +00:00
func TestEmptyTeamOSVersions ( t * testing . T ) {
ds := new ( mock . Store )
2022-11-15 14:08:05 +00:00
svc , ctx := newTestService ( t , ds , nil , nil )
2022-04-18 21:19:58 +00:00
testVersions := [ ] fleet . OSVersion { { HostsCount : 1 , Name : "macOS 12.1" , Platform : "darwin" } }
ds . TeamFunc = func ( ctx context . Context , teamID uint ) ( * fleet . Team , error ) {
if teamID == 1 {
return & fleet . Team {
Name : "team1" ,
} , nil
}
if teamID == 2 {
return & fleet . Team {
Name : "team2" ,
} , nil
}
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [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)
- ~[ ] 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:
- [X] 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)).~
2023-03-13 16:44:06 +00:00
return nil , newNotFoundError ( )
2022-04-18 21:19:58 +00:00
}
2022-08-22 19:34:00 +00:00
ds . OSVersionsFunc = func ( ctx context . Context , teamID * uint , platform * string , name * string , version * string ) ( * fleet . OSVersions , error ) {
2022-04-18 21:19:58 +00:00
if * teamID == 1 {
return & fleet . OSVersions { CountsUpdatedAt : time . Now ( ) , OSVersions : testVersions } , nil
}
if * teamID == 4 {
2022-06-10 21:52:24 +00:00
return nil , errors . New ( "some unknown error" )
2022-04-18 21:19:58 +00:00
}
Add UUID to Fleet errors and clean up error msgs (#10411)
#8129
Apart from fixing the issue in #8129, this change also introduces UUIDs
to Fleet errors. To be able to match a returned error from the API to a
error in the Fleet logs. See
https://fleetdm.slack.com/archives/C019WG4GH0A/p1677780622769939 for
more context.
Samples with the changes in this PR:
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d ''
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "Expected JSON Body"
}
],
"uuid": "a01f6e10-354c-4ff0-b96e-1f64adb500b0"
}
```
```
curl -k -H "Authorization: Bearer $TEST_TOKEN" -H 'Content-Type:application/json' "https://localhost:8080/api/v1/fleet/sso" -d 'asd'
{
"message": "Bad request",
"errors": [
{
"name": "base",
"reason": "json decoder error"
}
],
"uuid": "5f716a64-7550-464b-a1dd-e6a505a9f89d"
}
```
```
curl -k -X GET -H "Authorization: Bearer badtoken" "https://localhost:8080/api/latest/fleet/teams"
{
"message": "Authentication required",
"errors": [
{
"name": "base",
"reason": "Authentication required"
}
],
"uuid": "efe45bc0-f956-4bf9-ba4f-aa9020a9aaaf"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Authorization header required",
"errors": [
{
"name": "base",
"reason": "Authorization header required"
}
],
"uuid": "57f78cd0-4559-464f-9df7-36c9ef7c89b3"
}
```
```
curl -k -X PATCH -H "Authorization: Bearer $TEST_TOKEN" "https://localhost:8080/api/latest/fleet/users/14" -d '{"name": "Manuel2", "password": "what", "new_password": "p4ssw0rd.12345"}'
{
"message": "Permission Denied",
"uuid": "7f0220ad-6de7-4faf-8b6c-8d7ff9d2ca06"
}
```
- [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)
- ~[ ] 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:
- [X] 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)).~
2023-03-13 16:44:06 +00:00
return nil , newNotFoundError ( )
2022-04-18 21:19:58 +00:00
}
// team exists with stats
2022-11-15 14:08:05 +00:00
vers , err := svc . OSVersions ( test . UserContext ( ctx , test . UserAdmin ) , ptr . Uint ( 1 ) , ptr . String ( "darwin" ) , nil , nil )
2022-04-18 21:19:58 +00:00
require . NoError ( t , err )
assert . Len ( t , vers . OSVersions , 1 )
// team exists but no stats
2022-11-15 14:08:05 +00:00
vers , err = svc . OSVersions ( test . UserContext ( ctx , test . UserAdmin ) , ptr . Uint ( 2 ) , ptr . String ( "darwin" ) , nil , nil )
2022-04-18 21:19:58 +00:00
require . NoError ( t , err )
assert . Empty ( t , vers . OSVersions )
// team does not exist
2022-11-15 14:08:05 +00:00
_ , err = svc . OSVersions ( test . UserContext ( ctx , test . UserAdmin ) , ptr . Uint ( 3 ) , ptr . String ( "darwin" ) , nil , nil )
2022-04-18 21:19:58 +00:00
require . Error ( t , err )
require . Equal ( t , "not found" , fmt . Sprint ( err ) )
// some unknown error
2022-11-15 14:08:05 +00:00
_ , err = svc . OSVersions ( test . UserContext ( ctx , test . UserAdmin ) , ptr . Uint ( 4 ) , ptr . String ( "darwin" ) , nil , nil )
2022-04-18 21:19:58 +00:00
require . Error ( t , err )
require . Equal ( t , "some unknown error" , fmt . Sprint ( err ) )
}
2023-02-08 23:20:23 +00:00
func TestHostEncryptionKey ( t * testing . T ) {
cases := [ ] struct {
name string
host * fleet . Host
allowedUsers [ ] * fleet . User
disallowedUsers [ ] * fleet . User
} {
{
name : "global host" ,
host : & fleet . Host {
ID : 1 ,
Platform : "darwin" ,
NodeKey : ptr . String ( "test_key" ) ,
Hostname : "test_hostname" ,
UUID : "test_uuid" ,
TeamID : nil ,
} ,
allowedUsers : [ ] * fleet . User {
test . UserAdmin ,
test . UserMaintainer ,
test . UserObserver ,
2023-04-05 18:23:49 +00:00
test . UserObserverPlus ,
2023-02-08 23:20:23 +00:00
} ,
disallowedUsers : [ ] * fleet . User {
test . UserTeamAdminTeam1 ,
test . UserTeamMaintainerTeam1 ,
test . UserTeamObserverTeam1 ,
test . UserNoRoles ,
} ,
} ,
{
name : "team host" ,
host : & fleet . Host {
ID : 2 ,
Platform : "darwin" ,
NodeKey : ptr . String ( "test_key_2" ) ,
Hostname : "test_hostname_2" ,
UUID : "test_uuid_2" ,
TeamID : ptr . Uint ( 1 ) ,
} ,
allowedUsers : [ ] * fleet . User {
test . UserAdmin ,
test . UserMaintainer ,
test . UserObserver ,
2023-04-05 18:23:49 +00:00
test . UserObserverPlus ,
2023-02-08 23:20:23 +00:00
test . UserTeamAdminTeam1 ,
test . UserTeamMaintainerTeam1 ,
test . UserTeamObserverTeam1 ,
2023-04-05 18:23:49 +00:00
test . UserTeamObserverPlusTeam1 ,
2023-02-08 23:20:23 +00:00
} ,
disallowedUsers : [ ] * fleet . User {
test . UserTeamAdminTeam2 ,
test . UserTeamMaintainerTeam2 ,
test . UserTeamObserverTeam2 ,
2023-04-05 18:23:49 +00:00
test . UserTeamObserverPlusTeam2 ,
2023-02-08 23:20:23 +00:00
test . UserNoRoles ,
} ,
} ,
}
testBMToken := & nanodep_client . OAuth1Tokens {
ConsumerKey : "test_consumer" ,
ConsumerSecret : "test_secret" ,
AccessToken : "test_access_token" ,
AccessSecret : "test_access_secret" ,
AccessTokenExpiry : time . Date ( 2999 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ,
}
testCert , testKey , err := apple_mdm . NewSCEPCACertKey ( )
require . NoError ( t , err )
testCertPEM := tokenpki . PEMCertificate ( testCert . Raw )
testKeyPEM := tokenpki . PEMRSAPrivateKey ( testKey )
fleetCfg := config . TestConfig ( )
config . SetTestMDMConfig ( t , & fleetCfg , testCertPEM , testKeyPEM , testBMToken )
recoveryKey := "AAA-BBB-CCC"
encryptedKey , err := pkcs7 . Encrypt ( [ ] byte ( recoveryKey ) , [ ] * x509 . Certificate { testCert } )
require . NoError ( t , err )
base64EncryptedKey := base64 . StdEncoding . EncodeToString ( encryptedKey )
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
ds := new ( mock . Store )
svc , ctx := newTestServiceWithConfig ( t , ds , fleetCfg , nil , nil )
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
require . Equal ( t , tt . host . ID , id )
return tt . host , nil
}
ds . GetHostDiskEncryptionKeyFunc = func ( ctx context . Context , id uint ) ( * fleet . HostDiskEncryptionKey , error ) {
return & fleet . HostDiskEncryptionKey {
Base64Encrypted : base64EncryptedKey ,
Decryptable : ptr . Bool ( true ) ,
} , nil
}
ds . NewActivityFunc = func ( ctx context . Context , user * fleet . User , activity fleet . ActivityDetails ) error {
act := activity . ( fleet . ActivityTypeReadHostDiskEncryptionKey )
require . Equal ( t , tt . host . ID , act . HostID )
require . EqualValues ( t , act . HostDisplayName , tt . host . DisplayName ( ) )
return nil
}
t . Run ( "allowed users" , func ( t * testing . T ) {
for _ , u := range tt . allowedUsers {
_ , err := svc . HostEncryptionKey ( test . UserContext ( ctx , u ) , tt . host . ID )
require . NoError ( t , err )
}
} )
t . Run ( "disallowed users" , func ( t * testing . T ) {
for _ , u := range tt . disallowedUsers {
_ , err := svc . HostEncryptionKey ( test . UserContext ( ctx , u ) , tt . host . ID )
require . Error ( t , err )
require . Contains ( t , authz . ForbiddenErrorMessage , err . Error ( ) )
}
} )
t . Run ( "no user in context" , func ( t * testing . T ) {
_ , err := svc . HostEncryptionKey ( ctx , tt . host . ID )
require . Error ( t , err )
require . Contains ( t , authz . ForbiddenErrorMessage , err . Error ( ) )
} )
} )
}
t . Run ( "test error cases" , func ( t * testing . T ) {
ds := new ( mock . Store )
svc , ctx := newTestService ( t , ds , nil , nil )
ctx = test . UserContext ( ctx , test . UserAdmin )
hostErr := errors . New ( "host error" )
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
return nil , hostErr
}
_ , err := svc . HostEncryptionKey ( ctx , 1 )
require . ErrorIs ( t , err , hostErr )
ds . HostLiteFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
return & fleet . Host { } , nil
}
keyErr := errors . New ( "key error" )
ds . GetHostDiskEncryptionKeyFunc = func ( ctx context . Context , id uint ) ( * fleet . HostDiskEncryptionKey , error ) {
return nil , keyErr
}
_ , err = svc . HostEncryptionKey ( ctx , 1 )
require . ErrorIs ( t , err , keyErr )
ds . GetHostDiskEncryptionKeyFunc = func ( ctx context . Context , id uint ) ( * fleet . HostDiskEncryptionKey , error ) {
return & fleet . HostDiskEncryptionKey { Base64Encrypted : "key" } , nil
}
ds . NewActivityFunc = func ( ctx context . Context , user * fleet . User , activity fleet . ActivityDetails ) error {
return errors . New ( "activity error" )
}
_ , err = svc . HostEncryptionKey ( ctx , 1 )
require . Error ( t , err )
} )
}
2023-06-21 18:00:49 +00:00
func TestHostMDMProfileDetail ( t * testing . T ) {
ds := new ( mock . Store )
testBMToken := & nanodep_client . OAuth1Tokens {
ConsumerKey : "test_consumer" ,
ConsumerSecret : "test_secret" ,
AccessToken : "test_access_token" ,
AccessSecret : "test_access_secret" ,
AccessTokenExpiry : time . Date ( 2999 , 1 , 1 , 0 , 0 , 0 , 0 , time . UTC ) ,
}
testCert , testKey , err := apple_mdm . NewSCEPCACertKey ( )
require . NoError ( t , err )
testCertPEM := tokenpki . PEMCertificate ( testCert . Raw )
testKeyPEM := tokenpki . PEMRSAPrivateKey ( testKey )
fleetCfg := config . TestConfig ( )
config . SetTestMDMConfig ( t , & fleetCfg , testCertPEM , testKeyPEM , testBMToken )
svc , ctx := newTestServiceWithConfig ( t , ds , fleetCfg , nil , nil )
ctx = test . UserContext ( ctx , test . UserAdmin )
ds . HostFunc = func ( ctx context . Context , id uint ) ( * fleet . Host , error ) {
return & fleet . Host {
ID : 1 ,
} , nil
}
ds . LoadHostSoftwareFunc = func ( ctx context . Context , host * fleet . Host , includeCVEScores bool ) error {
return nil
}
ds . ListLabelsForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Label , error ) {
return nil , nil
}
ds . ListPacksForHostFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . Pack , error ) {
return nil , nil
}
ds . ListHostBatteriesFunc = func ( ctx context . Context , hid uint ) ( [ ] * fleet . HostBattery , error ) {
return nil , nil
}
ds . GetHostMDMMacOSSetupFunc = func ( ctx context . Context , hid uint ) ( * fleet . HostMDMMacOSSetup , error ) {
return nil , nil
}
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig {
MDM : fleet . MDM {
EnabledAndConfigured : true ,
} ,
} , nil
}
cases := [ ] struct {
name string
storedDetail string
expectedDetail string
} {
{
name : "no detail" ,
storedDetail : "" ,
expectedDetail : "" ,
} ,
{
name : "other detail" ,
storedDetail : "other detail" ,
expectedDetail : "other detail" ,
} ,
{
name : "failed was verifying" ,
storedDetail : string ( fleet . HostMDMProfileDetailFailedWasVerifying ) ,
expectedDetail : fleet . HostMDMProfileDetailFailedWasVerifying . Message ( ) ,
} ,
{
name : "failed was verified" ,
storedDetail : string ( fleet . HostMDMProfileDetailFailedWasVerified ) ,
expectedDetail : fleet . HostMDMProfileDetailFailedWasVerified . Message ( ) ,
} ,
}
for _ , tt := range cases {
t . Run ( tt . name , func ( t * testing . T ) {
ds . GetHostMDMProfilesFunc = func ( ctx context . Context , host_uuid string ) ( [ ] fleet . HostMDMAppleProfile , error ) {
return [ ] fleet . HostMDMAppleProfile {
{
Name : "test" ,
Identifier : "test" ,
OperationType : fleet . MDMAppleOperationTypeInstall ,
Status : & fleet . MDMAppleDeliveryFailed ,
Detail : tt . storedDetail ,
} ,
} , nil
}
h , err := svc . GetHost ( ctx , uint ( 1 ) , fleet . HostDetailOptions { } )
require . NoError ( t , err )
require . NotNil ( t , h . MDM . Profiles )
profs := * h . MDM . Profiles
require . Len ( t , profs , 1 )
require . Equal ( t , tt . expectedDetail , profs [ 0 ] . Detail )
} )
}
}
2023-08-21 18:47:19 +00:00
func TestHostRunScript ( t * testing . T ) {
ds := new ( mock . Store )
license := & fleet . LicenseInfo { Tier : fleet . TierPremium , Expiration : time . Now ( ) . Add ( 24 * time . Hour ) }
svc , ctx := newTestService ( t , ds , nil , nil , & TestServerOpts { License : license , SkipCreateTestUsers : true } )
// use a custom implementation of checkAuthErr as the service call will fail
// with a not found error for unknown host in case of authorization success,
// and the package-wide checkAuthErr requires no error.
checkAuthErr := func ( t * testing . T , shouldFail bool , err error ) {
if shouldFail {
require . Error ( t , err )
require . Equal ( t , ( & authz . Forbidden { } ) . Error ( ) , err . Error ( ) )
} else if err != nil {
require . NotEqual ( t , ( & authz . Forbidden { } ) . Error ( ) , err . Error ( ) )
}
}
2023-08-23 20:47:47 +00:00
teamHost := & fleet . Host { ID : 1 , Hostname : "host-team" , TeamID : ptr . Uint ( 1 ) , SeenTime : time . Now ( ) }
noTeamHost := & fleet . Host { ID : 2 , Hostname : "host-no-team" , TeamID : nil , SeenTime : time . Now ( ) }
2023-08-21 18:47:19 +00:00
nonExistingHost := & fleet . Host { ID : 3 , Hostname : "no-such-host" , TeamID : nil }
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig { } , nil
}
2023-08-23 20:47:47 +00:00
ds . HostFunc = func ( ctx context . Context , hostID uint ) ( * fleet . Host , error ) {
2023-08-21 18:47:19 +00:00
if hostID == 1 {
return teamHost , nil
}
if hostID == 2 {
return noTeamHost , nil
}
return nil , newNotFoundError ( )
}
ds . NewHostScriptExecutionRequestFunc = func ( ctx context . Context , request * fleet . HostScriptRequestPayload ) ( * fleet . HostScriptResult , error ) {
return & fleet . HostScriptResult { HostID : request . HostID , ScriptContents : request . ScriptContents , ExecutionID : "abc" } , nil
}
ds . ListPendingHostScriptExecutionsFunc = func ( ctx context . Context , hostID uint , ignoreOlder time . Duration ) ( [ ] * fleet . HostScriptResult , error ) {
return nil , nil
}
2023-09-05 23:09:29 +00:00
ds . NewActivityFunc = func ( ctx context . Context , user * fleet . User , activity fleet . ActivityDetails ) error {
require . IsType ( t , fleet . ActivityTypeRanScript { } , activity )
return nil
}
2023-08-21 18:47:19 +00:00
2023-08-23 20:47:47 +00:00
t . Run ( "authorization checks" , func ( t * testing . T ) {
testCases := [ ] struct {
name string
user * fleet . User
shouldFailTeamWrite bool
shouldFailGlobalWrite bool
} {
{
name : "global admin" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleAdmin ) } ,
shouldFailTeamWrite : false ,
shouldFailGlobalWrite : false ,
} ,
{
name : "global maintainer" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleMaintainer ) } ,
shouldFailTeamWrite : false ,
shouldFailGlobalWrite : false ,
} ,
{
name : "global observer" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleObserver ) } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "global observer+" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleObserverPlus ) } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "global gitops" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleGitOps ) } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team admin, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleAdmin } } } ,
shouldFailTeamWrite : false ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team maintainer, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleMaintainer } } } ,
shouldFailTeamWrite : false ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team observer, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleObserver } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team observer+, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleObserverPlus } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team gitops, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleGitOps } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team admin, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleAdmin } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team maintainer, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleMaintainer } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team observer, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleObserver } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team observer+, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleObserverPlus } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
{
name : "team gitops, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleGitOps } } } ,
shouldFailTeamWrite : true ,
shouldFailGlobalWrite : true ,
} ,
}
for _ , tt := range testCases {
t . Run ( tt . name , func ( t * testing . T ) {
ctx = viewer . NewContext ( ctx , viewer . Viewer { User : tt . user } )
_ , err := svc . RunHostScript ( ctx , & fleet . HostScriptRequestPayload { HostID : noTeamHost . ID , ScriptContents : "abc" } , 0 )
checkAuthErr ( t , tt . shouldFailGlobalWrite , err )
_ , err = svc . RunHostScript ( ctx , & fleet . HostScriptRequestPayload { HostID : teamHost . ID , ScriptContents : "abc" } , 0 )
checkAuthErr ( t , tt . shouldFailTeamWrite , err )
// a non-existing host is authorized as for global write (because we can't know what team it belongs to)
_ , err = svc . RunHostScript ( ctx , & fleet . HostScriptRequestPayload { HostID : nonExistingHost . ID , ScriptContents : "abc" } , 0 )
checkAuthErr ( t , tt . shouldFailGlobalWrite , err )
} )
}
} )
2023-08-21 18:47:19 +00:00
2023-08-23 20:47:47 +00:00
t . Run ( "script contents validation" , func ( t * testing . T ) {
testCases := [ ] struct {
name string
script string
wantErr string
} {
2023-09-05 19:14:09 +00:00
{ "empty script" , "" , "Script contents must not be empty." } ,
2023-08-31 14:08:50 +00:00
{ "overly long script" , strings . Repeat ( "a" , 10001 ) , "Script is too large." } ,
{ "invalid utf8" , "\xff\xfa" , "Wrong data format." } ,
2023-08-23 20:47:47 +00:00
{ "valid without hashbang" , "echo 'a'" , "" } ,
{ "valid with hashbang" , "#!/bin/sh\necho 'a'" , "" } ,
{ "valid with hashbang and spacing" , "#! /bin/sh \necho 'a'" , "" } ,
{ "valid with hashbang and Windows newline" , "#! /bin/sh \r\necho 'a'" , "" } ,
2023-08-31 14:08:50 +00:00
{ "invalid hashbang" , "#!/bin/bash\necho 'a'" , "Interpreter not supported." } ,
{ "invalid hashbang suffix" , "#!/bin/sh -n\necho 'a'" , "Interpreter not supported." } ,
2023-08-23 20:47:47 +00:00
}
2023-08-21 18:47:19 +00:00
2023-08-23 20:47:47 +00:00
ctx = viewer . NewContext ( ctx , viewer . Viewer { User : test . UserAdmin } )
for _ , tt := range testCases {
t . Run ( tt . name , func ( t * testing . T ) {
_ , err := svc . RunHostScript ( ctx , & fleet . HostScriptRequestPayload { HostID : noTeamHost . ID , ScriptContents : tt . script } , 0 )
if tt . wantErr != "" {
require . ErrorContains ( t , err , tt . wantErr )
} else {
require . NoError ( t , err )
}
} )
}
} )
2023-08-21 18:47:19 +00:00
}
2023-09-05 20:38:53 +00:00
func TestGetScriptResult ( t * testing . T ) {
ds := new ( mock . Store )
license := & fleet . LicenseInfo { Tier : fleet . TierPremium , Expiration : time . Now ( ) . Add ( 24 * time . Hour ) }
svc , ctx := newTestService ( t , ds , nil , nil , & TestServerOpts { License : license , SkipCreateTestUsers : true } )
const (
noTeamHostExecID = "no-team-host"
teamHostExecID = "team-host"
nonExistingHostExecID = "non-existing-host"
)
checkAuthErr := func ( t * testing . T , shouldFail bool , err error ) {
if shouldFail {
require . Error ( t , err )
require . Equal ( t , ( & authz . Forbidden { } ) . Error ( ) , err . Error ( ) )
} else if err != nil {
require . NotEqual ( t , ( & authz . Forbidden { } ) . Error ( ) , err . Error ( ) )
}
}
teamHost := & fleet . Host { ID : 1 , Hostname : "host-team" , TeamID : ptr . Uint ( 1 ) , SeenTime : time . Now ( ) }
noTeamHost := & fleet . Host { ID : 2 , Hostname : "host-no-team" , TeamID : nil , SeenTime : time . Now ( ) }
nonExistingHost := & fleet . Host { ID : 3 , Hostname : "no-such-host" , TeamID : nil }
ds . AppConfigFunc = func ( ctx context . Context ) ( * fleet . AppConfig , error ) {
return & fleet . AppConfig { } , nil
}
ds . GetHostScriptExecutionResultFunc = func ( ctx context . Context , executionID string ) ( * fleet . HostScriptResult , error ) {
switch executionID {
case noTeamHostExecID :
return & fleet . HostScriptResult { HostID : noTeamHost . ID , ScriptContents : "abc" , ExecutionID : executionID } , nil
case teamHostExecID :
return & fleet . HostScriptResult { HostID : teamHost . ID , ScriptContents : "abc" , ExecutionID : executionID } , nil
case nonExistingHostExecID :
return & fleet . HostScriptResult { HostID : nonExistingHost . ID , ScriptContents : "abc" , ExecutionID : executionID } , nil
default :
return nil , newNotFoundError ( )
}
}
ds . HostLiteFunc = func ( ctx context . Context , hostID uint ) ( * fleet . Host , error ) {
if hostID == 1 {
return teamHost , nil
}
if hostID == 2 {
return noTeamHost , nil
}
return nil , newNotFoundError ( )
}
testCases := [ ] struct {
name string
user * fleet . User
shouldFailTeamRead bool
shouldFailGlobalRead bool
} {
{
name : "global admin" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleAdmin ) } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : false ,
} ,
{
name : "global maintainer" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleMaintainer ) } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : false ,
} ,
{
name : "global observer" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleObserver ) } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : false ,
} ,
{
name : "global observer+" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleObserverPlus ) } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : false ,
} ,
{
name : "global gitops" ,
user : & fleet . User { GlobalRole : ptr . String ( fleet . RoleGitOps ) } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team admin, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleAdmin } } } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : true ,
} ,
{
name : "team maintainer, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleMaintainer } } } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : true ,
} ,
{
name : "team observer, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleObserver } } } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : true ,
} ,
{
name : "team observer+, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleObserverPlus } } } ,
shouldFailTeamRead : false ,
shouldFailGlobalRead : true ,
} ,
{
name : "team gitops, belongs to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 1 } , Role : fleet . RoleGitOps } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team admin, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleAdmin } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team maintainer, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleMaintainer } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team observer, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleObserver } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team observer+, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleObserverPlus } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
{
name : "team gitops, DOES NOT belong to team" ,
user : & fleet . User { Teams : [ ] fleet . UserTeam { { Team : fleet . Team { ID : 2 } , Role : fleet . RoleGitOps } } } ,
shouldFailTeamRead : true ,
shouldFailGlobalRead : true ,
} ,
}
for _ , tt := range testCases {
t . Run ( tt . name , func ( t * testing . T ) {
ctx = viewer . NewContext ( ctx , viewer . Viewer { User : tt . user } )
_ , err := svc . GetScriptResult ( ctx , noTeamHostExecID )
checkAuthErr ( t , tt . shouldFailGlobalRead , err )
_ , err = svc . GetScriptResult ( ctx , teamHostExecID )
checkAuthErr ( t , tt . shouldFailTeamRead , err )
// a non-existing host is authorized as for global write (because we can't know what team it belongs to)
_ , err = svc . GetScriptResult ( ctx , nonExistingHostExecID )
checkAuthErr ( t , tt . shouldFailGlobalRead , err )
} )
}
}