Skip to content

Commit

Permalink
Merge pull request #1 from mam-dev/initial-code
Browse files Browse the repository at this point in the history
Initial code
  • Loading branch information
bunny-therapist authored Nov 4, 2022
2 parents 0d123f7 + 7c8ba3d commit a12c65e
Show file tree
Hide file tree
Showing 14 changed files with 1,542 additions and 0 deletions.
7 changes: 7 additions & 0 deletions .flake8
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
[flake8]
max-line-length = 88
extend-ignore = E203
extend-exclude =
.tox,
build,
venv
26 changes: 26 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: CI

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

jobs:
build:
strategy:
matrix:
python_version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
runs-on: "ubuntu-latest"
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python_version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python_version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox
- name: Run tox
run: tox -e py$(sed 's/\.//' ${{ matrix.python_version }})
19 changes: 19 additions & 0 deletions .github/workflows/publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Thanks to: Sean Hammond
# https://www.seanh.cc/2022/05/21/publishing-python-packages-from-github-actions
name: Publish to PyPI.org
on:
release:
types: [published]
jobs:
pypi:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0
- run: python3 -m pip install --upgrade build && python3 -m build
- name: Publish package
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_API_TOKEN }}
96 changes: 96 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# security-constraints

Security-constraints is a command-line application used
to fetch security vulnerabilities in Python packages from
external sources and from them generate version constraints
for the packages.

The constraints can then be given to `pip install` with the `-c` option,
either on the command line or in a requirements file.

## Installation

Just install it with `pip`:
```bash
pip install security-constraints
```

## Usage

The environment variable `SC_GITHUB_TOKEN` needs to be set
to a valid GitHub token which provides read access to public
repositories. This is needed in order to access GitHub Security
Advisory. Once this is set, you can simply run the program to
output safe pip constraints to stdout.

```bash
>security-constraints
# Generated by security-constraints on 2022-11-04T08:33:54.523625
# Data sources: Github Security Advisory
# Configuration: {'ignore_ids': []}
...
vncauthproxy<0,>=1.2.0 # CVE-2022-36436 (ID: GHSA-237r-mx84-7x8c)
waitress!=1.4.2 # CVE-2020-5236 (ID: GHSA-73m2-3pwg-5fgc)
waitress>=1.4.0 # GHSA-4ppp-gpcr-7qf6 (ID: GHSA-4ppp-gpcr-7qf6)
ymlref>0.1.1 # CVE-2018-20133 (ID: GHSA-8r8j-xvfj-36f9)
>
```

You can use `--output` to instead output to a file.

```bash
>security-constraints --output constraints.txt
>cat constraints.txt
# Generated by security-constraints on 2022-11-04T08:33:54.523625
# Data sources: Github Security Advisory
# Configuration: {'ignore_ids': []}
...
vncauthproxy<0,>=1.2.0 # CVE-2022-36436 (ID: GHSA-237r-mx84-7x8c)
waitress!=1.4.2 # CVE-2020-5236 (ID: GHSA-73m2-3pwg-5fgc)
waitress>=1.4.0 # GHSA-4ppp-gpcr-7qf6 (ID: GHSA-4ppp-gpcr-7qf6)
ymlref>0.1.1 # CVE-2018-20133 (ID: GHSA-8r8j-xvfj-36f9)
>
```

You can provide a space-separated list of IDs of vulnerabilities that
should be ignored. The IDs in question are those that appear in after
`ID:` in the comments in the output.

```bash
>security-constraints --ignore-ids GHSA-4ppp-gpcr-7qf6 GHSA-8r8j-xvfj-36f9
# Generated by security-constraints on 2022-11-04T08:33:54.523625
# Data sources: Github Security Advisory
# Configuration: {'ignore_ids': ['GHSA-4ppp-gpcr-7qf6', 'GHSA-8r8j-xvfj-36f9']}
...
vncauthproxy<0,>=1.2.0 # CVE-2022-36436 (ID: GHSA-237r-mx84-7x8c)
waitress!=1.4.2 # CVE-2020-5236 (ID: GHSA-73m2-3pwg-5fgc)
>
```

The IDs to ignore can also be given in a configuration file using `--config`.
To create an initial configuration file, you can use `--dump-config`. This
will dump the current configuration (including any `--ignore-ids` passed) to
stdout and then exit. You can redirect this into a file to create an
initial configuration file. The configuration file is in yaml format.

```bash
>security-constraints --ignore-ids GHSA-4ppp-gpcr-7qf6 GHSA-8r8j-xvfj-36f9 --dump-config > sc_config.yaml
>cat sc_config.yaml
ignore_ids:
- GHSA-4ppp-gpcr-7qf6
- GHSA-8r8j-xvfj-36f9
>security-constraints --config sc_config.yaml
# Generated by security-constraints on 2022-11-04T08:33:54.523625
# Data sources: Github Security Advisory
# Configuration: {'ignore_ids': ['GHSA-4ppp-gpcr-7qf6', 'GHSA-8r8j-xvfj-36f9']}
...
vncauthproxy<0,>=1.2.0 # CVE-2022-36436 (ID: GHSA-237r-mx84-7x8c)
waitress!=1.4.2 # CVE-2020-5236 (ID: GHSA-73m2-3pwg-5fgc)
>
```

## Contributing
Pull requests as well as new issues are welcome.

[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
![example workflow](https://github.com/mam-dev/security-constraints/actions/workflows/ci.yml/badge.svg)
61 changes: 61 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
[project]
name = "security-constraints"
version = "1.0.0"
description = "Fetches security vulnerabilities and creates pip-constraints based on them."
readme = "README.md"
license = {file = "LICENSE"}
requires-python = ">=3.7"
dependencies = [
"requests",
"pyyaml",
"importlib-metadata >= 1.0 ; python_version < '3.8'"
]

[project.optional-dependencies]
test = [
"pytest",
"requests-mock",
"freezegun"
]
lint = [
"isort",
"black",
"flake8",
"mypy",
"types-requests",
"types-PyYAML"
]

[project.scripts]
security-constraints = "security_constraints.main:main"

[build-system]
requires = ["setuptools>=51", "wheel", "setuptools_scm[toml]>=6.2"]
build-backend = "setuptools.build_meta"

[tool.setuptools_scm]

[tool.setuptools.packages.find]
where = ["src"]
namespaces = false

[tool.isort]
profile = "black"
src_paths = ["src", "test"]

[tool.pytest.ini_options]
minversion = "6.0"
usefixtures = ["requests_mock"]
testpaths = ["test"]

[tool.mypy]
warn_return_any = true
warn_unused_configs = true
warn_unused_ignores = true
warn_redundant_casts = true
warn_unreachable = true
files = ["src", "test"]

[[tool.mypy.overrides]]
module = 'py'
ignore_missing_imports = true
Empty file.
96 changes: 96 additions & 0 deletions src/security_constraints/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
"""This module contains common definitions for use in any other module."""
import abc
import dataclasses
from typing import Dict, List


class SecurityConstraintsError(Exception):
"""Base class for all exceptions in this application."""


class FailedPrerequisitesError(SecurityConstraintsError):
"""Error raised when something is missing in order to run the application."""


class FetchVulnerabilitiesError(SecurityConstraintsError):
"""Error which occurred when fetching vulnerabilities."""


@dataclasses.dataclass
class Configuration:
"""The application configuration.
Corresponds to the contents of a configuration file.
"""

ignore_ids: List[str] = dataclasses.field(default_factory=list)

def to_dict(self) -> Dict:
return dataclasses.asdict(self)

@classmethod
def from_dict(cls, json: Dict) -> "Configuration":
return cls(**json)

@classmethod
def supported_keys(cls) -> List[str]:
"""Return a list of keys which are supported in the config file."""
return list(cls().to_dict().keys())


@dataclasses.dataclass
class PackageConstraints:
"""Version constraints for a single python package.
Attributes:
package: The name of the package.
specifies: A list of version specifiers, e.g. ">3.0".
"""

package: str
specifiers: List[str] = dataclasses.field(default_factory=list)

def __str__(self) -> str:
return f"{self.package}{','.join(self.specifiers)}"


@dataclasses.dataclass
class SecurityVulnerability:
"""A security vulnerability in a Python package.
Attributes:
name: Human-readable name of the vulnerability.
identifier: Used to uniquely identify this vulnerability,
e.g. when ignoring it.
package: The name of the affected Python package.
vulnerable_range: String specifying which versions are vulnerable.
Syntax:
= 0.2.0 denotes a single vulnerable version.
<= 1.0.8 denotes a version range up to and including the specified version
< 0.1.11 denotes a version range up to, but excluding, the specified version
>= 4.3.0, < 4.3.5 denotes a version range with a known min and max version.
>= 0.0.1 denotes a version range with a known minimum, but no known maximum.
"""

name: str
identifier: str
package: str
vulnerable_range: str

def __str__(self) -> str:
return self.name


class SecurityVulnerabilityDatabaseAPI(abc.ABC):
"""An API toward a database of security vulnerabilities in Python packages."""

@abc.abstractmethod
def get_database_name(self) -> str:
"""Return the name of the vulnerability database in human-readable text."""

@abc.abstractmethod
def get_vulnerabilities(self) -> List[SecurityVulnerability]:
"""Fetch and return all relevant security vulnerabilities from the database."""
Loading

0 comments on commit a12c65e

Please sign in to comment.