diff --git a/aws/resource_aws_appsync_graphql_api.go b/aws/resource_aws_appsync_graphql_api.go index 8f16d50024d..0ec2c447ce6 100644 --- a/aws/resource_aws_appsync_graphql_api.go +++ b/aws/resource_aws_appsync_graphql_api.go @@ -12,6 +12,13 @@ import ( "github.com/hashicorp/terraform/helper/validation" ) +var validAppsyncAuthTypes = []string{ + appsync.AuthenticationTypeApiKey, + appsync.AuthenticationTypeAwsIam, + appsync.AuthenticationTypeAmazonCognitoUserPools, + appsync.AuthenticationTypeOpenidConnect, +} + func resourceAwsAppsyncGraphqlApi() *schema.Resource { return &schema.Resource{ Create: resourceAwsAppsyncGraphqlApiCreate, @@ -24,15 +31,70 @@ func resourceAwsAppsyncGraphqlApi() *schema.Resource { }, Schema: map[string]*schema.Schema{ + "additional_authentication_provider": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "authentication_type": { + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(validAppsyncAuthTypes, false), + }, + "openid_connect_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "auth_ttl": { + Type: schema.TypeInt, + Optional: true, + }, + "client_id": { + Type: schema.TypeString, + Optional: true, + }, + "iat_ttl": { + Type: schema.TypeInt, + Optional: true, + }, + "issuer": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "user_pool_config": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "app_id_client_regex": { + Type: schema.TypeString, + Optional: true, + }, + "aws_region": { + Type: schema.TypeString, + Optional: true, + Computed: true, + }, + "user_pool_id": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, "authentication_type": { - Type: schema.TypeString, - Required: true, - ValidateFunc: validation.StringInSlice([]string{ - appsync.AuthenticationTypeApiKey, - appsync.AuthenticationTypeAwsIam, - appsync.AuthenticationTypeAmazonCognitoUserPools, - appsync.AuthenticationTypeOpenidConnect, - }, false), + Type: schema.TypeString, + Required: true, + ValidateFunc: validation.StringInSlice(validAppsyncAuthTypes, false), }, "schema": { Type: schema.TypeString, @@ -160,6 +222,10 @@ func resourceAwsAppsyncGraphqlApiCreate(d *schema.ResourceData, meta interface{} input.UserPoolConfig = expandAppsyncGraphqlApiUserPoolConfig(v.([]interface{}), meta.(*AWSClient).region) } + if v, ok := d.GetOk("additional_authentication_provider"); ok { + input.AdditionalAuthenticationProviders = expandAppsyncGraphqlApiAdditionalAuthProviders(v.([]interface{}), meta.(*AWSClient).region) + } + if v, ok := d.GetOk("tags"); ok { input.Tags = tagsFromMapGeneric(v.(map[string]interface{})) } @@ -211,6 +277,10 @@ func resourceAwsAppsyncGraphqlApiRead(d *schema.ResourceData, meta interface{}) return fmt.Errorf("error setting user_pool_config: %s", err) } + if err := d.Set("additional_authentication_provider", flattenAppsyncGraphqlApiAdditionalAuthenticationProviders(resp.GraphqlApi.AdditionalAuthenticationProviders)); err != nil { + return fmt.Errorf("error setting additonal_authentication_provider: %s", err) + } + if err := d.Set("uris", aws.StringValueMap(resp.GraphqlApi.Uris)); err != nil { return fmt.Errorf("error setting uris: %s", err) } @@ -248,6 +318,10 @@ func resourceAwsAppsyncGraphqlApiUpdate(d *schema.ResourceData, meta interface{} input.UserPoolConfig = expandAppsyncGraphqlApiUserPoolConfig(v.([]interface{}), meta.(*AWSClient).region) } + if v, ok := d.GetOk("additional_authentication_provider"); ok { + input.AdditionalAuthenticationProviders = expandAppsyncGraphqlApiAdditionalAuthProviders(v.([]interface{}), meta.(*AWSClient).region) + } + _, err := conn.UpdateGraphqlApi(input) if err != nil { return err @@ -344,6 +418,59 @@ func expandAppsyncGraphqlApiUserPoolConfig(l []interface{}, currentRegion string return userPoolConfig } +func expandAppsyncGraphqlApiAdditionalAuthProviders(items []interface{}, currentRegion string) []*appsync.AdditionalAuthenticationProvider { + if len(items) < 1 { + return nil + } + + additionalAuthProviders := make([]*appsync.AdditionalAuthenticationProvider, 0, len(items)) + for _, l := range items { + if l == nil { + continue + } + + m := l.(map[string]interface{}) + additionalAuthProvider := &appsync.AdditionalAuthenticationProvider{ + AuthenticationType: aws.String(m["authentication_type"].(string)), + } + + if v, ok := m["openid_connect_config"]; ok { + additionalAuthProvider.OpenIDConnectConfig = expandAppsyncGraphqlApiOpenIDConnectConfig(v.([]interface{})) + } + + if v, ok := m["user_pool_config"]; ok { + additionalAuthProvider.UserPoolConfig = expandAppsyncGraphqlApiCognitoUserPoolConfig(v.([]interface{}), currentRegion) + } + + additionalAuthProviders = append(additionalAuthProviders, additionalAuthProvider) + } + + return additionalAuthProviders +} + +func expandAppsyncGraphqlApiCognitoUserPoolConfig(l []interface{}, currentRegion string) *appsync.CognitoUserPoolConfig { + if len(l) < 1 || l[0] == nil { + return nil + } + + m := l[0].(map[string]interface{}) + + userPoolConfig := &appsync.CognitoUserPoolConfig{ + AwsRegion: aws.String(currentRegion), + UserPoolId: aws.String(m["user_pool_id"].(string)), + } + + if v, ok := m["app_id_client_regex"].(string); ok && v != "" { + userPoolConfig.AppIdClientRegex = aws.String(v) + } + + if v, ok := m["aws_region"].(string); ok && v != "" { + userPoolConfig.AwsRegion = aws.String(v) + } + + return userPoolConfig +} + func flattenAppsyncGraphqlApiLogConfig(logConfig *appsync.LogConfig) []interface{} { if logConfig == nil { return []interface{}{} @@ -390,6 +517,40 @@ func flattenAppsyncGraphqlApiUserPoolConfig(userPoolConfig *appsync.UserPoolConf return []interface{}{m} } +func flattenAppsyncGraphqlApiAdditionalAuthenticationProviders(additionalAuthenticationProviders []*appsync.AdditionalAuthenticationProvider) []interface{} { + if len(additionalAuthenticationProviders) == 0 { + return []interface{}{} + } + + result := make([]interface{}, len(additionalAuthenticationProviders)) + for i, provider := range additionalAuthenticationProviders { + result[i] = map[string]interface{}{ + "authentication_type": aws.StringValue(provider.AuthenticationType), + "openid_connect_config": flattenAppsyncGraphqlApiOpenIDConnectConfig(provider.OpenIDConnectConfig), + "user_pool_config": flattenAppsyncGraphqlApiCognitoUserPoolConfig(provider.UserPoolConfig), + } + } + + return result +} + +func flattenAppsyncGraphqlApiCognitoUserPoolConfig(userPoolConfig *appsync.CognitoUserPoolConfig) []interface{} { + if userPoolConfig == nil { + return []interface{}{} + } + + m := map[string]interface{}{ + "aws_region": aws.StringValue(userPoolConfig.AwsRegion), + "user_pool_id": aws.StringValue(userPoolConfig.UserPoolId), + } + + if userPoolConfig.AppIdClientRegex != nil { + m["app_id_client_regex"] = aws.StringValue(userPoolConfig.AppIdClientRegex) + } + + return []interface{}{m} +} + func resourceAwsAppsyncSchemaPut(d *schema.ResourceData, meta interface{}) error { conn := meta.(*AWSClient).appsyncconn diff --git a/aws/resource_aws_appsync_graphql_api_test.go b/aws/resource_aws_appsync_graphql_api_test.go index 69d4c83a1d9..ce4f2388ab1 100644 --- a/aws/resource_aws_appsync_graphql_api_test.go +++ b/aws/resource_aws_appsync_graphql_api_test.go @@ -87,6 +87,7 @@ func TestAccAWSAppsyncGraphqlApi_basic(t *testing.T) { resource.TestCheckResourceAttrSet(resourceName, "uris.%"), resource.TestCheckResourceAttrSet(resourceName, "uris.GRAPHQL"), resource.TestCheckNoResourceAttr(resourceName, "tags"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "0"), ), }, { @@ -694,6 +695,178 @@ func TestAccAWSAppsyncGraphqlApi_Tags(t *testing.T) { }) } +func TestAccAWSAppsyncGraphqlApi_AdditionalAuthentication_APIKey(t *testing.T) { + var api1 appsync.GraphqlApi + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appsync_graphql_api.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppsyncGraphqlApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncGraphqlApiConfig_AdditionalAuth_AuthType(rName, "AWS_IAM", "API_KEY"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppsyncGraphqlApiExists(resourceName, &api1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appsync", regexp.MustCompile(`apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "authentication_type", "AWS_IAM"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.authentication_type", "API_KEY"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.user_pool_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppsyncGraphqlApi_AdditionalAuthentication_AWSIAM(t *testing.T) { + var api1 appsync.GraphqlApi + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appsync_graphql_api.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppsyncGraphqlApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncGraphqlApiConfig_AdditionalAuth_AuthType(rName, "API_KEY", "AWS_IAM"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppsyncGraphqlApiExists(resourceName, &api1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appsync", regexp.MustCompile(`apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "authentication_type", "API_KEY"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.authentication_type", "AWS_IAM"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.user_pool_config.#", "0"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppsyncGraphqlApi_AdditionalAuthentication_CognitoUserPools(t *testing.T) { + var api1 appsync.GraphqlApi + rName := acctest.RandomWithPrefix("tf-acc-test") + cognitoUserPoolResourceName := "aws_cognito_user_pool.test" + resourceName := "aws_appsync_graphql_api.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppsyncGraphqlApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncGraphqlApiConfig_AdditionalAuth_UserPoolConfig(rName), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppsyncGraphqlApiExists(resourceName, &api1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appsync", regexp.MustCompile(`apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "authentication_type", "API_KEY"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.authentication_type", "AMAZON_COGNITO_USER_POOLS"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.user_pool_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "additional_authentication_provider.0.user_pool_config.0.user_pool_id", cognitoUserPoolResourceName, "id"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppsyncGraphqlApi_AdditionalAuthentication_OpenIDConnect(t *testing.T) { + var api1 appsync.GraphqlApi + rName := acctest.RandomWithPrefix("tf-acc-test") + resourceName := "aws_appsync_graphql_api.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppsyncGraphqlApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncGraphqlApiConfig_AdditionalAuth_OpenIdConnect(rName, "https://example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppsyncGraphqlApiExists(resourceName, &api1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appsync", regexp.MustCompile(`apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "authentication_type", "API_KEY"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.authentication_type", "OPENID_CONNECT"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.user_pool_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.0.issuer", "https://example.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccAWSAppsyncGraphqlApi_AdditionalAuthentication_Multiple(t *testing.T) { + var api1 appsync.GraphqlApi + rName := acctest.RandomWithPrefix("tf-acc-test") + cognitoUserPoolResourceName := "aws_cognito_user_pool.test" + resourceName := "aws_appsync_graphql_api.test" + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckAwsAppsyncGraphqlApiDestroy, + Steps: []resource.TestStep{ + { + Config: testAccAppsyncGraphqlApiConfig_AdditionalAuth_Multiple(rName, "https://example.com"), + Check: resource.ComposeTestCheckFunc( + testAccCheckAwsAppsyncGraphqlApiExists(resourceName, &api1), + testAccMatchResourceAttrRegionalARN(resourceName, "arn", "appsync", regexp.MustCompile(`apis/.+`)), + resource.TestCheckResourceAttr(resourceName, "name", rName), + resource.TestCheckResourceAttr(resourceName, "authentication_type", "API_KEY"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.#", "3"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.authentication_type", "AWS_IAM"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.user_pool_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.0.openid_connect_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.1.authentication_type", "AMAZON_COGNITO_USER_POOLS"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.1.openid_connect_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.1.user_pool_config.#", "1"), + resource.TestCheckResourceAttrPair(resourceName, "additional_authentication_provider.1.user_pool_config.0.user_pool_id", cognitoUserPoolResourceName, "id"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.2.authentication_type", "OPENID_CONNECT"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.2.user_pool_config.#", "0"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.2.openid_connect_config.#", "1"), + resource.TestCheckResourceAttr(resourceName, "additional_authentication_provider.2.openid_connect_config.0.issuer", "https://example.com"), + ), + }, + { + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckAwsAppsyncGraphqlApiDestroy(s *terraform.State) error { conn := testAccProvider.Meta().(*AWSClient).appsyncconn for _, rs := range s.RootModule().Resources { @@ -964,3 +1137,86 @@ resource "aws_appsync_graphql_api" "test" { } }`, rName) } + +func testAccAppsyncGraphqlApiConfig_AdditionalAuth_AuthType(rName, defaultAuthType, additionalAuthType string) string { + return fmt.Sprintf(` +resource "aws_appsync_graphql_api" "test" { + authentication_type = %q + name = %q + + additional_authentication_provider { + authentication_type = %q + } +}`, defaultAuthType, rName, additionalAuthType) +} + +func testAccAppsyncGraphqlApiConfig_AdditionalAuth_UserPoolConfig(rName string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %q +} + +resource "aws_appsync_graphql_api" "test" { + authentication_type = "API_KEY" + name = %q + + additional_authentication_provider { + authentication_type = "AMAZON_COGNITO_USER_POOLS" + + user_pool_config { + user_pool_id = "${aws_cognito_user_pool.test.id}" + } + } +} +`, rName, rName) +} + +func testAccAppsyncGraphqlApiConfig_AdditionalAuth_OpenIdConnect(rName, issuer string) string { + return fmt.Sprintf(` +resource "aws_appsync_graphql_api" "test" { + authentication_type = "API_KEY" + name = %q + + additional_authentication_provider { + authentication_type = "OPENID_CONNECT" + + openid_connect_config { + issuer = %q + } + } +} +`, rName, issuer) +} + +func testAccAppsyncGraphqlApiConfig_AdditionalAuth_Multiple(rName, issuer string) string { + return fmt.Sprintf(` +resource "aws_cognito_user_pool" "test" { + name = %q +} + +resource "aws_appsync_graphql_api" "test" { + authentication_type = "API_KEY" + name = %q + + additional_authentication_provider { + authentication_type = "AWS_IAM" + } + + additional_authentication_provider { + authentication_type = "AMAZON_COGNITO_USER_POOLS" + + user_pool_config { + user_pool_id = "${aws_cognito_user_pool.test.id}" + } + } + + additional_authentication_provider { + authentication_type = "OPENID_CONNECT" + + openid_connect_config { + issuer = %q + } + } +} +`, rName, rName, issuer) +} diff --git a/website/docs/r/appsync_graphql_api.html.markdown b/website/docs/r/appsync_graphql_api.html.markdown index 55fec58c7ae..f85146869fc 100644 --- a/website/docs/r/appsync_graphql_api.html.markdown +++ b/website/docs/r/appsync_graphql_api.html.markdown @@ -74,6 +74,19 @@ resource "aws_appsync_graphql_api" "example" { } ``` +### With Multiple Authentication Providers + +```hcl +resource "aws_appsync_graphql_api" "example" { + authentication_type = "API_KEY" + name = "example" + + additional_authentication_provider { + authentication_type = "AWS_IAM" + } +} +``` + ### Enabling Logging ```hcl @@ -121,6 +134,7 @@ The following arguments are supported: * `openid_connect_config` - (Optional) Nested argument containing OpenID Connect configuration. Defined below. * `user_pool_config` - (Optional) The Amazon Cognito User Pool configuration. Defined below. * `schema` - (Optional) The schema definition, in GraphQL schema language format. Terraform cannot perform drift detection of this configuration. +* `additional_authentication_provider` - (Optional) One or more additional authentication providers for the GraphqlApi. Defined below. * `tags` - (Optional) A mapping of tags to assign to the resource. ### log_config @@ -130,6 +144,14 @@ The following arguments are supported: * `cloudwatch_logs_role_arn` - (Required) Amazon Resource Name of the service role that AWS AppSync will assume to publish to Amazon CloudWatch logs in your account. * `field_log_level` - (Required) Field logging level. Valid values: `ALL`, `ERROR`, `NONE`. +### additional_authentication_provider + +The following arguments are supported: + +* `authentication_type` - (Required) The authentication type. Valid values: `API_KEY`, `AWS_IAM`, `AMAZON_COGNITO_USER_POOLS`, `OPENID_CONNECT` +* `openid_connect_config` - (Optional) Nested argument containing OpenID Connect configuration. Defined below. +* `user_pool_config` - (Optional) The Amazon Cognito User Pool configuration. Defined below. + ### openid_connect_config The following arguments are supported: @@ -143,7 +165,7 @@ The following arguments are supported: The following arguments are supported: -* `default_action` - (Required) The action that you want your GraphQL API to take when a request that uses Amazon Cognito User Pool authentication doesn't match the Amazon Cognito User Pool configuration. Valid: `ALLOW` and `DENY` +* `default_action` - (Required only if Cognito is used as the default auth provider) The action that you want your GraphQL API to take when a request that uses Amazon Cognito User Pool authentication doesn't match the Amazon Cognito User Pool configuration. Valid: `ALLOW` and `DENY` * `user_pool_id` - (Required) The user pool ID. * `app_id_client_regex` - (Optional) A regular expression for validating the incoming Amazon Cognito User Pool app client ID. * `aws_region` - (Optional) The AWS region in which the user pool was created.