2010年11月26日

Blogger で新しいはてなブックマークボタンを設置

はてなブックマークボタンが使いやすくなったらしいので、Blogger の各記事の下に加えるようにしてみた。

Blogger では記事の側にボタンを設置しようと思うと HTML 編集モードに入らないといけないっぽいのだが、どうも記事ひとつひとつに対して調節しようと思うと「ウィジェットのテンプレートを展開」を使って展開されたソースに対して書き換えを行う必要があるらしい。
ただこれを一度展開してしまうとテンプレが巨大になって見通しが悪くなるし「ここから先は自力で頑張って下さい」ムードが漂っていてせっかくのインターフェイスが使えなくなりそうで怖いので(※実際にどうなるのかは未確認)、JavaScript 経由で挿入する方法でお茶を濁すことにした。

このブログには既にシンタックスハイライト用の JavaScript を仕込んでいて、その時に jQuery も読み込んでいたので jQuery を使った。実際に必要なコードはこんな感じ。
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.4/jquery.min.js"></script>
<script type="text/javascript">
//<![CDATA[
$(function(){
  $('h3.post-title.entry-title').each(function(){
    var $this = $(this), title = $this.text(), url = $this.find('a:first').attr('href') || $('head:first link[rel=canonical]:first').attr('href'), encoded_url = encodeURIComponent(url);
    $this.closest('div.post.hentry').find('div.post-footer-line.post-footer-line-1')
      .prepend('<a href="http://b.hatena.ne.jp/entry/'+encoded_url+'" class="hatena-bookmark-button" data-hatena-bookmark-layout="standard" title="このエントリーをはてなブックマークに追加"><img src="http://b.st-hatena.com/images/entry-button/button-only.gif" alt="このエントリーをはてなブックマークに追加" width="20" height="20" style="border: none;" /></a><script type="text/javascript" src="http://b.st-hatena.com/js/bookmark_button.js" charset="utf-8" async="async"></scr'+'ipt>')
      ;
  })
});
//]]>
</script>
※記事個別ページで上手く出ていなかったのでURL取得処理を変更

最初に h3 拾ってきてるのでそんなに重くないと思いたい。
ただ、このままだと微妙にデザインが崩れた感じになってしまったので以下のCSSも追加。
a.hatena-bookmark-button, iframe.hatena-bookmark-button-frame {
  vertical-align: middle;
}
出来上がったのがこんな感じ。↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
Clip to Evernote

Debian に Go 言語をインストール

好き放題入れられるんだから興味がある golang も使えるようにしたい。
ただ VPS 上でコンパイルなどができる必要はないので、ローカルの Debian サーバにだけコンパイル環境を用意して、出来上がったバイナリを ServersMan@VPS で動かすような使い方をしようと思う。
以前はビルド前の環境変数の設定が必須だったが、いつの間にか簡単にできるようになっていた。
su
apt-get install bison ed gawk gcc libc6-dev make python-setuptools python-dev build-essential
easy_install mercurial
exit
hg clone -r release https://go.googlecode.com/hg/ go
cd go/src
./all.bash
ビルドが上手くいったので、.bash_profile に環境変数を設定しておく。 今回はまだ .bash_profile 自体が存在していなかったので新たに作成した。
vim ~/.bash_profile
if [ -f ~/.bashrc ]; then
     . ~/.bashrc
fi

GOROOT=~/go
PATH=$PATH:$GOROOT/bin
export GOROOT PATH
source .bash_profile
プログラムのビルドをテストするのはオフィシャルの Writing programs を参照すればいいと思うので省略。
これで自前のバイナリを走らせることもできるようになった。
Clip to Evernote

2010年11月25日

golang の netchan でデータが欠落することがある?

Go言語を最近触っていてちょっとした経緯で netchan を使おうと思ったのだが、どうすればいいのかよくわからなくなってきたので取り敢えずメモっておく。このコードは上手くいく。1秒ごとに画面にログが現れる。
package main

import (
  "log"
  "time"
  "netchan"
  "os/signal"
)

func exporter() {
  exp, _ := netchan.NewExporter("tcp","127.0.0.1:65432")
  expSendChan := make(chan int)
  exp.Export("chan", expSendChan, netchan.Send)
  go func(){
    for i:=1;;i++ {
      time.Sleep(1e9)
      expSendChan <- i
    }
  }()
}

func importer() {
  imp, _ := netchan.NewImporter("tcp","127.0.0.1:65432")
  impRecvChan := make(chan int)
  imp.Import("chan", impRecvChan, netchan.Recv)
  go func(){
    for {
      log.Printf("%d", <-impRecvChan)
    }
  }()
}

func main() {
  exporter()
  importer()
  <-signal.Incoming
}
ただ、これが Importer と Exporter を別のプロセスを分けた時などに起こるような複数回 Importer が接続に行くような状況で上手くデータが転送されないみたいだ。
このソースの main() にある exporter() と importer() の呼び出しのうち片方をコメントアウトし、一方だけを呼ぶバイナリを2つ作って、Exporter 側を立ち上げっぱなしの状態で Importer を立ち上げたり終了させたりすると、Importer が切断されたあとに送られたデータのひとつ目だけが抜け落ちてしまう。
Importer が終了したタイミングを拾って
close(expSendChan)
exp.Hangup("chan")
expSendChan = make(chan int)
exp.Export("chan", expSendChan, netchan.Send)
みたいな感じで仕切りなおしすると問題なく送信できるようにはなるのだが、Importer が接続しに来ていることを検知する方法自体が用意されていないし、内部の処理さえ上手く作れば起こらなくなる類の問題なのでユーザ側で何かやるようなコードは一切書かずにライブラリ側の修正で解決するのが望ましそう。
少なくとも送信したデータが欠落しているので普通に考えて netchan のバグだろうとは思う。

netchan が使えると恐らく色々な処理がプロセスを跨いでいながら自然に書けるようになると思うので、もうちょっと詳しく追ってみようと思う。golang 触り始めたばかりの小童にどこまでわかるのやら。

---- 2010-11-26 04:15 追記

処理を追っていると、ある程度流れは見えてきたと思う。
エラーが起きているのは netchan の common.go 内にある (*encDec) encode 関数なのだが、ここに「TODO: tear down connection if there is an error?」というコメントアウトがあった。
そもそもこの辺の処理自体があまり作り込みされていないのかな? と最初は思った。
ところがここでエラー処理をしようにも、そもそもこれを呼んでいる export.go の (*expClient) serveRecv 関数にあるループの頭でやっている val := ech.ch.Recv() の時点でデータを読み取ってしまっているのでエラー発生後 break したら当然この分のデータは欠落する。
欠落する原因自体はわかってきたが解決の難しさも見えてきた。

chan と同じような感じで使えるのが netchan のいいところだと思う。
そのためにはチャネルに送信されたデータの順番を無視するような形で再度挿入するような処理でエラーをリカバリーするのは気持ち悪い。
かといって複数のクライアントが同時に接続に来た場合の処理を考えると、送信が終わるまで待機する構造もパフォーマンス上の問題があるので用途によっては微妙そう。

色々考えているうちに、そもそもサーバ対クライアントで1対多の関係を想定した場合、サーバからクライアントに向けて順番が重要なデータを送信しようとしていること自体が結構コスト高めな処理だよなーという気がしてきた。
そうなると、そもそもサーバで Exporter から netchan.Send を作るんじゃなくて、クライアントで Exporter の netchan.Recv を作るようにした方がいいんじゃないかっていう何も解決しないとてもつまらない話にたどり着く。

依然として「1対1でプロセス間通信したいだけ」みたいな状況においてはサーバで Exporter、クライアントで Importer の構成であるべきだろうと思うしわりと簡単に上手くいきそうな感じがするので上手く読めるよう対策をしてもよさそうな気はする。
そのぐらいなら自分にでも書けそうだが、取りあえず今日は時間も遅いのでこの辺にしておこう。
Clip to Evernote

2010年11月18日

Debian に HAProxy をインストール

HAProxy はロードバランサで、複数の Web サーバに処理を振り分けるために使われたりするもの。
このマシンで Web サーバとして使い始めた nginx も同じ用途で使えるので、通常あれば HAProxy を入れなくても大体同じことができる。

が、密かに企んでいる将来の目的にはどうも nginx が使えないらしい。目的の詳細についてはまた別な記事で語ることにして、今回は取りあえず HAProxy を経由して nginx に接続させるところまでやってみる。
apt-get install haproxy
インストールができたら、/etc/default/haproxy の ENABLED を 1 に変更する。
vim /etc/default/haproxy
#ENABLED=0
ENABLED=1
更に haproxy の設定ファイルを書き換える。
最初の時点でなんかわりと色々と書いてあるのだが、今回は真面目にロードバランサとして使うわけではないので取りあえずファイルをバックアップしつつ全面的に書きなおす方向で進める。
cd /etc/haproxy
cp haproxy.cfg haproxy.cfg.default
vim haproxy.cfg
global
  user  haproxy
  group haproxy
  daemon

defaults
  mode    http
  retries 3
  option  redispatch
  option  forwardfor except 127.0.0.1/8
  option  SERVERID rewrite

backend web
  timeout client  30s
  timeout server  30s
  timeout connect 1s
  balance roundrobin
  server  web1 127.0.0.1:60080
  rspirep ^(Location:\ [a-z]+://[-.a-z0-9]+)(:[0-9]+)(.*)$ \1\3

frontend all 0.0.0.0:80
  default_backend web
※2010-12-01 追記 - Locationヘッダの書き換えを追加

HAProxy がポート 80 番を使いたいので、nginx 側を 60080 に切り替えることにした。
/usr/local/nginx/nginx.conf も編集する。
vim /usr/local/nginx/nginx.conf
#        listen       80;
        listen       127.0.0.1:60080;
現在は nginx が動作中、haproxy は停止中なのでそれぞれ reload, start させる。
invoke-rc.d nginx reload && invoke-rc.d haproxy start
ブラウザからアクセスしてみると上手くいっているようなので、haproxy を daemontools 経由で起動させるようにする。
update-rc.d -f haproxy remove
cd /etc/service
mkdir haproxy
cd haproxy
vim run
#!/bin/sh
exec 2>&1
exec /usr/sbin/haproxy -f /etc/haproxy/haproxy.cfg -db -q -p /var/run/haproxy.pid
停止して改めて起動させてみる。
chmod 700 run
invoke-rc.d haproxy stop && svc -u /etc/service/haproxy
立ち上がっているようなので成功した模様。確認のために一応 netstat -lnp もしておく。
netstat -lnp
タイムアウトの時間の設定の適切なところがよくわからんのでその辺は状況次第で考えないとダメそうかなあ。

今後は nginx には HAProxy が接続しにいくようになるので、当然ながら REMOTE_ADDR を参照しようとすると必ず 127.0.0.1 になってしまう。
この辺は恐らく /usr/local/nginx/fastcgi.conf を編集すれば REMOTE_ADDR を HTTP_X_FORWARDED_FOR の値に書き換えることもできそうな気配だけど、取りあえず保留にしておく。

2010-12-01 追記 --- 現時点で把握している問題点

  • pid ファイルが作成されない
    どうやら HAProxy は daemon 起動でなければ pid ファイルを作成しないらしい。
    普段は困ることはないものの、invoke-rc.d haproxy reload などが使えない。
    この辺見直した方がよさそうだ。
Clip to Evernote

2010年11月15日

Debian に Zend Framework をインストール

現在動いているメールフォームは PHP 標準機能とか mb_string ぐらいしか使っていなくて、わりと持ち運びしやすいスクリプトではあるのだが、ServersMan@VPS ではメールサーバを立てるつもりがないので mail 関数がそもそも使えなくなる。
外部の SMTP サーバを使ってメールを送信するには PEAR::Mail を使う手もあるが、今回は Zend Framework に存在する Zend_Mail を使ってその辺を作ってみようと思う。
なので Zend Framework 自体をインストールする。
cd ~
wget http://framework.zend.com/releases/ZendFramework-1.11.0/ZendFramework-1.11.0-minimal.tar.gz
tar -zxf ZendFramework-1.11.0-minimal.tar.gz
cd ZendFramework-1.11.0-minimal
bin と library ディレクトリをインストール先に配置したい。まずは library を取りあえず他のPHPのファイルもある /usr/share/php を真似て /usr/local/share/php に一緒に突っ込んでしまうか。
mv library /usr/local/share/php
あとは bin だけどこれも慣例的には /usr/local/bin でいいのかな? これは中身を入れる。
zf.bat は Windows 用のファイルで必要ないので消してしまおう。
あとマニュアル上では zf ってコマンドで呼ぼうとしてるので合わせるようにしてみる。
cd bin
rm zf.bat
mv zf.sh zf
chmod 755 zf
mv * /usr/local/bin
php.ini に /usr/local/share/php を見に行くようにさせる。php5-fpm と cli の両方を設定しないとダメそう。
あとスクリプトを書き始めてから php.ini で date.timezone と mbstring.language と mbstring.internal_encoding が未設定なことに気づいた。この設定もこの辺でしておくとよさげ。
vim /etc/php5/fpm/php.ini
;include_path = ".:/usr/share/php"
include_path = ".:/usr/share/php:/usr/local/share/php"
date.timezone = Asia/Tokyo
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8
ファイルのアップロードサイズも大きめのファイルを添付できるように少し引き上げる。ここの設定を書き換えると全体に適用されてしまうのであまりよくなさそうな気がする。
post_max_size = 25M
upload_max_filesize = 20M

cli側も同じように。アップロード周りは当然意味がないので特になにもしない。
vim /etc/php5/cli/php.ini
;include_path = ".:/usr/share/php"
include_path = ".:/usr/share/php:/usr/local/share/php"
date.timezone = Asia/Tokyo
mbstring.language = Japanese
mbstring.internal_encoding = UTF-8

設定が終わったので反映させる。
invoke-rc.d php5-fpm reload
これで Zend Framework のプロジェクトをコマンドラインから簡単に新規作成できるようになった。
が、今回は本当に Zend_Mail を使いたいだけで全部を Zend で作るつもりはなかったりするのでパスする。

実際にスクリプトを書くのは思ったよりも全然簡単に済ませることができた。
昔書いたメールフォームのスクリプトがちゃんとメール送信周りを綺麗に切り分けてあったおかげで、その周辺を Zend_Mail に切り替える以外の作業はしなくて済んだ。

sendmail に丸投げしていたものに比べると処理速度が若干遅くなってしまったのが残念だが、これを解決しようと思うとバックグラウンドジョブに投げるか、裏でサーバを立ててメールフォームのスクリプトからキューにデータを追加するようなものを書かなければいけなくなる。
そんなに死ぬほどいっぱい使われるフォームでもないのでその辺はサボることにしたら、メールフォームの移植作業終わり。
Clip to Evernote

2010年11月14日

自宅サーバを Ubuntu から Debian に変更

ServersMan@VPS で Debian を使い始めたので、自宅にあるサーバ用 Atom マシンを Ubuntu から Debian に切り替えた。
この PC は gPXE が使えるので、ネットワーク経由で小さいイメージを http ダウンロードして直接インストールできる。

基本的には ServersMan@VPS と同じような構成だけど、相変わらず自宅は固定 IP ではないので IP が変化するたびに DynamicDNS を更新する必要がある。
この更新は Ubuntu の時にも使っていた Python スクリプトがあるのでこれをそのまま持ち込んだ。
ただ、もちろん初期の状態では Python も入っていないので apt-get でインストールする。
apt-get install python
これでインストールされる Python は 2.5 で若干古いものの、世の中にある Python 用のライブラリなどを調べているとどうも 2.5 が一番無難そうな気配があるのでこのままにしておく。
Python も準備できたところでスクリプトを配置する。
mkdir /usr/local/share/diceoov/
vim /usr/local/share/diceoov/diceoov.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import urllib2, re, os

IPCHECKER = r"http://global-ip.appspot.com/"
DDNS_UPDATER = r"[DDNS更新用のリクエスト先URL]"

def main(OLD_IP):
  #現在のIPアドレスを取得する
  now_ip = urllib2.urlopen(IPCHECKER).read().strip()

  #正しいIPアドレス表現で、前回からIPアドレスは変わっているか?
  if re.match(r"\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}", now_ip) and now_ip != OLD_IP:
    #IPアドレスの更新リクエスト
    a = urllib2.urlopen(DDNS_UPDATER).read()
    #自分自身を読み込み
    lines = open(__file__, "rb").readlines()
    #一番最後のmain呼び出しコードの第一引数をIPアドレスに書き換える
    lines[len(lines)-1] = '  main("%s")' % now_ip
    #自分自身を書き換え
    open(__file__, "wb").writelines(lines)

if __name__ == "__main__":
  main("xxx.xxx.xxx.xxx")
スクリプト中に出てくる http://global-ip.appspot.com/ は Google App Engine 上に置いてある自作のアプリケーションで、IP 返す以外の機能を一切実装していない。似たような機能を持つスクリプトは世の中に腐るほどあるのだが、知らぬ間に止まったりするとわりと困るはずなので自分で用意した(自分で用意したものが止まるならまあしょうがない、という話で、このアプリケーションが確実に無停止だという話ではない)。

このスクリプトがIPの変化を察知すると DynamicDNS 更新用の URL へリクエストをかけて、その後自分自身のスクリプトファイルの最終行を書き換えて xxx.xxx.xxx.xxx の部分が実際のIPアドレスになるようにする(スクリプトが手抜きなせいで空行とかが入ってると書き換えに失敗するのでそこは注意)。
あとはこのスクリプトを cron で定期的に動かしてあげればいいだけ。IP が変わらない限り DynamicDNS の更新用リクエストは投げないので、Google App Engine のリクエスト数制限を超過しない程度なら頻繁に呼んでいても問題ない。
crontab -e
MAILTO=""
0,15,30,45 * * * * python /usr/local/share/diceoov/diceoov.py
Clip to Evernote

2010年11月12日

Debian に Kyoto Tycoon、Net_KyotoTycoon をインストール

前回の記事でカウンタを SQLite3 に移行することに成功したのだが、なんか妙にパフォーマンスが安定しない。何度か F5 で読み込み直すと 100msec ちょっとぐらいで返ってきたり(それでも遅いけど)、500msec 以上掛かったり。
もしかして SQLite2 だと問題ないのかなと思い結局 php-sqlite を入れたりして実験してみると、確かに SQLite2 のコードの方が安定して 100msec を切っていた(それでも同時アクセスが集中すると簡単に遅くなっていく)。

アクセスが少ないんだからそれでいいじゃんという話もあるが、せっかく好き放題できるサーバなのだから好き放題やってみようと思い、Kyoto Tycoon のサーバを立てて PHP からアクセスさせてみることにした。こんなアクセスの少ないサイトに対してこんな豪華なものが使えるなんて幸せだ。

mikio さんのチュートリアルを参考にインストールする。とは言っても王道の流れで入れられるので特に壁になるようなことはない。
apt-get install g++
wget http://fallabs.com/kyotocabinet/pkg/kyotocabinet-1.2.24.tar.gz
tar -zxf kyotocabinet-1.2.24.tar.gz
pushd kyotocabinet-1.2.24
./configure
make
make install
popd
wget http://fallabs.com/kyototycoon/pkg/kyototycoon-0.9.7.tar.gz
tar -zxf kyototycoon-0.9.7.tar.gz
pushd kyototycoon-0.9.7
./configure
make
make install
チュートリアルのように ktserver を起動して、ktremovemgr から set をしようとすると接続に失敗した。これは恐らく iptables によるものだと思うので、ループバックインターフェイスを正しく経由するように明示的に -h localhost を加えることで上手く動いた。
問題なく動いているようなので、PHP からアクセスできるように Net_KyotoTycoon をインストールする。これは pear 経由でインストールする必要がある。こちらを参考にしながら進める。
apt-get install php-pear
pear channel-discover openpear.org
pear install HTTP_Request2-alpha
pear install openpear/Net_KyotoTycoon-beta
インストールもできたので実際にスクリプトを書いてみると安定して 30msec でレスポンスが返ってくるようになった。素晴らしい。

実際に運用させるにあたり、取りあえずデータベースファイル置き場を準備する。
cd /usr/local
mkdir ktserver
cd ktserver
mkdir db
chown www-data db
chgrp www-data db
続いていつものように daemontools 経由で起動させるために run スクリプトを設置する。
そのままだと root 権限で動いてしまうので setuidgid で www-data に切り替えて走らせる。
cd /etc/service
mkdir ktserver
vim run
#!/bin/sh
exec 2>&1
exec setuidgid www-data ktserver -host 127.0.0.1 -le /usr/local/ktserver/db/counter.kch
chmod 700 run
これといったチューニングはしていないけどそこまでシビアな要求に答えるわけではないのでよしとしよう。
Clip to Evernote

2010年11月10日

ServersMan@VPS の Entry を契約した

無事にカードの再発行も済んだので、予定通り ServersMan@VPS の Entry プランに申し込んだ。
取りあえず使う予定のない ServersMan 提供ツール群は必要ないので Apache2 を消した。
その他 smadmin、serversman も必要ないので削除。

ServersMan@VPS の Debian はどうも locale が指定されていない状態になっているのか PuTTY で UTF-8 に設定した状態で日本語を打とうとしたら打てないことに気づいた。その辺の設定はここの記述を参考にして en_US.UTF-8 に設定した。
dpkg-reconfigure locales
update-locale LANG=en_US.UTF-8
そして今まで書いてきたのとほぼ同じ手順でサーバを準備して、早速サイトの移植を開始。
元々のサーバである XREA.COMCORESERVER.JP の時に入っていたモジュールが一部入っていなかったりするので、その辺は随時インストールしながら作業していくことにする。

アクセスカウンタ
ほとんど見えないぐらい小さいサイズでさり気なく存在しているアクセスカウンタは、簡単な自作スクリプト。
データは SQLite2 のデータベースで扱っていて、文字の出力は GD 経由で xbm 形式の画像を読み込んで、png 画像として出力している。
ここで使っている SQLite2 と GD はどちらも入っていない。とはいえ SQLite2 をわざわざ入れるよりは SQLite3 を使ったコードに直すべきだと思うので、そこはコード側を修正する方針で進める(直す対象のコードも少ない)。インストールついでに APC も入れておく。高速化バンザイ。
SQLite2 と SQLite3 のデータベースファイルは互換性がないのでコンバート作業が必要。
apt-get install php5-gd php5-apc sqlite sqlite3
invoke-rc.d php5-fpm reload
sqlite [カウンタのデータベースファイル名] .dump | sqlite3 [新しいデータベースファイル名]
とても単純な構造だったので特に問題なく変換できた(もちろん本当は生成されるSQLデータをよく確認すべき。更にこのままだとroot権限のファイルが生成されてしまうので chown と chgrp しないといけない)。
最初 Windows 側で変換してしまおうと思って SQLite のサイトを見に行ったらもう既に 2.x 系のコンパイル済みバイナリがなさそうだったので、Debian 側で作業することにした。
取りあえずこれで多分自作物の中で SQLite2 に依存しているものはなくなったはずなので、もう使うこともないであろう sqlite のパッケージは消しておく(sqlite3 は多分使うこともあるので残す)。
apt-get remove sqlite
これでカウンタの準備は完了。

ナポレオンのランダムメッセージ
一応このメッセージはサーバサイドで返すのでネタが全部ばれないようにはしてある(誰もそこまで見ないが)。
しかし実はこれでかい switch 文の中でひたすらメッセージを return するようなつくりになっていて、仕組みとしては非常に頭が悪い。だがおかげで何もしなくても普通に上手く表示される。よかった。ローテクバンザイ。

次回はメールフォームの移植作業をしたい。
とはいえここはほとんど最初から作り直しになりそうなので多分次回の記事はちょっと先になるはず。
Clip to Evernote

2010年11月9日

Debian に daemontools をインストール

実は以前 Windows でも nginx + PHP の環境を作って PHP スクリプトを書いていたこともあったのだが、その時「いつの間にかプロセスが居なくなっている」というミステリーに遭遇した。
Windows 版がそんなに安定動作するとは思えないのでそれ自体は不思議ではないのだが、やはりサーバとして使うのであれば勝手に nginx とか PHP が居なくなってサイトが見れなくなっているのは寂しい(とはいえアクセスが少ないので実質的な問題はほとんどないが……)。

ServerMan@VPS の Entry プランはメモリも少なめで swap 領域も現状はないので、OOM Killer 様がやってきて殺戮していく可能性もあるし、でも自分が書いたスクリプトのバグなどで大量にメモリ食い潰したりした時は落として欲しいので OOM Killer 様も必要。
というわけで落ちた後に何食わぬ顔で立ち上がってくるようにしておきたいので、今回は daemontools をインストールしてみようと思う。

daemontools は apt-get でインストール。
apt-get install daemontools daemontools-run
基本的にはこれだけで daemontools は起動する。
daemontools でプロセスを監視させる場合、daemontools 側からサービス起動をしなければいけないようなので、nginx と php5-fpm を自動実行の対象から外しておく。sshd は OOM Killer の対象外だし失敗すると作業続行が出来なくなる可能性があるので取りあえず今回は除外。 まずは nginx から。
invoke-rc.d nginx stop
update-rc.d -f nginx remove
pushd /etc/service
mkdir nginx
pushd nginx
vim run
#!/bin/sh
exec 2>&1
exec /usr/local/sbin/nginx -c /usr/local/nginx/nginx.conf -g 'daemon off;'
chmod 700 run
svc -u /etc/service/nginx
これで daemontools 経由で起動することができたので、
ps ax | grep nginx\\:
に現れたプロセスIDを全てkillしても違うプロセスIDで何度でも蘇るようならラピュタの完成。

続いて php5-fpm。
こちらは foreground で動かすためには conf を触らないといけないらしい。
vim /etc/php5/fpm/php5-fpm-daemontools.conf
daemonize = no
include = /etc/php5/fpm/php5-fpm.conf
これで恐らく php5-fpm-daemontools.conf を読んだあとに本来の php5-fpm.conf を読みに行ってくれるはず。
直接 php5-fpm.conf に書かなければ invoke-rc.d php5-fpm start した時にも問題なく動いてくれる。
invoke-rc.d php5-fpm stop
update-rc.d -f php5-fpm remove
popd
mkdir php5-fpm
pushd php5-fpm
vim run
#!/bin/sh
exec 2>&1
exec /usr/bin/php5-fpm --fpm-config /etc/php5/fpm/php5-fpm-daemontools.conf
スクリプトの準備もできたので実際に実行してみる。
chmod 700 run
svc -u /etc/service/php5-fpm
立ち上がる。が、php5-fpm はプロセスたくさん立ち上がるのね……。
同じように kill で頑張ってプロセスID全部入力して落としたけどすぐに起き上がってきたのでこれも成功した模様。

これで OOM Killer が物凄い勢いで頑張り出しても問題なく動きそうな感じになった。はず。
daemontools 自体がやばいことになった時には daemontools も落としてくれていいので、OOM Killer の対象外には特に何も指定していない。

2010-12-01 追記 ----
わかりやすい日本語ドキュメントを見つけた。
ここの説明を読んでみると、どうも daemontools にデーモンを登録する際は /etc/services/* にはシンボリックリンクを置く方針らしく、直接ファイルを作成したりはしていないようだ。
更にシンボリックリンクを配置したら自動的に起動すると書いているので、導入時手動で立ち上げる必要はなかったと思われる。
Clip to Evernote

Debian にインストールした nginx を再調整

前回の記事の最後に書いた通り、nginx を ./configure する際のオプションを見なおして記事を修正した。とは言っても --user=www-data --group=www-data を追加して、指定しなくても問題なさげな md5 と sha1 の指定を外しただけ。

まだこれといって nginx.conf もそんなに弄っていなかったので、今回は思い切って /usr/local/nginx まるごと削除して make install し直した。ps aux でプロセス一覧を確認した限りでは問題なくなっているので無事成功したようだ。

DBはなくとも、取りあえずは Web サーバとして使える状態になったので現サイトを少しずつ移植していきたい。本当はデザインとかサイト構成とかも変えたいものの、その辺は弄りだすと永遠に終わらなくなるので気にしない方向で。
Clip to Evernote

2010年11月8日

Debian に PHP5.3 をインストール

iptables の設定も終わったので、次は予定通り PHP のインストールをしようと思う。
PHP も頻繁にアップデートがあるのでソースから入れようかと思っていたのだが、丁度似たような時期に似たようなことをしている人がいて、明らかに自分より詳しそうな人なのでホイホイ参考にさせてもらいながら php5-fpm を入れてみようと思う。

php5-fpm 自体は Dotdeb のリポジトリにあるらしいので、一つ前の記事も参考にさせてもらいながら /etc/apt/sources.list にリポジトリを追加などを行う(Dotdebのインストラクションはこちら)。

ただし、hkp鍵サーバとの通信のために tcp の 11371番ポートで外部へアクセスできないといけないので、iptables の設定を弄る必要があった(※前回の記事を既に編集済み)。
su
echo "deb http://php53.dotdeb.org stable all" >> /etc/apt/sources.list
echo "deb-src http://php53.dotdeb.org stable all" >> /etc/apt/sources.list
gpg --keyserver keys.gnupg.net --recv-key 89DF5277
gpg -a --export 89DF5277 | apt-key add -
apt-get update
apt-get install php5-fpm
invoke-rc.d php5-fpm start
あっさりと立ち上がるところまできてしまった。
あとは書いてあるように nginx.conf を編集すれば使えそうだ。
vim /usr/local/nginx/nginx.conf
        location ~ \.php$ {
            root           html;
            fastcgi_pass   127.0.0.1:9000;
            fastcgi_index  index.php;
            fastcgi_param  SCRIPT_FILENAME  /usr/local/nginx/html$fastcgi_script_name;
            include        fastcgi_params;
        }
コメントアウトを外して fastcgi_param をフルパスに変更しただけ。あとは nginx をリロードしたら準備完了。
テスト用の php スクリプトを書いて、wget で取りにいって動作テストをしてみる。
invoke-rc.d nginx reload
echo "<?php phpinfo();" >> /usr/local/nginx/html/_test.php
wget -O - http://localhost/_test.php
でかいテキストが返ってくれば成功。
phpinfo() の出力結果を晒しっぱなしにするのは危険なので速やかに削除する。
rm /usr/local/nginx/html/_test.php
ちなみにこれは少し後になってから気付いたことだけど、初期状態では date.timezone や mbstring.language、mbstring.internal_encoding が特に設定されておらず、実際にスクリプトを書き始めるタイミングで warning などに遭遇する。その辺の設定に関する話はこの辺に少し書いた。

これで PHP が使えるようになったので、DB を使っていないうちの Web サイトぐらいなら大体は動くようになった(メールフォームの類はメール周りの整備をしていないので使えないが、その辺は GMail の SMTP サーバへ PHP から接続に行くようにして自分のサーバではメールサーバを立てない方向で進めたいと思っている)。

ただ、ここにきて nginx のインストールには問題があり、nginx を実行しているユーザが nobody になっていることがわかった。php5-fpm を実行しているユーザは www-data になっていて、明らかにこのユーザが web サーバ用っぽいので次回はまた こちらを参考にさせてもらいながら nginx を入れなおして、以前書いた記事を修正しようと思う。
(今は取りあえず nginx.conf の上の方にある user no-body を user www-data に書き換えて invoke-rc.d nginx restart させたので問題ないはずだが、変更忘れを防ぐためにもインストール時に指定してしまうべきだと思う)
Clip to Evernote

Debian に iptables を設定

前回から少し間があいてしまったが、sshd と httpd が立っている Debian に対して、必要最低限のアクセスのみを許可するような設定を施したい。ただ iptables で出来る設定はネットワークに関する知識が乏しい自分にはなかなか難しく、手探り状態ながら作業してみる。
方針としてはその辺のサイトの解説でもよくあるようなホワイトリスト方式。ただし作業元の環境は固定IPアドレスは持っていないので、IPによる接続元制限はできない。

念のため netstat で使用中のポート一覧も確認して知らぬ間にサーバが立っていないか確認しておく。
以降のコマンドは全て su で root になった状態で叩いている。
netstat -anp
問題なさそうだったので、/etc/firewall.sh というファイルを新規作成して、vim で内容を作る。
touch /etc/firewall.sh
chmod 700 /etc/firewall.sh
vim /etc/firewall.sh
#! /bin/sh
LOCALNET=192.168.11.0/255.255.255.0
MY_SSHD_CONFIG=/etc/ssh/sshd_config

iptables -F
iptables -X
iptables -P INPUT DROP
iptables -P OUTPUT DROP
iptables -P FORWARD DROP

# Loopback
iptables -A INPUT  -i lo -j ACCEPT
iptables -A OUTPUT -o lo -j ACCEPT

# SYN Cookiesを有効にする
# ※TCP SYN Flood攻撃対策
sysctl -w net.ipv4.tcp_syncookies=1 > /dev/null
sed -i '/net.ipv4.tcp_syncookies/d' /etc/sysctl.conf
echo "net.ipv4.tcp_syncookies=1" >> /etc/sysctl.conf

# ブロードキャストアドレス宛pingには応答しない
# ※Smurf攻撃対策
sysctl -w net.ipv4.icmp_echo_ignore_broadcasts=1 > /dev/null
sed -i '/net.ipv4.icmp_echo_ignore_broadcasts/d' /etc/sysctl.conf
echo "net.ipv4.icmp_echo_ignore_broadcasts=1" >> /etc/sysctl.conf

# ICMP Redirectパケットは拒否
sed -i '/net.ipv4.conf.*.accept_redirects/d' /etc/sysctl.conf
for dev in `ls /proc/sys/net/ipv4/conf/`
do
    sysctl -w net.ipv4.conf.$dev.accept_redirects=0 > /dev/null
    echo "net.ipv4.conf.$dev.accept_redirects=0" >> /etc/sysctl.conf
done

# Source Routedパケットは拒否
sed -i '/net.ipv4.conf.*.accept_source_route/d' /etc/sysctl.conf
for dev in `ls /proc/sys/net/ipv4/conf/`
do
    sysctl -w net.ipv4.conf.$dev.accept_source_route=0 > /dev/null
    echo "net.ipv4.conf.$dev.accept_source_route=0" >> /etc/sysctl.conf
done

# フラグメント化されたパケットはログを記録して破棄
iptables -A INPUT -f -j LOG --log-prefix '[IPTABLES FRAGMENT] : '
iptables -A INPUT -f -j DROP

# 外部とのNetBIOS関連、Dropbox関連のアクセスはログを記録せずに破棄
# ※不要ログ記録防止
iptables -A INPUT -s ! $LOCALNET -p tcp -m multiport --dports 135,137,138,139,445,17500 -j DROP
iptables -A INPUT -s ! $LOCALNET -p udp -m multiport --dports 135,137,138,139,445,17500 -j DROP
iptables -A OUTPUT -d ! $LOCALNET -p tcp -m multiport --sports 135,137,138,139,445,17500 -j DROP
iptables -A OUTPUT -d ! $LOCALNET -p udp -m multiport --sports 135,137,138,139,445,17500 -j DROP

# 1秒間に4回を超えるpingはログを記録して破棄
# ※Ping of Death攻撃対策
iptables -N LOG_PINGDEATH
iptables -A LOG_PINGDEATH -m limit --limit 1/s --limit-burst 4 -j ACCEPT
iptables -A LOG_PINGDEATH -j LOG --log-prefix '[IPTABLES PINGDEATH] '
iptables -A LOG_PINGDEATH -j DROP
iptables -A INPUT -p icmp --icmp-type echo-request -j LOG_PINGDEATH

# ポートスキャン対策
iptables -N LOG_PORTSCAN
iptables -A LOG_PORTSCAN -m limit --limit 1/s --limit-burst 4 -j RETURN
iptables -A LOG_PORTSCAN -j LOG --log-level info --log-prefix '[IPTABLES PORTSCAN] '
iptables -A LOG_PORTSCAN -j DROP
iptables -A INPUT -p tcp --tcp-flags SYN,ACK,FIN,RST RST -j LOG_PORTSCAN

# 不正パケット対策
iptables -N LOG_NEWSYNCHECK
iptables -A LOG_NEWSYNCHECK -j LOG --log-prefix '[IPTABLES NEWSYNCHECK] '
iptables -A LOG_NEWSYNCHECK -j DROP
iptables -A INPUT -p tcp ! --syn -m state --state NEW -j LOG_NEWSYNCHECK

# 全ホスト(ブロードキャストアドレス、マルチキャストアドレス)宛パケットはログを記録せずに破棄
# ※不要ログ記録防止
iptables -A INPUT -d 255.255.255.255 -j DROP
iptables -A INPUT -d 224.0.0.1 -j DROP

# 113番ポート(IDENT)へのアクセスには拒否応答
# ※メールサーバ等のレスポンス低下防止
iptables -A INPUT -p tcp --dport 113 -j REJECT --reject-with tcp-reset

# 確立に成功した tcp 接続は許可
iptables -A INPUT  -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -p tcp -m state --state ESTABLISHED,RELATED -j ACCEPT

# Ping 送受信
iptables -A INPUT  -p icmp --icmp-type echo-request -j ACCEPT
iptables -A INPUT  -p icmp --icmp-type echo-reply -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-request -j ACCEPT
iptables -A OUTPUT -p icmp --icmp-type echo-reply -j ACCEPT

# DNS 送信
iptables -A INPUT  -p udp --sport 53 -j ACCEPT
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
iptables -A OUTPUT -p tcp --dport 53 -m state --state NEW -j ACCEPT

# HTTP, HTTPS, POP3(SSL), IMAP, SMTP(TLS/STARTTLS, SSL), git, hkp 送信
iptables -A OUTPUT -p tcp -m state --state NEW -m multiport --dports 80,443,995,993,587,465,9418,11371 -j ACCEPT

# HTTP 受信
iptables -A INPUT -p tcp -m state --state NEW --dport 80 -j ACCEPT

# SSH 受信 / ブルートフォース攻撃対策
# sshd_config ファイルの中からポート番号を指定している箇所を探す
SSHD_LISTEN_PORT=`sed -e 's/^Port\s\+\([0-9]\+\)/\1/p' -e d $MY_SSHD_CONFIG`
iptables -N LOG_SSHBRUTEFORCE
iptables -A LOG_SSHBRUTEFORCE -m limit --limit 3/min --limit-burst 4 -j ACCEPT
iptables -A LOG_SSHBRUTEFORCE -j LOG --log-level info --log-prefix '[IPTABLES SSHBRUTEFORCE] '
iptables -A LOG_SSHBRUTEFORCE -j DROP
iptables -A INPUT -p tcp -m state --state NEW --dport $SSHD_LISTEN_PORT -j LOG_SSHBRUTEFORCE

# 拒否IPアドレスからのアクセスはログを記録せずに破棄
# ※拒否IPアドレスは/root/deny_ipに1行ごとに記述しておくこと
# (/root/deny_ipがなければなにもしない)
if [ -s /root/deny_ip ]; then
    for ip in `cat /root/deny_ip`
    do
        iptables -I INPUT -s $ip -j DROP
    done
fi

# 上記のルールにマッチしなかったアクセスで外部からのものはログを記録(その後自動的に破棄)
iptables -A INPUT -s ! $LOCALNET  -m limit --limit 1/s -j LOG --log-prefix '[IPTABLES NOMATCHINPUT] '
以下のサイトを参考にさせてもらいながら設定を作った。
基本的には参考文献の上2つで大体完結するぐらいの内容。下手に変えない方がよかったかも知れない。

ファイアウォール構築(iptables) - Fedoraで自宅サーバー構築
連載記事 「習うより慣れろ! iptablesテンプレート集」
Man page of IPTABLES
D: Software/iptables - Debian GNU/Linux スレッドテンプレ
セキュリティ強化対策(Iptables編)
ルータ・ファイアウォールiptables)/Fedora Core6

・基本的には全部塞いで必要なところだけ開く(内側から外部へのアクセスも全面的には許可しない)
・ループバックインターフェイスで完結する通信だけは全面的に許可する
・各種攻撃からは防御を試みる
・外部の DNS サーバへの問い合わせを許可する
・HTTP、HTTPS、git、あと GMail で何かすることがあれば使う可能性がある 995,993,587,465 は外部への接続を許可する
・sources.list にリポジトリを追加する際に hkp 鍵サーバと通信する必要があるので 11371 番も外部への接続を許可
・外部からHTTPサーバへの接続を許可する
・外部からSSHサーバへの接続を許可する。ただしブルートフォース攻撃は怖いので過剰なアクセスはDROPするように

この他例えば SSH への接続が日本以外だったら蹴るような対策もすべきだが、現状は仮想PCで外部からは一切アクセスできない状態でテストが面倒なのでとりあえず保留にしてある。
あと外部の HTTP サーバなどに接続する際、ポート番号に 8080 などが使われていると接続できないという問題もある。この手の問題はできれば遭遇するたびに甘くしていく方針にしたい。
今回は仮想サーバで直接マシンのコンソールが開けるので簡単にテストが出来て、SSH が塞がれたままになっても問題なく作業が続行できたものの、リモートのサーバとなるとそうも行かない。
その辺に関してはここにいい説明があって、Windows でモニタを設定を変更するときのように「数秒後に元の状態に戻す」というところまでまとめてコマンドを投げておくことでリモートでのテストをしやすくする解説がある。
実際にリモートでしかできないような環境で設定することになったらこれも試してみようと思う。

参考文献のひとつめの URL では例えば LOCALNET を netstat から自動取得したりするようなコードがあるのだが、Firewall を設定するタイミングではまだその辺は上手く取得できないので IP に関しては直書きしておくことにした。
また国別IPリストの取得処理なども含まれているが、この辺はよく見てないものの設定切り替え時に一瞬無防備になっているような気がして怖いのでとりあえず何もしてない(恐らくiptables-save が吐き出すテキストの中の最後に commit があるので、iptables-restore を使って新しい設定に移行できればいいんじゃないか、という気はちょっとする)。

/etc/firewall.sh を実行して、例えば Windows 側で DOS 窓を2つ開いてサーバに対して同時に ping を打ったりして Ping of Death 対策の確認、Putty を何度も開いてブルートフォース攻撃対策の確認をしたりして、行った設定が上手くいっていることが確認できたら実際にこれがマシン起動時に有効になるようにする。
ln -s /etc/firewall.sh /etc/network/if-pre-up.d/iptables
これで再起動して、Ping of Death の処理がうまく動いていることや SSH への過剰接続がブロックされることを確認したら iptables の設定は完了。
不安が残るもののとりあえずは大丈夫ということにしておこう。
Clip to Evernote

2010年11月4日

Debian に nginx をインストール

前回 SSH で秘密鍵を使ったログインができるようになった Debian に、今度は nginx を入れようと思う。
枯れているし .htaccess が使える Apache はとても便利だけど、非力なマシンでは軽さを優先したい。
そんな理由で今回は nginx を入れようと思う。

nginx は今もバリバリバージョンアップされているので、apt-get を使って入れた場合のバージョンを先に調べてみる。
apt-cache show nginx
どうやら apt-get で入れようとすると現状では 0.6.32 がインストールされるらしい。
ちょっと古過ぎる気もするのでソースからインストールすることにしようと思う。

本当に何もない状態からのインストールなので、gcc や make なども必要。
とりあえず今回は libpcre3-dev と lib-ssl-dev があれば configure に成功するようだ。
一気に make と make install までやる。
su
apt-get install gcc make libpcre3-dev libssl-dev
cd ~
wget http://nginx.org/download/nginx-0.8.53.tar.gz
tar -zxf nginx-0.8.53.tar.gz
cd nginx-0.8.53
./configure --user=www-data --group=www-data --sbin-path=/usr/local/sbin/nginx --conf-path=/usr/local/nginx/nginx.conf --pid-path=/usr/local/nginx/logs/nginx.pid --with-http_ssl_module
make
make install
インストールに成功したら、起動用のスクリプトをここからもらってくる。
configure の際のパラメータは予めこの起動用スクリプトに合わせたファイル構成にしてあるから改変なしで上手くいく。
wget -O /etc/init.d/nginx http://www.debianadmin.com/images/nginx
chmod 755 /etc/init.d/nginx
試しに起動して接続テストしてみる。
invoke-rc.d nginx start
wget -O - http://localhost/
「Welcome to nginx!」のドキュメントが返ってくれば成功。
上手く行ったら、マシン起動時に nginx が自動的に起動するようにするように登録する。
update-rc.d -f nginx defaults
せっかく登録したので Debian を実際に再起動させて nginx が自動的に本当に立ち上がるか確認する。
reboot
立ち上がったらもう一回 wget してみる。まあ wget せずに作業用のマシンでブラウザから実際に http://[IPアドレス]/ でアクセスして確認してもいいけど。
wget -O - http://localhost/
また「Welcome to nginx!」のドキュメントが返ってくれば成功。
Webサーバも立った状態になったので、次は PHP も使えるようにしておきたい。
でもその前に iptables を使って不要なアクセスを蹴るように設定したい。
次回へ続く。予定。
Clip to Evernote

2010年11月3日

Windows Virtual PC に Debian をインストールし、SSH の設定をする

ServersMan@VPS の Entry プランを試しに申し込んでみようかと思っているものの、先日起きた個人情報流出に巻き込まれてしまって普段使っているカードが一時的に使えなくなっているので、取りあえず予行練習ということで Windows Virtual PCDebian をインストールしてみようと思う。

元々 Windows 以外の OS に関してはそんなに詳しくはないものの、Ubuntu は多少使ったことがあるので CentOS よりは Debian の方が慣れやすいだろう、というところで Debian を選んだ。

取りあえずメモリは ServersMan@VPS の Entry プランの基本になる 256MB に。HDD は検索したところによると容量可変のHDDを使うと VirtualPC 2007 で問題が起こっていたらしいので、取りあえず「詳細オプションを使用して仮想ハードディスクを作成する」を選んで、「固定サイズ」、10240MB の HDD を作成。

Debian は 150MB 程の小さい ISO イメージをダウンロードした。
仮想マシンを起動したら ISO イメージを選択。インストーラの起動画面まで到達できた。


2007 だとここからもまだ Kernel Panic が起こったりして noreplace-paravirt というオプションが必要になったりするらしい話も見かけたものの、手元の環境では何も指定しなくても問題なさそうだ。

まず言語選択。ここは迷わず English を選択する。大抵の場合 English の方が都合がいいから。

次に「Choose a country, territory or area」と言われるが、ここでは「other」を選ぶ。
すると「Choose a continent or region」と言われるので、選択肢の中から「Asia」を選ぶ。
こうすると次の「Choose a country, territory or area」ではさっき存在しなかった「Japan」が選べるので選ぶ。

続いてキーボードレイアウトを選ぶ。日本語キーボードを使っているので「Japanese」を選ぶ。

少しの間色々と進行して、ネットワークの設定が始まる。Hostname を決めろと言われるが、その辺は好みで。今回はめんどいので debian にした。ドメイン名は空欄のまま進めた。

次にハードディスクのパーティション設定。全部自動設定にしたいところだが、現状 ServersMan@VPS では swap が使えないようだ(対応を検討中だとか)。
近い環境にするため「Guided - use entire disk」で「IDE1 master (hda) - 10.7 GB Virtual HD」、「All files in one partition (recommended for new users)」と選んで取りあえず分けてもらったあとで「Finish partitioning and write changes to disk」を選ぶ前に swap を消す(カーソルを上に移動すると swap 領域が選べるので、そこで「Delete the partition」すればいいだけ)。swap 領域がない状態で 256MB のメモリではなかなか心細いはずで、インストーラも「Do you want to return to the partitioning menu?」と確認してくるのだが、あえて似た環境にするために No を選んで次に進んでしまう。
次の画面に進むと変更点をディスクに書き込んでいいか確認されるので、ここで Yes と答えると HDD の準備が整い、基本となるシステム部分のインストールが進行する。

ここまで終わると root のパスワードを聞かれる。好みで強固なパスワードを入力。
パスワードの確認もあるので再度パスワードを入力。
更にユーザアカウントの作成に進む。ここも適当に入力。パスワードは root とは違うものを。

次にパッケージマネージャの設定。ミラーサーバーを選ぶ。
オススメらしいので「Japan」「ftp.jp.debian.org」と選んで、proxy は今回は必要ないので空欄で。
あとはネットワークからアレコレ落としてきて勝手にインストールが始まるのでしばらく待つ。

ある程度進むと「Participate in the package usage survey?」と言われるが、今回は No に。

次にインストールするものを選ぶ画面になる。取りあえず全部外す。
最後に GRUB boot loader を MBR に書き込むか聞かれる。これはもちろん Yes で。

これでインストールの工程は全て完了。再起動されてログイン画面まできた。


取りあえず root でログインして、SSH をインストールする。
apt-get install ssh
上手く行ったら、ssh の設定を弄る。作業順としてはポート変更、root でのログインを禁止して、一般ユーザ側で秘密鍵を使ってログインできるようにしたら、パスワードログインも無効化する。
cd /etc/ssh
cp sshd_config sshd_config.default #バックアップ
vim sshd_config #Ubuntuで使い慣れているのでvim
これで sshd_config ファイルが編集できるようになるので、以下の書き換えをしていく。
vim では基本的には i を押して挿入モードにして、方向キーでカーソル移動、
終わったら ESC を押して挿入モードを終了し、:wq を入力して上書き保存する。
#Port 22
Port [任意の数字]
#PermitRootLogin yes
PermitRootLogin no
設定ファイルの書き換えが終わったら sshd に設定を再読み込みさせる。
invoke-rc.d ssh reload
ここまでやったら、テストも兼ねて ssh 経由で一般ユーザアカウントでログインしてみる。
ssh -p [ポート番号] localhost #rootは無効化したので、パスワード入れてもログインできなければ成功
ssh -p [ポート番号] [一般ユーザアカウント名]@localhost
ssh 経由でログインできたら、ユーザ用の鍵ファイルを作成する。
ssh-keygen
保存先を聞かれるが、取りあえずそのまま Enter を押してデフォルト位置へ(~/.ssh/id_rsa)。
次に鍵用のパスフレーズを聞かれるので設定する。なしにもできるけどもちろん設定するに越したことはない。
確認用にもう一度入力すると、実際に秘密鍵と公開鍵のペアが作成される。

次に作成した公開鍵を sshd が読み取れるように設定する。
パーミッションを適切に設定しないと sshd に無視されるので注意。
cd .ssh
touch authorized_keys
chmod 600 authorized_keys
cat id_rsa.pub >> authorized_keys
これで鍵で認証するための準備が整ったが、肝心の秘密鍵をファイルを普段使用するマシンに渡さなければ何もできない。
なのでパスワードログインを無効化する前にパスワードログインして鍵ファイルをダウンロードする。
取りあえずもう一般ユーザからの SSH での操作は必要なくなったので、SSH での接続を終了して root に戻り、SSH の接続先になるIPアドレスを確認する。
exit
ifconfig
eth0 の inet addr に仮想マシンのIPアドレスが表示される。
作業マシンが Windows なので SSH クライアントには PuTTY を使いたい。PuTTY ごった煮版をダウンロードして解凍、psftp.exe を起動する。


「open [IPアドレス] [ポート番号]」のように入力するとサーバに接続される(IPとポート番号はスペースで区切る)。
ログインするアカウントを聞かれるので、ここでもまた一般ユーザアカウント名とパスワードを入れ、接続に成功するとログインしたアカウントのホームディレクトリで「psftp>」のプロンプトで待機状態になる。
cd .ssh !::.sshディレクトリに移動
get id_rsa !::ローカルのディレクトリに id_rsa ファイルをダウンロード
rm id_rsa* !::必要なくなった id_rsa と id_rsa.pub を削除
exit
※「!::」はコメント的なもの

普通に psftp を起動していれば恐らく同じディレクトリに id_rsa が作られているはず。
ただし PuTTY だと OpenSSH 形式の鍵ファイルは直接は扱えないので、puttygen.exe で変換する必要がある。
起動してメニューから [変換] > [鍵のインポート] を選び、さっきダウンロードした id_rsa を開く。


パスフレーズを指定していればパスフレーズを聞かれるはずなので入力する。あとはメニューから [ファイル] > [秘密鍵の保存] を選べば Putty で使える ppk ファイルが保存できる。
この生成された秘密鍵ファイルを使って SSH に接続テストをする。

まず pageant.exe を起動する。するとタスクトレイにアイコンが追加されるので、それをダブルクリックすると「Pageant 鍵の一覧」というダイアログが表示される。


ここで [鍵の追加] を押してさっき生成した *.ppk ファイルを選ぶと、またパスフレーズの入力がある。パスフレーズを保存しておくと、今後は入力する手間を省くことができる。さらに、この Pageant の起動と鍵登録の作業は Windows の起動のたびに必要になるので、この辺を参考にスタートアップに登録しておけばそれも自動化できて便利。もちろんパスワードを自動保存する危険性も含めてその辺はノーリスクというわけではないので、安易に使うのも危ないので注意。

鍵の登録が終わったら、putty.exe を起動する。ホスト名にはさっき ifconfig で調べたIPアドレス、ポート番号には自分で設定したポート番号を入力する。更に左側のツリーから [接続] > [データ] を選んで、「自動ログインのユーザ名」という入力欄があるので、ここに一般ユーザアカウント名を入れておく(入れなくてもいいけど入れた方が便利。ただこの辺もいちいち打った方がもちろん安全ではある)。
そして下にある [開く] ボタンを押すとログインしようとするが、その際 Pageant が「次の鍵への照会を許可しますか?」と聞いてくる。ここで許可すると、パスワードを入れないままログインに成功するはず。

ログインが上手くいけば設定も問題ないようなので、必要なくなったパスワードログインを無効化するため、実験も兼ねて Putty 上で一般ユーザから root へ su で切り替えて、sshd の設定ファイルを再度いじりに行く。
su
Password: [rootのパスワードを入力] #※これはsshでrootにログインしているわけではないので成功する
vim /etc/ssh/sshd_config
設定ファイルの編集画面になるので、パスワードでのログインを無効化する。
#PasswordAuthentication yes
PasswordAuthentication no
設定を書き換えたので sshd で設定ファイルを再読み込みさせる。
invoke-rc.d ssh reload
これで ssh ではもうパスワードでのログインはできないはずなので、試しに ssh で一般ユーザにパスワードログインしてみる。
ssh -p [ポート番号] [一般ユーザアカウント名]@localhost
ログイン出来なければ成功。

これで ssh 周りの設定が完了。これから先は ssh 経由で触ればいいので、仮想PC本体のコンソールは exit でログアウトし、ログイン待ちの状態まで戻しておく。
PuTTY で who コマンドを打つと、PuTTY から接続しているクライアントだけが見えていれば成功。
who
結構長くなったなあ。
Clip to Evernote