Merge "Add certificate validation scenario tests"
diff --git a/barbican_tempest_plugin/tests/scenario/barbican_manager.py b/barbican_tempest_plugin/tests/scenario/barbican_manager.py
index 64cc9a2..69cd2b9 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.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):
@@ -87,20 +106,40 @@
     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 @@
 
 
 class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest):
+    min_microversion = '2.1'
 
     """The test suite for encrypted ephemeral storage
 
@@ -41,6 +43,14 @@
             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.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 @@
 
 
 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 @@
         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 @@
 
 
 class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest):
+    min_microversion = '2.1'
 
     """The test suite for encrypted cinder volumes
 
@@ -45,6 +47,14 @@
         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 @@
         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 '[[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