0

I developed a real time chat app with Laravel Websockets/Websockets API on localhost + Self Signed Certificate to use a SSL in local development. Everything works up to this point. I uploaded everything to the test server + setup supervisord to run both queue and websockets:serve (except the self signed certificate). Since we used AWS Certificate Manager to setup SSL, there is no copy of private key and certificate anywhere. Because of that, I am unable to run websockets in test server.

Below is my setup (very basic)

/etc/httpd/conf.d/project.conf

<VirtualHost *:80>
    ServerAdmin webmaster@dummy-host.example.com
    DocumentRoot "/var/www/html/project/core/public"
<Directory "/var/www/html/project/core">
        Options Indexes FollowSymLinks Includes ExecCGI
        AllowOverride All
        Order allow,deny
        Allow from all
        Require all granted
</Directory>
    ErrorLog "logs/project/errors.log"
    CustomLog "logs/project/access.log" common
</VirtualHost>

/etc/supervisor.conf

[program:laravel-queue]
command=php /var/www/html/project/core/artisan queue:work --sleep=3 --tries=3
process_name=%(program_name)s
numprocs=1      
autostart=true  
autorestart=true  
startsecs=10    
startretries=3      
user=ec2-user
redirect_stderr=true      
stdout_logfile=/var/www/html/project/core/laravel-queue.log

[program:laravel-websockets]
command=php /var/www/html/project/core/artisan websockets:serve --host=127.0.0.1
process_name=%(program_name)s
numprocs=1      
autostart=true  
autorestart=true  
startsecs=10    
startretries=3      
user=ec2-user
redirect_stderr=true      
stdout_logfile=/var/www/html/project/core/laravel-websockets.log

broadcasting.php

'connections' => [
    'pusher' => [
        'driver' => 'pusher',
        'key' => env('PUSHER_APP_KEY'),
        'secret' => env('PUSHER_APP_SECRET'),
        'app_id' => env('PUSHER_APP_ID'),
        'options' => [
            'cluster' => env('PUSHER_APP_CLUSTER'),
            'useTLS' => true,
            'encrypted' => true,
            'host' => '127.0.0.1',
            'port' => env('LARAVEL_WEBSOCKETS_PORT'),
            'scheme' => 'https',
            'curl_options' => [
                CURLOPT_SSL_VERIFYHOST => 0,
                CURLOPT_SSL_VERIFYPEER => 0,
            ],
        ],
    ],

websockets.php

'ssl' => [
    'local_cert' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT', null),
    'local_pk' => env('LARAVEL_WEBSOCKETS_SSL_LOCAL_PK', null),
    'passphrase' => env('LARAVEL_WEBSOCKETS_SSL_PASSPHRASE', null),

.env

PUSHER_APP_ID=12345 
PUSHER_APP_KEY=ABCDEFG 
PUSHER_APP_SECRET=HIJKLMNOP 
PUSHER_APP_CLUSTER=mt1

LARAVEL_WEBSOCKETS_SSL_LOCAL_CERT= 
LARAVEL_WEBSOCKETS_SSL_LOCAL_PK= 
LARAVEL_WEBSOCKETS_SSL_PASSPHRASE="" 
LARAVEL_WEBSOCKETS_PORT=6001 
MIX_LARAVEL_WEBSOCKETS_PORT="${LARAVEL_WEBSOCKETS_PORT}"

Echo Initialization

const Echo = new initEcho({
    broadcaster: "pusher",
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    wsHost: window.location.hostname,
    wsPort: process.env.MIX_LARAVEL_WEBSOCKETS_PORT,
    wssPort: process.env.MIX_LARAVEL_WEBSOCKETS_PORT,
    forceTLS: true,
    encrypted: true,
    enabledTransports: ["ws", "wss"],
    auth: {
        headers: {
            Authorization:
                "Bearer " + access_token,
            Accept: "application/json",
        },
    },
});

Echo.connector.pusher.connection.strategy.transports.ws.transport.manager.livesLeft =
    Infinity;

Echo.connector.pusher.connection.strategy.transports.wss.transport.manager.livesLeft =
    Infinity;

Echo.connector.pusher.connection.bind("state_change", function (states) {
    // state change
});

const channel = Echo.join("chat-message")
    .here(() => {
        console.log("chat channel");
    })
    .joining((event) => {
        // console.log({ event }, "joining");
    })
    .leaving((event) => {
        // console.log({ event }, "leaving");
    })
    .listenForWhisper("typing", (event) => {
        // console.log({ event }, "listenForWhisper");
    })
    .listen(".chat-message", (event) => {
        // console.log({ event }, "listen");
    });

EDIT

A lot of SO Q&A said something about setting up Application Load Balancers, SSL Termination, etc (Don't really know about this) so we tried to do the following based on this aws document and changed 443 to 6001 (ws port number) and protocol to either http or https. Still the same issue.

UPDATE We also tried to do this aws add HTTPS listener. Still the same unable to listen to port.

My knowledge with AWS and its services is very limited (I was not the one to setup AWS ec2 and ACM) so if the solution is within aws, please teach me as simple as possible.

Mr. Kenneth
  • 384
  • 1
  • 2
  • 14
  • You may try to use an applicationLoadBalancer and configure your certificate on it https://aws.amazon.com/fr/premiumsupport/knowledge-center/associate-acm-certificate-alb-nlb/ – Fabrice Lefloch Nov 07 '22 at 10:44
  • Would you be able to elaborate further? – Mr. Kenneth Nov 08 '22 at 00:05
  • I don't know how your server is deployed actually, but if your domain name is pointing directly to your EC2 server, your should instead create an application Load balancer (as explained here) https://docs.aws.amazon.com/elasticloadbalancing/latest/application/create-application-load-balancer.html and then add your certificate on your load balancer instead of your EC2. This way your domain name will point to your LB which will handle your certificate (with other stuff) and then will point to your EC2 – Fabrice Lefloch Nov 08 '22 at 09:34
  • I believe we already added our certificate in load balancer based on this link: https://aws.amazon.com/premiumsupport/knowledge-center/associate-acm-certificate-alb-nlb/#Associate_an_ACM_SSL_certificate_with_an_Application_Load_Balancer . – Mr. Kenneth Nov 09 '22 at 01:34
  • we tried adding 443 ( for ssl ) and afterwards also added port 6001 with HTTP/HTTPS settings still did not work. we deleted 6001 in load balancer afterwards. – Mr. Kenneth Nov 09 '22 at 01:35

1 Answers1

0

I was able to work the code after almost 2 weeks. With this setup, you no longer need to add the private key and certificate in .env file.

broadcasting.php

'pusher' => [
    'driver' => 'pusher',
    'key' => env('PUSHER_APP_KEY'),
    'secret' => env('PUSHER_APP_SECRET'),
    'app_id' => env('PUSHER_APP_ID'),
    'options' => [
        'cluster' => env('PUSHER_APP_CLUSTER'),
        'useTLS' => true,
        #### ADD HERE
        'encrypted' => false,
        'host' => '127.0.0.1',
        'port' => env('LARAVEL_WEBSOCKETS_PORT'),
        'scheme' => 'http',
        ### ADD HERE
    ],
],

bootstrap.js

const Echo = new Echo({
    broadcaster: "pusher",
    key: process.env.MIX_PUSHER_APP_KEY,
    cluster: process.env.MIX_PUSHER_APP_CLUSTER,
    wsHost: window.location.hostname,
    // This is important
    wsPort: 80,
    wssPort: 443,
    forceTLS: true,
    encrypted: true,
    enabledTransports: ["ws", "wss"],
    // This is important

    // optional if you are using jwt
    auth: {
        headers: {
            Authorization:
                "Bearer " + access_token,
            Accept: "application/json",
        },
    },
    // optional if you are using jwt
});


// Socket will try to reconnect indefinetly with this. If not added, echo will only try to reconnect twice as default
Echo.connector.pusher.connection.strategy.transports.ws.transport.manager.livesLeft =
    Infinity;

Echo.connector.pusher.connection.strategy.transports.wss.transport.manager.livesLeft =
    Infinity;
// Socket will try to reconnect indefinetly with this. If not added, echo will only try to reconnect twice as default

Use this Apache Proxy Pass if you are using the Custom WebSocket Handlers

ProxyRequests off
ProxyVia on
RewriteEngine On

RewriteEngine On
RewriteCond %{HTTP:Connection} Upgrade [NC]
RewriteCond %{HTTP:Upgrade} websocket [NC]
RewriteRule /(.*) ws://127.0.0.1:6001/$1 [P,L]

ProxyPass               /ws/chat http://127.0.0.1:6001/ws/chat
ProxyPassReverse        /ws/chat http://127.0.0.1:6001/ws/chat
Mr. Kenneth
  • 384
  • 1
  • 2
  • 14