Написать файл с конкретными разрешениями в Python
Я пытаюсь создать файл, который только для чтения и записи (0600
).
Это единственный способ сделать это с помощью os.open()
следующее?
import os
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
myFileObject = os.fdopen(fd)
myFileObject.write(...)
myFileObject.close()
В идеале я хотел бы иметь возможность использовать with
ключевое слово, чтобы я мог закрыть объект автоматически. Есть ли лучший способ сделать то, что я делаю выше?
5 ответов
В чем проблема? file.close()
закроет файл, даже если он был открыт os.open()
,
with os.fdopen(os.open('/path/to/file', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
handle.write(...)
Этот ответ решает несколько проблем с ответом от vartec, особенно umask
беспокойство.
import os
import stat
# Define file params
fname = '/tmp/myfile'
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL # Refer to "man 2 open".
mode = stat.S_IRUSR | stat.S_IWUSR # This is 0o600 in octal.
umask = 0o777 ^ mode # Prevents always downgrading umask to 0.
# For security, remove file with potentially elevated mode
try:
os.remove(fname)
except OSError:
pass
# Open file descriptor
umask_original = os.umask(umask)
try:
fdesc = os.open(fname, flags, mode)
finally:
os.umask(umask_original)
# Open file handle and write to file
with os.fdopen(fdesc, 'w') as fout:
fout.write('something\n')
Если желаемый режим 0600
, это может быть более четко указано как восьмеричное число 0o600
, Еще лучше, просто используйте stat
модуль.
Несмотря на то, что старый файл был сначала удален, состояние гонки все еще возможно. В том числе os.O_EXCL
с os.O_CREAT
в флагах будет препятствовать созданию файла, если он существует из-за состояния гонки. Это необходимая дополнительная мера безопасности для предотвращения открытия файла, который может уже существовать с потенциально повышенным mode
, В Python 3 FileExistsError
с [Errno 17] вызывается, если файл существует.
Неспособность сначала установить umask
в 0
или 0o777 ^ mode
может привести к неверному mode
(разрешение) устанавливается os.open
, Это потому, что по умолчанию umask
обычно нет 0
и будет применяться к указанному mode
, Например, если мой оригинал umask
является 2
т.е. 0o002
и мой указанный режим 0o222
если мне не удастся сначала установить umask
полученный файл может иметь вместо mode
из 0o220
что не то, что я хотел. в man 2 open
, режим созданного файла mode & ~umask
,
umask
как можно скорее восстанавливается до первоначального значения. Это получение и настройка не являются потокобезопасными, и threading.Lock
должен использоваться в многопоточном приложении.
Для получения дополнительной информации о umask, обратитесь к этой теме.
обновите народ, пока я благодарю вас за положительные отзывы, я сам должен поспорить с предложенным ниже решением. Причина заключается в том, что все происходит таким образом, и в течение некоторого времени, хотя и небольшого, файл существует и не имеет надлежащих разрешений - это оставляет широкие возможности для атаки и даже с ошибочным поведением.
Конечно, прежде всего нужно создать файл с правильными разрешениями - вопреки правильности этого, используя Python. with
это просто конфетка.
Поэтому, пожалуйста, возьмите этот ответ в качестве примера "что не делать";
оригинальный пост
Ты можешь использовать os.chmod
вместо:
>>> import os
>>> name = "eek.txt"
>>> with open(name, "wt") as myfile:
... os.chmod(name, 0o600)
... myfile.write("eeek")
...
>>> os.system("ls -lh " + name)
-rw------- 1 gwidion gwidion 4 2011-04-11 13:47 eek.txt
0
>>>
(Обратите внимание, что способ использования восьмеричных чисел в Python заключается в том, чтобы быть явным - с помощью префикса "0o
" как в "0o600
Msgstr "В Python 2.x было бы хорошо написать 0600
- но это вводит в заблуждение и не рекомендуется.)
Однако, если ваша безопасность критична, вам, вероятно, следует прибегнуть к ее созданию с os.open
, как вы делаете и используете os.fdopen
получить объект Python File из дескриптора файла, возвращенного os.open
,
Вопрос заключается в настройке разрешений, чтобы файл не был доступен для чтения всем пользователям (только чтение / запись для текущего пользователя).
К сожалению, сам по себе код:
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
не гарантирует, что разрешение будет отказано в мире. Он пытается установить r/w для текущего пользователя (при условии, что umask это позволяет), вот и все!
В двух очень разных тестовых системах этот код создает файл с -rw-r - r-- с моим umask по умолчанию и -rw-rw-rw- с umask(0), что определенно не то, что нужно (и создает серьезный риск безопасности).
Если вы хотите убедиться, что в файле нет битов, установленных для группы и мира, вам нужно сначала замаскировать эти биты (помните - umask - это отказ в разрешениях):
os.umask(0o177)
Кроме того, чтобы быть на 100% уверенным в том, что файл еще не существует с другими разрешениями, сначала необходимо выполнить chmod / удалить его (удалить безопаснее, поскольку у вас могут не быть разрешения на запись в целевой каталог - и если у вас есть проблемы с безопасностью Вы не хотите писать какой-либо файл, к которому у вас нет прав!), иначе у вас может возникнуть проблема с безопасностью, если хакер создал файл перед вами с мировыми правами на чтение в ожидании вашего перемещения. В этом случае os.open откроет файл, не устанавливая его разрешения, и у вас останется секретный файл с мировым именем...
Итак, вам нужно:
import os
if os.path.isfile(file):
os.remove(file)
original_umask = os.umask(0o177) # 0o777 ^ 0o600
try:
handle = os.fdopen(os.open(file, os.O_WRONLY | os.O_CREAT, 0o600), 'w')
finally:
os.umask(original_umask)
Это безопасный способ обеспечить создание файла -rw ------- независимо от вашей среды и конфигурации. И, конечно, вы можете ловить и обрабатывать ошибки ввода-вывода по мере необходимости. Если у вас нет разрешения на запись в целевой каталог, вы не сможете создать файл, и, если он уже существует, удаление не удастся.
Я бы сделал по-другому.
from contextlib import contextmanager
@contextmanager
def umask_helper(desired_umask):
""" A little helper to safely set and restore umask(2). """
try:
prev_umask = os.umask(desired_umask)
yield
finally:
os.umask(prev_umask)
# ---------------------------------- […] ---------------------------------- #
[…]
with umask_helper(0o077):
os.mkdir(os.path.dirname(MY_FILE))
with open(MY_FILE, 'wt') as f:
[…]
Файл-манипулирующий код, как правило, уже try
-except
-heavy; что еще хуже с os.umask's finally
не собирается приносить ваши глаза больше радости. Между тем, развернуть свой собственный менеджер контекста так просто, что приводит к более аккуратному вложению отступов.
Я хотел бы предложить модификацию превосходного ответа АББ, которая разделяет проблемы немного более четко. Основным преимуществом будет то, что вы можете обрабатывать исключения, возникающие при открытии файлового дескриптора, отдельно от других проблем во время реальной записи в файл.
Внешний try ... finally
Блок заботится о обработке разрешения и umask
проблемы при открытии файлового дескриптора. Внутренний with
Блок работает с возможными исключениями при работе с файловым объектом Python (так как это было желанием OP):
try:
oldumask = os.umask(0)
fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT, 0o600)
with os.fdopen(fdesc, "w") as outf:
# ...write to outf, closes on success or on exceptions automatically...
except IOError, ... :
# ...handle possible os.open() errors here...
finally:
os.umask(oldumask)
Если вы хотите добавить в файл вместо записи, то дескриптор файла должен быть открыт следующим образом:
fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT | os.O_APPEND, 0o600)
и объект файла, как это:
with os.fdopen(fdesc, "a") as outf:
Конечно, все другие обычные комбинации возможны.