├── .github
├── release.yml
└── workflows
│ ├── ci.yml
│ └── tagpr.yml
├── .gitignore
├── .golangci.yml
├── .goreleaser.yml
├── .octocov.yml
├── .tagpr
├── CHANGELOG.md
├── CREDITS
├── LICENSE
├── Makefile
├── README.md
├── cmd
├── apply.go
├── export.go
├── notify.go
├── plan.go
└── root.go
├── go.mod
├── go.sum
├── main.go
├── sechub
├── apply.go
├── fetch.go
├── finding.go
├── finding_test.go
├── notify.go
├── notify_test.go
├── plan.go
├── sechub.go
├── sechub_test.go
├── testdata
│ ├── change_header.golden
│ ├── change_message.golden
│ ├── notify_critical.golden
│ ├── use_custom_template.golden
│ └── use_default_template.golden
├── yaml.go
└── yaml_test.go
└── version
└── version.go
/.github/release.yml:
--------------------------------------------------------------------------------
1 | changelog:
2 | exclude:
3 | labels:
4 | - tagpr
5 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: build
2 |
3 | on:
4 | push:
5 | branches:
6 | - main
7 | pull_request:
8 |
9 | jobs:
10 | job-test:
11 | name: Test
12 | runs-on: ubuntu-latest
13 | steps:
14 | - name: Check out source code
15 | uses: actions/checkout@v4
16 |
17 | - name: Set up Go
18 | uses: actions/setup-go@v5
19 | with:
20 | go-version-file: go.mod
21 |
22 | - name: Run test
23 | run: make ci
24 |
25 | - name: Run octocov
26 | uses: k1LoW/octocov-action@v1
27 |
--------------------------------------------------------------------------------
/.github/workflows/tagpr.yml:
--------------------------------------------------------------------------------
1 | name: tagpr
2 | on:
3 | push:
4 | branches:
5 | - main
6 |
7 | jobs:
8 | tagpr:
9 | runs-on: ubuntu-latest
10 | outputs:
11 | tagpr-tag: ${{ steps.run-tagpr.outputs.tag }}
12 | env:
13 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
14 | steps:
15 | - name: Check out source code
16 | uses: actions/checkout@v4
17 |
18 | - name: Set up Go
19 | uses: actions/setup-go@v5
20 | with:
21 | go-version-file: go.mod
22 |
23 | - id: run-tagpr
24 | name: Run tagpr
25 | uses: Songmu/tagpr@v1
26 |
27 | release:
28 | needs: tagpr
29 | if: needs.tagpr.outputs.tagpr-tag != ''
30 | runs-on: macos-latest
31 | env:
32 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
33 | steps:
34 | - name: Check out source code
35 | uses: actions/checkout@v4
36 | with:
37 | fetch-depth: 0
38 |
39 | - name: Set up Go
40 | uses: actions/setup-go@v5
41 | with:
42 | go-version-file: go.mod
43 |
44 | - name: Setup
45 | run: brew install goreleaser
46 |
47 | - name: Release
48 | run: make release
49 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | dist/
2 | coverage.out
3 | control-controls
4 | *.yml
5 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | linters:
2 | fast: false
3 | enable:
4 | - gosec
5 | linters-settings:
6 | staticcheck:
7 | go: 1.17
8 | issues:
9 | exclude:
10 | - SA3000
11 |
--------------------------------------------------------------------------------
/.goreleaser.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | before:
3 | hooks:
4 | - go mod download
5 | - go mod tidy
6 | builds:
7 | -
8 | id: control-controls-darwin
9 | ldflags:
10 | - -s -w -X github.com/pepabo/control-controls.version={{.Version}} -X github.com/pepabo/control-controls.commit={{.FullCommit}} -X github.com/pepabo/control-controls.date={{.Date}} -X github.com/pepabo/control-controls/version.Version={{.Version}}
11 | goos:
12 | - darwin
13 | goarch:
14 | - amd64
15 | - arm64
16 | -
17 | id: control-controls-linux
18 | ldflags:
19 | - -s -w -X github.com/pepabo/control-controls.version={{.Version}} -X github.com/pepabo/control-controls.commit={{.FullCommit}} -X github.com/pepabo/control-controls.date={{.Date}} -X github.com/pepabo/control-controls/version.Version={{.Version}}
20 | goos:
21 | - linux
22 | goarch:
23 | - amd64
24 | - arm64
25 | archives:
26 | -
27 | id: control-controls-archive
28 | name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
29 | format_overrides:
30 | - goos: darwin
31 | format: zip
32 | files:
33 | - CREDITS
34 | - README.md
35 | - CHANGELOG.md
36 | checksum:
37 | name_template: 'checksums.txt'
38 | snapshot:
39 | name_template: "{{ .Version }}-next"
40 | changelog:
41 | sort: asc
42 | filters:
43 | exclude:
44 | - '^docs:'
45 | - '^test:'
46 | nfpms:
47 | -
48 | id: control-controls-nfpms
49 | file_name_template: "{{ .ProjectName }}_{{ .Version }}-1_{{ .Arch }}"
50 | builds:
51 | - control-controls-linux
52 | homepage: https://github.com/pepabo/control-controls
53 | maintainer: 'GMO Pepabo, inc.'
54 | description: control-controls control controls of AWS Security Hub across all regions.
55 | license: MIT
56 | formats:
57 | - deb
58 | - rpm
59 | - apk
60 | bindir: /usr/bin
61 | epoch: 1
62 |
--------------------------------------------------------------------------------
/.octocov.yml:
--------------------------------------------------------------------------------
1 | # generated by octocov init
2 | coverage:
3 | if: true
4 | codeToTestRatio:
5 | code:
6 | - '**/*.go'
7 | - '!**/*_test.go'
8 | test:
9 | - '**/*_test.go'
10 | testExecutionTime:
11 | if: true
12 | diff:
13 | datastores:
14 | - artifact://${GITHUB_REPOSITORY}
15 | comment:
16 | if: is_pull_request
17 | report:
18 | if: is_default_branch
19 | datastores:
20 | - artifact://${GITHUB_REPOSITORY}
21 |
--------------------------------------------------------------------------------
/.tagpr:
--------------------------------------------------------------------------------
1 | # config file for the tagpr in git config format
2 | # The tagpr generates the initial configuration, which you can rewrite to suit your environment.
3 | # CONFIGURATIONS:
4 | # tagpr.releaseBranch
5 | # Generally, it is "main." It is the branch for releases. The pcpr tracks this branch,
6 | # creates or updates a pull request as a release candidate, or tags when they are merged.
7 | #
8 | # tagpr.versionFile
9 | # Versioning file containing the semantic version needed to be updated at release.
10 | # It will be synchronized with the "git tag".
11 | # Often this is a meta-information file such as gemspec, setup.cfg, package.json, etc.
12 | # Sometimes the source code file, such as version.go or Bar.pm, is used.
13 | # If you do not want to use versioning files but only git tags, specify the "-" string here.
14 | # You can specify multiple version files by comma separated strings.
15 | #
16 | # tagpr.vPrefix
17 | # Flag whether or not v-prefix is added to semver when git tagging. (e.g. v1.2.3 if true)
18 | # This is only a tagging convention, not how it is described in the version file.
19 | #
20 | # tagpr.changelog (Optional)
21 | # Flag whether or not changelog is added or changed during the release.
22 | #
23 | # tagpr.command (Optional)
24 | # Command to change files just before release.
25 | #
26 | # tagpr.tmplate (Optional)
27 | # Pull request template in go template format
28 | #
29 | # tagpr.release (Optional)
30 | # GitHub Release creation behavior after tagging [true, draft, false]
31 | # If this value is not set, the release is to be created.
32 | [tagpr]
33 | vPrefix = true
34 | releaseBranch = main
35 | versionFile = version/version.go
36 | command = "make prerelease_for_tagpr"
37 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## [v0.8.4](https://github.com/pepabo/control-controls/compare/v0.8.3...v0.8.4) - 2025-04-16
2 | - introduce the --dryrun option to the notify command by @hiboma in https://github.com/pepabo/control-controls/pull/43
3 |
4 | ## [v0.8.3](https://github.com/pepabo/control-controls/compare/v0.8.2...v0.8.3) - 2024-10-02
5 |
6 | ## [v0.8.2](https://github.com/pepabo/control-controls/compare/v0.8.1...v0.8.2) - 2024-10-02
7 | - Fix nil pointer dereference in ctrl.DisabledReason by @k1LoW in https://github.com/pepabo/control-controls/pull/40
8 |
9 | ## [v0.8.1](https://github.com/pepabo/control-controls/compare/v0.8.0...v0.8.1) - 2023-05-12
10 |
11 | ## [v0.8.0](https://github.com/pepabo/control-controls/compare/v0.7.0...v0.8.0) - 2023-05-12
12 | - (ref #34) feat: Add improve handling of ControlFindingGenerator by @htnosm in https://github.com/pepabo/control-controls/pull/35
13 |
14 | ## [v0.7.0](https://github.com/pepabo/control-controls/compare/v0.6.6...v0.7.0) - 2023-01-12
15 | - Update packages by @k1LoW in https://github.com/pepabo/control-controls/pull/31
16 | - Target only findings whose RecordState is ACTIVE. by @k1LoW in https://github.com/pepabo/control-controls/pull/33
17 |
18 | ## [v0.6.6](https://github.com/pepabo/control-controls/compare/v0.6.5...v0.6.6) - 2022-10-07
19 | - Add params for time condition by @k1LoW in https://github.com/pepabo/control-controls/pull/29
20 |
21 | ## [v0.6.5](https://github.com/pepabo/control-controls/compare/v0.6.4...v0.6.5) - 2022-10-07
22 | - Fix defaultTemplate and Add `message:` by @k1LoW in https://github.com/pepabo/control-controls/pull/27
23 |
24 | ## [v0.6.4](https://github.com/pepabo/control-controls/compare/v0.6.3...v0.6.4) - 2022-10-07
25 | - Change `cond:` to `if:` by @k1LoW in https://github.com/pepabo/control-controls/pull/23
26 | - Add `header:` to customize header only by @k1LoW in https://github.com/pepabo/control-controls/pull/25
27 | - Fix field name by @k1LoW in https://github.com/pepabo/control-controls/pull/26
28 |
29 | ## [v0.6.3](https://github.com/pepabo/control-controls/compare/v0.6.2...v0.6.3) - 2022-10-06
30 | - Expand env when load YAML by @k1LoW in https://github.com/pepabo/control-controls/pull/20
31 | - Fix defaultTemplate by @k1LoW in https://github.com/pepabo/control-controls/pull/22
32 |
33 | ## [v0.6.2](https://github.com/pepabo/control-controls/compare/v0.6.1...v0.6.2) - 2022-10-05
34 | - Remove homebrew-tap setting because updates in the homebrew-tap repository by @k1LoW in https://github.com/pepabo/control-controls/pull/15
35 | - Use tagpr by @k1LoW in https://github.com/pepabo/control-controls/pull/16
36 | - Support notification by @k1LoW in https://github.com/pepabo/control-controls/pull/18
37 | - Bump up go version by @k1LoW in https://github.com/pepabo/control-controls/pull/19
38 |
39 | ## [v0.6.1](https://github.com/pepabo/control-controls/compare/v0.6.0...v0.6.1) (2022-06-17)
40 |
41 | * Fix handling non region arn (eg. `arn:aws:s3:::` ) [#14](https://github.com/pepabo/control-controls/pull/14) ([k1LoW](https://github.com/k1LoW))
42 |
43 | ## [v0.6.0](https://github.com/pepabo/control-controls/compare/v0.5.0...v0.6.0) (2022-06-16)
44 |
45 | * Support workflow status (and note) management [#13](https://github.com/pepabo/control-controls/pull/13) ([k1LoW](https://github.com/k1LoW))
46 |
47 | ## [v0.5.0](https://github.com/pepabo/control-controls/compare/v0.4.0...v0.5.0) (2022-06-09)
48 |
49 | * Add Validate() [#12](https://github.com/pepabo/control-controls/pull/12) ([k1LoW](https://github.com/k1LoW))
50 |
51 | ## [v0.4.0](https://github.com/pepabo/control-controls/compare/v0.3.0...v0.4.0) (2022-06-08)
52 |
53 | * Add `--overlay` option for patch [#11](https://github.com/pepabo/control-controls/pull/11) ([k1LoW](https://github.com/k1LoW))
54 |
55 | ## [v0.3.0](https://github.com/pepabo/control-controls/compare/v0.2.1...v0.3.0) (2022-06-07)
56 |
57 | * Fix flag [#10](https://github.com/pepabo/control-controls/pull/10) ([k1LoW](https://github.com/k1LoW))
58 | * Add reason of disabled in the configuration file. [#9](https://github.com/pepabo/control-controls/pull/9) ([k1LoW](https://github.com/k1LoW))
59 |
60 | ## [v0.2.1](https://github.com/pepabo/control-controls/compare/v0.2.0...v0.2.1) (2022-04-18)
61 |
62 | * Fix nil pointer dereference [#8](https://github.com/pepabo/control-controls/pull/8) ([k1LoW](https://github.com/k1LoW))
63 |
64 | ## [v0.2.0](https://github.com/pepabo/control-controls/compare/v0.1.2...v0.2.0) (2022-04-18)
65 |
66 | * exit status 2 when plan diff is not empty [#7](https://github.com/pepabo/control-controls/pull/7) ([k1LoW](https://github.com/k1LoW))
67 |
68 | ## [v0.1.2](https://github.com/pepabo/control-controls/compare/v0.1.1...v0.1.2) (2022-04-15)
69 |
70 | * Fix contextcopy bug [#6](https://github.com/pepabo/control-controls/pull/6) ([k1LoW](https://github.com/k1LoW))
71 |
72 | ## [v0.1.1](https://github.com/pepabo/control-controls/compare/v0.1.0...v0.1.1) (2022-04-15)
73 |
74 | * Fix sechub.Override behavior [#5](https://github.com/pepabo/control-controls/pull/5) ([k1LoW](https://github.com/k1LoW))
75 |
76 | ## [v0.1.0](https://github.com/pepabo/control-controls/compare/60006830255c...v0.1.0) (2022-04-14)
77 |
78 | * Add option `--disabled-reason` [#4](https://github.com/pepabo/control-controls/pull/4) ([k1LoW](https://github.com/k1LoW))
79 | * Add command `plan` [#3](https://github.com/pepabo/control-controls/pull/3) ([k1LoW](https://github.com/k1LoW))
80 | * Fix apply [#2](https://github.com/pepabo/control-controls/pull/2) ([k1LoW](https://github.com/k1LoW))
81 | * Add command `apply` [#1](https://github.com/pepabo/control-controls/pull/1) ([k1LoW](https://github.com/k1LoW))
82 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright © 2022 GMO Pepabo, inc.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in
13 | all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21 | THE SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | PKG = github.com/pepabo/control-controls
2 | COMMIT = $$(git describe --tags --always)
3 | OSNAME=${shell uname -s}
4 | ifeq ($(OSNAME),Darwin)
5 | DATE = $$(gdate --utc '+%Y-%m-%d_%H:%M:%S')
6 | else
7 | DATE = $$(date --utc '+%Y-%m-%d_%H:%M:%S')
8 | endif
9 |
10 | export GO111MODULE=on
11 |
12 | BUILD_LDFLAGS = -X $(PKG).commit=$(COMMIT) -X $(PKG).date=$(DATE)
13 |
14 | default: test
15 |
16 | ci: depsdev test sec
17 |
18 | test:
19 | go test ./... -coverprofile=coverage.out -covermode=count
20 |
21 | sec:
22 | gosec ./...
23 |
24 | lint:
25 | golangci-lint run ./...
26 |
27 | build:
28 | go build -ldflags="$(BUILD_LDFLAGS)"
29 |
30 | depsdev:
31 | go install github.com/Songmu/ghch/cmd/ghch@v0.10.2
32 | go install github.com/Songmu/gocredits/cmd/gocredits@v0.2.0
33 | go install github.com/securego/gosec/v2/cmd/gosec@latest
34 |
35 | prerelease:
36 | git pull origin main --tag
37 | go mod tidy
38 | ghch -w -N ${VER}
39 | gocredits -w
40 | git add CHANGELOG.md CREDITS go.mod go.sum
41 | git commit -m'Bump up version number'
42 | git tag ${VER}
43 |
44 | prerelease_for_tagpr:
45 | gocredits -w .
46 | git add CHANGELOG.md CREDITS go.mod go.sum
47 |
48 | release:
49 | git push origin main --tag
50 | goreleaser --clean
51 |
52 | .PHONY: default test
53 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # control-controls
2 |
3 | control-controls control controls of AWS Security Hub across all regions.
4 |
5 | ## Usage
6 |
7 | Export current security standards controls as a controls.yml.
8 |
9 | ``` console
10 | $ control-controls export > controls.yml
11 | 2022-04-14T15:08:59+09:00 INF Fetching controls from eu-north-1
12 | 2022-04-14T15:09:04+09:00 INF Fetching controls from ap-south-1
13 | 2022-04-14T15:09:07+09:00 INF Fetching controls from eu-west-3
14 | 2022-04-14T15:09:12+09:00 INF Fetching controls from eu-west-2
15 | 2022-04-14T15:09:16+09:00 INF Fetching controls from eu-west-1
16 | 2022-04-14T15:09:21+09:00 INF Fetching controls from ap-northeast-3
17 | 2022-04-14T15:09:22+09:00 INF Fetching controls from ap-northeast-2
18 | 2022-04-14T15:09:24+09:00 INF Fetching controls from ap-northeast-1
19 | 2022-04-14T15:09:25+09:00 INF Fetching controls from sa-east-1
20 | 2022-04-14T15:09:30+09:00 INF Fetching controls from ca-central-1
21 | 2022-04-14T15:09:34+09:00 INF Fetching controls from ap-southeast-1
22 | 2022-04-14T15:09:36+09:00 INF Fetching controls from ap-southeast-2
23 | 2022-04-14T15:09:39+09:00 INF Fetching controls from eu-central-1
24 | 2022-04-14T15:09:43+09:00 INF Fetching controls from us-east-1
25 | 2022-04-14T15:09:47+09:00 INF Fetching controls from us-east-2
26 | 2022-04-14T15:09:50+09:00 INF Fetching controls from us-west-1
27 | 2022-04-14T15:09:53+09:00 INF Fetching controls from us-west-2
28 | $
29 | ```
30 |
31 |
32 |
33 | exported controls.yml is here
34 |
35 | ``` yaml
36 | autoEnable: true
37 | standards:
38 | aws-foundational-security-best-practices/v/1.0.0:
39 | enable: true
40 | controls:
41 | enable: [APIGateway.5, AutoScaling.1, AutoScaling.2, CloudTrail.1, CloudTrail.2, CloudTrail.4, CloudTrail.5, Config.1, DynamoDB.1, EC2.19, EC2.2, EC2.21, EC2.6, ECR.3, ELB.10, ELB.5, ELB.7, ES.4, ES.5, ES.6, ES.7, ES.8, IAM.1, IAM.2, IAM.3, IAM.5, IAM.6, IAM.7, IAM.8, NetworkFirewall.6, RDS.11, RDS.17, RDS.18, RDS.19, RDS.2, RDS.20, RDS.21, RDS.22, RDS.23, RDS.25, RDS.3, RDS.5, Redshift.4, Redshift.6, Redshift.8, S3.1, S3.10, S3.11, S3.12, S3.2, S3.3, S3.4, S3.5, S3.6, S3.9, SQS.1, SSM.1, SSM.4]
42 | cis-aws-foundations-benchmark/v/1.2.0:
43 | enable: true
44 | controls:
45 | enable: [CIS.1.1, CIS.1.10, CIS.1.11, CIS.1.13, CIS.1.14, CIS.1.16, CIS.1.2, CIS.1.22, CIS.1.3, CIS.1.4, CIS.1.5, CIS.1.6, CIS.1.7, CIS.1.8, CIS.1.9, CIS.2.1, CIS.2.2, CIS.2.3, CIS.2.4, CIS.2.5, CIS.2.6, CIS.2.7, CIS.2.8, CIS.2.9, CIS.3.1, CIS.3.10, CIS.3.11, CIS.3.12, CIS.3.13, CIS.3.14, CIS.3.2, CIS.3.3, CIS.3.4, CIS.3.5, CIS.3.6, CIS.3.7, CIS.3.8, CIS.3.9, CIS.4.3]
46 | pci-dss/v/3.2.1:
47 | enable: false
48 | regions:
49 | ap-northeast-1:
50 | standards:
51 | aws-foundational-security-best-practices/v/1.0.0:
52 | controls:
53 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
54 | cis-aws-foundations-benchmark/v/1.2.0:
55 | controls:
56 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
57 | ap-northeast-2:
58 | standards:
59 | aws-foundational-security-best-practices/v/1.0.0:
60 | controls:
61 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
62 | cis-aws-foundations-benchmark/v/1.2.0:
63 | controls:
64 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
65 | ap-northeast-3:
66 | standards:
67 | aws-foundational-security-best-practices/v/1.0.0:
68 | controls:
69 | enable: [RDS.16, RDS.24]
70 | ap-south-1:
71 | standards:
72 | aws-foundational-security-best-practices/v/1.0.0:
73 | controls:
74 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
75 | cis-aws-foundations-benchmark/v/1.2.0:
76 | controls:
77 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
78 | ap-southeast-1:
79 | standards:
80 | aws-foundational-security-best-practices/v/1.0.0:
81 | controls:
82 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
83 | cis-aws-foundations-benchmark/v/1.2.0:
84 | controls:
85 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
86 | ap-southeast-2:
87 | standards:
88 | aws-foundational-security-best-practices/v/1.0.0:
89 | controls:
90 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
91 | cis-aws-foundations-benchmark/v/1.2.0:
92 | controls:
93 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
94 | ca-central-1:
95 | standards:
96 | aws-foundational-security-best-practices/v/1.0.0:
97 | controls:
98 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
99 | cis-aws-foundations-benchmark/v/1.2.0:
100 | controls:
101 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
102 | eu-central-1:
103 | standards:
104 | aws-foundational-security-best-practices/v/1.0.0:
105 | controls:
106 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
107 | cis-aws-foundations-benchmark/v/1.2.0:
108 | controls:
109 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
110 | eu-north-1:
111 | standards:
112 | aws-foundational-security-best-practices/v/1.0.0:
113 | controls:
114 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
115 | cis-aws-foundations-benchmark/v/1.2.0:
116 | controls:
117 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
118 | eu-west-1:
119 | standards:
120 | aws-foundational-security-best-practices/v/1.0.0:
121 | controls:
122 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
123 | cis-aws-foundations-benchmark/v/1.2.0:
124 | controls:
125 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
126 | eu-west-2:
127 | standards:
128 | aws-foundational-security-best-practices/v/1.0.0:
129 | controls:
130 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
131 | cis-aws-foundations-benchmark/v/1.2.0:
132 | controls:
133 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
134 | eu-west-3:
135 | standards:
136 | aws-foundational-security-best-practices/v/1.0.0:
137 | controls:
138 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
139 | cis-aws-foundations-benchmark/v/1.2.0:
140 | controls:
141 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
142 | sa-east-1:
143 | standards:
144 | aws-foundational-security-best-practices/v/1.0.0:
145 | controls:
146 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.13, RDS.4, RDS.6, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
147 | cis-aws-foundations-benchmark/v/1.2.0:
148 | controls:
149 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
150 | us-east-1:
151 | standards:
152 | aws-foundational-security-best-practices/v/1.0.0:
153 | controls:
154 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CloudFront.1, CloudFront.2, CloudFront.3, CloudFront.4, CloudFront.5, CloudFront.6, CloudFront.7, CloudFront.8, CloudFront.9, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4, WAF.1]
155 | cis-aws-foundations-benchmark/v/1.2.0:
156 | controls:
157 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
158 | us-east-2:
159 | standards:
160 | aws-foundational-security-best-practices/v/1.0.0:
161 | controls:
162 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
163 | cis-aws-foundations-benchmark/v/1.2.0:
164 | controls:
165 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
166 | us-west-1:
167 | standards:
168 | aws-foundational-security-best-practices/v/1.0.0:
169 | controls:
170 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
171 | cis-aws-foundations-benchmark/v/1.2.0:
172 | controls:
173 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
174 | us-west-2:
175 | standards:
176 | aws-foundational-security-best-practices/v/1.0.0:
177 | controls:
178 | enable: [ACM.1, APIGateway.1, APIGateway.2, APIGateway.3, APIGateway.4, Autoscaling.5, CodeBuild.1, CodeBuild.2, CodeBuild.4, CodeBuild.5, DMS.1, DynamoDB.2, DynamoDB.3, EC2.1, EC2.10, EC2.15, EC2.16, EC2.17, EC2.18, EC2.20, EC2.22, EC2.3, EC2.4, EC2.7, EC2.8, EC2.9, ECS.1, ECS.2, EFS.1, EFS.2, ELB.2, ELB.3, ELB.4, ELB.6, ELB.8, ELB.9, ELBv2.1, EMR.1, ES.1, ES.2, ES.3, ElasticBeanstalk.1, ElasticBeanstalk.2, GuardDuty.1, IAM.21, IAM.4, KMS.1, KMS.2, KMS.3, Lambda.1, Lambda.2, Lambda.5, Opensearch.1, Opensearch.2, Opensearch.3, Opensearch.4, Opensearch.5, Opensearch.6, Opensearch.8, RDS.1, RDS.10, RDS.12, RDS.13, RDS.14, RDS.15, RDS.16, RDS.24, RDS.4, RDS.6, RDS.7, RDS.8, RDS.9, Redshift.1, Redshift.2, Redshift.3, Redshift.7, S3.8, SNS.1, SSM.2, SSM.3, SageMaker.1, SecretsManager.1, SecretsManager.2, SecretsManager.3, SecretsManager.4]
179 | cis-aws-foundations-benchmark/v/1.2.0:
180 | controls:
181 | enable: [CIS.1.12, CIS.1.20, CIS.4.1, CIS.4.2]
182 | ```
183 |
184 |
185 |
186 | For example, disable controls (Redshift.4, Redshift.6, Redshift.8).
187 |
188 | ``` yaml
189 | autoEnable: true
190 | standards:
191 | aws-foundational-security-best-practices/v/1.0.0:
192 | enable: true
193 | controls:
194 | enable: [APIGateway.5, AutoScaling.1, AutoScaling.2, CloudTrail.1, CloudTrail.2, CloudTrail.4, CloudTrail.5, Config.1, DynamoDB.1, EC2.19, EC2.2, EC2.21, EC2.6, ECR.3, ELB.10, ELB.5, ELB.7, ES.4, ES.5, ES.6, ES.7, ES.8, IAM.1, IAM.2, IAM.3, IAM.5, IAM.6, IAM.7, IAM.8, NetworkFirewall.6, RDS.11, RDS.17, RDS.18, RDS.19, RDS.2, RDS.20, RDS.21, RDS.22, RDS.23, RDS.25, RDS.3, RDS.5, S3.1, S3.10, S3.11, S3.12, S3.2, S3.3, S3.4, S3.5, S3.6, S3.9, SQS.1, SSM.1, SSM.4]
195 | disable:
196 | Redshift.4: Redshift is not running.
197 | Redshift.6: Redshift is not running.
198 | Redshift.8: Redshift is not running.
199 | [...]
200 | ```
201 |
202 | Dry run.
203 |
204 | ``` console
205 | $ control-controls plan controls.yml
206 | 2022-04-14T15:16:54+09:00 INF Checking eu-north-1
207 | 2022-04-14T15:17:02+09:00 INF Checking ap-south-1
208 | 2022-04-14T15:17:08+09:00 INF Checking eu-west-3
209 | 2022-04-14T15:17:15+09:00 INF Checking eu-west-2
210 | 2022-04-14T15:17:23+09:00 INF Checking eu-west-1
211 | 2022-04-14T15:17:31+09:00 INF Checking ap-northeast-3
212 | 2022-04-14T15:17:34+09:00 INF Checking ap-northeast-2
213 | 2022-04-14T15:17:37+09:00 INF Checking ap-northeast-1
214 | 2022-04-14T15:17:40+09:00 INF Checking sa-east-1
215 | 2022-04-14T15:17:49+09:00 INF Checking ca-central-1
216 | 2022-04-14T15:17:55+09:00 INF Checking ap-southeast-1
217 | 2022-04-14T15:17:59+09:00 INF Checking ap-southeast-2
218 | 2022-04-14T15:18:05+09:00 INF Checking eu-central-1
219 | 2022-04-14T15:18:13+09:00 INF Checking us-east-1
220 | 2022-04-14T15:18:19+09:00 INF Checking us-east-2
221 | 2022-04-14T15:18:25+09:00 INF Checking us-west-1
222 | 2022-04-14T15:18:31+09:00 INF Checking us-west-2
223 | - eu-north-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.4 (disabled reason: Redshift is not running.)
224 | - eu-north-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.6 (disabled reason: Redshift is not running.)
225 | - eu-north-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.8 (disabled reason: Redshift is not running.)
226 | - ap-south-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.4 (disabled reason: Redshift is not running.)
227 | - ap-south-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.6 (disabled reason: Redshift is not running.)
228 | [...]
229 | - us-west-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.6 (disabled reason: Redshift is not running.)
230 | - us-west-1::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.8 (disabled reason: Redshift is not running.)
231 | - us-west-2::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.4 (disabled reason: Redshift is not running.)
232 | - us-west-2::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.6 (disabled reason: Redshift is not running.)
233 | - us-west-2::standards::aws-foundational-security-best-practices/v/1.0.0::controls::Redshift.8 (disabled reason: Redshift is not running.)
234 |
235 | Plan: 0 to enable, 51 to disable
236 | ```
237 |
238 | Apply changes.
239 |
240 | ``` console
241 | $ control-controls apply controls.yml
242 | 2022-04-14T15:43:37+09:00 INF Applying to eu-north-1
243 | 2022-04-14T15:43:46+09:00 INF Disable control Control=Redshift.4 Reason="Redshift is not running." Region=eu-north-1 Standard=aws-foundational-security-best-practice
244 | s/v/1.0.0
245 | 2022-04-14T15:43:47+09:00 INF Disable control Control=Redshift.6 Reason="Redshift is not running." Region=eu-north-1 Standard=aws-foundational-security-best-practice
246 | s/v/1.0.0
247 | 2022-04-14T15:43:49+09:00 INF Disable control Control=Redshift.8 Reason="Redshift is not running." Region=eu-north-1 Standard=aws-foundational-security-best-practice
248 | s/v/1.0.0
249 | 2022-04-14T15:43:51+09:00 INF Applying to ap-south-1
250 | 2022-04-14T15:43:56+09:00 INF Disable control Control=Redshift.4 Reason="Redshift is not running." Region=ap-south-1 Standard=aws-foundational-security-best-practice
251 | s/v/1.0.0
252 | 2022-04-14T15:43:57+09:00 INF Disable control Control=Redshift.6 Reason="Redshift is not running." Region=ap-south-1 Standard=aws-foundational-security-best-practice
253 | s/v/1.0.0
254 | [...]
255 | 2022-04-14T15:46:18+09:00 INF Disable control Control=Redshift.6 Reason="Redshift is not running." Region=us-west-1 Standard=aws-foundational-security-best-practices
256 | /v/1.0.0
257 | 2022-04-14T15:46:19+09:00 INF Disable control Control=Redshift.8 Reason="Redshift is not running." Region=us-west-1 Standard=aws-foundational-security-best-practices
258 | /v/1.0.0
259 | 2022-04-14T15:46:20+09:00 INF Applying to us-west-2
260 | 2022-04-14T15:46:26+09:00 INF Disable control Control=Redshift.4 Reason="Redshift is not running." Region=us-west-2 Standard=aws-foundational-security-best-practices
261 | /v/1.0.0
262 | 2022-04-14T15:46:27+09:00 INF Disable control Control=Redshift.6 Reason="Redshift is not running." Region=us-west-2 Standard=aws-foundational-security-best-practices
263 | /v/1.0.0
264 | 2022-04-14T15:46:29+09:00 INF Disable control Control=Redshift.8 Reason="Redshift is not running." Region=us-west-2 Standard=aws-foundational-security-best-practices
265 | /v/1.0.0
266 |
267 | Apply complete
268 | ```
269 |
270 | ## Configuration
271 |
272 | ### `autoEnable`
273 |
274 | Automatically enabling new controls across all regions.
275 |
276 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/controls-auto-enable.html
277 |
278 | ``` yaml
279 | autoEnable: true
280 | ```
281 |
282 | ### `standards..enable`
283 |
284 | Enabling a security standard across all regions.
285 |
286 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-enable-disable.html
287 |
288 | ``` yaml
289 | standards:
290 | aws-foundational-security-best-practices/v/1.0.0:
291 | enable: true
292 | cis-aws-foundations-benchmark/v/1.2.0:
293 | enable: true
294 | pci-dss/v/3.2.1:
295 | enable: false
296 | ```
297 |
298 | ### `standards..controls.enable`
299 |
300 | Enabling individual controls across all regions.
301 |
302 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-enable-disable-controls.html
303 |
304 | ``` yaml
305 | standards:
306 | aws-foundational-security-best-practices/v/1.0.0:
307 | enable: true
308 | controls:
309 | enable: [APIGateway.5, AutoScaling.1, AutoScaling.2, CloudTrail.1, CloudTrail.2, CloudTrail.4, CloudTrail.5, Config.1, DynamoDB.1, EC2.19, EC2.2, EC2.21, EC2.6, ECR.3, ELB.10, ELB.5, ELB.7, ES.4, ES.5, ES.6, ES.7, ES.8, IAM.1, IAM.2, IAM.3, IAM.5, IAM.6, IAM.7, IAM.8, NetworkFirewall.6, RDS.11, RDS.17, RDS.18, RDS.19, RDS.2, RDS.20, RDS.21, RDS.22, RDS.23, RDS.25, RDS.3, RDS.5, Redshift.4, Redshift.6, Redshift.8, S3.1, S3.10, S3.11, S3.12, S3.2, S3.3, S3.4, S3.5, S3.6, S3.9, SQS.1, SSM.1, SSM.4]
310 | ```
311 |
312 | ### `standards..controls.disable`
313 |
314 | Disabling individual controls across all regions.
315 |
316 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/securityhub-standards-enable-disable-controls.html
317 |
318 | ``` yaml
319 | standards:
320 | aws-foundational-security-best-practices/v/1.0.0:
321 | enable: true
322 | controls:
323 | disable:
324 | Redshift.4: Redshift is not running.
325 | Redshift.6: Redshift is not running.
326 | Redshift.8: Redshift is not running.
327 | ```
328 |
329 | ### `standards..findings...status`
330 |
331 | Set workflow status to individual findings across all regions.
332 |
333 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/finding-workflow-status.html
334 |
335 | ``` yaml
336 | standards:
337 | aws-foundational-security-best-practices/v/1.0.0:
338 | findings:
339 | S3.2:
340 | arn:aws:s3:::static.example.com:
341 | status: SUPPRESSED
342 | note: Use as simple web hosting
343 | ```
344 |
345 | ### `standards..findings...note`
346 |
347 | Set note to individual findings across all regions.
348 |
349 | ref: https://docs.aws.amazon.com/securityhub/latest/userguide/asff-note.html
350 |
351 | ### `regions..standards.*`
352 |
353 | Set override settings for each region.
354 |
355 | ## Overlay
356 |
357 | It is possible to override the settings with `--overlay` option.
358 |
359 | ``` console
360 | $ control-controls plan base.yml --overlay custom.yml
361 | [...]
362 | $ control-controls apply base.yml --overlay custom.yml
363 | [...]
364 | ```
365 |
366 | ## Required permissions
367 |
368 | - `ec2:DescribeRegions`
369 | - `securityhub:*`
370 |
371 | ## Install
372 |
373 | **homebrew tap:**
374 |
375 | ```console
376 | $ brew install pepabo/tap/control-controls
377 | ```
378 |
379 | **manually:**
380 |
381 | Download binany from [releases page](https://github.com/pepabo/control-controls/releases)
382 |
383 | **go install:**
384 |
385 | ```console
386 | $ go install github.com/pepabo/control-controls@latest
387 | ```
388 |
--------------------------------------------------------------------------------
/cmd/apply.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package cmd
23 |
24 | import (
25 | "context"
26 | "fmt"
27 |
28 | "github.com/aws/aws-sdk-go-v2/config"
29 | "github.com/pepabo/control-controls/sechub"
30 | "github.com/rs/zerolog/log"
31 | "github.com/spf13/cobra"
32 | )
33 |
34 | var applyCmd = &cobra.Command{
35 | Use: "apply [CONFIG_FILE]",
36 | Short: "apply",
37 | Long: `apply.`,
38 | Args: cobra.ExactArgs(1),
39 | RunE: func(cmd *cobra.Command, args []string) error {
40 | ctx := context.Background()
41 | cfg, err := config.LoadDefaultConfig(ctx)
42 | if err != nil {
43 | return err
44 | }
45 | hub, err := sechub.Load(args[0])
46 | if err != nil {
47 | return err
48 | }
49 | for _, o := range overlays {
50 | oo, err := sechub.Load(o)
51 | if err != nil {
52 | return err
53 | }
54 | hub.Overlay(oo)
55 | }
56 | regions, err := regions(ctx, cfg)
57 | if err != nil {
58 | return err
59 | }
60 |
61 | for _, r := range regions {
62 | cfg.Region = r
63 | log.Info().Msg(fmt.Sprintf("Applying to %s", r))
64 | if err := hub.Apply(ctx, cfg, disabledReason); err != nil {
65 | return err
66 | }
67 | }
68 |
69 | cmd.Println("")
70 | cmd.Println("Apply complete")
71 | return nil
72 | },
73 | }
74 |
75 | func init() {
76 | rootCmd.AddCommand(applyCmd)
77 | applyCmd.Flags().StringVarP(&disabledReason, "disabled-reason", "", "", "A description of the reason why you are disabling a security standard control.")
78 | applyCmd.Flags().StringSliceVarP(&overlays, "overlay", "", []string{}, "patch file or directory for overlaying")
79 | }
80 |
--------------------------------------------------------------------------------
/cmd/export.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package cmd
23 |
24 | import (
25 | "context"
26 | "fmt"
27 |
28 | "github.com/aws/aws-sdk-go-v2/config"
29 | "github.com/goccy/go-yaml"
30 | "github.com/pepabo/control-controls/sechub"
31 | "github.com/rs/zerolog/log"
32 | "github.com/spf13/cobra"
33 | )
34 |
35 | var exportCmd = &cobra.Command{
36 | Use: "export",
37 | Short: "export",
38 | Long: `export.`,
39 | RunE: func(cmd *cobra.Command, args []string) error {
40 | ctx := context.Background()
41 | cfg, err := config.LoadDefaultConfig(ctx)
42 | if err != nil {
43 | return err
44 | }
45 |
46 | regions, err := regions(ctx, cfg)
47 | if err != nil {
48 | return err
49 | }
50 | var base *sechub.SecHub
51 | hubs := []*sechub.SecHub{}
52 | for _, r := range regions {
53 | cfg.Region = r
54 | log.Info().Msg(fmt.Sprintf("Fetching controls from %s", r))
55 | sh := sechub.New(r)
56 | if err := sh.Fetch(ctx, cfg); err != nil {
57 | return err
58 | }
59 | if base == nil {
60 | base = sh
61 | } else {
62 | base = sechub.Intersect(base, sh)
63 | }
64 | hubs = append(hubs, sh)
65 | }
66 |
67 | for _, h := range hubs {
68 | d, err := sechub.Diff(base, h)
69 | if err != nil {
70 | return err
71 | }
72 | if d != nil {
73 | base.Regions = append(base.Regions, d)
74 | }
75 | }
76 |
77 | b, err := yaml.Marshal(base)
78 | if err != nil {
79 | return err
80 | }
81 |
82 | cmd.Println(string(b))
83 | return nil
84 | },
85 | }
86 |
87 | func init() {
88 | rootCmd.AddCommand(exportCmd)
89 | }
90 |
--------------------------------------------------------------------------------
/cmd/notify.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package cmd
23 |
24 | import (
25 | "context"
26 |
27 | "github.com/aws/aws-sdk-go-v2/aws"
28 | "github.com/aws/aws-sdk-go-v2/aws/arn"
29 | "github.com/aws/aws-sdk-go-v2/config"
30 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
31 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
32 | "github.com/pepabo/control-controls/sechub"
33 | "github.com/spf13/cobra"
34 | )
35 |
36 | var dryrun bool
37 |
38 | var notifyCmd = &cobra.Command{
39 | Use: "notify [CONFIG_FILE]",
40 | Short: "notify",
41 | Long: `notify.`,
42 | Args: cobra.ExactArgs(1),
43 | RunE: func(cmd *cobra.Command, args []string) error {
44 | ctx := context.Background()
45 | cfg, err := config.LoadDefaultConfig(ctx)
46 | if err != nil {
47 | return err
48 | }
49 | hub, err := sechub.Load(args[0])
50 | if err != nil {
51 | return err
52 | }
53 | for _, o := range overlays {
54 | oo, err := sechub.Load(o)
55 | if err != nil {
56 | return err
57 | }
58 | hub.Overlay(oo)
59 | }
60 | if len(hub.Notifications) == 0 {
61 | cmd.Println("no notifications")
62 | return nil
63 | }
64 | region, err := detectAggregationRegion(ctx, cfg)
65 | if err != nil {
66 | return err
67 | }
68 | cfg.Region = region
69 | findings, err := collectActiveFindings(ctx, cfg)
70 | if err != nil {
71 | return err
72 | }
73 | if err := hub.Notify(ctx, cfg, findings, dryrun); err != nil {
74 | return err
75 | }
76 | return nil
77 | },
78 | }
79 |
80 | func init() {
81 | rootCmd.AddCommand(notifyCmd)
82 | notifyCmd.Flags().StringSliceVarP(&overlays, "overlay", "", []string{}, "patch file or directory for overlaying")
83 | notifyCmd.Flags().BoolVarP(&dryrun, "dryrun", "s", false, "output notifications to stdout")
84 |
85 | }
86 |
87 | func collectActiveFindings(ctx context.Context, cfg aws.Config) ([]sechub.NotifyFinding, error) {
88 | c := securityhub.NewFromConfig(cfg)
89 | hub, err := c.DescribeHub(ctx, &securityhub.DescribeHubInput{})
90 | if err != nil {
91 | return nil, err
92 | }
93 | a, err := arn.Parse(*hub.HubArn)
94 | if err != nil {
95 | return nil, err
96 | }
97 | var (
98 | nt *string
99 | findings []types.AwsSecurityFinding
100 | )
101 | for {
102 | o, err := c.GetFindings(ctx, &securityhub.GetFindingsInput{
103 | Filters: &types.AwsSecurityFindingFilters{
104 | AwsAccountId: []types.StringFilter{{Comparison: types.StringFilterComparisonEquals, Value: aws.String(a.AccountID)}},
105 | ProductName: []types.StringFilter{{Comparison: types.StringFilterComparisonEquals, Value: aws.String("Security Hub")}},
106 | RecordState: []types.StringFilter{{Comparison: types.StringFilterComparisonEquals, Value: aws.String("ACTIVE")}},
107 | SeverityLabel: []types.StringFilter{{Comparison: types.StringFilterComparisonNotEquals, Value: aws.String("INFORMATIONAL")}},
108 | },
109 | MaxResults: int32(100),
110 | NextToken: nt,
111 | })
112 | if err != nil {
113 | return nil, err
114 | }
115 | findings = append(findings, o.Findings...)
116 | if o.NextToken == nil {
117 | break
118 | }
119 | nt = o.NextToken
120 | }
121 | nf := []sechub.NotifyFinding{}
122 | for _, f := range findings {
123 | nf = append(nf, sechub.NotifyFinding{
124 | SeverityLabel: f.Severity.Label,
125 | WorkflowStatus: f.Workflow.Status,
126 | })
127 | }
128 | return nf, nil
129 | }
130 |
--------------------------------------------------------------------------------
/cmd/plan.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package cmd
23 |
24 | import (
25 | "context"
26 | "fmt"
27 | "os"
28 |
29 | "github.com/aws/aws-sdk-go-v2/config"
30 | "github.com/fatih/color"
31 | "github.com/pepabo/control-controls/sechub"
32 | "github.com/rs/zerolog/log"
33 | "github.com/spf13/cobra"
34 | )
35 |
36 | var planCmd = &cobra.Command{
37 | Use: "plan [CONFIG_FILE]",
38 | Short: "plan",
39 | Long: `plan.`,
40 | Args: cobra.ExactArgs(1),
41 | RunE: func(cmd *cobra.Command, args []string) error {
42 | ctx := context.Background()
43 | cfg, err := config.LoadDefaultConfig(ctx)
44 | if err != nil {
45 | return err
46 | }
47 | hub, err := sechub.Load(args[0])
48 | if err != nil {
49 | return err
50 | }
51 | for _, o := range overlays {
52 | oo, err := sechub.Load(o)
53 | if err != nil {
54 | return err
55 | }
56 | hub.Overlay(oo)
57 | }
58 | regions, err := regions(ctx, cfg)
59 | if err != nil {
60 | return err
61 | }
62 |
63 | changes := []*sechub.Change{}
64 | for _, r := range regions {
65 | cfg.Region = r
66 | log.Info().Msg(fmt.Sprintf("Checking %s", r))
67 | c, err := hub.Plan(ctx, cfg, disabledReason)
68 | if err != nil {
69 | return err
70 | }
71 | changes = append(changes, c...)
72 | }
73 |
74 | cmd.Println("")
75 |
76 | if len(changes) == 0 {
77 | cmd.Println("No changes. Controls are up-to-date.")
78 | } else {
79 | green := color.New(color.FgGreen).PrintfFunc()
80 | red := color.New(color.FgRed).PrintfFunc()
81 | yellow := color.New(color.FgYellow).PrintfFunc()
82 | enable := 0
83 | disable := 0
84 | change := 0
85 | for _, c := range changes {
86 | switch c.ChangeType {
87 | case sechub.ENABLE:
88 | enable += 1
89 | green("%s %s\n", c.ChangeType, c.Key)
90 | case sechub.DISABLE:
91 | disable += 1
92 | if c.DisabledReason == "" {
93 | red("%s %s\n", c.ChangeType, c.Key)
94 | } else {
95 | red("%s %s (disabled reason: %s)\n", c.ChangeType, c.Key, c.DisabledReason)
96 | }
97 | case sechub.CHANGE:
98 | change += 1
99 | yellow("%s %s %s\n", c.ChangeType, c.Key, c.Changed)
100 | }
101 | }
102 | cmd.Println("")
103 | cmd.Printf("Plan: %d to enable, %d to change, %d to disable\n", enable, change, disable)
104 | os.Exit(2)
105 | }
106 |
107 | return nil
108 | },
109 | }
110 |
111 | func init() {
112 | rootCmd.AddCommand(planCmd)
113 | planCmd.Flags().StringVarP(&disabledReason, "disabled-reason", "", "", "A description of the reason why you are disabling a security standard control.")
114 | planCmd.Flags().StringSliceVarP(&overlays, "overlay", "", []string{}, "patch file or directory for overlaying")
115 | }
116 |
--------------------------------------------------------------------------------
/cmd/root.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package cmd
23 |
24 | import (
25 | "context"
26 | "errors"
27 | "os"
28 | "time"
29 |
30 | "github.com/aws/aws-sdk-go-v2/aws"
31 | "github.com/aws/aws-sdk-go-v2/service/ec2"
32 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
33 | "github.com/pepabo/control-controls/version"
34 | "github.com/rs/zerolog"
35 | "github.com/rs/zerolog/log"
36 | "github.com/spf13/cobra"
37 | )
38 |
39 | var (
40 | disabledReason string
41 | overlays []string
42 | )
43 |
44 | var rootCmd = &cobra.Command{
45 | Use: "control-controls",
46 | Short: "control-controls control controls of AWS Security Hub across all regions",
47 | Long: `control-controls control controls of AWS Security Hub across all regions.`,
48 | SilenceUsage: true,
49 | Version: version.Version,
50 | }
51 |
52 | func Execute() {
53 | log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: time.RFC3339})
54 | zerolog.SetGlobalLevel(zerolog.InfoLevel)
55 | if os.Getenv("DEBUG") != "" && os.Getenv("DEBUG") != "0" {
56 | zerolog.SetGlobalLevel(zerolog.DebugLevel)
57 | }
58 |
59 | rootCmd.SetOut(os.Stdout)
60 | rootCmd.SetErr(os.Stderr)
61 | if err := rootCmd.Execute(); err != nil {
62 | os.Exit(1)
63 | }
64 | }
65 |
66 | func regions(ctx context.Context, cfg aws.Config) ([]string, error) {
67 | ec2s := ec2.NewFromConfig(cfg)
68 | rs, err := ec2s.DescribeRegions(ctx, &ec2.DescribeRegionsInput{AllRegions: aws.Bool(false)})
69 | if err != nil {
70 | return nil, err
71 | }
72 | regions := []string{}
73 | for _, r := range rs.Regions {
74 | regions = append(regions, *r.RegionName)
75 | }
76 | return regions, nil
77 | }
78 |
79 | func detectAggregationRegion(ctx context.Context, cfg aws.Config) (string, error) {
80 | rs, err := regions(ctx, cfg)
81 | if err != nil {
82 | return "", err
83 | }
84 | for _, r := range rs {
85 | cfg.Region = r
86 | c := securityhub.NewFromConfig(cfg)
87 | as, err := c.ListFindingAggregators(ctx, &securityhub.ListFindingAggregatorsInput{})
88 | if err != nil {
89 | return "", err
90 | }
91 | for _, a := range as.FindingAggregators {
92 | aa, err := c.GetFindingAggregator(ctx, &securityhub.GetFindingAggregatorInput{
93 | FindingAggregatorArn: a.FindingAggregatorArn,
94 | })
95 | if err != nil {
96 | return "", err
97 | }
98 | if aa.RegionLinkingMode != nil && aa.FindingAggregationRegion != nil {
99 | return *aa.FindingAggregationRegion, nil
100 | }
101 | }
102 | }
103 | return "", errors.New("no aggregation region")
104 | }
105 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/pepabo/control-controls
2 |
3 | go 1.22
4 |
5 | require (
6 | github.com/antonmedv/expr v1.9.0
7 | github.com/aws/aws-sdk-go-v2 v1.17.7
8 | github.com/aws/aws-sdk-go-v2/config v1.18.8
9 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.77.0
10 | github.com/aws/aws-sdk-go-v2/service/securityhub v1.29.3
11 | github.com/fatih/color v1.13.0
12 | github.com/goccy/go-yaml v1.9.8
13 | github.com/google/go-cmp v0.5.9
14 | github.com/k1LoW/expand v0.5.5
15 | github.com/k1LoW/httpstub v0.3.2
16 | github.com/rs/zerolog v1.28.0
17 | github.com/spf13/cobra v1.6.1
18 | github.com/tenntenn/golden v0.2.0
19 | )
20 |
21 | require (
22 | github.com/aws/aws-sdk-go-v2/credentials v1.13.8 // indirect
23 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 // indirect
24 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 // indirect
25 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 // indirect
26 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 // indirect
27 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 // indirect
28 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 // indirect
29 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 // indirect
30 | github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 // indirect
31 | github.com/aws/smithy-go v1.13.5 // indirect
32 | github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 // indirect
33 | github.com/inconshreveable/mousetrap v1.1.0 // indirect
34 | github.com/jmespath/go-jmespath v0.4.0 // indirect
35 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6 // indirect
36 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca // indirect
37 | github.com/mattn/go-colorable v0.1.13 // indirect
38 | github.com/mattn/go-isatty v0.0.17 // indirect
39 | github.com/spf13/pflag v1.0.5 // indirect
40 | golang.org/x/sys v0.4.0 // indirect
41 | golang.org/x/tools v0.1.7 // indirect
42 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
43 | )
44 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
2 | github.com/antonmedv/expr v1.9.0 h1:j4HI3NHEdgDnN9p6oI6Ndr0G5QryMY0FNxT4ONrFDGU=
3 | github.com/antonmedv/expr v1.9.0/go.mod h1:5qsM3oLGDND7sDmQGDXHkYfkjYMUX14qsgqmHhwGEk8=
4 | github.com/aws/aws-sdk-go-v2 v1.17.3/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
5 | github.com/aws/aws-sdk-go-v2 v1.17.7 h1:CLSjnhJSTSogvqUGhIC6LqFKATMRexcxLZ0i/Nzk9Eg=
6 | github.com/aws/aws-sdk-go-v2 v1.17.7/go.mod h1:uzbQtefpm44goOPmdKyAlXSNcwlRgF3ePWVW6EtJvvw=
7 | github.com/aws/aws-sdk-go-v2/config v1.18.8 h1:lDpy0WM8AHsywOnVrOHaSMfpaiV2igOw8D7svkFkXVA=
8 | github.com/aws/aws-sdk-go-v2/config v1.18.8/go.mod h1:5XCmmyutmzzgkpk/6NYTjeWb6lgo9N170m1j6pQkIBs=
9 | github.com/aws/aws-sdk-go-v2/credentials v1.13.8 h1:vTrwTvv5qAwjWIGhZDSBH/oQHuIQjGmD232k01FUh6A=
10 | github.com/aws/aws-sdk-go-v2/credentials v1.13.8/go.mod h1:lVa4OHbvgjVot4gmh1uouF1ubgexSCN92P6CJQpT0t8=
11 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21 h1:j9wi1kQ8b+e0FBVHxCqCGo4kxDU175hoDHcWAi0sauU=
12 | github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.12.21/go.mod h1:ugwW57Z5Z48bpvUyZuaPy4Kv+vEfJWnIrky7RmkBvJg=
13 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.27/go.mod h1:a1/UpzeyBBerajpnP5nGZa9mGzsBn5cOKxm6NWQsvoI=
14 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31 h1:sJLYcS+eZn5EeNINGHSCRAwUJMFVqklwkH36Vbyai7M=
15 | github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.31/go.mod h1:QT0BqUvX1Bh2ABdTGnjqEjvjzrCfIniM9Sc8zn9Yndo=
16 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.21/go.mod h1:+Gxn8jYn5k9ebfHEqlhrMirFjSW0v0C9fI+KN5vk2kE=
17 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25 h1:1mnRASEKnkqsntcxHaysxwgVoUUp5dkiB+l3llKnqyg=
18 | github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.4.25/go.mod h1:zBHOPwhBc3FlQjQJE/D3IfPWiWaQmT06Vq9aNukDo0k=
19 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28 h1:KeTxcGdNnQudb46oOl4d90f2I33DF/c6q3RnZAmvQdQ=
20 | github.com/aws/aws-sdk-go-v2/internal/ini v1.3.28/go.mod h1:yRZVr/iT0AqyHeep00SZ4YfBAKojXz08w3XMBscdi0c=
21 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.77.0 h1:m6HYlpZlTWb9vHuuRHpWRieqPHWlS0mvQ90OJNrG/Nk=
22 | github.com/aws/aws-sdk-go-v2/service/ec2 v1.77.0/go.mod h1:mV0E7631M1eXdB+tlGFIw6JxfsC7Pz7+7Aw15oLVhZw=
23 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21 h1:5C6XgTViSb0bunmU57b3CT+MhxULqHH2721FVA+/kDM=
24 | github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.9.21/go.mod h1:lRToEJsn+DRA9lW4O9L9+/3hjTkUzlzyzHqn8MTds5k=
25 | github.com/aws/aws-sdk-go-v2/service/securityhub v1.29.3 h1:+5S1gzriktBaFXPGgi+KeN5OoTX4CIajWn3UbBoFoOU=
26 | github.com/aws/aws-sdk-go-v2/service/securityhub v1.29.3/go.mod h1:s/xuTeRC/O8Ww3AIVz5aOi4NKwSOUH0uMgqH+cHa/j0=
27 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.0 h1:/2gzjhQowRLarkkBOGPXSRnb8sQ2RVsjdG1C/UliK/c=
28 | github.com/aws/aws-sdk-go-v2/service/sso v1.12.0/go.mod h1:wo/B7uUm/7zw/dWhBJ4FXuw1sySU5lyIhVg1Bu2yL9A=
29 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0 h1:Jfly6mRxk2ZOSlbCvZfKNS7TukSx1mIzhSsqZ/IGSZI=
30 | github.com/aws/aws-sdk-go-v2/service/ssooidc v1.14.0/go.mod h1:TZSH7xLO7+phDtViY/KUp9WGCJMQkLJ/VpgkTFd5gh8=
31 | github.com/aws/aws-sdk-go-v2/service/sts v1.18.0 h1:kOO++CYo50RcTFISESluhWEi5Prhg+gaSs4whWabiZU=
32 | github.com/aws/aws-sdk-go-v2/service/sts v1.18.0/go.mod h1:+lGbb3+1ugwKrNTWcf2RT05Xmp543B06zDFTwiTLp7I=
33 | github.com/aws/smithy-go v1.13.5 h1:hgz0X/DX0dGqTYpGALqXJoRKRj5oQ7150i5FdTePzO8=
34 | github.com/aws/smithy-go v1.13.5/go.mod h1:Tg+OJXh4MB2R/uN61Ko2f6hTZwB/ZYGOtib8J3gBHzA=
35 | github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251 h1:k6UDF1uPYOs0iy1HPeotNa155qXRWrzKnqAaGXHLZCE=
36 | github.com/buildkite/interpolate v0.0.0-20200526001904-07f35b4ae251/go.mod h1:gbPR1gPu9dB96mucYIR7T3B7p/78hRVSOuzIWLHK2Y4=
37 | github.com/coreos/go-systemd/v22 v22.3.3-0.20220203105225-a9a7ef127534/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
38 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
39 | github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
40 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
41 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
42 | github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
43 | github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
44 | github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
45 | github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
46 | github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
47 | github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
48 | github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
49 | github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
50 | github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
51 | github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
52 | github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
53 | github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
54 | github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ=
55 | github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE=
56 | github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
57 | github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
58 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
59 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
60 | github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
61 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
62 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
63 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
64 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
65 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
66 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
67 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6 h1:c+ctPFdISggaSNCfU1IueNBAsqetJSvMcpQlT+0OVdY=
68 | github.com/josharian/mapfs v0.0.0-20210615234106-095c008854e6/go.mod h1:Rv/momJI8DgrWnBZip+SgagpcgORIZQE5SERlxNb8LY=
69 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca h1:a8xeK4GsWLE4LYo5VI4u1Cn7ZvT1NtXouXR3DdKLB8Q=
70 | github.com/josharian/txtarfs v0.0.0-20210615234325-77aca6df5bca/go.mod h1:UbC32ft9G/jG+sZI8wLbIBNIrYr7vp/yqMDa9SxVBNA=
71 | github.com/k1LoW/expand v0.5.5 h1:XC+BYzAfJvn7jXVMaAfEvqKJHm6EcjXs2VR+7YJjATg=
72 | github.com/k1LoW/expand v0.5.5/go.mod h1:HyNqvB5984hIVR+HMPGBZ511XH+49hJa9DGSBkO3AP4=
73 | github.com/k1LoW/httpstub v0.3.2 h1:mBQT6qqP9ETDzQ5AGS6s+ED6Rmz9n+ZrmjChFh4Q9PE=
74 | github.com/k1LoW/httpstub v0.3.2/go.mod h1:+DdiOxIHHtRxNc4oHk997R2jYEytpj5s3E2+qR2526A=
75 | github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
76 | github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
77 | github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
78 | github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
79 | github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
80 | github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
81 | github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
82 | github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
83 | github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
84 | github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
85 | github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
86 | github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
87 | github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng=
88 | github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
89 | github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
90 | github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
91 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
92 | github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
93 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
94 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
95 | github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
96 | github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
97 | github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
98 | github.com/rs/zerolog v1.28.0 h1:MirSo27VyNi7RJYP3078AA1+Cyzd2GB66qy3aUHvsWY=
99 | github.com/rs/zerolog v1.28.0/go.mod h1:NILgTygv/Uej1ra5XxGf82ZFSLk58MFGAUS2o6usyD0=
100 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
101 | github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
102 | github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA=
103 | github.com/spf13/cobra v1.6.1/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY=
104 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
105 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
106 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
107 | github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
108 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
109 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
110 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
111 | github.com/tenntenn/golden v0.2.0 h1:ENbHNS5P2Bcnh2QWQcwtNPDYnIvFGuK4lKVDkCq4AHs=
112 | github.com/tenntenn/golden v0.2.0/go.mod h1:OB8A7xwUZ9xE19KXoOMPl223hhcH4uD8oeQS9fLTiEE=
113 | github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
114 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
115 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
116 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI=
117 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
118 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
119 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
120 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
121 | golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
122 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
123 | golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
124 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
125 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
126 | golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
127 | golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
128 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
129 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
130 | golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
131 | golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
132 | golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
133 | golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
134 | golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
135 | golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
136 | golang.org/x/sys v0.4.0 h1:Zr2JFtRQNX3BCZ8YtxRE9hNJYC8J6I1MVbMg6owUp18=
137 | golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
138 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
139 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
140 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
141 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
142 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
143 | golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
144 | golang.org/x/tools v0.1.7 h1:6j8CgantCy3yc8JGBqkDLMKWqZ0RDU2g1HVgacojGWQ=
145 | golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
146 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
147 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
148 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
149 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk=
150 | golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
151 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
152 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
153 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
154 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
155 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
156 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright © 2022 GMO Pepabo, inc.
3 |
4 | Permission is hereby granted, free of charge, to any person obtaining a copy
5 | of this software and associated documentation files (the "Software"), to deal
6 | in the Software without restriction, including without limitation the rights
7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the Software is
9 | furnished to do so, subject to the following conditions:
10 |
11 | The above copyright notice and this permission notice shall be included in
12 | all copies or substantial portions of the Software.
13 |
14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 | THE SOFTWARE.
21 | */
22 | package main
23 |
24 | import "github.com/pepabo/control-controls/cmd"
25 |
26 | func main() {
27 | cmd.Execute()
28 | }
29 |
--------------------------------------------------------------------------------
/sechub/apply.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "time"
7 |
8 | "github.com/aws/aws-sdk-go-v2/aws"
9 | "github.com/aws/aws-sdk-go-v2/aws/arn"
10 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
11 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
12 | "github.com/rs/zerolog/log"
13 | )
14 |
15 | const noteUpdateBy = "control-controls"
16 |
17 | func (sh *SecHub) Apply(ctx context.Context, cfg aws.Config, reason string) error {
18 | region := cfg.Region
19 | c := securityhub.NewFromConfig(cfg)
20 | d := sh.Regions.findByRegionName(region)
21 | a, err := contextcopy(sh)
22 | if err != nil {
23 | return err
24 | }
25 | if d != nil {
26 | a, err = Override(sh, d)
27 | if err != nil {
28 | return err
29 | }
30 | }
31 | current := New(region)
32 | if err := current.Fetch(ctx, cfg); err != nil {
33 | return err
34 | }
35 | if !current.enabled {
36 | log.Info().Str("Region", region).Msg("Skip because Security Hub is not enabled")
37 | return nil
38 | }
39 | diff, err := Diff(current, a)
40 | if err != nil {
41 | return err
42 | }
43 | if diff == nil {
44 | log.Info().Str("Region", region).Msg("No changes")
45 | return nil
46 | }
47 | update := false
48 |
49 | // AutoEnable
50 | if diff.AutoEnable != nil {
51 | if *diff.AutoEnable {
52 | log.Info().Str("Region", region).Msg("Enable auto-enable-controls")
53 | } else {
54 | log.Info().Str("Region", region).Msg("Disable auto-enable-controls")
55 | }
56 | if _, err := c.UpdateSecurityHubConfiguration(ctx, &securityhub.UpdateSecurityHubConfigurationInput{
57 | AutoEnableControls: *diff.AutoEnable,
58 | }); err != nil {
59 | return err
60 | }
61 | }
62 |
63 | // Standards
64 | stds, err := standards(ctx, c)
65 | if err != nil {
66 | return err
67 | }
68 |
69 | for _, std := range diff.Standards {
70 | key := std.Key
71 | s := stds.findByKey(key)
72 | a, err := arn.Parse(*s.subscriptionArn)
73 | if err != nil {
74 | return err
75 | }
76 | if s == nil {
77 | return fmt.Errorf("could not find standard on %s: %s", region, key)
78 | }
79 |
80 | // Standards.Enable
81 | if std.Enable != nil {
82 | update = true
83 | switch *std.Enable {
84 | case true:
85 | log.Info().Str("Region", region).Str("Standard", key).Msg("Enable standard")
86 | o, err := c.BatchEnableStandards(ctx, &securityhub.BatchEnableStandardsInput{
87 | StandardsSubscriptionRequests: []types.StandardsSubscriptionRequest{
88 | types.StandardsSubscriptionRequest{
89 | StandardsArn: s.arn,
90 | },
91 | },
92 | })
93 | if err != nil {
94 | return err
95 | }
96 | s.subscriptionArn = o.StandardsSubscriptions[0].StandardsSubscriptionArn
97 |
98 | // ref: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/securityhub@v1.20.0#pkg-overview
99 | // * BatchEnableStandards - RateLimit of 1 request per second, BurstLimit of 1 request per second.
100 | time.Sleep(1 * time.Second)
101 | case false:
102 | log.Info().Str("Region", region).Str("Standard", key).Msg("Disable standard")
103 | if _, err := c.BatchDisableStandards(ctx, &securityhub.BatchDisableStandardsInput{
104 | StandardsSubscriptionArns: []string{*s.subscriptionArn},
105 | }); err != nil {
106 | return err
107 | }
108 | continue
109 | }
110 | }
111 |
112 | if std.Controls == nil && std.Findings == nil {
113 | log.Debug().Str("Region", region).Str("Standard", key).Msg("Skip controls as there is no difference")
114 | continue
115 | }
116 |
117 | // Standards.Controls
118 | if std.Controls != nil {
119 | cs, err := ctrls(ctx, c, s.subscriptionArn)
120 | if err != nil {
121 | return err
122 | }
123 | for _, id := range std.Controls.Enable {
124 | arn, ok := cs.arns[id]
125 | if !ok {
126 | log.Debug().Str("Region", region).Str("Standard", key).Str("Control", id).Msg("Skip control")
127 | continue
128 | }
129 | if contains(cs.Enable, id) {
130 | continue
131 | }
132 | update = true
133 | log.Info().Str("Region", region).Str("Standard", key).Str("Control", id).Msg("Enable control")
134 | if _, err := c.UpdateStandardsControl(ctx, &securityhub.UpdateStandardsControlInput{
135 | StandardsControlArn: arn,
136 | ControlStatus: types.ControlStatusEnabled,
137 | }); err != nil {
138 | return err
139 | }
140 | // ref: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/securityhub@v1.20.0#pkg-overview
141 | // * UpdateStandardsControl - RateLimit of 1 request per second, BurstLimit of 5 requests per second.
142 | time.Sleep(1 * time.Second)
143 | }
144 | for _, d := range std.Controls.Disable {
145 | id := d.Key.(string)
146 | if d.Value.(string) != "" {
147 | reason = d.Value.(string)
148 | }
149 | arn, ok := cs.arns[id]
150 | if !ok {
151 | log.Debug().Str("Region", region).Str("Standard", key).Str("Control", id).Msg("Skip control")
152 | continue
153 | }
154 | if containsMapSlice(cs.Disable, id, reason) {
155 | continue
156 | }
157 | update = true
158 | log.Info().Str("Region", region).Str("Standard", key).Str("Control", id).Str("Reason", reason).Msg("Disable control")
159 | if _, err := c.UpdateStandardsControl(ctx, &securityhub.UpdateStandardsControlInput{
160 | StandardsControlArn: arn,
161 | ControlStatus: types.ControlStatusDisabled,
162 | DisabledReason: &reason,
163 | }); err != nil {
164 | return err
165 | }
166 | // ref: https://pkg.go.dev/github.com/aws/aws-sdk-go-v2/service/securityhub@v1.20.0#pkg-overview
167 | // * UpdateStandardsControl - RateLimit of 1 request per second, BurstLimit of 5 requests per second.
168 | time.Sleep(1 * time.Second)
169 | }
170 | }
171 |
172 | // ControlFindingGenerator
173 | hub, err := c.DescribeHub(ctx, &securityhub.DescribeHubInput{})
174 | if err != nil {
175 | return err
176 | }
177 | ctrlfg := hub.ControlFindingGenerator
178 |
179 | // Standards.Findings
180 | if std.Findings != nil {
181 | cs, err := ctrls(ctx, c, s.subscriptionArn)
182 | if err != nil {
183 | return err
184 | }
185 | for _, fg := range std.Findings {
186 | for _, r := range fg.Resources {
187 | aa, err := arn.Parse(r.Arn)
188 | if err != nil {
189 | return err
190 | }
191 | if region != "" && aa.Region != "" && aa.Region != region {
192 | continue
193 | }
194 | cArn, ok := cs.arns[fg.ControlID]
195 | if !ok {
196 | return fmt.Errorf("not found: %s", fg.ControlID)
197 | }
198 |
199 | findingFilters := &types.AwsSecurityFindingFilters{
200 | AwsAccountId: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(a.AccountID)}},
201 | ResourceId: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(r.Arn)}},
202 | ProductName: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String("Security Hub")}},
203 | RecordState: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String("ACTIVE")}},
204 | }
205 | switch ctrlfg {
206 | case types.ControlFindingGeneratorSecurityControl:
207 | findingFilters.ComplianceSecurityControlId = []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(fg.ControlID)}}
208 | findingFilters.ComplianceAssociatedStandardsId = []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(fmt.Sprintf("standards/%s", key))}}
209 | case types.ControlFindingGeneratorStandardControl:
210 | findingFilters.ProductFields = []types.MapFilter{types.MapFilter{Comparison: types.MapFilterComparisonEquals, Key: aws.String("StandardsControlArn"), Value: cArn}}
211 | default:
212 | return fmt.Errorf("unsupported ControlFindingGenerator: %v", ctrlfg)
213 | }
214 | got, err := c.GetFindings(ctx, &securityhub.GetFindingsInput{
215 | Filters: findingFilters,
216 | })
217 | if err != nil {
218 | return err
219 | }
220 | if len(got.Findings) != 1 {
221 | if len(got.Findings) == 0 && aa.Region == "" {
222 | // eg. arn:aws:s3:::
223 | continue
224 | }
225 | return fmt.Errorf("not found: %s", r.Arn)
226 | }
227 | gotFg := got.Findings[0]
228 | status := string(gotFg.Workflow.Status)
229 | note := ""
230 | if gotFg.Note != nil && gotFg.Note.Text != nil {
231 | note = *gotFg.Note.Text
232 | }
233 | if r.Status != status || r.Note != note {
234 | log.Info().Str("Region", region).Str("Standard", key).Str("Control", fg.ControlID).Str("Resource ID", r.Arn).Str("Status", r.Status).Str("Note", r.Note).Msg("Change workfow status")
235 |
236 | input := &securityhub.BatchUpdateFindingsInput{
237 | FindingIdentifiers: []types.AwsSecurityFindingIdentifier{{
238 | Id: gotFg.Id,
239 | ProductArn: gotFg.ProductArn,
240 | }},
241 | Workflow: &types.WorkflowUpdate{
242 | Status: types.WorkflowStatus(r.Status),
243 | },
244 | }
245 | if r.Note != "" {
246 | input.Note = &types.NoteUpdate{
247 | Text: aws.String(r.Note),
248 | UpdatedBy: aws.String(noteUpdateBy),
249 | }
250 | }
251 | if _, err := c.BatchUpdateFindings(ctx, input); err != nil {
252 | return err
253 | }
254 | }
255 | }
256 | }
257 | }
258 |
259 | }
260 |
261 | if !update {
262 | log.Info().Str("Region", region).Msg("No changes")
263 | }
264 |
265 | return nil
266 | }
267 |
--------------------------------------------------------------------------------
/sechub/fetch.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "context"
5 |
6 | "github.com/aws/aws-sdk-go-v2/aws"
7 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
8 | )
9 |
10 | func (sh *SecHub) Fetch(ctx context.Context, cfg aws.Config) error {
11 | c := securityhub.NewFromConfig(cfg)
12 | stds, err := standards(ctx, c)
13 | if err != nil {
14 | return err
15 | }
16 | hub, err := c.DescribeHub(ctx, &securityhub.DescribeHubInput{})
17 | if err != nil {
18 | return err
19 | }
20 | if hub.SubscribedAt != nil {
21 | sh.enabled = true
22 | }
23 | sh.AutoEnable = aws.Bool(hub.AutoEnableControls)
24 | for _, std := range stds {
25 | if std.Enable == nil || !*std.Enable {
26 | continue
27 | }
28 | cs, err := ctrls(ctx, c, std.subscriptionArn)
29 | if err != nil {
30 | return err
31 | }
32 | std.Controls = cs
33 | }
34 | sh.Standards = stds
35 |
36 | return nil
37 | }
38 |
--------------------------------------------------------------------------------
/sechub/finding.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import "fmt"
4 |
5 | type FindingGroup struct {
6 | ControlID string
7 | Resources FindingResources
8 | }
9 |
10 | type FindingGroups []*FindingGroup
11 |
12 | func (fgs FindingGroups) ControlIDs() []string {
13 | ids := []string{}
14 | for _, fg := range fgs {
15 | ids = append(ids, fg.ControlID)
16 | }
17 | return ids
18 | }
19 |
20 | func (fgs FindingGroups) FindByControlID(id string) (*FindingGroup, error) {
21 | for _, fg := range fgs {
22 | if fg.ControlID == id {
23 | return fg, nil
24 | }
25 | }
26 | return nil, fmt.Errorf("not found: %s", id)
27 | }
28 |
29 | type FindingResource struct {
30 | Arn string
31 | Status string
32 | Note string
33 | }
34 |
35 | type FindingResources []*FindingResource
36 |
37 | func (frs FindingResources) Arns() []string {
38 | arns := []string{}
39 | for _, fr := range frs {
40 | arns = append(arns, fr.Arn)
41 | }
42 | return arns
43 | }
44 |
45 | func (frs FindingResources) FindByArn(arn string) (*FindingResource, error) {
46 | for _, fr := range frs {
47 | if fr.Arn == arn {
48 | return fr, nil
49 | }
50 | }
51 | return nil, fmt.Errorf("not found: %s", arn)
52 | }
53 |
54 | func intersectFindingGroups(a, b FindingGroups) FindingGroups {
55 | fgs := FindingGroups{}
56 | ids := intersect(a.ControlIDs(), b.ControlIDs())
57 | for _, id := range ids {
58 | fg := &FindingGroup{ControlID: id}
59 | afg, _ := a.FindByControlID(id)
60 | bfg, _ := b.FindByControlID(id)
61 | if afg == nil || bfg == nil {
62 | continue
63 | }
64 | arns := intersect(afg.Resources.Arns(), bfg.Resources.Arns())
65 | for _, arn := range arns {
66 | ar, _ := afg.Resources.FindByArn(arn)
67 | br, _ := bfg.Resources.FindByArn(arn)
68 | if ar == nil || br == nil {
69 | continue
70 | }
71 | if ar.Status == br.Status && ar.Note == br.Note {
72 | fg.Resources = append(fg.Resources, &FindingResource{
73 | Arn: arn,
74 | Status: ar.Status,
75 | Note: ar.Note,
76 | })
77 | }
78 | }
79 | if len(fg.Resources) > 0 {
80 | fgs = append(fgs, fg)
81 | }
82 | }
83 | return fgs
84 | }
85 |
86 | func diffFindingGroups(base, a FindingGroups) FindingGroups {
87 | fgs := FindingGroups{}
88 | ids := unique(append(base.ControlIDs(), a.ControlIDs()...))
89 | for _, id := range ids {
90 | fg := &FindingGroup{ControlID: id}
91 | basefg, _ := base.FindByControlID(id)
92 | afg, _ := a.FindByControlID(id)
93 | switch {
94 | case afg == nil:
95 | // do nothing
96 | case basefg == nil:
97 | fg.Resources = afg.Resources
98 | case basefg != nil && afg != nil:
99 | arns := unique(append(basefg.Resources.Arns(), afg.Resources.Arns()...))
100 | for _, arn := range arns {
101 | baser, _ := basefg.Resources.FindByArn(arn)
102 | ar, _ := afg.Resources.FindByArn(arn)
103 | switch {
104 | case ar == nil:
105 | // do noting
106 | case baser == nil:
107 | fg.Resources = append(fg.Resources, ar)
108 | case baser != nil && ar != nil:
109 | if baser.Status != ar.Status || baser.Note != ar.Note {
110 | fg.Resources = append(fg.Resources, &FindingResource{
111 | Arn: arn,
112 | Status: ar.Status,
113 | Note: ar.Note,
114 | })
115 | }
116 | }
117 | }
118 | }
119 | if len(fg.Resources) > 0 {
120 | fgs = append(fgs, fg)
121 | }
122 | }
123 | return fgs
124 | }
125 |
126 | func overlayFindingGroups(base, overlay FindingGroups) FindingGroups {
127 | for _, ofg := range overlay {
128 | basefg, _ := base.FindByControlID(ofg.ControlID)
129 | if basefg == nil {
130 | base = append(base, ofg)
131 | continue
132 | }
133 | for _, r := range ofg.Resources {
134 | baser, _ := basefg.Resources.FindByArn(r.Arn)
135 | if baser == nil {
136 | basefg.Resources = append(basefg.Resources, r)
137 | continue
138 | }
139 | baser.Status = r.Status
140 | baser.Note = r.Note
141 | }
142 | }
143 | return base
144 | }
145 |
--------------------------------------------------------------------------------
/sechub/finding_test.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/google/go-cmp/cmp"
7 | )
8 |
9 | func TestIntersectFindingGroups(t *testing.T) {
10 | tests := []struct {
11 | a FindingGroups
12 | b FindingGroups
13 | want FindingGroups
14 | }{
15 | {nil, nil, FindingGroups{}},
16 | {
17 | FindingGroups{
18 | &FindingGroup{
19 | ControlID: "IAM.1",
20 | Resources: FindingResources{
21 | &FindingResource{
22 | Arn: "arn:aws:iam::1234567890:user/user-a",
23 | Status: "SUPPRESSED",
24 | Note: "This is suppressed",
25 | },
26 | &FindingResource{
27 | Arn: "arn:aws:iam::1234567890:user/user-b",
28 | Status: "RESOLVED",
29 | Note: "This is resolved",
30 | },
31 | },
32 | },
33 | },
34 | FindingGroups{
35 | &FindingGroup{
36 | ControlID: "IAM.1",
37 | Resources: FindingResources{
38 | &FindingResource{
39 | Arn: "arn:aws:iam::1234567890:user/user-a",
40 | Status: "SUPPRESSED",
41 | Note: "This is suppressed",
42 | },
43 | },
44 | },
45 | &FindingGroup{
46 | ControlID: "IAM.2",
47 | Resources: FindingResources{
48 | &FindingResource{
49 | Arn: "arn:aws:iam::1234567890:user/user-a",
50 | Status: "SUPPRESSED",
51 | Note: "This is suppressed",
52 | },
53 | },
54 | },
55 | },
56 | FindingGroups{
57 | &FindingGroup{
58 | ControlID: "IAM.1",
59 | Resources: FindingResources{
60 | &FindingResource{
61 | Arn: "arn:aws:iam::1234567890:user/user-a",
62 | Status: "SUPPRESSED",
63 | Note: "This is suppressed",
64 | },
65 | },
66 | },
67 | },
68 | },
69 | }
70 | for _, tt := range tests {
71 | got := intersectFindingGroups(tt.a, tt.b)
72 | if diff := cmp.Diff(got, tt.want, nil); diff != "" {
73 | t.Errorf("%s", diff)
74 | }
75 | }
76 | }
77 |
78 | func TestDiffFindingGroups(t *testing.T) {
79 | tests := []struct {
80 | base FindingGroups
81 | a FindingGroups
82 | want FindingGroups
83 | }{
84 | {nil, nil, FindingGroups{}},
85 | {
86 | FindingGroups{
87 | &FindingGroup{
88 | ControlID: "IAM.1",
89 | Resources: FindingResources{
90 | &FindingResource{
91 | Arn: "arn:aws:iam::1234567890:user/user-a",
92 | Status: "SUPPRESSED",
93 | Note: "This is suppressed",
94 | },
95 | &FindingResource{
96 | Arn: "arn:aws:iam::1234567890:user/user-b",
97 | Status: "RESOLVED",
98 | Note: "This is resolved",
99 | },
100 | },
101 | },
102 | },
103 | FindingGroups{
104 | &FindingGroup{
105 | ControlID: "IAM.1",
106 | Resources: FindingResources{
107 | &FindingResource{
108 | Arn: "arn:aws:iam::1234567890:user/user-a",
109 | Status: "SUPPRESSED",
110 | Note: "This is suppressed",
111 | },
112 | },
113 | },
114 | &FindingGroup{
115 | ControlID: "IAM.2",
116 | Resources: FindingResources{
117 | &FindingResource{
118 | Arn: "arn:aws:iam::1234567890:user/user-a",
119 | Status: "SUPPRESSED",
120 | Note: "This is suppressed",
121 | },
122 | },
123 | },
124 | },
125 | FindingGroups{
126 | &FindingGroup{
127 | ControlID: "IAM.2",
128 | Resources: FindingResources{
129 | &FindingResource{
130 | Arn: "arn:aws:iam::1234567890:user/user-a",
131 | Status: "SUPPRESSED",
132 | Note: "This is suppressed",
133 | },
134 | },
135 | },
136 | },
137 | },
138 | }
139 | for _, tt := range tests {
140 | got := diffFindingGroups(tt.base, tt.a)
141 | if diff := cmp.Diff(got, tt.want, nil); diff != "" {
142 | t.Errorf("%s", diff)
143 | }
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/sechub/notify.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "bytes"
5 | "context"
6 | "encoding/json"
7 | "errors"
8 | "fmt"
9 | "net/http"
10 | "strings"
11 | "time"
12 |
13 | "github.com/antonmedv/expr"
14 | "github.com/aws/aws-sdk-go-v2/aws"
15 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
16 | "github.com/goccy/go-yaml"
17 | "github.com/k1LoW/expand"
18 | )
19 |
20 | const (
21 | defaultHeader = "'*AWS Security Hub Notification*'"
22 | defaultMessageTmpl = "Notified because condition *'%s'* is met."
23 | defaultConsoleURL = "https://ap-northeast-1.console.aws.amazon.com/securityhub/home?region=ap-northeast-1#/findings?search=RecordState%3D%255Coperator%255C%253AEQUALS%255C%253AACTIVE%26WorkflowStatus%3D%255Coperator%255C%253AEQUALS%255C%253ANEW%26WorkflowStatus%3D%255Coperator%255C%253AEQUALS%255C%253ANOTIFIED"
24 | )
25 |
26 | var defaultTemplate = map[string]interface{}{
27 | "blocks": []interface{}{
28 | map[string]interface{}{
29 | "type": "section",
30 | "text": map[string]interface{}{
31 | "type": "mrkdwn",
32 | "text": "{{ header }}",
33 | },
34 | },
35 | map[string]interface{}{
36 | "type": "section",
37 | "text": map[string]interface{}{
38 | "type": "mrkdwn",
39 | "text": "{{ message }}",
40 | },
41 | },
42 | map[string]interface{}{
43 | "type": "section",
44 | "fields": []interface{}{
45 | map[string]interface{}{
46 | "type": "mrkdwn",
47 | "text": "*CRITICAL:*\n{{ critical - critical_resolved - critical_suppressed }}",
48 | },
49 | map[string]interface{}{
50 | "type": "mrkdwn",
51 | "text": "*HIGH:*\n{{ high - high_resolved - high_suppressed }}",
52 | },
53 | },
54 | },
55 | map[string]interface{}{
56 | "type": "section",
57 | "fields": []interface{}{
58 | map[string]interface{}{
59 | "type": "mrkdwn",
60 | "text": "*MEDIUM:*\n{{ medium - medium_resolved - medium_suppressed }}",
61 | },
62 | map[string]interface{}{
63 | "type": "mrkdwn",
64 | "text": "*LOW:*\n{{ low - low_resolved - low_suppressed }}",
65 | },
66 | },
67 | },
68 | map[string]interface{}{
69 | "type": "section",
70 | "text": map[string]interface{}{
71 | "type": "mrkdwn",
72 | "text": "<{{ consoleURL }}|View findings>",
73 | },
74 | },
75 | },
76 | }
77 |
78 | type NotifyFinding struct {
79 | SeverityLabel types.SeverityLabel
80 | WorkflowStatus types.WorkflowStatus
81 | }
82 |
83 | func (sh *SecHub) Notify(ctx context.Context, cfg aws.Config, findings []NotifyFinding, dryrun bool) error {
84 | urep := strings.NewReplacer("ap-northeast-1", cfg.Region)
85 | now := time.Now()
86 | env := map[string]interface{}{
87 | "region": sh.region,
88 | "consoleURL": urep.Replace(defaultConsoleURL),
89 | "month": int(now.Month()),
90 | "day": now.Day(),
91 | "hour": now.Hour(),
92 | "weekday": int(now.Weekday()),
93 | }
94 | for _, sl := range types.SeverityLabelCritical.Values() {
95 | slkey := strings.ToLower(string(sl))
96 | env[slkey] = 0
97 | for _, ws := range types.WorkflowStatusNew.Values() {
98 | wskey := strings.ToLower(string(ws))
99 | env[wskey] = 0
100 | key := fmt.Sprintf("%s_%s", slkey, wskey)
101 | env[key] = 0
102 | }
103 | }
104 | for _, f := range findings {
105 | slkey := strings.ToLower(string(f.SeverityLabel))
106 | wskey := strings.ToLower(string(f.WorkflowStatus))
107 | key := fmt.Sprintf("%s_%s", slkey, wskey)
108 | env[slkey] = env[slkey].(int) + 1
109 | env[wskey] = env[wskey].(int) + 1
110 | env[key] = env[key].(int) + 1
111 | }
112 | for _, n := range sh.Notifications {
113 | if n.Header == "" {
114 | n.Header = defaultHeader
115 | }
116 | if n.Message == "" {
117 | n.Message = fmt.Sprintf(defaultMessageTmpl, n.If)
118 | }
119 | if n.If == "" {
120 | return errors.New("no cond")
121 | }
122 | if !dryrun && n.WebhookURL == "" {
123 | return errors.New("no webhookURL")
124 | }
125 | env["header"] = n.Header
126 | env["cond"] = n.If
127 | env["message"] = n.Message
128 | tf, err := expr.Eval(fmt.Sprintf("(%s) == true", n.If), env)
129 | if err != nil {
130 | return err
131 | }
132 | if !tf.(bool) {
133 | continue
134 | }
135 | if n.Template == nil {
136 | n.Template = defaultTemplate
137 | }
138 | b, err := expandBody(n.Template, env)
139 | if err != nil {
140 | return err
141 | }
142 |
143 | if dryrun {
144 | var out bytes.Buffer
145 | if err := json.Indent(&out, b, "", " "); err != nil {
146 | return err
147 | }
148 | fmt.Println(out.String())
149 | continue
150 | }
151 |
152 | req, err := http.NewRequest(
153 | http.MethodPost,
154 | n.WebhookURL,
155 | bytes.NewBuffer(b),
156 | )
157 | if err != nil {
158 | return err
159 | }
160 | req.Header.Set("Content-Type", "application/json")
161 | client := &http.Client{}
162 | resp, err := client.Do(req)
163 | if err != nil {
164 | return err
165 | }
166 | if err := resp.Body.Close(); err != nil {
167 | return err
168 | }
169 | }
170 | return nil
171 | }
172 |
173 | func expandBody(tmpl, env interface{}) ([]byte, error) {
174 | const (
175 | delimStart = "{{"
176 | delimEnd = "}}"
177 | )
178 | b, err := yaml.Marshal(tmpl)
179 | if err != nil {
180 | return nil, err
181 | }
182 | e, err := expand.ReplaceYAML(string(b), expand.ExprRepFn(delimStart, delimEnd, env), false)
183 | if err != nil {
184 | return nil, err
185 | }
186 | var ee interface{}
187 | if err := yaml.Unmarshal([]byte(e), &ee); err != nil {
188 | return nil, err
189 | }
190 | buf := new(bytes.Buffer)
191 | enc := json.NewEncoder(buf)
192 | enc.SetEscapeHTML(false)
193 | if err := enc.Encode(ee); err != nil {
194 | return nil, err
195 | }
196 | return buf.Bytes(), nil
197 | }
198 |
--------------------------------------------------------------------------------
/sechub/notify_test.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "context"
5 | "net/http"
6 | "os"
7 | "strings"
8 | "testing"
9 |
10 | "github.com/aws/aws-sdk-go-v2/config"
11 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
12 | "github.com/k1LoW/httpstub"
13 | "github.com/tenntenn/golden"
14 | )
15 |
16 | func TestNotify(t *testing.T) {
17 | tests := []struct {
18 | name string
19 | notification *Notification
20 | findings []NotifyFinding
21 | notify bool
22 | }{
23 | {
24 | "use default template",
25 | &Notification{
26 | If: "true",
27 | },
28 | []NotifyFinding{
29 | {
30 | SeverityLabel: types.SeverityLabelCritical,
31 | WorkflowStatus: types.WorkflowStatusNew,
32 | },
33 | },
34 | true,
35 | },
36 | {
37 | "cond false",
38 | &Notification{
39 | If: "false",
40 | },
41 | []NotifyFinding{
42 | {
43 | SeverityLabel: types.SeverityLabelCritical,
44 | WorkflowStatus: types.WorkflowStatusNew,
45 | },
46 | },
47 | false,
48 | },
49 | {
50 | "notify critical",
51 | &Notification{
52 | If: "critical > 0",
53 | },
54 | []NotifyFinding{
55 | {
56 | SeverityLabel: types.SeverityLabelCritical,
57 | WorkflowStatus: types.WorkflowStatusNew,
58 | },
59 | },
60 | true,
61 | },
62 | {
63 | "not notify critical",
64 | &Notification{
65 | If: "critical > 0",
66 | },
67 | []NotifyFinding{
68 | {
69 | SeverityLabel: types.SeverityLabelHigh,
70 | WorkflowStatus: types.WorkflowStatusNew,
71 | },
72 | },
73 | false,
74 | },
75 | {
76 | "use custom template",
77 | &Notification{
78 | If: "true",
79 | Template: map[string]interface{}{
80 | "critical": "CRITICAL: {{ critical }}",
81 | },
82 | },
83 | []NotifyFinding{
84 | {
85 | SeverityLabel: types.SeverityLabelCritical,
86 | WorkflowStatus: types.WorkflowStatusNew,
87 | },
88 | },
89 | true,
90 | },
91 | {
92 | "change header",
93 | &Notification{
94 | Header: "Notification!!",
95 | If: "true",
96 | },
97 | []NotifyFinding{
98 | {
99 | SeverityLabel: types.SeverityLabelCritical,
100 | WorkflowStatus: types.WorkflowStatusNew,
101 | },
102 | },
103 | true,
104 | },
105 | {
106 | "change message",
107 | &Notification{
108 | Message: "Notice!!",
109 | If: "true",
110 | },
111 | []NotifyFinding{
112 | {
113 | SeverityLabel: types.SeverityLabelCritical,
114 | WorkflowStatus: types.WorkflowStatusNew,
115 | },
116 | },
117 | true,
118 | },
119 | {
120 | "dryrun true",
121 | &Notification{
122 | If: "true",
123 | },
124 | []NotifyFinding{
125 | {
126 | SeverityLabel: types.SeverityLabelCritical,
127 | WorkflowStatus: types.WorkflowStatusNew,
128 | },
129 | },
130 | false,
131 | },
132 | }
133 | ctx := context.Background()
134 | cfg, err := config.LoadDefaultConfig(ctx)
135 | if err != nil {
136 | t.Fatal(err)
137 | }
138 | region := "dummy-ap-1"
139 | cfg.Region = region
140 | for _, tt := range tests {
141 | t.Run(tt.name, func(t *testing.T) {
142 | r := httpstub.NewRouter(t)
143 | r.Method(http.MethodPost).Header("Content-Type", "application/json").ResponseString(http.StatusOK, ``)
144 | ts := r.Server()
145 | t.Cleanup(func() {
146 | ts.Close()
147 | })
148 | tt.notification.WebhookURL = ts.URL
149 | sh := New(region)
150 | sh.Notifications = append(sh.Notifications, tt.notification)
151 | dryrun := tt.name == "dryrun true"
152 | if err := sh.Notify(ctx, cfg, tt.findings, dryrun); err != nil {
153 | t.Error(err)
154 | }
155 | if len(r.Requests()) == 0 {
156 | if tt.notify {
157 | t.Error("want notify")
158 | }
159 | return
160 | }
161 | got := r.Requests()[0].Body
162 | t.Cleanup(func() {
163 | if err := r.Requests()[0].Body.Close(); err != nil {
164 | t.Error(err)
165 | }
166 | })
167 | key := strings.Replace(tt.name, " ", "_", -1)
168 | if os.Getenv("UPDATE_GOLDEN") != "" {
169 | golden.Update(t, "testdata", key, got)
170 | return
171 | }
172 | if diff := golden.Diff(t, "testdata", key, got); diff != "" {
173 | t.Error(diff)
174 | }
175 | })
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/sechub/plan.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "context"
5 | "fmt"
6 |
7 | "github.com/aws/aws-sdk-go-v2/aws"
8 | "github.com/aws/aws-sdk-go-v2/aws/arn"
9 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
10 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
11 | "github.com/rs/zerolog/log"
12 | )
13 |
14 | type ChangeType string
15 |
16 | const (
17 | ENABLE ChangeType = "+"
18 | DISABLE ChangeType = "-"
19 | CHANGE ChangeType = "~"
20 | )
21 |
22 | type Change struct {
23 | Key string
24 | ChangeType ChangeType
25 | DisabledReason string
26 | Changed interface{}
27 | }
28 |
29 | func (sh *SecHub) Plan(ctx context.Context, cfg aws.Config, reason string) ([]*Change, error) {
30 | changes := []*Change{}
31 | region := cfg.Region
32 | c := securityhub.NewFromConfig(cfg)
33 | d := sh.Regions.findByRegionName(region)
34 | a, err := contextcopy(sh)
35 | if err != nil {
36 | return nil, err
37 | }
38 | if d != nil {
39 | a, err = Override(sh, d)
40 | if err != nil {
41 | return nil, err
42 | }
43 | }
44 | current := New(region)
45 | if err := current.Fetch(ctx, cfg); err != nil {
46 | return nil, err
47 | }
48 | if !current.enabled {
49 | log.Info().Str("Region", region).Msg("Skip because Security Hub is not enabled")
50 | return changes, nil
51 | }
52 | diff, err := Diff(current, a)
53 | if err != nil {
54 | return nil, err
55 | }
56 |
57 | if diff == nil {
58 | return changes, nil
59 | }
60 |
61 | // AutoEnable
62 | if diff.AutoEnable != nil {
63 | if *diff.AutoEnable {
64 | changes = append(changes, &Change{
65 | Key: fmt.Sprintf("%s::%s", region, "auto-enable-controls"),
66 | ChangeType: ENABLE,
67 | })
68 | } else {
69 | changes = append(changes, &Change{
70 | Key: fmt.Sprintf("%s::%s", region, "auto-enable-controls"),
71 | ChangeType: DISABLE,
72 | })
73 | }
74 | }
75 |
76 | // Standards
77 | stds, err := standards(ctx, c)
78 | if err != nil {
79 | return nil, err
80 | }
81 |
82 | for _, std := range diff.Standards {
83 | key := std.Key
84 | s := stds.findByKey(key)
85 | a, err := arn.Parse(*s.subscriptionArn)
86 | if err != nil {
87 | return nil, err
88 | }
89 | if s == nil {
90 | return nil, fmt.Errorf("could not find standard on %s: %s", region, key)
91 | }
92 |
93 | // Standards.Enable
94 | if std.Enable != nil {
95 | switch *std.Enable {
96 | case true:
97 | changes = append(changes, &Change{
98 | Key: fmt.Sprintf("%s::standards::%s", region, key),
99 | ChangeType: ENABLE,
100 | })
101 | case false:
102 | changes = append(changes, &Change{
103 | Key: fmt.Sprintf("%s::standards::%s", region, key),
104 | ChangeType: DISABLE,
105 | })
106 | }
107 | continue
108 | }
109 |
110 | // Standards.Controls
111 | if std.Controls != nil {
112 | cs, err := ctrls(ctx, c, s.subscriptionArn)
113 | if err != nil {
114 | return nil, err
115 | }
116 | for _, id := range std.Controls.Enable {
117 | _, ok := cs.arns[id]
118 | if !ok {
119 | continue
120 | }
121 | changes = append(changes, &Change{
122 | Key: fmt.Sprintf("%s::standards::%s::controls::%s", region, key, id),
123 | ChangeType: ENABLE,
124 | DisabledReason: "",
125 | })
126 | }
127 | for _, d := range std.Controls.Disable {
128 | id := d.Key.(string)
129 | if d.Value.(string) != "" {
130 | reason = d.Value.(string)
131 | }
132 | _, ok := cs.arns[id]
133 | if !ok {
134 | continue
135 | }
136 | changes = append(changes, &Change{
137 | Key: fmt.Sprintf("%s::standards::%s::controls::%s", region, key, id),
138 | ChangeType: DISABLE,
139 | DisabledReason: reason,
140 | })
141 | }
142 | }
143 |
144 | // ControlFindingGenerator
145 | hub, err := c.DescribeHub(ctx, &securityhub.DescribeHubInput{})
146 | if err != nil {
147 | return nil, err
148 | }
149 | ctrlfg := hub.ControlFindingGenerator
150 |
151 | // Standards.Findings
152 | if std.Findings != nil {
153 | cs, err := ctrls(ctx, c, s.subscriptionArn)
154 | if err != nil {
155 | return nil, err
156 | }
157 | for _, fg := range std.Findings {
158 | for _, r := range fg.Resources {
159 | aa, err := arn.Parse(r.Arn)
160 | if err != nil {
161 | return nil, err
162 | }
163 | if region != "" && aa.Region != "" && aa.Region != region {
164 | continue
165 | }
166 | cArn, ok := cs.arns[fg.ControlID]
167 | if !ok {
168 | return nil, fmt.Errorf("not found: %s", fg.ControlID)
169 | }
170 |
171 | findingFilters := &types.AwsSecurityFindingFilters{
172 | AwsAccountId: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(a.AccountID)}},
173 | ResourceId: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(r.Arn)}},
174 | ProductName: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String("Security Hub")}},
175 | RecordState: []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String("ACTIVE")}},
176 | }
177 | switch ctrlfg {
178 | case types.ControlFindingGeneratorSecurityControl:
179 | findingFilters.ComplianceSecurityControlId = []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(fg.ControlID)}}
180 | findingFilters.ComplianceAssociatedStandardsId = []types.StringFilter{types.StringFilter{Comparison: types.StringFilterComparisonEquals, Value: aws.String(fmt.Sprintf("standards/%s", key))}}
181 | case types.ControlFindingGeneratorStandardControl:
182 | findingFilters.ProductFields = []types.MapFilter{types.MapFilter{Comparison: types.MapFilterComparisonEquals, Key: aws.String("StandardsControlArn"), Value: cArn}}
183 | default:
184 | return nil, fmt.Errorf("unsupported ControlFindingGenerator: %v", ctrlfg)
185 | }
186 | got, err := c.GetFindings(ctx, &securityhub.GetFindingsInput{
187 | Filters: findingFilters,
188 | })
189 | if err != nil {
190 | return nil, err
191 | }
192 | if len(got.Findings) != 1 {
193 | if len(got.Findings) == 0 && aa.Region == "" {
194 | // eg. arn:aws:s3:::
195 | continue
196 | }
197 | return nil, fmt.Errorf("not found: %s", r.Arn)
198 | }
199 | status := string(got.Findings[0].Workflow.Status)
200 | note := ""
201 | if got.Findings[0].Note != nil && got.Findings[0].Note.Text != nil {
202 | note = *got.Findings[0].Note.Text
203 | }
204 | if r.Status != status || r.Note != note {
205 | changed := fmt.Sprintf("%s -> %s (note: %s)", status, r.Status, r.Note)
206 | if r.Note == "" {
207 | changed = fmt.Sprintf("%s -> %s", status, r.Status)
208 | }
209 | changes = append(changes, &Change{
210 | Key: *cArn,
211 | ChangeType: CHANGE,
212 | Changed: changed,
213 | })
214 | }
215 | }
216 | }
217 | }
218 | }
219 |
220 | return changes, nil
221 | }
222 |
--------------------------------------------------------------------------------
/sechub/sechub.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "context"
5 | "fmt"
6 | "os"
7 | "path/filepath"
8 | "strings"
9 |
10 | "github.com/aws/aws-sdk-go-v2/aws"
11 | "github.com/aws/aws-sdk-go-v2/service/securityhub"
12 | "github.com/aws/aws-sdk-go-v2/service/securityhub/types"
13 | "github.com/goccy/go-yaml"
14 | "github.com/k1LoW/expand"
15 | )
16 |
17 | type Controls struct {
18 | Enable []string `yaml:"enable,flow,omitempty"`
19 | Disable yaml.MapSlice `yaml:"disable,omitempty"`
20 | arns map[string]*string
21 | }
22 |
23 | type Standard struct {
24 | Key string `yaml:"key,omitempty"`
25 | Enable *bool `yaml:"enable,omitempty"`
26 | Controls *Controls `yaml:"controls,omitempty"`
27 | Findings FindingGroups `yaml:"-"`
28 |
29 | arn *string
30 | subscriptionArn *string
31 | enabledByDefault bool
32 | }
33 |
34 | type Notification struct {
35 | If string `yaml:"if"`
36 | Header string `yaml:"header,omitempty"`
37 | Message string `yaml:"message,omitempty"`
38 | WebhookURL string `yaml:"webhookURL"`
39 | Template interface{}
40 | }
41 |
42 | type Standards []*Standard
43 |
44 | type Regions []*SecHub
45 |
46 | type Notifications []*Notification
47 |
48 | type SecHub struct {
49 | AutoEnable *bool `yaml:"autoEnable,omitempty"`
50 | Standards Standards
51 | Regions Regions
52 | Notifications Notifications `yaml:"notifications,omitempty"`
53 | region string // current region
54 | enabled bool // whether Security Hub is enabled in current region
55 | }
56 |
57 | func New(r string) *SecHub {
58 | return &SecHub{
59 | region: r,
60 | }
61 | }
62 |
63 | func Load(p string) (*SecHub, error) {
64 | b, err := os.ReadFile(filepath.Clean(p))
65 | if err != nil {
66 | return nil, err
67 | }
68 | hub := &SecHub{}
69 | if err := yaml.Unmarshal(expand.ExpandenvYAMLBytes(b), hub); err != nil {
70 | return nil, err
71 | }
72 | if err := hub.Validate(); err != nil {
73 | return nil, fmt.Errorf("validation error: %s: %w", p, err)
74 | }
75 | return hub, err
76 | }
77 |
78 | func Intersect(a, b *SecHub) *SecHub {
79 | i := &SecHub{}
80 | // AutoEnable
81 | if a.AutoEnable != nil && b.AutoEnable != nil && *a.AutoEnable == *b.AutoEnable {
82 | i.AutoEnable = a.AutoEnable
83 | } else {
84 | // default: true
85 | i.AutoEnable = aws.Bool(true)
86 | }
87 |
88 | // Standards
89 | i.Standards = Standards{}
90 | ikeys := intersect(a.Standards.keys(), b.Standards.keys())
91 | for _, k := range ikeys {
92 | is := &Standard{
93 | Key: k,
94 | }
95 | as := a.Standards.findByKey(k)
96 | bs := b.Standards.findByKey(k)
97 | // Standards.Enable
98 | if as.Enable != nil && bs.Enable != nil && *as.Enable == *bs.Enable {
99 | is.Enable = as.Enable
100 | } else {
101 | is.Enable = aws.Bool(as.enabledByDefault)
102 | }
103 | // Standards.Controls
104 | if as.Controls != nil && bs.Controls != nil {
105 | is.Controls = &Controls{}
106 | is.Controls.Enable = intersect(as.Controls.Enable, bs.Controls.Enable)
107 | is.Controls.Disable = intersectMapSlice(as.Controls.Disable, bs.Controls.Disable)
108 | }
109 |
110 | // Standards.Findngs
111 | is.Findings = intersectFindingGroups(as.Findings, bs.Findings)
112 |
113 | i.Standards = append(i.Standards, is)
114 | }
115 |
116 | return i
117 | }
118 |
119 | func Diff(base, a *SecHub) (*SecHub, error) {
120 | b, err := contextcopy(base)
121 | if err != nil {
122 | return nil, err
123 | }
124 | d := New(a.region)
125 | // AutoEnable
126 | if b.AutoEnable != nil && a.AutoEnable != nil && *b.AutoEnable == *a.AutoEnable {
127 | d.AutoEnable = nil
128 | } else {
129 | d.AutoEnable = a.AutoEnable
130 | }
131 | // Standards
132 | d.Standards = Standards{}
133 | for _, std := range a.Standards {
134 | bstd := b.Standards.findByKey(std.Key)
135 | if bstd == nil {
136 | d.Standards = append(d.Standards, std)
137 | continue
138 | }
139 | dstd := &Standard{Key: std.Key}
140 | // Standards.Enable
141 | if bstd.Enable != nil && std.Enable != nil && *bstd.Enable == *std.Enable {
142 | dstd.Enable = nil
143 | } else {
144 | dstd.Enable = std.Enable
145 | }
146 |
147 | if dstd.Enable == nil && bstd.Enable != nil && !*bstd.Enable {
148 | continue
149 | }
150 | if dstd.Enable != nil && !*dstd.Enable {
151 | d.Standards = append(d.Standards, dstd)
152 | continue
153 | }
154 |
155 | // Standards.Controls
156 | if bstd.Controls != nil && std.Controls != nil {
157 | dstd.Controls = &Controls{}
158 | if len(bstd.Controls.Enable) == len(std.Controls.Enable) && len(intersect(bstd.Controls.Enable, std.Controls.Enable)) == len(std.Controls.Enable) {
159 | dstd.Controls.Enable = nil
160 | } else {
161 | dstd.Controls.Enable = diff(bstd.Controls.Enable, std.Controls.Enable)
162 | }
163 | if len(bstd.Controls.Disable) == len(std.Controls.Disable) && len(intersectMapSlice(bstd.Controls.Disable, std.Controls.Disable)) == len(std.Controls.Disable) {
164 | dstd.Controls.Disable = nil
165 | } else {
166 | dstd.Controls.Disable = diffMapSlice(bstd.Controls.Disable, std.Controls.Disable)
167 | }
168 | } else {
169 | dstd.Controls = std.Controls
170 | }
171 |
172 | if dstd.Enable == nil && dstd.Controls == nil {
173 | continue
174 | }
175 |
176 | // Standards.Findings
177 | dstd.Findings = diffFindingGroups(bstd.Findings, std.Findings)
178 |
179 | if dstd.Enable == nil && dstd.Controls != nil && len(dstd.Controls.Enable) == 0 && len(dstd.Controls.Disable) == 0 && len(dstd.Findings) == 0 {
180 | continue
181 | }
182 |
183 | d.Standards = append(d.Standards, dstd)
184 |
185 | }
186 |
187 | if d.AutoEnable == nil && len(d.Standards) == 0 {
188 | return nil, nil
189 | }
190 |
191 | return d, nil
192 | }
193 |
194 | func Override(base, a *SecHub) (*SecHub, error) {
195 | o, err := contextcopy(base)
196 | if err != nil {
197 | return nil, err
198 | }
199 | o.overlay(a)
200 | return o, nil
201 | }
202 |
203 | func (base *SecHub) Overlay(overlay *SecHub) {
204 | base.overlay(overlay)
205 |
206 | for _, r := range base.Regions {
207 | if or := overlay.Regions.findByRegionName(r.region); or != nil {
208 | r.overlay(or)
209 | }
210 | }
211 | for _, or := range overlay.Regions {
212 | if r := base.Regions.findByRegionName(or.region); r == nil {
213 | base.Regions = append(base.Regions, or)
214 | }
215 | }
216 | }
217 |
218 | func (base *SecHub) overlay(overlay *SecHub) {
219 | // AutoEnable
220 | if overlay.AutoEnable != nil {
221 | base.AutoEnable = overlay.AutoEnable
222 | }
223 |
224 | // Standards
225 | for _, std := range base.Standards {
226 | as := overlay.Standards.findByKey(std.Key)
227 | if as == nil {
228 | continue
229 | }
230 | // Standards.Enable
231 | if as.Enable != nil {
232 | std.Enable = as.Enable
233 | }
234 | // Standards.Controls
235 | if as.Controls != nil {
236 | if len(as.Controls.Enable) > 0 {
237 | std.Controls.Enable = unique(append(std.Controls.Enable, as.Controls.Enable...))
238 | }
239 | if len(as.Controls.Disable) > 0 {
240 | // If 'Enable' and 'Disable' contain the same key, 'Enable' has priority.
241 | disable := yaml.MapSlice{}
242 | for _, d := range append(std.Controls.Disable, as.Controls.Disable...) {
243 | if !contains(std.Controls.Enable, d.Key.(string)) {
244 | disable = append(disable, d)
245 | }
246 | }
247 | std.Controls.Disable = uniqueMapSlice(disable)
248 | }
249 | }
250 | // Standards.Findings
251 | std.Findings = overlayFindingGroups(std.Findings, as.Findings)
252 | }
253 | for _, k := range diff(base.Standards.keys(), overlay.Standards.keys()) {
254 | as := overlay.Standards.findByKey(k)
255 | if as == nil {
256 | continue
257 | }
258 | base.Standards = append(overlay.Standards, as)
259 | }
260 |
261 | // Notifications
262 | ns := overlay.Notifications
263 | for _, b := range base.Notifications {
264 | exist := false
265 | for _, o := range overlay.Notifications {
266 | if b.If == o.If && b.WebhookURL == o.WebhookURL {
267 | exist = true
268 | }
269 | }
270 | if !exist {
271 | ns = append(ns, b)
272 | }
273 | }
274 | base.Notifications = ns
275 | }
276 |
277 | func (sh *SecHub) Validate() error {
278 | if err := sh.validate(); err != nil {
279 | return err
280 | }
281 | for _, r := range sh.Regions {
282 | if err := r.validate(); err != nil {
283 | return err
284 | }
285 | }
286 | return nil
287 | }
288 |
289 | func (sh *SecHub) validate() error {
290 | for _, std := range sh.Standards {
291 | disableKeys := []string{}
292 | m := map[string]struct{}{}
293 | if std.Controls != nil && len(std.Controls.Disable) > 0 {
294 | for _, d := range std.Controls.Disable {
295 | key := d.Key.(string)
296 | if _, ok := m[key]; ok {
297 | return fmt.Errorf("duplicate key: disable control %s", key)
298 | }
299 | disableKeys = append(disableKeys, key)
300 | m[key] = struct{}{}
301 | }
302 | }
303 | dup := intersect(std.Controls.Enable, disableKeys)
304 | if len(dup) > 0 {
305 | return fmt.Errorf("it exists for both enable contorol and disable contorol: %s", dup)
306 | }
307 | }
308 | return nil
309 | }
310 |
311 | func (rs Regions) findByRegionName(name string) *SecHub {
312 | for _, r := range rs {
313 | if r.region == name {
314 | return r
315 | }
316 | }
317 | return nil
318 | }
319 |
320 | func (stds Standards) findByKey(key string) *Standard {
321 | for _, std := range stds {
322 | if std.Key == key {
323 | return std
324 | }
325 | }
326 | return nil
327 | }
328 |
329 | func (stds Standards) keys() []string {
330 | keys := []string{}
331 | for _, std := range stds {
332 | keys = append(keys, std.Key)
333 | }
334 | return keys
335 | }
336 |
337 | func standards(ctx context.Context, c *securityhub.Client) (Standards, error) {
338 | stds := Standards{}
339 | r, err := c.DescribeStandards(ctx, &securityhub.DescribeStandardsInput{})
340 | if err != nil {
341 | return nil, err
342 | }
343 | for _, s := range r.Standards {
344 | key := key(*s.StandardsArn)
345 | stds = append(stds, &Standard{
346 | Key: key,
347 | Enable: aws.Bool(false),
348 | arn: s.StandardsArn,
349 | enabledByDefault: s.EnabledByDefault,
350 | })
351 | }
352 | enabled, err := c.GetEnabledStandards(ctx, &securityhub.GetEnabledStandardsInput{})
353 | if err != nil {
354 | return nil, err
355 | }
356 | for _, s := range enabled.StandardsSubscriptions {
357 | std := stds.findByKey(key(*s.StandardsArn))
358 | std.Enable = aws.Bool(true)
359 | std.subscriptionArn = s.StandardsSubscriptionArn
360 | }
361 |
362 | return stds, nil
363 | }
364 |
365 | func ctrls(ctx context.Context, c *securityhub.Client, subscriptionArn *string) (*Controls, error) {
366 | cs := &Controls{
367 | arns: map[string]*string{},
368 | }
369 | var nt *string
370 | for {
371 | ctrls, err := c.DescribeStandardsControls(ctx, &securityhub.DescribeStandardsControlsInput{
372 | StandardsSubscriptionArn: subscriptionArn,
373 | NextToken: nt,
374 | })
375 | if err != nil {
376 | return nil, err
377 | }
378 | for _, ctrl := range ctrls.Controls {
379 | cs.arns[*ctrl.ControlId] = ctrl.StandardsControlArn
380 | switch ctrl.ControlStatus {
381 | case types.ControlStatusEnabled:
382 | cs.Enable = append(cs.Enable, *ctrl.ControlId)
383 | case types.ControlStatusDisabled:
384 | reason := "No reason"
385 | if ctrl.DisabledReason != nil {
386 | reason = *ctrl.DisabledReason
387 | }
388 | cs.Disable = append(cs.Disable, yaml.MapItem{Key: *ctrl.ControlId, Value: reason})
389 | }
390 | }
391 | nt = ctrls.NextToken
392 | if ctrls.NextToken == nil {
393 | break
394 | }
395 | }
396 | return cs, nil
397 | }
398 |
399 | func intersect(a, b []string) []string {
400 | i := []string{}
401 | for _, e := range a {
402 | if contains(b, e) {
403 | i = append(i, e)
404 | }
405 | }
406 | return i
407 | }
408 |
409 | func intersectMapSlice(a, b yaml.MapSlice) yaml.MapSlice {
410 | i := yaml.MapSlice{}
411 | for _, e := range a {
412 | if containsMapSlice(b, e.Key.(string), e.Value.(string)) {
413 | i = append(i, e)
414 | }
415 | }
416 | return i
417 | }
418 |
419 | func diff(base, a []string) []string {
420 | i := []string{}
421 | for _, e := range a {
422 | if !contains(base, e) {
423 | i = append(i, e)
424 | }
425 | }
426 | return i
427 | }
428 |
429 | func diffMapSlice(base, a yaml.MapSlice) yaml.MapSlice {
430 | i := yaml.MapSlice{}
431 | for _, e := range a {
432 | if !containsMapSlice(base, e.Key.(string), e.Value.(string)) {
433 | i = append(i, e)
434 | }
435 | }
436 | return i
437 | }
438 |
439 | func contains(s []string, e string) bool {
440 | for _, v := range s {
441 | if e == v {
442 | return true
443 | }
444 | }
445 | return false
446 | }
447 |
448 | func containsMapSlice(s yaml.MapSlice, k, v string) bool {
449 | for _, ss := range s {
450 | if k == ss.Key.(string) && v == ss.Value.(string) {
451 | return true
452 | }
453 | }
454 | return false
455 | }
456 |
457 | func unique(in []string) []string {
458 | u := []string{}
459 | m := map[string]struct{}{}
460 | for _, s := range in {
461 | if _, ok := m[s]; ok {
462 | continue
463 | }
464 | u = append(u, s)
465 | m[s] = struct{}{}
466 | }
467 | return u
468 | }
469 |
470 | func uniqueMapSlice(in yaml.MapSlice) yaml.MapSlice {
471 | keys := []string{}
472 | m := map[string]yaml.MapItem{}
473 | for _, s := range in {
474 | if _, ok := m[s.Key.(string)]; !ok {
475 | keys = append(keys, s.Key.(string))
476 | }
477 | m[s.Key.(string)] = s
478 | }
479 | u := yaml.MapSlice{}
480 | for _, k := range keys {
481 | u = append(u, m[k])
482 | }
483 | return u
484 | }
485 |
486 | func contextcopy(in *SecHub) (*SecHub, error) {
487 | b, err := yaml.Marshal(in)
488 | if err != nil {
489 | return nil, err
490 | }
491 | out := &SecHub{}
492 | if err := yaml.UnmarshalWithOptions(b, out, yaml.DisallowDuplicateKey()); err != nil {
493 | return nil, err
494 | }
495 | out.Regions = nil
496 | return out, nil
497 | }
498 |
499 | func key(arn string) string {
500 | splitted := strings.SplitN(arn, "/", 2)
501 | return splitted[1]
502 | }
503 |
--------------------------------------------------------------------------------
/sechub/sechub_test.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "fmt"
5 | "testing"
6 |
7 | "github.com/aws/aws-sdk-go-v2/aws"
8 | "github.com/goccy/go-yaml"
9 | "github.com/google/go-cmp/cmp"
10 | "github.com/google/go-cmp/cmp/cmpopts"
11 | )
12 |
13 | func TestIntersect(t *testing.T) {
14 | tests := []struct {
15 | a *SecHub
16 | b *SecHub
17 | want *SecHub
18 | }{
19 | {
20 | &SecHub{},
21 | &SecHub{},
22 | &SecHub{
23 | AutoEnable: aws.Bool(true),
24 | Standards: Standards{},
25 | },
26 | },
27 | {
28 | &SecHub{AutoEnable: aws.Bool(false)},
29 | &SecHub{AutoEnable: aws.Bool(true)},
30 | &SecHub{
31 | AutoEnable: aws.Bool(true),
32 | Standards: Standards{},
33 | },
34 | },
35 | {
36 | &SecHub{AutoEnable: aws.Bool(false)},
37 | &SecHub{AutoEnable: aws.Bool(false)},
38 | &SecHub{
39 | AutoEnable: aws.Bool(false),
40 | Standards: Standards{},
41 | },
42 | },
43 | {
44 | &SecHub{Standards: Standards{
45 | &Standard{
46 | Key: "aws-foundational-security-best-practices/v/1.0.0",
47 | Enable: aws.Bool(true),
48 | enabledByDefault: true,
49 | },
50 | }},
51 | &SecHub{Standards: Standards{
52 | &Standard{
53 | Key: "aws-foundational-security-best-practices/v/1.0.0",
54 | Enable: aws.Bool(false),
55 | enabledByDefault: true,
56 | },
57 | }},
58 | &SecHub{
59 | AutoEnable: aws.Bool(true),
60 | Standards: Standards{
61 | &Standard{
62 | Key: "aws-foundational-security-best-practices/v/1.0.0",
63 | Enable: aws.Bool(true),
64 | enabledByDefault: true,
65 | Findings: FindingGroups{},
66 | },
67 | },
68 | },
69 | },
70 | {
71 | &SecHub{Standards: Standards{
72 | &Standard{
73 | Key: "aws-foundational-security-best-practices/v/1.0.0",
74 | enabledByDefault: true,
75 | Controls: &Controls{
76 | Enable: []string{"IAM.1", "IAM.2"},
77 | },
78 | },
79 | }},
80 | &SecHub{Standards: Standards{
81 | &Standard{
82 | Key: "aws-foundational-security-best-practices/v/1.0.0",
83 | enabledByDefault: true,
84 | Controls: &Controls{
85 | Enable: []string{"IAM.1", "IAM.3"},
86 | },
87 | },
88 | }},
89 | &SecHub{
90 | AutoEnable: aws.Bool(true),
91 | Standards: Standards{
92 | &Standard{
93 | Key: "aws-foundational-security-best-practices/v/1.0.0",
94 | Enable: aws.Bool(true),
95 | enabledByDefault: true,
96 | Controls: &Controls{
97 | Enable: []string{"IAM.1"},
98 | Disable: yaml.MapSlice{},
99 | },
100 | Findings: FindingGroups{},
101 | },
102 | },
103 | },
104 | },
105 | }
106 | for _, tt := range tests {
107 | got := Intersect(tt.a, tt.b)
108 | opt := cmpopts.IgnoreUnexported(SecHub{}, Standard{}, Controls{})
109 | if diff := cmp.Diff(got, tt.want, opt); diff != "" {
110 | t.Errorf("%s", diff)
111 | }
112 | }
113 | }
114 |
115 | func TestOverlay(t *testing.T) {
116 | tests := []struct {
117 | base *SecHub
118 | overlay *SecHub
119 | want *SecHub
120 | }{
121 | {
122 | &SecHub{},
123 | &SecHub{},
124 | &SecHub{},
125 | },
126 | {
127 | &SecHub{AutoEnable: aws.Bool(false)},
128 | &SecHub{AutoEnable: aws.Bool(true)},
129 | &SecHub{
130 | AutoEnable: aws.Bool(true),
131 | },
132 | },
133 | {
134 | &SecHub{Standards: Standards{
135 | &Standard{
136 | Key: "aws-foundational-security-best-practices/v/1.0.0",
137 | Enable: aws.Bool(true),
138 | Controls: &Controls{
139 | Enable: []string{"IAM.1", "IAM.2"},
140 | },
141 | },
142 | }},
143 | &SecHub{Standards: Standards{
144 | &Standard{
145 | Key: "aws-foundational-security-best-practices/v/1.0.0",
146 | Enable: aws.Bool(false),
147 | },
148 | }},
149 | &SecHub{Standards: Standards{
150 | &Standard{
151 | Key: "aws-foundational-security-best-practices/v/1.0.0",
152 | Enable: aws.Bool(false),
153 | Controls: &Controls{
154 | Enable: []string{"IAM.1", "IAM.2"},
155 | },
156 | },
157 | }},
158 | },
159 | {
160 | &SecHub{Standards: Standards{
161 | &Standard{
162 | Key: "aws-foundational-security-best-practices/v/1.0.0",
163 | Controls: &Controls{
164 | Enable: []string{"IAM.1", "IAM.2"},
165 | Disable: yaml.MapSlice{
166 | yaml.MapItem{Key: "Redshift.4", Value: "Redshit is not running."},
167 | yaml.MapItem{Key: "Redshift.6", Value: "Redshit is not running."},
168 | },
169 | },
170 | },
171 | }},
172 | &SecHub{Standards: Standards{
173 | &Standard{
174 | Key: "aws-foundational-security-best-practices/v/1.0.0",
175 | Controls: &Controls{
176 | Enable: []string{"IAM.1", "IAM.3", "Redshift.6"},
177 | Disable: yaml.MapSlice{
178 | yaml.MapItem{Key: "Redshift.7", Value: "Redshit is not running."},
179 | },
180 | },
181 | },
182 | }},
183 | &SecHub{Standards: Standards{
184 | &Standard{
185 | Key: "aws-foundational-security-best-practices/v/1.0.0",
186 | Controls: &Controls{
187 | Enable: []string{"IAM.1", "IAM.2", "IAM.3", "Redshift.6"},
188 | Disable: yaml.MapSlice{
189 | yaml.MapItem{Key: "Redshift.4", Value: "Redshit is not running."},
190 | yaml.MapItem{Key: "Redshift.7", Value: "Redshit is not running."},
191 | },
192 | },
193 | },
194 | }},
195 | },
196 | }
197 |
198 | for _, tt := range tests {
199 | tt.base.Overlay(tt.overlay)
200 | opt := cmpopts.IgnoreUnexported(SecHub{}, Standard{}, Controls{})
201 | if diff := cmp.Diff(tt.base, tt.want, opt); diff != "" {
202 | t.Errorf("%s", diff)
203 | }
204 | }
205 | }
206 |
207 | func TestDiff(t *testing.T) {
208 | tests := []struct {
209 | base *SecHub
210 | a *SecHub
211 | want *SecHub
212 | }{
213 | {
214 | &SecHub{Standards: Standards{
215 | &Standard{
216 | Key: "aws-foundational-security-best-practices/v/1.0.0",
217 | Enable: aws.Bool(true),
218 | Controls: &Controls{
219 | Enable: []string{"IAM.1", "IAM.2"},
220 | },
221 | },
222 | }},
223 | &SecHub{Standards: Standards{
224 | &Standard{
225 | Key: "aws-foundational-security-best-practices/v/1.0.0",
226 | Enable: aws.Bool(true),
227 | Controls: &Controls{
228 | Enable: []string{"IAM.1", "IAM.2", "IAM.3"},
229 | },
230 | },
231 | }},
232 | &SecHub{Standards: Standards{
233 | &Standard{
234 | Key: "aws-foundational-security-best-practices/v/1.0.0",
235 | Enable: nil,
236 | Controls: &Controls{
237 | Enable: []string{"IAM.3"},
238 | },
239 | Findings: FindingGroups{},
240 | },
241 | }},
242 | },
243 | {
244 | &SecHub{Standards: Standards{
245 | &Standard{
246 | Key: "aws-foundational-security-best-practices/v/1.0.0",
247 | Enable: aws.Bool(false),
248 | Controls: &Controls{},
249 | },
250 | &Standard{
251 | Key: "pci-dss/v/3.2.1",
252 | Enable: aws.Bool(false),
253 | Controls: &Controls{},
254 | },
255 | }},
256 | &SecHub{Standards: Standards{
257 | &Standard{
258 | Key: "aws-foundational-security-best-practices/v/1.0.0",
259 | Enable: aws.Bool(false),
260 | Controls: &Controls{},
261 | },
262 | &Standard{
263 | Key: "pci-dss/v/3.2.1",
264 | Enable: aws.Bool(true),
265 | Controls: &Controls{},
266 | },
267 | }},
268 | &SecHub{Standards: Standards{
269 | &Standard{
270 | Key: "pci-dss/v/3.2.1",
271 | Enable: aws.Bool(true),
272 | Controls: &Controls{},
273 | Findings: FindingGroups{},
274 | },
275 | }},
276 | },
277 | {
278 | &SecHub{Standards: Standards{
279 | &Standard{
280 | Key: "aws-foundational-security-best-practices/v/1.0.0",
281 | Enable: aws.Bool(false),
282 | Controls: &Controls{},
283 | },
284 | &Standard{
285 | Key: "pci-dss/v/3.2.1",
286 | Enable: aws.Bool(false),
287 | Controls: &Controls{},
288 | },
289 | }},
290 | &SecHub{Standards: Standards{
291 | &Standard{
292 | Key: "aws-foundational-security-best-practices/v/1.0.0",
293 | Enable: aws.Bool(false),
294 | Controls: &Controls{},
295 | },
296 | &Standard{
297 | Key: "pci-dss/v/3.2.1",
298 | Enable: aws.Bool(false),
299 | Controls: &Controls{},
300 | },
301 | }},
302 | nil,
303 | },
304 | {
305 | &SecHub{Standards: Standards{
306 | &Standard{
307 | Key: "aws-foundational-security-best-practices/v/1.0.0",
308 | Enable: aws.Bool(true),
309 | Controls: &Controls{
310 | Enable: []string{"IAM.1", "IAM.2"},
311 | Disable: yaml.MapSlice{
312 | yaml.MapItem{Key: "IAM.3", Value: "some reason"},
313 | },
314 | },
315 | },
316 | &Standard{
317 | Key: "cis-aws-foundations-benchmark/v/1.2.0",
318 | Enable: aws.Bool(true),
319 | Controls: &Controls{},
320 | },
321 | }},
322 | &SecHub{Standards: Standards{
323 | &Standard{
324 | Key: "aws-foundational-security-best-practices/v/1.0.0",
325 | Enable: aws.Bool(true),
326 | Controls: &Controls{
327 | Enable: []string{"IAM.1", "IAM.2"},
328 | Disable: yaml.MapSlice{
329 | yaml.MapItem{Key: "IAM.3", Value: "some reason"},
330 | },
331 | },
332 | },
333 | &Standard{
334 | Key: "cis-aws-foundations-benchmark/v/1.2.0",
335 | Enable: aws.Bool(true),
336 | Controls: &Controls{
337 | Disable: yaml.MapSlice{
338 | yaml.MapItem{Key: "CIS.1.1", Value: "some reason"},
339 | },
340 | },
341 | },
342 | }},
343 | &SecHub{Standards: Standards{
344 | &Standard{
345 | Key: "cis-aws-foundations-benchmark/v/1.2.0",
346 | Enable: nil,
347 | Controls: &Controls{
348 | Disable: yaml.MapSlice{
349 | yaml.MapItem{Key: "CIS.1.1", Value: "some reason"},
350 | },
351 | },
352 | Findings: FindingGroups{},
353 | },
354 | }},
355 | },
356 | }
357 |
358 | for i, tt := range tests {
359 | t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
360 | got, err := Diff(tt.base, tt.a)
361 | if err != nil {
362 | t.Error(err)
363 | }
364 | opt := cmpopts.IgnoreUnexported(SecHub{}, Standard{}, Controls{})
365 | if diff := cmp.Diff(got, tt.want, opt); diff != "" {
366 | t.Errorf("%s", diff)
367 | }
368 | })
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/sechub/testdata/change_header.golden:
--------------------------------------------------------------------------------
1 | {"blocks":[{"text":{"text":"Notification!!","type":"mrkdwn"},"type":"section"},{"text":{"text":"Notified because condition *'true'* is met.","type":"mrkdwn"},"type":"section"},{"fields":[{"text":"*CRITICAL:*\n1","type":"mrkdwn"},{"text":"*HIGH:*\n0","type":"mrkdwn"}],"type":"section"},{"fields":[{"text":"*MEDIUM:*\n0","type":"mrkdwn"},{"text":"*LOW:*\n0","type":"mrkdwn"}],"type":"section"},{"text":{"text":"","type":"mrkdwn"},"type":"section"}]}
2 |
--------------------------------------------------------------------------------
/sechub/testdata/change_message.golden:
--------------------------------------------------------------------------------
1 | {"blocks":[{"text":{"text":"*AWS Security Hub Notification*","type":"mrkdwn"},"type":"section"},{"text":{"text":"Notice!!","type":"mrkdwn"},"type":"section"},{"fields":[{"text":"*CRITICAL:*\n1","type":"mrkdwn"},{"text":"*HIGH:*\n0","type":"mrkdwn"}],"type":"section"},{"fields":[{"text":"*MEDIUM:*\n0","type":"mrkdwn"},{"text":"*LOW:*\n0","type":"mrkdwn"}],"type":"section"},{"text":{"text":"","type":"mrkdwn"},"type":"section"}]}
2 |
--------------------------------------------------------------------------------
/sechub/testdata/notify_critical.golden:
--------------------------------------------------------------------------------
1 | {"blocks":[{"text":{"text":"*AWS Security Hub Notification*","type":"mrkdwn"},"type":"section"},{"text":{"text":"Notified because condition *'critical > 0'* is met.","type":"mrkdwn"},"type":"section"},{"fields":[{"text":"*CRITICAL:*\n1","type":"mrkdwn"},{"text":"*HIGH:*\n0","type":"mrkdwn"}],"type":"section"},{"fields":[{"text":"*MEDIUM:*\n0","type":"mrkdwn"},{"text":"*LOW:*\n0","type":"mrkdwn"}],"type":"section"},{"text":{"text":"","type":"mrkdwn"},"type":"section"}]}
2 |
--------------------------------------------------------------------------------
/sechub/testdata/use_custom_template.golden:
--------------------------------------------------------------------------------
1 | {"critical":"CRITICAL: 1"}
2 |
--------------------------------------------------------------------------------
/sechub/testdata/use_default_template.golden:
--------------------------------------------------------------------------------
1 | {"blocks":[{"text":{"text":"*AWS Security Hub Notification*","type":"mrkdwn"},"type":"section"},{"text":{"text":"Notified because condition *'true'* is met.","type":"mrkdwn"},"type":"section"},{"fields":[{"text":"*CRITICAL:*\n1","type":"mrkdwn"},{"text":"*HIGH:*\n0","type":"mrkdwn"}],"type":"section"},{"fields":[{"text":"*MEDIUM:*\n0","type":"mrkdwn"},{"text":"*LOW:*\n0","type":"mrkdwn"}],"type":"section"},{"text":{"text":"","type":"mrkdwn"},"type":"section"}]}
2 |
--------------------------------------------------------------------------------
/sechub/yaml.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "fmt"
5 |
6 | "github.com/goccy/go-yaml"
7 | )
8 |
9 | func (s *SecHub) MarshalYAML() ([]byte, error) {
10 | stds := yaml.MapSlice{}
11 | for _, std := range s.Standards {
12 | k := std.Key
13 | fgs := yaml.MapSlice{}
14 | for _, fg := range std.Findings {
15 | rs := yaml.MapSlice{}
16 | for _, r := range fg.Resources {
17 | rs = append(rs, yaml.MapItem{
18 | Key: r.Arn,
19 | Value: yaml.MapSlice{
20 | yaml.MapItem{Key: "status", Value: r.Status},
21 | yaml.MapItem{Key: "note", Value: r.Note},
22 | },
23 | })
24 | }
25 | fgs = append(fgs, yaml.MapItem{
26 | Key: fg.ControlID,
27 | Value: rs,
28 | })
29 | }
30 | v := &StandardForYAML{
31 | Enable: std.Enable,
32 | Controls: std.Controls,
33 | Findings: fgs,
34 | }
35 |
36 | stds = append(stds, yaml.MapItem{
37 | Key: k,
38 | Value: v,
39 | })
40 | }
41 |
42 | // regions := yaml.MapSlice{}
43 | // for _, hub := range s.Regions {
44 | // k := hub.region
45 | // v := hub
46 | // regions = append(regions, yaml.MapItem{
47 | // Key: k,
48 | // Value: v,
49 | // })
50 | // }
51 |
52 | regions := map[string]*SecHub{}
53 | for _, hub := range s.Regions {
54 | k := hub.region
55 | regions[k] = hub
56 | }
57 |
58 | s2 := struct {
59 | AutoEnable *bool `yaml:"autoEnable,omitempty"`
60 | Standards yaml.MapSlice `yaml:"standards,omitempty"`
61 | Regions map[string]*SecHub `yaml:"regions,omitempty"`
62 | }{
63 | AutoEnable: s.AutoEnable,
64 | Standards: stds,
65 | Regions: regions,
66 | }
67 | return yaml.Marshal(s2)
68 | }
69 |
70 | type SecHubForUnmarshal struct {
71 | AutoEnable *bool `yaml:"autoEnable,omitempty"`
72 | Standards map[string]*Standard `yaml:"standards,omitempty"`
73 | Regions map[string]*SecHubForUnmarshal `yaml:"regions,omitempty"`
74 | Notifications Notifications `yaml:"notifications,omitempty"`
75 | }
76 |
77 | func (s *SecHub) UnmarshalYAML(b []byte) error {
78 | tmp := &SecHubForUnmarshal{}
79 | if err := yaml.Unmarshal(b, tmp); err != nil {
80 | return err
81 | }
82 | s.AutoEnable = tmp.AutoEnable
83 | for k, std := range tmp.Standards {
84 | if std.Controls == nil {
85 | std.Controls = &Controls{}
86 | }
87 | std.Key = k
88 | s.Standards = append(s.Standards, std)
89 | }
90 | for r, tmphub := range tmp.Regions {
91 | hub := New(r)
92 | hub.AutoEnable = tmphub.AutoEnable
93 | for k, std := range tmphub.Standards {
94 | if std.Controls == nil {
95 | std.Controls = &Controls{}
96 | }
97 | std.Key = k
98 | hub.Standards = append(hub.Standards, std)
99 | }
100 | s.Regions = append(s.Regions, hub)
101 | }
102 | s.Notifications = tmp.Notifications
103 | return nil
104 | }
105 |
106 | type StandardForYAML struct {
107 | Enable *bool `yaml:"enable,omitempty"`
108 | Controls *Controls `yaml:"controls,omitempty"`
109 | Findings yaml.MapSlice `yaml:"findings,omitempty"`
110 | }
111 |
112 | type StandardForUnmarshal struct {
113 | Key string `yaml:"key,omitempty"`
114 | Enable *bool `yaml:"enable,omitempty"`
115 | Controls *Controls `yaml:"controls,omitempty"`
116 | Findings yaml.MapSlice `yaml:"findings,omitempty"`
117 | }
118 |
119 | func (s *Standard) UnmarshalYAML(b []byte) error {
120 | tmp := &StandardForUnmarshal{}
121 | if err := yaml.UnmarshalWithOptions(b, tmp, yaml.UseOrderedMap()); err != nil {
122 | return err
123 | }
124 | s.Key = tmp.Key
125 | s.Enable = tmp.Enable
126 | s.Controls = tmp.Controls
127 | for _, f := range tmp.Findings {
128 | fg := &FindingGroup{
129 | ControlID: f.Key.(string),
130 | }
131 | r, ok := f.Value.(yaml.MapSlice)
132 | if !ok {
133 | return fmt.Errorf("invalid format: %v", string(b))
134 | }
135 | for _, kv := range r {
136 | fr := &FindingResource{
137 | Arn: kv.Key.(string),
138 | }
139 | rr, ok := kv.Value.(yaml.MapSlice)
140 | if !ok {
141 | return fmt.Errorf("invalid format: %v", string(b))
142 | }
143 | for _, kkv := range rr {
144 | switch kkv.Key.(string) {
145 | case "status":
146 | fr.Status = kkv.Value.(string)
147 | case "note":
148 | fr.Note = kkv.Value.(string)
149 | }
150 | }
151 | fg.Resources = append(fg.Resources, fr)
152 | }
153 | s.Findings = append(s.Findings, fg)
154 | }
155 |
156 | return nil
157 | }
158 |
159 | func (c *Controls) UnmarshalYAML(b []byte) error {
160 | s := struct {
161 | Enable []string `yaml:"enable,flow,omitempty"`
162 | Disable yaml.MapSlice `yaml:"disable,omitempty"`
163 | }{}
164 | if err := yaml.Unmarshal(b, &s); err == nil {
165 | c.Enable = s.Enable
166 | c.Disable = s.Disable
167 | return nil
168 | }
169 |
170 | // fallback as slice
171 | s2 := struct {
172 | Enable []string `yaml:"enable,flow,omitempty"`
173 | Disable []string `yaml:"disable,flow,omitempty"`
174 | }{}
175 | if err := yaml.Unmarshal(b, &s2); err != nil {
176 | return err
177 | }
178 |
179 | c.Enable = s2.Enable
180 | for _, d := range s2.Disable {
181 | c.Disable = append(c.Disable, yaml.MapItem{
182 | Key: d,
183 | Value: "",
184 | })
185 | }
186 | return nil
187 | }
188 |
--------------------------------------------------------------------------------
/sechub/yaml_test.go:
--------------------------------------------------------------------------------
1 | package sechub
2 |
3 | import (
4 | "testing"
5 |
6 | "github.com/aws/aws-sdk-go-v2/aws"
7 | "github.com/goccy/go-yaml"
8 | "github.com/google/go-cmp/cmp"
9 | "github.com/google/go-cmp/cmp/cmpopts"
10 | )
11 |
12 | func TestYAML(t *testing.T) {
13 | tests := []struct {
14 | in *SecHub
15 | want string
16 | }{
17 | {
18 | &SecHub{
19 | AutoEnable: aws.Bool(true),
20 | Standards: []*Standard{
21 | &Standard{
22 | Key: "cis-aws-foundations-benchmark/v/1.2.0",
23 | Enable: aws.Bool(true),
24 | Controls: &Controls{
25 | Enable: []string{"CIS.1.1", "CIS.1.2"},
26 | },
27 | },
28 | },
29 | },
30 | `autoEnable: true
31 | standards:
32 | cis-aws-foundations-benchmark/v/1.2.0:
33 | enable: true
34 | controls:
35 | enable: [CIS.1.1, CIS.1.2]
36 | `},
37 | }
38 | for _, tt := range tests {
39 | b, err := yaml.Marshal(tt.in)
40 | if err != nil {
41 | t.Fatal(err)
42 | }
43 | got := string(b)
44 | if got != tt.want {
45 | t.Errorf("got %v\nwant %v", got, tt.want)
46 | }
47 |
48 | hub := &SecHub{}
49 | if err := yaml.Unmarshal(b, hub); err != nil {
50 | t.Fatal(err)
51 | }
52 |
53 | opt := cmpopts.IgnoreUnexported(SecHub{}, Standard{}, Controls{})
54 | if diff := cmp.Diff(hub, tt.in, opt); diff != "" {
55 | t.Errorf("%s", diff)
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/version/version.go:
--------------------------------------------------------------------------------
1 | package version
2 |
3 | // Name for this
4 | const Name string = "control-controls"
5 |
6 | // Version for this
7 | var Version = "0.8.4"
8 |
--------------------------------------------------------------------------------