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