仙石浩明の日記

2006年

2006年4月13日

VPN-Warp 入門編

CTO日記のほうで 「VPN-Warp入門編」を始めた。 VPN-Warpというのは、port forwarder で、「relay agent」プログラムを動かしているマシンへ、「リレーサーバ」経由で TCP トンネルを掘ることができる。 つまり、イントラの中だろうが固定IPがついてない NAT の中だろうが、ダイアルアップするノートPCだろうが、インターネットへアクセスすることさえできるならば、relay agent を動かすだけで外部からアクセスできるトンネルが設置可能。

ssh の -R オプションと、-L オプションを組み合わせて同様のことが実現可能であるが、大きな違いは、ssh とちがってアカウントを作る必要がないこと。SSLクライアント証明書を発行するだけなので、異なるセキュリティポリシーのトンネルを何本でも手軽に設置できる。実際、KLab のインフラの各所で活用している。

Filed under: システム構築・運用 — hiroaki_sengoku @ 11:46
2006年4月13日

VPN-Warp 入門編 (1)

KLabセキュリティ(株)の製品に VPN-Warp という SSL VPN ソリューションがあります。 この製品は、最初のプロトタイプを私が開発し、 社内インフラ構築で長年 (といっても KLab(株) が設立からまだ 6年なので、 せいぜいそのくらいですが) 使い倒すことによって信頼性があがってきたものを、 商品化したものです。 私自身が最初に開発した、という愛着もあるので、 「仙石浩明CTO の日記」という場を利用して、 VPN-Warp を内部構造も含めて解説していきたいと思います。

VPN-Warp は、ふつうの SSL VPN に比べ、以下の特徴があります:

  1. サーバ側の設定が簡単
    特殊な設定の必要がありません。 イントラネット上の Web サーバに専用ソフトウェアをインストールするだけです。
  2. 既存のイントラネット環境を利用
    ファイアウォールの設定を変える必要がありません。
  3. PC、携帯電話、PDA などから利用可能
    主要端末に対応しています。
  4. ファイアウォール内部の情報を完璧に保護
    SSL により暗号化・認証 (クライアント・サーバ間の相互認証) で 高度なセキュリティを実現します。
  5. クライアント側への簡単な導入
    特別なソフトが必要なく SSL 証明書のインポートだけで使えます。
  6. 安定したシステム
    定評ある KLab のデータセンタを利用するため安心です。

VPN-Warp って何?

一言で言うと、ポートフォワーダです。 ssh のポートフォワードを使ったことがある人は多いと思うのですが、 基本はアレと同じです。 ssh だと、ローカルホスト (例えば自分の PC) 上で

ssh -L 8080:intra:10080 remote "sleep 1d"

などと実行することにより、 ローカルホストの 8080 番ポートを (remote から見て) intra:10080 番ポートへフォワードできます。 intra:10080 に、イントラへアクセス可能な http proxy が動いていれば、 ローカルホストの 8080 番ポートをイントラの http proxy として 使うことができるわけですね。

じゃ、ssh でいいんじゃない? なんで VPN-Warp を使うの?

ssh のポートフォワーダには大きく分けて二つの欠点があります:

  1. リモートホスト (この例では remote) にログインアカウントが必要
    ポートフォワードするためだけに、 ssh でログインできる権限を与える というのは、いかにも牛刀ですよね? また、サーバのアカウントというのは滅多矢鱈に増やせるものでもないので、 例えば数千人を超えるユーザにサービスする方法としては現実的ではありません。
  2. 外部からリモートホストへアクセスできることが必要
    この例の remote はイントラ内へアクセスできることが必要です。 イントラ内へアクセスできるサーバを 外部からアクセスできるセグメント (例えば DMZ) に置くのは 現実的ではありませんから、普通はいったん外部からアクセス可能な サーバ (例えばファイアウォール) の 適当なポート (例えば 10022 番ポート) で受けて、 それをイントラ内へフォワードするように設定しておくことになります。 DMZ に置くよりはマシであるものの、 外部から直接ログインできてしまうことにかわりはありません。

この二つの欠点は、どちらも「リモートホスト」の セキュリティレベルを下げる要因になります。 単刀直入に言えば、外部の第三者が「リモートホスト」にログインして フルコントロール権限を奪うリスクが高まっている、ということです。

次回は、VPN-Warp が上記欠点をどのように解決しているか説明します。

Filed under: システム構築・運用 — hiroaki_sengoku @ 08:06
2006年4月12日

ネットが切れる不安

ネットが切れる不安という日記を見つけた。

引っ越し先には光ファイバーが入っていないのだ(汗) 明日、ADSLが大丈夫かどうか確認して、なるべく間隔があかないように手続きをしないと。 今の時代、一日でもネットに接続できないと、なんだか不安になってしまう。

私も 6年前に引越したのだが、 ネットが切れる不安におそわれた。 なんとか間隔があかないように苦労したのを覚えている。 顛末は日経Linuxの連載の枕 (PDF版) に書いた。

引っ越し作業中も社宅でサーバーを動かし続ける必要があるわけですが,部屋中に電話線やらEthernetケーブルが張り巡らせてある状態で引っ越し作業ができるはずはないので,サーバー,ルーター,TA(Terminal Adapter),電話等のネットワーク関係を部屋の片隅の半畳程度の部分に再構築し,作業中絶対にこの部分には近付かないよう*3 引っ越し業者の方にお願いすることにしました

当時はまだ ADSL のサービスが始まっていなかったので、OCN エコノミーを利用していた。だから TA。

Filed under: システム構築・運用 — hiroaki_sengoku @ 20:01
2006年4月11日

情報処理のキホン (1) hatena_b

KLab の開発に参加いただいている協力会社の A 社の S 社長が、

- o -

私自身は情報処理のキホンをちゃんと「教わったり習ったり」した ことはなく、その点で、至る所で「やっぱりキホンがしっかりして いる人には適わないなぁ」と感じるシーンも経験します。

で、大学や専門学校で情報処理を学んで来た人に教えて欲しいの ですが・・・・・・

Q1) 学校でどんなコース(講義/単位)がありましたか。
○○学 とか ○○技法 とかそんな感じの。
Q2) その中で、印象深かったものや、今こうやって開発の現場で仕事を
していて「学んでよかったなぁ」と思ったものを教えてください。
Q3) 今この会社の、学校でそういうことを学んでこなかった人に対して
「俺が解るように教えてやる!」というのがあるとしたら
どんな事ですか。
- o -

という質問を tech ML にしました。
みなさんならどう答えますか?

A1)
私は情報工学科だったので、論理回路からコンパイラ、ソフトウェア工学まで一 通り学びました。ついでにいうと実践的なプログラミングは、某ソフトウェアハ ウスでバイトして覚え、ネットワークは、最初の就職先のイントラ構築の手伝い をして覚えました。
A2)
専門課程で学ぶ事で一番大事なのは、「大海」を知る事ですね、きっと。 「大海」を知らない「井の中の蛙」は、決して「井」から出る事はできません。
大事なのは知識や経験を身につける事ではなくて、自分が何を知らないかを 知る事です。
大学は「開発の現場で」役にたつことを教えてくれない、と多くの人が言います が、そういうことを言う人って、ほぼ確実に自分が何を知らないか分かってない と思います。
A3)
なんでもいいです。自分では到底作り出す事ができないものについて、 完全に理解するよう努力する経験をすることができるのであれば。
言い替えれば、何か (What) を学ぶ事が重要なのではなくて、
まして、どうやって (How) を覚える事なんて重要であるはずがなく、
体得すべきは、なぜ (Why) を問い続ける習慣そのものです。

「トランザクション輪読会」(社内輪読会です) の本を、 「分かったつもり」レベルではなく、 完全に理解できるまで頑張るとかでも、もちろんいいですね。 どこまで完全に理解できたか自体は、実はあまり重要ではなくて、 理解しようと努力して、自身の限界まで力を出し切ってみることが事が 大事なのではないかと。

「輪読会」に参加した人は実感していると思いますが、何が分からないか 分からない人は、質問さえできないわけです。自分が本当に理解できているか、 常に反省する習慣を身に付けたいものです。

ちなみに私は大学生の時、隣の理学部の数学科へ出かけていって、 なんとか理解しようとさんざん苦しんだ挙げ句、 どーしても「分かったつもり」レベルから脱することができずに 自身の理解力の限界を知る経験をしました。

いちおーその単位は取ったのですが、数学者になることはあきらめて 情報工学に専念しようと思ったのでした。

Filed under: 技術と経営 — hiroaki_sengoku @ 07:17
2006年4月11日

同時に考えよう (2)

同時に考えよう」で無意識の思考について私自身の経験について述べたのであるが、驚いたことにそういう仮説が実際にあって、実験も行われているらしい。

Don't think too much, It's allright」(医学都市伝説)で紹介されている論文によると、

多くの判断要素のある事柄の選択については、無意識的思考が勝るという作業仮説から研究を行った。仮説は「集中なき熟考」と名付けられ、それを元に消費選択についての四つの実験が行われた。これらは実験室だけでなく、街角の商店でも行われ、複雑な判断要素のある商品購入の際には、意識を集中させた熟考によって、かえって悪い結果を得られることが確かめられた」。

だそうだ。この仮説が「都市伝説」なのかどうかよくわからない (^^;) が、

著者たちはこれらの結果から、どこに住み、どんな仕事をするかというような、多くの判断基準が錯綜する事柄について選択をする場合、それらの条件をいったん胸に納め、それに集中することなく「無意識下」で暖めておくことが、もっともよい決断結果が得る道だと主張している。

という主張は、私の感覚ととてもよくマッチする。判断基準から選択を行う思考の場合だけでなく、初めて見る数学の問題を解くとか、さらには全く新しいアイディアを思いつく、などといったような創造的な思考をも無意識下に行える、と私としては主張したいところであるが、実験で「創造的な思考を行えたか」検証するのは難しそうだ。

Filed under: 自己啓発 — hiroaki_sengoku @ 06:44
2006年4月10日

守秘義務

取引先が秘密保持契約(NDA)のもと開示した秘密や、お客様のデータや、経営情報などについては、あからさまに「秘密」という感じがするので、それが守秘義務の範囲に含まれることは誰の目にも明らかだろう。では、ノウハウはどうだろうか?

本当に件のノウハウなんか「しゃべるな」と言われたから(=明確に秘するべき事と定義されたから)言わないだけで、別にそれを知ったからといってPerl のディストリビューションができるわけでも、俺もディストリビューションを作ってやろうと思わせるもんでもないし、マジで「コロンブスの卵」のノウハウ程度のもの。 (秘密にすべきことは明確にされるべき)

なにを「秘するべき事と定義」するかは各社の考え次第だから、私がとやかく言うことではないが、少なくとも弊社では、アイディア自体は秘密とは考えない。

ベンチャー起業の鉄則
アイデアなんて二束三文だ
(ベンチャー起業の秘訣!無料アイデア付き)

と社長自らが言っているし、 私も以前考案したDB サーバのセキュリティ向上策をはじめとして、 実地の運用ノウハウを公開していきたいと思っている。 「アイデアは人に話すことで熟成していく」 (ベンチャー起業の秘訣!無料アイデア付き)と考えるからだ。

そもそもノウハウは、アイディア自体にあるのではなく、 そのアイディアを具現化できる「人」にこそあるのだと思う。

Filed under: 技術と経営 — hiroaki_sengoku @ 07:38
2006年4月9日

好きなことをしよう

向いていることを仕事にしよう。 まさに、好きこそものの上手なれ、 嫌々やってる人と、好きでやっている人とでは、どんどん差がつく。 一生のうちのかなりの時間を仕事に費やすのだから好きなことをすべき。

好きなことやっててメシが食えれば苦労はしない、という人が いるかも知れない。しかし、 かなりニッチな分野でも、トップクラスならば飯の種には困らないもの。 ベンチャーと同じ。 市場規模が小さくても、他社に負けない競争力を持っていれば 商売になる。 市場規模がいくら大きくても、他社の後追いなら 価格競争に巻き込まれてジリ貧になる。 まさに、鶏口となるも牛後となるなかれ。

その一方で、

眠る開発屋blog から引用:

ベンチャーって割と志先行になりがちだけども、 どこかでそれと距離を置かないと行き詰まるよね。 多種多様な人間を巻き込む土壌がなければバランス悪くなるもの。 ってか事業を展開させようにも志だけじゃ人が集まらない。

確かにそういう面も否定できない。 多種多様な人間を巻き込みつつ、 各人が高い志を持ち続けるには? ベクトルが異なる志を持つ人達が、 お互いを尊重して総合力を発揮するには?

志まで雇われたいの? から引用:

会社を設立するに当っては、この志というものは欠かせない。 しかしそれは創立者や役員といった「コアメンバー」で押しとどめるべきだ。 会社は従業員に雇用契約以上のものを求めてはならないし、 従業員もまた会社に雇用契約以上のものを求めるべきではない。
...
しかし、セミナーだの訓示だので、志を植え付けるがごとくは、 会社の傲慢というものだ。それでよかった時代もあったのかも知れないが、 今や社畜という生き物は不良資産扱いだ。企業不祥事の際に 産廃扱いで捨てられるのがヲチである。
会社2.0に取りかかる前に、せめて会社1.0をリリースしませんか、みなさん。

志を押しつけ、異なるベクトルを矯正しようとするのではなく、 異なるベクトルの合力を生かす方法はないのだろうか? 力をあわせる唯一の目標が「皆でお金持ちになろうね」(眠る開発屋blog) だけだったとしたら、あまりに悲しい。

Filed under: 技術者の成長 — hiroaki_sengoku @ 08:43
2006年4月8日

マーケティングを学ぼう

先日、KLab のエンジニアの T さんが、 KLab 社内 ML でマーケティングの本を紹介しました。

今週のマーケティング本:「マーケティング / 恩蔵直人
私がマーケティングを学んだときの師から紹介された本。 入門書として読むのも良いですが、網羅的かつ体系的に纏められており、 マーケ脳を整理・刺激するのにも適していると思います。 電車の中でも読みやすい文庫サイズ。

さっそく買って読んでみました。 網羅的に分かりやすく説明してあるので、 マーケティングを専門としない人にもいい本ですね。 そこで、私も推薦文を tech ML (KLab 技術者 ML) に書きました:

- o -

T さんオススメのマーケ本を買いました。 この本に書いてある事くらいは、 きちんと押えておくと、 技術者以外の人と話す時に役立つのではないかと思います。

つまり、技術者、企画者、マーケター、セールス、 会社ではいろんな人が業務を分担して仕事を進めるわけですが、 共通言語を持っていないと意思疎通がうまく いかなくて仕事がスムーズに進みませんよね?

で、共通言語というかバックグラウンドを少しでも共有するために、 お互いの分野の基礎的な部分くらいは、網羅的に押えておく事が重要ですね。

なので、技術者こそ、こういうマーケティングの基礎を網羅的に解説した本を 読むべきだと思います。 後ほど本棚に置いておきますので、是非読んで下さい。

# 真っ先に読みたい、という人は私が本棚に置く前に取りに来てね。

- o -

というメールを tech ML に流した直後、 KLabセキュリティの Windows プログラミングが得意な S さんが取りに来ました。 彼は日頃、マーケティングな人達から無理難題 ;) を押しつけられて 困っているので、 マーケティングの基礎を学ぶことはきっと役にたつことでしょう。

こういう本を読んで、 マーケティングとはそもそもどんなことなのか知っておかないと、 同じプロジェクトで一緒に働いているマーケ担当な人が、 マーケタとしてどのくらい優秀な人なのか、 あるいはダメな人か分かりませんよね?

同僚が優秀なマーケタなら、 その人の言う事を信じて開発方針を決めてかまわないのですが、 そうでないマーケタの場合は、 言う事を鵜呑みにするのは危険ですよね? 信じる者は足をすくわれてしまうでしょう。 そういう場合は、鵜呑みにせずに、 他の人の意見を聞くなり、自分で考えるなりしなくてはなりません。

あるいは立場を逆にして考えてみると分かりやすいかも知れません。

例えば、技術の事が全く分からない人が、 技術者に開発を依頼をするとき、 その技術者がどのくらい優秀な人か分からないと、 その人が週間で開発できる、 という言葉を信じていいのか良くないのか判断できませんよね? できる、と言ったから信じたのに~、と言っても後の祭です。

だから技術者でない人も、 相手がどのくらい優秀な技術者か判断できる程度には、 技術の基本を網羅的に押えている必要がありますし、 その逆に、マーケティングの専門家でない人も、 相手がどのくらい優秀なマーケターか判断できる程度には、 マーケティングの基本を網羅的に押えている必要がある、 ってことだと思います。

Filed under: 元CTO の日記,技術と経営 — hiroaki_sengoku @ 18:07
2006年4月8日

stone 1.0

stone の最初のバージョン。 11年前 Version 1.0 を出したときは、わずか 278行だった (現在の Version 2.3a では、9100行を超えている)。

/*
 * stone.c        simple repeater
 * Copyright(C)1995 by Hiroaki Sengoku <sengoku@virgo.bekkoame.or.jp>
 * Version 1.0        Jan 28, 1995
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with GNU Emacs; see the file COPYING.  If not, write to
 * the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * Usage: stone <st> [-- <st>]...
 * <st> := <screen> [<hosts>...] | <host>:<port> <sport> [<hosts>...]
 *
 * (1) Any packets received by <screen> are passed to DISPLAY
 * (2) Any packets received by <sport> are passed to <host>:<port>
 * (3) as long as these packets are sent from <hosts>...
 * (4) if <hosts> are not given, any hosts are welcome.
 */

#include        <stdio.h>
#include        <stdlib.h>
#include        <string.h>
#include        <errno.h>
#include        <sys/types.h>
#include        <sys/time.h>
#include        <sys/socket.h>
#include        <netinet/in.h>
#include        <netdb.h>
#define        BACKLOG_MAX        5

#define XPORT                6000
#define BUFMAX                256

#define STONEMAX        (FD_SETSIZE/3)        /* max # of stones */
typedef struct {
    int sd;        /* socket descriptor to listen */
    struct sockaddr_in sin;        /* destination */
    int nhosts;                        /* # of hosts */
    struct in_addr xhosts[0];        /* hosts permitted to connect */
} Stone;

/* *addrp is permitted to connect to *stonep ? */
int checkXhost(stonep,addrp)
Stone *stonep;
struct in_addr *addrp;
{
    int i;
    if( !stonep->nhosts ) return 1; /* any hosts can access */
    for( i=0; i < stonep->nhosts; i++ ) {
        if( addrp->s_addr == stonep->xhosts[i].s_addr ) return 1;
    }
    return 0;
}

/* *stonep accept connection */
void doaccept(stonep,fdsp,pair)
Stone *stonep;
fd_set *fdsp;
int *pair;
{
    struct sockaddr_in from;
    int nsd, dsd;
    int len;
    len = sizeof(from);
    nsd = accept(stonep->sd,(struct sockaddr*)&from,&len);
#ifdef DEBUG
    printf("Accept: %x port %d ...",
           ntohl((unsigned long)from.sin_addr.s_addr),ntohs(from.sin_port));
#endif
    if( !checkXhost(stonep,&from.sin_addr) ) {
#ifdef DEBUG
        printf("denied.\n");
#endif
        if( nsd >= 0 ) close(nsd);
        return;
    }
#ifdef DEBUG
    printf("accepted.\n");
#endif
    if( nsd < 0 ) {
        if( errno == EINTR ) return;
        fprintf(stderr,"Accept error.\n");
        return;
    }
    if( (dsd=socket(PF_INET,SOCK_STREAM,0)) < 0 ) {
        fprintf(stderr,"Cannot create socket.\n");
        close(nsd);
        return;
    }
    if( connect(dsd,(struct sockaddr*)&stonep->sin,sizeof(stonep->sin)) < 0 ) {
        fprintf(stderr,"Cannot connect socket.\n");
        close(nsd);
        if( dsd >= 0 ) close(dsd);
        return;
    }
    pair[nsd] = dsd;
    pair[dsd] = nsd;
    FD_SET(nsd,fdsp);
    FD_SET(dsd,fdsp);
}

void repeater(nstones,stones)
int nstones;        /* # of stones */
Stone *stones[];
{
    int sdmax;
    int pair[FD_SETSIZE];
    fd_set fds, rfds;
    int width, i;
    char buf[BUFMAX];
    int nbyte;
    sdmax = stones[nstones-1]->sd + 1;        /* sd of last stone + 1 */
    FD_ZERO(&fds);
    for( i=0; i < nstones; i++ ) FD_SET(stones[i]->sd,&fds);
    width = ulimit(4,0);
    for( i=0; i < width; i++ ) pair[i] = -1;
    while( rfds=fds, select(width,&rfds,NULL,NULL,NULL) > 0 ) {
        for( i=0; i < width; i++ ) {
            if( FD_ISSET(i,&rfds) ) {
                if( i < sdmax ) doaccept(stones[nstones-sdmax+i],&fds,pair);
                else if( (nbyte=read(i,buf,BUFMAX)) > 0 )
                    write(pair[i],buf,nbyte);
                else {
#ifdef DEBUG
                    printf("shutdown %d, close %d\n",pair[i],i);
#endif
                    if( pair[i] >= 0 ) {
                        shutdown(pair[i],2);
                        pair[pair[i]] = -1;
                    }
                    pair[i] = -1;
                    close(i);
                    FD_CLR(i,&fds);
                }
            }
        }
    }
}

void host2addr(name,addrp,familyp)
char *name;
struct in_addr *addrp;
short *familyp;
{
    struct hostent *hp;
    if( hp=gethostbyname(name) ) {
        bcopy(hp->h_addr,(char *)addrp,hp->h_length);
        if( familyp ) *familyp = hp->h_addrtype;
    } else if( (addrp->s_addr=inet_addr(name)) != -1 ) {
        if( familyp ) *familyp = AF_INET;
    } else {
        fprintf(stderr,"Unknown host : %s\n",name);
        exit(1);
    }
}

/* make stone */
Stone *mkstone(dhost,dport,port,nhosts,hosts)
char *dhost;        /* destination hostname */
int dport;        /* destination port */
int port;        /* listening port */
int nhosts;        /* # of hosts to permit */
char *hosts[];        /* hosts to permit */
{
    Stone *stonep;
    struct sockaddr_in sin;
    int i;
    stonep = calloc(1,sizeof(Stone)+sizeof(struct in_addr)*nhosts);
    if( !stonep ) {
        fprintf(stderr,"Out of memory.\n");
        exit(1);
    }
    stonep->nhosts = nhosts;
    bzero((char *)&sin,sizeof(sin)); /* clear sin struct */
    sin.sin_family = AF_INET;
    sin.sin_port = htons(port);        /* convert to network byte order */
    host2addr(dhost,&stonep->sin.sin_addr,&stonep->sin.sin_family);
    for( i=0; i < nhosts; i++ ) {
        host2addr(hosts[i],&stonep->xhosts[i],NULL);
#ifdef DEBUG
        printf("permit %x to connecting to %x:%d\n",
               ntohl((unsigned long)stonep->xhosts[i].s_addr),
               ntohl((unsigned long)stonep->sin.sin_addr.s_addr),dport);
#endif
    }
    stonep->sin.sin_port = htons(dport);
    stonep->sd = socket(AF_INET,SOCK_STREAM,0);
    if( stonep->sd < 0 ) {
        fprintf(stderr,"Can't get socket.\n");
        exit(1);
    }
    if( bind(stonep->sd, (struct sockaddr*)&sin, sizeof(sin)) ) {
        fprintf(stderr,"Can't bind.\n");
        exit(1);
    }
    listen(stonep->sd,BACKLOG_MAX);
#ifdef DEBUG
    printf("stone%3d:%s:%d <- %d\n",stonep->sd,dhost,dport,port);
#endif
    return stonep;
}

help(com)
char *com;
{
    fprintf(stderr,
            "Usage: %s <st> [-- <st>]...\n"
            "   st: <screen> [<hosts>...]"
            "| <host>:<port> <port> [<hosts>...]\n"
            ,com);
    exit(1);
}

int getdist(p,portp)
char *p;
int *portp;
{
    while( *p ) {
        if( *p == ':' ) {
            *p++ = '\0';
            *portp = atoi(p);
            return 1;
        }
        p++;
    }
    return 0;
}

main(argc,argv)
int argc;
char *argv[];
{
    int nstones;        /* # of stones */
    Stone *stones[STONEMAX];
    int i, j, k;
    char display[256], *p, *q;
    char *disphost, *host;
    int dispport, port, sport;
    p = getenv("DISPLAY");
    if( p ) {
        strcpy(display,p);
        getdist(display,&dispport);
        disphost = display;
        dispport += XPORT;
    } else {
        disphost = NULL;
    }
    if( argc < 2 ) help(argv[0]);
    setbuf(stdout,NULL);
    nstones = 0;
    for( i=1; i < argc; i++ ) {
        if( getdist(argv[i],&port) ) {
            host = argv[i++];
            if( argc <= i ) help(argv[0]);
            sport = atoi(argv[i++]);
        } else {
            host = disphost;
            port = dispport;
            sport = XPORT+atoi(argv[i++]);
        }
        j = 0;
        k = i;
        for( ; i < argc; i++, j++ ) if( !strcmp(argv[i],"--") ) break;
        stones[nstones++] = mkstone(host,port,sport,j,&argv[k]);
    }
    q = argv[argc-1] + strlen(argv[argc-1]);
    for( p=argv[1]; p < q; p++ ) *p = '\0';
    repeater(nstones,stones);
}
Filed under: stone 開発日記 — hiroaki_sengoku @ 11:45
2006年4月7日

adsl-stop

今朝未明、bフレッツのメンテナンス工事があって、 GCDの対外線が切れた。 切れたこと自体は事前に NTT からアナウンスがあったことだし 問題はないのだが、

bフレッツが切れたことにより PPPoE を行っている pppd が終了すると、 /etc/ppp/adsl-lost が実行される。 GCD の adsl-lost は次のようになっている:

#!/bin/sh
PATH=/sbin:/usr/sbin:/bin:/usr/bin:/usr/local/sbin:/usr/local/bin
. /etc/rc.d/hostname
MASTER_FILE="/var/run/keepalived_vrrp.MASTER"
if [ -f $MASTER_FILE -a "$1" = "/etc/ppp/pppoe.conf" ]; then
    rm -f $MASTER_FILE
    ERROR_FILE="/var/run/keepalived_vrrp.ERROR"
    echo 2 >> $ERROR_FILE
    /command/svc -t /service/keepalived/
fi

つまり、/var/run/keepalived_vrrp.MASTER を削除したうえで keepalived を再起動する。 すると、keepalived は BACKUP 状態に移行する。 すると、

/etc/rc.d/vrrp_notify INSTANCE VI BACKUP

実行されて、vrrp_notify が adsl-stop を呼び出す。

そして、adsl-stop が adsl-connect が強制終了する。 こうして完全な BACKUP 状態になるはずだった。

ところが

今朝起きて見てみると、 ゲートウェイが両方とも BACKUP 状態になっていた。(*_*)

片方のゲートウェイは PPPoE がつながっていて、 インターネットに正常に接続しているのだが、 あいにく BACKUP 状態なので、 GCD LAN のデフォルトゲートウェイアドレスである 192.168.1.1 を 持っておらず、 GCD LAN の他のマシンからインターネットへ接続できない状態に 陥っていた。

どうしてこんな事態になってしまったのか考えるより、 まずは復旧が優先ということで、 PPPoE がつながっているのに BACKUP 状態となっている 中途半端なゲートウェイ上で adsl-connect プロセスを殺し、 keepalived を再起動させて、 完全な BACKUP 状態へ移行させた。

すると、もう片方のゲートウェイが自動的に MASTER へ昇格して、 無事正常な状態に戻った。

その後、原因を調べていると、 前述したように adsl-stop が adsl-connect を強制終了させることが できずに adsl-connect が走り続けていたことが原因と判明。 つまり、adsl-connect が走り続けたために、 BACKUP 状態であるにもかかわらず PPPoE のリトライを行っていた。 そして bフレッツのメンテナンス工事の終了にともなって PPPoE が成功、 BACKUP 状態なのに PPPoE でつながっている、という状態が発生した。 この状態では、正常にインターネットにつながっているために、 MASTER に昇格することはなく、BACKUP 状態が継続する。

一方、もう片方のゲートウェイは、MASTER に昇格しようとして PPPoE を試みても失敗してしまう。 つまり MASTER へ昇格できない。 したがって、両方のゲートウェイが BACKUP 状態のままになる。

というわけで問題は adsl-stop が adsl-connect を 終了させることができない点にありそうだと推測。 あらためて adsl-stop を読みなおすと...

    # Kill pppd, which should in turn kill pppoe
    if [ -r "$PPPD_PIDFILE" ] ; then
        PPPD_PID=`cat "$PPPD_PIDFILE"`
        $LOGGER -p daemon.notice "Killing pppd"
        echo "Killing pppd ($PPPD_PID)"
        kill $PPPD_PID > /dev/null 2>&1 || exit 1
    fi
    ....
    # Kill adsl-connect
    $LOGGER -p daemon.notice "Killing adsl-connect"
    echo "Killing adsl-connect ($PID)"
    kill $PID > /dev/null 2>&1

以前このスクリプトを読んだときは見落としていたのだが、 「kill $PPPD_PID」の後に「|| exit 1」がついている! なんだこれは!?

つまり、pppd プロセスが動いていなければ exit してしまうので、 adsl-connect を終了させないではないか! 誰だ、こんな頭悪いコードを書いた奴は。

adsl-connect は pppd が失敗すると、 リトライするまえに 5秒 sleep する。 つまりこの 5秒間は pppd プロセスが無い。 だから、運悪く sleep 中に adsl-stop を実行すると、 adsl-connect が強制終了されずに残ってしまう。

速攻で adsl-stop から「|| exit 1」の部分を削除した。 これで安心 (だといいな)。

Filed under: システム構築・運用 — hiroaki_sengoku @ 11:56
2006年4月7日

DB サーバのセキュリティ向上策 (5)

前々回(3)前回(4) と、 他人の DB パスワードを知ったユーザが、 その人になりすまして DB にログインしようとしても DBサーバへの通信を遮断する仕掛けについて説明しました。 (3) はクライアント側で、(4) はサーバ側で、 それぞれ DBサーバへの通信を監視し、 UNIX ユーザ名と、DB ユーザ名が一致しない場合は通信を遮断します。

DB サーバへの通信から DB ユーザ名を抽出する方法は、 DB サーバの種類によって異なるので、 専用のものを実装することになるのですが、 今回はその一例として MySQL 4.0.x の場合について説明しましょう。

MySQL の場合は、ソースが公開されていますし、 開発者向けにはプロトコル仕様書もあるようなので、 クライアント-サーバ間通信を完全に解析することも可能なのですが、 DBユーザ名を取り出す程度なら通信内容をダンプするだけでも 取り出し方が推測できます。

まずは手始めに通信内容をダンプしてみましょう。 どんなツールを使ってもよいのですが、 ここでは拙作 stone を使ってみます。 適当なポートで listen し、 それを MySQL サーバへ中継しつつ、 通信内容をダンプするように stone を実行します。 例えば、12345番ポートで listen し、 「-ppp」オプションを指定することによって通信内容をダンプさせ、 ホスト「dbserver」の 3306番ポートへ中継させるためには、 次のように stone を実行します。

stone -ppp dbserver:3306 12345

そして、MySQL のクライアントである mysql コマンドを実行します。 MySQL サーバに接続する代わりに、 stone が listen する 12345番ポートに接続させましょう。

mysql -P12345 -h127.0.0.1 -usengoku -pabcdefg

DB ユーザ名「sengoku」、パスワード「abcdefg」で接続します。 この時、stone の出力は次のようになります:

% stone -ppp dbserver:3306 12345
Apr  7 07:04:45.525293 16384 start (2.3a) [19635]
Apr  7 07:04:45.530733 16384 stone 3: senri.gcd.org:3306 <- 0.0.0.0:12345
Apr  7 07:05:03.405877 16384 stone 3: accepted TCP 5 from 127.0.0.1:37758 mode=3

Apr  7 07:05:03.417693 16384 3 5<6 34 00 00 00 0a 34 2e 30  4....4.0
Apr  7 07:05:03.417785 16384 3 5<6 2e 32 35 2d 73 74 61 6e  .xx-stan
Apr  7 07:05:03.417802 16384 3 5<6 64 61 72 64 2d 6c 6f 67  dard-log
Apr  7 07:05:03.417813 16384 3 5<6 00 e8 7e 00 00 2d 3a 79  ..~..-:y
Apr  7 07:05:03.417823 16384 3 5<6 75 5e 58 7e 31 00 2c 20  u^X~1.,
Apr  7 07:05:03.417833 16384 3 5<6 0c 02 00 00 00 00 00 00  ........
Apr  7 07:05:03.417844 16384 3 5<6 00 00 00 00 00 00 00 00  ........
Apr  7 07:05:03.418337 16384 3 5>6 15 00 00 01 85 24 00 00  .....$..
Apr  7 07:05:03.418374 16384 3 5>6 00 73 65 6e 67 6f 6b 75  .sengoku
Apr  7 07:05:03.418385 16384 3 5>6 00 53 4d 5c 4a 55 53 49  .SM\JUSI
Apr  7 07:05:03.418393 16384 3 5>6 57                       W
Apr  7 07:05:03.418643 16384 3 5<6 05 00 00 02 00 00 00 02  ........
Apr  7 07:05:03.418671 16384 3 5<6 00                       .

各行始めの「Apr 7 07:04:45.525293」は時刻で、マイクロ秒単位で表示しています (マイクロ秒単位まで表示するようになったのは stone 2.3a からです)。 次の「16384」はスレッドID ですが、今回は無視してもらって構いません。 最初の三行は stone のログなので無視するとして、 次の部分が MySQL サーバからクライアントへ送られた通信内容です。 「5<6」が、サーバ→クライアント方向の通信であることを示します。

Apr  7 07:05:03.417693 16384 3 5<6 34 00 00 00 0a 34 2e 30  4....4.0
Apr  7 07:05:03.417785 16384 3 5<6 2e 32 35 2d 73 74 61 6e  .xx-stan
Apr  7 07:05:03.417802 16384 3 5<6 64 61 72 64 2d 6c 6f 67  dard-log
Apr  7 07:05:03.417813 16384 3 5<6 00 e8 7e 00 00 2d 3a 79  ..~..-:y
Apr  7 07:05:03.417823 16384 3 5<6 75 5e 58 7e 31 00 2c 20  u^X~1.,
Apr  7 07:05:03.417833 16384 3 5<6 0c 02 00 00 00 00 00 00  ........
Apr  7 07:05:03.417844 16384 3 5<6 00 00 00 00 00 00 00 00  ........

MySQL のバージョン番号らしきものが送られている様子が分かります。 ここでの目的は DB ユーザ名を抽出することであり、 DB ユーザ名は、クライアント→サーバ方向に送られるはずなので、 サーバ→クライアント方向の通信は無視してしまっても構わないのですが、 先頭の「34 00」には注目しておいた方がよいでしょう。 この手のクライアント-サーバ間プロトコルでは、 先頭にこれから送るデータの長さを送信することが一般的だからです。 実際、「34 00」は十進数で言うと 52 ですが、 クライアントへ送られたデータは 56 バイトなので、 先頭 2 バイトがデータ長、次の 2 バイト「00 00」がデータの種別、 残りの 52 バイトがデータだろう、と推測できます。

続く部分が、クライアントからサーバへ送られた通信内容です。 「5>6」が、クライアント→サーバ方向の通信であることを示します。

Apr  7 07:05:03.418337 16384 3 5>6 15 00 00 01 85 24 00 00  .....$..
Apr  7 07:05:03.418374 16384 3 5>6 00 73 65 6e 67 6f 6b 75  .sengoku
Apr  7 07:05:03.418385 16384 3 5>6 00 53 4d 5c 4a 55 53 49  .SM\JUSI
Apr  7 07:05:03.418393 16384 3 5>6 57                       W

先頭 2 バイト「15 00」がデータ長であると仮定してみます。 十進数で言うと 21 で、 データ種別と推測される 2 バイトデータ「00 01」の後に 21 バイトのデータが続いています。 そして、データの中に明らかに DB ユーザ名と思われる文字列「sengoku」と そのデリミタと思われる「00」があります。 続く 8 バイトはパスワードと推測できます。 DB ユーザ名の前の 5 バイトのデータ「85 24 00 00 00」は、 固定長のデータと推測できるので、 DBユーザ名を抽出するには、 クライアント→サーバ方向のデータのうち、 最初から 10 バイト目から「00」までを取り出せば良さそうです。

もちろん実際に運用する際は、MySQL のソースないしプロトコル仕様書を 参照して確実を期するべきですが、 プロトコル仕様が公式には公開されていない DB サーバの場合でも、

  • DB ユーザ名が取り出せなかった場合は、ログに出力した上で通信を容認
  • DB ユーザ名が取り出せたが、その DB ユーザ名が DB に存在しない場合は、 ログに出力した上で通信を容認
  • DB ユーザ名が取り出せて、かつその DB ユーザ名が存在する場合は、 取り出した DB ユーザ名が正しいものとして扱い、通信の許可/遮断を行なう

などと、DB ユーザ名を正しく取り出せなかったと思われるときは とりあえず通信を容認しておいて、 後ほど DB ユーザ名の抽出方法を改訂する、という運用が可能でしょう。

なお、MySQL の場合は前述したように公式なプロトコル仕様書もありますし、 Ian Redfern 氏がソースを解析して作成したプロトコルの解説が 以下のページで公開されています。

MySQL Protocol (MySQL 3.22 ~ 4.0)
http://www.redferni.uklinux.net/mysql/MySQL-323.html
MySQL Protocol (MySQL 4.1)
http://www.redferni.uklinux.net/mysql/MySQL-Protocol.html
Filed under: stone 開発日記,システム構築・運用 — hiroaki_sengoku @ 09:35
2006年4月6日

GCDバックアップ回線の監視(3)

GCDバックアップ回線用のフレッツADSL では、 バックアップ回線ということで固定IPアドレスにはしていない。 どのくらい安定してつながっているか過去 10日間の統計を取ってみた。

毎分 ssh のテストログイン 14400回(=10*24*60)の結果:

エラー回数 112回
IPアドレスの切換回数 114回
IPアドレス平均持続時間 123.6分
持続時間の標準偏差(σ) 201.2分
持続時間の中央値 39分
最短持続時間 1分
最長持続時間 1385分

思ったより IPアドレスの切換が激しい。 一時間も持続しないIPアドレスが半数以上にのぼる。 10時間以上安定していたのは 4例だけ。

Filed under: システム構築・運用 — hiroaki_sengoku @ 19:48
2006年4月6日

DB サーバのセキュリティ向上策 (4)

前回は、 DB サーバへの通信を監視して、 UNIX でのユーザ権限と DB ユーザ名が一致する時に限り 通信を許可する方法について説明しました。

アクセス元となるクライアント側のマシンならば、 どのユーザがアクセスしているか容易に調べられるので、 通信の監視もアクセス元で行なった方が簡単です。

しかしながらこの方法では、 データセンタ内の全てのサーバで通信の監視を行わなければならず、 やや繁雑です。 また、UNIX でのユーザ権限ごとのアクセスコントロールを 実現する方法として、 前回は各ユーザごとに専用の UNIX ドメインソケットを用意し、 そのパーミッション設定でコントロールする方法を紹介しましたが、 当然のことながら DB アクセスを行うクライアントプログラムが UNIX ドメインソケットをサポートしていなければなりません。

DB サーバへの通信の監視をアクセス先、 つまり DB サーバー側で行うことができれば、 クライアント側は監視が行われていることを意識する必要がなくなります。 つまり前回説明した「入口規制」によるアクセス制限ですね。 クライアント側から見れば、 普通に DBサーバへ TCP 接続するだけなので、 どのようなクライアントプログラムでも利用可能でしょう。

この入口規制を実現するには、 どのユーザ権限でクライアントプログラムが動いているのか、 DB サーバ側から調べる必要があります。 しかしながら、TCP 接続ではサーバ側は送られてきた パケットを受け取るだけなので、 そのパケットを誰が送信したかまでは関知しません。 そこで、通信相手が誰なのか調べる方法を定めたのが、 RFC1413 で提案された IDENT プロトコルです。 IDENT プロトコルは、 受け取った TCP パケットに記録されている 送信元の IP アドレスとポート番号を手がかりに、 相手が誰かクライアント側へ問い合わせます。

IDENT プロトコルは、あまり使われなくなって久しいので、 まず簡単に紹介しましょう。

ホスト CLIENT からホスト SERVER の 12345番ポートへアクセスした 場合について考えます。 その接続状況は次のように netstat コマンドなどで確認できて、

SERVER % netstat
Proto Recv-Q Send-Q  Local Address  Foreign Address  State
tcp        0      0  SERVER:12345   CLIENT:45486     ESTABLISHED

この場合だと、CLIENT の 45486番ポートから SERVER の 12345番ポートへの TCP 接続が確立している (ESTABLISHED) ことが分かります。

このとき、SERVER は CLIENT に IDENT 問合せをすることによって、 CLIENT のどのユーザが TCP 接続の相手か調べることができます。

SERVER % telnet CLIENT 113
Connected to CLIENT.
45486,12345                                 … (1)
45486 , 12345 : USERID : OTHER :sengoku     … (2)
Connection closed by foreign host.

(1) が SERVER から CLIENT への問合せで、 送受信双方のポート番号の対を送信しています。 これに対し、(2) が CLIENT から SERVER への応答で、 TCP 接続の相手が「sengoku」であることが分かりました。

このように、通信相手が誰か手軽に確認できる便利なプロトコルなのですが、 相手ホストが信用できなければ当然 IDENT 応答も信じることはできません。 不特定多数のホストからの通信を受け付ける場合は、 IDENT 問合せを行なっても「正直な」応答が返ってくるとは期待できないわけで、 少なくともインターネット上ではあまり使われなくなったようです。

しかし データセンタのラック内のサーバであれば、 当然管理者が決まっているでしょうから、 全てのサーバで正しく IDENT 応答を返すように徹底することは容易です。 したがって、 IDENT プロトコルを使った入口規制を実施することができます。

まず DB サーバを、UNIX ドメインソケットでのみアクセスを受け付ける ように設定します。 このソケットは、local ユーザが勝手にアクセスしたりしないよう、 DB サーバを実行するユーザ権限でのみ読み書きできるように 設定しておくとよいでしょう。

次に、特定のポートで listen し、接続を受け付けたら この UNIX ドメインソケットへ中継するプログラムを、 DB サーバを実行するユーザと同じユーザ権限で走らせます。 この中継プログラムは、 中継する際に接続元ホストに対して IDENT 問合せを行ない、 IDENT 応答で得たユーザ名と、 通信を監視することによって得られた DB ユーザ名が 一致しない場合は通信を遮断するというわけです。

このような仕掛けにより、 クライアント側のユーザは、 仕掛けを意識することなく DB サーバにアクセスすることができます。 そして、 何かのはずみに別の DBユーザのパスワードを知り、 その DBユーザになりすましてアクセスしようとしても 通信は遮断されます。

Filed under: システム構築・運用 — hiroaki_sengoku @ 12:25
2006年4月5日

DB サーバのセキュリティ向上策 (3)

一般に、マシン C からマシン S へのアクセスを制限するには、

(IN) マシン S でアクセスを受け付けるところで制限する「入口規制」
(OUT) マシン C でアクセスを出すところで制限する「出口規制」

の2通りの方法があります。一般にアクセス制限というと (IN) の入口規制を用いることが多いのですが、 これはアクセスを出すマシンが特定できないか、 あるいは特定はできるのだけどマシンの数ないし種類が多過ぎるためです。

例えば、マシン S へのログインを制限したい場合を考えてみましょう。 一般的にはアクセス元となりうるマシンは特定できないか、 特定できたとしても数が多いでしょうし、 そのマシンで動く OS の種類も多種多様でしょう。 アクセス元となりうるマシンの全てで、 アクセスを出すことを制限しようとするのは非現実的です。 なので普通はアクセスを受け付ける側のマシン S で クライアントの認証を行なってアクセス制限をします。

例えば ssh の場合ならどこのマシンからのアクセスであっても、 秘密鍵を持っていることを証明できるならアクセスを許可し、 そうでなければ却下します。 アクセスを出す側のマシンでは何の出口規制もなく、 アクセスを受け付ける側のマシンでの入口規制だけですね。

ところが、データセンタのラック内のマシンに限定すれば、 事情は変わってきます。 ラック内に入れてあるマシンの数は高々有限 ;) ですし、 多くの場合全てのマシンで同じ OS が動いているでしょうし、 管理の都合からいえば全てのマシンを同じチームが管理すべきでしょう。

このような特殊な環境下では、 (OUT) の出口規制も現実的な方法となります。 例えば DB サーバへのアクセスを考えると、 DB サーバは通常のサーバ認証のみであっても、 クライアント側でアクセスを出すのを制限する出口規制を設けることによって、 セキュリティを向上させることができます。

クライアント側の OS が Linux であれば、 出口規制の方法として iptables の OUTPUT チェインを使うことができます。 例えば DB サーバが 12345番ポートでアクセスを受け付けているならば、 ラック内の全マシンにおいて

iptables -N dbaccess 2>/dev/null || iptables -F dbaccess
iptables -A dbaccess -j ACCEPT -m owner --uid-owner dbuser  …(2)
iptables -A dbaccess -j DROP                                …(3)

iptables -A OUTPUT -j dbaccess -p tcp --dport 12345 --syn   …(1)

を実行しておきます。

DB サーバへアクセスを出そうとして、 DB サーバが動いているマシンの 12345番ポートへの接続しようとすると、 まず OUTPUT チェインの (1) のルールによって dbaccess チェインへジャンプします。 アクセスを出そうとしたユーザが dbuser であれば、 dbaccess チェインの (2) のルールによって通信が許可されますが、 dbuser 以外のユーザであれば、 (3) のルールによって通信が却下されます。

つまり、dbuser 以外のユーザは、たとえ DB のパスワードを知っていても、 DB にアクセスできない、というわけです。

DB にアクセスしたいユーザが増えてくると、 そのたびに iptables の設定を変更するのは面倒ですので、 DB サーバへ接続できるユーザは一つだけ (例えば dbuser) にしておいて、 そのユーザ権限で中継プログラムを動かすようにすると便利でしょう。

例えば、DB にアクセスしたいユーザ user1, user2, user3, user4 が あるとします。 まず、DB 上に同名のユーザアカウントを作ります。 次に、ラック内の全サーバにおいて、 ユーザ毎に UNIX ドメインソケットを listen し、 そのソケットへ接続があったら、 それを DB サーバへ中継するプログラム (ここでは dbrelay と呼ぶことにしましょう) を動かします。 例えば次のようなソケットをオープンすることになるでしょう。

/home/user1/socket
/home/user2/socket
/home/user3/socket
/home/user4/socket

各ソケット (あるいはソケットを置いてあるディレクトリ) は、 自分以外のユーザは読み書きできないように パーミッションを設定します。 つまり dbrelay は root 権限で起動する必要があります。 さらに、前述したように DB サーバへアクセスするには dbuser ユーザ権限で実行する必要がありますから、 各ソケットをオープンした後は、 dbuser ユーザ権限に setuid する必要があります。

そして、ここが肝なのですが、 dbrelay は DB サーバへの通信を監視し、 接続を受け付けたソケットのユーザ名と、 DB へのアクセスを行なう DB ユーザ名が一致しない場合は 通信を遮断します。

以上のような仕掛けにより、 例えばユーザ user1 は、 /home/user1/socket へ接続することによって DB サーバへアクセスできるが、 そのとき使用できる DB ユーザ名は user1 だけで、 それ以外のユーザ名で DB へアクセスしようとしても dbrelay によって 通信を遮断されることになります。 しかも /home/user1/socket は user1 以外は読み書きできません。 つまり、各ユーザは自身のユーザ名以外では (仮に他の DB ユーザのパスワードを知っていたとしても) DB へアクセスできない ということになります。

Filed under: システム構築・運用 — hiroaki_sengoku @ 08:39
2006年4月5日

人の上に立とう (2) hatena_b

日経ソフトウェアの大森敏行氏の記事中の注釈に、

プログラマが「プログラミングに特別な能力が必要であること」を 強調したがるのには理由がある。 現在の日本のソフトウエア業界には 「プログラミングをしない人のほうが立場が上である」というケースが 往々にして見られ, そのせいでプログラムの質が上がらないという構造的な問題があるからだ。」

というくだりがある。

確かに一理あるし、 優秀なプログラマがエキスパートとして尊敬される土壌づくりは、 優れたソフトウェアを開発するために必須の条件であろう。 しかしながら、 「プログラミングをしない人のほうが立場が上」というのは 一面的な見方ではないのか。 プログラマ兼 CTO である私としては一言いいたくなる。;-)

「プログラミングをしない人」⇒「立場が上」

なのではなくて、

「プログラミングをする人」⇒「視野が狭い人が多い」
「視野が狭い」⇒「立場が下」

に (ムリヤリな) 三段論法を適用しているだけなのではないのだろうか。 自身の視野の狭さを棚に上げておいて、 「プログラマだから立場が低い」と嘆くのはいかがなものかと思う。 もちろん、 「プログラミングに特別な能力が必要」なのではなくて、 「優れたプログラムを書くには特別な能力が必要」が正しい。 「下っ端プログラマ」と「優秀なプログラマ」を同列に扱ってはいけない。

前述した大森氏の注釈は、次のように続く:

この問題に関しては「Life is beautiful」というブログの 「ソフトウェアの仕様書は料理のレシピに似ている」というエントリが参考になる。

さっそくこの中島聡氏のブログを 読んでみた。

ソフトウェアのアーキテクトが自らプログラムを書いたり、 下っ端のエンジニアの書いたコードをレビューするのは、 レストランのシェフが自ら料理をしたり、 下っ端の料理人の作ったスープの味見をするとの同じである。

とても適切な喩えのように思える。 しかしこの伝で行けば、

下っ端のプログラマ(料理人)がアーキテクト(シェフ)になれるかは 才能に依存するところが多分にあり、 適性がない人は、いくら努力しても永久に下っ端のまま、 一人月 30万円レベルから抜け出せない。 そういう人はさっさとプログラミング(料理)をあきらめて、 マネジメントをやったほうがいい。 マネジメントなら才能が無くても経験次第でそれなりのレベルにはなる。

というとても残酷ではあるが、 とても実状に近い類推が出てきてしまうのだが...

ちなみに中島氏のブログには

優秀なエンジニアとそうでないエンジニアの生産性は(誇張抜きで) 20対1ぐらいである。

という話が出てくるのだが、私はもっと差があると感じている。 超優秀なプログラマと、下っ端プログラマとでは、 生産性が(誇張抜きで) 3桁違う、 というのが私の主張。 トッププログラマの 1000分の1の生産性しかない、 そもそもプログラミングに適性がない人が 無理矢理プログラマを目指そうとするから、不幸が始まる。 しかも、プログラミングは適性のない人でも時間さえかければ (かろうじて)動くプログラムが作れてしまったりするから余計タチが悪い。

これが料理であれば適性のない人は何日かけたって美味しい料理は作れないわけで、 早い段階で才能の無さを自覚して他の職を探すものだと思うが、 下っ端プログラマにはその自覚がない。 自覚が無いからプログラマへの道をあきらめめようとしない。 仕方ないので 35歳くらいで周囲が引導を渡すわけで、 これが「プログラマ35歳定年説」の正体。

- o -

ここまでの話と全然関係ないが、大森氏の記事に

BASICというプログラミング言語の文法を知ったのは確か中学生のときだし, 高校生のときにはシャープの「MZ-80K2E」というマイコンで 簡単なゲームのプログラムくらいは書いていた。

と書いてあったのが、 実はこの記事を引用しようと思った真の理由だったりする (^^;)。 私も中学一年の終わり頃 BASIC を知り、 高校生のとき MZ-80K2E でゲームを作ったりして遊んでいた。

Filed under: 技術者の成長 — hiroaki_sengoku @ 06:52
« Newer PostsOlder Posts »