Merge "Add image signing verification"
This commit is contained in:
		@@ -63,6 +63,22 @@ class ImageDataController(object):
 | 
			
		||||
                    'e': encodeutils.exception_to_unicode(e)})
 | 
			
		||||
            LOG.exception(msg)
 | 
			
		||||
 | 
			
		||||
    def _delete(self, image_repo, image):
 | 
			
		||||
        """Delete the image.
 | 
			
		||||
 | 
			
		||||
        :param image_repo: The instance of ImageRepo
 | 
			
		||||
        :param image: The image that will be deleted
 | 
			
		||||
        """
 | 
			
		||||
        try:
 | 
			
		||||
            if image_repo and image:
 | 
			
		||||
                image.status = 'killed'
 | 
			
		||||
                image_repo.save(image)
 | 
			
		||||
        except Exception as e:
 | 
			
		||||
            msg = (_LE("Unable to delete image %(image_id)s: %(e)s") %
 | 
			
		||||
                   {'image_id': image.image_id,
 | 
			
		||||
                    'e': encodeutils.exception_to_unicode(e)})
 | 
			
		||||
            LOG.exception(msg)
 | 
			
		||||
 | 
			
		||||
    @utils.mutating
 | 
			
		||||
    def upload(self, req, image_id, data, size):
 | 
			
		||||
        image_repo = self.gateway.get_repo(req.context)
 | 
			
		||||
@@ -152,6 +168,14 @@ class ImageDataController(object):
 | 
			
		||||
            raise webob.exc.HTTPServiceUnavailable(explanation=msg,
 | 
			
		||||
                                                   request=req)
 | 
			
		||||
 | 
			
		||||
        except exception.SignatureVerificationError as e:
 | 
			
		||||
            msg = (_LE("Signature verification failed for image %(id)s: %(e)s")
 | 
			
		||||
                   % {'id': image_id,
 | 
			
		||||
                      'e': encodeutils.exception_to_unicode(e)})
 | 
			
		||||
            LOG.error(msg)
 | 
			
		||||
            self._delete(image_repo, image)
 | 
			
		||||
            raise webob.exc.HTTPBadRequest(explanation=msg)
 | 
			
		||||
 | 
			
		||||
        except webob.exc.HTTPGone as e:
 | 
			
		||||
            with excutils.save_and_reraise_exception():
 | 
			
		||||
                LOG.error(_LE("Failed to upload image data due to HTTP error"))
 | 
			
		||||
 
 | 
			
		||||
@@ -447,6 +447,10 @@ class MetadefTagNotFound(NotFound):
 | 
			
		||||
                " namespace=%(namespace_name)s.")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SignatureVerificationError(GlanceException):
 | 
			
		||||
    message = _("Unable to verify signature: %(reason)s")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class InvalidVersion(Invalid):
 | 
			
		||||
    message = _("Version is invalid: %(reason)s")
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										281
									
								
								glance/common/signature_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										281
									
								
								glance/common/signature_utils.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,281 @@
 | 
			
		||||
# Copyright (c) The Johns Hopkins University/Applied Physics Laboratory
 | 
			
		||||
# All Rights Reserved.
 | 
			
		||||
#
 | 
			
		||||
#    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.
 | 
			
		||||
 | 
			
		||||
"""Support signature verification."""
 | 
			
		||||
 | 
			
		||||
import base64
 | 
			
		||||
 | 
			
		||||
from castellan import key_manager
 | 
			
		||||
from cryptography import exceptions as crypto_exception
 | 
			
		||||
from cryptography.hazmat.backends import default_backend
 | 
			
		||||
from cryptography.hazmat.primitives.asymmetric import padding
 | 
			
		||||
from cryptography.hazmat.primitives.asymmetric import rsa
 | 
			
		||||
from cryptography.hazmat.primitives import hashes
 | 
			
		||||
from cryptography import x509
 | 
			
		||||
from oslo_log import log as logging
 | 
			
		||||
from oslo_utils import encodeutils
 | 
			
		||||
 | 
			
		||||
from glance.common import exception
 | 
			
		||||
from glance import i18n
 | 
			
		||||
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
_ = i18n._
 | 
			
		||||
_LE = i18n._LE
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# Note: This is the signature hash method, which is independent from the
 | 
			
		||||
# image data checksum hash method (which is handled elsewhere).
 | 
			
		||||
HASH_METHODS = {
 | 
			
		||||
    'SHA-224': hashes.SHA224(),
 | 
			
		||||
    'SHA-256': hashes.SHA256(),
 | 
			
		||||
    'SHA-384': hashes.SHA384(),
 | 
			
		||||
    'SHA-512': hashes.SHA512()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# These are the currently supported signature formats
 | 
			
		||||
(RSA_PSS,) = (
 | 
			
		||||
    'RSA-PSS',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
SIGNATURE_KEY_TYPES = {
 | 
			
		||||
    RSA_PSS
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# These are the currently supported certificate formats
 | 
			
		||||
(X_509,) = (
 | 
			
		||||
    'X.509',
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
CERTIFICATE_FORMATS = {
 | 
			
		||||
    X_509
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# These are the currently supported MGF formats, used for RSA-PSS signatures
 | 
			
		||||
MASK_GEN_ALGORITHMS = {
 | 
			
		||||
    'MGF1': padding.MGF1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Required image property names
 | 
			
		||||
(SIGNATURE, HASH_METHOD, KEY_TYPE, CERT_UUID) = (
 | 
			
		||||
    'signature',
 | 
			
		||||
    'signature_hash_method',
 | 
			
		||||
    'signature_key_type',
 | 
			
		||||
    'signature_certificate_uuid'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
# Optional image property names for RSA-PSS
 | 
			
		||||
(MASK_GEN_ALG, PSS_SALT_LENGTH) = (
 | 
			
		||||
    'mask_gen_algorithm',
 | 
			
		||||
    'pss_salt_length'
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def should_verify_signature(image_properties):
 | 
			
		||||
    """Determine whether a signature should be verified.
 | 
			
		||||
 | 
			
		||||
    Using the image properties, determine whether existing properties indicate
 | 
			
		||||
    that signature verification should be done.
 | 
			
		||||
 | 
			
		||||
    :param image_properties: the key-value properties about the image
 | 
			
		||||
    :return: True, if signature metadata properties exist, False otherwise
 | 
			
		||||
    """
 | 
			
		||||
    return (image_properties is not None and
 | 
			
		||||
            CERT_UUID in image_properties and
 | 
			
		||||
            HASH_METHOD in image_properties and
 | 
			
		||||
            SIGNATURE in image_properties and
 | 
			
		||||
            KEY_TYPE in image_properties)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def verify_signature(context, checksum_hash, image_properties):
 | 
			
		||||
    """Retrieve the image properties and use them to verify the signature.
 | 
			
		||||
 | 
			
		||||
    :param context: the user context for authentication
 | 
			
		||||
    :param checksum_hash: the 'checksum' hash of the image data
 | 
			
		||||
    :param image_properties: the key-value properties about the image
 | 
			
		||||
    :return: True if verification succeeds
 | 
			
		||||
    :raises: SignatureVerificationError if verification fails
 | 
			
		||||
    """
 | 
			
		||||
    if not should_verify_signature(image_properties):
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Required image properties for signature verification do not'
 | 
			
		||||
            ' exist. Cannot verify signature.')
 | 
			
		||||
 | 
			
		||||
    signature = get_signature(image_properties[SIGNATURE])
 | 
			
		||||
    hash_method = get_hash_method(image_properties[HASH_METHOD])
 | 
			
		||||
    signature_key_type = get_signature_key_type(
 | 
			
		||||
        image_properties[KEY_TYPE])
 | 
			
		||||
    public_key = get_public_key(context,
 | 
			
		||||
                                image_properties[CERT_UUID],
 | 
			
		||||
                                signature_key_type)
 | 
			
		||||
 | 
			
		||||
    # Initialize the verifier
 | 
			
		||||
    verifier = None
 | 
			
		||||
 | 
			
		||||
    # create the verifier based on the signature key type
 | 
			
		||||
    if signature_key_type == RSA_PSS:
 | 
			
		||||
        # retrieve other needed properties, or use defaults if not there
 | 
			
		||||
        if MASK_GEN_ALG in image_properties:
 | 
			
		||||
            mask_gen_algorithm = image_properties[MASK_GEN_ALG]
 | 
			
		||||
            if mask_gen_algorithm in MASK_GEN_ALGORITHMS:
 | 
			
		||||
                mgf = MASK_GEN_ALGORITHMS[mask_gen_algorithm](hash_method)
 | 
			
		||||
            else:
 | 
			
		||||
                raise exception.SignatureVerificationError(
 | 
			
		||||
                    'Invalid mask_gen_algorithm: %s' % mask_gen_algorithm)
 | 
			
		||||
        else:
 | 
			
		||||
            # default to MGF1
 | 
			
		||||
            mgf = padding.MGF1(hash_method)
 | 
			
		||||
        if PSS_SALT_LENGTH in image_properties:
 | 
			
		||||
            pss_salt_length = image_properties[PSS_SALT_LENGTH]
 | 
			
		||||
            try:
 | 
			
		||||
                salt_length = int(pss_salt_length)
 | 
			
		||||
            except ValueError:
 | 
			
		||||
                raise exception.SignatureVerificationError(
 | 
			
		||||
                    'Invalid pss_salt_length: %s' % pss_salt_length)
 | 
			
		||||
        else:
 | 
			
		||||
            # default to max salt length
 | 
			
		||||
            salt_length = padding.PSS.MAX_LENGTH
 | 
			
		||||
        # Create the verifier
 | 
			
		||||
        verifier = public_key.verifier(
 | 
			
		||||
            signature,
 | 
			
		||||
            padding.PSS(
 | 
			
		||||
                mgf=mgf,
 | 
			
		||||
                salt_length=salt_length
 | 
			
		||||
            ),
 | 
			
		||||
            hash_method
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
    if verifier:
 | 
			
		||||
        # Verify the signature
 | 
			
		||||
        verifier.update(checksum_hash)
 | 
			
		||||
        try:
 | 
			
		||||
            verifier.verify()
 | 
			
		||||
            return True
 | 
			
		||||
        except crypto_exception.InvalidSignature:
 | 
			
		||||
            raise exception.SignatureVerificationError(
 | 
			
		||||
                'Signature verification failed.')
 | 
			
		||||
    else:
 | 
			
		||||
        # Error creating the verifier
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Error occurred while verifying the signature')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_signature(signature_data):
 | 
			
		||||
    """Decode the signature data and returns the signature.
 | 
			
		||||
 | 
			
		||||
    :param siganture_data: the base64-encoded signature data
 | 
			
		||||
    :return: the decoded signature
 | 
			
		||||
    :raises: SignatureVerificationError if the signature data is malformatted
 | 
			
		||||
    """
 | 
			
		||||
    try:
 | 
			
		||||
        signature = base64.b64decode(signature_data)
 | 
			
		||||
    except TypeError:
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'The signature data was not properly encoded using base64')
 | 
			
		||||
 | 
			
		||||
    return signature
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_hash_method(hash_method_name):
 | 
			
		||||
    """Verify the hash method name and create the hash method.
 | 
			
		||||
 | 
			
		||||
    :param hash_method_name: the name of the hash method to retrieve
 | 
			
		||||
    :return: the hash method, a cryptography object
 | 
			
		||||
    :raises: SignatureVerificationError if the hash method name is invalid
 | 
			
		||||
    """
 | 
			
		||||
    if hash_method_name not in HASH_METHODS:
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Invalid signature hash method: %s' % hash_method_name)
 | 
			
		||||
 | 
			
		||||
    return HASH_METHODS[hash_method_name]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_signature_key_type(signature_key_type):
 | 
			
		||||
    """Verify the signature key type.
 | 
			
		||||
 | 
			
		||||
    :param signature_key_type: the key type of the signature
 | 
			
		||||
    :return: the validated signature key type
 | 
			
		||||
    :raises: SignatureVerificationError if the signature key type is invalid
 | 
			
		||||
    """
 | 
			
		||||
    if signature_key_type not in SIGNATURE_KEY_TYPES:
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Invalid signature key type: %s' % signature_key_type)
 | 
			
		||||
 | 
			
		||||
    return signature_key_type
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_public_key(context, signature_certificate_uuid, signature_key_type):
 | 
			
		||||
    """Create the public key object from a retrieved certificate.
 | 
			
		||||
 | 
			
		||||
    :param context: the user context for authentication
 | 
			
		||||
    :param signature_certificate_uuid: the uuid to use to retrieve the
 | 
			
		||||
                                       certificate
 | 
			
		||||
    :param signature_key_type: the key type of the signature
 | 
			
		||||
    :return: the public key cryptography object
 | 
			
		||||
    :raises: SignatureVerificationError if public key format is invalid
 | 
			
		||||
    """
 | 
			
		||||
    certificate = get_certificate(context, signature_certificate_uuid)
 | 
			
		||||
 | 
			
		||||
    # Note that this public key could either be
 | 
			
		||||
    # RSAPublicKey, DSAPublicKey, or EllipticCurvePublicKey
 | 
			
		||||
    public_key = certificate.public_key()
 | 
			
		||||
 | 
			
		||||
    # Confirm the type is of the type expected based on the signature key type
 | 
			
		||||
    if signature_key_type == RSA_PSS:
 | 
			
		||||
        if not isinstance(public_key, rsa.RSAPublicKey):
 | 
			
		||||
            raise exception.SignatureVerificationError(
 | 
			
		||||
                'Invalid public key type for signature key type: %s'
 | 
			
		||||
                % signature_key_type)
 | 
			
		||||
 | 
			
		||||
    return public_key
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def get_certificate(context, signature_certificate_uuid):
 | 
			
		||||
    """Create the certificate object from the retrieved certificate data.
 | 
			
		||||
 | 
			
		||||
    :param context: the user context for authentication
 | 
			
		||||
    :param signature_certificate_uuid: the uuid to use to retrieve the
 | 
			
		||||
                                       certificate
 | 
			
		||||
    :return: the certificate cryptography object
 | 
			
		||||
    :raises: SignatureVerificationError if the retrieval fails or the format
 | 
			
		||||
             is invalid
 | 
			
		||||
    """
 | 
			
		||||
    keymgr_api = key_manager.API()
 | 
			
		||||
 | 
			
		||||
    try:
 | 
			
		||||
        # The certificate retrieved here is a castellan certificate object
 | 
			
		||||
        cert = keymgr_api.get(context, signature_certificate_uuid)
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        # The problem encountered may be backend-specific, since castellan
 | 
			
		||||
        # can use different backends.  Rather than importing all possible
 | 
			
		||||
        # backends here, the generic "Exception" is used.
 | 
			
		||||
        msg = (_LE("Unable to retrieve certificate with ID %(id)s: %(e)s")
 | 
			
		||||
               % {'id': signature_certificate_uuid,
 | 
			
		||||
                  'e': encodeutils.exception_to_unicode(e)})
 | 
			
		||||
        LOG.error(msg)
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Unable to retrieve certificate with ID: %s'
 | 
			
		||||
            % signature_certificate_uuid)
 | 
			
		||||
 | 
			
		||||
    if cert.format not in CERTIFICATE_FORMATS:
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Invalid certificate format: %s' % cert.format)
 | 
			
		||||
 | 
			
		||||
    if cert.format == X_509:
 | 
			
		||||
        # castellan always encodes certificates in DER format
 | 
			
		||||
        cert_data = cert.get_encoded()
 | 
			
		||||
        certificate = x509.load_der_x509_certificate(cert_data,
 | 
			
		||||
                                                     default_backend())
 | 
			
		||||
 | 
			
		||||
    return certificate
 | 
			
		||||
@@ -23,6 +23,7 @@ from oslo_utils import encodeutils
 | 
			
		||||
from oslo_utils import excutils
 | 
			
		||||
 | 
			
		||||
from glance.common import exception
 | 
			
		||||
from glance.common import signature_utils
 | 
			
		||||
from glance.common import utils
 | 
			
		||||
import glance.domain.proxy
 | 
			
		||||
from glance import i18n
 | 
			
		||||
@@ -30,6 +31,7 @@ from glance import i18n
 | 
			
		||||
 | 
			
		||||
_ = i18n._
 | 
			
		||||
_LE = i18n._LE
 | 
			
		||||
_LI = i18n._LI
 | 
			
		||||
 | 
			
		||||
CONF = cfg.CONF
 | 
			
		||||
LOG = logging.getLogger(__name__)
 | 
			
		||||
@@ -375,6 +377,18 @@ class ImageProxy(glance.domain.proxy.Image):
 | 
			
		||||
                                 CONF.image_size_cap),
 | 
			
		||||
            size,
 | 
			
		||||
            context=self.context)
 | 
			
		||||
 | 
			
		||||
        # Verify the signature (if correct properties are present)
 | 
			
		||||
        if (signature_utils.should_verify_signature(
 | 
			
		||||
                self.image.extra_properties)):
 | 
			
		||||
            # NOTE(bpoulos): if verification fails, exception will be raised
 | 
			
		||||
            result = signature_utils.verify_signature(
 | 
			
		||||
                self.context, checksum, self.image.extra_properties)
 | 
			
		||||
            if result:
 | 
			
		||||
                msg = (_LI("Successfully verified signature for image "
 | 
			
		||||
                           "%s") % self.image.image_id)
 | 
			
		||||
                LOG.info(msg)
 | 
			
		||||
 | 
			
		||||
        self.image.locations = [{'url': location, 'metadata': loc_meta,
 | 
			
		||||
                                 'status': 'active'}]
 | 
			
		||||
        self.image.size = size
 | 
			
		||||
 
 | 
			
		||||
@@ -16,6 +16,7 @@ import glance_store
 | 
			
		||||
import mock
 | 
			
		||||
 | 
			
		||||
from glance.common import exception
 | 
			
		||||
from glance.common import signature_utils
 | 
			
		||||
import glance.location
 | 
			
		||||
from glance.tests.unit import base as unit_test_base
 | 
			
		||||
from glance.tests.unit import utils as unit_test_utils
 | 
			
		||||
@@ -41,12 +42,13 @@ class ImageRepoStub(object):
 | 
			
		||||
 | 
			
		||||
class ImageStub(object):
 | 
			
		||||
    def __init__(self, image_id, status=None, locations=None,
 | 
			
		||||
                 visibility=None):
 | 
			
		||||
                 visibility=None, extra_properties=None):
 | 
			
		||||
        self.image_id = image_id
 | 
			
		||||
        self.status = status
 | 
			
		||||
        self.locations = locations or []
 | 
			
		||||
        self.visibility = visibility
 | 
			
		||||
        self.size = 1
 | 
			
		||||
        self.extra_properties = extra_properties or {}
 | 
			
		||||
 | 
			
		||||
    def delete(self):
 | 
			
		||||
        self.status = 'deleted'
 | 
			
		||||
@@ -60,7 +62,8 @@ class ImageFactoryStub(object):
 | 
			
		||||
                  min_disk=0, min_ram=0, protected=False, owner=None,
 | 
			
		||||
                  disk_format=None, container_format=None,
 | 
			
		||||
                  extra_properties=None, tags=None, **other_args):
 | 
			
		||||
        return ImageStub(image_id, visibility=visibility, **other_args)
 | 
			
		||||
        return ImageStub(image_id, visibility=visibility,
 | 
			
		||||
                         extra_properties=extra_properties, **other_args)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeMemberRepo(object):
 | 
			
		||||
@@ -185,6 +188,62 @@ class TestStoreImage(utils.BaseTestCase):
 | 
			
		||||
                          self.store_api.get_from_backend,
 | 
			
		||||
                          image.locations[0]['url'], context={})
 | 
			
		||||
 | 
			
		||||
    def test_image_set_data_valid_signature(self):
 | 
			
		||||
        context = glance.context.RequestContext(user=USER1)
 | 
			
		||||
        extra_properties = {
 | 
			
		||||
            'signature_certificate_uuid': 'UUID',
 | 
			
		||||
            'signature_hash_method': 'METHOD',
 | 
			
		||||
            'signature_key_type': 'TYPE',
 | 
			
		||||
            'signature': 'VALID'
 | 
			
		||||
        }
 | 
			
		||||
        image_stub = ImageStub(UUID2, status='queued',
 | 
			
		||||
                               extra_properties=extra_properties)
 | 
			
		||||
        self.stubs.Set(signature_utils, 'verify_signature',
 | 
			
		||||
                       unit_test_utils.fake_verify_signature)
 | 
			
		||||
        image = glance.location.ImageProxy(image_stub, context,
 | 
			
		||||
                                           self.store_api, self.store_utils)
 | 
			
		||||
        image.set_data('YYYY', 4)
 | 
			
		||||
        self.assertEqual(UUID2, image.locations[0]['url'])
 | 
			
		||||
        self.assertEqual('Z', image.checksum)
 | 
			
		||||
        self.assertEqual('active', image.status)
 | 
			
		||||
 | 
			
		||||
    def test_image_set_data_invalid_signature(self):
 | 
			
		||||
        context = glance.context.RequestContext(user=USER1)
 | 
			
		||||
        extra_properties = {
 | 
			
		||||
            'signature_certificate_uuid': 'UUID',
 | 
			
		||||
            'signature_hash_method': 'METHOD',
 | 
			
		||||
            'signature_key_type': 'TYPE',
 | 
			
		||||
            'signature': 'INVALID'
 | 
			
		||||
        }
 | 
			
		||||
        image_stub = ImageStub(UUID2, status='queued',
 | 
			
		||||
                               extra_properties=extra_properties)
 | 
			
		||||
        self.stubs.Set(signature_utils, 'verify_signature',
 | 
			
		||||
                       unit_test_utils.fake_verify_signature)
 | 
			
		||||
        image = glance.location.ImageProxy(image_stub, context,
 | 
			
		||||
                                           self.store_api, self.store_utils)
 | 
			
		||||
        self.assertRaises(exception.SignatureVerificationError,
 | 
			
		||||
                          image.set_data,
 | 
			
		||||
                          'YYYY', 4)
 | 
			
		||||
 | 
			
		||||
    def test_image_set_data_invalid_signature_missing_metadata(self):
 | 
			
		||||
        context = glance.context.RequestContext(user=USER1)
 | 
			
		||||
        extra_properties = {
 | 
			
		||||
            'signature_hash_method': 'METHOD',
 | 
			
		||||
            'signature_key_type': 'TYPE',
 | 
			
		||||
            'signature': 'INVALID'
 | 
			
		||||
        }
 | 
			
		||||
        image_stub = ImageStub(UUID2, status='queued',
 | 
			
		||||
                               extra_properties=extra_properties)
 | 
			
		||||
        self.stubs.Set(signature_utils, 'verify_signature',
 | 
			
		||||
                       unit_test_utils.fake_verify_signature)
 | 
			
		||||
        image = glance.location.ImageProxy(image_stub, context,
 | 
			
		||||
                                           self.store_api, self.store_utils)
 | 
			
		||||
        image.set_data('YYYY', 4)
 | 
			
		||||
        self.assertEqual(UUID2, image.locations[0]['url'])
 | 
			
		||||
        self.assertEqual('Z', image.checksum)
 | 
			
		||||
        # Image is still active, since invalid signature was ignored
 | 
			
		||||
        self.assertEqual('active', image.status)
 | 
			
		||||
 | 
			
		||||
    def _add_image(self, context, image_id, data, len):
 | 
			
		||||
        image_stub = ImageStub(image_id, status='queued', locations=[])
 | 
			
		||||
        image = glance.location.ImageProxy(image_stub, context,
 | 
			
		||||
 
 | 
			
		||||
@@ -83,6 +83,15 @@ def fake_get_size_from_backend(uri, context=None):
 | 
			
		||||
    return 1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def fake_verify_signature(context, checksum_hash, image_properties):
 | 
			
		||||
    if (image_properties is not None and 'signature' in image_properties and
 | 
			
		||||
            image_properties['signature'] == 'VALID'):
 | 
			
		||||
        return True
 | 
			
		||||
    else:
 | 
			
		||||
        raise exception.SignatureVerificationError(
 | 
			
		||||
            'Signature verification failed.')
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class FakeDB(object):
 | 
			
		||||
 | 
			
		||||
    def __init__(self, initialize=True):
 | 
			
		||||
 
 | 
			
		||||
@@ -249,6 +249,15 @@ class TestImagesController(base.StoreClearingUnitTest):
 | 
			
		||||
                          self.controller.upload,
 | 
			
		||||
                          request, unit_test_utils.UUID2, 'YYYYYYY', 7)
 | 
			
		||||
 | 
			
		||||
    def test_upload_signature_verification_fails(self):
 | 
			
		||||
        request = unit_test_utils.get_fake_request()
 | 
			
		||||
        image = FakeImage()
 | 
			
		||||
        image.set_data = Raise(exception.SignatureVerificationError)
 | 
			
		||||
        self.image_repo.result = image
 | 
			
		||||
        self.assertRaises(webob.exc.HTTPBadRequest, self.controller.upload,
 | 
			
		||||
                          request, unit_test_utils.UUID1, 'YYYY', 4)
 | 
			
		||||
        self.assertEqual('killed', self.image_repo.saved_image.status)
 | 
			
		||||
 | 
			
		||||
    def test_image_size_limit_exceeded(self):
 | 
			
		||||
        request = unit_test_utils.get_fake_request()
 | 
			
		||||
        image = FakeImage()
 | 
			
		||||
 
 | 
			
		||||
@@ -67,3 +67,6 @@ glance-store>=0.7.1 # Apache-2.0
 | 
			
		||||
 | 
			
		||||
# Artifact repository
 | 
			
		||||
semantic-version>=2.3.1
 | 
			
		||||
 | 
			
		||||
castellan>=0.2.0 # Apache-2.0
 | 
			
		||||
cryptography>=1.0 # Apache-2.0
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user