Add certificate validation scenario tests

This patch adds certificate validation scenario tests
incorporate cursive's new certificate validation feature.

Change-Id: Ica1d32ca66d6f89692e9cc90e415bf569332f58d
Implements: blueprint nova-validate-certificates
This commit is contained in:
Jackie Truong 2017-10-25 17:06:12 -04:00 committed by Douglas Mendizábal
parent d1585db029
commit f36d46b724
6 changed files with 248 additions and 9 deletions

View File

@ -28,31 +28,50 @@ from cryptography import x509
from cryptography.x509.oid import NameOID from cryptography.x509.oid import NameOID
from oslo_log import log as logging from oslo_log import log as logging
from tempest.api.compute import api_microversion_fixture
from tempest import config from tempest import config
from barbican_tempest_plugin.tests.scenario import manager as mgr from barbican_tempest_plugin.tests.scenario import manager as mgr
CONF = config.CONF CONF = config.CONF
LOG = logging.getLogger(__name__) LOG = logging.getLogger(__name__)
COMPUTE_MICROVERSION = CONF.compute.min_microversion
class BarbicanScenarioTest(mgr.ScenarioTest): class BarbicanScenarioTest(mgr.ScenarioTest):
api_microversion_header_name = 'X-OpenStack-Nova-API-Version'
credentials = ('primary', 'admin') 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): def setUp(self):
super(BarbicanScenarioTest, self).setUp() super(BarbicanScenarioTest, self).setUp()
self.useFixture(api_microversion_fixture.APIMicroversionFixture(
self.request_microversion))
self.img_file = os.path.join(CONF.scenario.img_dir, self.img_file = os.path.join(CONF.scenario.img_dir,
CONF.scenario.img_file) CONF.scenario.img_file)
self.private_key = rsa.generate_private_key(public_exponent=3, self.private_key = rsa.generate_private_key(public_exponent=3,
key_size=1024, key_size=1024,
backend=default_backend()) backend=default_backend())
self.signing_certificate = self._create_self_signed_certificate( 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_cert_uuid = self._store_cert(
self.signing_certificate 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 @classmethod
def skip_checks(cls): def skip_checks(cls):
@ -89,20 +108,40 @@ class BarbicanScenarioTest(mgr.ScenarioTest):
def _get_uuid(self, href): def _get_uuid(self, href):
return href.split('/')[-1] 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([ issuer = x509.Name([
x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"),
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"), x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, u"CA"),
x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"), x509.NameAttribute(NameOID.LOCALITY_NAME, u"San Francisco"),
x509.NameAttribute(NameOID.ORGANIZATION_NAME, u"My Company"), 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( cert_builder = x509.CertificateBuilder(
issuer_name=issuer, subject_name=issuer, issuer_name=issuer,
subject_name=issuer,
public_key=private_key.public_key(), public_key=private_key.public_key(),
serial_number=x509.random_serial_number(), serial_number=x509.random_serial_number(),
not_valid_before=datetime.utcnow(), not_valid_before=datetime.utcnow(),
not_valid_after=datetime.utcnow() + timedelta(days=10) 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, cert = cert_builder.sign(private_key,
hashes.SHA256(), hashes.SHA256(),

View File

@ -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'])

View File

@ -15,6 +15,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from tempest.common import utils from tempest.common import utils
from tempest import config from tempest import config
from tempest.lib.common import api_version_utils
from tempest.lib import decorators from tempest.lib import decorators
from barbican_tempest_plugin.tests.scenario import barbican_manager from barbican_tempest_plugin.tests.scenario import barbican_manager
@ -24,6 +25,7 @@ LOG = logging.getLogger(__name__)
class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest): class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest):
min_microversion = '2.1'
"""The test suite for encrypted ephemeral storage """The test suite for encrypted ephemeral storage
@ -41,6 +43,14 @@ class EphemeralStorageEncryptionTest(barbican_manager.BarbicanScenarioTest):
raise cls.skipException( raise cls.skipException(
'Ephemeral storage encryption is not supported') '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') @decorators.idempotent_id('afe720b9-8b35-4a3c-8ff3-15841c2d3148')
@utils.services('compute', 'image') @utils.services('compute', 'image')
def test_encrypted_ephemeral_lvm_storage(self): def test_encrypted_ephemeral_lvm_storage(self):

View File

@ -17,6 +17,7 @@ from tempest.api.compute import base as compute_base
from tempest.common import utils from tempest.common import utils
from tempest import config from tempest import config
from tempest import exceptions from tempest import exceptions
from tempest.lib.common import api_version_utils
from tempest.lib import decorators from tempest.lib import decorators
from barbican_tempest_plugin.tests.scenario import barbican_manager from barbican_tempest_plugin.tests.scenario import barbican_manager
@ -26,6 +27,15 @@ LOG = logging.getLogger(__name__)
class ImageSigningTest(barbican_manager.BarbicanScenarioTest): 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') @decorators.idempotent_id('4343df3c-5553-40ea-8705-0cce73b297a9')
@utils.services('compute', 'image') @utils.services('compute', 'image')
@ -77,10 +87,8 @@ class ImageSigningTest(barbican_manager.BarbicanScenarioTest):
img_uuid = self.sign_and_upload_image() img_uuid = self.sign_and_upload_image()
LOG.debug("Modifying image signature to be incorrect") LOG.debug("Modifying image signature to be incorrect")
metadata = {'img_signature': 'fake_signature'} patch = [dict(replace='/img_signature', value='fake_signature')]
self.compute_images_client.update_image_metadata( self.image_client.update_image(image_id=img_uuid, patch=patch)
img_uuid, metadata
)
self.assertRaisesRegex(exceptions.BuildErrorException, self.assertRaisesRegex(exceptions.BuildErrorException,
"Signature verification for the image failed", "Signature verification for the image failed",

View File

@ -15,6 +15,7 @@
from oslo_log import log as logging from oslo_log import log as logging
from tempest.common import utils from tempest.common import utils
from tempest import config from tempest import config
from tempest.lib.common import api_version_utils
from tempest.lib import decorators from tempest.lib import decorators
from barbican_tempest_plugin.tests.scenario import barbican_manager from barbican_tempest_plugin.tests.scenario import barbican_manager
@ -24,6 +25,7 @@ LOG = logging.getLogger(__name__)
class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest): class VolumeEncryptionTest(barbican_manager.BarbicanScenarioTest):
min_microversion = '2.1'
"""The test suite for encrypted cinder volumes """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: if not CONF.compute_feature_enabled.attach_encrypted_volume:
raise cls.skipException('Encrypted volume attach is not supported') 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): def create_encrypted_volume(self, encryption_provider, volume_type):
volume_type = self.create_volume_type(name=volume_type) volume_type = self.create_volume_type(name=volume_type)
self.create_encryption_type(type_id=volume_type['id'], 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") LOG.info("Creating keypair and security group")
keypair = self.create_keypair() keypair = self.create_keypair()
security_group = self._create_security_group() security_group = self._create_security_group()
server = self.create_server( server = self.create_server(
name='signed_img_server', name='signed_img_server',
image_id=img_uuid, image_id=img_uuid,

View File

@ -32,6 +32,7 @@ echo -e 'enabled = True' >> $LOCALCONF_PATH
echo -e '[[test-config|$TEMPEST_CONFIG]]' >> $LOCALCONF_PATH echo -e '[[test-config|$TEMPEST_CONFIG]]' >> $LOCALCONF_PATH
echo -e '[auth]' >> $LOCALCONF_PATH echo -e '[auth]' >> $LOCALCONF_PATH
echo -e 'tempest_roles=creator' >> $LOCALCONF_PATH echo -e 'tempest_roles=creator' >> $LOCALCONF_PATH
# Glance v1 doesn't do signature verification on image upload # Glance v1 doesn't do signature verification on image upload
echo -e '[image-feature-enabled]' >> $LOCALCONF_PATH echo -e '[image-feature-enabled]' >> $LOCALCONF_PATH
echo -e 'api_v1=False' >> $LOCALCONF_PATH echo -e 'api_v1=False' >> $LOCALCONF_PATH