feat(misconf): variable support for Terraform Plan (#7228)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2024-08-21 07:01:30 +06:00
committed by GitHub
parent efdbd8f19a
commit db2c95598d
16 changed files with 446 additions and 10 deletions

2
go.mod
View File

@@ -358,6 +358,8 @@ require (
github.com/transparency-dev/merkle v0.0.2 // indirect
github.com/ulikunitz/xz v0.5.11 // indirect
github.com/vbatts/tar-split v0.11.5 // indirect
github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect

4
go.sum
View File

@@ -1368,7 +1368,11 @@ github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/X
github.com/vbatts/tar-split v0.11.5 h1:3bHCTIheBm1qFTcgh9oPu+nNBtX+XJIupG/vacinCts=
github.com/vbatts/tar-split v0.11.5/go.mod h1:yZbwRsSeGjusneWgA781EKej9HF8vme8okylkAeNKLk=
github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4=
github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU=
github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc=
github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
github.com/xanzy/go-gitlab v0.102.0 h1:ExHuJ1OTQ2yt25zBMMj0G96ChBirGYv8U7HyUiYkZ+4=
github.com/xanzy/go-gitlab v0.102.0/go.mod h1:ETg8tcj4OhrB84UEgeE8dSuV/0h4BBL1uOV/qK0vlyI=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=

View File

@@ -30,6 +30,10 @@ var (
}
)
var protoFiles = []string{
"pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto",
}
func init() {
slog.SetDefault(log.New(log.NewHandler(os.Stderr, nil))) // stdout is suppressed in mage
}
@@ -154,11 +158,11 @@ func Mock(dir string) error {
func Protoc() error {
// It is called in the protoc container
if _, ok := os.LookupEnv("TRIVY_PROTOC_CONTAINER"); ok {
protoFiles, err := findProtoFiles()
rpcProtoFiles, err := findRPCProtoFiles()
if err != nil {
return err
}
for _, file := range protoFiles {
for _, file := range rpcProtoFiles {
// Check if the generated Go file is up-to-date
dst := strings.TrimSuffix(file, ".proto") + ".pb.go"
if updated, err := target.Path(dst, file); err != nil {
@@ -173,6 +177,13 @@ func Protoc() error {
return err
}
}
for _, file := range protoFiles {
if err := sh.RunV("protoc", ".", "paths=source_relative", "--go_out", ".", "--go_opt",
"paths=source_relative", file); err != nil {
return err
}
}
return nil
}
@@ -331,11 +342,13 @@ func Fmt() error {
}
// Format proto files
protoFiles, err := findProtoFiles()
rpcProtoFiles, err := findRPCProtoFiles()
if err != nil {
return err
}
for _, file := range protoFiles {
allProtoFiles := append(protoFiles, rpcProtoFiles...)
for _, file := range allProtoFiles {
if err = sh.Run("clang-format", "-i", file); err != nil {
return err
}
@@ -422,7 +435,7 @@ func (Docs) Generate() error {
return sh.RunWith(ENV, "go", "run", "-tags=mage_docs", "./magefiles")
}
func findProtoFiles() ([]string, error) {
func findRPCProtoFiles() ([]string, error) {
var files []string
err := filepath.WalkDir("rpc", func(path string, d fs.DirEntry, err error) error {
switch {

View File

@@ -3,12 +3,15 @@ package parser
import (
"io/fs"
"github.com/zclconf/go-cty/cty"
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
)
type ConfigurableTerraformParser interface {
options.ConfigurableParser
SetTFVarsPaths(...string)
SetTFVars(vars map[string]cty.Value)
SetStopOnHCLError(bool)
SetWorkspaceName(string)
SetAllowDownloads(bool)
@@ -26,6 +29,14 @@ func OptionWithTFVarsPaths(paths ...string) options.ParserOption {
}
}
func OptionsWithTfVars(vars map[string]cty.Value) options.ParserOption {
return func(p options.ConfigurableParser) {
if tf, ok := p.(ConfigurableTerraformParser); ok {
tf.SetTFVars(vars)
}
}
}
func OptionStopOnHCLError(stop bool) options.ParserOption {
return func(p options.ConfigurableParser) {
if tf, ok := p.(ConfigurableTerraformParser); ok {

View File

@@ -39,6 +39,7 @@ type Parser struct {
moduleBlock *terraform.Block
files []sourceFile
tfvarsPaths []string
tfvars map[string]cty.Value
stopOnHCLError bool
workspaceName string
underlying *hclparse.Parser
@@ -59,6 +60,10 @@ func (p *Parser) SetTFVarsPaths(s ...string) {
p.tfvarsPaths = s
}
func (p *Parser) SetTFVars(vars map[string]cty.Value) {
p.tfvars = vars
}
func (p *Parser) SetStopOnHCLError(b bool) {
p.stopOnHCLError = b
}
@@ -90,6 +95,7 @@ func New(moduleFS fs.FS, moduleSource string, opts ...options.ParserOption) *Par
moduleFS: moduleFS,
moduleSource: moduleSource,
configsFS: moduleFS,
tfvars: make(map[string]cty.Value),
}
for _, option := range opts {
@@ -215,10 +221,15 @@ func (p *Parser) Load(ctx context.Context) (*evaluator, error) {
p.debug.Log("Read %d block(s) and %d ignore(s) for module '%s' (%d file[s])...", len(blocks), len(ignores), p.moduleName, len(p.files))
var inputVars map[string]cty.Value
if p.moduleBlock != nil {
switch {
case p.moduleBlock != nil:
inputVars = p.moduleBlock.Values().AsValueMap()
p.debug.Log("Added %d input variables from module definition.", len(inputVars))
} else {
case len(p.tfvars) > 0:
inputVars = p.tfvars
p.debug.Log("Added %d input variables from tfvars.", len(inputVars))
default:
inputVars, err = loadTFVars(p.configsFS, p.tfvarsPaths)
if err != nil {
return nil, err

View File

@@ -1746,6 +1746,33 @@ func TestTFVarsFileDoesNotExist(t *testing.T) {
assert.ErrorContains(t, err, "file does not exist")
}
func Test_OptionsWithTfVars(t *testing.T) {
fs := testutil.CreateFS(t, map[string]string{
"main.tf": `resource "test" "this" {
foo = var.foo
}
variable "foo" {}
`})
parser := New(fs, "", OptionsWithTfVars(
map[string]cty.Value{
"foo": cty.StringVal("bar"),
},
))
require.NoError(t, parser.ParseFS(context.TODO(), "."))
modules, _, err := parser.EvaluateAll(context.TODO())
require.NoError(t, err)
assert.Len(t, modules, 1)
rootModule := modules[0]
blocks := rootModule.GetResourcesByType("test")
assert.Len(t, blocks, 1)
assert.Equal(t, "bar", blocks[0].GetAttribute("foo").Value().AsString())
}
func TestDynamicWithIterator(t *testing.T) {
fsys := fstest.MapFS{
"main.tf": &fstest.MapFile{

View File

@@ -0,0 +1,64 @@
package snapshot
import (
"fmt"
"io"
"github.com/zclconf/go-cty/cty"
ctymsgpack "github.com/zclconf/go-cty/cty/msgpack"
"google.golang.org/protobuf/proto"
"github.com/aquasecurity/trivy/pkg/iac/scanners/terraformplan/snapshot/planproto"
)
type DynamicValue []byte
func (v DynamicValue) Decode(ty cty.Type) (cty.Value, error) {
if v == nil {
return cty.NilVal, nil
}
return ctymsgpack.Unmarshal([]byte(v), ty)
}
type Plan struct {
variableValues map[string]DynamicValue
}
func (p Plan) inputVariables() (map[string]cty.Value, error) {
vars := make(map[string]cty.Value)
for k, v := range p.variableValues {
val, err := v.Decode(cty.DynamicPseudoType)
if err != nil {
return nil, err
}
vars[k] = val
}
return vars, nil
}
func readTfPlan(r io.Reader) (*Plan, error) {
b, err := io.ReadAll(r)
if err != nil {
return nil, fmt.Errorf("failed to read plan: %w", err)
}
var rawPlan planproto.Plan
if err := proto.Unmarshal(b, &rawPlan); err != nil {
return nil, fmt.Errorf("failed to unmarshal plan: %w", err)
}
plan := Plan{
variableValues: make(map[string]DynamicValue),
}
for k, v := range rawPlan.Variables {
if len(v.Msgpack) == 0 { // len(0) because that's the default value for a "bytes" in protobuf
return nil, fmt.Errorf("dynamic value does not have msgpack serialization")
}
plan.variableValues[k] = DynamicValue(v.Msgpack)
}
return &plan, nil
}

View File

@@ -0,0 +1,222 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.32.0
// protoc v5.27.1
// source: pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.proto
package planproto
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type DynamicValue struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Msgpack []byte `protobuf:"bytes,1,opt,name=msgpack,proto3" json:"msgpack,omitempty"`
}
func (x *DynamicValue) Reset() {
*x = DynamicValue{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *DynamicValue) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DynamicValue) ProtoMessage() {}
func (x *DynamicValue) ProtoReflect() protoreflect.Message {
mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use DynamicValue.ProtoReflect.Descriptor instead.
func (*DynamicValue) Descriptor() ([]byte, []int) {
return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{0}
}
func (x *DynamicValue) GetMsgpack() []byte {
if x != nil {
return x.Msgpack
}
return nil
}
type Plan struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Variables map[string]*DynamicValue `protobuf:"bytes,2,rep,name=variables,proto3" json:"variables,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
}
func (x *Plan) Reset() {
*x = Plan{}
if protoimpl.UnsafeEnabled {
mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *Plan) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*Plan) ProtoMessage() {}
func (x *Plan) ProtoReflect() protoreflect.Message {
mi := &file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use Plan.ProtoReflect.Descriptor instead.
func (*Plan) Descriptor() ([]byte, []int) {
return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP(), []int{1}
}
func (x *Plan) GetVariables() map[string]*DynamicValue {
if x != nil {
return x.Variables
}
return nil
}
var File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto protoreflect.FileDescriptor
var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = []byte{
0x0a, 0x40, 0x70, 0x6b, 0x67, 0x2f, 0x69, 0x61, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65,
0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61, 0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e,
0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f, 0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x66, 0x69, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x06, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x22, 0x28, 0x0a, 0x0c, 0x44, 0x79,
0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x73,
0x67, 0x70, 0x61, 0x63, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x07, 0x6d, 0x73, 0x67,
0x70, 0x61, 0x63, 0x6b, 0x22, 0x95, 0x01, 0x0a, 0x04, 0x50, 0x6c, 0x61, 0x6e, 0x12, 0x39, 0x0a,
0x09, 0x76, 0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b,
0x32, 0x1b, 0x2e, 0x74, 0x66, 0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x50, 0x6c, 0x61, 0x6e, 0x2e, 0x56,
0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x76,
0x61, 0x72, 0x69, 0x61, 0x62, 0x6c, 0x65, 0x73, 0x1a, 0x52, 0x0a, 0x0e, 0x56, 0x61, 0x72, 0x69,
0x61, 0x62, 0x6c, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65,
0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x05,
0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x66,
0x70, 0x6c, 0x61, 0x6e, 0x2e, 0x44, 0x79, 0x6e, 0x61, 0x6d, 0x69, 0x63, 0x56, 0x61, 0x6c, 0x75,
0x65, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x42, 0x4d, 0x5a, 0x4b,
0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, 0x73,
0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x69, 0x61,
0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x2f, 0x74, 0x65, 0x72, 0x72, 0x61,
0x66, 0x6f, 0x72, 0x6d, 0x70, 0x6c, 0x61, 0x6e, 0x2f, 0x73, 0x6e, 0x61, 0x70, 0x73, 0x68, 0x6f,
0x74, 0x2f, 0x70, 0x6c, 0x61, 0x6e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x33,
}
var (
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce sync.Once
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc
)
func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescGZIP() []byte {
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescOnce.Do(func() {
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData = protoimpl.X.CompressGZIP(file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData)
})
return file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDescData
}
var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = []interface{}{
(*DynamicValue)(nil), // 0: tfplan.DynamicValue
(*Plan)(nil), // 1: tfplan.Plan
nil, // 2: tfplan.Plan.VariablesEntry
}
var file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = []int32{
2, // 0: tfplan.Plan.variables:type_name -> tfplan.Plan.VariablesEntry
0, // 1: tfplan.Plan.VariablesEntry.value:type_name -> tfplan.DynamicValue
2, // [2:2] is the sub-list for method output_type
2, // [2:2] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() }
func file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_init() {
if File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*DynamicValue); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*Plan); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 0,
},
GoTypes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes,
DependencyIndexes: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs,
MessageInfos: file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_msgTypes,
}.Build()
File_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto = out.File
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_rawDesc = nil
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_goTypes = nil
file_pkg_iac_scanners_terraformplan_snapshot_planproto_planfile_proto_depIdxs = nil
}

View File

@@ -0,0 +1,12 @@
syntax = "proto3";
package tfplan;
option go_package = "github.com/aquasecurity/trivy/iac/scanners/terraformplan/snapshot/planproto";
message DynamicValue {
bytes msgpack = 1;
}
message Plan {
map<string, DynamicValue> variables = 2;
}

View File

@@ -10,6 +10,7 @@ import (
"github.com/aquasecurity/trivy/pkg/iac/scan"
"github.com/aquasecurity/trivy/pkg/iac/scanners/options"
terraformScanner "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform"
tfparser "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform/parser"
)
type Scanner struct {
@@ -71,5 +72,9 @@ func (s *Scanner) Scan(ctx context.Context, reader io.Reader) (scan.Results, err
if err != nil {
return nil, fmt.Errorf("failed to convert snapshot to FS: %w", err)
}
s.inner.AddParserOptions(
tfparser.OptionsWithTfVars(snap.inputVariables),
)
return s.inner.ScanFS(ctx, fsys, ".")
}

View File

@@ -98,6 +98,10 @@ func Test_ScanFS(t *testing.T) {
dir: "with-remote-module",
expectedIDs: []string{"ID001"},
},
{
dir: "with-var",
expectedIDs: []string{"ID001"},
},
}
for _, tc := range tests {
@@ -133,5 +137,4 @@ func Test_ScanFS(t *testing.T) {
assert.Equal(t, tc.expectedIDs, ids)
})
}
}

View File

@@ -13,6 +13,7 @@ import (
"strings"
"github.com/liamg/memoryfs"
"github.com/zclconf/go-cty/cty"
iox "github.com/aquasecurity/trivy/pkg/x/io"
)
@@ -74,7 +75,8 @@ func parseSnapshot(r io.Reader) (*snapshot, error) {
}
snap := &snapshot{
modules: make(map[string]*snapshotModule),
modules: make(map[string]*snapshotModule),
inputVariables: make(map[string]cty.Value),
}
var moduleManifest configSnapshotModuleManifest
@@ -91,6 +93,23 @@ func parseSnapshot(r io.Reader) (*snapshot, error) {
if err := snap.addFile(file); err != nil {
return nil, err
}
case file.Name == tfplanFilename:
r, err := file.Open()
if err != nil {
return nil, fmt.Errorf("failed to open plan: %w", err)
}
plan, err := readTfPlan(r)
if err != nil {
_ = r.Close()
return nil, fmt.Errorf("failed to read tfplan: %w", err)
}
_ = r.Close()
snap.inputVariables, err = plan.inputVariables()
if err != nil {
return nil, err
}
}
}
@@ -140,7 +159,8 @@ type (
}
snapshot struct {
modules map[string]*snapshotModule
modules map[string]*snapshotModule
inputVariables map[string]cty.Value
}
)

View File

@@ -11,6 +11,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/zclconf/go-cty/cty"
)
func TestReadSnapshot(t *testing.T) {
@@ -108,3 +109,18 @@ func TestIsPlanSnapshot(t *testing.T) {
assert.False(t, got)
})
}
func TestPlanWithVariables(t *testing.T) {
f, err := os.Open(filepath.Join("testdata", "with-var", "tfplan"))
require.NoError(t, err)
defer f.Close()
snapshot, err := parseSnapshot(f)
require.NoError(t, err)
require.NotNil(t, snapshot)
expectedVars := map[string]cty.Value{
"bucket_name": cty.StringVal("test-bucket"),
}
assert.Equal(t, expectedVars, snapshot.inputVariables)
}

View File

@@ -0,0 +1,21 @@
# METADATA
# title: Test rego
# description: A bucket named "test-bucket" is not allowed
# schemas:
# - input: schema["cloud"]
# custom:
# avd_id: ID001
# severity: LOW
# input:
# selector:
# - type: cloud
# subtypes:
# - service: s3
# provider: aws
package user.aws.ID001
deny[res] {
bucket := input.aws.s3.buckets[_]
bucket.name.value == "test-bucket"
res := result.new("Bucket not allowed", bucket.name)
}

View File

@@ -0,0 +1,5 @@
variable "bucket_name" {}
resource "aws_s3_bucket" "this" {
bucket = var.bucket_name
}

Binary file not shown.