Запись в файл с привилегиями sudo в Python

Следующий код выдает ошибку, если он запускается пользователем без полномочий root для файла, принадлежащего root, даже если пользователь, не являющийся пользователем root, имеет привилегии sudo:

try:
  f = open(filename, "w+")
except IOError:
  sys.stderr.write('Error: Failed to open file %s' % (filename))
f.write(response + "\n" + new_line)
f.close()

Есть ли способ запустить open(filename, "w+") с привилегиями sudo или альтернативную функцию, которая делает это?

Ответ 1

У вас есть несколько вариантов:

  • Запустите ваш скрипт как root или с помощью sudo
  • Установите бит setuid и получите root для скрипта (хотя во многих системах это не будет работать со скриптами, и тогда скрипт будет вызываться кем угодно)
  • Определите, что вы не работаете от имени пользователя root (os.geteuid() != 0), затем позвоните себе с помощью sudo infront (который попросит пользователя ввести свой пароль) и выйдите:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3', *sys.argv])
    sys.exit()

Вызов это выглядит так:

$ python3 be_root.py
We're not root.
Password:
We're root!

Ответ 2

TL; DR:

Ответ L3viathan имеет SynaxError Edit: отлично работает на Python 3. 5+. Вот версия для Python 3.4.3 (распространяется по умолчанию в Ubuntu 16.04) и ниже:

if os.geteuid() == 0:
    # do root things
else:
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified

Тем не менее, я выбрал другой подход, потому что в моем случае это сделало код проще:

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)
# do root things

Explaination

Ответ L3viathan зависит от PEP 448, который был включен в Python 3.5 и ввел дополнительные контексты, где допускается расширение звездного аргумента. Для Python 3.4 и ниже, конкатенация списков может использоваться для того же:

import os
import sys
import subprocess

if os.geteuid() == 0:
    print("We're root!")
else:
    print("We're not root.")
    subprocess.call(['sudo', 'python3'] + sys.argv)  # modified

Но обратите внимание: subprocess.call() запускает дочерний процесс, то есть после того, как root завершит выполнение сценария, исходный пользователь также продолжит выполнение своего сценария. Это означает, что вам нужно поместить логику с повышенными правами в одну сторону блока if/else чтобы при завершении исходного скрипта он не пытался запустить какую-либо логику, требующую повышения (это делает пример L3viathan).

Это не обязательно плохо - это означает, что как нормальная/повышенная логика может быть написана одним и тем же сценарием, так и хорошо разделены - но моя задача требовала root для всей логики Я не хотел тратить уровень отступов, если другой блок будет пустым, и я не понимал, как использование дочернего процесса повлияет на вещи, поэтому я попробовал это:

import os
import sys
import subprocess

if os.geteuid() != 0:
    subprocess.call(['sudo', 'python3'] + sys.argv)

# do things that require root

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

Затем я обнаружил, что этот Gist рекомендует os.execvp() который заменяет запущенный процесс вместо запуска дочернего:

import os
import sys

if os.geteuid() != 0:
    os.execvp('sudo', ['sudo', 'python3'] + sys.argv)  # final version

# do things that require root

Это, кажется, ведет себя как ожидалось, сохраняет уровень отступа и 3 строки кода.

Предостережение: я не знал о os.execvp() десять минут назад и пока ничего не знаю о возможных подводных камнях или тонкостях его использования. YMMV.

Ответ 3

Ваш script ограничивается разрешениями, с которыми он выполняется, поскольку вы не можете изменять пользователей, не имея уже привилегий root.

Как сказал Роб, единственный способ сделать это, не изменяя права на файл, - это запустить с sudo.

sudo python ./your_file.py

Ответ 4

Наличие возможности использования sudo не дает вам никаких привилегий, если вы на самом деле не используете его. Так как другие ребята предположили, что вы, вероятно, должны просто запустить свою программу с помощью sudo. Но если вам не нравится эта идея (я не вижу причин для этого), вы можете сделать другой трюк.

Ваш script может проверить, запущен ли он с правами root или работает только с правами пользователя. Чем script может работать с более высокими привилегиями. Здесь у вас есть пример (обратите внимание, что сохранение пароля в исходном коде не является хорошей идеей).

import os.path
import subprocess

password_for_sudo = 'pass'

def started_as_root():
    if subprocess.check_output('whoami').strip() == 'root':
        return True
    return False

def runing_with_root_privileges():
    print 'I have the power!'

def main():
    if started_as_root():
        print 'OK, I have root privileges. Calling the function...'
        runing_with_root_privileges()
    else:
        print "I'm just a user. Need to start new process with root privileges..."
        current_script = os.path.realpath(__file__)
        os.system('echo %s|sudo -S python %s' % (password_for_sudo, current_script))

if __name__ == '__main__':
    main()

Вывод:

$ python test.py
I'm just a user. Need to start new process with root privileges...
OK, I have root privileges. Calling the function...
I have the power!

$ sudo python test.py
OK, I have root privileges.
Calling the function...
I have the power!

Ответ 5

Мне нравится ответ L3viathan. Я бы добавил четвертый вариант: Используйте subprocess.Popen() для выполнения команды sh для записи файла. Что-то вроде subprocess.Popen('sudo echo \'{}\' > {}'.format(new_line, filename)).