Skip to content
Open
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
3 changes: 3 additions & 0 deletions .changelog/48658.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:enhancement
resource/aws_bedrockagentcore_oauth2_credential_provider: Add `oauth2_provider_config.custom_oauth2_provider_config.on_behalf_of_token_exchange_config` configuration block to support on-behalf-of (OBO) token exchange
```
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,43 @@ func (r *oauth2CredentialProviderResource) Schema(ctx context.Context, request r
},
},
},
"on_behalf_of_token_exchange_config": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[onBehalfOfTokenExchangeConfigModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"grant_type": schema.StringAttribute{
Required: true,
CustomType: fwtypes.StringEnumType[awstypes.OnBehalfOfTokenExchangeGrantTypeType](),
Description: "The grant type for the on-behalf-of token exchange. Valid values are `TOKEN_EXCHANGE` (RFC 8693) and `JWT_AUTHORIZATION_GRANT` (RFC 7523).",
},
},
Blocks: map[string]schema.Block{
"token_exchange_grant_type_config": schema.ListNestedBlock{
CustomType: fwtypes.NewListNestedObjectTypeOf[tokenExchangeGrantTypeConfigModel](ctx),
Validators: []validator.List{
listvalidator.SizeAtMost(1),
},
NestedObject: schema.NestedBlockObject{
Attributes: map[string]schema.Attribute{
"actor_token_content": schema.StringAttribute{
Required: true,
CustomType: fwtypes.StringEnumType[awstypes.ActorTokenContentType](),
Description: "The content type for the actor token in the token exchange. Valid values are `M2M`, `AWS_IAM_ID_TOKEN_JWT`, and `NONE`.",
},
"actor_token_scopes": schema.SetAttribute{
CustomType: fwtypes.SetOfStringType,
Optional: true,
Description: "The scopes for the actor token. Only valid when `actor_token_content` is `M2M`.",
},
},
},
},
},
},
},
},
},
},
Expand Down Expand Up @@ -745,7 +782,18 @@ type oauth2DiscoveryModel struct {

type customOAuth2ProviderConfigModel struct {
oauth2ClientCredentialsModel
OAuthDiscovery fwtypes.ListNestedObjectValueOf[oauth2DiscoveryModel] `tfsdk:"oauth_discovery"`
OAuthDiscovery fwtypes.ListNestedObjectValueOf[oauth2DiscoveryModel] `tfsdk:"oauth_discovery"`
OnBehalfOfTokenExchangeConfig fwtypes.ListNestedObjectValueOf[onBehalfOfTokenExchangeConfigModel] `tfsdk:"on_behalf_of_token_exchange_config"`
}

type onBehalfOfTokenExchangeConfigModel struct {
GrantType fwtypes.StringEnum[awstypes.OnBehalfOfTokenExchangeGrantTypeType] `tfsdk:"grant_type"`
TokenExchangeGrantTypeConfig fwtypes.ListNestedObjectValueOf[tokenExchangeGrantTypeConfigModel] `tfsdk:"token_exchange_grant_type_config"`
}

type tokenExchangeGrantTypeConfigModel struct {
ActorTokenContent fwtypes.StringEnum[awstypes.ActorTokenContentType] `tfsdk:"actor_token_content"`
ActorTokenScopes fwtypes.SetOfString `tfsdk:"actor_token_scopes"`
}

type githubOAuth2ProviderConfigModel struct {
Expand Down
114 changes: 114 additions & 0 deletions internal/service/bedrockagentcore/oauth2_credential_provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,65 @@ func TestAccBedrockAgentCoreOAuth2CredentialProvider_authorizationServerMetadata
})
}

func TestAccBedrockAgentCoreOAuth2CredentialProvider_onBehalfOfTokenExchange(t *testing.T) {
ctx := acctest.Context(t)
var oauth2credentialprovider bedrockagentcorecontrol.GetOauth2CredentialProviderOutput
rName := acctest.RandomWithPrefix(t, acctest.ResourcePrefix)
resourceName := "aws_bedrockagentcore_oauth2_credential_provider.test"

acctest.ParallelTest(ctx, t, resource.TestCase{
PreCheck: func() {
acctest.PreCheck(ctx, t)
acctest.PreCheckPartitionHasService(t, names.BedrockEndpointID)
testAccPreCheckOAuth2CredentialProviders(ctx, t)
},
ErrorCheck: acctest.ErrorCheck(t, names.BedrockAgentCoreServiceID),
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
CheckDestroy: testAccCheckOAuth2CredentialProviderDestroy(ctx, t),
Steps: []resource.TestStep{
// Step 1: TOKEN_EXCHANGE grant type with M2M actor token.
{
Config: testAccOAuth2CredentialProviderConfig_oboTokenExchange(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckOAuth2CredentialProviderExists(ctx, t, resourceName, &oauth2credentialprovider),
resource.TestCheckResourceAttr(resourceName, "oauth2_provider_config.0.custom_oauth2_provider_config.0.on_behalf_of_token_exchange_config.0.grant_type", "TOKEN_EXCHANGE"),
resource.TestCheckResourceAttr(resourceName, "oauth2_provider_config.0.custom_oauth2_provider_config.0.on_behalf_of_token_exchange_config.0.token_exchange_grant_type_config.0.actor_token_content", "M2M"),
resource.TestCheckResourceAttr(resourceName, "oauth2_provider_config.0.custom_oauth2_provider_config.0.on_behalf_of_token_exchange_config.0.token_exchange_grant_type_config.0.actor_token_scopes.#", "2"),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionCreate),
},
},
},
{
ResourceName: resourceName,
ImportState: true,
ImportStateIdFunc: acctest.AttrImportStateIdFunc(resourceName, names.AttrName),
ImportStateVerify: true,
ImportStateVerifyIdentifierAttribute: names.AttrName,
ImportStateVerifyIgnore: []string{
"oauth2_provider_config.0.custom_oauth2_provider_config.0.client_credentials_wo_version",
},
},
// Step 2: switch to JWT_AUTHORIZATION_GRANT (no actor token config).
{
Config: testAccOAuth2CredentialProviderConfig_oboJWTAuthorizationGrant(rName),
Check: resource.ComposeAggregateTestCheckFunc(
testAccCheckOAuth2CredentialProviderExists(ctx, t, resourceName, &oauth2credentialprovider),
resource.TestCheckResourceAttr(resourceName, "oauth2_provider_config.0.custom_oauth2_provider_config.0.on_behalf_of_token_exchange_config.0.grant_type", "JWT_AUTHORIZATION_GRANT"),
resource.TestCheckResourceAttr(resourceName, "oauth2_provider_config.0.custom_oauth2_provider_config.0.on_behalf_of_token_exchange_config.0.token_exchange_grant_type_config.#", "0"),
),
ConfigPlanChecks: resource.ConfigPlanChecks{
PreApply: []plancheck.PlanCheck{
plancheck.ExpectResourceAction(resourceName, plancheck.ResourceActionUpdate),
},
},
},
},
})
}

func TestAccBedrockAgentCoreOAuth2CredentialProvider_full(t *testing.T) {
ctx := acctest.Context(t)
var oauth2credentialprovider bedrockagentcorecontrol.GetOauth2CredentialProviderOutput
Expand Down Expand Up @@ -446,6 +505,61 @@ resource "aws_bedrockagentcore_oauth2_credential_provider" "test" {
`, rName, clientId, clientSecret, version, issuer, authEndpoint, tokenEndpoint, responseType1, responseType2)
}

func testAccOAuth2CredentialProviderConfig_oboTokenExchange(rName string) string {
return fmt.Sprintf(`
resource "aws_bedrockagentcore_oauth2_credential_provider" "test" {
name = %[1]q

credential_provider_vendor = "CustomOauth2"
oauth2_provider_config {
custom_oauth2_provider_config {
client_id_wo = "obo-client-id"
client_secret_wo = "obo-client-secret"
client_credentials_wo_version = 1

oauth_discovery {
discovery_url = "https://dev-example.auth0.com/.well-known/openid-configuration"
}

on_behalf_of_token_exchange_config {
grant_type = "TOKEN_EXCHANGE"

token_exchange_grant_type_config {
actor_token_content = "M2M"
actor_token_scopes = ["scope1", "scope2"]
}
}
}
}
}
`, rName)
}

func testAccOAuth2CredentialProviderConfig_oboJWTAuthorizationGrant(rName string) string {
return fmt.Sprintf(`
resource "aws_bedrockagentcore_oauth2_credential_provider" "test" {
name = %[1]q

credential_provider_vendor = "CustomOauth2"
oauth2_provider_config {
custom_oauth2_provider_config {
client_id_wo = "obo-client-id"
client_secret_wo = "obo-client-secret"
client_credentials_wo_version = 1

oauth_discovery {
discovery_url = "https://dev-example.auth0.com/.well-known/openid-configuration"
}

on_behalf_of_token_exchange_config {
grant_type = "JWT_AUTHORIZATION_GRANT"
}
}
}
}
`, rName)
}

func testAccOAuth2CredentialProviderConfig_full(rName string) string {
return fmt.Sprintf(`
resource "aws_bedrockagentcore_oauth2_credential_provider" "test" {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,36 @@ resource "aws_bedrockagentcore_oauth2_credential_provider" "keycloak" {
}
```

### Custom OAuth Provider with On-Behalf-Of Token Exchange

```terraform
resource "aws_bedrockagentcore_oauth2_credential_provider" "obo" {
name = "obo-oauth-provider"

credential_provider_vendor = "CustomOauth2"
oauth2_provider_config {
custom_oauth2_provider_config {
client_id_wo = "obo-client-id"
client_secret_wo = "obo-client-secret"
client_credentials_wo_version = 1

oauth_discovery {
discovery_url = "https://my.idp.com/.well-known/openid-configuration"
}

on_behalf_of_token_exchange_config {
grant_type = "TOKEN_EXCHANGE"

token_exchange_grant_type_config {
actor_token_content = "M2M"
actor_token_scopes = ["scope1", "scope2"]
}
}
}
}
}
```

## Argument Reference

The following arguments are required:
Expand Down Expand Up @@ -120,6 +150,10 @@ The `custom_oauth2_provider_config` block supports the following:

* `oauth_discovery` - (Optional) OAuth discovery configuration. See [`oauth_discovery`](#oauth_discovery) below.

**On-Behalf-Of Token Exchange Configuration:**

* `on_behalf_of_token_exchange_config` - (Optional) On-behalf-of (OBO) token exchange configuration. Enables the provider to exchange an inbound user token for a downstream access token. See [`on_behalf_of_token_exchange_config`](#on_behalf_of_token_exchange_config) below.

### `github`, `google`, `microsoft`, `salesforce`, `slack`

These predefined provider blocks support the following:
Expand Down Expand Up @@ -153,6 +187,20 @@ The `authorization_server_metadata` block supports the following:
* `token_endpoint` - (Required) OAuth2 token endpoint URL.
* `response_types` - (Optional) Set of OAuth2 response types supported by the authorization server.

### `on_behalf_of_token_exchange_config`

The `on_behalf_of_token_exchange_config` block supports the following:

* `grant_type` - (Required) Grant type for the on-behalf-of token exchange. Valid values: `TOKEN_EXCHANGE` (RFC 8693 token exchange), `JWT_AUTHORIZATION_GRANT` (RFC 7523 JWT authorization grant).
* `token_exchange_grant_type_config` - (Optional) Configuration specific to the `TOKEN_EXCHANGE` grant type. See [`token_exchange_grant_type_config`](#token_exchange_grant_type_config) below.

### `token_exchange_grant_type_config`

The `token_exchange_grant_type_config` block supports the following:

* `actor_token_content` - (Required) Content type for the actor token in the token exchange. Valid values: `M2M` (machine-to-machine client credentials token), `AWS_IAM_ID_TOKEN_JWT` (AWS IAM web identity token), `NONE` (no actor token).
* `actor_token_scopes` - (Optional) Set of scopes for the actor token. Only valid when `actor_token_content` is `M2M`.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:
Expand Down