Подтвердить что ты не робот

Запись файла с определенными разрешениями в Python

Я пытаюсь создать файл, который доступен только для чтения и -writable (0600).

Это единственный способ сделать это, используя os.open() следующим образом?

import os
fd = os.open('/path/to/file', os.O_WRONLY, 0o600)
myFileObject = os.fdopen(fd)
myFileObject.write(...)
myFileObject.close()

В идеале, я бы хотел использовать ключевое слово with, чтобы я мог автоматически закрыть объект. Есть ли лучший способ сделать то, что я делаю выше?

4b9b3361

Ответ 1

Какая проблема? file.close() закроет файл, хотя он был открыт с помощью os.open().

with os.fdopen(os.open('/path/to/file', os.O_WRONLY | os.O_CREAT, 0o600), 'w') as handle:
  handle.write(...)

Ответ 2

В этом ответе рассматриваются многочисленные проблемы, связанные с ответом 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.
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, если мне не 0o222 сначала установить umask, результирующий файл может вместо этого иметь mode 0o220, а это не то, что я хотел. На man 2 open, режим созданного файла - mode & ~umask.

umask восстанавливается в свое первоначальное значение как можно скорее. Это получение и настройка не являются поточно-ориентированными, и в threading.Lock необходимо использовать threading.Lock.

Для получения дополнительной информации о umask, обратитесь к этой теме.

Ответ 3

Обновление Люди, в то время как я благодарю вас за upvotes здесь, я сам должен спорить с моим первоначально предложенным решением ниже. Причина в том, что так происходит, будет время, какое бы маленькое оно ни было, где файл существует, и не имеет надлежащих разрешений на месте - это открывает широкие возможности атаки и даже ошибочное поведение.
Конечно, создание файла с правильными разрешениями в первую очередь - это способ пойти - против правильности этого, используя 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". В Python 2.x он будет писать только 0600, но это и вводит в заблуждение, и устарело.)

Однако, если ваша безопасность важна, вы, вероятно, должны прибегнуть к ее созданию с помощью os.open, как и вы, и используйте os.fdopen для извлечения объекта файла Python из дескриптора файла, возвращаемого os.open.

Ответ 4

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

К сожалению, сам по себе код:

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/delete (удаление безопаснее, так как у вас могут не быть разрешения на запись в целевом каталоге), и если вы у вас проблемы с безопасностью, вы не хотите писать какой-то файл, где вам не разрешено!), в противном случае у вас может возникнуть проблема с безопасностью, если хакер создаст файл перед вами с глобальными полномочиями r/w в ожидании вашего переехать. В этом случае os.open откроет файл без установки его прав вообще, и вы останетесь с секретным файлом r/w в мире...

Итак, вам нужно:

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

Ответ 5

Я хотел бы предложить модификацию отличного ответа A-B-B, который отделяет проблемы немного более четко. Основное преимущество заключается в том, что вы можете обрабатывать исключения, возникающие при открытии дескриптора файла отдельно от других проблем во время фактической записи в файл.

Внешний 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:

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