Skip to content

Commit

Permalink
feat(cli): More detailed CSV compliance reports (#522)
Browse files Browse the repository at this point in the history
Updating the CLI CSV output of the compliance reports to closely
resemble the CSV exports from UI

JIRA: https://lacework.atlassian.net/browse/ALLY-534
  • Loading branch information
ipcrm authored Aug 23, 2021
1 parent 5586aec commit bd02a44
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 6 deletions.
94 changes: 94 additions & 0 deletions cli/cmd/compliance.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"fmt"
"sort"
"strings"
"time"

"github.com/olekukonko/tablewriter"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -213,6 +214,99 @@ func complianceReportRecommendationsTable(recommendations []api.ComplianceRecomm
return out
}

type complianceCSVReportDetails struct {
// For clouds with tenant models, supply tenant ID
TenantID string

// For clouds with tenant models, supply tenant name/alias
TenantName string

// Supply the account id for the cloud enviornment
AccountID string

// Supply the account name/alias for the cloud enviornment, if available
AccountName string

// The type of report being rendered
ReportType string

// The time of the report execution
ReportTime time.Time

// Recommendations
Recommendations []api.ComplianceRecommendation
}

func (c complianceCSVReportDetails) GetAccountDetails() []string {
accountAlias := c.AccountID
if c.AccountName != "" {
accountAlias = fmt.Sprintf("%s(%s)", c.AccountName, c.AccountID)
}

tenantAlias := c.TenantID
if c.TenantName != "" {
tenantAlias = fmt.Sprintf("%s(%s)", c.TenantName, c.TenantID)
}
out := []string{}
if tenantAlias != "" {
out = append(out, tenantAlias)
}

if accountAlias != "" {
out = append(out, accountAlias)
}
return out
}

func (c complianceCSVReportDetails) GetReportMetaData() []string {
return append([]string{c.ReportType, c.ReportTime.Format(time.RFC3339)}, c.GetAccountDetails()...)
}

func (c complianceCSVReportDetails) SortRecommendations() {
sort.Slice(c.Recommendations, func(i, j int) bool {
return c.Recommendations[i].Category < c.Recommendations[j].Category
})

}

func complianceCSVReportRecommendationsTable(details *complianceCSVReportDetails) [][]string {
details.SortRecommendations()
out := [][]string{}

for _, recommendation := range details.Recommendations {
for _, suppression := range recommendation.Suppressions {
out = append(out,
append(details.GetReportMetaData(),
recommendation.Category,
recommendation.RecID,
recommendation.Title,
"Suppressed",
recommendation.SeverityString(),
suppression,
"",
""))
}
for _, violation := range recommendation.Violations {
out = append(out,
append(details.GetReportMetaData(),
recommendation.Category,
recommendation.RecID,
recommendation.Title,
recommendation.Status,
recommendation.SeverityString(),
violation.Resource,
violation.Region,
strings.Join(violation.Reasons, ",")))
}
}

sort.Slice(out, func(i, j int) bool {
return severityOrder(out[i][3]) < severityOrder(out[j][3])
})

return out
}

func buildComplianceReportTable(detailsTable, summaryTable, recommendationsTable [][]string, filteredOutput string) string {
mainReport := &strings.Builder{}
mainReport.WriteString(
Expand Down
14 changes: 12 additions & 2 deletions cli/cmd/compliance_aws.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,24 @@ To run an ad-hoc compliance assessment of an AWS account:
return cli.OutputJSON(report)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
if cli.CSVOutput() {
recommendations := complianceCSVReportRecommendationsTable(
&complianceCSVReportDetails{
AccountName: report.AccountID,
AccountID: report.AccountID,
ReportType: report.ReportType,
ReportTime: report.ReportTime,
Recommendations: report.Recommendations,
},
)

return cli.OutputCSV(
[]string{"ID", "Recommendation", "Status", "Severity", "Service", "Affected", "Assessed"},
[]string{"Report_Type", "Report_Time", "Account", "Section", "ID", "Recommendation", "Status", "Severity", "Resource", "Region", "Reason"},
recommendations,
)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
cli.OutputHuman("\n")
cli.OutputHuman(
buildComplianceReportTable(
Expand Down
16 changes: 14 additions & 2 deletions cli/cmd/compliance_azure.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,14 +224,26 @@ To run an ad-hoc compliance assessment use the command:
return cli.OutputJSON(report)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
if cli.CSVOutput() {
recommendations := complianceCSVReportRecommendationsTable(
&complianceCSVReportDetails{
AccountName: report.SubscriptionName,
AccountID: report.SubscriptionID,
TenantName: report.TenantName,
TenantID: report.TenantID,
ReportType: report.ReportType,
ReportTime: report.ReportTime,
Recommendations: report.Recommendations,
},
)

return cli.OutputCSV(
[]string{"ID", "Recommendation", "Status", "Severity", "Service", "Affected", "Assessed"},
[]string{"Report_Type", "Report_Time", "Tenant", "Subscription", "Section", "ID", "Recommendation", "Status", "Severity", "Resource", "Region", "Reason"},
recommendations,
)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
cli.OutputHuman("\n")
cli.OutputHuman(
buildComplianceReportTable(
Expand Down
16 changes: 14 additions & 2 deletions cli/cmd/compliance_gcp.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,14 +227,26 @@ To run an ad-hoc compliance assessment use the command:
return cli.OutputJSON(report)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
if cli.CSVOutput() {
recommendations := complianceCSVReportRecommendationsTable(
&complianceCSVReportDetails{
TenantName: report.OrganizationName,
TenantID: report.OrganizationID,
AccountName: report.ProjectName,
AccountID: report.ProjectID,
ReportType: report.ReportType,
ReportTime: report.ReportTime,
Recommendations: report.Recommendations,
},
)

return cli.OutputCSV(
[]string{"ID", "Recommendation", "Status", "Severity", "Service", "Affected", "Assessed"},
[]string{"Report_Type", "Report_Time", "Organization", "Project", "Section", "ID", "Recommendation", "Status", "Severity", "Resource", "Region", "Reason"},
recommendations,
)
}

recommendations := complianceReportRecommendationsTable(report.Recommendations)
cli.OutputHuman("\n")
cli.OutputHuman(
buildComplianceReportTable(
Expand Down

0 comments on commit bd02a44

Please sign in to comment.