pkg/config/config.go
package config
import (
"fmt"
"os"
"strings"
"gopkg.in/yaml.v3"
"github.com/ekristen/libnuke/pkg/config"
"github.com/ekristen/libnuke/pkg/settings"
)
// New creates a new extended configuration from a file. This is necessary because we are extended the default
// libnuke configuration to contain additional attributes that are specific to the AWS Nuke tool.
func New(opts config.Options) (*Config, error) {
// Step 1 - Create the libnuke config
cfg, err := config.New(opts)
if err != nil {
return nil, err
}
// Step 2 - Instantiate the extended config
c := &Config{
CustomEndpoints: make(CustomEndpoints, 0),
}
// Step 3 - Load the same config file against the extended config
// Intentionally ignored, this will never error because we already validated the file exists
_ = c.Load(opts.Path)
// Step 4 - Set the libnuke config on the extended config
c.Config = cfg
// Step 5 - Resolve any deprecated feature flags
c.ResolveDeprecatedFeatureFlags()
return c, nil
}
// Config is an extended configuration implementation that adds some additional features on top of the libnuke config.
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.
BypassAliasCheckAccounts []string `yaml:"bypass-alias-check-accounts"`
// FeatureFlags is a collection of feature flags that can be used to enable or disable certain behaviors on
// resources. This is left over from the AWS Nuke tool and is deprecated. It was left to make the transition to the
// library and ekristen/aws-nuke@v3 easier for existing users.
// Deprecated: Use Settings instead. Will be removed in 4.x
FeatureFlags *FeatureFlags `yaml:"feature-flags"`
// CustomEndpoints is a collection of custom endpoints that can be used to override the default AWS endpoints.
CustomEndpoints CustomEndpoints `yaml:"endpoints"`
}
// Load loads a configuration from a file and parses it into a Config struct.
func (c *Config) Load(path string) error {
var err error
raw, err := os.ReadFile(path)
if err != nil {
return err
}
if err := yaml.Unmarshal(raw, c); err != nil {
return err
}
if !c.NoBlocklistTermsDefault {
c.BlocklistTerms = append(c.BlocklistTerms, "prod")
}
return nil
}
// InBypassAliasCheckAccounts returns true if the specified account ID is in the bypass alias check accounts list.
func (c *Config) InBypassAliasCheckAccounts(accountID string) bool {
for _, id := range c.BypassAliasCheckAccounts {
if id == accountID {
return true
}
}
return false
}
// ValidateAccount validates the account ID and aliases for the specified account. This will return an error if the
// account ID is invalid, the account ID is blocklisted, the account doesn't have an alias, the account alias contains
// the substring 'prod', or the account ID isn't listed in the config.
func (c *Config) ValidateAccount(accountID string, aliases []string, skipAliasChecks bool) error {
// Call the libnuke config validation first
if err := c.Config.ValidateAccount(accountID); err != nil {
return err
}
if skipAliasChecks {
if c.InBypassAliasCheckAccounts(accountID) {
return nil
}
c.Log.Warnf("--no-alias-check is set, but the account ID '%s' isn't in the bypass list.", accountID)
}
if len(aliases) == 0 {
return fmt.Errorf("specified account doesn't have an alias. " +
"For safety reasons you need to specify an account alias. " +
"Your production account should contain the term 'prod'")
}
for _, alias := range aliases {
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)
}
}
}
return nil
}
// ResolveDeprecatedFeatureFlags resolves any deprecated feature flags in the configuration. This converts the legacy
// feature flags into the new settings format. The feature flags will be deprecated with version 4.x. This was left in
// place to make the transition to the libnuke library and ekristen/aws-nuke@v3 easier for existing users.
func (c *Config) ResolveDeprecatedFeatureFlags() {
if c.FeatureFlags != nil {
c.Log.Warn("deprecated configuration key 'feature-flags' - please use 'settings' instead")
if c.FeatureFlags.ForceDeleteLightsailAddOns {
c.Settings.Set("LightsailInstance", &settings.Setting{
"ForceDeleteAddOns": true,
})
}
if c.FeatureFlags.DisableEC2InstanceStopProtection {
c.Settings.Set("EC2Instance", &settings.Setting{
"DisableStopProtection": true,
})
}
if c.FeatureFlags.DisableDeletionProtection.EC2Instance {
c.Settings.Set("EC2Instance", &settings.Setting{
"DisableDeletionProtection": true,
})
}
if c.FeatureFlags.DisableDeletionProtection.RDSInstance {
c.Settings.Set("RDSInstance", &settings.Setting{
"DisableDeletionProtection": true,
})
}
if c.FeatureFlags.DisableDeletionProtection.ELBv2 {
c.Settings.Set("ELBv2", &settings.Setting{
"DisableDeletionProtection": true,
})
}
if c.FeatureFlags.DisableDeletionProtection.CloudformationStack {
c.Settings.Set("CloudFormationStack", &settings.Setting{
"DisableDeletionProtection": true,
})
}
if c.FeatureFlags.DisableDeletionProtection.QLDBLedger {
c.Settings.Set("QLDBLedger", &settings.Setting{
"DisableDeletionProtection": true,
})
}
}
}
// FeatureFlags is a collection of feature flags that can be used to enable or disable certain features of the nuke
// This is left over from the AWS Nuke tool and is deprecated. It was left to make the transition to the library and
// ekristen/aws-nuke@v3 easier for existing users.
// Deprecated: Use Settings instead. Will be removed in 4.x
type FeatureFlags struct {
DisableDeletionProtection DisableDeletionProtection `yaml:"disable-deletion-protection"`
DisableEC2InstanceStopProtection bool `yaml:"disable-ec2-instance-stop-protection"`
ForceDeleteLightsailAddOns bool `yaml:"force-delete-lightsail-addons"`
}
// DisableDeletionProtection is a collection of feature flags that can be used to disable deletion protection for
// certain resource types. This is left over from the AWS Nuke tool and is deprecated. It was left to make transition
// to the library and ekristen/aws-nuke@v3 easier for existing users.
// Deprecated: Use Settings instead. Will be removed in 4.x
type DisableDeletionProtection struct {
RDSInstance bool `yaml:"RDSInstance"`
EC2Instance bool `yaml:"EC2Instance"`
CloudformationStack bool `yaml:"CloudformationStack"`
ELBv2 bool `yaml:"ELBv2"`
QLDBLedger bool `yaml:"QLDBLedger"`
}
// CustomService is a custom service endpoint that can be used to override the default AWS endpoints.
type CustomService struct {
Service string `yaml:"service"`
URL string `yaml:"url"`
TLSInsecureSkipVerify bool `yaml:"tls_insecure_skip_verify"`
}
// CustomServices is a collection of custom service endpoints that can be used to override the default AWS endpoints.
type CustomServices []*CustomService
// CustomRegion is a custom region endpoint that can be used to override the default AWS regions
type CustomRegion struct {
Region string `yaml:"region"`
Services CustomServices `yaml:"services"`
TLSInsecureSkipVerify bool `yaml:"tls_insecure_skip_verify"`
}
// CustomEndpoints is a collection of custom region endpoints that can be used to override the default AWS regions
type CustomEndpoints []*CustomRegion
// GetRegion returns the custom region or nil when no such custom endpoints are defined for this region
func (endpoints CustomEndpoints) GetRegion(region string) *CustomRegion {
for _, r := range endpoints {
if r.Region == region {
if r.TLSInsecureSkipVerify {
for _, s := range r.Services {
s.TLSInsecureSkipVerify = r.TLSInsecureSkipVerify
}
}
return r
}
}
return nil
}
// GetService returns the custom region or nil when no such custom endpoints are defined for this region
func (services CustomServices) GetService(serviceType string) *CustomService {
for _, s := range services {
if serviceType == s.Service {
return s
}
}
return nil
}
// GetURL returns the custom region or nil when no such custom endpoints are defined for this region
func (endpoints CustomEndpoints) GetURL(region, serviceType string) string {
r := endpoints.GetRegion(region)
if r == nil {
return ""
}
s := r.Services.GetService(serviceType)
if s == nil {
return ""
}
return s.URL
}