Skip to content

Commit

Permalink
Use full role ARN and support overriding roles on a per account basis (
Browse files Browse the repository at this point in the history
  • Loading branch information
andrew-corbalt authored Jul 16, 2024
1 parent 093d3f1 commit 92dc46c
Show file tree
Hide file tree
Showing 28 changed files with 345 additions and 29 deletions.
3 changes: 2 additions & 1 deletion .github/workflows/build-and-push-dev.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: build-and-push

on:
workflow_dispatch:
push:
branches:
- main
Expand Down Expand Up @@ -31,7 +32,7 @@ jobs:
run: echo "ECR_URI=${{ steps.login-ecr.outputs.registry }}/security-hub-collector" >> $GITHUB_ENV

- name: Build the Docker image
run: docker build . --file Dockerfile --tag $ECR_URI:$(git rev-parse --short $GITHUB_SHA) --tag $ECR_URI:v2
run: docker build . --file Dockerfile --tag $ECR_URI:$(git rev-parse --short $GITHUB_SHA)

- name: Push docker image to Amazon ECR
run: |
Expand Down
9 changes: 4 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Description

This tool pulls findings from AWS Security Hub and outputs them for consumption by visualization tools. To use this tool, you need a cross-account role that is valid for all accounts listed in the team map provided to the tool.
This tool pulls findings from AWS Security Hub and outputs them for consumption by visualization tools. To use this tool, you need a role ARN that is valid for each account listed in the team map provided to the tool.

## Installation

Expand All @@ -18,15 +18,15 @@ To display a full list of CLI options, build the application and run `security-h


You will need to create a team map file with a JSON object that describes
your teams based on account numbers and environments. For example:
your teams based on account numbers, environments and role ARN which will be used to query the account. For example:

```json
{
"teams": [
{
"accounts": [
{ "id": "000000000001", "environment": "dev" },
{ "id": "000000000011", "environment": "test" }
{ "id": "000000000011", "environment": "dev", "roleArn": "arn:aws:iam::000000000011:role/CustomRole" },
{ "id": "000000000012", "environment": "test", "roleArn": "arn:aws:iam::000000000012:role/delegatedadmin/developer/AnotherCustomRole" }
],
"name":"My Team"
}
Expand All @@ -48,7 +48,6 @@ To run the Docker image locally for testing, do the following:
docker run \
-e AWS_SECRET_ACCESS_KEY -e AWS_SESSION_TOKEN -e AWS_ACCESS_KEY_ID -e TEAM_MAP \
-e AWS_REGION={region}
-e ASSUME_ROLE={role name} \
-e S3_BUCKET_PATH={bucket name} \
local-collector-test
```
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/CMSGov/security-hub-collector
go 1.19

require (
github.com/aws/aws-sdk-go v1.54.17
github.com/aws/aws-sdk-go-v2 v1.17.7
github.com/aws/aws-sdk-go-v2/config v1.18.19
github.com/aws/aws-sdk-go-v2/credentials v1.13.18
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
github.com/aws/aws-sdk-go v1.54.17 h1:ZV/qwcCIhMHgsJ6iXXPVYI0s1MdLT+5LW28ClzCUPeI=
github.com/aws/aws-sdk-go v1.54.17/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU=
github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.4.10 h1:dK82zF6kkPeCo8J1e+tGx4JdvDIQzj7ygIoLg8WMuGs=
Expand Down
10 changes: 1 addition & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"context"
"fmt"
"os"
"path"
"strings"
Expand All @@ -23,7 +22,6 @@ import (

// Options describes the command line options available.
type Options struct {
AssumeRole string `short:"a" long:"assume-role" required:"true" description:"Role name to assume when collecting across all accounts."`
OutputFileName string `short:"o" long:"output" required:"false" description:"File to direct output to." default:"SecurityHub-Findings.csv"`
S3Region string `short:"s" long:"s3-region" env:"AWS_REGION" required:"false" description:"AWS region to use for s3 uploads."`
SecurityHubRegions []string `short:"r" long:"sechub-regions" required:"false" default:"us-east-1" default:"us-west-2" description:"AWS regions to use for Security Hub findings."`
Expand Down Expand Up @@ -105,14 +103,8 @@ func collectFindings(secHubRegions []string) {
log.Fatalf("could not parse team map file: %v", err)
}

// Add a leading slash to the provided role if it doesn't already have one
formattedRole := options.AssumeRole
if !strings.HasPrefix(formattedRole, "/") {
formattedRole = "/" + formattedRole
}

for account, teamName := range accountsToTeams {
roleArn := fmt.Sprintf("arn:aws:iam::%s:role%s", account.ID, formattedRole)
roleArn := account.RoleARN

for _, secHubRegion := range secHubRegions {
log.Printf("getting findings for account %v in %v", account.ID, secHubRegion)
Expand Down
12 changes: 8 additions & 4 deletions pkg/teams/team_map_test_duplicate.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"accounts": [
{
"environment": "dev",
"id": "account 1"
"id": "account 1",
"roleArn": "arn:aws:iam::000000000011:role/CustomRole"
},
{
"environment": "test",
"id": "account 11"
"id": "account 11",
"roleArn": "arn:aws:iam::000000000012:role/CustomRole"
}
],
"name": "Test Team 1"
Expand All @@ -17,11 +19,13 @@
"accounts": [
{
"environment": "impl",
"id": "account 2"
"id": "account 2",
"roleArn": "arn:aws:iam::000000000013:role/CustomRole"
},
{
"environment": "prod",
"id": "account 11"
"id": "account 11",
"roleArn": "arn:aws:iam::000000000014:role/CustomRole"
}
],
"name": "Test Team 2"
Expand Down
19 changes: 19 additions & 0 deletions pkg/teams/team_map_test_invalid_arn.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"teams": [
{
"accounts": [
{
"environment": "dev",
"id": "account 1",
"roleArn": "invalid:arn:format"
},
{
"environment": "prod",
"id": "account 2",
"roleArn": "arn:aws:iam::000000000011:role/CustomRole"
}
],
"name": "Test Team 1"
}
]
}
12 changes: 8 additions & 4 deletions pkg/teams/team_map_test_valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@
"accounts": [
{
"environment": "dev",
"id": "account 1"
"id": "account 1",
"roleArn": "arn:aws:iam::000000000011:role/CustomRole"
},
{
"environment": "test",
"id": "account 11"
"id": "account 11",
"roleArn": "arn:aws:iam::000000000012:role/CustomRole"
}
],
"name": "Test Team 1"
Expand All @@ -17,11 +19,13 @@
"accounts": [
{
"environment": "impl",
"id": "account 2"
"id": "account 2",
"roleArn": "arn:aws:iam::000000000013:role/CustomRole"
},
{
"environment": "prod",
"id": "account 22"
"id": "account 22",
"roleArn": "arn:aws:iam::000000000014:role/CustomRole"
}
],
"name": "Test Team 2"
Expand Down
17 changes: 17 additions & 0 deletions pkg/teams/teams.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import (
"os"
"path/filepath"

"github.com/aws/aws-sdk-go/aws/arn"

"github.com/CMSGov/security-hub-collector/pkg/helpers"
)

Expand All @@ -17,6 +19,14 @@ func (e *duplicateAccountIDError) Error() string {
return e.message
}

type invalidRoleARNError struct {
message string
}

func (e *invalidRoleARNError) Error() string {
return e.message
}

// Teams is a struct describing the format we expect in the JSON file
// describing the team mappings
type Teams struct {
Expand All @@ -34,6 +44,7 @@ type Team struct {
type Account struct {
ID string `json:"id"`
Environment string `json:"environment"`
RoleARN string `json:"roleArn"`
}

// ParseTeamMap takes a path to a team mapping JSON file, reads the file, and returns a Go map of Accounts to team names
Expand Down Expand Up @@ -101,6 +112,12 @@ func (t *Teams) accountsToTeamNames() (map[Account]string, error) {
message: fmt.Sprintf("duplicate account ID: %s", account.ID),
}
}

if !arn.IsARN(account.RoleARN) {
return nil, &invalidRoleARNError{
message: fmt.Sprintf("invalid role ARN for account %s: %s Input must be a valid Role ARN", account.ID, account.RoleARN),
}
}
a[account] = team.Name
}
}
Expand Down
18 changes: 13 additions & 5 deletions pkg/teams/teams_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
)

var expectedAccountsToTeams = map[Account]string{
{ID: "account 1", Environment: "dev"}: "Test Team 1",
{ID: "account 11", Environment: "test"}: "Test Team 1",
{ID: "account 2", Environment: "impl"}: "Test Team 2",
{ID: "account 22", Environment: "prod"}: "Test Team 2",
{ID: "account 1", Environment: "dev", RoleARN: "arn:aws:iam::000000000011:role/CustomRole"}: "Test Team 1",
{ID: "account 11", Environment: "test", RoleARN: "arn:aws:iam::000000000012:role/CustomRole"}: "Test Team 1",
{ID: "account 2", Environment: "impl", RoleARN: "arn:aws:iam::000000000013:role/CustomRole"}: "Test Team 2",
{ID: "account 22", Environment: "prod", RoleARN: "arn:aws:iam::000000000014:role/CustomRole"}: "Test Team 2",
}

func TestParseTeamMap(t *testing.T) {
Expand All @@ -26,9 +26,17 @@ func TestParseTeamMap(t *testing.T) {

// this test checks that a duplicate account ID is caught
_, err = ParseTeamMap("team_map_test_duplicate.json")
fmt.Printf("err: %v", err)
fmt.Printf("err: %v\n", err)
var duplicateAccountIDError *duplicateAccountIDError
if err == nil || !errors.As(err, &duplicateAccountIDError) {
t.Error("ERROR: didn't get expected error for duplicate account ID", err)
}

// Test invalid ARN
_, err = ParseTeamMap("team_map_test_invalid_arn.json")
fmt.Printf("err: %v\n", err)
var invalidRoleARNError *invalidRoleARNError
if err == nil || !errors.As(err, &invalidRoleARNError) {
t.Error("ERROR: didn't get expected error for invalid Role ARN", err)
}
}
1 change: 0 additions & 1 deletion scriptRunner.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ security-hub-collector \
-m teammap.json \
${OUTPUT:+-o "$OUTPUT"} \
${S3_KEY:+-k "$S3_KEY"} \
${ASSUME_ROLE:+-a "$ASSUME_ROLE"} \
${S3_BUCKET_PATH:+-b "$S3_BUCKET_PATH"}

echo "Task complete"
7 changes: 7 additions & 0 deletions terraform/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
## Collector

/collector contains the actual collector application for verification of customer issues.

## ECR

/ecr contains the terraform that sets up the infrastructure to deploy the image and share it to customers.
24 changes: 24 additions & 0 deletions terraform/collector/.terraform.lock.hcl

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 92dc46c

Please sign in to comment.