Skip to content

Commit

Permalink
Merge pull request #299 from ekristen/custom-block-keywords
Browse files Browse the repository at this point in the history
feat(config): custom alias keyword blocklist and disable default keywords
  • Loading branch information
ekristen authored Sep 26, 2024
2 parents d76d6ef + f5305ac commit d55389e
Show file tree
Hide file tree
Showing 5 changed files with 240 additions and 10 deletions.
61 changes: 59 additions & 2 deletions docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ The configuration is the user supplied configuration that is used to drive the n
The configuration is broken down into the following sections:

- [blocklist](#blocklist)
- [blocklist-terms](#blocklist-terms)
- [no-blocklist-terms-default](#no-blocklist-terms-default)
- [regions](#regions)
- [accounts](#accounts)
- [presets](#presets)
Expand All @@ -30,6 +32,13 @@ The configuration is broken down into the following sections:
```yaml
blocklist:
- 1234567890

blocklist-terms:
- "prod"
- "production"
- "live"

no-blocklist-terms-default: false # default value

regions:
- global
Expand Down Expand Up @@ -61,15 +70,58 @@ settings:
## Blocklist
The blocklist is simply a list of Accounts that the tool cannot run against. This is to protect the user from accidentally
The `blocklist` is simply a list of Accounts that the tool cannot run against. This is to protect the user from accidentally
running the tool against the wrong account. The blocklist must always be populated with at least one entry.

```yaml
blocklist:
- 1234567890
```

## Blocklist Terms

`blocklist-terms` is a list of terms that the tool will use to block accounts based on their aliases. If an account
alias contains any of the terms in the list, then the account will be blocked. However, if the bypass alias check flag
is set, then this feature has no affect.

```yaml
blocklist-terms:
- "prod"
- "production"
- "live"
```

## No Blocklist Terms Default

`no-blocklist-terms-default` is a boolean value that determines the default behavior of the blocklist. If set to true,
then the blocklist will be empty by default. If set to false, then the blocklist will be populated by default.

### Usage

**Default Value:** `false`

```yaml
no-blocklist-terms-default: true
```

### Default Terms

```yaml
- prod
```

## Regions

The regions is a list of AWS regions that the tool will run against. The tool will run against all regions specified in the
The `regions` is a list of AWS regions that the tool will run against. The tool will run against all regions specified in the
configuration. If no regions are listed, then the tool will **NOT** run against any region. Regions must be explicitly
provided.

```yaml
regions:
- global
- us-east-1
```

### All Enabled Regions

You may specify the special region `all` to run against all enabled regions. This will run against all regions that are
Expand All @@ -80,6 +132,11 @@ special region `global` which is for specific global resources.
The use of `all` will ignore all other regions specified in the configuration. It will only run against regions
that are enabled in the account.

```yaml
regions:
- all
```

## Accounts

The accounts section is a map of AWS Account IDs to their configuration. The account ID is the key and the value is the
Expand Down
20 changes: 17 additions & 3 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ type Config struct {
// Config is the underlying libnuke configuration.
*config.Config `yaml:",inline"`

// BlocklistTerms is a list of keywords that are blocklisted from being used in an alias.
// If any of these keywords are found in an alias, the nuke will abort.
BlocklistTerms []string `yaml:"blocklist-terms"`

// NoBlocklistTermsDefault is a setting that can be used to disable the default terms from being added to the
// blocklist.
NoBlocklistTermsDefault bool `yaml:"no-blocklist-terms-default"`

// BypassAliasCheckAccounts is a list of account IDs that will be allowed to bypass the alias check.
// This is useful for accounts that don't have an alias for a number of reasons, it must be used with a cli
// flag --no-alias-check to be effective.
Expand Down Expand Up @@ -71,6 +79,10 @@ func (c *Config) Load(path string) error {
return err
}

if !c.NoBlocklistTermsDefault {
c.BlocklistTerms = append(c.BlocklistTerms, "prod")
}

return nil
}

Expand Down Expand Up @@ -109,9 +121,11 @@ func (c *Config) ValidateAccount(accountID string, aliases []string, skipAliasCh
}

for _, alias := range aliases {
if strings.Contains(strings.ToLower(alias), "prod") {
return fmt.Errorf("you are trying to nuke an account with the alias '%s', "+
"but it has the substring 'prod' in it. Aborting", alias)
for _, keyword := range c.BlocklistTerms {
if strings.Contains(strings.ToLower(alias), keyword) {
return fmt.Errorf("you are trying to nuke an account with the alias '%s', "+
"but it contains the blocklisted keyword '%s'. Aborting", alias, keyword)
}
}
}

Expand Down
123 changes: 121 additions & 2 deletions pkg/config/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ func TestConfig_LoadExample(t *testing.T) {
},
},
ResourceTypes: libconfig.ResourceTypes{
Targets: types.Collection{"S3Bucket"},
Includes: types.Collection{"S3Bucket"},
},
},
},
ResourceTypes: libconfig.ResourceTypes{
Targets: types.Collection{"DynamoDBTable", "S3Bucket", "S3Object"},
Includes: types.Collection{"DynamoDBTable", "S3Bucket", "S3Object"},
Excludes: types.Collection{"IAMRole"},
},
Presets: map[string]libconfig.Preset{
Expand Down Expand Up @@ -111,6 +111,69 @@ func TestConfig_LoadExample(t *testing.T) {
},
},
},
BlocklistTerms: []string{"prod"},
}

assert.Equal(t, expect, *config)
}

func TestConfig_NoBlocklistTermsProd(t *testing.T) {
logger := logrus.New()
logger.SetOutput(io.Discard)
entry := logrus.WithField("test", true)

config, err := New(libconfig.Options{
Path: "testdata/no-blocklist-term-prod.yaml",
Log: entry,
})
if err != nil {
t.Fatal(err)
}

expect := Config{
Config: &libconfig.Config{
Blocklist: []string{"012345678901"},
Regions: []string{"global", "us-east-1"},
Accounts: map[string]*libconfig.Account{
"555133742": {
Presets: []string{"terraform"},
Filters: filter.Filters{
"IAMRole": {
filter.NewExactFilter("uber.admin"),
},
"IAMRolePolicyAttachment": {
filter.NewExactFilter("uber.admin -> AdministratorAccess"),
},
},
ResourceTypes: libconfig.ResourceTypes{
Includes: types.Collection{"S3Bucket"},
},
},
},
ResourceTypes: libconfig.ResourceTypes{
Includes: types.Collection{"DynamoDBTable", "S3Bucket", "S3Object"},
Excludes: types.Collection{"IAMRole"},
},
Presets: map[string]libconfig.Preset{
"terraform": {
Filters: filter.Filters{
"S3Bucket": {
filter.Filter{
Type: filter.Glob,
Value: "my-statebucket-*",
Values: []string{},
},
},
},
},
},
Settings: &settings.Settings{},
Deprecations: make(map[string]string),
Log: entry,
},
CustomEndpoints: CustomEndpoints{},
BlocklistTerms: []string{"alpha"},
NoBlocklistTermsDefault: true,
}

assert.Equal(t, expect, *config)
Expand Down Expand Up @@ -375,3 +438,59 @@ func TestConfig_DeprecatedFeatureFlags(t *testing.T) {
assert.NotNil(t, cloudformationStackSettings)
assert.Equal(t, true, cloudformationStackSettings.Get("DisableDeletionProtection"))
}

func TestConfig_ValidateAccount_Blocklist(t *testing.T) {
config, err := New(libconfig.Options{
Path: "testdata/example.yaml",
})
if err != nil {
t.Fatal(err)
}

// Add an account to the blocklist
config.Blocklist = append(config.Blocklist, "1234567890")
config.BlocklistTerms = append(config.BlocklistTerms, "alpha-tango")

// Test cases
cases := []struct {
ID string
Aliases []string
ShouldFail bool
}{
{
// Should fail due to blocklist
ID: "1234567890",
Aliases: []string{
"sandbox",
},
ShouldFail: true,
},
{
// Allowed account
ID: "555133742",
Aliases: []string{
"sandbox2",
},
ShouldFail: false,
},
{
// Allowed account but blocked by keyword
ID: "555133742",
Aliases: []string{
"alpha-tango-sandbox",
},
ShouldFail: true,
},
}

for _, tc := range cases {
t.Run(fmt.Sprintf("AccountID_%s", tc.ID), func(t *testing.T) {
err := config.ValidateAccount(tc.ID, tc.Aliases, false)
if tc.ShouldFail {
assert.Error(t, err)
} else {
assert.NoError(t, err)
}
})
}
}
6 changes: 3 additions & 3 deletions pkg/config/testdata/example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ regions:
- "eu-west-1"
- stratoscale

account-blocklist:
blocklist:
- 1234567890

endpoints:
Expand All @@ -17,7 +17,7 @@ endpoints:
tls_insecure_skip_verify: true

resource-types:
targets:
includes:
- DynamoDBTable
- S3Bucket
- S3Object
Expand All @@ -29,7 +29,7 @@ accounts:
presets:
- "terraform"
resource-types:
targets:
includes:
- S3Bucket
filters:
IAMRole:
Expand Down
40 changes: 40 additions & 0 deletions pkg/config/testdata/no-blocklist-term-prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
---
regions:
- global
- us-east-1

blocklist:
- 012345678901

blocklist-terms:
- alpha

no-blocklist-terms-default: true

resource-types:
includes:
- DynamoDBTable
- S3Bucket
- S3Object
excludes:
- IAMRole

accounts:
555133742:
presets:
- "terraform"
resource-types:
includes:
- S3Bucket
filters:
IAMRole:
- "uber.admin"
IAMRolePolicyAttachment:
- "uber.admin -> AdministratorAccess"

presets:
terraform:
filters:
S3Bucket:
- type: glob
value: "my-statebucket-*"

0 comments on commit d55389e

Please sign in to comment.