Skip to content

Commit c45c6ac

Browse files
Merge pull request #206 from DataDog/gaspard.barbier/iac-add-arguments-to-config
Parse a new "arguments" keyword in new IaC v1.4 config Co-authored-by: barbiergaspard <gaspard.barbier@datadoghq.com>
2 parents 5086043 + 8eeff26 commit c45c6ac

13 files changed

Lines changed: 207 additions & 87 deletions

File tree

pkg/config/config.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ type iacGlobalConfig struct {
4141

4242
// iacRuleConfigYaml is the YAML representation of a per-rule override block.
4343
type iacRuleConfigYaml struct {
44-
IgnorePaths []string `yaml:"ignore-paths,omitempty"`
45-
OnlyPaths []string `yaml:"only-paths,omitempty"`
46-
Severity *iacSeverity `yaml:"severity,omitempty"`
44+
IgnorePaths []string `yaml:"ignore-paths,omitempty"`
45+
OnlyPaths []string `yaml:"only-paths,omitempty"`
46+
Arguments map[string]any `yaml:"arguments,omitempty"`
47+
Severity *iacSeverity `yaml:"severity,omitempty"`
4748
}
4849

4950
// ParseConfig turns a YAML configuration file into a parsed configuration
@@ -104,6 +105,7 @@ func parseRuleConfigs(in map[string]iacRuleConfigYaml) map[string]IacRuleConfig
104105
out[ruleID] = IacRuleConfig{
105106
IgnorePaths: rc.IgnorePaths,
106107
OnlyPaths: rc.OnlyPaths,
108+
Arguments: rc.Arguments,
107109
Severity: (*string)(rc.Severity),
108110
}
109111
}
@@ -119,6 +121,7 @@ func unparseRuleConfigs(in map[string]IacRuleConfig) map[string]iacRuleConfigYam
119121
out[ruleID] = iacRuleConfigYaml{
120122
IgnorePaths: rc.IgnorePaths,
121123
OnlyPaths: rc.OnlyPaths,
124+
Arguments: rc.Arguments,
122125
Severity: (*iacSeverity)(rc.Severity),
123126
}
124127
}
@@ -195,7 +198,7 @@ var (
195198
// minIacVersion is the minimum schema version supporting the iac: configuration section.
196199
minIacVersion = schemaVersion{1, 2}
197200
// currentVersion is the current schema version emitted by UnparseConfig.
198-
currentVersion = schemaVersion{1, 3}
201+
currentVersion = schemaVersion{1, 4}
199202
// minRequiredVersion is the minimum schema version supported by this scanner.
200203
// It's different from minIacVersion for user-friendliness in case people add an `iac`
201204
// section to an old configuration file and forget to upgrade the version number.

pkg/config/config_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,13 @@ func TestUnparseConfig(t *testing.T) {
6464
assert.Equal(t, cfgFile, string(b))
6565
}
6666

67-
func TestUnparseConfigV13Fields(t *testing.T) {
67+
func TestUnparseConfigV14Fields(t *testing.T) {
6868
t.Run("config with only-platforms serializes field", func(t *testing.T) {
6969
cfg := parsedCfgFile
7070
cfg.OnlyPlatforms = []string{"Terraform"}
7171
b, err := UnparseConfig(&cfg)
7272
require.NoError(t, err)
73-
assert.Contains(t, string(b), "schema-version: v1.3")
73+
assert.Contains(t, string(b), "schema-version: v1.4")
7474
assert.Contains(t, string(b), "only-platforms:")
7575
})
7676

@@ -95,8 +95,8 @@ func TestUnparseConfigV13Fields(t *testing.T) {
9595
})
9696
}
9797

98-
func TestParseUnparseRoundTripV13(t *testing.T) {
99-
input := `schema-version: v1.3
98+
func TestParseUnparseRoundTripV14(t *testing.T) {
99+
input := `schema-version: v1.4
100100
iac:
101101
ignore-rules:
102102
- query1
@@ -111,6 +111,9 @@ iac:
111111
ignore-paths:
112112
- test/
113113
severity: low
114+
arguments:
115+
required_tags:
116+
- Env
114117
`
115118
cfg, err := ParseConfig([]byte(input))
116119
require.NoError(t, err)
@@ -123,4 +126,6 @@ iac:
123126
assert.Equal(t, []string{"test/"}, rc.IgnorePaths)
124127
require.NotNil(t, rc.Severity)
125128
assert.Equal(t, "low", *rc.Severity)
129+
require.NotNil(t, rc.Arguments)
130+
assert.Equal(t, []interface {}([]interface {}{"Env"}), rc.Arguments["required_tags"])
126131
}

pkg/config/config_validation_test.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ func TestParsePlatformValues(t *testing.T) {
141141

142142
func TestParseRuleConfigs(t *testing.T) {
143143
t.Run("full rule config", func(t *testing.T) {
144-
input := `schema-version: v1.3
144+
input := `schema-version: v1.4
145145
iac:
146146
rule-configs:
147147
terraform-aws-s3-unencrypted:
@@ -150,6 +150,8 @@ iac:
150150
only-paths:
151151
- src/
152152
severity: low
153+
arguments:
154+
required: enabled
153155
`
154156
cfg, err := ParseConfig([]byte(input))
155157
require.NoError(t, err)
@@ -160,10 +162,12 @@ iac:
160162
assert.Equal(t, []string{"src/"}, rc.OnlyPaths)
161163
require.NotNil(t, rc.Severity)
162164
assert.Equal(t, "low", *rc.Severity)
165+
require.NotNil(t, rc.Arguments)
166+
assert.Equal(t, "enabled", (rc.Arguments)["required"])
163167
})
164168

165169
t.Run("severity override only", func(t *testing.T) {
166-
input := `schema-version: v1.3
170+
input := `schema-version: v1.4
167171
iac:
168172
rule-configs:
169173
some-rule:
@@ -180,7 +184,7 @@ iac:
180184
})
181185

182186
t.Run("invalid severity in rule-config", func(t *testing.T) {
183-
input := `schema-version: v1.3
187+
input := `schema-version: v1.4
184188
iac:
185189
rule-configs:
186190
some-rule:
@@ -192,7 +196,7 @@ iac:
192196
})
193197

194198
t.Run("unknown field in rule-config", func(t *testing.T) {
195-
input := `schema-version: v1.3
199+
input := `schema-version: v1.4
196200
iac:
197201
rule-configs:
198202
some-rule:
@@ -202,6 +206,20 @@ iac:
202206
require.Error(t, err)
203207
assert.Contains(t, err.Error(), "iac.rule-configs.<rule-id>")
204208
})
209+
210+
t.Run("arguments in rule-config", func(t *testing.T) {
211+
input := `schema-version: v1.4
212+
iac:
213+
rule-configs:
214+
some-rule:
215+
arguments:
216+
required: enabled
217+
`
218+
cfg, err := ParseConfig([]byte(input))
219+
require.NoError(t, err)
220+
require.NotNil(t, cfg.RuleConfigs["some-rule"].Arguments)
221+
assert.Equal(t, "enabled", (cfg.RuleConfigs["some-rule"].Arguments)["required"])
222+
})
205223
}
206224

207225
func TestRewriteDecodeErrors(t *testing.T) {

pkg/config/reader_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import (
1111
"github.com/stretchr/testify/require"
1212
)
1313

14-
const cfgFile = `schema-version: v1.3
14+
const cfgFile = `schema-version: v1.4
1515
iac:
1616
ignore-rules:
1717
- query1
@@ -65,7 +65,7 @@ var parsedLegacyCfg = IacConfig{
6565
IgnoreCategories: []string{"Access Control", "Availability"},
6666
}
6767

68-
const convertedLegacyCfg = `schema-version: v1.3
68+
const convertedLegacyCfg = `schema-version: v1.4
6969
iac:
7070
ignore-rules:
7171
- query1
@@ -196,7 +196,7 @@ func TestReadLegacyOnly(t *testing.T) {
196196
func TestReadLegacyWithIncludeRulesOnly(t *testing.T) {
197197
tmp := t.TempDir()
198198
myCfgFile := legacyCfg + "include-queries: [query3, query4]\n"
199-
includeOnly := "schema-version: v1.3\niac:\n use-rules:\n - query3\n - query4\n"
199+
includeOnly := "schema-version: v1.4\niac:\n use-rules:\n - query3\n - query4\n"
200200
require.NoError(t, os.WriteFile(filepath.Join(tmp, LegacyConfigFileName), []byte(myCfgFile), 0644))
201201

202202
cfg, b, err := ReadConfiguration(t.Context(), tmp)

pkg/config/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ type IacConfig struct {
2020
type IacRuleConfig struct {
2121
IgnorePaths []string
2222
OnlyPaths []string
23+
Arguments map[string]any `yaml:"arguments,omitempty"`
2324
Severity *string
2425
}

pkg/engine/inspector.go

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -607,6 +607,45 @@ func (c *Inspector) GetFailedQueries() map[string]error {
607607
return maps.Clone(c.failedQueries)
608608
}
609609

610+
func ruleArgumentsValue(rc config.IacRuleConfig) (ast.Value, bool, error) {
611+
if rc.Arguments == nil {
612+
return nil, false, nil
613+
}
614+
615+
value, err := ast.InterfaceToValue(rc.Arguments)
616+
if err != nil {
617+
return nil, false, err
618+
}
619+
return value, true, nil
620+
}
621+
622+
func withRuleArguments(payload, args ast.Value) (ast.Value, error) {
623+
obj, ok := payload.(ast.Object)
624+
if !ok {
625+
return nil, fmt.Errorf("expected OPA input payload object, got %T", payload)
626+
}
627+
628+
out := ast.NewObject()
629+
_ = obj.Iter(func(k, v *ast.Term) error {
630+
out.Insert(k, v)
631+
return nil
632+
})
633+
out.Insert(ast.StringTerm("arguments"), ast.NewTerm(args))
634+
return out, nil
635+
}
636+
637+
func queryIDsFromMetadata(ctx context.Context, metadata *model.QueryMetadata) (queryID, legacyQueryID string) {
638+
queryID = DefaultQueryID
639+
legacyQueryID = DefaultQueryID
640+
if id, err := mapKeyToString(ctx, metadata.Metadata, "id", false); err == nil && id != nil {
641+
queryID = *id
642+
}
643+
if legacyID, err := mapKeyToString(ctx, metadata.Metadata, "legacyId", true); err == nil && legacyID != nil {
644+
legacyQueryID = *legacyID
645+
}
646+
return queryID, legacyQueryID
647+
}
648+
610649
func (c *Inspector) doRun(ctx context.Context, qCtx *QueryContext) (vulns []model.Vulnerability, err error) {
611650
contextLogger := logger.FromContext(ctx)
612651
defer func() {
@@ -617,7 +656,19 @@ func (c *Inspector) doRun(ctx context.Context, qCtx *QueryContext) (vulns []mode
617656
}
618657
}()
619658

620-
options := []rego.EvalOption{rego.EvalParsedInput(*qCtx.payload)}
659+
payload := *qCtx.payload
660+
queryID, legacyQueryID := queryIDsFromMetadata(ctx, &qCtx.Query.Metadata)
661+
if rc, found := lookupRuleConfig(c.ruleConfigs, queryID, legacyQueryID); found {
662+
if args, ok, err := ruleArgumentsValue(rc); err != nil {
663+
return nil, errors.Wrap(err, "Failed to prepare rule arguments for query "+queryID)
664+
} else if ok {
665+
payload, err = withRuleArguments(payload, args)
666+
if err != nil {
667+
return nil, err
668+
}
669+
}
670+
}
671+
options := []rego.EvalOption{rego.EvalParsedInput(payload)}
621672

622673
var cov *cover.Cover
623674
if c.enableCoverageReport {
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.

0 commit comments

Comments
 (0)