sudoポリス「誰だ！ 本番環境でsudo suしてるのは！」

# ……なんて強く言ったりはしませんが

本番環境で `sudo su` や `sudo -s` を使うのはよくない、と私は思います。
異論はあると思いますし、環境にもよると思います。面倒くさいのは確かですし。他の人が管理しているところで「使うべき、使わなければならない」と主張したいわけではありません。

ただ、私が管理している環境では原則、使用禁止にしています。それぐらいよくないと思っています。


# なぜ sudo su はよくないのか

sudo su や sudo -s を使うと、それからはずっと root として作業できます。そうでない場合、何をするにもコマンドのはじめに sudo を書かなければならないわけで、これは本当に面倒くさい。

それでも毎回 sudo を使うのは、ひとえに実行ログを残せるからです。
sudo でコマンドを実行するたび、以下のような内容が `/var/log/secure` に記録されます。

```:/var/log/secure
Aug 17 16:28:38 mx1 sudo:    keys : TTY=pts/1 ; PWD=/home/keys ; USER=root ; COMMAND=/bin/journalctl -xe
Aug 17 16:28:38 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:07 mx1 sudo:    keys : TTY=pts/0 ; PWD=/etc/httpd ; USER=root ; COMMAND=/bin/ls -la /run/httpd
Aug 17 16:29:07 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:07 mx1 sudo: pam_unix(sudo:session): session closed for user root
Aug 17 16:29:13 mx1 sudo:    keys : TTY=pts/0 ; PWD=/etc/httpd ; USER=root ; COMMAND=/bin/ls -la /run
Aug 17 16:29:13 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:13 mx1 sudo: pam_unix(sudo:session): session closed for user root
Aug 17 16:29:21 mx1 sudo:    keys : TTY=pts/0 ; PWD=/etc/httpd ; USER=root ; COMMAND=/bin/ls -la /run/httpd
Aug 17 16:29:21 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:21 mx1 sudo: pam_unix(sudo:session): session closed for user root
Aug 17 16:29:25 mx1 sudo:    keys : TTY=pts/0 ; PWD=/etc/httpd ; USER=root ; COMMAND=/bin/vim /usr/lib/tmpfiles.d/httpd.conf
Aug 17 16:29:25 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:38 mx1 sudo: pam_unix(sudo:session): session closed for user root
Aug 17 16:29:40 mx1 sudo:    keys : TTY=pts/0 ; PWD=/etc/httpd ; USER=root ; COMMAND=/bin/systemctl start httpd.service
Aug 17 16:29:40 mx1 sudo: pam_unix(sudo:session): session opened for user root by keys(uid=0)
Aug 17 16:29:40 mx1 sudo: pam_unix(sudo:session): session closed for user root
```

このログがどう役に立つのでしょうか。主に以下の2点です:

* **問題発生時に原因を追いやすい**<br>
root の権限で動かしたものが時系列で1つのログに残るので、上から見ていくことでどの状態で何をしたことで発生したか、見つけやすくなります。とくに PWD と COMMAND が絶対パスで記録されているので、コマンドの引数に相対パスを使っていても辿ることができます。
* **監査に備える**<br>
ログを残しておくことで、誰がなにをしたのか明らかにしておくことはセキュリティ監査に十分耐えうるでしょう。監査できるということは、もし情報漏洩が発生したときなどに自分が関与していないことの証明になります。

うっかり予防に役立つのも重要です[^ukkari]。
`hostname` など、コマンドが参照と変更を兼ねているものは、うっかりしてしまいがちです。参照するときには sudo を使わなけばうっかりの変更は避けられます。→[「【Linux】インフラエンジニアの僕が「hostname」コマンドを使わない理由とは？」- Qiita](https://qiita.com/satton6987/items/f50ad3df302290d1f544)

[^ukkari]: 一般には、これが sudo を使う一番の理由とされていると思います

おまけですが、自分のシェルのヒストリに履歴が残るので[ヒストリ検索に使える](https://qiita.com/keys/items/0baca41cd8bc6e8a320c)ので、ヒストリから実行できます。あと、きちんと[ヒストリファイルを整理](https://qiita.com/keys/items/d737bb3737e8f02fd930)して、sudo で実行しなくいいもの・してはいけないものを消しておくと、よりミスは減るでしょう。

sudo を毎回使うのは、面倒くさいです。いやあ、それはもう、面倒です。
ただそれでも使う理由がある……というのは、分かってもらえたのではないかと思いますが、どうでしょうか。


# でも無意識に使っちゃうんだよなあ

という人、わかります。面倒くさいですもんね。何度言っても言い足りないぐらい面倒です。

そこで！　`sudo su` や `sudo -s` を使わない癖をつけるための **sudo 養成ギプス** を作りました！
これで毎回 `sudo` を使う癖が付くと思いますよ！(ﾄﾞﾔｧ


## 養成ギプスのコード

軽い気持ちで書き始めたんですが、思ったより長くなってしまいました……[^waste_time]。

[^waste_time]: あほらしいものであればあるほど作るのが楽しいですよね

なお、GitHub にも置いてます。
https://github.com/keioni/qiita-sample/blob/master/sudo_gips.pl

```perl:sudo_gips.pl
#!/usr/bin/perl

use strict;
use warnings;
use Time::HiRes qw(usleep);

# "sudo police" says:
my $msg = "\n*** You ran a shell with root privileges! Arrested! ***\n";

my $wait = 50 * 1000;
my $secure_log = '/var/log/secure';
my $target_user = $ARGV[0] || 'any';
my $target_tty = $ARGV[1] || 'any';

sub arrest
{
    my ( $user, $tty, $cmd ) = @_;
    my $result;
    my @pids;

    # shell が起動する時間を待つ
    # 環境によるので $wait で調整する
    usleep( $wait );

    # 指定の tty にメッセージを送信する
    open( PTTY, '>>', "/dev/$tty" );
    print PTTY $msg;
    close( PTTY );

    # ps コマンドの結果をもとに kill するプロセスを拾う
    print STDERR "exec> ps\n";
    open( PPS, 'ps ao user,tty,pid,cmd |' );
    while ( <PPS> ) {
        my ( $ps_user, $ps_tty, $pid, $cmd ) = split( /\s+/ );
        if (( $ps_user eq 'root' ) && ( $ps_tty eq $tty )) {
            $result = 0;
            push( @pids, $pid );
        }
        else {
            $result = 1;
        }
        print STDERR (( 'kill ', '     ' )[$result]),  $_;
    }
    close( PPS );

    # 拾ったプロセスを kill する
    print STDERR 'exec> kill ', join( ' ', @pids ), "\n";
    kill( 'KILL', @pids );

    # 実行結果を標準出力に出力する
    my $now = localtime();
    print "$now: $user ran $cmd. (at $tty)\n";
}

sub inspect_log
{
    my ( $user, $tty ) = @_;

    # 引数で指定したユーザ、tty のものだけを拾い上げる
    # 同じ行から実行するコマンドも拾い上げる
    if ((( $target_user eq 'any' ) || ( $target_user eq $user ))
        && (( $target_tty eq 'any' ) || ( $target_tty eq $tty )))
    {
        print STDERR "find> sudo: user=$user, tty=$tty\n";
        if ( m{USER=root ; COMMAND=/(usr/)?bin/(su)(\s|$)} ) {
            # コマンドが su の場合
            return $2;
        }
        elsif ( m{USER=root ; COMMAND=/(usr/)?bin/(([a-z]{0,2})sh)(\s|$)} ) {
            # コマンドが shell の場合 (sudo -s)
            return $2;
        }
    }
    return;
}

sub watch_logs
{
    open( PLOG, "tail -n 0 -f $secure_log |" );
    while ( <PLOG> ) {
        # /var/log/secure には sudo 以外のログも含まれる
        # sudo のログだけ拾いたいが正規表現は重いので、まず index で選別する
        next if ( index( $_, 'sudo:' ) < 0 );

        # sudo のログを parse する
        if ( /sudo:\s+([^\s]+)\s+:\s+TTY=([^\s]+) / ) {
            my $user = $1;
            my $tty = $2;
            my $cmd = &inspect_log( $user, $tty );
            if ( $cmd ) {
                # コマンドが su または shellの場合 kill する
                &arrest( $user, $tty, $cmd );
            }
        }
    }
    close( PLOG );
}

&watch_logs()
```

## 養成ギプスの使い方

別のターミナルを開いて、このスクリプトを `sudo` で走らせておきます。

```
sudo perl sudo_gips.pl [user [tty]]
```

引数は以下のとおりです:

* `user`: ギプスの対象にするユーザを指定します。指定しない場合はすべてのユーザを対象にします
* `tty`: ギプスを使う tty を指定します。指定しない場合はすべての tty を対象にします

`w` コマンドを使うと簡単に tty を見つけることができると思います。

```
% w
 03:15:38 up 48 days, 20:14,  2 users,  load average: 0.00, 0.00, 0.00
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
keys     pts/0    210-191-93-11.pp 02:49    1.00s  0.15s  0.00s w
keys     pts/1    210-191-93-11.pp 02:50   11:54   0.03s  0.01s sshd: keys [priv]
```

上の場合では `pts/0` で `w` を実行している様子が分かります。

実行の見本は次の通りです:

```:example
sudo perl sudo_gips.pl keys pts/0
```

これで `pts/0` で `sudo su` してみましょう。

```
devel % sudo su
[root@devel keys]#
*** You ran a shell with root privileges! Arrested! ***
zsh: killed     sudo su
devel %
```

こんな感じで kill されます。
一方、ギプスを動かしている方はこのようなメッセージが出ます。

```
find> sudo: user=keys, tty=pts/0
exec> ps
     USER     TT         PID CMD
     keys     pts/0    11856 -zsh
     keys     pts/1    11990 -zsh
     root     pts/1    12009 sudo perl sudo_gips.pl keys pts/0
     root     pts/1    12010 perl sudo_gips.pl keys pts/0
     root     pts/1    12011 tail -n 0 -f /var/log/secure
kill root     pts/0    12012 sudo su
kill root     pts/0    12013 su
kill root     pts/0    12014 bash
     root     pts/1    12029 ps ao user,tty,pid,cmd
     root     tty1     15854 /sbin/agetty --noclear tty1 linux
     root     ttyS0    15858 /sbin/agetty --keep-baud 115200,38400,9600 ttyS0 vt220
exec> kill 12012 12013 12014
Sun Sep 13 09:47:29 2020: keys ran su. (at pts/0)
```

日付と実行したコマンド以外は `STDERR` に出力しているので、不要なときは `/dev/null` にリダイレクトするなど適当に扱ってください。


## 養成ギプスのポイント

あくまで養成ギプスなので、悪意のある操作を防ぐことはできません。
自分専用の環境で動かすのがお勧めです。共用環境だと user で自分に限定した方がいいでしょう。その方が人間関係に影響を与えずに済むと思います。

無論、本番環境で使ってはだめです。

さあみんな、鍛えていこうぜ！


# sudo を少しでも使いやすく

sudo の面倒くささは、パスワード入力によるところが大きいでしょう。あれはほんとにしんどい。急いでいるときは特にイラッときますよね。

パスワードを頻繁に入力しないといけない、というのは、ログを残すのが目的だとするとそこまで重要ではありません。環境によってはパスワード入力を省略するようにしてもいいかも。→[「sudo のパスワードを入力なしで使うには」- Qiita](https://qiita.com/RyodoTanaka/items/e9b15d579d17651650b7)


# 最後に真面目な sudo の話

sudo su の話は置いておいて、sudo そのものの話をしたいと思います。

sudo を使うことは、セキュリティの強化に繋がるのでしょうか？

たしかに sudo は設定次第で様々な制限をかけることができます。→[「sudo のセキュリティ(ごく一部CentOS7対応)」- Qiita](https://qiita.com/KurokoSin/items/e96475c475ab59b5244f)
しかし設定によっては、技術力を持つ人なら制限を回避できることもあるでしょう。

たとえば先のリンクの設定では `/usr/sbin` 以下のプログラムしか sudo できないようになっています。これはかなりきつい制限ですが、多くの場合このまま使うのは難しいでしょう。運用に必要なプログラムは `/bin` と `/usr/bin` にも多いからです。
といって例外[^exceptions]を加えていった結果、予期せぬ形で `/usr/sbin` のファイルを書き換えたり、追加できるようになってしまうと、制限はすべて回避されてしまいます。

[^exceptions]: `cp` のような分かりやすいものだけではなく、`less` や `find` のように一見問題なさそうなものも危ないので、例外を作る時は油断できません

sudo がセキュリティの強化に繋がるかというと、私は大いに疑問を感じます。
それでもなお sudo を使うのは、ログを残すこと[^log]と、うっかりミスを防止することができるからです。それが sudo の一番の存在価値ではないか、と個人的には思うのです。

[^log]: ログも改ざんできるので、全面的に信頼はできませんが

異論をお持ちの方も多いと思います。ぜひ気軽にコメントをお寄せください。:smile: