Запись файла с заданными правами доступа в 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()
.
В вашем примере вы используете os.fdopen()
для обертки файлового дескриптора, полученного от os.open()
. Это прекрасно работает, но важно помнить, что os.fdopen()
создает объект файла, на который with
Statement накладывает контекстный менеджер. Таким образом, когда блок with
завершается, вызывается handle.close()
, который закрывает файловый дескриптор.
Если ваш файл открывался через os.open()
, необходимо быть осторожным с тем, как вы управляете его закрытием. При использовании os.fdopen()
, вам не нужно вручную вызывать close()
, так как with
сам позаботится об этом, вызывая close()
на объекте handle
в конце блока with
.
Если вы пытаетесь отделить файл от его дескриптора и закрыть его позже, вам нужно сохранить дескриптор и убедиться, что оба обращения (через os.fdopen()
и close()
) не пересекаются. Используйте один из подходов - либо оберните только в with
, либо управляйте закрытием вручную.
Пример правильного использования:
import os
# Открываем файл с помощью os.open()
fd = os.open('/path/to/file', os.O_WRONLY | os.O_CREAT, 0o600)
try:
# Оборачиваем файловый дескриптор в файловый объект
with os.fdopen(fd, 'w') as handle:
handle.write('Ваши данные...')
finally:
# Не вызываем os.close(fd) здесь, потому что handle.close() сделает это за нас
pass
Таким образом, блок with
управляет закрытием, и нет необходимости в дополнительном вызове file.close()
.
Обновление
Ребята, благодарю вас за «лайки» к моему ответу, но мне нужно выступить против первоначально предложенного решения ниже. Дело в том, что при таком подходе будет момент, пусть и очень короткий, когда файл существует, но не имеет необходимых прав доступа — это открывает множество возможностей для атак и даже может привести к ошибкам в программе.
Конечно, создание файла с правильными разрешениями изначально — это правильный путь. Использование with
в Python в данном случае — это лишь глазурь на торте.
Поэтому, пожалуйста, воспринимайте этот ответ как пример того, что не стоит делать.
Оригинальный пост
Вы можете использовать вместо этого 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
". В Python 2.x можно было просто писать 0600
, но это как минимум вводит в заблуждение и считается устаревшим.)
Однако, если ваша безопасность критична, вам, скорее всего, стоит прибегнуть к созданию файла с помощью os.open
, а затем использовать os.fdopen
, чтобы получить объект файла Python из дескриптора файла, возвращаемого os.open
.
Вопрос касается настройки разрешений для гарантии того, что файл не будет доступен для чтения всем (только чтение/запись для текущего пользователя).
К сожалению, приведенный код:
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
сам по себе не гарантирует, что доступ будет отклонен для всех остальных. Он пытается установить права на чтение/запись для текущего пользователя (при условии, что umask это позволяет), и это все!
На двух разных тестовых системах этот код создаёт файл с правами -rw-r--r-- с моим умаском по умолчанию и -rw-rw-rw- при umask(0), что определенно не то, что нужно (и представляет серьезный риск с точки зрения безопасности).
Если вы хотите быть уверены, что у файла нет прав для группы и других пользователей, вам нужно сначала установить umask для этих битов (помните, что umask — это отказ в разрешениях):
os.umask(0o177)
Кроме того, чтобы быть на 100% уверенным, что файл не существует с другими разрешениями, вам нужно сначала изменить его права или удалить (удаление безопаснее, так как у вас могут не быть прав на запись в целевую директорию — и если вас беспокоят вопросы безопасности, вы не хотите создавать файл там, где это запрещено!). В противном случае может возникнуть проблема безопасности, если злоумышленник уже создал файл с правами r/w для всех перед вашим действием. В этом случае 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------- независимо от вашей среды и конфигурации. И, конечно, вы можете обрабатывать и устранять ошибки ввода-вывода по мере необходимости. Если у вас нет прав на запись в целевую директорию, вы не должны сможете создать файл, а если он уже существует, удаление будет неуспешным.
Я хотел бы предложить модификацию отличного ответа A-B-B, которая более четко разделяет обязанности. Главное преимущество этого подхода в том, что вы можете отдельно обрабатывать исключения, возникающие при открытии дескриптора файла, от других проблем, связанных с фактической записью в файл.
Внешний блок try ... finally
отвечает за обработку проблем с разрешениями и umask
при открытии дескриптора файла. Внутренний блок with
обрабатывает возможные исключения при работе с объектом файла в Python (как и желал автор вопроса):
try:
oldumask = os.umask(0)
fdesc = os.open(outfname, os.O_WRONLY | os.O_CREAT, 0o600)
with os.fdopen(fdesc, "w") as outf:
# ...пишите в outf, закрывается автоматически при успешном завершении или возникновении исключений...
except IOError as e:
# ...обрабатывайте возможные ошибки os.open() здесь...
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:
Конечно, все остальные обычные комбинации также возможны.
В вашем примере кода показан способ использования менеджера контекста для безопасной установки и восстановления umask
. Однако, я бы предложил сделать это немного иначе.
Во-первых, стоит отметить, что код, связанный с манипуляциями с файлами, часто уже является достаточно загроможденным из-за использования конструкции try
-except
. Добавление к этому блока finally
, отвечающего за восстановление umask
, может только усугубить читаемость. Вместо этого вы можете использовать написанный вами менеджер контекста, что позволит сделать код более аккуратным и чистым.
Вот как это может выглядеть:
from contextlib import contextmanager
import os
@contextmanager
def umask_helper(desired_umask):
""" Небольшой помощник для безопасной установки и восстановления 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)) # Создаем директорию с заданным umask
with open(MY_FILE, 'wt') as f:
[…]
Преимущество данного подхода в том, что он позволяет избежать лишней вложенности и делает код более читабельным. Вы организуете процесс, когда изменения umask
активируют и деактивируют необходимые условия, без излишнего нагромождения try
-except
. Это улучшит восприятие кода и сделает его более поддерживаемым.
UnicodeDecodeError: Кодек 'charmap' не может декодировать байт X в позиции Y: символ отображается как <неопределённый>
Правильный способ записи строки в файл?
Сохранение списка в файл с помощью Python с учетом переносов строк
Как открыть несколько файлов с помощью "with open" в Python?
Вывод строки в текстовый файл