cvs サーバの構築

問題と対策

cvs はもともと遠隔で動作するようには設計されていない。

もともと、cvs は cvs を実行したユーザの権限を自由に使ってよいという思想で作られている(としか思えない)ため、サーバ側でアクセス制限を行う場合、 cvs 自身はあまり信用せず、外側で制限することが望ましい。実際、ドキュメントにはリポジトリをアクセスできるようにすることはシステムに比較的自由にアクセスできるようにすることになるかもしれないと警告されている。 (参考: grep 'system access' cvs.texinfo)

そのために、chroot を行なってサーバを実行し、万が一ユーザがサーバ側でコマンドを実行できる権限を手に入れたとしてもそもそもそのコマンドが存在しないようにする。また、chroot jail (chroot システムコールで制限したディレクトリ)内では十分に考慮してパーミッションを設定し、サーバが必要以上のファイルにアクセスできないようにする。

なお、pserver method での read-only アクセスは例外で、匿名アクセスでも問題ないように実装されている(はずである(が、信用できたものではないという意見もある))。

pserver method はパスワードを平文で送る。
パスワードを平文で送ると通信経路を覗き見できる人にパスワードがばれてしまうため、 anonymous 以外のアクセスには pserver は使わず、かわりに ssh を使用する。
chroot には root 権限が必要。
ssh により起動した cvs サーバは ssh に -l で指定したアカウントで動作している。しかし、chroot するには root 権限が必要である。これは setuid bit を使って解決する。 (なお、chroot jail の中で sshd を動作させれば setuid は必要ない。)
chroot コマンドを使う場合 chroot jail 内に cvs を設置しなければならない。
chroot するのに chroot コマンドを使用すると、 chroot した後に cvs サーバを起動することになるため、 chroot jail 内に cvs コマンドが必要である。また、chroot 後に root 権限を放棄するためには su コマンドも必要である。 cvs を修正し、cvs 自身が chroot/setuid することにより、それらの chroot/su/cvs などのコマンドは chroot jail 内には設置しないようにする。また、そのように修正した cvs に setuid bit を設定する。
pserver 以外の method ではサーバを起動したアカウントの名前がログに残る。
このため、正しくログをとるには、各 committer にアカウントが必要である。これは cvs を修正して単一アカウントで動作させても正しくログをとれるようにする。なお、単一アカウントに対する ssh で committer を区別できるようにするため、 committer 各人で異なる鍵を使用し、 authorized_keys 内で、鍵毎に異なる command ないし environment を設定する。
pserver 以外の method では cvsroot を制限できない。
常に --allow-root が効くように cvs を修正する。
いつ誰が commit したかを把握するのは面倒である。

commit 時にそれを通知するメールを送る設定をする。ただし、次の要件を満たすようにする。

これらの要件を満たすために、SMTP で直接メールサーバと通信するようなプログラムを使う。

/bin/sh について。

(commit 時にメールを送信するために) loginfo を使う場合は chroot jail 内に /bin/sh が必要である。なるべく安全性を高めるように、pdksh を使用し、restricted shell とする。 (restricted shell として動作させるための環境変数の設定は cvs コマンドに埋め込む。)

また、cvs から /bin/sh を通さずにコマンドを実行する場合には、コマンドに / が含まれていないことを確認する。

これらの対策により、PATH の外のコマンドが実行されないことを保証する。

Checkin.prog と Update.prog。
Checkin.prog や Update.prog を使うと committer が任意のコマンドをサーバ側で実行できてしまうので、禁止する。
pserver と process group id。

commit 時のメールを送信するツールは、loginfo から起動されるが、 commit されるファイルが複数のディレクトリにまたがる場合、 loginfo はそれぞれのディレクトリ毎に起動される。したがって、素朴に毎回メールを送ると各ディレクトリ毎にメールが送信されるが、ここで 1回の commit についてメールを 1通だけ生成するために、 commitinfo を併用するということが行なわれる。 n個のディレクトリにまたがる commit が行なわれた場合、まず commitinfo が n回起動され、その後で loginfo が n回起動される。つまり、まず commitinfo の起動回数をカウントし、 loginfo の起動回数がそれと等しくなった時にメールを送るのである。

しかし、この方法は複数の commit が(時間的に)重ならない場合はうまくいくが、重なる場合には数え間違える可能性がある。そこで、個々の commit を区別するために、process group id が使用される。つまり、ジョブ制御を行なうシェルから起動した cvs およびその子孫のプロセスは unique な process group id を持つので、それをもとに個々の commit を区別し、正しく数えることができる。 (なお、親プロセスの process id を使うことはできない。 loginfo では /bin/sh 経由で呼び出されるため、親プロセスが /bin/sh となって cvs の process id が得られないためである。なお、loginfo で exec すればこの問題を避けられるが、 restricted shell では exec が使用不能なことが多いという問題がある。ただし、pdksh は例外的に restricted shell でも exec を使用可能なようである。)

そして、この方法は cvs を process group leader としないような形で起動した場合、うまく働かない。 pserver method はまさにこの状況にあてはまり、 cvs は inetd から起動され、すべての cvs プロセスが(inetd と)同じ process group id を持ち、個々の commit を区別できない。

この問題には、cvs が自分自身で process group leader になるよう修正して対処する。 (なお、pserver 経由で commit されないのならこの問題は起きない。)

cvsweb の annotate。

cvsweb で annotate という機能を動作させるにはリポジトリをロックする権限(書き込み権限)が必要である。つまり、CGI にリポジトリの書き込み権限を与えなければならない。これを避けるには次の方法がある。

リポジトリ全体の取得。(CVSup)

cvs はリポジトリ全体を取得する手段を提供していない。このため、手元にリポジトリを mirror することができない。そこで、CVSup により、リポジトリ全体を取得する手段を提供する。

なお、cvs client/server protocol によってリポジトリ(のほぼ)全体を取得することは不可能ではないように思われるが、いまのところそのためのツールは作られていないのではないかと思われる。

ssh 経由での anonymous アクセス。

ssh 経由の anonymous アクセスを実現するには次の方法がある。

複数の cvsroot を扱う。
commit に同期して web の contents を update する。

グループとユーザ

管理者権限とユーザ権限のそれぞれに対応したユーザ・グループを作る。

/etc/passwd:
cvs:*:UID-for-cvs:GID-for-cvs:cvs user:/cvs/home/cvs:/bin/sh
cvsadmin:*:UID-for-cvs-admin:GID-for-cvs-admin:cvs administrator:/cvs/home/cvsadmin:/bin/sh
/etc/group:
cvs:*:GID-for-cvs:cvsadmin
cvsadmin:*:GID-for-cvs-admin:

ディレクトリ構成とパーミッション

基本的に次の方針で設定する。

/cvs 以下にサーバに必要なファイルをおく場合、例えば次のように設定する。

全体構造
dr-xr-sr-x cvsadmin cvsadmin /cvs (トップレベルディレクトリ)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot (chroot jail のトップディレクトリ)
dr-xr-sr-x cvsadmin cvsadmin /cvs/home (cvs 関連ユーザのホームディレクトリをおくディレクトリ)
lrwxrwxrwx cvsadmin cvsadmin /cvs/root -> chroot/cvs/root ($CVSROOT として :local:/cvs/root を使えるようにするトリック。省略可)
chroot jail
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/cvs (chroot jail 内での /cvs)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/cvs/bin (cvs から実行可能なコマンドをおくディレクトリ)
drwxrwsr-t cvsadmin cvs      /cvs/chroot/cvs/root ($CVSROOT. stickey bit は /cvs/chroot/cvs/root/CVSROOT の rename を禁止するため。トップレベルの import を許可しないならば 755 でよい)
drwxrwsr-t cvsadmin cvs      /cvs/chroot/cvs/sample (他の $CVSROOT)

lrwxrwxrwx cvsadmin cvsadmin /cvs/chroot/cvs/chroot -> .. (/cvs/chroot への参照を chroot 内外で同じものにするためのトリック)

dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/root (chroot jail 内での root ユーザのホームディレクトリ)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/cvs/home (chroot jail 内でのホームディレクトリをおくディレクトリ)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/cvs/home/cvs (cvs ユーザのホームディレクトリ)

dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/bin (実行可能なコマンドをおくディレクトリ)
-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/bin/sh (chroot jail 内での /bin/sh. pdksh)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/dev (chroot jail 内での /dev)
crw-rw-rw- cvsadmin cvsadmin /cvs/chroot/dev/null (null デバイス)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/etc (chroot jail 内での /etc)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/var (chroot jail 内での /var)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/lib (chroot jail 内での /lib)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/usr (chroot jail 内での /usr)
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/usr/lib (chroot jail 内での /usr/lib)
drwxrwxrwt cvsadmin cvsadmin /cvs/chroot/tmp (chroot jail 内での /tmp)
/cvs/home
drwxr-sr-x cvsadmin cvsadmin /cvs/home/cvsadmin (cvsadmin のホームディレクトリ)
drwxr-x--- cvsadmin cvs      /cvs/home/cvsadmin/sbin (通常ユーザには実行されたくないコマンドをおくディレクトリ)
-r-sr-x--- root     cvs      /cvs/home/cvsadmin/sbin/cvs (setgid/initgroups/chroot/chdir/setuid/setpgid/環境変数の設定などを行なう特別仕様の cvs コマンド)
-rwxr-xr-x cvsadmin cvsadmin /cvs/home/cvsadmin/sbin/cvs-pserver (--allow-root つきで cvs コマンドを起動するラッパー)
drwxr-sr-x cvs      cvs      /cvs/home/cvs (cvs のホームディレクトリ)
drwx------ cvs      cvs      /cvs/home/cvs/.ssh (cvs ユーザ用 ssh 設定ディレクトリ)
-rw-r--r-- cvs      cvs      /cvs/home/cvs/.ssh/authorized_keys (comitter の公開鍵で cvs server を実行する設定ファイル)
/cvs/chroot/etc
ユーザデータベースを構成する。
OS によって異なるが、passwd(5), nsswitch.conf(5), pwd_mkdb(8) 等を参照して
root と cvs を参照できるように構成する。一例:
  -r--r--r-- cvsadmin cvsadmin /cvs/chroot/etc/nsswitch.conf
  -r--r--r-- cvsadmin cvsadmin /cvs/chroot/etc/passwd
commitinfo, loginfo

commitinfo, loginfo 用のコマンドの実行に必要なファイルを用意する。コマンドは /cvs/chroot/cvs/bin におき、その実行に必要なライブラリを適切な場所におく。 (cf. ldd(1))

一例:

-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/cvs/bin/cvs-commitinfo (commitinfo 用コマンド)
-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/cvs/bin/cvs-loginfo (loginfo 用コマンド)
-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/cvs/bin/www-update (www 更新用コマンド)

-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/lib/libc.so.6
-r-xr-xr-x cvsadmin cvsadmin /cvs/chroot/lib/ld-linux.so.2
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/lib/libnsl.so.1
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/lib/libdl.so.2
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/lib/libnss_files.so.2
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/lib/libm.so.6
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/lib/libcrypt.so.1

dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/usr/lib/ruby
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/usr/lib/ruby/1.4
dr-xr-sr-x cvsadmin cvsadmin /cvs/chroot/usr/lib/ruby/1.4/i386-linux
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/usr/lib/ruby/1.4/i386-linux/socket.so
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/usr/lib/ruby/1.4/i386-linux/etc.so

drwxrwsr-x cvsadmin cvs      /cvs/chroot/var/cvs-info
-rw-rw-r-- cvs      cvs      /cvs/chroot/var/cvs-info/references
リポジトリ

cvs -d /cvs/chroot/cvs/root init として作る。 (複数のリポジトリを提供する場合、$CVSROOT のテンプレートを用意しておくと便利である。) 一例: (注: cvs init によって作られるファイルのうち、興味深いものだけをあげてある。)

drwxrwsr-t cvsadmin cvs      /cvs/chroot/cvs/root ($CVSROOT. stickey bit は /cvs/chroot/cvs/root/CVSROOT の rename を禁止するため。トップレベルの import を許可しないならば 755 でよい)
drwxr-sr-x cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT
drwxrwsr-x cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/Emptydir (cvs-1.10.7 以前に cvs init でリポジトリを作った場合にはこのディレクトリができないので、確実に作る)
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/loginfo (DEFAULT cvs-loginfo)
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/loginfo,v
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/commitinfo (DEFAULT cvs-commitinfo)
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/commitinfo,v
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/taginfo (ブランチ主体で開発が行なわれている場合、cvs-taginfo を作って設定すべきであろう。たぶん)
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/taginfo,v
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/modules
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/modules,v
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/config (SystemAuth=no, LockDir=CVSROOT/.lockdir)
-r--r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/config,v
-rw-rw-rw- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/history (誰でも書き込めるようにする)
-rw-rw-rw- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/val-tags (誰でも書き込めるようにする)
-rw-r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/readers
-rw-r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/writers
-rw-r--r-- cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/passwd (cvs では管理<em>しない</em>)
drwxrwsrwt cvsadmin cvsadmin <a href="#lockdir">/cvs/chroot/cvs/root/CVSROOT/.lockdir</a>
drwxrwsr-x cvsadmin cvsadmin <a href="#lockdir">/cvs/chroot/cvs/root/CVSROOT/.lockdir/CVSROOT</a>
dr--r--r-- cvsadmin cvsadmin <a href="#lockdir">/cvs/chroot/cvs/root/CVSROOT/.lockdir/CVSROOT/.lockdir</a>
drwxrwsr-x cvs      cvs      /cvs/chroot/cvs/root/www
-r--r--r-- cvs      cvs      /cvs/chroot/cvs/root/www/index.en.html,v
LockDir

cvs ユーザ以外でも(ロックを伴う)read only な操作を行なえるようにするために LockDir を設定する。これはとくに cvsweb で必要になる。なお、cvs-1.10.8 では CVSROOT/config 内の LockDir には絶対パスしか使用できないので、 cvs を修正しない場合は絶対パスで指定する。 (なお、複数のリポジトリを用意する場合、$CVSROOT からの相対パスで指定できるように変更するとリポジトリごとに別の設定にしなくて済むので便利である。) また、cvsadmin 以外による CVSROOT の checkout を防ぐために、 /cvs/chroot/cvs/root/CVSROOT/.lockdir/CVSROOT を cvsadmin 以外には書き込めないディレクトリとする。さらに、 cvsadmin によって CVSROOT を checkout したときに無限ループに陥ることを防ぐために、 /cvs/chroot/cvs/root/CVSROOT/.lockdir/CVSROOT/.lockdir を書き込み不能な空のディレクトリにする。

drwxrwsrwt cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/.lockdir
drwxrwsr-x cvsadmin cvsadmin /cvs/chroot/cvs/root/CVSROOT/.lockdir/CVSROOT

cvs のはなし


akr@m17n.org