diff --git a/docs/resources/alks_iamrole.md b/docs/resources/alks_iamrole.md index e0cf23f3..0242d842 100644 --- a/docs/resources/alks_iamrole.md +++ b/docs/resources/alks_iamrole.md @@ -6,16 +6,41 @@ Creates an custom ALKS IAM role for usage in an AWS account. ### ALKS IAM Role Creation +#### IAM Role with a custom trust policy document + ```hcl resource "alks_iamrole" "test_role" { name = "My_Test_Role" - type = "Amazon EC2" + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "ec2.amazonaws.com" + }, + Sid = "" + } + ] + }) include_default_policies = false enable_alks_access = false } ``` -This will create a role with the exact name `My_Test_Role`. +This will create a role with the exact name `My_Test_Role`. Specifying a custom trust policy like this is currently only supported for single-service trust policies trusting an approved AWS service, and at the moment no extra fields may be provided such as the "Condition" or "Resource" keys. At this time, the only acceptable changes to the JSON string passed to the assume_role_policy field above are that `ec2.amazonaws.com` can be swapped out for any single approved service, and the `Sid` field may be omitted or populated with any valid Sid according to AWS's documentation. + +#### IAM Role specifying a role type + +```hcl +resource "alks_iamrole" "test_role" { + name = "My_Test_Role" + type = "Amazon EC2" + include_default_policies = false + enable_alks_access = false +} +``` ### ALKS IAM Role Creation with Name Prefix diff --git a/go.mod b/go.mod index f414371a..4ccfd65b 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/Cox-Automotive/terraform-provider-alks go 1.14 require ( - github.com/Cox-Automotive/alks-go v0.0.0-20220502192728-623c28f3b92b + github.com/Cox-Automotive/alks-go v0.0.0-20220610194553-5bc77030a1ba github.com/aws/aws-sdk-go v1.31.15 github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/terraform-json v0.13.0 // indirect diff --git a/go.sum b/go.sum index 39bd7ed6..00ee1dff 100644 --- a/go.sum +++ b/go.sum @@ -36,6 +36,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/Cox-Automotive/alks-go v0.0.0-20220502192728-623c28f3b92b h1:lTQ/h4MVJzOmrWk0a16zb9pUapImXFeTlQkO3vlZtUI= github.com/Cox-Automotive/alks-go v0.0.0-20220502192728-623c28f3b92b/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= +github.com/Cox-Automotive/alks-go v0.0.0-20220610194553-5bc77030a1ba h1:2a3ugAGVFcRPYNNeO3DVHlFDjhoWIgwOCOG+YTDaqaU= +github.com/Cox-Automotive/alks-go v0.0.0-20220610194553-5bc77030a1ba/go.mod h1:jJNgXthl59Vt2tJHSC3WZ0vlopV9xqdclfQuLgwHjOw= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= diff --git a/resource_alks_iamrole.go b/resource_alks_iamrole.go index 33292211..109c3d8d 100644 --- a/resource_alks_iamrole.go +++ b/resource_alks_iamrole.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" "log" @@ -40,9 +41,16 @@ func resourceAlksIamRole() *schema.Resource { ValidateFunc: ValidRolePrefix, }, "type": { - Type: schema.TypeString, - Required: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"assume_role_policy", "type"}, + }, + "assume_role_policy": { + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"assume_role_policy", "type"}, }, "include_default_policies": { Type: schema.TypeBool, @@ -87,7 +95,6 @@ func resourceAlksIamRole() *schema.Resource { func resourceAlksIamRoleCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { log.Printf("[INFO] ALKS IAM Role Create") var roleName = NameWithPrefix(d.Get("name").(string), d.Get("name_prefix").(string)) - var roleType = d.Get("type").(string) var incDefPol = d.Get("include_default_policies").(bool) var enableAlksAccess = d.Get("enable_alks_access").(bool) var rawTemplateFields = d.Get("template_fields").(map[string]interface{}) @@ -116,7 +123,6 @@ func resourceAlksIamRoleCreate(ctx context.Context, d *schema.ResourceData, meta options := &alks.CreateIamRoleOptions{ RoleName: &roleName, - RoleType: &roleType, IncludeDefaultPolicies: &include, AlksAccess: &enableAlksAccess, TemplateFields: &templateFields, @@ -124,6 +130,19 @@ func resourceAlksIamRoleCreate(ctx context.Context, d *schema.ResourceData, meta Tags: &allTags, } + if roleType, ok := d.GetOk("type"); ok { + roleTypeString := roleType.(string) + options.RoleType = &roleTypeString + } else { + trustPolicyString := d.Get("assume_role_policy").(string) + + trustPolicy := new(map[string]interface{}) + + json.Unmarshal([]byte(trustPolicyString), trustPolicy) + + options.TrustPolicy = trustPolicy + } + resp, err := client.CreateIamRole(options) if err != nil { return diag.FromErr(err) diff --git a/resource_alks_iamrole_test.go b/resource_alks_iamrole_test.go index 53c9e783..b3f11048 100644 --- a/resource_alks_iamrole_test.go +++ b/resource_alks_iamrole_test.go @@ -423,6 +423,43 @@ func testAccCheckAlksIamRoleAttributes(role *alks.IamRoleResponse) resource.Test } } +func TestIAMRole_RoleTypeAndTrustPolicyBothPresent(t *testing.T) { + var resp alks.IamRoleResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAlksIamRoleDestroy(&resp), + Steps: []resource.TestStep{ + { + Config: testAccCheckAlksIamRoleBothRoleTypeAndTrustPolicyPresent, + ExpectError: regexp.MustCompile(".*Error: ExactlyOne.*"), + }, + }, + }) +} + +func TestIAMRole_OnlyTrustPolicyPresent(t *testing.T) { + var resp alks.IamRoleResponse + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAlksIamRoleDestroy(&resp), + Steps: []resource.TestStep{ + { + Config: testAccCheckAlksIamRoleWithOnlyTrustPolicyPresent, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "alks_iamrole.both_type_and_trust_policy", "name", "both_type_and_trust_policy"), + resource.TestCheckResourceAttr( + "alks_iamrole.both_type_and_trust_policy", "include_default_policies", "false"), + ), + }, + }, + }) +} + const testAccCheckAlksIamRoleConfigBasic = ` resource "alks_iamrole" "foo" { name = "bar430" @@ -643,3 +680,44 @@ const testAccCheckAlksIamRoleConfigNameTooLong = ` include_default_policies = false } ` + +const testAccCheckAlksIamRoleBothRoleTypeAndTrustPolicyPresent = ` + resource "alks_iamrole" "both_type_and_trust_policy" { + name = "both_type_and_trust_policy" + include_default_policies = false + type = "Amazon EC2" + trust_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "databrew.amazonaws.com" + }, + Sid = "" + } + ] + }) + } +` + +const testAccCheckAlksIamRoleWithOnlyTrustPolicyPresent = ` + resource "alks_iamrole" "both_type_and_trust_policy" { + name = "both_type_and_trust_policy" + include_default_policies = false + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Action = "sts:AssumeRole", + Effect = "Allow", + Principal = { + Service = "databrew.amazonaws.com" + }, + Sid = "" + } + ] + }) + } +` diff --git a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go index 568adcbc..46c6df42 100644 --- a/vendor/github.com/Cox-Automotive/alks-go/iam_role.go +++ b/vendor/github.com/Cox-Automotive/alks-go/iam_role.go @@ -17,6 +17,7 @@ type Tag struct { type CreateIamRoleOptions struct { RoleName *string RoleType *string + TrustPolicy *map[string]interface{} IncludeDefaultPolicies *bool AlksAccess *bool TrustArn *string @@ -27,40 +28,43 @@ type CreateIamRoleOptions struct { // IamRoleRequest is used to represent a new IAM Role request. type IamRoleRequest struct { - RoleName string `json:"roleName"` - RoleType string `json:"roleType,omitempty"` - IncDefPols int `json:"includeDefaultPolicy,omitempty"` - AlksAccess bool `json:"enableAlksAccess,omitempty"` - TrustArn string `json:"trustArn,omitempty"` - TemplateFields map[string]string `json:"templateFields,omitempty"` - MaxSessionDurationInSeconds int `json:"maxSessionDurationInSeconds,omitempty"` - Tags []Tag `json:"tags,omitempty"` + RoleName string `json:"roleName"` + RoleType string `json:"roleType,omitempty"` + TrustPolicy map[string]interface{} `json:"trustPolicy,omitempty"` + IncDefPols int `json:"includeDefaultPolicy,omitempty"` + AlksAccess bool `json:"enableAlksAccess,omitempty"` + TrustArn string `json:"trustArn,omitempty"` + TemplateFields map[string]string `json:"templateFields,omitempty"` + MaxSessionDurationInSeconds int `json:"maxSessionDurationInSeconds,omitempty"` + Tags []Tag `json:"tags,omitempty"` } // IamRoleResponse is used to represent a a IAM Role. type IamRoleResponse struct { BaseResponse - RoleName string `json:"roleName"` - RoleType string `json:"roleType"` - RoleArn string `json:"roleArn"` - RoleIPArn string `json:"instanceProfileArn"` - RoleAddedToIP bool `json:"addedRoleToInstanceProfile"` - Exists bool `json:"roleExists"` - TemplateFields map[string]string `json:"templateFields,omitempty"` - MaxSessionDurationInSeconds int `json:"maxSessionDurationInSeconds"` + RoleName string `json:"roleName"` + RoleType string `json:"roleType"` + TrustPolicy map[string]interface{} `json:"trustPolicy"` + RoleArn string `json:"roleArn"` + RoleIPArn string `json:"instanceProfileArn"` + RoleAddedToIP bool `json:"addedRoleToInstanceProfile"` + Exists bool `json:"roleExists"` + TemplateFields map[string]string `json:"templateFields,omitempty"` + MaxSessionDurationInSeconds int `json:"maxSessionDurationInSeconds"` } // GetIamRoleResponse is used to represent a a IAM Role. type GetIamRoleResponse struct { BaseResponse - RoleName string `json:"roleName"` - RoleType string `json:"roleType"` - RoleArn string `json:"roleArn"` - RoleIPArn string `json:"instanceProfileArn"` - RoleAddedToIP bool `json:"addedRoleToInstanceProfile"` - Exists bool `json:"roleExists"` - AlksAccess bool `json:"machineIdentity"` - Tags []Tag `json:"tags"` + RoleName string `json:"roleName"` + RoleType string `json:"roleType"` + TrustPolicy map[string]interface{} `json:"trustPolicy"` + RoleArn string `json:"roleArn"` + RoleIPArn string `json:"instanceProfileArn"` + RoleAddedToIP bool `json:"addedRoleToInstanceProfile"` + Exists bool `json:"roleExists"` + AlksAccess bool `json:"machineIdentity"` + Tags []Tag `json:"tags"` } // GetRoleRequest is used to represent a request for details about @@ -114,14 +118,24 @@ func NewIamRoleRequest(options *CreateIamRoleOptions) (*IamRoleRequest, error) { return nil, fmt.Errorf("RoleName option must not be nil") } - if options.RoleType == nil { - return nil, fmt.Errorf("RoleType option must not be nil") + trustPolicyExists := options.TrustPolicy != nil + roleTypeExists := options.RoleType != nil + if trustPolicyExists == roleTypeExists { + return nil, fmt.Errorf("Either RoleType or TrustPolicy must be included, but not both") } iam := &IamRoleRequest{ RoleName: *options.RoleName, - RoleType: *options.RoleType, } + + if roleTypeExists { + iam.RoleType = *options.RoleType + } + + if trustPolicyExists { + iam.TrustPolicy = *options.TrustPolicy + } + if options.IncludeDefaultPolicies != nil && *options.IncludeDefaultPolicies { iam.IncDefPols = 1 } else { diff --git a/vendor/modules.txt b/vendor/modules.txt index a9749e25..1432f363 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -8,7 +8,7 @@ cloud.google.com/go/internal/trace cloud.google.com/go/internal/version # cloud.google.com/go/storage v1.10.0 cloud.google.com/go/storage -# github.com/Cox-Automotive/alks-go v0.0.0-20220502192728-623c28f3b92b +# github.com/Cox-Automotive/alks-go v0.0.0-20220610194553-5bc77030a1ba ## explicit github.com/Cox-Automotive/alks-go # github.com/agext/levenshtein v1.2.2