def _alarm(self): self.conn = pecan.request.alarm_storage_conn auth_project = rbac.get_limited_to_project(pecan.request.headers) alarms = list(self.conn.get_alarms(alarm_id=self._id, project=auth_project)) if not alarms: raise AlarmNotFound(alarm=self._id, auth_project=auth_project) return alarms[0]
def get_auth_project(on_behalf_of=None): auth_project = rbac.get_limited_to_project(pecan.request.headers) created_by = pecan.request.headers.get('X-Project-Id') is_admin = auth_project is None if is_admin and on_behalf_of != created_by: auth_project = on_behalf_of return auth_project
def _alarm(self): self.conn = pecan.request.alarm_storage_conn auth_project = rbac.get_limited_to_project(pecan.request.headers) alarms = list( self.conn.get_alarms(alarm_id=self._id, project=auth_project)) if not alarms: raise base.AlarmNotFound(alarm=self._id, auth_project=auth_project) return alarms[0]
def _force_visibility(self, visibility_field): """Force visibility field. If the tenant is not admin insert an extra "and <visibility_field>=<tenant's project_id>" clause to the query. """ authorized_project = rbac.get_limited_to_project(pecan.request.headers) is_admin = authorized_project is None if not is_admin: self._restrict_to_project(authorized_project, visibility_field) self._check_cross_project_references(authorized_project, visibility_field)
def _verify_query_segregation(query, auth_project=None): """Ensure non-admin queries are not constrained to another project.""" auth_project = (auth_project or rbac.get_limited_to_project(pecan.request.headers)) if not auth_project: return for q in query: if q.field in ('project', 'project_id') and auth_project != q.value: raise base.ProjectNotAuthorized(q.value)
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ rbac.enforce("get_resource", pecan.request) authorized_project = rbac.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources(resource=resource_id, project=authorized_project)) if not resources: raise base.EntityNotFound(_("Resource"), resource_id) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ rbac.enforce('get_resource', pecan.request) authorized_project = rbac.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources( resource=resource_id, project=authorized_project)) if not resources: raise base.EntityNotFound(_('Resource'), resource_id) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def history(self, q=None): """Assembles the alarm history requested. :param q: Filter rules for the changes to be described. """ rbac.enforce("alarm_history", pecan.request) q = q or [] # allow history to be returned for deleted alarms, but scope changes # returned to those carried out on behalf of the auth'd tenant, to # avoid inappropriate cross-tenant visibility of alarm history auth_project = rbac.get_limited_to_project(pecan.request.headers) conn = pecan.request.alarm_storage_conn kwargs = v2_utils.query_to_kwargs(q, conn.get_alarm_changes, ["on_behalf_of", "alarm_id"]) return [AlarmChange.from_db_model(ac) for ac in conn.get_alarm_changes(self._id, auth_project, **kwargs)]
def get_auth_project(on_behalf_of=None): # when an alarm is created by an admin on behalf of another tenant # we must ensure for: # - threshold alarm, that an implicit query constraint on project_id is # added so that admin-level visibility on statistics is not leaked # - combination alarm, that alarm ids verification is scoped to # alarms owned by the alarm project. # hence for null auth_project (indicating admin-ness) we check if # the creating tenant differs from the tenant on whose behalf the # alarm is being created auth_project = rbac.get_limited_to_project(pecan.request.headers) created_by = pecan.request.headers.get('X-Project-Id') is_admin = auth_project is None if is_admin and on_behalf_of != created_by: auth_project = on_behalf_of return auth_project
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ rbac.enforce("get_resource", pecan.request) # In case we have special character in resource id, for example, swift # can generate samples with resource id like # 29f809d9-88bb-4c40-b1ba-a77a1fcf8ceb/glance resource_id = urllib.parse.unquote(resource_id) authorized_project = rbac.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources(resource=resource_id, project=authorized_project)) if not resources: raise base.EntityNotFound(_("Resource"), resource_id) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def get_one(self, resource_id): """Retrieve details about one resource. :param resource_id: The UUID of the resource. """ rbac.enforce('get_resource', pecan.request) # In case we have special character in resource id, for example, swift # can generate samples with resource id like # 29f809d9-88bb-4c40-b1ba-a77a1fcf8ceb/glance resource_id = urllib.parse.unquote(resource_id) authorized_project = rbac.get_limited_to_project(pecan.request.headers) resources = list(pecan.request.storage_conn.get_resources( resource=resource_id, project=authorized_project)) if not resources: raise base.EntityNotFound(_('Resource'), resource_id) return Resource.from_db_and_links(resources[0], self._resource_links(resource_id))
def history(self, q=None): """Assembles the alarm history requested. :param q: Filter rules for the changes to be described. """ rbac.enforce('alarm_history', pecan.request) q = q or [] # allow history to be returned for deleted alarms, but scope changes # returned to those carried out on behalf of the auth'd tenant, to # avoid inappropriate cross-tenant visibility of alarm history auth_project = rbac.get_limited_to_project(pecan.request.headers) conn = pecan.request.alarm_storage_conn kwargs = v2_utils.query_to_kwargs( q, conn.get_alarm_changes, ['on_behalf_of', 'alarm_id']) return [AlarmChange.from_db_model(ac) for ac in conn.get_alarm_changes(self._id, auth_project, **kwargs)]
def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS max_actions = cfg.CONF.alarm.alarm_max_actions for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue action_set = set(actions) if len(actions) != len(action_set): LOG.info( _LI('duplicate actions are found: %s, ' 'remove duplicate ones') % actions) actions = list(action_set) setattr(alarm, actions_name, actions) if 0 < max_actions < len(actions): error = _('%(name)s count exceeds maximum value ' '%(maximum)d') % { "name": actions_name, "maximum": max_actions } raise base.ClientSideError(error) limited = rbac.get_limited_to_project(pecan.request.headers) for action in actions: try: url = netutils.urlsplit(action) except Exception: error = _("Unable to parse action %s") % action raise base.ClientSideError(error) if url.scheme not in actions_schema: error = _("Unsupported action %s") % action raise base.ClientSideError(error) if limited and url.scheme in ('log', 'test'): error = _('You are not authorized to create ' 'action: %s') % action raise base.ClientSideError(error, status_code=401)
def check_alarm_actions(alarm): actions_schema = ceilometer_alarm.NOTIFIER_SCHEMAS max_actions = cfg.CONF.alarm.alarm_max_actions for state in state_kind: actions_name = state.replace(" ", "_") + '_actions' actions = getattr(alarm, actions_name) if not actions: continue action_set = set(actions) if len(actions) != len(action_set): LOG.info(_LI('duplicate actions are found: %s, ' 'remove duplicate ones') % actions) actions = list(action_set) setattr(alarm, actions_name, actions) if 0 < max_actions < len(actions): error = _('%(name)s count exceeds maximum value ' '%(maximum)d') % {"name": actions_name, "maximum": max_actions} raise base.ClientSideError(error) limited = rbac.get_limited_to_project(pecan.request.headers) for action in actions: try: url = netutils.urlsplit(action) except Exception: error = _("Unable to parse action %s") % action raise base.ClientSideError(error) if url.scheme not in actions_schema: error = _("Unsupported action %s") % action raise base.ClientSideError(error) if limited and url.scheme in ('log', 'test'): error = _('You are not authorized to create ' 'action: %s') % action raise base.ClientSideError(error, status_code=401)
def post(self, samples): """Post a list of new Samples to Telemetry. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput( 'counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) published_samples.append(published_sample) s.message_id = published_sample.id with pecan.request.pipeline_manager.publisher( context.get_admin_context()) as publisher: publisher(published_samples) return samples
def post(self, direct="", samples=None): """Post a list of new Samples to Telemetry. :param direct: a flag indicates whether the samples will be posted directly to storage or not. :param samples: a list of samples within the request body. """ rbac.enforce("create_samples", pecan.request) direct = strutils.bool_from_string(direct) if not samples: msg = _("Samples should be included in request body") raise base.ClientSideError(msg) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get("X-Project-Id") def_user_id = pecan.request.headers.get("X-User-Id") published_samples = [] for s in samples: if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput("counter_name", s.counter_name, "should be %s" % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput("message_id", s.message_id, "The message_id must not be set") if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput( "counter_type", s.counter_type, "The counter type must be: " + ", ".join(sample.TYPES) ) s.user_id = s.user_id or def_user_id s.project_id = s.project_id or def_project_id s.source = "%s:%s" % (s.project_id, (s.source or def_source)) s.timestamp = s.timestamp or now if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = "can not post samples to other projects" raise wsme.exc.InvalidInput("project_id", s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator="."), source=s.source, ) s.message_id = published_sample.id sample_dict = publisher_utils.meter_message_from_counter( published_sample, pecan.request.cfg.publisher.telemetry_secret ) if direct: ts = timeutils.parse_isotime(sample_dict["timestamp"]) sample_dict["timestamp"] = timeutils.normalize_time(ts) pecan.request.storage_conn.record_metering_data(sample_dict) else: published_samples.append(sample_dict) if not direct: pecan.request.notifier.sample( {"user": def_user_id, "tenant": def_project_id, "is_admin": True}, "telemetry.api", {"samples": published_samples}, ) return samples
def post(self, direct='', samples=None): """Post a list of new Samples to Telemetry. :param direct: a flag indicates whether the samples will be posted directly to storage or not. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) direct = strutils.bool_from_string(direct) if not samples: msg = _('Samples should be included in request body') raise base.ClientSideError(msg) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput('counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) s.message_id = published_sample.id sample_dict = publisher_utils.meter_message_from_counter( published_sample, cfg.CONF.publisher.telemetry_secret) if direct: ts = timeutils.parse_isotime(sample_dict['timestamp']) sample_dict['timestamp'] = timeutils.normalize_time(ts) pecan.request.storage_conn.record_metering_data(sample_dict) else: published_samples.append(sample_dict) if not direct: ctxt = context.RequestContext(user=def_user_id, tenant=def_project_id, is_admin=True) notifier = pecan.request.notifier notifier.info(ctxt.to_dict(), 'telemetry.api', published_samples) return samples
def post(self, direct='', samples=None): """Post a list of new Samples to Telemetry. :param direct: a flag indicates whether the samples will be posted directly to storage or not. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) direct = strutils.bool_from_string(direct) if not samples: msg = _('Samples should be included in request body') raise base.ClientSideError(msg) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput( 'counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) s.message_id = published_sample.id sample_dict = publisher_utils.meter_message_from_counter( published_sample, cfg.CONF.publisher.telemetry_secret) if direct: ts = timeutils.parse_isotime(sample_dict['timestamp']) sample_dict['timestamp'] = timeutils.normalize_time(ts) pecan.request.storage_conn.record_metering_data(sample_dict) else: published_samples.append(sample_dict) if not direct: pecan.request.notifier.sample( { 'user': def_user_id, 'tenant': def_project_id, 'is_admin': True }, 'telemetry.api', {'samples': published_samples}) return samples
def post(self, samples): """Post a list of new Samples to Telemetry. :param samples: a list of samples within the request body. """ rbac.enforce('create_samples', pecan.request) now = timeutils.utcnow() auth_project = rbac.get_limited_to_project(pecan.request.headers) def_source = pecan.request.cfg.sample_source def_project_id = pecan.request.headers.get('X-Project-Id') def_user_id = pecan.request.headers.get('X-User-Id') published_samples = [] for s in samples: for p in pecan.request.pipeline_manager.pipelines: if p.support_meter(s.counter_name): break else: message = _("The metric %s is not supported by metering " "pipeline configuration.") % s.counter_name raise base.ClientSideError(message, status_code=409) if self.meter_name != s.counter_name: raise wsme.exc.InvalidInput('counter_name', s.counter_name, 'should be %s' % self.meter_name) if s.message_id: raise wsme.exc.InvalidInput('message_id', s.message_id, 'The message_id must not be set') if s.counter_type not in sample.TYPES: raise wsme.exc.InvalidInput('counter_type', s.counter_type, 'The counter type must be: ' + ', '.join(sample.TYPES)) s.user_id = (s.user_id or def_user_id) s.project_id = (s.project_id or def_project_id) s.source = '%s:%s' % (s.project_id, (s.source or def_source)) s.timestamp = (s.timestamp or now) if auth_project and auth_project != s.project_id: # non admin user trying to cross post to another project_id auth_msg = 'can not post samples to other projects' raise wsme.exc.InvalidInput('project_id', s.project_id, auth_msg) published_sample = sample.Sample( name=s.counter_name, type=s.counter_type, unit=s.counter_unit, volume=s.counter_volume, user_id=s.user_id, project_id=s.project_id, resource_id=s.resource_id, timestamp=s.timestamp.isoformat(), resource_metadata=utils.restore_nesting(s.resource_metadata, separator='.'), source=s.source) published_samples.append(published_sample) s.message_id = published_sample.id with pecan.request.pipeline_manager.publisher( context.get_admin_context()) as publisher: publisher(published_samples) return samples