2009/11/21

openssl haskell context

Haskell で Socket 使って少し通信できるようになったので
SSL で通信でもしてやろうかと思いました
クライアントとして
引きつづき HsOpenSSL を使って

OpenSSL でクライアントとして接続というと
context っていうクライアント認証の情報とか SSL のバージョンとかと
あと socket を作っておいて
その二つを合わせて SSL の接続へと socket を昇格? させます

で、ググりゃそりゃリファレンスが出てくるので
OpenSSL.Session
ghci で上に従ってやってみようと思ったんですが何か上手くいかず
色々ググったりして散々悩んでみたんですが
コード書いてコンパイルしたら通ってしまいました
ghci だとダメってよりは、withOpenSSL が無くてダメだったのかな

折角なんで Firefox から ThawtePremiumServerCA って証明書を抜き
引き数に hostname を取って 443 port で SSL 通信して
サーバ証明書を取ってきて証明書の検証ができるかどうか試してみました
$ ./cert_check www.google.com
VerifyFailure
$ ./cert_check www.blogger.com
VerifySuccess
有効期限の確認も FQDN と CN の比較もしてなくて
ルート証明書の公開鍵でサーバ証明書の署名の確認してるだけですが

import System
import Data.Maybe
import Network.Socket
import Network.BSD
import OpenSSL
import OpenSSL.Session
import OpenSSL.X509
import OpenSSL.PEM
import OpenSSL.RSA
import OpenSSL.EVP.PKey

main = withOpenSSL $
do
(host:_) <- getArgs
addr <- return . head . hostAddresses =<< getHostByName host

ctx <- context
sock <- socket AF_INET Stream 0
Network.Socket.connect sock (SockAddrInet 443 addr)
ssl <- connection ctx sock
OpenSSL.Session.connect ssl
x509 <- return . fromJust =<< getPeerCertificate ssl
-- putStr =<< writeX509 x509
OpenSSL.Session.shutdown ssl Unidirectional

root <- readX509 =<< readFile "ThawtePremiumServerCA.crt"
pk <- return . fromJust . toRSAKey =<< getPublicKey root
putStrLn . show =<< verifyX509 x509 pk
where
toRSAKey :: SomePublicKey -> Maybe RSAPubKey
toRSAKey = toPublicKey

2009/11/20

haskell hostname resolve

続きまして、HTTP でも叩こうかと思いました
直に Network.Socket を叩くよ!!

ってまぁ、流石にそんなことしてる人は沢山いらっしゃいまして
いくらでもサンプルが出てくるんですがシンプルだったのは
関数型プログラミング言語Haskell Part6
2ちゃんねるの過去ログの 607 さんでした
ちなみに検索語は "import Network.Socket"

で、挙がってた sample は
import Network.Socket 
main = putStrLn =<< do
sock <- socket AF_INET Stream 0
addr <- inet_addr "66.249.89.104" >>= \x -> return $ SockAddrInet 80 x
connect sock addr
send sock "GET / HTTP/1.1\nHost: www.google.co.jp\n\n"
recv sock 10000000 >>= \x -> sClose sock >> return x
おぉぉ

IP address ってのはちょっと寂しかったので
名前を解決してやりたいなと思いもうちょっとググりました
Network.BSD
なるほど、getHostByName 使えと
上の2ちゃんのログの 604 さんが
インタフェースはCのAPIそのままだから、そっちを調べるべき。
っつってたので
C のこと分かってればいいみたいです
わたし? えぇ、分かってないですけど

> :t getHostByName
getHostByName :: HostName -> IO HostEntry
> :i HostEntry
data HostEntry
= HostEntry {hostName :: HostName,
hostAliases :: [HostName],
hostFamily :: Network.Socket.Internal.Family,
hostAddresses :: [Network.Socket.Internal.HostAddress]}
-- Defined in Network.BSD
あと、HostName は String のこと
HostEntry の中の hostAddresses に IP address が入ってるので
> getHostByName "www.google.com" >>= return . hostAddresses
[1750726978,1666840898,2472147266,1733949762]
10進でアドレス見せてくれます

つわけで、こんなん作ったら
import System
import Network.BSD
import Network.Socket

main = do (host:path:_) <- getArgs
addr <- return . head . hostAddresses =<< getHostByName host
sock <- socket AF_INET Stream 0
connect sock (SockAddrInet 80 addr)
send sock $ "GET " ++ path ++ " HTTP/1.1\nHost: " ++ host ++ "\n\n"
putStrLn =<< recv sock 100000
sClose sock
こんなん出ました
$ ./get www.ietf.org /rfc/rfc5280.txt
HTTP/1.1 200 OK
Date: Fri, 20 Nov 2009 08:10:29 GMT
Server: Apache/2.2.4 (Linux/SUSE) mod_ssl/2.2.4 OpenSSL/0.9.8e PHP/5.2.6 with Suhosin-Patch mod_python/3.3.1 Python/2.5.1 mod_perl/2.0.3 Perl/v5.8.8
Last-Modified: Wed, 07 May 2008 17:42:25 GMT
ETag: "1caf1b6-56144-7cddc240"
Accept-Ranges: bytes
Content-Length: 352580
Vary: Accept-Encoding
Content-Type: text/plain







Network Working Group D. Cooper
Request for Comments: 5280 NIST
Obsoletes: 3280, 4325, 4630 S. Santesson
Category: Standards Track Microsoft
S. Farrell
Trinity College Dublin
S. Boeyen
Entrust
R. Housley
Vigil Security
W. Polk
NIST
May 2008


Internet X.509 Public Key Infrastructure
なんでこれ途中で切れるの?
recv に渡す数字でちゃんと表示内容変化してるのに
気付いていなかったみたいです
socket が分かっていない...

"Couldn't match expected type `IO *' against inferred type"

Haskell がきれいらしいので Haskell 少し真面目にしようかなと
証明書くらい読めなきゃいけないだろうということで X.509 を読むことに

ググったら HsOpenSSL という OpenSSL の binding があり
cabal は入れてあったので
$ cabal install HsOpenSSL
でインストール終了
OpenSSL を見て
    main = withOpenSSL $
do ...
と withOpenSSL で囲むというのと
Learning After School » HsOpenSSL Update を見て
ghci には --make というオプションを与えないとダメなことを認識

とりあえず読めればいいと思ったので
PEM への PATH を最初の引数で取ってそれを解釈して吐くことにしました
HsOpenSSL 関係で使った関数は
OpenSSL.PEM.readX509 :: String -> IO X509
OpenSSL.X509.getVersion :: X509 -> IO Int
OpenSSL.X509.getVersiongetSerialNumber :: X509 -> IO Integer
OpenSSL.X509.getVersiongetNotBefore :: X509 -> IO UTCTime
OpenSSL.X509.getVersiongetNotAfter :: X509 -> IO UTCTime
OpenSSL.X509.getVersiongetIssuerName :: X509 -> Bool -> IO [(String, String)]
OpenSSL.X509.getVersiongetSubjectName :: X509 -> Bool -> IO [(String, String)]
OpenSSL.X509.getVersiongetPublicKey :: X509 -> IO SomePublicKey
OpenSSL.EVP.PKey.toPublicKey :: (PublicKey k) => SomePublicKey -> Maybe k

UTCTime ってのは Data.Time.Clock で定義されてるんだけど
Show のインスタンスなので show で文字列にできる
getIssuerName と getSubjectName の取る Bool は名前の省略するか否か
tuple は ("countryName", "JP") みたいなのが入ってる

でー、ここまでは良かったんだけど分からなかったのが公開鍵で
SomePublicKey ってのは class なんだけど
> :i SomePublicKey
data SomePublicKey where
OpenSSL.EVP.PKey.SomePublicKey :: forall k.
(PublicKey k) =>
!k -> SomePublicKey
-- Defined in OpenSSL.EVP.PKey
instance Eq SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
> :i PublicKey
class (Eq k, Data.Typeable.Typeable k, PKey k) => PublicKey k where
fromPublicKey :: k -> SomePublicKey
toPublicKey :: SomePublicKey -> Maybe k
-- Defined in OpenSSL.EVP.PKey
instance PublicKey RSAKeyPair -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomePublicKey -- Defined in OpenSSL.EVP.PKey
instance PublicKey SomeKeyPair -- Defined in OpenSSL.EVP.PKey
instance PublicKey RSAPubKey -- Defined in OpenSSL.EVP.PKey

type とか class に関する理解が不足しているのは分かりつつ
toPublicKey に SomePublicKey かましたらいいんでしょ? と思って
で、IO SomePublicKey だから bind とか使ったらいいんでしょ? と思って
pk <- getPublicKey x509 >>= toPublicKey
としてみたら
Couldn't match expected type `IO b' against inferred type `Maybe k'
と言われてしまい
これが解決できなくて検索しました

Re: [Haskell-cafe] Convert IO Int to Int: msg#00458 haskell-cafe@haskell.org
何となく分かったのは、IO の中身取り出そうとしてて、そりゃダメだ
return かましてらしたんで、かましてみたら IO で包めて通りました!
と思いきや
SomePublicKey ってののインスタンスも指定しないとダメみたいで
:: Maybe RSAPublicKey ってのを付けないと toPubicKey が上手く動かず
ん〜、理解不足、RSA 決め打ち気持ち悪い

でもまぁ、こんな感じでとりあえず中身見れるようになりました
module Main where
import System
import OpenSSL
import OpenSSL.PEM
import OpenSSL.X509
import OpenSSL.EVP.PKey
import OpenSSL.RSA
import Data.Maybe

main :: IO ()
main = withOpenSSL $
do
x509 <- OpenSSL.PEM.readX509 =<< head =<< getArgs
putStr "version: "
getVersion x509 >>= putStrLn . show . succ
putStr "serialNumber: "
getSerialNumber x509 >>= putStrLn . show
putStr "notBefore: "
getNotBefore x509 >>= putStrLn . show
putStr "notAfter: "
getNotAfter x509 >>= putStrLn . show

putStrLn ""
issuerRDNs <- getIssuerName x509 True
putStrLn "[issuer]"
mapM_ printRDN issuerRDNs
subjectRDNs <- getSubjectName x509 True
putStrLn "[subject]"
mapM_ printRDN subjectRDNs

putStrLn "[publickKey]"
somePublicKey <- getPublicKey x509
pk <- return $ fromJust (toPublicKey somePublicKey :: Maybe RSAPubKey)
putStr "e = "
putStrLn $ show $ rsaE pk
putStr "n = "
putStrLn $ show $ rsaN pk<
where
printRDN (f, s) = putStrLn (f ++ " = " ++ s)
とりあえず動いたんで満足しました

$ ./x509 www.blogger.com.crt 
version: 3
serialNumber: 154451598012349719216436864807249350442
notBefore: 2008-04-29 22:42:19 UTC
notAfter: 2010-05-26 11:11:00 UTC

[issuer]
countryName = ZA
stateOrProvinceName = Western Cape
localityName = Cape Town
organizationName = Thawte Consulting cc
organizationalUnitName = Certification Services Division
commonName = Thawte Premium Server CA
emailAddress = premium-server@thawte.com
[subject]
countryName = US
stateOrProvinceName = California
localityName = Mountain View
organizationName = Google Inc
commonName = *.blogger.com
[publickKey]
e = 65537
n = 156261988165875299258992916522864140599934684523601467326503718745802091107083682835929499666111345772555979343409161090600093388197459190695135388517589095274179717212596199311728073635629373769115406240413244621434604136126497937524426697977932393973863819806782187408995863143657362345664311920806817108659

nginx mongrel_rails

Ubuntu で Rails と思ったわけです
パッケージ使えばいいとは思うのですが
Ruby というか Rails 周りは動きが速かったりするので
追従するには手作りかなと思い Ruby から作りました
で、gem 入れて rails 入れたり他のも入れたり

rails 自体は何で動かそうかなーと思ったのですが
mongrel ってのがスタンダードだと以前聞いていたのでそれで
で、その前に流行りの nginx を置いてみることにしました

ググってみたところ mongrel_cluster ってのを使えと
Ruby on Rails: mongrel_clusterのフロントエンドに nginxを使用する
これは、サービスとして mongrel を使うならば
バックエンドが例え1プロセスだとしても使った方が良いとのことで
素直に従うことに

でも、まぁ、結局 init script はどうにかするしかないかぁ〜
自前で書くような技術も根性も無いので参考が欲しい!
Ubuntu だしパッケージの中のを見ればいいだろうと思って
apt-get には無かったけど aptitude に download ってコマンドがあったので
$ aptitude download mongrel-cluster
$ aptitude download nginx
などして deb ファイル取ってきて
ar -x とか tar xfz で中身とりだして適当にスクリプトを眺めました
そして init script も完成

良く分からなかったのが gem install mongrel_cluster で入れたら
設定ファイルが /etc/mongrel_cluster であることが前提みたいで
できるだけ Ubuntu のものと被らせたくないなと思ってたんですが
設定方法が分からなかったんで symlink で逃げることにしました
もぉ、Ubuntu の mongrel-cluster は入れれません
や、入れないけど

mongrel_cluster によって何か恩恵にあずかれたのかは良く分からず
でも、とりあえず形にする為に一旦そこは放置で

nginx が前にいますが
Rails のとあるアプリ? だけ見えるようにしたかったので
proxy_pass で飛ばすのにルールが必要かなーと思ったんですが
今度は真面目にドキュメントにあたってみたら
NginxHttpProxyModule
rewrite 使って書き換えてから proxy_pass かましてました
や、あたりまえな気が今ではしています
        location / {
root html;
rewrite ^/$ /application break;
proxy_pass http://mongrel_cluster;
}
みたいにしてみました

あと、他のアプリは別途 location 立てて auth_basic しておきました
htpasswd は apache2-utils に入ってたので入れちゃいました
別途 location 立てるのが何か無駄な気がするんだけど仕方ないのかな
auth_basic が if の中では使えないので、まぁ、仕方ないのか?

久しぶりにこの辺の設定したので凄い頭痛くなりました

2009/11/18

ec2-ami-tools ruby curl ubuntu debootstrap

EC2 の上に minimal な ubuntu 環境が欲しいと思って
まぁ debootstrap で作って tool を入れたらいいのかなと思って
検索してたら

http://ec2ubuntu.googlecode.com/svn/trunk/bin/ec2ubuntu-build-ami
イメージ作る用のスクリプトが出てきました。
っつうか
ec2ubuntu - Project Hosting on Google Code
こういうのがちゃんとあるんですね、さすが。

で、欲しかった ami も C2 and Ubuntu - Alestic.com にあるし
apt-line には universe だけあるんで multiverse をコピー&置換で追加してやれば
$ aptitude install ec2-api-tools ec2-ami-tools
とかしてツールも入れれるみたいだし
あとは、真面目に作り込むだけみたいです...。
真面目に...。

2009/11/17

"from xen to vmware"

前回までのあらすじ:
ESXi 4 をインストールしてその上で Windows Server 2003 を動かしていたが
Xen の上に移行できないのかと思い OpenSUSE 上の Xen にコピーを試みる
ESXi の上ではその都度ファイルが大きくなるような奴は使っていなかったが
ide のドライバを入れた状態の ???-flat.vmdk とやらをコピーして起動してみたら
何の変換も無しに Xen の上で動いてしまったのだった!!

Xen の上で一通り遊んだので ESXi の上に帰ろうと思いました
で、あれー、変換しないといけないのかなーと思ったので
色々入れて検索してみて vmware 何とか converter とかも入れてみて
ん〜 Xen っぽいのが無いから変換できないじゃんと悩んでいました

でも、良く考えたら前回変換しないで動いたんですよね
コピーして ESXi のコンソールを立ちあげて
古いのをリネームして Xen からもってきたのをその名前にしてやったところ
何の躊躇いもなく ESXi さんは起動してくれちゃいました
つまり分割とか何とか機能使ってなかったら普通のイメージってことなのね!?

ドキュメント読まずに勘 95% で作業してるので
良く VMWare のこと分かってないんですが
ライブマイグレーションとか今んとこ考えてなくて
検証作業がポンポン作れたり
スナップショットで便利を感じたりできればいい程度なら
Xen も VMWare も、多分 KVM とかも、コピーでイメージ使い回せるみたい
でもドライバのこと忘れないでね、という感じでした

VMWare の便利ツールのことをもう少し調べないと無駄な苦労してそうです