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>pull/778/head
parent
054c401f71
commit
c3f94dd89f
@ -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>"
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
After Width: | Height: | Size: 85 KiB |
After Width: | Height: | Size: 124 KiB |
@ -0,0 +1,4 @@
|
||||
/*
|
||||
Package findings handles sending findings to Security Hub.
|
||||
*/
|
||||
package findings
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
Loading…
Reference in new issue