はじめに
個人的には「サーバのアカウント管理はシンプルにすべき」で、SSHログイン可能なユーザは
- 管理ユーザ(wheelあり)
- 構成管理用ユーザ(wheelなし、sudo設定あり)
の2ユーザを共通で用意しておけば十分だと考えています。1
ミドルウェアが利用するユーザのように増減することが滅多にないユーザは構成管理ツールを使えば管理コストはあまりかかりませんが、人間がログインして利用するユーザ(=増減する可能性が高いユーザ)をサーバ毎に管理するのは運用が辛くなります。
ただ、最近は1つのサーバに対して複数の組織体が関与して運用するプロジェクトに関わっており、管理ユーザを共用するのは主に監査の面でよろしくないため、組織毎に管理ユーザを作成しています。
このとき組織毎の管理ユーザは「wheelあり」とするか「wheelなし、sudo設定あり」のどちらかにするかと思いますが、前述したプロジェクトでは後者の方が一般的な設定かと思います。2
前者の場合、rootパスワードを知っている人がrootにスイッチ可能な状態ですが、特権ユーザを利用可能な組織が複数に渡るのは事故の元であり、「利用可能な人=パスワードを知っている人」という曖昧なグループは管理不可能だからです。
ところが「運用上の都合」でsu
を組織毎の管理ユーザについても使えるようにしなければならないケースが出てきました。
単純にwheelグループに追加すると前述の問題があるので、
-
su
を使うことができるユーザ - スイッチ先のユーザ
のそれぞれを管理できるようにしたいと考えていたところ、幸いにして昔からそういう要件はあったようで、今回はPermitting or Restricting a User's su
Access to Privileged Accountsを参考にして
- 組織A用ユーザ(wheelなし、ミドルウェアユーザへのスイッチ可)
というユーザを作成していきます。
PAMの動作概要(一部)
検証する前にPAMの設定ファイルの書式や動作概要を理解しておきます。
参考にしたドキュメントを以下に挙げておきます。
- PAMの設定ファイル概要
- PAMで利用可能なModule一覧
man pam.d
今回は認証周りなのでmodule interfaceはauth
だけに絞って整理していきます。
PAM の設定ファイル
PAMの設定ファイルの書式は以下の通りです。
module_interface control_flag module_name module_arguments
このうちControl Flagはmoduleの戻り値に応じた処理を定義します。
Control Flagの書式は
[value1=action1 value2=action2 ...]
となっており、"moduleの戻り値がvalue
の場合はaction
を実行する"という意味になります。
原則各行の処理結果をスタックしていき、それらが総合的に判断されてPAMの処理結果になります。
value
はPAMモジュールの戻り値の定義が多いので割愛しますが、action
として利用できるものは以下の通りです。
Action | 処理 |
---|---|
ignore | この行を無視する |
bad | この行を失敗とする |
die | badと同じだが、即時PAMの処理を終了する |
ok | この行を成功とする(直前の処理結果が失敗でない限り、この行の戻り値で上書きする) |
done | okと同じだが、即時PAMの処理を終了する |
N | この行を成功とし、次のN個のmoduleをスキップする(N>0としなければならない) |
reset | これまでの処理結果のスタックを初期化する |
一般的にPAMのポリシーで使われているrequired
などは利用頻度の高い処理を省略した表記になっています。
表記 | 本来の表記 | 処理 |
---|---|---|
required | [success=ok new_authtok_reqd=ok ignore=ignore default=bad] | moduleの処理が成功しなかった場合、失敗とする |
requisite | [success=ok new_authtok_reqd=ok ignore=ignore default=die] | moduleの処理が成功しなかった場合、失敗として即終了する |
sufficient | [success=done new_authtok_reqd=done default=ignore] | moduleの処理が成功した場合、成功として即終了する |
optional | [success=ok new_authtok_reqd=ok default=ignore] | moduleの処理結果は無視する |
また、他のファイルの内容を読み込む用のフラグとしてinclude
とsubstack
があります。
表記 | 意味 |
---|---|
include |
引数に指定されたファイルを読み込む(ファイルの内容をそのまま展開するようなイメージ) |
substack |
引数に指定されたファイルを読み込み、その処理結果をこの行の処理結果とする |
上記の情報をもとにいくつかPAMの定義を見ていきます。
例1. system-auth
最終行で常にNGになるので、PAMの処理がOKになるのは3行目が成功したときだけになります。
auth required pam_env.so
auth required pam_faildelay.so delay=2000000
auth sufficient pam_unix.so nullok try_first_pass
auth requisite pam_succeed_if.so uid >= 1000 quiet_success
auth required pam_deny.so
- 環境変数をセットできなかったらNG
- 失敗時の遅延をセットできなかったらNG
- パスワード認証できたら即時OK
- パスワードなし(nullok)
- ユーザから取得済みのパスワードを利用し、取得していない場合は入力を促す(try_first_pass)
- UIDが1000未満の場合は即時NG
- 成功ログは記録しない(quiet_success)
- 常にNG
例2. su
rootユーザ以外でPAMの処理がOKになるためは「wheelグループに所属していること」と「パスワード認証に通ること(=system-auth
の結果がOK)」の2つが必要となります。
auth sufficient pam_rootok.so
auth required pam_wheel.so use_uid
auth substack system-auth
auth include postlogin
- rootユーザの場合はOK
- 現在のUIDをもとに判定し、wheelグループに所属していない場合はNG
-
system-auth
を読み込み、その結果をこの行の判定結果とする -
postlogin
を読み込む
例3. Knowledgebaseの設定
skipを使ってgroupaに所属していない場合は影響がでないポリシーになっています。
auth [success=2 default=ignore] pam_succeed_if.so use_uid user notingroup groupa
auth required pam_wheel.so use_uid group=groupa
auth required pam_listfile.so item=user sense=allow onerr=fail file=/etc/security/su-groupa-access
- 現在のUIDをもとに判定し、groupaに所属していない場合は次の2つのモジュールをスキップする
- 現在のUIDをもとに判定し、groupaに所属していない場合はNG
- スイッチ先のユーザ名が
/etc/security/su-groupa-access
に含まれていない場合はNG
実際にやってみる
- OS: CentOS 7.6 (vagrant box)
事前準備
検証用のユーザの作成は以下の通り。
- admin(管理者: wheelあり)
- auser01(groupaユーザ, ansibleにのみスイッチ可能)
- buser01(groupbユーザ, スイッチ禁止)
- ansible(ミドルウェアユーザ)
# groupadd -g 1001 groupa
# groupadd -g 1002 groupb
# useradd -g users -G wheel admin
# useradd -g groupa auser01
# useradd -g groupb buser01
# useradd ansible
# echo 'admin:********' | chpasswd
# echo 'auser01:********' | chpasswd
# echo 'buser01:********' | chpasswd
# echo 'ansible:********' | chpasswd
# id admin
uid=2004(admin) gid=100(users) groups=100(users),10(wheel)
# id auser01
uid=2005(auser01) gid=1001(groupa) groups=1001(groupa)
# id buser01
uid=2006(buser01) gid=1002(groupb) groups=1002(groupb)
# id ansible
uid=2007(ansible) gid=2007(ansible) groups=2007(ansible)
groupaに所属するユーザがスイッチ可能なユーザ一覧を記載するファイルを作成。
ansible
PAMの設定は以下の通り。
#%PAM-1.0
auth sufficient pam_rootok.so
+auth [success=1 ignore=1 default=ignore] pam_wheel.so use_uid group=groupa
+auth required pam_wheel.so use_uid
+auth [success=1 default=ignore] pam_succeed_if.so use_uid user notingroup groupa
+auth required pam_listfile.so item=user sense=allow onerr=fail file=/etc/security/su-groupa-access
auth substack system-auth
auth include postlogin
2-3行目がsuできるユーザの判定、4-5行目がgroupaに対するスイッチ先のユーザの判定になっています。
pam_wheel.soが若干曲者でwheelグループに所属していると判定された場合、原則PAM_IGNORE
を返す(debug
オプションをつけると分かる)ので、ignoreに対してもskipを記載する必要があリます。
なおvagrant box利用時にrootまたはvagrant以外のユーザからrootユーザへのスイッチがaccount
で許可されていない場合があるので注意して下さい。修正する箇所は以下の通り。
account sufficient pam_succeed_if.so uid = 0 use_uid quiet
account [success=1 default=ignore] pam_succeed_if.so user = vagrant use_uid quiet
-account required pam_succeed_if.so user notin root:vagrant
+account required pam_succeed_if.so user notin vagrant
account include system-auth
確認
adminはrootもansibleもスイッチOK。
[admin@localhost ~]$ su -
Password:
Last login: 日 4月 7 07:22:43 UTC 2019 on pts/1
[root@localhost ~]# logout
[admin@localhost ~]$ su - ansible
Password:
Last login: 日 4月 7 07:22:31 UTC 2019 on pts/1
[ansible@localhost ~]$ logout
auser01はrootはスイッチNG, ansibleはスイッチOK。
[auser01@localhost ~]$ su -
Password:
su: Authentication failure
[auser01@localhost ~]$ su - ansible
Password:
Last login: 日 4月 7 07:23:54 UTC 2019 on pts/1
[ansible@localhost ~]$ logout
[auser01@localhost ~]$
buser01はroot, ansibleともにスイッチNG。
[buser01@localhost ~]$ su -
Password:
su: Authentication failure
[buser01@localhost ~]$ su - ansible
Password:
su: Authentication failure
[buser01@localhost ~]$
ちなみにpam_wheel.soで弾かれた場合でもパスワードを間違えた場合でも、エラーメッセージは「Authentication failure」が出ますが、どちらで拒否されたか確認したいときは/var/log/secureを確認してください。
Apr 7 07:49:13 localhost su: pam_unix(su-l:auth): authentication failure; logname=auser01 uid=2005 euid=0 tty=pts/1 ruser=auser01 rhost= user=ansible