Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
version: 2
updates:
# Python dependencies (requirements.txt): weekly check for updates and
# security fixes; PRs must pass CI before merging.
- package-ecosystem: "pip"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"

# Keep the GitHub Actions used by CI/release workflows current.
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
labels:
- "dependencies"
82 changes: 82 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: CI

on:
push:
branches: ["main"]
pull_request:
branches: ["main"]
workflow_call:
schedule:
# Weekly security audit, Monday 06:17 UTC
- cron: "17 6 * * 1"

permissions:
contents: read

jobs:
test:
name: Smoke test (Python ${{ matrix.python-version }})
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.10", "3.12", "3.14"]
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: pip install -r requirements.txt

- name: Run smoke test
run: python scripts/smoke_test.py

security-audit:
name: Dependency CVE audit (pip-audit)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install pip-audit
run: pip install pip-audit

- name: Audit pinned requirements for known CVEs
run: pip-audit -r requirements.txt

alert-on-scheduled-failure:
name: Open issue if weekly audit fails
runs-on: ubuntu-latest
needs: [test, security-audit]
if: always() && github.event_name == 'schedule' && contains(needs.*.result, 'failure')
permissions:
issues: write
steps:
- name: Create alert issue unless one is already open
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
title="Scheduled security/CI check failed"
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title \"$title\"" --json number --jq 'length')
if [ "$existing" -gt 0 ]; then
echo "An alert issue is already open; not creating a duplicate."
exit 0
fi
{
echo "The weekly scheduled run of CI failed — most likely a new CVE affects a pinned dependency, or the code no longer passes the smoke test."
echo ""
echo "Failed run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo ""
echo "Until this is resolved, release gating will block new releases. Review the run logs, decide on the fix (usually a dependency bump in requirements.txt), and close this issue once CI is green again."
} > issue-body.md
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "$title" \
--assignee "${{ github.repository_owner }}" \
--body-file issue-body.md
75 changes: 72 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,44 @@ on:
workflow_dispatch:
inputs:
tag:
description: "Tag to create the release under (e.g. v1.0.0)"
description: "Tag to create the release under (e.g. v1.0.2)"
required: true
default: "v1.0.0"
default: "v1.0.2"
skip_checks:
description: "OWNER OVERRIDE: publish the release even though checks failed (use only after reviewing the failure)"
type: boolean
required: false
default: false

permissions:
contents: write
issues: write

jobs:
release:
# Every release candidate must pass the same checks as CI:
# smoke test on all supported Python versions + dependency CVE audit.
checks:
if: >-
startsWith(github.ref, 'refs/tags/v') ||
startsWith(github.ref, 'refs/heads/release/v') ||
github.event_name == 'workflow_dispatch'
uses: ./.github/workflows/ci.yml
permissions:
contents: read
issues: write

release:
runs-on: ubuntu-latest
needs: [checks]
# Publish only when checks passed, or when a human explicitly set the
# skip_checks override on a manual run after reviewing the failure.
if: >-
always() &&
(startsWith(github.ref, 'refs/tags/v') ||
startsWith(github.ref, 'refs/heads/release/v') ||
github.event_name == 'workflow_dispatch') &&
(needs.checks.result == 'success' ||
(github.event_name == 'workflow_dispatch' && inputs.skip_checks))
steps:
- uses: actions/checkout@v4

Expand Down Expand Up @@ -57,8 +81,53 @@ jobs:
exit 1
fi

if [ "${{ needs.checks.result }}" != "success" ]; then
echo "::warning::Release published with skip_checks override; checks result was: ${{ needs.checks.result }}"
fi

gh release create "$tag" \
--repo "$GITHUB_REPOSITORY" \
--title "OCSP Server Test Suite $tag" \
--notes-file "$notes_file" \
"${extra_args[@]}"

# If checks failed, no release is created. Alert the owner with an issue
# so a human can fix the problem or consciously decide to release anyway.
alert-release-blocked:
runs-on: ubuntu-latest
needs: [checks]
if: >-
always() && needs.checks.result == 'failure' &&
!(github.event_name == 'workflow_dispatch' && inputs.skip_checks)
steps:
- name: Open issue describing the blocked release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
case "$GITHUB_REF" in
refs/tags/*) tag="${GITHUB_REF#refs/tags/}" ;;
refs/heads/release/*) tag="${GITHUB_REF#refs/heads/release/}" ;;
*) tag="${{ inputs.tag }}" ;;
esac
title="Release $tag blocked: pre-release checks failed"
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title \"$title\"" --json number --jq 'length')
if [ "$existing" -gt 0 ]; then
echo "An alert issue for $tag is already open; not creating a duplicate."
exit 0
fi
{
echo "A release of **$tag** was requested, but the pre-release checks (smoke test and dependency CVE audit) failed, so **no release was published**."
echo ""
echo "Failed run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo ""
echo "Your options:"
echo "1. **Fix the problem** (code fix or dependency bump), push it, and trigger the release again — it will publish automatically once checks pass."
echo "2. **Decide to release anyway**: go to Actions -> Create Release -> Run workflow, enter the tag, and set skip_checks. This is logged as an explicit owner override."
echo ""
echo "Close this issue once resolved."
} > issue-body.md
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "$title" \
--assignee "${{ github.repository_owner }}" \
--body-file issue-body.md
160 changes: 160 additions & 0 deletions .github/workflows/security-autopatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
name: Security Autopatch

# Daily watch for published CVE fixes in the project's dependencies.
# When a fix exists outside the allowed version range in requirements.txt,
# this workflow raises the range, re-runs the full check suite, and — only
# if everything passes — commits the bump and publishes a patch release.
# If checks fail, or a vulnerability has no fix, no release happens:
# an issue is opened and assigned to the owner to decide.

on:
schedule:
- cron: "43 10 * * *"
workflow_dispatch:

permissions:
contents: write
issues: write

concurrency:
group: security-autopatch
cancel-in-progress: false

jobs:
autopatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install tooling
run: pip install pip-audit packaging

- name: Audit dependencies and raise vulnerable ranges
id: patch
run: |
python scripts/security_autopatch.py audit
{
echo "changed=$(python -c "import json; print(str(json.load(open('autopatch-summary.json'))['changed']).lower())")"
echo "unfixable=$(python -c "import json; print(str(bool(json.load(open('autopatch-summary.json'))['unfixable'])).lower())")"
} >> "$GITHUB_OUTPUT"

- name: Verify patched requirements (install, re-audit, smoke test)
id: verify
if: steps.patch.outputs.changed == 'true'
continue-on-error: true
run: |
pip install -r requirements.txt
pip-audit -r requirements.txt
python scripts/smoke_test.py

- name: Commit bump and publish patch release
id: publish
if: steps.patch.outputs.changed == 'true' && steps.verify.outcome == 'success'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
latest=$(gh release view --repo "$GITHUB_REPOSITORY" --json tagName --jq .tagName)
base="${latest#v}"
IFS=. read -r major minor patch <<< "$base"
next="v${major}.${minor}.$((patch + 1))"
echo "next=$next" >> "$GITHUB_OUTPUT"
notes=".github/release-notes/${next}.md"

python scripts/security_autopatch.py notes --version "$next" --output "$notes"

git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git add requirements.txt "$notes"
git commit -m "fix(security): raise dependency ranges for published CVE fixes ($next)"
git push origin "HEAD:${GITHUB_REF_NAME}"

gh release create "$next" \
--repo "$GITHUB_REPOSITORY" \
--target "$(git rev-parse HEAD)" \
--title "OCSP Server Test Suite $next" \
--notes-file "$notes"

- name: Notify owner of the automated release
if: steps.publish.outcome == 'success' && steps.publish.outputs.next != ''
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
next="${{ steps.publish.outputs.next }}"
{
echo "The security-autopatch workflow found published CVE fixes for a dependency, raised the version range in requirements.txt, ran the full check suite (install, re-audit, smoke test on the updated dependencies), and published **$next** automatically."
echo ""
echo "Release: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/releases/tag/$next"
echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo ""
echo "No action is required; this issue is informational. Close it once you've seen it. If the release should not have happened, delete it from the releases page and revert the commit."
} > issue-body.md
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "Automated security release $next published" \
--assignee "${{ github.repository_owner }}" \
--body-file issue-body.md

- name: Alert owner that autopatch is blocked
if: steps.patch.outputs.changed == 'true' && steps.verify.outcome == 'failure'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
title="Security autopatch blocked: fix available but checks failed"
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title \"$title\"" --json number --jq 'length')
if [ "$existing" -gt 0 ]; then
echo "An alert issue is already open; not creating a duplicate."
exit 0
fi
{
echo "A dependency has a published CVE fix outside the allowed range in requirements.txt, but after raising the range the check suite (install, re-audit, smoke test) FAILED — most likely the new library version breaks the code. **No release was published and no changes were committed.**"
echo ""
echo "Failed run (see logs and the audit summary): $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo ""
echo "Your decision is needed:"
echo "1. Adapt the code to the new library version, then release normally - the gate will pass."
echo "2. Or release anyway via Actions -> Create Release -> Run workflow with skip_checks (not recommended while the smoke test fails)."
echo ""
echo "Close this issue once resolved. The workflow will keep failing daily (without duplicate issues) until the vulnerability is addressed."
} > issue-body.md
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "$title" \
--assignee "${{ github.repository_owner }}" \
--body-file issue-body.md

- name: Alert owner about vulnerabilities with no published fix
if: steps.patch.outputs.unfixable == 'true'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
title="Dependency vulnerability needs a manual decision"
existing=$(gh issue list --repo "$GITHUB_REPOSITORY" --state open \
--search "in:title \"$title\"" --json number --jq 'length')
if [ "$existing" -gt 0 ]; then
echo "An alert issue is already open; not creating a duplicate."
exit 0
fi
{
echo "The daily security audit found vulnerabilities that cannot be fixed automatically (no published fix version, or the package is not a direct requirement):"
echo ""
echo '```json'
python -c "import json; print(json.dumps(json.load(open('autopatch-summary.json'))['unfixable'], indent=2))"
echo '```'
echo ""
echo "Run: $GITHUB_SERVER_URL/$GITHUB_REPOSITORY/actions/runs/$GITHUB_RUN_ID"
echo ""
echo "Review the advisories and decide how to proceed (wait for a fix, mitigate, or pin differently). Close this issue once resolved."
} > issue-body.md
gh issue create --repo "$GITHUB_REPOSITORY" \
--title "$title" \
--assignee "${{ github.repository_owner }}" \
--body-file issue-body.md

- name: Fail the run when autopatch is blocked
if: steps.patch.outputs.changed == 'true' && steps.verify.outcome == 'failure'
run: |
echo "::error::A CVE fix is available but the verification suite failed; see the assigned issue."
exit 1
17 changes: 17 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Python bytecode and caches
__pycache__/
*.py[cod]

# Virtual environments
venv/
.venv/
env/

# Test and tooling caches
.pytest_cache/
.mypy_cache/
.ruff_cache/

# OS cruft
.DS_Store
Thumbs.db
Binary file removed __pycache__/app.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/__init__.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/config.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/exporters.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/models.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/monitor.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/ocsp_client.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/runner.cpython-313.pyc
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/tests_crl.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/tests_ikev2.cpython-313.pyc
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file removed ocsp_tester/__pycache__/tests_status.cpython-313.pyc
Binary file not shown.
Loading
Loading