Linux umask 介绍

发布时间: 更新时间: 总字数:2336 阅读时间:5m 作者:IP:上海 网址

umask (User file-creation mode mask,用户文件创建权限掩码) 是 Linux/Unix 系统中一个非常重要的安全概念。它决定了用户在创建新文件或新目录时的默认基础权限umask 就像一个“过滤器”,它告诉操作系统**“在创建文件/目录时,默认要拿掉哪些权限”**。

umask 的核心工作原理

在 Linux 中,创建文件和目录都有一个最大初始权限(也叫基准权限):

  • 目录的最大初始权限是 0777 (rwxrwxrwx):因为目录需要执行权限 (x) 才能 cd 进去。
  • 文件的最大初始权限是 0666 (rw-rw-rw-):为了安全,Linux 默认不允许新创建的文件带有可执行权限。

实际计算公式: 默认权限 = 最大初始权限 & (^umask)

💡 通俗理解法(做减法): 虽然底层是二进制位运算,但大多数情况下你可以直接用**“减法”**来理解(对应位减去对应位,如果不够减就当做 0): 实际权限 = 最大初始权限 - umask

常见计算示例:

1. 当 umask = 0022 时 (最常见的默认值)

  • 新建目录0777 - 0022 = 0755 (rwxr-xr-x,即所有者拥有全部权限,同组和其他人只能读和进入)。
  • 新建文件0666 - 0022 = 0644 (rw-r--r--,即所有者可读写,同组和其他人只能读)。

2. 当 umask = 0077 时 (你的 Ubuntu 系统上的配置)

  • 新建目录0777 - 0077 = 0700 (rwx------,只有所有者有全部权限,其他人没有任何权限)。
  • 新建文件0666 - 0077 = 0600 (rw-------,只有所有者能读写,其他人没有任何权限)。 (这也完美解释了为什么你刚才在 Ubuntu 22.04 上执行代码,最终权限变成了 0700)

3. 当 umask = 0002

  • 新建目录0777 - 0002 = 0775
  • 新建文件0666 - 0002 = 0664

如何查看和设置 umask

查看当前 umask

在终端直接输入:

bash
umask

输出通常是类似 0022 这样的八进制数字(第一位0代表特殊权限位,后面三位代表 User、Group、Other)。

你也可以加 -S 参数,以人类可读的字母形式查看最终会保留的权限

bash
umask -S
# 输出类似: u=rwx,g=rx,o=rx (对应 0022 时的目录权限)

临时设置 umask(仅对当前终端/会话有效)

bash
umask 0077

永久设置 umask

如果要永久生效,需要修改配置文件。通常有以下几个位置:

  • 系统级全局生效/etc/profile/etc/login.defs (通常用于定义系统底层默认安全策略)。
  • 单个用户生效:修改用户家目录下的 ~/.bashrc~/.profile 文件,在文件末尾加上 umask 0022,然后执行 source ~/.bashrc 即可生效。

Docker 使用 umask

  • Entrypoint 脚本(最推荐,适合生产环境)

这种方法最优雅,不会破坏原有的 CMD 结构,并且能确保主进程正确接收停止信号(如 SIGTERM)。

1. 创建一个启动脚本 entrypoint.sh

bash
#!/bin/sh
# 设置 umask 为 0000
umask 0000

# 执行 Dockerfile 中 CMD 传入的命令,并将其替换为 PID 1 的主进程
exec "$@"

2. 在 Dockerfile 中配置该脚本

dockerfile
FROM ubuntu:latest  # 替换成你的基础镜像

# 将脚本复制进容器
COPY entrypoint.sh /entrypoint.sh

# 赋予执行权限
RUN chmod +x /entrypoint.sh

# 设置入口点
ENTRYPOINT ["/entrypoint.sh"]

# 你的应用程序启动命令
CMD ["your-app-command", "arg1", "arg2"]
  • Dockerfile 中修改 CMD / ENTRYPOINT
dockerfile
# 必须使用 shell 模式(或者指定 sh -c),不能只写二进制路径
CMD sh -c "umask 0000 && exec your-app-command"
  • docker run 命令中临时修改

如果你只是想在运行某个现有镜像时临时修改 umask,可以覆盖它的启动命令。

bash
docker run -d --name my-container my-image sh -c "umask 0000 && exec my-command"
  • docker-compose.yml 中修改

如果你使用 Docker Compose,可以直接通过覆盖 command 来实现:

yaml
version: '3.8'
services:
  myapp:
    image: my-image:latest
    command: sh -c "umask 0000 && exec your-app-command"
    volumes:
      - ./data:/app/data

umask 的常见使用场景

umask 主要用于在安全性协作性之间寻找平衡,以下是 4 个最典型的应用场景:

场景 1:标准个人电脑/通用服务器 (umask 0022)

  • 目的:默认保护文件的写入权限,但允许别人查看。
  • 应用:绝大多数 Linux 发行版(如 CentOS, Ubuntu 的 root 用户等)的默认设置。这保证了用户创建的普通文件不会被其他用户意外修改或删除,但系统服务(如 Nginx、Apache)可以读取用户创建的静态资源(如 html 文件)。

场景 2:高安全性服务器/多租户环境 (umask 0077)

  • 目的:极端的隐私保护,防横向越权。
  • 应用:存放敏感信息(如金融数据、密钥凭据、数据库存储目录)的服务器。设置为 0077 意味着“除了我(和 root),谁也别想看、别想碰我创建的任何文件”。你的 Ubuntu 22.04 当前应该就是处于这种高安全配置下。

场景 3:团队共享协作开发 (umask 0002)

  • 目的:方便同一个用户组内的成员共同编辑文件。
  • 应用:假设公司有个开发组 group 叫 developers,有一个共享目录 /data/project。如果 umask 是 0022,张三建的文件李四只能看不能改。 如果在该目录下要求 umask 0002(通常配合共享目录的 chmod g+s 强制继承组),那么张三新建的目录权限是 0775,文件是 0664,此时李四(同属 developers 组)就可以直接修改张三创建的文件了。

场景 4:后台服务/守护进程安全加固 (umask 0027)

  • 目的:精细化控制服务的日志和数据文件。
  • 应用:在编写系统服务(Daemon)或者容器(Docker)的启动脚本时,经常会看到在启动主程序前先执行 umask 0027
    • 0027 意味着:屏蔽同组的写权限 (w),屏蔽其他人的所有权限 (rwx)。
    • 效果:服务生成的日志文件、配置文件,所属组(可能是运维组)可以查看排错,但不能修改;而无关人员(Other)连看都看不了,防止日志中包含的敏感信息(如 Token、密码)泄漏。

代理示例

go
package main

import (
	"fmt"
	"os"
	"syscall"
)

func main() {
	// 设置当前进程的 umask 为 0022 (Go 1.13+ 推荐使用 0o 前缀表示八进制)
	// 注意:syscall.Umask 会影响整个进程,实际项目中通常由操作系统环境去配置,不需要在代码里写死
	oldUmask := syscall.Umask(0o022)
	defer syscall.Umask(oldUmask)

	dirName := "test_dir"
	fileName := "test_file.txt"

	// 测试前清理可能存在的旧文件
	os.RemoveAll(dirName)
	os.Remove(fileName)
	defer os.RemoveAll(dirName)
	defer os.Remove(fileName)

	// 1. 创建目录,请求权限 0777
	err := os.MkdirAll(dirName, 0o777)
	if err != nil {
		panic(err)
	}

	// 2. 创建文件,请求权限 0777
	err = os.WriteFile(fileName,[]byte("hello golang"), 0o777)
	if err != nil {
		panic(err)
	}

	// 3. 读取并打印实际权限
	dirInfo, err := os.Stat(dirName)
	if err != nil {
		panic(err)
	}

	fileInfo, err := os.Stat(fileName)
	if err != nil {
		panic(err)
	}

	// %04o 格式化输出 4 位八进制数
	// .Mode().Perm() 仅获取权限位,.Mode().String() 会输出带文件类型标志(如 d 或 -)的完整字符串
	fmt.Printf("请求目录权限 0777 + Umask 0022 -> 实际目录权限: %04o (%s)\n", dirInfo.Mode().Perm(), dirInfo.Mode().String())
	fmt.Printf("请求文件权限 0777 + Umask 0022 -> 实际文件权限: %04o (%s)\n", fileInfo.Mode().Perm(), fileInfo.Mode().String())
}

运行输出结果:

text
请求目录权限 0777 + Umask 0022 -> 实际目录权限: 0755 (drwxr-xr-x)
请求文件权限 0777 + Umask 0022 -> 实际文件权限: 0755 (-rwxr-xr-x)