diff --git a/barbican_tempest_plugin/tests/scenario/barbican_manager.py b/barbican_tempest_plugin/tests/scenario/barbican_manager.py index d560a63..f6b87fe 100644 --- a/barbican_tempest_plugin/tests/scenario/barbican_manager.py +++ b/barbican_tempest_plugin/tests/scenario/barbican_manager.py @@ -28,31 +28,50 @@ from cryptography import x509 from cryptography.x509.oid import NameOID from oslo_log import log as logging +from tempest.api.compute import api_microversion_fixture from tempest import config from barbican_tempest_plugin.tests.scenario import manager as mgr CONF = config.CONF LOG = logging.getLogger(__name__) +COMPUTE_MICROVERSION = CONF.compute.min_microversion class BarbicanScenarioTest(mgr.ScenarioTest): + api_microversion_header_name = 'X-OpenStack-Nova-API-Version' credentials = ('primary', 'admin') + def get_headers(self): + headers = super(BarbicanScenarioTest, self).get_headers() + if COMPUTE_MICROVERSION: + headers[self.api_microversion_header_name] = COMPUTE_MICROVERSION + return headers + def setUp(self): super(BarbicanScenarioTest, self).setUp() + self.useFixture(api_microversion_fixture.APIMicroversionFixture( + self.request_microversion)) self.img_file = os.path.join(CONF.scenario.img_dir, CONF.scenario.img_file) self.private_key = rsa.generate_private_key(public_exponent=3, key_size=1024, backend=default_backend()) self.signing_certificate = self._create_self_signed_certificate( - self.private_key + self.private_key, + u"Test Certificate" ) self.signing_cert_uuid = self._store_cert( self.signing_certificate ) + self.bad_signing_certificate = self._create_self_signed_certificate( + self.private_key, + u"Bad Certificate" + ) + self.bad_cert_uuid = self._store_cert( + self.bad_signing_certificate + ) @classmethod def skip_checks(cls): @@ -89,20 +108,40 @@ class BarbicanScenarioTest(mgr.ScenarioTest): def _get_uuid(self, href): return href.split('/')[-1] - def _create_self_signed_certificate(self, private_key): + def _create_self_signed_certificate(self, private_key, common_name): issuer = x509.Name([ x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"), x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), - x509.NameAttribute(NameOID.COMMON_NAME, u"Test Certificate"), + x509.NameAttribute(NameOID.COMMON_NAME, common_name), ]) cert_builder = x509.CertificateBuilder( - issuer_name=issuer, subject_name=issuer, + issuer_name=issuer, + subject_name=issuer, public_key=private_key.public_key(), serial_number=x509.random_serial_number(), not_valid_before=datetime.utcnow(), not_valid_after=datetime.utcnow() + timedelta(days=10) + ).add_extension( + x509.BasicConstraints( + ca=True, + path_length=1 + ), + critical=True + ).add_extension( + x509.KeyUsage( + digital_signature=True, + content_commitment=True, + key_encipherment=False, + data_encipherment=False, + key_agreement=False, + key_cert_sign=True, + crl_sign=False, + encipher_only=False, + decipher_only=False + ), + critical=False ) cert = cert_builder.sign(private_key, hashes.SHA256(), diff --git a/barbican_tempest_plugin/tests/scenario/test_certificate_validation.py b/barbican_tempest_plugin/tests/scenario/test_certificate_validation.py new file mode 100644 index 0000000..5a6b5b7 --- /dev/null +++ b/barbican_tempest_plugin/tests/scenario/test_certificate_validation.py @@ -0,0 +1,172 @@ +# Copyright (c) 2017 Johns Hopkins University Applied Physics Laboratory +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from oslo_log import log as logging +from tempest.common import utils +from tempest.common import waiters +from tempest import config +from tempest import exceptions +from tempest.lib.common import api_version_utils +from tempest.lib import decorators + +from barbican_tempest_plugin.tests.scenario import barbican_manager + +CONF = config.CONF +LOG = logging.getLogger(__name__) + + +class CertificateValidationTest(barbican_manager.BarbicanScenarioTest): + min_microversion = '2.63' + max_microversion = 'latest' + + @classmethod + def resource_setup(cls): + super(CertificateValidationTest, cls).resource_setup() + cls.request_microversion = ( + api_version_utils.select_request_microversion( + cls.min_microversion, + CONF.compute.min_microversion)) + + @classmethod + def skip_checks(cls): + super(CertificateValidationTest, cls).skip_checks() + api_version_utils.check_skip_with_microversion( + cls.min_microversion, + cls.max_microversion, + CONF.compute.min_microversion, + CONF.compute.max_microversion) + + @decorators.idempotent_id('b41bc663-5662-4b1e-b8f1-27b2876f16a6') + @utils.services('compute', 'image') + def test_signed_image_upload_and_boot(self): + """Test that Nova boots a signed image. + + The test follows these steps: + * Create an asymmetric keypair + * Sign an image file with the private key + * Create a certificate with the public key + * Store the certificate in Barbican + * Store the signed image in Glance + * Boot the signed image with a valid trusted image certificate ID + * Confirm the instance changes state to Active + """ + img_uuid = self.sign_and_upload_image() + + LOG.debug("Booting server with self-signed image %s and certificate " + "ID %s", img_uuid, self.signing_cert_uuid) + instance = self.create_server(name='signed_img_server', + image_id=img_uuid, + wait_until='ACTIVE', + trusted_image_certificates=[ + self.signing_cert_uuid]) + self.servers_client.delete_server(instance['id']) + + @decorators.idempotent_id('6d354881-35a6-4568-94b8-2204bbf67b29') + @utils.services('compute', 'image') + def test_signed_image_invalid_cert_boot_failure(self): + """Test that Nova refuses to boot an unvalidated signed image. + + If the create_server call succeeds instead of throwing an + exception, it is likely that certificate validation is not + turned on. To turn on certificate validation, set + enable_certificate_validation=True in the nova configuration + file under the [glance] section. + + The test follows these steps: + * Create an asymmetric keypair + * Sign an image file with the private key + * Create a certificate with the public key + * Store the certificate in Barbican + * Store the signed image in Glance + * Attempt to boot the signed image with an invalid trusted + image certificate ID + * Confirm an exception is thrown + """ + img_uuid = self.sign_and_upload_image() + + LOG.debug("Booting server with self-signed image %s and invalid " + "certificate ID %s", img_uuid, self.bad_cert_uuid) + self.assertRaisesRegex(exceptions.BuildErrorException, + "Certificate chain building failed", + self.create_server, + image_id=img_uuid, + trusted_image_certificates=[self.bad_cert_uuid]) + + @decorators.idempotent_id('aed5254d-1e7a-46b6-8cb0-ef5fd798671a') + @utils.services('compute', 'image') + def test_signed_image_upload_and_hard_reboot(self): + """Test that Nova boots a signed image with certs after a hard reboot. + + The test follows these steps: + * Create an asymmetric keypair + * Sign an image file with the private key + * Create a certificate with the public key + * Store the certificate in Barbican + * Store the signed image in Glance + * Boot the signed image with a valid trusted image certificate ID + * Reboot the signed image + * Confirm the instance changes state to Active + """ + img_uuid = self.sign_and_upload_image() + + LOG.debug("Booting server with self-signed image %s and certificate " + "ID %s", img_uuid, self.signing_cert_uuid) + instance = self.create_server(name='server_to_reboot', + image_id=img_uuid, + wait_until='ACTIVE', + trusted_image_certificates=[ + self.signing_cert_uuid]) + + LOG.debug("Hard rebooting server with self-signed image %s and " + "certificate ID %s", img_uuid, self.signing_cert_uuid) + self.servers_client.reboot_server(instance['id'], type='HARD') + waiters.wait_for_server_status(self.servers_client, instance['id'], + 'ACTIVE') + self.servers_client.delete_server(instance['id']) + + @decorators.idempotent_id('f9c6de51-b027-476f-a6e3-847bb39cfa02') + @utils.services('compute', 'image') + def test_signed_image_upload_and_server_rebuild(self): + """Test that Nova boots a signed image with certs after a rebuild. + + The test follows these steps: + * Create an asymmetric keypair + * Sign an image file with the private key + * Create a certificate with the public key + * Store the certificate in Barbican + * Store the signed image in Glance + * Boot the server with the first signed image + * Build a second signed image + * Rebuild the server with the second signed image with a valid + trusted image certificate ID + * Confirm the instance changes state to Active + """ + img_uuid_create = self.sign_and_upload_image() + + LOG.debug("Booting server with self-signed image %s and certificate " + "ID %s", img_uuid_create, self.signing_cert_uuid) + instance = self.create_server(name='server_to_rebuild', + image_id=img_uuid_create, + wait_until='ACTIVE') + + img_uuid_rebuild = self.sign_and_upload_image() + LOG.debug("Rebuild server with self-signed image %s and certificate " + "ID %s", img_uuid_rebuild, self.signing_cert_uuid) + rebuild_kwargs = { + 'trusted_image_certificates': [self.signing_cert_uuid], + } + self.rebuild_server(instance['id'], + img_uuid_rebuild, + rebuild_kwargs=rebuild_kwargs) + self.servers_client.delete_server(instance['id']) diff --git a/barbican_tempest_plugin/tests/scenario/test_ephemeral_disk_encryption.py b/barbican_tempest_plugin/tests/scenario/test_ephemeral_disk_encryption.py index 3734019..ee1bda5 100644 --- a/barbican_tempest_plugin/tests/scenario/test_ephemeral_disk_encryption.py +++ b/barbican_tempest_plugin/tests/scenario/test_ephemeral_disk_encryption.py @@ -15,6 +15,7 @@ from oslo_log import log as logging from tempest.common import utils from tempest import config +from tempest.lib.common import api_version_utils from tempest.lib import decorators from barbican_tempest_plugin.tests.scenario import barbican_manager @@ -24,6 +25,7 @@ LOG = logging.getLogger(__name__) class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest): + min_microversion = '2.1' """The test suite for encrypted ephemeral storage @@ -41,6 +43,14 @@ class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest): raise cls.skipException( 'Ephemeral storage encryption is not supported') + @classmethod + def resource_setup(cls): + super(EphemeralStorageEncryptionTest, cls).resource_setup() + cls.request_microversion = ( + api_version_utils.select_request_microversion( + cls.min_microversion, + CONF.compute.min_microversion)) + @decorators.idempotent_id('afe720b9-8b35-4a3c-8ff3-15841c2d3148') @utils.services('compute', 'image') def test_encrypted_ephemeral_lvm_storage(self): diff --git a/barbican_tempest_plugin/tests/scenario/test_image_signing.py b/barbican_tempest_plugin/tests/scenario/test_image_signing.py index 191b613..d3d57c9 100644 --- a/barbican_tempest_plugin/tests/scenario/test_image_signing.py +++ b/barbican_tempest_plugin/tests/scenario/test_image_signing.py @@ -17,6 +17,7 @@ from tempest.api.compute import base as compute_base from tempest.common import utils from tempest import config from tempest import exceptions +from tempest.lib.common import api_version_utils from tempest.lib import decorators from barbican_tempest_plugin.tests.scenario import barbican_manager @@ -26,6 +27,15 @@ LOG = logging.getLogger(__name__) class ImageSigningTest(barbican_manager.BarbicanScenarioTest): + min_microversion = '2.1' + + @classmethod + def resource_setup(cls): + super(ImageSigningTest, cls).resource_setup() + cls.request_microversion = ( + api_version_utils.select_request_microversion( + cls.min_microversion, + CONF.compute.min_microversion)) @decorators.idempotent_id('4343df3c-5553-40ea-8705-0cce73b297a9') @utils.services('compute', 'image') @@ -77,10 +87,8 @@ class ImageSigningTest(barbican_manager.BarbicanScenarioTest): img_uuid = self.sign_and_upload_image() LOG.debug("Modifying image signature to be incorrect") - metadata = {'img_signature': 'fake_signature'} - self.compute_images_client.update_image_metadata( - img_uuid, metadata - ) + patch = [dict(replace='/img_signature', value='fake_signature')] + self.image_client.update_image(image_id=img_uuid, patch=patch) self.assertRaisesRegex(exceptions.BuildErrorException, "Signature verification for the image failed", diff --git a/barbican_tempest_plugin/tests/scenario/test_volume_encryption.py b/barbican_tempest_plugin/tests/scenario/test_volume_encryption.py index c2033fb..57c72fb 100644 --- a/barbican_tempest_plugin/tests/scenario/test_volume_encryption.py +++ b/barbican_tempest_plugin/tests/scenario/test_volume_encryption.py @@ -15,6 +15,7 @@ from oslo_log import log as logging from tempest.common import utils from tempest import config +from tempest.lib.common import api_version_utils from tempest.lib import decorators from barbican_tempest_plugin.tests.scenario import barbican_manager @@ -24,6 +25,7 @@ LOG = logging.getLogger(__name__) class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest): + min_microversion = '2.1' """The test suite for encrypted cinder volumes @@ -45,6 +47,14 @@ class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest): if not CONF.compute_feature_enabled.attach_encrypted_volume: raise cls.skipException('Encrypted volume attach is not supported') + @classmethod + def resource_setup(cls): + super(VolumeEncryptionTest, cls).resource_setup() + cls.request_microversion = ( + api_version_utils.select_request_microversion( + cls.min_microversion, + CONF.compute.min_microversion)) + def create_encrypted_volume(self, encryption_provider, volume_type): volume_type = self.create_volume_type(name=volume_type) self.create_encryption_type(type_id=volume_type['id'], @@ -103,7 +113,6 @@ class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest): LOG.info("Creating keypair and security group") keypair = self.create_keypair() security_group = self._create_security_group() - server = self.create_server( name='signed_img_server', image_id=img_uuid, diff --git a/tools/pre_test_hook.sh b/tools/pre_test_hook.sh index f42bb93..517fb00 100755 --- a/tools/pre_test_hook.sh +++ b/tools/pre_test_hook.sh @@ -32,6 +32,7 @@ echo -e 'enabled = True' >> $LOCALCONF_PATH echo -e '[[test-config|$TEMPEST_CONFIG]]' >> $LOCALCONF_PATH echo -e '[auth]' >> $LOCALCONF_PATH echo -e 'tempest_roles=creator' >> $LOCALCONF_PATH + # Glance v1 doesn't do signature verification on image upload echo -e '[image-feature-enabled]' >> $LOCALCONF_PATH echo -e 'api_v1=False' >> $LOCALCONF_PATH