Chienomi

rootで動作するシェルスクリプトで一般ユーザーとして実行する

Live With Linux::technique

この記事はRedHatのブログであるsystemd serviceから呼ぶシェルではsudoではなくsetprivを使うを受けてのものである。

当該記事に間違いはないが、言葉足らずであまり良い説明ではないと思ったためだ。

この記事の話題はプロセスの権限降格にあり、タイトルに反してシェルスクリプトに限らない。

そもそもsudo

sudoを他のユーザー権限でコマンドを実行するものと考えるのは仕方ないことではあるし、実際本人も “execute a command as another user” と言っているのだが、困ったことに既に言葉足らずである。

sudoは認証を経てプログラムを実行する。 単純に権限だけを変更するわけではない。認証が行われること、ログイン相当の処理を行うことなども含まれており、単純に権限のために使うには支障がある。

プロセスの権限

そもそもプロセスの情報はプロセスの構造体に入っている。 ここにUID, EUID, GID, EGIDが含まれている。 ユーザーは複数のグループに所属するが、プロセスはひとつのGIDしか持たない。

このあたりはLinuxなら/proc/$pid/statusを読むと分かりやすい。

そして、この情報は書き換え可能である。

だが、もちろん自由に書き換えられるわけではない。 もしそんなことができてしまえば、権限管理なんて成立しないからだ。

GIDを書き換えられるのは、このプロセスの所有者(UID)が所属しているグループに限られる。 EGIDも同様にEUIDに依存する。

UIDおよびEUIDを書き換えられるのは原則rootだけである。

つまり、rootから権限を落とすことに関しては、単純にプロセスのUID/GIDを書き換えることで実現できる。 例えばRubyだと、Process.uid=, Process.euid=, Process.gid=, Process.egid=というメソッドがあり、これらによってプロセスを書き換えることができる。 一般ユーザーでUIDを変更しようとした場合など、変更できないケースではErrno::EPERMが発生する。

Zshで権限降格

Zshには$UID, $EUID, $GID, $EGIDといった変数があり、これらがプロセスの権限になっている。 これらを書き換えることで権限を変更可能だ。

ただ、$GID, $EGID$UIDを書き換えてしまうと、たとえそのユーザーが所属するグループであっても書き換えられなくなってしまい、必ずグループから先に書き換える必要がある。 この問題をより簡単に解決するのが$USERNAMEだ。

この変数は書き換えると、UIDとGIDの両方が変更される。 つまり、USERNAME=fooとすれば、fooユーザーのUIDとなり、GIDに関してはfooユーザーのプライマリグループになる。 ほとんどの場合、この$USERNAME変数を使って降格を行う。

setprivはコマンドを実行するだけだが、$USERNAMEを使えば単純に降格して実行したい部分をカッコで囲むことで実現できるほか、関数の呼び出しも可能であるため、シェルスクリプトにおける権限降格としてはsetprivよりもかなり使いやすい。

# ROOT script
: ...

(
  # USER script
  USERNAME=http

  logcheck
  update_webdb
)

Bashでの事情とsetpriv

Bashにも同様の変数は存在するのだが、readonlyになっており、書き換えることができない。 BashにはBash内で完結する権限降格の方法が用意されていないのだ。

そのため、setprivのような外部コマンドを使うのだが、コマンド呼び出しにしか使えないのと、setprivはLinux固有であるため、いまひとつ潰しが効かない。

元記事で示された内容に誤りはないが、こうした事情や仕組みも併せて理解しておいたほうが良いだろう。