Skip to content

Conversation

@kbdraai
Copy link
Contributor

@kbdraai kbdraai commented Dec 18, 2024

Issue #, if available:

Description of changes:
Added certificate bound access token support using API Gateway and Cognito. Provided through a SAM template.

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@ellisms
Copy link
Contributor

ellisms commented Jan 16, 2025

@kbdraai I started testing this, but the Lambda Authorizer function fails with the following error:

[ERROR] Runtime.ImportModuleError: Unable to import module 'handlers.authorizer': PyO3 modules compiled for CPython 3.8 or older may only be initialized once per interpreter process
Traceback (most recent call last):

@kbdraai
Copy link
Contributor Author

kbdraai commented Jan 17, 2025

@ellisms Thanks for looking at this.

Did you run a sam build with use_container=true?
I ran through it again and it works for me. The issue seems with the build phase as it seems that it was build with python 3.8 modules.

Can you elaborate on the process you followed?
The OS?
Did you fill in the complete the samconfig.toml file with the values required?

@ellisms
Copy link
Contributor

ellisms commented Jan 17, 2025

@kbdraai --use-container resolves the build issue. The authorizer does run, but encounters the following error:

[ERROR]	2025-01-17T12:57:25.240Z	a7d3911a-9ba5-431e-b033-e61187dc635a	Traceback (most recent call last):
  File "/opt/python/jose/jws.py", line 176, in _load
    signing_input, crypto_segment = jwt.rsplit(b".", 1)
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: not enough values to unpack (expected 2, got 1)

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/opt/python/jose/jwt.py", line 183, in get_unverified_header
    headers = jws.get_unverified_headers(token)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/jose/jws.py", line 109, in get_unverified_headers
    return get_unverified_header(token)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/jose/jws.py", line 90, in get_unverified_header
    header, claims, signing_input, signature = _load(token)
                                               ^^^^^^^^^^^^
  File "/opt/python/jose/jws.py", line 180, in _load
    raise JWSError("Not enough segments")
jose.exceptions.JWSError: Not enough segments

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/var/task/handlers/authorizer.py", line 36, in lambda_handler
    verified_claims = verify_jwt(token)
                      ^^^^^^^^^^^^^^^^^
  File "/var/task/handlers/authorizer.py", line 66, in verify_jwt
    headers = jwt.get_unverified_headers(token)
              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/jose/jwt.py", line 205, in get_unverified_headers
    return get_unverified_header(token)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/python/jose/jwt.py", line 185, in get_unverified_header
    raise JWTError("Error decoding token headers.")
jose.exceptions.JWTError: Error decoding token headers.

@kbdraai
Copy link
Contributor Author

kbdraai commented Jan 20, 2025

This looks like an issue with the token. To make it easier to get the token I used the AdminInitiateAuth Cognito API:

class CognitoAuth(AuthBase):
    def __init__(self, user_pool_id, client_id, username, password):
        self.user_pool_id = user_pool_id
        self.client_id = client_id
        self.username = username
        self.password = password
        self.token = self.authenticate_user()

    def authenticate_user(self):
        client = boto3.client('cognito-idp', region_name='us-east-1')
        try:
            response = client.admin_initiate_auth(
                UserPoolId=self.user_pool_id,
                ClientId=self.client_id,
                AuthFlow='ADMIN_USER_PASSWORD_AUTH',
                AuthParameters={
                    'USERNAME': self.username,
                    'PASSWORD': self.password,
                }
            )
            return response['AuthenticationResult']['AccessToken']
        except client.exceptions.NotAuthorizedException:
            raise Exception("The username or password is incorrect")
        except Exception as e:
            raise Exception(f"An error occurred: {str(e)}")

    def __call__(self, r):
        r.headers['Authorization'] = f'Bearer {self.token}'
        return r

You can also manually decode the token to ensure that it is correct.


2. Change directory to the pattern directory:
```
cd apigw-certificate-bound-access-tokens
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
cd apigw-certificate-bound-access-tokens
cd apigw-cognito-certificate-bound-access-token

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Filename needs to be Makefile otherwise make fails.

cd apigw-certificate-bound-access-tokens
```

3. Ensure that you add the relevant parameters to `samconfig.toml`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Recommend using sam deploy --guided instead of editing samconfig.toml manually.

@@ -0,0 +1,89 @@
# Certificate-Bound Access Tokens using API Gateway and Cognito
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
# Certificate-Bound Access Tokens using API Gateway and Cognito
# Certificate-Bound Access Tokens using Amaon API Gateway and Amazon Cognito


## How it works

This pattern creates an Amazon API Gateway REST API as well as a custom domain name and enables mTLS. Further, it creates a Cognito User Pool. The Cognito User Pool is used to issue certificate-bound access tokens. The REST API makes use of an authorizer to compare the "cnf" claim in the access token to the fingerprint of the client certificate sent as part of the mutual authentication TLS handshake.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
This pattern creates an Amazon API Gateway REST API as well as a custom domain name and enables mTLS. Further, it creates a Cognito User Pool. The Cognito User Pool is used to issue certificate-bound access tokens. The REST API makes use of an authorizer to compare the "cnf" claim in the access token to the fingerprint of the client certificate sent as part of the mutual authentication TLS handshake.
This pattern creates an Amazon API Gateway REST API with a custom domain name and enables mTLS. Further, it creates a Cognito User Pool. The Cognito User Pool is used to issue certificate-bound access tokens. The REST API makes use of an authorizer to compare the "cnf" claim in the access token to the fingerprint of the client certificate sent as part of the mutual authentication TLS handshake.


4. Build the solution:
```
sam build
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
sam build
sam build --use-container


1. Delete the stack
```bash
aws cloudformation delete-stack --stack-name STACK_NAME
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use sam delete since you are using SAM.

@ellisms
Copy link
Contributor

ellisms commented Jan 22, 2025

Thanks @kbdraai I was able to test this. Please review the requested changes and make the updates before we move forward.

@kbdraai
Copy link
Contributor Author

kbdraai commented Jan 24, 2025

@ellisms ive updated README.md

For the deploying the deploy command will be very bulky with all of the parameters which is why I opted for samconfig.toml. Let me know if this is still okay. I can make the change.

I included the same code to get an access token using boto3. Let me know if this is fine as well. Otherwise I can add AWS CLI command.

@ellisms
Copy link
Contributor

ellisms commented Jan 24, 2025

Typically the CLI is used, rather than creating language-specific code. Please update to use the CLI.

For deploying, sam deploy --guided --use-container is the easier way to handle the arguments. Updating the samconfig.toml file manually requires properly escaping the parameters, which is messy.

@kbdraai
Copy link
Contributor Author

kbdraai commented Jan 27, 2025

Changes pushed

@ellisms
Copy link
Contributor

ellisms commented Jan 27, 2025

Thanks, adding this to the queue for publishing.

@julianwood julianwood merged commit 67213bc into aws-samples:main Feb 4, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants