How do I configure my Rails app (using Puma) to serve over HTTPS? - ruby-on-rails

I'm setting up a Rails application with Puma, I can't seem to make it serve over HTTPS.
I've tried maybe fifteen different guides and stack over flow answers, but neither of them have worked for me.
Now, I can't serve regular HTTP either, even when reverting the configuration.
What I've tried:
config/puma.rb
port ENV.fetch("PORT") { 3000 }
my_key = "#{File.join(Rails.root, 'public', '.well-known', 'privkey.pem')}"
my_crt = "#{File.join(Rails.root, 'public', '.well-known', 'cert.pem')}"
ssl_bind(
'mydomain.com',
3000,
key: my_key,
cert: my_crt,
verify_mode: 'peer'
)
mydomain.com is of course replaced by the proper domain name.
config/application.rb
config.force_ssl = true
I've put my certificate and private key in public/.well-known. The files were generated using Certbot.
Folder contents:
cert.pem
chain.pem
fullchain.pem
privkey.pem
The files were generated using Certbot.
The resulting error message is as follows:
HTTP parse error, malformed request: #<Puma::HttpParserError: Invalid HTTP format, parsing fails. Are you trying to open an SSL connection to a non-SSL Puma?>
What am I doing wrong?
-
Also, when resetting my configuration and testing out the site, I get the same error message.
What can be done about that?
SOLUTION:
Check out the comments in the reply by Jan Vítek to see how this was solved.

I see a few problems here:
This is not why it is not working, but please move your private key out of the /public path. It is not much private there.
You are binding the SSL to the same port as the HTTP server
You are binding the SSL to whatever.domain. It can bring many problems. If there is a NAT the app has no interface to bind to. If there is the server's FQDN in /etc/hosts it would bind to the localhost interface a you wouldn't be able to access it from the internet. If it actually bound to the interface with IP resolved from that domain, you might have problems accessing the app locally. Just change it to 0.0.0.0 for now and when you have it working you can experiment with this attribute.
SSL verify mode peer would verify client certificates. I'm not sure if it is your intention but it would require :ca option to be passed to ssl_bind() with a path to your CA certificate
You don't say if it is for development or production purposes. For production you might want to consider Apache + Passenger or Nginx + Passenger where the HTTPs would have been handled by the web server (Apache or Nginx)
Anyway I created a new Rails 7 application, placed my crt and key to config/ssl/ and modified only the config/puma.rb
#config/puma.rb
max_threads_count = ENV.fetch("RAILS_MAX_THREADS") { 5 }
min_threads_count = ENV.fetch("RAILS_MIN_THREADS") { max_threads_count }
threads min_threads_count, max_threads_count
worker_timeout 3600 if ENV.fetch("RAILS_ENV", "development") == "development"
port ENV.fetch("PORT") { 3000 }
ssl_bind '0.0.0.0', 3001, {
key: 'config/ssl/dev.mydomain.com.key',
cert: 'config/ssl/fullchain.cer',
verify_mode: 'none'
}
environment ENV.fetch("RAILS_ENV") { "development" }
pidfile ENV.fetch("PIDFILE") { "tmp/pids/server.pid" }
plugin :tmp_restart
Then simple rails s and I am able to connect to https://dev.mydomain.com:3001. Make sure to explicitly call https://.

Related

Can't call my rails server with nginx, got Puma::HttpParserError

I've trouble with my nginx / puma config. When I launch my rails server and try to interact with it, nothing happen and my request is stuck in the browser network. Also, I get this error in my console :
HTTP parse error, malformed request (): #<Puma::HttpParserError: Invalid HTTP format, parsing fails.>
I'm running ngrok on port 3000, but even when it's not running, I still get the error.
I saw a lot of similar issues (like this one without no answer) but none of them helps me :(
I've checked my ssl config with nginx and it's disabled
the output of grep -r ssl_protocol /etc/nginx gives
/etc/nginx/nginx.conf: ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
as expected
I've cleared my browser cache / history and cookies + restarted my nginx service et rails server
config.force_ssl is commented out in my environment/develop.rb (and I'm definively working on this environment) so I guess I'm not connectiong in https
I've also commented out the ipv6 conf in /etc/hosts and those two lines are set :
127.0.0.1 localhost
127.0.0.1 app.example.com
here is part of my default conf :
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://app.example.com:9000;
}
location /uploads/ {
proxy_pass https://example.s3.amazonaws.com/uploads/;
}
location /archives/ {
proxy_pass https://example.s3.amazonaws.com/archives/;
}
location /container/ {
proxy_pass https://example.s3.amazonaws.com;
}
}
Is there anything I forgot to check here ? I'm not that used to rails, maybe I'm missing something elementary :)
Figured out what happened :
I tried to connect my API with an angular front engine, what I've missing here is that I had an oauth conf to add in the angular environment file with https and not just http.
base_url: "http://localhost:3000/",
oauth: "https://localhost:3000/",
It wasn't relative to my rails server either not nginx !

Configure WEBrick to use automatically generated self-signed SSL/HTTPS certificate

I want to develop my Ruby on Rails application locally with SSL/HTTPS, but I'm having trouble trying to setup a server to use SSL. The following are the things I've already tried so far:
rails server [options]
The rails server command doesn't come with an ssl option (rails server --help):
Usage: rails server [mongrel, thin, etc] [options]
-p, --port=port Runs Rails on the specified port.
Default: 3000
-b, --binding=ip Binds Rails to the specified ip.
Default: 0.0.0.0
-c, --config=file Use custom rackup configuration file
-d, --daemon Make server run as a Daemon.
-u, --debugger Enable the debugger
-e, --environment=name Specifies the environment to run this server under
(test/development/production).
Default: development
-P, --pid=pid Specifies the PID file.
Default: tmp/pids/server.pid
-h, --help Show this help message.
Custom WEBrick instance with automatically generated self-signed SSL certificate
My Code
Following along with the WEBrick documentation for HTTPS, I made the following Ruby script that I run as ruby server.rb:
require 'webrick'
include WEBrick
root = File.expand_path './public'
cert_name = [
%w[CN localhost],
]
server = HTTPServer.new(
:BindAddress => '127.0.0.1',
:Port => '4430',
:DocumentRoot => root,
:SSLEnable => true,
:SSLCertName => cert_name # LOOK! SSLCertName IS SET!
)
# Shutdown gracefully on signal interrupt CTRL-C
# http://www.ruby-doc.org/core-2.1.1/Kernel.html#method-i-trap
trap('INT') { server.shutdown }
server.start
According to the documentation I linked to above:
This will start the server with a self-generated self-signed certificate.
and according to the documentation for WEBrick::Config,
WEBrick can automatically create a self-signed certificate if :SSLCertName is set.
The Errors
When I start the server, I get the following output:
INFO WEBrick 1.3.1
INFO ruby 2.1.1 (2014-02-24) [x86_64-darwin13.0]
INFO WEBrick::HTTPServer#start: pid=26059 port=4430
However, when I try to access https://localhost:4430/robots.txt, I get the following error in Chrome 33.0.1750.117:
and the following error when I try the same url in Firefox 27.0.1:
I looked up the ssl_error_rx_record_too_long error, and it looks like it can be caused by a few different things. Maybe WEBrick is still listening for HTTP requests on port 80, but that seems odd considering I explicitly set it to enable SSL on port 4430.
Access Logs
Additionally, here are the access log contents from WEBrick when I make the request for https://localhost:4430/robots.txt from Chrome, but I have no idea what any of it means (it looks like it's encoded in hex or something):
ERROR bad Request-Line `\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03S\x15ußð'¦\x14·áÚOá,j\x7FÅ=üüNn#\x02ëý\x0Fø‚\x00\x00(À+À/\x00žÌ\x14Ì\x13\x00œÀ'.
localhost - - [04/Mar/2014:01:42:39 EST] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x03S\x15ußð'¦\x14·áÚOá,j\x7FÅ=üüNn#\x02ëý\x0Fø‚\x00\x00(À+À/\x00žÌ\x14Ì\x13\x00œÀ" 400 417
- ->
ERROR bad Request-Line `\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x02S\x15ußj\x05ç©!€¿'ÄÃåë!t…ß\x06pDÒÒ4?”»7\x19\x00\x00\x1EV\x00À'.
localhost - - [04/Mar/2014:01:42:39 EST] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x02S\x15ußj\x05ç©!€¿'ÄÃåë!t…ß\x06pDÒÒ4?”»7\x19\x00\x00\x1EV\x00À" 400 398
- ->
ERROR bad Request-Line `\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x01S\x15ußñom¾u<n¨ý9yö“¤Øcƒ{½wh)M#š1;\x00\x00\x1EV\x00À'.
localhost - - [04/Mar/2014:01:42:39 EST] "\x16\x03\x01\x02\x00\x01\x00\x01ü\x03\x01S\x15ußñom¾u<n¨ý9yö“¤Øcƒ{½wh)M#š1;\x00\x00\x1EV\x00À" 400 392
- ->
ERROR bad URI `\x04ËB¿É\\ ˆ2ðiwñ·*\x02\x06^´\x00#v\x00\x00\x14\x00ÿV\x00\x009\x005\x003\x002\x00\x05\x00\x04\x00/\x00'.
localhost - - [04/Mar/2014:01:42:39 EST] "\x16\x03\x00\x00?\x01\x00\x00;\x03\x00S\x15uß…N®ˆ\r\x04ËB¿É\\ ˆ2ðiwñ·*\x02\x06^´\x00#v\x00\x00\x14\x00ÿV\x00\x009\x005\x003\x002\x00\x05\x00\x04\x00/\x00" 400 389
- -> \x04ËB¿É\\ ˆ2ðiwñ·*\x02\x06^´\x00#v\x00\x00\x14\x00ÿV\x00\x009\x005\x003\x002\x00\x05\x00\x04\x00/\x00
Ruby Source for SSL Module
Also, I checked the Ruby source code for the SSL module, but I don't see anything obvious in there for why this might not be working:
def setup_ssl_context(config) # :nodoc:
unless config[:SSLCertificate]
cn = config[:SSLCertName]
comment = config[:SSLCertComment]
cert, key = Utils::create_self_signed_cert(1024, cn, comment) # LOOK HERE!
config[:SSLCertificate] = cert
config[:SSLPrivateKey] = key
end
# etc...
end
# Higher up in the file...
def create_self_signed_cert(bits, cn, comment)
# etc ...
cert = OpenSSL::X509::Certificate.new
cert.version = 2
cert.serial = 1
name = OpenSSL::X509::Name.new(cn)
cert.subject = name
cert.issuer = name
# etc ...
end
My Environment
Here are the following things I'm using for development:
OS X Mavericks.
Ruby 2.1.1.
Rails 4.0.3.
Summary
So this is where I'm at currently, and I'm not sure how to proceed. I'm aware that I can just pass my own self-signed certificate file (generated with something like OpenSSL) to WEBrick, but the documentation says that WEBrick can automatically generate its own, and I'm really interested in getting that to work.
I'm also aware that I can use a different webserver like Thin with its --ssl option, but again, I wanted to use WEBrick, because it's the "out-of-the-box" web server for Rails, I want to be able to easily and quickly setup a development SSL web server without having to download additional gems and stuff like that.
I'm also aware that this solution exists, but again, I'm interested in having WEBrick automatically generate its own certificate (and besides, that solution seems to be a little overly complicated for what I'm trying to do).
So does anyone have any ideas of what might be wrong?
Okay, I figured out what was wrong, I should've paid closer attention to the instructions for HTTPS in WEBrick, this is the exact code from the example:
require 'webrick'
require 'webrick/https' # SEE THIS?
cert_name = [
%w[CN localhost],
]
server = WEBrick::HTTPServer.new(:Port => 8000,
:SSLEnable => true,
:SSLCertName => cert_name)
See that line that says require 'webrick/https'? I didn't have that in my original config. I didn't think that I'd need it.
Once I added it, my script started serving over HTTPS, and I could finally connect to https://localhost:4430/robots.txt. <face-palm>

How to configure Rails with Puma to use SSL?

I only found how to start puma using SSL:
$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'
However, there is no description about how to include an intermediate CA cert in the documentation. Could someone point me in the right direction? I am using Puma 1.6.3
Thanks!
Combining certificate and bundle will work only if you use nginx.
Without nginx, you can use ca and verify_mode options:
rails s puma -b 'ssl://0.0.0.0:9292?key=path_to_key.key&cert=path_to_cert.crt&verify_mode=none&ca=path_to_root_bundle.crt'
Source: https://github.com/puma/puma/blob/master/lib/puma/binder.rb
Kinda late to the party but, I have another solution, you can see my post for more details.
First create the certificate for your localhost using mkcert
mkcert localhost
If you want to have another domain to work on HTTPS, just replace localhost to the one you want, like mkcert mylocalhost-with-a-cool-domain.com
After this, I created a local-certs folder under the config folder and pasted the cert and key there.
Now you should mark these cert as trusted, I’m working on a Mac computer, so not sure how to handle this particular part on Windows or on a Linux distro. Check out the post, it has screenshoots. In resume, you will need to drag the certificate created with mkcert to the Keychain Access.
Then in your puma config file, create one if you don't have it and name it puma.rb, you should have something like
workers Integer(ENV['WEB_CONCURRENCY'] || 2)
threads_count = Integer(ENV['THREAD_COUNT'] || 5)
threads threads_count, threads_count
rackup DefaultRackup
port 3001
environment ENV['RACK_ENV'] || 'production'
if ENV['RACK_ENV'] == 'development'
# If you didn't place the cert and key under `local-certs` you should change this
localhost_key = "#{File.join('config', 'local-certs', 'localhost-key.pem')}"
localhost_crt = "#{File.join('config', 'local-certs', 'localhost.pem')}"
ssl_bind '0.0.0.0', 3000, {
key: localhost_key,
cert: localhost_crt,
verify_mode: 'none'
}
end
Then running bundle exec puma -C puma.rb or bundle exec rails s should do it :D
If anyone has a question, pls let me know. Hope it helps future readers!
while we are using combo Nginx+PhusionPassenger as well. You cant specify Chain cert file in nginx either. The trick is to bundle all certs within one certificate and then set the new certificate file as a certificate in your server configuration. You will find more information in nginx documentation. Check SLL Certificate Chains section.
cat www.example.com.crt bundle.crt > www.example.com.chained.crt
Hope it helped.
rails s puma -b 'ssl://0.0.0.0:9292?key=certkey.key&cert=cert.crt&verify_mode=peer&ca=root_bundle.crt
Just make sure you set the verify_mode=peer.
It may be a better idea to use Phusion Passenger + Nginx for SSL support. This combo has widely available documentation and is very easy to setup because it's currently the most popular app server choice, and used by the likes of New York Times, Symantec, AirBnB, etc. Here's how you do it if you have Nginx with Phusion Passenger installed:
server {
listen 443;
server_name yourapp.local;
ssl on;
ssl_certificate ...;
ssl_key ...;
root /path-to-your-app/public;
passenger_enabled on;
}

How to include SSL Certificate in Rails 3 project

I have developed a Rails 3 project which uses thin as server, along with Devise and Doorkeeper for Authentication
I would like to add SSL Certificate in my project. I have seen some posts which describes how to use SSL Certificate in Rails 3 Project,but none of then showing how to add certificate in Rails 3 project.
I have enabled config.force_ssl = true in my applicationcontroller.rb, and I tried starting thin server like:
thin start --ssl
It works without adding certificate and I can now access my website with https:// (Getting Certificate not verified Warning from Browser. I hope this is because my certificate is not verified by CA).
Recently I have seen a post showing how to add SSL Certificate in Rails 3 project;
How can I pass SSL options into "rails server" in Rails 3.0?
:SSLEnable => true,
:SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
:SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(current_dir + "/config/certs/server.key").read),
:SSLCertificate => OpenSSL::X509::Certificate.new(File.open(current_dir + "/config/certs/server.crt").read),
:SSLCertName => [ [ "CN", WEBrick::Utils::getservername ] ]
Is this the right way of adding the certificate? Each time when I try to start the server I need to specify these options.
Is there any other method to include certificate path in my project config (or include certificate in my project) so that there is no need for specifying certificate path each and every time I start my server?
Any help is appreciated....
Here I found answer about adding ssl to thin:
Thin with SSL support and ruby-debug
If you want to add ssl to nginx (I advice to use nginx in production) here is described how to add certificate to nginx
http://wiki.nginx.org/HttpSslModule
server {
listen 443;
ssl on;
ssl_certificate /path_to_certificate/cert.pem;
ssl_certificate_key /path_to_certificate/cert.key;
keepalive_timeout 70;
}

"502 Bad Gateway" with passenger

I am using rails(2.3.10)/passenger(2.2.15)/nginx(0.7.67) , when i run my application, it give me "502 Bad Gateway" and without any production log, my conf file is :
1 server {
2 listen 80;
3 server_name www.why.bz;
4 root /usr/local/apps/why/pro/public;
5 access_log /usr/local/apps/why/pro/log/access.log;
6 passenger_enabled on;
7 }
who can give me some tips, thanks!
I've done Rails deployments full-time for the last couple of years, so hopefully I can help. You've probably figured this problem out by now, but here are some questions to think about for next time:
Since this is a Ruby on Rails application, can you tell where the error is coming from: Nginx or Rails itself? That sounds more like a Nginx issue, but it would be good to know. A quicky peek into the Rails production.log or Nginx's error_log should elucidate this info.
How are you connecting to the application: web browser or command line? Is the IP and hostname set correctly (try the Linux "dig" command to verify)? Are you trying to hit http://localhost:80 ?
There are error log commands that nginx takes that could be configured to help you debug the issue (much like the access_log line): http://wiki.nginx.org/CoreModule#error_log
Lastly, verify the permissions that are set on your web files: The nginx web server needs to be able to access them. At least "read" permissions are needed.
Good luck,
Harmon
change it to
listen 443;
it could be as simple as that.
It could be that your secret key base is missing for the required stage.
One thing that I sometimes forget, especially if I don't start with a staging environment and then add it later, is to add the following to my secrets.yml file:
staging:
secret_key_base: <%= ENV["SECRET_KEY_BASE"] %>
This is telling Rails to get the secret_key_base value from the environment variable, SECRET_KEY_BASE. You must specify this variable by placing this in your nginx configuration:
passenger_env_var SECRET_KEY_BASE yoursupersecretkeybase;
If you don't have a SECRET_KEY_BASE, you can generate one with:
bundle exec rake secret

Resources