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
39 changes: 39 additions & 0 deletions documentation/docs/user-guide/runtime/quickstart.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,4 +197,43 @@ AgentCore Runtime requires ARM64 containers (AWS Graviton). The toolkit handles
- **[Add tools with Gateway](../gateway/quickstart.md)** - Connect your agent to APIs and services
- **[Enable memory](../../examples/memory-integration.md)** - Give your agent conversation history
- **[Configure authentication](../runtime/auth.md)** - Set up OAuth/JWT auth
- **[Request Header Configuration](#request-header-configuration)** - Forward headers to your agent
- **[View more examples](../../examples/README.md)** - Learn from complete implementations

## Request Header Configuration

When you configure OAuth authentication, the system automatically forwards the `Authorization` header to your agent container. You can also configure additional headers to be forwarded.

### Automatic OAuth Header Forwarding

When OAuth is enabled, the `Authorization` header is automatically forwarded to your agent:

```yaml
# .bedrock_agentcore.yaml
agents:
my-agent:
# ... other configuration ...
authorizer_configuration:
customJWTAuthorizer:
discoveryUrl: "https://example.com/.well-known/openid_configuration"
allowedClients: ["client1", "client2"]
allowedAudience: ["audience1", "audience2"]

# Automatically added when OAuth is configured
request_header_configuration:
allowed_headers:
- "Authorization"
```

### Custom Header Configuration

You can manually edit the configuration file to forward additional headers:

```yaml
request_header_configuration:
allowed_headers:
- "Authorization" # Automatically added for OAuth
- "X-User-ID" # Add your custom headers
- "X-Tenant-ID"
- "X-Custom-Header"
```
7 changes: 7 additions & 0 deletions src/bedrock_agentcore_starter_toolkit/cli/runtime/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,15 +233,21 @@ def configure(

# Handle OAuth authorization configuration
oauth_config = None
request_header_config = None
if authorizer_config:
# Parse provided JSON configuration
try:
oauth_config = json.loads(authorizer_config)
_print_success("Using provided OAuth authorizer configuration")
# Auto-configure request headers for OAuth
request_header_config = config_manager.get_request_header_config_for_oauth()
except json.JSONDecodeError as e:
_handle_error(f"Invalid JSON in --authorizer-config: {e}", e)
else:
oauth_config = config_manager.prompt_oauth_config()
# Auto-configure request headers if OAuth is configured
if oauth_config:
request_header_config = config_manager.get_request_header_config_for_oauth()

try:
result = configure_bedrock_agentcore(
Expand All @@ -254,6 +260,7 @@ def configure(
enable_observability=not disable_otel,
requirements_file=final_requirements_file,
authorizer_configuration=oauth_config,
request_header_configuration=request_header_config,
verbose=verbose,
region=region,
protocol=protocol.upper() if protocol else None,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,3 +145,9 @@ def _configure_oauth(self) -> dict:

_print_success("OAuth authorizer configuration created")
return config

def get_request_header_config_for_oauth(self) -> Optional[dict]:
"""Get request header configuration for OAuth (auto-configured)."""
return {
"allowed_headers": ["Authorization"]
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def configure_bedrock_agentcore(
enable_observability: bool = True,
requirements_file: Optional[str] = None,
authorizer_configuration: Optional[Dict[str, Any]] = None,
request_header_configuration: Optional[Dict[str, Any]] = None,
verbose: bool = False,
region: Optional[str] = None,
protocol: Optional[str] = None,
Expand All @@ -49,6 +50,7 @@ def configure_bedrock_agentcore(
enable_observability: Whether to enable observability
requirements_file: Path to requirements file
authorizer_configuration: JWT authorizer configuration dictionary
request_header_configuration: Request header configuration dictionary
verbose: Whether to provide verbose output during configuration
region: AWS region for deployment
protocol: agent server protocol, must be either HTTP or MCP
Expand Down Expand Up @@ -193,6 +195,7 @@ def configure_bedrock_agentcore(
),
bedrock_agentcore=BedrockAgentCoreDeploymentInfo(),
authorizer_configuration=authorizer_configuration,
request_header_configuration=request_header_configuration,
)

# Use simplified config merging
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ def _deploy_to_bedrock_agentcore(
execution_role_arn=agent_config.aws.execution_role,
network_config=network_config,
authorizer_config=agent_config.get_authorizer_configuration(),
request_header_config=agent_config.get_request_header_configuration(),
protocol_config=protocol_config,
env_vars=env_vars,
auto_update_on_conflict=auto_update_on_conflict,
Expand Down
13 changes: 12 additions & 1 deletion src/bedrock_agentcore_starter_toolkit/services/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ def create_agent(
execution_role_arn: str,
network_config: Optional[Dict] = None,
authorizer_config: Optional[Dict] = None,
request_header_config: Optional[Dict] = None,
protocol_config: Optional[Dict] = None,
env_vars: Optional[Dict] = None,
auto_update_on_conflict: bool = False,
Expand All @@ -142,6 +143,9 @@ def create_agent(
if authorizer_config is not None:
params["authorizerConfiguration"] = authorizer_config

if request_header_config is not None:
params["requestHeaderConfiguration"] = request_header_config

if protocol_config is not None:
params["protocolConfiguration"] = protocol_config

Expand Down Expand Up @@ -196,6 +200,7 @@ def create_agent(
execution_role_arn,
network_config,
authorizer_config,
request_header_config,
protocol_config,
env_vars,
)
Expand All @@ -216,6 +221,7 @@ def update_agent(
execution_role_arn: str,
network_config: Optional[Dict] = None,
authorizer_config: Optional[Dict] = None,
request_header_config: Optional[Dict] = None,
protocol_config: Optional[Dict] = None,
env_vars: Optional[Dict] = None,
) -> Dict[str, str]:
Expand All @@ -235,6 +241,9 @@ def update_agent(
if authorizer_config is not None:
params["authorizerConfiguration"] = authorizer_config

if request_header_config is not None:
params["requestHeaderConfiguration"] = request_header_config

if protocol_config is not None:
params["protocolConfiguration"] = protocol_config

Expand Down Expand Up @@ -297,21 +306,23 @@ def create_or_update_agent(
execution_role_arn: str,
network_config: Optional[Dict] = None,
authorizer_config: Optional[Dict] = None,
request_header_config: Optional[Dict] = None,
protocol_config: Optional[Dict] = None,
env_vars: Optional[Dict] = None,
auto_update_on_conflict: bool = False,
) -> Dict[str, str]:
"""Create or update agent."""
if agent_id:
return self.update_agent(
agent_id, image_uri, execution_role_arn, network_config, authorizer_config, protocol_config, env_vars
agent_id, image_uri, execution_role_arn, network_config, authorizer_config, request_header_config, protocol_config, env_vars
)
return self.create_agent(
agent_name,
image_uri,
execution_role_arn,
network_config,
authorizer_config,
request_header_config,
protocol_config,
env_vars,
auto_update_on_conflict,
Expand Down
14 changes: 14 additions & 0 deletions src/bedrock_agentcore_starter_toolkit/utils/runtime/schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,11 +82,25 @@ class BedrockAgentCoreAgentSchema(BaseModel):
codebuild: CodeBuildConfig = Field(default_factory=CodeBuildConfig)
authorizer_configuration: Optional[dict] = Field(default=None, description="JWT authorizer configuration")
oauth_configuration: Optional[dict] = Field(default=None, description="Oauth configuration")
request_header_configuration: Optional[dict] = Field(default=None, description="Request header configuration")

def get_authorizer_configuration(self) -> Optional[dict]:
"""Get the authorizer configuration."""
return self.authorizer_configuration

def get_request_header_configuration(self) -> Optional[dict]:
"""Get request header configuration for AWS API."""
if not self.request_header_configuration:
return None

allowed_headers = self.request_header_configuration.get("allowed_headers", [])
if not allowed_headers:
return None

return {
"requestHeaderAllowlist": allowed_headers
}

def validate(self, for_local: bool = False) -> List[str]:
"""Validate configuration and return list of errors.

Expand Down
15 changes: 15 additions & 0 deletions tests/cli/runtime/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -1680,6 +1680,21 @@ def test_configure_oauth(self, tmp_path):
mock_prompt.assert_any_call("Enter allowed OAuth audience (comma-separated)", "")
mock_success.assert_called_once_with("OAuth authorizer configuration created")

def test_get_request_header_config_for_oauth(self, tmp_path):
"""Test get_request_header_config_for_oauth returns correct configuration."""
from bedrock_agentcore_starter_toolkit.cli.runtime.commands import ConfigurationManager

with patch("bedrock_agentcore_starter_toolkit.utils.runtime.config.load_config_if_exists", return_value=None):
config_manager = ConfigurationManager(tmp_path / ".bedrock_agentcore.yaml")

result = config_manager.get_request_header_config_for_oauth()

expected_config = {
"allowed_headers": ["Authorization"]
}

assert result == expected_config

def test_configure_oauth_with_existing_values(self, tmp_path):
"""Test _configure_oauth with existing configuration values as defaults."""
from bedrock_agentcore_starter_toolkit.cli.runtime.commands import ConfigurationManager
Expand Down
28 changes: 28 additions & 0 deletions tests/services/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,34 @@ def test_create_agent_with_optional_configs(self, mock_boto3_clients):
assert call_args["protocolConfiguration"] == protocol_config
assert call_args["environmentVariables"] == env_vars

def test_create_agent_with_request_header_config(self, mock_boto3_clients):
"""Test create agent with request header configuration."""
client = BedrockAgentCoreClient("us-west-2")

network_config = {"networkMode": "PRIVATE"}
authorizer_config = {"type": "IAM"}
request_header_config = {"requestHeaderAllowlist": ["Authorization", "X-User-ID"]}
protocol_config = {"serverProtocol": "MCP"}
env_vars = {"ENV1": "HELLO", "ENV2": "WORLD"}

result = client.create_agent(
agent_name="test-agent",
image_uri="123456789012.dkr.ecr.us-west-2.amazonaws.com/test:latest",
execution_role_arn="arn:aws:iam::123456789012:role/TestRole",
network_config=network_config,
authorizer_config=authorizer_config,
request_header_config=request_header_config,
protocol_config=protocol_config,
env_vars=env_vars,
)

assert result["id"] == "test-agent-id"
assert result["arn"] == "arn:aws:bedrock_agentcore:us-west-2:123456789012:agent-runtime/test-agent-id"

# Verify the call included request header config
call_args = mock_boto3_clients["bedrock_agentcore"].create_agent_runtime.call_args[1]
assert call_args["requestHeaderConfiguration"] == request_header_config

def test_create_agent_error_handling(self, mock_boto3_clients):
"""Test create agent error handling."""
client = BedrockAgentCoreClient("us-west-2")
Expand Down
59 changes: 59 additions & 0 deletions tests/utils/runtime/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -351,3 +351,62 @@ def test_merge_agent_config_logging_calls(self, mock_log, tmp_path):
mock_log.reset_mock()
merge_agent_config(config_path, "agent2", agent2)
mock_log.info.assert_called_with("Keeping '%s' as default agent", "agent2")


class TestRequestHeaderConfiguration:
"""Test request header configuration functionality."""

def test_get_request_header_configuration_with_headers(self):
"""Test get_request_header_configuration with allowed headers."""
agent_config = BedrockAgentCoreAgentSchema(
name="test-agent",
entrypoint="test.py",
request_header_configuration={
"allowed_headers": ["Authorization", "X-User-ID", "X-Custom-Header"]
}
)

result = agent_config.get_request_header_configuration()

expected = {
"requestHeaderAllowlist": ["Authorization", "X-User-ID", "X-Custom-Header"]
}

assert result == expected

def test_get_request_header_configuration_empty_headers(self):
"""Test get_request_header_configuration with empty allowed headers."""
agent_config = BedrockAgentCoreAgentSchema(
name="test-agent",
entrypoint="test.py",
request_header_configuration={
"allowed_headers": []
}
)

result = agent_config.get_request_header_configuration()

assert result is None

def test_get_request_header_configuration_no_config(self):
"""Test get_request_header_configuration with no configuration."""
agent_config = BedrockAgentCoreAgentSchema(
name="test-agent",
entrypoint="test.py"
)

result = agent_config.get_request_header_configuration()

assert result is None

def test_get_request_header_configuration_missing_headers_key(self):
"""Test get_request_header_configuration with missing allowed_headers key."""
agent_config = BedrockAgentCoreAgentSchema(
name="test-agent",
entrypoint="test.py",
request_header_configuration={}
)

result = agent_config.get_request_header_configuration()

assert result is None
Loading