Files
trivy/pkg/iac/adapters/terraform/aws/iam/convert.go
2025-06-28 04:58:16 +00:00

238 lines
7.6 KiB
Go

package iam
import (
"strings"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/aquasecurity/iamgo"
"github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam"
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/terraform"
)
type wrappedDocument struct {
Source scan.MetadataProvider
Document iamgo.Document
}
func ParsePolicyFromAttr(attr *terraform.Attribute, owner *terraform.Block, modules terraform.Modules) (*iam.Document, error) {
attr.RewriteExpr(func(e hclsyntax.Expression) hclsyntax.Expression {
if te, ok := e.(*hclsyntax.TemplateExpr); ok {
return &terraform.PartialTemplateExpr{TemplateExpr: te}
}
return e
})
if !attr.IsString() {
return &iam.Document{
Metadata: owner.GetMetadata(),
}, nil
}
attrValue := attr.Value().AsString()
dataBlock, err := modules.GetBlockById(attrValue)
if err != nil {
parsed, err := iamgo.Parse([]byte(unescapeVars(attrValue)))
if err != nil {
return nil, err
}
return &iam.Document{
Parsed: *parsed,
Metadata: attr.GetMetadata(),
IsOffset: false,
HasRefs: len(attr.AllReferences()) > 0,
}, nil
}
if dataBlock.Type() == "data" && dataBlock.TypeLabel() == "aws_iam_policy_document" {
if doc, err := ConvertTerraformDocument(modules, dataBlock); err == nil {
return &iam.Document{
Metadata: dataBlock.GetMetadata(),
Parsed: doc.Document,
IsOffset: true,
HasRefs: false,
}, nil
}
}
return &iam.Document{
Metadata: owner.GetMetadata(),
}, nil
}
func unescapeVars(input string) string {
return strings.ReplaceAll(input, "&{", "${")
}
// ConvertTerraformDocument converts a terraform data policy into an iamgo policy https://registry.terraform.io/providers/hashicorp/aws/latest/docs/data-sources/iam_policy_document
func ConvertTerraformDocument(modules terraform.Modules, block *terraform.Block) (*wrappedDocument, error) {
builder := iamgo.NewPolicyBuilder()
if sourceAttr := block.GetAttribute("source_json"); sourceAttr.IsString() {
doc, err := iamgo.ParseString(sourceAttr.Value().AsString())
if err != nil {
return nil, err
}
builder = iamgo.PolicyBuilderFromDocument(*doc)
}
if sourceDocumentsAttr := block.GetAttribute("source_policy_documents"); sourceDocumentsAttr.IsIterable() {
docs := findAllPolicies(modules, sourceDocumentsAttr)
for _, doc := range docs {
statements, _ := doc.Document.Statements()
for _, statement := range statements {
builder.WithStatement(statement)
}
}
}
if idAttr := block.GetAttribute("policy_id"); idAttr.IsString() {
r := idAttr.GetMetadata().Range()
builder.WithId(idAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine())
}
if versionAttr := block.GetAttribute("version"); versionAttr.IsString() {
r := versionAttr.GetMetadata().Range()
builder.WithVersion(versionAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine())
}
for _, statementBlock := range block.GetBlocks("statement") {
statement := parseStatement(statementBlock)
builder.WithStatement(statement, statement.Range().StartLine, statement.Range().EndLine)
}
if overrideDocumentsAttr := block.GetAttribute("override_policy_documents"); overrideDocumentsAttr.IsIterable() {
docs := findAllPolicies(modules, overrideDocumentsAttr)
for _, doc := range docs {
statements, _ := doc.Document.Statements()
for _, statement := range statements {
builder.WithStatement(statement, statement.Range().StartLine, statement.Range().EndLine)
}
}
}
return &wrappedDocument{Document: builder.Build(), Source: block}, nil
}
// nolint
func parseStatement(statementBlock *terraform.Block) iamgo.Statement {
metadata := statementBlock.GetMetadata()
builder := iamgo.NewStatementBuilder()
builder.WithRange(metadata.Range().GetStartLine(), metadata.Range().GetEndLine())
if sidAttr := statementBlock.GetAttribute("sid"); sidAttr.IsString() {
r := sidAttr.GetMetadata().Range()
builder.WithSid(sidAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine())
}
if actionsAttr := statementBlock.GetAttribute("actions"); actionsAttr.IsIterable() {
r := actionsAttr.GetMetadata().Range()
values := actionsAttr.AsStringValues().AsStrings()
builder.WithActions(values, r.GetStartLine(), r.GetEndLine())
}
if notActionsAttr := statementBlock.GetAttribute("not_actions"); notActionsAttr.IsIterable() {
r := notActionsAttr.GetMetadata().Range()
values := notActionsAttr.AsStringValues().AsStrings()
builder.WithNotActions(values, r.GetStartLine(), r.GetEndLine())
}
if resourcesAttr := statementBlock.GetAttribute("resources"); resourcesAttr.IsIterable() {
r := resourcesAttr.GetMetadata().Range()
values := resourcesAttr.AsStringValues().AsStrings()
builder.WithResources(values, r.GetStartLine(), r.GetEndLine())
}
if notResourcesAttr := statementBlock.GetAttribute("not_resources"); notResourcesAttr.IsIterable() {
r := notResourcesAttr.GetMetadata().Range()
values := notResourcesAttr.AsStringValues().AsStrings()
builder.WithNotResources(values, r.GetStartLine(), r.GetEndLine())
}
if effectAttr := statementBlock.GetAttribute("effect"); effectAttr.IsString() {
r := effectAttr.GetMetadata().Range()
builder.WithEffect(effectAttr.Value().AsString(), r.GetStartLine(), r.GetEndLine())
} else {
builder.WithEffect(iamgo.EffectAllow)
}
for _, principalBlock := range statementBlock.GetBlocks("principals") {
typeAttr := principalBlock.GetAttribute("type")
if !typeAttr.IsString() {
continue
}
identifiersAttr := principalBlock.GetAttribute("identifiers")
if !identifiersAttr.IsIterable() {
continue
}
r := principalBlock.GetMetadata().Range()
switch typeAttr.Value().AsString() {
case "*":
builder.WithAllPrincipals(true, r.GetStartLine(), r.GetEndLine())
case "AWS":
values := identifiersAttr.AsStringValues().AsStrings()
builder.WithAWSPrincipals(values, r.GetStartLine(), r.GetEndLine())
case "Federated":
values := identifiersAttr.AsStringValues().AsStrings()
builder.WithFederatedPrincipals(values, r.GetStartLine(), r.GetEndLine())
case "Service":
values := identifiersAttr.AsStringValues().AsStrings()
builder.WithServicePrincipals(values, r.GetStartLine(), r.GetEndLine())
case "CanonicalUser":
values := identifiersAttr.AsStringValues().AsStrings()
builder.WithCanonicalUsersPrincipals(values, r.GetStartLine(), r.GetEndLine())
}
}
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")
values := valuesAttr.AsStringValues().AsStrings()
if valuesAttr.IsNil() || len(values) == 0 {
continue
}
r := conditionBlock.GetMetadata().Range()
builder.WithCondition(
testAttr.Value().AsString(),
variableAttr.Value().AsString(),
values,
r.GetStartLine(),
r.GetEndLine(),
)
}
return builder.Build()
}
func findAllPolicies(modules terraform.Modules, attr *terraform.Attribute) []wrappedDocument {
var documents []wrappedDocument
if !attr.IsIterable() {
return documents
}
policyDocIDs := attr.AsStringValues().AsStrings()
for _, policyDocID := range policyDocIDs {
if policyDoc, err := modules.GetBlockById(policyDocID); err == nil {
if document, err := ConvertTerraformDocument(modules, policyDoc); err == nil {
documents = append(documents, *document)
}
} else if parsed, err := iamgo.Parse([]byte(unescapeVars(policyDocID))); err == nil {
documents = append(documents, wrappedDocument{
Document: *parsed,
Source: attr,
})
}
}
return documents
}