diff --git a/avd_docs/aws/iam/AVD-AWS-0329/docs.md b/avd_docs/aws/iam/AVD-AWS-0329/docs.md new file mode 100644 index 000000000..8678cd1a9 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0329/docs.md @@ -0,0 +1,13 @@ + +Ensures all groups have at least one member + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_WorkingWithGroupsAndUsers.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0330/docs.md b/avd_docs/aws/iam/AVD-AWS-0330/docs.md new file mode 100644 index 000000000..abfb53888 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0330/docs.md @@ -0,0 +1,13 @@ + +Ensures that an IAM role, group or user exists with specific permissions to access support center. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://docs.aws.amazon.com/awssupport/latest/user/accessing-support.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0331/docs.md b/avd_docs/aws/iam/AVD-AWS-0331/docs.md new file mode 100644 index 000000000..a4eaca6b7 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0331/docs.md @@ -0,0 +1,13 @@ + +Ensure that at least one IAM user exists so that access to your AWS services and resources is made only through IAM users instead of the root account. + +### Impact + + + +{{ remediationActions }} + +### Links +- https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0332/docs.md b/avd_docs/aws/iam/AVD-AWS-0332/docs.md new file mode 100644 index 000000000..7e02d3ff8 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0332/docs.md @@ -0,0 +1,13 @@ + +Ensures password policy enforces a password expiration + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0333/docs.md b/avd_docs/aws/iam/AVD-AWS-0333/docs.md new file mode 100644 index 000000000..afea3d728 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0333/docs.md @@ -0,0 +1,13 @@ + +Ensures password policy requires at least one lowercase letter + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0334/docs.md b/avd_docs/aws/iam/AVD-AWS-0334/docs.md new file mode 100644 index 000000000..1fb482335 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0334/docs.md @@ -0,0 +1,13 @@ + +Ensures password policy requires the use of numbers + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0335/docs.md b/avd_docs/aws/iam/AVD-AWS-0335/docs.md new file mode 100644 index 000000000..213fab592 --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0335/docs.md @@ -0,0 +1,13 @@ + +Ensures password policy requires at least one uppercase letter + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html + + diff --git a/avd_docs/aws/iam/AVD-AWS-0336/docs.md b/avd_docs/aws/iam/AVD-AWS-0336/docs.md new file mode 100644 index 000000000..e36b89abf --- /dev/null +++ b/avd_docs/aws/iam/AVD-AWS-0336/docs.md @@ -0,0 +1,13 @@ + +Ensures password policy requires the use of symbols + +### Impact + + + +{{ remediationActions }} + +### Links +- http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html + + diff --git a/internal/adapters/cloud/aws/iam/iam.go b/internal/adapters/cloud/aws/iam/iam.go index f0655a8e2..f33681155 100644 --- a/internal/adapters/cloud/aws/iam/iam.go +++ b/internal/adapters/cloud/aws/iam/iam.go @@ -74,6 +74,7 @@ func (a *adapter) adaptPasswordPolicy(state *state.State) error { if policy.PasswordReusePrevention != nil { reusePrevention = int(*policy.PasswordReusePrevention) } + maxAge := 0 if policy.MaxPasswordAge != nil { maxAge = int(*policy.MaxPasswordAge) @@ -89,6 +90,7 @@ func (a *adapter) adaptPasswordPolicy(state *state.State) error { RequireUppercase: types.Bool(policy.RequireUppercaseCharacters, metadata), RequireNumbers: types.Bool(policy.RequireNumbers, metadata), RequireSymbols: types.Bool(policy.RequireSymbols, metadata), + ExpirePasswords: types.Bool(policy.ExpirePasswords, metadata), MaxAgeDays: types.Int(maxAge, metadata), MinimumLength: types.Int(minimumLength, metadata), } diff --git a/internal/adapters/cloudformation/aws/iam/iam.go b/internal/adapters/cloudformation/aws/iam/iam.go index 4ef15ddc5..85a83e7d0 100644 --- a/internal/adapters/cloudformation/aws/iam/iam.go +++ b/internal/adapters/cloudformation/aws/iam/iam.go @@ -16,6 +16,7 @@ func Adapt(cfFile parser.FileContext) iam.IAM { RequireUppercase: defsecTypes.BoolDefault(false, defsecTypes.NewUnmanagedMetadata()), RequireNumbers: defsecTypes.BoolDefault(false, defsecTypes.NewUnmanagedMetadata()), RequireSymbols: defsecTypes.BoolDefault(false, defsecTypes.NewUnmanagedMetadata()), + ExpirePasswords: defsecTypes.BoolDefault(false, defsecTypes.NewUnmanagedMetadata()), MaxAgeDays: defsecTypes.IntDefault(0, defsecTypes.NewUnmanagedMetadata()), MinimumLength: defsecTypes.IntDefault(0, defsecTypes.NewUnmanagedMetadata()), }, diff --git a/internal/adapters/terraform/aws/iam/passwords.go b/internal/adapters/terraform/aws/iam/passwords.go index 73a09700a..5d3dc238f 100644 --- a/internal/adapters/terraform/aws/iam/passwords.go +++ b/internal/adapters/terraform/aws/iam/passwords.go @@ -59,6 +59,11 @@ func adaptPasswordPolicy(modules terraform.Modules) iam.PasswordPolicy { } else { policy.ReusePreventionCount = defsecTypes.IntDefault(0, policyBlock.GetMetadata()) } + if attr := policyBlock.GetAttribute("expire_passwords"); attr.IsNotNil() { + policy.ExpirePasswords = defsecTypes.BoolExplicit(attr.IsTrue(), attr.GetMetadata()) + } else { + policy.ExpirePasswords = defsecTypes.BoolDefault(false, policyBlock.GetMetadata()) + } if attr := policyBlock.GetAttribute("max_password_age"); attr.IsNumber() { value := attr.AsNumber() policy.MaxAgeDays = defsecTypes.IntExplicit(int(value), attr.GetMetadata()) @@ -71,6 +76,5 @@ func adaptPasswordPolicy(modules terraform.Modules) iam.PasswordPolicy { } else { policy.MinimumLength = defsecTypes.IntDefault(0, policyBlock.GetMetadata()) } - return policy } diff --git a/internal/rules/policies/cloud/policies/aws/iam/empty_groups.rego b/internal/rules/policies/cloud/policies/aws/iam/empty_groups.rego new file mode 100644 index 000000000..0862874ec --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/empty_groups.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "Empty Groups" +# description: "Ensures all groups have at least one member" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_WorkingWithGroupsAndUsers.html +# custom: +# avd_id: AVD-AWS-0329 +# provider: aws +# service: iam +# severity: HIGH +# short_code: empty_groups +# recommended_action: "Remove unused groups without users" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0329 + +deny[res] { + group := input.aws.iam.groups[_] + not group.users + res := result.new("Group does not contain any users", group) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/empty_groups_test.rego b/internal/rules/policies/cloud/policies/aws/iam/empty_groups_test.rego new file mode 100644 index 000000000..c62a73eb1 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/empty_groups_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0329 + +test_detects_not_empty_group { + r := deny with input as {"aws": {"iam": {"groups": [{"users": [{"name": {"value": "user"}}]}]}}} + count(r) == 0 +} + +test_when_empty_group { + r := deny with input as {"aws": {"iam": {"groups": [{}]}}} + count(r) == 1 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy.rego b/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy.rego new file mode 100644 index 000000000..93ec21090 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "IAM Support Policy" +# description: "Ensures that an IAM role, group or user exists with specific permissions to access support center." +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - https://docs.aws.amazon.com/awssupport/latest/user/accessing-support.html +# custom: +# avd_id: AVD-AWS-0330 +# provider: aws +# service: iam +# severity: HIGH +# short_code: iam_support_policy +# recommended_action: "Ensure that an IAM role has permission to access support center." +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0330 + +deny[res] { + found := [policy| policy = input.aws.iam.policies[_]; policy.name.value == "AWSSupportAccess"] + count(found) == 0 + res := result.new("No role, user or group attached to the AWSSupportAccess policy", "") +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy_test.rego b/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy_test.rego new file mode 100644 index 000000000..d896e1002 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/iam_support_policy_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0330 + +test_detects_has_not_support_policy { + r := deny with input as {"aws": {"iam": {"policies": [{"name": {"value": "AWSSupportAccess"}}]}}} + count(r) == 0 +} + +test_when_has_support_policy { + r := deny with input as {"aws": {"iam": {"policies": [{"name": {"value": "s3-migration"}}]}}} + count(r) == 1 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/iam_user_present.rego b/internal/rules/policies/cloud/policies/aws/iam/iam_user_present.rego new file mode 100644 index 000000000..acd003960 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/iam_user_present.rego @@ -0,0 +1,24 @@ +# METADATA +# title: "IAM User Present" +# description: "Ensure that at least one IAM user exists so that access to your AWS services and resources is made only through IAM users instead of the root account." +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html +# custom: +# avd_id: AVD-AWS-0331 +# provider: aws +# service: iam +# severity: HIGH +# short_code: iam_user_present +# recommended_action: "Create IAM user(s) and use them to access AWS services and resources." +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0331 + +deny[res] { + count(input.aws.iam.users) == 0 + res := result.new("No users found", "") +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/iam_user_present_test.rego b/internal/rules/policies/cloud/policies/aws/iam/iam_user_present_test.rego new file mode 100644 index 000000000..ec786e96c --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/iam_user_present_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0331 + +test_detects_has_users{ + r := deny with input as {"aws": {"iam": {"users": [{"name": {"value": "user"}}]}}} + count(r) == 0 +} + +test_when_has_no_user{ + r := deny with input as {"aws": {"iam": {"users": []}}} + count(r) == 1 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_expiration.rego b/internal/rules/policies/cloud/policies/aws/iam/password_expiration.rego new file mode 100644 index 000000000..a49e7d62a --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_expiration.rego @@ -0,0 +1,30 @@ +# METADATA +# title: "Password Expiration" +# description: "Ensures password policy enforces a password expiration" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html +# custom: +# avd_id: AVD-AWS-0332 +# provider: aws +# service: iam +# severity: HIGH +# short_code: password_expiration +# recommended_action: "Enable password expiration for the account" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0332 + +deny[res] { + policy := input.aws.iam.passwordpolicy + not policy.expirepasswords.value + res := result.new("Password expiration policy is not set to expire passwords", policy.expirepasswords) +}{ + policy := input.aws.iam.passwordpolicy + policy.expirepasswords.value + policy.maxagedays.value > 180 + res := result.new("Password expiration days is greater than 180", policy.maxagedays) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_expiration_test.rego b/internal/rules/policies/cloud/policies/aws/iam/password_expiration_test.rego new file mode 100644 index 000000000..f4aaac45c --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_expiration_test.rego @@ -0,0 +1,16 @@ +package builtin.aws.iam.aws0332 + +test_detects_has_no_password_policy { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"expirepasswords": {"value": false}}}}} + count(r) == 1 +} + +test_when_has_expiration_greater_than_180{ + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"expirepasswords": {"value": true},"maxagedays": {"value": 185}}}}} + count(r) == 1 +} + +test_when_has_expiration_suitable{ + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"expirepasswords": {"value": true},"maxagedays": {"value": 90}}}}} + count(r) == 0 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase.rego new file mode 100644 index 000000000..d1a33658b --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "Password Requires Lowercase" +# description: "Ensures password policy requires at least one lowercase letter" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html +# custom: +# avd_id: AVD-AWS-0333 +# provider: aws +# service: iam +# severity: HIGH +# short_code: password_requires_lowercase +# recommended_action: "Update the password policy to require the use of lowercase letters" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0333 + +deny[res] { + policy := input.aws.iam.passwordpolicy + not policy.requirelowercase.value + res := result.new("Password policy does not require lowercase characters", policy.requirelowercase) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase_test.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase_test.rego new file mode 100644 index 000000000..8b7eb63ee --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_lowercase_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0333 + +test_detects_not_requires_lowercase { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requirelowercase": {"value": false}}}}} + count(r) == 1 +} + +test_detects_requires_lowercase { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requirelowercase": {"value": true}}}}} + count(r) == 0 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers.rego new file mode 100644 index 000000000..d5251dc89 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "Password Requires Numbers" +# description: "Ensures password policy requires the use of numbers" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html +# custom: +# avd_id: AVD-AWS-0334 +# provider: aws +# service: iam +# severity: HIGH +# short_code: password_requires_lowercase +# recommended_action: "Update the password policy to require the use of numbers" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0334 + +deny[res] { + policy := input.aws.iam.passwordpolicy + not policy.requirenumbers.value + res := result.new("Password policy does not require numbers", policy.requirenumbers) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers_test.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers_test.rego new file mode 100644 index 000000000..59a835c8b --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_numbers_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0334 + +test_detects_not_requires_numbers { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requirenumbers": {"value": false}}}}} + count(r) == 1 +} + +test_detects_requires_numbers { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requirenumbers": {"value": true}}}}} + count(r) == 0 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols.rego new file mode 100644 index 000000000..bae990e3e --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "Password Requires Symbols" +# description: "Ensures password policy requires the use of symbols" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html +# custom: +# avd_id: AVD-AWS-0336 +# provider: aws +# service: iam +# severity: HIGH +# short_code: password_requires_symbols +# recommended_action: "Update the password policy to require the use of symbols" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0336 + +deny[res] { + policy := input.aws.iam.passwordpolicy + not policy.requiresymbols.value + res := result.new("Password policy does not require symbols", policy.requiresymbols) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols_test.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols_test.rego new file mode 100644 index 000000000..f4bdd1bd9 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_symbols_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0336 + +test_detects_not_requires_symbols { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requiresymbols": {"value": false}}}}} + count(r) == 1 +} + +test_detects_requires_symbols { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requiresymbols": {"value": true}}}}} + count(r) == 0 +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase.rego new file mode 100644 index 000000000..246abe387 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase.rego @@ -0,0 +1,25 @@ +# METADATA +# title: "Password Requires Uppercase" +# description: "Ensures password policy requires at least one uppercase letter" +# scope: package +# schemas: +# - input: schema.input +# related_resources: +# - http://docs.aws.amazon.com/IAM/latest/UserGuide/Using_ManagingPasswordPolicies.html +# custom: +# avd_id: AVD-AWS-0335 +# provider: aws +# service: iam +# severity: HIGH +# short_code: password_requires_uppercase +# recommended_action: "Update the password policy to require the use of uppercase letters" +# input: +# selector: +# - type: cloud +package builtin.aws.iam.aws0335 + +deny[res] { + policy := input.aws.iam.passwordpolicy + not policy.requireuppercase.value + res := result.new("Password policy does not require uppercase characters", policy.requireuppercase) +} diff --git a/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase_test.rego b/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase_test.rego new file mode 100644 index 000000000..37c5f1cb1 --- /dev/null +++ b/internal/rules/policies/cloud/policies/aws/iam/password_requires_uppercase_test.rego @@ -0,0 +1,11 @@ +package builtin.aws.iam.aws0335 + +test_detects_not_requires_uppercase { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requireuppercase": {"value": false}}}}} + count(r) == 1 +} + +test_detects_requires_uppercase { + r := deny with input as {"aws": {"iam": {"passwordpolicy": {"requireuppercase": {"value": true}}}}} + count(r) == 0 +} diff --git a/pkg/providers/aws/iam/passwords.go b/pkg/providers/aws/iam/passwords.go index 790598f56..6e8bfef68 100755 --- a/pkg/providers/aws/iam/passwords.go +++ b/pkg/providers/aws/iam/passwords.go @@ -11,6 +11,7 @@ type PasswordPolicy struct { RequireUppercase defsecTypes.BoolValue RequireNumbers defsecTypes.BoolValue RequireSymbols defsecTypes.BoolValue + ExpirePasswords defsecTypes.BoolValue MaxAgeDays defsecTypes.IntValue MinimumLength defsecTypes.IntValue } diff --git a/pkg/rego/schemas/cloud.json b/pkg/rego/schemas/cloud.json index aecafa57b..d0a68808b 100644 --- a/pkg/rego/schemas/cloud.json +++ b/pkg/rego/schemas/cloud.json @@ -1899,6 +1899,10 @@ "github.com.aquasecurity.defsec.pkg.providers.aws.iam.PasswordPolicy": { "type": "object", "properties": { + "expirepasswords": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.types.BoolValue" + }, "maxagedays": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.defsec.pkg.types.IntValue"