Иногда стоит задача запускать некоторые процессы в *nix среде под правами root (суперпользователя) не имея таких прав. Это может пригодиться, например в хостинге для перезагрузки сервисов (httpd, mysqld, memcached, etc.) или, как в нашем случае – запуск автотестов (phphunit) под пользователем apache не имея прав sudo для установки пользователя под которым запускать программу.

Предыстория:

Когда у нас появились подрядчики, которые помогали нам разрабатывать услугу – мы выделили им виртуальные хосты и пользователей, которые, по понятным причинам не имели прав суперпользователя. Также наш регламент предписывает тестировать сборку автотестами, которые запускались с командной строки. Некоторые автотесты производили тестирование модуля генерации документов, которые складывались в несколько папок. Вот тут и пришла проблема, которую мы не ждали: при использовании модуля пользователем – файлы и папки создавались с владельцем apache с правами 755, тогда как при запуске автотестов – с владельцем текущего авторизованного пользователя системы, т.к. он запускает phpunit из командной строки, после чего, пользователь уже не может записать файл в папку, созданную автотестом.

Конечно, запустить что-либо в unix под другим пользователем не проблема и легко решается через явное указание пользователя под которым нужно запустить приложение:

$ sudo -u apache -s phpunit --debug

но для выполнение такой команды нужны права суперпользователя, которых нет у пользователей подрядчика.

Таким образом, после курения гугла и мануалов пришли к единственному рабочему решению для нас: написать бинарник, который используя s-бит, может быть запущен под правами root, пользователем, у которого таких прав нет.

Собственно, ниже представлен код, который устанавливает, на время выполнения, пользователя root (setuid(0)) и собирая аргументы для запуска, выполняет запуск автотестов под пользователем apache.

Его можно модифицировать под свои нужды:

Сохраняем в файл run_test.cpp содержимое:

#include <stdlib.h>
#include <iostream>
#include <string>
 
using namespace std;
 
// Escape and quote string for use as command line argument
string qEscape(const string s) {
 
    string result("'");
 
    for (string::const_iterator i = s.begin(); i != s.end(); ++i) {
        const char c = *i;
        if (c == '\'') {
            result += "'\\'";
        }
        result += c;
    }
 
    result += '\'';
    return result;
}
 
int main(int argc, char *argv[]) {
    // Set root user
    setuid(0);
    // Command string
    string commandToExec = "sudo -u apache -s phpunit";
 
    // Concatenate arguments to command string
    for(int i=1; i<argc; i++) {
        commandToExec += " " + qEscape(string(argv[i]));
    }
    // cout << commandToExec << endl;
    // Execute command
    system(commandToExec.c_str());
 
    return 0;
}

Компилируем:

$ g++ run_test.cpp -o run_test

(важно!) Устанавливаем s-бит, владельца root и исполняемые права на получившийся бинарный файл:

$ sudo chown root run_test
$ sudo chmod u=rwx,go=xr,+s run_test

Авторизуемся под пользователем без прав sudo (или заново авторизоваться под своим пользователем, чтобы сбросить sudo) и пробуем запустить:

$ ./run_test --debug api/09-DocumentGeneratorTest.php

Profit!!! phpunit запустился под пользователем apache без прав суперпользователя.

Делали этот костыль вместе с коллегой (rakcheev.ru)

Комментарии

comments powered by Disqus