USER 指令:为什么你的容器不该用 root 运行?

USER 指令:为什么你的容器不该用 root 运行?

概述

Docker 容器的默认用户是 root(UID 0)。在容器内以 root 运行看似方便,实则带来了严重的安全风险。USER 指令允许我们在 Dockerfile 中切换运行用户,让容器以非特权用户身份运行。这不仅是安全最佳实践,也是生产环境的硬性要求。

不使用 USER 的危险

场景 1:容器逃逸风险

FROM ubuntu:22.04
RUN apt update && apt install -y nginx
# 没有 USER 指令——默认以 root 运行
CMD ["nginx", "-g", "daemon off;"]
# 如果攻击者控制了应用进程,可以执行:
docker exec -it  bash
# 此时是 root 权限!
# 可以修改系统文件、安装恶意软件、甚至尝试容器逃逸

当容器内的进程以 root 运行时,如果存在内核漏洞或配置不当,攻击者可能:
– 通过 --privileged 模式逃逸到宿主机
– 修改 /proc/sys 等内核参数
– 在宿主机安装内核模块

场景 2:文件权限问题

FROM node:18
WORKDIR /app
COPY app.js .
RUN npm install
# 以 root 创建的所有文件
CMD ["node", "app.js"]
# 如果挂载了宿主机目录
docker run -v /host/logs:/app/logs myapp
# 宿主机上会看到文件属主是 root
ls -la /host/logs/
# -rw-r--r-- 1 root root ... app.log
# 普通用户无法查看或修改这些文件

USER 指令的正确用法

基本语法

FROM ubuntu:22.04

# 创建非 root 用户
RUN useradd -m -u 1001 -s /bin/bash appuser

# 切换用户
USER appuser

# 之后的指令全部以 appuser 运行
WORKDIR /home/appuser
COPY app.sh .
CMD ["./app.sh"]

指定 UID

FROM alpine:3.18

# 创建用户并指定 UID(方便权限管理)
RUN addgroup -g 1001 -S appgroup && \
    adduser -u 1001 -S appuser -G appgroup

USER appuser

COPY --chown=appuser:appgroup app /app
WORKDIR /app
CMD ["/app/start.sh"]

不同基础镜像的用户管理

flowchart TD
    A[创建非 root 用户] --> B{基础镜像类型}
    B --> C[Debian/Ubuntu]
    B --> D[Alpine]
    B --> E[Distroless]

    C --> C1["useradd -m appuser"]
    D --> D1["adduser -S appuser"]
    E --> E1["已内置非 root 用户"]

    C1 --> F["USER appuser"]
    D1 --> F
    E1 --> E2["USER nonroot\n(默认 65534)"]

Debian/Ubuntu 系列

FROM ubuntu:22.04

# 创建用户
RUN groupadd -r mygroup && \
    useradd -r -g mygroup -d /app -s /sbin/nologin myuser

# 设置工作目录所有权
RUN mkdir -p /app && chown -R myuser:mygroup /app

USER myuser
WORKDIR /app

Alpine 系列

FROM alpine:3.18

RUN addgroup -S mygroup && \
    adduser -S -G mygroup -h /app -s /sbin/nologin myuser

USER myuser
WORKDIR /app

Node.js 官方镜像

FROM node:18-alpine

# 官方 node 镜像已经创建了 node 用户
# 可以直接切换
USER node
WORKDIR /home/node/app
COPY --chown=node:node . .
RUN npm install
CMD ["node", "server.js"]

多阶段构建中的 USER

# 第一阶段:构建
FROM node:18 AS builder
WORKDIR /build
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

# 第二阶段:运行
FROM nginx:alpine
# 在 nginx 镜像中切换到非 root 用户
RUN sed -i 's/user nginx/user appuser/g' /etc/nginx/nginx.conf && \
    addgroup -S appgroup && \
    adduser -S -G appgroup -h /var/cache/nginx -s /sbin/nologin appuser

USER appuser
COPY --from=builder --chown=appuser:appgroup /build/dist /usr/share/nginx/html
CMD ["nginx", "-g", "daemon off;"]

特殊场景处理

场景 1:需要 root 权限初始化

FROM ubuntu:22.04

# 安装依赖必须用 root
RUN apt update && apt install -y curl nginx

# 创建非 root 用户
RUN useradd -m appuser

# 需要 root 权限的初始化操作
RUN mkdir -p /var/log/app && chown appuser:appuser /var/log/app

# 最后切换到非 root 用户
USER appuser
COPY app.sh /home/appuser/
CMD ["/home/appuser/app.sh"]

场景 2:绑定低端口

FROM ubuntu:22.04
RUN useradd -m appuser

# 非 root 用户无法绑定 <1024 的端口
# 方法1:在 nginx 中让主进程以 root 运行,worker 以 appuser 运行
# 方法2:使用高端口,通过反向代理
USER appuser
EXPOSE 8080  # 使用 8080 而非 80
CMD ["python3", "-m", "http.server", "8080"]
# 通过 iptables 转发
docker run -p 80:8080 myapp
# 宿主机 80 → 容器 8080,不需要容器内 root

安全最佳实践对比

实践项 ❌ 不良做法 ✅ 推荐做法
用户创建 不创建用户,直接用 root RUN useradd -m appuser
文件权限 COPY . . 不设置 owner COPY --chown=appuser:appgroup . .
运行时用户切换 依赖 root 运行,需要时再降权 构建时通过 USER 固定非 root 用户
容器 CAP 使用 --privileged 只添加必要 CAP(如 --cap-add=NET_BIND_SERVICE
安全上下文 不设置任何安全上下文 配合 securityContext(K8s)或 --security-opt

总结

  • 不要用 root 运行容器应用进程——这是最基本的安全实践
  • 使用 USER 指令在 Dockerfile 中切换到非 root 用户
  • 记得 chown 文件权限给非 root 用户,否则会权限不足
  • 创建用户时指定 UID,方便宿主机与容器的权限映射
  • 多阶段构建中,运行阶段必须切换到非 root 用户
  • 需要特权端口(<1024)时,用宿主机端口映射代替
© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容