ssh 自動運転用制限シェルってないのかな

ssh の話題がプチ盛り上がりしてたのにのっかるわけじゃないんですが,自動運転用に実行できるコマンドを制限できるシェルってないものですかね。

候補として考えられるのは

  1. rssh
    • scp, sftp, rsync, rdist, cvs 等に制限できる
    • chroot jail ができる
    • やりたいことに対してできることがちょっと多すぎ
    • でも上記以外のコマンドは実行できない
    • といいつつ重宝してます
  2. ibsh
    • ユーザのやれることを色々制限できるシェル
    • 今回やりたいことのわりには巨大すぎ(っぽくてあんまり調べてない)
  3. authorized_keys の command オプション

何を考えているかというと,

  • 自動運転してやりたいことなんて,scp とか rsync とかなにかしらのコマンド一発(DB のバックアップ)とか,固定化されてることが多い
  • 引数とかもある程度固定化されてることが多い
    • scp とか rsync とかまんべんなく使用できるようにすると scp -S とかでハマりがち
  • さりとてコマンドごとに鍵ペア用意するのはめんどくないすか
    • しかも rsync とかサーバにわたされるコマンドラインしらべるのちょっとめんどいし((-v オプション増やせばローカルでわかりますが,判明したコマンドラインから v を一個とらなきゃいけないですよね))

なので,

  • 設定ファイルで指定したコマンドだけ実行できる
  • 失敗するとエラーを吐く(設定ファイルに反映しやすいように)

というシンプルな仕様のシェルがほしいのです。

あくまで,コンセプトコードですが,

#!/usr/bin/perl
# rrss - Restricted Remote Shell for SSH

use strict;
use warnings;
use NetAddr::IP::Lite qw( :aton );
use Sys::Syslog qw( :standard :macros );
use YAML ();

my ($me) = ( $0 =~ m'([^/]+)$'o );

my $user = $ENV{USER};

my $remote = q{};
if (defined $ENV{SSH_CLIENT} && $ENV{SSH_CLIENT} =~ m'^(\S+)'o) {
    $remote = $1;
}

openlog $me, 'pid', LOG_AUTHPRIV;

eval {
    my $config = eval {
        YAML::LoadFile(sprintf '%s/.rrss/config', $ENV{HOME});
    };
    die 'configuration file not found'
        if ! $config;

    die 'only support remote execution'
        if ! $remote;

    die 'interactive mode is not supported'
        if @ARGV != 2 || $ARGV[0] ne '-c';

    my $command = $ARGV[1];

    my $remote_ip = NetAddr::IP::Lite->new($remote);
    foreach my $conf (@$config) {
        next
            if exists $conf->{host}
            && ! NetAddr::IP::Lite->new($conf->{host})->contains($remote_ip);

        if ($conf->{command} eq $command) {
            syslog LOG_INFO, '[%s@%s] execute: %s', $user, $remote, $command;

            exec $command
                or die "failed: $!";
        }
    }

    die "permission denied: $command";
};
if ($@) {
    my $error = $@;

    # trim error location
    $error =~ s{ \s+ at \s+ .+? \s+ line \s+ \S+ \s* \z }{}xmso;

    $remote ||= 'localhost';

    syslog LOG_CRIT, '[%s@%s] error: %s', $user, $remote, $error;
    print $error, "\n";
}

closelog;

exit 1;

設定ファイル ~/.rrss/config*1 は以下のような感じで。

---
- command: rsync --server -vlogDtprz . target_dir
- command: scp -r -t dest_dir
  host: 192.168.0.0/24
- command: scp -f /etc/passwd
  host: 192.168.0.1

別段 YAML じゃなくてもいいんですが。あと正規表現を使うことも考えましたが,ゆるさのメリットと危うさのデメリットだとデメリットがやや勝つかな,と思い,あえて eq で。

新しいコマンドを設定に追加したかったら,とりあえず実行すると LOG_AUTHPRIV なログが吐かれるのでそれを見て追加する,と(すれば rsyncコマンドラインも与えやすい)。

こんな感じで C で書かれたシェルが欲しい!ひょっとして私が知らないだけで既にあるんですか?それとも bash とかでそういう設定子があるとか?それともそれともそもそもこういう発想には穴があるとか?

おまけ:ssh でのリモートコマンド実行はどのように呼ばれるか

たとえば,

$ ssh -l foo foreign ls -l /tmp

は,リモート側で,

${SHELL} -c "ls -l /tmp"

みたく実行されるに等しいです。

scp, sftp, rsync はデフォルトで ssh をサブプロセスとして利用します。そのときのコマンドライン(第2引数)はどのようになるかを以下書きます。

local remote 備考
scp -r foo user@foreign:bar scp -r -t bar -t (to mode)
scp user@foreign:foo bar scp -f foo -f (from mode)
scp user@foreign:foo user@foreign:bar scp foo user@foreign:bar  
sftp user@foreign /usr/libexec/openssh/sftp-server ※1
rsync -avz foo user@foreign:bar rsync --server -vlogDtprz . bar ※2
rsync -avz user@foreign:foo bar rsync --server --sender -vlogDtprz . foo ※3

scp にしても rsync にしても,ローカルのパスなんて知ったことか,という態度でリモートにコマンドが渡されます。つまり,リモート側ではそれを元にアクセス制限をかけることはできない,ということです。

他,注釈

※1
当たり前といえば当たり前ですが,sftp で制限をかけるのは chroot 等使わないと難しいです。
※2
-v オプションが先頭にきて,-aオプションが展開されます。--server オプションがつきます。
※3
--sender オプションが追加され,<ローカル> <リモート> という引数順が変わらないことに注目。

2007/10/02 追記

authorized_keys の command がちょっと嫌な理由を思い出しました。

リモートからどのようなコマンドを与えても command で指定したコマンドしか実行されません。なので,

$ scp -r contents_1 user@foreign:target_1

に対応する

scp -r -t target_1

を command として authorized_keys に書いておくとします。

で,そのことを忘れて,

$ scp -r contents_2 user@foreign:target_2

とすると,contents_2 の内容が target_1 に上書きされてしまいます。


というのを実は昨日やってしまったんですね。アヒャ。

*1:$HOME においてますけど,/etc/rrss/$USER とかに置くようにしたほうが安全で管理も楽かも