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

使用受信任的证书颁发机构 (CA) 颁发的证书对容器映像进行签名和验证是一种有效的安全做法。 此安全措施将帮助你负责任地识别、授权和验证容器映像发布者和容器映像本身的标识。 受信任的证书颁发机构 (CA)(例如 GlobalSign、DigiCert 等)在验证用户或组织的标识、维护数字证书的安全性以及出现任何风险或滥用时立即吊销证书等方面发挥着至关重要的作用。

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

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

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

本文内容:

  • 安装 notation CLI 和 AKV 插件
  • 在 AKV 中创建或导入 CA 颁发的证书
  • 使用 ACR 任务生成并推送容器映像
  • 使用 Notation CLI 和 AKV 插件为容器映像签名
  • 使用 Notation CLI 验证容器映像签名
  • 时间戳

先决条件

注意

建议创建新的 Azure Key Vault 来仅存储证书。

安装 notation CLI 和 AKV 插件

  1. 在 Linux amd64 环境中安装 Notation v1.2.0。 按照 Notation 安装指南下载适用于其他环境的包。

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

    注意

    可以在插件的发布页上找到 Notation Azure Key Vault 插件的 URL 和 SHA256 校验和。

    notation plugin install --url https://github.com/Azure/notation-azure-kv/releases/download/v1.2.0/notation-azure-kv_1.2.0_linux_amd64.tar.gz --sha256sum 06bb5198af31ce11b08c4557ae4c2cbfb09878dfa6b637b7407ebc2d57b87b34
    
  3. 列出可用的插件,并确认版本为 1.2.0azure-kv 插件是否已包含在列表中。

    notation plugin ls
    

配置环境变量

注意

为了方便配置 AKV 和 ACR,本指南使用环境变量。 更新特定资源的这些环境变量的值。

  1. 为 AKV 和证书配置环境变量

    AKV_SUB_ID=myAkvSubscriptionId
    AKV_RG=myAkvResourceGroup
    AKV_NAME=myakv 
    
    # Name of the certificate created or imported in AKV 
    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 和映像配置环境变量。

    ACR_SUB_ID=myAcrSubscriptionId
    ACR_RG=myAcrResourceGroup
    # Name of the existing registry example: myregistry.azurecr.cn 
    ACR_NAME=myregistry 
    # Existing full domain of the ACR 
    REGISTRY=$ACR_NAME.azurecr.cn 
    # Container name inside ACR where image will be stored 
    REPO=net-monitor 
    TAG=v1 
    # Source code directory containing 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 Public Azure.

若要详细了解 Azure CLI 及其登录方式,请参阅使用 Azure CLI 登录

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

证书要求

创建证书进行签名和验证时,证书必须满足 Notary 项目证书要求

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

  • basicConstraints 扩展必须存在并且标记为“关键”。 CA 字段必须设置为 true
  • keyUsage 扩展必须存在并且标记为 critical。 必须设置 keyCertSign 的位位置。

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

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

重要

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

注意

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

创建 CA 颁发的证书

按照创建证书签名请求中的说明创建证书签名请求 (CSR)。

重要

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

在 AKV 中导入证书

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

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

注意

如果证书在创建或导入后不包含证书链,可以从 CA 供应商处获取中间证书和根证书。 可以要求供应商提供包含中间证书(如果有)和根证书的 PEM 文件。 此文件将在签名容器映像的步骤 5 中使用。

使用 Notation CLI 和 AKV 插件为容器映像签名

使用 ACR 和 AKV 时,必须授予适当的权限以确保安全性和受控访问。 可以根据特定方案为不同的实体(例如用户主体、服务主体或托管标识)授予访问权限。 在本教程中,访问权限授予给已登录的 Azure 用户。

创建对 ACR 的访问权限

在 ACR 中生成和签名容器映像需要 AcrPullAcrPush 角色。

  1. 设置包含 ACR 资源的订阅

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

    USER_ID=$(az ad signed-in-user show --query id -o tsv)
    az role assignment create --role "AcrPull" --role "AcrPush" --assignee $USER_ID --scope "/subscriptions/$ACR_SUB_ID/resourceGroups/$ACR_RG/providers/Microsoft.ContainerRegistry/registries/$ACR_NAME"
    

生成映像并向 ACR 推送

  1. 使用单个 Azure 标识向 ACR 进行身份验证。

    az acr login --name $ACR_NAME
    

    重要

    如果已在系统上安装 Docker 并使用 az acr logindocker login 向 ACR 进行身份验证,则凭据已存储并可用于表示法。 在这种情况下,无需再次运行 notation login 即可向 ACR 进行身份验证。 若要详细了解表示法的身份验证选项,请参阅使用符合 OCI 的注册表进行身份验证

  2. 使用 Azure 任务生成并推送新映像。 始终使用 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
    

创建对 AKV 的访问权限

  1. 设置包含 AKV 资源的订阅

    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 RBAC 管理访问权限

使用访问策略(旧版)

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

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

若要详细了解如何将策略分配给主体,请参阅分配访问策略

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

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

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

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

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

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

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

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

    如果你要指定凭据类型,请使用名为 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
    

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

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

使用 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
    

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

  4. 使用 notation policytrustpolicy.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,并通过信任存储和信任策略来建立信任。 时间戳消除了由于证书过期而定期重新签署映像的需要,从而降低了成本,这在使用短期证书时尤为重要。 有关如何使用时间戳进行签名和验证的详细说明,请参阅公证项目时间戳指南

常见问题解答

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

    如果证书已过期,则需要从受信任的 CA 供应商以及新的私钥获取新证书。 过期的证书不能用于对容器映像进行签名。 对于在证书过期之前签名的映像,如果已使用时间戳进行签名,它们仍可以成功进行验证。 如果没有时间戳,签名验证将失败,你需要使用新证书重新为这些映像签名才能成功进行验证。

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

    如果证书已吊销,则会导致签名失效。 有多种原因会导致发生这种情况,例如私钥遭到入侵或更改证书持有者的附属关系。 若要解决此问题,应首先确保源代码和生成环境是最新且安全的。 然后,从源代码生成容器映像、从受信任的 CA 供应商获取新证书以及新的私钥,并按照本指南使用新证书对新容器映像进行签名。

后续步骤

Notation 还在 Azure 管道和 GitHub Actions 工作流上提供 CI/CD 解决方案:

验证 AKS 或 Kubernetes 中的已签名映像部署: