Skip to content

Commit 094fb77

Browse files
author
Kyon Caldera
committed
feat(sigv4_helper): add region and service argument to _inject_metadata_hook to allow for proper resigning of sigv4 to work
1 parent eeee136 commit 094fb77

2 files changed

Lines changed: 159 additions & 73 deletions

File tree

mcp_proxy_for_aws/sigv4_helper.py

Lines changed: 75 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -33,10 +33,10 @@ class SigV4HTTPXAuth(httpx.Auth):
3333
"""HTTPX Auth class that signs requests with AWS SigV4."""
3434

3535
def __init__(
36-
self,
37-
credentials: Credentials,
38-
service: str,
39-
region: str,
36+
self,
37+
credentials: Credentials,
38+
service: str,
39+
region: str,
4040
):
4141
"""Initialize SigV4HTTPXAuth.
4242
@@ -123,11 +123,70 @@ async def _handle_error_response(response: httpx.Response) -> None:
123123
raise e
124124

125125

126-
async def _inject_metadata_hook(metadata: Dict[str, Any], request: httpx.Request) -> None:
126+
def _resign_request_with_sigv4(
127+
request: httpx.Request,
128+
region: str,
129+
service: str,
130+
profile: Optional[str] = None,
131+
) -> None:
132+
"""Re-sign an HTTP request with AWS SigV4 after content modification.
133+
134+
This function removes old signature headers, creates a new signature based on
135+
the current request content, and updates the request headers with the new signature.
136+
137+
Args:
138+
request: The HTTP request object to re-sign (modified in-place)
139+
region: AWS region for SigV4 signing
140+
service: AWS service name for SigV4 signing
141+
profile: AWS profile to use (optional)
142+
"""
143+
# Remove old signature headers before re-signing
144+
headers_to_remove = ['Content-Length', 'x-amz-date', 'x-amz-security-token', 'authorization']
145+
for header in headers_to_remove:
146+
request.headers.pop(header, None)
147+
148+
# Set the new Content-Length
149+
request.headers['Content-Length'] = str(len(request.content))
150+
151+
logger.info('Headers after cleanup: %s', request.headers)
152+
153+
# Get AWS credentials
154+
session = create_aws_session(profile)
155+
credentials = session.get_credentials()
156+
logger.info('Re-signing request with credentials for access key: %s', credentials.access_key)
157+
158+
# Create headers dict for signing, removing connection header like in auth_flow
159+
headers_for_signing = dict(request.headers)
160+
headers_for_signing.pop('connection', None) # Remove connection header for signing
161+
162+
# Create SigV4 signer and AWS request
163+
signer = SigV4Auth(credentials, service, region)
164+
aws_request = AWSRequest(
165+
method=request.method,
166+
url=str(request.url),
167+
data=request.content,
168+
headers=headers_for_signing,
169+
)
170+
171+
# Sign the request
172+
logger.info('AWS request before signing: %s', aws_request.headers)
173+
signer.add_auth(aws_request)
174+
logger.info('AWS request after signing: %s', aws_request.headers)
175+
176+
# Update request headers with signed headers
177+
request.headers.update(dict(aws_request.headers))
178+
logger.info('Request headers after re-signing: %s', request.headers)
179+
180+
181+
async def _inject_metadata_hook(
182+
metadata: Dict[str, Any], region: str, service: str, request: httpx.Request
183+
) -> None:
127184
"""Request hook to inject metadata into MCP calls.
128185
129186
Args:
130187
metadata: Dictionary of metadata to inject into _meta field
188+
region: AWS region for SigV4 re-signing after metadata injection
189+
service: AWS service name for SigV4 re-signing after metadata injection
131190
request: The HTTP request object
132191
"""
133192
logger.info('=== Outgoing Request ===')
@@ -174,47 +233,9 @@ async def _inject_metadata_hook(metadata: Dict[str, Any], request: httpx.Request
174233
request.stream = ByteStream(new_content)
175234
request._content = new_content
176235

177-
# Remove old signature headers before re-signing
178-
del request.headers['Content-Length']
179-
del request.headers['x-amz-date']
180-
del request.headers['x-amz-security-token']
181-
del request.headers['authorization']
182-
183-
# Set the new Content-Length AFTER deleting the old one
184-
request.headers['Content-Length'] = str(len(new_content))
185-
186-
logger.info('Headers are: %s', request.headers)
187-
service = 'eks-mcp'
188-
region = 'us-west-2'
189-
session = create_aws_session()
190-
credentials = session.get_credentials()
191-
logger.info('DEBUG about to sign again: %s', credentials)
192-
193-
# Create headers dict for signing, removing connection header like in auth_flow
194-
headers_for_signing = dict(request.headers)
195-
headers_for_signing.pop('connection', None) # Remove connection header for signing
196-
197-
signer = SigV4Auth(credentials, service, region)
198-
aws_request = AWSRequest(
199-
method=request.method,
200-
url=str(request.url),
201-
data=request.content,
202-
headers=headers_for_signing,
203-
)
204-
205-
# Sign the request with SigV4
206-
logger.info('aws_request before signing: %s', aws_request.headers)
207-
signer.add_auth(aws_request)
208-
logger.info('aws_request after signing: %s', aws_request.headers)
209-
210-
# Update request headers with signed headers (this includes Authorization, x-amz-date, etc.)
211-
request.headers['x-amz-date'] = aws_request.headers['x-amz-date']
212-
request.headers['x-amz-security-token'] = aws_request.headers[
213-
'x-amz-security-token'
214-
]
215-
request.headers['Authorization'] = aws_request.headers['Authorization']
236+
# Re-sign the request with the new content
237+
_resign_request_with_sigv4(request, region, service)
216238

217-
logger.info('New headers: %s', request.headers)
218239
logger.info('Injected metadata into _meta: %s', body['params']['_meta'])
219240

220241
except (json.JSONDecodeError, KeyError, TypeError) as e:
@@ -281,14 +302,14 @@ def create_sigv4_auth(service: str, region: str, profile: Optional[str] = None)
281302

282303

283304
def create_sigv4_client(
284-
service: str,
285-
region: str,
286-
timeout: Optional[httpx.Timeout] = None,
287-
profile: Optional[str] = None,
288-
headers: Optional[Dict[str, str]] = None,
289-
auth: Optional[httpx.Auth] = None,
290-
metadata: Optional[Dict[str, Any]] = None,
291-
**kwargs: Any,
305+
service: str,
306+
region: str,
307+
timeout: Optional[httpx.Timeout] = None,
308+
profile: Optional[str] = None,
309+
headers: Optional[Dict[str, str]] = None,
310+
auth: Optional[httpx.Auth] = None,
311+
metadata: Optional[Dict[str, Any]] = None,
312+
**kwargs: Any,
292313
) -> httpx.AsyncClient:
293314
"""Create an httpx.AsyncClient with SigV4 authentication.
294315
@@ -334,6 +355,6 @@ def create_sigv4_client(
334355
**client_kwargs,
335356
event_hooks={
336357
'response': [_handle_error_response],
337-
'request': [partial(_inject_metadata_hook, metadata or {})],
358+
'request': [partial(_inject_metadata_hook, metadata or {}, region, service)],
338359
},
339360
)

0 commit comments

Comments
 (0)