如何在 Docker 中处理权限?

DockerDockerBeginner
立即练习

💡 本教程由 AI 辅助翻译自英文原版。如需查看原文,您可以 切换至英文原版

简介

处理 Docker 中的权限是管理容器化应用程序的一个关键方面。本教程将指导你了解 Docker 文件权限、在 Docker 容器中设置权限,并探索权限管理的最佳实践。学完本教程后,你将掌握在基于 Docker 的项目中有效处理权限的知识。

在本实验中,你将学习 Docker 容器权限的工作原理、如何在容器中创建和使用非 root 用户,以及如何在主机和容器之间共享数据时管理权限。

理解 Docker 默认权限

在 Docker 中,理解权限的工作原理是维护容器安全的基础。让我们先从探究 Docker 容器的默认权限设置开始。

检查 Docker 中的默认用户

默认情况下,Docker 在容器内以 root 用户身份运行进程。如果容器被攻破,这可能会导致潜在的安全问题。为了观察这种行为,让我们创建一个简单的容器并检查运行进程的用户。

首先,确保你位于项目目录中:

cd ~/project

现在,运行一个基本的 Ubuntu 容器并检查当前用户:

docker run -it --rm ubuntu:22.04 whoami

你应该会看到以下输出:

root

这证实了 Docker 默认使用 root 用户。现在,让我们检查用户 ID (UID) 和组 ID (GID):

docker run -it --rm ubuntu:22.04 id

输出应该类似于:

uid=0(root) gid=0(root) groups=0(root)

uid=0gid=0 表明容器以 root 用户和组的身份运行,这意味着它们对容器内的所有资源拥有完全访问权限。

探究容器内的文件权限

让我们来研究一下 Docker 容器内的文件权限是如何工作的。在容器内创建一个简单的文件并检查其权限。

首先,创建一个在后台运行的容器:

docker run -d --name permissions-demo ubuntu:22.04 sleep 3600

现在,让我们在这个容器内执行命令来创建一个文件并检查其权限:

docker exec permissions-demo touch /test-file
docker exec permissions-demo ls -l /test-file

你应该会看到类似于以下的输出:

-rw-r--r-- 1 root root 0 May 15 12:34 /test-file

注意,该文件由 root 用户和组拥有。这表明 Docker 容器内默认创建的所有文件都由 root 用户拥有。

让我们再创建一个目录并检查其权限:

docker exec permissions-demo mkdir /test-directory
docker exec permissions-demo ls -ld /test-directory

输出应该类似于:

drwxr-xr-x 2 root root 4096 May 15 12:35 /test-directory

同样,该目录由 root 用户和组拥有。

在进入下一步之前,让我们清理这个容器:

docker stop permissions-demo
docker rm permissions-demo

这展示了 Docker 在用户权限方面的默认行为。在下一步中,我们将学习如何在 Docker 容器中创建和使用非 root 用户以提高安全性。

在 Docker 中创建和使用非 root 用户

在 Docker 容器内以 root 用户身份运行应用程序存在安全风险。如果攻击者攻破了以 root 身份运行的容器,他们有可能获得主机系统的更高权限。在这一步中,我们将学习如何在 Docker 容器中创建和使用非 root 用户。

创建包含非 root 用户的 Dockerfile

让我们创建一个 Dockerfile,定义一个非 root 用户并将其设置为运行命令的默认用户。

首先,为我们的项目创建一个新目录:

mkdir -p ~/project/non-root-user
cd ~/project/non-root-user

现在,使用 nano 文本编辑器创建一个 Dockerfile:

nano Dockerfile

将以下内容添加到 Dockerfile 中:

FROM ubuntu:22.04

## Create a new user called 'appuser' with user ID 1000
RUN useradd -m -u 1000 appuser

## Create a directory for the application and set ownership
RUN mkdir -p /app && chown -R appuser:appuser /app

## Set the working directory to /app
WORKDIR /app

## Switch to the non-root user
USER appuser

## Create a test file
RUN touch test-file.txt

## Command to run when the container starts
CMD ["bash", "-c", "echo 'Running as user:' && whoami && echo 'File ownership:' && ls -l test-file.txt && tail -f /dev/null"]

Ctrl+O 保存文件,然后按 Enter,再按 Ctrl+X 退出编辑器。

这个 Dockerfile 完成了以下操作:

  1. 创建一个名为 appuser、UID 为 1000 的新用户
  2. 创建一个应用程序目录并将其所有权赋予 appuser
  3. 将工作目录设置为 /app
  4. 切换到非 root 用户以执行后续命令
  5. 创建一个将由 appuser 拥有的测试文件
  6. 设置一个命令,用于显示用户和文件所有权信息

构建并运行非 root 用户容器

现在,让我们根据 Dockerfile 构建 Docker 镜像:

docker build -t non-root-image .

你应该会看到表明 Docker 正在构建镜像的输出。构建完成后,使用这个镜像运行一个容器:

docker run --name non-root-container -d non-root-image

现在,让我们查看容器的输出:

docker logs non-root-container

你应该会看到类似于以下的输出:

Running as user:
appuser
File ownership:
-rw-r--r-- 1 appuser appuser 0 May 15 12:45 test-file.txt

这证实了容器以 appuser 用户身份运行,并且测试文件由 appuser 拥有。

测试非 root 用户的权限

让我们连接到正在运行的容器并探究非 root 用户的权限:

docker exec -it non-root-container bash

现在你应该以 appuser 身份进入容器内部。让我们验证一下:

whoami

你应该会看到:

appuser

检查用户 ID 和组 ID:

id

输出应该类似于:

uid=1000(appuser) gid=1000(appuser) groups=1000(appuser)

现在,尝试在根目录下创建一个文件:

touch /root-test-file

你应该会看到权限被拒绝的错误:

touch: cannot touch '/root-test-file': Permission denied

这是因为非 root 用户在根目录下没有写入权限。不过,我们的用户可以在 /app 目录下写入文件:

touch /app/user-test-file
ls -l /app/user-test-file

你应该会看到新文件由 appuser 拥有:

-rw-r--r-- 1 appuser appuser 0 May 15 12:50 /app/user-test-file

退出容器的 shell:

exit

在进入下一步之前,让我们清理这个容器:

docker stop non-root-container
docker rm non-root-container

这一步展示了如何在 Docker 容器中创建和使用非 root 用户。使用非 root 用户可以通过限制应用程序的可用权限来提高容器化应用程序的安全性。

管理卷挂载权限

Docker 卷允许你在主机和容器之间共享数据。然而,在使用卷时正确管理权限对于避免问题至关重要。在这一步中,我们将学习如何在使用卷挂载时处理权限问题。

理解卷挂载权限问题

Docker 卷挂载的主要挑战在于,容器内的用户 ID 可能与主机系统上的用户 ID 不匹配。这可能会在访问挂载卷中的文件时导致权限问题。

让我们通过一个简单的示例来演示这个问题。

首先,在主机上创建一个新目录,我们将把它挂载到容器中:

mkdir -p ~/project/host-data
cd ~/project/host-data

在这个目录中创建一个测试文件:

echo "This is a test file created on the host" > host-file.txt

检查这个文件的所有权:

ls -l host-file.txt

你应该会看到该文件由 labex 用户(你在主机上的当前用户)拥有:

-rw-r--r-- 1 labex labex 39 May 15 13:00 host-file.txt

现在,让我们运行一个挂载这个目录的容器,并尝试修改该文件:

docker run -it --rm -v ~/project/host-data:/container-data ubuntu:22.04 bash

现在你已经进入容器内部。让我们检查挂载文件的所有权:

ls -l /container-data/host-file.txt

你可能会看到类似于以下的输出:

-rw-r--r-- 1 1000 1000 39 May 15 13:00 /container-data/host-file.txt

注意,文件显示的是数字 ID 而不是用户名,因为容器不知道主机上的 labex 用户。

尝试在挂载目录中创建一个新文件:

echo "This is a test file created in the container" > /container-data/container-file.txt

现在,检查这个新文件的所有权:

ls -l /container-data/container-file.txt

你应该会看到:

-rw-r--r-- 1 root root 47 May 15 13:05 /container-data/container-file.txt

该文件在容器内由 root 用户拥有(因为我们默认以 root 身份运行)。

退出容器:

exit

现在,检查主机上这两个文件的所有权:

ls -l ~/project/host-data/

你会看到从容器内部创建的文件在主机上由 root 用户拥有:

-rw-r--r-- 1 root  root  47 May 15 13:05 container-file.txt
-rw-r--r-- 1 labex labex 39 May 15 13:00 host-file.txt

如果主机上的非 root 用户需要访问或修改这些文件,这可能会导致权限问题。

解决卷权限问题

有几种方法可以解决卷权限问题。让我们探讨一些常见的方法。

方法 1:在 Dockerfile 中设置用户以匹配主机用户

为这个示例创建一个新目录:

mkdir -p ~/project/volume-permissions
cd ~/project/volume-permissions

创建一个新的 Dockerfile:

nano Dockerfile

添加以下内容:

FROM ubuntu:22.04

## Create a user with the same UID as the host user
RUN useradd -m -u 1000 appuser

## Create app directory and set ownership
RUN mkdir -p /app/data && chown -R appuser:appuser /app

## Set working directory
WORKDIR /app

## Switch to appuser
USER appuser

## Command to run
CMD ["bash", "-c", "echo 'I can write to the mounted volume' > /app/data/test.txt && tail -f /dev/null"]

保存并退出编辑器。

构建镜像:

docker build -t volume-permissions-image .

创建一个用于测试的主机目录:

mkdir -p ~/project/volume-permissions/host-data

运行一个挂载卷的容器:

docker run -d --name volume-test -v ~/project/volume-permissions/host-data:/app/data volume-permissions-image

过一会儿,检查主机上创建的文件的所有权:

ls -l ~/project/volume-permissions/host-data/

你应该会看到该文件由 UID 为 1000 的用户拥有,这应该与你主机用户的 UID 匹配:

-rw-r--r-- 1 labex labex 35 May 15 13:15 test.txt

这种方法之所以有效,是因为我们在容器中创建了一个与主机用户 UID 相同的用户。

方法 2:使用 --user 标志

另一种方法是使用 --user 标志来指定运行容器时使用的用户 ID。

首先,清理之前的容器:

docker stop volume-test
docker rm volume-test

现在使用 --user 标志运行一个容器:

docker run -d --name user-flag-test --user "$(id -u):$(id -g)" -v ~/project/volume-permissions/host-data:/data ubuntu:22.04 bash -c "echo 'Created with --user flag' > /data/user-flag-test.txt && sleep 3600"

这个命令使用你当前的用户 ID 和组 ID 运行容器。

检查新文件的所有权:

ls -l ~/project/volume-permissions/host-data/user-flag-test.txt

你应该会看到该文件由你的主机用户拥有:

-rw-r--r-- 1 labex labex 23 May 15 13:20 user-flag-test.txt

在进入下一步之前,让我们进行清理:

docker stop user-flag-test
docker rm user-flag-test

这些方法展示了如何在使用 Docker 卷时管理权限。通过使容器和主机之间的用户 ID 匹配,你可以避免在它们之间共享数据时出现权限问题。

实施权限最佳实践

让我们通过创建一个更实际的 Docker 容器示例,将所学的所有知识付诸实践,该示例遵循权限最佳实践。我们将创建一个简单的 Web 应用程序容器,遵循权限方面的安全最佳实践。

创建安全的 Web 应用程序容器

首先,为我们的示例创建一个新目录:

mkdir -p ~/project/secure-app
cd ~/project/secure-app

让我们创建一个简单的 Web 应用程序。首先,创建一个 app.py 文件:

nano app.py

添加以下 Python 代码以创建一个简单的 Flask Web 服务器:

from flask import Flask
import os

app = Flask(__name__)

@app.route('/')
def hello():
    return 'Hello from a secure container!'

@app.route('/whoami')
def whoami():
    return os.popen('id').read()

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8080)

保存并退出编辑器。

现在,为 Python 依赖项创建一个 requirements.txt 文件:

nano requirements.txt

添加以下内容:

flask==2.0.1

保存并退出编辑器。

现在,创建一个遵循权限最佳实践的 Dockerfile:

nano Dockerfile

添加以下内容:

FROM python:3.10-slim

## Create a non-root user to run the application
RUN groupadd -g 1000 appgroup \
  && useradd -u 1000 -g appgroup -s /bin/bash -m appuser

## Set working directory and create necessary directories
WORKDIR /app

## Copy requirements first to leverage Docker cache
COPY requirements.txt .

## Install dependencies as root
RUN pip install --no-cache-dir -r requirements.txt

## Copy application code
COPY app.py .

## Create a data directory that the application can write to
RUN mkdir -p /app/data \
  && chown -R appuser:appgroup /app

## Set proper permissions
RUN chmod -R 755 /app

## Switch to non-root user
USER appuser

## Expose the port the app will run on
EXPOSE 8080

## Command to run the application
CMD ["python", "app.py"]

保存并退出编辑器。

让我们构建 Docker 镜像:

docker build -t secure-web-app .

现在,运行容器:

docker run -d --name secure-app -p 8080:8080 secure-web-app

让我们测试该应用程序。首先,检查容器是否正在运行:

docker ps

你应该会在列表中看到你的 secure-app 容器。现在,使用 curl 测试应用程序:

curl http://localhost:8080/

你应该会看到:

Hello from a secure container!

让我们检查应用程序以哪个用户身份运行:

curl http://localhost:8080/whoami

你应该会看到类似于以下的输出:

uid=1000(appuser) gid=1000(appgroup) groups=1000(appgroup)

这证实了我们的应用程序以非 root 用户 appuser 身份运行。

安全分析

让我们分析一下我们的 Dockerfile 在安全方面的改进:

  1. 非 root 用户:我们创建了一个专用用户 (appuser) 来运行应用程序,降低了容器被攻破时的风险。
  2. 最小权限:我们仅设置了应用程序正常运行所需的必要权限。
  3. 明确的所有权:容器中的所有文件和目录都有明确的所有权定义。
  4. 合理的目录结构:我们为应用程序及其数据创建了专用的目录结构。

这些实践有助于确保即使攻击者设法利用了应用程序中的漏洞,他们对容器和主机系统的访问也将受到限制。

在安全应用程序中实施卷权限

让我们为我们的安全应用程序添加一个卷,以演示如何正确处理卷的权限。

首先,停止并移除现有的容器:

docker stop secure-app
docker rm secure-app

在主机上创建一个数据目录:

mkdir -p ~/project/secure-app/host-data

设置主机目录的正确权限:

sudo chown 1000:1000 ~/project/secure-app/host-data

现在,运行挂载了卷的容器:

docker run -d --name secure-app-with-volume \
  -p 8080:8080 \
  -v ~/project/secure-app/host-data:/app/data \
  secure-web-app

让我们连接到容器并在挂载的卷中创建一个文件:

docker exec -it secure-app-with-volume bash

现在,在容器内部,在挂载的卷中创建一个测试文件:

echo "Test file created from inside the container" > /app/data/container-file.txt

检查文件的所有权:

ls -l /app/data/container-file.txt

你应该会看到该文件由 appuser 拥有:

-rw-r--r-- 1 appuser appgroup 43 May 15 13:30 /app/data/container-file.txt

退出容器:

exit

现在,检查主机上文件的所有权:

ls -l ~/project/secure-app/host-data/container-file.txt

你应该会看到该文件由 UID 为 1000 的用户拥有(对应于你的主机用户):

-rw-r--r-- 1 labex labex 43 May 15 13:30 container-file.txt

这表明,通过正确的权限配置,容器内挂载卷中创建的文件在主机上也会有正确的所有权。

在结束之前,让我们进行清理:

docker stop secure-app-with-volume
docker rm secure-app-with-volume

通过遵循这些 Docker 权限的最佳实践,你可以创建安全、可靠的容器化应用程序,这些应用程序能够在容器内部以及与主机系统共享数据时正确管理文件权限。

总结

在本次实验中,你学习了在 Docker 容器中管理权限的重要技术:

  1. 理解 Docker 默认权限:你了解了 Docker 容器默认以 root 身份运行的情况,以及这可能带来的潜在安全风险。
  2. 创建和使用非 root 用户:你学会了如何在 Docker 容器中创建自定义的非 root 用户,这是一项关键的安全最佳实践。
  3. 管理卷挂载权限:你掌握了如何在使用卷在主机和容器之间共享数据时处理权限问题,解决了常见的权限问题。
  4. 实施权限最佳实践:你将所有这些概念应用到创建一个遵循 Docker 权限最佳实践的安全 Web 应用程序容器中。

通过在你的 Docker 项目中应用这些技术,你可以显著提高容器化应用程序的安全性和可靠性。请记住,以最小必要权限运行容器是一项基本的安全原则,应该始终遵循。

一些关键要点如下:

  • 始终在容器中使用非 root 用户
  • 使用卷时,确保主机和容器之间的用户 ID 匹配
  • 设置适当的文件和目录权限
  • 遵循最小权限原则

这些实践将帮助你构建更安全的 Docker 容器,并避免常见的与权限相关的问题。