Написать файл с конкретными разрешениями в 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" как в "0o600Msgstr "В 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:

Конечно, все другие обычные комбинации возможны.

Другие вопросы по тегам