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"
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-10-11 16:58:52 +00:00
"github.com/fleetdm/fleet/v4/pkg/fleethttp"
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-09-21 19:16:31 +00:00
s . withServer . commonTearDownTest ( s . T ( ) )
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 )
}
2022-10-11 16:58:52 +00:00
type slowReader struct { }
func ( s * slowReader ) Read ( p [ ] byte ) ( n int , err error ) {
time . Sleep ( 3 * time . Second )
return 0 , nil
}
func ( s * integrationTestSuite ) TestSlowOsqueryHost ( ) {
t := s . T ( )
2022-10-19 10:42:21 +00:00
_ , server := RunServerForTestsWithDS (
t ,
s . ds ,
& TestServerOpts {
SkipCreateTestUsers : true ,
//nolint:gosec // G112: server is just run for testing this explicit config.
HTTPServerConfig : & http . Server { ReadTimeout : 2 * time . Second } ,
} ,
)
2022-10-11 16:58:52 +00:00
defer func ( ) {
2022-10-19 10:42:21 +00:00
server . Close ( )
2022-10-11 16:58:52 +00:00
} ( )
2022-10-19 10:42:21 +00:00
req , err := http . NewRequest ( "POST" , server . URL + "/api/v1/osquery/distributed/write" , & slowReader { } )
2022-10-11 16:58:52 +00:00
require . NoError ( t , err )
client := fleethttp . NewClient ( )
resp , err := client . Do ( req )
require . NoError ( t , err )
assert . Equal ( t , http . StatusRequestTimeout , resp . StatusCode )
}
2021-09-15 19:27:53 +00:00
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" )
}
2022-10-03 16:29:01 +00:00
func ( s * integrationTestSuite ) TestUserEmailValidation ( ) {
params := fleet . UserPayload {
Name : ptr . String ( "user_invalid_email" ) ,
Email : ptr . String ( "invalid" ) ,
Password : & test . GoodPassword ,
GlobalRole : ptr . String ( fleet . RoleObserver ) ,
}
s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusUnprocessableEntity )
params . Email = ptr . String ( "user_valid_mail@example.com" )
s . Do ( "POST" , "/api/latest/fleet/users/admin" , & params , http . StatusOK )
}
2021-09-15 19:27:53 +00:00
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
2022-08-25 16:41:50 +00:00
features :
2021-09-15 19:27:53 +00:00
additional_queries :
time : SELECT * FROM time
enable_host_users : true
` )
s . applyConfig ( spec )
spec = [ ] byte ( `
2022-08-25 16:41:50 +00:00
features :
2021-09-15 19:27:53 +00:00
enable_host_users : true
additional_queries : null
` )
s . applyConfig ( spec )
config := s . getConfig ( )
2022-08-25 16:41:50 +00:00
assert . Nil ( t , config . Features . AdditionalQueries )
2021-09-15 19:27:53 +00:00
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 )
} )
}
2022-08-25 16:41:50 +00:00
func ( s * integrationTestSuite ) TestAppConfigDeprecatedFields ( ) {
t := s . T ( )
spec := [ ] byte ( `
host_settings :
additional_queries :
time : SELECT * FROM time
enable_host_users : true
enable_software_inventory : true
` )
s . applyConfig ( spec )
config := s . getConfig ( )
require . NotNil ( t , config . Features . AdditionalQueries )
require . True ( t , config . Features . EnableHostUsers )
require . True ( t , config . Features . EnableSoftwareInventory )
spec = [ ] byte ( `
host_settings :
additional_queries : null
enable_host_users : false
enable_software_inventory : false
` )
s . applyConfig ( spec )
config = s . getConfig ( )
require . Nil ( t , config . Features . AdditionalQueries )
require . False ( t , config . Features . EnableHostUsers )
require . False ( t , config . Features . EnableSoftwareInventory )
// Test raw API interactions
appConfigSpec := map [ string ] map [ string ] bool {
"host_settings" : { "enable_software_inventory" : true } ,
"server_settings" : { "enable_analytics" : false } ,
}
s . Do ( "PATCH" , "/api/latest/fleet/config" , appConfigSpec , http . StatusOK )
config = s . getConfig ( )
require . True ( t , config . Features . EnableSoftwareInventory )
// Skip our serialization mechanism, to make sure an old config stored in the DB is still valid
2022-09-19 17:53:44 +00:00
var previousRawConfig string
2022-08-25 16:41:50 +00:00
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
2022-09-19 17:53:44 +00:00
err := sqlx . GetContext ( context . Background ( ) , q , & previousRawConfig , "SELECT json_value FROM app_config_json" )
if err != nil {
return err
}
2022-08-25 16:41:50 +00:00
insertAppConfigQuery := ` INSERT INTO app_config_json(json_value) VALUES(?) ON DUPLICATE KEY UPDATE json_value = VALUES(json_value) `
2022-09-19 17:53:44 +00:00
_ , err = q . ExecContext ( context . Background ( ) , insertAppConfigQuery , `
2022-08-25 16:41:50 +00:00
{
"host_settings" : {
"enable_host_users" : false ,
"enable_software_inventory" : true ,
"additional_queries" : { "foo" : "bar" }
}
} ` )
return err
} )
var resp appConfigResponse
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & resp )
require . False ( t , resp . Features . EnableHostUsers )
require . True ( t , resp . Features . EnableSoftwareInventory )
require . NotNil ( t , resp . Features . AdditionalQueries )
2022-09-19 17:53:44 +00:00
// restore the previous appconfig so that other tests are not impacted
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
insertAppConfigQuery := ` INSERT INTO app_config_json(json_value) VALUES(?) ON DUPLICATE KEY UPDATE json_value = VALUES(json_value) `
_ , err := q . ExecContext ( context . Background ( ) , insertAppConfigQuery , previousRawConfig )
return err
} )
2022-08-25 16:41:50 +00:00
}
2021-09-15 19:27:53 +00:00
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 ]
}
2022-10-28 15:12:21 +00:00
n , err := s . ds . InsertSoftwareVulnerabilities (
2022-06-08 01:09:47 +00:00
context . Background ( ) , [ ] fleet . SoftwareVulnerability {
{
SoftwareID : soft1 . ID ,
CVE : "cve-123-123-132" ,
} ,
2022-06-23 20:44:45 +00:00
} , fleet . NVDSource ,
2022-06-08 01:09:47 +00:00
)
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
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-08-10 21:43:22 +00:00
// calculate hosts counts
hostsCountTs := time . Now ( ) . UTC ( )
require . NoError ( t , s . ds . SyncHostsSoftware ( context . Background ( ) , hostsCountTs ) )
countReq := countSoftwareRequest { }
countResp := countSoftwareResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/software/count" , countReq , http . StatusOK , & countResp )
assert . Equal ( t , 3 , countResp . Count )
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
// 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
2022-09-21 19:16:31 +00:00
func ( s * integrationTestSuite ) TestHostsCount ( ) {
2021-10-07 11:25:35 +00:00
t := s . T ( )
hosts := s . createHosts ( t )
2022-09-21 19:16:31 +00:00
// set disk space information for some hosts
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( context . Background ( ) , hosts [ 0 ] . ID , 10.0 , 2.0 ) ) // low disk
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( context . Background ( ) , hosts [ 1 ] . ID , 40.0 , 4.0 ) ) // not low disk
2021-10-07 11:25:35 +00:00
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 )
2022-08-10 19:15:01 +00:00
2022-09-21 19:16:31 +00:00
// filter by low_disk_space criteria is ignored (premium-only filter)
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "low_disk_space" , "32" )
require . Equal ( t , len ( hosts ) , resp . Count )
// but it is still validated for a correct value when provided (as that happens in a middleware before the handler)
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusInternalServerError , & resp , "low_disk_space" , "123456" ) // TODO: status code to be fixed with #4406
2022-08-10 19:15:01 +00:00
// filter by MDM criteria without any host having such information
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_id" , fmt . Sprint ( 999 ) )
require . Equal ( t , 0 , resp . Count )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "manual" )
require . Equal ( t , 0 , resp . Count )
// set MDM information on a host
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( context . Background ( ) , hosts [ 1 ] . ID , true , "https://simplemdm.com" , false , "" ) )
2022-08-10 19:15:01 +00:00
var mdmID uint
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , q , & mdmID ,
` SELECT id FROM mobile_device_management_solutions WHERE name = ? AND server_url = ? ` , fleet . WellKnownMDMSimpleMDM , "https://simplemdm.com" )
} )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_id" , fmt . Sprint ( mdmID ) )
require . Equal ( t , 1 , resp . Count )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "manual" )
require . Equal ( t , 1 , resp . Count )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "automatic" )
require . Equal ( t , 0 , resp . Count )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "unenrolled" )
require . Equal ( t , 0 , resp . Count )
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "manual" , "mdm_id" , fmt . Sprint ( mdmID ) )
require . Equal ( t , 1 , resp . Count )
2021-10-07 11:25:35 +00:00
}
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 )
2022-09-21 19:16:31 +00:00
// set disk space information for some hosts
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( context . Background ( ) , hosts [ 0 ] . ID , 10.0 , 2.0 ) ) // low disk
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( context . Background ( ) , hosts [ 1 ] . ID , 40.0 , 4.0 ) ) // not low disk
2021-10-11 14:37:48 +00:00
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-09-21 19:16:31 +00:00
for _ , h := range resp . Hosts {
switch h . ID {
case hosts [ 0 ] . ID :
assert . Equal ( t , 10.0 , h . GigsDiskSpaceAvailable )
assert . Equal ( t , 2.0 , h . PercentDiskSpaceAvailable )
case hosts [ 1 ] . ID :
assert . Equal ( t , 40.0 , h . GigsDiskSpaceAvailable )
assert . Equal ( t , 4.0 , h . PercentDiskSpaceAvailable )
}
}
// setting the low_disk_space criteria is ignored (premium-only)
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "low_disk_space" , "32" )
require . Len ( t , resp . Hosts , len ( hosts ) )
2021-10-11 14:37:48 +00:00
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
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 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2021-10-12 14:38:12 +00:00
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
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-08-15 16:57:25 +00:00
resp = listHostsResponse { }
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-08-15 16:57:25 +00:00
resp = listHostsResponse { }
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-08-15 16:57:25 +00:00
resp = listHostsResponse { }
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 )
2022-08-10 19:15:01 +00:00
// filter by MDM criteria without any host having such information
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_id" , fmt . Sprint ( 999 ) )
require . Len ( t , resp . Hosts , 0 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "manual" )
require . Len ( t , resp . Hosts , 0 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
// and same by munki issue id
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "munki_issue_id" , fmt . Sprint ( 999 ) )
require . Len ( t , resp . Hosts , 0 )
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
assert . Nil ( t , resp . MunkiIssue )
2022-08-10 19:15:01 +00:00
// set MDM information on a host
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( context . Background ( ) , host . ID , true , "https://simplemdm.com" , false , "" ) )
2022-08-10 19:15:01 +00:00
var mdmID uint
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , q , & mdmID ,
` SELECT id FROM mobile_device_management_solutions WHERE name = ? AND server_url = ? ` , fleet . WellKnownMDMSimpleMDM , "https://simplemdm.com" )
} )
2022-08-15 16:57:25 +00:00
// generate aggregated stats
require . NoError ( t , s . ds . GenerateAggregatedMunkiAndMDM ( context . Background ( ) ) )
2022-08-10 19:15:01 +00:00
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "manual" )
require . Len ( t , resp . Hosts , 1 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "automatic" )
require . Len ( t , resp . Hosts , 0 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_enrollment_status" , "unenrolled" )
require . Len ( t , resp . Hosts , 0 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_id" , fmt . Sprint ( mdmID ) )
require . Len ( t , resp . Hosts , 1 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
require . NotNil ( t , resp . MDMSolution )
assert . Equal ( t , mdmID , resp . MDMSolution . ID )
assert . Equal ( t , fleet . WellKnownMDMSimpleMDM , resp . MDMSolution . Name )
assert . Equal ( t , "https://simplemdm.com" , resp . MDMSolution . ServerURL )
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "mdm_id" , fmt . Sprint ( mdmID ) , "mdm_enrollment_status" , "manual" )
require . Len ( t , resp . Hosts , 1 )
2022-08-15 16:57:25 +00:00
assert . Nil ( t , resp . Software )
2022-08-29 18:40:16 +00:00
assert . Nil ( t , resp . MunkiIssue )
2022-08-15 16:57:25 +00:00
assert . NotNil ( t , resp . MDMSolution )
assert . Equal ( t , mdmID , resp . MDMSolution . ID )
2022-08-10 19:15:01 +00:00
2022-08-15 16:57:25 +00:00
resp = listHostsResponse { }
2022-08-10 19:15:01 +00:00
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusInternalServerError , & resp , "mdm_enrollment_status" , "invalid-status" ) // TODO: to be addressed by #4406
2022-08-22 19:34:00 +00:00
2022-08-29 18:40:16 +00:00
// set munki information on a host
require . NoError ( t , s . ds . SetOrUpdateMunkiInfo ( context . Background ( ) , host . ID , "1.2.3" , [ ] string { "err" } , [ ] string { "warn" } ) )
var errMunkiID uint
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , q , & errMunkiID ,
` SELECT id FROM munki_issues WHERE name = 'err' AND issue_type = 'error' ` )
} )
// generate aggregated stats
require . NoError ( t , s . ds . GenerateAggregatedMunkiAndMDM ( context . Background ( ) ) )
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "munki_issue_id" , fmt . Sprint ( errMunkiID ) )
require . Len ( t , resp . Hosts , 1 )
assert . Nil ( t , resp . Software )
assert . Nil ( t , resp . MDMSolution )
require . NotNil ( t , resp . MunkiIssue )
2022-09-06 14:34:06 +00:00
assert . Equal ( t , fleet . MunkiIssue {
ID : errMunkiID ,
Name : "err" ,
IssueType : "error" ,
2022-08-29 18:40:16 +00:00
} , * resp . MunkiIssue )
// filters can be combined, no problem
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "munki_issue_id" , fmt . Sprint ( errMunkiID ) , "mdm_id" , fmt . Sprint ( mdmID ) )
require . Len ( t , resp . Hosts , 1 )
assert . Nil ( t , resp . Software )
assert . NotNil ( t , resp . MDMSolution )
assert . NotNil ( t , resp . MunkiIssue )
2022-08-22 19:34:00 +00:00
// set operating system information on a host
testOS := fleet . OperatingSystem { Name : "fooOS" , Version : "4.2" , Arch : "64bit" , KernelVersion : "13.37" , Platform : "bar" }
require . NoError ( t , s . ds . UpdateHostOperatingSystem ( context . Background ( ) , host . ID , testOS ) )
var osID uint
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , q , & osID ,
` SELECT id FROM operating_systems WHERE name = ? AND version = ? ` , "fooOS" , "4.2" )
} )
require . Greater ( t , osID , uint ( 0 ) )
// generate aggregated stats
require . NoError ( t , s . ds . UpdateOSVersions ( context . Background ( ) ) )
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_name" , testOS . Name , "os_version" , testOS . Version )
require . Len ( t , resp . Hosts , 1 )
expected := resp . Hosts [ 0 ]
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_id" , fmt . Sprintf ( "%d" , osID ) )
require . Len ( t , resp . Hosts , 1 )
require . Equal ( t , expected , resp . Hosts [ 0 ] )
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_name" , "unknownOS" , "os_version" , "4.2" )
require . Len ( t , resp . Hosts , 0 )
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_id" , fmt . Sprintf ( "%d" , osID + 1337 ) )
require . Len ( t , resp . Hosts , 0 )
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 ( )
2022-09-21 19:16:31 +00:00
ctx := context . Background ( )
2021-11-09 14:35:36 +00:00
hosts := s . createHosts ( t )
2022-09-21 19:16:31 +00:00
team1 , err := s . ds . NewTeam ( ctx , & fleet . Team { Name : t . Name ( ) + "team1" } )
2021-11-09 14:35:36 +00:00
require . NoError ( t , err )
2022-09-21 19:16:31 +00:00
team2 , err := s . ds . NewTeam ( ctx , & fleet . Team { Name : t . Name ( ) + "team2" } )
2021-11-09 14:35:36 +00:00
require . NoError ( t , err )
2022-09-21 19:16:31 +00:00
require . NoError ( t , s . ds . AddHostsToTeam ( ctx , & team1 . ID , [ ] uint { hosts [ 0 ] . ID } ) )
// set disk space information for hosts [0] and [1]
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 0 ] . ID , 1.0 , 2.0 ) )
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 1 ] . ID , 3.0 , 4.0 ) )
var getHostResp getHostResponse
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , hosts [ 0 ] . ID ) , nil , http . StatusOK , & getHostResp )
assert . Equal ( t , 1.0 , getHostResp . Host . GigsDiskSpaceAvailable )
assert . Equal ( t , 2.0 , getHostResp . Host . PercentDiskSpaceAvailable )
2021-11-09 14:35:36 +00:00
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-09-21 19:56:17 +00:00
require . Nil ( t , resp . LowDiskSpaceCount )
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
2022-09-21 19:56:17 +00:00
// team filter, one host, low_disk_count is ignored as not premium
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusOK , & resp , "team_id" , fmt . Sprint ( team1 . ID ) , "low_disk_space" , "2" )
2021-11-09 14:35:36 +00:00
require . Equal ( t , resp . TotalsHostsCount , uint ( 1 ) )
2022-09-21 19:56:17 +00:00
require . Nil ( t , resp . LowDiskSpaceCount )
2021-11-09 14:35:36 +00:00
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 )
2022-09-21 19:56:17 +00:00
// invalid low_disk_space value is still validated and results in error
s . DoJSON ( "GET" , "/api/latest/fleet/host_summary" , nil , http . StatusInternalServerError , & resp , "low_disk_space" , "1234" ) // TODO: should be 400, see #4406
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 )
2022-10-12 12:35:36 +00:00
require . Len ( t , policiesResponse . InheritedPolicies , 0 )
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 )
2022-10-14 14:14:18 +00:00
hostsByID := make ( map [ uint ] fleet . HostResponse )
2022-05-02 21:34:14 +00:00
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 )
2022-10-14 14:14:18 +00:00
hostsByID := make ( map [ uint ] fleet . HostResponse )
2022-05-23 19:27:30 +00:00
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
2022-08-10 19:15:01 +00:00
type macadminsDataResponse struct {
Macadmins * struct {
2022-08-29 18:40:16 +00:00
Munki * fleet . HostMunkiInfo ` json:"munki" `
MunkiIssues [ ] * fleet . HostMunkiIssue ` json:"munki_issues" `
MDM * struct {
2022-08-10 19:15:01 +00:00
EnrollmentStatus string ` json:"enrollment_status" `
ServerURL string ` json:"server_url" `
Name * string ` json:"name" `
ID * uint ` json:"id" `
} ` json:"mobile_device_management" `
} ` json:"macadmins" `
}
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" ,
2022-11-01 17:22:07 +00:00
Platform : "darwin" ,
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" ,
2022-11-01 17:22:07 +00:00
Platform : "darwin" ,
2021-12-23 19:57:43 +00:00
} )
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" ,
2022-11-01 17:22:07 +00:00
Platform : "darwin" ,
2021-12-23 19:57:43 +00:00
} )
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" ,
2022-11-01 17:22:07 +00:00
Platform : "darwin" ,
2021-12-23 19:57:43 +00:00
} )
require . NoError ( t , err )
require . NotNil ( t , hostOnlyMDM )
2021-12-21 12:37:58 +00:00
2022-08-10 19:15:01 +00:00
hostMDMNoID , err := s . ds . NewHost ( ctx , & fleet . Host {
DetailUpdatedAt : time . Now ( ) ,
LabelUpdatedAt : time . Now ( ) ,
PolicyUpdatedAt : time . Now ( ) ,
SeenTime : time . Now ( ) ,
NodeKey : t . Name ( ) + "5" ,
UUID : t . Name ( ) + "5" ,
Hostname : t . Name ( ) + "foo.local5" ,
PrimaryIP : "192.168.1.5" ,
PrimaryMac : "30-65-EC-6F-D5-5A" ,
OsqueryHostID : "5" ,
2022-11-01 17:22:07 +00:00
Platform : "darwin" ,
2022-08-10 19:15:01 +00:00
} )
require . NoError ( t , err )
require . NotNil ( t , hostMDMNoID )
// insert a host_mdm row for hostMDMNoID without any mdm_id
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
_ , err := q . ExecContext ( ctx ,
` INSERT INTO host_mdm (host_id, enrolled, server_url, installed_from_dep) VALUES (?, ?, ?, ?) ` ,
hostMDMNoID . ID , true , "https://simplemdm.com" , true )
return err
} )
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , true , "url" , false , "" ) )
2022-08-29 18:40:16 +00:00
require . NoError ( t , s . ds . SetOrUpdateMunkiInfo ( ctx , hostAll . ID , "1.3.0" , [ ] string { "error1" } , [ ] string { "warning1" } ) )
2021-12-21 12:37:58 +00:00
2022-08-10 19:15:01 +00:00
macadminsData := macadminsDataResponse { }
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 )
2022-08-10 19:15:01 +00:00
assert . Nil ( t , macadminsData . Macadmins . MDM . Name )
require . NotNil ( t , macadminsData . Macadmins . MDM . ID )
assert . NotZero ( t , * macadminsData . Macadmins . MDM . ID )
2021-12-21 12:37:58 +00:00
assert . Equal ( t , "1.3.0" , macadminsData . Macadmins . Munki . Version )
2022-08-29 18:40:16 +00:00
require . Len ( t , macadminsData . Macadmins . MunkiIssues , 2 )
sort . Slice ( macadminsData . Macadmins . MunkiIssues , func ( i , j int ) bool {
l , r := macadminsData . Macadmins . MunkiIssues [ i ] , macadminsData . Macadmins . MunkiIssues [ j ]
return l . Name < r . Name
} )
assert . NotZero ( t , macadminsData . Macadmins . MunkiIssues [ 0 ] . MunkiIssueID )
assert . False ( t , macadminsData . Macadmins . MunkiIssues [ 0 ] . HostIssueCreatedAt . IsZero ( ) )
assert . Equal ( t , "error1" , macadminsData . Macadmins . MunkiIssues [ 0 ] . Name )
assert . Equal ( t , "error" , macadminsData . Macadmins . MunkiIssues [ 0 ] . IssueType )
assert . Equal ( t , "warning1" , macadminsData . Macadmins . MunkiIssues [ 1 ] . Name )
assert . NotZero ( t , macadminsData . Macadmins . MunkiIssues [ 1 ] . MunkiIssueID )
assert . False ( t , macadminsData . Macadmins . MunkiIssues [ 1 ] . HostIssueCreatedAt . IsZero ( ) )
assert . Equal ( t , "warning" , macadminsData . Macadmins . MunkiIssues [ 1 ] . IssueType )
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , true , "https://simplemdm.com" , true , "" ) )
2022-08-29 18:40:16 +00:00
require . NoError ( t , s . ds . SetOrUpdateMunkiInfo ( ctx , hostAll . ID , "1.5.0" , [ ] string { "error1" } , nil ) )
2021-12-21 12:37:58 +00:00
2022-08-10 19:15:01 +00:00
macadminsData = macadminsDataResponse { }
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 )
2022-08-10 19:15:01 +00:00
assert . Equal ( t , "https://simplemdm.com" , macadminsData . Macadmins . MDM . ServerURL )
2021-12-21 12:37:58 +00:00
assert . Equal ( t , "Enrolled (automated)" , macadminsData . Macadmins . MDM . EnrollmentStatus )
2022-08-10 19:15:01 +00:00
require . NotNil ( t , macadminsData . Macadmins . MDM . Name )
assert . Equal ( t , fleet . WellKnownMDMSimpleMDM , * macadminsData . Macadmins . MDM . Name )
require . NotNil ( t , macadminsData . Macadmins . MDM . ID )
assert . NotZero ( t , * macadminsData . Macadmins . MDM . ID )
2021-12-21 12:37:58 +00:00
assert . Equal ( t , "1.5.0" , macadminsData . Macadmins . Munki . Version )
2022-08-29 18:40:16 +00:00
require . Len ( t , macadminsData . Macadmins . MunkiIssues , 1 )
assert . Equal ( t , "error1" , macadminsData . Macadmins . MunkiIssues [ 0 ] . Name )
2021-12-21 12:37:58 +00:00
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostAll . ID , false , "url2" , false , "" ) )
2021-12-21 12:37:58 +00:00
2022-08-10 19:15:01 +00:00
macadminsData = macadminsDataResponse { }
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 )
2022-08-10 19:15:01 +00:00
assert . Nil ( t , macadminsData . Macadmins . MDM . Name )
require . NotNil ( t , macadminsData . Macadmins . MDM . ID )
assert . NotZero ( t , * macadminsData . Macadmins . MDM . ID )
2022-08-29 18:40:16 +00:00
assert . Len ( t , macadminsData . Macadmins . MunkiIssues , 1 )
2021-12-23 19:57:43 +00:00
// nothing returns null
2022-08-10 19:15:01 +00:00
macadminsData = macadminsDataResponse { }
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
2022-08-29 18:40:16 +00:00
require . NoError ( t , s . ds . SetOrUpdateMunkiInfo ( ctx , hostOnlyMunki . ID , "3.2.0" , nil , [ ] string { "warning1" } ) )
2022-08-10 19:15:01 +00:00
macadminsData = macadminsDataResponse { }
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 )
2022-08-29 18:40:16 +00:00
require . Len ( t , macadminsData . Macadmins . MunkiIssues , 1 )
assert . Equal ( t , "warning1" , macadminsData . Macadmins . MunkiIssues [ 0 ] . Name )
2021-12-23 19:57:43 +00:00
// only mdm returns null on munki info
2022-11-01 17:22:07 +00:00
require . NoError ( t , s . ds . SetOrUpdateMDMData ( ctx , hostOnlyMDM . ID , true , "https://kandji.io" , true , "" ) )
2022-08-10 19:15:01 +00:00
macadminsData = macadminsDataResponse { }
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 )
2022-08-10 19:15:01 +00:00
require . NotNil ( t , macadminsData . Macadmins . MDM . Name )
assert . Equal ( t , fleet . WellKnownMDMKandji , * macadminsData . Macadmins . MDM . Name )
require . NotNil ( t , macadminsData . Macadmins . MDM . ID )
assert . NotZero ( t , * macadminsData . Macadmins . MDM . ID )
require . Nil ( t , macadminsData . Macadmins . Munki )
2022-08-29 18:40:16 +00:00
require . Len ( t , macadminsData . Macadmins . MunkiIssues , 0 )
2022-08-10 19:15:01 +00:00
assert . Equal ( t , "https://kandji.io" , macadminsData . Macadmins . MDM . ServerURL )
assert . Equal ( t , "Enrolled (automated)" , macadminsData . Macadmins . MDM . EnrollmentStatus )
// host without mdm_id still works, returns nil id and unknown name
macadminsData = macadminsDataResponse { }
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d/macadmins" , hostMDMNoID . ID ) , nil , http . StatusOK , & macadminsData )
require . NotNil ( t , macadminsData . Macadmins )
require . NotNil ( t , macadminsData . Macadmins . MDM )
assert . Nil ( t , macadminsData . Macadmins . MDM . Name )
assert . Nil ( t , macadminsData . Macadmins . MDM . ID )
2021-12-23 19:57:43 +00:00
require . Nil ( t , macadminsData . Macadmins . Munki )
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 ,
} ,
} )
2022-08-29 18:40:16 +00:00
require . Len ( t , agg . Macadmins . MunkiIssues , 2 )
// ignore ids
agg . Macadmins . MunkiIssues [ 0 ] . ID = 0
agg . Macadmins . MunkiIssues [ 1 ] . ID = 0
assert . ElementsMatch ( t , agg . Macadmins . MunkiIssues , [ ] fleet . AggregatedMunkiIssue {
{
2022-09-06 14:34:06 +00:00
MunkiIssue : fleet . MunkiIssue {
Name : "error1" ,
IssueType : "error" ,
} ,
2022-08-29 18:40:16 +00:00
HostsCount : 1 ,
} ,
{
2022-09-06 14:34:06 +00:00
MunkiIssue : fleet . MunkiIssue {
Name : "warning1" ,
IssueType : "warning" ,
} ,
2022-08-29 18:40:16 +00:00
HostsCount : 1 ,
} ,
} )
2022-01-26 20:55:07 +00:00
assert . Equal ( t , agg . Macadmins . MDMStatus . EnrolledManualHostsCount , 0 )
2022-08-10 19:15:01 +00:00
assert . Equal ( t , agg . Macadmins . MDMStatus . EnrolledAutomatedHostsCount , 2 )
2022-01-26 20:55:07 +00:00
assert . Equal ( t , agg . Macadmins . MDMStatus . UnenrolledHostsCount , 1 )
2022-08-10 19:15:01 +00:00
assert . Equal ( t , agg . Macadmins . MDMStatus . HostsCount , 3 )
require . Len ( t , agg . Macadmins . MDMSolutions , 2 )
for _ , sol := range agg . Macadmins . MDMSolutions {
switch sol . ServerURL {
case "url2" :
assert . Equal ( t , fleet . UnknownMDMName , sol . Name )
assert . Equal ( t , 1 , sol . HostsCount )
case "https://kandji.io" :
assert . Equal ( t , fleet . WellKnownMDMKandji , sol . Name )
assert . Equal ( t , 1 , sol . HostsCount )
default :
require . Fail ( t , "unknown MDM server URL: %s" , sol . ServerURL )
}
}
2022-01-26 20:55:07 +00:00
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 )
2022-08-29 18:40:16 +00:00
require . Empty ( t , agg . Macadmins . MunkiIssues )
2022-01-26 20:55:07 +00:00
require . Empty ( t , agg . Macadmins . MDMStatus )
2022-08-10 19:15:01 +00:00
require . Empty ( t , agg . Macadmins . MDMSolutions )
2022-01-26 20:55:07 +00:00
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 )
2022-06-10 18:29:45 +00:00
// labels summary has the built-in ones
var summaryResp getLabelsSummaryResponse
s . DoJSON ( "GET" , "/api/latest/fleet/labels/summary" , nil , http . StatusOK , & summaryResp )
assert . Len ( t , summaryResp . Labels , builtInsCount )
for _ , lbl := range summaryResp . Labels {
assert . Equal ( t , fleet . LabelTypeBuiltIn , lbl . LabelType )
}
2021-12-21 14:53:15 +00:00
// 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 )
2022-06-10 18:29:45 +00:00
// labels summary
s . DoJSON ( "GET" , "/api/latest/fleet/labels/summary" , nil , http . StatusOK , & summaryResp )
assert . Len ( t , summaryResp . Labels , builtInsCount + 1 )
2021-12-21 14:53:15 +00:00
// 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 ) )
2022-10-19 21:05:10 +00:00
// list hosts in label searching by display_name
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/labels/%d/hosts" , lbl2 . ID ) , nil , http . StatusOK , & listHostsResp , "order_key" , "display_name" , "order_direction" , "desc" )
assert . Len ( t , listHostsResp . Hosts , len ( hosts ) )
// first in the list is the last one, as the names are ordered with the index
// of creation, and vice-versa
assert . Equal ( t , hosts [ len ( hosts ) - 1 ] . ID , listHostsResp . Hosts [ 0 ] . ID )
assert . Equal ( t , hosts [ 0 ] . ID , listHostsResp . Hosts [ len ( hosts ) - 1 ] . ID )
// count hosts in label order by display_name
var countResp countHostsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & countResp , "label_id" , fmt . Sprint ( lbl2 . ID ) , "order_key" , "display_name" , "order_direction" , "desc" )
assert . Equal ( t , len ( hosts ) , countResp . Count )
2021-12-21 14:53:15 +00:00
// 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 )
2022-10-19 21:05:10 +00:00
// count hosts in label
s . DoJSON ( "GET" , "/api/latest/fleet/hosts/count" , nil , http . StatusOK , & countResp , "label_id" , fmt . Sprint ( lbl1 . ID ) )
assert . Equal ( t , 0 , countResp . Count )
2021-12-21 14:53:15 +00:00
// 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
2022-06-10 18:29:45 +00:00
// labels summary, only the built-ins remains
s . DoJSON ( "GET" , "/api/latest/fleet/labels/summary" , nil , http . StatusOK , & summaryResp )
assert . Len ( t , summaryResp . Labels , builtInsCount )
for _ , lbl := range summaryResp . 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-09-19 17:53:44 +00:00
func ( s * integrationTestSuite ) TestHostStatusWebhookConfig ( ) {
t := s . T ( )
// enable with valid config
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"host_status_webhook" : {
"enable_host_status_webhook" : true ,
"destination_url" : "http://some/url" ,
"host_percentage" : 2 ,
"days_count" : 1
} ,
"interval" : "1h"
}
} ` ) , http . StatusOK )
config := s . getConfig ( )
require . True ( t , config . WebhookSettings . HostStatusWebhook . Enable )
require . Equal ( t , "http://some/url" , config . WebhookSettings . HostStatusWebhook . DestinationURL )
require . Equal ( t , 2.0 , config . WebhookSettings . HostStatusWebhook . HostPercentage )
require . Equal ( t , 1 , config . WebhookSettings . HostStatusWebhook . DaysCount )
// update without a destination url
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"host_status_webhook" : {
"enable_host_status_webhook" : true ,
"destination_url" : "" ,
"host_percentage" : 2 ,
"days_count" : 1
} ,
"interval" : "1h"
}
} ` ) , http . StatusUnprocessableEntity )
// update without a negative days count
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"host_status_webhook" : {
"enable_host_status_webhook" : true ,
"destination_url" : "http://other/url" ,
"host_percentage" : 2 ,
"days_count" : - 123
} ,
"interval" : "1h"
}
} ` ) , http . StatusUnprocessableEntity )
// update with 0%
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"host_status_webhook" : {
"enable_host_status_webhook" : true ,
"destination_url" : "http://other/url" ,
"host_percentage" : 0 ,
"days_count" : 12
} ,
"interval" : "1h"
}
} ` ) , http . StatusUnprocessableEntity )
// config left unmodified since last successful call
config = s . getConfig ( )
require . True ( t , config . WebhookSettings . HostStatusWebhook . Enable )
require . Equal ( t , "http://some/url" , config . WebhookSettings . HostStatusWebhook . DestinationURL )
require . Equal ( t , 2.0 , config . WebhookSettings . HostStatusWebhook . HostPercentage )
require . Equal ( t , 1 , config . WebhookSettings . HostStatusWebhook . DaysCount )
// disabling ignores the invalid parameters
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , [ ] byte ( ` {
"webhook_settings" : {
"host_status_webhook" : {
"enable_host_status_webhook" : false ,
"destination_url" : "" ,
"host_percentage" : 0
} ,
"interval" : "1h"
}
} ` ) , http . StatusOK )
config = s . getConfig ( )
require . False ( t , config . WebhookSettings . HostStatusWebhook . Enable )
}
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
2022-07-18 17:22:28 +00:00
t . Setenv ( "TEST_ZENDESK_CLIENT" , "true" )
2022-05-02 20:58:34 +00:00
// 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 )
2022-09-19 17:53:44 +00:00
assert . Equal ( t , "FleetTest" , acResp . OrgInfo . OrgName ) // set in SetupSuite
2022-01-25 14:34:00 +00:00
// 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 )
2022-08-24 12:32:45 +00:00
// the global agent options were not modified by the last call, so the
// corresponding activity should not have been created.
var listActivities listActivitiesResponse
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & listActivities , "order_key" , "id" , "order_direction" , "desc" )
if ! assert . Len ( t , listActivities . Activities , 0 ) {
// if there is an activity, make sure it is not edited_agent_options
require . NotEqual ( t , fleet . ActivityTypeEditedAgentOptions , listActivities . Activities [ 0 ] . Type )
}
// test a change that does modify the agent options.
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
2022-09-19 17:53:44 +00:00
"agent_options" : { "config" : { "views" : { "foo" : "bar" } } }
2022-08-24 12:32:45 +00:00
} ` ) , http . StatusOK , & acResp )
s . DoJSON ( "GET" , "/api/latest/fleet/activities" , nil , http . StatusOK , & listActivities , "order_key" , "id" , "order_direction" , "desc" )
require . True ( t , len ( listActivities . Activities ) > 0 )
require . Equal ( t , fleet . ActivityTypeEditedAgentOptions , listActivities . Activities [ 0 ] . Type )
require . NotNil ( t , listActivities . Activities [ 0 ] . Details )
assert . JSONEq ( t , ` { "global": true, "team_id": null, "team_name": null} ` , string ( * listActivities . Activities [ 0 ] . Details ) )
2022-09-19 17:53:44 +00:00
// try to set invalid agent options
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "nope" : true } }
} ` ) , http . StatusBadRequest , & acResp )
// did not update the appconfig
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "nope" ` )
// try to set an invalid agent options logger_tls_endpoint (must start with "/")
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "options" : { "logger_tls_endpoint" : "not-a-rooted-path" } } }
} ` ) , http . StatusBadRequest , & acResp )
// did not update the appconfig
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "not-a-rooted-path" ` )
// try to set a valid agent options logger_tls_endpoint
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "options" : { "logger_tls_endpoint" : "/rooted-path" } } }
} ` ) , http . StatusOK , & acResp )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "/rooted-path" ` )
// force-set invalid agent options
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "nope" : true } }
} ` ) , http . StatusOK , & acResp , "force" , "true" )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "nope" ` )
// dry-run valid agent options
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "views" : { "yep" : "ok" } } }
} ` ) , http . StatusOK , & acResp , "dry_run" , "true" )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "yep" ` )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "nope" ` )
// dry-run invalid agent options
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "config" : { "invalid" : true } }
} ` ) , http . StatusBadRequest , & acResp , "dry_run" , "true" )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "invalid" ` )
2022-10-03 12:29:41 +00:00
// set valid agent options command-line flag
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "command_line_flags" : { "enable_tables" : "table1" } }
} ` ) , http . StatusOK , & acResp )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "enable_tables": "table1" ` )
// set invalid agent options command-line flag
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "command_line_flags" : { "no_such_flag" : true } }
} ` ) , http . StatusBadRequest , & acResp )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "enable_tables": "table1" ` )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "no_such_flag" ` )
// set invalid value for a valid agent options command-line flag
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "command_line_flags" : { "enable_tables" : true } }
} ` ) , http . StatusBadRequest , & acResp )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "enable_tables": "table1" ` )
// force-set invalid value for a valid agent options command-line flag
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"agent_options" : { "command_line_flags" : { "enable_tables" : true } }
} ` ) , http . StatusOK , & acResp , "force" , "true" )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotContains ( t , string ( * acResp . AgentOptions ) , ` "enable_tables": "table1" ` )
require . Contains ( t , string ( * acResp . AgentOptions ) , ` "enable_tables": true ` )
2022-09-19 17:53:44 +00:00
// dry-run valid appconfig that uses legacy settings (returns error)
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"host_settings" : { "additional_queries" : { "foo" : "bar" } }
} ` ) , http . StatusBadRequest , & acResp , "dry_run" , "true" )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . Nil ( t , acResp . Features . AdditionalQueries )
// without dry-run, the valid appconfig that uses legacy settings is accepted
s . DoJSON ( "PATCH" , "/api/latest/fleet/config" , json . RawMessage ( ` {
"host_settings" : { "additional_queries" : { "foo" : "bar" } }
} ` ) , http . StatusOK , & acResp , "dry_run" , "false" )
s . DoJSON ( "GET" , "/api/latest/fleet/config" , nil , http . StatusOK , & acResp )
require . NotNil ( t , acResp . Features . AdditionalQueries )
require . Contains ( t , string ( * acResp . Features . AdditionalQueries ) , ` "foo": "bar" ` )
2022-01-25 14:34:00 +00:00
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 )
2022-10-05 12:35:36 +00:00
// apply spec, too many secrets
s . DoJSON ( "POST" , "/api/latest/fleet/spec/enroll_secret" , applyEnrollSecretSpecRequest {
Spec : & fleet . EnrollSecretSpec {
Secrets : createEnrollSecrets ( t , fleet . MaxEnrollSecretsCount + 1 ) ,
} ,
} , http . StatusUnprocessableEntity , & applyResp )
2022-01-25 14:34:00 +00:00
// 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-09-19 17:53:44 +00:00
// test setting the default app config we use for new installs (this check
// ensures that the default config passes the validation)
var defAppCfg fleet . AppConfig
defAppCfg . ApplyDefaultsForNewInstalls ( )
// must set org name and server settings
defAppCfg . OrgInfo . OrgName = acResp . OrgInfo . OrgName
defAppCfg . ServerSettings . ServerURL = acResp . ServerSettings . ServerURL
s . DoRaw ( "PATCH" , "/api/latest/fleet/config" , jsonMustMarshal ( t , defAppCfg ) , http . StatusOK )
2022-01-25 14:34:00 +00:00
}
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 ,
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)
2022-10-28 15:12:21 +00:00
n , err := s . ds . InsertSoftwareVulnerabilities ( context . Background ( ) , vulns , fleet . NVDSource )
2022-06-08 01:09:47 +00:00
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 ( )
2022-06-22 20:35:53 +00:00
require . NoError ( t , s . ds . SyncHostsSoftware ( context . Background ( ) , hostsCountTs ) )
2022-02-09 15:16:50 +00:00
// 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" )
}
2022-06-10 18:29:45 +00:00
func ( s * integrationTestSuite ) TestSearchHosts ( ) {
t := s . T ( )
2022-09-21 19:16:31 +00:00
ctx := context . Background ( )
2022-06-10 18:29:45 +00:00
hosts := s . createHosts ( t )
2022-09-21 19:16:31 +00:00
// set disk space information for hosts [0] and [1]
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 0 ] . ID , 1.0 , 2.0 ) )
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 1 ] . ID , 3.0 , 4.0 ) )
2022-06-10 18:29:45 +00:00
// no search criteria
var searchResp searchHostsResponse
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/search" , searchHostsRequest { } , http . StatusOK , & searchResp )
require . Len ( t , searchResp . Hosts , len ( hosts ) ) // no request params
2022-09-21 19:16:31 +00:00
for _ , h := range searchResp . Hosts {
switch h . ID {
case hosts [ 0 ] . ID :
assert . Equal ( t , 1.0 , h . GigsDiskSpaceAvailable )
assert . Equal ( t , 2.0 , h . PercentDiskSpaceAvailable )
case hosts [ 1 ] . ID :
assert . Equal ( t , 3.0 , h . GigsDiskSpaceAvailable )
assert . Equal ( t , 4.0 , h . PercentDiskSpaceAvailable )
}
}
2022-06-10 18:29:45 +00:00
searchResp = searchHostsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/search" , searchHostsRequest { ExcludedHostIDs : [ ] uint { } } , http . StatusOK , & searchResp )
require . Len ( t , searchResp . Hosts , len ( hosts ) ) // no omitted host id
searchResp = searchHostsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/search" , searchHostsRequest { ExcludedHostIDs : [ ] uint { hosts [ 1 ] . ID } } , http . StatusOK , & searchResp )
require . Len ( t , searchResp . Hosts , len ( hosts ) - 1 ) // one omitted host id
searchResp = searchHostsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/hosts/search" , searchHostsRequest { MatchQuery : "foo.local1" } , http . StatusOK , & searchResp )
require . Len ( t , searchResp . Hosts , 1 )
require . Contains ( t , searchResp . Hosts [ 0 ] . Hostname , "foo.local1" )
}
func ( s * integrationTestSuite ) TestCountTargets ( ) {
t := s . T ( )
team , err := s . ds . NewTeam ( context . Background ( ) , & fleet . Team { Name : "TestTeam" } )
require . NoError ( t , err )
require . Equal ( t , "TestTeam" , team . Name )
hosts := s . createHosts ( t )
lblIDs , err := s . ds . LabelIDsByName ( context . Background ( ) , [ ] string { "All Hosts" } )
require . NoError ( t , err )
require . Len ( t , lblIDs , 1 )
for i := range hosts {
err = s . ds . RecordLabelQueryExecutions ( context . Background ( ) , hosts [ i ] , map [ uint ] * bool { lblIDs [ 0 ] : ptr . Bool ( true ) } , time . Now ( ) , false )
require . NoError ( t , err )
}
var hostIDs [ ] uint
for _ , h := range hosts {
hostIDs = append ( hostIDs , h . ID )
}
err = s . ds . AddHostsToTeam ( context . Background ( ) , ptr . Uint ( team . ID ) , [ ] uint { hostIDs [ 0 ] } )
require . NoError ( t , err )
var countResp countTargetsResponse
// sleep to reduce flake in last seen time so that online/offline counts can be tested
time . Sleep ( 1 * time . Second )
// none selected
s . DoJSON ( "POST" , "/api/latest/fleet/targets/count" , countTargetsRequest { } , http . StatusOK , & countResp )
require . Equal ( t , uint ( 0 ) , countResp . TargetsCount )
require . Equal ( t , uint ( 0 ) , countResp . TargetsOnline )
require . Equal ( t , uint ( 0 ) , countResp . TargetsOffline )
// all hosts label selected
countResp = countTargetsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/targets/count" , countTargetsRequest { Selected : fleet . HostTargets { LabelIDs : lblIDs } } , http . StatusOK , & countResp )
require . Equal ( t , uint ( 3 ) , countResp . TargetsCount )
require . Equal ( t , uint ( 1 ) , countResp . TargetsOnline )
require . Equal ( t , uint ( 2 ) , countResp . TargetsOffline )
// team selected
countResp = countTargetsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/targets/count" , countTargetsRequest { Selected : fleet . HostTargets { TeamIDs : [ ] uint { team . ID } } } , http . StatusOK , & countResp )
require . Equal ( t , uint ( 1 ) , countResp . TargetsCount )
require . Equal ( t , uint ( 1 ) , countResp . TargetsOnline )
require . Equal ( t , uint ( 0 ) , countResp . TargetsOffline )
// host id selected
countResp = countTargetsResponse { }
s . DoJSON ( "POST" , "/api/latest/fleet/targets/count" , countTargetsRequest { Selected : fleet . HostTargets { HostIDs : [ ] uint { hosts [ 1 ] . ID } } } , http . StatusOK , & countResp )
require . Equal ( t , uint ( 1 ) , countResp . TargetsCount )
require . Equal ( t , uint ( 0 ) , countResp . TargetsOnline )
require . Equal ( t , uint ( 1 ) , countResp . TargetsOffline )
}
2022-02-15 20:22:19 +00:00
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 )
}
2022-10-07 14:36:17 +00:00
func ( s * integrationTestSuite ) TestReenrollHostCleansPolicies ( ) {
t := s . T ( )
ctx := context . Background ( )
host := s . createHosts ( t ) [ 0 ]
// set the enroll secret
var applyResp applyEnrollSecretSpecResponse
s . DoJSON ( "POST" , "/api/latest/fleet/spec/enroll_secret" , applyEnrollSecretSpecRequest {
Spec : & fleet . EnrollSecretSpec {
Secrets : [ ] * fleet . EnrollSecret { { Secret : t . Name ( ) } } ,
} ,
} , http . StatusOK , & applyResp )
var getHostResp getHostResponse
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host . ID ) , nil , http . StatusOK , & getHostResp )
require . Empty ( t , getHostResp . Host . Policies )
// create a policy and make the host fail it
pol , err := s . ds . NewGlobalPolicy ( ctx , nil , fleet . PolicyPayload { Name : t . Name ( ) , Query : "SELECT 1" , Platform : host . FleetPlatform ( ) } )
require . NoError ( t , err )
err = s . ds . RecordPolicyQueryExecutions ( ctx , & fleet . Host { ID : host . ID } , map [ uint ] * bool { pol . ID : ptr . Bool ( false ) } , time . Now ( ) , false )
require . NoError ( t , err )
// refetch the host details
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host . ID ) , nil , http . StatusOK , & getHostResp )
require . Len ( t , * getHostResp . Host . Policies , 1 )
// re-enroll the host, but using a different platform
j , err := json . Marshal ( & enrollAgentRequest {
EnrollSecret : t . Name ( ) ,
HostIdentifier : host . OsqueryHostID ,
HostDetails : map [ string ] ( map [ string ] string ) { "os_version" : map [ string ] string { "platform" : "windows" } } ,
} )
require . NoError ( t , err )
// prevent the enroll cooldown from being applied
mysql . ExecAdhocSQL ( t , s . ds , func ( db sqlx . ExtContext ) error {
_ , err := db . ExecContext (
context . Background ( ) ,
"UPDATE hosts SET last_enrolled_at = DATE_SUB(NOW(), INTERVAL '1' HOUR) WHERE id = ?" ,
host . ID ,
)
return err
} )
var resp enrollAgentResponse
hres := s . DoRawNoAuth ( "POST" , "/api/osquery/enroll" , j , http . StatusOK )
defer hres . Body . Close ( )
require . NoError ( t , json . NewDecoder ( hres . Body ) . Decode ( & resp ) )
require . NotEmpty ( t , resp . NodeKey )
// refetch the host details
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/%d" , host . ID ) , nil , http . StatusOK , & getHostResp )
// policies should be gone
require . Empty ( t , getHostResp . Host . Policies )
}
2022-03-08 16:27:38 +00:00
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-10-25 14:46:41 +00:00
res = s . DoRawNoAuth ( "POST" , "/api/latest/fleet/reset_password" , jsonMustMarshal ( t , resetPasswordRequest { PasswordResetToken : token , NewPassword : userUnusedPwd } ) , http . StatusUnauthorized )
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-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-09-21 19:16:31 +00:00
// set disk space information for hosts [0] and [1]
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 0 ] . ID , 1.0 , 2.0 ) )
require . NoError ( t , s . ds . SetOrUpdateHostDisksSpace ( ctx , hosts [ 1 ] . ID , 3.0 , 4.0 ) )
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-10-08 12:57:46 +00:00
require . Len ( t , rows [ 0 ] , 45 ) // 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 (
2022-09-21 19:16:31 +00:00
idCol = 2
issuesCol = 40
gigsDiskCol = 38
pctDiskCol = 39
2022-05-31 10:19:57 +00:00
)
2022-05-23 19:35:05 +00:00
2022-09-21 19:16:31 +00:00
// find the row for hosts[1], it should have issues=1 (1 failing policy) and the expected disk space
2022-05-23 19:35:05 +00:00
for _ , row := range rows [ 1 : ] {
if row [ idCol ] == fmt . Sprint ( hosts [ 1 ] . ID ) {
require . Equal ( t , "1" , row [ issuesCol ] , row )
2022-09-21 19:16:31 +00:00
require . Equal ( t , "3" , row [ gigsDiskCol ] , row )
require . Equal ( t , "4" , row [ pctDiskCol ] , row )
2022-05-23 19:35:05 +00:00
} else {
require . Equal ( t , "0" , row [ issuesCol ] , row )
}
}
2022-05-10 18:25:53 +00:00
// valid format, some columns
2022-09-21 19:16:31 +00:00
res = s . DoRaw ( "GET" , "/api/latest/fleet/hosts/report" , nil , http . StatusOK , "format" , "csv" , "columns" , "hostname" , "gigs_disk_space_available" , "percent_disk_space_available" )
2022-05-10 18:25:53 +00:00
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" )
2022-10-12 13:19:21 +00:00
require . Contains ( t , res . Header , "X-Content-Type-Options" )
2022-03-15 19:14:42 +00:00
require . Contains ( t , res . Header . Get ( "Content-Disposition" ) , "attachment;" )
require . Contains ( t , res . Header . Get ( "Content-Type" ) , "text/csv" )
2022-10-12 13:19:21 +00:00
require . Contains ( t , res . Header . Get ( "X-Content-Type-Options" ) , "nosniff" )
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-06-21 13:04:50 +00:00
func ( s * integrationTestSuite ) TestSSODisabled ( ) {
t := s . T ( )
var initiateResp initiateSSOResponse
s . DoJSON ( "POST" , "/api/v1/fleet/sso" , struct { } { } , http . StatusBadRequest , & initiateResp )
var callbackResp callbackSSOResponse
// callback without SAML response
s . DoJSON ( "POST" , "/api/v1/fleet/sso/callback" , nil , http . StatusBadRequest , & callbackResp )
// callback with invalid SAML response
s . DoJSON ( "POST" , "/api/v1/fleet/sso/callback?SAMLResponse=zz" , nil , http . StatusBadRequest , & callbackResp )
// callback with valid SAML response (<samlp:AuthnRequest></samlp:AuthnRequest>)
res := s . DoRaw ( "POST" , "/api/v1/fleet/sso/callback?SAMLResponse=PHNhbWxwOkF1dGhuUmVxdWVzdD48L3NhbWxwOkF1dGhuUmVxdWVzdD4%3D" , nil , http . StatusOK )
defer res . Body . Close ( )
body , err := io . ReadAll ( res . Body )
require . NoError ( t , err )
require . Contains ( t , string ( body ) , "/login?status=org_disabled" ) // html contains a script that redirects to this path
}
2022-07-27 19:47:39 +00:00
func ( s * integrationTestSuite ) TestSandboxEndpoints ( ) {
2022-07-01 19:52:55 +00:00
t := s . T ( )
validEmail := testUsers [ "user1" ] . Email
validPwd := testUsers [ "user1" ] . PlaintextPassword
hdrs := map [ string ] string { "Content-Type" : "application/x-www-form-urlencoded" }
2022-07-27 19:47:39 +00:00
// demo login endpoint always fails
2022-07-01 19:52:55 +00:00
formBody := make ( url . Values )
formBody . Set ( "email" , validEmail )
formBody . Set ( "password" , validPwd )
res := s . DoRawWithHeaders ( "POST" , "/api/v1/fleet/demologin" , [ ] byte ( formBody . Encode ( ) ) , http . StatusInternalServerError , hdrs )
require . NotEqual ( t , http . StatusOK , res . StatusCode )
2022-07-27 19:47:39 +00:00
// installers endpoint is not enabled
2022-08-16 15:54:41 +00:00
url , installersBody := installerPOSTReq ( enrollSecret , "pkg" , s . token , false )
s . DoRaw ( "POST" , url , installersBody , http . StatusInternalServerError )
2022-07-01 19:52:55 +00:00
}
2022-06-28 18:11:49 +00:00
func ( s * integrationTestSuite ) TestGetHostBatteries ( ) {
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 : strings . ReplaceAll ( 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 )
bats := [ ] * fleet . HostBattery {
{ HostID : host . ID , SerialNumber : "a" , CycleCount : 1 , Health : "Good" } ,
2022-07-21 02:16:03 +00:00
{ HostID : host . ID , SerialNumber : "b" , CycleCount : 1002 , Health : "Poor" } ,
2022-06-28 18:11:49 +00:00
}
require . NoError ( t , s . ds . ReplaceHostBatteries ( context . Background ( ) , host . ID , bats ) )
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 )
// only cycle count and health are returned
require . ElementsMatch ( t , [ ] * fleet . HostBattery {
2022-07-21 02:16:03 +00:00
{ CycleCount : 1 , Health : "Normal" } ,
{ CycleCount : 1002 , Health : "Replacement recommended" } ,
2022-06-28 18:11:49 +00:00
} , * getHostResp . Host . Batteries )
// same for get host by identifier
s . DoJSON ( "GET" , fmt . Sprintf ( "/api/latest/fleet/hosts/identifier/%s" , host . NodeKey ) , nil , http . StatusOK , & getHostResp )
require . Equal ( t , host . ID , getHostResp . Host . ID )
// only cycle count and health are returned
require . ElementsMatch ( t , [ ] * fleet . HostBattery {
2022-07-21 02:16:03 +00:00
{ CycleCount : 1 , Health : "Normal" } ,
{ CycleCount : 1002 , Health : "Replacement recommended" } ,
2022-06-28 18:11:49 +00:00
} , * getHostResp . Host . Batteries )
}
2022-08-22 19:34:00 +00:00
func ( s * integrationTestSuite ) TestOSVersions ( ) {
t := s . T ( )
testOS := fleet . OperatingSystem { Name : "barOS" , Version : "4.2" , Arch : "64bit" , KernelVersion : "13.37" , Platform : "foo" }
hosts := s . createHosts ( t )
var resp listHostsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp )
require . Len ( t , resp . Hosts , len ( hosts ) )
// set operating system information on a host
require . NoError ( t , s . ds . UpdateHostOperatingSystem ( context . Background ( ) , hosts [ 0 ] . ID , testOS ) )
var osID uint
mysql . ExecAdhocSQL ( t , s . ds , func ( q sqlx . ExtContext ) error {
return sqlx . GetContext ( context . Background ( ) , q , & osID ,
` SELECT id FROM operating_systems WHERE name = ? AND version = ? AND arch = ? AND kernel_version = ? AND platform = ? ` ,
testOS . Name , testOS . Version , testOS . Arch , testOS . KernelVersion , testOS . Platform )
} )
require . Greater ( t , osID , uint ( 0 ) )
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_name" , testOS . Name , "os_version" , testOS . Version )
require . Len ( t , resp . Hosts , 1 )
expected := resp . Hosts [ 0 ]
resp = listHostsResponse { }
s . DoJSON ( "GET" , "/api/latest/fleet/hosts" , nil , http . StatusOK , & resp , "os_id" , fmt . Sprintf ( "%d" , osID ) )
require . Len ( t , resp . Hosts , 1 )
require . Equal ( t , expected , resp . Hosts [ 0 ] )
// generate aggregated stats
require . NoError ( t , s . ds . UpdateOSVersions ( context . Background ( ) ) )
var osVersionsResp osVersionsResponse
s . DoJSON ( "GET" , "/api/latest/fleet/os_versions" , nil , http . StatusOK , & osVersionsResp )
require . Len ( t , osVersionsResp . OSVersions , 1 )
require . Equal ( t , fleet . OSVersion { HostsCount : 1 , Name : fmt . Sprintf ( "%s %s" , testOS . Name , testOS . Version ) , NameOnly : testOS . Name , Version : testOS . Version , Platform : testOS . Platform } , osVersionsResp . OSVersions [ 0 ] )
}
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
func ( s * integrationTestSuite ) TestPingEndpoints ( ) {
2022-09-26 17:39:56 +00:00
s . DoRaw ( "HEAD" , "/api/fleet/orbit/ping" , nil , http . StatusOK )
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
// unauthenticated works too
2022-09-26 17:39:56 +00:00
s . DoRawNoAuth ( "HEAD" , "/api/fleet/orbit/ping" , nil , http . StatusOK )
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
2022-09-26 17:39:56 +00:00
s . DoRaw ( "HEAD" , "/api/fleet/device/ping" , nil , http . StatusOK )
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +00:00
// unauthenticated works too
2022-09-26 17:39:56 +00:00
s . DoRawNoAuth ( "HEAD" , "/api/fleet/device/ping" , nil , http . StatusOK )
add headers denoting capabilities between fleet server / desktop / orbit (#7833)
This adds a new mechanism to allow us to handle compatibility issues between Orbit, Fleet Server and Fleet Desktop.
The general idea is to _always_ send a custom header of the form:
```
fleet-capabilities-header = "X-Fleet-Capabilities:" capabilities
capabilities = capability * (,)
capability = string
```
Both from the server to the clients (Orbit, Fleet Desktop) and vice-versa. For an example, see: https://github.com/fleetdm/fleet/commit/8c0bbdd291f54e03e19766bcdfead0fb8067f60c
Also, the following applies:
- Backwards compat: if the header is not present, assume that orbit/fleet doesn't have the capability
- The current capabilities endpoint will be removed
### Motivation
This solution is trying to solve the following problems:
- We have three independent processes communicating with each other (Fleet Desktop, Orbit and Fleet Server). Each process can be updated independently, and therefore we need a way for each process to know what features are supported by its peers.
- We originally implemented a dedicated API endpoint in the server that returned a list of the capabilities (or "features") enabled, we found this, and any other server-only solution (like API versioning) to be insufficient because:
- There are cases in which the server also needs to know which features are supported by its clients
- Clients needed to poll for changes to detect if the capabilities supported by the server change, by sending the capabilities on each request we have a much cleaner way to handling different responses.
- We are also introducing an unauthenticated endpoint to get the server features, this gives us flexibility if we need to implement different authentication mechanisms, and was one of the pitfalls of the first implementation.
Related to https://github.com/fleetdm/fleet/issues/7929
2022-09-26 10:53:53 +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"
}
} `
)