socketry / falcon

A high-performance web server for Ruby, supporting HTTP/1, HTTP/2 and TLS.
https://socketry.github.io/falcon/
MIT License
2.54k stars 79 forks source link

how to use custom certification files? #75

Open tonytonyjan opened 4 years ago

tonytonyjan commented 4 years ago

With puma, we can use our own cert files either by URL or a config file.

URL:

$ puma -b 'ssl://127.0.0.1:9292?key=path_to_key&cert=path_to_cert'

config:

ssl_bind '127.0.0.1', '9292', {
  cert: path_to_cert,
  key: path_to_key,
}

I wonder if falcon has the same feature.

wtn commented 4 years ago

The configuration options are different depending on how you start falcon.

I was able to set the path to certificates for falcon serve by monkey-patching the localhost gem with the following script:

#!/usr/bin/env ruby

require 'localhost/authority'

class Localhost::Authority
  def self.path
    File.join __dir__, 'ssl'
  end
end

require 'falcon/command'

serve = Falcon::Command::Serve[
  '--hostname', 'example.com',
  '--bind', 'https://localhost:443',
  parent: Falcon::Command::Top[]
]

serve.call

Certificates will be loaded based on the hostname specified, like:

ssl/example.com.crt
ssl/example.com.key
ioquatix commented 4 years ago

falcon serve is for local development and should generally not be used in production.

falcon host should be used in production and you use a falcon.rb file to configure it.

There is very limited documentation at this time as it's only just been released, but take a look at the following files:

Here are the parameters to set the tls certificates:

https://github.com/socketry/falcon/blob/b8e9ca5aeaf748bec550a54a8bcdeb7f7c3d3fe7/lib/falcon/configuration/tls.rb#L31-L38

Make your own falcon.rb file and put the tls configuration here:

https://github.com/socketry/falcon/blob/master/examples/hello/falcon.rb#L9-L14

Then use falcon host.

ioquatix commented 4 years ago

Also if possible, once you have it working can you let me know and can you help with documentation?

wtn commented 4 years ago

Note that OpenSSL::SSL::SSLContext#ssl_version= is deprecated in favor of min_version and max_version.

falcon serve works without any configuration, but falcon host gives me TLS errors. Here's the curl output:

% curl -v "https://example.com:3000"
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to example.com (127.0.0.1) port 3000 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS alert, handshake failure (552):
* error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure
* Closing connection 0
curl: (35) error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure

Here's my config file:

#!/usr/bin/env ./bin/falcon-host

load :rack, :tls

rack 'example.com', :tls do
  ssl_session_id 'falcon'
  ssl_ciphers Falcon::TLS::SERVER_CIPHERS

  scheme 'https'
  protocol { Async::HTTP::Protocol::HTTPS }

  endpoint do
    Async::HTTP::Endpoint.for(scheme, 'localhost', port: 3000, protocol: protocol)
  end

  ssl_certificate_path { File.expand_path 'ssl/certificate.pem', root }
  ssl_certificates { OpenSSL::X509.load_certificates ssl_certificate_path }

  ssl_certificate { ssl_certificates[0] }
  ssl_certificate_chain { ssl_certificates[1..-1] }

  ssl_private_key_path { File.expand_path 'ssl/private.key', root }
  ssl_private_key { OpenSSL::PKey::RSA.new File.read(ssl_private_key_path) }

  ssl_context do
    OpenSSL::SSL::SSLContext.new.tap do |context|
      context.add_certificate ssl_certificate, ssl_private_key, ssl_certificate_chain

      context.session_cache_mode = OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT
      context.session_id_context = ssl_session_id

      context.alpn_select_cb = lambda do |protocols|
        if protocols.include? 'h2'
          return 'h2'
        elsif protocols.include? 'http/1.1'
          return 'http/1.1'
        elsif protocols.include? 'http/1.0'
          return 'http/1.0'
        else
          return nil
        end
      end

      context.ssl_version = :TLSv1_2_server
      context.set_params ciphers: ssl_ciphers, verify_mode: OpenSSL::SSL::VERIFY_NONE
      context.setup
    end
  end
end

It works if I disable HTTPS by setting scheme 'http' with protocol { Async::HTTP::Protocol::HTTP1 }.

ioquatix commented 4 years ago

You don't need to duplicate the configuration in :tls if you aren't changing it. You should only need to set the ssl_certificate_path and ssl_private_key_path.

Can you try some other tool like wget. Are you on Darwin or Linux?

wtn commented 4 years ago

My operating system is darwin19.2.

Here's my updated falcon host config file, which also does not work. The endpoint line is needed to force falcon to bind with TCP. Without it Async::IO::UNIXEndpoint is used.

load :rack, :tls

rack 'example.com', :tls do
  endpoint { Async::HTTP::Endpoint.for scheme, 'localhost' }
  ssl_certificate_path { '/usr/local/project/ssl/example.com.crt' }
  ssl_private_key_path { '/usr/local/project/ssl/example.com.key' }
end

I figure my syntax must be incorrect, because no matter what I try, falcon host has bad TLS:

% openssl s_client -connect example.com:443

CONNECTED(00000003)
4636098156:error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-47.11.1/libressl-2.8/ssl/ssl_pkt.c:1200:SSL alert number 40
4636098156:error:140040E5:SSL routines:CONNECT_CR_SRVR_HELLO:ssl handshake failure:/BuildRoot/Library/Caches/com.apple.xbs/Sources/libressl/libressl-47.11.1/libressl-2.8/ssl/ssl_pkt.c:585:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
    Protocol  : TLSv1.2
    Cipher    : 0000
    Session-ID: 
    Session-ID-ctx: 
    Master-Key: 
    Start Time: 1580958321
    Timeout   : 7200 (sec)
    Verify return code: 0 (ok)
---

curl 7.68 suggests a client certificate is required:

*   Trying ::1:443...
* TCP_NODELAY set
* Connected to localhost (::1) port 443 (#0)
* ALPN, offering http/1.1
* SSL peer handshake failed, the server most likely requires a client certificate to connect
* Closing connection 0
curl: (35) SSL peer handshake failed, the server most likely requires a client certificate to connect

However, I haven't been able to override the ssl_context and make it work.

Meanwhile, falcon serve negotiates cipher ECDHE-RSA-AES256-GCM-SHA384 and my CA-signed certificate works correctly.

ioquatix commented 4 years ago

Interesting, I'll probably need to take a closer look. Thanks for reporting back.

wtn commented 4 years ago

I tried again with a clean setup on darwin18 and had the same outcome. No TLS under falcon host, and self-signed certificates only under falcon virtual.

I can successfully use my custom certificates using falcon serve with the workaround I posted before in this thread. I am running this in staging (rails production environment) as a temporary workaround.

ioquatix commented 4 years ago

Can you let me know what version of falcon you are using?

wtn commented 4 years ago

I'm on falcon 0.34.5, but I see that you've worked on related code since then. Sorry, I thought I had updated more recently. I'll test on the current release and report back.

wtn commented 4 years ago

I got falcon virtual to work under version 0.34.5 using supervisor. Under this setup, my :tls configuration block causes Errno::EISDIR exceptions so I removed it and use the default certificate paths:

ioquatix commented 4 years ago

Can you show me your current falcon.rb configuration?

Yes, by default it will use ssl/ directory for certificates.

https://github.com/socketry/falcon/blob/master/lib/falcon/configuration/tls.rb#L31-L38

So, is it working, or you still having the same issues?

wtn commented 4 years ago

I was using incorrect syntax. With corrected syntax, I am able to override the configuration. Thank you.

nic-lan commented 3 years ago

i have a similar issue.

i would like to move one rails app from using Puma used with ssl_bind to Falcon and following this thread i set up a falcon.rb. i am now trying to make it work on localhost before deploying on staging environment

# falcon.rb

load :rack, :tls

rack 'my_app', :tls do
  endpoint { Async::HTTP::Endpoint.for scheme, 'localhost', port: '3000' }
  ssl_certificate_path { './certificate.pem' }
  ssl_private_key_path { './private.key' }
end

the certificate.pem and private.key are generated with openssl similarly to

openssl req -new -newkey rsa:2048 -days 3650 -nodes -x509 -subj "some configurations" -keyout private.key -out certificate.pem

and are present in the root dir of the rails app as well as the falcon.rb file

when doing bundle exec falcon host the logs at startup shows:

  0.0s     info: Falcon::Command::Host [oid=0x210c] [pid=73062] [2021-01-04 20:44:50 +0000]
               | Falcon Host v0.37.1 taking flight!
               | - Configuration: falcon.rb
               | - To terminate: Ctrl-C or kill 73062
               | - To reload: kill -HUP 73062
 0.02s     info: Falcon::Service::Application [oid=0x2120] [pid=73062] [2021-01-04 20:44:50 +0000]
               | Binding to #<Async::HTTP::Endpoint https://localhost:3000 {:port=>"3000"}>...
 0.03s     info: Falcon::Service::Application [oid=0x2120] [pid=73085] [2021-01-04 20:44:50 +0000]

when trying to connect to https://localhost:3000 i receive

Screen Shot 2021-01-04 at 20 59 34

some async tasks fail with

OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 state=error: inappropriate fallback

other with

OpenSSL::SSL::SSLError: SSL_accept returned=1 errno=0 state=error: no shared cipher

Environment

Note

I see a self_signed_tls and lets_encrypt_tls files in environments but i don't understand if they could be used somehow to solve my issue and how. I would be more than happy to help with the docs because i found them not really helpful in this case

For example the same error pops up if the falcon.rb is

load :rack, :lets_encrypt_tls, :supervisor

rack 'my_app', :lets_encrypt_tls do
  endpoint { Async::HTTP::Endpoint.for scheme, 'localhost', port: '3000' }
end

supervisor
ioquatix commented 3 years ago

The tls environment should be sufficient. Can you try using curl --insecure and see what it prints out?

nic-lan commented 3 years ago
❯ curl --insecure -I https://localhost:3000
curl: (35) error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure

❯ curl --insecure https://localhost:3000
curl: (35) error:14004410:SSL routines:CONNECT_CR_SRVR_HELLO:sslv3 alert handshake failure
ioquatix commented 3 years ago

I see what's going on.

The normal TLS termination is at the falcon virtual load balancer.

falcon host by default is for the internal termination.

So, you forced it to be on the network interface by specifying the endpoint, but you didn't specify the ssl_context.

Your configuration needs to look like this:

# falcon.rb

load :rack, :tls

rack 'my_app', :tls do
  endpoint do
    Async::HTTP::Endpoint.for(scheme, 'localhost', port: '3000', ssl_context: ssl_context)
  end

  ssl_certificate_path { './certificate.pem' }
  ssl_private_key_path { './private.key' }
end

We can probably make an environment specific to this use case, e.g. direct_tls or something.

nic-lan commented 3 years ago

Thank you @ioquatix.

your solution proved to be working.

now running curl --insecure -I https://localhost:3000 returns the html from the root page.

Note 1

trying to visit the link https://localhost:3000 with the browser would fail because the localhost is not able to verify the browser certificate and would return

NET::ERR_CERT_INVALID

i was tented to say "this is a client problem so i guess we are good to go." but now i see the same is happening when i do

bundle exec falcon serve --port 3000

after checking i see that falcon is still serving a tls connection at https://localhost:3000. it would guess that it is not the right behavior and i suspect that something weird is happening under the hood but it is not clear to me why this is happening because as far as i understand falcon.rb ( where tls is configured ) should not be loaded at this point

when curling insecure the html is returned

Note 2

if you like i could try to set up a direct_tls environment PR

ioquatix commented 3 years ago

falcon serve does not use falcon.rb, it only looks at config.ru. falcon.rb is only used by falcon host which is a more complex mechanism that supports multiple hosts, rolling restarts, etc. falcon serve will use self-signed certificates if required, which are stored in ~/.localhost. falcon serve will therefore serve TLS connections by default as this is required for HTTP/2.

nic-lan commented 3 years ago

Thank you for the explanation

so at the end i was able to fix the issue client side by setting

brave://flags/#allow-insecure-localhost

or by doing

bundle exec falcon serve --bind http://localhost:9292
ioquatix commented 3 years ago

It's not well documented I guess since you didn't find it but it is explained here too: https://github.com/socketry/localhost#self-signed-localhost

troex commented 3 years ago

I'll add my case here, we use real signed certificates in development and testing and it's a must for our project, also it means each developer has it's own certificates. So far I'm only checking how we could use falcon in our environment but in order to run our certificates when using falcon serve you just need to place your key and cert into ~/.localhost/localhost.key (and .crt) and it works out of the box. I didn't find any issues if I not add --hostname mydev.doman

ioquatix commented 3 years ago

Yes this is a totally acceptable way to do it. That .localhost directory is considered a place for your local development certificates.

ioquatix commented 6 days ago

Just FYI, the directory was changed ~/.local/state/localhost.rb.