2015-03-20 16:59:48 +00:00
|
|
|
package aws
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2015-03-21 10:49:56 +00:00
|
|
|
"math/rand"
|
2015-08-25 00:00:54 +00:00
|
|
|
"regexp"
|
2015-03-21 10:49:56 +00:00
|
|
|
"time"
|
2015-03-20 16:59:48 +00:00
|
|
|
|
2015-08-06 16:26:41 +00:00
|
|
|
"github.com/aws/aws-sdk-go/aws"
|
|
|
|
"github.com/aws/aws-sdk-go/service/iam"
|
2015-03-20 16:59:48 +00:00
|
|
|
"github.com/hashicorp/vault/logical"
|
|
|
|
"github.com/hashicorp/vault/logical/framework"
|
|
|
|
)
|
|
|
|
|
|
|
|
const SecretAccessKeyType = "access_keys"
|
|
|
|
|
2015-04-19 05:25:37 +00:00
|
|
|
func secretAccessKeys(b *backend) *framework.Secret {
|
2015-03-20 16:59:48 +00:00
|
|
|
return &framework.Secret{
|
|
|
|
Type: SecretAccessKeyType,
|
|
|
|
Fields: map[string]*framework.FieldSchema{
|
|
|
|
"access_key": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Access Key",
|
|
|
|
},
|
|
|
|
|
|
|
|
"secret_key": &framework.FieldSchema{
|
|
|
|
Type: framework.TypeString,
|
|
|
|
Description: "Secret Key",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
2015-04-19 05:25:37 +00:00
|
|
|
DefaultDuration: 1 * time.Hour,
|
|
|
|
DefaultGracePeriod: 10 * time.Minute,
|
|
|
|
|
|
|
|
Renew: b.secretAccessKeysRenew,
|
2015-03-20 16:59:48 +00:00
|
|
|
Revoke: secretAccessKeysRevoke,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-21 10:49:56 +00:00
|
|
|
func (b *backend) secretAccessKeysCreate(
|
|
|
|
s logical.Storage,
|
2015-04-15 22:05:00 +00:00
|
|
|
displayName, policyName string, policy string) (*logical.Response, error) {
|
2015-03-21 10:49:56 +00:00
|
|
|
client, err := clientIAM(s)
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(err.Error()), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Generate a random username. We don't put the policy names in the
|
|
|
|
// username because the AWS console makes it pretty easy to see that.
|
2015-08-24 23:12:45 +00:00
|
|
|
username := fmt.Sprintf("vault-%s-%d-%d", normalizeDisplayName(displayName), time.Now().Unix(), rand.Int31n(10000))
|
2015-03-21 10:49:56 +00:00
|
|
|
|
|
|
|
// Write to the WAL that this user will be created. We do this before
|
|
|
|
// the user is created because if switch the order then the WAL put
|
|
|
|
// can fail, which would put us in an awkward position: we have a user
|
|
|
|
// we need to rollback but can't put the WAL entry to do the rollback.
|
|
|
|
walId, err := framework.PutWAL(s, "user", &walUser{
|
|
|
|
UserName: username,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Error writing WAL entry: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the user
|
2015-08-06 16:37:08 +00:00
|
|
|
_, err = client.CreateUser(&iam.CreateUserInput{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: aws.String(username),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Error creating IAM user: %s", err)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Add the user to all the groups
|
2015-08-06 16:37:08 +00:00
|
|
|
_, err = client.PutUserPolicy(&iam.PutUserPolicyInput{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: aws.String(username),
|
|
|
|
PolicyName: aws.String(policyName),
|
|
|
|
PolicyDocument: aws.String(policy),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Error adding user to group: %s", err)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create the keys
|
2015-08-06 16:37:08 +00:00
|
|
|
keyResp, err := client.CreateAccessKey(&iam.CreateAccessKeyInput{
|
2015-03-21 10:49:56 +00:00
|
|
|
UserName: aws.String(username),
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return logical.ErrorResponse(fmt.Sprintf(
|
|
|
|
"Error creating access keys: %s", err)), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remove the WAL entry, we succeeded! If we fail, we don't return
|
|
|
|
// the secret because it'll get rolled back anyways, so we have to return
|
|
|
|
// an error here.
|
|
|
|
if err := framework.DeleteWAL(s, walId); err != nil {
|
|
|
|
return nil, fmt.Errorf("Failed to commit WAL entry: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return the info!
|
|
|
|
return b.Secret(SecretAccessKeyType).Response(map[string]interface{}{
|
2015-08-19 01:12:51 +00:00
|
|
|
"access_key": *keyResp.AccessKey.AccessKeyId,
|
2015-03-21 10:49:56 +00:00
|
|
|
"secret_key": *keyResp.AccessKey.SecretAccessKey,
|
|
|
|
}, map[string]interface{}{
|
|
|
|
"username": username,
|
|
|
|
"policy": policy,
|
|
|
|
}), nil
|
|
|
|
}
|
|
|
|
|
2015-04-19 05:25:37 +00:00
|
|
|
func (b *backend) secretAccessKeysRenew(
|
|
|
|
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
lease, err := b.Lease(req.Storage)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
if lease == nil {
|
|
|
|
lease = &configLease{Lease: 1 * time.Hour}
|
|
|
|
}
|
|
|
|
|
2015-06-17 21:34:11 +00:00
|
|
|
f := framework.LeaseExtend(lease.Lease, lease.LeaseMax, false)
|
2015-04-19 05:25:37 +00:00
|
|
|
return f(req, d)
|
|
|
|
}
|
|
|
|
|
2015-03-20 16:59:48 +00:00
|
|
|
func secretAccessKeysRevoke(
|
|
|
|
req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
|
|
|
// Get the username from the internal data
|
|
|
|
usernameRaw, ok := req.Secret.InternalData["username"]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("secret is missing username internal data")
|
|
|
|
}
|
|
|
|
username, ok := usernameRaw.(string)
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("secret is missing username internal data")
|
|
|
|
}
|
|
|
|
|
2015-03-21 10:18:46 +00:00
|
|
|
// Use the user rollback mechanism to delete this user
|
|
|
|
err := pathUserRollback(req, "user", map[string]interface{}{
|
|
|
|
"username": username,
|
2015-03-20 16:59:48 +00:00
|
|
|
})
|
|
|
|
if err != nil {
|
2015-03-21 10:18:46 +00:00
|
|
|
return nil, err
|
2015-03-20 16:59:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
2015-08-24 23:12:45 +00:00
|
|
|
|
|
|
|
func normalizeDisplayName(displayName string) string {
|
2015-08-25 00:00:54 +00:00
|
|
|
re := regexp.MustCompile("[^a-zA-Z+=,.@_-]")
|
|
|
|
return re.ReplaceAllString(displayName, "_")
|
2015-08-24 23:12:45 +00:00
|
|
|
}
|