From 7f10707e5d060874f1a562b5efdce5ddc2701389 Mon Sep 17 00:00:00 2001 From: Jackie Truong Date: Sun, 3 Sep 2017 17:24:40 -0400 Subject: [PATCH] Microversion 2.63 - Add trusted_image_certificates This change adds a `--trusted-image-certificate-id` option to the `nova boot` and `nova rebuild` commands. This option takes in a a single trusted certificate ID. The option may be used multiple times to specify multiple trusted certificate IDs, which will be used to validate certificates in the image signature verification process. If ID values are not specified using this option, the value of the newly added OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable will be used instead. This value will be converted into a list before being passed on. The ``nova rebuild`` command also gets a new ``--trusted-image-certificates-unset`` option to unset/reset the trusted image certificates in a server during rebuild. This is similar to unsetting key_name and user_data during rebuild. Corresponding `trusted_image_certificates` kwarg has been added to the server create and rebuild Python API bindings. Co-Authored-By: Brianna Poulos Co-Authored-By: Matt Riedemann Change-Id: I235541a689732826950c7b2a510d5835211120c3 Implements: blueprint nova-validate-certificates --- doc/source/cli/nova.rst | 22 ++ doc/source/user/shell.rst | 10 + novaclient/__init__.py | 2 +- novaclient/tests/unit/v2/test_servers.py | 67 +++++ novaclient/tests/unit/v2/test_shell.py | 250 ++++++++++++++++++ novaclient/v2/servers.py | 35 ++- novaclient/v2/shell.py | 66 +++++ .../microversion-v2_63-cd058a9145550cae.yaml | 17 ++ 8 files changed, 465 insertions(+), 4 deletions(-) create mode 100644 releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml diff --git a/doc/source/cli/nova.rst b/doc/source/cli/nova.rst index e53f4c1a6..35b950b0c 100644 --- a/doc/source/cli/nova.rst +++ b/doc/source/cli/nova.rst @@ -1011,6 +1011,7 @@ nova boot [--config-drive ] [--poll] [--admin-pass ] [--access-ip-v4 ] [--access-ip-v6 ] [--description ] + [--trusted-image-certificate-id] Boot a new server. @@ -1164,6 +1165,13 @@ Boot a new server. Description for the server. (Supported by API versions '2.19' - '2.latest') +``--trusted-image-certificate-id `` + Trusted image certificate IDs used to validate certificates + during the image signature verification process. + Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. + May be specified multiple times to pass multiple trusted image + certificate IDs. (Supported by API versions '2.63' - '2.latest') + .. _nova_cell-capacities: nova cell-capacities @@ -2660,6 +2668,8 @@ nova rebuild [--minimal] [--preserve-ephemeral] [--name ] [--description ] [--meta ] [--file ] + [--trusted-image-certificate-id ] + [--trusted-image-certificates-unset] Shutdown, re-image, and re-boot a server. @@ -2707,6 +2717,18 @@ Shutdown, re-image, and re-boot a server. to on the new server. You may store up to 5 files. +``--trusted-image-certificate-id `` + Trusted image certificate IDs used to validate certificates + during the image signature verification process. + Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. + May be specified multiple times to pass multiple trusted image + certificate IDs. (Supported by API versions '2.63' - '2.latest') + +``--trusted-image-certificates-unset`` + Unset trusted_image_certificates in the server. Cannot be + specified with the ``--trusted-image-certificate-id`` option. + (Supported by API versions '2.63' - '2.latest') + .. _nova_refresh-network: nova refresh-network diff --git a/doc/source/user/shell.rst b/doc/source/user/shell.rst index bd1fb7e93..882bb7560 100644 --- a/doc/source/user/shell.rst +++ b/doc/source/user/shell.rst @@ -60,6 +60,16 @@ some environment variables: The Keystone region name. Defaults to the first region if multiple regions are available. +.. envvar:: OS_TRUSTED_IMAGE_CERTIFICATE_IDS + + A comma-delimited list of trusted image certificate IDs. Only used + with the ``nova boot`` and ``nova rebuild`` commands starting with the + 2.63 microversion. + + For example:: + + export OS_TRUSTED_IMAGE_CERTIFICATE_IDS=trusted-cert-id1,trusted-cert-id2 + For example, in Bash you'd use:: export OS_USERNAME=yourname diff --git a/novaclient/__init__.py b/novaclient/__init__.py index 1530b37a7..3bcad2062 100644 --- a/novaclient/__init__.py +++ b/novaclient/__init__.py @@ -25,4 +25,4 @@ API_MIN_VERSION = api_versions.APIVersion("2.1") # when client supported the max version, and bumped sequentially, otherwise # the client may break due to server side new version may include some # backward incompatible change. -API_MAX_VERSION = api_versions.APIVersion("2.62") +API_MAX_VERSION = api_versions.APIVersion("2.63") diff --git a/novaclient/tests/unit/v2/test_servers.py b/novaclient/tests/unit/v2/test_servers.py index 07ad1497e..540968cf5 100644 --- a/novaclient/tests/unit/v2/test_servers.py +++ b/novaclient/tests/unit/v2/test_servers.py @@ -1542,3 +1542,70 @@ class ServersV257Test(ServersV256Test): exceptions.UnsupportedAttribute, s.rebuild, image=1, name='new', meta={'foo': 'bar'}, files=files) self.assertIn('files', six.text_type(ex)) + + +class ServersV263Test(ServersV257Test): + + api_version = "2.63" + + def test_create_server_with_trusted_image_certificates(self): + self.cs.servers.create( + name="My server", + image=1, + flavor=1, + meta={'foo': 'bar'}, + userdata="hello moto", + key_name="fakekey", + nics=self._get_server_create_default_nics(), + trusted_image_certificates=['id1', 'id2'], + ) + self.assert_called('POST', '/servers', + {'server': { + 'flavorRef': '1', + 'imageRef': '1', + 'key_name': 'fakekey', + 'max_count': 1, + 'metadata': {'foo': 'bar'}, + 'min_count': 1, + 'name': 'My server', + 'networks': 'auto', + 'trusted_image_certificates': ['id1', 'id2'], + 'user_data': 'aGVsbG8gbW90bw==' + }} + ) + + def test_create_server_with_trusted_image_certificates_pre_263_fails(self): + self.cs.api_version = api_versions.APIVersion('2.62') + ex = self.assertRaises( + exceptions.UnsupportedAttribute, self.cs.servers.create, + name="My server", image=1, flavor=1, meta={'foo': 'bar'}, + userdata="hello moto", key_name="fakekey", + nics=self._get_server_create_default_nics(), + trusted_image_certificates=['id1', 'id2']) + self.assertIn('trusted_image_certificates', six.text_type(ex)) + + def test_rebuild_server_with_trusted_image_certificates(self): + s = self.cs.servers.get(1234) + ret = s.rebuild(image="1", trusted_image_certificates=['id1', 'id2']) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': { + 'imageRef': '1', + 'trusted_image_certificates': ['id1', 'id2']}}) + + def test_rebuild_server_with_trusted_image_certificates_none(self): + s = self.cs.servers.get(1234) + ret = s.rebuild(image="1", trusted_image_certificates=None) + self.assert_request_id(ret, fakes.FAKE_REQUEST_ID_LIST) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': { + 'imageRef': '1', + 'trusted_image_certificates': None}}) + + def test_rebuild_with_trusted_image_certificates_pre_263_fails(self): + self.cs.api_version = api_versions.APIVersion('2.62') + ex = self.assertRaises(exceptions.UnsupportedAttribute, + self.cs.servers.rebuild, + '1234', fakes.FAKE_IMAGE_UUID_1, + trusted_image_certificates=['id1', 'id2']) + self.assertIn('trusted_image_certificates', six.text_type(ex)) diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index b4ee79acd..a32d623b8 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -1155,6 +1155,113 @@ class ShellTest(utils.TestCase): self.assertRaises(SystemExit, self.run_command, cmd, api_version='2.51') + def test_boot_with_single_trusted_image_certificates(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server ' + '--trusted-image-certificate-id id1' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['id1'] + }}, + ) + + def test_boot_with_multiple_trusted_image_certificates(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['id1', 'id2'] + }}, + ) + + def test_boot_with_trusted_image_certificates_envar(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + self.run_command('boot --flavor 1 --image %s --nic auto some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['var_id1', 'var_id2'] + }}, + ) + + def test_boot_without_trusted_image_certificates_v263(self): + self.run_command('boot --flavor 1 --image %s --nic auto some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + }}, + ) + + def test_boot_with_trusted_image_certificates_pre_v263(self): + cmd = ('boot --flavor 1 --image %s some-server ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.62') + + # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in + # microversions < 2.63 (should result in an UnsupportedAttribute exception) + def test_boot_with_trusted_image_certificates_envar_pre_v263(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + cmd = ('boot --flavor 1 --image %s --nic auto some-server ' + % FAKE_UUID_1) + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + cmd, api_version='2.62') + + def test_boot_with_trusted_image_certificates_arg_and_envvar(self): + """Tests that if both the environment variable and argument are + specified, the argument takes precedence. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) + self.run_command('boot --flavor 1 --image %s --nic auto ' + '--trusted-image-certificate-id cert2 some-server' + % FAKE_UUID_1, api_version='2.63') + self.assert_called_anytime( + 'POST', '/servers', + {'server': { + 'flavorRef': '1', + 'name': 'some-server', + 'imageRef': FAKE_UUID_1, + 'min_count': 1, + 'max_count': 1, + 'networks': 'auto', + 'trusted_image_certificates': ['cert2'] + }}, + ) + def test_flavor_list(self): out, _ = self.run_command('flavor-list') self.assert_called_anytime('GET', '/flavors/detail') @@ -1664,6 +1771,148 @@ class ShellTest(utils.TestCase): self.assertIn("Cannot specify '--user-data-unset' with " "'--user-data'.", six.text_type(ex)) + def test_rebuild_with_single_trusted_image_certificates(self): + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificate-id id1' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': ['id1'] + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_multiple_trusted_image_certificate_ids(self): + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': ['id1', + 'id2'] + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_envar(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + self.run_command('rebuild sample-server %s' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': + ['var_id1', 'var_id2']} + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_without_trusted_image_certificates_v263(self): + self.run_command('rebuild sample-server %s' % FAKE_UUID_1, + api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_pre_v263(self): + cmd = ('rebuild sample-server %s' + '--trusted-image-certificate-id id1 ' + '--trusted-image-certificate-id id2' % FAKE_UUID_1) + self.assertRaises(SystemExit, self.run_command, + cmd, api_version='2.62') + + # OS_TRUSTED_IMAGE_CERTIFICATE_IDS environment variable is not supported in + # microversions < 2.63 (should result in an UnsupportedAttribute exception) + def test_rebuild_with_trusted_image_certificates_envar_pre_v263(self): + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1,var_id2')) + cmd = ('rebuild sample-server %s' % FAKE_UUID_1) + self.assertRaises(exceptions.UnsupportedAttribute, self.run_command, + cmd, api_version='2.62') + + def test_rebuild_with_trusted_image_certificates_unset(self): + """Tests explicitly unsetting the existing server trusted image + certificate IDs. + """ + self.run_command('rebuild sample-server %s ' + '--trusted-image-certificates-unset' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': None + } + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + + def test_rebuild_with_trusted_image_certificates_unset_arg_conflict(self): + """Tests the error condition that trusted image certs are both unset + and set via argument during rebuild. + """ + ex = self.assertRaises( + exceptions.CommandError, self.run_command, + 'rebuild sample-server %s --trusted-image-certificate-id id1 ' + '--trusted-image-certificates-unset' % FAKE_UUID_1, + api_version='2.63') + self.assertIn("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id'", + six.text_type(ex)) + + def test_rebuild_with_trusted_image_certificates_unset_env_conflict(self): + """Tests the error condition that trusted image certs are both unset + and set via environment variable during rebuild. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'var_id1')) + ex = self.assertRaises( + exceptions.CommandError, self.run_command, + 'rebuild sample-server %s --trusted-image-certificates-unset' % + FAKE_UUID_1, api_version='2.63') + self.assertIn("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id'", + six.text_type(ex)) + + def test_rebuild_with_trusted_image_certificates_arg_and_envar(self): + """Tests that if both the environment variable and argument are + specified, the argument takes precedence. + """ + self.useFixture(fixtures.EnvironmentVariable( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS', 'cert1')) + self.run_command('rebuild sample-server ' + '--trusted-image-certificate-id cert2 %s' + % FAKE_UUID_1, api_version='2.63') + self.assert_called('GET', '/servers?name=sample-server', pos=0) + self.assert_called('GET', '/servers/1234', pos=1) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_1, pos=2) + self.assert_called('POST', '/servers/1234/action', + {'rebuild': {'imageRef': FAKE_UUID_1, + 'description': None, + 'trusted_image_certificates': + ['cert2']} + }, pos=3) + self.assert_called('GET', '/v2/images/%s' % FAKE_UUID_2, pos=4) + def test_start(self): self.run_command('start sample-server') self.assert_called('POST', '/servers/1234/action', {'os-start': None}) @@ -3547,6 +3796,7 @@ class ShellTest(utils.TestCase): 60, # There are no client-side changes for volume multiattach. 61, # There are no version-wrapped shell method changes for this. 62, # There are no version-wrapped shell method changes for this. + 63, # There are no version-wrapped shell method changes for this. ]) versions_supported = set(range(0, novaclient.API_MAX_VERSION.ver_minor + 1)) diff --git a/novaclient/v2/servers.py b/novaclient/v2/servers.py index a7b3eda16..d872fa016 100644 --- a/novaclient/v2/servers.py +++ b/novaclient/v2/servers.py @@ -650,7 +650,7 @@ class ServerManager(base.BootingManagerWithFind): block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, admin_pass=None, disk_config=None, access_ip_v4=None, access_ip_v6=None, description=None, - tags=None, **kwargs): + tags=None, trusted_image_certificates=None, **kwargs): """ Create (boot) a new server. """ @@ -768,6 +768,10 @@ class ServerManager(base.BootingManagerWithFind): if tags: body['server']['tags'] = tags + if trusted_image_certificates: + body['server']['trusted_image_certificates'] = ( + trusted_image_certificates) + return self._create('/servers', body, response_key, return_raw=return_raw, **kwargs) @@ -1191,7 +1195,8 @@ class ServerManager(base.BootingManagerWithFind): block_device_mapping=None, block_device_mapping_v2=None, nics=None, scheduler_hints=None, config_drive=None, disk_config=None, admin_pass=None, - access_ip_v4=None, access_ip_v6=None, **kwargs): + access_ip_v4=None, access_ip_v6=None, + trusted_image_certificates=None, **kwargs): # TODO(anthony): indicate in doc string if param is an extension # and/or optional """ @@ -1252,6 +1257,8 @@ class ServerManager(base.BootingManagerWithFind): microversion 2.19) :param tags: A list of arbitrary strings to be added to the server as tags (allowed since microversion 2.52) + :param trusted_image_certificates: A list of trusted certificate IDs + (allowed since microversion 2.63) """ if not min_count: min_count = 1 @@ -1292,6 +1299,12 @@ class ServerManager(base.BootingManagerWithFind): if files and self.api_version >= personality_files_deprecation: raise exceptions.UnsupportedAttribute('files', '2.0', '2.56') + trusted_certs_microversion = api_versions.APIVersion("2.63") + if (trusted_image_certificates and + self.api_version < trusted_certs_microversion): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + boot_kwargs = dict( meta=meta, files=files, userdata=userdata, reservation_id=reservation_id, min_count=min_count, @@ -1299,7 +1312,8 @@ class ServerManager(base.BootingManagerWithFind): key_name=key_name, availability_zone=availability_zone, scheduler_hints=scheduler_hints, config_drive=config_drive, disk_config=disk_config, admin_pass=admin_pass, - access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, **kwargs) + access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, + trusted_image_certificates=trusted_image_certificates, **kwargs) if block_device_mapping: boot_kwargs['block_device_mapping'] = block_device_mapping @@ -1416,6 +1430,9 @@ class ServerManager(base.BootingManagerWithFind): well or a string. If None is specified, the existing user_data is unset. (starting from microversion 2.57) + :param trusted_image_certificates: A list of trusted certificate IDs + or None to unset/reset the servers trusted image + certificates (allowed since microversion 2.63) :returns: :class:`Server` """ descr_microversion = api_versions.APIVersion("2.19") @@ -1436,6 +1453,15 @@ class ServerManager(base.BootingManagerWithFind): if 'userdata' in kwargs and self.api_version < files_and_userdata: raise exceptions.UnsupportedAttribute('userdata', '2.57') + trusted_certs_microversion = api_versions.APIVersion("2.63") + # trusted_image_certificates is intentionally *not* a named kwarg + # so that trusted_image_certificates=None is not confused with an + # intentional unset/reset request. + if ("trusted_image_certificates" in kwargs and + self.api_version < trusted_certs_microversion): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + body = {'imageRef': base.getid(image)} if password is not None: body['adminPass'] = password @@ -1449,6 +1475,9 @@ class ServerManager(base.BootingManagerWithFind): body["description"] = kwargs["description"] if 'key_name' in kwargs: body['key_name'] = kwargs['key_name'] + if "trusted_image_certificates" in kwargs: + body["trusted_image_certificates"] = kwargs[ + "trusted_image_certificates"] if meta: body['metadata'] = meta if files: diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 160418cc5..c1c044356 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -510,6 +510,14 @@ def _boot(cs, args): if include_files: boot_kwargs['files'] = files + if ('trusted_image_certificates' in args and + args.trusted_image_certificates): + boot_kwargs['trusted_image_certificates'] = ( + args.trusted_image_certificates) + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + boot_kwargs["trusted_image_certificates"] = utils.env( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') + return boot_args, boot_kwargs @@ -874,6 +882,18 @@ def _boot(cs, args): action="store_true", default=False, help=_("Return a reservation id bound to created servers.")) +@utils.arg( + '--trusted-image-certificate-id', + metavar='', + action='append', + dest='trusted_image_certificates', + default=[], + help=_('Trusted image certificate IDs used to validate certificates ' + 'during the image signature verification process. ' + 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' + 'May be specified multiple times to pass multiple trusted image ' + 'certificate IDs.'), + start_version="2.63") def do_boot(cs, args): """Boot a new server.""" boot_args, boot_kwargs = _boot(cs, args) @@ -1807,6 +1827,25 @@ def do_reboot(cs, args): help=_("Unset user_data in the server. Cannot be specified with the " "'--user-data' option."), start_version='2.57') +@utils.arg( + '--trusted-image-certificate-id', + metavar='', + action='append', + dest='trusted_image_certificates', + default=[], + help=_('Trusted image certificate IDs used to validate certificates ' + 'during the image signature verification process. ' + 'Defaults to env[OS_TRUSTED_IMAGE_CERTIFICATE_IDS]. ' + 'May be specified multiple times to pass multiple trusted image ' + 'certificate IDs.'), + start_version="2.63") +@utils.arg( + '--trusted-image-certificates-unset', + action='store_true', + default=False, + help=_("Unset trusted_image_certificates in the server. Cannot be " + "specified with the '--trusted-image-certificate-id' option."), + start_version="2.63") def do_rebuild(cs, args): """Shutdown, re-image, and re-boot a server.""" server = _find_server(cs, args.server) @@ -1861,6 +1900,33 @@ def do_rebuild(cs, args): elif args.key_name: kwargs['key_name'] = args.key_name + if cs.api_version >= api_versions.APIVersion('2.63'): + # First determine if the user specified anything via the command line + # or the environment variable. + trusted_image_certificates = None + if ('trusted_image_certificates' in args and + args.trusted_image_certificates): + trusted_image_certificates = args.trusted_image_certificates + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + trusted_image_certificates = utils.env( + 'OS_TRUSTED_IMAGE_CERTIFICATE_IDS').split(',') + + if args.trusted_image_certificates_unset: + kwargs['trusted_image_certificates'] = None + # Check for conflicts in option usage. + if trusted_image_certificates: + raise exceptions.CommandError( + _("Cannot specify '--trusted-image-certificates-unset' " + "with '--trusted-image-certificate-id' or with " + "OS_TRUSTED_IMAGE_CERTIFICATE_IDS env variable set.")) + elif trusted_image_certificates: + # Only specify the kwarg if there is a value specified to avoid + # confusion with unsetting the value. + kwargs['trusted_image_certificates'] = trusted_image_certificates + elif utils.env('OS_TRUSTED_IMAGE_CERTIFICATE_IDS'): + raise exceptions.UnsupportedAttribute("trusted_image_certificates", + "2.63") + server = server.rebuild(image, _password, **kwargs) _print_server(cs, args, server) diff --git a/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml b/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml new file mode 100644 index 000000000..f8299656f --- /dev/null +++ b/releasenotes/notes/microversion-v2_63-cd058a9145550cae.yaml @@ -0,0 +1,17 @@ +--- +features: + - | + Added support for `microversion 2.63`_, which includes the following + changes: + + - New environment variable called ``OS_TRUSTED_IMAGE_CERTIFICATE_IDS`` + - New ``nova boot`` option called ``--trusted-image-certificate-id`` + - New ``nova rebuild`` options called ``--trusted-image-certificate-id`` + and ``--trusted-image-certificates-unset`` + - New kwarg called ``trusted_image_certificates`` added to python API + bindings: + + - ``novaclient.v2.servers.ServerManager.create()`` + - ``novaclient.v2.servers.ServerManager.rebuild()`` + + .. _microversion 2.63: https://docs.openstack.org/nova/latest/api_microversion_history.html#id57