なぜ従来のchmod/chownだけでは不十分なのか?
ファイルパーミッション(chmod、chown)はLinuxにおける最も基本的な防衛ラインです。しかし実際の運用環境では、それ単体では十分な安全性を確保できません。
よくある侵入シナリオを考えてみましょう。NginxやPython APIにRCE(リモートコード実行)の脆弱性があり、不正侵入を許してしまったとします。この時点で、攻撃者は即座にwww-dataユーザーの全権限を掌握します。その結果、/tmpディレクトリを探索されたり、データベースの認証情報が含まれるソースコードを盗み見られたり、/dev/shm配下に配置された悪意あるバイナリを実行されたりするリスクが生じます。
プロセスの隔離と特権昇格(Privilege Escalation)を阻止するため、システムエンジニアは主に以下の3つのアプローチを検討します:
- Chroot Jail / コンテナ(Docker、Podman):アプリケーションを独立したnamespaceやcgroupにカプセル化します。非常に手軽で便利ですが、カーネル脆弱性(Dirty COWなど)が存在する場合や、誤って
--privilegedフラグを付与した場合にコンテナエスケープ(Container Breakout)が発生する恐れがあります。 - SELinux(Security-Enhanced Linux):ラベルベースで動作する非常に強力な強制アクセス制御(MAC)機構です。しかし、ポリシーの構文が極めて複雑であり、マウントポイントを変更しただけでサービスが原因不明の停止に追い込まれるなど、運用負荷が高いのが難点です。
- AppArmor(Application Armor):Ubuntu、Debian、openSUSEなどに標準搭載されているMACソリューションです。ファイルパスを直接ベースにしたパスベース制御を行うため、ルールの可読性やデバッグ性に優れ、はるかに扱いやすいのが特徴です。
どのような場合にAppArmorを採用すべきか?
サーバーでUbuntuやDebianを運用している場合、ダウンタイムのリスクを最小限に抑えつつ迅速にサーバーのセキュリティ強化(Server Hardening)を図るなら、AppArmorが有力な第一候補となります。
具体的には、次のようなユースケースでAppArmorは最大限の効果を発揮します:
- ネットワークプロセス(Webサーバーやファイルアップロードを処理するカスタムスクリプトなど)を厳格に制限し、指定したディレクトリ以外への読み書きを完全に遮断したい場合。
- Webシェル等で悪意あるコードを注入された際に、対話型シェル(
/bin/shや/bin/bashなど)の起動を完全に無力化したい場合。 - コンテナ基盤を構築するオーバーヘッドをかけずに、ベアメタル上のサービスに対して軽量な防御層を追加したい場合。
実践:ゼロからAppArmorプロファイルを構築する
ステップ1:ツールのインストールとカーネルサポートの確認
主要なUbuntu LTSディストリビューション(20.04〜24.04)では、カーネルでAppArmorがデフォルトで有効化されています。まずは必要な管理ツールパッケージをインストールします:
sudo apt update
sudo apt install -y apparmor-utils apparmor-profiles
# システムの現在のステータスを確認
sudo aa-status
aa-statusコマンドを実行すると、enforceモード(違反をブロックするモード)およびcomplainモード(違反をブロックせずログ警告のみ記録するモード)で動作しているプロファイル数が表示されます。
ステップ2:保護対象となるサンプルのPythonサービスを作成
動作をイメージしやすくするため、/opt/myapp/app.pyにファイル処理を行うPythonスクリプトを作成します。このスクリプトに本来必要な正当な権限は、/var/log/myapp.logへのログ書き込みと、/var/data/uploads/へのファイル保存の2つだけです。
sudo mkdir -p /opt/myapp /var/data/uploads
sudo bash -c 'cat << "EOF" > /opt/myapp/app.py
#!/usr/bin/python3
import os
# 1. 正常な処理:動作ログの書き込み
with open("/var/log/myapp.log", "a") as f:
f.write("Service running normally.\n")
# 2. 異常な処理:システムの機密ファイル読み取り試行(攻撃シミュレーション)
try:
with open("/etc/shadow", "r") as f:
print("[警告] /etc/shadow の不正読み取りに成功しました!")
except Exception as e:
print(f"[安全] /etc/shadow へのアクセスに失敗しました: {e}")
EOF'
sudo chmod +x /opt/myapp/app.py
root権限で/opt/myapp/app.pyを実行してみると、スクリプトが/etc/shadowを簡単に読み取れてしまうことが確認できます。それでは、AppArmorを使ってこの不正アクセスを確実に阻止しましょう。
ステップ3:ホワイトリストを定義したカスタムプロファイルの作成
/etc/apparmor.d/ディレクトリ配下に配置するプロファイル名は、対象パスのスラッシュ(/)をドット(.)に置き換えた形式にします。それでは/etc/apparmor.d/opt.myapp.app.pyを作成しましょう:
sudo bash -c 'cat << "EOF" > /etc/apparmor.d/opt.myapp.app.py
#include <tunables/global>
/opt/myapp/app.py {
#include <abstractions/base>
#include <abstractions/python>
# プロファイルを継承してPythonインタープリタの実行を許可
/usr/bin/python3* ix,
# ソースコードの読み取りおよびデータディレクトリの読み書き権限
/opt/myapp/ r,
/opt/myapp/** r,
/var/data/uploads/ rw,
/var/data/uploads/** rw,
/var/log/myapp.log rwk,
# サブシェルの実行および機密ファイルへのアクセスを明示的に拒否
deny /bin/** mrwklx,
deny /usr/bin/** mrwklx,
deny /etc/shadow* mrwklx,
deny /etc/passwd* mrwklx,
}
EOF'
覚えておくべき主要なパーミッションフラグ:
r(Read):ファイルの読み取りおよびディレクトリの一覧表示。w(Write):ファイルの新規作成および上書き保存。k(Locking):ファイルロック(File Lock)の取得を許可。ix(Inherit Execute):現在のプロファイルを継承して子プロセスを実行。deny:明示的なアクセス拒否(許可ルールよりも常に優先して適用)。
ステップ4:プロファイルの読み込みとComplainモードでの監査
本番環境における鉄則は、いきなりenforceモードを有効化しないことです。まずはプロファイルをcomplainモードで稼働させ、実際のシステム挙動やアクセスログを十分に収集・監視します:
# 作成したプロファイルをカーネルにロード
sudo apparmor_parser -r /etc/apparmor.d/opt.myapp.app.py
# プロファイルを監視(complain)モードに設定
sudo aa-complain /opt/myapp/app.py
実際の運用では、アプリケーションが多数の共有ライブラリ(.so)や隠し設定ファイルを読み込むことが多く、プロファイル作成時にこれらを見落としがちです。いきなりenforceモードを適用するとPermission Deniedエラーでサービス停止に繋がりますが、complainモードであれば通常通り動作を継続させつつ、不足しているルールをシステムログから安全に洗い出すことができます。
ステップ5:ログ監査とEnforceモードの有効化
アプリケーションの一連の業務フローを一通りテストした後、記録されたAppArmorのログを確認します:
# systemd journalからAppArmorのログを抽出
sudo journalctl -fx | grep -i apparmor
# 対話型ウィザードを使ってログを解析し、不足しているルールを自動更新
sudo aa-logprof
# 必要なルールが揃ったことを確認後、強制ブロック(enforce)モードに切り替え
sudo aa-enforce /opt/myapp/app.py
ここで、再度sudo /opt/myapp/app.pyを実行してみます。実行結果は以下のようになります:
[安全] /etc/shadow へのアクセスに失敗しました: [Errno 13] Permission denied: '/etc/shadow'
root権限で実行されているにもかかわらず、プロファイルのホワイトリストに定義されていないため、カーネルレベルで/etc/shadowへのアクセスが確実にブロックされました。

