Python3, subprocess и sudo

newbie

Опубликован:  2018-11-19T06:29:55.846156Z
Отредактирован:  2018-11-26T06:59:57.866925Z

Вы когда-нибудь пробовали внутри программы на Питоне, в сессии рядового пользователя запускать программы с правами root? В этой демонстрации я покажу, как можно выполнить программу sudo с автоматическим вводом пароля прямо в интерактивной сессии Питона.

Питон, в силу своего простого синтаксиса и низкого порога входа, является великолепным инструментом для автоматизации рутинных действий, и поэтому достаточно часто используется для реализации сложных административных задач с одновременным мониторингом ресурсов системы. При этом иногда хочется, чтобы программа в целом исполнялась с правами обычного пользователя, и только некоторые действия в ней выполнялись с правами суперпользователя и автоматическим вводом пароля. Посмотрим, как это можно реализовать, попытаемся исполнить консольную программу whoami с правами root внутри программы на Питоне.

Программа whoami выводит на экран имя действующего пользователя. Если эту программу запустить в обёртке sudo, она выведет на экран имя суперпользователя.

RZoJJ09gXx.png

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

Как вообще исполнить консольную программу whoami внутри программы на Питоне? Есть несколько вариантов. Первый вариант - это функция system из модуля os стандартной библиотеки Питона.

newbie@stretch:~$ python3
Python 3.5.3 (default, Sep 27 2018, 17:25:39) 
[GCC 6.3.0 20170516] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import os
>>> os.system('whoami')
newbie
0
>>> 

И этот вариант в сочетании с sudo мне видится весьма проблемным.

Второй вариант - это инструменты модуля subprocess стандартной библиотеки Питона. Этот модуль позволяет порождать новые процессы, управлять ими, подключаться к их вводу, выводу, потоку ошибок и получать код возврата. Чтож.., давайте попробуем воспользоваться этими инструментами для решения нашей маленькой задачи.

Первое, что мне потребуется - модуль shlex, при помощи которого я получу исполняемую программу в виде списка аргументов. Импортирую его.

>>> import shlex
>>>

Далее, мне необходимы следующие инструменты из subprocess: Popen и PIPE. Импортирую их.

>>> from subprocess import Popen, PIPE
>>> 

Готовлю целевую команду, которую необходимо исполнить в порождённом под-процессе.

>>> cmd = shlex.split('sudo -S whoami')
>>> 

Здесь следует отметить, что sudo будет исполнена с опцией -S.

zHcR154g0B.png

Опция -S позволяет взять пароль из стандартного ввода, а приглашение на ввод пароля отдать в стандартный поток ошибок.

Пароль... Передать пароль в программу и при этом не печатать его в тексте программы можно различными методами:

  • стандартным запросом на ввод пароля перед исполнением кода программы;
  • посредством аргумента командной строки;
  • из переменной окружения etc...

В рамках этой демонстрации, поскольку все действия выполняются в интерактивной сессии интерпретатора на стендовой машине, я просто вобью пароль пользователя в качестве значения соответствующей переменной прямо в сессии.

>>> passwd = '3dGjXSa3' + '\n'
>>> 

Замечание: следует обратить внимание, что к паролю пользователя я присоединил символ окончания строки, сделано намерено и важно это понимать.

Все подготовительные действия выполнены. Остаётся исполнить задуманную команду.

>>> with Popen(cmd, stdin=PIPE, stdout=PIPE, stderr=PIPE) as whoami:
...     result = whoami.communicate(input=passwd.encode('utf-8'))
... 
>>> 

При помощи класса Popen, который поддерживает свойства контекстного менеджера, я создал процесс whoami, который исполняет команду cmd. Стандартные потоки ввода/вывода/ошибок этого процесса заданы при помощи специального значения PIPE. Метод communicate даёт возможность передать этому процессу заданный пароль на стандартный поток ввода, при этом передаваемое значение должно быть байт-строкой соответствующей кодировки и содержать символ конца строки.

После завершения процесса можно получить доступ к коду возврата.

>>> whoami.returncode
0
>>> 

Код возврата в данном случае подтверждает, что программа исполнена без ошибок.

В результате выполненного процесса в переменной result хранятся два значения:

  • стандартный вывод исполненного процесса;
  • стандартный поток ошибок.
>>> result
(b'root\n', b'[sudo] \xd0\xbf\xd0\xb0\xd1\x80\xd0\xbe\xd0\xbb\xd1\x8c \xd0\xb4\xd0\xbb\xd1\x8f newbie: ')
>>> 

При необходимости эти значения можно вывести на экран.

>>> result[0].decode('utf-8').strip()
'root'
>>> result[1].decode('utf-8')
'[sudo] пароль для newbie: '
>>> 

Поставленная задача решена. Вся отладка выглядит следующим образом.

V0n8k6nB0C.png

Задача оказалась несложной. В процессе решения этой задачи я обратился к гуглу и посмотрел, какие решения этой задачи предлагают другие люди. Одно из предложенных гуглом решений меня заинтересовало, поэтому в следующем выпуске этого блога я продемонстрирую и его. Продолжение следует...

Комментарии: