Repository: tfsec/tfsec
Pull request #402 (2020-10-08 08:19:29)

iam password reuse prevention
- warns if the password reuse count is less than 5 or not present


Patch:
diff --git a/README.md b/README.md
index f38be8ac0..366f9dbea 100644
--- a/README.md
+++ b/README.md
@@ -162,6 +162,7 @@ there are also checks which are provider agnostic.
 | AWS034  | aws      | Elasticsearch domain endpoint is using outdated TLS policy.
 | AWS035  | aws      | Unencrypted Elasticache Replication Group.
 | AWS036  | aws      | Elasticache Replication Group uses unencrypted traffic.
+| AWS037  | aws      | IAM password policy doesn't enforce reuse prevention.
 | AZU001  | azurerm  | An inbound network security rule allows traffic from `/0`.
 | AZU002  | azurerm  | An outbound network security rule allows traffic to `/0`.
 | AZU003  | azurerm  | Unencrypted managed disk.
diff --git a/internal/app/tfsec/checks/aws_enforce_password_reuse_prevention.go b/internal/app/tfsec/checks/aws_enforce_password_reuse_prevention.go
new file mode 100644
index 000000000..fd30cd31a
--- /dev/null
+++ b/internal/app/tfsec/checks/aws_enforce_password_reuse_prevention.go
@@ -0,0 +1,47 @@
+package checks
+
+import (
+	"fmt"
+	"github.com/zclconf/go-cty/cty"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+)
+
+// AWSBadBucketACL See https://github.com/tfsec/tfsec#included-checks for check info
+const AWSIAMPasswordReusePrevention scanner.RuleID = "AWS037"
+const AWSIAMPasswordReusePreventionDescription scanner.RuleDescription = "IAM Password policy should prevent password reuse."
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code:           AWSIAMPasswordReusePrevention,
+		Description:    AWSIAMPasswordReusePreventionDescription,
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_iam_account_password_policy"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+			if attr := block.GetAttribute("password_reuse_prevention"); attr == nil {
+				return []scanner.Result{
+					check.NewResult(
+						fmt.Sprintf("Resource '%s' does not have a password reuse prevention count set.", block.Name()),
+						block.Range(),
+						scanner.SeverityWarning,
+					),
+				}
+			} else if attr.Value().Type() == cty.Number {
+				value, _ := attr.Value().AsBigFloat().Float64()
+				if value < 5 {
+					return []scanner.Result{
+						check.NewResult(
+							fmt.Sprintf("Resource '%s' has a password reuse count less than 5.", block.Name()),
+							block.Range(),
+							scanner.SeverityWarning,
+						),
+					}
+				}
+			}
+			return nil
+		},
+	})
+}


Test patch:
diff --git a/internal/app/tfsec/aws_iam_no_password_reset_prevention_set_test.go b/internal/app/tfsec/aws_iam_no_password_reset_prevention_set_test.go
new file mode 100644
index 000000000..920cb97fe
--- /dev/null
+++ b/internal/app/tfsec/aws_iam_no_password_reset_prevention_set_test.go
@@ -0,0 +1,68 @@
+package tfsec
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSIamPasswordReusePrevention(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleID
+		mustExcludeResultCode scanner.RuleID
+	}{
+		{
+			name: "check aws_iam_account_password_policy has password_reuse_prevention set",
+			source: `
+resource "aws_iam_account_password_policy" "strict" {
+  minimum_password_length        = 8
+  require_lowercase_characters   = true
+  require_numbers                = true
+  require_uppercase_characters   = true
+  require_symbols                = true
+  allow_users_to_change_password = true
+}`,
+			mustIncludeResultCode: checks.AWSIAMPasswordReusePrevention,
+		},
+		{
+			name: "check aws_iam_account_password_policy has password_reuse_prevention less than 5",
+			source: `
+resource "aws_iam_account_password_policy" "strict" {
+  minimum_password_length        = 8
+  require_lowercase_characters   = true
+  require_numbers                = true
+  require_uppercase_characters   = true
+  require_symbols                = true
+  allow_users_to_change_password = true
+  password_reuse_prevention      = 4
+}`,
+			mustIncludeResultCode: checks.AWSIAMPasswordReusePrevention,
+		},
+		{
+			name: "check aws_iam_account_password_policy has password_reuse_prevention greater than 5",
+			source: `
+resource "aws_iam_account_password_policy" "strict" {
+  minimum_password_length        = 8
+  require_lowercase_characters   = true
+  require_numbers                = true
+  require_uppercase_characters   = true
+  require_symbols                = true
+  allow_users_to_change_password = true
+  password_reuse_prevention      = 5
+}`,
+			mustExcludeResultCode: checks.AWSIAMPasswordReusePrevention,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			results := scanSource(test.source)
+			assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+}