umask とファイル作成時のパーミッション決定

Python でファイルを作成するとき、パーミッションが意図どおりにならないことがある。これは umask の仕組みを理解していないと解決できない。

umask とは

umask はファイル作成時のパーミッションから「引かれる」ビットマスクだ。

指定したパーミッション

ファイル作成時に指定するパーミッション(例: 0o666)

実際のパーミッション

指定したパーミッションから umask を引いた値

# 現在の umask を確認
umask
# 0022

# 意味: 新規ファイルから w(書き込み)を group と other から除去

umask の計算

実際のパーミッションは以下の式で計算される。

# 実際のパーミッション = 指定パーミッション & ~umask

# 例: mode=0o666, umask=0o022 の場合
mode = 0o666      # 110 110 110
umask = 0o022     # 000 010 010
result = mode & ~umask
print(oct(result))  # 0o644 (110 100 100)

umask が 0o022 なら、group と other から書き込み権限が除去される。

Python でのファイル作成と umask

open() でファイルを作成すると、umask が適用される。

import os
import stat

# 現在の umask を確認
current_umask = os.umask(0)
os.umask(current_umask)  # 元に戻す
print(f"umask: {oct(current_umask)}")  # 0o22

# ファイルを作成(デフォルトは 0o666 から umask を引いた値)
with open("test.txt", "w") as f:
    f.write("hello")

# パーミッションを確認
mode = os.stat("test.txt").st_mode & 0o777
print(f"permission: {oct(mode)}")  # 0o644

umask を一時的に変更する

特定のパーミッションでファイルを作成したい場合、umask を一時的に変更する。

import os

def create_with_mode(path, mode, content):
    """指定したパーミッションでファイルを作成"""
    old_umask = os.umask(0)  # umask を 0 にして影響を無効化
    try:
        fd = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, mode)
        try:
            os.write(fd, content.encode())
        finally:
            os.close(fd)
    finally:
        os.umask(old_umask)  # umask を元に戻す

# 0o600(所有者のみ読み書き可能)でファイルを作成
create_with_mode("secret.txt", 0o600, "secret data")

os.open() と open() の違い

open()

Python レベルの関数。モード指定は文字列(“r”, “w” など)。パーミッションは指定できない

os.open()

システムコールの薄いラッパー。パーミッションを直接指定できる

import os

# os.open() でパーミッションを指定
fd = os.open("data.txt", os.O_WRONLY | os.O_CREAT, 0o600)
os.write(fd, b"hello")
os.close(fd)

# open() はパーミッションを指定できない(umask 依存)
with open("data2.txt", "w") as f:
    f.write("hello")
# パーミッションは umask に従う

ディレクトリの umask

ディレクトリ作成時も umask が適用される。ディレクトリのデフォルトパーミッションは 0o777 だ。

import os

# umask=0o022 の場合
os.mkdir("newdir")  # 0o777 & ~0o022 = 0o755

# 指定したパーミッションで作成(umask は適用される)
os.mkdir("newdir2", mode=0o700)  # 実際は 0o700 & ~umask
# シェルでディレクトリを作成
mkdir testdir
ls -ld testdir
# drwxr-xr-x 2 user user 4096 Jan 15 10:00 testdir (0o755)

chmod で事後変更

umask を経由せずにパーミッションを設定したい場合は、作成後に chmod する方法もある。

import os
import stat

# ファイルを作成
with open("secret.txt", "w") as f:
    f.write("secret")

# パーミッションを変更
os.chmod("secret.txt", 0o600)

# stat モジュールの定数を使う方法
os.chmod("secret.txt", stat.S_IRUSR | stat.S_IWUSR)  # 0o600 と同じ

ただし、作成と chmod の間に一瞬のすきまがある。機密ファイルの場合は os.open() で最初から正しいパーミッションを設定するほうが安全だ。

umask のセキュリティ上の意味

umask 0o022(一般的)

group と other から書き込み権限を除去。ファイルは 0o644、ディレクトリは 0o755 になる

umask 0o077(厳格)

group と other からすべての権限を除去。ファイルは 0o600、ディレクトリは 0o700 になる

umask 0o000(危険)

制限なし。すべてのユーザーが読み書き可能なファイルが作成される可能性がある

Python スクリプト全体で umask を設定

スクリプトの開始時に umask を設定しておくと、以降のすべてのファイル作成に適用される。

import os

# スクリプト開始時に厳格な umask を設定
os.umask(0o077)

# 以降、作成されるファイルはすべて 0o600
with open("file1.txt", "w") as f:
    f.write("data1")

with open("file2.txt", "w") as f:
    f.write("data2")

まとめ

関数パーミッション指定umask 適用
open()不可される
os.open()可能される
os.mkdir()可能される
os.chmod()可能されない
umask を意識すべき場面

セキュリティが重要なファイル、他ユーザーとの共有ファイル、サービスで作成するファイル

ベストプラクティス

機密ファイルは os.open() で明示的にパーミッションを指定し、作成後に chmod で再確認する