Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API Gateway with mTLS support #487

Open
1 of 2 tasks
markusz opened this issue Oct 29, 2021 · 4 comments
Open
1 of 2 tasks

API Gateway with mTLS support #487

markusz opened this issue Oct 29, 2021 · 4 comments
Labels
feature-request A feature should be added or improved needs-triage The issue or PR still needs to be triaged

Comments

@markusz
Copy link

markusz commented Oct 29, 2021

By default, the TLS protocol only requires a server to authenticate itself to the client. The authentication of the client to the server is managed by the application layer. The TLS protocol also offers the ability for the server to request that the client send an X.509 certificate to prove its identity. This is called mutual TLS (mTLS) as both parties are authenticated via certificates with TLS.

Mutual TLS is commonly used for business-to-business (B2B) applications. It’s used in standards such as Open Banking, which enables secure open API integrations for financial institutions across the United Kingdom and Australia. It’s common for Internet of Things (IoT) applications to authenticate devices using digital certificates. Also, many companies authenticate their employees before granting access to data and services when used with a private certificate authority (CA).

Use Case

mTLS APIs require a number of additional steps compared to regular APIs:

  • Upload a truststore.pem to S3 for client certificate verfication in API GW (S3)
  • Create a customer domain for the API GW (Route53)
  • Request and validate a certificate (ACM)

The construct aims to simplifies this use case.

Proposed Solution

I already implemented this for a different project (see code below) and believe that it can be useful for others.

I will add the following things:

  • add tests
  • adhere to code guidelines
  • add architecture diagram + README
  • make construct customizable via props
export default class PublicRestApiWithMutualTLS extends Construct {
  apiGw: RestApi

  constructor(scope: Construct, id: string, props: PublicRestApiWithmTLSProps) {
    super(scope, id);

    const apiDomainName = `api.${props.hostedZone.zoneName}`;
    const apiCert = new DnsValidatedCertificate(this, 'ApiCertificate', {
      domainName: apiDomainName,
      hostedZone: props.hostedZone,
      region: Stack.of(this).region,
    });

    const certBucket = new Bucket(this, 'TruststoreCertificatesBucket', {
      bucketName: 'some.bucket.name',
      autoDeleteObjects: true,
      removalPolicy: RemovalPolicy.DESTROY,
    });

    const truststoreCertificateFileName = 'truststore.pem';

    new BucketDeployment(this, 'TruststorePemUpload', {
      sources: [Source.asset('./cert/latest', {
        exclude: ['**', `!${truststoreCertificateFileName}`],
      })],
      destinationBucket: certBucket,
      retainOnDelete: false,
    });

    const fn = new Lambda(..)

    const logGroup = new LogGroup(this, 'APiGwLogGroup', {
      logGroupName: 'apigw/rest/PublicWithMutualTLS',
      removalPolicy: RemovalPolicy.DESTROY,
    });

    this.apiGw = new RestApi(this, 'PublicApi', {
      endpointConfiguration: {
        types: [EndpointType.REGIONAL],
      },
      disableExecuteApiEndpoint: true,
      restApiName: 'Public with mTLS',
      description: `A public REST API Gateway with mutual TLS on a custom domain (${apiDomainName})`,
      domainName: {
        domainName: apiDomainName,
        endpointType: EndpointType.REGIONAL,
        certificate: apiCert,
        securityPolicy: SecurityPolicy.TLS_1_2,
        mtls: {
          bucket: certBucket,
          key: `/${truststoreCertificateFileName}`,
        },
      },
    });

    new ARecord(this, 'ARecord', {
      zone: props.hostedZone,
      recordName: apiDomainName,
      target: RecordTarget.fromAlias(new ApiGateway(this.apiGw)),
    });

    this.apiGw.root.addMethod('GET', new LambdaIntegration(fn));
  }
}

Other

  • 👋 I may be able to implement this feature request
  • ⚠️ This feature might incur a breaking change

This is a 🚀 Feature Request

@markusz markusz added feature-request A feature should be added or improved needs-triage The issue or PR still needs to be triaged labels Oct 29, 2021
@biffgaut
Copy link
Contributor

biffgaut commented Nov 8, 2021

Sorry for the delayed response. Our team meeting is tomorrow and we'll discuss - thanks.

@biffgaut
Copy link
Contributor

biffgaut commented Nov 9, 2021

This seems like an idea that would bring value to customers. A couple adjustments-

  • All this functionality wouldn't be published as a single construct. We see it as 2 parts:
    • An aws-route53-apigateway construct. This is already on our roadmap, probably in the near term. The recent release of aws-route53-alb means that the construct interface for both Route53 and API gateway are defined.
    • Adding an option to implement mTLS authentication in all of our existing API Gateway constructs.

We've added the API Gateway construct updates to our backlog. That said, we believe the functionality has less broad appeal than other plans currently on our Roadmap and won't have the resources to address it in the near future. If you would like to implement it, we could work with you to ensure that it's something we could publish.

@markusz
Copy link
Author

markusz commented Nov 11, 2021

Hey @biffgaut

thanks for the reply. Just to make sure I understand correctly:

You don't see this as a single solutions construct as proposed by me, but would like to split the functionality across:

  1. A solutions construct called aws-route53-apigateway that will create an API GW + Route53 record pointing to it
  2. An addition to the L2 API GW constructs that would add support for mTLS to the respective API GWs

I'd be interested in implementing 2), but would need more info on how to approach this best. I read the design guidelines of aws-cdk and found the API GW v1 & v2 packages, but am not sure, where exactly I would add my code.

Also, can I use other constructs like BucketDeployment, Bucket, .. in my implementation (see my initial code sample above)

@biffgaut
Copy link
Contributor

Yes, that's how we see the functionality divided - although route53-apigateway will probably accept an existing API generated by another construct rather than create an API from scratch, I believe that's what we had to do with aws-wafwebacl-apigateway. We will write this construct.

We can definitely work with you on where to add the new functionality (I'm assuming you are a intermediate level Typescript programmer or above). Most of the mTLS configuration will probably be implemented by changing core/apigateway-helper.ts. Getting the .pem file to the bucket as an asset will be an interesting puzzle we will need to figure out, but I have no doubt it's achievable. All L2 constructs are available to you, but for things like buckets we will want you to use our core/s3-bucket-helper.ts functionality to stay consistent with our other constructs (helps ensure the same default settings, best practices, etc. All of our work so far has been with aws-apigateway and REST APIs. aws-apigatewayv2 is the more recent HTTP API functionality (we haven't gone down that road yet).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature-request A feature should be added or improved needs-triage The issue or PR still needs to be triaged
Projects
None yet
Development

No branches or pull requests

2 participants