Example #1
0
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
    )
Example #2
0
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)
Example #3
0
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
Example #4
0
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),
        ),
    )
Example #5
0
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
Example #6
0
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
Example #7
0
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,
    )
Example #8
0
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
Example #9
0
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)
Example #10
0
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])
Example #11
0
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
Example #12
0
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
Example #13
0
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")
Example #14
0
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
Example #15
0
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
Example #16
0
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
Example #17
0
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"),
    )
Example #18
0
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
Example #19
0
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()
Example #20
0
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,
    )
Example #21
0
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)
Example #22
0
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)
Example #23
0
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)
Example #24
0
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