-
Notifications
You must be signed in to change notification settings - Fork 9.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #9605 from hashicorp/keybase-aws-login-profile
provider/aws: aws_iam_user_login_profile resource
- Loading branch information
Showing
56 changed files
with
14,873 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
182 changes: 182 additions & 0 deletions
182
builtin/providers/aws/resource_aws_iam_user_login_profile.go
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,182 @@ | ||
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/aws/awserr" | ||
"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 < 4 { | ||
es = append(es, errors.New("minimum password_length is 4 characters")) | ||
} | ||
if length > 128 { | ||
es = append(es, errors.New("maximum password_length is 128 characters")) | ||
} | ||
return | ||
} | ||
|
||
// generatePassword generates a random password of a given length using | ||
// characters that are likely to satisfy any possible AWS password policy | ||
// (given sufficient length). | ||
func generatePassword(length int) string { | ||
charsets := []string{ | ||
"abcdefghijklmnopqrstuvwxyz", | ||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ", | ||
"012346789", | ||
"!@#$%^&*()_+-=[]{}|'", | ||
} | ||
|
||
// Use all character sets | ||
random := rand.New(rand.NewSource(time.Now().UTC().UnixNano())) | ||
components := make(map[int]byte, length) | ||
for i := 0; i < length; i++ { | ||
charset := charsets[i%len(charsets)] | ||
components[i] = charset[random.Intn(len(charset))] | ||
} | ||
|
||
// Randomise the ordering so we don't end up with a predictable | ||
// lower case, upper case, numeric, symbol pattern | ||
result := make([]byte, length) | ||
i := 0 | ||
for _, b := range components { | ||
result[i] = b | ||
i = i + 1 | ||
} | ||
|
||
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) | ||
|
||
_, err := iamconn.GetLoginProfile(&iam.GetLoginProfileInput{ | ||
UserName: aws.String(username), | ||
}) | ||
if err != nil { | ||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() != "NoSuchEntity" { | ||
// If there is already a login profile, bring it under management (to prevent | ||
// 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", "") | ||
return nil | ||
} | ||
} | ||
|
||
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 { | ||
if awsErr, ok := err.(awserr.Error); ok && awsErr.Code() == "EntityAlreadyExists" { | ||
// If there is already a login profile, bring it under management (to prevent | ||
// 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", "") | ||
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) | ||
return nil | ||
} |
Oops, something went wrong.