はじめに

この記事は、PHP の exec() 関数から別ユーザー(例:deploy や app)でインストールした CLI ツールを呼び出そうとして「Permission denied」にハマっている開発者向けです。
特に「CentOS / AlmaLinux / RHEL」系で SELinux が有効な環境や、sudoers をいじったことがない方は、Web 検索しても答えが見つからず何時間も溶かしてしまうことがあります。

読み進めることで以下がわかります。

  • なぜ「permission denied」が出るのか(ファイル権限・sudoers・SELinux の 3 パターン)
  • セキュアに sudo 権限を与える最小構成の書き方
  • SELinux を無効にせずに exec() を成功させるコマンド一発の対処法

前提知識

  • PHP で exec() が使えること(safe_mode OFF)
  • Linux ファイル権限(chmod / chown)の基礎
  • visudo で sudoers を編集したことがあると尚早い

そもそも「Permission denied」はどこで起きているのか

PHP(通常は apache または www-data ユーザー)が「他のユーザーが所有するディレクトリ内のバイナリ」を実行しようとすると、以下 3 箇所で遮られる可能性があります。

  1. ファイルのパーミッション(rwx)が足りない
  2. sudo で実行する際、sudoers に許可が書かれていない
  3. SELinux / AppArmor などの MAC(強制アクセス制御)が拒否

この記事では 2. と 3. を主に解説します。1. は chmod 755chown で解決できるため省略します。

実装・設定手順:sudoers + SELinux 編

ステップ1:問題の切り分け(audit.log で原因を特定)

まずは audit.log を見て、SELinux に阻まれているかを確認します。

Bash
# 拒否ログを grep sudo ausearch -m avc -ts recent | grep httpd

出力があれば SELinux の問題です。出力がなければ sudoers かファイル権限の問題です。

ステップ2:最小権限の sudoers エントリを記述

例として /opt/deploy/bin/my-tool を deploy ユーザー所有として、www-data ユーザーから実行したい場合を考えます。

Bash
# visudo で以下を追加 Cmnd_Alias MYTOOL = /opt/deploy/bin/my-tool www-data ALL=(deploy) NOPASSWD: MYTOOL

ポイント:

  • (deploy) と丸括弧で実行ユーザー(runas)を明示 → これにより sudo -u deploy my-tool の形でしか実行できない
  • NOPASSWD: を付けることでパスワード入力をスキップ
  • ワイルドカード(my-tool *)は極力使わず、必要なサブコマンドごとに列挙する

ステップ3:PHP 側で sudo 経由で呼び出す

Php
$out = []; $ret = -1; exec('sudo -u deploy /opt/deploy/bin/my-tool 2>&1', $out, $ret); if ($ret !== 0) { error_log('my-tool failed: ' . implode(PHP_EOL, $out)); }

ステップ4:SELinux を許可(httpd プロセスの transition)

SELinux が有効な環境では、httpd が直接別ユーザーのプロセスを起動することが制限されています。
以下のポリシーを当てるだけで exec() が通るようになります。

Bash
# 必要に応じて policycoreutils-python-utils をインストール sudo dnf install -y policycoreutils-python-utils # 許可モジュールを生成・適用 sudo audit2allow -a -M httpd_run_mytool sudo semodule -i httpd_run_mytool.pp

これで「avc: denied { transition }」のログが出なくなります。

ハマったポイント:sudoers 文法ミスで「sudo: no tty present」

Defaults requiretty が有効な環境(古い RHEL 系)だと、PHP からの sudo が TTY 不足でコケます。
/etc/sudoers.d/www-data に以下を追記して回避します。

Defaults:www-data !requiretty

解決策まとめ

  1. 実行したいコマンドを Cmnd_Alias で限定し、runas ユーザーまで明示
  2. SELinux 拒否が出たら audit2allow で許可モジュールを作る
  3. TTY エラーが出たら Defaults:www-data !requiretty を忘れずに

まとめ

本記事では、PHP の exec() から別ユーザーのツールが Permission denied になる原因と、sudoers + SELinux の両面からの解決法を解説しました。

  • sudoers では「実行対象コマンド」「実行ユーザー(runas)」を厳密に限定
  • SELinux 環境では audit2allow で許可モジュールを作るだけで OK
  • TTY エラーは !requiretty で回避

これで Web アプリから高権限ツールを呼び出す際も、セキュアに最小権限で運用できます。
次回は「systemd の User= 指定で PHP-FPM プールを分ける」方法を紹介します。

参考資料