Ubuntu 12.04 (Precise) のインストールではまったこと
普通にインストールするぶんにははまらないけど個人的事情ではまった部分です。
つまり備忘録。
LVM を有効にしたインストール
基本路線は Ubuntu 8.04 を LVM 有効にしてインストールする - daily dayflower のとおり。ただし,dm-mod
はインストールする必要ない (だいぶ前からだけど)。
一点はまったのは,「インストールされた環境に lvm2 をインストールする」部分。
ネットワークブートな Live 環境からインストールしようとしたからかもしれないけど,chroot
したあとにうまくネットにつながらなかった。
procfs をマウントしたり,Live 環境の /etc/resolve.conf
をコピーする必要があった。
ubuntu@ubuntu:~$ sudo mount /dev/sda1 /target ubuntu@ubuntu:~$ sudo mount -o bind /proc /target/proc ubuntu@ubuntu:~$ sudo cp /etc/resolve.conf /target/etc/
ネットブートからのインストールだと,ここで /target/etc/network/interface
をいじっといたほうがいいかも (eth0
な部分を削除するか dhcp
に指定する)。
あとは元の手順通り。
ubuntu@ubuntu:~$ sudo chroot /target root@ubuntu:/# apt-get update root@ubuntu:/# apt-get install lvm2 root@ubuntu:/# exit
終了時は umount
もよしなに。
ubuntu@ubuntu:~$ sudo umount /target/proc ubuntu@ubuntu:~$ sudo umount /target
winbind 認証ログイン
基本的に winbind による Active Directory 認証 on Ubuntu 11.04 - daily dayflower のとおりなんだけど,Samba が 3.6.3 になってて設定ディレクティブ等がかわってたのではまった。
[global] workgroup = HOGE realm = HOGE.EXAMPLE.COM security = ADS obey pam restrictions = Yes algorithmic rid base = 10000 template homedir = /home/%U template shell = /bin/bash # winbind separator = ! winbind cache time = 60 winbind use default domain = Yes winbind nss info = sfu:HOGE winbind refresh tickets = Yes winbind normalize names = Yes winbind enum users = Yes winbind enum groups = Yes idmap config * : backend = tdb idmap config * : range = 1000000-1999999 idmap config DOMAIN : backend = rid idmap config DOMAIN : range = 10000-19999
idmap
のデフォルトの backend
等は idmap config * :
のように,ドメインごとの設定と統一感のある記述になった (古い書き方でも deprecated っていわれるだけだけど)。
あと,じっさいに使うドメイン向けじゃなくてデフォルトのを tdb
idmapper 等の永続性のある backend
に指定しておく必要があるみたい (BUILTIN sid とかのために((idmap_rid(8)
のマニュアルのうけうり。)) )。この部分に一番はまった。
winbind ユーザも lightdm からログインする
ディスプレイマネージャが lightdm に変更されていて,こいつはデフォルトだと登録されたユーザしかログオンできない (ユーザ名は選択する)。このままだと,winbind や ldap なユーザはログインできない。
ユーザ名も自分で入力できるようにするためには lightdm の設定をいじる必要がある。
具体的には,/etc/lightdm/lightdm.conf
の [SeatDefaults]
セクションに下記のような設定を付け足す。
[SeatDefaults] greeter-show-manual-login=true
DataMapper でカスタムリレーション
DataMapper - Associations の「Customizing Associations」に書いてある。
User と Message というモデルがあり,Message には宛先が(複数)指定できる,とする。できるだけ規約ベースで書くと以下のようになる。
class User include DataMapper::Resource property :id, Serial has n, :message_receipients has n, :messages, :through => :message_receipients end class Message include DataMapper::Resource property :id, Serial has n, :message_receipients has n, :users, :through => :message_receipients end class MessageReceipient include DataMapper::Resource property :id, Serial belongs_to :user belongs_to :message end u = User.create m = Message.new m.users << u m.save p m.users p u.messages
でもこれだと,「宛先」ぽさとか User の「受信したメッセージ」ぽさの意図がなくなってわかりにくい。
class User has n, :message_receipients, :child_key => [ :receipient_id ] has n, :received_messages, :model => 'Message', :through => :message_receipients, :via => :message end class Message has n, :message_receipients has n, :receipients, :model => 'User', :through => :message_receipients end class MessageReceipient belongs_to :receipient, :model => 'User' belongs_to :message end u = User.create m = Message.new m.receipients << u m.save p m.receipients p u.received_messages
要するに :child_key
とか (今回は使ってないけど) :parent_key
とか :via
などのオプションを使えば,リレーション時にどのような外部キーを使うかなどをカスタマイズできる。
新規開発だけでなくレガシースキーマを相手にする場合にも。
まぁ ORM でリレーションまでやってしまうかどうかという問題もあるけど。
Microsoft Word で LL 言語から差し込み文書作成
タイトルは釣り。
簡単な帳票 (Excel 系じゃなくて Word 系の文書) をプログラムから作成する必要がでてきたので調べた。テンプレートとなる文書にちょっとした変数の差し込みを行う必要があるケース。
ただし
- 旧来の Microsoft Office Document (OLE2) はサポートしない
- COM や UNO は使わない
- (ODF や OOXML の) 仕様書は読まない
LibreOffice (OpenOffice.org) や最近の Microsoft Office だと,XML ファイルを ZIP で固めているだけなので単純な差し込みだけならわりと簡単にできそう。
注意: ODF にしても OOXML にしても XML namespace を使っているので,厳密なことをいうと以下の例のようにタグ名を決め打ちで置換していてはうまくいかないことがありうる。とはいえそうそう変更しないだろうからこのままでうまくいくだろうし,気になるなら対象タグの xmlns
の部分だけバリデーション (というかサポートするターゲットかどうか判別) すればいいんじゃないかと思う。
OpenDocument Format (ODF) 編
LibreOffice 3.3.2 Writer on Ubuntu 11.04 で作成したドキュメントで検証をおこなった。
内容は「こんにちは ○○ さん」というだけの単純なもの。○○の部分にプログラムから差しこみたい。
テンプレート文書の作成
差しこみフィールドの作成のやり方。
展開
$ unzip ../hello.odt Archive: ../hello.odt extracting: mimetype inflating: content.xml inflating: manifest.rdf inflating: styles.xml extracting: meta.xml extracting: Thumbnails/thumbnail.png inflating: Configurations2/accelerator/current.xml creating: Configurations2/progressbar/ creating: Configurations2/floater/ creating: Configurations2/popupmenu/ creating: Configurations2/toolpanel/ creating: Configurations2/menubar/ creating: Configurations2/toolbar/ creating: Configurations2/images/Bitmaps/ creating: Configurations2/statusbar/ inflating: settings.xml inflating: META-INF/manifest.xml
無駄に空フォルダが多いが,実態は追える範囲。
解析と置換
文書の内容はおもに content.xml
に記述されている。
今回の主眼となる部分の抜粋 (改行やインデントは編集してある; 元は全部1行*1 )。
<text:p text:style-name="Standard"> こんにちは <text:placeholder text:placeholder-type="text"><hogehoge></text:placeholder> さん! </text:p>
単純明快。
Perl のワンライナーでざっくり置換してみた。XML を展開・再構築するほうが行儀いいんだろうけど,バリデーションを行うのでもなければ PCRE で必要十分な気がする。
$ perl -i -pe 's{<text:placeholder .*?> \s* <hogehoge> \s* </text:placeholder> }{dayflower}gxms' content.xml
これで一応完成。だけど,文書の統計情報 (何文字含まれているかとか何ページあるかとか) も文書に含まれている。基本的に文書ファイルのプロパティで表示するためだけのものなので間違っていても構わないんだけど,なんか気持ち悪いので削除しておく。
統計情報の実態は meta.xml
というファイルの <meta:document-statistic/>
タグに存在する。以下は例。
<meta:document-statistic meta:table-count="0" meta:image-count="0" meta:object-count="0" meta:page-count="1" meta:paragraph-count="1" meta:word-count="3" meta:character-count="20"/>
なので meta.xml
ファイルから該当する XML タグを削除しておけばよい。
再構築
普通に再圧縮するだけ。
$ zip -r9 ../new.odt * updating: Configurations2/ (stored 0%) updating: META-INF/ (stored 0%) ......
作成された文書を再度 LibreOffice で開いてみたところ,きちんと埋め込みされていた。
Office Open XML (OOXML) 編
Microsoft Office 2010 Word で作成したドキュメントで検証をおこなった。
テンプレート文書の作成
差しこみフィールドの作成のやり方。
展開
$ unzip ../hello.docx Archive: ../hello.docx inflating: [Content_Types].xml inflating: _rels/.rels inflating: word/_rels/document.xml.rels inflating: word/document.xml inflating: word/theme/theme1.xml inflating: word/settings.xml inflating: word/webSettings.xml inflating: word/stylesWithEffects.xml inflating: docProps/core.xml inflating: word/styles.xml inflating: word/fontTable.xml inflating: docProps/app.xml
無駄な空フォルダは少ない。結構多くのファイルに分散しているのは ODF と同等。
解析と置換
文書の内容はおもに word/document.xml
に記載されている。
今回のターゲットとなる部位の抜粋。
<w:p w:rsidR="00332438" w:rsidRDefault="00792A30"> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>こんにちは</w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t xml:space="preserve"> </w:t> </w:r> <w:fldSimple w:instr=" MERGEFIELD hogehoge \* MERGEFORMAT "> <w:r> <w:rPr> <w:noProof/> </w:rPr> <w:t>«hogehoge»</w:t> </w:r> </w:fldSimple> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t xml:space="preserve"> </w:t> </w:r> <w:r> <w:rPr> <w:rFonts w:hint="eastAsia"/> </w:rPr> <w:t>さん!</w:t> </w:r> <w:bookmarkStart w:id="0" w:name="_GoBack"/> <w:bookmarkEnd w:id="0"/> </w:p>
埋め込みを行う立場から見ると,ちょっとめんどい (あくまで ODF に比べての話だけど)。MergeField じゃないもっと適切なフィールドタイプがあるのかもしれない。
フィールド埋め込みについては Perl のワンライナーで書くなら以下のような感じ。
$ perl -pe 's{<w:fldSimple .*?> (.*? <w:t>) .*? (</w:t> .*?) </w:fldSimple> }{$1dayflower$2}gxms' document.xml
さきほどの ODF の例とは置換内容がちょっと違う (全埋め込みフィールド対象になっちゃってる) けど,ちゃんとやるなら置換後のほうにコード仕込んでとかやるかな。
OOXML でもやはり文書の統計情報が存在する。実態は docProps/app.xml
のいくつかのタグ。以下抜粋。
<Pages>1</Pages> <Words>9</Words> <Characters>52</Characters> <Lines>1</Lines> <Paragraphs>1</Paragraphs>
再構築
$ zip -r9 ../new2.docx * adding: [Content_Types].xml (deflated 75%) adding: _rels/ (stored 0%) ......
作成された文書を再度 Microsoft Word 2010 で開いてみたところ,きちんと埋め込みされていた。
同じ値を複数ヶ所に同時に埋め込みたい場合
もちろん全プレースホルダを上記のようにプログラムから書き換えてもいいんだけど,もっとスマートな解決策もある。OpenDocument などにはユーザ定義変数のようなものがあって,それを文書中に埋め込むことができる。この変数の値の部分だけ書き換えれば,文書中のそのユーザ変数フィールドは全部置き換わることになる。
以下は OpenDocument での例。面倒なので Microsoft Word ではどうやるか調べていないが,同等の機能はたぶんあると思う。
- 「挿入」メニューから「フィールド」「その他」メニューを選択する
- 「変数」タブを選択
- 「フィールドタイプ」で「ユーザー欄」を選択
- 「名前」フィールドに「変数名」を,「値」フィールドにデフォルトの値を入力
- (書式は「テキスト」を選択しておくのが無難)
これでユーザ定義変数が定義される (「選択」ペインにユーザ変数一覧が表示される)。
あとは文書中の挿入したい場所にカーソルを移動して「フィールド」の「挿入」ダイアログをさきほどと同じように出し,「選択」ペインで作成したユーザ定義変数を選択し,「挿入」ボタンを押下すれば埋め込まれる。
このときの content.xml
の内容は以下の通り。
<text:user-field-decls> <text:user-field-decl office:value-type="string" office:string-value="hogehoge" text:name="holder"/> </text:user-field-decls> <text:p text:style-name="Standard"> こんにちは <text:user-field-get text:name="holder">hogehoge</text:user-field-get> さん <text:user-field-get text:name="holder">hogehoge</text:user-field-get> さん <text:user-field-get text:name="holder">hogehoge</text:user-field-get> さん! </text:p>
さきほどと差しこみフィールドの実現のされ方が結構違っている。
変数部分 (<text:user-field-decl>
) のほうだけ書き換えればいいので,適当なワンライナーだけど (ほんとうなら text:name
のほうでチェックするべき)
$ perl -i -pe 's{string-value="hogehoge"}{string-value="dayflower"}gxms' content.xml
こんな感じで。
実態のドキュメント部分 (<text:user-field-get>
) のほうは置き換えてないけど,LibreOffice で再度開いた際には変数の埋めこまれた部分は自動的に変更されていた。ファイル自体はちょっと気持ち悪い状態だけど。
メモ
その他既存のアプローチについて (COM 以外)。
- Perl の場合
- OpenOffice-OODoc を使えば OpenDocument を読み・書き・操作できるみたい?そこそこメンテナンスされているようだ。
- Ruby の場合
- Java の場合
- そもそも UNO を使えば Java から OpenOffice.org, LibreOffice 等を操れるはず。
- Apache POI で OLE2 (旧来の MS Office document コンテナ) や OOXML のファイルを読み・書き・操作できるようだ。
- とはいえコアはあくまでコンテナの読み書きっぽい。各アプリケーション (Word など) の文書を操作する高次 API も用意されているようだが (例: Apache POI HWPF) クオリティについては不明。
winbind による Active Directory 認証 on Ubuntu 11.04
Ubuntu での winbind による Active Directory 認証は以前 winbind で Linux 認証 on Ubuntu - daily dayflower で書いたんだけど,だいぶ古くなったんで今の環境むけに。一応,対象 Natty だけど Maverick とかたぶん Lucid あたりまで適用できると思う。
面倒といえば面倒なのでそれを回避したいなら LikeWise 使うとか (第174回 Likewise OpenでActive Directoryを利用する:Ubuntu Weekly Recipe|gihyo.jp … 技術評論社 参照)。ただ LikeWise もカスタマイズされた Samba 等を内包してたりするんで,中身が見えないと嫌な人には向いてない。
諸元
Active Directory サーバ
- Windows 2000 Server
- Active Directory with SFU schema
2011年にもなるのに Windows 2000 Server なんてセキュリティの面とかいろいろ間違っている気がするけど,大人の事情。Windows Server 2003 以降なら SFU schema はいらない。というか 2000 Server でもシェル等が決め打ちでよかったりすれば SFU schema はいらない。
凡例
以前のを踏襲。
- ドメイン名
- HOGE
- Active Directory のレルム
- hoge.example.com
- Active Directory のドメインコントローラ
- dc.hoge.example.com
- クライアントホスト名
- Penguin
Kerberos の設定
デフォルトでは入っていないんで krb5-config をインストール。
$ sudo apt-get install krb5-config
以前は krb5-user もインストールしていたんだけど,今は kinit
しなくても net ads join
できるんでインストールする必要はないと思う。実際なくてもできた。
/etc/krb5.conf の抜粋:
[libdefaults] default_realm = HOGE.EXAMPLE.COM [realms] HOGE.EXAMPLE.COM = { kdc = dc.hoge.example.com admin_server = dc.hoge.example.com default_domain = hoge.example.com } [domain_realm] .hoge.example.com = HOGE.EXAMPLE.COM hoge.example.com = HOGE.EXAMPLE.COM
Samba, winbind の設定
samba, winbind をインストール。
$ sudo apt-get install samba winbind
/etc/samba/smb.conf の抜粋:
[global] workgroup = HOGE realm = HOGE.EXAMPLE.COM security = ADS obey pam restrictions = Yes algorithmic rid base = 10000 template homedir = /home/%U template shell = /bin/bash # winbind separator = ! winbind cache time = 60 winbind use default domain = Yes winbind nss info = sfu:HOGE winbind refresh tickets = Yes winbind normalize names = Yes winbind enum users = Yes winbind enum groups = Yes idmap uid = 10000-19999 idmap gid = 10000-19999 idmap backend = rid
winbind separator
を指定するとなぜか pam モジュールでの認証がうまくいかない。winbind separator
を指定した場合というか winbind normalize names
と併用した場合の気がする。なので泣く泣く (?) 指定をはずしている。ま,実際には使うことほとんどないし,DOMAIN\user
な指定はメジャー (かつ Windows 側との整合性もとれてる) んで泣く泣くというほどではない。ただ,いざ使うときはシェル等から使うことが多いからエスケープが必要だったりしてダサい。
Active Directory への参加とテスト
さきほど書いたとおり kinit
は必要ないんでいきなり net ads join
する。
$ sudo net ads join -U Administrator Administrator's password: ******** Using short domain name -- HOGE Joined 'PENGUIN' to realm 'hoge.example.com' No DNS domain configured for penguin. Unabled to perform DNS Update. DNS update failed!
以前書いたとおり,手元の環境では DNS をドメインコントローラに任せていないんで,上記のように怒られている。これでも問題なく使える。
きちんと join できてるかテスト((testjoin
は join の試行を行うのではなく,join がされているかどうか調べるコマンド。名前が悪い。))。
$ sudo net ads testjoin Join is OK
できてる。
いよいよ winbind を立ち上げる。
$ sudo service winbind start * Starting the Winbind daemon winbind ...done.
なぜか winbind だけは upstart 形式になってない。ので SysV init 系の service コマンドで起動 (もちろん /etc/init.d/winbind で実行してもいいけど)。
きちんと winbind が働いているか調べる。
$ wbinfo -t checking the trust secret for domain HOGE via RPC calls failed Could not check secret $ sudo wbinfo -t checking the trust secret for domain HOGE via RPC calls succeeded
一瞬焦ったが,wbinfo -t
に限っては root 権限でないと実行できないようだ。
Active Directory のグループ一覧を取得してみる (こちらはユーザ権限で可; ユーザ一覧は -u
オプション)。
$ wbinfo -g netshow administrators dhcp users dhcp administrators ...... (以下略)
nss の設定
winbind が起動しただけでは,Active Directory のユーザ情報は winbind 内で完結しており Linux から使えない。これを Linux のユーザ情報管理システムと結合するのが nss である。
/etc/nsswitch.conf の抜粋:
passwd: compat winbind group: compat winbind shadow: compat winbind
shadow:
んとこはいらない気もするけど一応。RHEL ではコマンドで設定できたけど,Ubuntu だと全部手書きしなくちゃなんない。
ついでに,Windows マシンの名前解決ができるように hosts に wins をいれておく。
hosts: files mdns4_minimal [NOTFOUND=return] dns wins mdns4
うまくユーザ情報を利用できるかテスト。
$ id dayflower uid=11190(dayflower) gid=10513(domain_users) 所属グループ=10513(domain_users),10512(domain_admins)
できてる。もちろん getent passwd とかでたしかめてもよい。
pam の設定
以上で Active Directory 上のユーザのアカウント (情報) が Linux のアカウントと結合された。だが,ログイン時等の認証は pam 経由でおこなっているのでそちらの設定をする必要がある。
設定に必要なファイルはインストールされているので pam-auth-update を実行するだけでよい。
$ sudo pam-auth-update
のだが,ホームディレクトリが存在していないユーザはログイン等できない。これでは不便なので,認証時にホームディレクトリが存在しない場合,自動的にホームディレクトリを作成するようにする。
ホームディレクトリの作成自体は pam_mkhomedir.so を使えばできる。これをさきほどの pam-auth-update コマンドで導入されるよう,下記のようなファイルを作成する。
/usr/share/pam-configs/mkhomedir :
Name: Automatic User Dir Generation Default: no Priority: 192 Session-Type: Additional Session: optional pam_mkhomedir.so skel=/etc/skel/ umask=0022
実は (いつごろからか忘れたけど) pam_winbind.so に mkhomedir
というオプションが用意され,それを使えばわざわざ pam_mkhomedir.so を利用する必要はなくなった。んでもそっちだと umask=
等のオプションはないし,システム提供の /usr/share/pam-configs/winbind を書き換える必要があるので敬遠してる。
pam の認証とは関係ないが,Domain Admins に所属するユーザは sudo できたほうがいいので,そのように設定する。
$ sudo visudo
/etc/sudoers の抜粋:
# Members of the admin group may gain root privileges %admin ALL=(ALL) ALL %domain_admins ALL=(ALL) ALL
winbind normalize names = Yes
という設定にしているので domain_admins
という指定になっている。普通に書くなら domain\ admins
。
あと,このままだと GNOME 等で音がでなかったり USB 機器が使えなかったりする可能性がある*1ので,/etc/group を編集して,ログインするユーザを各グループに適宜所属させている。人数が多くなると面倒 (だし一元管理の趣旨に反する) ので,その場合は /etc/security/group.conf を設定すればうまくできるのかな?ただ現状 (Natty 11.04) だと pam_group は login にしか記述されてないんでうまくいくのか不明。
起動スクリプト (upstart)
以上で Active Directory に登録されているユーザであればこの Linux マシンにログインできる。
そのためには winbind さえあがっていればよい。Windows マシンからこのホスト名 (例では Penguin) を名前解決するためには nmbd もあがっていたほうがよい。SMB 共有を行わないのであれば smbd はあがっている必要はない。
ということで起動時に smbd をあがらないようにしてみる (Ubuntu だと Samba をインストールすると smbd, nmbd が自動的に起動するようになっているので)。smbd, nmbd は upstart 経由でサービスコントロールされているので,下記のファイルをいじる (のだと思う。 upstart にくわしくないのでほんとうにこれでいいかわからない)。
/etc/init/smbd.conf の抜粋:
#start on (local-filesystems and net-device-up)
start on
というところをコメントアウトした。
あとは今現在あがっている smbd をおとす。
$ sudo initctl stop smbd
もちろん sudo stop smbd でもよい。
*1:惰性で /etc/group いじってしまってるんで,もしいじらなかった場合に使えないのかどうか実は知らない。
Java で RSA 暗号を使う
まず OpenSSL コマンドで RSA 秘密鍵を生成する。
$ openssl genrsa -out private_key.pem 1024 Generating RSA private key, 1024 bit long modulus ........................++++++ ...++++++ e is 65537 (0x10001)
末尾にビット数を指定している。OpenSSL のコマンド側だとたとえば 256 とかでも通るんだけど,これを Java のほうにもってくと
java.security.InvalidKeyException: RSA keys must be at least 512 bits long
のように怒られたりする。ただし Sun JRE 付属のプロバイダ (JCE) だとそうなだけで Bouncy Castle をプロバイダに使うと怒られなかった。
このあと openssl rsa
コマンドで DER 形式にすればいいだけかなと思ったらのちのち怒られたので PKCS #8 形式かつ DER 形式に変換する。
openssl pkcs8 -in private_key.pem -topk8 -nocrypt -outform DER -out private_key.pk8
公開鍵はふつうに DER 形式で出力すればいいだけ。
openssl rsa -in private_key.pem -pubout -outform DER -out public_key.der writing RSA key
以上のようにして OpenSSL で生成した鍵を使って Java で暗号化・復号化してみる。
なお,本来公開鍵暗号方式では,平文を公開鍵 (受信者側が公開した鍵) で暗号化し,暗号文を秘密鍵 (受信者側が秘匿している鍵) で復号化する。だが,今回の案件では秘密鍵 (送信者側が秘匿している鍵) で暗号化し,公開鍵 (送信者側が公開した鍵) で復号をおこなった。つまり RSA による暗号化というより半ば署名的に使ったことになる。通信内容の秘匿には使えないが,
- 第3者に暗号文を生成させたくない
- カジュアルに解読されなければいい
などの理由によりこのような仕様とした。
プログラムは以下のとおり。
package com.example.rsajava; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyFactory; import java.security.spec.KeySpec; import java.security.spec.PKCS8EncodedKeySpec; import java.security.spec.X509EncodedKeySpec; import javax.crypto.Cipher; import org.apache.commons.codec.binary.Base64; public class Main { private static final String CIPHER_ALGORITHM = "RSA"; private static final String CIPHER_MODE = CIPHER_ALGORITHM + "/ECB/PKCS1PADDING"; private static final String PRIVATE_KEY_FILE = "private_key.pk8"; private static final String PUBLIC_KEY_FILE = "public_key.der"; public static void main(String[] args) { Main app = new Main(); InputStreamReader input = new InputStreamReader(System.in); BufferedReader reader = new BufferedReader(input, 1); for (;;) { System.out.print("INPUT: "); System.out.flush(); String line; try { line = reader.readLine(); if (line == null) break; } catch (IOException e) { e.printStackTrace(); break; } byte[] encrypted = app.encryptWithPrivateKey(line.getBytes()); System.out.print("ENCRYPTED: "); System.out.println(Base64.encodeBase64String(encrypted)); byte[] decrypted = app.decryptWithPublicKey(encrypted); System.out.print("DECRYPTED: "); System.out.println(new String(decrypted)); System.out.println(); } } private Cipher cipher; private Key secret_key; private Key public_key; public Main() { try { cipher = Cipher.getInstance(CIPHER_MODE); KeyFactory keyfactory = KeyFactory.getInstance(CIPHER_ALGORITHM); KeySpec keyspec; keyspec = new PKCS8EncodedKeySpec(loadBinaryFile(PRIVATE_KEY_FILE)); secret_key = keyfactory.generatePrivate(keyspec); keyspec = new X509EncodedKeySpec(loadBinaryFile(PUBLIC_KEY_FILE)); public_key = keyfactory.generatePublic(keyspec); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } public byte[] encryptWithPrivateKey(byte[] source) { try { cipher.init(Cipher.ENCRYPT_MODE, secret_key); return cipher.doFinal(source); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } public byte[] decryptWithPublicKey(byte[] source) { try { cipher.init(Cipher.DECRYPT_MODE, public_key); return cipher.doFinal(source); } catch (GeneralSecurityException e) { throw new RuntimeException(e); } } private static byte[] loadBinaryFile(String filename) { try { FileInputStream in = new FileInputStream(filename); byte[] data = new byte[in.available()]; in.read(data); in.close(); return data; } catch (IOException e) { e.printStackTrace(); } return new byte[0]; } }
かなり RSA 暗号のコード - BiBoLoG からパクっている。Base64 形式の出力のため Apache Commons Codec を利用している。あと例外処理は適当すぎる。
以下実行例。
INPUT: Hello, World! ENCRYPTED: gswm4jxzVI7QDM+EFqYq+uzniO81FsGfZVYsGtsecD22TPw+AqB33QKE7WzP0+fKiIPcCjyz/eJL Z6bm+mplpY5I7QZkIOD+rtW443YaXULU/DXTf0kb/pPMOSbyHMLF5hWZJbzJSq7iGOFuyWqxZ9DB JvlpobhKF9DLZGJ3JJ0= DECRYPTED: Hello, World! INPUT: Hello, World! ENCRYPTED: gswm4jxzVI7QDM+EFqYq+uzniO81FsGfZVYsGtsecD22TPw+AqB33QKE7WzP0+fKiIPcCjyz/eJL Z6bm+mplpY5I7QZkIOD+rtW443YaXULU/DXTf0kb/pPMOSbyHMLF5hWZJbzJSq7iGOFuyWqxZ9DB JvlpobhKF9DLZGJ3JJ0= DECRYPTED: Hello, World! INPUT: hello, World! ENCRYPTED: NR7vRLEkRHLpUwFzmgS2lf8FwkpUiX0HGnv6rCgx9wOc2SFupXVog3+7AJmGZlq7RA24LrRKCjsS awIwtiyPaPDVtT+MYgmb5DWcV9fivewygvw5Ct6qRmvjUMl26+uglYkQ0yF6g250rp8NOJa1k5l/ 0NBx4e7ls5Q2C7drHjw= DECRYPTED: hello, World!
(少なくとも Java での) RSA 暗号化には初期ベクタがないので,同じ文から同じ暗号文が生成されている (1 つめの例と 2 つめの例)。しかし 3 つめの例から少なくとも先頭の文字が違っていれば暗号文の全体 (というかブロック長) が変化するので,自分で初期化ベクタを用意してやればカジュアルに傍受している側をまどわせることができると思われる。
ほか注意点
KVM で PXE ブートしようとしたら難儀した
KVM の場合,virt-install
コマンドに --pxe
オプションをつければ PXE ブートさせることができる。Xen と違って完全仮想化だから。ばんざい。
# virt-install --name=hogehoge --accelerate --os-variant=rhel5.4 \ --ram=384 --vcpus=1 \ --file=hogehoge.img \ --network=bridge:br0 --pxe \ --vnc --noautoconsole
あるいは qemu-kvm コマンドを直接叩いて network option rom を指定するという方法もあるようだ (参考, qemu-kvm の直接起動は Stray Penguin - Linux Memo (KVM-2) 参照)。
これで普通に DHCP サーバと TFTP サーバを構成していればうまく PXE ブートできるはずなのだが,手元の環境では以前 Proxy DHCP サーバで PXE ブート - daily dayflower で書いたように Proxy DHCP を使っている。
この Proxy DHCP 環境下で KVM を PXE ブートさせようとしたらなかなかうまくいかなかった。
結論からいうと,KVM (Qemu) で利用しているネットワークコントローラ用の ROM (Etherboot) にバグがあった。
ちなみに Etherboot 由来のネットワークブート ROM は Oracle VirtualBox でも使っている。ので同様に Proxy DHCP 環境ではうまく PXE できない (かった)。
原因 1: Proxy DHCPOFFER の NBP file name に対応していない
Proxy DHCP サーバで PXE ブート - daily dayflower でも書いたように,PXE_DISCOVERY_CONTROL Option の bit 3 を立てると DHCPOFFER の段階でブートファイル名を返すことができる。
ところが,Etherboot 由来の PXE BIOS はこのオプションを無視する。そして UDP 4011 宛に DHCPREQUEST を投げてくる。
しかたないので pxe-pdhcp を改造して 68 番と 4011 番両ポートで待ち受けするように変更した。
原因 2: Etherboot の ProxyDHCP 時の DHCPREQUEST パケットがおかしい
これで一件落着かと思ったら,DHCPREQUEST のベンダオプション項がうまくパースできない。仕様の読み漏れかと思って Etherboot のサイトからソースを落として読んだら,バグがあった。ベンダオプション項の最後に End of Options (FFh) をつけ忘れている。
Etherboot 4.4.2 でのパッチは以下のとおり。
diff -r e53c2fc7ea6a src/core/nic.c --- a/src/core/nic.c Wed Feb 16 12:19:14 2011 +0900 +++ b/src/core/nic.c Wed Feb 16 14:07:59 2011 +0900 @@ -1166,6 +1166,7 @@ /* Construct the ProxyDHCPREQUEST packet */ memcpy(ip.bp.bp_vend, rfc1533_cookie, sizeof rfc1533_cookie); memcpy(ip.bp.bp_vend + sizeof rfc1533_cookie, proxydhcprequest, sizeof proxydhcprequest); + ip.bp.bp_vend[sizeof rfc1533_cookie + sizeof proxydhcprequest] = RFC1533_END; for (reqretry = 0; reqretry < MAX_BOOTP_RETRIES; ) { printf ( "\nSending ProxyDHCP request to %@...", arptable[ARP_PROXYDHCP].ipaddr.s_addr); udp_transmit(arptable[ARP_PROXYDHCP].ipaddr.s_addr, BOOTP_CLIENT, PROXYDHCP_SERVER,
報告しようと思ったらバグトラッカも落ちてるしレポジトリもみつからない。どうもサイトが故障していたようだ。一部サービス (ソース提供や rom-o-matic での NIC ROM 自動生成) はすでに復旧している。
一応上記のパッチを適用してビルドした ROM ではうまく PXE boot できた((NIC ROM の置き換えは,/usr/share/kvm
以下の pxe-virtio.bin
などを置き換える。もともとは /usr/share/qemu-pxe-roms/
以下の ROM ファイルへのシンボリックリンク。))。だが,前述したように,VirtualBox でも問題のある Etherboot 由来の NIC ROM を使っており,こちらをどのように差し替えるかがわからない。なので,pxe-pdhcp 側で workaround をおこなった。
ほんとは workaround のソースを示せればいいんだけど,諸般の事情*1でまだだせない。あくまで Etherboot のバグ対策だけだが,
- DHCP Options の Class Identifier が 32 バイト続いたあとで,(本来
FFh
があるべきだが) 「:UNDI:〜
」とゴミが続いてしまっている - よって
pdhcp.c
のcheck_dhcp_packet()
において DHCP Option が3Ah
,55h
の並びになった場合,そこを終端とみなすようにする
という対策をおこなったところ,CentOS 5.5 の KVM guest および VirtualBox 4.0.2 において PXE ブートでできるようになった。
余談
Etherboot プロジェクトは実質 gPXE プロジェクトが後継となっているようだ。こちらのソースは一から書きおこしている部分が多く,上記のようなバグはない (なさそうだった)。
だが gPXE の NIC ROM を生成して KVM で使ってみたところ,なぜか主 DHCP サーバ宛に TFTP 要求をだしていた。ソース上は ProxyDHCP 用のコードもあるようだがまだきちんとインプリメントされていないのかもしれない。
なお,Etherboot では上記のパッチを適用して自分で ROM をビルドしたが,gPXE の NIC ROM は http://rom-o-matic.net/gpxe/gpxe-1.0.1/contrib/rom-o-matic/ で NIC ROM を生成した。いろいろ条件を指定して NIC ROM を生成できるのでおもしろいサイトである。
ちなみに,Etherboot (と gPXE) では,条件を指定することで TFTP だけでなく,HTTP 経由でイメージをとってこれる NIC ROM も作成できるようだ。また,gPXE のほうは iSCSI による SAN Boot にも対応しているらしい (id:adsaria さんによる実践例)。
Debian 6.0 Live イメージを PXE 経由でブート
さいきんの Debian は Live 起動用イメージも用意しているらしい。
んで,Live 起動イメージはネットワークブート用のイメージも用意されている。
たとえば,http://ftp.jaist.ac.jp/pub/Linux/Debian-CD/6.0.0-live/i386/net/ をみるといくつかの種類 (GNOME 環境とか Rescue 環境とか) のネットブート用 Live イメージ (*.tar.gz) がおいてあることがわかる。
そこで,CentOS で作った PXE ブート環境に,選択肢のひとつとして Debian 6.0 Live を追加してみた。index.rb [nofuture.tv] が参考になる。
なお PXE 環境自体の構築については今回触れない。
ライブイメージディストリビューションのなかから debian-live-6.0.0-i386-standard.tar.gz を選択してみた。standard といいつつほとんど何も入ってないプレーンな状態だけど。
取得して展開すると,だいたい下記のようなディレクトリツリーになってる。
+ debian-live/ + tftpboot/ + tftpboot/debian-live/ + tftpboot/debian-install/
おおまかにいうと,tftpboot/
フォルダ以下が TFTP による初期ブートに必要なファイル群 (カーネルとか initramfs とか)。debian-live/
は initramfs が起動したあとに NFS 経由で読み込むルートイメージ。NFS 経由といっても生ルートツリーを NFS マウントするわけじゃなくてルートイメージが SquashFS 形式でおいてあるんでそれを NFS 経由で読み込んでマウントする感じ。
なお tftpboot/debian-install/
のほうは,Debian をネットワークインストールする際に使う TFTP ブートイメージで,今回は使用しなかった。
NFS 用ルートイメージの用意
前述のとおり debian-live/
以下が NFS 経由のルートイメージになるので適当な場所において NFS で公開する。
# cp -R debian-live /pub/linux/debian/squeeze
別にディレクトリはこの名前にする必要があるわけじゃない。自分の環境では上記のようにしているだけ。
上記例だと exports
はこんな感じ。
# cat /etc/exports /pub/linux/debian *(ro,all_squash,no_subtree_check,crossmnt) /pub/linux/debian/squeeze *(ro,all_squash,no_subtree_check,crossmnt)
んで NFS デーモンを再起動。
# service nfs restart
PXE用ファイルの用意
こんどは TFTP ブート用ファイルを /tftpboot/
以下の適当なディレクトリにコピー。
# cp -R tftpboot/debian-live /tftpboot/debian-squeeze
んで,Debian のドキュメントとかだと付属する pxelinux.cfg
をそのまま使うような形で紹介されてるんだけど,そうするといままで使用していた他の PXE ブート環境がなくなってしまってもったいないので要所だけコピーする。
Debian Live の場合,たとえば debian-live/i386/boot-screens/live.cfg
に PXE ブート用の設定が書いてある。
label live menu label Live kernel debian-live/i386/vmlinuz-2.6.32-5-486 append initrd=debian-live/i386/initrd.img-2.6.32-5-486 boot=live config netboot=nfs nfsroot=192.168.1.1:/srv/debian-live quiet
改行いれちゃってるけど,append 行のとこは1行で書く必要があると思う (以下も同様)。これをもとに,自分の pxelinux.cfg
に書き足す。
今回の例示だと以下のような内容。/tftpboot/
の下とか NFS 用ディレクトリに好き勝手な名前をつけたので,対応する部分を変更してある。
LABEL squeezelive MENU LABEL Debian 6.0 Live KERNEL debian-squeeze/i386/vmlinuz-2.6.32-5-486 APPEND initrd=debian-squeeze/i386/initrd.img-2.6.32-5-486 boot=live config netboot=nfs nfsroot=<NFS Server Address>:/pub/linux/debian/squeeze quiet
以上で無事 Debian 6.0 Squeeze の Live 環境が立ち上がった。あまりにプレーンなのでほんとにうまく動いてるか自信がないので今 GNOME 版の Live イメージをダウンロード中*1。
なお,Ubuntu でも同じようにすれば Live 環境の PXE 起動はできる。つっても違うところはいろいろあって
- TFTP ブート用のファイルは netboot.tar.gz というのをとってくる
- NFS 用ルートイメージは,Ubuntu インストール DVD をマウントしたものを NFS エクスポートすればそのまま使える
pxelinux.cfg
まわりは preseed とか casper まわりとかをAPPEND
行でいろいろ指定する必要がある
いろいろたいへんそうだけど,たいていの人は Ubuntu のライブイメージを手元に持ってるだろうから,ハードルは Debian より高くない。
Ubuntu の場合の pxelinux.cfg
例を下記においとく。
LABEL mavericklive MENU LABEL Ubuntu 10.10 Live KERNEL ubuntu-maverick/i386/casper/vmlinuz APPEND initrd=ubuntu-maverick/i386/casper/initrd.lz file=ubuntu-maverick/i386/preseed/ubuntu.seed boot=casper netboot=nfs nfsroot=<NFS Server Address>:/pub/linux/ubuntu/maverick quiet splash --
TFTP 用ファイルは /tftpboot/ubuntu-maverick/
においており,NFS 用ルートイメージが /pub/linux/ubuntu/maverick/
においてあるという前提だけど。当然ながら KERNEL 行や APPEND 行のファイルパスは適宜書き換えること。