使用 Notation、Azure Key Vault 和 自签名证书 对容器映像进行签名

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

对容器映像进行签名的过程有助于确保其真实性和完整性。 在部署过程中验证添加到容器镜像的数字签名。 签名有助于验证映像是否来自受信任的发布者,并且未修改。

本文讨论签名过程中涉及的以下工具:

  • 表示法 是由 公证项目社区 开发的开源供应链安全工具,由Microsoft提供支持。 它支持对容器映像和其他项目进行签名和验证。

    如果要在持续集成和持续交付(CI/CD)管道中使用 Notation 签名容器映像,请遵循 Azure Pipeline 或 GitHub Actions 的指导。

  • Azure Key Vault 是用于存储具有签名密钥的证书的服务。 Notation 可以通过 Key Vault 插件(notation-azure-kv)使用这些密钥来签名和验证容器镜像及其他工件。
  • Azure 容器注册表是一个专用注册表,可用于将签名附加到容器映像和其他项目,以及查看这些签名。

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

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

Prerequisites

安装 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 binary 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
    

配置环境变量

为方便执行本文中的命令,请提供 Azure 资源的值以匹配现有的容器注册表和 Key Vault 资源。

  1. 配置 Key Vault 资源名称:

    AKV_SUB_ID=myAkvSubscriptionId
    AKV_RG=myAkvResourceGroup
    # Name of the existing key vault used to store the signing keys
    AKV_NAME=myakv
    # Name of the certificate created in the key vault
    CERT_NAME=wabbit-networks-io
    CERT_SUBJECT="CN=wabbit-networks.io,O=Notation,L=Seattle,ST=WA,C=US"
    CERT_PATH=./${CERT_NAME}.pem
    
  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
    IMAGE=$REGISTRY/${REPO}:$TAG
    # 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 的访问权限

使用容器注册表和 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. 分配角色。 角色分配中使用的正确角色取决于注册表是否启用了 ABAC。

    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"
    

授权访问“Key Vault”

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

使用自签名证书进行签名时需要以下角色:

  • Key Vault Certificates Officer,用于创建和读取证书
  • Key Vault Certificates User 用于读取现有证书
  • Key Vault Crypto User,用于签署操作

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

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

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

    USER_ID=$(az ad signed-in-user show --query id -o tsv)
    az role assignment create --role "Key Vault Certificates Officer" --role "Key Vault Crypto User" --assignee $USER_ID --scope "/subscriptions/$AKV_SUB_ID/resourceGroups/$AKV_RG/providers/Microsoft.KeyVault/vaults/$AKV_NAME"
    

在 Key Vault 中分配访问策略(旧版)

标识需要以下权限:

  • Create 权限,用于创建证书
  • Get 用于读取现有证书的权限
  • 拥有 Sign 权限,可执行签名操作

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

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

    az account set --subscription $AKV_SUB_ID
    
  2. 在 Key Vault 中设置访问策略:

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

Important

此示例显示了创建证书和对容器映像进行签名所需的最低权限。 根据要求,可能需要授予更多权限。

在 Key Vault 中创建自签名证书(Azure CLI)

以下步骤演示如何创建自签名证书以进行测试:

  1. 创建证书策略文件。

    通过以下代码执行证书策略文件后,它会创建与 Key Vault 中的 公证项目证书要求 兼容的有效证书。 该值 ekus 是用于代码签名的,但Notation工具在签署工件时并不需要它。 在验证期间,该主体稍后用作可信身份。

    cat <<EOF > ./my_policy.json
    {
        "issuerParameters": {
        "certificateTransparency": null,
        "name": "Self"
        },
        "keyProperties": {
          "exportable": false,
          "keySize": 2048,
          "keyType": "RSA",
          "reuseKey": true
        },
        "secretProperties": {
          "contentType": "application/x-pem-file"
        },
        "x509CertificateProperties": {
        "ekus": [
            "1.3.6.1.5.5.7.3.3"
        ],
        "keyUsage": [
            "digitalSignature"
        ],
        "subject": "$CERT_SUBJECT",
        "validityInMonths": 12
        }
    }
    EOF
    
  2. 创建证书:

    az keyvault certificate create -n $CERT_NAME --vault-name $AKV_NAME -p @my_policy.json
    

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

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

    az acr login --name $ACR_NAME
    

    Important

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

  2. 使用 Azure 容器注册表任务生成和推送新映像。 始终使用摘要值标识用于签名的映像,因为标记是可变的,可以覆盖。

    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
    
  3. 获取签名密钥的 ID。 Key Vault 中的证书可以有多个版本。 以下命令获取最新版本的密钥 ID:

    KEY_ID=$(az keyvault certificate show -n $CERT_NAME --vault-name $AKV_NAME --query 'kid' -o tsv)
    
  4. 使用签名密钥 ID,以CBOR 对象签名和加密(COSE)的签名格式对容器映像进行签名。 若要使用自签名证书进行签名,需要设置插件配置值 self_signed=true。

    notation sign --signature-format cose --id $KEY_ID --plugin azure-kv --plugin-config self_signed=true $IMAGE
    

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

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

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

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

    凭据类型 credential_type 的值
    环境凭据 environment
    工作负载标识凭据 workloadid
    托管标识凭据 managedid
    Azure CLI 凭据 azurecli

    Note

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

  5. 查看已签名图像及其关联签名的图表。

    notation ls $IMAGE
    

使用 Notation CLI 验证容器镜像

若要验证容器映像,请将将叶证书签名的根证书添加到信任存储,并创建用于验证的信任策略。 对于本文使用的自签名证书,根证书本身是自签名证书。

  1. 下载公共证书:

    az keyvault certificate download --name $CERT_NAME --vault-name $AKV_NAME --file $CERT_PATH
    
  2. 将下载的公共证书添加到命名信任存储进行签名验证:

    STORE_TYPE="ca"
    STORE_NAME="wabbit-networks.io"
    notation cert add --type $STORE_TYPE --store $STORE_NAME $CERT_PATH
    
  3. 列出要确认的证书:

    notation cert ls
    
  4. 在验证之前配置信任策略。

    信任策略使用户能够指定微调的验证策略。 以下示例配置名为 wabbit-networks-images 的信任策略。 此策略适用于$REGISTRY/$REPO中的所有工件,并使用$STORE_NAME类型的$STORE_TYPE命名信任存储。 它还假设用户信任特定身份,其 X.509 主题为 $CERT_SUBJECT。 有关详细信息,请参阅 信任存储和信任策略规范。

    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
    
  5. 使用 notation policy 从之前创建的 JSON 文件中导入信任策略配置。

    notation policy import ./trustpolicy.json
    notation policy show
    
  6. 用于 notation verify 验证容器映像在生成后未更改:

    notation verify $IMAGE
    

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

使用时间戳

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

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

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

表示法在 Azure Pipelines 和 GitHub Actions 上提供 CI/CD 解决方案:

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