From 9379ca043654ee57db711e7bb71c2b11724a2f47 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 4 Jun 2025 07:38:53 -0700 Subject: [PATCH 1/7] get rid of oscrypto --- app/notifications/sns_cert_validator.py | 27 +++++++++++++++----- poetry.lock | 33 +------------------------ pyproject.toml | 1 - 3 files changed, 22 insertions(+), 39 deletions(-) diff --git a/app/notifications/sns_cert_validator.py b/app/notifications/sns_cert_validator.py index 861927ea9..de5c353e5 100644 --- a/app/notifications/sns_cert_validator.py +++ b/app/notifications/sns_cert_validator.py @@ -2,8 +2,12 @@ import base64 import re from urllib.parse import urlparse -import oscrypto.asymmetric -import oscrypto.errors +#import oscrypto.asymmetric +#import oscrypto.errors +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import padding +from cryptography.exceptions import InvalidSignature import requests import six @@ -110,15 +114,26 @@ 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), + public_key = certificate.public_key() + public_key.verify( signature, string_to_sign, - "sha1", + padding.PKCS1v15(), + hashes.SHA256() # or SHA1? ) + #oscrypto.asymmetric.rsa_pkcs1v15_verify( + # oscrypto.asymmetric.load_certificate(certificate), + # signature, + # string_to_sign, + # "sha1", + #) return True - except oscrypto.errors.SignatureError: + #except oscrypto.errors.SignatureError: + except InvalidSignature: raise ValidationError("Invalid signature") diff --git a/poetry.lock b/poetry.lock index 1b7291d3d..cfa62e31f 100644 --- a/poetry.lock +++ b/poetry.lock @@ -211,18 +211,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" @@ -3135,25 +3123,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" @@ -5608,4 +5577,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 3e4577d64..f026ced23 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -41,7 +41,6 @@ marshmallow = "==3.26.1" marshmallow-sqlalchemy = "==1.0.0" 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" From 5eade384c53ab3d301840e707b71bcce69c9f2cd Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 4 Jun 2025 07:42:41 -0700 Subject: [PATCH 2/7] fix imports --- app/notifications/sns_cert_validator.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/app/notifications/sns_cert_validator.py b/app/notifications/sns_cert_validator.py index de5c353e5..0ea874015 100644 --- a/app/notifications/sns_cert_validator.py +++ b/app/notifications/sns_cert_validator.py @@ -2,15 +2,16 @@ import base64 import re from urllib.parse import urlparse -#import oscrypto.asymmetric -#import oscrypto.errors -from cryptography import x509 -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.asymmetric import padding -from cryptography.exceptions import InvalidSignature import requests import six +# import oscrypto.asymmetric +# import oscrypto.errors +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 @@ -122,18 +123,15 @@ def validate_sns_cert(sns_payload): try: public_key = certificate.public_key() public_key.verify( - signature, - string_to_sign, - padding.PKCS1v15(), - hashes.SHA256() # or SHA1? + signature, string_to_sign, padding.PKCS1v15(), hashes.SHA256() # or SHA1? ) - #oscrypto.asymmetric.rsa_pkcs1v15_verify( + # oscrypto.asymmetric.rsa_pkcs1v15_verify( # oscrypto.asymmetric.load_certificate(certificate), # signature, # string_to_sign, # "sha1", - #) + # ) return True - #except oscrypto.errors.SignatureError: + # except oscrypto.errors.SignatureError: except InvalidSignature: raise ValidationError("Invalid signature") From 57f98b15070b170372372be10a754a4e16197999 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Wed, 4 Jun 2025 08:03:01 -0700 Subject: [PATCH 3/7] cleanup --- .ds.baseline | 6 +++--- .github/workflows/checks.yml | 10 +--------- .github/workflows/daily_checks.yml | 10 +--------- app/notifications/sns_cert_validator.py | 10 ---------- 4 files changed, 5 insertions(+), 31 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index 9077a065b..ca965b9fe 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -161,7 +161,7 @@ "filename": ".github/workflows/daily_checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 71, + "line_number": 63, "is_secret": false }, { @@ -169,7 +169,7 @@ "filename": ".github/workflows/daily_checks.yml", "hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8", "is_verified": false, - "line_number": 87, + "line_number": 79, "is_secret": false } ], @@ -384,5 +384,5 @@ } ] }, - "generated_at": "2025-06-02T13:22:36Z" + "generated_at": "2025-06-04T15:02:41Z" } 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 0ea874015..2ea0d724f 100644 --- a/app/notifications/sns_cert_validator.py +++ b/app/notifications/sns_cert_validator.py @@ -4,9 +4,6 @@ from urllib.parse import urlparse import requests import six - -# import oscrypto.asymmetric -# import oscrypto.errors from cryptography import x509 from cryptography.exceptions import InvalidSignature from cryptography.hazmat.primitives import hashes @@ -125,13 +122,6 @@ def validate_sns_cert(sns_payload): public_key.verify( signature, string_to_sign, padding.PKCS1v15(), hashes.SHA256() # or SHA1? ) - # oscrypto.asymmetric.rsa_pkcs1v15_verify( - # oscrypto.asymmetric.load_certificate(certificate), - # signature, - # string_to_sign, - # "sha1", - # ) return True - # except oscrypto.errors.SignatureError: except InvalidSignature: raise ValidationError("Invalid signature") From ed4cbbc05b1b5ca0d8698601060d353e86eb1e29 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Wed, 4 Jun 2025 12:02:52 -0400 Subject: [PATCH 4/7] Unpin egress proxy release This changeset unpins the egress proxy release now that we have resolved the other issues surrounding the connectivity to S3. Signed-off-by: Carlo Costino --- .ds.baseline | 12 +----------- .github/actions/deploy-proxy/action.yml | 2 +- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index ca965b9fe..784df96d9 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", @@ -384,5 +374,5 @@ } ] }, - "generated_at": "2025-06-04T15:02:41Z" + "generated_at": "2025-06-04T16:02:28Z" } 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: From aa9cafb5f17b5128e0505816973275c7fd06099c Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 5 Jun 2025 08:12:56 -0700 Subject: [PATCH 5/7] improve debug of external issues --- app/status/healthcheck.py | 49 ++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 19 deletions(-) diff --git a/app/status/healthcheck.py b/app/status/healthcheck.py index 16fd65a09..3c9093e8a 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,29 +11,40 @@ 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 ) @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, + ) def get_db_version(): From 2abcbc55602001d47831b41ec770b813e52906c2 Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 5 Jun 2025 08:17:30 -0700 Subject: [PATCH 6/7] cleanup --- app/status/healthcheck.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/app/status/healthcheck.py b/app/status/healthcheck.py index 3c9093e8a..35e304f00 100644 --- a/app/status/healthcheck.py +++ b/app/status/healthcheck.py @@ -28,6 +28,7 @@ def show_status(): 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") @@ -45,9 +46,17 @@ def live_service_and_organization_counts(): 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") From a6bc7b4985d7bff5b07ecfaa32992099b8b7f0d2 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Thu, 5 Jun 2025 17:41:03 -0400 Subject: [PATCH 7/7] Add one more cert environment variable This changeset adds an additional environment variable to enforce usage of the correct CA certificate in case any libraries override it. Please see https://cloud.gov/docs/management/container-to-container/#addressing-certificate-validation-errors for more details. Signed-off-by: Carlo Costino --- manifest.yml | 1 + 1 file changed, 1 insertion(+) 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"