Skip to content

Commit

Permalink
Project filtering for reporting server ListReports endpoint.
Browse files Browse the repository at this point in the history
  • Loading branch information
phiggins committed May 31, 2019
1 parent cd9ab90 commit 19bf12c
Show file tree
Hide file tree
Showing 3 changed files with 200 additions and 11 deletions.
6 changes: 6 additions & 0 deletions components/compliance-service/api/reporting/server/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,13 @@ func (srv *Server) ListProfiles(ctx context.Context, in *reporting.Query) (*repo
if err != nil {
return nil, status.Error(codes.InvalidArgument, err.Error())
}

formattedFilters := formatFilters(in.Filters)
formattedFilters, err = filterByProjects(ctx, formattedFilters)
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

profiles, counts, err := srv.es.GetAllProfilesFromNodes(from, perPage, formattedFilters, SORT_FIELDS[sort], asc)
if err != nil {
return nil, utils.FormatErrorMsg(err, "")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
package integration_test

import (
"testing"
"time"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

authzConstants "github.com/chef/automate/components/authz-service/constants/v2"
"github.com/chef/automate/components/compliance-service/api/reporting"
reportingServer "github.com/chef/automate/components/compliance-service/api/reporting/server"
"github.com/chef/automate/components/compliance-service/ingest/events/compliance"
"github.com/chef/automate/components/compliance-service/reporting/relaxting"
"github.com/chef/automate/components/compliance-service/inspec"
iam_v2 "github.com/chef/automate/api/interservice/authz/v2"
)

func TestListProfiles(t *testing.T) {
es2Backend := relaxting.ES2Backend{ESUrl: elasticsearchUrl}
server := reportingServer.New(&es2Backend)

reportFileName := "../ingest/examples/compliance-success-tiny-report.json"
everythingCtx := contextWithProjects([]string{authzConstants.AllProjectsExternalID})

n := 5

reportIds := make([]string, n)

for i := 0; i < n; i++ {
err := suite.ingestReport(reportFileName, func(r *compliance.Report) {
id := newUUID()

r.Environment = id
r.NodeName = id
r.Platform.Name = id
r.Profiles[0].Controls = r.Profiles[0].Controls[:1]
r.Profiles[0].Controls[0].Id = id
r.Profiles[0].Controls[0].Title = id
r.Profiles = r.Profiles[:1]
r.Profiles[0].Sha256 = id
r.Profiles[0].Title = id
r.Recipes = []string{id}
r.ReportUuid = id
r.Roles = []string{id}
r.EndTime = time.Now().UTC().Format(time.RFC3339)

reportIds[i] = id
})

require.NoError(t, err)
}

defer suite.DeleteAllDocuments()

waitFor(func() bool {
response, _ := server.ListReports(everythingCtx, &reporting.Query{})

return response != nil && len(response.Reports) == n
})

reportsProjects := map[string][]string{
"project1": reportIds[1:3],
"project2": reportIds[2:5],
"project3": reportIds[3:],
}

projectRules := map[string]*iam_v2.ProjectRules{}
for k, v := range reportsProjects {
projectRules[k] = &iam_v2.ProjectRules{
Rules: []*iam_v2.ProjectRule{
{
Conditions: []*iam_v2.Condition{
{
Type: iam_v2.ProjectRuleConditionTypes_ROLES,
Values: v,
},
},
},
},
}
}

// Send a project rules update event
esJobID, err := suite.ingesticESClient.UpdateReportProjectsTags(everythingCtx, projectRules)
assert.Nil(t, err)

suite.WaitForESJobToComplete(esJobID)

suite.RefreshComplianceReportIndex()

esJobID, err = suite.ingesticESClient.UpdateSummaryProjectsTags(everythingCtx, projectRules)
assert.Nil(t, err)

suite.WaitForESJobToComplete(esJobID)

suite.RefreshComplianceSummaryIndex()

profileIds := make([]string, len(reportIds))
for i := range reportIds {
profileId := reportIds[i]
profileIds[i] = profileId

profile := inspec.Profile{
Name: "a",
Title: "b",
Version: "1",
Sha256: profileId,
}

err := es2Backend.StoreProfile(profile)
require.NoError(t, err)
}

successCases := []struct {
description string
allowedProjects []string
expectedIds []string
}{
{
description: "Projects: user has access to all projects",
allowedProjects: []string{authzConstants.AllProjectsExternalID},
expectedIds: profileIds,
},
{
description: "Projects: user has access to one project with reports",
allowedProjects: []string{"project1"},
expectedIds: profileIds[1:3],
},
{
description: "Projects: user has access to some projects with reports",
allowedProjects: []string{"project1", "project2"},
expectedIds: profileIds[1:5],
},
{
description: "Projects: user has access to projects without reports",
allowedProjects: []string{"project4", "project5"},
expectedIds: []string{},
},
{
description: "Projects: user has access to one project with reports and unassigned reports",
allowedProjects: []string{"project1", authzConstants.UnassignedProjectID},
expectedIds: profileIds[:3],
},
{
description: "Projects: user has access to some projects with reports and unassigned reports",
allowedProjects: []string{"project1", "project2", authzConstants.UnassignedProjectID},
expectedIds: profileIds[:5],
},
{
description: "Projects: user has access to projects without reports and unassigned reports",
allowedProjects: []string{"project4", "project5", authzConstants.UnassignedProjectID},
expectedIds: profileIds[:1],
},
{
description: "Projects: user has access to unassigned reports",
allowedProjects: []string{authzConstants.UnassignedProjectID},
expectedIds: profileIds[:1],
},
}

for _, test := range successCases {
t.Run(test.description, func(t *testing.T) {
ctx := contextWithProjects(test.allowedProjects)

response, err := server.ListProfiles(ctx, &reporting.Query{})

assert.NoError(t, err)
require.NotNil(t, response)

assert.Equal(t, len(test.expectedIds), int(response.Counts.Total))

actualIds := make([]string, len(response.Profiles))
for i, profile := range response.Profiles {
actualIds[i] = profile.Id
}

assert.ElementsMatch(t, test.expectedIds, actualIds)
})
}
}
24 changes: 13 additions & 11 deletions components/compliance-service/reporting/relaxting/profiles.go
Original file line number Diff line number Diff line change
Expand Up @@ -558,23 +558,22 @@ func (backend *ES2Backend) GetAllProfilesFromNodes(from int32, size int32, filte
return nil, nil, errors.Wrapf(err, "%s, cannot connect to ElasticSearch", myName)
}

var inspecProfilesQuery elastic.Query

query := elastic.NewIdsQuery(mappings.DocType)
//if one of the "other" filters are sent in, regardless of profile_id, we need to get the ids from scans
profileMins, counts, err := backend.getProfileMinsFromNodes(filters)
if err != nil {
return nil, nil, errors.Wrapf(err, "%s, cannot get profileIDs from nodes", myName)
}
logrus.Errorf("ProfileMins: %+v", profileMins)
logrus.Errorf("counts: %+v", counts)

profileIDs := make([]string, 0)
for _, profileMin := range profileMins {
profileIDs = append(profileIDs, profileMin.ID)
logrus.Debugf("profile id: %s", profileMin.ID)
logrus.Errorf("profile id: %s", profileMin.ID)
}

query := elastic.NewIdsQuery(mappings.DocType)
query.Ids(profileIDs...)
inspecProfilesQuery = query

fsc := elastic.NewFetchSourceContext(true).Include(
"name",
Expand All @@ -583,7 +582,7 @@ func (backend *ES2Backend) GetAllProfilesFromNodes(from int32, size int32, filte

searchSource := elastic.NewSearchSource().
FetchSourceContext(fsc).
Query(inspecProfilesQuery).
Query(query).
Sort(sort_field, sort_asc).
From(int(from)).
Size(int(size))
Expand All @@ -610,10 +609,13 @@ func (backend *ES2Backend) GetAllProfilesFromNodes(from int32, size int32, filte

profiles := make([]*reportingapi.ProfileMin, 0)
if err != nil {
/*
// should we be returning the error here...instead of logging it and then returning nil for the error?
logrus.Errorf("%s: Error while trying to get data from ES for index: %s error: %s",
myName, esIndex, err.Error())
return profiles, nil, nil
*/
return nil, nil, err
}

if searchResult.TotalHits() > 0 && searchResult.Hits.TotalHits > 0 {
Expand Down Expand Up @@ -641,10 +643,8 @@ func (backend ES2Backend) getProfileMinsFromNodes(
filters map[string][]string) (map[string]reporting.ProfileMin, *reportingapi.ProfileCounts, error) {
myName := "getProfileMinsFromNodes"

profileMins := make(map[string]reporting.ProfileMin)

for filterName, filterValue := range filters {
logrus.Debugf("%s, filter: name=>%s value=>%s\n", myName, filterName, filterValue)
logrus.Errorf("%s, filter: name=>%s value=>%s\n", myName, filterName, filterValue)
}

statusFilters := filters["status"]
Expand All @@ -653,7 +653,7 @@ func (backend ES2Backend) getProfileMinsFromNodes(

depth, err := backend.NewDepth(filters, false, true)
if err != nil {
return profileMins, nil, errors.Wrap(err, fmt.Sprintf("%s unable to get depth level for report", myName))
return nil, nil, errors.Wrap(err, fmt.Sprintf("%s unable to get depth level for report", myName))
}

queryInfo := depth.getQueryInfo()
Expand All @@ -663,6 +663,7 @@ func (backend ES2Backend) getProfileMinsFromNodes(
Size(0)

for aggName, agg := range depth.getProfileMinsFromNodesAggs(filters) {
logrus.Errorf("Aggregation aggName: %s, agg: %s", aggName, agg)
searchSource.Aggregation(aggName, agg)
}

Expand All @@ -672,13 +673,14 @@ func (backend ES2Backend) getProfileMinsFromNodes(
}
LogQueryPartMin(queryInfo.esIndex, source, fmt.Sprintf("%s query", myName))

logrus.Errorf("esIndex: %s", queryInfo.esIndex)
searchResult, err := queryInfo.client.Search().
Index(queryInfo.esIndex).
SearchSource(searchSource).
Do(context.Background())

if err != nil {
return profileMins, nil, errors.Wrapf(err, "%s unable to complete search", myName)
return nil, nil, errors.Wrapf(err, "%s unable to complete search", myName)
}

LogQueryPartMin(queryInfo.esIndex, searchResult, fmt.Sprintf("%s - search results", myName))
Expand Down

0 comments on commit 19bf12c

Please sign in to comment.