……なんて強く言ったりはしませんが
本番環境で sudo su
や sudo -s
を使うのはよくない、と私は思います。
異論はあると思いますし、環境にもよると思います。面倒くさいのは確かですし。他の人が管理しているところで「使うべき、使わなければならない」と主張したいわけではありません。
ただ、私が管理している環境では原則、使用禁止にしています。それぐらいよくないと思っています。
なぜ sudo su はよくないのか
sudo su や sudo -s を使うと、それからはずっと root として作業できます。そうでない場合、何をするにもコマンドのはじめに sudo を書かなければならないわけで、これは本当に面倒くさい。
それでも毎回 sudo を使うのは、ひとえに実行ログを残せるからです。
sudo でコマンドを実行するたび、以下のような内容が /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点です:
-
問題発生時に原因を追いやすい
root の権限で動かしたものが時系列で1つのログに残るので、上から見ていくことでどの状態で何をしたことで発生したか、見つけやすくなります。とくに PWD と COMMAND が絶対パスで記録されているので、コマンドの引数に相対パスを使っていても辿ることができます。 -
監査に備える
ログを残しておくことで、誰がなにをしたのか明らかにしておくことはセキュリティ監査に十分耐えうるでしょう。監査できるということは、もし情報漏洩が発生したときなどに自分が関与していないことの証明になります。
うっかり予防に役立つのも重要です1。
hostname
など、コマンドが参照と変更を兼ねているものは、うっかりしてしまいがちです。参照するときには sudo を使わなけばうっかりの変更は避けられます。→「【Linux】インフラエンジニアの僕が「hostname」コマンドを使わない理由とは?」- Qiita
おまけですが、自分のシェルのヒストリに履歴が残るのでヒストリ検索に使えるので、ヒストリから実行できます。あと、きちんとヒストリファイルを整理して、sudo で実行しなくいいもの・してはいけないものを消しておくと、よりミスは減るでしょう。
sudo を毎回使うのは、面倒くさいです。いやあ、それはもう、面倒です。
ただそれでも使う理由がある……というのは、分かってもらえたのではないかと思いますが、どうでしょうか。
でも無意識に使っちゃうんだよなあ
という人、わかります。面倒くさいですもんね。何度言っても言い足りないぐらい面倒です。
そこで! sudo su
や sudo -s
を使わない癖をつけるための sudo 養成ギプス を作りました!
これで毎回 sudo
を使う癖が付くと思いますよ!(ドヤァ
養成ギプスのコード
軽い気持ちで書き始めたんですが、思ったより長くなってしまいました……2。
なお、GitHub にも置いてます。
https://github.com/keioni/qiita-sample/blob/master/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
を実行している様子が分かります。
実行の見本は次の通りです:
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
最後に真面目な sudo の話
sudo su の話は置いておいて、sudo そのものの話をしたいと思います。
sudo を使うことは、セキュリティの強化に繋がるのでしょうか?
たしかに sudo は設定次第で様々な制限をかけることができます。→「sudo のセキュリティ(ごく一部CentOS7対応)」- Qiita
しかし設定によっては、技術力を持つ人なら制限を回避できることもあるでしょう。
たとえば先のリンクの設定では /usr/sbin
以下のプログラムしか sudo できないようになっています。これはかなりきつい制限ですが、多くの場合このまま使うのは難しいでしょう。運用に必要なプログラムは /bin
と /usr/bin
にも多いからです。
といって例外3を加えていった結果、予期せぬ形で /usr/sbin
のファイルを書き換えたり、追加できるようになってしまうと、制限はすべて回避されてしまいます。
sudo がセキュリティの強化に繋がるかというと、私は大いに疑問を感じます。
それでもなお sudo を使うのは、ログを残すこと4と、うっかりミスを防止することができるからです。それが sudo の一番の存在価値ではないか、と個人的には思うのです。
異論をお持ちの方も多いと思います。ぜひ気軽にコメントをお寄せください。