使用 Notation、Azure Key Vault 和 CA 颁发的证书对容器映像进行签名

本文是一系列关于确保容器映像和其他开放容器计划(OCI)项目的完整性和真实性的一部分。 对于完整情况,请从概述开始,该 概述解释了签名为何重要并概述了各种方案。

使用来自受信任的证书颁发机构(CA)的证书对容器映像进行签名和验证是一种有价值的安全做法。 它可帮助你负责识别、授权和验证容器映像的发布者和容器映像本身的标识。 GlobalSign、DigiCert 等受信任 CA 在以下方面发挥着关键作用:

  • 验证用户或组织的标识。
  • 维护数字证书的安全性。
  • 如果出现任何风险或滥用,立即撤销证书。

下面是一些基本组件,可帮助你使用受信任的 CA 中的证书对容器映像进行签名和验证:

  • 表示法 是由 公证项目社区 开发的开源供应链安全工具,由Microsoft提供支持。 它支持对容器映像和其他项目进行签名和验证。
  • Azure Key Vault 是一种基于云的服务,用于管理加密密钥、机密和证书。 它可帮助你使用签名密钥安全地存储和管理证书。
  • Key Vault 插件 (notation-azure-kv) 是表示法的扩展。 它使用 Key Vault 中存储的密钥对容器映像和项目的数字签名进行签名和验证。
  • Azure 容器注册表是一个专用注册表,可用于将签名附加到容器映像,以及存储和管理这些映像。

验证映像时,签名用于验证映像的完整性和签名者的标识。 此验证有助于确保容器映像不会被篡改,并且来自受信任的源。

在这篇文章中,你将学会如何:

  • 安装 Notation 命令行界面(CLI)和 Key Vault 插件。
  • 在 Key Vault 中创建或导入 CA 颁发的证书。
  • 使用容器注册表任务生成和推送容器映像。
  • 使用 Notation CLI 和 Key Vault 插件对容器镜像进行签署。
  • 使用 Notation CLI 验证容器镜像签名。
  • 使用时间戳。

Prerequisites

  • 创建或使用 容器注册表 来存储容器映像和签名。
  • 创建或使用 密钥保管库。 建议创建一个新的密钥保管库,以便仅存储证书。
  • 安装和配置最新的 Azure CLI 版本。

安装 Notation CLI 和 Key Vault 插件

  1. 在 Linux AMD64 环境中安装 Notation v1.3.2。 若要下载适用于其他环境的包,请按照 符号安装指南进行安装。

    # Download, extract, and install
    curl -Lo notation.tar.gz https://github.com/notaryproject/notation/releases/download/v1.3.2/notation_1.3.2_linux_amd64.tar.gz
    tar xvzf notation.tar.gz
    
    # Copy the Notation CLI to the desired bin directory in PATH, for example
    cp ./notation /usr/local/bin
    
  2. 在 Linux AMD64 环境中安装 Key Vault 插件 (notation-azure-kv) v1.2.1。

    Note

    可以在插件的 发布页面找到插件的 URL 和 SHA256 校验和。

    notation plugin install --url https://github.com/Azure/notation-azure-kv/releases/download/v1.2.1/notation-azure-kv_1.2.1_linux_amd64.tar.gz --sha256sum 67c5ccaaf28dd44d2b6572684d84e344a02c2258af1d65ead3910b3156d3eaf5
    
  3. 列出可用的插件,并确认 notation-azure-kv 列表中是否包含带有版本的 1.2.1 插件:

    notation plugin ls
    

配置环境变量

本文使用环境变量来方便配置 Key Vault 和容器注册表。 更新特定资源的这些环境变量的值。

  1. 为 Key Vault 和证书配置环境变量:

    AKV_SUB_ID=myAkvSubscriptionId
    AKV_RG=myAkvResourceGroup
    AKV_NAME=myakv 
    
    # Name of the certificate created or imported in Key Vault 
    CERT_NAME=wabbit-networks-io 
    
    # X.509 certificate subject
    CERT_SUBJECT="CN=wabbit-networks.io,O=Notation,L=Seattle,ST=WA,C=US"
    
  2. 为容器注册表和映像配置环境变量:

    ACR_SUB_ID=myAcrSubscriptionId
    ACR_RG=myAcrResourceGroup
    # Name of the existing registry example: myregistry.azurecr.cn 
    ACR_NAME=myregistry 
    # Existing full domain of the container registry 
    REGISTRY=$ACR_NAME.azurecr.cn 
    # Container name inside the container registry where the image will be stored 
    REPO=net-monitor 
    TAG=v1 
    # Source code directory that contains the Dockerfile to build 
    IMAGE_SOURCE=https://github.com/wabbit-networks/net-monitor.git#main  
    

使用 Azure CLI 登录

az cloud set -n AzureChinaCloud
az login
# az cloud set -n AzureCloud   //means return to Azure Public Cloud.

有关详细信息,请参阅 使用 Azure CLI 向 Azure 进行身份验证。

在 Key Vault 中创建或导入 CA 颁发的证书

了解证书要求

创建用于签名和验证的证书时,证书必须满足 公证项目证书要求。

以下是根证书和中间证书的要求:

  • 扩展 basicConstraints 必须存在并标记为 critical。 字段 CA 必须设置为 true。
  • 扩展 keyUsage 必须存在并标记为 critical。 必须设置其 keyCertSign 位位置。

下面是 CA 颁发证书的要求:

  • X.509 证书属性:
    • 主题必须包含公用名(CN)、国家/地区(C)、州或省(ST)和组织(O)。 本文使用 $CERT_SUBJECT 作为主题。
    • X.509 密钥用法标记必须仅为 DigitalSignature。
    • 扩展密钥用法(EKUs)必须为空或 1.3.6.1.5.5.7.3.3 (用于代码签名)。
  • 密钥属性:
    • exportable属性必须设置为false。
    • 从 Notary 项目规范选择支持的密钥类型和大小。

Important

为了确保与映像完整性成功集成,证书的内容类型应设置为 PEM。

本指南使用 Key Vault 插件版本 1.0.1。 早期版本的插件有一个限制,要求证书链中的特定证书顺序。 插件版本 1.0.1 没有此限制,因此建议使用版本 1.0.1 或更高版本。

创建 CA 颁发的证书

按照 Key Vault 中创建和合并证书签名请求中的说明创建证书签名请求(CSR)。

合并 CSR 时,请确保合并从 CA 供应商处拿回的整个证书链。

在 Key Vault 中导入证书

若要导入证书,请执行以下操作:

  1. 使用整个证书链从 CA 供应商获取证书文件。
  2. 按照 在 Azure Key Vault 中导入证书的说明将证书导入 Key Vault。

如果在创建或导入证书后,证书不包含证书链,可以从您的证书颁发机构供应商获取中间证书和根证书。 可以要求供应商提供包含中间证书(如果有)和根证书的 PEM 文件。 然后,在对容器映像进行签名时,可以使用此文件。

使用 Notation CLI 和 Key Vault 插件对容器镜像进行签名

使用容器注册表和 Key Vault 时,必须授予适当的权限,以帮助确保安全和控制的访问。 可以根据特定方案为各种实体(例如用户主体、服务主体或托管标识)授予访问权限。 在本文中,已登录的 Azure 用户已授权访问。

授权访问容器注册表

对于启用 Microsoft Entra 属性访问控制(ABAC)的注册表,在容器注册表中生成和签名容器映像需要Container Registry Repository Reader和Container Registry Repository Writer角色。

对于未启用 ABAC 的注册表,需要 AcrPull 和 AcrPush 角色。

有关 ABAC 的详细信息,请参阅 Microsoft Entra 基于属性的存储库权限访问控制。

  1. 设置包含容器注册表资源的订阅:

    az account set --subscription $ACR_SUB_ID
    
  2. 分配角色:

    USER_ID=$(az ad signed-in-user show --query id -o tsv)
    ROLE1="Container Registry Repository Reader" # For ABAC-enabled registries. Otherwise, use "AcrPull" for non-ABAC-enabled registries.
    ROLE2="Container Registry Repository Writer" # For ABAC-enabled registries. Otherwise, use "AcrPush" for non-ABAC-enabled registries.
    az role assignment create --role "$ROLE1" --role "$ROLE2" --assignee $USER_ID --scope "/subscriptions/$ACR_SUB_ID/resourceGroups/$ACR_RG/providers/Microsoft.ContainerRegistry/registries/$ACR_NAME"
    

生成容器映像并将其推送到容器注册表

  1. 使用单个 Azure 标识向容器注册表进行身份验证:

    az acr login --name $ACR_NAME
    

    Important

    如果在系统上安装了 Docker,并且使用 az acr login 或 docker login 对容器注册表进行了身份验证,则您的凭据已存储并可供 Notation 使用。 在这种情况下,无需再次运行 notation login ,才能向容器注册表进行身份验证。 若要详细了解 Notation 的身份验证选项,请参阅 在符合 OCI 的注册表中进行身份验证。

  2. 使用容器注册表任务生成和推送新映像。 始终使用 digest 来标识签名图像,因为标签是可变的并且可以被覆盖。

    DIGEST=$(az acr build -r $ACR_NAME -t $REGISTRY/${REPO}:$TAG $IMAGE_SOURCE --no-logs --query "outputImages[0].digest" -o tsv)
    IMAGE=$REGISTRY/${REPO}@$DIGEST
    

    在本文中,如果映像已生成并存储在注册表中,则标记将用作该映像的标识符,以便于:

    IMAGE=$REGISTRY/${REPO}@$TAG
    

授权访问“Key Vault”

本部分探讨两个选项,用于授权访问 Key Vault。

  1. 设置包含 Key Vault 资源的订阅:

    az account set --subscription $AKV_SUB_ID
    
  2. 分配角色。

    如果证书包含整个证书链,则主体必须被分配以下角色:

    • Key Vault Secrets User(用于读取机密)
    • Key Vault Certificates User 用于读取证书
    • Key Vault Crypto User 用于签名操作
    USER_ID=$(az ad signed-in-user show --query id -o tsv)
    az role assignment create --role "Key Vault Secrets User" --role "Key Vault Certificates User" --role "Key Vault Crypto User" --assignee $USER_ID --scope "/subscriptions/$AKV_SUB_ID/resourceGroups/$AKV_RG/providers/Microsoft.KeyVault/vaults/$AKV_NAME"
    

    如果证书不包含证书链,则必须为主体分配以下角色:

    • Key Vault Certificates User 用于读取证书
    • Key Vault Crypto User 用于签名操作
    USER_ID=$(az ad signed-in-user show --query id -o tsv)
    az role assignment create --role "Key Vault Certificates User" --role "Key Vault Crypto User" --assignee $USER_ID --scope "/subscriptions/$AKV_SUB_ID/resourceGroups/$AKV_RG/providers/Microsoft.KeyVault/vaults/$AKV_NAME"
    

若要详细了解使用 Azure 基于角色的访问控制(RBAC)进行 Key Vault 访问,请参阅 使用 Azure 基于角色的访问控制提供对 Key Vault 密钥、证书和机密的访问权限。

使用访问策略(旧版)

若要设置包含 Key Vault 资源的订阅,请运行以下命令:

az account set --subscription $AKV_SUB_ID

如果证书包含整个证书链,则必须向主体授予密钥权限 Sign、机密权限 Get和证书权限 Get。 若要向主体授予这些权限,请使用以下命令:

USER_ID=$(az ad signed-in-user show --query id -o tsv)
az keyvault set-policy -n $AKV_NAME --key-permissions sign --secret-permissions get --certificate-permissions get --object-id $USER_ID

如果证书不包含链,则必须向主体授予密钥权限 Sign 和证书权限 Get。 若要向主体授予这些权限,请使用以下命令:

USER_ID=$(az ad signed-in-user show --query id -o tsv)
az keyvault set-policy -n $AKV_NAME --key-permissions sign --certificate-permissions get --object-id $USER_ID

若要详细了解如何将策略分配给主体,请参阅“分配 Key Vault 访问策略”(旧版)。

使用 Key Vault 中的证书对容器映像进行签名

  1. 获取证书的密钥 ID。 Key Vault 中的证书可以有多个版本。 以下命令获取最新版本证书的 $CERT_NAME 密钥 ID:

    KEY_ID=$(az keyvault certificate show -n $CERT_NAME --vault-name $AKV_NAME --query 'kid' -o tsv) 
    
  2. 使用密钥 ID,以 CBOR 对象签名和加密(COSE)签名格式对容器映像进行签名。

    如果证书包含整个证书链,请运行以下命令:

    notation sign --signature-format cose $IMAGE --id $KEY_ID --plugin azure-kv 
    

    如果证书不包含链,请使用 --plugin-config ca_certs=<ca_bundle_file> 参数将 PEM 文件中的 CA 证书传递给 Key Vault 插件。 运行下面的命令:

    notation sign --signature-format cose $IMAGE --id $KEY_ID --plugin azure-kv --plugin-config ca_certs=<ca_bundle_file> 
    

    若要使用 Key Vault 进行身份验证,默认情况下,会按顺序尝试以下凭据类型(如果已启用):

    如果要指定凭据类型,请使用名为 credential_type 的其他插件配置。 例如,可以显式地将credential_type设置为azurecli以使用 Azure CLI 凭据,如以下示例所示:

    notation sign --signature-format cose --id $KEY_ID --plugin azure-kv --plugin-config credential_type=azurecli $IMAGE
    

    下表显示了各种凭据类型的credential_type值。

    凭据类型 credential_type 的值
    环境凭据 environment
    工作负载标识凭据 workloadid
    托管标识凭据 managedid
    Azure CLI 凭据 azurecli
  3. 查看已签名图像及其关联签名的图表。

    notation ls $IMAGE
    

    在以下示例输出中,由摘要application/vnd.cncf.notary.signature标识sha256:d7258166ca820f5ab7190247663464f2dcb149df4d1b6c4943dcaac59157de8e类型的签名与$IMAGE相关联。

    myregistry.azurecr.cn/net-monitor@sha256:17cc5dd7dfb8739e19e33e43680e43071f07497ed716814f3ac80bd4aac1b58f
    └── application/vnd.cncf.notary.signature
        └── sha256:d7258166ca820f5ab7190247663464f2dcb149df4d1b6c4943dcaac59157de8e
    

Note

由于表示法 v1.2.0,表示法默认使用 OCI 引用器标记架构 将签名存储在容器注册表中。 如有必要,还可以使用标志启用 --force-referrers-tag false。 容器注册表功能支持 OCI 引用者 API,但通过客户管理的密钥(CMK)加密的注册表除外。

使用 Notation CLI 验证容器镜像

  1. 将根证书添加到命名信任存储,以便进行签名验证。 如果没有根证书,可以从 CA 获取它。 以下示例将根证书 $ROOT_CERT 添加到 $STORE_NAME 信任存储:

    STORE_TYPE="ca" 
    STORE_NAME="wabbit-networks.io" 
    notation cert add --type $STORE_TYPE --store $STORE_NAME $ROOT_CERT  
    
  2. 列出根证书,确认 $ROOT_CERT 已成功添加:

    notation cert ls 
    
  3. 在验证之前配置信任策略。 信任策略使用户能够指定微调的验证策略。 使用以下命令:

    cat <<EOF > ./trustpolicy.json
    {
        "version": "1.0",
        "trustPolicies": [
            {
                "name": "wabbit-networks-images",
                "registryScopes": [ "$REGISTRY/$REPO" ],
                "signatureVerification": {
                    "level" : "strict" 
                },
                "trustStores": [ "$STORE_TYPE:$STORE_NAME" ],
                "trustedIdentities": [
                    "x509.subject: $CERT_SUBJECT"
                ]
            }
        ]
    }
    EOF
    

    上述 文件定义了一个名为 的信任策略。 此信任策略适用于 $REGISTRY/$REPO 存储库中存储的所有项目。 $STORE_NAME 类型的命名信任存储 $STORE_TYPE 包含根证书。 此策略还假定用户信任一个特定标识,该标识具有 X.509 主题 $CERT_SUBJECT。 有关详细信息,请参阅 信任存储和信任策略规范。

  4. 使用notation policy从trustpolicy.json导入信任策略配置:

    notation policy import ./trustpolicy.json
    
  5. 显示信任策略配置以确认其成功导入:

    notation policy show
    
  6. 使用 notation verify 验证映像的完整性:

    notation verify $IMAGE
    

    通过信任策略成功验证映像后,验证图像的 SHA256 摘要会在成功的输出消息中返回。 下面是输出示例:

    Successfully verified signature for myregistry.azurecr.cn/net-monitor@sha256:17cc5dd7dfb8739e19e33e43680e43071f07497ed716814f3ac80bd4aac1b58f

使用时间戳

自 Notation v1.2.0 版本以来,Notation 支持符合 RFC 3161 的时间戳。 此增强通过信任时间戳颁发机构(TSA)来延长证书有效期内创建的签名的信任。 即使证书过期,此信任也能成功进行签名验证。

作为映像签名者,应确保使用受信任的 TSA 生成的时间戳对容器映像进行签名。 作为图像验证者,您应确保信任图像签名者和关联的 TSA,并通过信任存储和信任策略来建立信任。

时间戳通过消除由于证书过期而定期重新签名映像的需要来降低成本。 使用生存期较短的证书时,此功能尤其重要。 有关如何使用时间戳对图像进行签名和验证的详细说明,请参阅 公证项目时间戳指南。

FAQ

  • 如果证书过期,该怎么办?

    如果证书过期,则需要从受信任的 CA 供应商获取新证书以及新的私钥。 无法使用过期的证书对容器映像进行签名。

    如果已使用 时间戳签名,则证书过期前签名的映像仍可以成功验证。 如果没有时间戳,签名验证将失败,并且需要使用新证书重新为这些映像签名才能成功验证。

  • 如果证书已吊销,该怎么办?

    吊销证书会使签名失效。 这种情况可能会由于多种原因而发生,例如私钥泄露或更改证书持有者的附属关系。

    若要解决此问题,应首先确保源代码和生成环境是最新的且安全的。 然后,从源代码生成容器映像、从受信任的 CA 供应商获取新证书以及新的私钥,并按照本指南使用新证书对新容器映像进行签名。

Notation 在 Azure Pipelines 和 GitHub Actions 上提供持续集成和持续交付(CI/CD)解决方案。

为了确保仅在 Azure Kubernetes 服务(AKS)上部署受信任的容器映像: