Ubuntu Weekly Recipe

第799回UbuntuでAppArmorのプロファイルを作ってみる

Ubuntuではセキュリティ対策の一環としてAppArmorを採用しています。前回のUbuntuのセキュリティを支えるAppArmor入門ではAppArmorの基本的な説明と、UbuntuにおけるAppArmorの設定方法と仕組みを紹介しました。今回はAppArmorのキモであり、普通のユーザーにはあまり縁がないものの、システム管理者にとっては必要になることもある「プロファイル」の作り方について紹介しましょう。

AppArmorのおさらい

まずは前回のおさらいをしましょう。AppArmor名前ベースの強制アクセス制御で、LSMを用いて実装されている仕組みです。ファイルパスベースで何を許可・拒否するかを設定し、管理者ですら強制的に制限を受ける仕組みをカーネルのLSM機能を用いて実現しています。これにより「rootになればかんたんになんでもできるというわけではない」ようにし、システム全体のセキュリティ強化を行います。

AppArmorは「プロファイル」によって設定を行います。これは/etc/apparmor.d/以下にあるプロファイルをシステム起動時にロードし、その内容によってシステム上の操作に制約をかけているのです。さらにプロファイルには次のようなモードが存在し、モードによって挙動が変わります。

  • enforced(適用)モード:プロファイルで許可されていない処理を行おうとした場合は、エラー扱いにしログに残す
  • complaining(学習)モード:プロファイルで許可されていない処理を行う場合、ログに残すものの、エラー扱いにはしない
  • unconfined(非制限)モード:何も制限しない

新規にプロファイルを作る場合、もしくは既存のプロファイルをカスタマイズする場合は、とりあえずcomplainingモードで動かしてどのような操作が許可されていないかを確認し、その内容に合わせてプロファイルを書くことになります。

AppArmorプロファイルの作成

それではプロファイルのフォーマットを確認しながら、適当なプロファイルを作ってみましょう。

プロファイルを作るためにはターゲットとなるプログラムが必要になります。既存のプログラムに対してプロファイルを作っても良いのですが、変更に失敗してシステムの他の部分が動かなくなると大変です。そこで実行パスが通っていない場所に、既存のプログラムを移動して、それに対するプロファイルを作ってみましょう。おすすめはbusyboxです。busyboxはさまざまなプログラムの集合体であるため、これ単体でいろいろな機能を試せます。

まずはbusyboxを/opt/sample/binに移動します。ためしにbusyboxのshアプレットを起動しましょう。

$ sudo mkdir -p /opt/sample/bin
$ sudo cp /usr/bin/busybox /opt/sample/bin/
$ /opt/sample/bin/busybox sh

BusyBox v1.30.1 (Ubuntu 1:1.30.1-7ubuntu3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ $ exit

このようにbusyboxは複数の機能(アプレット)を持っており、第2引数にそれを指定すればその機能が起動します。まずはshアプレットのうち、上記のみ可能なプロファイルを作成してみましょう。

AppArmorにおいてプロファイルを作成・編集する場合は、次のいずれかの手順をとります。

  • aa-genprofコマンドで特定のプログラムの様々な機能を使ってみて、対話的にプロファイルを生成する
  • 先にcomplainingモードで雛形を作成し、実際にプログラムを動かして記録されたログから、aa-logprofコマンドでプロファイルを変更する
  • 先にcomplainingモードで雛形を作成し、実際にプログラムを動かして記録されたログから、目視確認してプロファイルを変更する

新規に作成するなら一番上が最もかんたんです[1]。2番目・3番目もできなくはないものの、どちらかというと既存のプロファイルを修正する際に使われます。

aa-genprofaa-logprofを使うのであれば、apparmor-utilsパッケージが必要です。あらかじめ次の手順でインストールしておいてください。

$ sudo apt install apparmor-utils

では試しに、aa-genprofで作成するプログラムを指定します。最後の引数がAppArmorでいうところの、ターゲットとなるパスであり、プロファイルはこのパスをベースに(スラッシュをピリオドに変換して)作られます。

$ sudo aa-genprof /opt/sample/bin/busybox
(中略)
Please start the application to be profiled in
another window and exercise its functionality now.

Once completed, select the "Scan" option below in
order to scan the system logs for AppArmor events.

For each AppArmor event, you will be given the
opportunity to choose whether the access should be
allowed or denied.

[(S)can system log for AppArmor events] / (F)inish

これでプロファイル作成モードに入りました。あとは実際にプログラムを動かしながら権限が必要となりそうな操作を行います。このために別の端末で該当するプログラムを実行してください。

$ /opt/sample/bin/busybox sh

BusyBox v1.30.1 (Ubuntu 1:1.30.1-7ubuntu3) built-in shell (ash)
Enter 'help' for a list of built-in commands.

~ $ ls /
bin         cdrom       etc         lib         lib64       lost+found  mnt         proc        run         snap        swapfile    tmp         var
boot        dev         home        lib32       libx32      media       opt         root        sbin        srv         sys         usr
~ $ exit

ここでは試しにbusyboxのシェルを起動して、/をlsしています。必要な作業が終わったら、aa-genprofの端末に戻ってSを入力してスキャンを開始してみましょう。これは監査(audit)ログから、必要な権限を調べるモードです。次のように個別に「許可するか」⁠許可しないか」を順番に設定できます。

Reading log entries from /var/log/syslog.

(中略)
Complain-mode changes:

標準出力に出力しているため、コンソールの書き込み権限を許可するか、
もしくはそれらを定義しているabstractionファイルをロードするか確認:
Profile:  /opt/sample/bin/busybox
Path:     /dev/tty
New Mode: rw
Severity: 9

 [1 - include <abstractions/consoles>]
  2 - /dev/tty rw,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish
Adding include <abstractions/consoles> to profile.

(中略)

 [1 - include <abstractions/opencl-pocl>]
  2 - / r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish
Adding include <abstractions/opencl-pocl> to profile.

= Changed Local Profiles =

The following local profiles were changed. Would you like to save them?

 [1 - /opt/sample/bin/busybox]
(S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t  <= ここで「S」を押して保存
Writing updated profile for /opt/sample/bin/busybox.
(中略)
[(S)can system log for AppArmor events] / (F)inish  <= ここで「F」を押して終了
Setting /opt/sample/bin/busybox to enforce mode. <= プロファイルがロードされる
(中略)
Finished generating profile for /opt/sample/bin/busybox.

「abstraction」は共通で使いそうな設定をまとめたものです。Ubuntuの場合は、/etc/apparmor.d/abstractions/以下になります。プログラムのカテゴリーごとに必要な権限はabstractionにまとまっていますので、どのプログラムでもまずはいくつかのabstractionのセットを取り込むことになるでしょう。

これでプロファイルの雛形が/etc/apparmor.d/opt.sample.bin.busyboxに保存されました。実際にその中身を確認してみましょう。このようなシンプルな構成になっています。

$ sudo cat /etc/apparmor.d/opt.sample.bin.busybox
# Last Modified: Sat Jan 27 00:29:37 2024
abi <abi/3.0>,

include <tunables/global>

/opt/sample/bin/busybox {
  include <abstractions/base>
  include <abstractions/consoles>
  include <abstractions/opencl-pocl>

  /etc/passwd r,
  /opt/sample/bin/busybox mr,

}

まずabiはプロファイルで利用する機能セット(実質AppArmorのベースバージョン)を指定します。Ubuntu 22.04 LTS以降であれば<abi/3.0>で問題ありません。

AppArmorのプロファイルではファイルパス { 設定 }という書式を使っています。よって同じプロファイルに複数のコマンドのプロファイルをスコープを分けて、書くことが可能です。今回は/opt/sample/bin/busyboxのプロファイル文のみを設定しています。{ 設定 }の中は、カンマ区切りで設定を列挙していきます。#で始まる行はコメントです。

include文は、/etc/apparmor.d以下のファイルをプロファイルの一部として読み込みます。よく利用するものはここにテンプレートという形でまとめて設定されています。

  • tunables/globalは基本的にすべてのプロファイルでロードしたほうが良いものをまとめてincludeしてくれるファイルです
  • abstractions/baseは一般的なLinuxコマンドならまず必要になるであろう設定が入っています
  • abstractions/consolesは標準入出力を使うコマンドに必要な設定が入っています
  • abstractions/opencl-poclはOpenCLを使う際に必要な設定が入っています

abstractions/consolesが選択されているのはbusyboxが標準入出力を使っていたからです。これについては/dev/tty rwを設定するという選択肢もありますが、環境によってデバイスファイルが異なることを考えるとテンプレートを使ったほうが良いでしょう。

注意が必要なのはabstractions/opencl-poclのincludeです。これはls /した結果、設定されています。実際のところ本当に必要なのは/の読み込み権限」だけなのですが、⁠この権限はabstractions/opencl-poclでも設定されている」ということで、aa-logprofでは次のようにどちらを使うかの問い合わせがありました。

 [1 - include <abstractions/opencl-pocl>]
  2 - / r,
(A)llow / [(D)eny] / (I)gnore / (G)lob / Glob with (E)xtension / (N)ew / Audi(t) / Abo(r)t / (F)inish
Adding include <abstractions/opencl-pocl> to profile.

今回はOpenCLプログラムではないため、本来は2 - / r,を選ぶべきだったのです。前述のログで選ぶものを間違えた結果、許可範囲を広く取りすぎてしまったという状態です。これについては手で修正することになります。ちなみに/ rだと/そのものの読み込みしか許可されません。たとえば/optはlsできないことになります。もしサブディレクトリも含めて読み込み許可を与えたければ/** rのように記述します。/* rだとそのディレクトリの直下にあるファイルだけを許可することになります。

最後の/etc/passwd r,/opt/sample/bin/busybox mr,がAppArmorの基本文法です。つまり「どのファイルパスに対して何を許可するのか」を書いていくのです。許可内容についてはおおよそ次のような項目を指定することになります。

  • r:読み込みを許可する
  • w:書き込みを許可する
  • ?x:実行を許可する?の部分でさらに細かい調整をする)
  • mmmap()を許可する
  • l:リンクを許可する
  • k:ファイルロックを許可する

/etc/passwd rはbusyboxのsh起動時に/etc/passwdを読んでいたために追加されました。/opt/sample/bin/busybox mrは、プロファイルのターゲット自身のコマンドを実行する際に必要となる権限です。

さてaa-genprofで作ったプロファイルは自動的にロードされています。ここで許可されていないコマンドを実行してみましょう。

$ /opt/sample/bin/busybox sh -c "ls /opt"
ls: can't open '/opt': Permission denied

先ほど作ったプロファイルでは/の読み込みは許可しましたが、/optについては許可していないのでエラーとなりました。これは前述したように/** rと記述し、sudo apparmor_parser -r /etc/apparmor.d/を実行すれば、解消できます。

AppAromorプロファイルをさらにカスタマイズする

さらに別のアプレットを試してみましょう。たとえば管理者権限でpingコマンドを実行してもPermission deniedエラーとなります。

$ sudo /opt/sample/bin/busybox ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
ping: can't create raw socket: Permission denied

せっかくなのでbusyboxのpingコマンドも実行できるように、プロファイルをカスタマイズしてみましょう。プロファイルの更新にもaa-genprofを利用できます。先ほどと同じようにaa-genprofを起動してください。

$ sudo aa-genprof /opt/sample/bin/busybox

別端末でpingコマンドを実行すると、先ほどと同じようなエラーになるはずです。

$ sudo /opt/sample/bin/busybox ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
ping: can't create raw socket: Permission denied

aa-genprofの端末に戻って、auditログをスキャンしてみましょう。

[(S)can system log for AppArmor events] / (F)inish
Reading log entries from /var/log/syslog.

Enforce-mode changes:

Profile:        /opt/sample/bin/busybox
Network Family: inet
Socket Type:    raw

 [1 - network inet raw,]
(A)llow / [(D)eny] / (I)gnore / Audi(t) / Abo(r)t / (F)inish

inetファミリーのrawソケットの利用権限が必要そうなので許可(allow)しておきます。ちなみに「inetファミリー」と示されていることからわかるように、IPv6アドレスへのpingを実行したい場合は、別途inet6ファミリーの権限が必要です。

Adding network inet raw, to profile.
(中略)
 [1 - /opt/sample/bin/busybox]
(S)ave Changes / Save Selec(t)ed Profile / [(V)iew Changes] / View Changes b/w (C)lean profiles / Abo(r)t
Writing updated profile for /opt/sample/bin/busybox.

許可したら設定を保存します。このタイミングでプロファイルがリロードされます。本来ならここで終了なのですが、今回はaa-genprofの端末はそのままにして、もう一度pingコマンドを実行してみましょう。

$ sudo /opt/sample/bin/busybox ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
ping: permission denied (are you root?)

あれ、まだ成功しないようですね。aa-genprof側でauditログを再スキャンしてみます。

Profile:    /opt/sample/bin/busybox
Capability: net_raw
Severity:   8

 [1 - capability net_raw,]
(A)llow / [(D)eny] / (I)gnore / Audi(t) / Abo(r)t / (F)inish

どうやらnet_rawケーパビリティも必要だったようです。このようにある機能を使うためには、複数の権限を許可しなくてはいけないケースもあります。aa-genprofを使う場合は、成功するまで何度も「スキャン・許可」を繰り返す必要があることに注意しておきましょう。もう一度保存してから、再度pingを実行します。

$ sudo /opt/sample/bin/busybox ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1): 56 data bytes
64 bytes from 127.0.0.1: seq=0 ttl=64 time=0.064 ms
64 bytes from 127.0.0.1: seq=1 ttl=64 time=0.135 ms
64 bytes from 127.0.0.1: seq=2 ttl=64 time=0.135 ms
^C
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 packets received, 0% packet loss
round-trip min/avg/max = 0.064/0.111/0.135 ms

今度は無事に成功したようです。/etc/apparmor.d/opt.sample.bin.busyboxを見てみると、次の2行が追加されていることがわかります。

  capability net_raw,
  network inet raw,

このようにAppArmorのプロファイルの作成には、auditログを見ながら必要な権限をこまめに許可していく必要があります。aa-genprofなどである程度自動化できるとはいえ、適切な権限付与にはそれなりの手間と時間がかかりますし、プログラムに対する理解も必要です。

また、AppArmorのプロファイル自体、今回紹介したもの以外にもかなり多くの書式が存在し、柔軟な設定が可能です。プロファイルのフォーマットについてはapparmor.dのマニュアルがよくまとまっていますのでそちらも確認してください。日本語の解説がほしいのであれば、日本openSUSEユーザ会のopenSUSEのドキュメントの翻訳のAppArmorの章がおすすめです。もっと詳しく知りたい場合は、AppArmor本家のドキュメントが参考になるでしょう。

まずはこれらの情報を元に、既存のプロファイルが何をやっているのか読み解くのも良いかもしれませんね。

おすすめ記事

記事・ニュース一覧