Restrict editing of managed records to policy based ACL
* Added new context param (edit_managed_records) * Added new HTTP Header (X-Designate-Edit-Managed-Records:True) * Added new HTTP Query param (?edit_managed_records=True) * Added policy check (edit_managed_records), defaulting to rule:admin Change-Id: Ib68369fd7302384fd4fbd1396baa513265edb0a0 Closes-Bug: #1441283
This commit is contained in:
parent
7dd7682946
commit
a4f3ad90b9
|
@ -92,6 +92,17 @@ class ContextMiddleware(base.Middleware):
|
|||
strutils.bool_from_string(params['all_tenants'])
|
||||
else:
|
||||
ctxt.all_tenants = False
|
||||
if 'edit_managed_records' in params:
|
||||
ctxt.edit_managed_records = \
|
||||
strutils.bool_from_string(params['edit_managed_records'])
|
||||
|
||||
elif headers.get('X-Designate-Edit-Managed-Records'):
|
||||
ctxt.edit_managed_records = \
|
||||
strutils.bool_from_string(
|
||||
headers.get('X-Designate-Edit-Managed-Records'))
|
||||
|
||||
else:
|
||||
ctxt.edit_managed_records = False
|
||||
finally:
|
||||
request.environ['context'] = ctxt
|
||||
|
||||
|
|
|
@ -206,8 +206,8 @@ def update_record(domain_id, record_id):
|
|||
criterion = {'domain_id': domain_id, 'id': record_id}
|
||||
record = central_api.find_record(context, criterion)
|
||||
|
||||
# Cannot update a managed record via the API.
|
||||
if record['managed'] is True:
|
||||
# TODO(graham): Move this further down the stack
|
||||
if record.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
# Find the associated recordset
|
||||
|
@ -263,10 +263,6 @@ def delete_record(domain_id, record_id):
|
|||
criterion = {'domain_id': domain_id, 'id': record_id}
|
||||
record = central_api.find_record(context, criterion)
|
||||
|
||||
# Cannot delete a managed record via the API.
|
||||
if record['managed'] is True:
|
||||
raise exceptions.BadRequest('Managed records may not be deleted')
|
||||
|
||||
central_api.delete_record(
|
||||
context, domain_id, record['recordset_id'], record_id)
|
||||
|
||||
|
|
|
@ -140,6 +140,10 @@ class RecordSetsController(rest.RestController):
|
|||
recordset = self.central_api.get_recordset(context, zone_id,
|
||||
recordset_id)
|
||||
|
||||
# TODO(graham): Move this further down the stack
|
||||
if recordset.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
# SOA recordsets cannot be updated manually
|
||||
if recordset['type'] == 'SOA':
|
||||
raise exceptions.BadRequest(
|
||||
|
|
|
@ -1232,6 +1232,9 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
policy.check('update_recordset', context, target)
|
||||
|
||||
if recordset.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
recordset, domain = self._update_recordset_in_storage(
|
||||
context, domain, recordset, increment_serial=increment_serial)
|
||||
|
||||
|
@ -1299,6 +1302,9 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
policy.check('delete_recordset', context, target)
|
||||
|
||||
if recordset.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
recordset, domain = self._delete_recordset_in_storage(
|
||||
context, domain, recordset, increment_serial=increment_serial)
|
||||
|
||||
|
@ -1472,6 +1478,9 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
policy.check('update_record', context, target)
|
||||
|
||||
if recordset.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
record, domain = self._update_record_in_storage(
|
||||
context, domain, record, increment_serial=increment_serial)
|
||||
|
||||
|
@ -1530,6 +1539,9 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
policy.check('delete_record', context, target)
|
||||
|
||||
if recordset.managed and not context.edit_managed_records:
|
||||
raise exceptions.BadRequest('Managed records may not be updated')
|
||||
|
||||
record, domain = self._delete_record_in_storage(
|
||||
context, domain, record, increment_serial=increment_serial)
|
||||
|
||||
|
@ -1652,6 +1664,7 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
elevated_context = context.elevated()
|
||||
elevated_context.all_tenants = True
|
||||
elevated_context.edit_managed_records = True
|
||||
|
||||
criterion = {
|
||||
'managed': True,
|
||||
|
@ -1692,6 +1705,7 @@ class Service(service.RPCService, service.Service):
|
|||
"""
|
||||
elevated_context = context.elevated()
|
||||
elevated_context.all_tenants = True
|
||||
elevated_context.edit_managed_records = True
|
||||
|
||||
if records > 0:
|
||||
for r in records:
|
||||
|
@ -1782,6 +1796,7 @@ class Service(service.RPCService, service.Service):
|
|||
"""
|
||||
elevated_context = context.elevated()
|
||||
elevated_context.all_tenants = True
|
||||
elevated_context.edit_managed_records = True
|
||||
|
||||
tenant_fips = self._list_floatingips(context)
|
||||
|
||||
|
@ -1817,6 +1832,7 @@ class Service(service.RPCService, service.Service):
|
|||
|
||||
elevated_context = context.elevated()
|
||||
elevated_context.all_tenants = True
|
||||
elevated_context.edit_managed_records = True
|
||||
|
||||
tenant_fips = self._list_floatingips(context, region=region)
|
||||
|
||||
|
@ -1917,6 +1933,7 @@ class Service(service.RPCService, service.Service):
|
|||
"""
|
||||
elevated_context = context.elevated()
|
||||
elevated_context.all_tenants = True
|
||||
elevated_context.edit_managed_records = True
|
||||
|
||||
criterion = {
|
||||
'managed_resource_id': floatingip_id,
|
||||
|
|
|
@ -30,13 +30,16 @@ class DesignateContext(context.RequestContext):
|
|||
_all_tenants = False
|
||||
_abandon = None
|
||||
original_tenant = None
|
||||
_edit_managed_records = False
|
||||
|
||||
def __init__(self, auth_token=None, user=None, tenant=None, domain=None,
|
||||
user_domain=None, project_domain=None, is_admin=False,
|
||||
read_only=False, show_deleted=False, request_id=None,
|
||||
resource_uuid=None, overwrite=True, roles=None,
|
||||
service_catalog=None, all_tenants=False, abandon=None,
|
||||
tsigkey_id=None, user_identity=None, original_tenant=None):
|
||||
tsigkey_id=None, user_identity=None, original_tenant=None,
|
||||
edit_managed_records=False):
|
||||
|
||||
# NOTE: user_identity may be passed in, but will be silently dropped as
|
||||
# it is a generated field based on several others.
|
||||
super(DesignateContext, self).__init__(
|
||||
|
@ -61,6 +64,7 @@ class DesignateContext(context.RequestContext):
|
|||
|
||||
self.all_tenants = all_tenants
|
||||
self.abandon = abandon
|
||||
self.edit_managed_records = edit_managed_records
|
||||
|
||||
def deepcopy(self):
|
||||
d = self.to_dict()
|
||||
|
@ -93,6 +97,7 @@ class DesignateContext(context.RequestContext):
|
|||
'service_catalog': self.service_catalog,
|
||||
'all_tenants': self.all_tenants,
|
||||
'abandon': self.abandon,
|
||||
'edit_managed_records': self.edit_managed_records,
|
||||
'tsigkey_id': self.tsigkey_id
|
||||
})
|
||||
|
||||
|
@ -167,6 +172,16 @@ class DesignateContext(context.RequestContext):
|
|||
policy.check('abandon_domain', self)
|
||||
self._abandon = value
|
||||
|
||||
@property
|
||||
def edit_managed_records(self):
|
||||
return self._edit_managed_records
|
||||
|
||||
@edit_managed_records.setter
|
||||
def edit_managed_records(self, value):
|
||||
if value:
|
||||
policy.check('edit_managed_records', self)
|
||||
self._edit_managed_records = value
|
||||
|
||||
|
||||
def get_current():
|
||||
return context.get_current()
|
||||
|
|
|
@ -132,7 +132,9 @@ class BaseAddressHandler(NotificationHandler):
|
|||
LOG.debug('Event data: %s' % data)
|
||||
data['domain'] = domain['name']
|
||||
|
||||
context = DesignateContext.get_admin_context(all_tenants=True)
|
||||
context = DesignateContext().elevated()
|
||||
context.all_tenants = True
|
||||
context.edit_managed_records = True
|
||||
|
||||
for addr in addresses:
|
||||
event_data = data.copy()
|
||||
|
@ -178,7 +180,9 @@ class BaseAddressHandler(NotificationHandler):
|
|||
'to remove managed=False'))
|
||||
criterion = criterion or {}
|
||||
|
||||
context = DesignateContext.get_admin_context(all_tenants=True)
|
||||
context = DesignateContext().elevated()
|
||||
context.all_tenants = True
|
||||
context.edit_managed_records = True
|
||||
|
||||
criterion.update({'domain_id': cfg.CONF[self.name].domain_id})
|
||||
|
||||
|
|
|
@ -48,6 +48,14 @@ class RecordSet(base.DictObjectMixin, base.PersistentObjectMixin,
|
|||
action = 'UPDATE'
|
||||
return action
|
||||
|
||||
@property
|
||||
def managed(self):
|
||||
managed = False
|
||||
for record in self.records:
|
||||
if record.managed:
|
||||
return True
|
||||
return managed
|
||||
|
||||
@property
|
||||
def status(self):
|
||||
# Return the worst status in order of ERROR, PENDING, ACTIVE
|
||||
|
|
|
@ -51,3 +51,18 @@ class TestDesignateContext(TestCase):
|
|||
ctxt = context.DesignateContext(user='12345', tenant='54321')
|
||||
with testtools.ExpectedException(exceptions.Forbidden):
|
||||
ctxt.all_tenants = True
|
||||
|
||||
def test_edit_managed_records(self):
|
||||
ctxt = context.DesignateContext(user='12345', tenant='54321')
|
||||
admin_ctxt = ctxt.elevated()
|
||||
|
||||
admin_ctxt.edit_managed_records = True
|
||||
|
||||
self.assertFalse(ctxt.is_admin)
|
||||
self.assertTrue(admin_ctxt.is_admin)
|
||||
self.assertTrue(admin_ctxt.edit_managed_records)
|
||||
|
||||
def test_edit_managed_records_failure(self):
|
||||
ctxt = context.DesignateContext(user='12345', tenant='54321')
|
||||
with testtools.ExpectedException(exceptions.Forbidden):
|
||||
ctxt.edit_managed_records = True
|
||||
|
|
|
@ -46,6 +46,16 @@ tutorial`_ for more info.
|
|||
.. _cURL: http://curl.haxx.se/
|
||||
.. _cURL tutorial: http://curl.haxx.se/docs/manual.html
|
||||
|
||||
HTTP Headers
|
||||
============
|
||||
|
||||
These headers work for all APIs
|
||||
|
||||
* X-Designate-Edit-Managed-Records
|
||||
- Allows admins (or users with the right role) to modify managed records (records created by designate-sink / reverse floating ip API)
|
||||
* X-Auth-All-Projects
|
||||
- Allows admins (or users with the right role) to view and edit zones / recordsets for all tenants
|
||||
|
||||
API Versions
|
||||
============
|
||||
|
||||
|
|
|
@ -15,6 +15,8 @@
|
|||
|
||||
"all_tenants": "rule:admin",
|
||||
|
||||
"edit_managed_records" : "rule:admin",
|
||||
|
||||
"use_low_ttl": "rule:admin",
|
||||
|
||||
"get_quotas": "rule:admin_or_owner",
|
||||
|
|
Loading…
Reference in New Issue