Skip to content

Commit

Permalink
Implement AWS IAM resources
Browse files Browse the repository at this point in the history
- Users
- Groups
- Roles
- Inline policies for the above three
- Instance profiles
- Managed policies
- Access keys

This is most of the data types provided by IAM. There are a few things
missing, but the functionality here is probably sufficient for 95% of
the cases. Makes a dent in hashicorp#28.
  • Loading branch information
Phil Frost committed Apr 28, 2015
1 parent 4787035 commit b6dfc95
Show file tree
Hide file tree
Showing 17 changed files with 1,461 additions and 11 deletions.
11 changes: 2 additions & 9 deletions builtin/providers/aws/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/autoscaling"
"github.com/awslabs/aws-sdk-go/service/ec2"
"github.com/awslabs/aws-sdk-go/service/elasticache"
"github.com/awslabs/aws-sdk-go/service/elb"
"github.com/awslabs/aws-sdk-go/service/iam"
"github.com/awslabs/aws-sdk-go/service/rds"
Expand All @@ -37,7 +36,6 @@ type AWSClient struct {
region string
rdsconn *rds.RDS
iamconn *iam.IAM
elasticacheconn *elasticache.ElastiCache
}

// Client configures and returns a fully initailized AWSClient
Expand Down Expand Up @@ -97,9 +95,6 @@ func (c *Config) Client() (interface{}, error) {
Credentials: creds,
Region: "us-east-1",
})

log.Println("[INFO] Initializing Elasticache Connection")
client.elasticacheconn = elasticache.New(awsConfig)
}

if len(errs) > 0 {
Expand All @@ -109,8 +104,8 @@ func (c *Config) Client() (interface{}, error) {
return &client, nil
}

// ValidateRegion returns an error if the configured region is not a
// valid aws region and nil otherwise.
// IsValidRegion returns true if the configured region is a valid AWS
// region and false if it's not
func (c *Config) ValidateRegion() error {
var regions = [11]string{"us-east-1", "us-west-2", "us-west-1", "eu-west-1",
"eu-central-1", "ap-southeast-1", "ap-southeast-2", "ap-northeast-1",
Expand All @@ -124,8 +119,6 @@ func (c *Config) ValidateRegion() error {
return fmt.Errorf("Not a valid region: %s", c.Region)
}

// ValidateAccountId returns a context-specific error if the configured account
// id is explicitly forbidden or not authorised; and nil if it is authorised.
func (c *Config) ValidateAccountId(iamconn *iam.IAM) error {
if c.AllowedAccountIds == nil && c.ForbiddenAccountIds == nil {
return nil
Expand Down
13 changes: 11 additions & 2 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,15 @@ func Provider() terraform.ResourceProvider {
"aws_elasticache_security_group": resourceAwsElasticacheSecurityGroup(),
"aws_eip": resourceAwsEip(),
"aws_elb": resourceAwsElb(),
"aws_iam_access_key": resourceAwsIamAccessKey(),
"aws_iam_group_policy": resourceAwsIamGroupPolicy(),
"aws_iam_group": resourceAwsIamGroup(),
"aws_iam_instance_profile": resourceAwsIamInstanceProfile(),
"aws_iam_policy": resourceAwsIamPolicy(),
"aws_iam_role_policy": resourceAwsIamRolePolicy(),
"aws_iam_role": resourceAwsIamRole(),
"aws_iam_user_policy": resourceAwsIamUserPolicy(),
"aws_iam_user": resourceAwsIamUser(),
"aws_instance": resourceAwsInstance(),
"aws_internet_gateway": resourceAwsInternetGateway(),
"aws_key_pair": resourceAwsKeyPair(),
Expand All @@ -94,13 +103,13 @@ func Provider() terraform.ResourceProvider {
"aws_network_interface": resourceAwsNetworkInterface(),
"aws_route53_record": resourceAwsRoute53Record(),
"aws_route53_zone": resourceAwsRoute53Zone(),
"aws_route_table": resourceAwsRouteTable(),
"aws_route_table_association": resourceAwsRouteTableAssociation(),
"aws_route_table": resourceAwsRouteTable(),
"aws_s3_bucket": resourceAwsS3Bucket(),
"aws_security_group": resourceAwsSecurityGroup(),
"aws_subnet": resourceAwsSubnet(),
"aws_vpc": resourceAwsVpc(),
"aws_vpc_peering_connection": resourceAwsVpcPeeringConnection(),
"aws_vpc": resourceAwsVpc(),
"aws_vpn_gateway": resourceAwsVpnGateway(),
},

Expand Down
116 changes: 116 additions & 0 deletions builtin/providers/aws/resource_aws_iam_access_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package aws

import (
"fmt"

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIamAccessKey() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamAccessKeyCreate,
Read: resourceAwsIamAccessKeyRead,
Delete: resourceAwsIamAccessKeyDelete,

Schema: map[string]*schema.Schema{
"user": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"status": &schema.Schema{
Type: schema.TypeString,
// this could be settable, but goamz does not support the
// UpdateAccessKey API yet.
Computed: true,
},
"secret": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceAwsIamAccessKeyCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

request := &iam.CreateAccessKeyInput{
UserName: aws.String(d.Get("user").(string)),
}

createResp, err := iamconn.CreateAccessKey(request)
if err != nil {
return fmt.Errorf(
"Error creating access key for user %s: %s",
*request.UserName,
err,
)
}

if err := d.Set("secret", createResp.AccessKey.SecretAccessKey); err != nil {
return err
}
return resourceAwsIamAccessKeyReadResult(d, &iam.AccessKeyMetadata{
AccessKeyID: createResp.AccessKey.AccessKeyID,
CreateDate: createResp.AccessKey.CreateDate,
Status: createResp.AccessKey.Status,
UserName: createResp.AccessKey.UserName,
})
}

func resourceAwsIamAccessKeyRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

request := &iam.ListAccessKeysInput{
UserName: aws.String(d.Get("user").(string)),
}

getResp, err := iamconn.ListAccessKeys(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX TEST ME
// the user does not exist, so the key can't exist.
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM acces key: %s", err)
}

for _, key := range getResp.AccessKeyMetadata {
if key.AccessKeyID != nil && *key.AccessKeyID == d.Id() {
return resourceAwsIamAccessKeyReadResult(d, key)
}
}

// Guess the key isn't around anymore.
d.SetId("")
return nil
}

func resourceAwsIamAccessKeyReadResult(d *schema.ResourceData, key *iam.AccessKeyMetadata) error {
d.SetId(*key.AccessKeyID)
if err := d.Set("user", key.UserName); err != nil {
return err
}
if err := d.Set("status", key.Status); err != nil {
return err
}
return nil
}

func resourceAwsIamAccessKeyDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

request := &iam.DeleteAccessKeyInput{
AccessKeyID: aws.String(d.Id()),
UserName: aws.String(d.Get("user").(string)),
}

if _, err := iamconn.DeleteAccessKey(request); err != nil {
return fmt.Errorf("Error deleting access key %s: %s", d.Id(), err)
}
return nil
}
106 changes: 106 additions & 0 deletions builtin/providers/aws/resource_aws_iam_group.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
package aws

import (
"fmt"

"github.com/awslabs/aws-sdk-go/aws"
"github.com/awslabs/aws-sdk-go/service/iam"

"github.com/hashicorp/terraform/helper/schema"
)

func resourceAwsIamGroup() *schema.Resource {
return &schema.Resource{
Create: resourceAwsIamGroupCreate,
Read: resourceAwsIamGroupRead,
// TODO
//Update: resourceAwsIamGroupUpdate,
Delete: resourceAwsIamGroupDelete,

Schema: map[string]*schema.Schema{
"arn": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"unique_id": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"path": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "/",
ForceNew: true,
},
},
}
}

func resourceAwsIamGroupCreate(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn
name := d.Get("name").(string)

request := &iam.CreateGroupInput{
Path: aws.String(d.Get("path").(string)),
GroupName: aws.String(name),
}

createResp, err := iamconn.CreateGroup(request)
if err != nil {
return fmt.Errorf("Error creating IAM Group %s: %s", name, err)
}
return resourceAwsIamGroupReadResult(d, createResp.Group)
}

func resourceAwsIamGroupRead(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

request := &iam.GetGroupInput{
GroupName: aws.String(d.Id()),
}

getResp, err := iamconn.GetGroup(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" {
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM Group %s: %s", d.Id(), err)
}
return resourceAwsIamGroupReadResult(d, getResp.Group)
}

func resourceAwsIamGroupReadResult(d *schema.ResourceData, group *iam.Group) error {
d.SetId(*group.GroupName)
if err := d.Set("name", group.GroupName); err != nil {
return err
}
if err := d.Set("arn", group.ARN); err != nil {
return err
}
if err := d.Set("path", group.Path); err != nil {
return err
}
if err := d.Set("unique_id", group.GroupID); err != nil {
return err
}
return nil
}

func resourceAwsIamGroupDelete(d *schema.ResourceData, meta interface{}) error {
iamconn := meta.(*AWSClient).iamconn

request := &iam.DeleteGroupInput{
GroupName: aws.String(d.Id()),
}

if _, err := iamconn.DeleteGroup(request); err != nil {
return fmt.Errorf("Error deleting IAM Group %s: %s", d.Id(), err)
}
return nil
}
Loading

0 comments on commit b6dfc95

Please sign in to comment.