

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# 映像安全
<a name="image-security"></a>

您应该将容器映像视为抵御“攻击”的第一道防线。不安全、构造不佳的映像可能允许攻击者逃离容器的边界并获得对主机的访问权限。一旦进入主机，攻击者就可以访问敏感信息，或者在集群内或使用您的 AWS 账户横向移动。以下最佳实践将有助于降低发生这种情况的风险。

## 建议
<a name="_recommendations"></a>

### 创建最少的图像
<a name="_create_minimal_images"></a>

首先从容器映像中移除所有多余的二进制文件。如果你使用的是来自 Dockerhub 的不熟悉镜像，请使用像 D [iv](https://github.com/wagoodman/dive) e 这样的应用程序检查镜像，它可以向你显示容器每个层的内容。删除所有带有 SETUID 和 SETGID 位的二进制文件，因为它们可以用来升级权限，并考虑删除所有可用于邪恶目的的 shell 和实用程序，例如 nc 和 curl。您可以使用以下命令查找带有 SETUID 和 SETGID 位的文件：

```
find / -perm /6000 -type f -exec ls -ld {} \;
```

要从这些文件中删除特殊权限，请在您的容器映像中添加以下指令：

```
RUN find / -xdev -perm /6000 -type f -exec chmod a-s {} \; || true
```

通俗地说，这被称为去除你的形象。

### 使用多阶段构建
<a name="_use_multi_stage_builds"></a>

使用多阶段构建是一种创建最少图像的方法。通常，多阶段构建用于自动化持续集成周期的某些部分。例如，多阶段构建可用于整理源代码或执行静态代码分析。这使开发人员有机会获得近乎即时的反馈，而不必等待管道执行。从安全角度来看，多阶段构建很有吸引力，因为它们允许您最大限度地减少推送到容器注册表的最终映像的大小。缺乏构建工具和其他无关二进制文件的容器映像通过减少映像的攻击面来改善您的安全状况。有关多阶段构建的更多信息，请参阅 [Docker 的多阶段](https://docs.docker.com/develop/develop-images/multistage-build/)构建文档。

### 为您的容器映像创建软件物料清单 (SBOMs)
<a name="_create_software_bill_of_materials_sboms_for_your_container_image"></a>

“软件物料清单” (SBOM) 是构成容器映像的软件工件的嵌套清单。SBOM 是软件安全和软件供应链风险管理的关键组成部分。[生成 SBOMS，将其存储在中央存储库中并扫描 SBOMs 漏洞](https://anchore.com/sbom/)有助于解决以下问题：
+  **可见性**：了解哪些组件构成了您的容器镜像。存储在中央存储库中 SBOMs 允许随时进行审计和扫描，即使在部署后也是如此，以检测和响应新漏洞，例如未修补的漏洞。
+  **来源验证**：确保对工件的来源和来源的现有假设是正确的，并且在构建或交付过程中工件或其随附的元数据没有被篡改。
+  **可信度：保证可以信任**给定的工件及其内容来做它声称要做的事情，即适合于某一目的。这包括判断代码是否可以安全执行，并就与执行代码相关的风险做出明智的决定。通过创建经过验证的管道执行报告以及经过验证的 SBOM 和经认证的 CVE 扫描报告来确保可信度，从而向图像的使用者保证，该图像实际上是通过带有安全组件的安全手段（管道）创建的。
+  **依赖信任验证**：递归检查工件的依赖树，以确定其使用的工件的可信度和来源。Drift in SBOMs 可以帮助检测恶意活动，包括未经授权、不受信任的依赖关系、渗透尝试。

以下工具可用于生成 SBOM：
+  [Amazon Inspector](https://docs.aws.amazon.com/inspector) 可用于[创建和导出 SBOMs](https://docs.aws.amazon.com/inspector/latest/user/sbom-export.html)。
+  [Anchore 的 Syft](https://github.com/anchore/syft) 也可以用于生成 SBOM。为了更快地扫描漏洞，可以将为容器映像生成的 SBOM 用作扫描的输入。然后，系统会对 SBOM 和扫描报告[进行证明，并将其附加到映像上，然后将映像推送到中央 OCI 存储库（例如 Amazon ECR）进行审查和](https://github.com/sigstore/cosign/blob/main/doc/cosign_attach_attestation.md)审计。

查看 [CNCF 软件供应链最佳实践指南，了解有关保护软件供应链](https://project.linuxfoundation.org/hubfs/CNCF_SSCP_v1.pdf)的更多信息。

### 定期扫描图像中是否存在漏洞
<a name="_scan_images_for_vulnerabilities_regularly"></a>

与虚拟机镜像一样，容器镜像可能包含有漏洞的二进制文件和应用程序库，或者随着时间的推移而出现漏洞。防范漏洞的最佳方法是定期使用图像扫描仪扫描图像。存储在 Amazon ECR 中的图像可以通过推送或按需扫描（在 24 小时内扫描一次）。ECR 目前支持[两种扫描类型：基本扫描和增强](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning.html)扫描。基本扫描免费利用 [Clair](https://github.com/quay/clair) 开源图像扫描解决方案。[增强型扫描](https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-scanning-enhanced.html)使用 Amazon Inspector 提供自动连续扫描，[但需要额外付费](https://aws.amazon.com/inspector/pricing/)。扫描图像后，结果将记录到事件流中，以便在 ECR 中执行。EventBridge您还可以从 ECR 控制台中查看扫描结果。应删除或重建具有 “高” 或 “严重” 漏洞的图像。如果已部署的映像出现漏洞，则应尽快将其更换。

了解存在漏洞的映像的部署位置对于确保环境安全至关重要。虽然你可以自己构建图像跟踪解决方案，但已经有几种商业产品提供了开箱即用的此功能和其他高级功能，包括：
+  [Grype](https://github.com/anchore/grype) 
+  [帕洛阿尔托——Prisma Cloud (twistcli)](https://docs.paloaltonetworks.com/prisma/prisma-cloud/prisma-cloud-admin-compute/tools/twistcli_scan_images) 
+  [水族](https://www.aquasec.com/) 
+  [Kubei](https://github.com/Portshift/kubei) 
+  [Trivy](https://github.com/aquasecurity/trivy) 
+  [Snyk](https://support.snyk.io/hc/en-us/articles/360003946917-Test-images-with-the-Snyk-Container-CLI) 

Kubernetes 验证 webhook 也可以用来验证镜像是否没有严重漏洞。验证网络挂钩在 Kubernetes API 之前被调用。它们通常用于拒绝不符合 webhook 中定义的验证标准的请求。[这是一个](https://aws.amazon.com/blogs/containers/building-serverless-admission-webhooks-for-kubernetes-with-aws-sam/)无服务器 Webhook 的示例，它调用 ECR describeImageScan Findings API 来确定 pod 是否正在提取存在严重漏洞的图像。如果发现漏洞，Pod 将被拒绝，并以事件形式返回包含列表的消息。 CVEs 

### 使用认证来验证工件的完整性
<a name="_use_attestations_to_validate_artifact_integrity"></a>

认证是一种加密签名的 “声明”，它声称某件事——一个 “谓词”，例如管道运行、SBOM 或漏洞扫描报告对另一件事是正确的—— “主题”，即容器镜像。

认证可以帮助用户验证工件是否来自软件供应链中的可信来源。例如，我们可能在不知道镜像中包含的所有软件组件或依赖项的情况下使用容器镜像。但是，如果我们相信容器镜像的制作者所说的关于存在什么软件，我们就可以使用制作人的认证来依赖该工件。这意味着我们可以继续在工作流程中安全地使用工件，而不必自己进行分析。
+ [可以使用 AW [S Signer 或 Sigstore 共同签名](https://docs.aws.amazon.com/signer/latest/developerguide/Welcome.html)来创建证明。](https://github.com/sigstore/cosign/blob/main/doc/cosign_attest.md)
+ [Kubernetes 准入控制器（例如 [Kyverno）可用于验证](https://kyverno.io/)认证。](https://kyverno.io/docs/writing-policies/verify-images/sigstore/)
+ 请参阅此[研讨会](https://catalog.us-east-1.prod.workshops.aws/workshops/49343bb7-2cc5-4001-9d3b-f6a33b3c4442/en-US/0-introduction)，详细了解 AWS 上使用开源工具进行软件供应链管理的最佳实践，主题包括创建和附加对容器映像的认证。

### 为 ECR 存储库创建 IAM 策略
<a name="_create_iam_policies_for_ecr_repositories"></a>

如今，一个组织在共享的 AWS 账户中拥有多个开发团队独立运行的情况并不少见。如果这些团队不需要共享资产，则可能需要创建一组 IAM 策略来限制对每个团队可以与之交互的存储库的访问权限。实现这一点的一个好方法是使用 ECR [命名](https://docs.aws.amazon.com/AmazonECR/latest/userguide/Repositories.html#repository-concepts)空间。命名空间是一种将相似的存储库组合在一起的方法。例如，团队 A 的所有注册表都可以以 team-a/ 开头，而团队 B 的注册表可以使用 team-b/ 前缀。限制访问的策略可能如下所示：

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement": [
    {
      "Sid": "AllowPushPull",
      "Effect": "Allow",
      "Action": [
        "ecr:GetDownloadUrlForLayer",
        "ecr:BatchGetImage",
        "ecr:BatchCheckLayerAvailability",
        "ecr:PutImage",
        "ecr:InitiateLayerUpload",
        "ecr:UploadLayerPart",
        "ecr:CompleteLayerUpload"
      ],
      "Resource": [
        "arn:aws:ecr:us-east-1:123456789012:repository/team-a/*"
      ]
    }
  ]
}
```

### 考虑使用 ECR 私有端点
<a name="_consider_using_ecr_private_endpoints"></a>

ECR API 有一个公共端点。因此，只要请求已通过 IAM 的身份验证和授权，就可以从互联网访问 ECR 注册表。对于那些需要在集群 VPC 缺少 Internet Gateway (IGW) 的沙盒环境中操作的用户，您可以为 ECR 配置私有终端节点。通过创建私有终端节点，您可以通过私有 IP 地址私密访问 ECR API，而不是通过互联网路由流量。有关此主题的更多信息，请参阅 [Amazon ECR 接口 VPC 终端节点](https://docs.aws.amazon.com/AmazonECR/latest/userguide/vpc-endpoints.html)。

### 为 ECR 实施端点策略
<a name="_implement_endpoint_policies_for_ecr"></a>

的默认终端节点策略允许访问一个区域内的所有 ECR 存储库。这可能允许通过 attacker/insider 将数据打包为容器镜像并将其推送到另一个 AWS 账户的注册表来泄露数据。要降低这种风险，需要创建一个端点策略，限制 API 对 ECR 存储库的访问。例如，以下策略允许您账户中的所有 AWS 原则对您的 ECR 存储库执行所有操作，且仅允许您的 ECR 存储库执行所有操作：

```
{
  "Statement": [
    {
      "Sid": "LimitECRAccess",
      "Principal": "*",
      "Action": "*",
      "Effect": "Allow",
      "Resource": "arn:aws:ecr:<region>:<account_id>:repository/*"
    }
  ]
}
```

您可以通过设置一个使用新`PrincipalOrgID`属性的条件来进一步增强这一点，该条件将根据不属于您的 AWS 组织的 IAM 原则阻止 pushing/pulling 映像。有关更多详细信息，请参阅 aws[: PrincipalOrg ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_condition-keys.html#condition-keys-principalorgid)。建议对 `com.amazonaws.<region>.ecr.dkr` 和 `com.amazonaws.<region>.ecr.api` 端点应用相同的策略。由于 EKS 从 ECR 中提取 kube-proxy、coredns 和 aws-node 的图像，因此您需要添加注册表的账户 ID，例如`602401143452.dkr.ecr.us-west-2.amazonaws.com/ `添加到端点策略中的资源列表中，或者更改策略以允许从您的账户 ID 提取和限制推送到您的账户 ID。``下表显示了出售 EKS 映像的 AWS 账户与集群区域之间的对应关系。


| 账号 | Region | 
| --- | --- | 
|  602401143452  |  除下面列出的区域以外的所有商业区域  | 
|  —  |  —  | 
|  800184023465  |  ap-east-1-亚太地区（香港）  | 
|  558608220178  |  me-south-1-中东（巴林）  | 
|  918309763551  |  cn-north-1-中国（北京）  | 
|  961992271922  |  cn-northwest-1-中国（宁夏）  | 

有关使用终端节点策略的更多信息，请参阅[使用 VPC 终端节点策略控制 Amazon ECR 访问权限](https://aws.amazon.com/blogs/containers/using-vpc-endpoint-policies-to-control-amazon-ecr-access/)。

### 为 ECR 实施生命周期策略
<a name="_implement_lifecycle_policies_for_ecr"></a>

[NIST《应用程序容器安全指南》](https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-190.pdf)警告 “注册表中存在陈旧映像” 的风险，并指出，随着时间的推移，应删除存在漏洞的旧映像， out-of-date软件包应被删除，以防止意外部署和泄露。每个 ECR 存储库都可以有一个生命周期策略，用于设置镜像何时过期的规则。A [WS 官方文档](https://docs.aws.amazon.com/AmazonECR/latest/userguide/LifecyclePolicies.html)描述了如何设置测试规则、对其进行评估然后应用这些规则。官方文档中有几个[生命周期策略示例](https://docs.aws.amazon.com/AmazonECR/latest/userguide/lifecycle_policy_examples.html)，显示了筛选存储库中图像的不同方法：
+ 按图片年龄或数量筛选
+ 按带标签或未加标签的图像进行筛选
+ 按图像标签筛选，可以在多个规则中或单个规则中筛选

？？？ \$1 警告如果从 ECR 中清除长时间运行的应用程序的映像，则在重新部署或水平扩展应用程序时，可能会导致映像提取错误。使用映像生命周期策略时，请确保您有良好的 CI/CD 实践来保持部署及其引用的映像是最新的，并始终根据发布/部署的频率创建 [image] 到期规则。

### 创建一组精选映像
<a name="_create_a_set_of_curated_images"></a>

与其允许开发人员创建自己的映像，不如考虑为组织中的不同应用程序堆栈创建一组经过审查的图像。通过这样做，开发人员可以放弃学习如何编写 Dockerfiles，而专注于编写代码。当更改合并到 Master 时， CI/CD 管道可以自动编译资产，将其存储在工件存储库中，然后将工件复制到相应的镜像中，然后再将其推送到 ECR 之类的 Docker 注册表。至少你应该创建一组基础镜像，开发者可以从中创建自己的 Dockerfiles。理想情况下，你要避免从 Dockerhub 提取镜像，因为 1/ 你并不总是知道镜像中有什么，而且 2/ 在前 1000 张镜像中，大约[有五分之一](https://www.kennasecurity.com/blog/one-fifth-of-the-most-used-docker-containers-have-at-least-one-critical-vulnerability/)存在漏洞。可以在[此处](https://vulnerablecontainers.org/)找到这些图像及其漏洞的列表。

### 将 USER 指令添加到你的 Dockerfiles 中，以非 root 用户身份运行
<a name="_add_the_user_directive_to_your_dockerfiles_to_run_as_a_non_root_user"></a>

正如 pod 安全部分所述，你应该避免以 root 用户身份运行容器。虽然你可以将其配置为 PodSpec 的一部分，但在 Dockerfiles 中使用该`USER`指令是个好习惯。该`USER`指令设置运行时使用的 UID `RUN``ENTRYPOINT`，或显示在 USE `CMD` R 指令之后的指令。

### 整理你的 DockerFiles
<a name="_lint_your_dockerfiles"></a>

Linting 可用于验证您的 Dockerfile 是否遵守了一组预定义的准则，例如包含`USER`指令或要求对所有图像进行标记。 [dockerfile\$1lint](https://github.com/projectatomic/dockerfile_lint) 是一个开源项目 RedHat ，它验证了常见的最佳实践，并包含一个规则引擎，你可以用它来构建自己的清理 Dockerfiles 的规则。它可以合并到 CI 管道中，因为使用违反规则的 Dockerfiles 构建将自动失败。

### 从头开始构建镜像
<a name="_build_images_from_scratch"></a>

在构建镜像时，减少容器镜像的攻击面应该是主要目标。实现此目的的理想方法是创建最少的镜像，其中不包含可用于利用漏洞的二进制文件。幸运的是，Docker 有一种机制可以用来创建镜像。[https://docs.docker.com/develop/develop-images/baseimages/#create-a-simple-parent-image-using-scratch](https://docs.docker.com/develop/develop-images/baseimages/#create-a-simple-parent-image-using-scratch)使用像 Go 这样的语言，你可以创建一个静态链接的二进制文件，并在你的 Dockerfile 中引用它，如下例所示：

```
############################
# STEP 1 build executable binary
############################
FROM golang:alpine AS builder# Install git.
# Git is required for fetching the dependencies.
RUN apk update && apk add --no-cache gitWORKDIR $GOPATH/src/mypackage/myapp/COPY . . # Fetch dependencies.
# Using go get.
RUN go get -d -v# Build the binary.
RUN go build -o /go/bin/hello

############################
# STEP 2 build a small image
############################
FROM scratch# Copy our static executable.
COPY --from=builder /go/bin/hello /go/bin/hello# Run the hello binary.
ENTRYPOINT ["/go/bin/hello"]
```

这将创建一个容器镜像，该镜像仅包含您的应用程序，仅包含其他任何内容，因此非常安全。

### 在 ECR 中使用不可变标签
<a name="_use_immutable_tags_with_ecr"></a>

 [不可变标签](https://aws.amazon.com/about-aws/whats-new/2019/07/amazon-ecr-now-supports-immutable-image-tags/)会强制您在每次推送到图像存储库时更新图像标签。这可以阻止攻击者在不更改图像标签的情况下使用恶意版本覆盖图像。此外，它还为您提供了一种轻松且唯一地识别图像的方法。

### 签署您的图片 SBOMs、管道运行和漏洞报告
<a name="_sign_your_images_sboms_pipeline_runs_and_vulnerability_reports"></a>

当首次引入 Docker 时，还没有用于验证容器镜像的加密模型。在 v2 中，Docker 在镜像清单中添加了摘要。这允许对图像的配置进行哈希处理，并使用哈希值为图像生成 ID。启用图像签名后，Docker 引擎会验证清单的签名，确保内容来自可信来源且未发生任何篡改。下载每个图层后，引擎都会验证图层的摘要，确保内容与清单中指定的内容相匹配。图像签名实际上允许您通过验证与图像相关的数字签名来创建安全的供应链。

我们可以使用 [AWS Signer](https://docs.aws.amazon.com/signer/latest/developerguide/Welcome.html) 或 [Sigstore Cosign](https://github.com/sigstore/cosign) 来签署容器映像 SBOMs、为漏洞扫描报告和管道运行报告创建证明。这些证明确保了图像的可信度和完整性，它实际上是由可信管道创建的，没有任何干扰或篡改，并且它仅包含经过图像发布者验证和信任的记录在案（在 SBOM 中）的软件组件。这些证明可以附加到容器镜像并推送到存储库。

在下一节中，我们将看到如何使用经过认证的工件进行审计和招生控制员验证。

### 使用 Kubernetes 准入控制器进行图像完整性验证
<a name="_image_integrity_verification_using_kubernetes_admission_controller"></a>

在使用[动态准入控制器将映像部署到目标 Kubernetes 集群之前，我们可以自动验证图像签名和已验证的工件，并且只有在工件的安全元数据符合准入控制器](https://kubernetes.io/blog/2019/03/21/a-guide-to-kubernetes-admission-controllers/)策略时才允许部署。

例如，我们可以编写一个策略，以加密方式验证图像的签名、经过验证的 SBOM、经认证的管道运行报告或经过验证的 CVE 扫描报告。我们可以在策略中写入条件来检查报告中的数据，例如，CVE 扫描不应包含任何关键CVEs内容。只有满足这些条件的映像才允许部署，所有其他部署都将被招生控制器拒绝。

准入控制器的示例包括：
+  [Kyverno](https://kyverno.io/) 
+  [OPA 看门人](https://github.com/open-policy-agent/gatekeeper) 
+  [Portieris](https://github.com/IBM/portieris) 
+  [批准](https://github.com/deislabs/ratify) 
+  [Kritis](https://github.com/grafeas/kritis) 
+  [Grafeas 教程](https://github.com/kelseyhightower/grafeas-tutorial) 
+  [代金券](https://github.com/Shopify/voucher) 

### 更新容器镜像中的软件包
<a name="_update_the_packages_in_your_container_images"></a>

你应该在 Dockerfiles `apt-get update && apt-get upgrade` 中加入 RUN 来升级镜像中的软件包。尽管升级需要您以 root 用户身份运行，但这发生在映像构建阶段。该应用程序不需要以 root 用户身份运行。您可以安装更新，然后使用 USER 指令切换到其他用户。如果您的基础映像以非 root 用户身份运行，请切换到 root 用户并返回；不要仅仅依靠基础映像的维护者来安装最新的安全更新。

运行`apt-get clean`以从中删除安装程序文件`/var/cache/apt/archives/`。你也可以在安装软件包`rm -rf /var/lib/apt/lists/*`后运行。这将删除索引文件或可供安装的软件包列表。请注意，每个软件包管理器的这些命令可能有所不同。例如：

```
RUN apt-get update && apt-get install -y \
    curl \
    git \
    libsqlite3-dev \
    && apt-get clean && rm -rf /var/lib/apt/lists/*
```

## 工具和资源
<a name="_tools_and_resources"></a>
+  [Amazon EKS 安全沉浸式研讨会-图像安全](https://catalog.workshops.aws/eks-security-immersionday/en-US/12-image-security) 
+  [docker-slim 构建安全的最小](https://github.com/docker-slim/docker-slim)镜像
+  [dockle](https://github.com/goodwithtech/dockle) 验证你的 Dockerfile 是否符合创建安全镜像的最佳实践
+  [dockerfile-lint 基于 Dockerfiles 规则的 lint](https://github.com/projectatomic/dockerfile_lint) er
+  [hadolint 一个聪明的 dockerfile lint](https://github.com/hadolint/hadolint) er
+  [Gatekeeper 和 OPA](https://github.com/open-policy-agent/gatekeeper) 基于策略的准入控制器
+  [Kyverno Kubern](https://kyverno.io/) etes 原生策略引擎
+  [in-toto 允许](https://in-toto.io/)用户验证供应链中的某个步骤是否打算执行，以及该步骤是否由正确的参与者执行
+  [Notary](https://github.com/theupdateframework/notary) 一个用于签署容器镜像的项目
+  [Notary v2](https://github.com/notaryproject/nv2) 
+  [Grafeas](https://grafeas.io/) 一个用于审计和管理软件供应链的开放工件元数据 API
+  [NeuVector 由 SUSE](https://www.suse.com/neuvector/) 开源提供的零信任容器安全平台提供容器、镜像和注册表扫描，以查找漏洞、机密和合规性。