Search code examples
amazon-web-serviceswebsocketaws-api-gatewayamazon-cloudfronthttp-status-code-403

403 Forbidden when AWS Websocket Api Gateway try to serve through Cloudfront


I have created a Websocket API using AWS's websocket API gateway. After creating the api i got the endpoint like

wss://x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com/dev

I could use this directly and invoke the $connect lambda without any issue

Now I wanted this to serve via cloudfront.

I already have a distribution which has Alternate domain name like app.blablabla.cloud and it serves multiple REST APIs (Origin Type: API Gateway) for different services. I wanted to do the same for the websocket API as well

I created a new custom origin for the distribution

  • origin domain - x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com
  • protocol - HTTPS only
  • HTTPS port - 443
  • Minimum origin SSL protocol - TLS v1
  • Origin path - left blank
  • Rest of config - default

After that I created a behaviour and attached this origin

  • Path pattern - /dev/my-socket/*
  • Compress objects automatically - Yes
  • Viewer - HTTPS only
  • Allowed HTTP methods - GET, HEAD, OPTIONS, PUT, POST, PATCH, DELETE
  • Restrict viewer access - No
  • Cache key and origin requests - Cache policy and origin request policy
    Cache policy - CachingDisabled
    Origin request policy - created a policy with Cookies - All, Query strings - All and Headers - Include the following headers - Sec-WebSocket-Key, Sec-WebSocket-Version, Sec-WebSocket-Protocol, Sec-WebSocket-Accept, Sec-WebSocket-Extensions
  • Rest of config - default

Now i tried to invoke a wss initial call from postman with the url wss://app.blablabla.cloud/dev/my-socket/?param1=1&param2=test

but i get Status Code: 403 Forbidden (I'm expecting a 101 Switching Protocols) and also request headers are

Sec-WebSocket-Version: 13
Sec-WebSocket-Key: TVMDG46dqkDM4a7DoM20ZB==
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits
Host: app.blablabla.cloud

so it looks like cloudfront is hit just can't get it through. the api gateway dashboard also showed something like enter image description here

I don't understand where the issue is. Help is much appreciated. Thanks in advance!


Solution

  • EDIT.
    Later tried with cloudfront functions here and think it is a better approach


    the issue was API Gateway’s WebSocket API does not support a path parameter in the connection URL. By design (or say it a design oversight), the connection URL path is fixed to the root path / (stage would be prepended when execute-api endpoint is used). as mentioned in this medium article.

    In simple terms trying to connect wss://x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com/dev/my-socket/?param1=1&param2=test will give the same 403 Forbidden . Only wss://x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com/dev/?param1=1&param2=test or wss://x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com/dev?param1=1&param2=test works.

    i.e when i try to connect wss://app.blablabla.cloud/dev/my-socket/?param1=1&param2=test it must be trying to connect to wss://x5g9h3p2rq.execute-api.eu-central-1.amazonaws.com/dev/my-socket/?param1=1&param2=test

    So approach 1

    • In the behaviour change Path pattern - /dev and bring the precedence down so that the rest of the dev/stuff/* are not affected.
    • Now I am able to do wss://app.blablabla.cloud/dev?param1=1&param2=test

    But this is not scalable. What if we need another socket API. we cannot use the same /dev for everything.

    This leads to better approach 2

    Using Lambda@Edge

    • Keep the behaviour as it is with Path pattern - /dev/my-socket/*.
    • Create a Lambda@Edge and associate it with Origin request
    • The lambda was simple, just changing the uri from /dev/my-socket/ to /dev
    'use strict';
    
    exports.handler = (event, context, callback) => {
      const request = event.Records[0].cf.request;
      request.uri = request.uri.replace(/\/[^\/]+\/$/, '');
      return callback(null, request);
    };
    
    
    • Now I am able to do wss://app.blablabla.cloud/dev/my-socket/?param1=1&param2=test and it will strip the my-socket and forward to the correct origin.
    • Also the Lambda is invoked only during the connection call. After the handshake is done the Lambda is not involved.
    • Also noticed there is a slight latency with this approach compared to using the API gateway url directly