web-dev-qa-db-ja.com

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キーワードを使用して、オブジェクトを自動的に閉じることができるようにします。上記のことを行うより良い方法はありますか?

51
lfaraone

どうしたの? file.close()は、os.open()で開いていたファイルを閉じます。

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

この回答は、 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の場合、8進数0o600としてより明確に指定できます。さらに良いのは、statモジュールを使用するだけです。

古いファイルが最初に削除されたとしても、競合状態は依然として可能です。フラグにos.O_EXCLとともにos.O_CREATを含めると、競合状態が原因でファイルが存在する場合、ファイルが作成されなくなります。これは、潜在的に昇格されたmodeで既に存在する可能性があるファイルを開くことを防ぐために必要な二次的なセキュリティ対策です。 Python 3、[Errno 17]のFileExistsErrorは、ファイルが存在する場合に発生します。

最初にumask0または0o777 ^ modeに設定しないと、os.openによって誤ったmode(許可)が設定される可能性があります。これは、デフォルトのumaskが通常0ではなく、指定されたmodeに適用されるためです。たとえば、元のumask2である場合、つまり0o002であり、指定したモードが0o222である場合、最初にumaskの設定に失敗すると、代わりにmode of 0o220を持つことができますが、これは私が望んでいたものではありません。 man 2 openごとに、作成されたファイルのモードはmode & ~umaskです。

umaskは、できるだけ早く元の値に復元されます。この取得と設定はスレッドセーフではないため、threading.Lockをマルチスレッドアプリケーションで使用する必要があります。

Umaskの詳細については、 this thread を参照してください。

28
Acumenus

問題は、ファイルが誰でも読み取り可能にならないように許可を設定することです(現在のユーザーの読み取り/書き込みのみ)。

残念ながら、コード自体は次のとおりです。

fd = os.open('/path/to/file', os.O_WRONLY, 0o600)

許可が世界に対して拒否されることを保証しません。現在のユーザーにr/wを設定しようとします(umaskで許可されている場合)、それだけです!

2つの非常に異なるテストシステムで、このコードは、デフォルトのumaskで-rw-r--r-でファイルを作成し、-rw-rw-rw-でファイルを作成しますumask(0)これは間違いなく望ましいものではありません(深刻なセキュリティリスクをもたらします)。

ファイルにグループおよびワールド用のビットが設定されていないことを確認したい場合は、これらのビットを最初にumaskする必要があります(umaskはdenialパーミッションのことです):

os.umask(0o177)

さらに、ファイルが異なるパーミッションですでに存在しないことを100%確実にするために、最初にそれをchmod /削除する必要があります(ターゲットディレクトリに書き込みパーミッションがない場合があるため、削除はより安全です-そしてセキュリティ上の懸念がある場合、許可されていない場所にファイルを書きたくない!)、そうしないと、ハッカーがあなたの前にファイルを作成した場合、あなたの移動を見越して世界中の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 -------ファイルを確実に作成するための安全な方法です。そしてもちろん、必要に応じてIOErrorをキャッチして対処することができます。ターゲットディレクトリに書き込み権限がない場合、ファイルを作成できません。既に存在する場合、削除は失敗します。

3
jytou

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:

もちろん、他のすべての通常の組み合わせが可能です。

1
Laryx Decidua

私は違うやり方をするでしょう。

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のfinallyでそれをさらに悪化させることは、あなたの目をこれ以上喜ばせないでしょう。一方、独自のコンテキストマネージャをローリングするとその簡単なになり、インデントの入れ子が多少きれいになります。

0
ulidtko