フールプルーフ設計・実装
以前書いた「フールプルーフ呪文図鑑」が好評でしたので続編となります。
0.目的
本番環境の運用でやってはいけない行為を不用意にやっちまう輩が・・・。
そして、やらかす度に運用管理は厳しくなり無駄な稼働を強いられていませんか?
真っ当に仕事している人達からすると迷惑千万此の上無し!
そんな輩を少しでも排除すべく立ち上がりました。
ただし、シェルやその解説があるので長くなっているので読む人は覚悟してください。
また、環境構築手順、試験項目やそのエビデンスも次の記事で記載しましたので、参考にしてください。
1. フールプルーフ(foolproof)とは
「人がミスをしようとしてもできないようにする工夫」のことであり、信頼性設計の一部です。
セキュリティや誤操作(ヒューマンエラー)によるシステムの破損を防ぐために重要です。
1.1. フェイルセーフ(fail safe)との違い
似たようなシーンで使われる言葉にフェイルセーフという言葉もあります。フェイルセーフは「失敗しても安全にする工夫」のことです。
フェイルセーフとフールプルーフは「人はミスをする」を前提としてます。失敗したとしても安全を守るのがフェイルセーフ、そもそも失敗しないようにするのがフールプルーフです。
2. よく聞くやらかしとその対策
sudoersでユーザ毎に実行できるコマンドを制御する方法もありますが、そうするとコマンドを実行できるユーザにsuする輩(suできるユーザも制御すればいいだけですが・・・)がいるので、基本的にprofileにて特定のコマンドを回避する手段を記載しております。
2.1. rm、mv、cpコマンド
やらかしで一番聞くのは、やっぱりファイル削除や移動そして上書きですよね。
私も若い時は、構築中なので影響は少ないものの上書きしてしまって、焦ったことはあります。
root ユーザーは /root/.bashrc でaliasで上書きする前に確認するオプション設定されているので、上書きでやっちまったって事は少ないですが、その他のユーザではalias設定がないので大事なファイルを不用意に削除してしまいトラブルになるケースはちょいちょい聞きますよね。
特にバルス級の特級呪文を唱えて、自らも消えてしまう輩w
2.1.1 rm,rndirコマンド
rm -rf /*
それと、シンボリックリンクがディレクトリの場合、 rm -rf <symbolic link>/ とシンボリックリンクを削除したつもりがリンク先のディレクトリを削除・・・。
後日、やらかした輩はこっぴどく叱られるし、その上司も大変なことになる(;^ω^)
「シンボリックリンクの削除」は、 unlinkで対処してくれと切に願う。
なのでディレクトリをシンボリックリンク にする場合は、chattr +i <シンボリックリンク> で削除に対する対処しましょう。
rm -rf <symbolic link>/
chattr +i <シンボリックリンク>
rm -rfはもちろん回避すべきですが、シンボリックリンクの削除回避用のラッパースクリプトを以下となります。
#!/bin/bash
# =========================================================
# safe_rm.sh - rmコマンド用フールプルーフ・ラッパースクリプト
# 目的: rm -rf の実行、およびルート(/)やシンボリックリンク誤削除の防止
# =========================================================
HAS_RECURSIVE=0
HAS_FORCE=0
TARGETS=()
# 1. 引数の解析 (-r/-R と -f が含まれているか、対象は何かを判定)
for arg in "$@"; do
if [[ "$arg" == -* ]]; then
# 短いオプションの解析 (例: -rf, -r -f)
if [[ "$arg" == *r* ]] || [[ "$arg" == *R* ]] || [[ "$arg" == "--recursive" ]]; then
HAS_RECURSIVE=1
fi
if [[ "$arg" == *f* ]] || [[ "$arg" == "--force" ]]; then
HAS_FORCE=1
fi
else
# オプション以外はターゲット(対象ファイル/ディレクトリ)として配列に格納
TARGETS+=("$arg")
fi
done
# 2. 禁断の組み合わせ「-rf」のブロック
if [ $HAS_RECURSIVE -eq 1 ] && [ $HAS_FORCE -eq 1 ]; then
echo "【フールプルーフ警告】"
echo "安全のため、'rm -rf' (再帰的かつ強制削除) の実行は禁止されています。"
echo "ディレクトリを削除する場合は 'rm -r' を使用し、確認プロンプトに従ってください。"
exit 1
fi
# 3. ターゲットごとの個別危険チェック
for target in "${TARGETS[@]}"; do
# 対策A: 特級呪文「rm -rf /」等のルートディレクトリ削除防止
if [ $HAS_RECURSIVE -eq 1 ] && [ "$target" == "/" ]; then
echo "【フールプルーフ警告】"
echo "世界を消し去る禁断の呪文が検知されました。"
echo "ルートディレクトリ '/' の再帰的削除は固く禁じられています!"
exit 1
fi
# 対策B: シンボリックリンクの末尾スラッシュによるディレクトリ誤削除防止
# ターゲットが "/" で終わっているか判定
if [[ "$target" == */ ]]; then
# 末尾の "/" を除いたパスがシンボリックリンクか確認
clean_target="${target%/}"
if [ -L "$clean_target" ]; then
echo "【フールプルーフ警告】"
echo "シンボリックリンクに対する末尾 '/' 付きの削除要求を検知しました。"
echo "リンク先のディレクトリが削除される大事故を防ぐため実行をブロックします。"
echo "シンボリックリンクのみを削除する場合は 'unlink ${clean_target}' を使用してください。"
exit 1
fi
fi
done
# 4. 全ての安全確認を通過した場合のみ、本来のrmコマンドを実行
# ※念のため、常に確認プロンプトが出るように -i オプションを強制付与
/bin/rm -i "$@"
【スクリプトの解説】
1.引数の解析(オプションとターゲットの分離)
引数の解析 (-r/-R と -f が含まれているか、対象は何かを判定)
入力されたコマンドの引数($@)を一つずつループ処理で確認します。
-rf や --recursive などのオプションと、削除対象の**ファイル/ディレクトリ名(ターゲット)**を切り分けます。
再帰削除(-r や -R)が含まれていれば HAS_RECURSIVE=1 とし、強制削除(-f)が含まれていれば HAS_FORCE=1 としてフラグを立てます。
オプション以外の文字列は、削除対象として TARGETS 配列に格納します。
2. 禁断の組み合わせ「-rf」のブロック
禁断の組み合わせ「-rf」のブロック
ステップ1で立てたフラグを確認し、再帰削除(-r)と強制削除(-f)が同時に指定された場合は、問答無用でコマンドの実行をブロック(exit 1)します。 不用意にこの組み合わせを使用すると、ディレクトリごと確認なしで消え去ってしまうため、代わりに rm -r を使用して確認プロンプトを出すようユーザーに促します。
3. ターゲットごとの個別危険チェック
ターゲットごとの個別危険チェック
配列に格納した削除対象(ターゲット)を一つずつチェックし、記事で紹介されているような特定の「やらかし」に該当しないかを判定します。
対策A: 特級呪文「rm -rf /」の防止 ターゲットがルートディレクトリ / であり、かつ再帰削除フラグが立っている場合を検知します。これは**「世界を消し去る禁断の呪文」として知られるシステム消滅コマンド**であるため、強力な警告とともに実行を強制終了させます
。
対策B: シンボリックリンクの誤削除防止 「シンボリックリンクを削除したつもりが、末尾にスラッシュ(/)をつけてしまったため、リンク先のディレクトリ本体を削除してしまった」という、よくある重大な事故を防ぎます
。 ターゲットの末尾が / で終わっており、かつその実体がシンボリックリンクであるかを判定し、該当した場合はブロックします。同時に、安全な「unlink コマンドで対処してくれ」という運用ルールをメッセージとして啓蒙します
。
4. 安全確認後のコマンド実行(護符の付与)
全ての安全確認を通過した場合のみ、本来のrmコマンドを実行
/bin/rm -i "$@"
ステップ2および3の厳しいチェックをすべてすり抜けた、安全とみなされた操作のみがここに到達します。 最後に本来の /bin/rm コマンドに引数を渡して実行しますが、念のため必ず -i(確認プロンプト)オプションを裏で強制付与しています。これにより、最終的な削除の直前にもう一度ユーザーへ確認を求める「護符」として機能します。
#!/bin/bash
# =========================================================
# safe_rmdir.sh - rmdirコマンド用フールプルーフ・ラッパースクリプト
# 目的: 空のディレクトリの不用意な削除防止と、システムディレクトリの保護
# =========================================================
TARGETS=()
OPTIONS=()
# 1. 引数の解析(オプションとターゲットを分離)
for arg in "$@"; do
if [[ "$arg" == -* ]]; then
OPTIONS+=("$arg")
else
TARGETS+=("$arg")
fi
done
# ターゲット(ディレクトリ)が指定されていない場合は、そのまま本来のrmdirに渡す(エラーやヘルプを出させるため)
if [ ${#TARGETS[@]} -eq 0 ]; then
/bin/rmdir "$@"
exit $?
fi
# 2. 危険なシステムディレクトリのチェック
for target in "${TARGETS[@]}"; do
# 念のため、/ や /etc、/var などを直接指定した場合は弾く(空ではないはずですがフールプルーフとして)
if [ "$target" == "/" ] || [ "$target" == "/etc" ] || [ "$target" == "/var" ] || [ "$target" == "/boot" ]; then
echo "【フールプルーフ警告】"
echo "システムディレクトリ ($target) を削除しようとしています。この操作は許可されていません。"
exit 1
fi
done
# 3. 削除前の確認プロンプト(rmdirには -i オプションがないため独自実装)
echo "以下のディレクトリを削除しようとしています:"
for target in "${TARGETS[@]}"; do
echo " - $target"
done
read -p "本当に削除しますか? (y/N): " answer
case "$answer" in
[yY]|[yY][eE][sS] )
# 'y' または 'yes' が入力された場合のみ本来のrmdirを実行
/bin/rmdir "${OPTIONS[@]}" "${TARGETS[@]}"
;;
* )
echo "削除をキャンセルしました。"
exit 0
;;
esac
【スクリプトの解説】
1. 疑似的な -i オプションの実装
rmdirには標準で確認オプションがないため、readコマンドを使って「本当に削除しますか? (y/N)」というプロンプトを出し、ユーザーにワンクッション考えさせる時間を強制的に作ります。これが「不用意な削除」に対する最大のフールプルーフになります。
2. システムディレクトリのブロック
/や/etcなどは通常空ではないためrmdirでは消えませんが、万が一空になっていた場合に消されてしまうのを防ぐため、事前にブロックする処理を入れています。
alias rm='/usr/local/bin/safe_rm.sh'
alias rmdir='/usr/local/bin/safe_rmdir.sh'
2.1.2 mvコマンド
alias mv='mv -i' は上書きを防いでくれますが、**移動先パスのタイポによる意図せぬリネーム(ファイル行方不明事件)**は防げません。だからこそ、移動先が存在しなくてもあえて確認プロンプトを出す、以下のような専用ラッパーが必要なのです。
mv -rf <Directory> <Directory>
#!/bin/bash
# 安全な mv コマンド(常に確認付き)
# 使い方: safe_mv source target
if [ "$#" -ne 2 ]; then
echo "Usage: safe_mv <source> <target>"
exit 1
fi
SOURCE="$1"
TARGET="$2"
# 移動先が存在する場合
if [ -e "$TARGET" ]; then
echo "WARNING: '$TARGET' already exists."
read -p "Do you want to overwrite it? [y/N]: " confirm
case "$confirm" in
[yY][eE][sS]|[yY])
mv "$SOURCE" "$TARGET"
echo "File moved and overwritten."
;;
*)
echo "Operation cancelled."
;;
esac
else
# 移動先が存在しない場合でも確認
echo "Target '$TARGET' does not exist."
read -p "Do you want to move '$SOURCE' to '$TARGET'? [y/N]: " confirm
case "$confirm" in
[yY][eE][sS]|[yY])
mv "$SOURCE" "$TARGET"
echo "File moved."
;;
*)
echo "Operation cancelled."
;;
esac
fi
alias mv='/usr/local/bin/safe_mv.sh'
【スクリプトの解説】
1. 引数の厳格な制限(一対一の操作の強制)
if [ "$#" -ne 2 ]; then
echo "Usage: safe_mv <source> <target>"
exit 1
fi
通常の mv コマンドは mv file1 file2 file3 dir/ のように複数ファイルを一度に移動できますが、このスクリプトは引数を「移動元」と「移動先」のぴったり2つに制限しています。 これにより、ワイルドカード(*)の展開ミスなどによって、意図しない大量のファイルが一気に別のディレクトリへ移動(または上書き)されてしまう事故を根源から防いでいます。
2. 移動先が存在する場合の上書き防止(基本の護符)
if [ -e "$TARGET" ]; then
echo "WARNING: '$TARGET' already exists."
read -p "Do you want to overwrite it? [y/N]: " confirm
対象のパスにすでに同名のファイルやディレクトリが存在する場合の処理です。 これは mv -i と同様の働きですが、独自の警告メッセージ(WARNING: ... already exists.)を表示した上で y/N の入力を求めるため、ユーザーに対して「上書きして上書き前のデータが消えてしまっても本当に大丈夫か?」という注意喚起を強く促します。重要な設定ファイルなどを誤って上書きしてしまう事故を防ぎます。
3. 移動先が存在しない場合の確認(このスクリプト最大の価値)
else
# 移動先が存在しない場合でも確認
echo "Target '$TARGET' does not exist."
read -p "Do you want to move '$SOURCE' to '$TARGET'? [y/N]: " confirm
ここが、alias mv='mv -i'としたとしても移動先に同名ファイルがない時は移動されてしまうという課題を解決している中核部分です。
通常の mv や mv -i は、移動先が存在しない場合、確認なしでそのまま移動(名前変更)移動先のディレクトリ名をタイポ(打ち間違い)した場合に事故が起きます。
【意図】 mv data.csv /var/backups/ (バックアップディレクトリに入れたい)
【やらかし】 mv data.csv /var/backup/ (存在しないディレクトリ名を指定してしまった) 👉 結果として、data.csv というファイルが /var/ 配下に backup というファイル名に変更されて置かれてしまい、「ファイルが消えた!」と大騒ぎになります。
このラッパースクリプトは、移動先が存在しない場合でも必ず「Target does not exist(対象は存在しませんが、移動しますか?)」とワンクッション置くため、ユーザーは「あれ?ディレクトリがあるはずなのに存在しないと言われている。パスを間違えたかな?」と気づくことができます。
2.1.3 cpコマンド
cpコマンドでもシステム上、大事なファイル(/etc/passwd, /etc/fstab, /etc/ssh/sshd_configなど)を間違って上書きしてしまうとログイン不能や起動失敗の原因になります。
chattr +i <大事なファイル>
alias cp='cp -i'
2.2. hostname
hostname コマンドでログインしたサーバが意図したサーバの確認のため、実行する運用が多いですね。
ですが、シェルスクリプトでシステム単位の全てサーバを一斉にコマンド実行するツールを導入しているシステムで、実行させるコマンドを間違え hostname xxxと打って、全てのサーバホスト名を xxx としてしまったのを目撃したことがありますw
自ホスト名の確認は、hostnamectl status --static なり uname -n で確認してくださいよ。
alias hostname='hostnamectl status --static'
2.3. crontab
crontab -e を実行しようとして r キーが e キーの隣にあるので、間違って crontab -r を実行してしまう、しかもそういった輩に限って事前の確認コマンド crontab -l を実行してないとかファイルもバックアップしていないとかありえないだろ!
crontabもデフォルトでalias設定で
-i オプションを作って欲しいものだ!
#!/bin/bash
if [[ "$1" == "-r" ]]; then
echo "Deleting crontab is not allowed."
exit 1
else
/usr/bin/crontab "$@"
fi
【スクリプトの解説】
1. 危険なオプションのピンポイント検知
if [[ "$1" == "-r" ]]; then
引数として渡された最初の文字列($1)が -r(削除オプション)と完全一致するかどうかを判定しています。これにより、crontab -e(編集)のつもりで crontab -r と打ち間違えてしまった操作を正確に検知します。
2. 実行のブロックと警告
echo "Deleting crontab is not allowed."
exit 1
-r が検知された場合、警告メッセージを表示して即座に処理を終了(exit 1)します。これにより、すべての定期実行ジョブ(定期魔法)が一瞬で消え去る大事故(一級呪文の詠唱)を未然に防ぎます。
3. 安全な操作の許可
else
/usr/bin/crontab "$@"
fi
入力されたオプションが -r 以外(例: -e や、事前確認のための -l など)であれば、そのまま本来の /usr/bin/crontab コマンドにすべての引数($@)を渡して実行させます。
。安全な操作は一切阻害しないという、フールプルーフの基本思想に沿った作りになっています。
alias crontab='/usr/local/bin/safe_crontab.sh'
2.4. shutdown
リモートでパワーオン操作が難しい環境でリブートするべきであるのにも関わらず shutdown -h now って、お前が今直ぐ消えてくれ!
実運用ではrebootコマンドを使うようにしてください。(BCP環境などは目も当てられない・・・)
#!/bin/bash
if [[ "$1" == "-h" ]]; then
echo "【フールプルーフ警告】"
echo "システムの完全停止(-h)は許可されていません。"
echo "リモートから起動できなくなる恐れがあります。"
echo "再起動を行う場合は 'shutdown -r now' または 'reboot' コマンドを使用してください。"
exit 1
else
/usr/bin/shutdown "$@"
fi
【スクリプトの解説】
1. 危険なオプション -h(Halt:停止)のピンポイント検知とブロック
if [[ "$1" == "-h" ]]; then
echo "shutdown is not allowed."
exit 1
引数として渡された最初の文字列($1)が -h と完全一致するかどうかを判定しています。 記事のドラフトにもある通り、リモートでパワーオン操作が難しい環境(データセンターやクラウドなど)において shutdown -h now を実行してしまうと、物理的に電源ボタンを押しに行かない限りシステムが復旧しないという大事故(BCP環境では目も当てられない事態)に繋がります。 このスクリプトは -h を検知した瞬間に警告メッセージを出して処理を強制終了(exit 1)し、サーバの意図せぬ完全停止をブロックします。
2. 安全な操作(再起動など)の許可
else
/usr/bin/shutdown "$@"
fi
入力されたオプションが -h 以外(例えば、再起動を行う -r オプションなど)であれば、そのまま本来の /usr/bin/shutdown コマンドにすべての引数($@)を渡して実行させます。 ドラフトで「実運用ではrebootコマンドを使うようにしてください」とある通り、再起動などの必要なメンテナンス作業は阻害しない設計になっています。
alias shutdown='/usr/local/bin/safe_shutdown.sh'
2.5. chmod
こいつも特級呪文なりえます。
chmod -R 000 /*
はい!もう誰も入れません。
しかも、シングルユーザモードで入ったとしても、元の状態が分からん。
分かったとしてもってやつです。
#!/bin/bash
for arg in "$@"; do
if [[ "$arg" == "-R" || "$arg" == "--recursive" ]]; then
echo "ERROR: Recursive chmod (-R) is disabled for safety."
exit 1
fi
done
/bin/chmod "$@"
【スクリプトの解説】
1. 危険なオプション -R の厳格な検知
for arg in "$@"; do
if [[ "$arg" == "-R" || "$arg" == "--recursive" ]]; then
引数として入力された文字列($@)を一つずつループで確認し、再帰的(ディレクトリ配下すべて)に権限を変更するオプションである -R や --recursive が含まれていないかをチェックします。 単純なエイリアス(alias chmod='...')では特定のオプションだけを弾くことはできませんが、このようにラッパースクリプトで引数を解析することで、危険な操作だけを正確に狙い撃ちできます。
2. 実行のブロックと警告
echo "ERROR: Recursive chmod (-R) is disabled for safety."
exit 1
fi
-R オプションが検知された場合、警告メッセージを表示して即座に処理を強制終了(exit 1)します。 これにより、「ディレクトリ配下の権限を一括で書き換えてしまい、システムを破壊する」という特級呪文の発動を未然にブロックします
。
3. 安全な操作(単一ファイルの権限変更)の許可
done
/bin/chmod "$@"
ループチェックを無事に抜け、-R オプションが含まれていないと判断された場合は、入力された引数をそのまま本来の /bin/chmod コマンドに渡して実行させます。 これにより、単一のファイルやディレクトリに対する通常の権限変更操作(例: chmod 755 script.sh など)は全く阻害されず、普段通りの業務を安全に行うことができます。
alias chmod='/usr/local/bin/safe_chmod.sh'
2.6. chown,chgrp
こいつも場合によっては特級呪文
chown -R [ユーザ]:[グループ] /*
chgrp -R [グループ] /*
てか、なんで -R ? なんで /* ? 理解不能なんですけど・・・。
#!/bin/bash
for arg in "$@"; do
if [[ "$arg" == "-R" || "$arg" == "--recursive" ]]; then
echo "ERROR: Recursive chown (-R) is disabled for safety."
exit 1
fi
done
/bin/chown "$@"
【スクリプトの解説】
1. 危険なオプション -R の厳格な検知
for arg in "$@"; do
if [[ "$arg" == "-R" || "$arg" == "--recursive" ]]; then
引数として入力された文字列($@)を一つずつループで確認し、再帰的(ディレクトリ配下すべて)に所有者を変更するオプションである -R や --recursive が含まれていないかをチェックします。 alias ではコマンド全体を置き換えることしかできませんが、このようにラッパースクリプトで引数を解析することで、危険なオプションだけを正確に狙い撃ちして制限をかけることができます。
2. 実行のブロックと警告
echo "ERROR: Recursive chown (-R) is disabled for safety."
exit 1
fi
-R オプションが検知された場合、警告メッセージを表示して即座に処理を強制終了(exit 1)します。 これにより、「間違って / 配下すべての所有権を書き換えてシステムを破壊する」という特級呪文の発動を未然にブロックします。
3. 安全な操作(単一ファイルの所有権変更)の許可
done
/bin/chown "$@"
ループチェックを無事に抜け、-R オプションが含まれていないと判断された場合は、入力された引数をそのまま本来の /bin/chown コマンドに渡して実行させます。 これにより、単一のファイルやディレクトリに対する通常の所有権変更操作(例: chown appuser:appgroup app.log など)は全く阻害されず、普段通りの業務を安全に行うことができます。
#!/bin/bash
# =========================================================
# safe_chgrp.sh - chgrpコマンド用フールプルーフ・ラッパースクリプト
# 目的: -R オプションによる意図せぬ再帰的なグループ変更を防止
# =========================================================
HAS_RECURSIVE=0
# 1. 引数の解析 (-R または --recursive が含まれているか判定)
for arg in "$@"; do
# 完全一致でのチェック
if [[ "$arg" == "-R" ]] || [[ "$arg" == "--recursive" ]]; then
HAS_RECURSIVE=1
break
fi
# 結合オプションのチェック (例: -vR や -Rv など)
if [[ "$arg" =~ ^-[a-zA-Z0-9]*R[a-zA-Z0-9]*$ ]]; then
HAS_RECURSIVE=1
break
fi
done
# 2. -Rが含まれていた場合のブロック処理
if [ $HAS_RECURSIVE -eq 1 ]; then
echo "【フールプルーフ警告】"
echo "安全のため、'chgrp -R' (再帰的なグループ変更) の実行は禁止されています。"
echo "ディレクトリ配下を全て変更するとシステムに重大な影響を及ぼす可能性があります。"
echo "一括変更が必要な場合は、影響範囲を確認の上、管理者へ依頼してください。"
exit 1
fi
# 3. 安全確認を通過した場合のみ本来のchgrpコマンドを実行
/bin/chgrp "$@"
【スクリプトの解説】
1. オプションの厳密な検知: 単純な -R や --recursive だけでなく、-vR や -cR のように他のオプションと結合して指定された場合でも正規表現で検知し、すり抜けを防ぎます。
2. 安全な実行: 再帰オプションが含まれていない、単一ファイルやディレクトリに対する通常のグループ変更操作はそのまま本来の /bin/chgrp に渡して実行させます。
alias chown='/usr/local/bin/safe_chown.sh'
alias chgrp='/usr/local/bin/safe_chgrp.sh'
2.7. kill,killall
2.7.1. kill
こいつは場合によっては1級呪文
kill -9 1
PID 1って何か知ってる?
#!/bin/bash
for arg in "$@"; do
if [[ "$arg" == "1" ]]; then
echo "【フールプルーフ警告】"
echo "安全のため、PID 1 (init/systemd) への kill コマンドは禁止されています。"
echo "世界の根幹となるプロセスを終了させると、システムがクラッシュします。"
echo "対象のPIDが正しいか、もう一度確認してください。"
exit 1
fi
done
/bin/kill "$@"
【スクリプトの解説】
1. 危険なターゲット「PID 1」のピンポイント検知
for arg in "$@"; do
if [[ "$arg" == "1" ]]; then
引数として入力された文字列($@)を一つずつループで確認し、プロセスIDとして 1 が指定されていないかを厳密にチェックしています。 ユーザーが kill 1 や kill -9 1 と入力した場合、オプションの有無に関わらず 1 という独立した引数を検知できるため、危険な操作を正確に捉えることができます。
2. 実行のブロックとシステムの守護
echo "【フールプルーフ警告】"
echo "安全のため、PID 1 (init/systemd) への kill コマンドは禁止されています。"
echo "世界の根幹となるプロセスを終了させると、システムがクラッシュします。"
echo "対象のPIDが正しいか、もう一度確認してください。"
exit 1
fi
引数に 1 が検知された場合、警告メッセージを表示して即座に処理を強制終了(exit 1)します。 これにより、「間違ってPID 1を指定してしまい、システムを巻き込んで自爆する」という一級呪文の発動をシステム側で物理的にブロックします。
3. 安全な操作(通常のプロセス終了)の許可
done
/bin/kill "$@"
ループチェックを抜け、引数に 1 が含まれていないと判断された場合は、入力された引数をそのまま本来の /bin/kill コマンドに渡して実行させます。 アプリケーションの再起動や、暴走したプロセスの停止(例: kill 12345)といった、運用上必要な通常の kill 操作は一切阻害しないフールプルーフ設計になっています。
alias kill='/usr/local/bin/safe_kill.sh'
2.7.2. killall
killall -e [停止しようと思っていたPID]
オプションの意味ぐらい、頼むから抑えてネ゙!!!
#!/bin/bash
# 安全な killall コマンド
# 危険なオプションやプロセス名を検出して拒否する
# 危険なプロセス名リスト(必要に応じて追加)
PROTECTED_PROCESSES=("init" "systemd" "sshd" "cron" "dbus-daemon")
# 危険なオプションを検出
for arg in "$@"; do
if [[ "$arg" == "-e" ]]; then
echo "【フールプルーフ警告】"
echo "安全のため、'-e' オプションを使用した killall は禁止されています。"
exit 1
fi
done
# プロセス名を検査
for proc in "$@"; do
for protected in "${PROTECTED_PROCESSES[@]}"; do
if [[ "$proc" == "$protected" ]]; then
echo "【フールプルーフ警告】"
echo "システムプロセス '${proc}' の一括終了はブロックされました。"
echo "このプロセスを終了するとシステム全体に致命的な影響を及ぼします。"
exit 1
fi
done
done
# 安全と判断された場合のみ実行
/usr/bin/killall "$@"
【スクリプトの解説】
1. 危険なオプション -e のピンポイント検知
# 危険なオプションを検出
for arg in "$@"; do
if [[ "$arg" == "-e" ]]; then
echo "【フールプルーフ警告】"
echo "安全のため、'-e' オプションを使用した killall は禁止されています。"
exit 1
fi
done
killall の -e(--exact)オプションは、非常に長い名前のプロセスを完全一致で指定する機能ですが、誤った使い方をすると意図しないプロセスを巻き込んだり、逆に終了させたいものが終了しなかったりと、事故の温床になり得ます。 記事にある「オプションの意味を抑えていない」ユーザーが不用意に使うことを防ぐため、引数をループで解析して -e を正確に弾きます。
2. 世界の根幹と重要インフラ(デーモン)の保護
# 危険なプロセス名リスト(必要に応じて追加)
PROTECTED_PROCESSES=("init" "systemd" "sshd" "cron" "dbus-daemon")
...
# プロセス名を検査
for proc in "$@"; do
for protected in "${PROTECTED_PROCESSES[@]}"; do
if [[ "$proc" == "$protected" ]]; then
echo "【フールプルーフ警告】"
echo "システムプロセス '${proc}' の一括終了はブロックされました。"
echo "このプロセスを終了するとシステム全体に致命的な影響を及ぼします。"
exit 1
fi
done
done
ここがこのスクリプトの最大の要です。先ほどの safe_kill.sh では「PID 1」を直接指定する事故(一級呪文)を防ぎましたが、killall はプロセス名で指定するため、killall systemd や killall init と唱えられてしまうと全く同じシステムクラッシュ(世界の根幹の切断)が起きてしまいます。 さらに、リモート接続を司る sshd や定期実行の cron など、停止すると運用が破綻する重要なプロセス群をリスト化して保護しており、「名前指定による自爆」を完全にブロックします。
3. 安全な操作(一般プロセスの終了)の許可
# 安全と判断された場合のみ実行
/usr/bin/killall "$@"
オプションのチェックと保護プロセスのチェックを両方とも無事に通過した場合のみ、本来の /usr/bin/killall コマンドに引数を渡して実行します。 これにより、暴走したWebサーバ(例: killall httpd や killall nginx)など、運用上必要なプロセスの一括停止は問題なく行えます。
alias killall='/usr/local/bin/safe_killall.sh'
2.8. iptables
はい!オンプレならデータセンター緊急召喚呪文です・・・
iptables -I INPUT -p tcp -s 0.0.0.0/0 --dport 22 -j DROP
iptablesは、設定ミスが致命的になる場合もあるので、いきなり設定を保存せず、一定時間が経過したらサービスを再起動する手段を事前に考えて欲しいですねー。というか想像力の欠如ですか?
2.9. リダイレクトによる上書き
リダイレクトによる環境変数のPATH設定を間違えてほとんどのコマンドが command not found になってしまったなんて経験はありませんか?私は見たことがありますw
そもそも環境変数を追加する際に後ろに :$PATH 付けるって習わなかったか?
export PATH="[追加したいPATH]:$PATH"
まあ、コマンドをフルパスで実行すれば、なんとかなりますが・・・。
各profileやpasswdファイルなどの重要ファイルは、chattrで書き込みを禁止するよう対策してください。
chattr +i /etc/passwd
また、既存ファイルへの > による上書きをOSレベルで安全にブロックします。
set -o noclobber
2.10. fstab
メンテナンスモードかシングルユーザモードで対処できない人は触ってはダメです。
修正が終わったら必ず mount −av を実行して構文チェックしてください。
#!/bin/bash
FSTAB="/etc/fstab"
EDITOR="${EDITOR:-vi}"
BKUPDIR="/usr/local/BKUP"
TIMESTAMP=$(date +%Y%m%d%H%M%S)
BACKUP="${BKUPDIR}/fstab.bak.${TIMESTAMP}"
mkdir -p "$BKUPDIR"
echo "[3] バックアップ作成: $BACKUP"
cp "$FSTAB" "$BACKUP"
echo "[4] 編集開始 ($EDITOR)"
$EDITOR "$FSTAB"
echo
echo "[5] 差分確認"
diff -u "$BACKUP" "$FSTAB" || true
echo
echo "[1] syntax check(mount --fake -a)を実行します"
if ! mount --fake -a; then
echo "【フールプルーフ警告】"
echo "fstabの構文エラー(記述ミス)を検知しました!"
echo "このままOSを再起動すると起動不能になるため、自動的に編集前の状態へロールバックします。"
cp "$BACKUP" "$FSTAB"
exit 1
fi
echo
read -p "[2] 変更を反映するために 'mount -av' を実行しますか? [y/N]: " confirm
case "$confirm" in
[yY][eE][sS]|[yY])
echo "mount -av 実行"
mount -av
;;
*)
echo "mount -av は実行されませんでした。"
;;
esac
【スクリプトの解説】
1.編集前の自動バックアップ(命綱の確保)
「fstabを編集する際は必ずバックアップを取る」というのはインフラエンジニアの基本ですが、人間は必ず忘れます。このスクリプトは、編集を開始する前に /usr/local/BKUP/ 配下へタイムスタンプ付きで強制的にバックアップを作成します。これにより、万が一の際にも確実に元の状態へ戻せる命綱を確保します。
2.編集と差分の可視化
環境変数 $EDITOR(デフォルトは vi)を開いて編集を行った後、終了時に自動で編集前後の差分(diff)を表示します。自分がどこを書き換えたのか、余計な行を消してしまっていないかを視覚的に確認させることで、ヒューマンエラーに気づくチャンスを与えます。
3.構文チェックと自動ロールバック(最大のフールプルーフ)
ここがこのスクリプトの最も価値のある核心部分であり、ブラッシュアップしたエラーメッセージを導入した箇所です。 mount --fake -a は、実際にはマウント処理を行わず、/etc/fstab の記述に沿ってマウントが可能か(構文に間違いがないか)だけをテストするコマンドです。
もしここでエラー(記述ミス)が検知された場合、ブラッシュアップしたエラーメッセージが表示されます。「なぜ元に戻されたのか(再起動すると起動不能になるから)」をはっきりとユーザーに伝え、即座に
で作成したバックアップからファイルを上書きして元の状態に戻し(ロールバック)、処理を強制終了します。これにより「壊れた状態のまま保存されてしまう」という最悪の事態をシステムが物理的に阻止します。
4.確実な反映の実行(mount -av)
構文チェックを無事に通過した場合のみ、記事のドラフトにもある「修正が終わったら必ず mount -av を実行して構文チェックしてください」という運用ルールを、プロンプトとしてユーザーに促します
。ユーザーが y を押せば、そのまま安全に設定がシステムに反映されます。
alias safe_fstab_edit='/usr/local/bin/safe_fstab_edit.sh'
vi() {
if [[ "$1" == "/etc/fstab" ]]; then
echo "/etc/fstab の編集は 'safe_fstab_edit' を使ってください。"
return 1
else
command vi "$@"
fi
}
2.11. [Ctrl]+[Alt]+[Delete]キー
Linuxのコンソール画面では、[Ctrl]+[Alt]+[Delete]キーを押すと、意図せぬ再起動してしまう。1人で使っているLinuxマシンならそれでも構わないが、複数のユーザーで使っている場合には、すべての人が「shutdown -r now」の権限を持っていることになり、セキュリティ上好ましくない。
systemctl mask ctrl-alt-del.target
2.12. yum
不用意に yum update
yumコマンドを実行時にupdateしてくださいと警告が出ても従わないでください。
#!/bin/bash
# =========================================================
# safe_yum.sh - yumコマンド用フールプルーフ・ラッパースクリプト
# 目的: 不用意な yum update による依存関係崩壊の防止
# =========================================================
# 1. 引数の中に 'update' または 'upgrade' があるか確認
for arg in "$@"; do
if [[ "$arg" == "update" ]] || [[ "$arg" == "upgrade" ]]; then
echo "【フールプルーフ警告】"
echo "本番環境での 'yum $arg' の直接実行はブロックされました。"
echo "不用意な更新は依存関係の崩壊を招く恐れがあります。"
echo "必ず検証環境でテストを行い、計画的なメンテナンス手順に従ってください。"
exit 1
fi
done
# 2. 安全なサブコマンド(install, info, searchなど)の場合は元のyumを実行
/usr/bin/yum "$@"
【スクリプトの解説】
1. 危険なサブコマンド(update / upgrade)の検知とブロック
# 1. 引数の中に 'update' または 'upgrade' があるか確認
for arg in "$@"; do
if [[ "$arg" == "update" ]] || [[ "$arg" == "upgrade" ]]; then
echo "【フールプルーフ警告】"
echo "本番環境での 'yum $arg' の直接実行はブロックされました。"
echo "不用意な更新は依存関係の崩壊を招く恐れがあります。"
echo "必ず検証環境でテストを行い、計画的なメンテナンス手順に従ってください。"
exit 1
fi
done
入力された引数($@)を一つずつループで確認し、パッケージの更新を意味する update または upgrade が含まれていないかを厳密にチェックします
。 もし検知された場合は、処理を強制終了(exit 1)させて実行を物理的にブロックします。同時に、表示されるエラーメッセージ内で「なぜ禁止されているのか(依存関係の崩壊の恐れ)」「本来どうすべきなのか(検証環境でのテストと計画的な手順)」という運用ルールをユーザーに啓蒙しています。これは、記事の設計思想にある「萎縮させる管理ではなく、安心して作業できる環境を作る」ための重要なアプローチです。
2. 安全な操作(インストールや検索など)の許可
# 2. 安全なサブコマンド(install, info, searchなど)の場合は元のyumを実行
/usr/bin/yum "$@"
ループチェックを無事に抜け、更新系のサブコマンドが含まれていないと判断された場合は、入力された引数をそのまま本来の /usr/bin/yum コマンドに渡して実行させます。 これにより、必要なパッケージの新規インストール(yum install)やパッケージ情報の検索(yum search, yum info)といった、安全で日常的な運用業務は一切阻害されません。
alias yum='/usr/local/bin/safe_yum.sh'
2.18. visudo
sudoersも間違った記述をすると致命的になりかねないので、ラッパーシェルにて事前にバックアップを取るようにしました。
また、sudoersファイルの編集をvisudoコマンドではなく、viなどのエディタで編集する輩の対策もしないと・・・
#!/bin/bash
SUDOERS="/etc/sudoers"
BKUPDIR="/usr/local/BKUP"
# 念のためバックアップ先ディレクトリを作成
mkdir -p "$BKUPDIR"
# 編集前にタイムスタンプ付きでバックアップ
cp "$SUDOERS" "${BKUPDIR}/sudoers.bak.$(date +%Y%m%d%H%M%S)"
# visudo本体の実行
/usr/sbin/visudo
【スクリプトの解説】
1.編集前の自動バックアップ(命綱の強制取得)
人間は「1行追加するだけだから」と油断してバックアップを取らずに作業しがちです。このスクリプトは、エディタが開く前に強制的に /usr/local/BKUP/ へタイムスタンプ付きのバックアップを作成します。これにより、万が一設定ミスで sudo が壊れても、別セッションなどから確実に戻せる復旧ポイントを作ります。
2.安全なエディタ(visudo)の呼び出し
バックアップ取得後、本来の /usr/sbin/visudo を起動します。visudo 自体にも保存時の構文チェック機能がありますが、権限の与え方などの「論理的なミス」までは防げません。そのため、事前バックアップとの組み合わせが強力なフールプルーフとなります。
alias visudo=`/usr/local/bin/safe_visudo_edit.sh`
2.19. date
dateコマンドを間違えてしまうとシステム時刻を強制的に書き換えてしまうという危険性を秘めています。 本番環境で時刻が意図せずズレると、ログの時系列が崩壊するだけでなく、ジョブスケジューラやデータベースのトランザクション、証明書の有効期限判定などに致命的な影響を与えます。
#!/bin/bash
# =========================================================
# safe_date.sh - dateコマンド用フールプルーフ・ラッパースクリプト
# 目的: dateコマンドによる意図しないシステム時刻の変更を防止
# =========================================================
for arg in "$@"; do
# 1. 明示的な時刻設定オプション(-s, --set)のブロック
if [[ "$arg" == "-s" ]] || [[ "$arg" == "--set" ]] || [[ "$arg" == --set=* ]]; then
echo "【フールプルーフ警告】"
echo "安全のため、dateコマンドによる手動でのシステム時刻変更は禁止されています。"
echo "本番環境の時刻はNTP(chronyd等)で同期されており、手動変更はシステムに致命的な影響を及ぼします。"
exit 1
fi
# 2. 旧式の時刻指定(MMDDhhmm[[CC]YY][.ss])による変更のブロック
# 引数がオプション(-で始まる)でもフォーマット指定(+で始まる)でもなく、数字のみで構成されている場合
# ※ -d や -r オプションの引数として渡されている場合は除くための簡易判定
if [[ ! "$arg" =~ ^[-+] ]] && [[ ! " $* " =~ " -d " ]] && [[ ! " $* " =~ " --date" ]] && [[ ! " $* " =~ " -r " ]]; then
if [[ "$arg" =~ ^[2-10]+(\.[2-10]+)?$ ]]; then
echo "【フールプルーフ警告】"
echo "数値のみの指定によるシステム時刻の変更は禁止されています。"
echo "時刻の表示フォーマットを指定する場合は '+' から始めてください。(例: date '+%Y-%m-%d')"
exit 1
fi
fi
done
# 3. 安全確認を通過した場合のみ本来のdateコマンドを実行
/bin/date "$@"
alias date='/usr/local/bin/safe_date.sh'
【スクリプトの解説】
1. -s / --set のブロック
時刻変更で最もよく使われるオプションを確実に検知し、実行をブロックします。同時に「システム時刻はNTPで同期されるべきである」という理由をメッセージとして表示し、運用ルールを啓蒙します。
2. 旧フォーマットによる変更のブロック
date 10231234 のように、オプションをつけずに数字だけを入力してシステム時刻を変更する古い仕様(タイポなどで意図せず実行してしまう可能性)も検知して防ぎます。ただし、-d "10 days ago" や +"%Y%m%d" といった安全な参照系の使い方は許可するよう正規表現で調整しています。
3. フールプルーフ設計
3.1. フールプルーフの設計思想
人間工学的視点
「人は必ずミスをする」という前提を強調し、操作系をシンプルにすることの重要性。
心理的安全性との関係
フールプルーフ設計が「萎縮させる管理」ではなく「安心して作業できる環境」を作ることにつながる点。
レベル分けすることで、上位のレベルの人が任されている認識をし、
3.2. ユーザ・グループによるコマンド制御をするには
3.2.1 グループ設計の考え方(役割 × 技術レベル)
Linuxのグループ機能を使い、ユーザーを「主グループ(役割の制御用)」と「サブグループ(技術レベル)」の両方に所属させます。
3.2.2 主グループ(役割の制御用)
目的: ディレクトリやファイルへのアクセス権(Read/Writeなど)をLinuxのパーミッション設定(chmod/chown)で制御します。
| グループ | 内容 |
|---|---|
| enduser | ユーザがログを参照するためのエンドユーザ用グループ |
| operator | 監視を実施するユーザがログ参照やジョブスケジューラの再実行するためのオペレータ用グループ |
| app | アプリケーションログの確認、アプリケーション資材のリリース、アプリケーションの再起動を実行するためのグループ |
| infra | インフラのログ確認、メンテナンス、OS・ミドルウェアのバージョンアップ、OS・ミドルウェアの再起動するためのグループ |
3.2.3 技術レベル
目的: sudoersや/etc/profileでのサブグループで条件分岐し、レベルに応じたコマンド制限を適用します。
| グループ | 許可するコマンド | 目的 |
|---|---|---|
| lv1_read | 参照系 | 読み取りのみ、安全第一 |
| lv2_nonfatal | 参照系+ 非致命系 | 軽微な操作は可能 |
| lv3_admin | 参照系+ 非致命系+ 致命系 | 上級者・管理者 |
3.2.4 レベル毎のコマンド許可一覧
各利用者レベルに応じたLinuxコマンド許可・拒否の一覧を以下に示します。
| コマンド | 説明 | レベル1 | レベル2 | レベル3 | 補足 |
|---|---|---|---|---|---|
| cd | 指定したディレクトリへ移動します | 〇 | 〇 | 〇 | シェル組み込みコマンドのため制御不可 |
| ls | ディレクトリの内容を一覧表示 | 〇 | 〇 | 〇 | |
| pwd | カレントディレクトリのパスを表示 | 〇 | 〇 | 〇 | |
| cp | ファイルをコピー | 〇 | 〇 | 〇 | aliasにて一部回避 |
| mv | ファイルを移動または名前を変更 | 〇 | 〇 | 〇 | alias,wrapperにて一部回避 |
| rm | ファイルを削除 | 〇 | 〇 | 〇 | alias,wrapperにて一部回避 |
| mkdir | ディレクトリを作成 | 〇 | 〇 | 〇 | |
| rmdir | 空のディレクトリを削除 | 〇 | 〇 | 〇 | alias,wrapperにて一部回避 |
| touch | 空のファイルを作成またはタイムスタンプを変更 | 〇 | 〇 | 〇 | |
| cat | ファイルの内容を表示 | 〇 | 〇 | 〇 | |
| more/less | ファイルの内容をページ単位で表示 | 〇 | 〇 | 〇 | |
| head/tail | ファイルの先頭/末尾部分を表示 | 〇 | 〇 | 〇 | |
| find | ファイルを検索 | 〇 | 〇 | 〇 | |
| locate | インデックスを使用してファイルを高速検索 | 〇 | 〇 | 〇 | |
| du | ファイルシステムのディスク使用量を表示 | 〇 | 〇 | 〇 | |
| chmod | ファイルのアクセス許可を変更 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| chown | ファイルの所有者を変更 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| chgrp | ファイルのグループを変更 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| ln | ハードリンクまたはシンボリックリンクを作成 | × | 〇 | 〇 | |
| grep | テキストを検索 | 〇 | 〇 | 〇 | |
| awk | テキストを処理 | 〇 | 〇 | 〇 | |
| sed | ストリームエディタ(テキストの置換など) | × | 〇 | 〇 | |
| sort | テキストを並べ替え | 〇 | 〇 | 〇 | |
| uniq | 重複行を削除 | 〇 | 〇 | 〇 | |
| wc | 行数、単語数、バイト数をカウント | 〇 | 〇 | 〇 | |
| cut | テキストを分割 | 〇 | 〇 | 〇 | |
| paste | テキストを結合 | 〇 | 〇 | 〇 | |
| tr | 文字の置換・削除 | 〇 | 〇 | 〇 | |
| ps | 現在のプロセスを表示 | 〇 | 〇 | 〇 | |
| top/htop | リアルタイムでプロセス情報を表示 | 〇 | 〇 | 〇 | |
| kill | プロセスを終了 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| killall | プロセス名でプロセスを終了 | × | × | 〇 | alias,wrapperにて一部回避 |
| uptime | システムの稼働時間を表示 | 〇 | 〇 | 〇 | |
| uname | システム情報を表示 | 〇 | 〇 | 〇 | |
| free | メモリ使用量を表示 | 〇 | 〇 | 〇 | |
| iostat | I/O統計情報を表示 | 〇 | 〇 | 〇 | |
| vmstat | 仮想メモリの統計情報を表示 | 〇 | 〇 | 〇 | |
| lsof | 開いているファイルの一覧を表示 | 〇 | 〇 | 〇 | |
| dmesg | カーネルのメッセージを表示 | 〇 | 〇 | 〇 | |
| service | サービスを管理 | × | 〇 | 〇 | |
| systemctl | systemdサービスを管理 | × | 〇 | 〇 | |
| chkconfig | SysVinitサービスを管理 | × | 〇 | 〇 | |
| ping | ネットワーク接続を確認 | 〇 | 〇 | 〇 | |
| traceroute | パケットの経路を追跡 | 〇 | 〇 | 〇 | |
| ifconfig/ip | ネットワークインターフェースの設定を表示・管理 | 〇 | 〇 | 〇 | |
| netstat/ss | ネットワーク接続、ルーティングテーブルなどを表示 | 〇 | 〇 | 〇 | |
| scp | セキュアコピー | × | 〇 | 〇 | |
| rsync | リモートおよびローカル間でファイルを同期 | × | 〇 | 〇 | |
| wget | ファイルをダウンロード | 〇 | 〇 | 〇 | |
| curl | データを転送 | 〇 | 〇 | 〇 | |
| tar | アーカイブを作成・展開 | 〇 | 〇 | 〇 | |
| gzip/gunzip | 圧縮・解凍 | 〇 | 〇 | 〇 | |
| zip/unzip | 圧縮・解凍 | 〇 | 〇 | 〇 | |
| passwd | パスワードを変更 | 〇 | 〇 | 〇 | alias,wrapperにて一部回避 |
| useradd/userdel | ユーザーを追加/削除 | × | 〇 | 〇 | |
| usermod | ユーザー情報を変更 | × | 〇 | 〇 | |
| groupadd/groupdel | グループを追加/削除 | × | 〇 | 〇 | |
| groups | ユーザーが所属するグループを表示 | 〇 | 〇 | 〇 | |
| echo | メッセージを表示 | 〇 | 〇 | 〇 | |
| date | 日付と時刻を表示・設定 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| cal | カレンダーを表示 | 〇 | 〇 | 〇 | |
| who/w | ログインしているユーザーを表示 | 〇 | 〇 | 〇 | |
| man | マニュアルページを表示 | 〇 | 〇 | 〇 | |
| alias/unalias | コマンドのエイリアスを設定/削除 | × | × | 〇 | シェル組み込みコマンドのため制御不可 |
| history | コマンド履歴を表示 | 〇 | 〇 | 〇 | シェル組み込みコマンドのため制御不可 |
| crontab | 定期的にコマンドを実行するスケジュールを設定 | × | 〇 | 〇 | alias,wrapperにて一部回避 |
| at | 指定した時刻にコマンドを実行 | × | 〇 | 〇 | |
| nohup | コマンドを終了してもバックグラウンドで実行し続ける | × | 〇 | 〇 | |
| yes | 指定した文字(文字列)を画面上にひたすら表示する | × | × | 〇 | |
| dd | ブロック単位でファイルをコピー | × | × | 〇 | |
| fdisk | パーティションを作成、削除 | × | × | × | rootユーザのみ |
| parted | パーティションの作成や削除 | × | × | × | rootユーザのみ |
| gdisk | GPT対応のパーティションを作成、削除 | × | × | × | rootユーザのみ |
| blkid | UUIDに対応するディスクパーティション確認 | 〇 | 〇 | 〇 | |
| mkfs | ファイルシステム作成 | × | × | × | rootユーザのみ |
| resize2fs | ファイルシステムのサイズ変更 | × | × | × | rootユーザのみ |
| e2fsck/fsck | ファイルシステムの整合性をチェック | × | × | × | rootユーザのみ |
| fsfreeze | Filesystemへのアクセスを一時凍結 | × | × | × | rootユーザのみ |
| e4defrag | ext4ファイルシステムの断片化(フラグメンテーション)を検査 | × | × | × | rootユーザのみ |
| filefrag | ファイルの断片化を調査 | 〇 | 〇 | 〇 | |
| debugfs | ファイルシステムの調査・デバッグ | 〇 | 〇 | 〇 | |
| e2image | ファイルシステムメタデータをファイルに保存 | 〇 | 〇 | 〇 | |
| dumpe2fs | ファイルシステムの詳細情報を表示 | 〇 | 〇 | 〇 | |
| tune2fs | ファイルシステムの設定を調整 | × | × | × | rootユーザのみ |
| dump | ファイルシステムのバックアップ | × | 〇 | 〇 | |
| restore | ファイルシステムのリストア | × | × | 〇 | |
| e4defrag | ext4ファイルシステムで断片化(フラグメンテーション)を解消 | × | × | × | rootユーザのみ |
| insmod | モジュールの手動挿入 | × | × | × | rootユーザのみ |
| rmmod | モジュールの手動削除 | × | × | × | rootユーザのみ |
| lsmod | ロードされているカーネルモジュールを一覧表示 | 〇 | 〇 | 〇 | |
| modprobe | カーネルモジュールをロードまたはアンロード | × | × | × | rootユーザのみ |
| depmod | モジュール依存関係の生成 | × | × | × | rootユーザのみ |
| nmcli | ネットワーク設定 | × | × | × | rootユーザのみ |
| host | 名前解決確認 | 〇 | 〇 | 〇 | |
| dig | 名前解決確認 | 〇 | 〇 | 〇 | |
| nslookup | 名前解決確認 | 〇 | 〇 | 〇 | |
| nc | ポートスキャニング | 〇 | 〇 | 〇 | |
| curl | HTTPクライアント | 〇 | 〇 | 〇 | |
| route | ルーティングテーブルを確認 | 〇 | 〇 | 〇 | |
| tcpdump | パケットキャプチャ | 〇 | 〇 | 〇 | |
| yum | パッケージ管理 | × | 〇 | 〇 | |
| dnf | パッケージ管理 | × | 〇 | 〇 |
3.3 具体的な実装方法
目標とするレベル別の要件(Lv1: 参照系、Lv2: 非致命系操作、Lv3: 致命系・rootスイッチ可能)を満たすための3つのアプローチを組み合わせます。
3.3.1 sudoersを用いた権限定義とrootスイッチ制御
sudoersにレベルごとのCmnd_Alias(コマンドグループ)を定義することで、シンプルに管理できます。また、Lv3のみにrootへのスイッチを許可します。
# --- コマンドエイリアスの定義 ---
Cmnd_Alias READ_CMDS = /bin/ls, /bin/pwd, /bin/cp, /bin/mv, /bin/rm, /bin/mkdir, /bin/rmdir, /bin/touch, /bin/cat, /bin/more, /usr/bin/less, /usr/bin/head, /usr/bin/tail, /usr/bin/du, /bin/grep, /usr/bin/awk, /usr/bin/sort, /usr/bin/uniq, /usr/bin/wc, /usr/bin/cut, /usr/bin/paste, /usr/bin/tr, /bin/ps, /usr/bin/top, /usr/bin/htop, /usr/bin/uptime, /bin/uname, /usr/bin/free, /usr/bin/iostat, /usr/bin/vmstat, /usr/sbin/lsof, /bin/dmesg, /bin/ping, /bin/traceroute, /sbin/ifconfig, /sbin/ip, /bin/netstat, /sbin/ss, /usr/bin/wget, /usr/bin/curl, /bin/tar, /bin/gzip, /bin/gunzip, /usr/bin/zip, /usr/bin/unzip, /usr/bin/passwd, /usr/bin/groups, /bin/echo, /usr/bin/cal, /usr/bin/who, /usr/bin/w, /usr/bin/man, /sbin/blkid, /usr/sbin/filefrag, /sbin/debugfs, /sbin/e2image, /sbin/dumpe2fs, /sbin/lsmod
Cmnd_Alias NONFATAL_CMDS = /bin/chmod, /bin/chown, /bin/chgrp, /bin/sed, /bin/kill, /sbin/service, /bin/systemctl, /sbin/chkconfig, /usr/bin/scp, /usr/bin/rsync, /usr/sbin/useradd, /usr/sbin/userdel, /usr/sbin/usermod, /usr/sbin/groupadd, /usr/sbin/groupdel, /bin/date, /usr/bin/crontab, /usr/bin/at, /usr/bin/nohup, /sbin/dump, /sbin/yum, /sbin/dnf
Cmnd_Alias FATAL_CMDS = /usr/bin/killall, /usr/bin/yes, /bin/dd, /sbin/restore, /bin/su, /usr/bin/sudo
# --- サブグループ(レベル)への権限割り当て ---
# Lv1: 参照系のみ許可
%lv1_read ALL=(ALL) READ_CMDS
# Lv2: 参照系+非致命系を許可
%lv2_nonfatal ALL=(ALL) READ_CMDS, NONFATAL_CMDS
# Lv3: 全てのコマンド(致命系含む)に加え、rootへのスイッチ(su -)を許可
%lv3_admin ALL=(ALL) READ_CMDS, NONFATAL_CMDS, FATAL_CMDS
%lv3_admin ALL=(root) /bin/su -, /bin/sudo su -
3.3.2 /etc/profile.d/ を活用した動的なコマンド・オプション制限
sudo制限だけでは運用上不十分なため、profileにて特定のコマンドを回避・制御する仕組みを設けます。
/etc/profile.d/foolproof.sh を作成し、ログインしたユーザーの所属サブグループを判定して、Aliasやラッパースクリプトを動的に適用します。
# ==========================================
# フールプルーフ共通エイリアス(全ユーザー・全レベル対象)
# ==========================================
alias hostname='hostnamectl status --static'
alias crontab='/usr/local/bin/safe_crontab.sh'
alias rm='/usr/local/bin/safe_rm.sh'
alias rmdir='/usr/local/bin/safe_rmdir.sh'
alias mv='/usr/local/bin/safe_mv.sh'
alias cp='cp -i'
alias shutdown='/usr/local/bin/safe_shutdown.sh'
alias chmod='/usr/local/bin/safe_chmod.sh'
alias chown='/usr/local/bin/safe_chown.sh'
alias chgrp='/usr/local/bin/safe_chgrp.sh'
alias kill='/usr/local/bin/safe_kill.sh'
alias killall='/usr/local/bin/safe_killall.sh'
alias safe_fstab_edit='/usr/local/bin/safe_fstab_edit.sh'
alias yum='/usr/local/bin/safe_yum.sh'
alias visudo='/usr/local/bin/safe_visudo_edit.sh'
alias date='/usr/local/bin/safe_date.sh'
# /etc/fstab編集のフック (vimも考慮)
vi() {
if [[ "$1" == "/etc/fstab" ]]; then
echo "【フールプルーフ警告】/etc/fstab の直接編集は危険です。"
echo "代わりに 'safe_fstab_edit' コマンドを使用してください。"
return 1
elif [[ "$1" == "/etc/sudoers" || "$1" == "/etc/sudoers.d/"* ]]; then
echo "【フールプルーフ警告】sudoers ファイルの直接編集はロックアウトの原因になります。"
echo "構文チェックと自動バックアップが行われる 'visudo' (またはエイリアス) を使用してください。"
return 1
else
command vi "$@"
fi
}
# ==========================================
# リダイレクトによる既存ファイル上書き防止
# ==========================================
# trap DEBUGは副作用が大きいため、bash標準の noclobber を使用して
# 「>」による既存ファイルの上書き事故を防ぎます
set -o noclobber
# ==========================================
# レベル別コマンド・オプション制御
# ==========================================
USER_GROUPS=$(groups)
if echo "$USER_GROUPS" | grep -q "\blv1_read\b"; then
# 【Lv1向け制御】: 参照系のみ。ファイル操作系コマンドは実行自体を無効化
alias rm='echo "Lv1権限では rm コマンドは実行できません"'
alias mv='echo "Lv1権限では mv コマンドは実行できません"'
alias cp='echo "Lv1権限では cp コマンドは実行できません"'
alias chmod='echo "Lv1権限では chmod コマンドは実行できません"'
alias chown='echo "Lv1権限では chown コマンドは実行できません"'
elif echo "$USER_GROUPS" | grep -q "\blv2_nonfatal\b"; then
# 【Lv2向け制御】: 非致命系操作は可能。
# 共通エイリアスで設定したラッパースクリプト(safe_rm.sh等)がそのまま適用されるため、
# ここで上書きして echo で禁止する必要はありません。
: # 何もしない
elif echo "$USER_GROUPS" | grep -q "\blv3_admin\b"; then
# 【Lv3向け制御】: 管理者であってもヒューマンエラーは起きるため、基本の護符は付与する
# ラッパースクリプトではなく、標準コマンド+確認オプション(-i)程度に留める設定例
alias rm='rm -i'
alias mv='mv -i'
# Lv3は管理者のため、chmodやchownのラッパー制限( -R 禁止など)を解除する場合は以下を記述
unalias chmod 2>/dev/null
unalias chown 2>/dev/null
fi
※リダイレクトによる環境変数のPATH上書き事故(command not found)を防ぐため、profile系ファイルはchattr +iで書き込み禁止にしておきます
。
3.4. 実運用での工夫
3.4.1 テスト環境での強制練習
本番前に「やらかしシナリオ」を模擬して、誤操作が防げるか検証する文化。
3.4.2 監査ログの活用
誤操作が起きても即座に原因を追えるよう、操作ログを必ず残す設計。
3.4.3 権限分離の徹底
root権限を持つユーザを最小限にし、用途別ユーザを設計する「職務分掌」の考え方。
4. 技術的な補強ポイント
ここからは、本記事では触りだけに留め将来的に記事できたらいいなと思ってます。
4.1.1 シェル以外の対策
systemdのProtectSystemやProtectHomeで誤操作を防ぐ。
SELinux/AppArmorで「誤操作しても壊せない」制御を入れる。
4.1.2 インフラコード化
手作業での設定変更は必ずヒューマンエラーを招きます。TerraformやAnsibleでコード化し、レビューを通してから適用することで「そもそも危険な操作を直接打たない」仕組みを作りましょう。
→ 誤操作を防ぐだけでなく、再現性・監査性も確保できる。
4.1.3. etckeeperでの/etc保護
etckeeperを導入すると、/etc配下の設定ファイルが自動的にGit管理されます。誤操作があってもgit revertで即座に復旧できるため、安心感が段違いです。
5. 文化・教育面
5.1「やらかし共有文化」
SlackやWikiで「やらかし事例」を共有し、再発防止に役立てる。
ただし、やらかした人を貶める行為を避け、隠蔽を回避するため匿名とすること。
5.2 新人教育での必須科目
「rm -rf /*」や「crontab -r」など典型的な呪文を敢えて紹介し、恐ろしさを体験させる。
5.3 レビュー文化
本番投入前に必ず「第三者レビュー」を通す仕組み。
Lv1向け:制限付きシェル(rbash)の導入
「読み取りのみ、安全第一」であるLv1をさらに厳格に運用する場合、rbash(Restricted Bash)をログインシェルにする方法も記事のトピックとして非常に有用です。
cdによる移動制限や、環境変数の変更禁止、リダイレクト(>)の禁止などがシェルレベルで強制されるため、不用意な破壊工作をシステム根幹から防げます
