2021-09-15 19:27:53 +00:00
package service
import (
2022-05-23 19:35:05 +00:00
"bytes"
2021-09-15 19:27:53 +00:00
"context"
2022-01-25 14:34:00 +00:00
"crypto/rand"
2022-01-13 19:57:44 +00:00
"encoding/base64"
2022-03-15 19:14:42 +00:00
"encoding/csv"
2021-11-24 17:16:42 +00:00
"encoding/json"
2021-09-15 19:27:53 +00:00
"fmt"
2022-05-23 19:35:05 +00:00
"io"
2021-09-15 19:27:53 +00:00
"io/ioutil"
"net/http"
2022-04-06 11:55:25 +00:00
"net/http/httptest"
2021-12-15 14:35:40 +00:00
"net/url"
2022-05-02 20:58:34 +00:00
"os"
2021-11-24 17:16:42 +00:00
"reflect"
2022-04-26 18:16:59 +00:00
"sort"
2021-12-21 14:53:15 +00:00
"strconv"
2021-12-15 14:35:40 +00:00
"strings"
2021-09-15 19:27:53 +00:00
"testing"
"time"
2022-05-23 19:27:30 +00:00
"github.com/fleetdm/fleet/v4/server"
2022-03-08 16:27:38 +00:00
"github.com/fleetdm/fleet/v4/server/datastore/mysql"
2021-09-15 19:27:53 +00:00
"github.com/fleetdm/fleet/v4/server/fleet"
"github.com/fleetdm/fleet/v4/server/ptr"
2021-10-15 10:34:30 +00:00
"github.com/fleetdm/fleet/v4/server/test"
2021-09-15 19:27:53 +00:00
"github.com/ghodss/yaml"
2021-12-14 21:34:11 +00:00
"github.com/google/uuid"
2022-03-08 16:27:38 +00:00
"github.com/jmoiron/sqlx"
2021-09-15 19:27:53 +00:00
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
2021-11-11 20:33:06 +00:00
"gopkg.in/guregu/null.v3"
2021-09-15 19:27:53 +00:00
)
type integrationTestSuite struct {
suite . Suite
withServer
}
func ( s * integrationTestSuite ) SetupSuite ( ) {
s . withServer . SetupSuite ( "integrationTestSuite" )
}
2021-09-29 16:13:23 +00:00
func ( s * integrationTestSuite ) TearDownTest ( ) {
2022-01-10 19:43:39 +00:00
t := s . T ( )
ctx := context . Background ( )
2021-09-29 16:13:23 +00:00
u := s . users [ "admin1@example.com" ]
filter := fleet . TeamFilter { User : & u }
2022-01-10 19:43:39 +00:00
hosts , err := s . ds . ListHosts ( ctx , filter , fleet . HostListOptions { } )
require . NoError ( t , err )
2021-09-29 16:13:23 +00:00
for _ , host := range hosts {
2022-02-09 15:16:50 +00:00
require . NoError ( t , s . ds . UpdateHostSoftware ( context . Background ( ) , host . ID , nil ) )
2022-03-09 21:13:56 +00:00
require . NoError ( t , s . ds . DeleteHost ( ctx , host . ID ) )
2022-01-10 19:43:39 +00:00
}
2021-12-21 14:53:15 +00:00
2022-02-09 15:16:50 +00:00
// recalculate software counts will remove the software entries
require . NoError ( t , s . ds . CalculateHostsPerSoftware ( context . Background ( ) , time . Now ( ) ) )
2022-01-10 19:43:39 +00:00
lbls , err := s . ds . ListLabels ( ctx , fleet . TeamFilter { } , fleet . ListOptions { } )
require . NoError ( t , err )
2021-12-21 14:53:15 +00:00
for _ , lbl := range lbls {
if lbl . LabelType != fleet . LabelTypeBuiltIn {
2022-01-10 19:43:39 +00:00
err := s . ds . DeleteLabel ( ctx , lbl . Name )
require . NoError ( t , err )
}
}
users , err := s . ds . ListUsers ( ctx , fleet . UserListOptions { } )
require . NoError ( t , err )
for _ , u := range users {
if _ , ok := s . users [ u . Email ] ; ! ok {
err := s . ds . DeleteUser ( ctx , u . ID )
require . NoError ( t , err )
2021-12-21 14:53:15 +00:00
}
}
2022-01-18 16:18:40 +00:00
2022-02-15 20:22:19 +00:00
teams , err := s . ds . ListTeams ( ctx , fleet . TeamFilter { User : & u } , fleet . ListOptions { } )
require . NoError ( t , err )
for _ , tm := range teams {
err := s . ds . DeleteTeam ( ctx , tm . ID )
require . NoError ( t , err )
}
2022-01-18 16:18:40 +00:00
globalPolicies , err := s . ds . ListGlobalPolicies ( ctx )
require . NoError ( t , err )
if len ( globalPolicies ) > 0 {
var globalPolicyIDs [ ] uint
for _ , gp := range globalPolicies {
globalPolicyIDs = append ( globalPolicyIDs , gp . ID )
}
_ , err = s . ds . DeleteGlobalPolicies ( ctx , globalPolicyIDs )
require . NoError ( t , err )
}
2022-02-14 18:13:44 +00:00
// CalculateHostsPerSoftware performs a cleanup.
err = s . ds . CalculateHostsPerSoftware ( ctx , time . Now ( ) )
require . NoError ( t , err )
2021-09-29 16:13:23 +00:00
}
2021-09-15 19:27:53 +00:00
func TestIntegrations ( t * testing . T ) {
testingSuite := new ( integrationTestSuite )
testingSuite . s = & testingSuite . Suite
suite . Run ( t , testingSuite )
}
func ( s * integrationTestSuite ) TestDoubleUserCreationErrors ( ) {
t := s . T ( )
params := fleet . UserPayload {
Name : ptr . String ( "user1" ) ,
Email : ptr . String ( "email@asd.com" ) ,
2022-05-18 17:03:00 +00:00
Password : & test . GoodPassword ,
2021-09-15 19:27:53 +00:00
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
2022-04-05 15:35:53 +00:00
s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusOK )
respSecond := s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusConflict )
2021-09-15 19:27:53 +00:00
assertBodyContains ( t , respSecond , ` Error 1062: Duplicate entry 'email@asd.com' ` )
}
func ( s * integrationTestSuite ) TestUserWithoutRoleErrors ( ) {
t := s . T ( )
params := fleet . UserPayload {
Name : ptr . String ( "user1" ) ,
Email : ptr . String ( "email@asd.com" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2021-09-15 19:27:53 +00:00
}
2022-04-05 15:35:53 +00:00
resp := s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusUnprocessableEntity )
2021-09-15 19:27:53 +00:00
assertErrorCodeAndMessage ( t , resp , fleet . ErrNoRoleNeeded , "either global role or team role needs to be defined" )
}
func ( s * integrationTestSuite ) TestUserWithWrongRoleErrors ( ) {
t := s . T ( )
params := fleet . UserPayload {
Name : ptr . String ( "user1" ) ,
Email : ptr . String ( "email@asd.com" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2021-09-15 19:27:53 +00:00
GlobalRole : ptr . String ( "wrongrole" ) ,
}
2022-04-05 15:35:53 +00:00
resp := s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusUnprocessableEntity )
2021-09-15 19:27:53 +00:00
assertErrorCodeAndMessage ( t , resp , fleet . ErrNoRoleNeeded , "GlobalRole role can only be admin, observer, or maintainer." )
}
func ( s * integrationTestSuite ) TestUserCreationWrongTeamErrors ( ) {
t := s . T ( )
teams := [ ] fleet . UserTeam {
{
Team : fleet . Team {
ID : 9999 ,
} ,
Role : fleet . RoleObserver ,
} ,
}
params := fleet . UserPayload {
Name : ptr . String ( "user2" ) ,
Email : ptr . String ( "email2@asd.com" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2021-09-15 19:27:53 +00:00
Teams : & teams ,
}
2022-04-05 15:35:53 +00:00
resp := s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusUnprocessableEntity )
2021-09-15 19:27:53 +00:00
assertBodyContains ( t , resp , ` Error 1452: Cannot add or update a child row: a foreign key constraint fails ` )
}
func ( s * integrationTestSuite ) TestQueryCreationLogsActivity ( ) {
t := s . T ( )
admin1 := s . users [ "admin1@example.com" ]
admin1 . GravatarURL = "http://iii.com"
err := s . ds . SaveUser ( context . Background ( ) , & admin1 )
require . NoError ( t , err )
params := fleet . QueryPayload {
Name : ptr . String ( "user1" ) ,
Query : ptr . String ( "select * from time;" ) ,
}
2022-01-31 21:35:22 +00:00
var createQueryResp createQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , & params , http . StatusOK , & createQueryResp )
2022-01-31 21:35:22 +00:00
defer cleanupQuery ( s , createQueryResp . Query . ID )
2021-09-15 19:27:53 +00:00
activities := listActivitiesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & activities )
2021-09-15 19:27:53 +00:00
2021-11-24 17:16:42 +00:00
assert . GreaterOrEqual ( t , len ( activities . Activities ) , 1 )
found := false
for _ , activity := range activities . Activities {
if activity . Type == "created_saved_query" {
found = true
assert . Equal ( t , "Test Name admin1@example.com" , activity . ActorFullName )
require . NotNil ( t , activity . ActorGravatar )
assert . Equal ( t , "http://iii.com" , * activity . ActorGravatar )
}
}
require . True ( t , found )
2021-09-15 19:27:53 +00:00
}
2021-11-11 16:45:39 +00:00
2022-02-16 15:33:56 +00:00
func ( s * integrationTestSuite ) TestPolicyDeletionLogsActivity ( ) {
t := s . T ( )
admin1 := s . users [ "admin1@example.com" ]
admin1 . GravatarURL = "http://iii.com"
err := s . ds . SaveUser ( context . Background ( ) , & admin1 )
require . NoError ( t , err )
testPolicies := [ ] fleet . PolicyPayload { {
Name : "policy1" ,
Query : "select * from time;" ,
} , {
Name : "policy2" ,
Query : "select * from osquery_info;" ,
} }
var policyIDs [ ] uint
for _ , policy := range testPolicies {
var resp globalPolicyResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , policy , http . StatusOK , & resp )
2022-02-16 15:33:56 +00:00
policyIDs = append ( policyIDs , resp . Policy . PolicyData . ID )
}
prevActivities := listActivitiesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & prevActivities )
2022-02-16 15:33:56 +00:00
require . GreaterOrEqual ( t , len ( prevActivities . Activities ) , 2 )
var deletePoliciesResp deleteGlobalPoliciesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies/delete" , deleteGlobalPoliciesRequest { policyIDs } , http . StatusOK , & deletePoliciesResp )
2022-02-16 15:33:56 +00:00
require . Equal ( t , len ( policyIDs ) , len ( deletePoliciesResp . Deleted ) )
newActivities := listActivitiesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & newActivities )
2022-02-16 15:33:56 +00:00
require . Equal ( t , len ( newActivities . Activities ) , ( len ( prevActivities . Activities ) + 2 ) )
var prevDeletes [ ] * fleet . Activity
for _ , a := range prevActivities . Activities {
if a . Type == "deleted_policy" {
prevDeletes = append ( prevDeletes , a )
}
}
var newDeletes [ ] * fleet . Activity
for _ , a := range newActivities . Activities {
if a . Type == "deleted_policy" {
newDeletes = append ( newDeletes , a )
}
}
require . Equal ( t , len ( newDeletes ) , ( len ( prevDeletes ) + 2 ) )
type policyDetails struct {
PolicyID uint ` json:"policy_id" `
PolicyName string ` json:"policy_name" `
}
for _ , id := range policyIDs {
found := false
for _ , d := range newDeletes {
var details policyDetails
err := json . Unmarshal ( [ ] byte ( * d . Details ) , & details )
require . NoError ( t , err )
require . NotNil ( t , details . PolicyID )
if id == details . PolicyID {
found = true
}
}
require . True ( t , found )
}
for _ , p := range testPolicies {
found := false
for _ , d := range newDeletes {
var details policyDetails
err := json . Unmarshal ( [ ] byte ( * d . Details ) , & details )
require . NoError ( t , err )
require . NotNil ( t , details . PolicyName )
if p . Name == details . PolicyName {
found = true
}
}
require . True ( t , found )
}
}
2021-09-15 19:27:53 +00:00
func ( s * integrationTestSuite ) TestAppConfigAdditionalQueriesCanBeRemoved ( ) {
t := s . T ( )
spec := [ ] byte ( `
host_expiry_settings :
host_expiry_enabled : true
host_expiry_window : 0
host_settings :
additional_queries :
time : SELECT * FROM time
enable_host_users : true
` )
s . applyConfig ( spec )
spec = [ ] byte ( `
host_settings :
enable_host_users : true
additional_queries : null
` )
s . applyConfig ( spec )
config := s . getConfig ( )
assert . Nil ( t , config . HostSettings . AdditionalQueries )
assert . True ( t , config . HostExpirySettings . HostExpiryEnabled )
}
func ( s * integrationTestSuite ) TestAppConfigDefaultValues ( ) {
config := s . getConfig ( )
s . Run ( "Update interval" , func ( ) {
require . Equal ( s . T ( ) , 1 * time . Hour , config . UpdateInterval . OSQueryDetail )
} )
s . Run ( "has logging" , func ( ) {
require . NotNil ( s . T ( ) , config . Logging )
} )
}
func ( s * integrationTestSuite ) TestUserRolesSpec ( ) {
t := s . T ( )
_ , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
ID : 42 ,
Name : "team1" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
email := t . Name ( ) + "@asd.com"
u := & fleet . User {
Password : [ ] byte ( "asd" ) ,
Name : t . Name ( ) ,
Email : email ,
GravatarURL : "http://asd.com" ,
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
user , err := s . ds . NewUser ( context . Background ( ) , u )
require . NoError ( t , err )
assert . Len ( t , user . Teams , 0 )
spec := [ ] byte ( fmt . Sprintf ( `
roles :
% s :
global_role : null
teams :
- role : maintainer
team : team1
` ,
email ) )
var userRoleSpec applyUserRoleSpecsRequest
err = yaml . Unmarshal ( spec , & userRoleSpec . Spec )
require . NoError ( t , err )
2022-04-05 15:35:53 +00:00
s . Do ( "POST" , "/api/latest/fleet/users/roles/spec" , & userRoleSpec , http . StatusOK )
2021-09-15 19:27:53 +00:00
user , err = s . ds . UserByEmail ( context . Background ( ) , email )
require . NoError ( t , err )
require . Len ( t , user . Teams , 1 )
assert . Equal ( t , fleet . RoleMaintainer , user . Teams [ 0 ] . Role )
}
func ( s * integrationTestSuite ) TestGlobalSchedule ( ) {
t := s . T ( )
2021-12-15 14:06:34 +00:00
// list the existing global schedules (none yet)
2021-09-15 19:27:53 +00:00
gs := fleet . GlobalSchedulePayload { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/schedule" , nil , http . StatusOK , & gs )
2021-09-15 19:27:53 +00:00
require . Len ( t , gs . GlobalSchedule , 0 )
2021-12-15 14:06:34 +00:00
// create a query that can be scheduled
2021-09-15 19:27:53 +00:00
qr , err := s . ds . NewQuery ( context . Background ( ) , & fleet . Query {
Name : "TestQuery1" ,
Description : "Some description" ,
Query : "select * from osquery;" ,
ObserverCanRun : true ,
} )
require . NoError ( t , err )
2021-12-15 14:06:34 +00:00
// schedule that query
2021-09-15 19:27:53 +00:00
gsParams := fleet . ScheduledQueryPayload { QueryID : ptr . Uint ( qr . ID ) , Interval : ptr . Uint ( 42 ) }
r := globalScheduleQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/schedule" , gsParams , http . StatusOK , & r )
2021-09-15 19:27:53 +00:00
2021-12-15 14:06:34 +00:00
// list the scheduled queries, get the one just created
2021-09-15 19:27:53 +00:00
gs = fleet . GlobalSchedulePayload { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/schedule" , nil , http . StatusOK , & gs )
2021-09-15 19:27:53 +00:00
require . Len ( t , gs . GlobalSchedule , 1 )
assert . Equal ( t , uint ( 42 ) , gs . GlobalSchedule [ 0 ] . Interval )
assert . Equal ( t , "TestQuery1" , gs . GlobalSchedule [ 0 ] . Name )
id := gs . GlobalSchedule [ 0 ] . ID
2021-12-15 14:06:34 +00:00
// list page 2, should be empty
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/schedule" , nil , http . StatusOK , & gs , "page" , "2" , "per_page" , "4" )
2021-12-15 14:06:34 +00:00
require . Len ( t , gs . GlobalSchedule , 0 )
// update the scheduled query
2021-09-15 19:27:53 +00:00
gs = fleet . GlobalSchedulePayload { }
gsParams = fleet . ScheduledQueryPayload { Interval : ptr . Uint ( 55 ) }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , id ) , gsParams , http . StatusOK , & gs )
2021-09-15 19:27:53 +00:00
2021-12-15 14:06:34 +00:00
// update a non-existing schedule
gsParams = fleet . ScheduledQueryPayload { Interval : ptr . Uint ( 66 ) }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , id + 1 ) , gsParams , http . StatusNotFound , & gs )
2021-12-15 14:06:34 +00:00
// read back that updated scheduled query
2021-09-15 19:27:53 +00:00
gs = fleet . GlobalSchedulePayload { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/schedule" , nil , http . StatusOK , & gs )
2021-09-15 19:27:53 +00:00
require . Len ( t , gs . GlobalSchedule , 1 )
2021-12-15 14:06:34 +00:00
assert . Equal ( t , id , gs . GlobalSchedule [ 0 ] . ID )
2021-09-15 19:27:53 +00:00
assert . Equal ( t , uint ( 55 ) , gs . GlobalSchedule [ 0 ] . Interval )
2021-12-15 14:06:34 +00:00
// delete the scheduled query
2021-09-15 19:27:53 +00:00
r = globalScheduleQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , id ) , nil , http . StatusOK , & r )
2021-09-15 19:27:53 +00:00
2021-12-15 14:06:34 +00:00
// delete a non-existing schedule
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , id + 1 ) , nil , http . StatusNotFound , & r )
2021-12-15 14:06:34 +00:00
// list the scheduled queries, back to none
2021-09-15 19:27:53 +00:00
gs = fleet . GlobalSchedulePayload { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/schedule" , nil , http . StatusOK , & gs )
2021-09-15 19:27:53 +00:00
require . Len ( t , gs . GlobalSchedule , 0 )
}
func ( s * integrationTestSuite ) TestTranslator ( ) {
t := s . T ( )
payload := translatorResponse { }
params := translatorRequest { List : [ ] fleet . TranslatePayload {
{
Type : fleet . TranslatorTypeUserEmail ,
Payload : fleet . StringIdentifierToIDPayload { Identifier : "admin1@example.com" } ,
} ,
} }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/translate" , & params , http . StatusOK , & payload )
2021-09-15 19:27:53 +00:00
require . Len ( t , payload . List , 1 )
assert . Equal ( t , s . users [ payload . List [ 0 ] . Payload . Identifier ] . ID , payload . List [ 0 ] . Payload . ID )
}
func ( s * integrationTestSuite ) TestVulnerableSoftware ( ) {
t := s . T ( )
host , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
2021-09-27 19:27:38 +00:00
PolicyUpdatedAt : time . Now ( ) ,
2021-09-15 19:27:53 +00:00
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "1" ,
UUID : t . Name ( ) + "1" ,
2021-10-20 21:01:20 +00:00
Hostname : t . Name ( ) + "foo.local" ,
2021-09-15 19:27:53 +00:00
PrimaryIP : "192.168.1.1" ,
PrimaryMac : "30-65-EC-6F-C4-58" ,
} )
require . NoError ( t , err )
require . NotNil ( t , host )
2022-01-18 01:52:09 +00:00
software := [ ] fleet . Software {
{ Name : "foo" , Version : "0.0.1" , Source : "chrome_extensions" } ,
{ Name : "bar" , Version : "0.0.3" , Source : "apps" } ,
2022-02-07 20:57:55 +00:00
{ Name : "baz" , Version : "0.0.4" , Source : "apps" } ,
2021-09-15 19:27:53 +00:00
}
2022-01-18 01:52:09 +00:00
require . NoError ( t , s . ds . UpdateHostSoftware ( context . Background ( ) , host . ID , software ) )
2022-06-01 16:06:57 +00:00
require . NoError ( t , s . ds . LoadHostSoftware ( context . Background ( ) , host , false ) )
2021-09-15 19:27:53 +00:00
soft1 := host . Software [ 0 ]
if soft1 . Name != "bar" {
soft1 = host . Software [ 1 ]
}
require . NoError ( t , s . ds . AddCPEForSoftware ( context . Background ( ) , soft1 , "somecpe" ) )
2022-06-08 01:09:47 +00:00
// Reload software so that 'GeneratedCPEID is set.
require . NoError ( t , s . ds . LoadHostSoftware ( context . Background ( ) , host , false ) )
soft1 = host . Software [ 0 ]
if soft1 . Name != "bar" {
soft1 = host . Software [ 1 ]
}
n , err := s . ds . InsertVulnerabilities (
context . Background ( ) , [ ] fleet . SoftwareVulnerability {
{
SoftwareID : soft1 . ID ,
CPEID : soft1 . GeneratedCPEID ,
CVE : "cve-123-123-132" ,
} ,
} , fleet . NVD ,
)
2022-02-02 21:34:37 +00:00
require . NoError ( t , err )
2022-06-08 01:09:47 +00:00
require . Equal ( t , 1 , int ( n ) )
2021-09-15 19:27:53 +00:00
2022-04-05 15:35:53 +00:00
resp := s . Do ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host . ID ) , nil , http . StatusOK )
2021-09-15 19:27:53 +00:00
bodyBytes , err := ioutil . ReadAll ( resp . Body )
require . NoError ( t , err )
expectedJSONSoft2 := ` "name" : "bar" ,
"version" : "0.0.3" ,
"source" : "apps" ,
"generated_cpe" : "somecpe" ,
"vulnerabilities" : [
{
"cve" : "cve-123-123-132" ,
"details_link" : "https://nvd.nist.gov/vuln/detail/cve-123-123-132"
}
] `
expectedJSONSoft1 := ` "name" : "foo" ,
"version" : "0.0.1" ,
"source" : "chrome_extensions" ,
"generated_cpe" : "" ,
"vulnerabilities" : null `
// We are doing Contains instead of equals to test the output for software in particular
// ignoring other things like timestamps and things that are outside the cope of this ticket
assert . Contains ( t , string ( bodyBytes ) , expectedJSONSoft2 )
assert . Contains ( t , string ( bodyBytes ) , expectedJSONSoft1 )
2021-10-20 21:01:20 +00:00
2021-12-03 13:54:17 +00:00
countReq := countSoftwareRequest { }
countResp := countSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software/count" , countReq , http . StatusOK , & countResp )
2022-02-07 20:57:55 +00:00
assert . Equal ( t , 3 , countResp . Count )
2021-12-03 13:54:17 +00:00
2022-01-26 14:47:56 +00:00
// no software host counts have been calculated yet, so this returns nothing
var lsResp listSoftwareResponse
2022-04-05 15:35:53 +00:00
resp = s . Do ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , "vulnerable" , "true" , "order_key" , "generated_cpe" , "order_direction" , "desc" )
2022-01-31 22:08:03 +00:00
bodyBytes , err = ioutil . ReadAll ( resp . Body )
require . NoError ( t , err )
assert . Contains ( t , string ( bodyBytes ) , ` "counts_updated_at": null ` )
require . NoError ( t , json . Unmarshal ( bodyBytes , & lsResp ) )
2022-01-26 14:47:56 +00:00
require . Len ( t , lsResp . Software , 0 )
2022-01-31 22:08:03 +00:00
assert . Nil ( t , lsResp . CountsUpdatedAt )
2021-11-11 11:49:17 +00:00
2022-01-26 14:47:56 +00:00
// the software/count endpoint is different, it doesn't care about hosts counts
2022-02-07 20:57:55 +00:00
countResp = countSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software/count" , countReq , http . StatusOK , & countResp , "vulnerable" , "true" , "order_key" , "generated_cpe" , "order_direction" , "desc" )
2021-12-03 13:54:17 +00:00
assert . Equal ( t , 1 , countResp . Count )
2022-01-26 14:47:56 +00:00
// calculate hosts counts
hostsCountTs := time . Now ( ) . UTC ( )
require . NoError ( t , s . ds . CalculateHostsPerSoftware ( context . Background ( ) , hostsCountTs ) )
// now the list software endpoint returns the software
2022-02-07 20:57:55 +00:00
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "vulnerable" , "true" , "order_key" , "generated_cpe" , "order_direction" , "desc" )
2022-01-26 14:47:56 +00:00
require . Len ( t , lsResp . Software , 1 )
2021-11-11 11:49:17 +00:00
assert . Equal ( t , soft1 . ID , lsResp . Software [ 0 ] . ID )
assert . Len ( t , lsResp . Software [ 0 ] . Vulnerabilities , 1 )
2022-01-31 22:08:03 +00:00
require . NotNil ( t , lsResp . CountsUpdatedAt )
assert . WithinDuration ( t , hostsCountTs , * lsResp . CountsUpdatedAt , time . Second )
2022-01-26 14:47:56 +00:00
// the count endpoint still returns 1
2022-02-07 20:57:55 +00:00
countResp = countSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software/count" , countReq , http . StatusOK , & countResp , "vulnerable" , "true" , "order_key" , "generated_cpe" , "order_direction" , "desc" )
2022-01-26 14:47:56 +00:00
assert . Equal ( t , 1 , countResp . Count )
// default sort, not only vulnerable
2022-02-07 20:57:55 +00:00
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp )
2022-01-26 14:47:56 +00:00
require . True ( t , len ( lsResp . Software ) >= len ( software ) )
2022-01-31 22:08:03 +00:00
require . NotNil ( t , lsResp . CountsUpdatedAt )
assert . WithinDuration ( t , hostsCountTs , * lsResp . CountsUpdatedAt , time . Second )
2022-02-07 20:57:55 +00:00
// request with a per_page limit (see #4058)
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "page" , "0" , "per_page" , "2" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-07 20:57:55 +00:00
require . Len ( t , lsResp . Software , 2 )
require . NotNil ( t , lsResp . CountsUpdatedAt )
assert . WithinDuration ( t , hostsCountTs , * lsResp . CountsUpdatedAt , time . Second )
// request next page, with per_page limit
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "2" , "page" , "1" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-07 20:57:55 +00:00
require . Len ( t , lsResp . Software , 1 )
require . NotNil ( t , lsResp . CountsUpdatedAt )
assert . WithinDuration ( t , hostsCountTs , * lsResp . CountsUpdatedAt , time . Second )
// request one past the last page
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "2" , "page" , "2" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-07 20:57:55 +00:00
require . Len ( t , lsResp . Software , 0 )
require . Nil ( t , lsResp . CountsUpdatedAt )
2021-09-15 19:27:53 +00:00
}
func ( s * integrationTestSuite ) TestGlobalPolicies ( ) {
t := s . T ( )
for i := 0 ; i < 3 ; i ++ {
_ , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
2021-09-27 19:27:38 +00:00
PolicyUpdatedAt : time . Now ( ) ,
2021-09-15 19:27:53 +00:00
SeenTime : time . Now ( ) . Add ( - time . Duration ( i ) * time . Minute ) ,
2021-09-29 16:13:23 +00:00
OsqueryHostID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
NodeKey : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
UUID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
Hostname : fmt . Sprintf ( "%sfoo.local%d" , t . Name ( ) , i ) ,
2021-09-15 19:27:53 +00:00
} )
require . NoError ( t , err )
}
qr , err := s . ds . NewQuery ( context . Background ( ) , & fleet . Query {
Name : "TestQuery3" ,
Description : "Some description" ,
Query : "select * from osquery;" ,
ObserverCanRun : true ,
} )
require . NoError ( t , err )
2021-11-24 17:16:42 +00:00
gpParams := globalPolicyRequest {
QueryID : & qr . ID ,
Resolution : "some global resolution" ,
}
2021-09-15 19:27:53 +00:00
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusOK , & gpResp )
2021-09-15 19:27:53 +00:00
require . NotNil ( t , gpResp . Policy )
2021-11-24 17:16:42 +00:00
assert . Equal ( t , qr . Name , gpResp . Policy . Name )
assert . Equal ( t , qr . Query , gpResp . Policy . Query )
assert . Equal ( t , qr . Description , gpResp . Policy . Description )
2021-10-28 13:10:03 +00:00
require . NotNil ( t , gpResp . Policy . Resolution )
assert . Equal ( t , "some global resolution" , * gpResp . Policy . Resolution )
2021-09-15 19:27:53 +00:00
policiesResponse := listGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/policies" , nil , http . StatusOK , & policiesResponse )
2021-09-15 19:27:53 +00:00
require . Len ( t , policiesResponse . Policies , 1 )
2021-11-24 17:16:42 +00:00
assert . Equal ( t , qr . Name , policiesResponse . Policies [ 0 ] . Name )
assert . Equal ( t , qr . Query , policiesResponse . Policies [ 0 ] . Query )
assert . Equal ( t , qr . Description , policiesResponse . Policies [ 0 ] . Description )
2021-09-15 19:27:53 +00:00
2022-01-12 13:04:16 +00:00
// Get an unexistent policy
2022-04-05 15:35:53 +00:00
s . Do ( "GET" , fmt . Sprintf ( "/api/latest/fleet/policies/%d" , 9999 ) , nil , http . StatusNotFound )
2022-01-12 13:04:16 +00:00
2021-09-15 19:27:53 +00:00
singlePolicyResponse := getPolicyByIDResponse { }
2022-04-05 15:35:53 +00:00
singlePolicyURL := fmt . Sprintf ( "/api/latest/fleet/policies/%d" , policiesResponse . Policies [ 0 ] . ID )
2021-09-15 19:27:53 +00:00
s . DoJSON ( "GET" , singlePolicyURL , nil , http . StatusOK , & singlePolicyResponse )
2021-11-24 17:16:42 +00:00
assert . Equal ( t , qr . Name , singlePolicyResponse . Policy . Name )
assert . Equal ( t , qr . Query , singlePolicyResponse . Policy . Query )
assert . Equal ( t , qr . Description , singlePolicyResponse . Policy . Description )
2021-09-15 19:27:53 +00:00
2022-04-05 15:35:53 +00:00
listHostsURL := fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d" , policiesResponse . Policies [ 0 ] . ID )
2021-09-15 19:27:53 +00:00
listHostsResp := listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 3 )
h1 := listHostsResp . Hosts [ 0 ]
h2 := listHostsResp . Hosts [ 1 ]
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d&policy_response=passing" , policiesResponse . Policies [ 0 ] . ID )
2021-09-15 19:27:53 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 0 )
2021-11-08 14:42:37 +00:00
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h1 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h2 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : nil } , time . Now ( ) , false ) )
2021-09-15 19:27:53 +00:00
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d&policy_response=passing" , policiesResponse . Policies [ 0 ] . ID )
2021-09-15 19:27:53 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 1 )
deletePolicyParams := deleteGlobalPoliciesRequest { IDs : [ ] uint { policiesResponse . Policies [ 0 ] . ID } }
deletePolicyResp := deleteGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies/delete" , deletePolicyParams , http . StatusOK , & deletePolicyResp )
2021-09-15 19:27:53 +00:00
policiesResponse = listGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/policies" , nil , http . StatusOK , & policiesResponse )
2021-09-15 19:27:53 +00:00
require . Len ( t , policiesResponse . Policies , 0 )
}
2021-09-29 16:13:23 +00:00
func ( s * integrationTestSuite ) TestBulkDeleteHostsFromTeam ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
team1 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team { Name : t . Name ( ) + "team1" } )
require . NoError ( t , err )
2022-01-19 13:28:08 +00:00
p , err := s . ds . NewPack ( context . Background ( ) , & fleet . Pack {
Name : t . Name ( ) ,
Hosts : [ ] fleet . Target {
{
Type : fleet . TargetHost ,
TargetID : hosts [ 0 ] . ID ,
} ,
} ,
} )
require . NoError ( t , err )
2021-09-29 16:13:23 +00:00
require . NoError ( t , s . ds . AddHostsToTeam ( context . Background ( ) , & team1 . ID , [ ] uint { hosts [ 0 ] . ID } ) )
req := deleteHostsRequest {
Filters : struct {
MatchQuery string ` json:"query" `
Status fleet . HostStatus ` json:"status" `
LabelID * uint ` json:"label_id" `
TeamID * uint ` json:"team_id" `
} { TeamID : ptr . Uint ( team1 . ID ) } ,
}
resp := deleteHostsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/delete" , req , http . StatusOK , & resp )
2021-09-29 16:13:23 +00:00
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 0 ] . ID )
2021-09-29 16:13:23 +00:00
require . Error ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 1 ] . ID )
2021-09-29 16:13:23 +00:00
require . NoError ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 2 ] . ID )
2021-09-29 16:13:23 +00:00
require . NoError ( t , err )
err = s . ds . DeleteHosts ( context . Background ( ) , [ ] uint { hosts [ 1 ] . ID , hosts [ 2 ] . ID } )
require . NoError ( t , err )
2022-01-19 13:28:08 +00:00
newP , err := s . ds . Pack ( context . Background ( ) , p . ID )
require . NoError ( t , err )
require . Empty ( t , newP . Hosts )
require . NoError ( t , s . ds . DeletePack ( context . Background ( ) , newP . Name ) )
2021-09-29 16:13:23 +00:00
}
func ( s * integrationTestSuite ) TestBulkDeleteHostsInLabel ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
label := & fleet . Label {
Name : "foo" ,
Query : "select * from foo;" ,
}
label , err := s . ds . NewLabel ( context . Background ( ) , label )
require . NoError ( t , err )
2021-11-08 14:42:37 +00:00
require . NoError ( t , s . ds . RecordLabelQueryExecutions ( context . Background ( ) , hosts [ 1 ] , map [ uint ] * bool { label . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
require . NoError ( t , s . ds . RecordLabelQueryExecutions ( context . Background ( ) , hosts [ 2 ] , map [ uint ] * bool { label . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
2021-09-29 16:13:23 +00:00
req := deleteHostsRequest {
Filters : struct {
MatchQuery string ` json:"query" `
Status fleet . HostStatus ` json:"status" `
LabelID * uint ` json:"label_id" `
TeamID * uint ` json:"team_id" `
} { LabelID : ptr . Uint ( label . ID ) } ,
}
resp := deleteHostsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/delete" , req , http . StatusOK , & resp )
2021-09-29 16:13:23 +00:00
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 0 ] . ID )
2021-09-29 16:13:23 +00:00
require . NoError ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 1 ] . ID )
2021-09-29 16:13:23 +00:00
require . Error ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 2 ] . ID )
2021-09-29 16:13:23 +00:00
require . Error ( t , err )
err = s . ds . DeleteHosts ( context . Background ( ) , [ ] uint { hosts [ 0 ] . ID } )
require . NoError ( t , err )
}
func ( s * integrationTestSuite ) TestBulkDeleteHostByIDs ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
req := deleteHostsRequest {
IDs : [ ] uint { hosts [ 0 ] . ID , hosts [ 1 ] . ID } ,
}
resp := deleteHostsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/delete" , req , http . StatusOK , & resp )
2021-09-29 16:13:23 +00:00
2022-05-25 16:30:03 +00:00
_ , err := s . ds . Host ( context . Background ( ) , hosts [ 0 ] . ID )
2021-09-29 16:13:23 +00:00
require . Error ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 1 ] . ID )
2021-09-29 16:13:23 +00:00
require . Error ( t , err )
2022-05-25 16:30:03 +00:00
_ , err = s . ds . Host ( context . Background ( ) , hosts [ 2 ] . ID )
2021-09-29 16:13:23 +00:00
require . NoError ( t , err )
err = s . ds . DeleteHosts ( context . Background ( ) , [ ] uint { hosts [ 2 ] . ID } )
require . NoError ( t , err )
}
func ( s * integrationTestSuite ) createHosts ( t * testing . T ) [ ] * fleet . Host {
var hosts [ ] * fleet . Host
2022-02-07 16:50:36 +00:00
platforms := [ ] string { "debian" , "rhel" , "linux" }
2021-09-29 16:13:23 +00:00
for i := 0 ; i < 3 ; i ++ {
host , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) . Add ( - time . Duration ( i ) * time . Minute ) ,
OsqueryHostID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
NodeKey : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
2021-12-14 21:34:11 +00:00
UUID : uuid . New ( ) . String ( ) ,
2021-09-29 16:13:23 +00:00
Hostname : fmt . Sprintf ( "%sfoo.local%d" , t . Name ( ) , i ) ,
2022-02-07 16:50:36 +00:00
Platform : platforms [ i ] ,
2021-09-29 16:13:23 +00:00
} )
require . NoError ( t , err )
hosts = append ( hosts , host )
}
return hosts
}
func ( s * integrationTestSuite ) TestBulkDeleteHostsErrors ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
req := deleteHostsRequest {
IDs : [ ] uint { hosts [ 0 ] . ID , hosts [ 1 ] . ID } ,
Filters : struct {
MatchQuery string ` json:"query" `
Status fleet . HostStatus ` json:"status" `
LabelID * uint ` json:"label_id" `
TeamID * uint ` json:"team_id" `
} { LabelID : ptr . Uint ( 1 ) } ,
}
resp := deleteHostsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/delete" , req , http . StatusBadRequest , & resp )
2021-09-29 16:13:23 +00:00
}
2021-10-07 11:25:35 +00:00
func ( s * integrationTestSuite ) TestCountSoftware ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
label := & fleet . Label {
Name : t . Name ( ) + "foo" ,
Query : "select * from foo;" ,
}
label , err := s . ds . NewLabel ( context . Background ( ) , label )
require . NoError ( t , err )
2021-11-08 14:42:37 +00:00
require . NoError ( t , s . ds . RecordLabelQueryExecutions ( context . Background ( ) , hosts [ 0 ] , map [ uint ] * bool { label . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
2021-10-07 11:25:35 +00:00
req := countHostsRequest { }
resp := countHostsResponse { }
s . DoJSON (
2022-04-05 15:35:53 +00:00
"GET" , "/api/latest/fleet/hosts/count" , req , http . StatusOK , & resp ,
2021-10-07 11:25:35 +00:00
"additional_info_filters" , "*" ,
)
assert . Equal ( t , 3 , resp . Count )
req = countHostsRequest { }
resp = countHostsResponse { }
s . DoJSON (
2022-04-05 15:35:53 +00:00
"GET" , "/api/latest/fleet/hosts/count" , req , http . StatusOK , & resp ,
2021-10-07 11:25:35 +00:00
"additional_info_filters" , "*" ,
"label_id" , fmt . Sprint ( label . ID ) ,
)
assert . Equal ( t , 1 , resp . Count )
}
2021-10-11 14:17:21 +00:00
2021-12-15 14:35:40 +00:00
func ( s * integrationTestSuite ) TestPacks ( ) {
2021-10-11 14:17:21 +00:00
t := s . T ( )
var packResp getPackResponse
2021-12-15 14:35:40 +00:00
// get non-existing pack
2022-04-05 15:35:53 +00:00
s . Do ( "GET" , "/api/latest/fleet/packs/999" , nil , http . StatusNotFound )
2021-12-15 14:35:40 +00:00
// create some packs
packs := make ( [ ] fleet . Pack , 3 )
for i := range packs {
req := & createPackRequest {
PackPayload : fleet . PackPayload {
Name : ptr . String ( fmt . Sprintf ( "%s_%d" , strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) , i ) ) ,
} ,
}
var createResp createPackResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs" , req , http . StatusOK , & createResp )
2021-12-15 14:35:40 +00:00
packs [ i ] = createResp . Pack . Pack
}
2021-10-11 14:17:21 +00:00
2021-12-15 14:35:40 +00:00
// get existing pack
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/packs/%d" , packs [ 0 ] . ID ) , nil , http . StatusOK , & packResp )
2021-12-15 14:35:40 +00:00
require . Equal ( t , packs [ 0 ] . ID , packResp . Pack . ID )
// list packs
var listResp listPacksResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/packs" , nil , http . StatusOK , & listResp , "per_page" , "2" , "order_key" , "name" )
2021-12-15 14:35:40 +00:00
require . Len ( t , listResp . Packs , 2 )
assert . Equal ( t , packs [ 0 ] . ID , listResp . Packs [ 0 ] . ID )
assert . Equal ( t , packs [ 1 ] . ID , listResp . Packs [ 1 ] . ID )
// get page 1
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/packs" , nil , http . StatusOK , & listResp , "page" , "1" , "per_page" , "2" , "order_key" , "name" )
2021-12-15 14:35:40 +00:00
require . Len ( t , listResp . Packs , 1 )
assert . Equal ( t , packs [ 2 ] . ID , listResp . Packs [ 0 ] . ID )
// get page 2, empty
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/packs" , nil , http . StatusOK , & listResp , "page" , "2" , "per_page" , "2" , "order_key" , "name" )
2021-12-15 14:35:40 +00:00
require . Len ( t , listResp . Packs , 0 )
var delResp deletePackResponse
// delete non-existing pack by name
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/%s" , "zzz" ) , nil , http . StatusNotFound , & delResp )
2021-12-15 14:35:40 +00:00
// delete existing pack by name
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/%s" , url . PathEscape ( packs [ 0 ] . Name ) ) , nil , http . StatusOK , & delResp )
2021-12-15 14:35:40 +00:00
// delete non-existing pack by id
var delIDResp deletePackByIDResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/id/%d" , packs [ 2 ] . ID + 1 ) , nil , http . StatusNotFound , & delIDResp )
2021-12-15 14:35:40 +00:00
// delete existing pack by id
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/id/%d" , packs [ 1 ] . ID ) , nil , http . StatusOK , & delIDResp )
2021-12-15 14:35:40 +00:00
var modResp modifyPackResponse
// modify non-existing pack
req := & fleet . PackPayload { Name : ptr . String ( "updated_" + packs [ 2 ] . Name ) }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/packs/%d" , packs [ 2 ] . ID + 1 ) , req , http . StatusNotFound , & modResp )
2021-12-15 14:35:40 +00:00
// modify existing pack
req = & fleet . PackPayload { Name : ptr . String ( "updated_" + packs [ 2 ] . Name ) }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/packs/%d" , packs [ 2 ] . ID ) , req , http . StatusOK , & modResp )
2021-12-15 14:35:40 +00:00
require . Equal ( t , packs [ 2 ] . ID , modResp . Pack . ID )
require . Contains ( t , modResp . Pack . Name , "updated_" )
// list packs, only packs[2] remains
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/packs" , nil , http . StatusOK , & listResp , "per_page" , "2" , "order_key" , "name" )
2021-12-15 14:35:40 +00:00
require . Len ( t , listResp . Packs , 1 )
assert . Equal ( t , packs [ 2 ] . ID , listResp . Packs [ 0 ] . ID )
2021-10-11 14:17:21 +00:00
}
2021-10-11 14:37:48 +00:00
func ( s * integrationTestSuite ) TestListHosts ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
var resp listHostsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp )
2021-10-11 14:37:48 +00:00
require . Len ( t , resp . Hosts , len ( hosts ) )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "per_page" , "1" )
2021-10-11 14:37:48 +00:00
require . Len ( t , resp . Hosts , 1 )
2021-10-12 18:59:01 +00:00
assert . Nil ( t , resp . Software )
2021-10-12 14:38:12 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "order_key" , "h.id" , "after" , fmt . Sprint ( hosts [ 1 ] . ID ) )
2021-11-29 18:06:00 +00:00
require . Len ( t , resp . Hosts , len ( hosts ) - 2 )
2021-10-12 14:38:12 +00:00
host := hosts [ 2 ]
2022-01-18 01:52:09 +00:00
software := [ ] fleet . Software {
{ Name : "foo" , Version : "0.0.1" , Source : "chrome_extensions" } ,
2021-10-12 14:38:12 +00:00
}
2022-01-18 01:52:09 +00:00
require . NoError ( t , s . ds . UpdateHostSoftware ( context . Background ( ) , host . ID , software ) )
2022-06-01 16:06:57 +00:00
require . NoError ( t , s . ds . LoadHostSoftware ( context . Background ( ) , host , false ) )
2021-10-12 14:38:12 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "software_id" , fmt . Sprint ( host . Software [ 0 ] . ID ) )
2021-10-12 14:38:12 +00:00
require . Len ( t , resp . Hosts , 1 )
assert . Equal ( t , host . ID , resp . Hosts [ 0 ] . ID )
2021-10-12 18:59:01 +00:00
assert . Equal ( t , "foo" , resp . Software . Name )
2021-10-15 10:34:30 +00:00
2021-11-24 17:16:42 +00:00
user1 := test . NewUser ( t , s . ds , "Alice" , "alice@example.com" , true )
2021-10-15 10:34:30 +00:00
q := test . NewQuery ( t , s . ds , "query1" , "select 1" , 0 , true )
2022-01-31 21:35:22 +00:00
defer cleanupQuery ( s , q . ID )
2021-11-24 17:16:42 +00:00
p , err := s . ds . NewGlobalPolicy ( context . Background ( ) , & user1 . ID , fleet . PolicyPayload {
QueryID : & q . ID ,
} )
2021-10-15 10:34:30 +00:00
require . NoError ( t , err )
2021-11-08 14:42:37 +00:00
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , host , map [ uint ] * bool { p . ID : ptr . Bool ( false ) } , time . Now ( ) , false ) )
2021-10-15 10:34:30 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "software_id" , fmt . Sprint ( host . Software [ 0 ] . ID ) )
2021-10-15 10:34:30 +00:00
require . Len ( t , resp . Hosts , 1 )
assert . Equal ( t , 1 , resp . Hosts [ 0 ] . HostIssues . FailingPoliciesCount )
assert . Equal ( t , 1 , resp . Hosts [ 0 ] . HostIssues . TotalIssuesCount )
2021-11-29 21:04:33 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "software_id" , fmt . Sprint ( host . Software [ 0 ] . ID ) , "disable_failing_policies" , "true" )
2021-11-29 21:04:33 +00:00
require . Len ( t , resp . Hosts , 1 )
assert . Equal ( t , 0 , resp . Hosts [ 0 ] . HostIssues . FailingPoliciesCount )
assert . Equal ( t , 0 , resp . Hosts [ 0 ] . HostIssues . TotalIssuesCount )
2021-10-11 14:37:48 +00:00
}
2021-11-09 14:35:36 +00:00
2021-11-11 20:33:06 +00:00
func ( s * integrationTestSuite ) TestInvites ( ) {
t := s . T ( )
team , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
Name : t . Name ( ) + "team1" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
2022-02-15 20:22:19 +00:00
// list invites, none yet
var listResp listInvitesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites" , nil , http . StatusOK , & listResp )
2022-02-15 20:22:19 +00:00
require . Len ( t , listResp . Invites , 0 )
// create valid invite
createInviteReq := createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( "some email" ) ,
Name : ptr . String ( "some name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleAdmin ) ,
} }
2021-11-11 20:33:06 +00:00
createInviteResp := createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusOK , & createInviteResp )
2021-11-11 20:33:06 +00:00
require . NotNil ( t , createInviteResp . Invite )
require . NotZero ( t , createInviteResp . Invite . ID )
2022-02-15 20:22:19 +00:00
validInvite := * createInviteResp . Invite
2021-11-11 20:33:06 +00:00
2022-03-08 16:27:38 +00:00
// create user from valid invite - the token was not returned via the
// response's json, must get it from the db
inv , err := s . ds . Invite ( context . Background ( ) , validInvite . ID )
require . NoError ( t , err )
validInviteToken := inv . Token
// verify the token with valid invite
var verifyInvResp verifyInviteResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites/" + validInviteToken , nil , http . StatusOK , & verifyInvResp )
2022-03-08 16:27:38 +00:00
require . Equal ( t , validInvite . ID , verifyInvResp . Invite . ID )
// verify the token with an invalid invite
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites/invalid" , nil , http . StatusNotFound , & verifyInvResp )
2022-03-08 16:27:38 +00:00
2022-02-15 20:22:19 +00:00
// create invite without an email
createInviteReq = createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : nil ,
Name : ptr . String ( "some other name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleObserver ) ,
} }
createInviteResp = createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusUnprocessableEntity , & createInviteResp )
2022-02-15 20:22:19 +00:00
// create invite for an existing user
existingEmail := "admin1@example.com"
createInviteReq = createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( existingEmail ) ,
Name : ptr . String ( "some other name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleObserver ) ,
} }
createInviteResp = createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusUnprocessableEntity , & createInviteResp )
2022-02-15 20:22:19 +00:00
// create invite for an existing user with email ALL CAPS
createInviteReq = createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( strings . ToUpper ( existingEmail ) ) ,
Name : ptr . String ( "some other name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleObserver ) ,
} }
createInviteResp = createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusUnprocessableEntity , & createInviteResp )
2022-02-15 20:22:19 +00:00
// list invites, we have one now
listResp = listInvitesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites" , nil , http . StatusOK , & listResp )
2022-02-15 20:22:19 +00:00
require . Len ( t , listResp . Invites , 1 )
require . Equal ( t , validInvite . ID , listResp . Invites [ 0 ] . ID )
// list invites, next page is empty
listResp = listInvitesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites" , nil , http . StatusOK , & listResp , "page" , "1" , "per_page" , "2" )
2022-02-15 20:22:19 +00:00
require . Len ( t , listResp . Invites , 0 )
// update a non-existing invite
updateInviteReq := updateInviteRequest { InvitePayload : fleet . InvitePayload {
Teams : [ ] fleet . UserTeam {
{ Team : fleet . Team { ID : team . ID } , Role : fleet . RoleObserver } ,
2022-03-15 19:14:42 +00:00
} ,
} }
2021-11-11 20:33:06 +00:00
updateInviteResp := updateInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID + 1 ) , updateInviteReq , http . StatusNotFound , & updateInviteResp )
2021-11-11 20:33:06 +00:00
2022-02-15 20:22:19 +00:00
// update the valid invite created earlier, make it an observer of a team
updateInviteResp = updateInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID ) , updateInviteReq , http . StatusOK , & updateInviteResp )
2022-02-15 20:22:19 +00:00
2022-02-28 12:34:44 +00:00
// update the valid invite: set an email that already exists for a user
updateInviteReq = updateInviteRequest {
InvitePayload : fleet . InvitePayload {
Email : ptr . String ( s . users [ "admin1@example.com" ] . Email ) ,
Teams : [ ] fleet . UserTeam {
{ Team : fleet . Team { ID : team . ID } , Role : fleet . RoleObserver } ,
} ,
} ,
}
updateInviteResp = updateInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID ) , updateInviteReq , http . StatusConflict , & updateInviteResp )
2022-02-28 12:34:44 +00:00
// update the valid invite: set an email that already exists for another invite
createInviteReq = createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( "some@other.email" ) ,
Name : ptr . String ( "some name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleAdmin ) ,
} }
createInviteResp = createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusOK , & createInviteResp )
2022-02-28 12:34:44 +00:00
updateInviteReq = updateInviteRequest {
InvitePayload : fleet . InvitePayload {
Email : createInviteReq . Email ,
Teams : [ ] fleet . UserTeam {
{ Team : fleet . Team { ID : team . ID } , Role : fleet . RoleObserver } ,
} ,
} ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID ) , updateInviteReq , http . StatusConflict , & updateInviteResp )
2022-02-28 12:34:44 +00:00
// update the valid invite to an email that is ok
updateInviteReq = updateInviteRequest {
InvitePayload : fleet . InvitePayload {
Email : ptr . String ( "something@nonexistent.yet123" ) ,
Teams : [ ] fleet . UserTeam {
{ Team : fleet . Team { ID : team . ID } , Role : fleet . RoleObserver } ,
} ,
} ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID ) , updateInviteReq , http . StatusOK , & updateInviteResp )
2022-02-28 12:34:44 +00:00
2022-02-15 20:22:19 +00:00
verify , err := s . ds . Invite ( context . Background ( ) , validInvite . ID )
2021-11-11 20:33:06 +00:00
require . NoError ( t , err )
require . Equal ( t , "" , verify . GlobalRole . String )
require . Len ( t , verify . Teams , 1 )
assert . Equal ( t , team . ID , verify . Teams [ 0 ] . ID )
2022-02-15 20:22:19 +00:00
2022-03-08 16:27:38 +00:00
var createFromInviteResp createUserResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users" , fleet . UserPayload {
2022-03-08 16:27:38 +00:00
Name : ptr . String ( "Full Name" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( validInviteToken ) ,
} , http . StatusOK , & createFromInviteResp )
// keep the invite token from the other valid invite (before deleting it)
inv , err = s . ds . Invite ( context . Background ( ) , createInviteResp . Invite . ID )
require . NoError ( t , err )
deletedInviteToken := inv . Token
2022-02-15 20:22:19 +00:00
// delete an existing invite
var delResp deleteInviteResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , createInviteResp . Invite . ID ) , nil , http . StatusOK , & delResp )
2022-02-15 20:22:19 +00:00
// list invites, is now empty
listResp = listInvitesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/invites" , nil , http . StatusOK , & listResp )
2022-02-15 20:22:19 +00:00
require . Len ( t , listResp . Invites , 0 )
// delete a now non-existing invite
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , validInvite . ID ) , nil , http . StatusNotFound , & delResp )
2022-03-08 16:27:38 +00:00
// create user from never used but deleted invite
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users" , fleet . UserPayload {
2022-03-08 16:27:38 +00:00
Name : ptr . String ( "Full Name" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( deletedInviteToken ) ,
} , http . StatusNotFound , & createFromInviteResp )
}
func ( s * integrationTestSuite ) TestCreateUserFromInviteErrors ( ) {
t := s . T ( )
// create a valid invite
createInviteReq := createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( "a@b.c" ) ,
Name : ptr . String ( "A" ) ,
GlobalRole : null . StringFrom ( fleet . RoleObserver ) ,
} }
createInviteResp := createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusOK , & createInviteResp )
2022-03-08 16:27:38 +00:00
// make sure to delete it on exit
defer func ( ) {
var delResp deleteInviteResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/invites/%d" , createInviteResp . Invite . ID ) , nil , http . StatusOK , & delResp )
2022-03-08 16:27:38 +00:00
} ( )
// the token is not returned via the response's json, must get it from the db
invite , err := s . ds . Invite ( context . Background ( ) , createInviteResp . Invite . ID )
require . NoError ( t , err )
cases := [ ] struct {
desc string
pld fleet . UserPayload
want int
} {
{
"empty name" ,
fleet . UserPayload {
Name : ptr . String ( "" ) ,
2022-05-18 17:03:00 +00:00
Password : & test . GoodPassword ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( invite . Token ) ,
} ,
http . StatusUnprocessableEntity ,
} ,
{
"empty email" ,
fleet . UserPayload {
Name : ptr . String ( "Name" ) ,
2022-05-18 17:03:00 +00:00
Password : & test . GoodPassword ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "" ) ,
InviteToken : ptr . String ( invite . Token ) ,
} ,
http . StatusUnprocessableEntity ,
} ,
{
"empty password" ,
fleet . UserPayload {
Name : ptr . String ( "Name" ) ,
Password : ptr . String ( "" ) ,
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( invite . Token ) ,
} ,
http . StatusUnprocessableEntity ,
} ,
{
"empty token" ,
fleet . UserPayload {
Name : ptr . String ( "Name" ) ,
2022-05-18 17:03:00 +00:00
Password : & test . GoodPassword ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( "" ) ,
} ,
http . StatusUnprocessableEntity ,
} ,
{
"invalid token" ,
fleet . UserPayload {
Name : ptr . String ( "Name" ) ,
2022-05-18 17:03:00 +00:00
Password : & test . GoodPassword ,
2022-03-08 16:27:38 +00:00
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( "invalid" ) ,
} ,
http . StatusNotFound ,
} ,
{
"invalid password" ,
fleet . UserPayload {
Name : ptr . String ( "Name" ) ,
Password : ptr . String ( "password" ) , // no number or symbol
Email : ptr . String ( "a@b.c" ) ,
InviteToken : ptr . String ( invite . Token ) ,
} ,
http . StatusUnprocessableEntity ,
} ,
}
for _ , c := range cases {
t . Run ( c . desc , func ( t * testing . T ) {
var resp createUserResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users" , c . pld , c . want , & resp )
2022-03-08 16:27:38 +00:00
} )
}
2021-11-11 20:33:06 +00:00
}
2021-11-09 14:35:36 +00:00
func ( s * integrationTestSuite ) TestGetHostSummary ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
team1 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team { Name : t . Name ( ) + "team1" } )
require . NoError ( t , err )
team2 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team { Name : t . Name ( ) + "team2" } )
require . NoError ( t , err )
require . NoError ( t , s . ds . AddHostsToTeam ( context . Background ( ) , & team1 . ID , [ ] uint { hosts [ 0 ] . ID } ) )
var resp getHostSummaryResponse
// no team filter
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp )
2021-11-09 14:35:36 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( len ( hosts ) ) )
2022-02-07 16:50:36 +00:00
require . Len ( t , resp . Platforms , 3 )
gotPlatforms , wantPlatforms := make ( [ ] string , 3 ) , [ ] string { "linux" , "debian" , "rhel" }
for i , p := range resp . Platforms {
gotPlatforms [ i ] = p . Platform
// each platform has a count of 1
require . Equal ( t , uint ( 1 ) , p . HostsCount )
}
require . ElementsMatch ( t , wantPlatforms , gotPlatforms )
2021-11-15 14:56:13 +00:00
require . Nil ( t , resp . TeamID )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 3 ) , resp . AllLinuxCount )
assert . True ( t , len ( resp . BuiltinLabels ) > 0 )
for _ , lbl := range resp . BuiltinLabels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
builtinsCount := len ( resp . BuiltinLabels )
// host summary builtin labels match list labels response
var listResp listLabelsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/labels" , nil , http . StatusOK , & listResp )
assert . True ( t , len ( listResp . Labels ) > 0 )
for _ , lbl := range listResp . Labels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
assert . Equal ( t , len ( listResp . Labels ) , builtinsCount )
2021-11-09 14:35:36 +00:00
// team filter, no host
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "team_id" , fmt . Sprint ( team2 . ID ) )
2021-11-09 14:35:36 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 0 ) )
require . Len ( t , resp . Platforms , 0 )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 0 ) , resp . AllLinuxCount )
2021-11-15 14:56:13 +00:00
require . Equal ( t , team2 . ID , * resp . TeamID )
2021-11-09 14:35:36 +00:00
// team filter, one host
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "team_id" , fmt . Sprint ( team1 . ID ) )
2021-11-09 14:35:36 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 1 ) )
require . Len ( t , resp . Platforms , 1 )
2022-02-07 16:50:36 +00:00
require . Equal ( t , "debian" , resp . Platforms [ 0 ] . Platform )
2021-11-09 14:35:36 +00:00
require . Equal ( t , uint ( 1 ) , resp . Platforms [ 0 ] . HostsCount )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 1 ) , resp . AllLinuxCount )
2021-11-15 14:56:13 +00:00
require . Equal ( t , team1 . ID , * resp . TeamID )
2022-01-24 17:49:21 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "team_id" , fmt . Sprint ( team1 . ID ) , "platform" , "linux" )
2022-01-24 17:49:21 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 1 ) )
2022-02-07 16:50:36 +00:00
require . Equal ( t , "debian" , resp . Platforms [ 0 ] . Platform )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 1 ) , resp . AllLinuxCount )
2022-02-07 16:50:36 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "platform" , "rhel" )
2022-02-07 16:50:36 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 1 ) )
require . Equal ( t , "rhel" , resp . Platforms [ 0 ] . Platform )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 1 ) , resp . AllLinuxCount )
2022-01-24 17:49:21 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "platform" , "linux" )
2022-01-24 17:49:21 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 3 ) )
2022-05-10 15:32:55 +00:00
require . Equal ( t , uint ( 3 ) , resp . AllLinuxCount )
2022-02-07 16:50:36 +00:00
require . Len ( t , resp . Platforms , 3 )
for i , p := range resp . Platforms {
gotPlatforms [ i ] = p . Platform
// each platform has a count of 1
require . Equal ( t , uint ( 1 ) , p . HostsCount )
}
require . ElementsMatch ( t , wantPlatforms , gotPlatforms )
2022-01-24 17:49:21 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "platform" , "darwin" )
2022-01-24 17:49:21 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 0 ) )
2022-05-10 15:32:55 +00:00
require . Equal ( t , resp . AllLinuxCount , uint ( 0 ) )
2022-02-07 16:50:36 +00:00
require . Len ( t , resp . Platforms , 0 )
2021-11-09 14:35:36 +00:00
}
2021-11-24 17:16:42 +00:00
func ( s * integrationTestSuite ) TestGlobalPoliciesProprietary ( ) {
t := s . T ( )
for i := 0 ; i < 3 ; i ++ {
_ , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) . Add ( - time . Duration ( i ) * time . Minute ) ,
OsqueryHostID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
NodeKey : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
UUID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
Hostname : fmt . Sprintf ( "%sfoo.local%d" , t . Name ( ) , i ) ,
2021-12-03 18:33:33 +00:00
Platform : "darwin" ,
2021-11-24 17:16:42 +00:00
} )
require . NoError ( t , err )
}
qr , err := s . ds . NewQuery ( context . Background ( ) , & fleet . Query {
Name : "TestQuery321" ,
Description : "Some description" ,
Query : "select * from osquery;" ,
ObserverCanRun : true ,
} )
require . NoError ( t , err )
// Cannot set both QueryID and Query.
gpParams0 := globalPolicyRequest {
QueryID : & qr . ID ,
Query : "select * from osquery;" ,
}
gpResp0 := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams0 , http . StatusBadRequest , & gpResp0 )
2021-11-24 17:16:42 +00:00
require . Nil ( t , gpResp0 . Policy )
gpParams := globalPolicyRequest {
Name : "TestQuery3" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some global resolution" ,
2021-12-10 16:55:49 +00:00
Platform : "darwin" ,
2021-11-24 17:16:42 +00:00
}
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusOK , & gpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , gpResp . Policy )
require . NotEmpty ( t , gpResp . Policy . ID )
assert . Equal ( t , "TestQuery3" , gpResp . Policy . Name )
assert . Equal ( t , "select * from osquery;" , gpResp . Policy . Query )
assert . Equal ( t , "Some description" , gpResp . Policy . Description )
require . NotNil ( t , gpResp . Policy . Resolution )
assert . Equal ( t , "some global resolution" , * gpResp . Policy . Resolution )
assert . NotNil ( t , gpResp . Policy . AuthorID )
assert . Equal ( t , "Test Name admin1@example.com" , gpResp . Policy . AuthorName )
assert . Equal ( t , "admin1@example.com" , gpResp . Policy . AuthorEmail )
2021-12-06 16:56:28 +00:00
assert . Equal ( t , "darwin" , gpResp . Policy . Platform )
2021-11-24 17:16:42 +00:00
mgpParams := modifyGlobalPolicyRequest {
ModifyPolicyPayload : fleet . ModifyPolicyPayload {
Name : ptr . String ( "TestQuery4" ) ,
Query : ptr . String ( "select * from osquery_info;" ) ,
Description : ptr . String ( "Some description updated" ) ,
Resolution : ptr . String ( "some global resolution updated" ) ,
} ,
}
mgpResp := modifyGlobalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/policies/%d" , gpResp . Policy . ID ) , mgpParams , http . StatusOK , & mgpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , gpResp . Policy )
assert . Equal ( t , "TestQuery4" , mgpResp . Policy . Name )
assert . Equal ( t , "select * from osquery_info;" , mgpResp . Policy . Query )
assert . Equal ( t , "Some description updated" , mgpResp . Policy . Description )
require . NotNil ( t , mgpResp . Policy . Resolution )
assert . Equal ( t , "some global resolution updated" , * mgpResp . Policy . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , mgpResp . Policy . Platform )
2021-11-24 17:16:42 +00:00
ggpResp := getPolicyByIDResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/policies/%d" , gpResp . Policy . ID ) , getPolicyByIDRequest { } , http . StatusOK , & ggpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , ggpResp . Policy )
assert . Equal ( t , "TestQuery4" , ggpResp . Policy . Name )
assert . Equal ( t , "select * from osquery_info;" , ggpResp . Policy . Query )
assert . Equal ( t , "Some description updated" , ggpResp . Policy . Description )
require . NotNil ( t , ggpResp . Policy . Resolution )
assert . Equal ( t , "some global resolution updated" , * ggpResp . Policy . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , mgpResp . Policy . Platform )
2021-11-24 17:16:42 +00:00
policiesResponse := listGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/policies" , nil , http . StatusOK , & policiesResponse )
2021-11-24 17:16:42 +00:00
require . Len ( t , policiesResponse . Policies , 1 )
assert . Equal ( t , "TestQuery4" , policiesResponse . Policies [ 0 ] . Name )
assert . Equal ( t , "select * from osquery_info;" , policiesResponse . Policies [ 0 ] . Query )
assert . Equal ( t , "Some description updated" , policiesResponse . Policies [ 0 ] . Description )
require . NotNil ( t , policiesResponse . Policies [ 0 ] . Resolution )
assert . Equal ( t , "some global resolution updated" , * policiesResponse . Policies [ 0 ] . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , policiesResponse . Policies [ 0 ] . Platform )
2021-11-24 17:16:42 +00:00
2022-04-05 15:35:53 +00:00
listHostsURL := fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d" , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp := listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 3 )
h1 := listHostsResp . Hosts [ 0 ]
h2 := listHostsResp . Hosts [ 1 ]
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d&policy_response=passing" , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 0 )
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h1 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h2 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : nil } , time . Now ( ) , false ) )
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d&policy_response=passing" , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 1 )
deletePolicyParams := deleteGlobalPoliciesRequest { IDs : [ ] uint { policiesResponse . Policies [ 0 ] . ID } }
deletePolicyResp := deleteGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies/delete" , deletePolicyParams , http . StatusOK , & deletePolicyResp )
2021-11-24 17:16:42 +00:00
policiesResponse = listGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/policies" , nil , http . StatusOK , & policiesResponse )
2021-11-24 17:16:42 +00:00
require . Len ( t , policiesResponse . Policies , 0 )
}
func ( s * integrationTestSuite ) TestTeamPoliciesProprietary ( ) {
t := s . T ( )
team1 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
ID : 42 ,
Name : "team1-policies" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
hosts := make ( [ ] uint , 2 )
for i := 0 ; i < 2 ; i ++ {
h , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) . Add ( - time . Duration ( i ) * time . Minute ) ,
OsqueryHostID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
NodeKey : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
UUID : fmt . Sprintf ( "%s%d" , t . Name ( ) , i ) ,
Hostname : fmt . Sprintf ( "%sfoo.local%d" , t . Name ( ) , i ) ,
2021-12-03 18:33:33 +00:00
Platform : "darwin" ,
2021-11-24 17:16:42 +00:00
} )
require . NoError ( t , err )
hosts [ i ] = h . ID
}
err = s . ds . AddHostsToTeam ( context . Background ( ) , & team1 . ID , hosts )
require . NoError ( t , err )
tpParams := teamPolicyRequest {
Name : "TestQuery3" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some team resolution" ,
2021-12-10 16:55:49 +00:00
Platform : "darwin" ,
2021-11-24 17:16:42 +00:00
}
tpResp := teamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , tpParams , http . StatusOK , & tpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , tpResp . Policy )
require . NotEmpty ( t , tpResp . Policy . ID )
assert . Equal ( t , "TestQuery3" , tpResp . Policy . Name )
assert . Equal ( t , "select * from osquery;" , tpResp . Policy . Query )
assert . Equal ( t , "Some description" , tpResp . Policy . Description )
require . NotNil ( t , tpResp . Policy . Resolution )
assert . Equal ( t , "some team resolution" , * tpResp . Policy . Resolution )
assert . NotNil ( t , tpResp . Policy . AuthorID )
assert . Equal ( t , "Test Name admin1@example.com" , tpResp . Policy . AuthorName )
assert . Equal ( t , "admin1@example.com" , tpResp . Policy . AuthorEmail )
mtpParams := modifyTeamPolicyRequest {
ModifyPolicyPayload : fleet . ModifyPolicyPayload {
Name : ptr . String ( "TestQuery4" ) ,
Query : ptr . String ( "select * from osquery_info;" ) ,
Description : ptr . String ( "Some description updated" ) ,
Resolution : ptr . String ( "some team resolution updated" ) ,
} ,
}
mtpResp := modifyTeamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies/%d" , team1 . ID , tpResp . Policy . ID ) , mtpParams , http . StatusOK , & mtpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , mtpResp . Policy )
assert . Equal ( t , "TestQuery4" , mtpResp . Policy . Name )
assert . Equal ( t , "select * from osquery_info;" , mtpResp . Policy . Query )
assert . Equal ( t , "Some description updated" , mtpResp . Policy . Description )
require . NotNil ( t , mtpResp . Policy . Resolution )
assert . Equal ( t , "some team resolution updated" , * mtpResp . Policy . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , mtpResp . Policy . Platform )
2021-11-24 17:16:42 +00:00
gtpResp := getPolicyByIDResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies/%d" , team1 . ID , tpResp . Policy . ID ) , getPolicyByIDRequest { } , http . StatusOK , & gtpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , gtpResp . Policy )
assert . Equal ( t , "TestQuery4" , gtpResp . Policy . Name )
assert . Equal ( t , "select * from osquery_info;" , gtpResp . Policy . Query )
assert . Equal ( t , "Some description updated" , gtpResp . Policy . Description )
require . NotNil ( t , gtpResp . Policy . Resolution )
assert . Equal ( t , "some team resolution updated" , * gtpResp . Policy . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , gtpResp . Policy . Platform )
2021-11-24 17:16:42 +00:00
policiesResponse := listTeamPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , nil , http . StatusOK , & policiesResponse )
2021-11-24 17:16:42 +00:00
require . Len ( t , policiesResponse . Policies , 1 )
assert . Equal ( t , "TestQuery4" , policiesResponse . Policies [ 0 ] . Name )
assert . Equal ( t , "select * from osquery_info;" , policiesResponse . Policies [ 0 ] . Query )
assert . Equal ( t , "Some description updated" , policiesResponse . Policies [ 0 ] . Description )
require . NotNil ( t , policiesResponse . Policies [ 0 ] . Resolution )
assert . Equal ( t , "some team resolution updated" , * policiesResponse . Policies [ 0 ] . Resolution )
2021-12-07 16:01:00 +00:00
assert . Equal ( t , "darwin" , policiesResponse . Policies [ 0 ] . Platform )
2021-11-24 17:16:42 +00:00
2022-04-05 15:35:53 +00:00
listHostsURL := fmt . Sprintf ( "/api/latest/fleet/hosts?policy_id=%d" , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp := listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 2 )
h1 := listHostsResp . Hosts [ 0 ]
h2 := listHostsResp . Hosts [ 1 ]
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?team_id=%d&policy_id=%d&policy_response=passing" , team1 . ID , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 0 )
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h1 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : ptr . Bool ( true ) } , time . Now ( ) , false ) )
require . NoError ( t , s . ds . RecordPolicyQueryExecutions ( context . Background ( ) , h2 . Host , map [ uint ] * bool { policiesResponse . Policies [ 0 ] . ID : nil } , time . Now ( ) , false ) )
2022-04-05 15:35:53 +00:00
listHostsURL = fmt . Sprintf ( "/api/latest/fleet/hosts?team_id=%d&policy_id=%d&policy_response=passing" , team1 . ID , policiesResponse . Policies [ 0 ] . ID )
2021-11-24 17:16:42 +00:00
listHostsResp = listHostsResponse { }
s . DoJSON ( "GET" , listHostsURL , nil , http . StatusOK , & listHostsResp )
require . Len ( t , listHostsResp . Hosts , 1 )
deletePolicyParams := deleteTeamPoliciesRequest { IDs : [ ] uint { policiesResponse . Policies [ 0 ] . ID } }
deletePolicyResp := deleteTeamPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies/delete" , team1 . ID ) , deletePolicyParams , http . StatusOK , & deletePolicyResp )
2021-11-24 17:16:42 +00:00
policiesResponse = listTeamPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , nil , http . StatusOK , & policiesResponse )
2021-11-24 17:16:42 +00:00
require . Len ( t , policiesResponse . Policies , 0 )
}
func ( s * integrationTestSuite ) TestTeamPoliciesProprietaryInvalid ( ) {
t := s . T ( )
team1 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
ID : 42 ,
Name : "team1-policies-2" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
tpParams := teamPolicyRequest {
Name : "TestQuery3-Team" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some team resolution" ,
}
tpResp := teamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , tpParams , http . StatusOK , & tpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , tpResp . Policy )
teamPolicyID := tpResp . Policy . ID
gpParams := globalPolicyRequest {
Name : "TestQuery3-Global" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some global resolution" ,
}
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusOK , & gpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , gpResp . Policy )
require . NotEmpty ( t , gpResp . Policy . ID )
globalPolicyID := gpResp . Policy . ID
for _ , tc := range [ ] struct {
tname string
testUpdate bool
queryID * uint
name string
query string
2021-12-03 18:33:33 +00:00
platforms string
2021-11-24 17:16:42 +00:00
} {
{
tname : "set both QueryID and Query" ,
testUpdate : false ,
queryID : ptr . Uint ( 1 ) ,
name : "Some name" ,
query : "select * from osquery;" ,
} ,
{
tname : "empty query" ,
testUpdate : true ,
name : "Some name" ,
query : "" ,
} ,
{
tname : "empty name" ,
testUpdate : true ,
name : "" ,
query : "select 1;" ,
} ,
2022-01-19 19:07:58 +00:00
{
tname : "empty with space" ,
testUpdate : true ,
name : " " , // #3704
query : "select 1;" ,
} ,
2021-11-24 17:16:42 +00:00
{
tname : "Invalid query" ,
testUpdate : true ,
name : "Invalid query" ,
query : "ATTACH 'foo' AS bar;" ,
} ,
} {
t . Run ( tc . tname , func ( t * testing . T ) {
tpReq := teamPolicyRequest {
2021-12-10 16:55:49 +00:00
QueryID : tc . queryID ,
Name : tc . name ,
Query : tc . query ,
Platform : tc . platforms ,
2021-11-24 17:16:42 +00:00
}
tpResp := teamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , tpReq , http . StatusBadRequest , & tpResp )
2021-11-24 17:16:42 +00:00
require . Nil ( t , tpResp . Policy )
testUpdate := tc . queryID == nil
if testUpdate {
tpReq := modifyTeamPolicyRequest {
ModifyPolicyPayload : fleet . ModifyPolicyPayload {
2021-12-07 16:01:00 +00:00
Name : ptr . String ( tc . name ) ,
Query : ptr . String ( tc . query ) ,
2021-11-24 17:16:42 +00:00
} ,
}
tpResp := modifyTeamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies/%d" , team1 . ID , teamPolicyID ) , tpReq , http . StatusBadRequest , & tpResp )
2021-11-24 17:16:42 +00:00
require . Nil ( t , tpResp . Policy )
}
gpReq := globalPolicyRequest {
2021-12-10 16:55:49 +00:00
QueryID : tc . queryID ,
Name : tc . name ,
Query : tc . query ,
Platform : tc . platforms ,
2021-11-24 17:16:42 +00:00
}
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpReq , http . StatusBadRequest , & gpResp )
2021-11-24 17:16:42 +00:00
require . Nil ( t , tpResp . Policy )
if testUpdate {
gpReq := modifyGlobalPolicyRequest {
ModifyPolicyPayload : fleet . ModifyPolicyPayload {
2021-12-07 16:01:00 +00:00
Name : ptr . String ( tc . name ) ,
Query : ptr . String ( tc . query ) ,
2021-11-24 17:16:42 +00:00
} ,
}
gpResp := modifyGlobalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/policies/%d" , globalPolicyID ) , gpReq , http . StatusBadRequest , & gpResp )
2021-11-24 17:16:42 +00:00
require . Nil ( t , tpResp . Policy )
}
} )
}
}
func ( s * integrationTestSuite ) TestHostDetailsPolicies ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
host1 := hosts [ 0 ]
team1 , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
ID : 42 ,
Name : "HostDetailsPolicies-Team" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
err = s . ds . AddHostsToTeam ( context . Background ( ) , & team1 . ID , [ ] uint { host1 . ID } )
require . NoError ( t , err )
gpParams := globalPolicyRequest {
Name : "HostDetailsPolicies" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some global resolution" ,
}
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusOK , & gpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , gpResp . Policy )
require . NotEmpty ( t , gpResp . Policy . ID )
tpParams := teamPolicyRequest {
Name : "HostDetailsPolicies-Team" ,
Query : "select * from osquery;" ,
Description : "Some description" ,
Resolution : "some team resolution" ,
}
tpResp := teamPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , tpParams , http . StatusOK , & tpResp )
2021-11-24 17:16:42 +00:00
require . NotNil ( t , tpResp . Policy )
require . NotEmpty ( t , tpResp . Policy . ID )
err = s . ds . RecordPolicyQueryExecutions (
context . Background ( ) ,
host1 ,
map [ uint ] * bool { gpResp . Policy . ID : ptr . Bool ( true ) } ,
time . Now ( ) ,
false ,
)
require . NoError ( t , err )
2022-04-05 15:35:53 +00:00
resp := s . Do ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host1 . ID ) , nil , http . StatusOK )
2021-11-24 17:16:42 +00:00
b , err := ioutil . ReadAll ( resp . Body )
require . NoError ( t , err )
var r struct {
Host * HostDetailResponse ` json:"host" `
Err error ` json:"error,omitempty" `
}
err = json . Unmarshal ( b , & r )
require . NoError ( t , err )
require . Nil ( t , r . Err )
hd := r . Host . HostDetail
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
policies := * hd . Policies
require . Len ( t , policies , 2 )
require . True ( t , reflect . DeepEqual ( gpResp . Policy . PolicyData , policies [ 0 ] . PolicyData ) )
require . Equal ( t , policies [ 0 ] . Response , "pass" )
2021-11-24 17:16:42 +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
require . True ( t , reflect . DeepEqual ( tpResp . Policy . PolicyData , policies [ 1 ] . PolicyData ) )
require . Equal ( t , policies [ 1 ] . Response , "" ) // policy didn't "run"
2021-11-24 17:16:42 +00:00
// Try to create a global policy with an existing name.
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusConflict , & gpResp )
2021-11-24 17:16:42 +00:00
// Try to create a team policy with an existing name.
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , team1 . ID ) , tpParams , http . StatusConflict , & tpResp )
2021-11-24 17:16:42 +00:00
}
2021-11-29 13:12:22 +00:00
func ( s * integrationTestSuite ) TestListActivities ( ) {
t := s . T ( )
ctx := context . Background ( )
u := s . users [ "admin1@example.com" ]
details := make ( map [ string ] interface { } )
2022-02-16 15:33:56 +00:00
prevActivities , err := s . ds . ListActivities ( ctx , fleet . ListOptions { } )
require . NoError ( t , err )
err = s . ds . NewActivity ( ctx , & u , fleet . ActivityTypeAppliedSpecPack , & details )
2021-11-29 13:12:22 +00:00
require . NoError ( t , err )
err = s . ds . NewActivity ( ctx , & u , fleet . ActivityTypeDeletedPack , & details )
require . NoError ( t , err )
err = s . ds . NewActivity ( ctx , & u , fleet . ActivityTypeEditedPack , & details )
require . NoError ( t , err )
2022-02-16 15:33:56 +00:00
lenPage := len ( prevActivities ) + 2
2021-11-29 13:12:22 +00:00
var listResp listActivitiesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & listResp , "per_page" , strconv . Itoa ( lenPage ) , "order_key" , "id" )
2022-02-16 15:33:56 +00:00
require . Len ( t , listResp . Activities , lenPage )
assert . Equal ( t , fleet . ActivityTypeAppliedSpecPack , listResp . Activities [ lenPage - 2 ] . Type )
assert . Equal ( t , fleet . ActivityTypeDeletedPack , listResp . Activities [ lenPage - 1 ] . Type )
2021-11-29 13:12:22 +00:00
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & listResp , "per_page" , strconv . Itoa ( lenPage ) , "order_key" , "id" , "page" , "1" )
2021-11-29 13:12:22 +00:00
require . Len ( t , listResp . Activities , 1 )
assert . Equal ( t , fleet . ActivityTypeEditedPack , listResp . Activities [ 0 ] . Type )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & listResp , "per_page" , "1" , "order_key" , "id" , "order_direction" , "desc" )
2021-11-29 13:12:22 +00:00
require . Len ( t , listResp . Activities , 1 )
assert . Equal ( t , fleet . ActivityTypeEditedPack , listResp . Activities [ 0 ] . Type )
}
2021-12-01 20:45:29 +00:00
func ( s * integrationTestSuite ) TestListGetCarves ( ) {
t := s . T ( )
ctx := context . Background ( )
hosts := s . createHosts ( t )
c1 , err := s . ds . NewCarve ( ctx , & fleet . CarveMetadata {
CreatedAt : time . Now ( ) ,
HostId : hosts [ 0 ] . ID ,
Name : t . Name ( ) + "_1" ,
SessionId : "ssn1" ,
} )
require . NoError ( t , err )
c2 , err := s . ds . NewCarve ( ctx , & fleet . CarveMetadata {
CreatedAt : time . Now ( ) ,
HostId : hosts [ 1 ] . ID ,
Name : t . Name ( ) + "_2" ,
SessionId : "ssn2" ,
} )
require . NoError ( t , err )
c3 , err := s . ds . NewCarve ( ctx , & fleet . CarveMetadata {
CreatedAt : time . Now ( ) ,
HostId : hosts [ 2 ] . ID ,
Name : t . Name ( ) + "_3" ,
SessionId : "ssn3" ,
} )
require . NoError ( t , err )
// set c1 max block
c1 . MaxBlock = 3
require . NoError ( t , s . ds . UpdateCarve ( ctx , c1 ) )
// make c2 expired, set max block
c2 . Expired = true
c2 . MaxBlock = 3
require . NoError ( t , s . ds . UpdateCarve ( ctx , c2 ) )
var listResp listCarvesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/carves" , nil , http . StatusOK , & listResp , "per_page" , "2" , "order_key" , "id" )
2021-12-01 20:45:29 +00:00
require . Len ( t , listResp . Carves , 2 )
assert . Equal ( t , c1 . ID , listResp . Carves [ 0 ] . ID )
assert . Equal ( t , c3 . ID , listResp . Carves [ 1 ] . ID )
// include expired
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/carves" , nil , http . StatusOK , & listResp , "per_page" , "2" , "order_key" , "id" , "expired" , "1" )
2021-12-01 20:45:29 +00:00
require . Len ( t , listResp . Carves , 2 )
assert . Equal ( t , c1 . ID , listResp . Carves [ 0 ] . ID )
assert . Equal ( t , c2 . ID , listResp . Carves [ 1 ] . ID )
// empty page
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/carves" , nil , http . StatusOK , & listResp , "page" , "3" , "per_page" , "2" , "order_key" , "id" , "expired" , "1" )
2021-12-01 20:45:29 +00:00
require . Len ( t , listResp . Carves , 0 )
// get specific carve
var getResp getCarveResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/carves/%d" , c2 . ID ) , nil , http . StatusOK , & getResp )
2021-12-01 20:45:29 +00:00
require . Equal ( t , c2 . ID , getResp . Carve . ID )
require . True ( t , getResp . Carve . Expired )
// get non-existing carve
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/carves/%d" , c3 . ID + 1 ) , nil , http . StatusNotFound , & getResp )
2021-12-01 20:45:29 +00:00
// get expired carve block
var blkResp getCarveBlockResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/carves/%d/block/%d" , c2 . ID , 1 ) , nil , http . StatusInternalServerError , & blkResp )
2021-12-01 20:45:29 +00:00
// get valid carve block, but block not inserted yet
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/carves/%d/block/%d" , c1 . ID , 1 ) , nil , http . StatusNotFound , & blkResp )
2021-12-01 20:45:29 +00:00
require . NoError ( t , s . ds . NewBlock ( ctx , c1 , 1 , [ ] byte ( "block1" ) ) )
require . NoError ( t , s . ds . NewBlock ( ctx , c1 , 2 , [ ] byte ( "block2" ) ) )
require . NoError ( t , s . ds . NewBlock ( ctx , c1 , 3 , [ ] byte ( "block3" ) ) )
// get valid carve block
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/carves/%d/block/%d" , c1 . ID , 1 ) , nil , http . StatusOK , & blkResp )
2021-12-01 20:45:29 +00:00
require . Equal ( t , "block1" , string ( blkResp . Data ) )
}
2021-12-14 21:34:11 +00:00
func ( s * integrationTestSuite ) TestHostsAddToTeam ( ) {
t := s . T ( )
ctx := context . Background ( )
tm1 , err := s . ds . NewTeam ( ctx , & fleet . Team {
Name : uuid . New ( ) . String ( ) ,
} )
require . NoError ( t , err )
tm2 , err := s . ds . NewTeam ( ctx , & fleet . Team {
Name : uuid . New ( ) . String ( ) ,
} )
require . NoError ( t , err )
hosts := s . createHosts ( t )
var refetchResp refetchHostResponse
// refetch existing
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/refetch" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & refetchResp )
2021-12-14 21:34:11 +00:00
require . NoError ( t , refetchResp . Err )
// refetch unknown
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/refetch" , hosts [ 2 ] . ID + 1 ) , nil , http . StatusNotFound , & refetchResp )
2021-12-14 21:34:11 +00:00
// get by identifier unknown
var getResp getHostResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/identifier/no-such-host" , nil , http . StatusNotFound , & getResp )
2021-12-14 21:34:11 +00:00
// get by identifier valid
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/identifier/%s" , hosts [ 0 ] . UUID ) , nil , http . StatusOK , & getResp )
2021-12-14 21:34:11 +00:00
require . Equal ( t , hosts [ 0 ] . ID , getResp . Host . ID )
require . Nil ( t , getResp . Host . TeamID )
// assign hosts to team 1
var addResp addHostsToTeamResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/transfer" , addHostsToTeamRequest {
2021-12-14 21:34:11 +00:00
TeamID : & tm1 . ID ,
HostIDs : [ ] uint { hosts [ 0 ] . ID , hosts [ 1 ] . ID } ,
} , http . StatusOK , & addResp )
// check that hosts are now part of that team
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & getResp )
2021-12-14 21:34:11 +00:00
require . NotNil ( t , getResp . Host . TeamID )
require . Equal ( t , tm1 . ID , * getResp . Host . TeamID )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 1 ] . ID ) , nil , http . StatusOK , & getResp )
2021-12-14 21:34:11 +00:00
require . NotNil ( t , getResp . Host . TeamID )
require . Equal ( t , tm1 . ID , * getResp . Host . TeamID )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 2 ] . ID ) , nil , http . StatusOK , & getResp )
2021-12-14 21:34:11 +00:00
require . Nil ( t , getResp . Host . TeamID )
// assign host to team 2 with filter
var addfResp addHostsToTeamByFilterResponse
req := addHostsToTeamByFilterRequest { TeamID : & tm2 . ID }
req . Filters . MatchQuery = hosts [ 2 ] . Hostname
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/transfer/filter" , req , http . StatusOK , & addfResp )
2021-12-14 21:34:11 +00:00
// check that host 2 is now part of team 2
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 2 ] . ID ) , nil , http . StatusOK , & getResp )
2021-12-14 21:34:11 +00:00
require . NotNil ( t , getResp . Host . TeamID )
require . Equal ( t , tm2 . ID , * getResp . Host . TeamID )
// delete host 0
var delResp deleteHostResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & delResp )
2021-12-14 21:34:11 +00:00
// delete non-existing host
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 2 ] . ID + 1 ) , nil , http . StatusNotFound , & delResp )
2021-12-14 21:34:11 +00:00
// list the hosts
var listResp listHostsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & listResp , "per_page" , "3" )
2021-12-14 21:34:11 +00:00
require . Len ( t , listResp . Hosts , 2 )
ids := [ ] uint { listResp . Hosts [ 0 ] . ID , listResp . Hosts [ 1 ] . ID }
require . ElementsMatch ( t , ids , [ ] uint { hosts [ 1 ] . ID , hosts [ 2 ] . ID } )
}
2021-12-15 15:23:08 +00:00
func ( s * integrationTestSuite ) TestScheduledQueries ( ) {
t := s . T ( )
// create a pack
var createPackResp createPackResponse
reqPack := & createPackRequest {
PackPayload : fleet . PackPayload {
Name : ptr . String ( strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) ) ,
} ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs" , reqPack , http . StatusOK , & createPackResp )
2021-12-15 15:23:08 +00:00
pack := createPackResp . Pack . Pack
2022-01-12 13:04:16 +00:00
// try a non existent query
2022-04-05 15:35:53 +00:00
s . Do ( "GET" , fmt . Sprintf ( "/api/latest/fleet/queries/%d" , 9999 ) , nil , http . StatusNotFound )
2022-01-12 13:04:16 +00:00
2022-01-31 21:35:22 +00:00
// list queries
var listQryResp listQueriesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/queries" , nil , http . StatusOK , & listQryResp )
2022-01-31 21:35:22 +00:00
assert . Len ( t , listQryResp . Queries , 0 )
2021-12-15 15:23:08 +00:00
// create a query
var createQueryResp createQueryResponse
reqQuery := & fleet . QueryPayload {
2022-01-31 21:35:22 +00:00
Name : ptr . String ( strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) ) ,
2021-12-15 15:23:08 +00:00
Query : ptr . String ( "select * from time;" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , reqQuery , http . StatusOK , & createQueryResp )
2021-12-15 15:23:08 +00:00
query := createQueryResp . Query
2022-01-31 21:35:22 +00:00
// listing returns that query
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/queries" , nil , http . StatusOK , & listQryResp )
2022-01-31 21:35:22 +00:00
require . Len ( t , listQryResp . Queries , 1 )
assert . Equal ( t , query . Name , listQryResp . Queries [ 0 ] . Name )
// next page returns nothing
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/queries" , nil , http . StatusOK , & listQryResp , "per_page" , "2" , "page" , "1" )
2022-01-31 21:35:22 +00:00
require . Len ( t , listQryResp . Queries , 0 )
// getting that query works
var getQryResp getQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/queries/%d" , query . ID ) , nil , http . StatusOK , & getQryResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , query . ID , getQryResp . Query . ID )
2021-12-15 15:23:08 +00:00
// list scheduled queries in pack, none yet
var getInPackResp getScheduledQueriesInPackResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/packs/%d/scheduled" , pack . ID ) , nil , http . StatusOK , & getInPackResp )
2021-12-15 15:23:08 +00:00
assert . Len ( t , getInPackResp . Scheduled , 0 )
// list scheduled queries in non-existing pack
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/packs/%d/scheduled" , pack . ID + 1 ) , nil , http . StatusOK , & getInPackResp )
2021-12-15 15:23:08 +00:00
assert . Len ( t , getInPackResp . Scheduled , 0 )
// create scheduled query
var createResp scheduleQueryResponse
reqSQ := & scheduleQueryRequest {
PackID : pack . ID ,
QueryID : query . ID ,
Interval : 1 ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs/schedule" , reqSQ , http . StatusOK , & createResp )
2021-12-15 15:23:08 +00:00
sq1 := createResp . Scheduled . ScheduledQuery
assert . NotZero ( t , sq1 . ID )
assert . Equal ( t , uint ( 1 ) , sq1 . Interval )
// create scheduled query with invalid pack
reqSQ = & scheduleQueryRequest {
PackID : pack . ID + 1 ,
QueryID : query . ID ,
Interval : 2 ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs/schedule" , reqSQ , http . StatusUnprocessableEntity , & createResp )
2021-12-15 15:23:08 +00:00
// create scheduled query with invalid query
reqSQ = & scheduleQueryRequest {
PackID : pack . ID ,
QueryID : query . ID + 1 ,
Interval : 3 ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs/schedule" , reqSQ , http . StatusNotFound , & createResp )
2021-12-15 15:23:08 +00:00
// list scheduled queries in pack
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/packs/%d/scheduled" , pack . ID ) , nil , http . StatusOK , & getInPackResp )
2021-12-15 15:23:08 +00:00
require . Len ( t , getInPackResp . Scheduled , 1 )
assert . Equal ( t , sq1 . ID , getInPackResp . Scheduled [ 0 ] . ID )
// list scheduled queries in pack, next page
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/packs/%d/scheduled" , pack . ID ) , nil , http . StatusOK , & getInPackResp , "page" , "1" , "per_page" , "2" )
2021-12-15 15:23:08 +00:00
require . Len ( t , getInPackResp . Scheduled , 0 )
// get non-existing scheduled query
var getResp getScheduledQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , sq1 . ID + 1 ) , nil , http . StatusNotFound , & getResp )
2021-12-15 15:23:08 +00:00
// get existing scheduled query
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , sq1 . ID ) , nil , http . StatusOK , & getResp )
2021-12-15 15:23:08 +00:00
assert . Equal ( t , sq1 . ID , getResp . Scheduled . ID )
assert . Equal ( t , sq1 . Interval , getResp . Scheduled . Interval )
// modify scheduled query
var modResp modifyScheduledQueryResponse
reqMod := fleet . ScheduledQueryPayload {
Interval : ptr . Uint ( 4 ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/packs/schedule/%d" , sq1 . ID ) , reqMod , http . StatusOK , & modResp )
2021-12-15 15:23:08 +00:00
assert . Equal ( t , sq1 . ID , modResp . Scheduled . ID )
assert . Equal ( t , uint ( 4 ) , modResp . Scheduled . Interval )
// modify non-existing scheduled query
reqMod = fleet . ScheduledQueryPayload {
Interval : ptr . Uint ( 5 ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/packs/schedule/%d" , sq1 . ID + 1 ) , reqMod , http . StatusNotFound , & modResp )
2021-12-15 15:23:08 +00:00
// delete non-existing scheduled query
var delResp deleteScheduledQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/schedule/%d" , sq1 . ID + 1 ) , nil , http . StatusNotFound , & delResp )
2021-12-15 15:23:08 +00:00
// delete existing scheduled query
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/packs/schedule/%d" , sq1 . ID ) , nil , http . StatusOK , & delResp )
2021-12-15 15:23:08 +00:00
// get the now-deleted scheduled query
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/schedule/%d" , sq1 . ID ) , nil , http . StatusNotFound , & getResp )
2022-01-31 21:35:22 +00:00
// modify the query
var modQryResp modifyQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/queries/%d" , query . ID ) , fleet . QueryPayload { Description : ptr . String ( "updated" ) } , http . StatusOK , & modQryResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , "updated" , modQryResp . Query . Description )
// modify a non-existing query
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/queries/%d" , query . ID + 1 ) , fleet . QueryPayload { Description : ptr . String ( "updated" ) } , http . StatusNotFound , & modQryResp )
2022-01-31 21:35:22 +00:00
// delete the query by name
var delByNameResp deleteQueryResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/queries/%s" , query . Name ) , nil , http . StatusOK , & delByNameResp )
2022-01-31 21:35:22 +00:00
// delete unknown query by name (i.e. the same, now deleted)
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/queries/%s" , query . Name ) , nil , http . StatusNotFound , & delByNameResp )
2022-01-31 21:35:22 +00:00
// create another query
reqQuery = & fleet . QueryPayload {
Name : ptr . String ( strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) + "_2" ) ,
Query : ptr . String ( "select 2" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , reqQuery , http . StatusOK , & createQueryResp )
2022-01-31 21:35:22 +00:00
query2 := createQueryResp . Query
// delete it by id
var delByIDResp deleteQueryByIDResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/queries/id/%d" , query2 . ID ) , nil , http . StatusOK , & delByIDResp )
2022-01-31 21:35:22 +00:00
// delete unknown query by id (same id just deleted)
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/queries/id/%d" , query2 . ID ) , nil , http . StatusNotFound , & delByIDResp )
2022-01-31 21:35:22 +00:00
// create another query
reqQuery = & fleet . QueryPayload {
Name : ptr . String ( strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) + "_3" ) ,
Query : ptr . String ( "select 3" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , reqQuery , http . StatusOK , & createQueryResp )
2022-01-31 21:35:22 +00:00
query3 := createQueryResp . Query
// batch-delete by id, 3 ids, only one exists
var delBatchResp deleteQueriesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries/delete" , map [ string ] interface { } {
2022-02-14 18:13:44 +00:00
"ids" : [ ] uint { query . ID , query2 . ID , query3 . ID } ,
} , http . StatusOK , & delBatchResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , uint ( 1 ) , delBatchResp . Deleted )
// batch-delete by id, none exist
delBatchResp . Deleted = 0
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries/delete" , map [ string ] interface { } {
2022-02-14 18:13:44 +00:00
"ids" : [ ] uint { query . ID , query2 . ID , query3 . ID } ,
} , http . StatusNotFound , & delBatchResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , uint ( 0 ) , delBatchResp . Deleted )
2021-12-15 15:23:08 +00:00
}
2021-12-21 12:37:58 +00:00
2021-12-21 20:36:19 +00:00
func ( s * integrationTestSuite ) TestHostDeviceMapping ( ) {
t := s . T ( )
ctx := context . Background ( )
hosts := s . createHosts ( t )
// get host device mappings of invalid host
var listResp listHostDeviceMappingResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/device_mapping" , hosts [ 2 ] . ID + 1 ) , nil , http . StatusNotFound , & listResp )
2021-12-21 20:36:19 +00:00
// existing host but none yet
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/device_mapping" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & listResp )
2021-12-21 20:36:19 +00:00
require . Len ( t , listResp . DeviceMapping , 0 )
// create some mappings
s . ds . ReplaceHostDeviceMapping ( ctx , hosts [ 0 ] . ID , [ ] * fleet . HostDeviceMapping {
{ HostID : hosts [ 0 ] . ID , Email : "a@b.c" , Source : "google_chrome_profiles" } ,
{ HostID : hosts [ 0 ] . ID , Email : "b@b.c" , Source : "google_chrome_profiles" } ,
} )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/device_mapping" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & listResp )
2021-12-21 20:36:19 +00:00
require . Len ( t , listResp . DeviceMapping , 2 )
require . Equal ( t , "a@b.c" , listResp . DeviceMapping [ 0 ] . Email )
require . Equal ( t , "google_chrome_profiles" , listResp . DeviceMapping [ 0 ] . Source )
require . Zero ( t , listResp . DeviceMapping [ 0 ] . HostID )
require . Equal ( t , "b@b.c" , listResp . DeviceMapping [ 1 ] . Email )
require . Equal ( t , "google_chrome_profiles" , listResp . DeviceMapping [ 1 ] . Source )
require . Zero ( t , listResp . DeviceMapping [ 1 ] . HostID )
require . Equal ( t , hosts [ 0 ] . ID , listResp . HostID )
// other host still has none
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/device_mapping" , hosts [ 1 ] . ID ) , nil , http . StatusOK , & listResp )
2021-12-21 20:36:19 +00:00
require . Len ( t , listResp . DeviceMapping , 0 )
var listHosts listHostsResponse
2022-05-02 21:34:14 +00:00
// list hosts response includes device mappings
s . DoJSON ( "GET" , "/api/latest/fleet/hosts?device_mapping=true" , nil , http . StatusOK , & listHosts )
require . Len ( t , listHosts . Hosts , 3 )
hostsByID := make ( map [ uint ] HostResponse )
for _ , h := range listHosts . Hosts {
hostsByID [ h . ID ] = h
}
var dm [ ] * fleet . HostDeviceMapping
// device mapping for host 1
host1 := hosts [ 0 ]
require . NotNil ( t , * hostsByID [ host1 . ID ] . DeviceMapping )
err := json . Unmarshal ( * hostsByID [ host1 . ID ] . DeviceMapping , & dm )
require . NoError ( t , err )
assert . Len ( t , dm , 2 )
var emails [ ] string
for _ , e := range dm {
emails = append ( emails , e . Email )
}
assert . Contains ( t , emails , "a@b.c" )
assert . Contains ( t , emails , "b@b.c" )
assert . Equal ( t , "google_chrome_profiles" , dm [ 0 ] . Source )
assert . Equal ( t , "google_chrome_profiles" , dm [ 1 ] . Source )
// no device mapping for other hosts
assert . Nil ( t , hostsByID [ hosts [ 1 ] . ID ] . DeviceMapping )
assert . Nil ( t , hostsByID [ hosts [ 2 ] . ID ] . DeviceMapping )
// search host by email address finds the corresponding host
s . DoJSON ( "GET" , "/api/latest/fleet/hosts?device_mapping=true" , nil , http . StatusOK , & listHosts , "query" , "a@b.c" )
2021-12-21 20:36:19 +00:00
require . Len ( t , listHosts . Hosts , 1 )
2022-05-02 21:34:14 +00:00
require . Equal ( t , host1 . ID , listHosts . Hosts [ 0 ] . ID )
require . NotNil ( t , listHosts . Hosts [ 0 ] . DeviceMapping )
err = json . Unmarshal ( * listHosts . Hosts [ 0 ] . DeviceMapping , & dm )
require . NoError ( t , err )
assert . Len ( t , dm , 2 )
for _ , e := range dm {
emails = append ( emails , e . Email )
}
assert . Contains ( t , emails , "a@b.c" )
assert . Contains ( t , emails , "b@b.c" )
assert . Equal ( t , "google_chrome_profiles" , dm [ 0 ] . Source )
assert . Equal ( t , "google_chrome_profiles" , dm [ 1 ] . Source )
2021-12-21 20:36:19 +00:00
2022-05-02 21:34:14 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts?device_mapping=true" , nil , http . StatusOK , & listHosts , "query" , "c@b.c" )
2021-12-21 20:36:19 +00:00
require . Len ( t , listHosts . Hosts , 0 )
}
2022-05-23 19:27:30 +00:00
func ( s * integrationTestSuite ) TestListHostsDeviceMappingSize ( ) {
t := s . T ( )
ctx := context . Background ( )
hosts := s . createHosts ( t )
testSize := 50
var mappings [ ] * fleet . HostDeviceMapping
for i := 0 ; i < testSize ; i ++ {
testEmail , _ := server . GenerateRandomText ( 14 )
mappings = append ( mappings , & fleet . HostDeviceMapping { HostID : hosts [ 0 ] . ID , Email : testEmail , Source : "google_chrome_profiles" } )
}
s . ds . ReplaceHostDeviceMapping ( ctx , hosts [ 0 ] . ID , mappings )
var listHosts listHostsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/hosts?device_mapping=true" , nil , http . StatusOK , & listHosts )
hostsByID := make ( map [ uint ] HostResponse )
for _ , h := range listHosts . Hosts {
hostsByID [ h . ID ] = h
}
require . NotNil ( t , * hostsByID [ hosts [ 0 ] . ID ] . DeviceMapping )
var dm [ ] * fleet . HostDeviceMapping
err := json . Unmarshal ( * hostsByID [ hosts [ 0 ] . ID ] . DeviceMapping , & dm )
require . NoError ( t , err )
require . Len ( t , dm , testSize )
}
2021-12-21 20:36:19 +00:00
2021-12-21 12:37:58 +00:00
func ( s * integrationTestSuite ) TestGetMacadminsData ( ) {
t := s . T ( )
ctx := context . Background ( )
2021-12-23 19:57:43 +00:00
hostAll , err := s . ds . NewHost ( ctx , & fleet . Host {
2021-12-21 12:37:58 +00:00
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "1" ,
UUID : t . Name ( ) + "1" ,
Hostname : t . Name ( ) + "foo.local" ,
PrimaryIP : "192.168.1.1" ,
PrimaryMac : "30-65-EC-6F-C4-58" ,
2021-12-23 19:57:43 +00:00
OsqueryHostID : "1" ,
2021-12-21 12:37:58 +00:00
} )
require . NoError ( t , err )
2021-12-23 19:57:43 +00:00
require . NotNil ( t , hostAll )
hostNothing , err := s . ds . NewHost ( ctx , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "2" ,
UUID : t . Name ( ) + "2" ,
Hostname : t . Name ( ) + "foo.local2" ,
PrimaryIP : "192.168.1.2" ,
PrimaryMac : "30-65-EC-6F-C4-59" ,
OsqueryHostID : "2" ,
} )
require . NoError ( t , err )
require . NotNil ( t , hostNothing )
hostOnlyMunki , err := s . ds . NewHost ( ctx , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "3" ,
UUID : t . Name ( ) + "3" ,
Hostname : t . Name ( ) + "foo.local3" ,
PrimaryIP : "192.168.1.3" ,
PrimaryMac : "30-65-EC-6F-C4-5F" ,
OsqueryHostID : "3" ,
} )
require . NoError ( t , err )
require . NotNil ( t , hostOnlyMunki )
hostOnlyMDM , err := s . ds . NewHost ( ctx , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "4" ,
UUID : t . Name ( ) + "4" ,
Hostname : t . Name ( ) + "foo.local4" ,
PrimaryIP : "192.168.1.4" ,
PrimaryMac : "30-65-EC-6F-C4-5A" ,
OsqueryHostID : "4" ,
} )
require . NoError ( t , err )
require . NotNil ( t , hostOnlyMDM )
2021-12-21 12:37:58 +00:00
2021-12-23 19:57:43 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , true , "url" , false ) )
require . NoError ( t , s . ds . SetOrUpdateMunkiVersion ( ctx , hostAll . ID , "1.3.0" ) )
2021-12-21 12:37:58 +00:00
macadminsData := getMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostAll . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-21 12:37:58 +00:00
require . NotNil ( t , macadminsData . Macadmins )
assert . Equal ( t , "url" , macadminsData . Macadmins . MDM . ServerURL )
assert . Equal ( t , "Enrolled (manual)" , macadminsData . Macadmins . MDM . EnrollmentStatus )
assert . Equal ( t , "1.3.0" , macadminsData . Macadmins . Munki . Version )
2021-12-23 19:57:43 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , true , "url2" , true ) )
require . NoError ( t , s . ds . SetOrUpdateMunkiVersion ( ctx , hostAll . ID , "1.5.0" ) )
2021-12-21 12:37:58 +00:00
macadminsData = getMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostAll . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-21 12:37:58 +00:00
require . NotNil ( t , macadminsData . Macadmins )
assert . Equal ( t , "url2" , macadminsData . Macadmins . MDM . ServerURL )
assert . Equal ( t , "Enrolled (automated)" , macadminsData . Macadmins . MDM . EnrollmentStatus )
assert . Equal ( t , "1.5.0" , macadminsData . Macadmins . Munki . Version )
2021-12-23 19:57:43 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , false , "url2" , false ) )
2021-12-21 12:37:58 +00:00
macadminsData = getMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostAll . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-21 12:37:58 +00:00
require . NotNil ( t , macadminsData . Macadmins )
assert . Equal ( t , "Unenrolled" , macadminsData . Macadmins . MDM . EnrollmentStatus )
2021-12-23 19:57:43 +00:00
// nothing returns null
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostNothing . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-23 19:57:43 +00:00
require . Nil ( t , macadminsData . Macadmins )
// only munki info returns null on mdm
require . NoError ( t , s . ds . SetOrUpdateMunkiVersion ( ctx , hostOnlyMunki . ID , "3.2.0" ) )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostOnlyMunki . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-23 19:57:43 +00:00
require . NotNil ( t , macadminsData . Macadmins )
require . Nil ( t , macadminsData . Macadmins . MDM )
require . NotNil ( t , macadminsData . Macadmins . Munki )
assert . Equal ( t , "3.2.0" , macadminsData . Macadmins . Munki . Version )
// only mdm returns null on munki info
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostOnlyMDM . ID , true , "AAA" , true ) )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostOnlyMDM . ID ) , nil , http . StatusOK , & macadminsData )
2021-12-23 19:57:43 +00:00
require . NotNil ( t , macadminsData . Macadmins )
require . NotNil ( t , macadminsData . Macadmins . MDM )
require . Nil ( t , macadminsData . Macadmins . Munki )
assert . Equal ( t , "AAA" , macadminsData . Macadmins . MDM . ServerURL )
assert . Equal ( t , "Enrolled (automated)" , macadminsData . Macadmins . MDM . EnrollmentStatus )
2022-01-26 20:55:07 +00:00
// generate aggregated data
require . NoError ( t , s . ds . GenerateAggregatedMunkiAndMDM ( context . Background ( ) ) )
agg := getAggregatedMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/macadmins" , nil , http . StatusOK , & agg )
2022-01-26 20:55:07 +00:00
require . NotNil ( t , agg . Macadmins )
2022-02-07 17:53:33 +00:00
assert . NotZero ( t , agg . Macadmins . CountsUpdatedAt )
2022-01-26 20:55:07 +00:00
assert . Len ( t , agg . Macadmins . MunkiVersions , 2 )
assert . ElementsMatch ( t , agg . Macadmins . MunkiVersions , [ ] fleet . AggregatedMunkiVersion {
{
HostMunkiInfo : fleet . HostMunkiInfo { Version : "1.5.0" } ,
HostsCount : 1 ,
} ,
{
HostMunkiInfo : fleet . HostMunkiInfo { Version : "3.2.0" } ,
HostsCount : 1 ,
} ,
} )
assert . Equal ( t , agg . Macadmins . MDMStatus . EnrolledManualHostsCount , 0 )
assert . Equal ( t , agg . Macadmins . MDMStatus . EnrolledAutomatedHostsCount , 1 )
assert . Equal ( t , agg . Macadmins . MDMStatus . UnenrolledHostsCount , 1 )
assert . Equal ( t , agg . Macadmins . MDMStatus . HostsCount , 2 )
team , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
Name : "team1" + t . Name ( ) ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
agg = getAggregatedMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/macadmins" , nil , http . StatusOK , & agg , "team_id" , fmt . Sprint ( team . ID ) )
2022-01-26 20:55:07 +00:00
require . NotNil ( t , agg . Macadmins )
require . Empty ( t , agg . Macadmins . MunkiVersions )
require . Empty ( t , agg . Macadmins . MDMStatus )
agg = getAggregatedMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/macadmins" , nil , http . StatusNotFound , & agg , "team_id" , "9999999" )
2021-12-21 12:37:58 +00:00
}
2021-12-21 14:53:15 +00:00
func ( s * integrationTestSuite ) TestLabels ( ) {
t := s . T ( )
// list labels, has the built-in ones
var listResp listLabelsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/labels" , nil , http . StatusOK , & listResp )
2021-12-21 14:53:15 +00:00
assert . True ( t , len ( listResp . Labels ) > 0 )
for _ , lbl := range listResp . Labels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
builtInsCount := len ( listResp . Labels )
// create a label without name, an error
var createResp createLabelResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/labels" , & fleet . LabelPayload { Query : ptr . String ( "select 1" ) } , http . StatusUnprocessableEntity , & createResp )
2021-12-21 14:53:15 +00:00
// create a valid label
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/labels" , & fleet . LabelPayload { Name : ptr . String ( t . Name ( ) ) , Query : ptr . String ( "select 1" ) } , http . StatusOK , & createResp )
2021-12-21 14:53:15 +00:00
assert . NotZero ( t , createResp . Label . ID )
assert . Equal ( t , t . Name ( ) , createResp . Label . Name )
lbl1 := createResp . Label . Label
// get the label
var getResp getLabelResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d" , lbl1 . ID ) , nil , http . StatusOK , & getResp )
2021-12-21 14:53:15 +00:00
assert . Equal ( t , lbl1 . ID , getResp . Label . ID )
// get a non-existing label
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d" , lbl1 . ID + 1 ) , nil , http . StatusNotFound , & getResp )
2021-12-21 14:53:15 +00:00
// modify that label
var modResp modifyLabelResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/labels/%d" , lbl1 . ID ) , & fleet . ModifyLabelPayload { Name : ptr . String ( t . Name ( ) + "zzz" ) } , http . StatusOK , & modResp )
2021-12-21 14:53:15 +00:00
assert . Equal ( t , lbl1 . ID , modResp . Label . ID )
assert . NotEqual ( t , lbl1 . Name , modResp . Label . Name )
// modify a non-existing label
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/labels/%d" , lbl1 . ID + 1 ) , & fleet . ModifyLabelPayload { Name : ptr . String ( "zzz" ) } , http . StatusNotFound , & modResp )
2021-12-21 14:53:15 +00:00
// list labels
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/labels" , nil , http . StatusOK , & listResp , "per_page" , strconv . Itoa ( builtInsCount + 1 ) )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listResp . Labels , builtInsCount + 1 )
// next page is empty
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/labels" , nil , http . StatusOK , & listResp , "per_page" , "2" , "page" , "1" , "query" , t . Name ( ) )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listResp . Labels , 0 )
// create another label
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/labels" , & fleet . LabelPayload { Name : ptr . String ( strings . ReplaceAll ( t . Name ( ) , "/" , "_" ) ) , Query : ptr . String ( "select 1" ) } , http . StatusOK , & createResp )
2021-12-21 14:53:15 +00:00
assert . NotZero ( t , createResp . Label . ID )
lbl2 := createResp . Label . Label
// create hosts and add them to that label
hosts := s . createHosts ( t )
for _ , h := range hosts {
err := s . ds . RecordLabelQueryExecutions ( context . Background ( ) , h , map [ uint ] * bool { lbl2 . ID : ptr . Bool ( true ) } , time . Now ( ) , false )
require . NoError ( t , err )
}
// list hosts in label
var listHostsResp listHostsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d/hosts" , lbl2 . ID ) , nil , http . StatusOK , & listHostsResp )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listHostsResp . Hosts , len ( hosts ) )
// lists hosts in label without hosts
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d/hosts" , lbl1 . ID ) , nil , http . StatusOK , & listHostsResp )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listHostsResp . Hosts , 0 )
// lists hosts in invalid label
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d/hosts" , lbl2 . ID + 1 ) , nil , http . StatusOK , & listHostsResp )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listHostsResp . Hosts , 0 )
// delete a label by id
var delIDResp deleteLabelByIDResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/labels/id/%d" , lbl1 . ID ) , nil , http . StatusOK , & delIDResp )
2021-12-21 14:53:15 +00:00
// delete a non-existing label by id
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/labels/id/%d" , lbl2 . ID + 1 ) , nil , http . StatusNotFound , & delIDResp )
2021-12-21 14:53:15 +00:00
// delete a label by name
var delResp deleteLabelResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/labels/%s" , url . PathEscape ( lbl2 . Name ) ) , nil , http . StatusOK , & delResp )
2021-12-21 14:53:15 +00:00
// delete a non-existing label by name
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/labels/%s" , url . PathEscape ( lbl2 . Name ) ) , nil , http . StatusNotFound , & delResp )
2021-12-21 14:53:15 +00:00
// list labels, only the built-ins remain
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/labels" , nil , http . StatusOK , & listResp , "per_page" , strconv . Itoa ( builtInsCount + 1 ) )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listResp . Labels , builtInsCount )
for _ , lbl := range listResp . Labels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
2022-05-10 15:32:55 +00:00
// host summary matches built-ins count
var hostSummaryResp getHostSummaryResponse
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & hostSummaryResp )
assert . Len ( t , hostSummaryResp . BuiltinLabels , builtInsCount )
for _ , lbl := range hostSummaryResp . BuiltinLabels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
2021-12-21 14:53:15 +00:00
}
func ( s * integrationTestSuite ) TestLabelSpecs ( ) {
t := s . T ( )
// list label specs, only those of the built-ins
var listResp getLabelSpecsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/labels" , nil , http . StatusOK , & listResp )
2021-12-21 14:53:15 +00:00
assert . True ( t , len ( listResp . Specs ) > 0 )
for _ , spec := range listResp . Specs {
assert . Equal ( t , fleet . LabelTypeBuiltIn , spec . LabelType )
}
builtInsCount := len ( listResp . Specs )
name := strings . ReplaceAll ( t . Name ( ) , "/" , "_" )
// apply an invalid label spec - dynamic membership with host specified
var applyResp applyLabelSpecsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/labels" , applyLabelSpecsRequest {
2021-12-21 14:53:15 +00:00
Specs : [ ] * fleet . LabelSpec {
{
Name : name ,
Query : "select 1" ,
Platform : "linux" ,
LabelMembershipType : fleet . LabelMembershipTypeDynamic ,
Hosts : [ ] string { "abc" } ,
} ,
} ,
} , http . StatusInternalServerError , & applyResp )
// apply an invalid label spec - manual membership without a host specified
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/labels" , applyLabelSpecsRequest {
2021-12-21 14:53:15 +00:00
Specs : [ ] * fleet . LabelSpec {
{
Name : name ,
Query : "select 1" ,
Platform : "linux" ,
LabelMembershipType : fleet . LabelMembershipTypeManual ,
} ,
} ,
} , http . StatusInternalServerError , & applyResp )
// apply a valid label spec
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/labels" , applyLabelSpecsRequest {
2021-12-21 14:53:15 +00:00
Specs : [ ] * fleet . LabelSpec {
{
Name : name ,
Query : "select 1" ,
Platform : "linux" ,
LabelMembershipType : fleet . LabelMembershipTypeDynamic ,
} ,
} ,
} , http . StatusOK , & applyResp )
// list label specs, has the newly created one
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/labels" , nil , http . StatusOK , & listResp )
2021-12-21 14:53:15 +00:00
assert . Len ( t , listResp . Specs , builtInsCount + 1 )
// get a specific label spec
var getResp getLabelSpecResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/spec/labels/%s" , url . PathEscape ( name ) ) , nil , http . StatusOK , & getResp )
2021-12-21 14:53:15 +00:00
assert . Equal ( t , name , getResp . Spec . Name )
// get a non-existing label spec
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/labels/zzz" , nil , http . StatusNotFound , & getResp )
2021-12-21 14:53:15 +00:00
}
2021-12-23 21:26:55 +00:00
2022-01-10 19:43:39 +00:00
func ( s * integrationTestSuite ) TestUsers ( ) {
2022-03-08 16:27:38 +00:00
// ensure that on exit, the admin token is used
defer func ( ) { s . token = s . getTestAdminToken ( ) } ( )
2022-01-10 19:43:39 +00:00
t := s . T ( )
// list existing users
var listResp listUsersResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/users" , nil , http . StatusOK , & listResp )
2022-01-10 19:43:39 +00:00
assert . Len ( t , listResp . Users , len ( s . users ) )
2022-01-13 19:57:44 +00:00
// test available teams returned by `/me` endpoint for existing user
var getMeResp getUserResponse
2022-01-25 14:34:00 +00:00
ssn := createSession ( t , 1 , s . ds )
2022-04-05 15:35:53 +00:00
resp := s . DoRawWithHeaders ( "GET" , "/api/latest/fleet/me" , [ ] byte ( "" ) , http . StatusOK , map [ string ] string {
2022-01-25 14:34:00 +00:00
"Authorization" : fmt . Sprintf ( "Bearer %s" , ssn . Key ) ,
2022-01-13 19:57:44 +00:00
} )
2022-01-25 14:34:00 +00:00
err := json . NewDecoder ( resp . Body ) . Decode ( & getMeResp )
2022-01-13 19:57:44 +00:00
require . NoError ( t , err )
assert . Equal ( t , uint ( 1 ) , getMeResp . User . ID )
assert . NotNil ( t , getMeResp . User . GlobalRole )
assert . Len ( t , getMeResp . User . Teams , 0 )
assert . Len ( t , getMeResp . AvailableTeams , 0 )
2022-01-10 19:43:39 +00:00
// create a new user
var createResp createUserResponse
2022-05-18 17:03:00 +00:00
userRawPwd := test . GoodPassword
2022-01-10 19:43:39 +00:00
params := fleet . UserPayload {
Name : ptr . String ( "extra" ) ,
Email : ptr . String ( "extra@asd.com" ) ,
2022-03-08 16:27:38 +00:00
Password : ptr . String ( userRawPwd ) ,
2022-01-10 19:43:39 +00:00
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users/admin" , params , http . StatusOK , & createResp )
2022-01-10 19:43:39 +00:00
assert . NotZero ( t , createResp . User . ID )
2022-03-08 16:27:38 +00:00
assert . True ( t , createResp . User . AdminForcedPasswordReset )
2022-01-10 19:43:39 +00:00
u := * createResp . User
2022-01-13 19:57:44 +00:00
// login as that user and check that teams info is empty
var loginResp loginResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/login" , params , http . StatusOK , & loginResp )
2022-01-13 19:57:44 +00:00
require . Equal ( t , loginResp . User . ID , u . ID )
assert . Len ( t , loginResp . User . Teams , 0 )
assert . Len ( t , loginResp . AvailableTeams , 0 )
// get that user from `/users` endpoint and check that teams info is empty
2022-01-10 19:43:39 +00:00
var getResp getUserResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , nil , http . StatusOK , & getResp )
2022-01-10 19:43:39 +00:00
assert . Equal ( t , u . ID , getResp . User . ID )
2022-01-13 19:57:44 +00:00
assert . Len ( t , getResp . User . Teams , 0 )
assert . Len ( t , getResp . AvailableTeams , 0 )
2022-01-10 19:43:39 +00:00
// get non-existing user
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID + 1 ) , nil , http . StatusNotFound , & getResp )
2022-01-10 19:43:39 +00:00
// modify that user - simple name change
var modResp modifyUserResponse
params = fleet . UserPayload {
Name : ptr . String ( "extraz" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusOK , & modResp )
2022-01-10 19:43:39 +00:00
assert . Equal ( t , u . ID , modResp . User . ID )
assert . Equal ( t , u . Name + "z" , modResp . User . Name )
2022-02-28 12:34:44 +00:00
// modify that user - set an existing email
params = fleet . UserPayload {
Email : & getMeResp . User . Email ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusConflict , & modResp )
2022-02-28 12:34:44 +00:00
2022-02-28 16:17:10 +00:00
// modify that user - set an email that has an invite for it
createInviteReq := createInviteRequest { InvitePayload : fleet . InvitePayload {
Email : ptr . String ( "colliding@email.com" ) ,
Name : ptr . String ( "some name" ) ,
GlobalRole : null . StringFrom ( fleet . RoleAdmin ) ,
} }
createInviteResp := createInviteResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/invites" , createInviteReq , http . StatusOK , & createInviteResp )
2022-02-28 16:17:10 +00:00
params = fleet . UserPayload {
Email : ptr . String ( "colliding@email.com" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusConflict , & modResp )
2022-02-28 16:17:10 +00:00
2022-02-28 12:34:44 +00:00
// modify that user - set a non existent email
params = fleet . UserPayload {
Email : ptr . String ( "someemail@qowieuowh.com" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusOK , & modResp )
2022-02-28 12:34:44 +00:00
2022-01-10 19:43:39 +00:00
// modify user - email change, password does not match
params = fleet . UserPayload {
Email : ptr . String ( "extra2@asd.com" ) ,
Password : ptr . String ( "wrongpass" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusForbidden , & modResp )
2022-01-10 19:43:39 +00:00
// modify user - email change, password ok
params = fleet . UserPayload {
Email : ptr . String ( "extra2@asd.com" ) ,
2022-05-18 17:03:00 +00:00
Password : ptr . String ( test . GoodPassword ) ,
2022-01-10 19:43:39 +00:00
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , params , http . StatusOK , & modResp )
2022-01-10 19:43:39 +00:00
assert . Equal ( t , u . ID , modResp . User . ID )
assert . NotEqual ( t , u . ID , modResp . User . Email )
// modify invalid user
params = fleet . UserPayload {
Name : ptr . String ( "nosuchuser" ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID + 1 ) , params , http . StatusNotFound , & modResp )
2022-01-10 19:43:39 +00:00
2022-03-08 16:27:38 +00:00
// perform a required password change as the user themselves
s . token = s . getTestToken ( u . Email , userRawPwd )
var perfPwdResetResp performRequiredPasswordResetResponse
2022-05-18 17:03:00 +00:00
newRawPwd := test . GoodPassword2
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/perform_required_password_reset" , performRequiredPasswordResetRequest {
2022-03-08 16:27:38 +00:00
Password : newRawPwd ,
ID : u . ID ,
} , http . StatusOK , & perfPwdResetResp )
assert . False ( t , perfPwdResetResp . User . AdminForcedPasswordReset )
oldUserRawPwd := userRawPwd
userRawPwd = newRawPwd
// perform a required password change again, this time it fails as there is no request pending
perfPwdResetResp = performRequiredPasswordResetResponse { }
newRawPwd = "new_password2!"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/perform_required_password_reset" , performRequiredPasswordResetRequest {
2022-03-08 16:27:38 +00:00
Password : newRawPwd ,
ID : u . ID ,
} , http . StatusInternalServerError , & perfPwdResetResp ) // TODO: should be 40?, see #4406
s . token = s . getTestAdminToken ( )
// login as that user to verify that the new password is active (userRawPwd was updated to the new pwd)
loginResp = loginResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/login" , loginRequest { Email : u . Email , Password : userRawPwd } , http . StatusOK , & loginResp )
2022-03-08 16:27:38 +00:00
require . Equal ( t , loginResp . User . ID , u . ID )
// logout for that user
s . token = loginResp . Token
var logoutResp logoutResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/logout" , nil , http . StatusOK , & logoutResp )
2022-03-08 16:27:38 +00:00
// logout again, even though not logged in
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/logout" , nil , http . StatusInternalServerError , & logoutResp ) // TODO: should be OK even if not logged in, see #4406.
2022-03-08 16:27:38 +00:00
s . token = s . getTestAdminToken ( )
// login as that user with previous pwd fails
loginResp = loginResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/login" , loginRequest { Email : u . Email , Password : oldUserRawPwd } , http . StatusUnauthorized , & loginResp )
2022-03-08 16:27:38 +00:00
2022-01-10 19:43:39 +00:00
// require a password reset
var reqResetResp requirePasswordResetResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/users/%d/require_password_reset" , u . ID ) , map [ string ] bool { "require" : true } , http . StatusOK , & reqResetResp )
2022-01-10 19:43:39 +00:00
assert . Equal ( t , u . ID , reqResetResp . User . ID )
assert . True ( t , reqResetResp . User . AdminForcedPasswordReset )
// require a password reset to invalid user
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , fmt . Sprintf ( "/api/latest/fleet/users/%d/require_password_reset" , u . ID + 1 ) , map [ string ] bool { "require" : true } , http . StatusNotFound , & reqResetResp )
2022-01-10 19:43:39 +00:00
// delete user
var delResp deleteUserResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , nil , http . StatusOK , & delResp )
2022-01-10 19:43:39 +00:00
// delete invalid user
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , nil , http . StatusNotFound , & delResp )
2022-01-10 19:43:39 +00:00
}
2021-12-23 21:26:55 +00:00
func ( s * integrationTestSuite ) TestGlobalPoliciesAutomationConfig ( ) {
t := s . T ( )
gpParams := globalPolicyRequest {
Name : "policy1" ,
Query : "select 41;" ,
}
gpResp := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams , http . StatusOK , & gpResp )
2022-01-18 16:18:40 +00:00
require . NotNil ( t , gpResp . Policy )
2021-12-23 21:26:55 +00:00
2022-04-05 15:35:53 +00:00
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2021-12-23 21:26:55 +00:00
"webhook_settings" : {
"failing_policies_webhook" : {
"enable_failing_policies_webhook" : true ,
"destination_url" : "http://some/url" ,
"policy_ids" : [ % d ] ,
"host_batch_size" : 1000
} ,
"interval" : "1h"
}
} ` , gpResp . Policy . ID ) ) , http . StatusOK )
config := s . getConfig ( )
require . True ( t , config . WebhookSettings . FailingPoliciesWebhook . Enable )
require . Equal ( t , "http://some/url" , config . WebhookSettings . FailingPoliciesWebhook . DestinationURL )
require . Equal ( t , [ ] uint { gpResp . Policy . ID } , config . WebhookSettings . FailingPoliciesWebhook . PolicyIDs )
require . Equal ( t , 1 * time . Hour , config . WebhookSettings . Interval . Duration )
require . Equal ( t , 1000 , config . WebhookSettings . FailingPoliciesWebhook . HostBatchSize )
deletePolicyParams := deleteGlobalPoliciesRequest { IDs : [ ] uint { gpResp . Policy . ID } }
deletePolicyResp := deleteGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies/delete" , deletePolicyParams , http . StatusOK , & deletePolicyResp )
2021-12-23 21:26:55 +00:00
config = s . getConfig ( )
require . Empty ( t , config . WebhookSettings . FailingPoliciesWebhook . PolicyIDs )
}
2022-01-18 16:18:40 +00:00
2022-01-27 13:48:46 +00:00
func ( s * integrationTestSuite ) TestVulnerabilitiesWebhookConfig ( ) {
t := s . T ( )
2022-04-05 15:35:53 +00:00
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : { "jira" : [ ] , "zendesk" : [ ] } ,
2022-01-27 13:48:46 +00:00
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
} ` ) , http . StatusOK )
config := s . getConfig ( )
require . True ( t , config . WebhookSettings . VulnerabilitiesWebhook . Enable )
require . Equal ( t , "http://some/url" , config . WebhookSettings . VulnerabilitiesWebhook . DestinationURL )
require . Equal ( t , 1234 , config . WebhookSettings . VulnerabilitiesWebhook . HostBatchSize )
require . Equal ( t , 1 * time . Hour , config . WebhookSettings . Interval . Duration )
}
2022-05-02 20:58:34 +00:00
func ( s * integrationTestSuite ) TestExternalIntegrationsConfig ( ) {
2022-03-30 13:10:02 +00:00
t := s . T ( )
2022-05-02 20:58:34 +00:00
// create a test http server to act as the Jira and Zendesk server
2022-06-06 14:41:51 +00:00
srvURL := startExternalServiceWebServer ( t )
2022-04-06 11:55:25 +00:00
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-03-30 13:10:02 +00:00
config := s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Jira [ 0 ] . URL )
2022-04-06 11:55:25 +00:00
require . Equal ( t , "ok" , config . Integrations . Jira [ 0 ] . Username )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Jira [ 0 ] . APIToken )
2022-03-30 13:10:02 +00:00
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . True ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
2022-05-02 20:58:34 +00:00
// add a second, disabled Jira integration
2022-04-06 11:55:25 +00:00
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux2" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 2 )
// first integration
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Jira [ 0 ] . URL )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "ok" , config . Integrations . Jira [ 0 ] . Username )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Jira [ 0 ] . APIToken )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . True ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
// second integration
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Jira [ 1 ] . URL )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "ok" , config . Integrations . Jira [ 1 ] . Username )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Jira [ 1 ] . APIToken )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "qux2" , config . Integrations . Jira [ 1 ] . ProjectKey )
require . False ( t , config . Integrations . Jira [ 1 ] . EnableSoftwareVulnerabilities )
// delete first Jira integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"project_key" : "qux2" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . Equal ( t , "qux2" , config . Integrations . Jira [ 0 ] . ProjectKey )
// replace Jira integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "ok" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . False ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
// try adding Jira integration without sending API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "ok" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "ok" ,
"project_key" : "qux2" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// try adding Jira integration with masked API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "ok" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "ok" ,
2022-06-06 14:41:51 +00:00
"api_token" : % q ,
2022-05-02 20:58:34 +00:00
"project_key" : "qux2" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL , fleet . MaskedPassword ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// edit Jira integration without sending API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . True ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
// edit Jira integration with masked API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
2022-06-06 14:41:51 +00:00
"api_token" : % q ,
2022-05-02 20:58:34 +00:00
"project_key" : "qux" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL , fleet . MaskedPassword ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . False ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
// edit Jira integration sending explicit "" as API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . Equal ( t , "qux" , config . Integrations . Jira [ 0 ] . ProjectKey )
require . True ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
// unknown fields fails as bad request
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [ {
"url" : % q ,
"UNKNOWN_FIELD" : "foo"
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// unknown project key fails as bad request
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
2022-06-06 14:41:51 +00:00
"api_token" : % q ,
2022-05-02 20:58:34 +00:00
"project_key" : "qux3" ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL , fleet . MaskedPassword ) ) , http . StatusBadRequest )
2022-03-30 13:10:02 +00:00
// cannot have two integrations enabled at the same time
2022-04-06 11:55:25 +00:00
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "ok" ,
"api_token" : "bar2" ,
"project_key" : "qux2" ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-05-02 20:58:34 +00:00
// cannot have two jira integrations with the same project key
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "ok" ,
"api_token" : "bar2" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-03-30 13:10:02 +00:00
2022-04-11 20:42:16 +00:00
// even disabled integrations are tested for Jira connection and credentials,
// so this fails because the 2nd one uses the "fail" username.
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [
{
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"username" : "fail" ,
"api_token" : "bar2" ,
2022-06-06 14:41:51 +00:00
"project_key" : "qux2" ,
2022-05-02 20:58:34 +00:00
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-04-11 20:42:16 +00:00
2022-03-30 13:10:02 +00:00
// cannot enable webhook with a jira integration already enabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( ` {
2022-05-02 20:58:34 +00:00
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
} ` ) , http . StatusUnprocessableEntity )
2022-03-30 13:10:02 +00:00
// disable jira, now we can enable webhook
2022-04-06 11:55:25 +00:00
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : false
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-03-30 13:10:02 +00:00
// cannot enable jira with webhook already enabled
2022-04-06 11:55:25 +00:00
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-04-06 11:55:25 +00:00
// disable webhook, enable jira with wrong credentials
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "fail" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : false ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-04-06 11:55:25 +00:00
// update jira config to correct credentials (need to disable webhook too as
// last request failed)
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : false ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
// remove all integrations
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( ` {
"integrations" : {
"jira" : [ ] ,
"zendesk" : [ ]
}
} ` ) , http . StatusOK )
// set environmental varible to use Zendesk test client
os . Setenv ( "TEST_ZENDESK_CLIENT" , "true" )
// create zendesk integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Zendesk [ 0 ] . URL )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "ok@example.com" , config . Integrations . Zendesk [ 0 ] . Email )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Zendesk [ 0 ] . APIToken )
2022-05-02 20:58:34 +00:00
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . True ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// add a second, disabled Zendesk integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "test123@example.com" ,
"api_token" : "ok" ,
"group_id" : 123 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 2 )
// first integration
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Zendesk [ 0 ] . URL )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "ok@example.com" , config . Integrations . Zendesk [ 0 ] . Email )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Zendesk [ 0 ] . APIToken )
2022-05-02 20:58:34 +00:00
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . True ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// second integration
2022-06-06 14:41:51 +00:00
require . Equal ( t , srvURL , config . Integrations . Zendesk [ 1 ] . URL )
2022-05-02 20:58:34 +00:00
require . Equal ( t , "test123@example.com" , config . Integrations . Zendesk [ 1 ] . Email )
2022-06-06 14:41:51 +00:00
require . Equal ( t , fleet . MaskedPassword , config . Integrations . Zendesk [ 1 ] . APIToken )
2022-05-02 20:58:34 +00:00
require . Equal ( t , int64 ( 123 ) , config . Integrations . Zendesk [ 1 ] . GroupID )
require . False ( t , config . Integrations . Zendesk [ 1 ] . EnableSoftwareVulnerabilities )
// delete first Zendesk integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "test123@example.com" ,
"group_id" : 123 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . Equal ( t , int64 ( 123 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
// replace Zendesk integration
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . False ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// try adding Zendesk integration without sending API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "test123@example.com" ,
"group_id" : 123 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// try adding Zendesk integration with masked API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "test123@example.com" ,
2022-06-06 14:41:51 +00:00
"api_token" : % q ,
2022-05-02 20:58:34 +00:00
"group_id" : 123 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL , fleet . MaskedPassword ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// edit Zendesk integration without sending API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . True ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// edit Zendesk integration with masked API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
2022-06-06 14:41:51 +00:00
"api_token" : % q ,
2022-05-02 20:58:34 +00:00
"group_id" : 122 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL , fleet . MaskedPassword ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . False ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// edit Zendesk integration with explicit "" API token
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . Equal ( t , int64 ( 122 ) , config . Integrations . Zendesk [ 0 ] . GroupID )
require . True ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// unknown fields fails as bad request
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"UNKNOWN_FIELD" : "foo"
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// unknown group id fails as bad request
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 999 ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// cannot have two zendesk integrations enabled at the same time
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "not.ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 123 ,
"enable_software_vulnerabilities" : true
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-05-02 20:58:34 +00:00
// cannot have two zendesk integrations with the same group id
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "not.ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-05-02 20:58:34 +00:00
// even disabled integrations are tested for Zendesk connection and credentials,
// so this fails because the 2nd one uses the "fail" token.
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [
{
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ,
{
"url" : % [ 1 ] q ,
"email" : "not.ok@example.com" ,
"api_token" : "fail" ,
"group_id" : 123 ,
"enable_software_vulnerabilities" : false
}
]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// cannot enable webhook with a zendesk integration already enabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
} ` ) , http . StatusUnprocessableEntity )
// disable zendesk, now we can enable webhook
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : false
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
// cannot enable zendesk with webhook already enabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-05-02 20:58:34 +00:00
// disable webhook, enable zendesk with wrong credentials
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "not.ok@example.com" ,
"api_token" : "fail" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : false ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusBadRequest )
2022-05-02 20:58:34 +00:00
// update zendesk config to correct credentials (need to disable webhook too as
// last request failed)
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"zendesk" : [ {
"url" : % q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
} ,
"webhook_settings" : {
"vulnerabilities_webhook" : {
"enable_vulnerabilities_webhook" : false ,
"destination_url" : "http://some/url" ,
"host_batch_size" : 1234
} ,
"interval" : "1h"
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
// can have jira enabled and zendesk disabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ] ,
"zendesk" : [ {
"url" : % [ 1 ] q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : false
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . True ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . False ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// can have jira disabled and zendesk enabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : false
} ] ,
"zendesk" : [ {
"url" : % [ 1 ] q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusOK )
2022-05-02 20:58:34 +00:00
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 1 )
require . False ( t , config . Integrations . Jira [ 0 ] . EnableSoftwareVulnerabilities )
require . Len ( t , config . Integrations . Zendesk , 1 )
require . True ( t , config . Integrations . Zendesk [ 0 ] . EnableSoftwareVulnerabilities )
// cannot have both jira enabled and zendesk enabled
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( fmt . Sprintf ( ` {
"integrations" : {
"jira" : [ {
"url" : % q ,
"username" : "ok" ,
"api_token" : "bar" ,
"project_key" : "qux" ,
"enable_software_vulnerabilities" : true
} ] ,
"zendesk" : [ {
"url" : % [ 1 ] q ,
"email" : "ok@example.com" ,
"api_token" : "ok" ,
"group_id" : 122 ,
"enable_software_vulnerabilities" : true
} ]
}
2022-06-06 14:41:51 +00:00
} ` , srvURL ) ) , http . StatusUnprocessableEntity )
2022-04-06 11:55:25 +00:00
// remove all integrations on exit, so that other tests can enable the
// webhook as needed
s . DoRaw ( "PATCH" , "/api/v1/fleet/config" , [ ] byte ( ` {
2022-05-02 20:58:34 +00:00
"integrations" : {
"jira" : [ ] ,
"zendesk" : [ ]
}
} ` ) , http . StatusOK )
config = s . getConfig ( )
require . Len ( t , config . Integrations . Jira , 0 )
require . Len ( t , config . Integrations . Zendesk , 0 )
2022-03-30 13:10:02 +00:00
}
2022-01-19 19:07:58 +00:00
func ( s * integrationTestSuite ) TestQueriesBadRequests ( ) {
t := s . T ( )
reqQuery := & fleet . QueryPayload {
Name : ptr . String ( "existing query" ) ,
Query : ptr . String ( "select 42;" ) ,
}
createQueryResp := createQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , reqQuery , http . StatusOK , & createQueryResp )
2022-01-19 19:07:58 +00:00
require . NotNil ( t , createQueryResp . Query )
existingQueryID := createQueryResp . Query . ID
2022-01-31 21:35:22 +00:00
defer cleanupQuery ( s , existingQueryID )
2022-01-19 19:07:58 +00:00
for _ , tc := range [ ] struct {
tname string
name string
query string
} {
{
tname : "empty name" ,
name : " " , // #3704
query : "select 42;" ,
} ,
{
tname : "empty query" ,
name : "Some name" ,
query : "" ,
} ,
{
tname : "Invalid query" ,
name : "Invalid query" ,
query : "ATTACH 'foo' AS bar;" ,
} ,
} {
t . Run ( tc . tname , func ( t * testing . T ) {
reqQuery := & fleet . QueryPayload {
Name : ptr . String ( tc . name ) ,
Query : ptr . String ( tc . query ) ,
}
createQueryResp := createQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries" , reqQuery , http . StatusBadRequest , & createQueryResp )
2022-01-19 19:07:58 +00:00
require . Nil ( t , createQueryResp . Query )
payload := fleet . QueryPayload {
Name : ptr . String ( tc . name ) ,
Query : ptr . String ( tc . query ) ,
}
mResp := modifyQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/queries/%d" , existingQueryID ) , & payload , http . StatusBadRequest , & mResp )
2022-01-19 19:07:58 +00:00
require . Nil ( t , mResp . Query )
} )
}
}
func ( s * integrationTestSuite ) TestPacksBadRequests ( ) {
t := s . T ( )
reqPacks := & fleet . PackPayload {
Name : ptr . String ( "existing pack" ) ,
}
createPackResp := createPackResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs" , reqPacks , http . StatusOK , & createPackResp )
2022-01-19 19:07:58 +00:00
existingPackID := createPackResp . Pack . ID
for _ , tc := range [ ] struct {
tname string
name string
} {
{
tname : "empty name" ,
name : " " , // #3704
} ,
} {
t . Run ( tc . tname , func ( t * testing . T ) {
reqQuery := & fleet . PackPayload {
Name : ptr . String ( tc . name ) ,
}
createPackResp := createQueryResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/packs" , reqQuery , http . StatusBadRequest , & createPackResp )
2022-01-19 19:07:58 +00:00
payload := fleet . PackPayload {
Name : ptr . String ( tc . name ) ,
}
mResp := modifyPackResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/packs/%d" , existingPackID ) , & payload , http . StatusBadRequest , & mResp )
2022-01-19 19:07:58 +00:00
} )
}
}
2022-01-19 15:52:14 +00:00
func ( s * integrationTestSuite ) TestTeamsEndpointsWithoutLicense ( ) {
t := s . T ( )
// list teams, none
var listResp listTeamsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/teams" , nil , http . StatusPaymentRequired , & listResp )
2022-01-19 15:52:14 +00:00
assert . Len ( t , listResp . Teams , 0 )
2022-02-04 17:33:22 +00:00
// get team
var getResp getTeamResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/teams/123" , nil , http . StatusPaymentRequired , & getResp )
2022-02-04 17:33:22 +00:00
assert . Nil ( t , getResp . Team )
2022-01-19 15:52:14 +00:00
// create team
var tmResp teamResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/teams" , & createTeamRequest { } , http . StatusPaymentRequired , & tmResp )
2022-01-19 15:52:14 +00:00
assert . Nil ( t , tmResp . Team )
// modify team
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , "/api/latest/fleet/teams/123" , fleet . TeamPayload { } , http . StatusPaymentRequired , & tmResp )
2022-01-19 15:52:14 +00:00
assert . Nil ( t , tmResp . Team )
// delete team
var delResp deleteTeamResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , "/api/latest/fleet/teams/123" , nil , http . StatusPaymentRequired , & delResp )
2022-01-19 15:52:14 +00:00
2022-02-03 19:24:03 +00:00
// apply team specs
2022-01-19 15:52:14 +00:00
var specResp applyTeamSpecsResponse
teamSpecs := applyTeamSpecsRequest { Specs : [ ] * fleet . TeamSpec { { Name : "newteam" , Secrets : [ ] fleet . EnrollSecret { { Secret : "ABC" } } } } }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/teams" , teamSpecs , http . StatusPaymentRequired , & specResp )
2022-01-19 15:52:14 +00:00
// modify team agent options
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/teams/123/agent_options" , nil , http . StatusPaymentRequired , & tmResp )
2022-01-19 15:52:14 +00:00
assert . Nil ( t , tmResp . Team )
// list team users
var usersResp listUsersResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/teams/123/users" , nil , http . StatusPaymentRequired , & usersResp , "page" , "1" )
2022-01-19 15:52:14 +00:00
assert . Len ( t , usersResp . Users , 0 )
// add team users
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , "/api/latest/fleet/teams/123/users" , modifyTeamUsersRequest { Users : [ ] fleet . TeamUser { { User : fleet . User { ID : 1 } } } } , http . StatusPaymentRequired , & tmResp )
2022-01-19 15:52:14 +00:00
assert . Nil ( t , tmResp . Team )
// delete team users
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , "/api/latest/fleet/teams/123/users" , modifyTeamUsersRequest { Users : [ ] fleet . TeamUser { { User : fleet . User { ID : 1 } } } } , http . StatusPaymentRequired , & tmResp )
2022-01-19 15:52:14 +00:00
assert . Nil ( t , tmResp . Team )
// get team enroll secrets
var secResp teamEnrollSecretsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/teams/123/secrets" , nil , http . StatusPaymentRequired , & secResp )
2022-01-19 15:52:14 +00:00
assert . Len ( t , secResp . Secrets , 0 )
// modify team enroll secrets
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , "/api/latest/fleet/teams/123/secrets" , modifyTeamEnrollSecretsRequest { Secrets : [ ] fleet . EnrollSecret { { Secret : "DEF" } } } , http . StatusPaymentRequired , & secResp )
2022-01-19 15:52:14 +00:00
assert . Len ( t , secResp . Secrets , 0 )
}
2022-01-18 16:18:40 +00:00
// TestGlobalPoliciesBrowsing tests that team users can browse (read) global policies (see #3722).
func ( s * integrationTestSuite ) TestGlobalPoliciesBrowsing ( ) {
t := s . T ( )
team , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
ID : 42 ,
Name : "team_for_global_policies_browsing" ,
Description : "desc team1" ,
} )
require . NoError ( t , err )
gpParams0 := globalPolicyRequest {
Name : "global policy" ,
Query : "select * from osquery;" ,
}
gpResp0 := globalPolicyResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/policies" , gpParams0 , http . StatusOK , & gpResp0 )
2022-01-18 16:18:40 +00:00
require . NotNil ( t , gpResp0 . Policy )
email := "team.observer@example.com"
teamObserver := & fleet . User {
Name : "team observer user" ,
Email : email ,
GlobalRole : nil ,
Teams : [ ] fleet . UserTeam {
{
Team : * team ,
Role : fleet . RoleObserver ,
} ,
} ,
}
2022-05-18 17:03:00 +00:00
password := test . GoodPassword
2022-01-18 16:18:40 +00:00
require . NoError ( t , teamObserver . SetPassword ( password , 10 , 10 ) )
_ , err = s . ds . NewUser ( context . Background ( ) , teamObserver )
require . NoError ( t , err )
oldToken := s . token
s . token = s . getTestToken ( email , password )
t . Cleanup ( func ( ) {
s . token = oldToken
} )
policiesResponse := listGlobalPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/policies" , nil , http . StatusOK , & policiesResponse )
2022-01-18 16:18:40 +00:00
require . Len ( t , policiesResponse . Policies , 1 )
assert . Equal ( t , "global policy" , policiesResponse . Policies [ 0 ] . Name )
assert . Equal ( t , "select * from osquery;" , policiesResponse . Policies [ 0 ] . Query )
}
2022-01-19 21:17:42 +00:00
func ( s * integrationTestSuite ) TestTeamPoliciesTeamNotExists ( ) {
t := s . T ( )
teamPoliciesResponse := listTeamPoliciesResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/teams/%d/policies" , 9999999 ) , nil , http . StatusNotFound , & teamPoliciesResponse )
2022-01-19 21:17:42 +00:00
require . Len ( t , teamPoliciesResponse . Policies , 0 )
}
2022-01-25 14:34:00 +00:00
func ( s * integrationTestSuite ) TestSessionInfo ( ) {
t := s . T ( )
ssn := createSession ( t , 1 , s . ds )
var meResp getUserResponse
2022-04-05 15:35:53 +00:00
resp := s . DoRawWithHeaders ( "GET" , "/api/latest/fleet/me" , nil , http . StatusOK , map [ string ] string {
2022-01-25 14:34:00 +00:00
"Authorization" : fmt . Sprintf ( "Bearer %s" , ssn . Key ) ,
} )
require . NoError ( t , json . NewDecoder ( resp . Body ) . Decode ( & meResp ) )
assert . Equal ( t , uint ( 1 ) , meResp . User . ID )
// get info about session
var getResp getInfoAboutSessionResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/sessions/%d" , ssn . ID ) , nil , http . StatusOK , & getResp )
2022-01-25 14:34:00 +00:00
assert . Equal ( t , ssn . ID , getResp . SessionID )
assert . Equal ( t , uint ( 1 ) , getResp . UserID )
// get info about session - non-existing: appears to deliberately return 500 due to forbidden,
// which takes precedence vs the not found returned by the datastore (it still shouldn't be a
// 500 though).
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/sessions/%d" , ssn . ID + 1 ) , nil , http . StatusInternalServerError , & getResp )
2022-01-25 14:34:00 +00:00
// delete session
var delResp deleteSessionResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/sessions/%d" , ssn . ID ) , nil , http . StatusOK , & delResp )
2022-01-25 14:34:00 +00:00
// delete session - non-existing: again, 500 due to forbidden instead of 404.
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/sessions/%d" , ssn . ID ) , nil , http . StatusInternalServerError , & delResp )
2022-01-25 14:34:00 +00:00
}
func ( s * integrationTestSuite ) TestAppConfig ( ) {
t := s . T ( )
// get the app config
var acResp appConfigResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
2022-01-25 14:34:00 +00:00
assert . Equal ( t , "free" , acResp . License . Tier )
assert . Equal ( t , "" , acResp . OrgInfo . OrgName )
// no server settings set for the URL, so not possible to test the
// certificate endpoint
acResp = appConfigResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
2022-01-25 14:34:00 +00:00
"org_info" : {
"org_name" : "test"
}
} ` ) , http . StatusOK , & acResp )
assert . Equal ( t , "test" , acResp . OrgInfo . OrgName )
var verResp versionResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/version" , nil , http . StatusOK , & verResp )
2022-01-25 14:34:00 +00:00
assert . NotEmpty ( t , verResp . Branch )
// get enroll secrets, none yet
var specResp getEnrollSecretSpecResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/enroll_secret" , nil , http . StatusOK , & specResp )
2022-01-25 14:34:00 +00:00
assert . Empty ( t , specResp . Spec . Secrets )
// apply spec, one secret
var applyResp applyEnrollSecretSpecResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/enroll_secret" , applyEnrollSecretSpecRequest {
2022-01-25 14:34:00 +00:00
Spec : & fleet . EnrollSecretSpec {
Secrets : [ ] * fleet . EnrollSecret { { Secret : "XYZ" } } ,
} ,
} , http . StatusOK , & applyResp )
// get enroll secrets, one
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/enroll_secret" , nil , http . StatusOK , & specResp )
2022-01-25 14:34:00 +00:00
require . Len ( t , specResp . Spec . Secrets , 1 )
assert . Equal ( t , "XYZ" , specResp . Spec . Secrets [ 0 ] . Secret )
// remove secret just to prevent affecting other tests
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/enroll_secret" , applyEnrollSecretSpecRequest {
2022-01-25 14:34:00 +00:00
Spec : & fleet . EnrollSecretSpec { } ,
} , http . StatusOK , & applyResp )
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/enroll_secret" , nil , http . StatusOK , & specResp )
2022-01-25 14:34:00 +00:00
require . Len ( t , specResp . Spec . Secrets , 0 )
}
2022-01-31 21:35:22 +00:00
func ( s * integrationTestSuite ) TestQuerySpecs ( ) {
t := s . T ( )
// list specs, none yet
var getSpecsResp getQuerySpecsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/queries" , nil , http . StatusOK , & getSpecsResp )
2022-01-31 21:35:22 +00:00
assert . Len ( t , getSpecsResp . Specs , 0 )
// get unknown one
var getSpecResp getQuerySpecResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/queries/nonesuch" , nil , http . StatusNotFound , & getSpecResp )
2022-01-31 21:35:22 +00:00
// create some queries via apply specs
q1 := strings . ReplaceAll ( t . Name ( ) , "/" , "_" )
q2 := q1 + "_2"
var applyResp applyQuerySpecsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/queries" , applyQuerySpecsRequest {
2022-01-31 21:35:22 +00:00
Specs : [ ] * fleet . QuerySpec {
{ Name : q1 , Query : "SELECT 1" } ,
{ Name : q2 , Query : "SELECT 2" } ,
} ,
} , http . StatusOK , & applyResp )
// get the queries back
var listQryResp listQueriesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/queries" , nil , http . StatusOK , & listQryResp , "order_key" , "name" )
2022-01-31 21:35:22 +00:00
require . Len ( t , listQryResp . Queries , 2 )
assert . Equal ( t , q1 , listQryResp . Queries [ 0 ] . Name )
assert . Equal ( t , q2 , listQryResp . Queries [ 1 ] . Name )
q1ID , q2ID := listQryResp . Queries [ 0 ] . ID , listQryResp . Queries [ 1 ] . ID
// list specs
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/queries" , nil , http . StatusOK , & getSpecsResp )
2022-01-31 21:35:22 +00:00
require . Len ( t , getSpecsResp . Specs , 2 )
names := [ ] string { getSpecsResp . Specs [ 0 ] . Name , getSpecsResp . Specs [ 1 ] . Name }
assert . ElementsMatch ( t , [ ] string { q1 , q2 } , names )
// get specific spec
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/spec/queries/%s" , q1 ) , nil , http . StatusOK , & getSpecResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , getSpecResp . Spec . Query , "SELECT 1" )
// apply specs again - create q3 and update q2
q3 := q1 + "_3"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/queries" , applyQuerySpecsRequest {
2022-01-31 21:35:22 +00:00
Specs : [ ] * fleet . QuerySpec {
{ Name : q2 , Query : "SELECT -2" } ,
{ Name : q3 , Query : "SELECT 3" } ,
} ,
} , http . StatusOK , & applyResp )
// list specs - has 3, not 4 (one was an update)
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/spec/queries" , nil , http . StatusOK , & getSpecsResp )
2022-01-31 21:35:22 +00:00
require . Len ( t , getSpecsResp . Specs , 3 )
names = [ ] string { getSpecsResp . Specs [ 0 ] . Name , getSpecsResp . Specs [ 1 ] . Name , getSpecsResp . Specs [ 2 ] . Name }
assert . ElementsMatch ( t , [ ] string { q1 , q2 , q3 } , names )
// get the queries back again
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/queries" , nil , http . StatusOK , & listQryResp , "order_key" , "name" )
2022-01-31 21:35:22 +00:00
require . Len ( t , listQryResp . Queries , 3 )
assert . Equal ( t , q1ID , listQryResp . Queries [ 0 ] . ID )
assert . Equal ( t , q2ID , listQryResp . Queries [ 1 ] . ID )
assert . Equal ( t , "SELECT -2" , listQryResp . Queries [ 1 ] . Query )
q3ID := listQryResp . Queries [ 2 ] . ID
// delete all queries created
var delBatchResp deleteQueriesResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/queries/delete" , map [ string ] interface { } {
2022-02-14 18:13:44 +00:00
"ids" : [ ] uint { q1ID , q2ID , q3ID } ,
} , http . StatusOK , & delBatchResp )
2022-01-31 21:35:22 +00:00
assert . Equal ( t , uint ( 3 ) , delBatchResp . Deleted )
}
2022-02-09 15:16:50 +00:00
func ( s * integrationTestSuite ) TestPaginateListSoftware ( ) {
t := s . T ( )
// create a few hosts specific to this test
hosts := make ( [ ] * fleet . Host , 20 )
for i := range hosts {
host , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + strconv . Itoa ( i ) ,
OsqueryHostID : t . Name ( ) + strconv . Itoa ( i ) ,
UUID : t . Name ( ) + strconv . Itoa ( i ) ,
Hostname : t . Name ( ) + "foo" + strconv . Itoa ( i ) + ".local" ,
PrimaryIP : "192.168.1." + strconv . Itoa ( i ) ,
PrimaryMac : fmt . Sprintf ( "30-65-EC-6F-C4-%02d" , i ) ,
} )
require . NoError ( t , err )
require . NotNil ( t , host )
hosts [ i ] = host
}
// create a bunch of software
sws := make ( [ ] fleet . Software , 20 )
for i := range sws {
sw := fleet . Software { Name : "sw" + strconv . Itoa ( i ) , Version : "0.0." + strconv . Itoa ( i ) , Source : "apps" }
sws [ i ] = sw
}
// mark them as installed on the hosts, with host at index 0 having all 20,
// at index 1 having 19, index 2 = 18, etc. until index 19 = 1. So software
// sws[0] is only used by 1 host, while sws[19] is used by all.
for i , h := range hosts {
require . NoError ( t , s . ds . UpdateHostSoftware ( context . Background ( ) , h . ID , sws [ i : ] ) )
2022-06-01 16:06:57 +00:00
require . NoError ( t , s . ds . LoadHostSoftware ( context . Background ( ) , h , false ) )
2022-02-09 15:16:50 +00:00
if i == 0 {
// this host has all software, refresh the list so we have the software.ID filled
sws = h . Software
}
}
for i , sw := range sws {
cpe := "somecpe" + strconv . Itoa ( i )
require . NoError ( t , s . ds . AddCPEForSoftware ( context . Background ( ) , sw , cpe ) )
2022-06-08 01:09:47 +00:00
}
2022-02-09 15:16:50 +00:00
2022-06-08 01:09:47 +00:00
// Reload software to load GeneratedCPEID
require . NoError ( t , s . ds . LoadHostSoftware ( context . Background ( ) , hosts [ 0 ] , false ) )
var vulns [ ] fleet . SoftwareVulnerability
for i , sw := range hosts [ 0 ] . Software [ : 10 ] {
vulns = append ( vulns , fleet . SoftwareVulnerability {
SoftwareID : sw . ID ,
CPEID : sw . GeneratedCPEID ,
CVE : fmt . Sprintf ( "cve-123-123-%03d" , i ) ,
} )
2022-02-09 15:16:50 +00:00
}
2022-06-08 01:09:47 +00:00
// add CVEs for the first 10 software, which are the least used (lower hosts_count)
n , err := s . ds . InsertVulnerabilities ( context . Background ( ) , vulns , fleet . NVD )
require . NoError ( t , err )
require . Equal ( t , 10 , int ( n ) )
2022-02-28 18:55:14 +00:00
// create a team and make the last 3 hosts part of it (meaning 3 that use
// sws[19], 2 for sws[18], and 1 for sws[17])
tm , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team {
Name : t . Name ( ) ,
} )
require . NoError ( t , err )
require . NoError ( t , s . ds . AddHostsToTeam ( context . Background ( ) , & tm . ID , [ ] uint { hosts [ 19 ] . ID , hosts [ 18 ] . ID , hosts [ 17 ] . ID } ) )
2022-02-09 15:16:50 +00:00
assertResp := func ( resp listSoftwareResponse , want [ ] fleet . Software , ts time . Time , counts ... int ) {
require . Len ( t , resp . Software , len ( want ) )
for i := range resp . Software {
wantID , gotID := want [ i ] . ID , resp . Software [ i ] . ID
assert . Equal ( t , wantID , gotID )
wantCount , gotCount := counts [ i ] , resp . Software [ i ] . HostsCount
assert . Equal ( t , wantCount , gotCount )
}
if ts . IsZero ( ) {
assert . Nil ( t , resp . CountsUpdatedAt )
} else {
require . NotNil ( t , resp . CountsUpdatedAt )
assert . WithinDuration ( t , ts , * resp . CountsUpdatedAt , time . Second )
}
}
// no software host counts have been calculated yet, so this returns nothing
var lsResp listSoftwareResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , nil , time . Time { } )
2022-02-28 18:55:14 +00:00
// same with a team filter
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "order_key" , "hosts_count" , "order_direction" , "desc" , "team_id" , fmt . Sprintf ( "%d" , tm . ID ) )
2022-02-28 18:55:14 +00:00
assertResp ( lsResp , nil , time . Time { } )
2022-02-09 15:16:50 +00:00
// calculate hosts counts
hostsCountTs := time . Now ( ) . UTC ( )
require . NoError ( t , s . ds . CalculateHostsPerSoftware ( context . Background ( ) , hostsCountTs ) )
// now the list software endpoint returns the software, get the first page without vulns
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "5" , "page" , "0" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 19 ] , sws [ 18 ] , sws [ 17 ] , sws [ 16 ] , sws [ 15 ] } , hostsCountTs , 20 , 19 , 18 , 17 , 16 )
// second page (page=1)
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "5" , "page" , "1" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 14 ] , sws [ 13 ] , sws [ 12 ] , sws [ 11 ] , sws [ 10 ] } , hostsCountTs , 15 , 14 , 13 , 12 , 11 )
// third page (page=2)
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "5" , "page" , "2" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 9 ] , sws [ 8 ] , sws [ 7 ] , sws [ 6 ] , sws [ 5 ] } , hostsCountTs , 10 , 9 , 8 , 7 , 6 )
// last page (page=3)
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "5" , "page" , "3" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 4 ] , sws [ 3 ] , sws [ 2 ] , sws [ 1 ] , sws [ 0 ] } , hostsCountTs , 5 , 4 , 3 , 2 , 1 )
// past the end
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "5" , "page" , "4" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , nil , time . Time { } )
// no explicit sort order, defaults to hosts_count DESC
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "2" , "page" , "0" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 19 ] , sws [ 18 ] } , hostsCountTs , 20 , 19 )
// hosts_count ascending
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "3" , "page" , "0" , "order_key" , "hosts_count" , "order_direction" , "asc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 0 ] , sws [ 1 ] , sws [ 2 ] } , hostsCountTs , 1 , 2 , 3 )
// vulnerable software only
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "vulnerable" , "true" , "per_page" , "5" , "page" , "0" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 9 ] , sws [ 8 ] , sws [ 7 ] , sws [ 6 ] , sws [ 5 ] } , hostsCountTs , 10 , 9 , 8 , 7 , 6 )
// vulnerable software only, next page
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "vulnerable" , "true" , "per_page" , "5" , "page" , "1" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 4 ] , sws [ 3 ] , sws [ 2 ] , sws [ 1 ] , sws [ 0 ] } , hostsCountTs , 5 , 4 , 3 , 2 , 1 )
// vulnerable software only, past last page
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "vulnerable" , "true" , "per_page" , "5" , "page" , "2" , "order_key" , "hosts_count" , "order_direction" , "desc" )
2022-02-09 15:16:50 +00:00
assertResp ( lsResp , nil , time . Time { } )
2022-02-28 18:55:14 +00:00
// filter by the team, 2 by page
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "2" , "page" , "0" , "order_key" , "hosts_count" , "order_direction" , "desc" , "team_id" , fmt . Sprintf ( "%d" , tm . ID ) )
2022-02-28 18:55:14 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 19 ] , sws [ 18 ] } , hostsCountTs , 3 , 2 )
// filter by the team, 2 by page, next page
lsResp = listSoftwareResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/software" , nil , http . StatusOK , & lsResp , "per_page" , "2" , "page" , "1" , "order_key" , "hosts_count" , "order_direction" , "desc" , "team_id" , fmt . Sprintf ( "%d" , tm . ID ) )
2022-02-28 18:55:14 +00:00
assertResp ( lsResp , [ ] fleet . Software { sws [ 17 ] } , hostsCountTs , 1 )
2022-02-09 15:16:50 +00:00
}
2022-02-15 20:22:19 +00:00
func ( s * integrationTestSuite ) TestChangeUserEmail ( ) {
t := s . T ( )
// create a new test user
user := & fleet . User {
Name : t . Name ( ) ,
Email : "testchangeemail@example.com" ,
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
userRawPwd := "foobarbaz1234!"
err := user . SetPassword ( userRawPwd , 10 , 10 )
require . Nil ( t , err )
user , err = s . ds . NewUser ( context . Background ( ) , user )
require . Nil ( t , err )
// try to change email with an invalid token
var changeResp changeEmailResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/email/change/invalidtoken" , nil , http . StatusNotFound , & changeResp )
2022-02-15 20:22:19 +00:00
// create a valid token for the test user
err = s . ds . PendingEmailChange ( context . Background ( ) , user . ID , "testchangeemail2@example.com" , "validtoken" )
require . Nil ( t , err )
// try to change email with a valid token, but request made from different user
changeResp = changeEmailResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/email/change/validtoken" , nil , http . StatusNotFound , & changeResp )
2022-02-15 20:22:19 +00:00
// switch to the test user and make the change email request
s . token = s . getTestToken ( user . Email , userRawPwd )
defer func ( ) { s . token = s . getTestAdminToken ( ) } ( )
changeResp = changeEmailResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/email/change/validtoken" , nil , http . StatusOK , & changeResp )
2022-02-15 20:22:19 +00:00
require . Equal ( t , "testchangeemail2@example.com" , changeResp . NewEmail )
// using the token consumes it, so making another request with the same token fails
changeResp = changeEmailResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/email/change/validtoken" , nil , http . StatusNotFound , & changeResp )
2022-02-15 20:22:19 +00:00
}
func ( s * integrationTestSuite ) TestSearchTargets ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
lblIDs , err := s . ds . LabelIDsByName ( context . Background ( ) , [ ] string { "All Hosts" } )
require . NoError ( t , err )
require . Len ( t , lblIDs , 1 )
// no search criteria
var searchResp searchTargetsResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/targets" , searchTargetsRequest { } , http . StatusOK , & searchResp )
2022-02-15 20:22:19 +00:00
require . Equal ( t , uint ( 0 ) , searchResp . TargetsCount )
require . Len ( t , searchResp . Targets . Hosts , len ( hosts ) ) // the HostTargets.HostIDs are actually host IDs to *omit* from the search
require . Len ( t , searchResp . Targets . Labels , 1 )
require . Len ( t , searchResp . Targets . Teams , 0 )
searchResp = searchTargetsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/targets" , searchTargetsRequest { Selected : fleet . HostTargets { LabelIDs : lblIDs } } , http . StatusOK , & searchResp )
2022-02-15 20:22:19 +00:00
require . Equal ( t , uint ( 0 ) , searchResp . TargetsCount )
require . Len ( t , searchResp . Targets . Hosts , len ( hosts ) ) // no omitted host id
require . Len ( t , searchResp . Targets . Labels , 0 ) // labels have been omitted
require . Len ( t , searchResp . Targets . Teams , 0 )
searchResp = searchTargetsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/targets" , searchTargetsRequest { Selected : fleet . HostTargets { HostIDs : [ ] uint { hosts [ 1 ] . ID } } } , http . StatusOK , & searchResp )
2022-02-15 20:22:19 +00:00
require . Equal ( t , uint ( 1 ) , searchResp . TargetsCount )
require . Len ( t , searchResp . Targets . Hosts , len ( hosts ) - 1 ) // one omitted host id
require . Len ( t , searchResp . Targets . Labels , 1 ) // labels have not been omitted
require . Len ( t , searchResp . Targets . Teams , 0 )
searchResp = searchTargetsResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/targets" , searchTargetsRequest { MatchQuery : "foo.local1" } , http . StatusOK , & searchResp )
2022-02-15 20:22:19 +00:00
require . Equal ( t , uint ( 0 ) , searchResp . TargetsCount )
require . Len ( t , searchResp . Targets . Hosts , 1 )
require . Len ( t , searchResp . Targets . Labels , 1 )
require . Len ( t , searchResp . Targets . Teams , 0 )
require . Contains ( t , searchResp . Targets . Hosts [ 0 ] . Hostname , "foo.local1" )
}
func ( s * integrationTestSuite ) TestStatus ( ) {
var statusResp statusResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/status/result_store" , nil , http . StatusOK , & statusResp )
s . DoJSON ( "GET" , "/api/latest/fleet/status/live_query" , nil , http . StatusOK , & statusResp )
2022-02-15 20:22:19 +00:00
}
2022-03-07 18:10:55 +00:00
func ( s * integrationTestSuite ) TestOsqueryConfig ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
req := getClientConfigRequest { NodeKey : hosts [ 0 ] . NodeKey }
var resp getClientConfigResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/config" , req , http . StatusOK , & resp )
2022-03-07 18:10:55 +00:00
// test with invalid node key
var errRes map [ string ] interface { }
req . NodeKey += "zzzz"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/config" , req , http . StatusUnauthorized , & errRes )
2022-03-07 18:10:55 +00:00
assert . Contains ( t , errRes [ "error" ] , "invalid node key" )
}
2022-03-08 16:27:38 +00:00
func ( s * integrationTestSuite ) TestEnrollHost ( ) {
t := s . T ( )
// set the enroll secret
var applyResp applyEnrollSecretSpecResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/spec/enroll_secret" , applyEnrollSecretSpecRequest {
2022-03-08 16:27:38 +00:00
Spec : & fleet . EnrollSecretSpec {
Secrets : [ ] * fleet . EnrollSecret { { Secret : t . Name ( ) } } ,
} ,
} , http . StatusOK , & applyResp )
// invalid enroll secret fails
j , err := json . Marshal ( & enrollAgentRequest {
EnrollSecret : "nosuchsecret" ,
HostIdentifier : "abcd" ,
} )
require . NoError ( t , err )
2022-04-05 15:35:53 +00:00
s . DoRawNoAuth ( "POST" , "/api/osquery/enroll" , j , http . StatusUnauthorized )
2022-03-08 16:27:38 +00:00
// valid enroll secret succeeds
j , err = json . Marshal ( & enrollAgentRequest {
EnrollSecret : t . Name ( ) ,
HostIdentifier : t . Name ( ) ,
} )
require . NoError ( t , err )
var resp enrollAgentResponse
2022-04-05 15:35:53 +00:00
hres := s . DoRawNoAuth ( "POST" , "/api/osquery/enroll" , j , http . StatusOK )
2022-03-08 16:27:38 +00:00
defer hres . Body . Close ( )
require . NoError ( t , json . NewDecoder ( hres . Body ) . Decode ( & resp ) )
require . NotEmpty ( t , resp . NodeKey )
}
func ( s * integrationTestSuite ) TestCarve ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
// begin a carve with an invalid node key
var errRes map [ string ] interface { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey + "zzz" ,
BlockCount : 1 ,
BlockSize : 1 ,
CarveSize : 1 ,
CarveId : "c1" ,
} , http . StatusUnauthorized , & errRes )
assert . Contains ( t , errRes [ "error" ] , "invalid node key" )
// invalid carve size
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey ,
BlockCount : 3 ,
BlockSize : 3 ,
CarveSize : 0 ,
CarveId : "c1" ,
} , http . StatusInternalServerError , & errRes ) // TODO: should be 4xx, see #4406
assert . Contains ( t , errRes [ "error" ] , "carve_size must be greater" )
// invalid block size too big
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey ,
BlockCount : 3 ,
BlockSize : maxBlockSize + 1 ,
CarveSize : maxCarveSize ,
CarveId : "c1" ,
} , http . StatusInternalServerError , & errRes ) // TODO: should be 4xx, see #4406
assert . Contains ( t , errRes [ "error" ] , "block_size exceeds max" )
// invalid carve size too big
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey ,
BlockCount : 3 ,
BlockSize : maxBlockSize ,
CarveSize : maxCarveSize + 1 ,
CarveId : "c1" ,
} , http . StatusInternalServerError , & errRes ) // TODO: should be 4xx, see #4406
assert . Contains ( t , errRes [ "error" ] , "carve_size exceeds max" )
// invalid carve size, does not match blocks
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey ,
BlockCount : 3 ,
BlockSize : 3 ,
CarveSize : 1 ,
CarveId : "c1" ,
} , http . StatusInternalServerError , & errRes ) // TODO: should be 4xx, see #4406
assert . Contains ( t , errRes [ "error" ] , "carve_size does not match" )
// valid carve begin
var beginResp carveBeginResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/begin" , carveBeginRequest {
2022-03-08 16:27:38 +00:00
NodeKey : hosts [ 0 ] . NodeKey ,
BlockCount : 3 ,
BlockSize : 3 ,
CarveSize : 8 ,
CarveId : "c1" ,
RequestId : "r1" ,
} , http . StatusOK , & beginResp )
require . NotEmpty ( t , beginResp . SessionId )
sid := beginResp . SessionId
// sending a block with invalid session id
var blockResp carveBlockResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 1 ,
SessionId : sid + "zz" ,
RequestId : "??" ,
Data : [ ] byte ( "p1." ) ,
} , http . StatusNotFound , & blockResp )
// sending a block with valid session id but invalid request id
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 1 ,
SessionId : sid ,
RequestId : "??" ,
Data : [ ] byte ( "p1." ) ,
} , http . StatusInternalServerError , & blockResp ) // TODO: should be 400, see #4406
// sending a block with unexpected block id (expects 0, got 1)
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 1 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p1." ) ,
} , http . StatusInternalServerError , & blockResp ) // TODO: should be 400, see #4406
// sending a block with valid payload, block 0
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 0 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p1." ) ,
} , http . StatusOK , & blockResp )
require . True ( t , blockResp . Success )
// sending next block
blockResp = carveBlockResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 1 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p2." ) ,
} , http . StatusOK , & blockResp )
require . True ( t , blockResp . Success )
// sending already-sent block again
blockResp = carveBlockResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 1 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p2." ) ,
} , http . StatusInternalServerError , & blockResp ) // TODO: should be 400, see #4406
// sending final block with too many bytes
blockResp = carveBlockResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 2 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p3extra" ) ,
} , http . StatusInternalServerError , & blockResp ) // TODO: should be 400, see #4406
// sending actual final block
blockResp = carveBlockResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 2 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p3" ) ,
} , http . StatusOK , & blockResp )
require . True ( t , blockResp . Success )
// sending unexpected block
blockResp = carveBlockResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/osquery/carve/block" , carveBlockRequest {
2022-03-08 16:27:38 +00:00
BlockId : 3 ,
SessionId : sid ,
RequestId : "r1" ,
Data : [ ] byte ( "p4." ) ,
} , http . StatusInternalServerError , & blockResp ) // TODO: should be 400, see #4406
}
func ( s * integrationTestSuite ) TestPasswordReset ( ) {
t := s . T ( )
// create a new user
var createResp createUserResponse
2022-05-18 17:03:00 +00:00
userRawPwd := test . GoodPassword
2022-03-08 16:27:38 +00:00
params := fleet . UserPayload {
Name : ptr . String ( "forgotpwd" ) ,
Email : ptr . String ( "forgotpwd@example.com" ) ,
Password : ptr . String ( userRawPwd ) ,
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users/admin" , params , http . StatusOK , & createResp )
2022-03-08 16:27:38 +00:00
require . NotZero ( t , createResp . User . ID )
u := * createResp . User
// request forgot password, invalid email
2022-04-05 15:35:53 +00:00
res := s . DoRawNoAuth ( "POST" , "/api/latest/fleet/forgot_password" , jsonMustMarshal ( t , forgotPasswordRequest { Email : "invalid@asd.com" } ) , http . StatusAccepted )
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
// TODO: tested manually (adds too much time to the test), works but hitting the rate
// limit returns 500 instead of 429, see #4406. We get the authz check missing error instead.
//// trigger the rate limit with a batch of requests in a short burst
//for i := 0; i < 20; i++ {
2022-04-05 15:35:53 +00:00
// s.DoJSON("POST", "/api/latest/fleet/forgot_password", forgotPasswordRequest{Email: "invalid@asd.com"}, http.StatusAccepted, &forgotResp)
2022-03-08 16:27:38 +00:00
//}
// request forgot password, valid email
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/forgot_password" , jsonMustMarshal ( t , forgotPasswordRequest { Email : u . Email } ) , http . StatusAccepted )
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
var token string
mysql . ExecAdhocSQL ( t , s . ds , func ( db sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , db , & token , "SELECT token FROM password_reset_requests WHERE user_id = ?" , u . ID )
} )
// proceed with reset password
2022-05-18 17:03:00 +00:00
userNewPwd := test . GoodPassword2
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/reset_password" , jsonMustMarshal ( t , resetPasswordRequest { PasswordResetToken : token , NewPassword : userNewPwd } ) , http . StatusOK )
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
// attempt it again with already-used token
userUnusedPwd := "unusedpassw0rd!"
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/reset_password" , jsonMustMarshal ( t , resetPasswordRequest { PasswordResetToken : token , NewPassword : userUnusedPwd } ) , http . StatusInternalServerError ) // TODO: should be 40x, see #4406
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
// login with the old password, should not succeed
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/login" , jsonMustMarshal ( t , loginRequest { Email : u . Email , Password : userRawPwd } ) , http . StatusUnauthorized )
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
// login with the new password, should succeed
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/login" , jsonMustMarshal ( t , loginRequest { Email : u . Email , Password : userNewPwd } ) , http . StatusOK )
2022-03-08 16:27:38 +00:00
res . Body . Close ( )
}
2022-03-09 21:13:56 +00:00
func ( s * integrationTestSuite ) TestDeviceAuthenticatedEndpoints ( ) {
t := s . T ( )
hosts := s . createHosts ( t )
2022-03-16 14:15:25 +00:00
ac , err := s . ds . AppConfig ( context . Background ( ) )
require . NoError ( t , err )
ac . OrgInfo . OrgLogoURL = "http://example.com/logo"
err = s . ds . SaveAppConfig ( context . Background ( ) , ac )
require . NoError ( t , err )
2022-03-09 21:13:56 +00:00
// create some mappings and MDM/Munki data
s . ds . ReplaceHostDeviceMapping ( context . Background ( ) , hosts [ 0 ] . ID , [ ] * fleet . HostDeviceMapping {
{ HostID : hosts [ 0 ] . ID , Email : "a@b.c" , Source : "google_chrome_profiles" } ,
{ HostID : hosts [ 0 ] . ID , Email : "b@b.c" , Source : "google_chrome_profiles" } ,
} )
require . NoError ( t , s . ds . SetOrUpdateMDMData ( context . Background ( ) , hosts [ 0 ] . ID , true , "url" , false ) )
require . NoError ( t , s . ds . SetOrUpdateMunkiVersion ( context . Background ( ) , hosts [ 0 ] . ID , "1.3.0" ) )
// create an auth token for hosts[0]
token := "much_valid"
mysql . ExecAdhocSQL ( t , s . ds , func ( db sqlx . ExtContext ) error {
_ , err := db . ExecContext ( context . Background ( ) , ` INSERT INTO host_device_auth (host_id, token) VALUES (?, ?) ` , hosts [ 0 ] . ID , token )
return err
} )
// get host without token
2022-04-05 15:35:53 +00:00
res := s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" , nil , http . StatusNotFound )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
// get host with invalid token
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/no_such_token" , nil , http . StatusUnauthorized )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
// get host with valid token
2022-03-16 14:15:25 +00:00
var getHostResp getDeviceHostResponse
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
2022-03-09 21:13:56 +00:00
json . NewDecoder ( res . Body ) . Decode ( & getHostResp )
res . Body . Close ( )
require . Equal ( t , hosts [ 0 ] . ID , getHostResp . Host . ID )
require . False ( t , getHostResp . Host . RefetchRequested )
2022-03-16 14:15:25 +00:00
require . Equal ( t , "http://example.com/logo" , getHostResp . OrgLogoURL )
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
require . Nil ( t , getHostResp . Host . Policies )
2022-03-09 21:13:56 +00:00
hostDevResp := getHostResp . Host
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
// make request for same host on the host details API endpoint, responses should match, except for policies
2022-03-16 14:15:25 +00:00
getHostResp = getDeviceHostResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & getHostResp )
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
getHostResp . Host . Policies = nil
2022-03-09 21:13:56 +00:00
require . Equal ( t , hostDevResp , getHostResp . Host )
// request a refetch for that valid host
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/device/" + token + "/refetch" , nil , http . StatusOK )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
// host should have that flag turned to true
2022-03-16 14:15:25 +00:00
getHostResp = getDeviceHostResponse { }
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
2022-03-09 21:13:56 +00:00
json . NewDecoder ( res . Body ) . Decode ( & getHostResp )
res . Body . Close ( )
require . True ( t , getHostResp . Host . RefetchRequested )
// request a refetch for an invalid token
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/device/no_such_token/refetch" , nil , http . StatusUnauthorized )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
// list device mappings for valid token
var listDMResp listHostDeviceMappingResponse
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token + "/device_mapping" , nil , http . StatusOK )
2022-03-09 21:13:56 +00:00
json . NewDecoder ( res . Body ) . Decode ( & listDMResp )
res . Body . Close ( )
require . Equal ( t , hosts [ 0 ] . ID , listDMResp . HostID )
require . Len ( t , listDMResp . DeviceMapping , 2 )
devDMs := listDMResp . DeviceMapping
// compare response with standard list device mapping API for that same host
listDMResp = listHostDeviceMappingResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/device_mapping" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & listDMResp )
2022-03-09 21:13:56 +00:00
require . Equal ( t , hosts [ 0 ] . ID , listDMResp . HostID )
require . Equal ( t , devDMs , listDMResp . DeviceMapping )
// list device mappings for invalid token
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/no_such_token/device_mapping" , nil , http . StatusUnauthorized )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
// get macadmins for valid token
var getMacadm getMacadminsDataResponse
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token + "/macadmins" , nil , http . StatusOK )
2022-03-09 21:13:56 +00:00
json . NewDecoder ( res . Body ) . Decode ( & getMacadm )
res . Body . Close ( )
require . Equal ( t , "1.3.0" , getMacadm . Macadmins . Munki . Version )
devMacadm := getMacadm . Macadmins
// compare response with standard macadmins API for that same host
getMacadm = getMacadminsDataResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & getMacadm )
2022-03-09 21:13:56 +00:00
require . Equal ( t , devMacadm , getMacadm . Macadmins )
// get macadmins for invalid token
2022-04-05 15:35:53 +00:00
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/no_such_token/macadmins" , nil , http . StatusUnauthorized )
2022-03-09 21:13:56 +00:00
res . Body . Close ( )
2022-05-19 21:28:49 +00:00
// response includes license info
getHostResp = getDeviceHostResponse { }
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
json . NewDecoder ( res . Body ) . Decode ( & getHostResp )
res . Body . Close ( )
require . NotNil ( t , getHostResp . License )
require . Equal ( t , getHostResp . License . Tier , "free" )
add premium, device authed endpoint to retrieve policies (#5967)
This adds a new device authenticated endpoint, `/api/_version_/fleet/device/{token}/policies` to retrieve the device policies.
An example request / response looks like:
```bash
curl https://localhost:8080/api/latest/fleet/device/1804e808-171f-4dda-9bec-f695b2f2371a/policies
```
```json
{
"policies": [
{
"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": "Admin",
"author_email": "admin@example.com",
"team_id": null,
"resolution": "Ensure ClamAV and Freshclam are installed and running.",
"platform": "darwin,windows,linux",
"created_at": "2022-05-23T20:53:36Z",
"updated_at": "2022-05-23T20:53:36Z",
"response": "fail"
}
]
}
```
Related to [#5685](https://github.com/fleetdm/fleet/issues/5685), in another changeset I will be adding "client" endpoints so we can consume this endpoint from Fleet Desktop
2022-05-31 17:54:43 +00:00
// device policies are not accessible for free endpoints
listPoliciesResp := listDevicePoliciesResponse { }
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token + "/policies" , nil , http . StatusPaymentRequired )
json . NewDecoder ( res . Body ) . Decode ( & getHostResp )
res . Body . Close ( )
require . Nil ( t , listPoliciesResp . Policies )
2022-06-09 13:17:55 +00:00
// get list of api features
apiFeaturesResp := deviceAPIFeaturesResponse { }
res = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token + "/api_features" , nil , http . StatusOK )
json . NewDecoder ( res . Body ) . Decode ( & apiFeaturesResp )
res . Body . Close ( )
require . Nil ( t , apiFeaturesResp . Err )
require . NotNil ( t , apiFeaturesResp . Features )
2022-03-09 21:13:56 +00:00
}
2022-06-10 15:39:02 +00:00
// TestDefaultTransparencyURL tests that Fleet Free licensees are restricted to the default transparency url.
func ( s * integrationTestSuite ) TestDefaultTransparencyURL ( ) {
t := s . T ( )
host , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) . Add ( - 1 * time . Minute ) ,
OsqueryHostID : t . Name ( ) ,
NodeKey : t . Name ( ) ,
UUID : uuid . New ( ) . String ( ) ,
Hostname : fmt . Sprintf ( "%sfoo.local" , t . Name ( ) ) ,
Platform : "darwin" ,
} )
require . NoError ( t , err )
// create device token for host
token := "token_test_default_transparency_url"
mysql . ExecAdhocSQL ( t , s . ds , func ( db sqlx . ExtContext ) error {
_ , err := db . ExecContext ( context . Background ( ) , ` INSERT INTO host_device_auth (host_id, token) VALUES (?, ?) ` , host . ID , token )
return err
} )
// confirm initial default url
acResp := appConfigResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotNil ( t , acResp )
require . Equal ( t , fleet . DefaultTransparencyURL , acResp . FleetDesktop . TransparencyURL )
// confirm device endpoint returns initial default url
deviceResp := & getDeviceHostResponse { }
rawResp := s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
json . NewDecoder ( rawResp . Body ) . Decode ( deviceResp )
rawResp . Body . Close ( )
require . NoError ( t , deviceResp . Err )
require . Equal ( t , fleet . DefaultTransparencyURL , deviceResp . TransparencyURL )
// empty string applies default url
acResp = appConfigResponse { }
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , fleet . AppConfig { FleetDesktop : fleet . FleetDesktopSettings { TransparencyURL : "" } } , http . StatusOK , & acResp )
require . NotNil ( t , acResp )
require . Equal ( t , fleet . DefaultTransparencyURL , acResp . FleetDesktop . TransparencyURL )
// device endpoint returns default url
deviceResp = & getDeviceHostResponse { }
rawResp = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
json . NewDecoder ( rawResp . Body ) . Decode ( deviceResp )
rawResp . Body . Close ( )
require . NoError ( t , deviceResp . Err )
require . Equal ( t , fleet . DefaultTransparencyURL , deviceResp . TransparencyURL )
// modify transparency url with custom url fails
acResp = appConfigResponse { }
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , fleet . AppConfig { FleetDesktop : fleet . FleetDesktopSettings { TransparencyURL : "customURL" } } , http . StatusUnprocessableEntity , & acResp )
// device endpoint still returns default url
deviceResp = & getDeviceHostResponse { }
rawResp = s . DoRawNoAuth ( "GET" , "/api/latest/fleet/device/" + token , nil , http . StatusOK )
json . NewDecoder ( rawResp . Body ) . Decode ( deviceResp )
rawResp . Body . Close ( )
require . NoError ( t , deviceResp . Err )
require . Equal ( t , fleet . DefaultTransparencyURL , deviceResp . TransparencyURL )
}
2022-03-15 12:11:53 +00:00
func ( s * integrationTestSuite ) TestModifyUser ( ) {
t := s . T ( )
// create a new user
var createResp createUserResponse
2022-05-18 17:03:00 +00:00
userRawPwd := test . GoodPassword
2022-03-15 12:11:53 +00:00
params := fleet . UserPayload {
Name : ptr . String ( "moduser" ) ,
Email : ptr . String ( "moduser@example.com" ) ,
Password : ptr . String ( userRawPwd ) ,
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
AdminForcedPasswordReset : ptr . Bool ( false ) ,
}
2022-04-05 15:35:53 +00:00
s . DoJSON ( "POST" , "/api/latest/fleet/users/admin" , params , http . StatusOK , & createResp )
2022-03-15 12:11:53 +00:00
require . NotZero ( t , createResp . User . ID )
u := * createResp . User
s . token = s . getTestToken ( u . Email , userRawPwd )
require . NotEmpty ( t , s . token )
defer func ( ) { s . token = s . getTestAdminToken ( ) } ( )
// as the user: modify email without providing current password
var modResp modifyUserResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
Email : ptr . String ( "moduser2@example.com" ) ,
} , http . StatusUnprocessableEntity , & modResp )
// as the user: modify email with invalid password
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
Email : ptr . String ( "moduser2@example.com" ) ,
Password : ptr . String ( "nosuchpwd" ) ,
} , http . StatusForbidden , & modResp )
// as the user: modify email with current password
newEmail := "moduser2@example.com"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
Email : ptr . String ( newEmail ) ,
Password : ptr . String ( userRawPwd ) ,
} , http . StatusOK , & modResp )
require . Equal ( t , u . ID , modResp . User . ID )
require . Equal ( t , u . Email , modResp . User . Email ) // new email is pending confirmation, not changed immediately
// as the user: set new password without providing current one
2022-05-18 17:03:00 +00:00
newRawPwd := test . GoodPassword2
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( newRawPwd ) ,
} , http . StatusUnprocessableEntity , & modResp )
// as the user: set new password with an invalid current password
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( newRawPwd ) ,
Password : ptr . String ( "nosuchpwd" ) ,
} , http . StatusForbidden , & modResp )
// as the user: set new password and change name, with a valid current password
modResp = modifyUserResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( newRawPwd ) ,
Password : ptr . String ( userRawPwd ) ,
Name : ptr . String ( "moduser2" ) ,
} , http . StatusOK , & modResp )
require . Equal ( t , u . ID , modResp . User . ID )
require . Equal ( t , "moduser2" , modResp . User . Name )
s . token = s . getTestToken ( testUsers [ "user2" ] . Email , testUsers [ "user2" ] . PlaintextPassword )
// as a different user: set new password with different user's old password (ensure
// any other user that is not admin cannot change another user's password)
newRawPwd = userRawPwd + "3"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( newRawPwd ) ,
Password : ptr . String ( testUsers [ "user2" ] . PlaintextPassword ) ,
} , http . StatusForbidden , & modResp )
s . token = s . getTestAdminToken ( )
// as an admin, set a new email, name and password without a current password
newRawPwd = userRawPwd + "4"
modResp = modifyUserResponse { }
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( newRawPwd ) ,
Email : ptr . String ( "moduser3@example.com" ) ,
Name : ptr . String ( "moduser3" ) ,
} , http . StatusOK , & modResp )
require . Equal ( t , u . ID , modResp . User . ID )
require . Equal ( t , "moduser3" , modResp . User . Name )
// as an admin, set new password that doesn't meet requirements
invalidUserPwd := "abc"
2022-04-05 15:35:53 +00:00
s . DoJSON ( "PATCH" , fmt . Sprintf ( "/api/latest/fleet/users/%d" , u . ID ) , fleet . UserPayload {
2022-03-15 12:11:53 +00:00
NewPassword : ptr . String ( invalidUserPwd ) ,
} , http . StatusUnprocessableEntity , & modResp )
// login as the user, with the last password successfully set (to confirm it is the current one)
var loginResp loginResponse
2022-04-05 15:35:53 +00:00
resp := s . DoRawNoAuth ( "POST" , "/api/latest/fleet/login" , jsonMustMarshal ( t , loginRequest {
2022-03-15 12:11:53 +00:00
Email : u . Email , // all email changes made are still pending, never confirmed
Password : newRawPwd ,
} ) , http . StatusOK )
require . NoError ( t , json . NewDecoder ( resp . Body ) . Decode ( & loginResp ) )
resp . Body . Close ( )
require . Equal ( t , u . ID , loginResp . User . ID )
}
2022-04-26 18:16:59 +00:00
func ( s * integrationTestSuite ) TestGetHostLastOpenedAt ( ) {
t := s . T ( )
host , err := s . ds . NewHost ( context . Background ( ) , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "1" ,
UUID : t . Name ( ) + "1" ,
Hostname : t . Name ( ) + "foo.local" ,
PrimaryIP : "192.168.1.1" ,
PrimaryMac : "30-65-EC-6F-C4-58" ,
} )
require . NoError ( t , err )
require . NotNil ( t , host )
today := time . Now ( )
yesterday := today . Add ( - 24 * time . Hour )
software := [ ] fleet . Software {
{ Name : "foo" , Version : "0.0.1" , Source : "chrome_extensions" } ,
{ Name : "bar" , Version : "0.0.3" , Source : "apps" , LastOpenedAt : & today } ,
{ Name : "baz" , Version : "0.0.4" , Source : "apps" , LastOpenedAt : & yesterday } ,
}
require . NoError ( t , s . ds . UpdateHostSoftware ( context . Background ( ) , host . ID , software ) )
var getHostResp getHostResponse
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host . ID ) , nil , http . StatusOK , & getHostResp )
require . Equal ( t , host . ID , getHostResp . Host . ID )
require . Len ( t , getHostResp . Host . Software , len ( software ) )
sort . Slice ( getHostResp . Host . Software , func ( l , r int ) bool {
lsw , rsw := getHostResp . Host . Software [ l ] , getHostResp . Host . Software [ r ]
return lsw . Name < rsw . Name
} )
// bar, baz, foo, in this order
2022-05-02 20:58:34 +00:00
wantTs := [ ] time . Time { today , yesterday , { } }
2022-04-26 18:16:59 +00:00
for i , want := range wantTs {
sw := getHostResp . Host . Software [ i ]
if want . IsZero ( ) {
require . Nil ( t , sw . LastOpenedAt )
} else {
require . WithinDuration ( t , want , * sw . LastOpenedAt , time . Second )
}
}
// listing hosts does not return the last opened at timestamp, only the GET /hosts/{id} endpoint
var listHostsResp listHostsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & listHostsResp )
var hostSeen bool
for _ , h := range listHostsResp . Hosts {
if h . ID == host . ID {
hostSeen = true
}
for _ , sw := range h . Software {
require . Nil ( t , sw . LastOpenedAt )
}
}
require . True ( t , hostSeen )
}
2022-03-15 19:14:42 +00:00
func ( s * integrationTestSuite ) TestHostsReportDownload ( ) {
t := s . T ( )
2022-05-23 19:35:05 +00:00
ctx := context . Background ( )
2022-03-15 19:14:42 +00:00
hosts := s . createHosts ( t )
2022-03-16 13:01:52 +00:00
err := s . ds . ApplyLabelSpecs ( context . Background ( ) , [ ] * fleet . LabelSpec {
{ Name : t . Name ( ) , LabelMembershipType : fleet . LabelMembershipTypeManual , Query : "select 1" , Hosts : [ ] string { hosts [ 2 ] . Hostname } } ,
} )
require . NoError ( t , err )
lids , err := s . ds . LabelIDsByName ( context . Background ( ) , [ ] string { t . Name ( ) } )
require . NoError ( t , err )
require . Len ( t , lids , 1 )
customLabelID := lids [ 0 ]
2022-03-15 19:14:42 +00:00
2022-05-23 19:35:05 +00:00
// create a policy and make host[1] fail that policy
pol , err := s . ds . NewGlobalPolicy ( ctx , nil , fleet . PolicyPayload { Name : t . Name ( ) , Query : "SELECT 1" } )
require . NoError ( t , err )
err = s . ds . RecordPolicyQueryExecutions ( ctx , hosts [ 1 ] , map [ uint ] * bool { pol . ID : ptr . Bool ( false ) } , time . Now ( ) , false )
require . NoError ( t , err )
// create some device mappings for host[2]
err = s . ds . ReplaceHostDeviceMapping ( ctx , hosts [ 2 ] . ID , [ ] * fleet . HostDeviceMapping {
{ HostID : hosts [ 2 ] . ID , Email : "a@b.c" , Source : "google_chrome_profiles" } ,
{ HostID : hosts [ 2 ] . ID , Email : "b@b.c" , Source : "google_chrome_profiles" } ,
} )
require . NoError ( t , err )
2022-04-05 15:35:53 +00:00
res := s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusUnsupportedMediaType , "format" , "gzip" )
2022-03-15 19:14:42 +00:00
var errs struct {
Message string ` json:"message" `
Errors [ ] struct {
Name string ` json:"name" `
Reason string ` json:"reason" `
} ` json:"errors" `
}
require . NoError ( t , json . NewDecoder ( res . Body ) . Decode ( & errs ) )
res . Body . Close ( )
require . Len ( t , errs . Errors , 1 )
assert . Equal ( t , "format" , errs . Errors [ 0 ] . Name )
2022-05-10 18:25:53 +00:00
// valid format, no column specified so all columns returned
2022-04-05 15:35:53 +00:00
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" )
2022-03-15 19:14:42 +00:00
rows , err := csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
2022-05-10 18:25:53 +00:00
require . Len ( t , rows , len ( hosts ) + 1 ) // all hosts + header row
2022-05-23 19:35:05 +00:00
require . Len ( t , rows [ 0 ] , 44 ) // total number of cols
2022-05-10 18:25:53 +00:00
t . Log ( rows [ 0 ] )
2022-05-31 10:19:57 +00:00
const (
idCol = 2
issuesCol = 40
)
2022-05-23 19:35:05 +00:00
// find the row for hosts[1], it should have issues=1 (1 failing policy)
for _ , row := range rows [ 1 : ] {
if row [ idCol ] == fmt . Sprint ( hosts [ 1 ] . ID ) {
require . Equal ( t , "1" , row [ issuesCol ] , row )
} else {
require . Equal ( t , "0" , row [ issuesCol ] , row )
}
}
2022-05-10 18:25:53 +00:00
// valid format, some columns
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "columns" , "hostname" )
rows , err = csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
2022-03-15 19:14:42 +00:00
require . Len ( t , rows , len ( hosts ) + 1 )
require . Contains ( t , rows [ 0 ] , "hostname" ) // first row contains headers
require . Contains ( t , res . Header , "Content-Disposition" )
require . Contains ( t , res . Header , "Content-Type" )
require . Contains ( t , res . Header . Get ( "Content-Disposition" ) , "attachment;" )
require . Contains ( t , res . Header . Get ( "Content-Type" ) , "text/csv" )
2022-03-16 13:01:52 +00:00
// pagination does not apply to this endpoint, it returns the complete list of hosts
2022-05-10 18:25:53 +00:00
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "page" , "1" , "per_page" , "2" , "columns" , "hostname" )
2022-03-16 13:01:52 +00:00
rows , err = csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
require . Len ( t , rows , len ( hosts ) + 1 )
// search criteria are applied
2022-05-10 18:25:53 +00:00
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "query" , "local0" , "columns" , "hostname" )
2022-03-16 13:01:52 +00:00
rows , err = csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
require . Len ( t , rows , 2 ) // headers + matching host
require . Contains ( t , rows [ 1 ] , hosts [ 0 ] . Hostname )
2022-05-23 19:35:05 +00:00
// with device mapping results
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "columns" , "id,hostname,device_mapping" )
rawCSV , err := io . ReadAll ( res . Body )
2022-05-31 10:19:57 +00:00
require . NoError ( t , err )
2022-05-23 19:35:05 +00:00
require . Contains ( t , string ( rawCSV ) , ` "a@b.c,b@b.c" ` ) // inside quotes because it contains a comma
rows , err = csv . NewReader ( bytes . NewReader ( rawCSV ) ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
require . Len ( t , rows , len ( hosts ) + 1 )
for _ , row := range rows [ 1 : ] {
if row [ 0 ] == fmt . Sprint ( hosts [ 2 ] . ID ) {
require . Equal ( t , "a@b.c,b@b.c" , row [ 2 ] , row )
} else {
require . Equal ( t , "" , row [ 2 ] , row )
}
}
2022-03-16 13:01:52 +00:00
// with a label id
2022-05-10 18:25:53 +00:00
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "columns" , "hostname" , "label_id" , fmt . Sprintf ( "%d" , customLabelID ) )
2022-03-16 13:01:52 +00:00
rows , err = csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
require . Len ( t , rows , 2 ) // headers + member host
require . Contains ( t , rows [ 1 ] , hosts [ 2 ] . Hostname )
2022-05-10 18:25:53 +00:00
// valid format but an invalid column is provided
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusBadRequest , "format" , "csv" , "columns" , "memory,hostname,status,nosuchcolumn" )
require . NoError ( t , json . NewDecoder ( res . Body ) . Decode ( & errs ) )
res . Body . Close ( )
require . Len ( t , errs . Errors , 1 )
require . Contains ( t , errs . Errors [ 0 ] . Reason , "nosuchcolumn" )
// valid format, valid columns, order is respected, sorted
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "order_key" , "hostname" , "order_direction" , "desc" , "columns" , "memory,hostname,status" )
rows , err = csv . NewReader ( res . Body ) . ReadAll ( )
res . Body . Close ( )
require . NoError ( t , err )
require . Len ( t , rows , len ( hosts ) + 1 )
require . Equal ( t , [ ] string { "memory" , "hostname" , "status" } , rows [ 0 ] ) // first row contains headers
require . Len ( t , rows [ 1 ] , 3 )
// status is timing-dependent, ignore in the assertion
require . Equal ( t , [ ] string { "0" , "TestIntegrations/TestHostsReportDownloadfoo.local2" } , rows [ 1 ] [ : 2 ] )
require . Len ( t , rows [ 2 ] , 3 )
require . Equal ( t , [ ] string { "0" , "TestIntegrations/TestHostsReportDownloadfoo.local1" } , rows [ 2 ] [ : 2 ] )
require . Len ( t , rows [ 3 ] , 3 )
require . Equal ( t , [ ] string { "0" , "TestIntegrations/TestHostsReportDownloadfoo.local0" } , rows [ 3 ] [ : 2 ] )
t . Log ( rows )
2022-03-15 19:14:42 +00:00
}
2022-04-05 15:35:53 +00:00
// this test can be deleted once the "v1" version is removed.
func ( s * integrationTestSuite ) TestAPIVersion_v1_2022_04 ( ) {
t := s . T ( )
// create a query that can be scheduled
qr , err := s . ds . NewQuery ( context . Background ( ) , & fleet . Query {
Name : "TestQuery2" ,
Query : "select * from osquery;" ,
ObserverCanRun : true ,
} )
require . NoError ( t , err )
// try to schedule that query on the endpoint that is deprecated
// in that version
gsParams := fleet . ScheduledQueryPayload { QueryID : ptr . Uint ( qr . ID ) , Interval : ptr . Uint ( 42 ) }
res := s . DoRaw ( "POST" , "/api/2022-04/fleet/global/schedule" , jsonMustMarshal ( t , gsParams ) , http . StatusNotFound )
res . Body . Close ( )
// use the correct version for that deprecated API
createResp := globalScheduleQueryResponse { }
s . DoJSON ( "POST" , "/api/v1/fleet/global/schedule" , gsParams , http . StatusOK , & createResp )
require . NotZero ( t , createResp . Scheduled . ID )
// list the scheduled queries with the new endpoint, but the old version
res = s . DoRaw ( "GET" , "/api/v1/fleet/schedule" , nil , http . StatusMethodNotAllowed )
res . Body . Close ( )
// list again, this time with the correct version
gs := fleet . GlobalSchedulePayload { }
s . DoJSON ( "GET" , "/api/2022-04/fleet/schedule" , nil , http . StatusOK , & gs )
require . Len ( t , gs . GlobalSchedule , 1 )
// delete using the old endpoint but on the wrong new version
res = s . DoRaw ( "DELETE" , fmt . Sprintf ( "/api/2022-04/fleet/global/schedule/%d" , createResp . Scheduled . ID ) , nil , http . StatusNotFound )
res . Body . Close ( )
// properly delete with old endpoint and old version
var delResp deleteGlobalScheduleResponse
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/v1/fleet/global/schedule/%d" , createResp . Scheduled . ID ) , nil , http . StatusOK , & delResp )
}
2022-01-25 14:34:00 +00:00
// creates a session and returns it, its key is to be passed as authorization header.
func createSession ( t * testing . T , uid uint , ds fleet . Datastore ) * fleet . Session {
key := make ( [ ] byte , 64 )
_ , err := rand . Read ( key )
require . NoError ( t , err )
sessionKey := base64 . StdEncoding . EncodeToString ( key )
Add read replica testing helpers and fix non-sso login bug (#4908)
not set on the INSERT.
- OUT: Only sets the ID on the passed session and returns it. (`CreatedAt`, `AccessedAt`, are not set.)
New version:
```go
func (ds *Datastore) NewSession(ctx context.Context, userID uint, sessionKey string) (*fleet.Session, error) {
sqlStatement := `
INSERT INTO sessions (
user_id,
` + "`key`" + `
)
VALUES(?,?)
`
result, err := ds.writer.ExecContext(ctx, sqlStatement, userID, sessionKey)
if err != nil {
return nil, ctxerr.Wrap(ctx, err, "inserting session")
}
id, _ := result.LastInsertId() // cannot fail with the mysql driver
return ds.sessionByID(ctx, ds.writer, uint(id))
}
```
- IN: Define arguments that are truly used when creating a session.
- OUT: Load and return the fleet.Session struct with all values set (using the `ds.writer` to support read replicas correctly).
PS: The new `NewSession` version mimics what we already do with other entities, like policies (`Datastore.NewGlobalPolicy`).
2022-04-04 23:52:05 +00:00
ssn , err := ds . NewSession ( context . Background ( ) , uid , sessionKey )
2022-01-25 14:34:00 +00:00
require . NoError ( t , err )
return ssn
}
2022-01-31 21:35:22 +00:00
func cleanupQuery ( s * integrationTestSuite , queryID uint ) {
var delResp deleteQueryByIDResponse
2022-04-05 15:35:53 +00:00
s . DoJSON ( "DELETE" , fmt . Sprintf ( "/api/latest/fleet/queries/id/%d" , queryID ) , nil , http . StatusOK , & delResp )
2022-01-31 21:35:22 +00:00
}
2022-03-08 16:27:38 +00:00
func jsonMustMarshal ( t testing . TB , v interface { } ) [ ] byte {
b , err := json . Marshal ( v )
require . NoError ( t , err )
return b
}
2022-04-06 11:55:25 +00:00
2022-06-06 14:41:51 +00:00
// starts a test web server that mocks responses to requests to external
// services with a valid payload (if the request is valid) or a status code
// error. It returns the URL to use to make requests to that server.
//
// For Jira, the project keys "qux" and "qux2" are supported.
// For Zendesk, the group IDs "122" and "123" are supported.
//
// The basic auth's user (or password for Zendesk) "ok" means that auth is
// allowed, while "fail" means unauthorized and anything else results in status
// 502.
func startExternalServiceWebServer ( t * testing . T ) string {
// create a test http server to act as the Jira and Zendesk server
srv := httptest . NewServer ( http . HandlerFunc ( func ( w http . ResponseWriter , r * http . Request ) {
if r . Method != "GET" {
w . WriteHeader ( 501 )
return
}
switch r . URL . Path {
case "/rest/api/2/project/qux" :
switch usr , _ , _ := r . BasicAuth ( ) ; usr {
case "ok" :
w . Write ( [ ] byte ( jiraProjectResponsePayload ) )
case "fail" :
w . WriteHeader ( http . StatusUnauthorized )
default :
w . WriteHeader ( 502 )
}
case "/rest/api/2/project/qux2" :
switch usr , _ , _ := r . BasicAuth ( ) ; usr {
case "ok" :
w . Write ( [ ] byte ( jiraProjectResponsePayload ) )
case "fail" :
w . WriteHeader ( http . StatusUnauthorized )
default :
w . WriteHeader ( 502 )
}
case "/api/v2/groups/122.json" :
switch _ , pwd , _ := r . BasicAuth ( ) ; pwd {
case "ok" :
w . Write ( [ ] byte ( ` { "group": { "id": 122,"name": "test122"}} ` ) )
case "fail" :
w . WriteHeader ( http . StatusUnauthorized )
default :
w . WriteHeader ( 502 )
}
case "/api/v2/groups/123.json" :
switch _ , pwd , _ := r . BasicAuth ( ) ; pwd {
case "ok" :
w . Write ( [ ] byte ( ` { "group": { "id": 123,"name": "test123"}} ` ) )
case "fail" :
w . WriteHeader ( http . StatusUnauthorized )
default :
w . WriteHeader ( 502 )
}
default :
w . WriteHeader ( 502 )
}
} ) )
t . Cleanup ( srv . Close )
return srv . URL
}
2022-04-06 11:55:25 +00:00
const (
// example response from the Jira docs
jiraProjectResponsePayload = ` {
"self" : "https://your-domain.atlassian.net/rest/api/2/project/EX" ,
"id" : "10000" ,
"key" : "EX" ,
"description" : "This project was created as an example for REST." ,
"lead" : {
"self" : "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" ,
"key" : "" ,
"accountId" : "5b10a2844c20165700ede21g" ,
"accountType" : "atlassian" ,
"name" : "" ,
"avatarUrls" : {
"48x48" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48" ,
"24x24" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24" ,
"16x16" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16" ,
"32x32" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32"
} ,
"displayName" : "Mia Krystof" ,
"active" : false
} ,
"components" : [
{
"self" : "https://your-domain.atlassian.net/rest/api/2/component/10000" ,
"id" : "10000" ,
"name" : "Component 1" ,
"description" : "This is a Jira component" ,
"lead" : {
"self" : "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" ,
"key" : "" ,
"accountId" : "5b10a2844c20165700ede21g" ,
"accountType" : "atlassian" ,
"name" : "" ,
"avatarUrls" : {
"48x48" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48" ,
"24x24" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24" ,
"16x16" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16" ,
"32x32" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32"
} ,
"displayName" : "Mia Krystof" ,
"active" : false
} ,
"assigneeType" : "PROJECT_LEAD" ,
"assignee" : {
"self" : "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" ,
"key" : "" ,
"accountId" : "5b10a2844c20165700ede21g" ,
"accountType" : "atlassian" ,
"name" : "" ,
"avatarUrls" : {
"48x48" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48" ,
"24x24" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24" ,
"16x16" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16" ,
"32x32" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32"
} ,
"displayName" : "Mia Krystof" ,
"active" : false
} ,
"realAssigneeType" : "PROJECT_LEAD" ,
"realAssignee" : {
"self" : "https://your-domain.atlassian.net/rest/api/2/user?accountId=5b10a2844c20165700ede21g" ,
"key" : "" ,
"accountId" : "5b10a2844c20165700ede21g" ,
"accountType" : "atlassian" ,
"name" : "" ,
"avatarUrls" : {
"48x48" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=48&s=48" ,
"24x24" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=24&s=24" ,
"16x16" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=16&s=16" ,
"32x32" : "https://avatar-management--avatars.server-location.prod.public.atl-paas.net/initials/MK-5.png?size=32&s=32"
} ,
"displayName" : "Mia Krystof" ,
"active" : false
} ,
"isAssigneeTypeValid" : false ,
"project" : "HSP" ,
"projectId" : 10000
}
] ,
"issueTypes" : [
{
"self" : "https://your-domain.atlassian.net/rest/api/2/issueType/3" ,
"id" : "3" ,
"description" : "A task that needs to be done." ,
"iconUrl" : "https://your-domain.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10299&avatarType=issuetype\"," ,
"name" : "Task" ,
"subtask" : false ,
"avatarId" : 1 ,
"hierarchyLevel" : 0
} ,
{
"self" : "https://your-domain.atlassian.net/rest/api/2/issueType/1" ,
"id" : "1" ,
"description" : "A problem with the software." ,
"iconUrl" : "https://your-domain.atlassian.net/secure/viewavatar?size=xsmall&avatarId=10316&avatarType=issuetype\"," ,
"name" : "Bug" ,
"subtask" : false ,
"avatarId" : 10002 ,
"entityId" : "9d7dd6f7-e8b6-4247-954b-7b2c9b2a5ba2" ,
"hierarchyLevel" : 0 ,
"scope" : {
"type" : "PROJECT" ,
"project" : {
"id" : "10000" ,
"key" : "KEY" ,
"name" : "Next Gen Project"
}
}
}
] ,
"url" : "https://www.example.com" ,
"email" : "from-jira@example.com" ,
"assigneeType" : "PROJECT_LEAD" ,
"versions" : [ ] ,
"name" : "Example" ,
"roles" : {
"Developers" : "https://your-domain.atlassian.net/rest/api/2/project/EX/role/10000"
} ,
"avatarUrls" : {
"48x48" : "https://your-domain.atlassian.net/secure/projectavatar?size=large&pid=10000" ,
"24x24" : "https://your-domain.atlassian.net/secure/projectavatar?size=small&pid=10000" ,
"16x16" : "https://your-domain.atlassian.net/secure/projectavatar?size=xsmall&pid=10000" ,
"32x32" : "https://your-domain.atlassian.net/secure/projectavatar?size=medium&pid=10000"
} ,
"projectCategory" : {
"self" : "https://your-domain.atlassian.net/rest/api/2/projectCategory/10000" ,
"id" : "10000" ,
"name" : "FIRST" ,
"description" : "First Project Category"
} ,
"simplified" : false ,
"style" : "classic" ,
"properties" : {
"propertyKey" : "propertyValue"
} ,
"insight" : {
"totalIssueCount" : 100 ,
"lastIssueUpdateTime" : "2022-04-05T04:51:35.670+0000"
}
} `
)