def cve(cve_id): """ Retrieve and display an individual CVE details page """ cve = db_session.query(CVE).get(cve_id.upper()) if not cve: flask.abort(404) releases = ( db_session.query(Release) .order_by(desc(Release.release_date)) .filter( or_( Release.codename == "upstream", Release.support_expires > datetime.now(), Release.esm_expires > datetime.now(), ) ) .all() ) return flask.render_template( "security/cve/cve.html", cve=cve, releases=releases )
def notice(notice_id): notice = db_session.query(Notice).get(notice_id) if not notice: flask.abort(404) package_descriptions = set() release_packages = SortedDict() if notice.release_packages: for codename, pkgs in notice.release_packages.items(): release_version = ( db_session.query(Release) .filter(Release.codename == codename) .one() .version ) release_packages[release_version] = [] for package in pkgs: if package["is_source"]: package_descriptions.add( f"{package['name']} - {package['description']}" ) else: release_packages[release_version].append(package) # Order packages for release by the name key release_packages[release_version].sort(key=lambda pkg: pkg["name"]) notice_cve_ids = [cve.id for cve in notice.cves] related_notices = ( db_session.query(Notice) .filter(Notice.cves.any(CVE.id.in_(notice_cve_ids))) .filter(Notice.id != notice.id) .all() ) notice = { "id": notice.id, "title": notice.title, "published": notice.published, "summary": notice.summary, "details": markdown_parser(notice.details), "instructions": markdown_parser(notice.instructions), "package_descriptions": sorted(package_descriptions), "release_packages": release_packages, "releases": notice.releases, "cves": notice.cves, "references": notice.references, "related_notices": [ { "id": related_notice.id, "package_list": ", ".join(related_notice.package_list), } for related_notice in related_notices ], } return flask.render_template("security/notice.html", notice=notice)
def update_notice(notice_id): """ PUT method to update a single notice """ notice = db_session.query(Notice).get(notice_id) if not notice: return ( flask.jsonify({"message": f"Notice {notice_id} doesn't exist"}), 404, ) notice_schema = NoticeSchema() notice_schema.context["release_codenames"] = [ rel.codename for rel in db_session.query(Release).all() ] try: notice_data = notice_schema.load(flask.request.json, unknown=EXCLUDE) except ValidationError as error: return ( flask.jsonify({ "message": "Invalid payload", "errors": error.messages }), 400, ) notice = _update_notice_object(notice, notice_data) db_session.add(notice) db_session.commit() return flask.jsonify({"message": "Notice updated"}), 200
def notices(): page = flask.request.args.get("page", default=1, type=int) details = flask.request.args.get("details", type=str) release = flask.request.args.get("release", type=str) order_by = flask.request.args.get("order", type=str) releases = ( db_session.query(Release) .order_by(desc(Release.release_date)) .filter(Release.version.isnot(None)) .all() ) notices_query = db_session.query(Notice) if release: notices_query = notices_query.join(Release, Notice.releases).filter( Release.codename == release ) if details: notices_query = notices_query.filter( or_( Notice.id.ilike(f"%{details}%"), Notice.details.ilike(f"%{details}%"), Notice.title.ilike(f"%{details}%"), Notice.cves.any(CVE.id.ilike(f"%{details}%")), ) ) # Snapshot total results for search page_size = 10 total_results = notices_query.count() total_pages = ceil(total_results / page_size) offset = page * page_size - page_size if page < 1 or 1 < page > total_pages: flask.abort(404) sort = asc if order_by == "oldest" else desc notices = ( notices_query.order_by(sort(Notice.published)) .offset(offset) .limit(page_size) .all() ) return flask.render_template( "security/notices.html", notices=notices, releases=releases, pagination=dict( current_page=page, total_pages=total_pages, total_results=total_results, page_first_result=offset + 1, page_last_result=offset + len(notices), ), )
def single_notices_sitemap(offset): notices = ( db_session.query(Notice) .order_by(Notice.published) .offset(offset) .limit(10000) .all() ) xml_sitemap = flask.render_template( "sitemap.xml", links=[ { "url": f"https://ubuntu.com/security/notices/{notice.id}", "last_updated": notice.published.strftime("%Y-%m-%d"), } for notice in notices ], ) response = flask.make_response(xml_sitemap) response.headers["Content-Type"] = "application/xml" response.headers["Cache-Control"] = "public, max-age=43200" return response
def delete_release(codename): """ DELETE method to delete a single release """ release = db_session.query(Release).get(codename) if not release: return ( flask.jsonify({"message": f"Release {codename} doesn't exist"}), 404, ) if len(release.statuses) > 0: return ( flask.jsonify( { "message": ( f"Cannot delete '{codename}' release. " f"Release already in use" ) } ), 400, ) db_session.delete(release) db_session.commit() return flask.jsonify({"message": f"Release {codename} deleted"}), 200
def read_notices(): """ GET method to get notices """ limit = flask.request.args.get("limit", default=20, type=int) offset = flask.request.args.get("offset", default=0, type=int) notices = ( db_session.query(Notice) .order_by(Notice.published) .offset(offset) .limit(limit) .all() ) return ( flask.jsonify( { "data": [notice.as_dict() for notice in notices], "limit": limit, "offset": offset, } ), 200, )
def create_notice(): """ POST method to create a new notice """ notice_schema = NoticeSchema() notice_schema.context["release_codenames"] = [ rel.codename for rel in db_session.query(Release).all() ] try: notice_data = notice_schema.load(flask.request.json) except ValidationError as error: return ( flask.jsonify({ "message": "Invalid payload", "errors": error.messages }), 400, ) db_session.add( _update_notice_object(Notice(id=notice_data["id"]), notice_data)) try: db_session.commit() except IntegrityError: return ( flask.jsonify( {"message": f"Notice {notice_data['id']} already exists"}), 400, ) return flask.jsonify({"message": "Notice created"}), 201
def notice(notice_id): notice = db_session.query(Notice).get(notice_id) if not notice: flask.abort(404) notice_packages = set() releases_packages = {} for release, packages in notice.packages.items(): release_name = ( db_session.query(Release) .filter(Release.codename == release) .one() .version ) releases_packages[release_name] = [] for name, package in packages.get("sources", {}).items(): # Build pacakges per release dict package["name"] = name releases_packages[release_name].append(package) # Build full package list description = package.get("description") package_name = f"{name} - {description}" if description else name notice_packages.add(package_name) # Guarantee release order releases_packages = OrderedDict( sorted(releases_packages.items(), reverse=True) ) notice = { "id": notice.id, "title": notice.title, "published": notice.published, "summary": notice.summary, "isummary": notice.isummary, "details": markdown_parser(notice.details), "instructions": markdown_parser(notice.instructions), "packages": notice_packages, "releases_packages": releases_packages, "releases": notice.releases, "cves": notice.cves, "references": notice.references, } return flask.render_template("security/notice.html", notice=notice)
def update_statuses(cve, data, packages): statuses = cve.packages statuses_query = db_session.query(Status).filter(Status.cve_id == cve.id) statuses_to_delete = { f"{v.package_name}||{v.release_codename}": v for v in statuses_query.all() } for package_data in data.get("packages", []): name = package_data["name"] if packages.get(name) is None: package = Package(name=name) package.source = package_data["source"] package.ubuntu = package_data["ubuntu"] package.debian = package_data["debian"] packages[name] = package db_session.add(package) for status_data in package_data["statuses"]: update_status = False codename = status_data["release_codename"] status = statuses[name].get(codename) if status is None: update_status = True status = Status( cve_id=cve.id, package_name=name, release_codename=codename ) elif f"{name}||{codename}" in statuses_to_delete: del statuses_to_delete[f"{name}||{codename}"] if status.status != status_data["status"]: update_status = True status.status = status_data["status"] if status.description != status_data["description"]: update_status = True status.description = status_data["description"] if status.component != status_data.get("component"): update_status = True status.component = status_data.get("component") if status.pocket != status_data.get("pocket"): update_status = True status.pocket = status_data.get("pocket") if update_status: statuses[name][codename] = status db_session.add(status) for key in statuses_to_delete: db_session.delete(statuses_to_delete[key])
def _update_notice_object(notice, data): """ Set fields on a Notice model object """ notice.title = data["title"] notice.summary = data["summary"] notice.details = data["description"] notice.release_packages = data["release_packages"] notice.published = data["published"] notice.references = data["references"] notice.instructions = data["instructions"] notice.releases = [ db_session.query(Release).get(codename) for codename in data["release_packages"].keys() ] notice.cves.clear() for cve_id in data["cves"]: notice.cves.append(db_session.query(CVE).get(cve_id) or CVE(id=cve_id)) return notice
def read_notice(notice_id): """ GET method to get notice by id """ notice = db_session.query(Notice).get(notice_id) if not notice: return ( flask.jsonify({"message": f"Notice {notice_id} does not exist"}), 404, ) return flask.jsonify({"data": notice.as_dict()}), 200
def notices_feed(feed_type): if feed_type not in ["atom", "rss"]: flask.abort(404) url_root = flask.request.url_root base_url = flask.request.base_url feed = FeedGenerator() feed.generator("Feedgen") feed.id(url_root) feed.copyright( f"{datetime.now().year} Canonical Ltd. " "Ubuntu and Canonical are registered trademarks of Canonical Ltd." ) feed.title("Ubuntu security notices") feed.description("Recent content on Ubuntu security notices") feed.link(href=base_url, rel="self") def feed_entry(notice, url_root): _id = notice.id title = f"{_id}: {notice.title}" description = notice.details published = notice.published notice_path = flask.url_for(".notice", notice_id=notice.id).lstrip("/") link = f"{url_root}{notice_path}" entry = FeedEntry() entry.id(link) entry.title(title) entry.description(description) entry.link(href=link) entry.published(f"{published} UTC") entry.author({"name": "Ubuntu Security Team"}) return entry notices = ( db_session.query(Notice) .order_by(desc(Notice.published)) .limit(10) .all() ) for notice in notices: feed.add_entry(feed_entry(notice, url_root), order="append") payload = feed.atom_str() if feed_type == "atom" else feed.rss_str() return flask.Response(payload, mimetype="text/xml")
def delete_notice(notice_id): """ DELETE method to delete a single notice """ notice = db_session.query(Notice).get(notice_id) if not notice: return ( flask.jsonify({"message": f"Notice {notice_id} doesn't exist"}), 404, ) db_session.delete(notice) db_session.commit() return flask.jsonify({"message": f"Notice {notice_id} deleted"}), 200
def delete_cve(cve_id): """ Delete a CVE from db @params string: query string with the CVE id """ cve_query = db_session.query(CVE) cve = cve_query.filter(CVE.id == cve_id).first() try: db_session.delete(cve) db_session.commit() except IntegrityError as error: return flask.jsonify({"message": error.orig.args[0]}), 400 return flask.jsonify({"message": "CVE deleted successfully"}), 200
def cves_sitemap(): cves_count = db_session.query(CVE).order_by(CVE.published).count() base_url = "https://ubuntu.com/security/cve" xml_sitemap = flask.render_template( "sitemap_index_template.xml", base_url=base_url, links=[{ "url": f"{base_url}/sitemap-{link * 10000}.xml", } for link in range(ceil(cves_count / 10000))], ) response = flask.make_response(xml_sitemap) response.headers["Content-Type"] = "application/xml" response.headers["Cache-Control"] = "public, max-age=43200" return response
def cve_index(): """ Display the list of CVEs, with pagination. Also accepts the following filtering query parameters: - order-by - "oldest" or "newest" - query - search query for the description field - priority - limit - default 20 - offset - default 0 """ # Query parameters query = flask.request.args.get("q", "").strip() priority = flask.request.args.get("priority") package = flask.request.args.get("package") limit = flask.request.args.get("limit", default=20, type=int) offset = flask.request.args.get("offset", default=0, type=int) component = flask.request.args.get("component") is_cve_id = re.match(r"^CVE-\d{4}-\d{4,7}$", query.upper()) if is_cve_id and db_session.query(CVE).get(query.upper()): return flask.redirect(f"/security/{query.lower()}") cves_query = (db_session.query(CVE).filter( CVE.statuses.any(Status.status.in_( Status.active_statuses))).filter(CVE.status == "active")) # Apply search filters if package: cves_query = cves_query.filter( CVE.statuses.any(Status.package_name.ilike(f"%{package}%"))) if priority: cves_query = cves_query.filter(CVE.priority == priority) if query: cves_query = cves_query.filter(CVE.description.ilike(f"%{query}%")) cves = (cves_query.order_by(desc( CVE.published)).offset(offset).limit(limit).all()) # Pagination total_results = cves_query.count() releases = (db_session.query(Release).order_by(desc( Release.release_date)).filter( or_( Release.support_expires > datetime.now(), Release.esm_expires > datetime.now(), )).all()) return flask.render_template( "security/cve/index.html", releases=releases, cves=cves, total_results=total_results, total_pages=ceil(total_results / limit), offset=offset, limit=limit, priority=priority, query=query, package=package, component=component, versions=flask.request.args.getlist("version"), statuses=flask.request.args.getlist("status"), )
def update_notice(): if not flask.request.json: return (flask.jsonify({"message": "No payload received"}), 400) notice_schema = NoticeSchema() try: data = notice_schema.load(flask.request.json, unknown=EXCLUDE) except ValidationError as error: return ( flask.jsonify( {"message": "Invalid payload", "errors": error.messages} ), 400, ) notice = db_session.query(Notice).get(data["notice_id"]) if not notice: return ( flask.jsonify( {"message": f"Notice {data['notice_id']} doesn't exist"} ), 404, ) notice.title = data["title"] notice.summary = data["summary"] notice.details = data["description"] notice.packages = data["releases"] notice.published = datetime.fromtimestamp(data["timestamp"]) if "action" in data: notice.instructions = data["action"] if "isummary" in data: notice.isummary = data["isummary"] # Clear m2m relations to re-add notice.cves.clear() notice.releases.clear() notice.references.clear() # Link releases for release_codename in data["releases"].keys(): try: notice.releases.append( db_session.query(Release) .filter(Release.codename == release_codename) .one() ) except NoResultFound: message = f"No release with codename: {release_codename}." return (flask.jsonify({"message": message}), 400) # Link CVEs, creating them if they don't exist refs = set(data.get("references", [])) for ref in refs: if ref.startswith("CVE-"): cve_id = ref[4:] cve = db_session.query(CVE).get(cve_id) if not cve: cve = CVE(id=cve_id) notice.cves.append(cve) else: reference = ( db_session.query(Reference) .filter(Reference.uri == ref) .first() ) if not reference: reference = Reference(uri=ref) notice.references.append(reference) db_session.add(notice) db_session.commit() return flask.jsonify({"message": "Notice updated"}), 200
def load_releases(): releases = [ Release( codename="warty", version="4.10", name="Warty Warthog", development=False, lts=False, release_date=datetime.strptime("2004-10-20", "%Y-%m-%d"), esm_expires=datetime.strptime("2004-10-20", "%Y-%m-%d"), support_expires=datetime.strptime("2004-10-20", "%Y-%m-%d"), ), Release( codename="hoary", version="5.04", name="Hoary Hedgehog", development=False, lts=False, release_date=datetime.strptime("2005-04-08", "%Y-%m-%d"), esm_expires=datetime.strptime("2005-04-08", "%Y-%m-%d"), support_expires=datetime.strptime("2005-04-08", "%Y-%m-%d"), ), Release( codename="breezy", version="5.10", name="Breezy Badger", development=False, lts=False, release_date=datetime.strptime("2005-10-13", "%Y-%m-%d"), esm_expires=datetime.strptime("2005-10-13", "%Y-%m-%d"), support_expires=datetime.strptime("2005-10-13", "%Y-%m-%d"), ), Release( codename="dapper", version="6.06", name="Dapper Drake", development=False, lts=True, release_date=datetime.strptime("2006-06-01", "%Y-%m-%d"), esm_expires=datetime.strptime("2006-06-01", "%Y-%m-%d"), support_expires=datetime.strptime("2006-06-01", "%Y-%m-%d"), ), Release( codename="edgy", version="6.10", name="Edgy Eft", development=False, lts=False, release_date=datetime.strptime("2006-10-26", "%Y-%m-%d"), esm_expires=datetime.strptime("2006-10-26", "%Y-%m-%d"), support_expires=datetime.strptime("2006-10-26", "%Y-%m-%d"), ), Release( codename="feisty", version="7.04", name="Feisty Fawn", development=False, lts=False, release_date=datetime.strptime("2007-04-19", "%Y-%m-%d"), esm_expires=datetime.strptime("2007-04-19", "%Y-%m-%d"), support_expires=datetime.strptime("2007-04-19", "%Y-%m-%d"), ), Release( codename="gutsy", version="7.10", name="Gutsy Gibbon", development=False, lts=False, release_date=datetime.strptime("2007-10-18", "%Y-%m-%d"), esm_expires=datetime.strptime("2007-10-18", "%Y-%m-%d"), support_expires=datetime.strptime("2007-10-18", "%Y-%m-%d"), ), Release( codename="hardy", version="8.04", name="Hardy Heron", development=False, lts=True, release_date=datetime.strptime("2008-04-24", "%Y-%m-%d"), esm_expires=datetime.strptime("2008-04-24", "%Y-%m-%d"), support_expires=datetime.strptime("2008-04-24", "%Y-%m-%d"), ), Release( codename="intrepid", version="8.10", name="Intrepid Ibex", development=False, lts=False, release_date=datetime.strptime("2008-10-30", "%Y-%m-%d"), esm_expires=datetime.strptime("2008-10-30", "%Y-%m-%d"), support_expires=datetime.strptime("2008-10-30", "%Y-%m-%d"), ), Release( codename="jaunty", version="9.04", name="Jaunty Jackalope", development=False, lts=False, release_date=datetime.strptime("2009-04-23", "%Y-%m-%d"), esm_expires=datetime.strptime("2008-10-30", "%Y-%m-%d"), support_expires=datetime.strptime("2008-10-30", "%Y-%m-%d"), ), Release( codename="karmic", version="9.10", name="Karmic Koala", development=False, lts=False, release_date=datetime.strptime("2009-10-29", "%Y-%m-%d"), esm_expires=datetime.strptime("2009-10-29", "%Y-%m-%d"), support_expires=datetime.strptime("2009-10-29", "%Y-%m-%d"), ), Release( codename="lucid", version="10.04", name="Lucid Lynx", development=False, lts=True, release_date=datetime.strptime("2010-04-29", "%Y-%m-%d"), esm_expires=datetime.strptime("2010-04-29", "%Y-%m-%d"), support_expires=datetime.strptime("2010-04-29", "%Y-%m-%d"), ), Release( codename="maverick", version="10.10", name="Maverick Meerkat", development=False, lts=False, release_date=datetime.strptime("2010-10-10", "%Y-%m-%d"), esm_expires=datetime.strptime("2010-10-10", "%Y-%m-%d"), support_expires=datetime.strptime("2010-10-10", "%Y-%m-%d"), ), Release( codename="natty", version="11.04", name="Natty Narwhal", development=False, lts=False, release_date=datetime.strptime("2011-04-28", "%Y-%m-%d"), esm_expires=datetime.strptime("2011-04-28", "%Y-%m-%d"), support_expires=datetime.strptime("2011-04-28", "%Y-%m-%d"), ), Release( codename="oneiric", version="11.10", name="Oneiric Ocelot", development=False, lts=False, release_date=datetime.strptime("2011-10-13", "%Y-%m-%d"), esm_expires=datetime.strptime("2011-10-13", "%Y-%m-%d"), support_expires=datetime.strptime("2011-10-13", "%Y-%m-%d"), ), Release( codename="precise", version="12.04", name="Precise Pangolin", development=False, lts=True, release_date=datetime.strptime("2012-04-26", "%Y-%m-%d"), esm_expires=datetime.strptime("2021-04-30", "%Y-%m-%d"), support_expires=datetime.strptime("2012-04-26", "%Y-%m-%d"), ), Release( codename="quantal", version="12.10", name="Quantal Quetzal", development=False, lts=False, release_date=datetime.strptime("2012-10-18", "%Y-%m-%d"), esm_expires=datetime.strptime("2012-10-18", "%Y-%m-%d"), support_expires=datetime.strptime("2012-10-18", "%Y-%m-%d"), ), Release( codename="raring", version="13.04", name="Raring Ringtail", development=False, lts=False, release_date=datetime.strptime("2013-04-25", "%Y-%m-%d"), esm_expires=datetime.strptime("2013-04-25", "%Y-%m-%d"), support_expires=datetime.strptime("2013-04-25", "%Y-%m-%d"), ), Release( codename="saucy", version="13.10", name="Saucy Salamander", development=False, lts=False, release_date=datetime.strptime("2013-10-17", "%Y-%m-%d"), esm_expires=datetime.strptime("2013-10-17", "%Y-%m-%d"), support_expires=datetime.strptime("2013-10-17", "%Y-%m-%d"), ), Release( codename="trusty", version="14.04", name="Trusty Tahr", development=False, lts=True, release_date=datetime.strptime("2014-04-17", "%Y-%m-%d"), esm_expires=datetime.strptime("2022-04-30", "%Y-%m-%d"), support_expires=datetime.strptime("2019-04-30", "%Y-%m-%d"), ), Release( codename="utopic", version="14.10", name="Utopic Unicorn", development=False, lts=False, release_date=datetime.strptime("2014-10-23", "%Y-%m-%d"), esm_expires=datetime.strptime("2014-10-23", "%Y-%m-%d"), support_expires=datetime.strptime("2014-10-23", "%Y-%m-%d"), ), Release( codename="vivid", version="15.04", name="Vivid Vervet", development=False, lts=False, release_date=datetime.strptime("2015-04-23", "%Y-%m-%d"), esm_expires=datetime.strptime("2015-04-23", "%Y-%m-%d"), support_expires=datetime.strptime("2015-04-23", "%Y-%m-%d"), ), Release( codename="wily", version="15.10", name="Wily Werewolf", development=False, lts=False, release_date=datetime.strptime("2015-10-22", "%Y-%m-%d"), esm_expires=datetime.strptime("2015-10-22", "%Y-%m-%d"), support_expires=datetime.strptime("2015-10-22", "%Y-%m-%d"), ), Release( codename="xenial", version="16.04", name="Xenial Xerus", development=False, lts=True, release_date=datetime.strptime("2016-04-21", "%Y-%m-%d"), esm_expires=datetime.strptime("2024-04-30", "%Y-%m-%d"), support_expires=datetime.strptime("2021-04-30", "%Y-%m-%d"), ), Release( codename="yakkety", version="16.10", name="Yakkety Yak", development=False, lts=False, release_date=datetime.strptime("2016-10-13", "%Y-%m-%d"), esm_expires=datetime.strptime("2016-10-13", "%Y-%m-%d"), support_expires=datetime.strptime("2016-10-13", "%Y-%m-%d"), ), Release( codename="zesty", version="17.04", name="Zesty Zapus", development=False, lts=False, release_date=datetime.strptime("2017-04-13", "%Y-%m-%d"), esm_expires=datetime.strptime("2017-04-13", "%Y-%m-%d"), support_expires=datetime.strptime("2017-04-13", "%Y-%m-%d"), ), Release( codename="artful", version="17.10", name="Artful Aardvark", development=False, lts=False, release_date=datetime.strptime("2017-10-19", "%Y-%m-%d"), esm_expires=datetime.strptime("2017-10-19", "%Y-%m-%d"), support_expires=datetime.strptime("2017-10-19", "%Y-%m-%d"), ), Release( codename="bionic", version="18.04", name="Bionic Beaver", development=False, lts=True, release_date=datetime.strptime("2018-04-26", "%Y-%m-%d"), esm_expires=datetime.strptime("2028-04-30", "%Y-%m-%d"), support_expires=datetime.strptime("2023-04-30", "%Y-%m-%d"), ), Release( codename="cosmic", version="18.10", name="Cosmic Cuttlefish", development=False, lts=False, release_date=datetime.strptime("2018-10-18", "%Y-%m-%d"), esm_expires=datetime.strptime("2018-10-18", "%Y-%m-%d"), support_expires=datetime.strptime("2018-10-18", "%Y-%m-%d"), ), Release( codename="disco", version="19.04", name="Disco Dingo", development=False, lts=False, release_date=datetime.strptime("2019-04-18", "%Y-%m-%d"), esm_expires=datetime.strptime("2019-04-18", "%Y-%m-%d"), support_expires=datetime.strptime("2019-04-18", "%Y-%m-%d"), ), Release( codename="eoan", version="19.10", name="Eoan Ermine", development=False, lts=False, release_date=datetime.strptime("2019-10-17", "%Y-%m-%d"), esm_expires=datetime.strptime("2020-07-31", "%Y-%m-%d"), support_expires=datetime.strptime("2019-10-17", "%Y-%m-%d"), ), Release( codename="focal", version="20.04", name="Focal Fossa", development=False, lts=True, release_date=datetime.strptime("2020-04-23", "%Y-%m-%d"), esm_expires=datetime.strptime("2030-04-30", "%Y-%m-%d"), support_expires=datetime.strptime("2025-04-30", "%Y-%m-%d"), ), Release( codename="groovy", version="20.10", name="Groovy Gorilla", development=True, lts=False, release_date=datetime.strptime("2020-10-20", "%Y-%m-%d"), esm_expires=datetime.strptime("2021-07-31", "%Y-%m-%d"), support_expires=datetime.strptime("2021-07-31", "%Y-%m-%d"), ), Release(codename="upstream", name="Upstream"), ] for release in releases: exists = db_session.query(Release).get(release.codename) if not exists: db_session.add(release) db_session.commit()
def cve_index(): """ Display the list of CVEs, with pagination. Also accepts the following filtering query parameters: - order-by - "oldest" or "newest" - query - search query for the description field - priority - limit - default 20 - offset - default 0 """ # Query parameters query = flask.request.args.get("q", "").strip() priority = flask.request.args.get("priority") package = flask.request.args.get("package") limit = flask.request.args.get("limit", default=20, type=int) offset = flask.request.args.get("offset", default=0, type=int) component = flask.request.args.get("component") versions = flask.request.args.getlist("version") statuses = flask.request.args.getlist("status") is_cve_id = re.match(r"^CVE-\d{4}-\d{4,7}$", query.upper()) if is_cve_id and db_session.query(CVE).get(query.upper()): return flask.redirect(f"/security/{query.lower()}") all_releases = ( db_session.query(Release) .order_by(desc(Release.release_date)) .filter(Release.codename != "upstream") .all() ) releases_query = db_session.query(Release).order_by(Release.release_date) if versions and not any(a in ["", "current"] for a in versions): releases_query = releases_query.filter(Release.codename.in_(versions)) else: releases_query = releases_query.filter( or_( Release.support_expires > datetime.now(), Release.esm_expires > datetime.now(), ) ).filter(Release.codename != "upstream") releases = releases_query.all() should_filter_by_version_and_status = ( versions and statuses and len(versions) == len(statuses) ) clean_versions = [] clean_statuses = [] if should_filter_by_version_and_status: raw_all_statuses = db_session.execute( "SELECT unnest(enum_range(NULL::statuses));" ).fetchall() all_statuses = ["".join(s) for s in raw_all_statuses] clean_versions = [ ( [version] if version not in ["", "current"] else [r.codename for r in releases] ) for version in versions ] clean_statuses = [ ([status] if status != "" else all_statuses) for status in statuses ] # query cves by filters cves_query = db_session.query( CVE, func.count("*").over().label("total") ).filter(CVE.status == "active") if priority: cves_query = cves_query.filter(CVE.priority == priority) if query: cves_query = cves_query.filter( or_( CVE.description.ilike(f"%{query}%"), CVE.ubuntu_description.ilike(f"%{query}%"), ) ) parameters = [] if package: parameters.append(Status.package_name == package) if component: parameters.append(Status.component == component) if should_filter_by_version_and_status: conditions = [] for key, version in enumerate(clean_versions): conditions.append( and_( Status.release_codename.in_(version), Status.status.in_(clean_statuses[key]), ) ) parameters.append(or_(*[c for c in conditions])) conditions = [] for key, version in enumerate(clean_versions): sub_conditions = [ Status.release_codename.in_(version), Status.status.in_(clean_statuses[key]), CVE.id == Status.cve_id, ] if package: sub_conditions.append(Status.package_name == package) if component: sub_conditions.append(Status.component == component) condition = Package.statuses.any( and_(*[sc for sc in sub_conditions]) ) conditions.append(condition) parameters.append(Status.package.has(and_(*[c for c in conditions]))) else: parameters.append(Status.status.in_(Status.active_statuses)) if len(parameters) > 0: cves_query = cves_query.filter( CVE.statuses.any(and_(*[p for p in parameters])) ) cves_query = ( cves_query.group_by(CVE.id) .order_by( case( [(CVE.published.is_(None), 1)], else_=0, ), desc(CVE.published), ) .limit(limit) .offset(offset) .from_self() .join(CVE.statuses) .options(contains_eager(CVE.statuses)) ) raw_cves = cves_query.all() # Pagination total_results = raw_cves[0][1] if raw_cves else 0 cves = [] for raw_cve in raw_cves: packages = raw_cve[0].packages # filter by package name if package: packages = { package_name: package_statuses for package_name, package_statuses in packages.items() if package_name == package } # filter by component if component: packages = { package_name: package_statuses for package_name, package_statuses in packages.items() if any( status.component == component for status in package_statuses.values() ) } if should_filter_by_version_and_status: packages = { package_name: package_statuses for package_name, package_statuses in packages.items() if all( any( package_status.release_codename in version and package_status.status in clean_statuses[key] for package_status in package_statuses.values() ) for key, version in enumerate(clean_versions) ) } # do not return cve if it has no packages left if not packages: continue cve = { "id": raw_cve[0].id, "priority": raw_cve[0].priority, "packages": packages, } cves.append(cve) return flask.render_template( "security/cve/index.html", releases=releases, all_releases=all_releases, cves=cves, total_results=total_results, total_pages=ceil(total_results / limit), offset=offset, limit=limit, priority=priority, query=query, package=package, component=component, versions=versions, statuses=statuses, )
def notice(notice_id): notice = db_session.query(Notice).get(notice_id) if not notice: flask.abort(404) package_descriptions = {} package_versions = {} release_packages = SortedDict() releases = { release.codename: release.version for release in db_session.query(Release).all() } if notice.release_packages: for codename, pkgs in notice.release_packages.items(): release_version = releases[codename] release_packages[release_version] = {} for package in pkgs: name = package["name"] if not package["is_source"]: release_packages[release_version][name] = package continue if notice.get_type == "LSN": if name not in package_descriptions: package_versions[name] = [] package_versions[name].append(package["version"]) versions = ", >= ".join(package_versions[name]) description = (f"{package['description']} - " f"(>= {versions})") package_descriptions[name] = description continue if name not in package_descriptions: package_descriptions[name] = package["description"] package_descriptions = { key: package_descriptions[key] for key in sorted(package_descriptions.keys()) } notice_cve_ids = [cve.id for cve in notice.cves] related_notices = (db_session.query(Notice).filter( Notice.cves.any( CVE.id.in_(notice_cve_ids))).filter(Notice.id != notice.id).all()) if notice.get_type == "LSN": template = "security/notices/lsn.html" else: template = "security/notices/usn.html" notice = { "id": notice.id, "title": notice.title, "published": notice.published, "summary": notice.summary, "details": markdown_parser(notice.get_processed_details), "instructions": markdown_parser(notice.instructions), "package_descriptions": package_descriptions, "release_packages": release_packages, "releases": notice.releases, "cves": notice.cves, "references": notice.references, "related_notices": [{ "id": related_notice.id, "package_list": ", ".join(related_notice.package_list), } for related_notice in related_notices], } return flask.render_template(template, notice=notice)
def notice(notice_id): notice = db_session.query(Notice).get(notice_id) if not notice: flask.abort(404) package_descriptions = {} release_packages = SortedDict() if notice.release_packages: for codename, pkgs in notice.release_packages.items(): release_version = ( db_session.query(Release) .filter(Release.codename == codename) .one() .version ) release_packages[release_version] = [] if notice.get_type == "USN": for package in pkgs: if package["is_source"]: name = package["name"] if name not in package_descriptions: package_descriptions[name] = package["description"] else: release_packages[release_version].append(package) elif notice.get_type == "LSN": for package in pkgs: name = package["name"] if name not in package_descriptions: package_descriptions[name] = package["description"] release_packages[release_version].append(package) # Order packages for release by the name key release_packages[release_version].sort(key=lambda pkg: pkg["name"]) package_descriptions = { key: package_descriptions[key] for key in sorted(package_descriptions.keys()) } instructions = "" instruction_packages = [] if notice.get_type == "USN": instructions = markdown_parser(notice.instructions) elif notice.get_type == "LSN": instructions = literal_eval(notice.instructions) for packages_lists in instructions.values(): instruction_packages += packages_lists set(instruction_packages) notice_cve_ids = [cve.id for cve in notice.cves] related_notices = ( db_session.query(Notice) .filter(Notice.cves.any(CVE.id.in_(notice_cve_ids))) .filter(Notice.id != notice.id) .all() ) if notice.get_type == "LSN": template = "security/notices/lsn.html" else: template = "security/notices/usn.html" notice = { "id": notice.id, "title": notice.title, "published": notice.published, "summary": notice.summary, "details": markdown_parser(notice.get_processed_details), "instructions": instructions, "instruction_packages": instruction_packages, "package_descriptions": package_descriptions, "release_packages": release_packages, "releases": notice.releases, "cves": notice.cves, "references": notice.references, "related_notices": [ { "id": related_notice.id, "package_list": ", ".join(related_notice.package_list), } for related_notice in related_notices ], } return flask.render_template(template, notice=notice)
def bulk_upsert_cve(): """ Receives a PUT request from load_cve.py Parses the object and bulk inserts or updates @returns 3 lists of CVEs, created CVEs, updated CVEs and failed CVEs """ cves_schema = CVESchema(many=True) cves_schema.context["release_codenames"] = [ rel.codename for rel in db_session.query(Release).all() ] try: cves_data = cves_schema.load(flask.request.json) except ValidationError as error: return ( flask.jsonify({ "message": "Invalid payload", "errors": error.messages }), 400, ) if len(cves_data) > 50: return ( flask.jsonify({ "message": ("Please only submit up to 50 CVEs at a time. " f"({len(cves_data)} submitted)") }), 413, ) packages = {} for package in db_session.query(Package).all(): packages[package.name] = package for data in cves_data: cve = db_session.query(CVE).get(data["id"]) or CVE(id=data["id"]) cve.status = data.get("status") cve.published = data.get("published") cve.priority = data.get("priority") cve.cvss3 = data.get("cvss3") cve.description = data.get("description") cve.ubuntu_description = data.get("ubuntu_description") cve.notes = data.get("notes") cve.references = data.get("references") cve.bugs = data.get("bugs") statuses = update_statuses(cve, data, packages, releases=db_session.query(Release)) db_session.add(cve) db_session.add_all(statuses) created = defaultdict(lambda: 0) updated = defaultdict(lambda: 0) for item in db_session.new: created[type(item).__name__] += 1 for item in db_session.dirty: updated[type(item).__name__] += 1 try: db_session.commit() except DataError as error: return ( flask.jsonify({ "message": "Failed bulk upserting session", "error": error.orig.args[0], }), 400, ) return (flask.jsonify({"created": created, "updated": updated}), 200)
def api_create_notice(): if not flask.request.json: return (flask.jsonify({"message": f"No payload received"}), 400) # Because we get a dict with ID as a key and the payload as a value notice_id, payload = flask.request.json.popitem() notice = db_session.query(Notice).filter(Notice.id == notice_id).first() if notice: return ( flask.jsonify({"message": f"Notice '{notice.id}' already exists"}), 400, ) notice_schema = NoticeSchema() try: data = notice_schema.load(payload, unknown=EXCLUDE) except ValidationError as error: return ( flask.jsonify({ "message": "Invalid payload", "errors": error.messages }), 400, ) notice = Notice( id=data["notice_id"], title=data["title"], summary=data["summary"], details=data["description"], packages=data["releases"], published=datetime.fromtimestamp(data["timestamp"]), ) if "action" in data: notice.instructions = data["action"] if "isummary" in data: notice.isummary = data["isummary"] # Link releases for release_codename in data["releases"].keys(): try: notice.releases.append( db_session.query(Release).filter( Release.codename == release_codename).one()) except NoResultFound: message = f"No release with codename: {release_codename}." return (flask.jsonify({"message": message}), 400) # Link CVEs, creating them if they don't exist refs = set(data.get("references", [])) for ref in refs: if ref.startswith("CVE-"): cve_id = ref[4:] cve = db_session.query(CVE).filter(CVE.id == cve_id).first() if not cve: cve = CVE(id=cve_id) notice.cves.append(cve) else: reference = (db_session.query(Reference).filter( Reference.uri == ref).first()) if not reference: reference = Reference(uri=ref) notice.references.append(reference) db_session.add(notice) db_session.commit() return flask.jsonify({"message": "Notice created"}), 201