Skip to content

Commit 5420040

Browse files
committed
Validate against secret attributes
1 parent 5624739 commit 5420040

4 files changed

+198
-0
lines changed
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# aws_secretsmanager_secret_version_secret_string
2+
3+
Disallows using the `secret_string` attribute, in favor of the `secret_string_wo` attribute.
4+
5+
## Example
6+
7+
```hcl
8+
resource "aws_secretsmanager_secret_version" "test" {
9+
secret_string = var.secret
10+
}
11+
```
12+
13+
```
14+
$ tflint
15+
1 issue(s) found:
16+
17+
Warning: "secret_string" is a non-ephemeral attribute, which means this secret is stored in state. Please use "secret_string_wo". (aws_secretsmanager_secret_version_secret_string)
18+
19+
on test.tf line 3:
20+
3: secret_string = var.secret
21+
22+
```
23+
24+
## Why
25+
26+
Saving secrets to state or plan files is a bad practice. It can cause serious security issues. Keeping secrets from these files is possible in most of the cases by using write-only attributes.
27+
28+
## How To Fix
29+
30+
Replace the `secret_string` with `secret_string_wo` and `secret_string_wo_version`, either via using an ephemeral resource as source or using an ephemeral variable:
31+
32+
```hcl
33+
ephemeral "random_password" "test" {
34+
length = 32
35+
override_special = "!#$%&*()-_=+[]{}<>:?"
36+
}
37+
38+
resource "aws_secretsmanager_secret_version" "test" {
39+
secret_string_wo = ephemeral.random_password.test.value
40+
secret_string_wo_version = 1
41+
}
42+
```
43+
44+
```hcl
45+
variable "test" {
46+
type = string
47+
ephemeral = true # Optional, non-ephemeral values can also be used for write-only attributes
48+
description = "Input variable for a secret"
49+
}
50+
51+
resource "aws_secretsmanager_secret_version" "test" {
52+
secret_string_wo = var.test
53+
secret_string_wo_version = 1
54+
}
55+
```
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package rules
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/terraform-linters/tflint-plugin-sdk/hclext"
7+
"github.com/terraform-linters/tflint-plugin-sdk/tflint"
8+
"github.com/terraform-linters/tflint-ruleset-aws/project"
9+
"github.com/zclconf/go-cty/cty"
10+
)
11+
12+
// AwsSecretsmanagerSecretVersionSecretStringRule checks if the secret_string attribute is used in secretsmanager_secret_version
13+
// It emits a warning if the attribute is used, as it is a non-ephemeral attribute and the secret is stored in state
14+
// It suggests using secret_string_wo instead
15+
type AwsSecretsmanagerSecretVersionSecretStringRule struct {
16+
tflint.DefaultRule
17+
18+
resourceType string
19+
attributeName string
20+
writeOnlyAttributeName string
21+
}
22+
23+
// NewAwsSecretsmanagerSecretVersionSecretStringRule returns new rule with default attributes
24+
func NewAwsSecretsmanagerSecretVersionSecretStringRule() *AwsSecretsmanagerSecretVersionSecretStringRule {
25+
return &AwsSecretsmanagerSecretVersionSecretStringRule{
26+
resourceType: "aws_secretsmanager_secret_version",
27+
attributeName: "secret_string",
28+
writeOnlyAttributeName: "secret_string_wo",
29+
}
30+
}
31+
32+
// Name returns the rule name
33+
func (r *AwsSecretsmanagerSecretVersionSecretStringRule) Name() string {
34+
return "aws_secretsmanager_secret_version_secret_string"
35+
}
36+
37+
// Enabled returns whether the rule is enabled by default
38+
func (r *AwsSecretsmanagerSecretVersionSecretStringRule) Enabled() bool {
39+
return true
40+
}
41+
42+
// Severity returns the rule severity
43+
func (r *AwsSecretsmanagerSecretVersionSecretStringRule) Severity() tflint.Severity {
44+
return tflint.WARNING
45+
}
46+
47+
// Link returns the rule reference link
48+
func (r *AwsSecretsmanagerSecretVersionSecretStringRule) Link() string {
49+
return project.ReferenceLink(r.Name())
50+
}
51+
52+
// Check checks whether the secret string attribute exists
53+
func (r *AwsSecretsmanagerSecretVersionSecretStringRule) Check(runner tflint.Runner) error {
54+
resources, err := runner.GetResourceContent(r.resourceType, &hclext.BodySchema{
55+
Attributes: []hclext.AttributeSchema{
56+
{Name: r.attributeName},
57+
},
58+
}, nil)
59+
if err != nil {
60+
return err
61+
}
62+
63+
for _, resource := range resources.Blocks {
64+
attribute, exists := resource.Body.Attributes[r.attributeName]
65+
if !exists {
66+
continue
67+
}
68+
69+
err := runner.EvaluateExpr(attribute.Expr, func(val cty.Value) error {
70+
if !val.IsNull() {
71+
runner.EmitIssue(
72+
r,
73+
fmt.Sprintf("\"%s\" is a non-ephemeral attribute, which means this secret is stored in state. Please use write-only attribute \"%s\".", r.attributeName, r.writeOnlyAttributeName),
74+
attribute.Expr.Range(),
75+
)
76+
}
77+
return nil
78+
}, nil)
79+
if err != nil {
80+
return err
81+
}
82+
}
83+
84+
return nil
85+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package rules
2+
3+
import (
4+
"testing"
5+
6+
hcl "github.com/hashicorp/hcl/v2"
7+
"github.com/terraform-linters/tflint-plugin-sdk/helper"
8+
)
9+
10+
func Test_AwsSecretsmanagerSecretVersionSecretString(t *testing.T) {
11+
cases := []struct {
12+
Name string
13+
Content string
14+
Expected helper.Issues
15+
}{
16+
{
17+
Name: "basic",
18+
Content: `
19+
resource "aws_secretsmanager_secret_version" "test" {
20+
secret_string = "test"
21+
}
22+
`,
23+
Expected: helper.Issues{
24+
{
25+
Rule: NewAwsSecretsmanagerSecretVersionSecretStringRule(),
26+
Message: `"secret_string" is a non-ephemeral attribute, which means this secret is stored in state. Please use write-only attribute "secret_string_wo".`,
27+
Range: hcl.Range{
28+
Filename: "resource.tf",
29+
Start: hcl.Pos{Line: 3, Column: 19},
30+
End: hcl.Pos{Line: 3, Column: 25},
31+
},
32+
},
33+
},
34+
},
35+
{
36+
Name: "everything is fine",
37+
Content: `
38+
resource "aws_secretsmanager_secret_version" "test" {
39+
secret_string_wo = "test"
40+
}
41+
`,
42+
Expected: helper.Issues{},
43+
},
44+
}
45+
46+
rule := NewAwsSecretsmanagerSecretVersionSecretStringRule()
47+
48+
for _, tc := range cases {
49+
runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content})
50+
51+
if err := rule.Check(runner); err != nil {
52+
t.Fatalf("Unexpected error occurred: %s", err)
53+
}
54+
55+
helper.AssertIssues(t, tc.Expected, runner.Issues)
56+
}
57+
}

rules/provider.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var manualRules = []tflint.Rule{
4343
NewAwsProviderMissingDefaultTagsRule(),
4444
NewAwsSecurityGroupInlineRulesRule(),
4545
NewAwsSecurityGroupRuleDeprecatedRule(),
46+
NewAwsSecretsmanagerSecretVersionSecretStringRule(),
4647
}
4748

4849
// Rules is a list of all rules

0 commit comments

Comments
 (0)