SVX日記
2024-03-02(Sat) Sinatraでhtpasswd認証したりldap認証したり
回転のプログラミングの途中だが、ひょんなことから、Sinatraでウェブサービスを提供する各種のコンテナに認証機能を付ける必要が生じた。ハテ?基本的にはHAProxyを被せて運用しているのだが、認証ってどうすんだっけ?
use Rack::Auth::Basic do |username, password|
username == 'admin' && password == 'secret'
end
これを追加すると全体に認証がかかる。恐ろしいほどに見たまんまなコードだw。逆に言えば、ここに自分で仕組みを組み込んでやれば、好みの認証機能を実現できるということだ。ハッシュの知識はあるし、既にLDAPにアクセスするコードも持っている。ほんじゃ、ということで書いてみた。
require 'sinatra'
eval(File.read('pv/sinatra.config')) rescue true
@configs ||= {}
unless(@configs[:no_auth])
use Rack::Auth::Basic, 'Authorization Required' do |username, password|
authorized = false
if(@configs[:auth_htpasswd])
ht_password = false
open(@configs[:htpasswd_file]) {|fh|
fh.each {|l|
l =~ /^#{username}:(.+)/ and ht_password = $1 and break
}
}
if((it = ht_password) and it =~ /^{SHA}(.+)/i)
require 'digest/sha1'
hash_base64 = $1
hash = hash_base64.unpack('m*')[0]
challenge = Digest::SHA1.digest(password)
authorized |= (hash == challenge)
end
end
if(@configs[:auth_ldap])
require 'net/ldap'
ldap_password = false
ldap = Net::LDAP.new(
:host => @configs[:ldap_host],
:port => @configs[:ldap_port],
:auth => @configs[:ldap_auth],
)
results = ldap.search(
:base => @configs[:ldap_search_base],
:filter => '(cn=%s)' % username,
:attributes => ['userpassword'],
)
(it = results) and (it = it[0]) and (it = it[:userpassword]) and (it = it[0]) and ldap_password = it
if((it = ldap_password) and it =~ /^{SSHA}(.+)/i)
require 'digest/sha1'
hash_salt_base64 = $1
hash_salt = hash_salt_base64.unpack('m*')[0]
hash = hash_salt[0, 20]
salt = hash_salt[20, 4]
challenge = Digest::SHA1.digest(password + salt)
authorized |= (hash == challenge)
end
end
authorized
end
end
上記のコードをapp.rbの冒頭に追加すればいい。ApacheのhtpasswdとLDAPの両方に対応するが、今のところ前者は「{SHA}」後者は「{SSHA}」形式のみ対応。当初「$apr1$」形式に対応しようと思ったのだが、それは単なるMD5ではなく、どうもApacheの独自実装らしい。ソース読めばRubyで実装できなくはないが、そんなのに付き合っても今後とも得はなさそうなので「{SHA}」への対応にした。なので、htpasswdでハッシュを生成する際には-sを指定する必要がある。
どうでもいいが、Rubyのpack/unpackって、ホントに覚えられないなぁ。いつもpack/unpackのどっちが配列化/バイナリ化だっけ? ってなる。で、考えるもの面倒になって、適当にコードを書いて済ましちゃう。pack/unpackって、機能の格納方法としては天才的な発想だと思うけど、記述方法としてはどうなのよ……Perlが元祖なのかな。うーん……うううーん。
ま、それはそれとして、GitLabに置いてあるSinatraのスケルトンコンテナに上記のコードを組み込んで、とりあえずは作業完了である。
話は替わるが、だいぶ前から我が家では卓上カレンダを自作しているのだが、時期がきたので印刷したらグレートシングだった。え、もう2周もしたの? と思ったらそのとおりだった。うげぇ。エンジニアすぐ死ぬ。