镜像签名证书验证

Nova 可以确定用于生成和验证签名镜像的证书(请参阅 Glance 镜像签名验证文档)是否受用户信任。此功能称为证书验证,可以应用于实例的创建或重建。

证书验证旨在与镜像签名验证一起执行,但每个功能都有自己的 Nova 配置选项,需要在 [glance] 部分的 nova.conf 配置文件中指定。要启用证书验证,请将 glance.enable_certificate_validation 设置为 True。要启用签名验证,请将 glance.verify_glance_signatures 设置为 True。相反,要禁用这些功能中的任何一个,请将其选项设置为 False,或者根本不要在 Nova 配置中包含该选项。

证书验证与 Cursive 中的签名验证协同工作。它接收一个受信任证书 ID 列表,并验证用于签名要启动的镜像的证书是否与提供的受信任证书中的至少一个证书具有密码学关联。这为用户提供了对正在启动的镜像的身份和完整性的信心。

只有在启用镜像签名验证时才会执行证书验证。但是,受信任证书 ID 的存在会覆盖 enable_certificate_validationverify_glance_signatures 设置。换句话说,如果将受信任证书 ID 列表提供给实例创建或重建命令,则无论 Nova 配置中的设置如何,都将执行签名验证和证书验证。有关详细信息,请参阅 使用签名验证

注意

证书验证配置选项必须在控制 nova-osapi_computenova-compute 服务的 Nova 配置文件中指定,而不是其他 Nova 服务(conductor、scheduler 等)。

需求

密钥管理器是 Castellan 接口 的后端。可能的密钥管理器是

限制

  • 从 18.0.0 Rocky 版本开始,只有 libvirt 计算驱动程序支持受信任镜像认证验证。但是,该功能并非驱动程序特定,因此其他驱动程序应随着时间的推移支持此功能。有关哪些驱动程序在任何给定版本中支持该功能的的信息,请参阅 功能支持矩阵

  • 从 18.0.0 Rocky 版本开始,在使用 rbd 镜像后端 ([libvirt]/images_type=rbd) 和 RAW 格式镜像时,libvirt 计算驱动程序不支持镜像签名和受信任镜像认证验证。这是由于镜像直接在 RBD 后端中克隆,从而避免了在计算节点上下载和验证的调用。

  • 从 18.0.0 Rocky 版本开始,受信任镜像认证验证不支持基于卷的实例 (从卷启动)。块存储服务支持可能在未来的版本中提供

    https://blueprints.launchpad.net/cinder/+spec/certificate-validate

  • 如果需要禁用受信任镜像认证支持,可以通过 策略配置 进行控制。请参阅 os_compute_api:servers:create:trusted_certsos_compute_api:servers:rebuild:trusted_certs 策略规则。

配置

Nova 将使用由 Castellan 密钥管理器接口定义的密钥管理器,默认情况下为 Barbican 密钥管理器。要使用不同的密钥管理器,请更新 nova 配置文件中 [key_manager] 组中的 backend 值。例如

[key_manager]
backend = barbican

注意

如果这些行不存在,则只需将它们添加到文件末尾即可。

使用签名验证

要启用签名验证,镜像需要一些属性

img_signature

您的镜像的签名。签名限制是

  • 255 个字符限制

img_signature_hash_method

用于哈希您的签名的方法。可能的哈希方法是

  • SHA-224

  • SHA-256

  • SHA-384

  • SHA-512

img_signature_key_type

用于您的镜像的密钥类型。可能的密钥类型是

  • RSA-PSS

  • DSA

  • ECC-CURVES

    • SECT571K1

    • SECT409K1

    • SECT571R1

    • SECT409R1

    • SECP521R1

    • SECP384R1

img_signature_certificate_uuid

您上传到密钥管理器的证书的 UUID。可能的证书类型是

  • X_509

使用证书验证

证书验证通过以下两种方式之一触发

  1. Nova 配置选项 verify_glance_signaturesenable_certificate_validation 都设置为 True

    [glance]
    verify_glance_signatures = True
    enable_certificate_validation = True
    
  2. 受信任证书 ID 列表通过以下三种方式之一提供

    注意

    命令行支持正在等待 https://review.opendev.org/#/c/500396/https://review.opendev.org/#/c/501926/ 对 python-novaclient 和 python-openstackclient 的更改。

    环境变量

    使用环境变量 OS_TRUSTED_IMAGE_CERTIFICATE_IDS 定义以逗号分隔的受信任证书 ID 列表。例如

    $ export OS_TRUSTED_IMAGE_CERTIFICATE_IDS=79a6ad17-3298-4e55-8b3a-1672dd93c40f,b20f5600-3c9d-4af5-8f37-3110df3533a0
    
    命令行标志

    如果使用 nova 命令启动或重建实例,请使用 --trusted-image-certificate-id 标志定义单个受信任证书 ID。可以使用该标志多次以指定多个受信任证书 ID。例如

    $ nova boot myInstanceName \
        --flavor 1 \
        --image myImageId \
        --trusted-image-certificate-id 79a6ad17-3298-4e55-8b3a-1672dd93c40f \
        --trusted-image-certificate-id b20f5600-3c9d-4af5-8f37-3110df3533a0
    

    如果使用 openstack server 命令启动或重建实例,请使用 --trusted-image-certificate-id 标志定义单个受信任证书 ID。可以使用该标志多次以指定多个受信任证书 ID。例如

    $ openstack --os-compute-api-version=2.63 server create myInstanceName \
        --flavor 1 \
        --image myImageId \
        --nic net-id=fd25c0b2-b36b-45a8-82e4-ab52516289e5 \
        --trusted-image-certificate-id 79a6ad17-3298-4e55-8b3a-1672dd93c40f \
        --trusted-image-certificate-id b20f5600-3c9d-4af5-8f37-3110df3533a0
    
    Nova 配置选项

    使用 Nova 配置选项 glance.default_trusted_certificate_ids 定义以逗号分隔的受信任证书 ID 列表。仅当 verify_glance_signaturesenable_certificate_validation 选项设置为 True,并且受信任证书 ID 在其他地方未指定时,才使用此配置值。例如

    [glance]
    default_trusted_certificate_ids=79a6ad17-3298-4e55-8b3a-1672dd93c40f,b20f5600-3c9d-4af5-8f37-3110df3533a0
    

示例用法

对于这些说明,我们将构建一个 4 证书链,以说明拥有单个受信任根证书是可能的。我们将把所有四个证书上传到 Barbican。然后,我们将对镜像进行签名并将其上传到 Glance,这将说明镜像签名验证。最后,我们将从 Glance 启动已签名的镜像,以显示证书验证已强制执行。

启用证书验证

通过将 Nova 配置选项都设置为 True 来启用镜像签名验证和证书验证

[glance]
verify_glance_signatures = True
enable_certificate_validation = True

创建证书链

如上所述,我们将构建一个 4 证书链,以说明拥有单个受信任根证书是可能的。在开始构建我们的证书链之前,我们必须首先为 OpenSSL 创建文件以用于索引和序列号跟踪

$ touch index.txt
$ echo '01' > serial.txt

创建证书配置文件

对于这些说明,我们将创建一个名为 ca.conf 的单个配置文件,其中包含各种部分,我们可以在命令行期间在证书请求和生成期间指定这些部分以供使用。

请注意,此证书能够签署其他证书,因为它是一个证书颁发机构。另外请注意根 CA 的唯一通用名称(“root”)。中间证书的通用名称将在生成相应的证书请求时在命令行上指定。

ca.conf:

[ req ]
prompt             = no
distinguished_name = dn-param
x509_extensions    = ca_cert_extensions

[ ca ]
default_ca = ca_default

[ dn-param ]
C  = US
CN = Root CA

[ ca_cert_extensions ]
keyUsage         = keyCertSign, digitalSignature
basicConstraints = CA:TRUE, pathlen:2

[ ca_default ]
new_certs_dir = .              # Location for new certs after signing
database      = ./index.txt    # Database index file
serial        = ./serial.txt   # The current serial number

default_days  = 1000
default_md    = sha256

policy        = signing_policy
email_in_dn   = no

[ intermediate_cert_extensions ]
keyUsage         = keyCertSign, digitalSignature
basicConstraints = CA:TRUE, pathlen:1

[client_cert_extensions]
keyUsage         = keyCertSign, digitalSignature
basicConstraints = CA:FALSE

[ signing_policy ]
countryName            = optional
stateOrProvinceName    = optional
localityName           = optional
organizationName       = optional
organizationalUnitName = optional
commonName             = supplied
emailAddress           = optional

生成证书颁发机构 (CA) 及其对应的私钥

对于这些说明,我们将把证书保存为 cert_ca.pem,将私钥保存为 key_ca.pem。此证书将是一个自签名根证书颁发机构 (CA),可以签署其他 CA 和非 CA 证书。

$ openssl req \
    -x509 \
    -nodes \
    -newkey rsa:1024 \
    -config ca.conf \
    -keyout key_ca.pem \
    -out cert_ca.pem

Generating a 1024 bit RSA private key
............................++++++
...++++++
writing new private key to 'key_ca.pem'
-----

创建第一个中间证书

为第一个中间证书创建一个证书请求。对于这些说明,我们将把证书请求保存为 cert_intermediate_a.csr,将私钥保存为 key_intermediate_a.pem

$ openssl req \
    -nodes \
    -newkey rsa:2048 \
    -subj '/CN=First Intermediate Certificate' \
    -keyout key_intermediate_a.pem \
    -out cert_intermediate_a.csr

Generating a 2048 bit RSA private key
.............................................................................................................+++
.....+++
writing new private key to 'key_intermediate_a.pem'
-----

通过使用 CA 签署其证书请求来生成第一个中间证书。对于这些说明,我们将把证书保存为 cert_intermediate_a.pem

$ openssl ca \
    -config ca.conf \
    -extensions intermediate_cert_extensions \
    -cert cert_ca.pem \
    -keyfile key_ca.pem \
    -out cert_intermediate_a.pem \
    -infiles cert_intermediate_a.csr

Using configuration from ca.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'First Intermediate Certificate'
Certificate is to be certified until Nov 15 16:24:21 2020 GMT (1000 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

创建第二个中间证书

为第二个中间证书创建一个证书请求。对于这些说明,我们将把证书请求保存为 cert_intermediate_b.csr,将私钥保存为 key_intermediate_b.pem

$ openssl req \
    -nodes \
    -newkey rsa:2048 \
    -subj '/CN=Second Intermediate Certificate' \
    -keyout key_intermediate_b.pem \
    -out cert_intermediate_b.csr

Generating a 2048 bit RSA private key
..........+++
............................................+++
writing new private key to 'key_intermediate_b.pem'
-----

通过使用第一个中间证书签署其证书请求来生成第二个中间证书。对于这些说明,我们将把证书保存为 cert_intermediate_b.pem

$ openssl ca \
    -config ca.conf \
    -extensions intermediate_cert_extensions \
    -cert cert_intermediate_a.pem \
    -keyfile key_intermediate_a.pem \
    -out cert_intermediate_b.pem \
    -infiles cert_intermediate_b.csr

Using configuration from ca.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'Second Intermediate Certificate'
Certificate is to be certified until Nov 15 16:25:42 2020 GMT (1000 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

创建客户端证书

为客户端证书创建一个证书请求。对于这些说明,我们将把证书请求保存为 cert_client.csr,将私钥保存为 key_client.pem

$ openssl req \
    -nodes \
    -newkey rsa:2048 \
    -subj '/CN=Client Certificate' \
    -keyout key_client.pem \
    -out cert_client.csr

Generating a 2048 bit RSA private key
.............................................................................................................................+++
..............................................................................................+++
writing new private key to 'key_client.pem'
-----

通过使用第二个中间证书签署其证书请求来生成客户端证书。对于这些说明,我们将把证书保存为 cert_client.pem

$ openssl ca \
    -config ca.conf \
    -extensions client_cert_extensions \
    -cert cert_intermediate_b.pem \
    -keyfile key_intermediate_b.pem \
    -out cert_client.pem \
    -infiles cert_client.csr

Using configuration from ca.conf
Check that the request matches the signature
Signature ok
The Subject's Distinguished Name is as follows
commonName            :ASN.1 12:'Client Certificate'
Certificate is to be certified until Nov 15 16:26:46 2020 GMT (1000 days)
Sign the certificate? [y/n]:y


1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated

将生成的证书上传到密钥管理器

为了与密钥管理器交互,用户需要具有 creator 角色。

要列出所有具有 creator 角色的用户,请以管理员身份运行以下命令

$ openstack role assignment list --role creator --names

+---------+-----------------------------+-------+-------------------+--------+-----------+
| Role    | User                        | Group | Project           | Domain | Inherited |
+---------+-----------------------------+-------+-------------------+--------+-----------+
| creator | project_a_creator_2@Default |       | project_a@Default |        | False     |
| creator | project_b_creator@Default   |       | project_b@Default |        | False     |
| creator | project_a_creator@Default   |       | project_a@Default |        | False     |
+---------+-----------------------------+-------+-------------------+--------+-----------+

要将 demo 用户在 demo 项目中赋予 creator 角色,请以管理员身份运行以下命令

$ openstack role add --user demo --project demo creator

注意

此命令不提供任何输出。如果命令失败,用户将看到“4xx 客户端错误”,指示“不允许创建密钥”和“请查看您的用户/项目权限”。

注意

以下“openstack secret”命令要求安装了 python-barbicanclient 包。

$ openstack secret store \
    --name CA \
    --algorithm RSA \
    --expiration 2018-06-29 \
    --secret-type certificate \
    --payload-content-type "application/octet-stream" \
    --payload-content-encoding base64 \
    --payload "$(base64 cert_ca.pem)"

$ openstack secret store \
    --name IntermediateA \
    --algorithm RSA \
    --expiration 2018-06-29 \
    --secret-type certificate \
    --payload-content-type "application/octet-stream" \
    --payload-content-encoding base64 \
    --payload "$(base64 cert_intermediate_a.pem)"

$ openstack secret store \
    --name IntermediateB \
    --algorithm RSA \
    --expiration 2018-06-29 \
    --secret-type certificate \
    --payload-content-type "application/octet-stream" \
    --payload-content-encoding base64 \
    --payload "$(base64 cert_intermediate_b.pem)"

$ openstack secret store \
    --name Client \
    --algorithm RSA \
    --expiration 2018-06-29 \
    --secret-type certificate \
    --payload-content-type "application/octet-stream" \
    --payload-content-encoding base64 \
    --payload "$(base64 cert_client.pem)"

响应应如下所示

+---------------+------------------------------------------------------------------------------+
| Field         | Value                                                                        |
+---------------+------------------------------------------------------------------------------+
| Secret href   | http://127.0.0.1/key-manager/v1/secrets/8fbcce5d-d646-4295-ba8a-269fc9451eeb |
| Name          | CA                                                                           |
| Created       | None                                                                         |
| Status        | None                                                                         |
| Content types | {u'default': u'application/octet-stream'}                                    |
| Algorithm     | RSA                                                                          |
| Bit length    | 256                                                                          |
| Secret type   | certificate                                                                  |
| Mode          | cbc                                                                          |
| Expiration    | 2018-06-29T00:00:00+00:00                                                    |
+---------------+------------------------------------------------------------------------------+

保存证书 UUID(在 secret href 中找到)

$ cert_ca_uuid=8fbcce5d-d646-4295-ba8a-269fc9451eeb
$ cert_intermediate_a_uuid=0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8
$ cert_intermediate_b_uuid=674736e3-f25c-405c-8362-bbf991e0ce0a
$ cert_client_uuid=125e6199-2de4-46e3-b091-8e2401ef0d63

创建已签名的镜像

对于这些说明,我们将下载一个小的 CirrOS 镜像

$ wget -nc -O cirros.tar.gz http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-source.tar.gz

--2018-02-19 11:37:52--  http://download.cirros-cloud.net/0.3.5/cirros-0.3.5-source.tar.gz
Resolving download.cirros-cloud.net (download.cirros-cloud.net)... 64.90.42.85
Connecting to download.cirros-cloud.net (download.cirros-cloud.net)|64.90.42.85|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 434333 (424K) [application/x-tar]
Saving to: ‘cirros.tar.gz’

cirros.tar.gz       100%[===================>] 424.15K  --.-KB/s    in 0.1s

2018-02-19 11:37:54 (3.79 MB/s) - ‘cirros.tar.gz’ saved [434333/434333]

使用生成的客户端私钥对镜像进行签名

$ openssl dgst \
    -sha256 \
    -sign key_client.pem \
    -sigopt rsa_padding_mode:pss \
    -out cirros.self_signed.signature \
    cirros.tar.gz

注意

此命令不会产生任何输出。

保存 base64 编码的签名

$ base64_signature=$(base64 -w 0 cirros.self_signed.signature)

将已签名的镜像上传到 Glance

$ openstack image create \
    --public \
    --container-format bare \
    --disk-format qcow2 \
    --property img_signature="$base64_signature" \
    --property img_signature_certificate_uuid="$cert_client_uuid" \
    --property img_signature_hash_method='SHA-256' \
    --property img_signature_key_type='RSA-PSS' \
    --file cirros.tar.gz \
    cirros_client_signedImage

+------------------+------------------------------------------------------------------------+
| Field            | Value                                                                  |
+------------------+------------------------------------------------------------------------+
| checksum         | d41d8cd98f00b204e9800998ecf8427e                                       |
| container_format | bare                                                                   |
| created_at       | 2019-02-06T06:29:56Z                                                   |
| disk_format      | qcow2                                                                  |
| file             | /v2/images/17f48a6c-e592-446e-9c91-00fbc436d47e/file                   |
| id               | 17f48a6c-e592-446e-9c91-00fbc436d47e                                   |
| min_disk         | 0                                                                      |
| min_ram          | 0                                                                      |
| name             | cirros_client_signedImage                                              |
| owner            | 45e13e63606f40d6b23275c3cd91aec2                                       |
| properties       | img_signature='swA/hZi3WaNh35VMGlnfGnBWuXMlUbdO8h306uG7W3nwOyZP6dGRJ3  |
|                  | Xoi/07Bo2dMUB9saFowqVhdlW5EywQAK6vgDsi9O5aItHM4u7zUPw+2e8eeaIoHlGhTks  |
|                  | kmW9isLy0mYA9nAfs3coChOIPXW4V8VgVXEfb6VYGHWm0nShiAP1e0do9WwitsE/TVKoS  |
|                  | QnWjhggIYij5hmUZ628KAygPnXklxVhqPpY/dFzL+tTzNRD0nWAtsc5wrl6/8HcNzZsaP  |
|                  | oexAysXJtcFzDrf6UQu66D3UvFBVucRYL8S3W56It3Xqu0+InLGaXJJpNagVQBb476zB2  |
|                  | ZzZ5RJ/4Zyxw==',                                                       |
|                  | img_signature_certificate_uuid='125e6199-2de4-46e3-b091-8e2401ef0d63', |
|                  | img_signature_hash_method='SHA-256',                                   |
|                  | img_signature_key_type='RSA-PSS',                                      |
|                  | os_hash_algo='sha512',                                                 |
|                  | os_hash_value='cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a92 |
|                  | 1d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927d |
|                  | a3e',                                                                  |
|                  | os_hidden='False'                                                      |
| protected        | False                                                                  |
| schema           | /v2/schemas/image                                                      |
| size             | 0                                                                      |
| status           | active                                                                 |
| tags             |                                                                        |
| updated_at       | 2019-02-06T06:29:56Z                                                   |
| virtual_size     | None                                                                   |
| visibility       | public                                                                 |
+------------------+------------------------------------------------------------------------+

注意

如果验证不成功,创建镜像可能会失败。这将导致删除镜像,并且 Glance 日志将报告“签名验证失败”的给定镜像 ID。

启动已签名的镜像

在未指定受信任证书 ID 的情况下启动已签名的镜像

$ nova boot myInstance \
    --flavor m1.tiny \
    --image cirros_client_signedImage

注意

由于在启用该功能但未提供受信任镜像证书的情况下,实例将无法启动。Nova 日志输出应指示“镜像签名证书验证失败”,因为“证书链构建失败”。

使用受信任证书 ID 启动已签名的镜像

$ nova boot myInstance \
    --flavor m1.tiny \
    --image cirros_client_signedImage \
    --trusted-image-certificate-id $cert_ca_uuid,$cert_intermediate_a_uuid \
    --trusted-image-certificate-id $cert_intermediate_b_uuid

注意

实例应该成功启动,并且证书验证应该成功。Nova日志输出应该表明“镜像签名证书验证成功”。