Beispiel #1
0
 def update(self, req, image_id, tag_value):
     image_repo = self.gateway.get_repo(req.context,
                                        authorization_layer=False)
     try:
         image = image_repo.get(image_id)
         api_policy.ImageAPIPolicy(req.context, image,
                                   self.policy).modify_image()
         image.tags.add(tag_value)
         image_repo.save(image)
     except exception.NotFound:
         msg = _("Image %s not found.") % image_id
         LOG.warning(msg)
         raise webob.exc.HTTPNotFound(explanation=msg)
     except exception.Forbidden:
         msg = _("Not allowed to update tags for image %s.") % image_id
         LOG.warning(msg)
         raise webob.exc.HTTPForbidden(explanation=msg)
     except exception.Invalid as e:
         msg = (_("Could not update image: %s") %
                encodeutils.exception_to_unicode(e))
         LOG.warning(msg)
         raise webob.exc.HTTPBadRequest(explanation=msg)
     except exception.ImageTagLimitExceeded as e:
         msg = (_("Image tag limit exceeded for image %(id)s: %(e)s:") % {
             "id": image_id,
             "e": encodeutils.exception_to_unicode(e)
         })
         LOG.warning(msg)
         raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg)
Beispiel #2
0
 def test_add_image(self):
     generic_target = {
         'project_id': self.context.project_id,
         'owner': self.context.project_id,
         'visibility': 'private'
     }
     self.policy = policy.ImageAPIPolicy(self.context, {},
                                         enforcer=self.enforcer)
     self.policy.add_image()
     self.enforcer.enforce.assert_called_once_with(self.context,
                                                   'add_image',
                                                   generic_target)
Beispiel #3
0
    def test_add_image_translates_owner_failure(self):
        self.policy = policy.ImageAPIPolicy(self.context, {'owner': 'else'},
                                            enforcer=self.enforcer)
        # Make sure add_image works with no exception
        self.policy.add_image()

        # Make sure we don't get in the way of any other exceptions
        self.enforcer.enforce.side_effect = exception.Duplicate
        self.assertRaises(exception.Duplicate, self.policy.add_image)

        # If the exception is HTTPForbidden and the owner differs,
        # make sure we get the proper message translation
        self.enforcer.enforce.side_effect = webob.exc.HTTPForbidden('original')
        exc = self.assertRaises(webob.exc.HTTPForbidden, self.policy.add_image)
        self.assertIn('You are not permitted to create images owned by',
                      str(exc))

        # If the owner does not differ, make sure we get the original reason
        self.policy = policy.ImageAPIPolicy(self.context, {},
                                            enforcer=self.enforcer)
        exc = self.assertRaises(webob.exc.HTTPForbidden, self.policy.add_image)
        self.assertIn('original', str(exc))
Beispiel #4
0
    def delete(self, req, image_id, tag_value):
        image_repo = self.gateway.get_repo(req.context,
                                           authorization_layer=False)
        try:
            image = image_repo.get(image_id)
            api_policy.ImageAPIPolicy(req.context, image,
                                      self.policy).modify_image()

            if tag_value not in image.tags:
                raise webob.exc.HTTPNotFound()
            image.tags.remove(tag_value)
            image_repo.save(image)
        except exception.NotFound:
            msg = _("Image %s not found.") % image_id
            LOG.warning(msg)
            raise webob.exc.HTTPNotFound(explanation=msg)
        except exception.Forbidden:
            msg = _("Not allowed to delete tags for image %s.") % image_id
            LOG.warning(msg)
            raise webob.exc.HTTPForbidden(explanation=msg)
Beispiel #5
0
    def download(self, req, image_id):
        image_repo = self.gateway.get_repo(req.context,
                                           authorization_layer=False)
        try:
            image = image_repo.get(image_id)
            if image.status == 'deactivated' and not req.context.is_admin:
                msg = _('The requested image has been deactivated. '
                        'Image data download is forbidden.')
                raise exception.Forbidden(message=msg)
            # NOTE(abhishekk): This is the right place to verify whether
            # user has permission to download the image or not.
            api_pol = api_policy.ImageAPIPolicy(req.context, image,
                                                self.policy)
            api_pol.download_image()
        except exception.NotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.msg)
        except exception.Forbidden as e:
            LOG.debug("User not permitted to download image '%s'", image_id)
            raise webob.exc.HTTPForbidden(explanation=e.msg)

        return image
Beispiel #6
0
    def test_add_image_falls_back_to_legacy(self):
        self.config(enforce_secure_rbac=False)

        self.context.is_admin = False
        self.policy = policy.ImageAPIPolicy(self.context, {'owner': 'else'},
                                            enforcer=self.enforcer)
        self.assertRaises(exception.Forbidden, self.policy.add_image)

        # Make sure we're calling the legacy handler if secure_rbac is False
        with mock.patch('glance.api.v2.policy.check_admin_or_same_owner') as m:
            self.policy.add_image()
            m.assert_called_once_with(self.context, {
                'project_id': 'else',
                'owner': 'else',
                'visibility': 'private'
            })

        # Make sure we are not calling the legacy handler if
        # secure_rbac is being used. We won't fail the check because
        # our enforcer is a mock, just make sure we don't call that handler.
        self.config(enforce_secure_rbac=True)
        with mock.patch('glance.api.v2.policy.check_admin_or_same_owner') as m:
            self.policy.add_image()
            m.assert_not_called()
Beispiel #7
0
 def _enforce(self, req, image):
     """Authorize an action against our policies"""
     api_pol = api_policy.ImageAPIPolicy(req.context, image,
                                         self.policy)
     api_pol.download_image()
Beispiel #8
0
 def setUp(self):
     super(APIImagePolicy, self).setUp()
     self.image = mock.MagicMock()
     self.policy = policy.ImageAPIPolicy(self.context,
                                         self.image,
                                         enforcer=self.enforcer)
Beispiel #9
0
    def stage(self, req, image_id, data, size):
        try:
            ks_quota.enforce_image_staging_total(req.context,
                                                 req.context.owner)
        except exception.LimitExceeded as e:
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=str(e),
                                                      request=req)

        image_repo = self.gateway.get_repo(req.context,
                                           authorization_layer=False)
        # NOTE(abhishekk): stage API call does not have its own policy but
        # it requires get_image access, this is the right place to check
        # whether user has access to image or not
        try:
            image = image_repo.get(image_id)
        except exception.NotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.msg)

        api_pol = api_policy.ImageAPIPolicy(req.context,
                                            image,
                                            enforcer=self.policy)
        try:
            api_pol.modify_image()
        except exception.Forbidden as e:
            # NOTE(abhishekk): This will throw Forbidden if S-RBAC is not
            # enabled
            raise webob.exc.HTTPForbidden(explanation=e.msg)

        # NOTE(jokke): this is horrible way to do it but as long as
        # glance_store is in a shape it is, the only way. Don't hold me
        # accountable for it.
        # TODO(abhishekk): After removal of backend module from glance_store
        # need to change this to use multi_backend module.
        def _build_staging_store():
            conf = cfg.ConfigOpts()

            try:
                backend.register_opts(conf)
            except cfg.DuplicateOptError:
                pass

            conf.set_override('filesystem_store_datadir',
                              CONF.node_staging_uri[7:],
                              group='glance_store')
            staging_store = backend._load_store(conf, 'file')

            try:
                staging_store.configure()
            except AttributeError:
                msg = _("'node_staging_uri' is not set correctly. Could not "
                        "load staging store.")
                raise exception.BadStoreUri(message=msg)
            return staging_store

        # NOTE(abhishekk): Use reserved 'os_glance_staging_store' for staging
        # the data, the else part will be removed once multiple backend feature
        # is declared as stable.
        if CONF.enabled_backends:
            staging_store = glance_store.get_store_from_store_identifier(
                'os_glance_staging_store')
        else:
            staging_store = _build_staging_store()

        try:
            image.status = 'uploading'
            image_repo.save(image, from_state='queued')
            ks_quota.enforce_image_count_uploading(req.context,
                                                   req.context.owner)
            try:
                uri, size, id, store_info = staging_store.add(
                    image_id,
                    utils.LimitingReader(utils.CooperativeReader(data),
                                         CONF.image_size_cap), 0)
                image.size = size
            except glance_store.Duplicate:
                msg = _("The image %s has data on staging") % image_id
                raise webob.exc.HTTPConflict(explanation=msg)

            # NOTE(danms): Record this worker's
            # worker_self_reference_url in the image metadata so we
            # know who has the staging data.
            self_url = CONF.worker_self_reference_url or CONF.public_endpoint
            if self_url:
                image.extra_properties['os_glance_stage_host'] = self_url
            image_repo.save(image, from_state='uploading')

        except exception.NotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.msg)

        except glance_store.StorageFull as e:
            msg = _("Image storage media "
                    "is full: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._unstage(image_repo, image, staging_store)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.StorageQuotaFull as e:
            msg = _("Image exceeds the storage "
                    "quota: %s") % encodeutils.exception_to_unicode(e)
            LOG.debug(msg)
            self._unstage(image_repo, image, staging_store)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.ImageSizeLimitExceeded as e:
            msg = _("The incoming image is "
                    "too large: %s") % encodeutils.exception_to_unicode(e)
            LOG.debug(msg)
            self._unstage(image_repo, image, staging_store)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.LimitExceeded as e:
            LOG.debug(str(e))
            self._unstage(image_repo, image, staging_store)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=str(e),
                                                      request=req)

        except glance_store.StorageWriteDenied as e:
            msg = _("Insufficient permissions on image "
                    "storage media: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._unstage(image_repo, image, staging_store)
            raise webob.exc.HTTPServiceUnavailable(explanation=msg,
                                                   request=req)

        except exception.InvalidImageStatusTransition as e:
            msg = encodeutils.exception_to_unicode(e)
            LOG.debug(msg)
            raise webob.exc.HTTPConflict(explanation=e.msg, request=req)

        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.exception(
                    _LE("Failed to stage image data due to "
                        "internal error"))
                self._restore(image_repo, image)
Beispiel #10
0
    def upload(self, req, image_id, data, size):
        try:
            ks_quota.enforce_image_size_total(req.context, req.context.owner)
        except exception.LimitExceeded as e:
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=str(e),
                                                      request=req)

        backend = None
        if CONF.enabled_backends:
            backend = req.headers.get('x-image-meta-store',
                                      CONF.glance_store.default_backend)

            try:
                glance_store.get_store_from_store_identifier(backend)
            except glance_store.UnknownScheme as exc:
                raise webob.exc.HTTPBadRequest(explanation=exc.msg,
                                               request=req,
                                               content_type='text/plain')

        image_repo = self.gateway.get_repo(req.context,
                                           authorization_layer=False)
        image = None
        refresher = None
        cxt = req.context
        try:
            image = image_repo.get(image_id)
            # NOTE(abhishekk): This is the right place to check whether user
            # have permission to upload the image and remove the policy check
            # later from the policy layer.
            api_pol = api_policy.ImageAPIPolicy(req.context, image,
                                                self.policy)
            api_pol.upload_image()
            image.status = 'saving'
            try:
                # create a trust if backend is registry
                try:
                    # request user plugin for current token
                    user_plugin = req.environ.get('keystone.token_auth')
                    roles = []
                    # use roles from request environment because they
                    # are not transformed to lower-case unlike cxt.roles
                    for role_info in req.environ.get(
                            'keystone.token_info')['token']['roles']:
                        roles.append(role_info['name'])
                    refresher = trust_auth.TokenRefresher(
                        user_plugin, cxt.project_id, roles)
                except Exception as e:
                    LOG.info(
                        _LI("Unable to create trust: %s "
                            "Use the existing user token."),
                        encodeutils.exception_to_unicode(e))

                image_repo.save(image, from_state='queued')
                ks_quota.enforce_image_count_uploading(req.context,
                                                       req.context.owner)
                image.set_data(data, size, backend=backend)

                try:
                    image_repo.save(image, from_state='saving')
                except exception.NotAuthenticated:
                    if refresher is not None:
                        # request a new token to update an image in database
                        cxt.auth_token = refresher.refresh_token()
                        image_repo.save(image, from_state='saving')
                    else:
                        raise

                try:
                    # release resources required for re-auth
                    if refresher is not None:
                        refresher.release_resources()
                except Exception as e:
                    LOG.info(
                        _LI("Unable to delete trust %(trust)s: %(msg)s"), {
                            "trust": refresher.trust_id,
                            "msg": encodeutils.exception_to_unicode(e)
                        })

            except (glance_store.NotFound, exception.ImageNotFound,
                    exception.Conflict):
                msg = (_("Image %s could not be found after upload. "
                         "The image may have been deleted during the "
                         "upload, cleaning up the chunks uploaded.") %
                       image_id)
                LOG.warn(msg)
                # NOTE(sridevi): Cleaning up the uploaded chunks.
                try:
                    image.delete()
                except exception.ImageNotFound:
                    # NOTE(sridevi): Ignore this exception
                    pass
                raise webob.exc.HTTPGone(explanation=msg,
                                         request=req,
                                         content_type='text/plain')
            except exception.NotAuthenticated:
                msg = (_("Authentication error - the token may have "
                         "expired during file upload. Deleting image data for "
                         "%s.") % image_id)
                LOG.debug(msg)
                try:
                    image.delete()
                except exception.NotAuthenticated:
                    # NOTE: Ignore this exception
                    pass
                raise webob.exc.HTTPUnauthorized(explanation=msg,
                                                 request=req,
                                                 content_type='text/plain')
        except ValueError as e:
            LOG.debug("Cannot save data for image %(id)s: %(e)s", {
                'id': image_id,
                'e': encodeutils.exception_to_unicode(e)
            })
            self._restore(image_repo, image)
            raise webob.exc.HTTPBadRequest(
                explanation=encodeutils.exception_to_unicode(e))

        except glance_store.StoreAddDisabled:
            msg = _("Error in store configuration. Adding images to store "
                    "is disabled.")
            LOG.exception(msg)
            self._restore(image_repo, image)
            raise webob.exc.HTTPGone(explanation=msg,
                                     request=req,
                                     content_type='text/plain')

        except exception.InvalidImageStatusTransition as e:
            msg = encodeutils.exception_to_unicode(e)
            LOG.exception(msg)
            raise webob.exc.HTTPConflict(explanation=e.msg, request=req)

        except exception.Forbidden:
            msg = ("Not allowed to upload image data for image %s" % image_id)
            LOG.debug(msg)
            raise webob.exc.HTTPForbidden(explanation=msg, request=req)

        except exception.NotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.msg)

        except glance_store.StorageFull as e:
            msg = _("Image storage media "
                    "is full: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._restore(image_repo, image)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.StorageQuotaFull as e:
            msg = _("Image exceeds the storage "
                    "quota: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._restore(image_repo, image)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.ImageSizeLimitExceeded as e:
            msg = _("The incoming image is "
                    "too large: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._restore(image_repo, image)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=msg,
                                                      request=req)

        except exception.LimitExceeded as e:
            LOG.error(str(e))
            self._restore(image_repo, image)
            raise webob.exc.HTTPRequestEntityTooLarge(explanation=str(e),
                                                      request=req)

        except glance_store.StorageWriteDenied as e:
            msg = _("Insufficient permissions on image "
                    "storage media: %s") % encodeutils.exception_to_unicode(e)
            LOG.error(msg)
            self._restore(image_repo, image)
            raise webob.exc.HTTPServiceUnavailable(explanation=msg,
                                                   request=req)

        except cursive_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._restore(image_repo, image)
            raise webob.exc.HTTPBadRequest(explanation=msg)

        except webob.exc.HTTPGone:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE("Failed to upload image data due to HTTP error"))

        except webob.exc.HTTPError:
            with excutils.save_and_reraise_exception():
                LOG.error(_LE("Failed to upload image data due to HTTP error"))
                self._restore(image_repo, image)

        except Exception:
            with excutils.save_and_reraise_exception():
                LOG.error(
                    _LE("Failed to upload image data due to "
                        "internal error"))
                self._restore(image_repo, image)