Mitigate OSSN-0075

Modified the current ``glance-manage db purge`` command to eliminate images
table from purging the records. Added new command
``glance-manage db purge_images_table`` to purge the records from images
table.

DocImpact
SecurityImpact

Change-Id: Ie6641659b54543ed9f96c393d664e52a26bfaf6a
Implements: blueprint mitigate-ossn-0075
This commit is contained in:
Abhishek Kekane 2018-07-02 10:03:48 +00:00
parent 54329c6a21
commit 5cc9d99935
3 changed files with 89 additions and 12 deletions

View File

@ -329,24 +329,17 @@ class DbCommands(object):
metadata.db_export_metadefs(db_api.get_engine(),
path)
@args('--age_in_days', type=int,
help='Purge deleted rows older than age in days')
@args('--max_rows', type=int,
help='Limit number of records to delete')
def purge(self, age_in_days=30, max_rows=100):
"""Purge deleted rows older than a given age from glance tables."""
def _purge(self, age_in_days, max_rows, purge_images_only=False):
try:
age_in_days = int(age_in_days)
except ValueError:
sys.exit(_("Invalid int value for age_in_days: "
"%(age_in_days)s") % {'age_in_days': age_in_days})
try:
max_rows = int(max_rows)
except ValueError:
sys.exit(_("Invalid int value for max_rows: "
"%(max_rows)s") % {'max_rows': max_rows})
if age_in_days < 0:
sys.exit(_("Must supply a non-negative value for age."))
if age_in_days >= (int(time.time()) / 86400):
@ -354,15 +347,34 @@ class DbCommands(object):
if max_rows < 1:
sys.exit(_("Minimal rows limit is 1."))
ctx = context.get_admin_context(show_deleted=True)
try:
db_api.purge_deleted_rows(ctx, age_in_days, max_rows)
if purge_images_only:
db_api.purge_deleted_rows_from_images(ctx, age_in_days,
max_rows)
else:
db_api.purge_deleted_rows(ctx, age_in_days, max_rows)
except exception.Invalid as exc:
sys.exit(exc.msg)
except db_exc.DBReferenceError:
sys.exit(_("Purge command failed, check glance-manage"
" logs for more details."))
@args('--age_in_days', type=int,
help='Purge deleted rows older than age in days')
@args('--max_rows', type=int,
help='Limit number of records to delete')
def purge(self, age_in_days=30, max_rows=100):
"""Purge deleted rows older than a given age from glance tables."""
self._purge(age_in_days, max_rows)
@args('--age_in_days', type=int,
help='Purge deleted rows older than age in days')
@args('--max_rows', type=int,
help='Limit number of records to delete')
def purge_images_table(self, age_in_days=30, max_rows=100):
"""Purge deleted rows older than a given age from images table."""
self._purge(age_in_days, max_rows, purge_images_only=True)
class DbLegacyCommands(object):
"""Class for managing the db using legacy commands"""

View File

@ -1322,6 +1322,11 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None):
LOG.warning(_LW('Expected table %(tbl)s was not found in DB.'),
{'tbl': tbl})
else:
# NOTE(abhishekk): To mitigate OSSN-0075 images records should be
# purged with new ``purge-images-table`` command.
if tbl == 'images':
continue
tables.append(tbl)
for tbl in tables:
@ -1354,6 +1359,50 @@ def purge_deleted_rows(context, age_in_days, max_rows, session=None):
{'rows': rows, 'tbl': tbl})
def purge_deleted_rows_from_images(context, age_in_days, max_rows,
session=None):
"""Purges soft deleted rows
Deletes rows of table images table according to given age for
relevant models.
"""
# check max_rows for its maximum limit
_validate_db_int(max_rows=max_rows)
session = session or get_session()
metadata = MetaData(get_engine())
deleted_age = timeutils.utcnow() - datetime.timedelta(days=age_in_days)
tbl = 'images'
tab = Table(tbl, metadata, autoload=True)
LOG.info(
_LI('Purging deleted rows older than %(age_in_days)d day(s) '
'from table %(tbl)s'),
{'age_in_days': age_in_days, 'tbl': tbl})
column = tab.c.id
deleted_at_column = tab.c.deleted_at
query_delete = sql.select(
[column], deleted_at_column < deleted_age).order_by(
deleted_at_column).limit(max_rows)
delete_statement = DeleteFromSelect(tab, query_delete, column)
try:
with session.begin():
result = session.execute(delete_statement)
except db_exception.DBReferenceError as ex:
with excutils.save_and_reraise_exception():
LOG.error(_LE('DBError detected when purging from '
"%(tablename)s: %(error)s"),
{'tablename': tbl, 'error': six.text_type(ex)})
rows = result.rowcount
LOG.info(_LI('Deleted %(rows)d row(s) from table %(tbl)s'),
{'rows': rows, 'tbl': tbl})
def user_get_storage_usage(context, owner_id, image_id=None, session=None):
_check_image_id(image_id)
session = session or get_session()

View File

@ -1990,11 +1990,27 @@ class DBPurgeTests(test_utils.BaseTestCase):
def test_db_purge(self):
self.db_api.purge_deleted_rows(self.adm_context, 1, 5)
images = self.db_api.image_get_all(self.adm_context)
# Verify that no records from images have been deleted
# as images table will be purged using 'purge_images_table'
# command.
self.assertEqual(len(images), 3)
tasks = self.db_api.task_get_all(self.adm_context)
self.assertEqual(len(tasks), 2)
def test_db_purge_images_table(self):
# purge records from images_tags table
self.db_api.purge_deleted_rows(self.adm_context, 1, 5)
# purge records from images table
self.db_api.purge_deleted_rows_from_images(self.adm_context, 1, 5)
images = self.db_api.image_get_all(self.adm_context)
self.assertEqual(len(images), 2)
tasks = self.db_api.task_get_all(self.adm_context)
self.assertEqual(len(tasks), 2)
def test_purge_fk_constraint_failure(self):
def test_purge_images_table_fk_constraint_failure(self):
"""Test foreign key constraint failure
Test whether foreign key constraint failure during purge
@ -2053,7 +2069,7 @@ class DBPurgeTests(test_utils.BaseTestCase):
# Purge all records deleted at least 10 days ago
self.assertRaises(db_exception.DBReferenceError,
db_api.purge_deleted_rows,
db_api.purge_deleted_rows_from_images,
self.adm_context,
age_in_days=10,
max_rows=50)