diff --git a/.changelog/37255.txt b/.changelog/37255.txt new file mode 100644 index 00000000000..f42ce8a8e25 --- /dev/null +++ b/.changelog/37255.txt @@ -0,0 +1,3 @@ +``release-note:enhancement +resource/aws_iam_openid_connect_provider: Make `thumbprint_list` optional +``` diff --git a/internal/service/iam/openid_connect_provider.go b/internal/service/iam/openid_connect_provider.go index 0ba671fdb8c..eb176b262d2 100644 --- a/internal/service/iam/openid_connect_provider.go +++ b/internal/service/iam/openid_connect_provider.go @@ -55,7 +55,8 @@ func resourceOpenIDConnectProvider() *schema.Resource { names.AttrTagsAll: tftags.TagsSchemaComputed(), "thumbprint_list": { Type: schema.TypeList, - Required: true, + Optional: true, + Computed: true, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringLenBetween(40, 40), @@ -79,10 +80,13 @@ func resourceOpenIDConnectProviderCreate(ctx context.Context, d *schema.Resource conn := meta.(*conns.AWSClient).IAMClient(ctx) input := &iam.CreateOpenIDConnectProviderInput{ - ClientIDList: flex.ExpandStringValueSet(d.Get("client_id_list").(*schema.Set)), - Tags: getTagsIn(ctx), - ThumbprintList: flex.ExpandStringValueList(d.Get("thumbprint_list").([]interface{})), - Url: aws.String(d.Get(names.AttrURL).(string)), + ClientIDList: flex.ExpandStringValueSet(d.Get("client_id_list").(*schema.Set)), + Tags: getTagsIn(ctx), + Url: aws.String(d.Get(names.AttrURL).(string)), + } + + if v, ok := d.GetOk("thumbprint_list"); ok { + input.ThumbprintList = flex.ExpandStringValueList(v.([]interface{})) } output, err := conn.CreateOpenIDConnectProvider(ctx, input) @@ -149,15 +153,22 @@ func resourceOpenIDConnectProviderUpdate(ctx context.Context, d *schema.Resource conn := meta.(*conns.AWSClient).IAMClient(ctx) if d.HasChange("thumbprint_list") { - input := &iam.UpdateOpenIDConnectProviderThumbprintInput{ - OpenIDConnectProviderArn: aws.String(d.Id()), - ThumbprintList: flex.ExpandStringValueList(d.Get("thumbprint_list").([]interface{})), - } - - _, err := conn.UpdateOpenIDConnectProviderThumbprint(ctx, input) + if v := d.Get("thumbprint_list").([]interface{}); len(v) > 0 { + // Issues with an update to clear the thumbprint_list: + // - A cleared thumbprint_list will have a length of 0, and not enter this block. + // - Setting it to empty triggers an API error (the API requires either no thumbprints at + // **creation** or at least one thumbprint on update). + // - Removing the thumbprint_list attribute entirely doesn’t work because it won’t register as + // a change (no diff is detected). + // See https://github.com/hashicorp/terraform-provider-aws/issues/40509 + input := &iam.UpdateOpenIDConnectProviderThumbprintInput{ + OpenIDConnectProviderArn: aws.String(d.Id()), + ThumbprintList: flex.ExpandStringValueList(v), + } - if err != nil { - return sdkdiag.AppendErrorf(diags, "updating IAM OIDC Provider (%s) thumbprint: %s", d.Id(), err) + if _, err := conn.UpdateOpenIDConnectProviderThumbprint(ctx, input); err != nil { + return sdkdiag.AppendErrorf(diags, "updating IAM OIDC Provider (%s) thumbprint: %s", d.Id(), err) + } } } diff --git a/internal/service/iam/openid_connect_provider_test.go b/internal/service/iam/openid_connect_provider_test.go index f46acb41964..3f56ef6f65c 100644 --- a/internal/service/iam/openid_connect_provider_test.go +++ b/internal/service/iam/openid_connect_provider_test.go @@ -66,6 +66,122 @@ func TestAccIAMOpenIDConnectProvider_basic(t *testing.T) { }) } +func TestAccIAMOpenIDConnectProvider_Thumbprints_none(t *testing.T) { + ctx := acctest.Context(t) + url := "accounts.google.com" + resourceName := "aws_iam_openid_connect_provider.test" + + resource.Test(t, resource.TestCase{ // can't run in parallel b/c of google URL, needed for no thumbprints + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOpenIDConnectProviderDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOpenIDConnectProviderConfig_withoutThumbprints(), + Check: resource.ComposeTestCheckFunc( + testAccCheckOpenIDConnectProviderExists(ctx, resourceName), + acctest.CheckResourceAttrGlobalARN(ctx, resourceName, names.AttrARN, "iam", fmt.Sprintf("oidc-provider/%s", url)), + resource.TestCheckResourceAttr(resourceName, names.AttrURL, url), + resource.TestCheckResourceAttr(resourceName, "client_id_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "client_id_list.0", + "266362248691-342342xasdasdasda-apps.googleusercontent.com"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + }, + }) +} + +func TestAccIAMOpenIDConnectProvider_Thumbprints_withToWithout(t *testing.T) { + ctx := acctest.Context(t) + url := "accounts.google.com" + resourceName := "aws_iam_openid_connect_provider.test" + + resource.Test(t, resource.TestCase{ // can't run in parallel b/c of google URL, needed for no thumbprints + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOpenIDConnectProviderDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOpenIDConnectProviderConfig_thumbprint(), + Check: resource.ComposeTestCheckFunc( + testAccCheckOpenIDConnectProviderExists(ctx, resourceName), + acctest.CheckResourceAttrGlobalARN(ctx, resourceName, names.AttrARN, "iam", fmt.Sprintf("oidc-provider/%s", url)), + resource.TestCheckResourceAttr(resourceName, names.AttrURL, url), + resource.TestCheckResourceAttr(resourceName, "client_id_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "client_id_list.0", + "266362248691-342342xasdasdasda-apps.googleusercontent.com"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.0", "cf23df2207d99a74fbe169e3eba035e633b65d94"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + { + Config: testAccOpenIDConnectProviderConfig_withoutThumbprints(), + Check: resource.ComposeTestCheckFunc( + testAccCheckOpenIDConnectProviderExists(ctx, resourceName), + acctest.CheckResourceAttrGlobalARN(ctx, resourceName, names.AttrARN, "iam", fmt.Sprintf("oidc-provider/%s", url)), + resource.TestCheckResourceAttr(resourceName, names.AttrURL, url), + resource.TestCheckResourceAttr(resourceName, "client_id_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "client_id_list.0", + "266362248691-342342xasdasdasda-apps.googleusercontent.com"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.#", "1"), + // This is a bug: the thumbprint should be the AWS provided for the top intermediate CA of the OIDC IdP + // See https://github.com/hashicorp/terraform-provider-aws/issues/40509 + //resource.TestCheckResourceAttr(resourceName, "thumbprint_list.0", "08745487e891c19e3078c1f2a07e452950ef36f6"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + }, + }) +} + +func TestAccIAMOpenIDConnectProvider_Thumbprints_withoutToWith(t *testing.T) { + ctx := acctest.Context(t) + url := "accounts.google.com" + resourceName := "aws_iam_openid_connect_provider.test" + + resource.Test(t, resource.TestCase{ // can't run in parallel b/c of google URL, needed for no thumbprints + PreCheck: func() { acctest.PreCheck(ctx, t) }, + ErrorCheck: acctest.ErrorCheck(t, names.IAMServiceID), + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories, + CheckDestroy: testAccCheckOpenIDConnectProviderDestroy(ctx), + Steps: []resource.TestStep{ + { + Config: testAccOpenIDConnectProviderConfig_withoutThumbprints(), + Check: resource.ComposeTestCheckFunc( + testAccCheckOpenIDConnectProviderExists(ctx, resourceName), + acctest.CheckResourceAttrGlobalARN(ctx, resourceName, names.AttrARN, "iam", fmt.Sprintf("oidc-provider/%s", url)), + resource.TestCheckResourceAttr(resourceName, names.AttrURL, url), + resource.TestCheckResourceAttr(resourceName, "client_id_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "client_id_list.0", + "266362248691-342342xasdasdasda-apps.googleusercontent.com"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.0", "08745487e891c19e3078c1f2a07e452950ef36f6"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + { + Config: testAccOpenIDConnectProviderConfig_thumbprint(), + Check: resource.ComposeTestCheckFunc( + testAccCheckOpenIDConnectProviderExists(ctx, resourceName), + acctest.CheckResourceAttrGlobalARN(ctx, resourceName, names.AttrARN, "iam", fmt.Sprintf("oidc-provider/%s", url)), + resource.TestCheckResourceAttr(resourceName, names.AttrURL, url), + resource.TestCheckResourceAttr(resourceName, "client_id_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "client_id_list.0", + "266362248691-342342xasdasdasda-apps.googleusercontent.com"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.#", "1"), + resource.TestCheckResourceAttr(resourceName, "thumbprint_list.0", "cf23df2207d99a74fbe169e3eba035e633b65d94"), + resource.TestCheckResourceAttr(resourceName, acctest.CtTagsPercent, "0"), + ), + }, + }, + }) +} + func TestAccIAMOpenIDConnectProvider_disappears(t *testing.T) { ctx := acctest.Context(t) rString := sdkacctest.RandString(5) @@ -240,6 +356,32 @@ resource "aws_iam_openid_connect_provider" "test" { `, rName) } +func testAccOpenIDConnectProviderConfig_thumbprint() string { + return ` +resource "aws_iam_openid_connect_provider" "test" { + url = "https://accounts.google.com" + + client_id_list = [ + "266362248691-342342xasdasdasda-apps.googleusercontent.com", + ] + + thumbprint_list = ["cf23df2207d99a74fbe169e3eba035e633b65d94"] +} +` +} + +func testAccOpenIDConnectProviderConfig_withoutThumbprints() string { + return ` +resource "aws_iam_openid_connect_provider" "test" { + url = "https://accounts.google.com" + + client_id_list = [ + "266362248691-342342xasdasdasda-apps.googleusercontent.com", + ] +} +` +} + func testAccOpenIDConnectProviderConfig_clientIDList_first(rName string) string { return fmt.Sprintf(` resource "aws_iam_openid_connect_provider" "test" { diff --git a/website/docs/r/iam_openid_connect_provider.html.markdown b/website/docs/r/iam_openid_connect_provider.html.markdown index e605f2f17aa..a5ac615be67 100644 --- a/website/docs/r/iam_openid_connect_provider.html.markdown +++ b/website/docs/r/iam_openid_connect_provider.html.markdown @@ -12,6 +12,8 @@ Provides an IAM OpenID Connect provider. ## Example Usage +### Basic Usage + ```terraform resource "aws_iam_openid_connect_provider" "default" { url = "https://accounts.google.com" @@ -24,21 +26,33 @@ resource "aws_iam_openid_connect_provider" "default" { } ``` +### Without A Thumbprint + +```terraform +resource "aws_iam_openid_connect_provider" "default" { + url = "https://accounts.google.com" + + client_id_list = [ + "266362248691-342342xasdasdasda-apps.googleusercontent.com", + ] +} +``` + ## Argument Reference This resource supports the following arguments: -* `url` - (Required) The URL of the identity provider. Corresponds to the _iss_ claim. -* `client_id_list` - (Required) A list of client IDs (also known as audiences). When a mobile or web app registers with an OpenID Connect provider, they establish a value that identifies the application. (This is the value that's sent as the client_id parameter on OAuth requests.) -* `thumbprint_list` - (Required) A list of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). +* `url` - (Required) URL of the identity provider, corresponding to the `iss` claim. +* `client_id_list` - (Required) List of client IDs (audiences) that identify the application registered with the OpenID Connect provider. This is the value sent as the `client_id` parameter in OAuth requests. +* `thumbprint_list` - (Optional) List of server certificate thumbprints for the OpenID Connect (OIDC) identity provider's server certificate(s). For certain OIDC identity providers (e.g., Auth0, GitHub, GitLab, Google, or those using an Amazon S3-hosted JWKS endpoint), AWS relies on its own library of trusted root certificate authorities (CAs) for validation instead of using any configured thumbprints. In these cases, any configured `thumbprint_list` is retained in the configuration but not used for verification. For other IdPs, if no `thumbprint_list` is provided, IAM automatically retrieves and uses the top intermediate CA thumbprint from the OIDC IdP server certificate. However, if a `thumbprint_list` is initially configured and later removed, Terraform does not prompt IAM to retrieve a thumbprint the same way. Instead, it continues using the original thumbprint list from the initial configuration. This differs from the behavior when creating an `aws_iam_openid_connect_provider` without a `thumbprint_list`. * `tags` - (Optional) Map of resource tags for the IAM OIDC provider. If configured with a provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block) present, tags with matching keys will overwrite those defined at the provider-level. ## Attribute Reference This resource exports the following attributes in addition to the arguments above: -* `arn` - The ARN assigned by AWS for this provider. -* `tags_all` - A map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). +* `arn` - ARN assigned by AWS for this provider. +* `tags_all` - Map of tags assigned to the resource, including those inherited from the provider [`default_tags` configuration block](https://registry.terraform.io/providers/hashicorp/aws/latest/docs#default_tags-configuration-block). ## Import