DianaIonita/serverless-api-gateway-caching

client certificate as cacheKeyParameters

Closed this issue · 7 comments

Hi, I'm writing APIs that use MTLS (client certificates) as authentication, I would like to cache results based on that property (i.e. cache per client), I tried:

caching:
  enabled: true
  ttlInSeconds: 60
  cacheKeyParameters:
    - name: request.requestContext.identity.clientCert.serialNumber

but fails with Invalid mapping expression

For reference, this is the event my lambda receives:

{
  "event": {
    "resource": "/v1/...",
    "path": "/v1/...",
    "httpMethod": "GET",
    "headers": {
      "accept": "*/*",
      "accept-encoding": "gzip, deflate, br",
      "cache-control": "no-cache",
      "User-Agent": "PostmanRuntime/7.28.2"
    },
    "multiValueHeaders": {
      "accept": [
        "*/*"
      ],
      "accept-encoding": [
        "gzip, deflate, br"
      ],
      "cache-control": [
        "no-cache"
      ],
      "User-Agent": [
        "PostmanRuntime/7.28.2"
      ]
    },
    "queryStringParameters": null,
    "multiValueQueryStringParameters": null,
    "pathParameters": null,
    "stageVariables": null,
    "requestContext": {
      "resourceId": "yzpjp5",
      "resourcePath": "/v1/...",
      "httpMethod": "GET",
      "extendedRequestId": "PCkYaG3ziYcFouQ=",
      "requestTime": "15/Mar/2022:19:22:16 +0000",
      "path": "/v1/...",
      "accountId": "105843011380",
      "protocol": "HTTP/1.1",
      "stage": "dev",
      "domainPrefix": "core",
      "requestTimeEpoch": 1647372136811,
      "requestId": "1cfff372-3db0-4ad0-a023-46c570c8adeb",
      "identity": {
        "cognitoIdentityPoolId": null,
        "clientCert": {
          "clientCertPem": "-----BEGIN CERTIFICATE-----...-----END CERTIFICATE-----",
          "serialNumber": "620155093573104977087960001264040226218798030419",
          "issuerDN": "CN=...",
          "validity": {
            "notAfter": "Mar  3 20:27:42 2023 GMT",
            "notBefore": "Mar  3 20:27:43 2022 GMT"
          },
          "subjectDN": "CN=acme,O=acme"
        },
        "cognitoIdentityId": null,
        "principalOrgId": null,
        "cognitoAuthenticationType": null,
        "userArn": null,
        "userAgent": "PostmanRuntime/7.28.2",
        "accountId": null,
        "caller": null,
        "accessKey": null,
        "cognitoAuthenticationProvider": null,
        "user": null
      },
      "apiId": "lz44gudovl"
    },
    "body": null,
    "isBase64Encoded": false
  }
}

Is this possible? Thanks

Hi @andres-ntropy,

I don't know whether it's possible to do this, I haven't done it myself.
However, could you please try the syntax that is used for cache key parameters in the request body, for example:

events:
  - http:
      path: / # replace with your path
      method: get # replace with your method
      integration: lambda # use lambda integration
      caching:
        enabled: true
        ttlInSeconds: 60
        cacheKeyParameters:
          - name: integration.request.header.clientCertSerialNumber # the name of the cache key parameter
            mappedFrom: method.request.requestContext.identity.clientCert.serialNumber # where it lives in the request

Please let me know if this works.

Hi @DianaIonita

Unfortunately that didn't work. I did some trial & error and managed to make it work by commenting out this if condition

I'm not sure what that if is for, and what consequences it brings by removing it, but by talking to AWS support & reading the docs, the way to make it work is to have an integration that looks like this (output from my actual aws apigateway get-integration command):

{
    "type": "AWS_PROXY",
    "httpMethod": "POST",
    "uri": "arn:aws:apigateway:us-east-2:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-2:XXXXXXXX:function:get-active-queue-count/invocations",
    "requestParameters": {
        "integration.request.header.clientCertSerialNumber": "context.identity.clientCert.serialNumber"
    },
    "requestTemplates": {},
    "passthroughBehavior": "WHEN_NO_MATCH",
    "timeoutInMillis": 29000,
    "cacheNamespace": "ApiGatewayMethodV1CertificateQueueDashcountGetCacheNS",
    "cacheKeyParameters": [
        "integration.request.header.clientCertSerialNumber"
    ]
}

As you can see, the integration type is AWS_PROXY but that doesn't prevent it from working and caching properly

The serverless.yml snippet I used to produce that result:

    events:
      - http:
          path: v1/certificate/queue-count
          method: get
          caching:
            enabled: true
            ttlInSeconds: 60
            cacheKeyParameters:
              - name: integration.request.header.clientCertSerialNumber
                mappedFrom: context.identity.clientCert.serialNumber

Thanks,

Hi @andres-ntropy

Thank you for the information provided.
You're right, the AWS_PROXY integration type check doesn't seem required. If I recall correctly, it was added to support cache key parameters coming from the body of a request. However, I ran some tests and it doesn't make a difference whether it's AWS or AWS_PROXY, the cache key parameters are still properly configured.

I have therefore released v1.8.0 of the plugin which removes that check.

Feel free to reopen this issue if it doesn't solve your problem.

Hi @DianaIonita,

FYI unfortunately something must have changed in AWS, because I deployed today a stack with this setting turned on and it failed with InvalidSignatureException from API Gateway to Lambda, seemingly unrelated but when I removed the plugin it worked again

Hi @andres-ntropy,

Are you seeing this error in the latest version of the plugin 1.8.1?
If so, could you please try version 1.8.0? I'm curious if this is because of something I added.

hello Diana, I tried this with 1.9.0 and it's working again. Unfortunately there's no support from AWS to cache by client certificate, so I'll have to live with that 🤷‍♂️

Hi @tulsidas,
That's unfortunate :(
Thank you for the update.