provider/aws: aws_iam_user_login_profile resource

This commit introduces an `aws_iam_user_login_profile` resource which
creates a password for an IAM user, and encrypts it using a PGP key
specified in the configuration or obtained from Keybase.

For example:

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

resource "aws_iam_user_login_profile" "u" {
        user = "${aws_iam_user.u.name}"
        pgp_key = "keybase:some_person_that_exists"
}

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

The resulting attribute "encrypted_password" can be decrypted using
PGP or Keybase - for example:

```
terraform output password | base64 --decode | keybase pgp decrypt
```

Optionally the user can retain the password rather than the default of
being forced to change it at first login. Generated passwords are
currently 20 characters long.
This commit is contained in:
James Nugent 2016-10-25 10:33:42 -05:00
parent 43dd13cd36
commit 513c2f9720
2 changed files with 138 additions and 0 deletions

View File

@ -261,6 +261,7 @@ func Provider() terraform.ResourceProvider {
"aws_iam_user_policy": resourceAwsIamUserPolicy(),
"aws_iam_user_ssh_key": resourceAwsIamUserSshKey(),
"aws_iam_user": resourceAwsIamUser(),
"aws_iam_user_login_profile": resourceAwsIamUserLoginProfile(),
"aws_instance": resourceAwsInstance(),
"aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(),

View File

@ -0,0 +1,137 @@
package aws
import (
"encoding/base64"
"errors"
"fmt"
"log"
"math/rand"
"strings"
"time"
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/iam"
"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/vault/helper/pgpkeys"
)
func resourceAwsIamUserLoginProfile() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamUserLoginProfileCreate,
Read: schema.Noop,
Update: schema.Noop,
Delete: schema.RemoveFromState,
Schema: map[string]*schema.Schema{
"user": {
Type: schema.TypeString,
Required: true,
},
"pgp_key": {
Type: schema.TypeString,
Required: true,
},
"password_reset_required": {
Type: schema.TypeBool,
Optional: true,
Default: true,
},
"password_length": {
Type: schema.TypeInt,
Optional: true,
Default: 20,
ValidateFunc: validateAwsIamLoginProfilePasswordLength,
},
"key_fingerprint": {
Type: schema.TypeString,
Computed: true,
},
"encrypted_password": {
Type: schema.TypeString,
Computed: true,
},
},
}
}
func validateAwsIamLoginProfilePasswordLength(v interface{}, _ string) (_ []string, es []error) {
length := v.(int)
if length < 1 {
es = append(es, errors.New("minimum password_length is 1 character"))
}
if length > 128 {
es = append(es, errors.New("maximum password_length is 128 characters"))
}
return
}
func generatePassword(length int) string {
const CharSetIAMPassword = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ012346789!@#$%^&*()_+-=[]{}|'"
charSetLength := len(CharSetIAMPassword)
rand.Seed(time.Now().UTC().UnixNano())
result := make([]byte, length)
for i := 0; i < length; i++ {
result[i] = CharSetIAMPassword[rand.Intn(charSetLength)]
}
return string(result)
}
func encryptPassword(password string, pgpKey string) (string, string, error) {
const keybasePrefix = "keybase:"
encryptionKey := pgpKey
if strings.HasPrefix(pgpKey, keybasePrefix) {
publicKeys, err := pgpkeys.FetchKeybasePubkeys([]string{pgpKey})
if err != nil {
return "", "", errwrap.Wrapf(
fmt.Sprintf("Error retrieving Public Key for %s: {{err}}", pgpKey), err)
}
encryptionKey = publicKeys[pgpKey]
}
fingerprints, encrypted, err := pgpkeys.EncryptShares([][]byte{[]byte(password)}, []string{encryptionKey})
if err != nil {
return "", "", errwrap.Wrapf(
fmt.Sprintf("Error encrypting password for %s: {{err}}", pgpKey), err)
}
return fingerprints[0], base64.StdEncoding.EncodeToString(encrypted[0]), nil
}
func resourceAwsIamUserLoginProfileCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
username := d.Get("user").(string)
passwordResetRequired := d.Get("password_reset_required").(bool)
passwordLength := d.Get("password_length").(int)
var pgpKey string
if pgpKeyInterface, ok := d.GetOk("pgp_key"); ok {
pgpKey = pgpKeyInterface.(string)
}
initialPassword := generatePassword(passwordLength)
fingerprint, encrypted, err := encryptPassword(initialPassword, pgpKey)
if err != nil {
return err
}
request := &iam.CreateLoginProfileInput{
UserName: aws.String(username),
Password: aws.String(initialPassword),
PasswordResetRequired: aws.Bool(passwordResetRequired),
}
log.Println("[DEBUG] Create IAM User Login Profile request:", request)
createResp, err := iamconn.CreateLoginProfile(request)
if err != 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)
return nil
}