SVX日記

2004|04|05|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|12|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|04|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
2018|01|02|03|04|05|06|07|08|09|10|11|12|
2019|01|02|03|04|05|06|07|08|09|10|11|12|
2020|01|02|03|04|05|06|07|08|09|10|11|12|
2021|01|02|03|04|05|06|07|08|09|10|11|12|
2022|01|02|03|04|05|06|07|08|09|10|11|12|
2023|01|02|03|04|05|06|07|08|09|10|11|12|
2024|01|02|03|04|05|06|07|08|09|10|11|

2024-03-02(Sat) Sinatraでhtpasswd認証したりldap認証したり

  回転のプログラミングの途中だが、ひょんなことから、Sinatraでウェブサービスを提供する各種のコンテナに認証機能を付ける必要が生じた。ハテ?基本的にはHAProxyを被せて運用しているのだが、認証ってどうすんだっけ?

  調べると、HAProxyには単純な認証機能はあるものの、どうもLDAP認証の機能はないらしい。まぁ、Sinatra側でやるべきだよな、と思ったら、Sinatraにも単純な認証機能しかないようだ。

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周もしたの? と思ったらそのとおりだった。うげぇ。エンジニアすぐ死ぬ。