From 0f3db709ea5930c227394a4e3099974b58b4a863 Mon Sep 17 00:00:00 2001 From: Joseph Schorr Date: Tue, 13 Oct 2015 18:14:52 -0400 Subject: [PATCH] Add a vulnerability_found event for notice when we detect a vuln Fixes #637 Note: This PR does *not* actually raise the event; it merely adds support for it --- config.py | 2 +- data/database.py | 1 + .../50925110da8c_add_event_specific_config.py | 27 +++ ...add_support_for_quay_s_security_indexer.py | 2 +- ...dc2d819c5_add_vulnerability_found_event.py | 41 ++++ data/model/notification.py | 5 +- endpoints/api/repositorynotification.py | 5 + endpoints/notificationevent.py | 34 +++ events/vulnerability_found.html | 4 + initdb.py | 4 +- .../create-external-notification-dialog.css | 20 ++ .../create-external-notification-dialog.html | 210 ++++++++++-------- static/js/directives/object-order-by.js | 17 ++ .../ui/create-external-notification-dialog.js | 3 + .../js/services/external-notification-data.js | 20 +- static/js/services/notification-service.js | 22 +- static/js/services/string-builder-service.js | 109 +++++---- static/js/services/vulnerability-service.js | 98 ++++++++ util/migrate/backfill_checksums.py | 11 +- 19 files changed, 476 insertions(+), 159 deletions(-) create mode 100644 data/migrations/versions/50925110da8c_add_event_specific_config.py create mode 100644 data/migrations/versions/5cdc2d819c5_add_vulnerability_found_event.py create mode 100644 events/vulnerability_found.html create mode 100644 static/css/directives/ui/create-external-notification-dialog.css create mode 100644 static/js/directives/object-order-by.js create mode 100644 static/js/services/vulnerability-service.js diff --git a/config.py b/config.py index 39a2697dc..b049152d0 100644 --- a/config.py +++ b/config.py @@ -255,5 +255,5 @@ class DefaultConfig(object): FEATURE_SECURITY_SCANNER = True SECURITY_SCANNER = { 'ENDPOINT': 'http://192.168.99.100:6060', - 'ENGINE_VERSION_TARGET': 1 + 'ENGINE_VERSION_TARGET': 1, } diff --git a/data/database.py b/data/database.py index 305a34436..c87ece328 100644 --- a/data/database.py +++ b/data/database.py @@ -751,6 +751,7 @@ class RepositoryNotification(BaseModel): method = ForeignKeyField(ExternalNotificationMethod) title = CharField(null=True) config_json = TextField() + event_config_json = TextField(default='{}') class RepositoryAuthorizedEmail(BaseModel): diff --git a/data/migrations/versions/50925110da8c_add_event_specific_config.py b/data/migrations/versions/50925110da8c_add_event_specific_config.py new file mode 100644 index 000000000..eb9e9c1c8 --- /dev/null +++ b/data/migrations/versions/50925110da8c_add_event_specific_config.py @@ -0,0 +1,27 @@ +"""Add event-specific config + +Revision ID: 50925110da8c +Revises: 2fb9492c20cc +Create Date: 2015-10-13 18:03:14.859839 + +""" + +# revision identifiers, used by Alembic. +revision = '50925110da8c' +down_revision = '2fb9492c20cc' + +from alembic import op +import sqlalchemy as sa +from util.migrate import UTF8LongText + + +def upgrade(tables): + ### commands auto generated by Alembic - please adjust! ### + op.add_column('repositorynotification', sa.Column('event_config_json', UTF8LongText, nullable=False)) + ### end Alembic commands ### + + +def downgrade(tables): + ### commands auto generated by Alembic - please adjust! ### + op.drop_column('repositorynotification', 'event_config_json') + ### end Alembic commands ### diff --git a/data/migrations/versions/57dad559ff2d_add_support_for_quay_s_security_indexer.py b/data/migrations/versions/57dad559ff2d_add_support_for_quay_s_security_indexer.py index c834ab6db..7cd0f84c4 100644 --- a/data/migrations/versions/57dad559ff2d_add_support_for_quay_s_security_indexer.py +++ b/data/migrations/versions/57dad559ff2d_add_support_for_quay_s_security_indexer.py @@ -6,7 +6,7 @@ Create Date: 2015-07-13 16:51:41.669249 # revision identifiers, used by Alembic. revision = '57dad559ff2d' -down_revision = '3ff4fbc94644' +down_revision = '35f538da62' from alembic import op import sqlalchemy as sa diff --git a/data/migrations/versions/5cdc2d819c5_add_vulnerability_found_event.py b/data/migrations/versions/5cdc2d819c5_add_vulnerability_found_event.py new file mode 100644 index 000000000..76051323a --- /dev/null +++ b/data/migrations/versions/5cdc2d819c5_add_vulnerability_found_event.py @@ -0,0 +1,41 @@ +"""Add vulnerability_found event + +Revision ID: 5cdc2d819c5 +Revises: 50925110da8c +Create Date: 2015-10-13 18:05:32.157858 + +""" + +# revision identifiers, used by Alembic. +revision = '5cdc2d819c5' +down_revision = '50925110da8c' + +from alembic import op +import sqlalchemy as sa + + + +def upgrade(tables): + op.bulk_insert(tables.externalnotificationevent, + [ + {'id':6, 'name':'vulnerability_found'}, + ]) + + op.bulk_insert(tables.notificationkind, + [ + {'id':11, 'name':'vulnerability_found'}, + ]) + + +def downgrade(tables): + op.execute( + (tables.externalnotificationevent.delete() + .where(tables.externalnotificationevent.c.name == op.inline_literal('vulnerability_found'))) + + ) + + op.execute( + (tables.notificationkind.delete() + .where(tables.notificationkind.c.name == op.inline_literal('vulnerability_found'))) + + ) \ No newline at end of file diff --git a/data/model/notification.py b/data/model/notification.py index 87ae7f7ca..409d6c000 100644 --- a/data/model/notification.py +++ b/data/model/notification.py @@ -113,12 +113,13 @@ def delete_matching_notifications(target, kind_name, **kwargs): notification.delete_instance() -def create_repo_notification(repo, event_name, method_name, config, title=None): +def create_repo_notification(repo, event_name, method_name, method_config, event_config, title=None): event = ExternalNotificationEvent.get(ExternalNotificationEvent.name == event_name) method = ExternalNotificationMethod.get(ExternalNotificationMethod.name == method_name) return RepositoryNotification.create(repository=repo, event=event, method=method, - config_json=json.dumps(config), title=title) + config_json=json.dumps(method_config), title=title, + event_config_json=json.dumps(event_config)) def get_repo_notification(uuid): diff --git a/endpoints/api/repositorynotification.py b/endpoints/api/repositorynotification.py index 832328cbe..30c71cf54 100644 --- a/endpoints/api/repositorynotification.py +++ b/endpoints/api/repositorynotification.py @@ -57,6 +57,10 @@ class RepositoryNotificationList(RepositoryParamResource): 'type': 'object', 'description': 'JSON config information for the specific method of notification' }, + 'eventConfig': { + 'type': 'object', + 'description': 'JSON config information for the specific event of notification', + }, 'title': { 'type': 'string', 'description': 'The human-readable title of the notification', @@ -84,6 +88,7 @@ class RepositoryNotificationList(RepositoryParamResource): new_notification = model.notification.create_repo_notification(repo, parsed['event'], parsed['method'], parsed['config'], + parsed['eventConfig'], parsed.get('title', None)) resp = notification_view(new_notification) diff --git a/endpoints/notificationevent.py b/endpoints/notificationevent.py index b1d319a1f..ebd7e10b6 100644 --- a/endpoints/notificationevent.py +++ b/endpoints/notificationevent.py @@ -84,6 +84,40 @@ def _build_summary(event_data): return summary +class VulnerabilityFoundEvent(NotificationEvent): + @classmethod + def event_name(cls): + return 'vulnerability_found' + + def get_level(self, event_data, notification_data): + priority = event_data['vulnerability']['priority'] + if priority == 'Defcon1' or priority == 'Critical': + return 'error' + + if priority == 'Medium' or priority == 'High': + return 'warning' + + return 'info' + + def get_sample_data(self, repository): + return build_event_data(repository, { + 'tags': ['latest', 'prod'], + 'image': 'some-image-id', + 'vulnerability': { + 'id': 'CVE-FAKE-CVE', + 'description': 'A futurist vulnerability', + 'link': 'https://security-tracker.debian.org/tracker/CVE-FAKE-CVE', + 'priority': 'Critical', + }, + }) + + def get_summary(self, event_data, notification_data): + msg = '%s vulnerability detected in repository %s in tags %s' + return msg % (event_data['vulnerability']['priority'], + event_data['repository'], + ', '.join(event_data['tags'])) + + class BuildQueueEvent(NotificationEvent): @classmethod def event_name(cls): diff --git a/events/vulnerability_found.html b/events/vulnerability_found.html new file mode 100644 index 000000000..f20f4053b --- /dev/null +++ b/events/vulnerability_found.html @@ -0,0 +1,4 @@ +A {{ event_data.vulnerability.priority }} vulnerability ({{ event_data.vulnerability.id }}) was detected in tags + {{ 'tags' | icon_image }} +{% for tag in event_data.tags %}{%if loop.index > 1 %}, {% endif %}{{ (event_data.repository, tag) | repository_tag_reference }}{% endfor %} in + repository {{ event_data.repository | repository_reference }} \ No newline at end of file diff --git a/initdb.py b/initdb.py index 357afd5e2..80c9fa952 100644 --- a/initdb.py +++ b/initdb.py @@ -95,7 +95,7 @@ def __create_subtree(repo, structure, creator_username, parent, tag_map): for path_builder in paths: path = path_builder(new_image.storage.uuid) store.put_content('local_us', path, checksum) - + new_image.security_indexed = False new_image.security_indexed_engine = maxsize new_image.save() @@ -314,6 +314,7 @@ def initialize_database(): ExternalNotificationEvent.create(name='build_start') ExternalNotificationEvent.create(name='build_success') ExternalNotificationEvent.create(name='build_failure') + ExternalNotificationEvent.create(name='vulnerability_found') ExternalNotificationMethod.create(name='quay_notification') ExternalNotificationMethod.create(name='email') @@ -328,6 +329,7 @@ def initialize_database(): NotificationKind.create(name='build_start') NotificationKind.create(name='build_success') NotificationKind.create(name='build_failure') + NotificationKind.create(name='vulnerability_found') NotificationKind.create(name='password_required') NotificationKind.create(name='over_private_usage') diff --git a/static/css/directives/ui/create-external-notification-dialog.css b/static/css/directives/ui/create-external-notification-dialog.css new file mode 100644 index 000000000..12394955c --- /dev/null +++ b/static/css/directives/ui/create-external-notification-dialog.css @@ -0,0 +1,20 @@ +#createNotificationModal .dropdown-select { + margin: 0px; +} + +#createNotificationModal .options-table { + width: 100%; + margin-bottom: 10px; +} + +#createNotificationModal .options-table td { + padding-bottom: 6px; +} + +#createNotificationModal .options-table td.name { + width: 160px; +} + +#createNotificationModal .options-table-wrapper { + padding: 10px; +} \ No newline at end of file diff --git a/static/directives/create-external-notification-dialog.html b/static/directives/create-external-notification-dialog.html index 592efc322..b249e819f 100644 --- a/static/directives/create-external-notification-dialog.html +++ b/static/directives/create-external-notification-dialog.html @@ -1,12 +1,12 @@ -