0

Запись файла с заданными правами доступа в Python

17

Я пытаюсь создать файл, который будет доступен для чтения и записи только пользователю (права доступа 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 ответ(ов)

0

Проблема в том, что метод 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().

0

Обновление

Ребята, благодарю вас за «лайки» к моему ответу, но мне нужно выступить против первоначально предложенного решения ниже. Дело в том, что при таком подходе будет момент, пусть и очень короткий, когда файл существует, но не имеет необходимых прав доступа — это открывает множество возможностей для атак и даже может привести к ошибкам в программе.

Конечно, создание файла с правильными разрешениями изначально — это правильный путь. Использование 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.

0

Вопрос касается настройки разрешений для гарантии того, что файл не будет доступен для чтения всем (только чтение/запись для текущего пользователя).

К сожалению, приведенный код:

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------- независимо от вашей среды и конфигурации. И, конечно, вы можете обрабатывать и устранять ошибки ввода-вывода по мере необходимости. Если у вас нет прав на запись в целевую директорию, вы не должны сможете создать файл, а если он уже существует, удаление будет неуспешным.

0

Я хотел бы предложить модификацию отличного ответа 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:

Конечно, все остальные обычные комбинации также возможны.

0

В вашем примере кода показан способ использования менеджера контекста для безопасной установки и восстановления 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. Это улучшит восприятие кода и сделает его более поддерживаемым.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь