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 29, 2015
1 parent f6979cd commit e448a40
Show file tree
Hide file tree
Showing 16 changed files with 1,459 additions and 2 deletions.
13 changes: 11 additions & 2 deletions builtin/providers/aws/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,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 @@ -97,13 +106,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
}
110 changes: 110 additions & 0 deletions builtin/providers/aws/resource_aws_iam_group_policy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package aws

import (
"fmt"
"net/url"
"strings"

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

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

func resourceAwsIamGroupPolicy() *schema.Resource {
return &schema.Resource{
// PutGroupPolicy API is idempotent, so these can be the same.
Create: resourceAwsIamGroupPolicyPut,
Update: resourceAwsIamGroupPolicyPut,

Read: resourceAwsIamGroupPolicyRead,
Delete: resourceAwsIamGroupPolicyDelete,

Schema: map[string]*schema.Schema{
"policy": &schema.Schema{
Type: schema.TypeString,
Required: true,
},
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"group": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
},
}
}

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

request := &iam.PutGroupPolicyInput{
GroupName: aws.String(d.Get("group").(string)),
PolicyName: aws.String(d.Get("name").(string)),
PolicyDocument: aws.String(d.Get("policy").(string)),
}

if _, err := iamconn.PutGroupPolicy(request); err != nil {
return fmt.Errorf("Error putting IAM group policy %s: %s", *request.PolicyName, err)
}

d.SetId(fmt.Sprintf("%s:%s", *request.GroupName, *request.PolicyName))
return nil
}

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

group, name := resourceAwsIamGroupPolicyParseId(d)

request := &iam.GetGroupPolicyInput{
PolicyName: aws.String(name),
GroupName: aws.String(group),
}

getResp, err := iamconn.GetGroupPolicy(request)
if err != nil {
if iamerr, ok := err.(aws.APIError); ok && iamerr.Code == "NoSuchEntity" { // XXX test me
d.SetId("")
return nil
}
return fmt.Errorf("Error reading IAM policy %s from group %s: %s", name, group, err)
}

if getResp.PolicyDocument == nil {
return fmt.Errorf("GetGroupPolicy returned a nil policy document")
}

policy, err := url.QueryUnescape(*getResp.PolicyDocument)
if err != nil {
return err
}
return d.Set("policy", policy)
}

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

group, name := resourceAwsIamGroupPolicyParseId(d)

request := &iam.DeleteGroupPolicyInput{
PolicyName: aws.String(name),
GroupName: aws.String(group),
}

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

func resourceAwsIamGroupPolicyParseId(d *schema.ResourceData) (groupName, policyName string) {
parts := strings.SplitN(d.Id(), ":", 2)
groupName = parts[0]
policyName = parts[1]
return
}
Loading

1 comment on commit e448a40

@ketzacoatl
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

my hero!

Please sign in to comment.