mirror of
https://github.com/aquasecurity/kube-bench.git
synced 2024-11-24 17:08:14 +00:00
Aws asff (#770)
* add aasf * add AASF format * credentials provider * add finding publisher * add finding publisher * add write AASF path * add testing * read config from file * update docker file * refactor * remove sample * add comments * Add comment in EKS config.yaml * Fix comment typo * Fix spelling of ASFF * Fix typo and other small code review suggestions * Limit length of Actual result field Avoids this message seen in testing: Message:Finding does not adhere to Amazon Finding Format. data.ProductFields['Actual result'] should NOT be longer than 1024 characters. * Add comment for ASFF schema * Add Security Hub documentation * go mod tidy * remove dupe lines in docs * support integration in any region * fix README link * fix README links Co-authored-by: Liz Rice <liz@lizrice.com>
This commit is contained in:
parent
054c401f71
commit
c3f94dd89f
@ -4,6 +4,7 @@ COPY go.mod go.sum ./
|
||||
COPY main.go .
|
||||
COPY check/ check/
|
||||
COPY cmd/ cmd/
|
||||
COPY internal/ internal/
|
||||
ARG KUBEBENCH_VERSION
|
||||
ARG GOOS=linux
|
||||
ARG GOARCH=amd64
|
||||
|
@ -231,6 +231,10 @@ docker push <AWS_ACCT_NUMBER>.dkr.ecr.<AWS_REGION>.amazonaws.com/k8s/kube-bench:
|
||||
8. Retrieve the value of this Pod and output the report, note the Pod name will vary: `kubectl logs kube-bench-<value>`
|
||||
- You can save the report for later reference: `kubectl logs kube-bench-<value> > kube-bench-report.txt`
|
||||
|
||||
#### Report kube-bench findings to AWS Security Hub
|
||||
|
||||
You can configure kube-bench with the `--asff` option to send findings to AWS Security Hub for any benchmark tests that fail or that generate a warning. See [this page][kube-bench-aws-security-hub] for more information on how to enable the kube-bench integration with AWS Security Hub.
|
||||
|
||||
### Running on OpenShift
|
||||
|
||||
| OpenShift Hardening Guide | kube-bench config |
|
||||
@ -426,3 +430,5 @@ We welcome pull requests!
|
||||
- Your PR is more likely to be accepted if it includes tests. (We have not historically been very strict about tests, but we would like to improve this!).
|
||||
- You're welcome to submit a draft PR if you would like early feedback on an idea or an approach.
|
||||
- Happy coding!
|
||||
|
||||
[kube-bench-aws-security-hub]: ./docs/asff.md
|
@ -1,2 +1,9 @@
|
||||
---
|
||||
## Version-specific settings that override the values in cfg/config.yaml
|
||||
## These settings are required if you are using the --asff option to report findings to AWS Security Hub
|
||||
## AWS account number is required.
|
||||
AWS_ACCOUNT: "<AWS_ACCT_NUMBER>"
|
||||
## AWS region is required.
|
||||
AWS_REGION: "<AWS_REGION>"
|
||||
## EKS Cluster ARN is required.
|
||||
CLUSTER_ARN: "<AWS_CLUSTER_ARN>"
|
||||
|
@ -19,12 +19,27 @@ import (
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/golang/glog"
|
||||
"github.com/onsi/ginkgo/reporters"
|
||||
"github.com/spf13/viper"
|
||||
"gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
const (
|
||||
// UNKNOWN is when the AWS account can't be found
|
||||
UNKNOWN = "Unknown"
|
||||
// ARN for the AWS Security Hub service
|
||||
ARN = "arn:aws:securityhub:%s::product/aqua-security/kube-bench"
|
||||
// SCHEMA for the AWS Security Hub service
|
||||
SCHEMA = "2018-10-08"
|
||||
// TYPE is type of Security Hub finding
|
||||
TYPE = "Software and Configuration Checks/Industry and Regulatory Standards/CIS Kubernetes Benchmark"
|
||||
)
|
||||
|
||||
// Controls holds all controls to check for master nodes.
|
||||
type Controls struct {
|
||||
ID string `yaml:"id" json:"id"`
|
||||
@ -75,7 +90,7 @@ func NewControls(t NodeType, in []byte) (*Controls, error) {
|
||||
}
|
||||
|
||||
// RunChecks runs the checks with the given Runner. Only checks for which the filter Predicate returns `true` will run.
|
||||
func (controls *Controls) RunChecks(runner Runner, filter Predicate, skipIdMap map[string]bool) Summary {
|
||||
func (controls *Controls) RunChecks(runner Runner, filter Predicate, skipIDMap map[string]bool) Summary {
|
||||
var g []*Group
|
||||
m := make(map[string]*Group)
|
||||
controls.Summary.Pass, controls.Summary.Fail, controls.Summary.Warn, controls.Info = 0, 0, 0, 0
|
||||
@ -87,8 +102,8 @@ func (controls *Controls) RunChecks(runner Runner, filter Predicate, skipIdMap m
|
||||
continue
|
||||
}
|
||||
|
||||
_, groupSkippedViaCmd := skipIdMap[group.ID]
|
||||
_, checkSkippedViaCmd := skipIdMap[check.ID]
|
||||
_, groupSkippedViaCmd := skipIDMap[group.ID]
|
||||
_, checkSkippedViaCmd := skipIDMap[check.ID]
|
||||
|
||||
if group.Skip || groupSkippedViaCmd || checkSkippedViaCmd {
|
||||
check.Type = SKIP
|
||||
@ -185,6 +200,80 @@ func (controls *Controls) JUnit() ([]byte, error) {
|
||||
return b.Bytes(), nil
|
||||
}
|
||||
|
||||
// ASFF encodes the results of last run to AWS Security Finding Format(ASFF).
|
||||
func (controls *Controls) ASFF() ([]*securityhub.AwsSecurityFinding, error) {
|
||||
fs := []*securityhub.AwsSecurityFinding{}
|
||||
a, err := getConfig("AWS_ACCOUNT")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c, err := getConfig("CLUSTER_ARN")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
region, err := getConfig("AWS_REGION")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
arn := fmt.Sprintf(ARN, region)
|
||||
|
||||
ti := time.Now()
|
||||
tf := ti.Format(time.RFC3339)
|
||||
for _, g := range controls.Groups {
|
||||
for _, check := range g.Checks {
|
||||
if check.State == FAIL || check.State == WARN {
|
||||
// ASFF ProductFields['Actual result'] can't be longer than 1024 characters
|
||||
actualValue := check.ActualValue
|
||||
if len(check.ActualValue) > 1024 {
|
||||
actualValue = check.ActualValue[0:1023]
|
||||
}
|
||||
f := securityhub.AwsSecurityFinding{
|
||||
AwsAccountId: aws.String(a),
|
||||
Confidence: aws.Int64(100),
|
||||
GeneratorId: aws.String(fmt.Sprintf("%s/cis-kubernetes-benchmark/%s/%s", arn, controls.Version, check.ID)),
|
||||
Id: aws.String(fmt.Sprintf("%s%sEKSnodeID+%s%s", arn, a, check.ID, tf)),
|
||||
CreatedAt: aws.String(tf),
|
||||
Description: aws.String(check.Text),
|
||||
ProductArn: aws.String(arn),
|
||||
SchemaVersion: aws.String(SCHEMA),
|
||||
Title: aws.String(fmt.Sprintf("%s %s", check.ID, check.Text)),
|
||||
UpdatedAt: aws.String(tf),
|
||||
Types: []*string{aws.String(TYPE)},
|
||||
Severity: &securityhub.Severity{
|
||||
Label: aws.String(securityhub.SeverityLabelHigh),
|
||||
},
|
||||
Remediation: &securityhub.Remediation{
|
||||
Recommendation: &securityhub.Recommendation{
|
||||
Text: aws.String(check.Remediation),
|
||||
},
|
||||
},
|
||||
ProductFields: map[string]*string{
|
||||
"Reason": aws.String(check.Reason),
|
||||
"Actual result": aws.String(actualValue),
|
||||
"Expected result": aws.String(check.ExpectedResult),
|
||||
"Section": aws.String(fmt.Sprintf("%s %s", controls.ID, controls.Text)),
|
||||
"Subsection": aws.String(fmt.Sprintf("%s %s", g.ID, g.Text)),
|
||||
},
|
||||
Resources: []*securityhub.Resource{
|
||||
{
|
||||
Id: aws.String(c),
|
||||
Type: aws.String(TYPE),
|
||||
},
|
||||
},
|
||||
}
|
||||
fs = append(fs, &f)
|
||||
}
|
||||
}
|
||||
}
|
||||
return fs, nil
|
||||
}
|
||||
func getConfig(name string) (string, error) {
|
||||
r := viper.GetString(name)
|
||||
if len(r) == 0 {
|
||||
return "", fmt.Errorf("%s not set", name)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
func summarize(controls *Controls, state State) {
|
||||
switch state {
|
||||
case PASS:
|
||||
|
@ -18,12 +18,17 @@ import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/onsi/ginkgo/reporters"
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"gopkg.in/yaml.v2"
|
||||
@ -357,3 +362,104 @@ func assertEqualGroupSummary(t *testing.T, pass, fail, info, warn int, actual *G
|
||||
assert.Equal(t, info, actual.Info)
|
||||
assert.Equal(t, warn, actual.Warn)
|
||||
}
|
||||
|
||||
func TestControls_ASFF(t *testing.T) {
|
||||
type fields struct {
|
||||
ID string
|
||||
Version string
|
||||
Text string
|
||||
Groups []*Group
|
||||
Summary Summary
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
want []*securityhub.AwsSecurityFinding
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Test simple conversion",
|
||||
fields: fields{
|
||||
ID: "test1",
|
||||
Version: "1",
|
||||
Text: "test runnner",
|
||||
Summary: Summary{
|
||||
Fail: 99,
|
||||
Pass: 100,
|
||||
Warn: 101,
|
||||
Info: 102,
|
||||
},
|
||||
Groups: []*Group{
|
||||
{
|
||||
ID: "g1",
|
||||
Text: "Group text",
|
||||
Checks: []*Check{
|
||||
{ID: "check1id",
|
||||
Text: "check1text",
|
||||
State: FAIL,
|
||||
Remediation: "fix me",
|
||||
Reason: "failed",
|
||||
ExpectedResult: "failed",
|
||||
ActualValue: "failed",
|
||||
},
|
||||
},
|
||||
},
|
||||
}},
|
||||
want: []*securityhub.AwsSecurityFinding{
|
||||
{
|
||||
AwsAccountId: aws.String("foo account"),
|
||||
Confidence: aws.Int64(100),
|
||||
GeneratorId: aws.String(fmt.Sprintf("%s/cis-kubernetes-benchmark/%s/%s", fmt.Sprintf(ARN, "somewhere"), "1", "check1id")),
|
||||
Description: aws.String("check1text"),
|
||||
ProductArn: aws.String(fmt.Sprintf(ARN, "somewhere")),
|
||||
SchemaVersion: aws.String(SCHEMA),
|
||||
Title: aws.String(fmt.Sprintf("%s %s", "check1id", "check1text")),
|
||||
Types: []*string{aws.String(TYPE)},
|
||||
Severity: &securityhub.Severity{
|
||||
Label: aws.String(securityhub.SeverityLabelHigh),
|
||||
},
|
||||
Remediation: &securityhub.Remediation{
|
||||
Recommendation: &securityhub.Recommendation{
|
||||
Text: aws.String("fix me"),
|
||||
},
|
||||
},
|
||||
ProductFields: map[string]*string{
|
||||
"Reason": aws.String("failed"),
|
||||
"Actual result": aws.String("failed"),
|
||||
"Expected result": aws.String("failed"),
|
||||
"Section": aws.String(fmt.Sprintf("%s %s", "test1", "test runnner")),
|
||||
"Subsection": aws.String(fmt.Sprintf("%s %s", "g1", "Group text")),
|
||||
},
|
||||
Resources: []*securityhub.Resource{
|
||||
{
|
||||
Id: aws.String("foo Cluster"),
|
||||
Type: aws.String(TYPE),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
viper.Set("AWS_ACCOUNT", "foo account")
|
||||
viper.Set("CLUSTER_ARN", "foo Cluster")
|
||||
viper.Set("AWS_REGION", "somewhere")
|
||||
controls := &Controls{
|
||||
ID: tt.fields.ID,
|
||||
Version: tt.fields.Version,
|
||||
Text: tt.fields.Text,
|
||||
Groups: tt.fields.Groups,
|
||||
Summary: tt.fields.Summary,
|
||||
}
|
||||
got, _ := controls.ASFF()
|
||||
tt.want[0].CreatedAt = got[0].CreatedAt
|
||||
tt.want[0].UpdatedAt = got[0].UpdatedAt
|
||||
tt.want[0].Id = got[0].Id
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("Controls.ASFF() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -373,6 +373,10 @@ func writeOutput(controlsCollection []*check.Controls) {
|
||||
writePgsqlOutput(controlsCollection)
|
||||
return
|
||||
}
|
||||
if aSFF {
|
||||
writeASFFOutput(controlsCollection)
|
||||
return
|
||||
}
|
||||
writeStdoutOutput(controlsCollection)
|
||||
}
|
||||
|
||||
@ -404,6 +408,18 @@ func writePgsqlOutput(controlsCollection []*check.Controls) {
|
||||
}
|
||||
}
|
||||
|
||||
func writeASFFOutput(controlsCollection []*check.Controls) {
|
||||
for _, controls := range controlsCollection {
|
||||
out, err := controls.ASFF()
|
||||
if err != nil {
|
||||
exitWithError(fmt.Errorf("failed to format findings as ASFF: %v", err))
|
||||
}
|
||||
if err := writeFinding(out); err != nil {
|
||||
exitWithError(fmt.Errorf("failed to output to ASFF: %v", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeStdoutOutput(controlsCollection []*check.Controls) {
|
||||
for _, controls := range controlsCollection {
|
||||
summary := controls.Summary
|
||||
|
@ -42,6 +42,7 @@ var (
|
||||
jsonFmt bool
|
||||
junitFmt bool
|
||||
pgSQL bool
|
||||
aSFF bool
|
||||
masterFile = "master.yaml"
|
||||
nodeFile = "node.yaml"
|
||||
etcdFile = "etcd.yaml"
|
||||
@ -156,6 +157,7 @@ func init() {
|
||||
RootCmd.PersistentFlags().BoolVar(&jsonFmt, "json", false, "Prints the results as JSON")
|
||||
RootCmd.PersistentFlags().BoolVar(&junitFmt, "junit", false, "Prints the results as JUnit")
|
||||
RootCmd.PersistentFlags().BoolVar(&pgSQL, "pgsql", false, "Save the results to PostgreSQL")
|
||||
RootCmd.PersistentFlags().BoolVar(&aSFF, "asff", false, "Send the results to AWS Security Hub")
|
||||
RootCmd.PersistentFlags().BoolVar(&filterOpts.Scored, "scored", true, "Run the scored CIS checks")
|
||||
RootCmd.PersistentFlags().BoolVar(&filterOpts.Unscored, "unscored", true, "Run the unscored CIS checks")
|
||||
RootCmd.PersistentFlags().StringVar(&skipIds, "skip", "", "List of comma separated values of checks to be skipped")
|
||||
|
47
cmd/securityHub.go
Normal file
47
cmd/securityHub.go
Normal file
@ -0,0 +1,47 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/aquasecurity/kube-bench/internal/findings"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
//REGION ...
|
||||
const REGION = "AWS_REGION"
|
||||
|
||||
func writeFinding(in []*securityhub.AwsSecurityFinding) error {
|
||||
r := viper.GetString(REGION)
|
||||
if len(r) == 0 {
|
||||
return fmt.Errorf("%s not set", REGION)
|
||||
}
|
||||
sess, err := session.NewSession(&aws.Config{
|
||||
Region: aws.String(r)},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
svc := securityhub.New(sess)
|
||||
p := findings.New(svc)
|
||||
out, perr := p.PublishFinding(in)
|
||||
print(out)
|
||||
return perr
|
||||
}
|
||||
|
||||
func print(out *findings.PublisherOutput) {
|
||||
if out.SuccessCount > 0 {
|
||||
log.Printf("Number of findings that were successfully imported:%v\n", out.SuccessCount)
|
||||
}
|
||||
if out.FailedCount > 0 {
|
||||
log.Printf("Number of findings that failed to import:%v\n", out.FailedCount)
|
||||
for _, f := range out.FailedFindings {
|
||||
log.Printf("ID:%s", *f.Id)
|
||||
log.Printf("Message:%s", *f.ErrorMessage)
|
||||
log.Printf("Error Code:%s", *f.ErrorCode)
|
||||
}
|
||||
}
|
||||
}
|
39
docs/asff.md
Normal file
39
docs/asff.md
Normal file
@ -0,0 +1,39 @@
|
||||
# Integrating kube-bench with AWS Security Hub
|
||||
|
||||
You can configure kube-bench with the `--asff` to send findings to AWS Security Hub. There are some additional steps required so that kube-bench has information and permissions to send these findings.
|
||||
|
||||
## Enable the AWS Security Hub integration
|
||||
|
||||
* You will need AWS Security Hub to be enabled in your account
|
||||
* In the Security Hub console, under Integrations, search for kube-bench
|
||||
|
||||
<p align="center">
|
||||
<img src="../images/kube-bench-security-hub.png">
|
||||
</p>
|
||||
|
||||
* Click on `Accept findings`. This gives information about the IAM permissions required to send findings to your Security Hub account. kube-bench runs within a pod on your EKS cluster, and will need to be associated with a Role that has these permissions.
|
||||
|
||||
## Configure permissions in an IAM Role
|
||||
|
||||
* Grant these permissions to the IAM Role that the kube-bench pod will be associated with. There are two potions:
|
||||
* You can run the kube-bench pod under a specific [service account associated with an IAM role](https://docs.aws.amazon.com/eks/latest/userguide/iam-roles-for-service-accounts.html) that has these permissions to write Security Hub findings.
|
||||
* Alternatively the pod can be granted permissions specified by the Role that your [EKS node group uses](https://docs.aws.amazon.com/eks/latest/userguide/managed-node-groups.html).
|
||||
|
||||
## Configure and rebuild kube-bench
|
||||
|
||||
You will need to download, build and push the kube-bench container image to your ECR repo as described in Step 3 of the [EKS instructions][eks-instructions], except that before you build the container image, you need to edit `cfg/eks-1.0/config.yaml` to specify the AWS account, AWS region, and the EKS Cluster ARN.
|
||||
|
||||
## Modify the job configuration
|
||||
|
||||
* Modify `job-eks.yaml` to specify the `--asff` flag, so that kube-bench writes output in ASFF format to Security Hub
|
||||
* Make sure that `job-eks.yaml` specifies the container image you just pushed to your ECR registry.
|
||||
|
||||
You can now run kube-bench as a pod in your cluster: `kubectl apply -f job-eks.yaml`
|
||||
|
||||
Findings will be generated for any kube-bench test that generates a `[FAIL]` or `[WARN]` output. If all tests pass, no findings will be generated. However, it's recommended that you consult the pod log output to check whether any findings were generated but could not be written to Security Hub.
|
||||
|
||||
<p align="center">
|
||||
<img src="../images/asff-example-finding.png">
|
||||
</p>
|
||||
|
||||
[eks-instructions]: ../README.md#running-in-an-EKS-cluster
|
10
go.mod
10
go.mod
@ -3,11 +3,13 @@ module github.com/aquasecurity/kube-bench
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/aws/aws-sdk-go v1.35.28
|
||||
github.com/denisenkom/go-mssqldb v0.0.0-20190515213511-eb9f6a1743f3 // indirect
|
||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 // indirect
|
||||
github.com/fatih/color v1.5.0
|
||||
github.com/go-sql-driver/mysql v1.4.1 // indirect
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b
|
||||
github.com/google/go-cmp v0.3.1 // indirect
|
||||
github.com/imdario/mergo v0.3.5 // indirect
|
||||
github.com/jinzhu/gorm v0.0.0-20160404144928-5174cc5c242a
|
||||
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d // indirect
|
||||
@ -17,13 +19,13 @@ require (
|
||||
github.com/mattn/go-isatty v0.0.0-20170307163044-57fdcb988a5c // indirect
|
||||
github.com/mattn/go-sqlite3 v1.10.0 // indirect
|
||||
github.com/onsi/ginkgo v1.10.1
|
||||
github.com/pkg/errors v0.8.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/spf13/cobra v0.0.3
|
||||
github.com/spf13/viper v1.4.0
|
||||
github.com/stretchr/testify v1.3.0
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a // indirect
|
||||
github.com/stretchr/testify v1.4.0
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
|
||||
google.golang.org/appengine v1.5.0 // indirect
|
||||
gopkg.in/yaml.v2 v2.2.4
|
||||
gopkg.in/yaml.v2 v2.2.8
|
||||
k8s.io/api v0.0.0-20190409021203-6e4e0e4f393b
|
||||
k8s.io/apimachinery v0.0.0-20190404173353-6a84e37a896d
|
||||
k8s.io/client-go v11.0.0+incompatible
|
||||
|
22
go.sum
22
go.sum
@ -18,6 +18,8 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ=
|
||||
github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
|
||||
github.com/aws/aws-sdk-go v1.35.28 h1:S2LuRnfC8X05zgZLC8gy/Sb82TGv2Cpytzbzz7tkeHc=
|
||||
github.com/aws/aws-sdk-go v1.35.28/go.mod h1:tlPOdRjfxPBpNIwqDj61rmsnA85v9jc0Ps9+muhnW+k=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||
@ -87,6 +89,8 @@ github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Z
|
||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||
github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ=
|
||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||
github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg=
|
||||
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI=
|
||||
github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
@ -118,6 +122,10 @@ github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVt
|
||||
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M=
|
||||
github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
|
||||
github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs=
|
||||
@ -177,6 +185,8 @@ github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
@ -223,6 +233,8 @@ github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRci
|
||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
|
||||
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
|
||||
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
|
||||
@ -257,10 +269,12 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR
|
||||
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2 h1:CCH4IOTTfewWjGOlSp+zGcjutRKlBEZQ6wTn8ozI/nI=
|
||||
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a h1:tImsplftrFpALCYumobsd0K86vlAs/eXGFms2txfJfA=
|
||||
golang.org/x/oauth2 v0.0.0-20190402181905-9f3314589c9a/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d h1:TzXSXBo42m9gQenoE3b9BGiEpg5IG2JkU5FkPIawgtw=
|
||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -320,8 +334,8 @@ gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWD
|
||||
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
|
BIN
images/asff-example-finding.png
Normal file
BIN
images/asff-example-finding.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
BIN
images/kube-bench-security-hub.png
Normal file
BIN
images/kube-bench-security-hub.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 124 KiB |
4
internal/findings/doc.go
Normal file
4
internal/findings/doc.go
Normal file
@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package findings handles sending findings to Security Hub.
|
||||
*/
|
||||
package findings
|
69
internal/findings/publisher.go
Normal file
69
internal/findings/publisher.go
Normal file
@ -0,0 +1,69 @@
|
||||
package findings
|
||||
|
||||
import (
|
||||
"github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/aws/aws-sdk-go/service/securityhub/securityhubiface"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// A Publisher represents an object that publishes finds to AWS Security Hub.
|
||||
type Publisher struct {
|
||||
client securityhubiface.SecurityHubAPI // AWS Security Hub Service Client
|
||||
}
|
||||
|
||||
// A PublisherOutput represents an object that contains information about the service call.
|
||||
type PublisherOutput struct {
|
||||
// The number of findings that failed to import.
|
||||
//
|
||||
// FailedCount is a required field
|
||||
FailedCount int64
|
||||
|
||||
// The list of findings that failed to import.
|
||||
FailedFindings []*securityhub.ImportFindingsError
|
||||
|
||||
// The number of findings that were successfully imported.
|
||||
//
|
||||
// SuccessCount is a required field
|
||||
SuccessCount int64
|
||||
}
|
||||
|
||||
// New creates a new Publisher.
|
||||
func New(client securityhubiface.SecurityHubAPI) *Publisher {
|
||||
return &Publisher{
|
||||
client: client,
|
||||
}
|
||||
}
|
||||
|
||||
// PublishFinding publishes findings to AWS Security Hub Service
|
||||
func (p *Publisher) PublishFinding(finding []*securityhub.AwsSecurityFinding) (*PublisherOutput, error) {
|
||||
o := PublisherOutput{}
|
||||
i := securityhub.BatchImportFindingsInput{}
|
||||
i.Findings = finding
|
||||
var errs error
|
||||
|
||||
// Split the slice into batches of 100 finding.
|
||||
batch := 100
|
||||
|
||||
for i := 0; i < len(finding); i += batch {
|
||||
j := i + batch
|
||||
if j > len(finding) {
|
||||
j = len(finding)
|
||||
}
|
||||
i := securityhub.BatchImportFindingsInput{}
|
||||
i.Findings = finding
|
||||
r, err := p.client.BatchImportFindings(&i) // Process the batch.
|
||||
if err != nil {
|
||||
errs = errors.Wrap(err, "finding publish failed")
|
||||
}
|
||||
if r.FailedCount != nil {
|
||||
o.FailedCount += *r.FailedCount
|
||||
}
|
||||
if r.SuccessCount != nil {
|
||||
o.SuccessCount += *r.SuccessCount
|
||||
}
|
||||
for _, ff := range r.FailedFindings {
|
||||
o.FailedFindings = append(o.FailedFindings, ff)
|
||||
}
|
||||
}
|
||||
return &o, errs
|
||||
}
|
68
internal/findings/publisher_test.go
Normal file
68
internal/findings/publisher_test.go
Normal file
@ -0,0 +1,68 @@
|
||||
package findings
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/aws/aws-sdk-go/service/securityhub"
|
||||
"github.com/aws/aws-sdk-go/service/securityhub/securityhubiface"
|
||||
)
|
||||
|
||||
// Define a mock struct to be used in your unit tests of myFunc.
|
||||
type MockSHClient struct {
|
||||
securityhubiface.SecurityHubAPI
|
||||
Batches int
|
||||
NumberOfFinding int
|
||||
}
|
||||
|
||||
func NewMockSHClient() *MockSHClient {
|
||||
return &MockSHClient{}
|
||||
}
|
||||
|
||||
func (m *MockSHClient) BatchImportFindings(input *securityhub.BatchImportFindingsInput) (*securityhub.BatchImportFindingsOutput, error) {
|
||||
o := securityhub.BatchImportFindingsOutput{}
|
||||
m.Batches++
|
||||
m.NumberOfFinding = len(input.Findings)
|
||||
return &o, nil
|
||||
}
|
||||
|
||||
func TestPublisher_publishFinding(t *testing.T) {
|
||||
type fields struct {
|
||||
client *MockSHClient
|
||||
}
|
||||
type args struct {
|
||||
finding []*securityhub.AwsSecurityFinding
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
fields fields
|
||||
args args
|
||||
wantBatchCount int
|
||||
wantFindingCount int
|
||||
}{
|
||||
{"Test single finding", fields{NewMockSHClient()}, args{makeFindings(1)}, 1, 1},
|
||||
{"Test 150 finding should return 2 batches", fields{NewMockSHClient()}, args{makeFindings(150)}, 2, 150},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
p := New(tt.fields.client)
|
||||
p.PublishFinding(tt.args.finding)
|
||||
if tt.fields.client.NumberOfFinding != tt.wantFindingCount {
|
||||
t.Errorf("Publisher.publishFinding() want = %v, got %v", tt.wantFindingCount, tt.fields.client.NumberOfFinding)
|
||||
}
|
||||
if tt.fields.client.Batches != tt.wantBatchCount {
|
||||
t.Errorf("Publisher.publishFinding() want = %v, got %v", tt.wantBatchCount, tt.fields.client.Batches)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func makeFindings(count int) []*securityhub.AwsSecurityFinding {
|
||||
var findings []*securityhub.AwsSecurityFinding
|
||||
|
||||
for i := 0; i < count; i++ {
|
||||
t := securityhub.AwsSecurityFinding{}
|
||||
findings = append(findings, &t)
|
||||
|
||||
}
|
||||
return findings
|
||||
}
|
@ -11,6 +11,10 @@ spec:
|
||||
- name: kube-bench
|
||||
# Push the image to your ECR and then refer to it here
|
||||
image: <ID.dkr.ecr.region.amazonaws.com/aquasec/kube-bench:ref>
|
||||
# Use the --asff flag if you would like to send findings to AWS Security Hub
|
||||
# Note that this requires you to rebuild a version of the kube-bench image
|
||||
# after editing the cfg/eks-1.0/config.yaml with your account information
|
||||
# command: ["kube-bench", "node", "--benchmark", "eks-1.0", "--asff"]
|
||||
command: ["kube-bench", "node", "--benchmark", "eks-1.0"]
|
||||
volumeMounts:
|
||||
- name: var-lib-kubelet
|
||||
|
Loading…
Reference in New Issue
Block a user