From ac95bf371e47939f3d3dfa4bf5639545325ac8db Mon Sep 17 00:00:00 2001 From: dandds Date: Mon, 6 Aug 2018 11:12:58 -0400 Subject: [PATCH] implement CRL checking from authnid --- .gitignore | 4 +++ atst/app.py | 13 ++++++++++ atst/routes/__init__.py | 4 +-- config/base.ini | 2 ++ config/ci.ini | 1 + config/test.ini | 1 + script/sync-crls | 2 +- tests/fixtures/crl/client-ca.der.crl | Bin 0 -> 502 bytes tests/test_auth.py | 35 ++++++++++++++++++++++++++- 9 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tests/fixtures/crl/client-ca.der.crl diff --git a/.gitignore b/.gitignore index d3668074..6afb33d1 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,7 @@ static/assets/* log/* config/dev.ini + +# CRLs +/crl +/crl-tmp diff --git a/atst/app.py b/atst/app.py index 5a540b05..696cf645 100644 --- a/atst/app.py +++ b/atst/app.py @@ -1,5 +1,6 @@ import os import re +import pathlib from configparser import ConfigParser from flask import Flask, request, g from flask_session import Session @@ -13,6 +14,7 @@ from atst.routes import bp from atst.routes.workspaces import bp as workspace_routes from atst.routes.requests import requests_bp from atst.routes.dev import bp as dev_routes +from atst.domain.authnid.crl.validator import Validator ENV = os.getenv("FLASK_ENV", "dev") @@ -33,6 +35,7 @@ def make_app(config): app.config.update({"SESSION_REDIS": redis}) make_flask_callbacks(app) + make_crl_validator(app) db.init_app(app) Session(app) @@ -123,3 +126,13 @@ def make_config(): def make_redis(config): return redis.Redis.from_url(config['REDIS_URI']) + +def make_crl_validator(app): + crl_locations = [] + for filename in pathlib.Path(app.config["CRL_DIRECTORY"]).glob("*"): + crl_locations.append(filename.absolute()) + app.crl_validator = Validator( + roots=[app.config["CA_CHAIN"]], crl_locations=crl_locations + ) + for e in app.crl_validator.errors: + app.logger.error(e) diff --git a/atst/routes/__init__.py b/atst/routes/__init__.py index 8b0decc4..660796d1 100644 --- a/atst/routes/__init__.py +++ b/atst/routes/__init__.py @@ -35,7 +35,7 @@ def catch_all(path): @bp.route('/login-redirect') def login_redirect(): - if request.environ.get('HTTP_X_SSL_CLIENT_VERIFY') == 'SUCCESS' and is_valid_certificate(request): + if request.environ.get('HTTP_X_SSL_CLIENT_VERIFY') == 'SUCCESS' and _is_valid_certificate(request): sdn = request.environ.get('HTTP_X_SSL_CLIENT_S_DN') sdn_parts = parse_sdn(sdn) user = Users.get_or_create_by_dod_id(**sdn_parts) @@ -54,7 +54,7 @@ def unauthorized(): return response -def is_valid_certificate(request): +def _is_valid_certificate(request): cert = request.environ.get('HTTP_X_SSL_CLIENT_CERT') if cert: result = app.crl_validator.validate(cert.encode()) diff --git a/config/base.ini b/config/base.ini index ac66c00c..ad7afb41 100644 --- a/config/base.ini +++ b/config/base.ini @@ -19,3 +19,5 @@ PGDATABASE = atat SESSION_TYPE = redis SESSION_COOKIE_NAME=atat SESSION_USE_SIGNER = True +CRL_DIRECTORY = crl +CA_CHAIN = ssl/server-certs/ca-chain.pem diff --git a/config/ci.ini b/config/ci.ini index ebf2036e..e7e5be1a 100644 --- a/config/ci.ini +++ b/config/ci.ini @@ -2,3 +2,4 @@ PGHOST = postgreshost PGDATABASE = atat_test REDIS_URI = redis://redishost:6379 +CRL_DIRECTORY = tests/fixtures/crl diff --git a/config/test.ini b/config/test.ini index 0074f60b..fe38c777 100644 --- a/config/test.ini +++ b/config/test.ini @@ -1,2 +1,3 @@ [default] PGDATABASE = atat_test +CRL_DIRECTORY = tests/fixtures/crl diff --git a/script/sync-crls b/script/sync-crls index d4535173..93ec6772 100755 --- a/script/sync-crls +++ b/script/sync-crls @@ -5,7 +5,7 @@ set -e cd "$(dirname "$0")/.." mkdir -p crl-tmp -pipenv run python ./authnid/crl/util.py crl-tmp +pipenv run python ./atst/domain/authnid/crl/util.py crl-tmp mkdir -p crl rsync -rq crl-tmp/. crl/. rm -rf crl-tmp diff --git a/tests/fixtures/crl/client-ca.der.crl b/tests/fixtures/crl/client-ca.der.crl new file mode 100644 index 0000000000000000000000000000000000000000..8ec9e37f49341a8bb3a3c06983e137b13dd37b0a GIT binary patch literal 502 zcmXqLV*F&#c-w%NjZ>@5qwPB{BO?ndgF$1jA-4f18*?ZNn=n&ou%W1dFo?q;%oC8B zmsebwQk&-o|njK3Bg9mNhdnFfuSPGc=4s^BQv#BO^oYlu6G64Cl*QbgG{zkF@-2 zef+9i?_OuKi~6tAj3T!G?p`UVI%j&d`?iiLTLjLkM{j!g>_fSH!I9mm&idca$FwXx zlk;=wze|Ga#&Jvn1vu-sYODqmiUvrA72#gzD-db;!NwW z<^uoeD#r|U x6I-&Rd%xqkuPkeow{EkY8QrSSHO(dTutv|tj(tM!)WUXud%4ogXjQ_p3IKK+xTXLA literal 0 HcmV?d00001 diff --git a/tests/test_auth.py b/tests/test_auth.py index f72483f0..bb0ebb4a 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -10,7 +10,7 @@ def _fetch_user_info(c, t): def test_successful_login_redirect(client, monkeypatch): - monkeypatch.setattr("atst.routes.is_valid_certificate", lambda *args: True) + monkeypatch.setattr("atst.routes._is_valid_certificate", lambda *args: True) resp = client.get( "/login-redirect", @@ -52,3 +52,36 @@ def test_protected_route(client, app): resp = client.post(route) assert resp.status_code == 302 assert resp.headers["Location"] == "http://localhost/" + + +# this implicitly relies on the test config and test CRL in tests/fixtures/crl +def test_crl_validation_on_login(client): + good_cert = open('ssl/client-certs/atat.mil.crt', 'rb').read() + bad_cert = open('ssl/client-certs/bad-atat.mil.crt', 'rb').read() + + # bad cert is on the test CRL + resp = client.get( + "/login-redirect", + environ_base={ + "HTTP_X_SSL_CLIENT_VERIFY": "SUCCESS", + "HTTP_X_SSL_CLIENT_S_DN": DOD_SDN, + "HTTP_X_SSL_CLIENT_CERT": bad_cert.decode() + }, + ) + assert resp.status_code == 302 + assert "unauthorized" in resp.headers["Location"] + assert "user_id" not in session + + # good cert is not on the test CRL, passes + resp = client.get( + "/login-redirect", + environ_base={ + "HTTP_X_SSL_CLIENT_VERIFY": "SUCCESS", + "HTTP_X_SSL_CLIENT_S_DN": DOD_SDN, + "HTTP_X_SSL_CLIENT_CERT": good_cert.decode() + }, + ) + assert resp.status_code == 302 + assert "home" in resp.headers["Location"] + assert session["user_id"] +