Skip to content
This repository has been archived by the owner on Oct 17, 2024. It is now read-only.

Commit

Permalink
Producer: AWS Security Hub (#131)
Browse files Browse the repository at this point in the history
* Dracon producer for AWS Security Hub findings

* add securityhub finding test

* lint

* tidy deps

* lint

Co-authored-by: sheslop <[email protected]>
  • Loading branch information
sHesl and sheslop authored Jun 15, 2022
1 parent da285e1 commit e36e9ab
Show file tree
Hide file tree
Showing 11 changed files with 566 additions and 2 deletions.
39 changes: 39 additions & 0 deletions producers/securityhub/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
subinclude("//third_party/defs:docker")

go_binary(
name = "securityhub",
srcs = [
"main.go",
],
deps = [
"//api/proto:v1",
"//producers",
"//third_party/go:aws-sdk-go-v2-securityhub",
],
)

go_test(
name = "securityhub_test",
srcs = [
"main.go",
"main_test.go",
],
data = glob(["./testcases/*.json"]),
deps = [
"//api/proto:v1",
"//pkg/putil",
"//producers",
"//third_party/go:aws-sdk-go-v2-securityhub",
"//third_party/go:stretchr_testify",
],
)

docker_image(
name = "dracon-producer-securityhub",
srcs = [
":securityhub",
],
base_image = "//build/docker:dracon-base-go",
image = "dracon-producer-securityhub",
visibility = ["//examples/..."],
)
5 changes: 5 additions & 0 deletions producers/securityhub/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
FROM //build/docker:dracon-base-go

COPY securityhub /parse

ENTRYPOINT ["/parse"]
104 changes: 104 additions & 0 deletions producers/securityhub/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
package main

import (
"log"

v1 "github.com/thought-machine/dracon/api/proto/v1"
"github.com/thought-machine/dracon/producers"

securityhub "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
)

func main() {
if err := producers.ParseFlags(); err != nil {
log.Fatal(err)
}

inFile, err := producers.ReadInFile()
if err != nil {
log.Fatal(err)
}

var results securityHubFindings
if err := producers.ParseJSON(inFile, &results); err != nil {
log.Fatal(err)
}

issues := parseIssues(&results)

if err := producers.WriteDraconOut("securityhub", issues); err != nil {
log.Fatal(err)
}
}

type securityHubFindings struct {
Findings []securityhub.AwsSecurityFinding `json:"Findings"`
}

func parseIssues(shf *securityHubFindings) []*v1.Issue {
severityMap := map[securityhub.SeverityLabel]v1.Severity{
securityhub.SeverityLabelInformational: v1.Severity_SEVERITY_INFO,
securityhub.SeverityLabelLow: v1.Severity_SEVERITY_LOW,
securityhub.SeverityLabelMedium: v1.Severity_SEVERITY_MEDIUM,
securityhub.SeverityLabelHigh: v1.Severity_SEVERITY_HIGH,
securityhub.SeverityLabelCritical: v1.Severity_SEVERITY_CRITICAL,
}

issues := make([]*v1.Issue, len(shf.Findings))
for i, r := range shf.Findings {
issue := &v1.Issue{Confidence: v1.Confidence_CONFIDENCE_MEDIUM}

if r.Title != nil {
issue.Title = *r.Title
}

if r.Description != nil {
issue.Description = *r.Description
}

if r.SourceUrl != nil {
issue.Source = *r.SourceUrl
}

switch {
case r.ProductName != nil && *r.ProductName == "Inspector" && len(r.Resources) > 0:
if r.Resources[0].Details != nil && r.Resources[0].Details.AwsEc2Instance != nil {
issue.Target = *r.Resources[0].Details.AwsEc2Instance.ImageId
}
case len(r.Resources) > 0:
issue.Target = *r.Resources[0].Id
case r.AwsAccountId != nil:
issue.Target = *r.AwsAccountId
}

switch {
case len(r.Types) > 0:
issue.Type = r.Types[0]
case r.ProductName != nil:
issue.Type = *r.ProductName
}

switch {
case r.Severity != nil:
issue.Severity = severityMap[r.Severity.Label]
case r.FindingProviderFields != nil && r.FindingProviderFields.Severity != nil:
issue.Severity = severityMap[r.FindingProviderFields.Severity.Label]
}

if len(r.Vulnerabilities) > 0 {
issue.Cve = *r.Vulnerabilities[0].Id

highestCvss := 0.0
for _, cvss := range r.Vulnerabilities[0].Cvss {
if cvss.BaseScore > highestCvss {
highestCvss = cvss.BaseScore
}
}
issue.Cvss = highestCvss
}

issues[i] = issue
}

return issues
}
142 changes: 142 additions & 0 deletions producers/securityhub/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package main

import (
"os"
"os/exec"
"strings"
"testing"

"github.com/stretchr/testify/assert"

v1 "github.com/thought-machine/dracon/api/proto/v1"
"github.com/thought-machine/dracon/pkg/putil"
)

func TestMain(m *testing.M) {
for i, arg := range os.Args {
if arg == "--test_execution" {
os.Args = append(os.Args[:i], os.Args[i+1:]...)
main()
return
}
}

m.Run()
}

func TestMainFn(t *testing.T) {
type testCase struct {
inputFileName string
expErr bool
expStdout string
expIssues []*v1.Issue
}

testCases := []testCase{
{
inputFileName: "no_findings.json",
expErr: false,
expStdout: `wrote 0 issues from to ./producers/securityhub/out.pb`,
},
{
inputFileName: "empty_finding.json",
expErr: false,
expStdout: `wrote 1 issues from to ./producers/securityhub/out.pb`,
expIssues: []*v1.Issue{
{
Target: "",
Type: "",
Title: "",
Severity: 0,
Cvss: 0.0,
Confidence: 2,
Description: "",
Source: "unknown",
Cve: "",
},
},
},
{
inputFileName: "securityhub_finding.json",
expErr: false,
expStdout: `wrote 1 issues from to ./producers/securityhub/out.pb`,
expIssues: []*v1.Issue{
{
Target: "arn:aws:ec2:eu-west-2:123456789:security-group/sg-01ef4a31dbea6f188cfbf",
Type: "Software and Configuration Checks/Industry and Regulatory Standards/CIS AWS Foundations Benchmark",
Title: "4.3 Ensure the default security group of every VPC restricts all traffic",
Severity: 3,
Cvss: 0,
Confidence: 2,
Description: "A VPC comes with a default security group whose initial settings deny all inbound traffic, allow all outbound traffic, and allow all traffic between instances assigned to the security group. If you don't specify a security group when you launch an instance, the instance is automatically assigned to this default security group. It is recommended that the default security group restrict all traffic.",
Source: "unknown",
Cve: "",
},
},
},
{
inputFileName: "inspector_finding.json",
expErr: false,
expStdout: `wrote 1 issues from to ./producers/securityhub/out.pb`,
expIssues: []*v1.Issue{
{
Target: "ami-053269b2b68617f7c",
Type: "Software and Configuration Checks/Vulnerabilities/CVE",
Title: "CVE-2022-25315 - expat",
Severity: 4,
Cvss: 9.8,
Confidence: 2,
Description: "An integer overflow was found in expat. The issue occurs in storeRawNames() by abusing the m_buffer expansion logic to allow allocations very close to INT_MAX and out-of-bounds heap writes. This flaw can cause a denial of service or potentially arbitrary code execution.",
Source: "unknown",
Cve: "CVE-2022-25315",
},
},
},
{
inputFileName: "bad-input",
expErr: true,
expStdout: `open ./producers/securityhub/testcases/bad-input: no such file or directory`,
},
{
inputFileName: "unparseable.json",
expErr: true,
expStdout: `invalid character '.' looking for beginning of value`,
},
}

for _, tc := range testCases {
t.Run(tc.inputFileName, func(t *testing.T) {
cmd := runProducerMain(`-in=./producers/securityhub/testcases/`+tc.inputFileName, `-out=./producers/securityhub/out.pb`)
stdoutStderr, err := cmd.CombinedOutput()

if err != nil && !tc.expErr {
assert.Fail(t, "unexpected err executing producer with input file '%s'. err: '%s", tc.inputFileName, err)
} else if tc.expErr {
assert.Error(t, err)
} else {
assert.Nil(t, err)
}

expMsg := "expected output from '%s' to end with '%s'. got: '%s'"
stdOut := strings.TrimSpace(string(stdoutStderr))
suffixCheck := strings.HasSuffix(stdOut, tc.expStdout)

assert.True(t, suffixCheck, expMsg, tc.inputFileName, tc.expStdout, stdOut)

ltr, err := putil.LoadToolResponse("./producers/securityhub")
assert.NoError(t, err)

if tc.expIssues != nil {
assert.EqualValues(t, ltr[0].Issues, tc.expIssues)
} else if !tc.expErr {
assert.Empty(t, ltr[0].Issues)
}

os.Remove("./producers/securityhub/out.pb")
})
}
}

func runProducerMain(args ...string) *exec.Cmd {
return exec.Command(os.Args[0], append([]string{"--test_execution"}, args...)...)
}
3 changes: 3 additions & 0 deletions producers/securityhub/testcases/empty_finding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"Findings": [{}]
}
Loading

0 comments on commit e36e9ab

Please sign in to comment.