Skip to content

Commit

Permalink
feat(terraform): add support for AWS provider block (#50)
Browse files Browse the repository at this point in the history
* feat(terraform): add support for AWS provider block

* chore: bump defsec
  • Loading branch information
nikpivkin authored Nov 23, 2023
1 parent 0cb0329 commit dafbd42
Show file tree
Hide file tree
Showing 8 changed files with 400 additions and 18 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ require (
github.com/BurntSushi/toml v1.3.2
github.com/Masterminds/semver v1.5.0
github.com/apparentlymart/go-cidr v1.1.0
github.com/aquasecurity/defsec v0.93.2-0.20231117234854-a13ada52a90f
github.com/aquasecurity/defsec v0.93.2-0.20231120220217-6818261529c8
github.com/aquasecurity/trivy-policies v0.6.1-0.20231120231532-f6f2330bf842
github.com/aws/smithy-go v1.14.2
github.com/bmatcuk/doublestar/v4 v4.6.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -238,8 +238,8 @@ github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
github.com/aquasecurity/defsec v0.93.2-0.20231117234854-a13ada52a90f h1:cO9S78J2eBx9tEIZYwFoousuYWV4DtgQlGsZUusMyNY=
github.com/aquasecurity/defsec v0.93.2-0.20231117234854-a13ada52a90f/go.mod h1:J30VViSgmoW2Ic/6aqVJO2qvuADsmZ3MYuNxPcU6Vt0=
github.com/aquasecurity/defsec v0.93.2-0.20231120220217-6818261529c8 h1:w/Sm2fVtb0Rv1bcLLwsW9j37mNUya8MwzKMcjG9OW/Q=
github.com/aquasecurity/defsec v0.93.2-0.20231120220217-6818261529c8/go.mod h1:J30VViSgmoW2Ic/6aqVJO2qvuADsmZ3MYuNxPcU6Vt0=
github.com/aquasecurity/trivy-policies v0.6.1-0.20231120231532-f6f2330bf842 h1:RnxM3eTcwPlA/WBwnmaEpeEk3WOCDcnz7yTIFxVL7us=
github.com/aquasecurity/trivy-policies v0.6.1-0.20231120231532-f6f2330bf842/go.mod h1:BmEeSFgmBjo3avCli71736sy0veGcSUzGATupp1MCgA=
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
Expand Down
4 changes: 4 additions & 0 deletions internal/adapters/terraform/aws/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/mq"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/msk"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/neptune"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/provider"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/rds"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/redshift"
"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/aws/s3"
Expand All @@ -39,6 +40,9 @@ import (

func Adapt(modules terraform.Modules) aws.AWS {
return aws.AWS{
Meta: aws.Meta{
TFProviders: provider.Adapt(modules),
},
APIGateway: apigateway.Adapt(modules),
Athena: athena.Adapt(modules),
Cloudfront: cloudfront.Adapt(modules),
Expand Down
166 changes: 166 additions & 0 deletions internal/adapters/terraform/aws/provider/adapt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package provider

import (
"github.com/aquasecurity/defsec/pkg/providers/aws"
"github.com/aquasecurity/defsec/pkg/terraform"
"github.com/aquasecurity/defsec/pkg/types"
)

const (
defaultMaxRetires = 25
defaultSharedConfigFile = "~/.aws/config"
//#nosec G101 -- False positive
defaultSharedCredentialsFile = "~/.aws/credentials"
)

func Adapt(modules terraform.Modules) []aws.TerraformProvider {
return adaptProviders(modules)
}

func adaptProviders(modules terraform.Modules) []aws.TerraformProvider {
var providers []aws.TerraformProvider
for _, providerBlock := range modules.GetBlocks().OfType("provider") {
if providerBlock.Label() == "aws" {
providers = append(providers, adaptProvider(providerBlock))
}
}

return providers
}

func adaptProvider(b *terraform.Block) aws.TerraformProvider {
return aws.TerraformProvider{
Metadata: b.GetMetadata(),
Alias: getStringAttrValue("alias", b),
Version: getStringAttrValue("version", b),
AccessKey: getStringAttrValue("access_key", b),
AllowedAccountsIDs: b.GetAttribute("allowed_account_ids").AsStringValueSliceOrEmpty(),
AssumeRole: adaptAssumeRole(b),
AssumeRoleWithWebIdentity: adaptAssumeRoleWithWebIdentity(b),
CustomCABundle: getStringAttrValue("custom_ca_bundle", b),
DefaultTags: adaptDefaultTags(b),
EC2MetadataServiceEndpoint: getStringAttrValue("ec2_metadata_service_endpoint", b),
EC2MetadataServiceEndpointMode: getStringAttrValue("ec2_metadata_service_endpoint_mode", b),
Endpoints: adaptEndpoints(b),
ForbiddenAccountIDs: b.GetAttribute("forbidden_account_ids").AsStringValueSliceOrEmpty(),
HttpProxy: getStringAttrValue("http_proxy", b),
IgnoreTags: adaptIgnoreTags(b),
Insecure: b.GetAttribute("insecure").AsBoolValueOrDefault(false, b),
MaxRetries: b.GetAttribute("max_retries").AsIntValueOrDefault(defaultMaxRetires, b),
Profile: getStringAttrValue("profile", b),
Region: getStringAttrValue("region", b),
RetryMode: getStringAttrValue("retry_mode", b),
S3UsePathStyle: b.GetAttribute("s3_use_path_style").AsBoolValueOrDefault(false, b),
S3USEast1RegionalEndpoint: getStringAttrValue("s3_us_east_1_regional_endpoint", b),
SecretKey: getStringAttrValue("secret_key", b),
SharedConfigFiles: b.GetAttribute("shared_config_files").AsStringValuesOrDefault(b, defaultSharedConfigFile),
SharedCredentialsFiles: b.GetAttribute("shared_credentials_files").AsStringValuesOrDefault(b, defaultSharedCredentialsFile),
SkipCredentialsValidation: b.GetAttribute("skip_credentials_validation").AsBoolValueOrDefault(false, b),
SkipMetadataAPICheck: b.GetAttribute("skip_metadata_api_check").AsBoolValueOrDefault(false, b),
SkipRegionValidation: b.GetAttribute("skip_region_validation").AsBoolValueOrDefault(false, b),
SkipRequestingAccountID: b.GetAttribute("skip_requesting_account_id").AsBoolValueOrDefault(false, b),
STSRegion: getStringAttrValue("sts_region", b),
Token: getStringAttrValue("token", b),
UseDualstackEndpoint: b.GetAttribute("use_dualstack_endpoint").AsBoolValueOrDefault(false, b),
UseFIPSEndpoint: b.GetAttribute("use_fips_endpoint").AsBoolValueOrDefault(false, b),
}
}

func adaptAssumeRole(p *terraform.Block) aws.AssumeRole {
assumeRoleBlock := p.GetBlock("assume_role")

if assumeRoleBlock.IsNil() {
return aws.AssumeRole{
Metadata: p.GetMetadata(),
Duration: types.StringDefault("", p.GetMetadata()),
ExternalID: types.StringDefault("", p.GetMetadata()),
Policy: types.StringDefault("", p.GetMetadata()),
RoleARN: types.StringDefault("", p.GetMetadata()),
SessionName: types.StringDefault("", p.GetMetadata()),
SourceIdentity: types.StringDefault("", p.GetMetadata()),
}
}

return aws.AssumeRole{
Metadata: assumeRoleBlock.GetMetadata(),
Duration: getStringAttrValue("duration", p),
ExternalID: getStringAttrValue("external_id", p),
Policy: getStringAttrValue("policy", p),
PolicyARNs: p.GetAttribute("policy_arns").AsStringValueSliceOrEmpty(),
RoleARN: getStringAttrValue("role_arn", p),
SessionName: getStringAttrValue("session_name", p),
SourceIdentity: getStringAttrValue("source_identity", p),
Tags: p.GetAttribute("tags").AsMapValue(),
TransitiveTagKeys: p.GetAttribute("transitive_tag_keys").AsStringValueSliceOrEmpty(),
}
}

func adaptAssumeRoleWithWebIdentity(p *terraform.Block) aws.AssumeRoleWithWebIdentity {
block := p.GetBlock("assume_role_with_web_identity")
if block.IsNil() {
return aws.AssumeRoleWithWebIdentity{
Metadata: p.GetMetadata(),
Duration: types.StringDefault("", p.GetMetadata()),
Policy: types.StringDefault("", p.GetMetadata()),
RoleARN: types.StringDefault("", p.GetMetadata()),
SessionName: types.StringDefault("", p.GetMetadata()),
WebIdentityToken: types.StringDefault("", p.GetMetadata()),
WebIdentityTokenFile: types.StringDefault("", p.GetMetadata()),
}
}

return aws.AssumeRoleWithWebIdentity{
Metadata: block.GetMetadata(),
Duration: getStringAttrValue("duration", p),
Policy: getStringAttrValue("policy", p),
PolicyARNs: p.GetAttribute("policy_arns").AsStringValueSliceOrEmpty(),
RoleARN: getStringAttrValue("role_arn", p),
SessionName: getStringAttrValue("session_name", p),
WebIdentityToken: getStringAttrValue("web_identity_token", p),
WebIdentityTokenFile: getStringAttrValue("web_identity_token_file", p),
}
}

func adaptEndpoints(p *terraform.Block) types.MapValue {
block := p.GetBlock("endpoints")
if block.IsNil() {
return types.MapDefault(make(map[string]string), p.GetMetadata())
}

values := make(map[string]string)

for name, attr := range block.Attributes() {
values[name] = attr.AsStringValueOrDefault("", block).Value()
}

return types.Map(values, block.GetMetadata())
}

func adaptDefaultTags(p *terraform.Block) aws.DefaultTags {
attr, _ := p.GetNestedAttribute("default_tags.tags")
if attr.IsNil() {
return aws.DefaultTags{}
}

return aws.DefaultTags{
Metadata: attr.GetMetadata(),
Tags: attr.AsMapValue(),
}
}

func adaptIgnoreTags(p *terraform.Block) aws.IgnoreTags {
block := p.GetBlock("ignore_tags")
if block.IsNil() {
return aws.IgnoreTags{}
}

return aws.IgnoreTags{
Metadata: block.GetMetadata(),
Keys: block.GetAttribute("keys").AsStringValueSliceOrEmpty(),
KeyPrefixes: block.GetAttribute("key_prefixes").AsStringValueSliceOrEmpty(),
}
}

func getStringAttrValue(name string, parent *terraform.Block) types.StringValue {
return parent.GetAttribute(name).AsStringValueOrDefault("", parent)
}
129 changes: 129 additions & 0 deletions internal/adapters/terraform/aws/provider/adapt_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package provider

import (
"testing"

"github.com/aquasecurity/defsec/pkg/providers/aws"
"github.com/aquasecurity/defsec/pkg/types"

"github.com/aquasecurity/trivy-iac/internal/adapters/terraform/tftestutil"
"github.com/aquasecurity/trivy-iac/test/testutil"
)

func TestAdapt(t *testing.T) {
tests := []struct {
name string
source string
expected []aws.TerraformProvider
}{
{
name: "happy",
source: `
variable "s3_use_path_style" {
default = true
}
provider "aws" {
version = "~> 5.0"
region = "us-east-1"
profile = "localstack"
access_key = "fake"
secret_key = "fake"
skip_credentials_validation = true
skip_metadata_api_check = true
skip_requesting_account_id = true
s3_use_path_style = var.s3_use_path_style
endpoints {
dynamodb = "http://localhost:4566"
s3 = "http://localhost:4566"
}
default_tags {
tags = {
Environment = "Local"
Name = "LocalStack"
}
}
}`,
expected: []aws.TerraformProvider{
{
Version: types.String("~> 5.0", types.NewTestMetadata()),
Region: types.String("us-east-1", types.NewTestMetadata()),
DefaultTags: aws.DefaultTags{
Metadata: types.NewTestMetadata(),
Tags: types.Map(map[string]string{
"Environment": "Local",
"Name": "LocalStack",
}, types.NewTestMetadata()),
},
Endpoints: types.Map(map[string]string{
"dynamodb": "http://localhost:4566",
"s3": "http://localhost:4566",
}, types.NewTestMetadata()),
Profile: types.String("localstack", types.NewTestMetadata()),
AccessKey: types.String("fake", types.NewTestMetadata()),
SecretKey: types.String("fake", types.NewTestMetadata()),
SkipCredentialsValidation: types.Bool(true, types.NewTestMetadata()),
SkipMetadataAPICheck: types.Bool(true, types.NewTestMetadata()),
SkipRequestingAccountID: types.Bool(true, types.NewTestMetadata()),
S3UsePathStyle: types.Bool(true, types.NewTestMetadata()),
MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()),
SharedConfigFiles: types.StringValueList{
types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()),
},
SharedCredentialsFiles: types.StringValueList{
types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()),
},
},
},
},
{
name: "multiply provider configurations",
source: `
provider "aws" {
region = "us-east-1"
}
provider "aws" {
alias = "west"
region = "us-west-2"
}
`,
expected: []aws.TerraformProvider{
{
Region: types.String("us-east-1", types.NewTestMetadata()),
Endpoints: types.Map(make(map[string]string), types.NewTestMetadata()),
MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()),
SharedConfigFiles: types.StringValueList{
types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()),
},
SharedCredentialsFiles: types.StringValueList{
types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()),
},
},
{
Alias: types.String("west", types.NewTestMetadata()),
Region: types.String("us-west-2", types.NewTestMetadata()),
Endpoints: types.Map(make(map[string]string), types.NewTestMetadata()),
MaxRetries: types.IntDefault(defaultMaxRetires, types.NewTestMetadata()),
SharedConfigFiles: types.StringValueList{
types.StringDefault(defaultSharedConfigFile, types.NewTestMetadata()),
},
SharedCredentialsFiles: types.StringValueList{
types.StringDefault(defaultSharedCredentialsFile, types.NewTestMetadata()),
},
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
modules := tftestutil.CreateModulesFromSource(t, test.source, ".tf")
testutil.AssertDefsecEqual(t, test.expected, Adapt(modules))
})
}
}
8 changes: 1 addition & 7 deletions internal/adapters/terraform/google/gke/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,13 +141,7 @@ func (a *adapter) adaptCluster(resource *terraform.Block, module *terraform.Modu

resourceLabelsAttr := resource.GetAttribute("resource_labels")
if resourceLabelsAttr.IsNotNil() {
resourceLabels := make(map[string]string)
_ = resourceLabelsAttr.Each(func(key, val cty.Value) {
if key.Type() == cty.String && val.Type() == cty.String {
resourceLabels[key.AsString()] = val.AsString()
}
})
cluster.ResourceLabels = defsecTypes.Map(resourceLabels, resourceLabelsAttr.GetMetadata())
cluster.ResourceLabels = resourceLabelsAttr.AsMapValue()
}

cluster.RemoveDefaultNodePool = resource.GetAttribute("remove_default_node_pool").AsBoolValueOrDefault(false, resource)
Expand Down
23 changes: 16 additions & 7 deletions pkg/rego/schemas/cloud.json
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,10 @@
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.lambda.Lambda"
},
"meta": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.Meta"
},
"mq": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.mq.MQ"
Expand All @@ -150,13 +154,6 @@
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.neptune.Neptune"
},
"providers": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.TerraformProvider"
}
},
"rds": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.rds.RDS"
Expand Down Expand Up @@ -302,6 +299,18 @@
}
}
},
"github.com.aquasecurity.defsec.pkg.providers.aws.Meta": {
"type": "object",
"properties": {
"tfproviders": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.providers.aws.TerraformProvider"
}
}
}
},
"github.com.aquasecurity.defsec.pkg.providers.aws.TerraformProvider": {
"type": "object",
"properties": {
Expand Down
Loading

0 comments on commit dafbd42

Please sign in to comment.