Open6

Sinatra+PumaでSSL (TLS)クライアント証明書認証サイトを立てたり止めたりする

Yusuke IwakiYusuke Iwaki

Sinatra::Base#run! と Puma::Server#run の関係

Sinatra::Base#run!

https://github.com/sinatra/sinatra/blob/v3.2.0/lib/sinatra/base.rb#L1588-L1601

Rack::Handler::Pumaのインスタンスを作成して、server_settingをハッシュにして、start_serverしている。なんかブロック引数もそのまま渡されている。 If given a block, will call with the constructed handler once we have taken the stage. うーん、なんだろう。

Sinatra::Base#start_server (private)

https://github.com/sinatra/sinatra/blob/v3.2.0/lib/sinatra/base.rb#L1656-L1674

Rack::Handler::Puma#run(app, server_setting) のように呼び出している。呼び出し時にブロックが指定されて、 on_start_callback が引数無しで呼ばれ、 Sinatra::Base#run! から渡されたブロックが呼ばれる。

Rack::Handler::Puma#run

https://github.com/puma/puma/blob/v6.4.2/lib/rack/handler/puma.rb#L67-L82

Puma::Launcher を作って、 runを呼ぶ直前にlauncherを引数に渡してブロックを実行して Puma::Launcher#run を実行している。

Puma::Launcher

https://github.com/puma/puma/blob/v6.4.2/lib/puma/launcher.rb#L40-L45

argsが渡されて、

https://github.com/puma/puma/blob/v6.4.2/lib/puma/launcher.rb#L102

runはただ Puma::Runner#run を呼ぶだけ。

Puma::Runner

基本的には Puma::Server#run を呼ぶだけ。

https://github.com/puma/puma/blob/v6.4.2/lib/puma/single.rb#L52-L53

https://github.com/puma/puma/blob/v6.4.2/lib/puma/runner.rb#L176-L180

Yusuke IwakiYusuke Iwaki

一見すると、外からadd_ssl_listenerを仕掛ける口はなさそうに見える。

ただ、ソースをよく見てみると...

https://github.com/puma/puma/blob/v6.4.2/lib/puma/server.rb#L663-L666

そもそも Puma::Server#add_ssl_listenerPuma::Binder#add_ssl_listener にスルーパスしているだけで、

https://github.com/puma/puma/blob/v6.4.2/lib/puma/binder.rb#L217-L254

Puma::Binder のなかには .parse というメソッドで ssl:// のようなURLが指定されると、クライアント証明書を指定してContextを作って、 Puma::Binder#add_ssl_listener している実装がある。

https://github.com/puma/puma/blob/master/lib/puma/runner.rb#L168

Puma::Runnerの options[:bind]ssl:// ならいい。

おおもとは、 Rack::Handler::Pumaのほうで

https://github.com/puma/puma/blob/master/lib/rack/handler/puma.rb#L98-L101

このあたりか。

Rack::Handler::Puma#run の最初に実行されている configっていうメソッドで、Hostが ssl:// なURLだったら自動的にSSLモードになる、と。

https://github.com/puma/puma/blob/master/lib/rack/handler/puma.rb#L67-L68

Yusuke IwakiYusuke Iwaki
app.run!(port: port, bind: 'localhost')

だと

Puma starting in single mode...
* Puma version: 6.4.2 (ruby 3.3.0-p0) ("The Eagle of Durango")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 66068
* Listening on http://127.0.0.1:8000
* Listening on http://[::1]:8000

app.run!(port: port, bind: '127.0.0.1')

だと

Puma starting in single mode...
* Puma version: 6.4.2 (ruby 3.3.0-p0) ("The Eagle of Durango")
*  Min threads: 0
*  Max threads: 5
*  Environment: development
*          PID: 66704
* Listening on http://127.0.0.1:8000

のように、 bind パラメータを指定すると挙動は変わってそうだ。

Yusuke IwakiYusuke Iwaki

ssl://localhost/?ca=xxx&cert=yyy のように、クエリパラメータで指定したものが

https://github.com/puma/puma/blob/v6.4.2/lib/puma/binder.rb#L222-L241

ここでparamsに入り、そのままMiniSSL::ContextBuilderに渡されるっぽい。

https://github.com/puma/puma/blob/master/lib/puma/minissl/context_builder.rb

verify_mode=force_peer を指定すると、ちょうどPumaのテストコードにあったクライアント証明書必須、になるようだ。