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:
parent
43dd13cd36
commit
513c2f9720
|
@ -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(),
|
||||
|
|
|
@ -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
|
||||
}
|
Loading…
Reference in New Issue