Skip to content

Commit 4375dac

Browse files
committed
fix M2M
1 parent fc2f717 commit 4375dac

File tree

18 files changed

+35045
-1
lines changed

18 files changed

+35045
-1
lines changed

packages/@aws-cdk/aws-bedrock-agentcore-alpha/lib/gateway/gateway.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -389,6 +389,16 @@ export class Gateway extends GatewayBase {
389389
*/
390390
public userPoolClient?: cognito.IUserPoolClient;
391391

392+
/**
393+
* The Cognito User Pool Domain created for the gateway (if using default Cognito authorizer)
394+
*/
395+
public userPoolDomain?: cognito.UserPoolDomain;
396+
397+
/**
398+
* The Cognito Resource Server created for the gateway (if using default Cognito authorizer)
399+
*/
400+
public resourceServer?: cognito.UserPoolResourceServer;
401+
392402
constructor(scope: Construct, id: string, props: GatewayProps) {
393403
super(scope, id);
394404
// Enhanced CDK Analytics Telemetry
@@ -669,19 +679,68 @@ export class Gateway extends GatewayBase {
669679

670680
/**
671681
* Creates a default Cognito authorizer for the gateway
672-
* Provisions a Cognito User Pool and configures JWT authentication
682+
* Provisions a Cognito User Pool and configures M2M (machine-to-machine) JWT authentication
683+
* using OAuth 2.0 client credentials grant flow
684+
* @see https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity-idp-cognito.html
673685
* @internal
674686
*/
675687
private createDefaultCognitoAuthorizerConfig(): IGatewayAuthorizerConfig {
688+
// Create User Pool for M2M authentication
676689
const userPool = new cognito.UserPool(this, 'UserPool', {
677690
userPoolName: `${this.name}-gw-userpool`,
678691
signInCaseSensitive: false,
679692
});
693+
694+
const resourceServer = userPool.addResourceServer('ResourceServer', {
695+
identifier: `${this.name}-gateway-resource-server`,
696+
userPoolResourceServerName: `${this.name}-GatewayResourceServer`,
697+
scopes: [
698+
{
699+
scopeName: 'read',
700+
scopeDescription: 'Read access to gateway tools',
701+
},
702+
{
703+
scopeName: 'write',
704+
scopeDescription: 'Write access to gateway tools',
705+
},
706+
],
707+
});
708+
680709
const userPoolClient = userPool.addClient('DefaultClient', {
681710
userPoolClientName: `${this.name}-gw-client`,
711+
generateSecret: true,
712+
oAuth: {
713+
flows: {
714+
clientCredentials: true,
715+
},
716+
scopes: [
717+
cognito.OAuthScope.resourceServer(resourceServer, {
718+
scopeName: 'read',
719+
scopeDescription: 'Read access to gateway tools',
720+
}),
721+
cognito.OAuthScope.resourceServer(resourceServer, {
722+
scopeName: 'write',
723+
scopeDescription: 'Write access to gateway tools',
724+
}),
725+
],
726+
},
682727
});
728+
729+
// Create Cognito Domain for OAuth2 token endpoint
730+
// Use gateway name as domain prefix (already validated to be alphanumeric with hyphens/underscores)
731+
// Replace underscores with hyphens and convert to lowercase for Cognito domain requirements
732+
const domainPrefix = `${this.name}-gw`.replace(/_/g, '-').toLowerCase();
733+
const userPoolDomain = userPool.addDomain('Domain', {
734+
cognitoDomain: {
735+
domainPrefix: domainPrefix,
736+
},
737+
});
738+
683739
this.userPool = userPool;
684740
this.userPoolClient = userPoolClient;
741+
this.userPoolDomain = userPoolDomain;
742+
this.resourceServer = resourceServer;
743+
685744
return GatewayAuthorizer.usingCognito({
686745
userPool: userPool,
687746
allowedClients: [userPoolClient],
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Minimal test application for Gateway integration
2+
FROM public.ecr.aws/docker/library/python:3.12-slim
3+
4+
WORKDIR /app
5+
6+
# Copy application code
7+
COPY app.py .
8+
9+
# Expose port for AgentCore Runtime
10+
EXPOSE 8080
11+
12+
# Run the application
13+
CMD ["python", "app.py"]
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import json
2+
import os
3+
import base64
4+
import urllib.request
5+
from http.server import HTTPServer, BaseHTTPRequestHandler
6+
7+
8+
def get_token():
9+
"""Get M2M access token from Cognito"""
10+
credentials = f"{os.environ['CLIENT_ID']}:{os.environ['CLIENT_SECRET']}"
11+
auth = base64.b64encode(credentials.encode()).decode()
12+
13+
req = urllib.request.Request(
14+
os.environ['TOKEN_ENDPOINT'],
15+
data=f"grant_type=client_credentials&scope={os.environ['SCOPE']}".encode(),
16+
headers={
17+
'Content-Type': 'application/x-www-form-urlencoded',
18+
'Authorization': f'Basic {auth}'
19+
}
20+
)
21+
22+
with urllib.request.urlopen(req) as response:
23+
return json.loads(response.read())['access_token']
24+
25+
26+
def call_tool(url, token):
27+
"""Call Gateway tool via MCP"""
28+
if not url.endswith('/mcp'):
29+
url = f"{url.rstrip('/')}/mcp"
30+
31+
req = urllib.request.Request(
32+
url,
33+
data=json.dumps({
34+
'jsonrpc': '2.0',
35+
'method': 'tools/call',
36+
'params': {'name': 'test-tools___hello', 'arguments': {}},
37+
'id': 1
38+
}).encode(),
39+
headers={
40+
'Content-Type': 'application/json',
41+
'Authorization': f'Bearer {token}'
42+
}
43+
)
44+
45+
with urllib.request.urlopen(req) as response:
46+
return json.loads(response.read())['result']
47+
48+
49+
class Handler(BaseHTTPRequestHandler):
50+
def do_GET(self):
51+
if self.path == '/ping':
52+
self.send_response(200)
53+
self.send_header('Content-Type', 'application/json')
54+
self.end_headers()
55+
self.wfile.write(json.dumps({'status': 'healthy'}).encode())
56+
57+
def do_POST(self):
58+
if self.path == '/invocations':
59+
try:
60+
token = get_token()
61+
result = call_tool(os.environ['GATEWAY_URL'], token)
62+
63+
self.send_response(200)
64+
self.send_header('Content-Type', 'application/json')
65+
self.end_headers()
66+
self.wfile.write(json.dumps({
67+
'output': {
68+
'message': 'Gateway M2M authentication successful',
69+
'result': result
70+
}
71+
}).encode())
72+
except Exception as e:
73+
self.send_response(500)
74+
self.send_header('Content-Type', 'application/json')
75+
self.end_headers()
76+
self.wfile.write(json.dumps({'error': str(e)}).encode())
77+
78+
def log_message(self, format, *args):
79+
pass
80+
81+
82+
if __name__ == '__main__':
83+
HTTPServer(('', 8080), Handler).serve_forever()
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# No external dependencies required for minimal test application

packages/@aws-cdk/aws-bedrock-agentcore-alpha/test/agentcore/gateway/integ.gateway-with-runtime.js.snapshot/BedrockAgentCoreRuntimeGatewayIntegTest.assets.json

Lines changed: 48 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)