Add trusted_image_certificates to REST API
This change adds support for the trusted_image_certificates parameter, which is used to define a list of trusted certificate IDs that can be used during image signature verification and certificate validation. The parameter may contain a list of strings, each string representing the ID of a trusted certificate. The list is restricted to a maximum of 50 IDs. The list of certificate IDs will be stored in the trusted_certs field of the instance InstanceExtra and will be used to verify the validity of the signing certificate of a signed instance image. The trusted_image_certificates request parameter can be passed to the server create and rebuild APIs (if allowed by policy): * POST /servers * POST /servers/{server_id}/action (rebuild) The following policy rules were added to restrict the usage of the ``trusted_image_certificates`` request parameter in the server create and rebuild APIs: * os_compute_api:servers:create:trusted_certs * os_compute_api:servers:rebuild:trusted_certs The trusted_image_certificates parameter will be in the response body of the following APIs (not restricted by policy): * GET /servers/detail * GET /servers/{server_id} * PUT /servers/{server_id} * POST /servers/{server_id}/action (rebuild) APIImpact Implements blueprint: nova-validate-certificates Change-Id: Iedd3fea0e86648fae364f075915555dcb2c4f199
This commit is contained in:
parent
ca7d23a3e7
commit
8c7ca368b1
@ -5706,6 +5706,40 @@ server_tags_create:
|
||||
required: false
|
||||
type: array
|
||||
min_version: 2.52
|
||||
server_trusted_image_certificates_create_req:
|
||||
description: |
|
||||
A list of trusted certificate IDs, which are used during image
|
||||
signature verification to verify the signing certificate. The list is
|
||||
restricted to a maximum of 50 IDs. This parameter is optional in server
|
||||
create requests if allowed by policy, and is not supported for
|
||||
volume-backed instances.
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
min_version: 2.63
|
||||
server_trusted_image_certificates_rebuild_req:
|
||||
description: |
|
||||
A list of trusted certificate IDs, which are used during image
|
||||
signature verification to verify the signing certificate. The list is
|
||||
restricted to a maximum of 50 IDs. This parameter is optional in server
|
||||
rebuild requests if allowed by policy, and is not supported
|
||||
for volume-backed instances.
|
||||
|
||||
If ``null`` is specified, the existing trusted certificate IDs are either
|
||||
unset or reset to the configured defaults.
|
||||
in: body
|
||||
required: false
|
||||
type: array
|
||||
min_version: 2.63
|
||||
server_trusted_image_certificates_resp:
|
||||
description: |
|
||||
A list of trusted certificate IDs, that were used during image signature
|
||||
verification to verify the signing certificate. The list is restricted
|
||||
to a maximum of 50 IDs.
|
||||
in: body
|
||||
required: true
|
||||
type: array
|
||||
min_version: 2.63
|
||||
server_usages:
|
||||
description: |
|
||||
A list of the server usage objects.
|
||||
|
@ -488,10 +488,11 @@ Request
|
||||
- description: server_description
|
||||
- key_name: key_name_rebuild_req
|
||||
- user_data: user_data_rebuild_req
|
||||
- trusted_image_certificates: server_trusted_image_certificates_rebuild_req
|
||||
|
||||
**Example Rebuild Server (rebuild Action) (v2.54)**
|
||||
**Example Rebuild Server (rebuild Action) (v2.63)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.54/server-action-rebuild.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-action-rebuild.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
@ -537,10 +538,11 @@ Response
|
||||
- tags: tags
|
||||
- key_name: key_name_rebuild_resp
|
||||
- user_data: user_data_rebuild_resp
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
|
||||
**Example Rebuild Server (rebuild Action) (v2.54)**
|
||||
**Example Rebuild Server (rebuild Action) (v2.63)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.54/server-action-rebuild-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-action-rebuild-resp.json
|
||||
:language: javascript
|
||||
|
||||
Remove (Disassociate) Floating Ip (removeFloatingIp Action) (DEPRECATED)
|
||||
|
@ -381,6 +381,7 @@ Request
|
||||
- os:scheduler_hints.query: os:scheduler_hints_query
|
||||
- os:scheduler_hints.same_host: os:scheduler_hints_same_host
|
||||
- os:scheduler_hints.target_cell: os:scheduler_hints_target_cell
|
||||
- trusted_image_certificates: server_trusted_image_certificates_create_req
|
||||
|
||||
**Example Create Server**
|
||||
|
||||
@ -392,6 +393,11 @@ Request
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.37/server-create-req.json
|
||||
:language: javascript
|
||||
|
||||
**Example Create Server With Trusted Image Certificates (v2.63)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-create-req.json
|
||||
:language: javascript
|
||||
|
||||
Response
|
||||
--------
|
||||
|
||||
@ -610,10 +616,11 @@ Response
|
||||
- host_status: host_status
|
||||
- description: server_description_resp
|
||||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
|
||||
**Example List Servers Detailed (2.47)**
|
||||
**Example List Servers Detailed (2.63)**
|
||||
|
||||
.. literalinclude:: /../../doc/api_samples/servers/v2.47/servers-details-resp.json
|
||||
.. literalinclude:: /../../doc/api_samples/servers/v2.63/servers-details-resp.json
|
||||
:language: javascript
|
||||
|
||||
|
||||
@ -716,10 +723,11 @@ Response
|
||||
- host_status: host_status
|
||||
- description: server_description_resp
|
||||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
|
||||
**Example Show Server Details (2.47)**
|
||||
**Example Show Server Details (2.63)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.47/server-get-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-get-resp.json
|
||||
:language: javascript
|
||||
|
||||
Update Server
|
||||
@ -808,10 +816,11 @@ Response
|
||||
- locked: locked
|
||||
- description: server_description_resp
|
||||
- tags: tags
|
||||
- trusted_image_certificates: server_trusted_image_certificates_resp
|
||||
|
||||
**Example Update server name (2.47)**
|
||||
**Example Update server name (2.63)**
|
||||
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.47/server-update-resp.json
|
||||
.. literalinclude:: ../../doc/api_samples/servers/v2.63/server-update-resp.json
|
||||
:language: javascript
|
||||
|
||||
Delete Server
|
||||
|
@ -0,0 +1,71 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"adminPass": "seekr3t",
|
||||
"created": "2017-10-10T16:06:02Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:cpu_policy": "dedicated",
|
||||
"hw:mem_page_size": "2048"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "28d8d56f0e3a77e20891f455721cbb68032e017045e20aa5dfc6cb66",
|
||||
"id": "a0a80a94-3d81-4a10-822a-daa0cf9e870b",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/a4baaf2a-3768-4e45-8847-13becef6bc5e",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/a4baaf2a-3768-4e45-8847-13becef6bc5e",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"meta_var": "meta_val"
|
||||
},
|
||||
"name": "foobar",
|
||||
"key_name": "new-key",
|
||||
"description" : "description of foobar",
|
||||
"progress": 0,
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "2017-10-10T16:06:03Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
||||
|
20
doc/api_samples/servers/v2.63/server-action-rebuild.json
Normal file
20
doc/api_samples/servers/v2.63/server-action-rebuild.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"rebuild" : {
|
||||
"accessIPv4" : "1.2.3.4",
|
||||
"accessIPv6" : "80fe::",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"name" : "foobar",
|
||||
"key_name": "new-key",
|
||||
"description" : "description of foobar",
|
||||
"adminPass" : "seekr3t",
|
||||
"metadata" : {
|
||||
"meta_var" : "meta_val"
|
||||
},
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
]
|
||||
}
|
||||
}
|
28
doc/api_samples/servers/v2.63/server-create-req.json
Normal file
28
doc/api_samples/servers/v2.63/server-create-req.json
Normal file
@ -0,0 +1,28 @@
|
||||
{
|
||||
"server" : {
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"name" : "new-server-test",
|
||||
"imageRef" : "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"flavorRef" : "6",
|
||||
"availability_zone": "nova",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"user_data" : "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"networks": "auto",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
]
|
||||
},
|
||||
"OS-SCH-HNT:scheduler_hints": {
|
||||
"same_host": "[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
|
||||
}
|
||||
}
|
22
doc/api_samples/servers/v2.63/server-create-resp.json
Normal file
22
doc/api_samples/servers/v2.63/server-create-resp.json
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"adminPass": "wKLKinb9u7GM",
|
||||
"id": "aab35fd0-b459-4b59-9308-5a23147f3165",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/aab35fd0-b459-4b59-9308-5a23147f3165",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/aab35fd0-b459-4b59-9308-5a23147f3165",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
93
doc/api_samples/servers/v2.63/server-get-resp.json
Normal file
93
doc/api_samples/servers/v2.63/server-get-resp.json
Normal file
@ -0,0 +1,93 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "r-ov3q80zj",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "2017-02-14T19:23:59.895661",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "2017-02-14T19:23:58Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:cpu_policy": "dedicated",
|
||||
"hw:mem_page_size": "2048"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "9168b536-cd40-4630-b43f-b259807c6e87",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/9168b536-cd40-4630-b43f-b259807c6e87",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/9168b536-cd40-4630-b43f-b259807c6e87",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "2017-02-14T19:24:00Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
8
doc/api_samples/servers/v2.63/server-update-req.json
Normal file
8
doc/api_samples/servers/v2.63/server-update-req.json
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"server": {
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"name" : "new-server-test"
|
||||
}
|
||||
}
|
66
doc/api_samples/servers/v2.63/server-update-resp.json
Normal file
66
doc/api_samples/servers/v2.63/server-update-resp.json
Normal file
@ -0,0 +1,66 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "2012-12-02T02:11:57Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:cpu_policy": "dedicated",
|
||||
"hw:mem_page_size": "2048"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "6e84af987b4e7ec1c039b16d21f508f4a505672bd94fb0218b668d07",
|
||||
"id": "324dfb7d-f4a9-419a-9a19-237df04b443b",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2/6f70656e737461636b20342065766572/servers/324dfb7d-f4a9-419a-9a19-237df04b443b",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/324dfb7d-f4a9-419a-9a19-237df04b443b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"progress": 0,
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "2012-12-02T02:11:58Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
95
doc/api_samples/servers/v2.63/servers-details-resp.json
Normal file
95
doc/api_samples/servers/v2.63/servers-details-resp.json
Normal file
@ -0,0 +1,95 @@
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "compute",
|
||||
"OS-EXT-SRV-ATTR:hostname": "new-server-test",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "fake-mini",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "instance-00000001",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "r-y0w4v32k",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "IyEvYmluL2Jhc2gKL2Jpbi9zdQplY2hvICJJIGFtIGluIHlvdSEiCg==",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"OS-SRV-USG:launched_at": "2017-10-10T15:49:09.516729",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"accessIPv4": "1.2.3.4",
|
||||
"accessIPv6": "80fe::",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"config_drive": "",
|
||||
"created": "2017-10-10T15:49:08Z",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:cpu_policy": "dedicated",
|
||||
"hw:mem_page_size": "2048"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "2091634baaccdc4c5a1d57069c833e402921df696b7f970791b12ec6",
|
||||
"host_status": "UP",
|
||||
"id": "569f39f9-7c76-42a1-9c2d-8394e2638a6d",
|
||||
"image": {
|
||||
"id": "70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/images/70a599e0-31e7-49b7-b260-868f441e862b",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "http://openstack.example.com/v2.1/6f70656e737461636b20342065766572/servers/569f39f9-7c76-42a1-9c2d-8394e2638a6d",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "http://openstack.example.com/6f70656e737461636b20342065766572/servers/569f39f9-7c76-42a1-9c2d-8394e2638a6d",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "2017-10-10T15:49:09Z",
|
||||
"user_id": "fake"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,7 +19,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.62",
|
||||
"version": "2.63",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
||||
}
|
||||
],
|
||||
"status": "CURRENT",
|
||||
"version": "2.62",
|
||||
"version": "2.63",
|
||||
"min_version": "2.1",
|
||||
"updated": "2013-07-23T11:33:21Z"
|
||||
}
|
||||
|
@ -148,6 +148,8 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
/flavors APIs.
|
||||
* 2.62 - Add ``host`` and ``hostId`` fields to instance action detail API
|
||||
responses.
|
||||
* 2.63 - Add support for applying trusted certificates when creating or
|
||||
rebuilding a server.
|
||||
"""
|
||||
|
||||
# The minimum and maximum versions of the API supported
|
||||
@ -156,7 +158,7 @@ REST_API_VERSION_HISTORY = """REST API Version History:
|
||||
# Note(cyeoh): This only applies for the v2.1 API once microversions
|
||||
# support is fully merged. It does not affect the V2 API.
|
||||
_MIN_API_VERSION = "2.1"
|
||||
_MAX_API_VERSION = "2.62"
|
||||
_MAX_API_VERSION = "2.63"
|
||||
DEFAULT_API_VERSION = _MIN_API_VERSION
|
||||
|
||||
# Almost all proxy APIs which are related to network, images and baremetal
|
||||
|
@ -795,3 +795,26 @@ the newly added ``host`` field will be controlled via policy rule
|
||||
``os_compute_api:os-instance-actions:events``, which is the same policy used
|
||||
for the ``events.traceback`` field. If the user is prevented by policy, only
|
||||
``hostId`` will be displayed.
|
||||
|
||||
2.63
|
||||
----
|
||||
|
||||
Adds support for the ``trusted_image_certificates`` parameter, which is used to
|
||||
define a list of trusted certificate IDs that can be used during image
|
||||
signature verification and certificate validation. The list is restricted to
|
||||
a maximum of 50 IDs. Note that ``trusted_image_certificates`` is not supported
|
||||
with volume-backed servers.
|
||||
|
||||
The ``trusted_image_certificates`` request parameter can be passed to
|
||||
the server create and rebuild APIs:
|
||||
|
||||
* ``POST /servers``
|
||||
* ``POST /servers/{server_id}/action (rebuild)``
|
||||
|
||||
The ``trusted_image_certificates`` parameter will be in the response body of
|
||||
the following APIs:
|
||||
|
||||
* ``GET /servers/detail``
|
||||
* ``GET /servers/{server_id}``
|
||||
* ``PUT /servers/{server_id}``
|
||||
* ``POST /servers/{server_id}/action (rebuild)``
|
||||
|
@ -148,6 +148,13 @@ base_create_v257 = copy.deepcopy(base_create_v252)
|
||||
base_create_v257['properties']['server']['properties'].pop('personality')
|
||||
|
||||
|
||||
# 2.63 builds on 2.57 and makes the following changes:
|
||||
# Allowing adding trusted certificates to instances when booting
|
||||
base_create_v263 = copy.deepcopy(base_create_v257)
|
||||
base_create_v263['properties']['server']['properties'][
|
||||
'trusted_image_certificates'] = parameter_types.trusted_certs
|
||||
|
||||
|
||||
base_update = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
@ -224,6 +231,12 @@ base_rebuild_v257['properties']['rebuild']['properties']['user_data'] = ({
|
||||
]
|
||||
})
|
||||
|
||||
# 2.63 builds on 2.57 and makes the following changes:
|
||||
# Allowing adding trusted certificates to instances when rebuilding
|
||||
base_rebuild_v263 = copy.deepcopy(base_rebuild_v257)
|
||||
base_rebuild_v263['properties']['rebuild']['properties'][
|
||||
'trusted_image_certificates'] = parameter_types.trusted_certs
|
||||
|
||||
resize = {
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
|
@ -86,6 +86,9 @@ class ServersController(wsgi.Controller):
|
||||
schema_server_create_v252 = schema_servers.base_create_v252
|
||||
schema_server_create_v257 = schema_servers.base_create_v257
|
||||
|
||||
schema_server_create_v263 = schema_servers.base_create_v263
|
||||
schema_server_rebuild_v263 = schema_servers.base_rebuild_v263
|
||||
|
||||
# NOTE(alex_xu): Please do not add more items into this list. This list
|
||||
# should be removed in the future.
|
||||
schema_func_list = [
|
||||
@ -132,6 +135,7 @@ class ServersController(wsgi.Controller):
|
||||
|
||||
# TODO(alex_xu): The final goal is that merging all of
|
||||
# extended json-schema into server main json-schema.
|
||||
self._create_schema(self.schema_server_create_v263, '2.63')
|
||||
self._create_schema(self.schema_server_create_v257, '2.57')
|
||||
self._create_schema(self.schema_server_create_v252, '2.52')
|
||||
self._create_schema(self.schema_server_create_v242, '2.42')
|
||||
@ -304,6 +308,8 @@ class ServersController(wsgi.Controller):
|
||||
expected_attrs.append('services')
|
||||
if api_version_request.is_supported(req, '2.26'):
|
||||
expected_attrs.append("tags")
|
||||
if api_version_request.is_supported(req, '2.63'):
|
||||
expected_attrs.append("trusted_certs")
|
||||
|
||||
# merge our expected attrs with what the view builder needs for
|
||||
# showing details
|
||||
@ -345,6 +351,8 @@ class ServersController(wsgi.Controller):
|
||||
if is_detail:
|
||||
if api_version_request.is_supported(req, '2.26'):
|
||||
expected_attrs.append("tags")
|
||||
if api_version_request.is_supported(req, '2.63'):
|
||||
expected_attrs.append("trusted_certs")
|
||||
expected_attrs = self._view_builder.get_show_expected_attrs(
|
||||
expected_attrs)
|
||||
instance = common.get_instance(self.compute_api, context,
|
||||
@ -456,7 +464,8 @@ class ServersController(wsgi.Controller):
|
||||
@validation.schema(schema_server_create_v237, '2.37', '2.41')
|
||||
@validation.schema(schema_server_create_v242, '2.42', '2.51')
|
||||
@validation.schema(schema_server_create_v252, '2.52', '2.56')
|
||||
@validation.schema(schema_server_create_v257, '2.57')
|
||||
@validation.schema(schema_server_create_v257, '2.57', '2.62')
|
||||
@validation.schema(schema_server_create_v263, '2.63')
|
||||
def create(self, req, body):
|
||||
"""Creates a new server for a given user."""
|
||||
context = req.environ['nova.context']
|
||||
@ -489,6 +498,14 @@ class ServersController(wsgi.Controller):
|
||||
'availability_zone': availability_zone}
|
||||
context.can(server_policies.SERVERS % 'create', target)
|
||||
|
||||
# Skip policy check for 'create:trusted_certs' if no trusted
|
||||
# certificate IDs were provided.
|
||||
trusted_certs = server_dict.get('trusted_image_certificates', None)
|
||||
if trusted_certs:
|
||||
create_kwargs['trusted_certs'] = trusted_certs
|
||||
context.can(server_policies.SERVERS % 'create:trusted_certs',
|
||||
target=target)
|
||||
|
||||
# TODO(Shao He, Feng) move this policy check to os-availability-zone
|
||||
# extension after refactor it.
|
||||
parse_az = self.compute_api.parse_availability_zone
|
||||
@ -634,13 +651,15 @@ class ServersController(wsgi.Controller):
|
||||
exception.RealtimeMaskNotFoundOrInvalid,
|
||||
exception.SnapshotNotFound,
|
||||
exception.UnableToAutoAllocateNetwork,
|
||||
exception.MultiattachNotSupportedOldMicroversion) as error:
|
||||
exception.MultiattachNotSupportedOldMicroversion,
|
||||
exception.CertificateValidationFailed) as error:
|
||||
raise exc.HTTPBadRequest(explanation=error.format_message())
|
||||
except (exception.PortInUse,
|
||||
exception.InstanceExists,
|
||||
exception.NetworkAmbiguous,
|
||||
exception.NoUniqueMatch,
|
||||
exception.MultiattachSupportNotYetAvailable) as error:
|
||||
exception.MultiattachSupportNotYetAvailable,
|
||||
exception.CertificateValidationNotYetAvailable) as error:
|
||||
raise exc.HTTPConflict(explanation=error.format_message())
|
||||
|
||||
# If the caller wanted a reservation_id, return it
|
||||
@ -895,7 +914,8 @@ class ServersController(wsgi.Controller):
|
||||
@validation.schema(schema_server_rebuild, '2.1', '2.18')
|
||||
@validation.schema(schema_server_rebuild_v219, '2.19', '2.53')
|
||||
@validation.schema(schema_server_rebuild_v254, '2.54', '2.56')
|
||||
@validation.schema(schema_server_rebuild_v257, '2.57')
|
||||
@validation.schema(schema_server_rebuild_v257, '2.57', '2.62')
|
||||
@validation.schema(schema_server_rebuild_v263, '2.63')
|
||||
def _action_rebuild(self, req, id, body):
|
||||
"""Rebuild an instance with the given attributes."""
|
||||
rebuild_dict = body['rebuild']
|
||||
@ -906,9 +926,9 @@ class ServersController(wsgi.Controller):
|
||||
|
||||
context = req.environ['nova.context']
|
||||
instance = self._get_server(context, req, id)
|
||||
context.can(server_policies.SERVERS % 'rebuild',
|
||||
target = {'user_id': instance.user_id,
|
||||
'project_id': instance.project_id})
|
||||
'project_id': instance.project_id}
|
||||
context.can(server_policies.SERVERS % 'rebuild', target=target)
|
||||
attr_map = {
|
||||
'name': 'display_name',
|
||||
'description': 'display_description',
|
||||
@ -930,6 +950,19 @@ class ServersController(wsgi.Controller):
|
||||
if include_user_data and 'user_data' in rebuild_dict:
|
||||
kwargs['user_data'] = rebuild_dict['user_data']
|
||||
|
||||
# Skip policy check for 'rebuild:trusted_certs' if no trusted
|
||||
# certificate IDs were provided.
|
||||
if ((api_version_request.is_supported(req, min_version='2.63'))
|
||||
# Note that this is different from server create since with
|
||||
# rebuild a user can unset/reset the trusted certs by
|
||||
# specifying trusted_image_certificates=None, similar to
|
||||
# key_name.
|
||||
and ('trusted_image_certificates' in rebuild_dict)):
|
||||
kwargs['trusted_certs'] = rebuild_dict.get(
|
||||
'trusted_image_certificates')
|
||||
context.can(server_policies.SERVERS % 'rebuild:trusted_certs',
|
||||
target=target)
|
||||
|
||||
for request_attribute, instance_attribute in attr_map.items():
|
||||
try:
|
||||
if request_attribute == 'name':
|
||||
@ -947,7 +980,8 @@ class ServersController(wsgi.Controller):
|
||||
image_href,
|
||||
password,
|
||||
**kwargs)
|
||||
except exception.InstanceIsLocked as e:
|
||||
except (exception.InstanceIsLocked,
|
||||
exception.CertificateValidationNotYetAvailable) as e:
|
||||
raise exc.HTTPConflict(explanation=e.format_message())
|
||||
except exception.InstanceInvalidState as state_error:
|
||||
common.raise_http_conflict_for_instance_invalid_state(state_error,
|
||||
@ -970,7 +1004,8 @@ class ServersController(wsgi.Controller):
|
||||
exception.FlavorDiskTooSmall,
|
||||
exception.FlavorMemoryTooSmall,
|
||||
exception.InvalidMetadata,
|
||||
exception.AutoDiskConfigDisabledByImage) as error:
|
||||
exception.AutoDiskConfigDisabledByImage,
|
||||
exception.CertificateValidationFailed) as error:
|
||||
raise exc.HTTPBadRequest(explanation=error.format_message())
|
||||
|
||||
instance = self._get_server(context, req, id, is_detail=True)
|
||||
|
@ -171,6 +171,12 @@ class ViewBuilder(common.ViewBuilder):
|
||||
if api_version_request.is_supported(request, min_version="2.26"):
|
||||
server["server"]["tags"] = [t.tag for t in instance.tags]
|
||||
|
||||
if api_version_request.is_supported(request, min_version="2.63"):
|
||||
trusted_certs = None
|
||||
if instance.trusted_certs:
|
||||
trusted_certs = instance.trusted_certs.ids
|
||||
server["server"]["trusted_image_certificates"] = trusted_certs
|
||||
|
||||
return server
|
||||
|
||||
def index(self, request, instances):
|
||||
|
@ -476,3 +476,16 @@ pagination_parameters = {
|
||||
'limit': multi_params(non_negative_integer),
|
||||
'marker': multi_params({'type': 'string'})
|
||||
}
|
||||
|
||||
# The trusted_certs list is restricted to a maximum of 50 IDs.
|
||||
# "null" is allowed to unset/reset trusted certs during rebuild.
|
||||
trusted_certs = {
|
||||
"type": ["array", "null"],
|
||||
"minItems": 1,
|
||||
"maxItems": 50,
|
||||
"uniqueItems": True,
|
||||
"items": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
}
|
||||
}
|
||||
|
@ -104,6 +104,7 @@ AGGREGATE_ACTION_ADD = 'Add'
|
||||
BFV_RESERVE_MIN_COMPUTE_VERSION = 17
|
||||
CINDER_V3_ATTACH_MIN_COMPUTE_VERSION = 24
|
||||
MIN_COMPUTE_MULTIATTACH = 27
|
||||
MIN_COMPUTE_TRUSTED_CERTS = 31
|
||||
|
||||
# FIXME(danms): Keep a global cache of the cells we find the
|
||||
# first time we look. This needs to be refreshed on a timer or
|
||||
@ -853,7 +854,7 @@ class API(base.Base):
|
||||
max_count, base_options, boot_meta, security_groups,
|
||||
block_device_mapping, shutdown_terminate,
|
||||
instance_group, check_server_group_quota, filter_properties,
|
||||
key_pair, tags, supports_multiattach=False):
|
||||
key_pair, tags, trusted_certs, supports_multiattach=False):
|
||||
# Check quotas
|
||||
num_instances = compute_utils.check_num_instances_quota(
|
||||
context, instance_type, min_count, max_count)
|
||||
@ -887,6 +888,10 @@ class API(base.Base):
|
||||
instance.keypairs = objects.KeyPairList(objects=[])
|
||||
if key_pair:
|
||||
instance.keypairs.objects.append(key_pair)
|
||||
|
||||
instance.trusted_certs = self._retrieve_trusted_certs_object(
|
||||
context, trusted_certs)
|
||||
|
||||
instance = self.create_db_entry_for_new_instance(context,
|
||||
instance_type, boot_meta, instance, security_groups,
|
||||
block_device_mapping, num_instances, i,
|
||||
@ -961,6 +966,65 @@ class API(base.Base):
|
||||
|
||||
return instances_to_build
|
||||
|
||||
@staticmethod
|
||||
def _retrieve_trusted_certs_object(context, trusted_certs, rebuild=False):
|
||||
"""Convert user-requested trusted cert IDs to TrustedCerts object
|
||||
|
||||
Also validates that the deployment is new enough to support trusted
|
||||
image certification validation.
|
||||
|
||||
:param context: The user request auth context
|
||||
:param trusted_certs: list of user-specified trusted cert string IDs,
|
||||
may be None
|
||||
:param rebuild: True if rebuilding the server, False if creating a
|
||||
new server
|
||||
:returns: nova.objects.TrustedCerts object or None if no user-specified
|
||||
trusted cert IDs were given and nova is not configured with
|
||||
default trusted cert IDs
|
||||
:raises: nova.exception.CertificateValidationNotYetAvailable: If
|
||||
rebuilding a server with trusted certs on a compute host that is
|
||||
too old to supported trusted image cert validation, or if creating
|
||||
a server with trusted certs and there are no compute hosts in the
|
||||
deployment that are new enough to support trusted image cert
|
||||
validation
|
||||
"""
|
||||
# Retrieve trusted_certs parameter, or use CONF value if certificate
|
||||
# validation is enabled
|
||||
if trusted_certs:
|
||||
certs_to_return = objects.TrustedCerts(ids=trusted_certs)
|
||||
elif (CONF.glance.verify_glance_signatures and
|
||||
CONF.glance.enable_certificate_validation and
|
||||
CONF.glance.default_trusted_certificate_ids):
|
||||
certs_to_return = objects.TrustedCerts(
|
||||
ids=CONF.glance.default_trusted_certificate_ids)
|
||||
else:
|
||||
return None
|
||||
|
||||
# Confirm trusted_certs are supported by the minimum nova
|
||||
# compute service version
|
||||
# TODO(mriedem): This minimum version compat code can be dropped in the
|
||||
# 19.0.0 Stein release when all computes must be at a minimum running
|
||||
# Rocky code.
|
||||
if rebuild:
|
||||
# we only care about the current cell since this is
|
||||
# a rebuild
|
||||
min_compute_version = objects.Service.get_minimum_version(
|
||||
context, 'nova-compute')
|
||||
else:
|
||||
# we don't know which cell it's going to get scheduled
|
||||
# to, so check all cells
|
||||
# NOTE(mriedem): For multi-create server requests, we're hitting
|
||||
# this for each instance since it's not cached; we could likely
|
||||
# optimize this.
|
||||
min_compute_version = \
|
||||
objects.service.get_minimum_version_all_cells(
|
||||
context, ['nova-compute'])
|
||||
|
||||
if min_compute_version < MIN_COMPUTE_TRUSTED_CERTS:
|
||||
raise exception.CertificateValidationNotYetAvailable()
|
||||
|
||||
return certs_to_return
|
||||
|
||||
def _get_bdm_image_metadata(self, context, block_device_mapping,
|
||||
legacy_bdm=True):
|
||||
"""If we are booting from a volume, we need to get the
|
||||
@ -1031,7 +1095,7 @@ class API(base.Base):
|
||||
block_device_mapping, auto_disk_config, filter_properties,
|
||||
reservation_id=None, legacy_bdm=True, shutdown_terminate=False,
|
||||
check_server_group_quota=False, tags=None,
|
||||
supports_multiattach=False):
|
||||
supports_multiattach=False, trusted_certs=None):
|
||||
"""Verify all the input parameters regardless of the provisioning
|
||||
strategy being performed and schedule the instance(s) for
|
||||
creation.
|
||||
@ -1049,6 +1113,14 @@ class API(base.Base):
|
||||
if image_href:
|
||||
image_id, boot_meta = self._get_image(context, image_href)
|
||||
else:
|
||||
# This is similar to the logic in _retrieve_trusted_certs_object.
|
||||
if (trusted_certs or
|
||||
(CONF.glance.verify_glance_signatures and
|
||||
CONF.glance.enable_certificate_validation and
|
||||
CONF.glance.default_trusted_certificate_ids)):
|
||||
msg = _("Image certificate validation is not supported "
|
||||
"when booting from volume")
|
||||
raise exception.CertificateValidationFailed(message=msg)
|
||||
image_id = None
|
||||
boot_meta = self._get_bdm_image_metadata(
|
||||
context, block_device_mapping, legacy_bdm)
|
||||
@ -1096,7 +1168,8 @@ class API(base.Base):
|
||||
context, instance_type, min_count, max_count, base_options,
|
||||
boot_meta, security_groups, block_device_mapping,
|
||||
shutdown_terminate, instance_group, check_server_group_quota,
|
||||
filter_properties, key_pair, tags, supports_multiattach)
|
||||
filter_properties, key_pair, tags, trusted_certs,
|
||||
supports_multiattach)
|
||||
|
||||
instances = []
|
||||
request_specs = []
|
||||
@ -1577,7 +1650,7 @@ class API(base.Base):
|
||||
config_drive=None, auto_disk_config=None, scheduler_hints=None,
|
||||
legacy_bdm=True, shutdown_terminate=False,
|
||||
check_server_group_quota=False, tags=None,
|
||||
supports_multiattach=False):
|
||||
supports_multiattach=False, trusted_certs=None):
|
||||
"""Provision instances, sending instance information to the
|
||||
scheduler. The scheduler will determine where the instance(s)
|
||||
go and will handle creating the DB entries.
|
||||
@ -1617,7 +1690,8 @@ class API(base.Base):
|
||||
legacy_bdm=legacy_bdm,
|
||||
shutdown_terminate=shutdown_terminate,
|
||||
check_server_group_quota=check_server_group_quota,
|
||||
tags=tags, supports_multiattach=supports_multiattach)
|
||||
tags=tags, supports_multiattach=supports_multiattach,
|
||||
trusted_certs=trusted_certs)
|
||||
|
||||
def _check_auto_disk_config(self, instance=None, image=None,
|
||||
**extra_instance_updates):
|
||||
@ -2998,6 +3072,18 @@ class API(base.Base):
|
||||
instance.key_data = None
|
||||
instance.keypairs = objects.KeyPairList(objects=[])
|
||||
|
||||
# Use trusted_certs value from kwargs to create TrustedCerts object
|
||||
trusted_certs = None
|
||||
if 'trusted_certs' in kwargs:
|
||||
# Note that the user can set, change, or unset / reset trusted
|
||||
# certs. If they are explicitly specifying
|
||||
# trusted_image_certificates=None, that means we'll either unset
|
||||
# them on the instance *or* reset to use the defaults (if defaults
|
||||
# are configured).
|
||||
trusted_certs = kwargs.pop('trusted_certs')
|
||||
instance.trusted_certs = self._retrieve_trusted_certs_object(
|
||||
context, trusted_certs, rebuild=True)
|
||||
|
||||
image_id, image = self._get_image(context, image_href)
|
||||
self._check_auto_disk_config(image=image, **kwargs)
|
||||
|
||||
@ -3012,6 +3098,14 @@ class API(base.Base):
|
||||
is_volume_backed = compute_utils.is_volume_backed_instance(
|
||||
context, instance, bdms)
|
||||
if is_volume_backed:
|
||||
if trusted_certs:
|
||||
# The only way we can get here is if the user tried to set
|
||||
# trusted certs or specified trusted_image_certificates=None
|
||||
# and default_trusted_certificate_ids is configured.
|
||||
msg = _("Image certificate validation is not supported "
|
||||
"for volume-backed servers.")
|
||||
raise exception.CertificateValidationFailed(message=msg)
|
||||
|
||||
# For boot from volume, instance.image_ref is empty, so we need to
|
||||
# query the image from the volume.
|
||||
if root_bdm is None:
|
||||
|
@ -2297,3 +2297,9 @@ class AllocationCreateFailed(NovaException):
|
||||
class CertificateValidationFailed(NovaException):
|
||||
msg_fmt = _("Image signature certificate validation failed for "
|
||||
"certificate: %(cert_uuid)s. %(reason)s")
|
||||
|
||||
|
||||
class CertificateValidationNotYetAvailable(NovaException):
|
||||
msg_fmt = _("Image signature certificate validation support is "
|
||||
"not yet available.")
|
||||
code = 409
|
||||
|
@ -127,6 +127,16 @@ rules = [
|
||||
'path': '/servers'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
SERVERS % 'create:trusted_certs',
|
||||
RULE_AOO,
|
||||
"Create a server with trusted image certificate IDs",
|
||||
[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/servers'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
NETWORK_ATTACH_EXTERNAL,
|
||||
'is_admin:True',
|
||||
@ -213,6 +223,16 @@ rules = [
|
||||
'path': '/servers/{server_id}/action (rebuild)'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
SERVERS % 'rebuild:trusted_certs',
|
||||
RULE_AOO,
|
||||
"Rebuild a server with trusted image certificate IDs",
|
||||
[
|
||||
{
|
||||
'method': 'POST',
|
||||
'path': '/servers/{server_id}/action (rebuild)'
|
||||
}
|
||||
]),
|
||||
policy.DocumentedRuleDefault(
|
||||
SERVERS % 'create_image',
|
||||
RULE_AOO,
|
||||
|
@ -0,0 +1,69 @@
|
||||
{
|
||||
"server": {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "%(ip)s",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"adminPass": "%(password)s",
|
||||
"created": "%(isotime)s",
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:mem_page_size": "2048",
|
||||
"hw:cpu_policy": "dedicated"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "%(hostid)s",
|
||||
"id": "%(uuid)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"meta_var": "meta_val"
|
||||
},
|
||||
"name": "%(name)s",
|
||||
"key_name": "%(key_name)s",
|
||||
"description": "%(description)s",
|
||||
"progress": 0,
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
{
|
||||
"rebuild" : {
|
||||
"accessIPv4" : "%(access_ip_v4)s",
|
||||
"accessIPv6" : "%(access_ip_v6)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"imageRef" : "%(uuid)s",
|
||||
"name" : "%(name)s",
|
||||
"key_name" : "%(key_name)s",
|
||||
"description" : "%(description)s",
|
||||
"adminPass" : "%(pass)s",
|
||||
"metadata" : {
|
||||
"meta_var" : "meta_val"
|
||||
},
|
||||
"user_data": "ZWNobyAiaGVsbG8gd29ybGQi",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
{
|
||||
"server" : {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"name" : "%(name)s",
|
||||
"imageRef" : "%(image_id)s",
|
||||
"flavorRef" : "6",
|
||||
"availability_zone": "nova",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"metadata" : {
|
||||
"My Server Name" : "Apache1"
|
||||
},
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"user_data" : "%(user_data)s",
|
||||
"networks": "auto",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
]
|
||||
},
|
||||
"OS-SCH-HNT:scheduler_hints": {
|
||||
"same_host": "%(uuid)s"
|
||||
}
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"adminPass": "%(password)s",
|
||||
"id": "%(id)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,93 @@
|
||||
{
|
||||
"server": {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "%(ip)s",
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"host_status": "UP",
|
||||
"locked": false,
|
||||
"tags": [],
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:cpu_policy": "dedicated",
|
||||
"hw:mem_page_size": "2048"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "%(hostid)s",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"config_drive": "%(cdrive)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
|
||||
"OS-EXT-SRV-ATTR:hostname": "%(hostname)s",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "%(user_data)s",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"OS-SRV-USG:launched_at": "%(strtime)s",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
]
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
{
|
||||
"server": {
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"name" : "new-server-test"
|
||||
}
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
{
|
||||
"server": {
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "192.168.0.3",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:mem_page_size": "2048",
|
||||
"hw:cpu_policy": "dedicated"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "%(hostid)s",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"locked": false,
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"progress": 0,
|
||||
"status": "ACTIVE",
|
||||
"tags": [],
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
{
|
||||
"servers": [
|
||||
{
|
||||
"accessIPv4": "%(access_ip_v4)s",
|
||||
"accessIPv6": "%(access_ip_v6)s",
|
||||
"addresses": {
|
||||
"private": [
|
||||
{
|
||||
"addr": "%(ip)s",
|
||||
"OS-EXT-IPS-MAC:mac_addr": "aa:bb:cc:dd:ee:ff",
|
||||
"OS-EXT-IPS:type": "fixed",
|
||||
"version": 4
|
||||
}
|
||||
]
|
||||
},
|
||||
"created": "%(isotime)s",
|
||||
"description": null,
|
||||
"host_status": "UP",
|
||||
"locked": false,
|
||||
"tags": [],
|
||||
"flavor": {
|
||||
"disk": 1,
|
||||
"ephemeral": 0,
|
||||
"extra_specs": {
|
||||
"hw:cpu_model": "SandyBridge",
|
||||
"hw:mem_page_size": "2048",
|
||||
"hw:cpu_policy": "dedicated"
|
||||
},
|
||||
"original_name": "m1.tiny.specs",
|
||||
"ram": 512,
|
||||
"swap": 0,
|
||||
"vcpus": 1
|
||||
},
|
||||
"hostId": "%(hostid)s",
|
||||
"id": "%(id)s",
|
||||
"image": {
|
||||
"id": "%(uuid)s",
|
||||
"links": [
|
||||
{
|
||||
"href": "%(compute_endpoint)s/images/%(uuid)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
]
|
||||
},
|
||||
"key_name": null,
|
||||
"links": [
|
||||
{
|
||||
"href": "%(versioned_compute_endpoint)s/servers/%(uuid)s",
|
||||
"rel": "self"
|
||||
},
|
||||
{
|
||||
"href": "%(compute_endpoint)s/servers/%(id)s",
|
||||
"rel": "bookmark"
|
||||
}
|
||||
],
|
||||
"metadata": {
|
||||
"My Server Name": "Apache1"
|
||||
},
|
||||
"name": "new-server-test",
|
||||
"config_drive": "%(cdrive)s",
|
||||
"OS-DCF:diskConfig": "AUTO",
|
||||
"OS-EXT-AZ:availability_zone": "nova",
|
||||
"OS-EXT-SRV-ATTR:host": "%(compute_host)s",
|
||||
"OS-EXT-SRV-ATTR:hostname": "%(hostname)s",
|
||||
"OS-EXT-SRV-ATTR:hypervisor_hostname": "%(hypervisor_hostname)s",
|
||||
"OS-EXT-SRV-ATTR:instance_name": "%(instance_name)s",
|
||||
"OS-EXT-SRV-ATTR:kernel_id": "",
|
||||
"OS-EXT-SRV-ATTR:launch_index": 0,
|
||||
"OS-EXT-SRV-ATTR:ramdisk_id": "",
|
||||
"OS-EXT-SRV-ATTR:reservation_id": "%(reservation_id)s",
|
||||
"OS-EXT-SRV-ATTR:root_device_name": "/dev/sda",
|
||||
"OS-EXT-SRV-ATTR:user_data": "%(user_data)s",
|
||||
"OS-EXT-STS:power_state": 1,
|
||||
"OS-EXT-STS:task_state": null,
|
||||
"OS-EXT-STS:vm_state": "active",
|
||||
"os-extended-volumes:volumes_attached": [],
|
||||
"OS-SRV-USG:launched_at": "%(strtime)s",
|
||||
"OS-SRV-USG:terminated_at": null,
|
||||
"progress": 0,
|
||||
"security_groups": [
|
||||
{
|
||||
"name": "default"
|
||||
}
|
||||
],
|
||||
"status": "ACTIVE",
|
||||
"tenant_id": "6f70656e737461636b20342065766572",
|
||||
"trusted_image_certificates": [
|
||||
"0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8",
|
||||
"674736e3-f25c-405c-8362-bbf991e0ce0a"
|
||||
],
|
||||
"updated": "%(isotime)s",
|
||||
"user_id": "fake"
|
||||
}
|
||||
]
|
||||
}
|
@ -19,10 +19,13 @@ import time
|
||||
import six
|
||||
|
||||
from nova.api.openstack import api_version_request as avr
|
||||
import nova.conf
|
||||
from nova.tests.functional.api_sample_tests import api_sample_base
|
||||
from nova.tests.unit.api.openstack import fakes
|
||||
from nova.tests.unit.image import fake
|
||||
|
||||
CONF = nova.conf.CONF
|
||||
|
||||
|
||||
class ServersSampleBase(api_sample_base.ApiSampleTestBaseV21):
|
||||
microversion = None
|
||||
@ -237,6 +240,75 @@ class ServersSampleJson252Test(ServersSampleJsonTest):
|
||||
use_common_server_post = False
|
||||
|
||||
|
||||
class ServersSampleJson263Test(ServersSampleBase):
|
||||
microversion = '2.63'
|
||||
scenarios = [('v2_63', {'api_major_version': 'v2.1'})]
|
||||
|
||||
def setUp(self):
|
||||
super(ServersSampleJson263Test, self).setUp()
|
||||
self.common_subs = {
|
||||
'hostid': '[a-f0-9]+',
|
||||
'instance_name': 'instance-\d{8}',
|
||||
'hypervisor_hostname': r'[\w\.\-]+',
|
||||
'hostname': r'[\w\.\-]+',
|
||||
'access_ip_v4': '1.2.3.4',
|
||||
'access_ip_v6': '80fe::',
|
||||
'user_data': (self.user_data if six.PY2
|
||||
else self.user_data.decode('utf-8')),
|
||||
'cdrive': '.*',
|
||||
}
|
||||
|
||||
def test_servers_post(self):
|
||||
self._post_server(use_common_server_api_samples=False)
|
||||
|
||||
def test_server_rebuild(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
fakes.stub_out_key_pair_funcs(self)
|
||||
image = fake.get_valid_image_id()
|
||||
|
||||
params = {
|
||||
'uuid': image,
|
||||
'name': 'foobar',
|
||||
'key_name': 'new-key',
|
||||
'description': 'description of foobar',
|
||||
'pass': 'seekr3t',
|
||||
'access_ip_v4': '1.2.3.4',
|
||||
'access_ip_v6': '80fe::',
|
||||
}
|
||||
|
||||
resp = self._do_post('servers/%s/action' % uuid,
|
||||
'server-action-rebuild', params)
|
||||
|
||||
exp_resp = params.copy()
|
||||
del exp_resp['uuid']
|
||||
exp_resp['hostid'] = '[a-f0-9]+'
|
||||
|
||||
self._verify_response('server-action-rebuild-resp',
|
||||
exp_resp, resp, 202)
|
||||
|
||||
def test_servers_details(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
response = self._do_get('servers/detail')
|
||||
subs = self.common_subs.copy()
|
||||
subs['id'] = uuid
|
||||
self._verify_response('servers-details-resp', subs, response, 200)
|
||||
|
||||
def test_server_get(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
response = self._do_get('servers/%s' % uuid)
|
||||
subs = self.common_subs.copy()
|
||||
subs['id'] = uuid
|
||||
self._verify_response('server-get-resp', subs, response, 200)
|
||||
|
||||
def test_server_update(self):
|
||||
uuid = self._post_server(use_common_server_api_samples=False)
|
||||
subs = self.common_subs.copy()
|
||||
subs['id'] = uuid
|
||||
response = self._do_put('servers/%s' % uuid,
|
||||
'server-update-req', subs)
|
||||
self._verify_response('server-update-resp', subs, response, 200)
|
||||
|
||||
|
||||
class ServersUpdateSampleJsonTest(ServersSampleBase):
|
||||
|
||||
def test_update_server(self):
|
||||
|
@ -2406,6 +2406,200 @@ class ServersControllerRebuildTestV219(ServersControllerRebuildInstanceTest):
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
|
||||
|
||||
# NOTE(jaypipes): Not based from ServersControllerRebuildInstanceTest because
|
||||
# that test case's setUp is completely b0rked
|
||||
class ServersControllerRebuildTestV263(ControllerTest):
|
||||
|
||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
||||
|
||||
def setUp(self):
|
||||
super(ServersControllerRebuildTestV263, self).setUp()
|
||||
self.req = fakes.HTTPRequest.blank('/fake/servers/a/action')
|
||||
self.req.method = 'POST'
|
||||
self.req.headers["content-type"] = "application/json"
|
||||
self.req_user_id = self.req.environ['nova.context'].user_id
|
||||
self.req_project_id = self.req.environ['nova.context'].project_id
|
||||
self.req.api_version_request = \
|
||||
api_version_request.APIVersionRequest('2.63')
|
||||
self.body = {
|
||||
'rebuild': {
|
||||
'name': 'new_name',
|
||||
'imageRef': self.image_uuid,
|
||||
'metadata': {
|
||||
'open': 'stack',
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@mock.patch('nova.compute.api.API.get')
|
||||
def _rebuild_server(self, mock_get, certs=None,
|
||||
conf_enabled=True, conf_certs=None):
|
||||
fakes.stub_out_trusted_certs(self, certs=certs)
|
||||
ctx = self.req.environ['nova.context']
|
||||
mock_get.return_value = fakes.stub_instance_obj(ctx,
|
||||
vm_state=vm_states.ACTIVE, trusted_certs=certs,
|
||||
project_id=self.req_project_id, user_id=self.req_user_id)
|
||||
|
||||
self.flags(default_trusted_certificate_ids=conf_certs, group='glance')
|
||||
|
||||
if conf_enabled:
|
||||
self.flags(verify_glance_signatures=True, group='glance')
|
||||
self.flags(enable_certificate_validation=True, group='glance')
|
||||
|
||||
self.body['rebuild']['trusted_image_certificates'] = certs
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
server = self.controller._action_rebuild(
|
||||
self.req, FAKE_UUID, body=self.body).obj['server']
|
||||
|
||||
if certs:
|
||||
self.assertEqual(certs, server['trusted_image_certificates'])
|
||||
else:
|
||||
if conf_enabled:
|
||||
# configuration file default is used
|
||||
self.assertEqual(
|
||||
conf_certs, server['trusted_image_certificates'])
|
||||
else:
|
||||
# either not set or empty
|
||||
self.assertIsNone(server['trusted_image_certificates'])
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_rebuild_server_with_trusted_certs(self, get_min_ver):
|
||||
"""Test rebuild with valid trusted_image_certificates argument"""
|
||||
self._rebuild_server(
|
||||
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8',
|
||||
'674736e3-f25c-405c-8362-bbf991e0ce0a'])
|
||||
|
||||
def test_rebuild_server_without_trusted_certs(self):
|
||||
"""Test rebuild without trusted image certificates"""
|
||||
self._rebuild_server()
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_rebuild_server_conf_options_turned_off_set(self, get_min_ver):
|
||||
"""Test rebuild with feature disabled and certs specified"""
|
||||
self._rebuild_server(
|
||||
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'], conf_enabled=False)
|
||||
|
||||
def test_rebuild_server_conf_options_turned_off_empty(self):
|
||||
"""Test rebuild with feature disabled"""
|
||||
self._rebuild_server(conf_enabled=False)
|
||||
|
||||
def test_rebuild_server_default_trusted_certificates_empty(self):
|
||||
"""Test rebuild with feature enabled and no certs specified"""
|
||||
self._rebuild_server(conf_enabled=True)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_rebuild_server_default_trusted_certificates(self, get_min_ver):
|
||||
"""Test rebuild with certificate specified in configurations"""
|
||||
self._rebuild_server(conf_enabled=True, conf_certs=['conf-id'])
|
||||
|
||||
def test_rebuild_server_with_empty_trusted_cert_id(self):
|
||||
"""Make sure that we can't rebuild with an empty certificate ID"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = ['']
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('is too short', six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_empty_trusted_certs(self):
|
||||
"""Make sure that we can't rebuild with an empty array of IDs"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = []
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('is too short', six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_too_many_trusted_certs(self):
|
||||
"""Make sure that we can't rebuild with an array of >50 unique IDs"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = [
|
||||
'cert{}'.format(i) for i in range(51)]
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('is too long', six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_nonunique_trusted_certs(self):
|
||||
"""Make sure that we can't rebuild with a non-unique array of IDs"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = ['cert', 'cert']
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('has non-unique elements', six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_invalid_trusted_cert_id(self):
|
||||
"""Make sure that we can't rebuild with non-string certificate IDs"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = [1, 2]
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('is not of type', six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_invalid_trusted_certs(self):
|
||||
"""Make sure that we can't rebuild with certificates in a non-array"""
|
||||
self.body['rebuild']['trusted_image_certificates'] = "not-an-array"
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('is not of type', six.text_type(ex))
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_rebuild_server_with_trusted_certs_pre_2_63_fails(self,
|
||||
get_min_ver):
|
||||
"""Make sure we can't use trusted_certs before 2.63"""
|
||||
self._rebuild_server(certs=['trusted-cert-id'])
|
||||
self.req.api_version_request = \
|
||||
api_version_request.APIVersionRequest('2.62')
|
||||
ex = self.assertRaises(exception.ValidationError,
|
||||
self.controller._action_rebuild,
|
||||
self.req, FAKE_UUID, body=self.body)
|
||||
self.assertIn('Additional properties are not allowed',
|
||||
six.text_type(ex))
|
||||
|
||||
def test_rebuild_server_with_trusted_certs_policy_failed(self):
|
||||
rule_name = "os_compute_api:servers:rebuild:trusted_certs"
|
||||
rules = {"os_compute_api:servers:rebuild": "@",
|
||||
rule_name: "project:fake"}
|
||||
self.policy.set_rules(rules)
|
||||
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self._rebuild_server,
|
||||
certs=['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'])
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
@mock.patch.object(compute_api.API, 'rebuild')
|
||||
def test_rebuild_server_with_cert_validation_error(
|
||||
self, mock_rebuild):
|
||||
mock_rebuild.side_effect = exception.CertificateValidationFailed(
|
||||
cert_uuid="cert id", reason="test cert validation error")
|
||||
|
||||
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self._rebuild_server,
|
||||
certs=['trusted-cert-id'])
|
||||
self.assertIn('test cert validation error',
|
||||
six.text_type(ex))
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
||||
def test_rebuild_server_with_cert_validation_not_available(
|
||||
self, get_min_ver):
|
||||
ex = self.assertRaises(webob.exc.HTTPConflict,
|
||||
self._rebuild_server,
|
||||
certs=['trusted-cert-id'])
|
||||
self.assertIn('Image signature certificate validation support '
|
||||
'is not yet available',
|
||||
six.text_type(ex))
|
||||
|
||||
|
||||
class ServersControllerUpdateTest(ControllerTest):
|
||||
|
||||
def _get_request(self, body=None):
|
||||
@ -4215,6 +4409,137 @@ class ServersControllerCreateTestV260(test.NoDBTestCase):
|
||||
six.text_type(ex))
|
||||
|
||||
|
||||
class ServersControllerCreateTestV263(ServersControllerCreateTest):
|
||||
def _create_instance_req(self, certs=None):
|
||||
self.body['server']['trusted_image_certificates'] = certs
|
||||
|
||||
self.flags(verify_glance_signatures=True, group='glance')
|
||||
self.flags(enable_certificate_validation=True, group='glance')
|
||||
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
self.req.api_version_request = \
|
||||
api_version_request.APIVersionRequest('2.63')
|
||||
|
||||
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_create_instance_with_trusted_certs(self, get_min_ver):
|
||||
"""Test create with valid trusted_image_certificates argument"""
|
||||
self._create_instance_req(
|
||||
['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8',
|
||||
'674736e3-f25c-405c-8362-bbf991e0ce0a'])
|
||||
# The fact that the action doesn't raise is enough validation
|
||||
self.controller.create(self.req, body=self.body).obj
|
||||
|
||||
def test_create_instance_without_trusted_certs(self):
|
||||
"""Test create without trusted image certificates"""
|
||||
self._create_instance_req()
|
||||
# The fact that the action doesn't raise is enough validation
|
||||
self.controller.create(self.req, body=self.body).obj
|
||||
|
||||
def test_create_instance_with_empty_trusted_cert_id(self):
|
||||
"""Make sure we can't create with an empty certificate ID"""
|
||||
self._create_instance_req([''])
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('is too short', six.text_type(ex))
|
||||
|
||||
def test_create_instance_with_empty_trusted_certs(self):
|
||||
"""Make sure we can't create with an empty array of IDs"""
|
||||
self.body['server']['trusted_image_certificates'] = []
|
||||
self.req.body = jsonutils.dump_as_bytes(self.body)
|
||||
self.req.api_version_request = \
|
||||
api_version_request.APIVersionRequest('2.63')
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('is too short', six.text_type(ex))
|
||||
|
||||
def test_create_instance_with_too_many_trusted_certs(self):
|
||||
"""Make sure we can't create with an array of >50 unique IDs"""
|
||||
self._create_instance_req(['cert{}'.format(i) for i in range(51)])
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('is too long', six.text_type(ex))
|
||||
|
||||
def test_create_instance_with_nonunique_trusted_certs(self):
|
||||
"""Make sure we can't create with a non-unique array of IDs"""
|
||||
self._create_instance_req(['cert', 'cert'])
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('has non-unique elements', six.text_type(ex))
|
||||
|
||||
def test_create_instance_with_invalid_trusted_cert_id(self):
|
||||
"""Make sure we can't create with non-string certificate IDs"""
|
||||
self._create_instance_req([1, 2])
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('is not of type', six.text_type(ex))
|
||||
|
||||
def test_create_instance_with_invalid_trusted_certs(self):
|
||||
"""Make sure we can't create with certificates in a non-array"""
|
||||
self._create_instance_req("not-an-array")
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('is not of type', six.text_type(ex))
|
||||
|
||||
def test_create_server_with_trusted_certs_pre_2_63_fails(self):
|
||||
"""Make sure we can't use trusted_certs before 2.63"""
|
||||
self._create_instance_req(['trusted-cert-id'])
|
||||
self.req.api_version_request = \
|
||||
api_version_request.APIVersionRequest('2.62')
|
||||
ex = self.assertRaises(
|
||||
exception.ValidationError, self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('Additional properties are not allowed',
|
||||
six.text_type(ex))
|
||||
|
||||
def test_create_server_with_trusted_certs_policy_failed(self):
|
||||
rule_name = "os_compute_api:servers:create:trusted_certs"
|
||||
rules = {"os_compute_api:servers:create": "@",
|
||||
"os_compute_api:servers:create:forced_host": "@",
|
||||
"os_compute_api:servers:create:attach_volume": "@",
|
||||
"os_compute_api:servers:create:attach_network": "@",
|
||||
rule_name: "project:fake"}
|
||||
self._create_instance_req(['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8'])
|
||||
self.policy.set_rules(rules)
|
||||
exc = self.assertRaises(exception.PolicyNotAuthorized,
|
||||
self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertEqual(
|
||||
"Policy doesn't allow %s to be performed." % rule_name,
|
||||
exc.format_message())
|
||||
|
||||
@mock.patch.object(compute_api.API, 'create')
|
||||
def test_create_server_with_cert_validation_error(
|
||||
self, mock_create):
|
||||
mock_create.side_effect = exception.CertificateValidationFailed(
|
||||
cert_uuid="cert id", reason="test cert validation error")
|
||||
|
||||
self._create_instance_req(['trusted-cert-id'])
|
||||
ex = self.assertRaises(webob.exc.HTTPBadRequest,
|
||||
self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('test cert validation error',
|
||||
six.text_type(ex))
|
||||
|
||||
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
||||
def test_create_server_with_cert_validation_not_available(
|
||||
self, mock_get_min_version_all_cells):
|
||||
self._create_instance_req(['trusted-cert-id'])
|
||||
ex = self.assertRaises(webob.exc.HTTPConflict,
|
||||
self.controller.create, self.req,
|
||||
body=self.body)
|
||||
self.assertIn('Image signature certificate validation support '
|
||||
'is not yet available',
|
||||
six.text_type(ex))
|
||||
|
||||
|
||||
class ServersControllerCreateTestWithMock(test.TestCase):
|
||||
image_uuid = '76fa36fc-c930-4bf3-8c8a-ea2a2420deb6'
|
||||
flavor_ref = 'http://localhost/123/flavors/3'
|
||||
|
@ -105,6 +105,30 @@ def stub_out_key_pair_funcs(testcase, have_key_pair=True, **kwargs):
|
||||
testcase.stub_out('nova.db.key_pair_get_all_by_user', no_key_pair)
|
||||
|
||||
|
||||
def stub_out_trusted_certs(test, certs=None):
|
||||
def fake_trusted_certs(cls, context, instance_uuid):
|
||||
return objects.TrustedCerts(ids=trusted_certs)
|
||||
|
||||
def fake_instance_extra(context, instance_uuid, columns):
|
||||
if columns is ['trusted_certs']:
|
||||
return {'trusted_certs': trusted_certs}
|
||||
else:
|
||||
return {'numa_topology': None,
|
||||
'pci_requests': None,
|
||||
'flavor': None,
|
||||
'vcpu_model': None,
|
||||
'trusted_certs': trusted_certs,
|
||||
'migration_context': None}
|
||||
|
||||
trusted_certs = []
|
||||
if certs:
|
||||
trusted_certs = certs
|
||||
test.stub_out('nova.objects.TrustedCerts.get_by_instance_uuid',
|
||||
fake_trusted_certs)
|
||||
test.stub_out('nova.db.instance_extra_get_by_instance_uuid',
|
||||
fake_instance_extra)
|
||||
|
||||
|
||||
def stub_out_instance_quota(test, allowed, quota, resource='instances'):
|
||||
def fake_reserve(context, **deltas):
|
||||
requested = deltas.pop(resource, 0)
|
||||
@ -424,7 +448,7 @@ def stub_instance(id=1, user_id=None, project_id=None, host=None,
|
||||
memory_mb=0, vcpus=0, root_gb=0, ephemeral_gb=0,
|
||||
instance_type=None, launch_index=0, kernel_id="",
|
||||
ramdisk_id="", user_data=None, system_metadata=None,
|
||||
services=None):
|
||||
services=None, trusted_certs=None):
|
||||
if user_id is None:
|
||||
user_id = 'fake_user'
|
||||
if project_id is None:
|
||||
@ -531,10 +555,12 @@ def stub_instance(id=1, user_id=None, project_id=None, host=None,
|
||||
"extra": {"numa_topology": None,
|
||||
"pci_requests": None,
|
||||
"flavor": flavorinfo,
|
||||
"trusted_certs": trusted_certs,
|
||||
},
|
||||
"cleaned": cleaned,
|
||||
"services": services,
|
||||
"tags": []}
|
||||
"tags": [],
|
||||
}
|
||||
|
||||
instance.update(info_cache)
|
||||
instance['info_cache']['instance_uuid'] = instance['uuid']
|
||||
|
@ -262,6 +262,29 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
self.assertEqual(2, mock_get_image.call_count)
|
||||
self.assertEqual(2, mock_limit_check_pu.call_count)
|
||||
|
||||
@mock.patch('nova.objects.Quotas.limit_check')
|
||||
def test_create_volume_backed_instance_with_trusted_certs(self,
|
||||
check_limit):
|
||||
# Creating an instance with no image_ref specified will result in
|
||||
# creating a volume-backed instance
|
||||
self.assertRaises(exception.CertificateValidationFailed,
|
||||
self.compute_api.create, self.context,
|
||||
instance_type=self._create_flavor(), image_href=None,
|
||||
trusted_certs=['test-cert-1', 'test-cert-2'])
|
||||
|
||||
@mock.patch('nova.objects.Quotas.limit_check')
|
||||
def test_create_volume_backed_instance_with_conf_trusted_certs(
|
||||
self, check_limit):
|
||||
self.flags(verify_glance_signatures=True, group='glance')
|
||||
self.flags(enable_certificate_validation=True, group='glance')
|
||||
self.flags(default_trusted_certificate_ids=['certs'], group='glance')
|
||||
# Creating an instance with no image_ref specified will result in
|
||||
# creating a volume-backed instance
|
||||
self.assertRaises(exception.CertificateValidationFailed,
|
||||
self.compute_api.create, self.context,
|
||||
instance_type=self._create_flavor(),
|
||||
image_href=None)
|
||||
|
||||
def _test_create_max_net_count(self, max_net_count, min_count, max_count):
|
||||
with test.nested(
|
||||
mock.patch.object(self.compute_api, '_get_image',
|
||||
@ -3790,6 +3813,171 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
self.assertNotEqual(orig_key_name, instance.key_name)
|
||||
self.assertNotEqual(orig_key_data, instance.key_data)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
||||
@mock.patch.object(objects.Instance, 'save')
|
||||
@mock.patch.object(objects.Instance, 'get_flavor')
|
||||
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
||||
@mock.patch.object(compute_api.API, '_get_image')
|
||||
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
||||
@mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild')
|
||||
@mock.patch.object(compute_api.API, '_record_action_start')
|
||||
def test_rebuild_change_trusted_certs(self, _record_action_start,
|
||||
_checks_for_create_and_rebuild, _check_auto_disk_config,
|
||||
_get_image, bdm_get_by_instance_uuid, get_flavor, instance_save,
|
||||
req_spec_get_by_inst_uuid, get_min_version):
|
||||
orig_system_metadata = {}
|
||||
orig_trusted_certs = ['orig-trusted-cert-1', 'orig-trusted-cert-2']
|
||||
new_trusted_certs = ['new-trusted-cert-1', 'new-trusted-cert-2']
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context, vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
||||
launched_at=timeutils.utcnow(),
|
||||
system_metadata=orig_system_metadata, image_ref='foo',
|
||||
expected_attrs=['system_metadata'],
|
||||
trusted_certs=orig_trusted_certs)
|
||||
get_flavor.return_value = test_flavor.fake_flavor
|
||||
flavor = instance.get_flavor()
|
||||
image_href = 'foo'
|
||||
image = {
|
||||
"min_ram": 10, "min_disk": 1,
|
||||
"properties": {'architecture': fields_obj.Architecture.X86_64,
|
||||
'vm_mode': 'hvm'}}
|
||||
admin_pass = ''
|
||||
files_to_inject = []
|
||||
bdms = objects.BlockDeviceMappingList()
|
||||
|
||||
_get_image.return_value = (None, image)
|
||||
bdm_get_by_instance_uuid.return_value = bdms
|
||||
|
||||
fake_spec = objects.RequestSpec()
|
||||
req_spec_get_by_inst_uuid.return_value = fake_spec
|
||||
|
||||
with mock.patch.object(self.compute_api.compute_task_api,
|
||||
'rebuild_instance') as rebuild_instance:
|
||||
self.compute_api.rebuild(self.context, instance, image_href,
|
||||
admin_pass, files_to_inject,
|
||||
trusted_certs=new_trusted_certs)
|
||||
|
||||
rebuild_instance.assert_called_once_with(
|
||||
self.context, instance=instance, new_pass=admin_pass,
|
||||
injected_files=files_to_inject, image_ref=image_href,
|
||||
orig_image_ref=image_href,
|
||||
orig_sys_metadata=orig_system_metadata, bdms=bdms,
|
||||
preserve_ephemeral=False, host=instance.host,
|
||||
request_spec=fake_spec, kwargs={})
|
||||
|
||||
_check_auto_disk_config.assert_called_once_with(image=image)
|
||||
_checks_for_create_and_rebuild.assert_called_once_with(
|
||||
self.context, None, image, flavor, {}, [], None)
|
||||
self.assertEqual(new_trusted_certs, instance.trusted_certs.ids)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
@mock.patch.object(objects.RequestSpec, 'get_by_instance_uuid')
|
||||
@mock.patch.object(objects.Instance, 'save')
|
||||
@mock.patch.object(objects.Instance, 'get_flavor')
|
||||
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
||||
@mock.patch.object(compute_api.API, '_get_image')
|
||||
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
||||
@mock.patch.object(compute_api.API, '_checks_for_create_and_rebuild')
|
||||
@mock.patch.object(compute_api.API, '_record_action_start')
|
||||
def test_rebuild_unset_trusted_certs(self, _record_action_start,
|
||||
_checks_for_create_and_rebuild,
|
||||
_check_auto_disk_config,
|
||||
_get_image, bdm_get_by_instance_uuid,
|
||||
get_flavor, instance_save,
|
||||
req_spec_get_by_inst_uuid,
|
||||
get_min_version):
|
||||
"""Tests the scenario that the server was created with some trusted
|
||||
certs and then rebuilt without trusted_image_certificates=None
|
||||
explicitly to unset the trusted certs on the server.
|
||||
"""
|
||||
orig_system_metadata = {}
|
||||
orig_trusted_certs = ['orig-trusted-cert-1', 'orig-trusted-cert-2']
|
||||
new_trusted_certs = None
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context, vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
||||
launched_at=timeutils.utcnow(),
|
||||
system_metadata=orig_system_metadata, image_ref='foo',
|
||||
expected_attrs=['system_metadata'],
|
||||
trusted_certs=orig_trusted_certs)
|
||||
get_flavor.return_value = test_flavor.fake_flavor
|
||||
flavor = instance.get_flavor()
|
||||
image_href = 'foo'
|
||||
image = {
|
||||
"min_ram": 10, "min_disk": 1,
|
||||
"properties": {'architecture': fields_obj.Architecture.X86_64,
|
||||
'vm_mode': 'hvm'}}
|
||||
admin_pass = ''
|
||||
files_to_inject = []
|
||||
bdms = objects.BlockDeviceMappingList()
|
||||
|
||||
_get_image.return_value = (None, image)
|
||||
bdm_get_by_instance_uuid.return_value = bdms
|
||||
|
||||
fake_spec = objects.RequestSpec()
|
||||
req_spec_get_by_inst_uuid.return_value = fake_spec
|
||||
|
||||
with mock.patch.object(self.compute_api.compute_task_api,
|
||||
'rebuild_instance') as rebuild_instance:
|
||||
self.compute_api.rebuild(self.context, instance, image_href,
|
||||
admin_pass, files_to_inject,
|
||||
trusted_certs=new_trusted_certs)
|
||||
|
||||
rebuild_instance.assert_called_once_with(
|
||||
self.context, instance=instance, new_pass=admin_pass,
|
||||
injected_files=files_to_inject, image_ref=image_href,
|
||||
orig_image_ref=image_href,
|
||||
orig_sys_metadata=orig_system_metadata, bdms=bdms,
|
||||
preserve_ephemeral=False, host=instance.host,
|
||||
request_spec=fake_spec, kwargs={})
|
||||
|
||||
_check_auto_disk_config.assert_called_once_with(image=image)
|
||||
_checks_for_create_and_rebuild.assert_called_once_with(
|
||||
self.context, None, image, flavor, {}, [], None)
|
||||
self.assertIsNone(instance.trusted_certs)
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
@mock.patch.object(compute_utils, 'is_volume_backed_instance',
|
||||
return_value=True)
|
||||
@mock.patch.object(objects.Instance, 'get_flavor')
|
||||
@mock.patch.object(objects.BlockDeviceMappingList, 'get_by_instance_uuid')
|
||||
@mock.patch.object(compute_api.API, '_get_image')
|
||||
@mock.patch.object(compute_api.API, '_check_auto_disk_config')
|
||||
@mock.patch.object(compute_api.API, '_record_action_start')
|
||||
def test_rebuild_volume_backed_instance_with_trusted_certs(
|
||||
self, _record_action_start, _check_auto_disk_config, _get_image,
|
||||
bdm_get_by_instance_uuid, get_flavor, instance_is_volume_backed,
|
||||
get_min_version):
|
||||
orig_system_metadata = {}
|
||||
new_trusted_certs = ['new-trusted-cert-1', 'new-trusted-cert-2']
|
||||
instance = fake_instance.fake_instance_obj(
|
||||
self.context, vm_state=vm_states.ACTIVE, cell_name='fake-cell',
|
||||
launched_at=timeutils.utcnow(),
|
||||
system_metadata=orig_system_metadata, image_ref=None,
|
||||
expected_attrs=['system_metadata'], trusted_certs=None)
|
||||
get_flavor.return_value = test_flavor.fake_flavor
|
||||
image_href = 'foo'
|
||||
image = {
|
||||
"min_ram": 10, "min_disk": 1,
|
||||
"properties": {'architecture': fields_obj.Architecture.X86_64,
|
||||
'vm_mode': 'hvm'}}
|
||||
admin_pass = ''
|
||||
files_to_inject = []
|
||||
bdms = objects.BlockDeviceMappingList()
|
||||
|
||||
_get_image.return_value = (None, image)
|
||||
bdm_get_by_instance_uuid.return_value = bdms
|
||||
|
||||
self.assertRaises(exception.CertificateValidationFailed,
|
||||
self.compute_api.rebuild, self.context, instance,
|
||||
image_href, admin_pass, files_to_inject,
|
||||
trusted_certs=new_trusted_certs)
|
||||
|
||||
_check_auto_disk_config.assert_called_once_with(image=image)
|
||||
|
||||
def _test_check_injected_file_quota_onset_file_limit_exceeded(self,
|
||||
side_effect):
|
||||
injected_files = [
|
||||
@ -4272,6 +4460,7 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
check_server_group_quota = False
|
||||
filter_properties = {'scheduler_hints': None,
|
||||
'instance_type': flavor}
|
||||
trusted_certs = None
|
||||
|
||||
self.assertRaises(expected_exception,
|
||||
self.compute_api._provision_instances, ctxt,
|
||||
@ -4279,7 +4468,7 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
boot_meta, security_groups, block_device_mapping,
|
||||
shutdown_terminate, instance_group,
|
||||
check_server_group_quota, filter_properties,
|
||||
None, objects.TagList())
|
||||
None, objects.TagList(), trusted_certs)
|
||||
|
||||
do_test()
|
||||
|
||||
@ -4348,7 +4537,7 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
{}, None,
|
||||
None, None, None, {}, None,
|
||||
fake_keypair,
|
||||
objects.TagList())
|
||||
objects.TagList(), None)
|
||||
self.assertEqual(
|
||||
'test',
|
||||
mock_instance.return_value.keypairs.objects[0].name)
|
||||
@ -4357,7 +4546,8 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
1, 1, mock.MagicMock(),
|
||||
{}, None,
|
||||
None, None, None, {}, None,
|
||||
None, objects.TagList())
|
||||
None, objects.TagList(),
|
||||
None)
|
||||
self.assertEqual(
|
||||
0,
|
||||
len(mock_instance.return_value.keypairs.objects))
|
||||
@ -4424,6 +4614,7 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
mock_volume.get.return_value = {'id': '1', 'multiattach': False}
|
||||
instance_tags = objects.TagList(objects=[objects.Tag(tag='tag')])
|
||||
shutdown_terminate = True
|
||||
trusted_certs = None
|
||||
instance_group = None
|
||||
check_server_group_quota = False
|
||||
filter_properties = {'scheduler_hints': None,
|
||||
@ -4434,7 +4625,7 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
min_count, max_count, base_options, boot_meta,
|
||||
security_groups, block_device_mappings, shutdown_terminate,
|
||||
instance_group, check_server_group_quota,
|
||||
filter_properties, None, instance_tags)
|
||||
filter_properties, None, instance_tags, trusted_certs)
|
||||
|
||||
for rs, br, im in instances_to_build:
|
||||
self.assertIsInstance(br.instance, objects.Instance)
|
||||
@ -4508,13 +4699,14 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
check_server_group_quota = False
|
||||
filter_properties = {'scheduler_hints': None,
|
||||
'instance_type': flavor}
|
||||
trusted_certs = None
|
||||
|
||||
instances_to_build = (
|
||||
self.compute_api._provision_instances(ctxt, flavor,
|
||||
min_count, max_count, base_options, boot_meta,
|
||||
security_groups, block_device_mapping, shutdown_terminate,
|
||||
instance_group, check_server_group_quota,
|
||||
filter_properties, None, objects.TagList()))
|
||||
filter_properties, None, objects.TagList(), trusted_certs))
|
||||
rs, br, im = instances_to_build[0]
|
||||
self.assertTrue(uuidutils.is_uuid_like(br.instance.uuid))
|
||||
self.assertEqual(br.instance_uuid, im.instance_uuid)
|
||||
@ -4605,13 +4797,14 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
filter_properties = {'scheduler_hints': None,
|
||||
'instance_type': flavor}
|
||||
tags = objects.TagList()
|
||||
trusted_certs = None
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
self.compute_api._provision_instances, ctxt,
|
||||
flavor, min_count, max_count, base_options,
|
||||
boot_meta, security_groups, block_device_mapping,
|
||||
shutdown_terminate, instance_group,
|
||||
check_server_group_quota, filter_properties,
|
||||
None, tags)
|
||||
None, tags, trusted_certs)
|
||||
# First instance, build_req, mapping is created and destroyed
|
||||
self.assertTrue(build_req_mocks[0].create.called)
|
||||
self.assertTrue(build_req_mocks[0].destroy.called)
|
||||
@ -4707,13 +4900,14 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
filter_properties = {'scheduler_hints': None,
|
||||
'instance_type': flavor}
|
||||
tags = objects.TagList()
|
||||
trusted_certs = None
|
||||
self.assertRaises(exception.InvalidVolume,
|
||||
self.compute_api._provision_instances, ctxt,
|
||||
flavor, min_count, max_count, base_options,
|
||||
boot_meta, security_groups, block_device_mapping,
|
||||
shutdown_terminate, instance_group,
|
||||
check_server_group_quota, filter_properties,
|
||||
None, tags)
|
||||
None, tags, trusted_certs)
|
||||
# First instance, build_req, mapping is created and destroyed
|
||||
self.assertTrue(build_req_mocks[0].create.called)
|
||||
self.assertTrue(build_req_mocks[0].destroy.called)
|
||||
@ -4745,7 +4939,8 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
self.compute_api._provision_instances(ctxt, None, None, None,
|
||||
mock.MagicMock(), None, None,
|
||||
[], None, None, None, None,
|
||||
None, objects.TagList())
|
||||
None, objects.TagList(),
|
||||
None)
|
||||
secgroups = mock_secgroup.populate_security_groups.return_value
|
||||
mock_objects.RequestSpec.from_components.assert_called_once_with(
|
||||
mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY, mock.ANY,
|
||||
@ -5697,6 +5892,60 @@ class _ComputeAPIUnitTestMixIn(object):
|
||||
False)
|
||||
self.assertEqual(0, len(instance.security_groups))
|
||||
|
||||
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_retrieve_trusted_certs_object(self, get_min_version):
|
||||
ids = ['0b5d2c72-12cc-4ba6-a8d7-3ff5cc1d8cb8',
|
||||
'674736e3-f25c-405c-8362-bbf991e0ce0a']
|
||||
|
||||
retrieved_certs = self.compute_api._retrieve_trusted_certs_object(
|
||||
self.context, ids)
|
||||
self.assertEqual(ids, retrieved_certs.ids)
|
||||
|
||||
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
||||
def test_retrieve_trusted_certs_object_old_compute(self, get_min_version):
|
||||
ids = ['trusted-cert-id']
|
||||
|
||||
self.assertRaises(exception.CertificateValidationNotYetAvailable,
|
||||
self.compute_api._retrieve_trusted_certs_object,
|
||||
self.context, ids)
|
||||
|
||||
@mock.patch('nova.objects.service.get_minimum_version_all_cells',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS)
|
||||
def test_retrieve_trusted_certs_object_conf(self, get_min_version):
|
||||
ids = ['conf-trusted-cert-1', 'conf-trusted-cert-2']
|
||||
|
||||
self.flags(verify_glance_signatures=True, group='glance')
|
||||
self.flags(enable_certificate_validation=True, group='glance')
|
||||
self.flags(default_trusted_certificate_ids='conf-trusted-cert-1, '
|
||||
'conf-trusted-cert-2',
|
||||
group='glance')
|
||||
retrieved_certs = self.compute_api._retrieve_trusted_certs_object(
|
||||
self.context, None)
|
||||
self.assertEqual(ids, retrieved_certs.ids)
|
||||
|
||||
def test_retrieve_trusted_certs_object_none(self):
|
||||
self.flags(enable_certificate_validation=False, group='glance')
|
||||
self.assertIsNone(
|
||||
self.compute_api._retrieve_trusted_certs_object(self.context,
|
||||
None))
|
||||
|
||||
def test_retrieve_trusted_certs_object_empty(self):
|
||||
self.flags(enable_certificate_validation=False, group='glance')
|
||||
self.assertIsNone(self.compute_api._retrieve_trusted_certs_object(
|
||||
self.context, []))
|
||||
|
||||
@mock.patch('nova.objects.Service.get_minimum_version',
|
||||
return_value=compute_api.MIN_COMPUTE_TRUSTED_CERTS - 1)
|
||||
def test_retrieve_trusted_certs_object_old_compute_rebuild(
|
||||
self, get_min_version):
|
||||
ids = ['trusted-cert-id']
|
||||
self.assertRaises(exception.CertificateValidationNotYetAvailable,
|
||||
self.compute_api._retrieve_trusted_certs_object,
|
||||
self.context, ids, rebuild=True)
|
||||
get_min_version.assert_called_once_with(self.context, 'nova-compute')
|
||||
|
||||
|
||||
class ComputeAPIUnitTestCase(_ComputeAPIUnitTestMixIn, test.NoDBTestCase):
|
||||
def setUp(self):
|
||||
|
@ -377,12 +377,14 @@ class RealRolePolicyTestCase(test.NoDBTestCase):
|
||||
"os_compute_api:servers:create",
|
||||
"os_compute_api:servers:create:attach_network",
|
||||
"os_compute_api:servers:create:attach_volume",
|
||||
"os_compute_api:servers:create:trusted_certs",
|
||||
"os_compute_api:servers:create_image",
|
||||
"os_compute_api:servers:delete",
|
||||
"os_compute_api:servers:detail",
|
||||
"os_compute_api:servers:index",
|
||||
"os_compute_api:servers:reboot",
|
||||
"os_compute_api:servers:rebuild",
|
||||
"os_compute_api:servers:rebuild:trusted_certs",
|
||||
"os_compute_api:servers:resize",
|
||||
"os_compute_api:servers:revert_resize",
|
||||
"os_compute_api:servers:show",
|
||||
|
@ -0,0 +1,30 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
The 2.63 compute REST API microversion adds support for the
|
||||
``trusted_image_certificates`` parameter, which is used to define a
|
||||
list of trusted certificate IDs that can be used during image
|
||||
signature verification and certificate validation. The list is
|
||||
restricted to a maximum of 50 IDs. Note that there is not support
|
||||
with volume-backed servers.
|
||||
|
||||
The ``trusted_image_certificates`` request parameter can be passed to
|
||||
the server create and rebuild APIs (if allowed by policy):
|
||||
|
||||
* ``POST /servers``
|
||||
* ``POST /servers/{server_id}/action (rebuild)``
|
||||
|
||||
The following policy rules were added to restrict the usage of the
|
||||
``trusted_image_certificates`` request parameter in the server create and
|
||||
rebuild APIs:
|
||||
|
||||
* ``os_compute_api:servers:create:trusted_certs``
|
||||
* ``os_compute_api:servers:rebuild:trusted_certs``
|
||||
|
||||
The ``trusted_image_certificates`` parameter will be in the response
|
||||
body of the following APIs (not restricted by policy):
|
||||
|
||||
* ``GET /servers/detail``
|
||||
* ``GET /servers/{server_id}``
|
||||
* ``PUT /servers/{server_id}``
|
||||
* ``POST /servers/{server_id}/action (rebuild)``
|
Loading…
Reference in New Issue
Block a user