Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Convert AWS IAM to defsec #1361

Merged
merged 9 commits into from
Jan 19, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions example/mfa-module/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
data aws_caller_identity current {}

resource aws_iam_group support {
name = "support"
}

module enforce_mfa {
source = "terraform-module/enforce-mfa/aws"
version = "0.12.0"

policy_name = "managed-mfa-enforce"
account_id = data.aws_caller_identity.current.id
groups = [aws_iam_group.support.name]
manage_own_signing_certificates = true
manage_own_ssh_public_keys = true
manage_own_git_credentials = true
}

4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ go 1.17

require (
github.com/apparentlymart/go-cidr v1.1.0
github.com/aquasecurity/defsec v0.3.20
github.com/aquasecurity/defsec v0.3.21
github.com/bmatcuk/doublestar v1.3.4
github.com/google/go-cmp v0.5.6
github.com/google/uuid v1.3.0
github.com/hashicorp/go-uuid v1.0.2
github.com/hashicorp/go-version v1.4.0
github.com/hashicorp/hcl/v2 v2.11.1
github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf
github.com/liamg/iamgo v0.0.2
github.com/liamg/tml v0.4.0
github.com/mitchellh/go-homedir v1.1.0
github.com/olekukonko/tablewriter v0.0.5
Expand Down Expand Up @@ -42,7 +43,6 @@ require (
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/liamg/clinch v1.5.6 // indirect
github.com/liamg/gifwrap v0.0.6 // indirect
github.com/liamg/iamgo v0.0.2 // indirect
github.com/lucasb-eyer/go-colorful v1.0.3 // indirect
github.com/mattn/go-runewidth v0.0.12 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
Expand Down
22 changes: 2 additions & 20 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -74,26 +74,8 @@ github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
github.com/aquasecurity/defsec v0.3.5 h1:8aO8HYyo9PYX7Y3ewXdl7c5oo1lQEv1xlcg1akEPryU=
github.com/aquasecurity/defsec v0.3.5/go.mod h1:UxYfQXEX2Qd+SFOo+IFGQhYWfZzLHOb5Xxxv5oKdmYM=
github.com/aquasecurity/defsec v0.3.9 h1:EyfIH1E3qKbbeYqznP9Zx+GUkyAKDyrlF5r1WMdn8/8=
github.com/aquasecurity/defsec v0.3.9/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.10 h1:xLatl1nuIS2UEMe6dzkWNbaFJjhYwqSIEWw+dsmz1Lg=
github.com/aquasecurity/defsec v0.3.10/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.11 h1:MTcqZ1e6fZqnzL2HcXvpOa3DUt/JJ46KC3DkT/WB+0A=
github.com/aquasecurity/defsec v0.3.11/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.12 h1:kNB6pCnY2DZzkK0HNR3l/uuMHcSRwAksXqWIusqEVq4=
github.com/aquasecurity/defsec v0.3.12/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.13 h1:f2CKlYz/u8oAjbswFOD1L1SQx74D9XvuXtDswI5uSLk=
github.com/aquasecurity/defsec v0.3.13/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.16 h1:qCsnAuu8pEQcSuUj4vMA7SWM+XzoJBFhDNkUE4h7zFc=
github.com/aquasecurity/defsec v0.3.16/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.17 h1:crMKcMqc9mAQ7eFUTcT5lm24qECNG0VqiMO3U10pek4=
github.com/aquasecurity/defsec v0.3.17/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.18 h1:dBU9I3WOMEjdMFnu2hgcn3ImHak4p/HKo681TDGH/xY=
github.com/aquasecurity/defsec v0.3.18/go.mod h1:+7iLruZRqRy54zcVCBcEw05YKOtOK6Dd+tEWQjfQPEM=
github.com/aquasecurity/defsec v0.3.20 h1:J0ye7AlX4e7dm+xfNt84pvxmzv0Oz7EptmwRg8A7fUQ=
github.com/aquasecurity/defsec v0.3.20/go.mod h1:k69jht4X6WIpU4Q9py0o9hYHs4lnGTsBb64ncWCuQvE=
github.com/aquasecurity/defsec v0.3.21 h1:teUw4IzI7hl94yoO3cK83N6LCPe9hxRo8deC58mQhYc=
github.com/aquasecurity/defsec v0.3.21/go.mod h1:k69jht4X6WIpU4Q9py0o9hYHs4lnGTsBb64ncWCuQvE=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-metrics v0.3.10/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc=
Expand Down
5 changes: 2 additions & 3 deletions internal/app/tfsec/adapter/aws/ecr/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,6 @@ func adaptRepository(resource block.Block, module block.Module) ecr.Repository {
encryptionTypeVal := types.StringDefault("AES256", *resource.GetMetadata())
kmsKeyVal := types.StringDefault("", *resource.GetMetadata())

var policies []types.StringValue

if resource.HasChild("image_scanning_configuration") {
imageScanningBlock := resource.GetBlock("image_scanning_configuration")
scanOnPushAttr := imageScanningBlock.GetAttribute("scan_on_push")
Expand All @@ -45,10 +43,11 @@ func adaptRepository(resource block.Block, module block.Module) ecr.Repository {
}

policyBlocks := module.GetReferencingResources(resource, "aws_ecr_repository_policy", "repository")
var policies []types.StringValue
for _, policyRes := range policyBlocks {
if policyRes.HasChild("policy") && policyRes.GetAttribute("policy").IsString() {
policyAttr := policyRes.GetAttribute("policy")
policies = append(policies, policyAttr.AsStringValueOrDefault("", resource))
policies = append(policies, policyAttr.AsStringValueOrDefault("", policyRes))
}
}

Expand Down
8 changes: 7 additions & 1 deletion internal/app/tfsec/adapter/aws/iam/adapt.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,11 @@ import (
)

func Adapt(modules []block.Module) iam.IAM {
return iam.IAM{}
return iam.IAM{
PasswordPolicy: adaptPasswordPolicy(modules),
Policies: adaptPolicies(modules),
Groups: adaptGroups(modules),
Users: adaptUsers(modules),
Roles: adaptRoles(modules),
}
}
214 changes: 214 additions & 0 deletions internal/app/tfsec/adapter/aws/iam/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package iam

import (
"encoding/json"
"strings"

"github.com/aquasecurity/defsec/rules"
"github.com/aquasecurity/defsec/types"
"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
"github.com/liamg/iamgo"
)

type wrappedDocument struct {
source rules.MetadataProvider
document iamgo.Document
}

func parsePolicyFromAttr(attr block.Attribute, owner block.Block, modules block.Modules) (types.StringValue, error) {

documents := findAllPolicies(modules, owner, attr)
if len(documents) > 0 {
output, err := json.Marshal(documents[0].document)
if err != nil {
return nil, err
}
return types.String(unescapeVars(string(output)), *documents[0].source.GetMetadata()), nil
}

if attr.IsString() {
return types.String(unescapeVars(attr.Value().AsString()), owner.Metadata()), nil
}

return attr.AsStringValueOrDefault("", owner), nil
}

func unescapeVars(input string) string {
return strings.ReplaceAll(input, "&{", "${")
}

// https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
func convertTerraformDocument(modules block.Modules, block block.Block) (*wrappedDocument, error) {

var document iamgo.Document

if sourceAttr := block.GetAttribute("source_json"); sourceAttr.IsString() {
doc, err := iamgo.ParseString(sourceAttr.Value().AsString())
if err != nil {
return nil, err
}
document = *doc
}

if sourceDocumentsAttr := block.GetAttribute("source_policy_documents"); sourceDocumentsAttr.IsIterable() {
docs := findAllPolicies(modules, block, sourceDocumentsAttr)
for _, doc := range docs {
document.Statement = append(document.Statement, doc.document.Statement...)
}
}

if idAttr := block.GetAttribute("policy_id"); idAttr.IsString() {
document.Id = idAttr.Value().AsString()
}

if versionAttr := block.GetAttribute("version"); versionAttr.IsString() {
document.Version = iamgo.Version(versionAttr.Value().AsString())
}

// count number of statements in the source json to ensure we only override these with regular statements
sourceCount := len(document.Statement)

for _, statementBlock := range block.GetBlocks("statement") {

statement := parseStatement(statementBlock)
mergeInStatement(&document, statement, sourceCount)
}

sourceCount = len(document.Statement)
if overrideDocumentsAttr := block.GetAttribute("override_policy_documents"); overrideDocumentsAttr.IsIterable() {
docs := findAllPolicies(modules, block, overrideDocumentsAttr)
for _, doc := range docs {
for _, statement := range doc.document.Statement {
mergeInStatement(&document, statement, sourceCount)
}
}
}

return &wrappedDocument{document: document, source: block}, nil
}

func mergeInStatement(document *iamgo.Document, statement iamgo.Statement, overrideToIndex int) {
var sidExists bool
for i, existing := range document.Statement {
if i >= overrideToIndex {
break
}
if existing.Sid == statement.Sid {
sidExists = true
document.Statement[i] = statement
break
}
}
if !sidExists {
document.Statement = append(document.Statement, statement)
}
}

func parseStatement(statementBlock block.Block) iamgo.Statement {
var statement iamgo.Statement
if sidAttr := statementBlock.GetAttribute("sid"); sidAttr.IsString() {
statement.Sid = sidAttr.Value().AsString()
}
if actionsAttr := statementBlock.GetAttribute("actions"); actionsAttr.IsIterable() {
statement.Action = actionsAttr.ValueAsStrings()
}
if notActionsAttr := statementBlock.GetAttribute("not_actions"); notActionsAttr.IsIterable() {
statement.NotAction = notActionsAttr.ValueAsStrings()
}
if resourcesAttr := statementBlock.GetAttribute("resources"); resourcesAttr.IsIterable() {
statement.Resource = resourcesAttr.ValueAsStrings()
}
if notResourcesAttr := statementBlock.GetAttribute("not_resources"); notResourcesAttr.IsIterable() {
statement.NotResource = notResourcesAttr.ValueAsStrings()
}
if effectAttr := statementBlock.GetAttribute("effect"); effectAttr.IsString() {
statement.Effect = iamgo.Effect(effectAttr.Value().AsString())
} else {
statement.Effect = iamgo.EffectAllow
}

statement.Principal = readPrincipal(statementBlock.GetBlocks("principals"))
statement.NotPrincipal = readPrincipal(statementBlock.GetBlocks("not_principals"))

for _, conditionBlock := range statementBlock.GetBlocks("condition") {
testAttr := conditionBlock.GetAttribute("test")
if !testAttr.IsString() {
continue
}
variableAttr := conditionBlock.GetAttribute("variable")
if !variableAttr.IsString() {
continue
}
valuesAttr := conditionBlock.GetAttribute("values")
if valuesAttr.IsNil() || len(valuesAttr.ValueAsStrings()) == 0 {
continue
}
statement.Condition = append(statement.Condition, iamgo.Condition{
Operator: testAttr.Value().AsString(),
Key: variableAttr.Value().AsString(),
Value: valuesAttr.ValueAsStrings(),
})
}
return statement
}

func readPrincipal(blocks block.Blocks) *iamgo.Principals {
var principals *iamgo.Principals
for _, principalBlock := range blocks {

typeAttr := principalBlock.GetAttribute("type")
if !typeAttr.IsString() {
continue
}
identifiersAttr := principalBlock.GetAttribute("identifiers")
if !identifiersAttr.IsIterable() {
continue
}

if principals == nil {
principals = &iamgo.Principals{}
}
switch typeAttr.Value().AsString() {
case "*":
principals.All = true
case "AWS":
principals.AWS = append(principals.AWS, identifiersAttr.ValueAsStrings()...)
case "Federated":
principals.Federated = append(principals.Federated, identifiersAttr.ValueAsStrings()...)
case "Service":
principals.Service = append(principals.Service, identifiersAttr.ValueAsStrings()...)
case "CanonicalUser":
principals.CanonicalUsers = append(principals.CanonicalUsers, identifiersAttr.ValueAsStrings()...)
}
}
return principals
}

func findAllPolicies(modules block.Modules, parentBlock block.Block, attr block.Attribute) []wrappedDocument {
var documents []wrappedDocument
for _, ref := range attr.AllReferences() {
for _, block := range modules.GetBlocks() {
if block.Type() != "data" || block.TypeLabel() != "aws_iam_policy_document" {
continue
}
if ref.RefersTo(block.Reference()) {
document, err := convertTerraformDocument(modules, block)
if err != nil {
continue
}
documents = append(documents, *document)
continue
}
kref := *ref
kref.SetKey(parentBlock.Reference().RawKey())
if kref.RefersTo(block.Reference()) {
document, err := convertTerraformDocument(modules, block)
if err != nil {
continue
}
documents = append(documents, *document)
}
}
}
return documents
}
Loading