Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

provider/aws: Added aws_iam_login_profile custom password setting #10734

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 51 additions & 18 deletions builtin/providers/aws/resource_aws_iam_user_login_profile.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,21 +28,28 @@ func resourceAwsIamUserLoginProfile() *schema.Resource {
Required: true,
},
"pgp_key": {
Type: schema.TypeString,
Required: true,
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"password"},
},
"password": {
Type: schema.TypeString,
Optional: true,
Sensitive: true,
ConflictsWith: []string{"pgp_key", "password_length"},
},
"password_reset_required": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"password_length": {
Type: schema.TypeInt,
Optional: true,
Default: 20,
ValidateFunc: validateAwsIamLoginProfilePasswordLength,
Type: schema.TypeInt,
Optional: true,
Default: 20,
ValidateFunc: validateAwsIamLoginProfilePasswordLength,
ConflictsWith: []string{"password"},
},

"key_fingerprint": {
Type: schema.TypeString,
Computed: true,
Expand Down Expand Up @@ -100,14 +107,20 @@ func generatePassword(length int) string {
func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

var (
initialPassword string
fingerprint string
encrypted string
)

encryptionKey, err := encryption.RetrieveGPGKey(d.Get("pgp_key").(string))
if err != nil {
return err
}

username := d.Get("user").(string)
passwordResetRequired := d.Get("password_reset_required").(bool)
passwordLength := d.Get("password_length").(int)
password, hasPassword := d.GetOk("password")

_, err = iamconn.GetLoginProfile(&iam.GetLoginProfileInput{
UserName: aws.String(username),
Expand All @@ -118,16 +131,26 @@ func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface
// resource creation diffs) - we will never modify it, but obviously cannot
// set the password.
d.SetId(username)
d.Set("key_fingerprint", "")
d.Set("encrypted_password", "")
if hasPassword {
d.Set("password", "")
} else {
d.Set("key_fingerprint", "")
d.Set("encrypted_password", "")
}
return nil
}
}

initialPassword := generatePassword(passwordLength)
fingerprint, encrypted, err := encryption.EncryptValue(encryptionKey, initialPassword, "Password")
if err != nil {
return err
if hasPassword {
p := []byte(password.(string))
initialPassword = string(p)
} else {
passwordLength := d.Get("password_length").(int)
initialPassword = generatePassword(passwordLength)
fingerprint, encrypted, err = encryption.EncryptValue(encryptionKey, initialPassword, "Password")
if err != nil {
return err
}
}

request := &iam.CreateLoginProfileInput{
Expand All @@ -144,15 +167,25 @@ func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface
// resource creation diffs) - we will never modify it, but obviously cannot
// set the password.
d.SetId(username)
d.Set("key_fingerprint", "")
d.Set("encrypted_password", "")
if hasPassword {
d.Set("password", "")
} else {
d.Set("key_fingerprint", "")
d.Set("encrypted_password", "")
}
return nil
}
return errwrap.Wrapf(fmt.Sprintf("Error creating IAM User Login Profile for %q: {{err}}", username), err)
}

d.SetId(*createResp.LoginProfile.UserName)
d.Set("key_fingerprint", fingerprint)
d.Set("encrypted_password", encrypted)

if hasPassword {
d.Set("password", password)
} else {
d.Set("key_fingerprint", fingerprint)
d.Set("encrypted_password", encrypted)
}

return nil
}
76 changes: 54 additions & 22 deletions builtin/providers/aws/resource_aws_iam_user_login_profile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,27 @@ func TestAccAWSUserLoginProfile_notAKey(t *testing.T) {
{
// We own this account but it doesn't have any key associated with it
Config: testAccAWSUserLoginProfileConfig(username, "/", "lolimnotakey"),
ExpectError: regexp.MustCompile(`Error encrypting password`),
ExpectError: regexp.MustCompile(`Error encrypting Password`),
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There was a typo here for password, with error:

* aws_iam_user_login_profile.user: Error encrypting Password: Error parsing given PGP key: unexpected EOF

},
},
})
}

func TestAccAWSUserLoginProfile_customPassword(t *testing.T) {
var conf iam.GetLoginProfileOutput

username := fmt.Sprintf("test-user-%d", acctest.RandInt())

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckAWSUserLoginProfileDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSUserLoginProfileConfigCustomPassword(username, "/", "MyP@ssw0rd@#$!@#$^&*()_+-=[]{}|'%"),
Check: resource.ComposeTestCheckFunc(
testAccCheckAWSUserLoginProfileExists("aws_iam_user_login_profile.user", &conf),
),
},
},
})
Expand Down Expand Up @@ -169,7 +189,7 @@ func testDecryptPasswordAndTest(nProfile, nAccessKey, key string) resource.TestC
NewPassword: aws.String(generatePassword(20)),
})
if err != nil {
if awserr, ok := err.(awserr.Error); ok && awserr.Code() == "InvalidClientTokenId" {
if awserr, ok := err.(awserr.Error); ok && (awserr.Code() == "InvalidClientTokenId" || awserr.Code() == "EntityTemporarilyUnmodifiable") {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required since I encountered this error:

Check failed: Check 2/2 error: Error changing decrypted password: EntityTemporarilyUnmodifiable: Login Profile for User test-user-5423997845776594466 cannot be modified while login profile is being created.

return resource.RetryableError(err)
}

Expand Down Expand Up @@ -206,46 +226,58 @@ func testAccCheckAWSUserLoginProfileExists(n string, res *iam.GetLoginProfileOut
}
}

func testAccAWSUserLoginProfileConfig(r, p, key string) string {
return fmt.Sprintf(`
const testAccAWSUserLoginProfileBaseConfig = `
resource "aws_iam_user" "user" {
name = "%s"
path = "%s"
force_destroy = true
name = "%s"
path = "%s"
force_destroy = true
}

data "aws_caller_identity" "current" {}

data "aws_iam_policy_document" "user" {
statement {
effect = "Allow"
actions = ["iam:GetAccountPasswordPolicy"]
resources = ["*"]
}
statement {
effect = "Allow"
actions = ["iam:ChangePassword"]
resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/&{aws:username}"]
}
statement {
effect = "Allow"
actions = ["iam:GetAccountPasswordPolicy"]
resources = ["*"]
}
statement {
effect = "Allow"
actions = ["iam:ChangePassword"]
resources = ["arn:aws:iam::${data.aws_caller_identity.current.account_id}:user/&{aws:username}"]
}
}

resource "aws_iam_user_policy" "user" {
name = "AllowChangeOwnPassword"
user = "${aws_iam_user.user.name}"
policy = "${data.aws_iam_policy_document.user.json}"
name = "AllowChangeOwnPassword"
user = "${aws_iam_user.user.name}"
policy = "${data.aws_iam_policy_document.user.json}"
}

resource "aws_iam_access_key" "user" {
user = "${aws_iam_user.user.name}"
user = "${aws_iam_user.user.name}"
}
`

func testAccAWSUserLoginProfileConfig(r, p, key string) string {
return fmt.Sprintf(testAccAWSUserLoginProfileBaseConfig+`
resource "aws_iam_user_login_profile" "user" {
user = "${aws_iam_user.user.name}"
pgp_key = "%s"
pgp_key = <<EOF
%sEOF
}
`, r, p, key)
}

func testAccAWSUserLoginProfileConfigCustomPassword(r, p, password string) string {
return fmt.Sprintf(testAccAWSUserLoginProfileBaseConfig+`
resource "aws_iam_user_login_profile" "user" {
user = "${aws_iam_user.user.name}"
password = "%s"
}
`, r, p, password)
}

const testPubKey1 = `mQENBFXbjPUBCADjNjCUQwfxKL+RR2GA6pv/1K+zJZ8UWIF9S0lk7cVIEfJiprzzwiMwBS5cD0da
rGin1FHvIWOZxujA7oW0O2TUuatqI3aAYDTfRYurh6iKLC+VS+F7H+/mhfFvKmgr0Y5kDCF1j0T/
063QZ84IRGucR/X43IY7kAtmxGXH0dYOCzOe5UBX1fTn3mXGe2ImCDWBH7gOViynXmb6XNvXkP0f
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,26 @@ Provides one-time creation of a IAM user login profile, and uses PGP to
encrypt the password for safe transport to the user. PGP keys can be
obtained from Keybase.

## Example Usage
## Example usage

```
resource "aws_iam_user" "u" {
name = "auser"
path = "/"
force_destroy = true
}

resource "aws_iam_user_login_profile" "u" {
user = "${aws_iam_user.u.name}"
password = "MyP@ssw0rd"
}

output "password" {
value = "${aws_iam_user_login_profile.u.password}"
}
```

## PGP Key Example

```
resource "aws_iam_user" "u" {
Expand All @@ -31,28 +50,31 @@ output "password" {
}
```


## Argument Reference

The following arguments are supported:

* `user` - (Required) The IAM user's name.
* `pgp_key` - (Required) Either a base-64 encoded PGP public key, or a
* `password` - (Optional) The password you want to set. It not specified,
one will be generated.
* `pgp_key` - (Optional) Either a base-64 encoded PGP public key, or a
keybase username in the form `keybase:username`.
* `password_length` - (Optional, default 20) The length of the generated
password. Used when the password is auto-generated.
* `password_reset_required` - (Optional, default "true") Whether the
user should be forced to reset the generated password on first login.
* `password_length` - (Optional, default 20) The length of the generated
password.

## Attributes Reference

The following attributes are exported:

* `key_fingerprint` - The fingerprint of the PGP key used to encrypt
the password
* `encrypted_password` - The encrypted password, base64 encoded.
* `encrypted_password` - The encrypted password when using PGP **only**, base64 encoded.

~> **NOTE:** The encrypted password may be decrypted using the command line,
for example: `terraform output password | base64 --decode | keybase pgp decrypt`.
for example: `terraform output password | base64 --decode | keybase pgp decrypt`.

## Import

Expand Down