Passenger 向けに AppArmor を設定する

2013/01/06

2013年最初の『Rails雑感』のテーマは、AppArmorです。Ubuntu Server上でPassengerを利用してRailsアプリケーションを運用する場合にAppArmorをどう設定すべきかを考えてみたいと思います。

本稿が対象とするUbuntu Serverのバージョンは12.04 LTSです。すでにApacheとPassengerを使ってRailsアプリケーションを運用している、という前提でお話しします。Rubyのバージョンは1.9.3で、/usr/local/bin にインストールされています。

本稿の内容を実際に試してみたい方は、Rails本番環境構築ガイドを参考にして環境を整えてください。

AppArmorの基礎知識

AppArmorは、Linuxカーネルのセキュリティモジュールの一種です。同様のモジュールとしてはSELinuxがあり、おそらくはこちらの方が有名です。これらのセキュリティモジュールはLinux Security Modules(LSM)と呼ばれるインターフェースを通じて、Linuxカーネルに組み込まれます。基本的には、セキュリティモジュールは1個しかLinuxカーネルに組み込めませんので、Linuxのディストリビューションごとにデフォルトのセキュリティモジュールが異なります。CentOSはSELinuxを、UbuntuはAppArmorを採用しています。

AppArmorは、SELinuxと同様に強制アクセス制御(MAC)と呼ばれる仕組みをLinuxカーネルにもたらします。所有者とグループの概念に基礎を置く伝統的なLinuxのアクセス制御(任意アクセス制御)との違いをまとめると次のようになります:

  • 誰が何に対してどんな処理を行えるのかを細かく制御できる。
  • セキュリティポリシーは管理者によって一元的に管理される。
  • 管理者以外のユーザーは、自分が所有するファイルであってもセキュリティポリシーの範囲を超えてアクセス制限を緩和できない。

Webサイトを運営する視点では、ApacheやMySQLなどのサービスに対してアクセス制限をかけられることが重要です。任意アクセス制御のもとでは、攻撃者がこれらのサービスの乗っ取りに成功すると、事実上何でもやりたい放題だったのですが、強制アクセス制御が適用されていれば、攻撃者はセキュリティポリシーの範囲内でしか動けない、ということになります。つまり、強制アクセス制御を利用すると「サービスを閉じこめる」ことができるのです。

AppArmorでは、サービスごとにプロファイルと呼ばれる設定ファイルを作り、それをAppArmorにロードすることで対象となるサービスがAppArmorの管理下に入ります。その際、サービスごとに強制モード(Enforce mode)と学習モード(Complain mode)の2つのモードのいずれかを選択できます。サービスがセキュリティポリシーに違反するアクセスを行ったときの結果が異なります。前者では単純に拒否されます。後者ではアクセス自体は可能ですが、監査ログに記録が残ります。

AppArmorに関する情報は、http://wiki.apparmor.net が詳しいです(英語)。この記事の執筆にあたっては、特に Mod apparmor example を参照しました。

AppArmorの状態を確認する

では、AppArmorの状態を確認するところから作業を始めましょう。

Ubuntu ServerのインストールされたマシンにSSHでログインして、apparmor サービスの status を表示します。

$ sudo service apparmor status
apparmor module is loaded.
6 profiles are loaded.
6 profiles are in enforce mode.
   /sbin/dhclient
   /usr/lib/NetworkManager/nm-dhcp-client.action
   /usr/lib/connman/scripts/dhclient-script
   /usr/sbin/mysqld
   /usr/sbin/ntpd
   /usr/sbin/tcpdump
0 profiles are in complain mode.
3 processes have profiles defined.
3 processes are in enforce mode.
   /sbin/dhclient (874)
   /usr/sbin/mysqld (1093)
   /usr/sbin/ntpd (1363)
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

注目すべきは 3 processes are in enforce mode. に続く3行です。ここに列挙されているプログラムが強制モードでAppArmorの管理に入っています。1個目はDHCP(動的ホスト設定プロトコル)のクライアント、2個目はMySQLサーバ、NTP(時刻同期プロトコル)サーバです。Apacheが入っていませんね。つまり、Apacheは強制アクセス制御によって閉じこめられていない、というわけです。

mod_apparmorの導入

Apacheに強制アクセス制御を導入するには、Apacheモジュールの mod_apparmor を利用するのが簡便です。

apt-get でパッケージ libapache2-mod-apparmor をインストールしましょう。

$ sudo apt-get install libapache2-mod-apparmor

続いて、a2enmod コマンドで mod_apparmor をApacheに組み込みます。

$ sudo a2enmod apparmor

すると、/etc/apparmor.d ディレクトリに、Apache用のプロファイル usr.lib.apache2.mpm-prefork.apache2 が生成されます。初期状態での中身は次の通りです(コメント行と余分な空行を除外してあります)。

#include <tunables/global>
/usr/lib/apache2/mpm-prefork/apache2 flags=(complain) {
  #include <abstractions/base>
  #include <abstractions/nameservice>

  capability kill,
  capability net_bind_service,
  capability setgid,
  capability setuid,
  capability sys_tty_config,

  / rw,
  /** mrwlkix,

  ^DEFAULT_URI flags=(complain) {
    #include <abstractions/base>
    #include <abstractions/nameservice>

    / rw,
    /** mrwlkix,
  }

  ^HANDLING_UNTRUSTED_INPUT flags=(complain) {
    #include <abstractions/nameservice>

    / rw,
    /** mrwlkix,
  }

  #include <apache2.d>
  #include <local/usr.lib.apache2.mpm-prefork.apache2>
}

Apache向けのプロファイルを修正

/etc/apparmor.d/usr.lib.apache2.mpm-prefork.apache2 をエディタで開き、capability sys_tty_config, の直後にに以下の記述を追加します。

  capability chown,
  capability dac_override,
  capability fowner,
  capability fsetid,
  capability sys_ptrace,
  capability sys_resource,

バーチャルホストの設定変更

次に、バーチャルホストの設定ファイルを修正します。現在の内容は次のようになっています。

<VirtualHost *:80>
  ServerName example.com
  AADefaultHatName passenger
  DocumentRoot /home/kuroda/example/current/public

  <Directory /home/kuroda/example/current/public>
    AllowOverride all
    Options -MultiViews
  </Directory>

  (省略)
</VirtualHost>

ここで、AllowOverride all の上に次の記述を挿入してください。

  AAHatName passenger

Passenger向けのサブプロファイルを作成

続いて、Passenger向けのサブプロファイルを作成します。ディレクトリ /etc/apparmor.d/apache2.d の直下に新規ファイル passenger を作成し、以下の内容を書き込んでください。

^passenger {
  #include <abstractions/apache2-common>
  #include <abstractions/base>
  #include <abstractions/nameservice>

  /usr/local/bin/ruby rix,
  /usr/local/lib/ruby/**/*.so m,
  /usr/local/lib/ruby/** r,
  /usr/local/lib/ruby/gems/1.9.1/gems/passenger-*/agents/** rix,

  /var/log/apache2/*.log w,
  /var/log/apache2/*.log w,

  /home/kuroda/example/current/** r,
  /home/kuroda/example/current/public/log/*.log a,
}

最後の2行は、実際のディレクトリ構成に従って変更してください。

プロファイルの有効化

aa-enforce コマンドでプロファイルを有効にします。

$ sudo aa-enforce /etc/apparmor.d/usr.lib.apache2.mpm-prefork.apache2

Apacheを再起動します。

$ sudo service apache2 restart

ブラウザでWebサイトにアクセスして、Railsアプリケーションが正常に機能すればOKです。うまく行かない場合は、まずはプロファイルの書き間違いを疑ってください。誤りがあれば修正し、次のコマンドでプロファイルをリロードしてください。

$ sudo apparmor_parser -r /etc/apparmor.d/usr.lib.apache2.mpm-prefork.apache2

もし、プロファイルに間違いがないのにRailsアプリケーションが正常に動作しない場合は、aa-genprof ユーティリティを用いてプロファイルを修正するのですが、これについてはまたの機会に。