phusion / passenger_library

Phusion Passenger documentation
https://www.phusionpassenger.com/docs
Other
47 stars 111 forks source link

Is a custom Rackup file still required when running the Action Cable server on a subdomain? #111

Open JamesChevalier opened 2 years ago

JamesChevalier commented 2 years ago

I am trying to configure ActionCable in Rails 6.1.4.1 to run on a subdomain, but when I follow these instructions the env["warden"] (which is used by Devise) is nil in app/channels/application_cable/connection.rb.

This post will explain my environment/configuration for my attempts at getting things to work with a custom Rackup file, and then it will explain the changes I made to get things working. I've separated the sections with horizontal lines, for easier skimming.

This is a rather long post, so I want to thank you ahead of time for even reading this far.


I have the main domain (and www) proxied through CloudFlare, but I do not have the actioncable subdomain proxied. My web infrastructure consists of a single EC2 instance.

I have ActionCable mounted in my routes file:

Rails.application.routes.draw do
  mount ActionCable.server => "/cable"
end

I have the session_store set to share across subdomains, and the URL & allowed_request_origins set in my production.rb file:

config.session_store :cookie_store, key: "_app_session", same_site: :lax, domain: ".appdomain.com", tld_length: 2
config.action_cable.url = "wss://actioncable.appdomain.com/cable"
config.action_cable.allowed_request_origins = ["https://appdomain.com", "https://actioncable.appdomain.com", /https:\/\/*.appdomain.com/]

I have a cable/config.ru file specifically for the ActionCable server:

require_relative "../config/environment"
Rails.application.eager_load!
run ActionCable.server

My Nginx config forces SSL & non-www, and runs ActionCable separately:

# Force SSL
server {
  listen      80;
  listen      [::]:80;
  return      301 https://appdomain.com$request_uri;
}

# Force non-www in SSL
server {
  listen [::]:443 ssl http2;
  listen      443 ssl http2;
  server_name www.appdomain.com;
  gzip off;
  ssl_certificate     /etc/letsencrypt/live/appdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1;
  ssl_ecdh_curve secp384r1;
  ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_prefer_server_ciphers on;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 10s;
  add_header X-Frame-Options DENY;
  add_header X-Content-Type-Options nosniff;
  return 301 https://appdomain.com$request_uri;
}

# The actual site
server {
  listen [::]:443 ssl http2;
  listen      443 ssl http2;
  server_name appdomain.com;
  gzip off;
  ssl_certificate     /etc/letsencrypt/live/appdomain.com/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/appdomain.com/privkey.pem;
  ssl_protocols TLSv1.2 TLSv1.1 TLSv1; 
  ssl_ecdh_curve secp384r1; 
  ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; # Score=90 (recommended)
  add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
  ssl_session_cache shared:SSL:10m;
  ssl_session_timeout 10m;
  ssl_prefer_server_ciphers on;
  ssl_stapling on;
  ssl_stapling_verify on;
  resolver 8.8.8.8 8.8.4.4 valid=300s;
  resolver_timeout 10s;
  add_header X-Frame-Options SAMEORIGIN;
  add_header X-Content-Type-Options nosniff;
  location ^~ /assets/ {
    gzip_static on;
    expires max;
    add_header Cache-Control public;
  }
  error_page 500 502 503 504 /500.html;
  keepalive_timeout 10;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_min_instances 4;
  root /home/myself/production/current/public;
  # ActionCable; allowing this because I'm moving from subfolder to subdomain & have active clients
  location /cable {
#    return 302 https://actioncable.appdomain.com/cable; # Once this is working, I plan on forcing clients over to the subdomain
    passenger_app_group_name app_action_cable_f;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

# ActionCable
server {
  listen 443;
  server_name actioncable.appdomain.com;
  root /home/myself/production/current/public;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_app_type rack;
  passenger_min_instances 4;
  passenger_startup_file cable/config.ru;
  location /cable {
    passenger_app_group_name app_action_cable_sd;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

Things do work properly if I comment out these custom Rackup file lines in the ActionCable server in nginx:

passenger_app_type rack; passenger_startup_file cable/config.ru;

This effectively means that I'm running two instances of my Rails app, so if I want to keep traffic out of the subdomain, I can also add a redirect before the location /cable line, resulting in this server block:

server {
  listen 443;
  server_name actioncable.appdomain.com;
  root /home/myself/production/current/public;
  passenger_enabled on;
  passenger_env_var LD_PRELOAD /usr/lib/x86_64-linux-gnu/libjemalloc.so.2;
  passenger_ruby /home/myself/.rubies/ruby-3.0.2/bin/ruby;
  passenger_app_env production;
  passenger_min_instances 4;
  location / {
    return 301 https://appdomain.com$request_uri;
  }
  location /cable {
    passenger_app_group_name app_action_cable_sd;
    passenger_force_max_concurrent_requests_per_process 0;
  }
}

An aside: I don't understand why none of the ssl-related lines are required in my ActionCable server instance. :shrug: Everything works without specifying ssl_certificate etc within that ActionCable server block.