package service import ( "context" "fmt" "io/ioutil" "net/http" "strconv" "testing" "time" "github.com/fleetdm/fleet/v4/server/fleet" "github.com/fleetdm/fleet/v4/server/ptr" "github.com/ghodss/yaml" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) type integrationTestSuite struct { suite.Suite withServer } func (s *integrationTestSuite) SetupSuite() { s.withServer.SetupSuite("integrationTestSuite") } func TestIntegrations(t *testing.T) { testingSuite := new(integrationTestSuite) testingSuite.s = &testingSuite.Suite suite.Run(t, testingSuite) } func (s *integrationTestSuite) TestDoubleUserCreationErrors() { t := s.T() params := fleet.UserPayload{ Name: ptr.String("user1"), Email: ptr.String("email@asd.com"), Password: ptr.String("pass"), GlobalRole: ptr.String(fleet.RoleObserver), } s.Do("POST", "/api/v1/fleet/users/admin", ¶ms, http.StatusOK) respSecond := s.Do("POST", "/api/v1/fleet/users/admin", ¶ms, http.StatusConflict) 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"), Password: ptr.String("pass"), } resp := s.Do("POST", "/api/v1/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity) assertErrorCodeAndMessage(t, resp, fleet.ErrNoRoleNeeded, "either global role or team role needs to be defined") } func (s *integrationTestSuite) TestUserWithWrongRoleErrors() { t := s.T() params := fleet.UserPayload{ Name: ptr.String("user1"), Email: ptr.String("email@asd.com"), Password: ptr.String("pass"), GlobalRole: ptr.String("wrongrole"), } resp := s.Do("POST", "/api/v1/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity) 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"), Password: ptr.String("pass"), Teams: &teams, } resp := s.Do("POST", "/api/v1/fleet/users/admin", ¶ms, http.StatusUnprocessableEntity) 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;"), } s.Do("POST", "/api/v1/fleet/queries", ¶ms, http.StatusOK) activities := listActivitiesResponse{} s.DoJSON("GET", "/api/v1/fleet/activities", nil, http.StatusOK, &activities) assert.Len(t, activities.Activities, 1) assert.Equal(t, "Test Name admin1@example.com", activities.Activities[0].ActorFullName) require.NotNil(t, activities.Activities[0].ActorGravatar) assert.Equal(t, "http://iii.com", *activities.Activities[0].ActorGravatar) assert.Equal(t, "created_saved_query", activities.Activities[0].Type) } func (s *integrationTestSuite) TestAppConfigAdditionalQueriesCanBeRemoved() { t := s.T() spec := []byte(` host_expiry_settings: host_expiry_enabled: true host_expiry_window: 0 host_settings: additional_queries: time: SELECT * FROM time enable_host_users: true `) s.applyConfig(spec) spec = []byte(` host_settings: enable_host_users: true additional_queries: null `) s.applyConfig(spec) config := s.getConfig() assert.Nil(t, config.HostSettings.AdditionalQueries) assert.True(t, config.HostExpirySettings.HostExpiryEnabled) } func (s *integrationTestSuite) TestAppConfigDefaultValues() { config := s.getConfig() s.Run("Update interval", func() { require.Equal(s.T(), 1*time.Hour, config.UpdateInterval.OSQueryDetail) }) s.Run("has logging", func() { require.NotNil(s.T(), config.Logging) }) } func (s *integrationTestSuite) TestUserRolesSpec() { t := s.T() _, err := s.ds.NewTeam(context.Background(), &fleet.Team{ ID: 42, Name: "team1", Description: "desc team1", }) require.NoError(t, err) email := t.Name() + "@asd.com" u := &fleet.User{ Password: []byte("asd"), Name: t.Name(), Email: email, GravatarURL: "http://asd.com", GlobalRole: ptr.String(fleet.RoleObserver), } user, err := s.ds.NewUser(context.Background(), u) require.NoError(t, err) assert.Len(t, user.Teams, 0) spec := []byte(fmt.Sprintf(` roles: %s: global_role: null teams: - role: maintainer team: team1 `, email)) var userRoleSpec applyUserRoleSpecsRequest err = yaml.Unmarshal(spec, &userRoleSpec.Spec) require.NoError(t, err) s.Do("POST", "/api/v1/fleet/users/roles/spec", &userRoleSpec, http.StatusOK) 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() gs := fleet.GlobalSchedulePayload{} s.DoJSON("GET", "/api/v1/fleet/global/schedule", nil, http.StatusOK, &gs) require.Len(t, gs.GlobalSchedule, 0) qr, err := s.ds.NewQuery(context.Background(), &fleet.Query{ Name: "TestQuery1", Description: "Some description", Query: "select * from osquery;", ObserverCanRun: true, }) require.NoError(t, err) gsParams := fleet.ScheduledQueryPayload{QueryID: ptr.Uint(qr.ID), Interval: ptr.Uint(42)} r := globalScheduleQueryResponse{} s.DoJSON("POST", "/api/v1/fleet/global/schedule", gsParams, http.StatusOK, &r) gs = fleet.GlobalSchedulePayload{} s.DoJSON("GET", "/api/v1/fleet/global/schedule", nil, http.StatusOK, &gs) 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 gs = fleet.GlobalSchedulePayload{} gsParams = fleet.ScheduledQueryPayload{Interval: ptr.Uint(55)} s.DoJSON("PATCH", fmt.Sprintf("/api/v1/fleet/global/schedule/%d", id), gsParams, http.StatusOK, &gs) gs = fleet.GlobalSchedulePayload{} s.DoJSON("GET", "/api/v1/fleet/global/schedule", nil, http.StatusOK, &gs) require.Len(t, gs.GlobalSchedule, 1) assert.Equal(t, uint(55), gs.GlobalSchedule[0].Interval) r = globalScheduleQueryResponse{} s.DoJSON("DELETE", fmt.Sprintf("/api/v1/fleet/global/schedule/%d", id), nil, http.StatusOK, &r) gs = fleet.GlobalSchedulePayload{} s.DoJSON("GET", "/api/v1/fleet/global/schedule", nil, http.StatusOK, &gs) 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"}, }, }} s.DoJSON("POST", "/api/v1/fleet/translate", ¶ms, http.StatusOK, &payload) 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(), PolicyUpdatedAt: time.Now(), SeenTime: time.Now(), NodeKey: t.Name() + "1", UUID: t.Name() + "1", Hostname: "foo.local", PrimaryIP: "192.168.1.1", PrimaryMac: "30-65-EC-6F-C4-58", }) require.NoError(t, err) require.NotNil(t, host) soft := fleet.HostSoftware{ Modified: true, Software: []fleet.Software{ {Name: "foo", Version: "0.0.1", Source: "chrome_extensions"}, {Name: "bar", Version: "0.0.3", Source: "apps"}, }, } host.HostSoftware = soft require.NoError(t, s.ds.SaveHostSoftware(context.Background(), host)) require.NoError(t, s.ds.LoadHostSoftware(context.Background(), host)) soft1 := host.Software[0] if soft1.Name != "bar" { soft1 = host.Software[1] } require.NoError(t, s.ds.AddCPEForSoftware(context.Background(), soft1, "somecpe")) require.NoError(t, s.ds.InsertCVEForCPE(context.Background(), "cve-123-123-132", []string{"somecpe"})) resp := s.Do("GET", fmt.Sprintf("/api/v1/fleet/hosts/%d", host.ID), nil, http.StatusOK) 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) } 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(), PolicyUpdatedAt: time.Now(), SeenTime: time.Now().Add(-time.Duration(i) * time.Minute), OsqueryHostID: strconv.Itoa(i), NodeKey: fmt.Sprintf("%d", i), UUID: fmt.Sprintf("%d", i), Hostname: fmt.Sprintf("foo.local%d", i), }) 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) gpParams := globalPolicyRequest{QueryID: qr.ID} gpResp := globalPolicyResponse{} s.DoJSON("POST", "/api/v1/fleet/global/policies", gpParams, http.StatusOK, &gpResp) require.NotNil(t, gpResp.Policy) assert.Equal(t, qr.ID, gpResp.Policy.QueryID) policiesResponse := listGlobalPoliciesResponse{} s.DoJSON("GET", "/api/v1/fleet/global/policies", nil, http.StatusOK, &policiesResponse) require.Len(t, policiesResponse.Policies, 1) assert.Equal(t, qr.ID, policiesResponse.Policies[0].QueryID) singlePolicyResponse := getPolicyByIDResponse{} singlePolicyURL := fmt.Sprintf("/api/v1/fleet/global/policies/%d", policiesResponse.Policies[0].ID) s.DoJSON("GET", singlePolicyURL, nil, http.StatusOK, &singlePolicyResponse) assert.Equal(t, qr.ID, singlePolicyResponse.Policy.QueryID) assert.Equal(t, qr.Name, singlePolicyResponse.Policy.QueryName) listHostsURL := fmt.Sprintf("/api/v1/fleet/hosts?policy_id=%d", policiesResponse.Policies[0].ID) listHostsResp := listHostsResponse{} s.DoJSON("GET", listHostsURL, nil, http.StatusOK, &listHostsResp) require.Len(t, listHostsResp.Hosts, 3) h1 := listHostsResp.Hosts[0] h2 := listHostsResp.Hosts[1] listHostsURL = fmt.Sprintf("/api/v1/fleet/hosts?policy_id=%d&policy_response=passing", policiesResponse.Policies[0].ID) 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())) require.NoError(t, s.ds.RecordPolicyQueryExecutions(context.Background(), h2.Host, map[uint]*bool{policiesResponse.Policies[0].ID: nil}, time.Now())) listHostsURL = fmt.Sprintf("/api/v1/fleet/hosts?policy_id=%d&policy_response=passing", policiesResponse.Policies[0].ID) 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{} s.DoJSON("POST", "/api/v1/fleet/global/policies/delete", deletePolicyParams, http.StatusOK, &deletePolicyResp) policiesResponse = listGlobalPoliciesResponse{} s.DoJSON("GET", "/api/v1/fleet/global/policies", nil, http.StatusOK, &policiesResponse) require.Len(t, policiesResponse.Policies, 0) }