mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
feat(misconf): variable support for Terraform Plan (#7228)
Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
2
go.mod
2
go.mod
@@ -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
4
go.sum
@@ -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=
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
64
pkg/iac/scanners/terraformplan/snapshot/plan.go
Normal file
64
pkg/iac/scanners/terraformplan/snapshot/plan.go
Normal 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
|
||||
}
|
||||
222
pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go
Normal file
222
pkg/iac/scanners/terraformplan/snapshot/planproto/planfile.pb.go
Normal 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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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, ".")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
21
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego
vendored
Normal file
21
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/checks/s3-bucket-name.rego
vendored
Normal 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)
|
||||
}
|
||||
5
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf
vendored
Normal file
5
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/main.tf
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
variable "bucket_name" {}
|
||||
|
||||
resource "aws_s3_bucket" "this" {
|
||||
bucket = var.bucket_name
|
||||
}
|
||||
BIN
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan
vendored
Normal file
BIN
pkg/iac/scanners/terraformplan/snapshot/testdata/with-var/tfplan
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user