diff options
30 files changed, 937 insertions, 323 deletions
@@ -54,6 +54,24 @@ pre-run: playbooks/enable-fips.yaml - job: + name: designate-bind9-scoped-tokens + post-run: playbooks/designate-bind9/post.yaml + parent: designate-base + vars: + devstack_local_conf: + post-config: + $DESIGNATE_CONF: + oslo_policy: + enforce_scope: True + enforce_new_defaults: True + test-config: + "$TEMPEST_CONFIG": + enforce_scope: + designate: True + dns_feature_enabled: + enforce_new_defaults: True + +- job: name: designate-pdns4 post-run: playbooks/designate-pdns4/post.yaml parent: designate-base @@ -135,6 +153,7 @@ - designate-bind9 - designate-bind9-centos8stream-fips: voting: false + - designate-bind9-scoped-tokens - designate-pdns4 - designate-grenade-pdns4 - designate-ipv6-only-pdns4 @@ -143,6 +162,7 @@ gate: jobs: - designate-bind9 + - designate-bind9-scoped-tokens - designate-pdns4 - designate-grenade-pdns4 - designate-ipv6-only-pdns4 diff --git a/designate/api/middleware.py b/designate/api/middleware.py index 07fd46b1..2a451606 100644 --- a/designate/api/middleware.py +++ b/designate/api/middleware.py @@ -128,14 +128,13 @@ class KeystoneContextMiddleware(ContextMiddleware): pass tenant_id = headers.get('X-Tenant-ID') - if tenant_id is None: - return flask.Response(status=401) catalog = None if headers.get('X-Service-Catalog'): catalog = jsonutils.loads(headers.get('X-Service-Catalog')) roles = headers.get('X-Roles').split(',') + system_scope = headers.get('Openstack-System-Scope') try: self.make_context( @@ -144,7 +143,8 @@ class KeystoneContextMiddleware(ContextMiddleware): user_id=headers.get('X-User-ID'), project_id=tenant_id, roles=roles, - service_catalog=catalog + service_catalog=catalog, + system_scope=system_scope ) except exceptions.Forbidden: return flask.Response(status=403) diff --git a/designate/api/v2/controllers/zones/tasks/exports.py b/designate/api/v2/controllers/zones/tasks/exports.py index 60a852b9..29d97c78 100644 --- a/designate/api/v2/controllers/zones/tasks/exports.py +++ b/designate/api/v2/controllers/zones/tasks/exports.py @@ -16,10 +16,11 @@ import pecan from oslo_log import log as logging +from designate.api.v2.controllers import rest +from designate.common import constants from designate import exceptions from designate import policy from designate import utils -from designate.api.v2.controllers import rest from designate.objects.adapters import DesignateAdapter LOG = logging.getLogger(__name__) @@ -31,7 +32,11 @@ class ZoneExportController(rest.RestController): @utils.validate_uuid('export_id') def get_all(self, export_id): context = pecan.request.environ['context'] - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('zone_export', context, target) export = self.central_api.get_zone_export(context, export_id) diff --git a/designate/central/service.py b/designate/central/service.py index 35cd0e3c..fe3fb04c 100644 --- a/designate/central/service.py +++ b/designate/central/service.py @@ -33,6 +33,7 @@ from oslo_config import cfg import oslo_messaging as messaging from oslo_log import log as logging +from designate.common import constants from designate import context as dcontext from designate import coordination from designate import exceptions @@ -483,6 +484,12 @@ class Service(service.RPCService): raise exceptions.InvalidTTL('TTL is below the minimum: %s' % min_ttl) + def _is_valid_project_id(self, project_id): + if project_id is None: + raise exceptions.MissingProjectID( + "A project ID must be specified when not using a project " + "scoped token.") + def _increment_zone_serial(self, context, zone, set_delayed_notify=False): """Update the zone serial and the SOA record Optionally set delayed_notify to have PM issue delayed notify @@ -660,17 +667,30 @@ class Service(service.RPCService): # Quota Methods @rpc.expected_exceptions() def get_quotas(self, context, tenant_id): - target = {'tenant_id': tenant_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: tenant_id, + 'all_tenants': context.all_tenants} + else: + target = {'tenant_id': tenant_id} policy.check('get_quotas', context, target) - if tenant_id != context.project_id and not context.all_tenants: + # TODO(johnsom) Deprecated since Wallaby, remove with legacy default + # policies. System scoped admin doesn't have a project_id + if (tenant_id != context.project_id and not context.all_tenants and not + policy.enforce_new_defaults()): raise exceptions.Forbidden() return self.quota.get_quotas(context, tenant_id) @rpc.expected_exceptions() def get_quota(self, context, tenant_id, resource): - target = {'tenant_id': tenant_id, 'resource': resource} + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: tenant_id, + 'resource': resource + } + else: + target = {'tenant_id': tenant_id, 'resource': resource} policy.check('get_quota', context, target) return self.quota.get_quota(context, tenant_id, resource) @@ -678,21 +698,34 @@ class Service(service.RPCService): @rpc.expected_exceptions() @transaction def set_quota(self, context, tenant_id, resource, hard_limit): - target = { - 'tenant_id': tenant_id, - 'resource': resource, - 'hard_limit': hard_limit, - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: tenant_id, + 'resource': resource, + 'hard_limit': hard_limit, + } + else: + target = { + 'tenant_id': tenant_id, + 'resource': resource, + 'hard_limit': hard_limit, + } policy.check('set_quota', context, target) - if tenant_id != context.project_id and not context.all_tenants: + # TODO(johnsom) Deprecated since Wallaby, remove with legacy default + # policies. System scoped admin doesn't have a project_id + if (tenant_id != context.project_id and not context.all_tenants and not + policy.enforce_new_defaults()): raise exceptions.Forbidden() return self.quota.set_quota(context, tenant_id, resource, hard_limit) @transaction def reset_quotas(self, context, tenant_id): - target = {'tenant_id': tenant_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: tenant_id} + else: + target = {'tenant_id': tenant_id} policy.check('reset_quotas', context, target) self.quota.reset_quotas(context, tenant_id) @@ -808,9 +841,10 @@ class Service(service.RPCService): @rpc.expected_exceptions() def get_tenant(self, context, tenant_id): - target = { - 'tenant_id': tenant_id - } + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: tenant_id} + else: + target = {'tenant_id': tenant_id} policy.check('get_tenant', context, target) @@ -857,13 +891,21 @@ class Service(service.RPCService): # Default to creating in the current users tenant zone.tenant_id = zone.tenant_id or context.project_id - target = { - 'tenant_id': zone.tenant_id, - 'zone_name': zone.name - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zone.tenant_id, + 'zone_name': zone.name + } + else: + target = { + 'tenant_id': zone.tenant_id, + 'zone_name': zone.name + } policy.check('create_zone', context, target) + self._is_valid_project_id(zone.tenant_id) + # Ensure the tenant has enough quota to continue self._enforce_zone_quota(context, zone.tenant_id) @@ -971,11 +1013,19 @@ class Service(service.RPCService): """ zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } + policy.check('get_zone', context, target) return zone @@ -988,11 +1038,19 @@ class Service(service.RPCService): pool_id = cfg.CONF['service:central'].default_pool_id else: zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } pool_id = zone.pool_id policy.check('get_zone_ns_records', context, target) @@ -1010,7 +1068,11 @@ class Service(service.RPCService): sort_key=None, sort_dir=None): """List existing zones including the ones flagged for deletion. """ - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('find_zones', context, target) return self.storage.find_zones(context, criterion, marker, limit, @@ -1018,7 +1080,11 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_zone(self, context, criterion=None): - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('find_zone', context, target) return self.storage.find_zone(context, criterion) @@ -1032,11 +1098,19 @@ class Service(service.RPCService): :returns: updated zone """ - target = { - 'zone_id': zone.obj_get_original_value('id'), - 'zone_name': zone.obj_get_original_value('name'), - 'tenant_id': zone.obj_get_original_value('tenant_id'), - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone.obj_get_original_value('id'), + 'zone_name': zone.obj_get_original_value('name'), + constants.RBAC_PROJECT_ID: ( + zone.obj_get_original_value('tenant_id')), + } + else: + target = { + 'zone_id': zone.obj_get_original_value('id'), + 'zone_name': zone.obj_get_original_value('name'), + 'tenant_id': zone.obj_get_original_value('tenant_id'), + } policy.check('update_zone', context, target) @@ -1102,11 +1176,18 @@ class Service(service.RPCService): """ zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } if hasattr(context, 'abandon') and context.abandon: policy.check('abandon_zone', context, target) @@ -1161,11 +1242,18 @@ class Service(service.RPCService): def xfr_zone(self, context, zone_id): zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } policy.check('xfr_zone', context, target) @@ -1191,9 +1279,14 @@ class Service(service.RPCService): if criterion is None: criterion = {} - target = { - 'tenant_id': criterion.get('tenant_id', None) - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: criterion.get('tenant_id', None) + } + else: + target = { + 'tenant_id': criterion.get('tenant_id', None) + } policy.check('count_zones', context, target) @@ -1235,11 +1328,18 @@ class Service(service.RPCService): def touch_zone(self, context, zone_id): zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } policy.check('touch_zone', context, target) @@ -1268,13 +1368,22 @@ class Service(service.RPCService): if zone.action == 'DELETE': raise exceptions.BadRequest('Can not update a deleting zone') - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'zone_type': zone.type, - 'recordset_name': recordset.name, - 'tenant_id': zone.tenant_id, - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_name': recordset.name, + constants.RBAC_PROJECT_ID: zone.tenant_id, + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_name': recordset.name, + 'tenant_id': zone.tenant_id, + } policy.check('create_recordset', context, target) @@ -1359,12 +1468,20 @@ class Service(service.RPCService): else: zone = self.storage.get_zone(context, recordset.zone_id) - target = { - 'zone_id': zone.id, - 'zone_name': zone.name, - 'recordset_id': recordset.id, - 'tenant_id': zone.tenant_id, - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone.id, + 'zone_name': zone.name, + 'recordset_id': recordset.id, + constants.RBAC_PROJECT_ID: zone.tenant_id, + } + else: + target = { + 'zone_id': zone.id, + 'zone_name': zone.name, + 'recordset_id': recordset.id, + 'tenant_id': zone.tenant_id, + } policy.check('get_recordset', context, target) @@ -1377,7 +1494,11 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_recordsets(self, context, criterion=None, marker=None, limit=None, sort_key=None, sort_dir=None, force_index=False): - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('find_recordsets', context, target) recordsets = self.storage.find_recordsets(context, criterion, marker, @@ -1388,7 +1509,10 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_recordset(self, context, criterion=None): - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} policy.check('find_recordset', context, target) recordset = self.storage.find_recordset(context, criterion) @@ -1432,13 +1556,22 @@ class Service(service.RPCService): if zone.action == 'DELETE': raise exceptions.BadRequest('Can not update a deleting zone') - target = { - 'zone_id': recordset.obj_get_original_value('zone_id'), - 'zone_type': zone.type, - 'recordset_id': recordset.obj_get_original_value('id'), - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': recordset.obj_get_original_value('zone_id'), + 'zone_type': zone.type, + 'recordset_id': recordset.obj_get_original_value('id'), + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': recordset.obj_get_original_value('zone_id'), + 'zone_type': zone.type, + 'recordset_id': recordset.obj_get_original_value('id'), + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } policy.check('update_recordset', context, target) @@ -1496,13 +1629,22 @@ class Service(service.RPCService): if zone.action == 'DELETE': raise exceptions.BadRequest('Can not update a deleting zone') - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'zone_type': zone.type, - 'recordset_id': recordset.id, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset.id, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset.id, + 'tenant_id': zone.tenant_id + } policy.check('delete_recordset', context, target) @@ -1545,9 +1687,12 @@ class Service(service.RPCService): if criterion is None: criterion = {} - target = { - 'tenant_id': criterion.get('tenant_id', None) - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: criterion.get('tenant_id', None) + } + else: + target = {'tenant_id': criterion.get('tenant_id', None)} policy.check('count_recordsets', context, target) @@ -1567,14 +1712,24 @@ class Service(service.RPCService): recordset = self.storage.get_recordset(context, recordset_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'zone_type': zone.type, - 'recordset_id': recordset_id, - 'recordset_name': recordset.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'tenant_id': zone.tenant_id + } policy.check('create_record', context, target) @@ -1621,14 +1776,24 @@ class Service(service.RPCService): if recordset.id != record.recordset_id: raise exceptions.RecordNotFound() - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'recordset_id': recordset_id, - 'recordset_name': recordset.name, - 'record_id': record.id, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record.id, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record.id, + 'tenant_id': zone.tenant_id + } policy.check('get_record', context, target) @@ -1637,7 +1802,11 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_records(self, context, criterion=None, marker=None, limit=None, sort_key=None, sort_dir=None): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} policy.check('find_records', context, target) return self.storage.find_records(context, criterion, marker, limit, @@ -1645,7 +1814,11 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_record(self, context, criterion=None): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} policy.check('find_record', context, target) return self.storage.find_record(context, criterion) @@ -1679,15 +1852,26 @@ class Service(service.RPCService): raise exceptions.BadRequest('Moving a recordset between ' 'recordsets is not allowed') - target = { - 'zone_id': record.obj_get_original_value('zone_id'), - 'zone_name': zone.name, - 'zone_type': zone.type, - 'recordset_id': record.obj_get_original_value('recordset_id'), - 'recordset_name': recordset.name, - 'record_id': record.obj_get_original_value('id'), - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': record.obj_get_original_value('zone_id'), + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': record.obj_get_original_value('recordset_id'), + 'recordset_name': recordset.name, + 'record_id': record.obj_get_original_value('id'), + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': record.obj_get_original_value('zone_id'), + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': record.obj_get_original_value('recordset_id'), + 'recordset_name': recordset.name, + 'record_id': record.obj_get_original_value('id'), + 'tenant_id': zone.tenant_id + } policy.check('update_record', context, target) @@ -1741,15 +1925,26 @@ class Service(service.RPCService): if recordset.id != record.recordset_id: raise exceptions.RecordNotFound() - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'zone_type': zone.type, - 'recordset_id': recordset_id, - 'recordset_name': recordset.name, - 'record_id': record.id, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record.id, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'zone_type': zone.type, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record.id, + 'tenant_id': zone.tenant_id + } policy.check('delete_record', context, target) @@ -1785,9 +1980,12 @@ class Service(service.RPCService): if criterion is None: criterion = {} - target = { - 'tenant_id': criterion.get('tenant_id', None) - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: criterion.get('tenant_id', None) + } + else: + target = {'tenant_id': criterion.get('tenant_id', None)} policy.check('count_records', context, target) return self.storage.count_records(context, criterion) @@ -1814,11 +2012,18 @@ class Service(service.RPCService): def sync_zone(self, context, zone_id): zone = self.storage.get_zone(context, zone_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'tenant_id': zone.tenant_id + } policy.check('diagnostics_sync_zone', context, target) @@ -1830,14 +2035,24 @@ class Service(service.RPCService): zone = self.storage.get_zone(context, zone_id) recordset = self.storage.get_recordset(context, recordset_id) - target = { - 'zone_id': zone_id, - 'zone_name': zone.name, - 'recordset_id': recordset_id, - 'recordset_name': recordset.name, - 'record_id': record_id, - 'tenant_id': zone.tenant_id - } + if policy.enforce_new_defaults(): + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record_id, + constants.RBAC_PROJECT_ID: zone.tenant_id + } + else: + target = { + 'zone_id': zone_id, + 'zone_name': zone.name, + 'recordset_id': recordset_id, + 'recordset_name': recordset.name, + 'record_id': record_id, + 'tenant_id': zone.tenant_id + } policy.check('diagnostics_sync_record', context, target) @@ -2257,6 +2472,8 @@ class Service(service.RPCService): policy.check('create_pool', context) + self._is_valid_project_id(pool.tenant_id) + created_pool = self.storage.create_pool(context, pool) return created_pool @@ -2508,9 +2725,11 @@ class Service(service.RPCService): if zone.action == 'DELETE': raise exceptions.BadRequest('Can not transfer a deleting zone') - target = { - 'tenant_id': zone.tenant_id, - } + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: zone.tenant_id} + else: + target = {'tenant_id': zone.tenant_id} + policy.check('create_zone_transfer_request', context, target) zone_transfer_request.key = self._transfer_key_generator() @@ -2518,6 +2737,8 @@ class Service(service.RPCService): if zone_transfer_request.tenant_id is None: zone_transfer_request.tenant_id = context.project_id + self._is_valid_project_id(zone_transfer_request.tenant_id) + created_zone_transfer_request = \ self.storage.create_zone_transfer_request( context, zone_transfer_request) @@ -2534,10 +2755,18 @@ class Service(service.RPCService): elevated_context, zone_transfer_request_id) LOG.info('Target Tenant ID found - using scoped policy') - target = { - 'target_tenant_id': zone_transfer_request.target_tenant_id, - 'tenant_id': zone_transfer_request.tenant_id, - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_TARGET_PROJECT_ID: (zone_transfer_request. + target_tenant_id), + constants.RBAC_PROJECT_ID: zone_transfer_request.tenant_id, + } + else: + target = { + 'target_tenant_id': zone_transfer_request.target_tenant_id, + 'tenant_id': zone_transfer_request.tenant_id, + } + policy.check('get_zone_transfer_request', context, target) return zone_transfer_request @@ -2557,9 +2786,15 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_zone_transfer_request(self, context, criterion): - target = { - 'tenant_id': context.project_id, - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: context.project_id, + } + else: + target = { + 'tenant_id': context.project_id, + } + policy.check('find_zone_transfer_request', context, target) return self.storage.find_zone_transfer_requests(context, criterion) @@ -2571,9 +2806,14 @@ class Service(service.RPCService): if 'zone_id' in zone_transfer_request.obj_what_changed(): raise exceptions.InvalidOperation('Zone cannot be changed') - target = { - 'tenant_id': zone_transfer_request.tenant_id, - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zone_transfer_request.tenant_id, + } + else: + target = { + 'tenant_id': zone_transfer_request.tenant_id, + } policy.check('update_zone_transfer_request', context, target) request = self.storage.update_zone_transfer_request( context, zone_transfer_request) @@ -2587,9 +2827,14 @@ class Service(service.RPCService): # Get zone transfer request zone_transfer_request = self.storage.get_zone_transfer_request( context, zone_transfer_request_id) - target = { - 'tenant_id': zone_transfer_request.tenant_id, - } + + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zone_transfer_request.tenant_id + } + else: + target = {'tenant_id': zone_transfer_request.tenant_id} + policy.check('delete_zone_transfer_request', context, target) return self.storage.delete_zone_transfer_request( context, @@ -2616,14 +2861,23 @@ class Service(service.RPCService): raise exceptions.IncorrectZoneTransferKey( 'Key does not match stored key for request') - target = { - 'target_tenant_id': zone_transfer_request.target_tenant_id - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_TARGET_PROJECT_ID: (zone_transfer_request. + target_tenant_id) + } + else: + target = { + 'target_tenant_id': zone_transfer_request.target_tenant_id + } + policy.check('create_zone_transfer_accept', context, target) if zone_transfer_accept.tenant_id is None: zone_transfer_accept.tenant_id = context.project_id + self._is_valid_project_id(zone_transfer_accept.tenant_id) + created_zone_transfer_accept = \ self.storage.create_zone_transfer_accept( context, zone_transfer_accept) @@ -2666,9 +2920,15 @@ class Service(service.RPCService): zone_transfer_accept = self.storage.get_zone_transfer_accept( context, zone_transfer_accept_id) - target = { - 'tenant_id': zone_transfer_accept.tenant_id - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zone_transfer_accept.tenant_id + } + else: + target = { + 'tenant_id': zone_transfer_accept.tenant_id + } + policy.check('get_zone_transfer_accept', context, target) return zone_transfer_accept @@ -2690,9 +2950,14 @@ class Service(service.RPCService): @notification('dns.zone_transfer_accept.update') @transaction def update_zone_transfer_accept(self, context, zone_transfer_accept): - target = { - 'tenant_id': zone_transfer_accept.tenant_id - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zone_transfer_accept.tenant_id + } + else: + target = { + 'tenant_id': zone_transfer_accept.tenant_id + } policy.check('update_zone_transfer_accept', context, target) accept = self.storage.update_zone_transfer_accept( context, zone_transfer_accept) @@ -2707,9 +2972,15 @@ class Service(service.RPCService): zt_accept = self.storage.get_zone_transfer_accept( context, zone_transfer_accept_id) - target = { - 'tenant_id': zt_accept.tenant_id - } + if policy.enforce_new_defaults(): + target = { + constants.RBAC_PROJECT_ID: zt_accept.tenant_id + } + else: + target = { + 'tenant_id': zt_accept.tenant_id + } + policy.check('delete_zone_transfer_accept', context, target) return self.storage.delete_zone_transfer_accept( context, @@ -2719,9 +2990,16 @@ class Service(service.RPCService): @rpc.expected_exceptions() @notification('dns.zone_import.create') def create_zone_import(self, context, request_body): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('create_zone_import', context, target) + self._is_valid_project_id(context.project_id) + values = { 'status': 'PENDING', 'message': None, @@ -2813,7 +3091,12 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_zone_imports(self, context, criterion=None, marker=None, limit=None, sort_key=None, sort_dir=None): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('find_zone_imports', context, target) if not criterion: @@ -2828,16 +3111,22 @@ class Service(service.RPCService): @rpc.expected_exceptions() def get_zone_import(self, context, zone_import_id): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('get_zone_import', context, target) return self.storage.get_zone_import(context, zone_import_id) @rpc.expected_exceptions() @notification('dns.zone_import.update') def update_zone_import(self, context, zone_import): - target = { - 'tenant_id': zone_import.tenant_id, - } + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: zone_import.tenant_id} + else: + target = {'tenant_id': zone_import.tenant_id} policy.check('update_zone_import', context, target) return self.storage.update_zone_import(context, zone_import) @@ -2846,10 +3135,18 @@ class Service(service.RPCService): @notification('dns.zone_import.delete') @transaction def delete_zone_import(self, context, zone_import_id): - target = { - 'zone_import_id': zone_import_id, - 'tenant_id': context.project_id - } + + if policy.enforce_new_defaults(): + target = { + 'zone_import_id': zone_import_id, + constants.RBAC_PROJECT_ID: context.project_id + } + else: + target = { + 'zone_import_id': zone_import_id, + 'tenant_id': context.project_id + } + policy.check('delete_zone_import', context, target) zone_import = self.storage.delete_zone_import(context, zone_import_id) @@ -2863,9 +3160,15 @@ class Service(service.RPCService): # Try getting the zone to ensure it exists zone = self.storage.get_zone(context, zone_id) - target = {'tenant_id': context.project_id} + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('create_zone_export', context, target) + self._is_valid_project_id(context.project_id) + values = { 'status': 'PENDING', 'message': None, @@ -2886,7 +3189,11 @@ class Service(service.RPCService): @rpc.expected_exceptions() def find_zone_exports(self, context, criterion=None, marker=None, limit=None, sort_key=None, sort_dir=None): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} policy.check('find_zone_exports', context, target) if not criterion: @@ -2901,7 +3208,12 @@ class Service(service.RPCService): @rpc.expected_exceptions() def get_zone_export(self, context, zone_export_id): - target = {'tenant_id': context.project_id} + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: context.project_id} + else: + target = {'tenant_id': context.project_id} + policy.check('get_zone_export', context, target) return self.storage.get_zone_export(context, zone_export_id) @@ -2909,9 +3221,12 @@ class Service(service.RPCService): @rpc.expected_exceptions() @notification('dns.zone_export.update') def update_zone_export(self, context, zone_export): - target = { - 'tenant_id': zone_export.tenant_id, - } + + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: zone_export.tenant_id} + else: + target = {'tenant_id': zone_export.tenant_id} + policy.check('update_zone_export', context, target) return self.storage.update_zone_export(context, zone_export) @@ -2920,10 +3235,18 @@ class Service(service.RPCService): @notification('dns.zone_export.delete') @transaction def delete_zone_export(self, context, zone_export_id): - target = { - 'zone_export_id': zone_export_id, - 'tenant_id': context.project_id - } + + if policy.enforce_new_defaults(): + target = { + 'zone_export_id': zone_export_id, + constants.RBAC_PROJECT_ID: context.project_id + } + else: + target = { + 'zone_export_id': zone_export_id, + 'tenant_id': context.project_id + } + policy.check('delete_zone_export', context, target) zone_export = self.storage.delete_zone_export(context, zone_export_id) diff --git a/designate/common/constants.py b/designate/common/constants.py index 295ee8b7..3857e02f 100644 --- a/designate/common/constants.py +++ b/designate/common/constants.py @@ -22,3 +22,7 @@ QUOTA_ZONE_RECORDSETS = 'zone_recordsets' QUOTA_ZONES = 'zones' VALID_QUOTAS = [QUOTA_API_EXPORT_SIZE, QUOTA_RECORDSET_RECORDS, QUOTA_ZONE_RECORDS, QUOTA_ZONE_RECORDSETS, QUOTA_ZONES] + +# RBAC related constants +RBAC_PROJECT_ID = 'project_id' +RBAC_TARGET_PROJECT_ID = 'target_project_id' diff --git a/designate/common/policies/base.py b/designate/common/policies/base.py index adb2a6c6..c09298db 100644 --- a/designate/common/policies/base.py +++ b/designate/common/policies/base.py @@ -12,17 +12,14 @@ # License for the specific language governing permissions and limitations # under the License. - +from oslo_log import versionutils from oslo_policy import policy -RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner' -RULE_ADMIN = 'rule:admin' -RULE_ZONE_PRIMARY_OR_ADMIN = \ - "('PRIMARY':%(zone_type)s and rule:admin_or_owner) "\ - "OR ('SECONDARY':%(zone_type)s AND is_admin:True)" -RULE_ZONE_TRANSFER = "rule:admin_or_owner OR tenant:%(target_tenant_id)s " \ - "OR None:%(target_tenant_id)s" +DEPRECATED_REASON = """ +The designate API now supports system scope and default roles. +""" + RULE_ANY = "@" # Generic policy check string for system administrators. These are the people @@ -59,37 +56,56 @@ SYSTEM_OR_PROJECT_READER = ( '(' + SYSTEM_READER + ') or (' + PROJECT_READER + ')' ) +# Designate specific "secure RBAC" rules +ALL_TENANTS = 'True:%(all_tenants)s' + +ALL_TENANTS_READER = ALL_TENANTS + ' and role:reader' + +SYSTEM_OR_PROJECT_READER_OR_ALL_TENANTS_READER = ( + '(' + SYSTEM_READER + ') or (' + PROJECT_READER + ') or (' + + ALL_TENANTS_READER + ')' +) + +RULE_ZONE_TRANSFER = ( + '(' + SYSTEM_ADMIN_OR_PROJECT_MEMBER + ') or ' + 'project_id:%(target_project_id)s or ' + 'None:%(target_project_id)s') + + +# Deprecated in Wallaby as part of the "secure RBAC" work. +# TODO(johnsom) remove when the deprecated RBAC rules are removed. +RULE_ADMIN = 'rule:admin' +RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner' +LEGACY_RULE_ZONE_TRANSFER = "rule:admin_or_owner OR " \ + "tenant:%(target_tenant_id)s " \ + "OR None:%(target_tenant_id)s" + +deprecated_default = policy.DeprecatedRule( + name="default", + check_str=RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) + rules = [ + # TODO(johnsom) remove when the deprecated RBAC rules are removed. policy.RuleDefault( name="admin", check_str="role:admin or is_admin:True"), - policy.RuleDefault( - name="primary_zone", - check_str="target.zone_type:SECONDARY"), + # TODO(johnsom) remove when the deprecated RBAC rules are removed. policy.RuleDefault( name="owner", check_str="tenant:%(tenant_id)s"), + # TODO(johnsom) remove when the deprecated RBAC rules are removed. policy.RuleDefault( name="admin_or_owner", check_str="rule:admin or rule:owner"), + + # Default policy policy.RuleDefault( name="default", - check_str="rule:admin_or_owner"), - policy.RuleDefault( - name="target", - check_str="tenant:%(target_tenant_id)s"), - policy.RuleDefault( - name="owner_or_target", - check_str="rule:target or rule:owner"), - policy.RuleDefault( - name="admin_or_owner_or_target", - check_str="rule:owner_or_target or rule:admin"), - policy.RuleDefault( - name="admin_or_target", - check_str="rule:admin or rule:target"), - policy.RuleDefault( - name="zone_primary_or_admin", - check_str=RULE_ZONE_PRIMARY_OR_ADMIN) + check_str=SYSTEM_ADMIN_OR_PROJECT_MEMBER, + deprecated_rule=deprecated_default), ] diff --git a/designate/common/policies/context.py b/designate/common/policies/context.py index 08a528f3..81ab54d5 100644 --- a/designate/common/policies/context.py +++ b/designate/common/policies/context.py @@ -13,28 +13,62 @@ # under the License. +from oslo_log import versionutils from oslo_policy import policy from designate.common.policies import base +deprecated_all_tenants = policy.DeprecatedRule( + name="all_tenants", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_edit_managed_records = policy.DeprecatedRule( + name="edit_managed_records", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_use_low_ttl = policy.DeprecatedRule( + name="use_low_ttl", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_use_sudo = policy.DeprecatedRule( + name="use_sudo", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) + rules = [ policy.RuleDefault( name="all_tenants", - check_str=base.RULE_ADMIN, - description='Action on all tenants.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Action on all tenants.', + deprecated_rule=deprecated_all_tenants), policy.RuleDefault( name="edit_managed_records", - check_str=base.RULE_ADMIN, - description='Edit managed records.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Edit managed records.', + deprecated_rule=deprecated_edit_managed_records), policy.RuleDefault( name="use_low_ttl", - check_str=base.RULE_ADMIN, - description='Use low TTL.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Use low TTL.', + deprecated_rule=deprecated_use_low_ttl), policy.RuleDefault( name="use_sudo", - check_str=base.RULE_ADMIN, - description='Accept sudo from user to tenant.') + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Accept sudo from user to tenant.', + deprecated_rule=deprecated_use_sudo) ] diff --git a/designate/common/policies/diagnostics.py b/designate/common/policies/diagnostics.py index 9b903231..55574bf5 100644 --- a/designate/common/policies/diagnostics.py +++ b/designate/common/policies/diagnostics.py @@ -12,29 +12,62 @@ # License for the specific language governing permissions and limitations # under the License. - +from oslo_log import versionutils from oslo_policy import policy from designate.common.policies import base +deprecated_diagnostics_ping = policy.DeprecatedRule( + name="diagnostics_ping", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_diagnostics_sync_zones = policy.DeprecatedRule( + name="diagnostics_sync_zones", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_diagnostics_sync_zone = policy.DeprecatedRule( + name="diagnostics_sync_zone", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_diagnostics_sync_record = policy.DeprecatedRule( + name="diagnostics_sync_record", + check_str=base.RULE_ADMIN, + deprecated_reason=base.DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) + rules = [ policy.RuleDefault( name="diagnostics_ping", - check_str=base.RULE_ADMIN, - description='Diagnose ping.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Diagnose ping.', + deprecated_rule=deprecated_diagnostics_ping), policy.RuleDefault( name="diagnostics_sync_zones", - check_str=base.RULE_ADMIN, - description='Diagnose sync zones.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Diagnose sync zones.', + deprecated_rule=deprecated_diagnostics_sync_zones), policy.RuleDefault( name="diagnostics_sync_zone", - check_str=base.RULE_ADMIN, - description='Diagnose sync zone.'), + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Diagnose sync zone.', + deprecated_rule=deprecated_diagnostics_sync_zone), policy.RuleDefault( name="diagnostics_sync_record", - check_str=base.RULE_ADMIN, - description='Diagnose sync record.') + check_str=base.SYSTEM_ADMIN, + scope_types=['system'], + description='Diagnose sync record.', + deprecated_rule=deprecated_diagnostics_sync_record) ] diff --git a/designate/common/policies/quota.py b/designate/common/policies/quota.py index f430d9a7..0ddb4459 100644 --- a/designate/common/policies/quota.py +++ b/designate/common/policies/quota.py @@ -50,7 +50,7 @@ deprecated_reset_quotas = policy.DeprecatedRule( rules = [ policy.DocumentedRuleDefault( name="get_quotas", - check_str=base.SYSTEM_OR_PROJECT_READER, + check_str=base.SYSTEM_OR_PROJECT_READER_OR_ALL_TENANTS_READER, scope_types=['system', 'project'], description="View Current Project's Quotas.", operations=[ diff --git a/designate/common/policies/recordset.py b/designate/common/policies/recordset.py index e77025eb..6dad34fc 100644 --- a/designate/common/policies/recordset.py +++ b/designate/common/policies/recordset.py @@ -22,9 +22,15 @@ DEPRECATED_REASON = """ The record set API now supports system scope and default roles. """ +# Deprecated in Wallaby as part of the "secure RBAC" work. +# TODO(johnsom) remove when the deprecated RBAC rules are removed. +RULE_ZONE_PRIMARY_OR_ADMIN = ( + "('PRIMARY':%(zone_type)s and rule:admin_or_owner) " + "OR ('SECONDARY':%(zone_type)s AND is_admin:True)") + deprecated_create_recordset = policy.DeprecatedRule( name="create_recordset", - check_str=base.RULE_ZONE_PRIMARY_OR_ADMIN, + check_str=RULE_ZONE_PRIMARY_OR_ADMIN, deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) @@ -40,15 +46,27 @@ deprecated_get_recordset = policy.DeprecatedRule( deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) +deprecated_find_recordset = policy.DeprecatedRule( + name="find_recordset", + check_str=base.RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) +deprecated_find_recordsets = policy.DeprecatedRule( + name="find_recordsets", + check_str=base.RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) deprecated_update_recordset = policy.DeprecatedRule( name="update_recordset", - check_str=base.RULE_ZONE_PRIMARY_OR_ADMIN, + check_str=RULE_ZONE_PRIMARY_OR_ADMIN, deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) deprecated_delete_recordset = policy.DeprecatedRule( name="delete_recordset", - check_str=base.RULE_ZONE_PRIMARY_OR_ADMIN, + check_str=RULE_ZONE_PRIMARY_OR_ADMIN, deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) @@ -69,7 +87,7 @@ SYSTEM_ADMIN_AND_SECONDARY_ZONE = ( '(' + base.SYSTEM_ADMIN + ') and (\'SECONDARY\':%(zone_type)s)' ) -SYSTEM_ADMIN_OR_PROJECT_MEMBER = ''.join( +SYSTEM_ADMIN_OR_PROJECT_MEMBER_ZONE_TYPE = ' or '.join( [PROJECT_MEMBER_AND_PRIMARY_ZONE, SYSTEM_ADMIN_AND_PRIMARY_ZONE, SYSTEM_ADMIN_AND_SECONDARY_ZONE] @@ -79,16 +97,13 @@ SYSTEM_ADMIN_OR_PROJECT_MEMBER = ''.join( rules = [ policy.DocumentedRuleDefault( name="create_recordset", - check_str=SYSTEM_ADMIN_AND_SECONDARY_ZONE, + check_str=SYSTEM_ADMIN_OR_PROJECT_MEMBER_ZONE_TYPE, scope_types=['system', 'project'], description="Create Recordset", operations=[ { 'path': '/v2/zones/{zone_id}/recordsets', 'method': 'POST' - }, { - 'path': '/v2/reverse/floatingips/{region}:{floatingip_id}', - 'method': 'PATCH' } ], deprecated_rule=deprecated_create_recordset @@ -108,35 +123,46 @@ rules = [ { 'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}', 'method': 'GET' - }, { - 'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}', - 'method': 'DELETE' - }, { - 'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}', - 'method': 'PUT' } ], deprecated_rule=deprecated_get_recordset ), + policy.RuleDefault( + name="find_recordset", + check_str=base.SYSTEM_OR_PROJECT_READER, + scope_types=['system', 'project'], + description="List a Recordset in a Zone", + deprecated_rule=deprecated_find_recordset + ), + policy.DocumentedRuleDefault( + name="find_recordsets", + check_str=base.SYSTEM_OR_PROJECT_READER, + scope_types=['system', 'project'], + description="List Recordsets in a Zone", + operations=[ + { + 'path': '/v2/zones/{zone_id}/recordsets', + 'method': 'GET' + }, + ], + deprecated_rule=deprecated_find_recordsets + ), policy.DocumentedRuleDefault( name="update_recordset", - check_str=SYSTEM_ADMIN_AND_SECONDARY_ZONE, + check_str=SYSTEM_ADMIN_OR_PROJECT_MEMBER_ZONE_TYPE, scope_types=['system', 'project'], description="Update recordset", operations=[ { 'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}', 'method': 'PUT' - }, { - 'path': '/v2/reverse/floatingips/{region}:{floatingip_id}', - 'method': 'PATCH' } ], deprecated_rule=deprecated_update_recordset ), policy.DocumentedRuleDefault( name="delete_recordset", - check_str=SYSTEM_ADMIN_AND_SECONDARY_ZONE, + check_str=SYSTEM_ADMIN_OR_PROJECT_MEMBER_ZONE_TYPE, scope_types=['system', 'project'], description="Delete RecordSet", operations=[ diff --git a/designate/common/policies/tsigkey.py b/designate/common/policies/tsigkey.py index b3562315..2df26f3e 100644 --- a/designate/common/policies/tsigkey.py +++ b/designate/common/policies/tsigkey.py @@ -89,9 +89,6 @@ rules = [ operations=[ { 'path': '/v2/tsigkeys/{tsigkey_id}', - 'method': 'PATCH' - }, { - 'path': '/v2/tsigkeys/{tsigkey_id}', 'method': 'GET' } ], diff --git a/designate/common/policies/zone.py b/designate/common/policies/zone.py index 74552597..028dcff5 100644 --- a/designate/common/policies/zone.py +++ b/designate/common/policies/zone.py @@ -46,6 +46,12 @@ deprecated_get_zone_servers = policy.DeprecatedRule( deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) +deprecated_get_zone_ns_records = policy.DeprecatedRule( + name="get_zone_ns_records", + check_str=base.RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) deprecated_find_zones = policy.DeprecatedRule( name="find_zones", check_str=base.RULE_ADMIN_OR_OWNER, @@ -131,12 +137,6 @@ rules = [ { 'path': '/v2/zones/{zone_id}', 'method': 'GET' - }, { - 'path': '/v2/zones/{zone_id}', - 'method': 'PATCH' - }, { - 'path': '/v2/zones/{zone_id}/recordsets/{recordset_id}', - 'method': 'PUT' } ], deprecated_rule=deprecated_get_zone @@ -148,6 +148,19 @@ rules = [ deprecated_rule=deprecated_get_zone_servers ), policy.DocumentedRuleDefault( + name="get_zone_ns_records", + check_str=base.SYSTEM_OR_PROJECT_READER, + scope_types=['system', 'project'], + description="Get the Name Servers for a Zone", + operations=[ + { + 'path': '/v2/zones/{zone_id}/nameservers', + 'method': 'GET' + } + ], + deprecated_rule=deprecated_get_zone_ns_records + ), + policy.DocumentedRuleDefault( name="find_zones", check_str=base.SYSTEM_OR_PROJECT_READER, scope_types=['system', 'project'], diff --git a/designate/common/policies/zone_export.py b/designate/common/policies/zone_export.py index c3f02443..ca45971b 100644 --- a/designate/common/policies/zone_export.py +++ b/designate/common/policies/zone_export.py @@ -52,6 +52,12 @@ deprecated_update_zone_export = policy.DeprecatedRule( deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) +deprecated_delete_zone_export = policy.DeprecatedRule( + name="delete_zone_export", + check_str=base.RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY +) rules = [ @@ -103,9 +109,6 @@ rules = [ { 'path': '/v2/zones/tasks/exports/{zone_export_id}', 'method': 'GET' - }, { - 'path': '/v2/zones/tasks/exports/{zone_export_id}/export', - 'method': 'GET' } ], deprecated_rule=deprecated_get_zone_export @@ -122,7 +125,20 @@ rules = [ } ], deprecated_rule=deprecated_update_zone_export - ) + ), + policy.DocumentedRuleDefault( + name="delete_zone_export", + check_str=base.SYSTEM_ADMIN_OR_PROJECT_MEMBER, + scope_types=['system', 'project'], + description="Delete a zone export", + operations=[ + { + 'path': '/v2/zones/tasks/exports/{zone_export_id}', + 'method': 'DELETE' + } + ], + deprecated_rule=deprecated_delete_zone_export + ), ] diff --git a/designate/common/policies/zone_import.py b/designate/common/policies/zone_import.py index 02a383e1..8d4f2b17 100644 --- a/designate/common/policies/zone_import.py +++ b/designate/common/policies/zone_import.py @@ -115,7 +115,7 @@ rules = [ operations=[ { 'path': '/v2/zones/tasks/imports/{zone_import_id}', - 'method': 'GET' + 'method': 'DELETE' } ], deprecated_rule=deprecated_delete_zone_import diff --git a/designate/common/policies/zone_transfer_accept.py b/designate/common/policies/zone_transfer_accept.py index eeca7435..05e18a64 100644 --- a/designate/common/policies/zone_transfer_accept.py +++ b/designate/common/policies/zone_transfer_accept.py @@ -24,7 +24,7 @@ The zone transfer accept API now supports system scope and default roles. deprecated_create_zone_transfer_accept = policy.DeprecatedRule( name="create_zone_transfer_accept", - check_str=base.RULE_ZONE_TRANSFER, + check_str=base.LEGACY_RULE_ZONE_TRANSFER, deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) @@ -64,13 +64,15 @@ rules = [ policy.DocumentedRuleDefault( name="create_zone_transfer_accept", check_str=base.RULE_ZONE_TRANSFER, + scope_types=['system', 'project'], description="Create Zone Transfer Accept", operations=[ { 'path': '/v2/zones/tasks/transfer_accepts', 'method': 'POST' } - ] + ], + deprecated_rule=deprecated_create_zone_transfer_accept ), policy.DocumentedRuleDefault( name="get_zone_transfer_accept", diff --git a/designate/common/policies/zone_transfer_request.py b/designate/common/policies/zone_transfer_request.py index 0ed2c8d3..5178aaf6 100644 --- a/designate/common/policies/zone_transfer_request.py +++ b/designate/common/policies/zone_transfer_request.py @@ -23,14 +23,14 @@ The zone transfer request API now supports system scope and default roles. """ deprecated_create_zone_transfer_request = policy.DeprecatedRule( - name="create_zone_transfer_request", - check_str=base.RULE_ADMIN_OR_OWNER, - deprecated_reason=DEPRECATED_REASON, - deprecated_since=versionutils.deprecated.WALLABY + name="create_zone_transfer_request", + check_str=base.RULE_ADMIN_OR_OWNER, + deprecated_reason=DEPRECATED_REASON, + deprecated_since=versionutils.deprecated.WALLABY ) deprecated_get_zone_transfer_request = policy.DeprecatedRule( name="get_zone_transfer_request", - check_str=base.RULE_ZONE_TRANSFER, + check_str=base.LEGACY_RULE_ZONE_TRANSFER, deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) @@ -40,12 +40,6 @@ deprecated_get_zone_transfer_request_detailed = policy.DeprecatedRule( deprecated_reason=DEPRECATED_REASON, deprecated_since=versionutils.deprecated.WALLABY ) -deprecated_find_zone_transfer_requests = policy.DeprecatedRule( - name="find_zone_transfer_requests", - check_str=base.RULE_ANY, - deprecated_reason=DEPRECATED_REASON, - deprecated_since=versionutils.deprecated.WALLABY -) deprecated_update_zone_transfer_request = policy.DeprecatedRule( name="update_zone_transfer_request", check_str=base.RULE_ADMIN_OR_OWNER, @@ -77,16 +71,15 @@ rules = [ policy.DocumentedRuleDefault( name="get_zone_transfer_request", check_str=base.RULE_ZONE_TRANSFER, + scope_types=['system', 'project'], description="Show a Zone Transfer Request", operations=[ { 'path': '/v2/zones/tasks/transfer_requests/{zone_transfer_request_id}', # noqa 'method': 'GET' - }, { - 'path': '/v2/zones/tasks/transfer_requests/{zone_transfer_request_id}', # noqa - 'method': 'PATCH' } - ] + ], + deprecated_rule=deprecated_get_zone_transfer_request ), policy.RuleDefault( name="get_zone_transfer_request_detailed", @@ -103,7 +96,7 @@ rules = [ 'path': '/v2/zones/tasks/transfer_requests', 'method': 'GET' } - ] + ], ), policy.RuleDefault( name="find_zone_transfer_request", diff --git a/designate/context.py b/designate/context.py index 13ccda13..b5c2c1e8 100644 --- a/designate/context.py +++ b/designate/context.py @@ -107,6 +107,8 @@ class DesignateContext(context.RequestContext): # NOTE(kiall): Ugly - required to match http://tinyurl.com/o3y8qmw context.roles.append('admin') + if policy.enforce_new_defaults(): + context.system_scope = 'all' if show_deleted is not None: context.show_deleted = show_deleted @@ -132,7 +134,8 @@ class DesignateContext(context.RequestContext): def get_admin_context(cls, **kwargs): # TODO(kiall): Remove Me kwargs['is_admin'] = True - kwargs['roles'] = ['admin'] + kwargs['roles'] = ['admin', 'reader'] + kwargs['system_scope'] = 'all' return cls(None, **kwargs) diff --git a/designate/exceptions.py b/designate/exceptions.py index 2aa73127..071addf5 100644 --- a/designate/exceptions.py +++ b/designate/exceptions.py @@ -256,6 +256,10 @@ class IncorrectZoneTransferKey(Forbidden): error_type = 'invalid_key' +class InvalidTokenScope(Forbidden): + error_type = 'invalid_token_scope' + + class Duplicate(DesignateException): expected = True error_code = 409 @@ -473,3 +477,12 @@ class LastServerDeleteNotAllowed(BadRequest): class ResourceNotFound(NotFound): # TODO(kiall): Should this be extending NotFound?? pass + + +class MissingProjectID(BadRequest): + # Note: This should be 400, but is 401 for compatibility with + # previous versions of the API. + # https://github.com/openstack/designate/blob/stable/wallaby/ \ + # designate/api/middleware.py#L132 + error_code = 401 + error_type = 'missing_project_id' diff --git a/designate/objects/adapters/api_v2/zone_transfer_request.py b/designate/objects/adapters/api_v2/zone_transfer_request.py index 56b9790f..5e430d9a 100644 --- a/designate/objects/adapters/api_v2/zone_transfer_request.py +++ b/designate/objects/adapters/api_v2/zone_transfer_request.py @@ -12,10 +12,11 @@ # License for the specific language governing permissions and limitations # under the License. -from designate.objects.adapters.api_v2 import base +from designate.common import constants +from designate import exceptions from designate import objects +from designate.objects.adapters.api_v2 import base from designate import policy -from designate import exceptions class ZoneTransferRequestAPIv2Adapter(base.APIv2Adapter): @@ -66,9 +67,10 @@ class ZoneTransferRequestAPIv2Adapter(base.APIv2Adapter): object, *args, **kwargs) try: - target = { - 'tenant_id': object.tenant_id, - } + if policy.enforce_new_defaults(): + target = {constants.RBAC_PROJECT_ID: object.tenant_id} + else: + target = {'tenant_id': object.tenant_id} policy.check( 'get_zone_transfer_request_detailed', diff --git a/designate/objects/blacklist.py b/designate/objects/blacklist.py index 1a5eb388..a0dd4fcf 100644 --- a/designate/objects/blacklist.py +++ b/designate/objects/blacklist.py @@ -20,8 +20,8 @@ from designate.objects import fields class Blacklist(base.DictObjectMixin, base.PersistentObjectMixin, base.DesignateObject): fields = { - 'pattern': fields.StringFields(maxLength=255), - 'description': fields.StringFields(maxLength=160, nullable=True), + 'pattern': fields.DenylistFields(maxLength=255), + 'description': fields.DenylistFields(maxLength=160, nullable=True), } STRING_KEYS = [ diff --git a/designate/objects/fields.py b/designate/objects/fields.py index 9b533438..dd3268c8 100644 --- a/designate/objects/fields.py +++ b/designate/objects/fields.py @@ -451,3 +451,25 @@ class IPOrHost(IPV4AndV6AddressField): if not re.match(StringFields.RE_ZONENAME, value): raise ValueError("%s is not IP address or host name" % value) return value + + +class DenylistFields(StringFields): + def __init__(self, **kwargs): + super(DenylistFields, self).__init__(**kwargs) + + def coerce(self, obj, attr, value): + value = super(DenylistFields, self).coerce(obj, attr, value) + + if value is None: + return self._null(obj, attr) + + # determine the validity if a regex expression filter has been used. + msg = ("%s is not a valid regular expression" % value) + if not len(value): + raise ValueError(msg) + try: + re.compile(value) + except Exception: + raise ValueError(msg) + + return value diff --git a/designate/policy.py b/designate/policy.py index 863f0ddc..041e7f41 100644 --- a/designate/policy.py +++ b/designate/policy.py @@ -73,10 +73,17 @@ def init(default_rule=None, policy_file=None): def check(rule, ctxt, target=None, do_raise=True, exc=exceptions.Forbidden): - creds = ctxt.to_dict() + if enforce_new_defaults(): + creds = ctxt.to_policy_values() + else: + creds = ctxt.to_dict() target = target or {} try: result = _ENFORCER.enforce(rule, target, creds, do_raise, exc) + except policy.InvalidScope: + result = False + if do_raise: + raise exceptions.InvalidTokenScope except Exception: result = False raise @@ -93,3 +100,9 @@ def check(rule, ctxt, target=None, do_raise=True, exc=exceptions.Forbidden): LOG.info("Policy check failed for rule '%(rule)s' " "on target %(target)s", {'rule': rule, 'target': repr(target)}, extra=extra) + + +def enforce_new_defaults(): + if CONF.get('oslo_policy'): + return CONF['oslo_policy'].get('enforce_new_defaults', False) + return False diff --git a/designate/storage/impl_sqlalchemy/__init__.py b/designate/storage/impl_sqlalchemy/__init__.py index 7449f9e2..fe46a88b 100644 --- a/designate/storage/impl_sqlalchemy/__init__.py +++ b/designate/storage/impl_sqlalchemy/__init__.py @@ -1488,6 +1488,12 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage): ).select_from(ljoin) if not context.all_tenants: + # If we have a system scoped token with no project_id and + # all_tenants was not used, we don't know what records to return, + # so return an empty list. + if not context.project_id: + return objects.ZoneTransferRequestList() + query = query.where(or_( table.c.tenant_id == context.project_id, table.c.target_tenant_id == context.project_id)) @@ -1498,7 +1504,8 @@ class SQLAlchemyStorage(sqlalchemy_base.SQLAlchemy, storage_base.Storage): exceptions.ZoneTransferRequestNotFound, criterion, one=one, marker=marker, limit=limit, sort_dir=sort_dir, - sort_key=sort_key, query=query, apply_tenant_criteria=False + sort_key=sort_key, query=query, + apply_tenant_criteria=False ) def create_zone_transfer_request(self, context, zone_transfer_request): diff --git a/designate/tests/__init__.py b/designate/tests/__init__.py index 364fad82..07bf510d 100644 --- a/designate/tests/__init__.py +++ b/designate/tests/__init__.py @@ -388,6 +388,8 @@ class TestCase(base.BaseTestCase): self.central_service = self.start_service('central') self.admin_context = self.get_admin_context() + self.admin_context_all_tenants = self.get_admin_context( + all_tenants=True) storage_driver = CONF['service:central'].storage_driver self.storage = storage.get_storage(storage_driver) @@ -437,10 +439,11 @@ class TestCase(base.BaseTestCase): def get_context(self, **kwargs): return DesignateContext(**kwargs) - def get_admin_context(self): + def get_admin_context(self, **kwargs): return DesignateContext.get_admin_context( project_id=utils.generate_uuid(), - user_id=utils.generate_uuid()) + user_id=utils.generate_uuid(), + **kwargs) # Fixture methods def get_quota_fixture(self, fixture=0, values=None): @@ -795,7 +798,7 @@ class TestCase(base.BaseTestCase): # Retrieve it, and ensure it's the same zone_import = self.central_service.get_zone_import( - self.admin_context, zone_import_id) + self.admin_context_all_tenants, zone_import_id) # If the import is done, we're done if zone_import.status == 'COMPLETE': diff --git a/designate/tests/test_api/test_middleware.py b/designate/tests/test_api/test_middleware.py index 3425c1c6..6bb8be79 100644 --- a/designate/tests/test_api/test_middleware.py +++ b/designate/tests/test_api/test_middleware.py @@ -102,7 +102,8 @@ class KeystoneContextMiddlewareTest(ApiTestCase): # Process the request response = app(request) - self.assertEqual(401, response.status_code) + # Ensure request was not blocked + self.assertEqual(response, 'FakeResponse') class NoAuthContextMiddlewareTest(ApiTestCase): diff --git a/designate/tests/test_api/test_v2/test_blacklists.py b/designate/tests/test_api/test_v2/test_blacklists.py index 2dff9b8c..0677b7e1 100644 --- a/designate/tests/test_api/test_v2/test_blacklists.py +++ b/designate/tests/test_api/test_v2/test_blacklists.py @@ -165,3 +165,48 @@ class ApiV2BlacklistsTest(ApiV2TestCase): url = '/blacklists?description=test' self.policy({'find_blacklists': '@'}) self._assert_exception('bad_request', 400, self.client.get, url) + + def test_create_invalid_denylist_pattern(self): + self.policy({'create_blacklist': '@'}) + body = { + 'description': u'This is the description.' + } + + url = '/blacklists/' + + # doing each pattern individually so upon error one can trace + # back to the exact line number + body['pattern'] = '' + self._assert_exception( + 'invalid_object', 400, self.client.post_json, url, body) + + body['pattern'] = '#(*&^%$%$#@$' + self._assert_exception( + 'invalid_object', 400, self.client.post_json, url, body) + + body['pattern'] = 'a' * 1000 + self._assert_exception( + 'invalid_object', 400, self.client.post_json, url, body) + + def test_update_invalid_denylist_pattern(self): + blacklist = self.create_blacklist(fixture=0) + self.policy({'update_blacklist': '@'}) + + url = ('/blacklists/%s' % blacklist['id']) + + # doing each pattern individually so upon error one can trace + # back to the exact line number + body = {'pattern': ''} + self._assert_exception( + 'invalid_object', 400, self.client.patch_json, url, body, + status=400) + + body = {'pattern': '#(*&^%$%$#@$'} + self._assert_exception( + 'invalid_object', 400, self.client.patch_json, url, body, + status=400) + + body = {'pattern': 'a' * 1000} + self._assert_exception( + 'invalid_object', 400, self.client.patch_json, url, body, + status=400) diff --git a/designate/tests/test_central/test_service.py b/designate/tests/test_central/test_service.py index 2c6f8cf5..5db47b4f 100644 --- a/designate/tests/test_central/test_service.py +++ b/designate/tests/test_central/test_service.py @@ -33,9 +33,10 @@ from oslo_messaging.rpc import dispatcher as rpc_dispatcher from designate import exceptions from designate import objects from designate.mdns import rpcapi as mdns_api +from designate.storage.impl_sqlalchemy import tables from designate.tests import fixtures from designate.tests.test_central import CentralTestCase -from designate.storage.impl_sqlalchemy import tables +from designate import utils LOG = logging.getLogger(__name__) @@ -3534,7 +3535,7 @@ class CentralServiceTest(CentralTestCase): # Zone Import Tests def test_create_zone_import(self): # Create a Zone Import - context = self.get_context() + context = self.get_context(project_id=utils.generate_uuid()) request_body = self.get_zonefile_fixture() zone_import = self.central_service.create_zone_import(context, request_body) @@ -3548,7 +3549,7 @@ class CentralServiceTest(CentralTestCase): self.wait_for_import(zone_import.id) def test_find_zone_imports(self): - context = self.get_context() + context = self.get_context(project_id=utils.generate_uuid()) # Ensure we have no zone_imports to start with. zone_imports = self.central_service.find_zone_imports( @@ -3565,7 +3566,7 @@ class CentralServiceTest(CentralTestCase): # Ensure we can retrieve the newly created zone_import zone_imports = self.central_service.find_zone_imports( - self.admin_context) + self.admin_context_all_tenants) self.assertEqual(1, len(zone_imports)) # Create a second zone_import @@ -3578,14 +3579,14 @@ class CentralServiceTest(CentralTestCase): # Ensure we can retrieve both zone_imports zone_imports = self.central_service.find_zone_imports( - self.admin_context) + self.admin_context_all_tenants) self.assertEqual(2, len(zone_imports)) self.assertEqual('COMPLETE', zone_imports[0].status) self.assertEqual('COMPLETE', zone_imports[1].status) def test_get_zone_import(self): # Create a Zone Import - context = self.get_context() + context = self.get_context(project_id=utils.generate_uuid()) request_body = self.get_zonefile_fixture() zone_import = self.central_service.create_zone_import( context, request_body) @@ -3595,7 +3596,7 @@ class CentralServiceTest(CentralTestCase): # Retrieve it, and ensure it's the same zone_import = self.central_service.get_zone_import( - self.admin_context, zone_import.id) + self.admin_context_all_tenants, zone_import.id) self.assertEqual(zone_import.id, zone_import['id']) self.assertEqual(zone_import.status, zone_import['status']) @@ -3603,7 +3604,7 @@ class CentralServiceTest(CentralTestCase): def test_update_zone_import(self): # Create a Zone Import - context = self.get_context() + context = self.get_context(project_id=utils.generate_uuid()) request_body = self.get_zonefile_fixture() zone_import = self.central_service.create_zone_import( context, request_body) @@ -3615,7 +3616,7 @@ class CentralServiceTest(CentralTestCase): # Perform the update zone_import = self.central_service.update_zone_import( - self.admin_context, zone_import) + self.admin_context_all_tenants, zone_import) # Fetch the zone_import again zone_import = self.central_service.get_zone_import(context, @@ -3626,7 +3627,7 @@ class CentralServiceTest(CentralTestCase): def test_delete_zone_import(self): # Create a Zone Import - context = self.get_context() + context = self.get_context(project_id=utils.generate_uuid()) request_body = self.get_zonefile_fixture() zone_import = self.central_service.create_zone_import( context, request_body) diff --git a/designate/tests/unit/test_central/test_basic.py b/designate/tests/unit/test_central/test_basic.py index 688ca340..e75922e3 100644 --- a/designate/tests/unit/test_central/test_basic.py +++ b/designate/tests/unit/test_central/test_basic.py @@ -256,6 +256,7 @@ class CentralBasic(TestCase): 'set_rules', 'init', 'check', + 'enforce_new_defaults', ]) designate.central.service.quota = mock.NonCallableMock(spec_set=[ @@ -932,7 +933,7 @@ class CentralZoneTestCase(CentralBasic): n, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual(CentralZoneTestCase.zone__id, target['zone_id']) self.assertEqual('foo', target['zone_name']) - self.assertEqual('2', target['tenant_id']) + self.assertEqual('2', target['project_id']) def test_get_zone_servers(self): self.service.storage.get_zone.return_value = RoObject( @@ -995,6 +996,7 @@ class CentralZoneTestCase(CentralBasic): 'set_rules', 'init', 'check', + 'enforce_new_defaults', ]) self.context.abandon = True self.service.storage.count_zones.return_value = 0 @@ -1187,7 +1189,7 @@ class CentralZoneTestCase(CentralBasic): 'zone_id': CentralZoneTestCase.zone__id_2, 'zone_name': 'example.org.', 'recordset_id': CentralZoneTestCase.recordset__id, - 'tenant_id': '2'}, target) + 'project_id': '2'}, target) def test_find_recordsets(self): self.context = mock.Mock() @@ -1196,7 +1198,7 @@ class CentralZoneTestCase(CentralBasic): self.assertTrue(self.service.storage.find_recordsets.called) n, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual('find_recordsets', n) - self.assertEqual({'tenant_id': 't'}, target) + self.assertEqual({'project_id': 't'}, target) def test_find_recordset(self): self.context = mock.Mock() @@ -1205,7 +1207,7 @@ class CentralZoneTestCase(CentralBasic): self.assertTrue(self.service.storage.find_recordset.called) n, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual('find_recordset', n) - self.assertEqual({'tenant_id': 't'}, target) + self.assertEqual({'project_id': 't'}, target) def test_update_recordset_fail_on_changes(self): self.service.storage.get_zone.return_value = RoObject() @@ -1298,7 +1300,7 @@ class CentralZoneTestCase(CentralBasic): 'zone_name': 'example.org.', 'zone_type': 'foo', 'recordset_id': '9c85d9b0-1e9d-4e99-aede-a06664f1af2e', - 'tenant_id': '2'}, target) + 'project_id': '2'}, target) def test__update_recordset_in_storage(self): recordset = mock.Mock() @@ -1532,7 +1534,7 @@ class CentralZoneTestCase(CentralBasic): self.service.count_recordsets(self.context) n, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual('count_recordsets', n) - self.assertEqual({'tenant_id': None}, target) + self.assertEqual({'project_id': None}, target) self.assertEqual( {}, self.service.storage.count_recordsets.call_args[0][1] @@ -1587,7 +1589,7 @@ class CentralZoneTestCase(CentralBasic): 'zone_type': 'foo', 'recordset_id': CentralZoneTestCase.recordset__id, 'recordset_name': 'rs', - 'tenant_id': '2'}, target) + 'project_id': '2'}, target) def test_create_record_worker(self): self._test_create_record() @@ -1689,7 +1691,7 @@ class CentralZoneTestCase(CentralBasic): 'record_id': CentralZoneTestCase.record__id, 'recordset_id': CentralZoneTestCase.recordset__id_2, 'recordset_name': 'foo', - 'tenant_id': 2}, target) + 'project_id': 2}, target) def test_update_record_fail_on_changes(self): self.service.storage.get_zone.return_value = RoObject( @@ -1789,7 +1791,7 @@ class CentralZoneTestCase(CentralBasic): 'record_id': 'abc12a-1e9d-4e99-aede-a06664f1af2e', 'recordset_id': 'abc12a-1e9d-4e99-aede-a06664f1af2e', 'recordset_name': 'rsn', - 'tenant_id': 'tid'}, target) + 'project_id': 'tid'}, target) def test__update_record_in_storage(self): self.service._update_zone_in_storage = mock.Mock() @@ -1893,7 +1895,7 @@ class CentralZoneTestCase(CentralBasic): 'record_id': CentralZoneTestCase.record__id_2, 'recordset_id': CentralZoneTestCase.recordset__id_2, 'recordset_name': 'rsn', - 'tenant_id': 'tid'}, target) + 'project_id': 'tid'}, target) def test_delete_record_in_storage(self): self.service._delete_record_in_storage( @@ -1911,7 +1913,7 @@ class CentralZoneTestCase(CentralBasic): self.service.count_records(self.context) t, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual('count_records', t) - self.assertEqual({'tenant_id': None}, target) + self.assertEqual({'project_id': None}, target) def test_sync_zones(self): self.service._sync_zone = mock.Mock() @@ -1938,7 +1940,7 @@ class CentralZoneTestCase(CentralBasic): t, ctx, target = designate.central.service.policy.check.call_args[0] self.assertEqual('diagnostics_sync_zone', t) - self.assertEqual({'tenant_id': 'tid', + self.assertEqual({'project_id': 'tid', 'zone_id': CentralZoneTestCase.zone__id, 'zone_name': 'n'}, target) @@ -1965,7 +1967,7 @@ class CentralZoneTestCase(CentralBasic): 'record_id': CentralZoneTestCase.record__id, 'recordset_id': CentralZoneTestCase.recordset__id, 'recordset_name': 'n', - 'tenant_id': 'tid'}, target) + 'project_id': 'tid'}, target) def test_ping(self): self.service.storage.ping.return_value = True @@ -2118,7 +2120,7 @@ class CentralZoneExportTests(CentralBasic): n, ctx, target = designate.central.service.policy.check.call_args[0] # Check arguments to policy - self.assertEqual('t', target['tenant_id']) + self.assertEqual('t', target['project_id']) # Check output self.assertEqual(CentralZoneTestCase.zone__id, out.zone_id) diff --git a/releasenotes/notes/Fix-to-address-denylist-invalid-patterns-not-being-checked-ec1f1316ccc6cb1d.yaml b/releasenotes/notes/Fix-to-address-denylist-invalid-patterns-not-being-checked-ec1f1316ccc6cb1d.yaml new file mode 100644 index 00000000..43a3ca94 --- /dev/null +++ b/releasenotes/notes/Fix-to-address-denylist-invalid-patterns-not-being-checked-ec1f1316ccc6cb1d.yaml @@ -0,0 +1,16 @@ +--- +fixes: + - | + Fixes `bug 1934252`_ which ignored invalid denylist patterns. The fix + entailed checking the pattern string via regular expression compiler and + testing for zero length. + + Previously you could create blacklist/denylist using string that cannot + be used either as a regex or as a zone name, for example: + patterns = ['', ``'#(*&^%$%$#@$']`` + + In addition, the server will return a 400 BadRequest response to an + invalid pattern. + + .. _Bug 1934252: https://bugs.launchpad.net/designate/+bug/1934252 + diff --git a/releasenotes/notes/Support-scoped-tokens-6b7d6052a258cd11.yaml b/releasenotes/notes/Support-scoped-tokens-6b7d6052a258cd11.yaml new file mode 100644 index 00000000..3571dbb3 --- /dev/null +++ b/releasenotes/notes/Support-scoped-tokens-6b7d6052a258cd11.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Adds support for keystone default roles and scoped tokens. |