diff --git a/.ds.baseline b/.ds.baseline index 9f962cba3..68fec2da9 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -127,16 +127,6 @@ } ], "results": { - ".github/actions/deploy-proxy/action.yml": [ - { - "type": "Hex High Entropy String", - "filename": ".github/actions/deploy-proxy/action.yml", - "hashed_secret": "a6c13f5da3788e8d654cd24001dc79a238723248", - "is_verified": false, - "line_number": 18, - "is_secret": false - } - ], ".github/workflows/checks.yml": [ { "type": "Secret Keyword", @@ -161,7 +151,7 @@ "filename": ".github/workflows/daily_checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 71, + "line_number": 63, "is_secret": false }, { @@ -169,7 +159,7 @@ "filename": ".github/workflows/daily_checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 87, + "line_number": 79, "is_secret": false } ], diff --git a/.github/actions/deploy-proxy/action.yml b/.github/actions/deploy-proxy/action.yml index 02393d6a1..0ffc05066 100644 --- a/.github/actions/deploy-proxy/action.yml +++ b/.github/actions/deploy-proxy/action.yml @@ -15,7 +15,7 @@ inputs: default: https://github.com/GSA-TTS/cg-egress-proxy.git proxy_version: description: git ref to be deployed - default: 1500c67157c1a7a6fbbda7a2de172b3d0a67e703 + default: main runs: using: composite steps: diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index e180a92eb..cd2670d05 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -87,15 +87,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - name: Create requirements.txt - run: poetry export --output requirements_tmp.txt --without-hashes - - name: Filter requirements.txt - run: grep -v "oscrypto@ git" requirements_tmp.txt > requirements.txt - - name: Verify requirements.txt - run: ls -l requirements.txt - - name: Print requirements.txt - run: | - echo "Contents of requirements.txt:" - cat requirements.txt + run: poetry export --output requirements.txt - uses: pypa/gh-action-pip-audit@v1.1.0 with: inputs: requirements.txt diff --git a/.github/workflows/daily_checks.yml b/.github/workflows/daily_checks.yml index 43bd01dcd..717b4825c 100644 --- a/.github/workflows/daily_checks.yml +++ b/.github/workflows/daily_checks.yml @@ -26,15 +26,7 @@ jobs: - uses: actions/checkout@v4 - uses: ./.github/actions/setup-project - name: Create requirements.txt - run: poetry export --output requirements_tmp.txt --without-hashes - - name: Filter requirements.txt - run: grep -v "oscrypto@ git" requirements_tmp.txt > requirements.txt - - name: Verify requirements.txt - run: ls -l requirements.txt - - name: Print requirements.txt - run: | - echo "Contents of requirements.txt:" - cat requirements.txt + run: poetry export --output requirements.txt - uses: pypa/gh-action-pip-audit@v1.1.0 with: inputs: requirements.txt diff --git a/app/notifications/sns_cert_validator.py b/app/notifications/sns_cert_validator.py index 861927ea9..2ea0d724f 100644 --- a/app/notifications/sns_cert_validator.py +++ b/app/notifications/sns_cert_validator.py @@ -2,10 +2,12 @@ import base64 import re from urllib.parse import urlparse -import oscrypto.asymmetric -import oscrypto.errors import requests import six +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding from app import redis_store from app.config import Config @@ -110,15 +112,16 @@ def validate_sns_cert(sns_payload): if isinstance(certificate, six.text_type): certificate = certificate.encode() + # load the certificate + certificate = x509.load_pem_x509_certificate(certificate) + signature = base64.b64decode(sns_payload["Signature"]) try: - oscrypto.asymmetric.rsa_pkcs1v15_verify( - oscrypto.asymmetric.load_certificate(certificate), - signature, - string_to_sign, - "sha1", + public_key = certificate.public_key() + public_key.verify( + signature, string_to_sign, padding.PKCS1v15(), hashes.SHA256() # or SHA1? ) return True - except oscrypto.errors.SignatureError: + except InvalidSignature: raise ValidationError("Invalid signature") diff --git a/app/status/healthcheck.py b/app/status/healthcheck.py index 16fd65a09..35e304f00 100644 --- a/app/status/healthcheck.py +++ b/app/status/healthcheck.py @@ -1,4 +1,4 @@ -from flask import Blueprint, jsonify, request +from flask import Blueprint, current_app, jsonify, request from sqlalchemy import text from app import db, version @@ -11,32 +11,52 @@ status = Blueprint("status", __name__) @status.route("/", methods=["GET"]) @status.route("/_status", methods=["GET", "POST"]) def show_status(): - if request.args.get("simple", None): - return jsonify(status="ok"), 200 - else: - return ( - jsonify( - status="ok", # This should be considered part of the public API - git_commit=version.__git_commit__, - build_time=version.__time__, - db_version=get_db_version(), - ), - 200, + try: + if request.args.get("simple", None): + return jsonify(status="ok"), 200 + else: + return ( + jsonify( + status="ok", # This should be considered part of the public API + git_commit=version.__git_commit__, + build_time=version.__time__, + db_version=get_db_version(), + ), + 200, + ) + except Exception as e: + current_app.logger.error( + f"Unexpected error in show_status: {str(e)}", exc_info=True ) + raise Exception(status_code=503, detail="Service temporarily unavailable") @status.route("/_status/live-service-and-organization-counts") def live_service_and_organization_counts(): - return ( - jsonify( - organizations=dao_count_organizations_with_live_services(), - services=dao_count_live_services(), - ), - 200, - ) + try: + return ( + jsonify( + organizations=dao_count_organizations_with_live_services(), + services=dao_count_live_services(), + ), + 200, + ) + except Exception as e: + current_app.logger.error( + f"Unexpected error in live_service_and_organization_counts: {str(e)}", + exc_info=True, + ) + raise Exception(status_code=503, detail="Service temporarily unavailable") def get_db_version(): - query = "SELECT version_num FROM alembic_version" - full_name = db.session.execute(text(query)).fetchone()[0] - return full_name + try: + query = "SELECT version_num FROM alembic_version" + full_name = db.session.execute(text(query)).fetchone()[0] + return full_name + except Exception as e: + current_app.logger.error( + f"Unexpected error in get_db_version: {str(e)}", + exc_info=True, + ) + raise Exception(status_code=503, detail="Database temporarily unavailable") diff --git a/manifest.yml b/manifest.yml index 0763a1911..29fc23b42 100644 --- a/manifest.yml +++ b/manifest.yml @@ -54,5 +54,6 @@ applications: SECRET_KEY: ((SECRET_KEY)) AWS_US_TOLL_FREE_NUMBER: ((default_toll_free_number)) + SSL_CERT_FILE: "/etc/ssl/certs/ca-certificates.crt" REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt" NEW_RELIC_CA_BUNDLE_PATH: "/etc/ssl/certs/ca-certificates.crt" diff --git a/poetry.lock b/poetry.lock index a93533774..50dbdd718 100644 --- a/poetry.lock +++ b/poetry.lock @@ -297,18 +297,6 @@ types-python-dateutil = ">=2.8.10" doc = ["doc8", "sphinx (>=7.0.0)", "sphinx-autobuild", "sphinx-autodoc-typehints", "sphinx_rtd_theme (>=1.3.0)"] test = ["dateparser (==1.*)", "pre-commit", "pytest", "pytest-cov", "pytest-mock", "pytz (==2021.1)", "simplejson (==3.*)"] -[[package]] -name = "asn1crypto" -version = "1.5.1" -description = "Fast ASN.1 parser and serializer with definitions for private keys, public keys, certificates, CRL, OCSP, CMS, PKCS#3, PKCS#7, PKCS#8, PKCS#12, PKCS#5, X.509 and TSP" -optional = false -python-versions = "*" -groups = ["main"] -files = [ - {file = "asn1crypto-1.5.1-py2.py3-none-any.whl", hash = "sha256:db4e40728b728508912cbb3d44f19ce188f218e9eba635821bb4b68564f8fd67"}, - {file = "asn1crypto-1.5.1.tar.gz", hash = "sha256:13ae38502be632115abf8a24cbe5f4da52e3b5231990aff31123c805306ccb9c"}, -] - [[package]] name = "async-timeout" version = "5.0.1" @@ -3229,25 +3217,6 @@ files = [ [package.extras] dev = ["black", "mypy", "pytest"] -[[package]] -name = "oscrypto" -version = "1.3.0" -description = "TLS (SSL) sockets, key generation, encryption, decryption, signing, verification and KDFs using the OS crypto libraries. Does not require a compiler, and relies on the OS for patching. Works on Windows, OS X and Linux/BSD." -optional = false -python-versions = "*" -groups = ["main"] -files = [] -develop = false - -[package.dependencies] -asn1crypto = ">=1.5.1" - -[package.source] -type = "git" -url = "https://github.com/wbond/oscrypto.git" -reference = "1547f53" -resolved_reference = "1547f535001ba568b239b8797465536759c742a3" - [[package]] name = "packageurl-python" version = "0.16.0" @@ -5701,4 +5670,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = "^3.13.2" -content-hash = "12dd1482c9ad1e19d4edefb9fa0abf614346883c37dc600769bb3acf610410d4" +content-hash = "879c7bb9dd451bb098c7a092498dd458224dcc766eb504fafe2cdc10255ccf7e" diff --git a/pyproject.toml b/pyproject.toml index f275b2324..1e68ee973 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -40,7 +40,6 @@ marshmallow = "^4.0.0" marshmallow-sqlalchemy = "^1.4.2" newrelic = "*" notifications-python-client = "==10.0.1" -oscrypto = { git = "https://github.com/wbond/oscrypto.git", rev = "1547f53" } packaging = "==25.0" poetry-dotenv-plugin = "==0.2.0" psycopg2-binary = "==2.9.10"