Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support STS role configuration for AWS auth backend. #22

Merged
merged 5 commits into from
Oct 12, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 6 additions & 5 deletions vault/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,12 @@ func Provider() terraform.ResourceProvider {
},

ResourcesMap: map[string]*schema.Resource{
"vault_auth_backend": authBackendResource(),
"vault_aws_auth_backend_role": awsAuthBackendRoleResource(),
"vault_generic_secret": genericSecretResource(),
"vault_policy": policyResource(),
"vault_mount": mountResource(),
"vault_auth_backend": authBackendResource(),
"vault_aws_auth_backend_role": awsAuthBackendRoleResource(),
"vault_aws_auth_backend_sts_role": awsAuthBackendSTSRoleResource(),
"vault_generic_secret": genericSecretResource(),
"vault_policy": policyResource(),
"vault_mount": mountResource(),
},
}
}
Expand Down
184 changes: 184 additions & 0 deletions vault/resource_aws_auth_backend_sts_role.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package vault

import (
"fmt"
"log"
"regexp"
"strings"

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

var (
awsAuthBackendSTSRoleBackendFromPathRegex = regexp.MustCompile("^auth/(.+)/config/sts/.+$")
awsAuthBackendSTSRoleAccountIDFromPathRegex = regexp.MustCompile("^auth/.+/config/sts/(.+)$")
)

func awsAuthBackendSTSRoleResource() *schema.Resource {
return &schema.Resource{
Create: awsAuthBackendSTSRoleCreate,
Read: awsAuthBackendSTSRoleRead,
Update: awsAuthBackendSTSRoleUpdate,
Delete: awsAuthBackendSTSRoleDelete,
Exists: awsAuthBackendSTSRoleExists,
Importer: &schema.ResourceImporter{
State: schema.ImportStatePassthrough,
},

Schema: map[string]*schema.Schema{
"account_id": {
Type: schema.TypeString,
Required: true,
Description: "AWS account ID to be associated with STS role.",
},
"sts_role": {
Type: schema.TypeString,
Required: true,
Description: "AWS ARN for STS role to be assumed when interacting with the account specified.",
},
"backend": {
Type: schema.TypeString,
Optional: true,
Description: "Unique name of the auth backend to configure.",
ForceNew: true,
Default: "aws",
// standardise on no beginning or trailing slashes
StateFunc: func(v interface{}) string {
return strings.Trim(v.(string), "/")
},
},
},
}
}

func awsAuthBackendSTSRoleCreate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

backend := d.Get("backend").(string)
accountID := d.Get("account_id").(string)
stsRole := d.Get("sts_role").(string)

path := awsAuthBackendSTSRolePath(backend, accountID)

log.Printf("[DEBUG] Writing STS role %q to AWS auth backend", path)
_, err := client.Logical().Write(path, map[string]interface{}{
"sts_role": stsRole,
})

d.SetId(path)

if err != nil {
d.SetId("")
return fmt.Errorf("Error writing STS role %q to AWS auth backend: %s", path, err)
}
log.Printf("[DEBUG] Wrote STS role %q to AWS auth backend", path)

return awsAuthBackendSTSRoleRead(d, meta)
}

func awsAuthBackendSTSRoleRead(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

path := d.Id()

backend, err := awsAuthBackendSTSRoleBackendFromPath(path)
if err != nil {
return fmt.Errorf("Invalid path %q for AWS auth backend STS role: %s", path, err)
}

accountID, err := awsAuthBackendSTSRoleAccountIDFromPath(path)
if err != nil {
return fmt.Errorf("Invalid path %q for AWS auth backend STS role: %s", path, err)
}

log.Printf("[DEBUG] Reading STS role %q from AWS auth backend", path)
resp, err := client.Logical().Read(path)
if err != nil {
return fmt.Errorf("Error reading STS role %q from AWS auth backend %s", path, err)
}
log.Printf("[DEBUG] Read STS role %q from AWS auth backend", path)
if resp == nil {
log.Printf("[WARN} AWS auth backend STS role %q not found, removing from state", path)
d.SetId("")
return nil
}

d.Set("backend", backend)
d.Set("account_id", accountID)
d.Set("sts_role", resp.Data["sts_role"])
return nil
}

func awsAuthBackendSTSRoleUpdate(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

stsRole := d.Get("sts_role").(string)
path := d.Id()

log.Printf("[DEBUG] Updating STS role %q in AWS auth backend", path)
_, err := client.Logical().Write(path, map[string]interface{}{
"sts_role": stsRole,
})
if err != nil {
return fmt.Errorf("Error updating STS role %q in AWS auth backend", path)
}
log.Printf("[DEBUG] Updated STS role %q in AWS auth backend", path)

return awsAuthBackendSTSRoleRead(d, meta)
}

func awsAuthBackendSTSRoleDelete(d *schema.ResourceData, meta interface{}) error {
client := meta.(*api.Client)

path := d.Id()
log.Printf("[DEBUG] Deleting STS role %q from AWS auth backend", path)
_, err := client.Logical().Delete(path)
if err != nil {
return fmt.Errorf("Error deleting STS role %q from AWS auth backend", path)
}
log.Printf("[DEBUG] Deleted STS role %q from AWS auth backend", path)

return nil
}

func awsAuthBackendSTSRoleExists(d *schema.ResourceData, meta interface{}) (bool, error) {
client := meta.(*api.Client)

path := d.Id()
log.Printf("[DEBUG] Checking if STS role %q exists in AWS auth backend", path)

resp, err := client.Logical().Read(path)
if err != nil {
return true, fmt.Errorf("Error checking if STS role %q exists in AWS auth backend: %s", path, err)
}
log.Printf("[DEBUG] Checked if STS role %q exists in AWS auth backend", path)

return resp != nil, nil
}

func awsAuthBackendSTSRolePath(backend, account string) string {
return "auth/" + strings.Trim(backend, "/") + "/config/sts/" + strings.Trim(account, "/")
}

func awsAuthBackendSTSRoleBackendFromPath(path string) (string, error) {
if !awsAuthBackendSTSRoleBackendFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no backend found")
}
res := awsAuthBackendSTSRoleBackendFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for backend", len(res))
}
return res[1], nil
}

func awsAuthBackendSTSRoleAccountIDFromPath(path string) (string, error) {
if !awsAuthBackendSTSRoleAccountIDFromPathRegex.MatchString(path) {
return "", fmt.Errorf("no account ID found")
}
res := awsAuthBackendSTSRoleAccountIDFromPathRegex.FindStringSubmatch(path)
if len(res) != 2 {
return "", fmt.Errorf("unexpected number of matches (%d) for account ID", len(res))
}
return res[1], nil
}
132 changes: 132 additions & 0 deletions vault/resource_aws_auth_backend_sts_role_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
package vault

import (
"fmt"
"strconv"
"testing"

"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/vault/api"
)

func TestAccAWSAuthBackendSTSRole_import(t *testing.T) {
backend := acctest.RandomWithPrefix("aws")
accountID := strconv.Itoa(acctest.RandInt())
arn := acctest.RandomWithPrefix("arn:aws:iam::" + accountID + ":role/test-role")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testProviders,
CheckDestroy: testAccCheckAWSAuthBackendSTSRoleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, arn),
Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, arn),
},
{
ResourceName: "vault_aws_auth_backend_sts_role.role",
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccAWSAuthBackendSTSRole_basic(t *testing.T) {
backend := acctest.RandomWithPrefix("aws")
accountID := strconv.Itoa(acctest.RandInt())
arn := acctest.RandomWithPrefix("arn:aws:iam::" + accountID + ":role/test-role")
updatedArn := acctest.RandomWithPrefix("arn:aws:iam::" + accountID + ":role/test-role")
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testProviders,
CheckDestroy: testAccCheckAWSAuthBackendSTSRoleDestroy,
Steps: []resource.TestStep{
{
Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, arn),
Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, arn),
},
{
Config: testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, updatedArn),
Check: testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, updatedArn),
},
},
})
}

func testAccCheckAWSAuthBackendSTSRoleDestroy(s *terraform.State) error {
client := testProvider.Meta().(*api.Client)

for _, rs := range s.RootModule().Resources {
if rs.Type != "vault_aws_auth_backend_sts_role" {
continue
}
secret, err := client.Logical().Read(rs.Primary.ID)
if err != nil {
return fmt.Errorf("Error checking for AWS auth backend STS role %q: %s", rs.Primary.ID, err)
}
if secret != nil {
return fmt.Errorf("AWS auth backend STS role %q still exists", rs.Primary.ID)
}
}
return nil
}

func testAccAWSAuthBackendSTSRoleCheck_attrs(backend, accountID, stsRole string) resource.TestCheckFunc {
return func(s *terraform.State) error {
resourceState := s.Modules[0].Resources["vault_aws_auth_backend_sts_role.role"]
if resourceState == nil {
return fmt.Errorf("resource not found in state")
}

instanceState := resourceState.Primary
if instanceState == nil {
return fmt.Errorf("resource has no primary instance state")
}

endpoint := instanceState.ID

if endpoint != "auth/"+backend+"/config/sts/"+accountID {
return fmt.Errorf("expected ID to be %q, got %q instead", "auth/"+backend+"/config/sts/"+accountID, endpoint)
}

client := testProvider.Meta().(*api.Client)
resp, err := client.Logical().Read(endpoint)
if err != nil {
return fmt.Errorf("error reading back sts role from %q: %s", endpoint, err)
}

if resp == nil {
return fmt.Errorf("%q doesn't exist", endpoint)
}

attrs := map[string]string{
"sts_role": "sts_role",
}
for stateAttr, apiAttr := range attrs {
if resp.Data[apiAttr] == nil && instanceState.Attributes[stateAttr] == "" {
continue
}
if resp.Data[apiAttr] != instanceState.Attributes[stateAttr] {
return fmt.Errorf("Expected %s (%s) of %q to be %q, got %q", apiAttr, stateAttr, endpoint, instanceState.Attributes[stateAttr], resp.Data[apiAttr])
}
}
return nil
}
}

func testAccAWSAuthBackendSTSRoleConfig_basic(backend, accountID, stsRole string) string {
return fmt.Sprintf(`
resource "vault_auth_backend" "aws" {
type = "aws"
path = "%s"
}

resource "vault_aws_auth_backend_sts_role" "role" {
backend = "${vault_auth_backend.aws.path}"
account_id = "%s"
sts_role = "%s"
}
`, backend, accountID, stsRole)
}
26 changes: 13 additions & 13 deletions website/docs/r/aws_auth_backend_role.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ resource "vault_auth_backend" "aws" {
}

resource "vault_aws_auth_backend_role" "example" {
backend = "${vault_auth_backend.aws.path}"
role = "test-role"
auth_type = "iam"
bound_ami_id = "ami-8c1be5f6"
bound_account_id = "123456789012"
bound_vpc_id = "vpc-b61106d4"
bound_subnet_id = "vpc-133128f1"
bound_iam_role_arn = "arn:aws:iam::123456789012:role/MyRole"
backend = "${vault_auth_backend.aws.path}"
role = "test-role"
auth_type = "iam"
bound_ami_id = "ami-8c1be5f6"
bound_account_id = "123456789012"
bound_vpc_id = "vpc-b61106d4"
bound_subnet_id = "vpc-133128f1"
bound_iam_role_arn = "arn:aws:iam::123456789012:role/MyRole"
bound_iam_instance_profile_arn = "arn:aws:iam::123456789012:instance-profile/MyProfile"
inferred_entity_type = "ec2_instance"
inferred_aws_region = "us-east-1"
ttl = 60
max_ttl = 120
policies = ["default", "dev", "prod"]
inferred_entity_type = "ec2_instance"
inferred_aws_region = "us-east-1"
ttl = 60
max_ttl = 120
policies = ["default", "dev", "prod"]
}
```

Expand Down
Loading