Ответ 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(...)
Я пытаюсь создать файл, который доступен только для чтения и -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
, чтобы я мог автоматически закрыть объект. Есть ли лучший способ сделать то, что я делаю выше?
Какая проблема? 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.
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, обратитесь к этой теме.
Обновление
Люди, в то время как я благодарю вас за 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
.
Вопрос о настройке разрешений, чтобы файл не был доступен для чтения (только чтение/запись для текущего пользователя).
К сожалению, сам по себе код:
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 по мере необходимости. Если у вас нет прав на запись в целевом каталоге, вы не сможете создать файл, и если он уже существует, удаление завершится неудачно.
Я хотел бы предложить модификацию отличного ответа 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:
Конечно, возможны и другие обычные комбинации.