diff --git a/builtin/providers/aws/provider.go b/builtin/providers/aws/provider.go index b26cbc503..fdf84166f 100644 --- a/builtin/providers/aws/provider.go +++ b/builtin/providers/aws/provider.go @@ -154,6 +154,7 @@ func Provider() terraform.ResourceProvider { "aws_flow_log": resourceAwsFlowLog(), "aws_glacier_vault": resourceAwsGlacierVault(), "aws_iam_access_key": resourceAwsIamAccessKey(), + "aws_iam_account_password_policy": resourceAwsIamAccountPasswordPolicy(), "aws_iam_group_policy": resourceAwsIamGroupPolicy(), "aws_iam_group": resourceAwsIamGroup(), "aws_iam_group_membership": resourceAwsIamGroupMembership(), diff --git a/builtin/providers/aws/resource_aws_iam_account_password_policy.go b/builtin/providers/aws/resource_aws_iam_account_password_policy.go new file mode 100644 index 000000000..6f4f092f9 --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_account_password_policy.go @@ -0,0 +1,165 @@ +package aws + +import ( + "fmt" + "log" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/iam" + + "github.com/hashicorp/terraform/helper/schema" +) + +func resourceAwsIamAccountPasswordPolicy() *schema.Resource { + return &schema.Resource{ + Create: resourceAwsIamAccountPasswordPolicyUpdate, + Read: resourceAwsIamAccountPasswordPolicyRead, + Update: resourceAwsIamAccountPasswordPolicyUpdate, + Delete: resourceAwsIamAccountPasswordPolicyDelete, + + Schema: map[string]*schema.Schema{ + "allow_users_to_change_password": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "expire_passwords": &schema.Schema{ + Type: schema.TypeBool, + Computed: true, + }, + "hard_expiry": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "max_password_age": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "minimum_password_length": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Default: 6, + }, + "password_reuse_prevention": &schema.Schema{ + Type: schema.TypeInt, + Optional: true, + Computed: true, + }, + "require_lowercase_characters": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "require_numbers": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "require_symbols": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + "require_uppercase_characters": &schema.Schema{ + Type: schema.TypeBool, + Optional: true, + Computed: true, + }, + }, + } +} + +func resourceAwsIamAccountPasswordPolicyUpdate(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.UpdateAccountPasswordPolicyInput{} + + if v, ok := d.GetOk("allow_users_to_change_password"); ok { + input.AllowUsersToChangePassword = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("hard_expiry"); ok { + input.HardExpiry = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("max_password_age"); ok { + input.MaxPasswordAge = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("minimum_password_length"); ok { + input.MinimumPasswordLength = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("password_reuse_prevention"); ok { + input.PasswordReusePrevention = aws.Int64(int64(v.(int))) + } + if v, ok := d.GetOk("require_lowercase_characters"); ok { + input.RequireLowercaseCharacters = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("require_numbers"); ok { + input.RequireNumbers = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("require_symbols"); ok { + input.RequireSymbols = aws.Bool(v.(bool)) + } + if v, ok := d.GetOk("require_uppercase_characters"); ok { + input.RequireUppercaseCharacters = aws.Bool(v.(bool)) + } + + log.Printf("[DEBUG] Updating IAM account password policy: %s", input) + _, err := iamconn.UpdateAccountPasswordPolicy(input) + if err != nil { + return fmt.Errorf("Error updating IAM Password Policy: %s", err) + } + log.Println("[DEBUG] IAM account password policy updated") + + d.SetId("iam-account-password-policy") + + return resourceAwsIamAccountPasswordPolicyRead(d, meta) +} + +func resourceAwsIamAccountPasswordPolicyRead(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + input := &iam.GetAccountPasswordPolicyInput{} + resp, err := iamconn.GetAccountPasswordPolicy(input) + if err != nil { + awsErr, ok := err.(awserr.Error) + if ok && awsErr.Code() == "NoSuchEntity" { + log.Printf("[WARN] IAM account password policy is gone (i.e. default)") + d.SetId("") + return nil + } + return fmt.Errorf("Error reading IAM account password policy: %s", err) + } + + log.Printf("[DEBUG] Received IAM account password policy: %s", resp) + + policy := resp.PasswordPolicy + + d.Set("allow_users_to_change_password", policy.AllowUsersToChangePassword) + d.Set("expire_passwords", policy.ExpirePasswords) + d.Set("hard_expiry", policy.HardExpiry) + d.Set("max_password_age", policy.MaxPasswordAge) + d.Set("minimum_password_length", policy.MinimumPasswordLength) + d.Set("password_reuse_prevention", policy.PasswordReusePrevention) + d.Set("require_lowercase_characters", policy.RequireLowercaseCharacters) + d.Set("require_numbers", policy.RequireNumbers) + d.Set("require_symbols", policy.RequireSymbols) + d.Set("require_uppercase_characters", policy.RequireUppercaseCharacters) + + return nil +} + +func resourceAwsIamAccountPasswordPolicyDelete(d *schema.ResourceData, meta interface{}) error { + iamconn := meta.(*AWSClient).iamconn + + log.Println("[DEBUG] Deleting IAM account password policy") + input := &iam.DeleteAccountPasswordPolicyInput{} + if _, err := iamconn.DeleteAccountPasswordPolicy(input); err != nil { + return fmt.Errorf("Error deleting IAM Password Policy: %s", err) + } + d.SetId("") + log.Println("[DEBUG] Deleted IAM account password policy") + + return nil +} diff --git a/builtin/providers/aws/resource_aws_iam_account_password_policy_test.go b/builtin/providers/aws/resource_aws_iam_account_password_policy_test.go new file mode 100644 index 000000000..b909fc05a --- /dev/null +++ b/builtin/providers/aws/resource_aws_iam_account_password_policy_test.go @@ -0,0 +1,105 @@ +package aws + +import ( + "fmt" + "testing" + + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/hashicorp/terraform/helper/resource" + "github.com/hashicorp/terraform/terraform" +) + +func TestAccAWSIAMAccountPasswordPolicy_basic(t *testing.T) { + var policy iam.GetAccountPasswordPolicyOutput + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAWSIAMAccountPasswordPolicyDestroy, + Steps: []resource.TestStep{ + resource.TestStep{ + Config: testAccAWSIAMAccountPasswordPolicy, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSIAMAccountPasswordPolicyExists("aws_iam_account_password_policy.default", &policy), + resource.TestCheckResourceAttr("aws_iam_account_password_policy.default", "minimum_password_length", "8"), + ), + }, + resource.TestStep{ + Config: testAccAWSIAMAccountPasswordPolicy_modified, + Check: resource.ComposeTestCheckFunc( + testAccCheckAWSIAMAccountPasswordPolicyExists("aws_iam_account_password_policy.default", &policy), + resource.TestCheckResourceAttr("aws_iam_account_password_policy.default", "minimum_password_length", "7"), + ), + }, + }, + }) +} + +func testAccCheckAWSIAMAccountPasswordPolicyDestroy(s *terraform.State) error { + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + for _, rs := range s.RootModule().Resources { + if rs.Type != "aws_iam_account_password_policy" { + continue + } + + // Try to get policy + _, err := iamconn.GetAccountPasswordPolicy(&iam.GetAccountPasswordPolicyInput{}) + if err == nil { + return fmt.Errorf("still exist.") + } + + // Verify the error is what we want + awsErr, ok := err.(awserr.Error) + if !ok { + return err + } + if awsErr.Code() != "NoSuchEntity" { + return err + } + } + + return nil +} + +func testAccCheckAWSIAMAccountPasswordPolicyExists(n string, res *iam.GetAccountPasswordPolicyOutput) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("Not found: %s", n) + } + + if rs.Primary.ID == "" { + return fmt.Errorf("No policy ID is set") + } + + iamconn := testAccProvider.Meta().(*AWSClient).iamconn + + resp, err := iamconn.GetAccountPasswordPolicy(&iam.GetAccountPasswordPolicyInput{}) + if err != nil { + return err + } + + *res = *resp + + return nil + } +} + +const testAccAWSIAMAccountPasswordPolicy = ` +resource "aws_iam_account_password_policy" "default" { + allow_users_to_change_password = true + minimum_password_length = 8 + require_numbers = true +} +` +const testAccAWSIAMAccountPasswordPolicy_modified = ` +resource "aws_iam_account_password_policy" "default" { + allow_users_to_change_password = true + minimum_password_length = 7 + require_numbers = false + require_symbols = true + require_uppercase_characters = true +} +` diff --git a/website/source/docs/providers/aws/r/iam_account_password_policy.html.markdown b/website/source/docs/providers/aws/r/iam_account_password_policy.html.markdown new file mode 100644 index 000000000..072f2c48c --- /dev/null +++ b/website/source/docs/providers/aws/r/iam_account_password_policy.html.markdown @@ -0,0 +1,51 @@ +--- +layout: "aws" +page_title: "AWS: aws_iam_account_password_policy" +sidebar_current: "docs-aws-resource-iam-account-password-policy" +description: |- + Manages Password Policy for the AWS Account. +--- + +# aws\_iam\_account_password_policy + +-> **Note:** There is only a single policy allowed per AWS account. An existing policy will be lost when using this resource as an effect of this limitation. + +Manages Password Policy for the AWS Account. +See more about [Account Password Policy](http://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_passwords_account-policy.html) +in the official AWS docs. + +## Example Usage + +``` +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 +} +``` + +## Argument Reference + +The following arguments are supported: + +* `allow_users_to_change_password` - (Optional) Whether to allow users to change their own password +* `hard_expiry` - (Optional) Whether users are prevented from setting a new password after their password has expired + (i.e. require administrator reset) +* `max_password_age` - (Optional) The number of days that an user password is valid. +* `minimum_password_length` - (Optional) Minimum length to require for user passwords. +* `password_reuse_prevention` - (Optional) The number of previous passwords that users are prevented from reusing. +* `require_lowercase_characters` - (Optional) Whether to require lowercase characters for user passwords. +* `require_numbers` - (Optional) Whether to require numbers for user passwords. +* `require_symbols` - (Optional) Whether to require symbols for user passwords. +* `require_uppercase_characters` - (Optional) Whether to require uppercase characters for user passwords. + +## Attributes Reference + +The following attributes are exported: + +* `expire_passwords` - Indicates whether passwords in the account expire. + Returns `true` if `max_password_age` contains a value greater than `0`. + Returns `false` if it is `0` or _not present_. diff --git a/website/source/layouts/aws.erb b/website/source/layouts/aws.erb index 4dbbd1d16..9643b18ff 100644 --- a/website/source/layouts/aws.erb +++ b/website/source/layouts/aws.erb @@ -283,6 +283,10 @@ aws_iam_access_key +