Merge pull request #9 from 18F/jim/063022/protectbeta

set up basicauth config to protect staging site
This commit is contained in:
Jim Moffet
2022-07-19 19:02:40 -07:00
committed by GitHub
13 changed files with 76 additions and 49 deletions
-15
View File
@@ -1,15 +0,0 @@
[flake8]
# Rule definitions: http://flake8.pycqa.org/en/latest/user/error-codes.html
exclude = venv*,__pycache__,node_modules,cache
max-complexity = 14
max-line-length = 120
# B003 assigning to os.environ
# B306 BaseException.message has been deprecated
# W503: line break before binary operator
# W504 line break after binary operator
extend_ignore=
B003,
B306,
W503,
W504
+2
View File
@@ -109,6 +109,8 @@ environment.sh
.env .env
.env* .env*
varsfile varsfile
*.log.json
logs/**
# CloudFoundry # CloudFoundry
.cf .cf
+7 -1
View File
@@ -37,6 +37,7 @@ from werkzeug.local import LocalProxy
from app import proxy_fix, webauthn_server from app import proxy_fix, webauthn_server
from app.asset_fingerprinter import asset_fingerprinter from app.asset_fingerprinter import asset_fingerprinter
from app.config import configs from app.config import configs
from app.custom_auth import CustomBasicAuth
from app.extensions import antivirus_client, redis_client, zendesk_client from app.extensions import antivirus_client, redis_client, zendesk_client
from app.formatters import ( from app.formatters import (
convert_to_boolean, convert_to_boolean,
@@ -134,7 +135,7 @@ from app.url_converters import (
login_manager = LoginManager() login_manager = LoginManager()
csrf = CSRFProtect() csrf = CSRFProtect()
metrics = GDSMetrics() metrics = GDSMetrics()
basic_auth = CustomBasicAuth()
# The current service attached to the request stack. # The current service attached to the request stack.
def _get_current_service(): def _get_current_service():
@@ -221,6 +222,8 @@ def create_app(application):
login_manager.session_protection = None login_manager.session_protection = None
login_manager.anonymous_user = AnonymousUser login_manager.anonymous_user = AnonymousUser
setup_basic_auth(application)
# make sure we handle unicode correctly # make sure we handle unicode correctly
redis_client.redis_store.decode_responses = True redis_client.redis_store.decode_responses = True
@@ -586,3 +589,6 @@ def init_jinja(application):
] ]
jinja_loader = jinja2.FileSystemLoader(template_folders) jinja_loader = jinja2.FileSystemLoader(template_folders)
application.jinja_loader = jinja_loader application.jinja_loader = jinja_loader
def setup_basic_auth(application):
application.basic_auth = CustomBasicAuth(application)
+33 -12
View File
@@ -4,13 +4,16 @@ import os
if os.environ.get('VCAP_APPLICATION'): if os.environ.get('VCAP_APPLICATION'):
# on cloudfoundry, config is a json blob in VCAP_APPLICATION - unpack it, and populate # on cloudfoundry, config is a json blob in VCAP_APPLICATION - unpack it, and populate
# standard environment variables from it # standard environment variables from it
from app.cloudfoundry_config import extract_cloudfoundry_config # from app.cloudfoundry_config import extract_cloudfoundry_config
extract_cloudfoundry_config() # extract_cloudfoundry_config()
vcap_services = json.loads(os.environ['VCAP_SERVICES'])
os.environ['REDIS_URL'] = vcap_services['aws-elasticache-redis'][0]['credentials']['uri']
class Config(object): class Config(object):
ADMIN_CLIENT_SECRET = os.environ.get('ADMIN_CLIENT_SECRET') ADMIN_CLIENT_SECRET = os.environ.get('ADMIN_CLIENT_SECRET')
API_HOST_NAME = os.environ.get('API_HOST_NAME') ADMIN_CLIENT_USER_NAME = os.environ.get('ADMIN_CLIENT_USERNAME')
API_HOST_NAME = os.environ.get('API_HOST_NAME', 'localhost')
SECRET_KEY = os.environ.get('SECRET_KEY') SECRET_KEY = os.environ.get('SECRET_KEY')
DANGEROUS_SALT = os.environ.get('DANGEROUS_SALT') DANGEROUS_SALT = os.environ.get('DANGEROUS_SALT')
ZENDESK_API_KEY = os.environ.get('ZENDESK_API_KEY') ZENDESK_API_KEY = os.environ.get('ZENDESK_API_KEY')
@@ -23,9 +26,7 @@ class Config(object):
# Logging # Logging
DEBUG = False DEBUG = False
NOTIFY_LOG_PATH = os.getenv('NOTIFY_LOG_PATH') NOTIFY_LOG_PATH = os.environ.get('NOTIFY_LOG_PATH', 'application.log')
ADMIN_CLIENT_USER_NAME = 'notify-admin'
ANTIVIRUS_API_HOST = os.environ.get('ANTIVIRUS_API_HOST') ANTIVIRUS_API_HOST = os.environ.get('ANTIVIRUS_API_HOST')
ANTIVIRUS_API_KEY = os.environ.get('ANTIVIRUS_API_KEY') ANTIVIRUS_API_KEY = os.environ.get('ANTIVIRUS_API_KEY')
@@ -40,7 +41,7 @@ class Config(object):
HEADER_COLOUR = '#81878b' # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey")) HEADER_COLOUR = '#81878b' # mix(govuk-colour("dark-grey"), govuk-colour("mid-grey"))
HTTP_PROTOCOL = 'http' HTTP_PROTOCOL = 'http'
NOTIFY_APP_NAME = 'admin' NOTIFY_APP_NAME = 'admin'
NOTIFY_LOG_LEVEL = 'DEBUG' NOTIFY_LOG_LEVEL = os.environ.get('NOTIFY_LOG_LEVEL', 'DEBUG')
PERMANENT_SESSION_LIFETIME = 20 * 60 * 60 # 20 hours PERMANENT_SESSION_LIFETIME = 20 * 60 * 60 # 20 hours
SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year SEND_FILE_MAX_AGE_DEFAULT = 365 * 24 * 60 * 60 # 1 year
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
@@ -62,14 +63,18 @@ class Config(object):
LOGO_UPLOAD_BUCKET_NAME = 'public-logos-local' LOGO_UPLOAD_BUCKET_NAME = 'public-logos-local'
MOU_BUCKET_NAME = 'local-mou' MOU_BUCKET_NAME = 'local-mou'
TRANSIENT_UPLOADED_LETTERS = 'local-transient-uploaded-letters' TRANSIENT_UPLOADED_LETTERS = 'local-transient-uploaded-letters'
ROUTE_SECRET_KEY_1 = os.environ.get('ROUTE_SECRET_KEY_1', '') ROUTE_SECRET_KEY_1 = os.environ.get('ROUTE_SECRET_KEY_1', 'dev-route-secret-key-1')
ROUTE_SECRET_KEY_2 = os.environ.get('ROUTE_SECRET_KEY_2', '') ROUTE_SECRET_KEY_2 = os.environ.get('ROUTE_SECRET_KEY_2', 'dev-route-secret-key-2')
CHECK_PROXY_HEADER = False CHECK_PROXY_HEADER = False
ANTIVIRUS_ENABLED = True ANTIVIRUS_ENABLED = True
REDIS_URL = os.environ.get('REDIS_URL') REDIS_URL = os.environ.get('REDIS_URL')
REDIS_ENABLED = True REDIS_ENABLED = True
BASIC_AUTH_USERNAME = os.environ.get('BASIC_AUTH_USERNAME')
BASIC_AUTH_PASSWORD = os.environ.get('BASIC_AUTH_PASSWORD')
BASIC_AUTH_FORCE = True
ASSET_DOMAIN = '' ASSET_DOMAIN = ''
ASSET_PATH = '/static/' ASSET_PATH = '/static/'
@@ -93,6 +98,7 @@ class Config(object):
class Development(Config): class Development(Config):
BASIC_AUTH_FORCE = True
NOTIFY_LOG_PATH = 'application.log' NOTIFY_LOG_PATH = 'application.log'
DEBUG = True DEBUG = True
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False
@@ -104,7 +110,7 @@ class Development(Config):
MOU_BUCKET_NAME = 'notify.tools-mou' MOU_BUCKET_NAME = 'notify.tools-mou'
TRANSIENT_UPLOADED_LETTERS = 'development-transient-uploaded-letters' TRANSIENT_UPLOADED_LETTERS = 'development-transient-uploaded-letters'
PRECOMPILED_ORIGINALS_BACKUP_LETTERS = 'development-letters-precompiled-originals-backup' PRECOMPILED_ORIGINALS_BACKUP_LETTERS = 'development-letters-precompiled-originals-backup'
ADMIN_CLIENT_SECRET = 'dev-notify-secret-key' ADMIN_CLIENT_SECRET = os.environ.get('ADMIN_CLIENT_SECRET')
# check for local compose orchestration variable # check for local compose orchestration variable
API_HOST_NAME = os.environ.get('DEV_API_HOST_NAME', 'http://dev:6011') API_HOST_NAME = os.environ.get('DEV_API_HOST_NAME', 'http://dev:6011')
DANGEROUS_SALT = 'dev-notify-salt' DANGEROUS_SALT = 'dev-notify-salt'
@@ -116,11 +122,11 @@ class Development(Config):
ASSET_PATH = '/static/' ASSET_PATH = '/static/'
REDIS_URL = os.environ.get('DEV_REDIS_URL', 'http://redis:6379') REDIS_URL = os.environ.get('DEV_REDIS_URL', 'http://redis:6379')
REDIS_ENABLED = True
REDIS_ENABLED = os.environ.get('REDIS_ENABLED') == '1'
class Test(Development): class Test(Development):
BASIC_AUTH_FORCE = False
DEBUG = True DEBUG = True
TESTING = True TESTING = True
WTF_CSRF_ENABLED = False WTF_CSRF_ENABLED = False
@@ -143,6 +149,7 @@ class Test(Development):
class Preview(Config): class Preview(Config):
BASIC_AUTH_FORCE = True
HTTP_PROTOCOL = 'https' HTTP_PROTOCOL = 'https'
HEADER_COLOUR = '#F499BE' # $baby-pink HEADER_COLOUR = '#F499BE' # $baby-pink
CSV_UPLOAD_BUCKET_NAME = 'preview-notifications-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'preview-notifications-csv-upload'
@@ -162,6 +169,7 @@ class Preview(Config):
class Staging(Config): class Staging(Config):
BASIC_AUTH_FORCE = True
HTTP_PROTOCOL = 'https' HTTP_PROTOCOL = 'https'
HEADER_COLOUR = '#6F72AF' # $mauve HEADER_COLOUR = '#6F72AF' # $mauve
CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload' CSV_UPLOAD_BUCKET_NAME = 'staging-notifications-csv-upload'
@@ -178,6 +186,7 @@ class Staging(Config):
class Live(Config): class Live(Config):
BASIC_AUTH_FORCE = True
HEADER_COLOUR = '#005EA5' # $govuk-blue HEADER_COLOUR = '#005EA5' # $govuk-blue
HTTP_PROTOCOL = 'https' HTTP_PROTOCOL = 'https'
CSV_UPLOAD_BUCKET_NAME = 'notifications.prototype.csv_upload' CSV_UPLOAD_BUCKET_NAME = 'notifications.prototype.csv_upload'
@@ -192,6 +201,18 @@ class Live(Config):
ASSET_DOMAIN = 'static.notifications.service.gov.uk' ASSET_DOMAIN = 'static.notifications.service.gov.uk'
ASSET_PATH = 'https://static.notifications.service.gov.uk/' ASSET_PATH = 'https://static.notifications.service.gov.uk/'
REDIS_URL = os.environ.get('REDIS_URL')
REDIS_ENABLED = True
ADMIN_CLIENT_SECRET = os.environ.get('ADMIN_CLIENT_SECRET')
ADMIN_CLIENT_USER_NAME = os.environ.get('ADMIN_CLIENT_USERNAME')
API_HOST_NAME = os.environ.get('API_HOST_NAME')
DANGEROUS_SALT = os.environ.get('DANGEROUS_SALT')
SECRET_KEY = os.environ.get('SECRET_KEY')
ANTIVIRUS_API_HOST = 'http://localhost:6016'
ANTIVIRUS_API_KEY = 'test-key'
ANTIVIRUS_ENABLED = False
class CloudFoundryConfig(Config): class CloudFoundryConfig(Config):
pass pass
+15
View File
@@ -0,0 +1,15 @@
from flask_basicauth import BasicAuth
from flask import jsonify, request
class CustomBasicAuth(BasicAuth):
"""
Description:
Override BasicAuth to permit anonymous healthcheck at /_status?simple=true
"""
def challenge(self):
if "/_status" in request.url:
if request.args.get('elb', None) or request.args.get('simple', None):
return jsonify(status="ok"), 200
return super(CustomBasicAuth, self).challenge()
custom_basic_auth = CustomBasicAuth()
+1 -1
View File
@@ -1,6 +1,6 @@
import os
from app.notify_client import NotifyAdminAPIClient, cache from app.notify_client import NotifyAdminAPIClient, cache
class StatusApiClient(NotifyAdminAPIClient): class StatusApiClient(NotifyAdminAPIClient):
def get_status(self, *params): def get_status(self, *params):
-12
View File
@@ -1,12 +0,0 @@
Logging configured
Logging configured
Logging configured
Logging configured again
Logging configured
Logging configured again
Logging configured
Logging configured again
Logging configured
Logging configured again
Logging configured
Logging configured again
-3
View File
@@ -23,16 +23,13 @@
} }
}, },
"extensions": [ "extensions": [
"ms-python.python", // "ms-python.black-formatter",
"donjayamanne.python-extension-pack", "donjayamanne.python-extension-pack",
"ms-azuretools.vscode-docker",
"ms-python.vscode-pylance", "ms-python.vscode-pylance",
"eamodio.gitlens", "eamodio.gitlens",
"wholroyd.jinja", "wholroyd.jinja",
"pmbenjamin.vscode-snyk", "pmbenjamin.vscode-snyk",
"visualstudioexptteam.vscodeintellicode", "visualstudioexptteam.vscodeintellicode",
"yzhang.markdown-all-in-one", "yzhang.markdown-all-in-one",
"ms-ossdata.vscode-postgresql",
"GitHub.copilot" "GitHub.copilot"
], ],
"forwardPorts": [ "forwardPorts": [
@@ -7,6 +7,8 @@ set -ex
# tools and the filesystem mount enabled should be located here. # tools and the filesystem mount enabled should be located here.
################################################################### ###################################################################
echo "RUNNING ENTRYPOINT SCRIPT"
# Define aliases # Define aliases
echo -e "\n\n# User's Aliases" >> ~/.zshrc echo -e "\n\n# User's Aliases" >> ~/.zshrc
echo -e "alias fd=fdfind" >> ~/.zshrc echo -e "alias fd=fdfind" >> ~/.zshrc
@@ -29,6 +31,7 @@ pip3 install -r requirements_for_test.txt
make generate-version-file make generate-version-file
# make babel # make babel
# npm ci install
if [ ! -d "/node_modules" ]; then if [ ! -d "/node_modules" ]; then
npm ci install npm ci install
fi fi
@@ -37,3 +40,5 @@ npm run build
# run flask # run flask
# make run # make run
echo "FINISHED ENTRYPOINT SCRIPT"
+7 -4
View File
@@ -18,22 +18,25 @@ applications:
env: env:
NOTIFY_APP_NAME: admin NOTIFY_APP_NAME: admin
NOTIFY_LOG_PATH: /home/vcap/logs/app.log NOTIFY_LOG_PATH: /home/vcap/logs/app.log
NOTIFY_LOG_LEVEL: DEBUG
FLASK_APP: application.py FLASK_APP: application.py
FLASK_ENV: production FLASK_ENV: production
REDIS_ENABLED: ((REDIS_ENABLED))
NOTIFY_ENVIRONMENT: live NOTIFY_ENVIRONMENT: live
# Credentials variables # Credentials variables
ADMIN_CLIENT_SECRET: ((ADMIN_CLIENT_SECRET)) ADMIN_CLIENT_SECRET: ((ADMIN_CLIENT_SECRET))
ADMIN_BASE_URL: notifications-admin.app.cloud.gov ADMIN_CLIENT_USERNAME: ((ADMIN_CLIENT_USERNAME))
API_HOST_NAME: notifications-api.app.cloud.gov ADMIN_BASE_URL: https://notifications-admin.app.cloud.gov
API_HOST_NAME: https://notifications-api.app.cloud.gov
DANGEROUS_SALT: ((DANGEROUS_SALT)) DANGEROUS_SALT: ((DANGEROUS_SALT))
SECRET_KEY: ((SECRET_KEY)) SECRET_KEY: ((SECRET_KEY))
ROUTE_SECRET_KEY_1: ((ROUTE_SECRET_KEY_1))
ROUTE_SECRET_KEY_2: ((ROUTE_SECRET_KEY_2))
AWS_REGION: us-west-2 AWS_REGION: us-west-2
AWS_ACCESS_KEY_ID: ((AWS_ACCESS_KEY_ID)) AWS_ACCESS_KEY_ID: ((AWS_ACCESS_KEY_ID))
AWS_SECRET_ACCESS_KEY: ((AWS_SECRET_ACCESS_KEY)) AWS_SECRET_ACCESS_KEY: ((AWS_SECRET_ACCESS_KEY))
BASIC_AUTH_USERNAME: ((BASIC_AUTH_USERNAME))
BASIC_AUTH_PASSWORD: ((BASIC_AUTH_PASSWORD))
NOTIFY_BILLING_DETAILS: [] NOTIFY_BILLING_DETAILS: []
+1
View File
@@ -10,6 +10,7 @@ wtforms==3.0.1
Flask-Login==0.6.1 Flask-Login==0.6.1
Werkzeug==2.1.2 Werkzeug==2.1.2
jinja2==3.1.2 jinja2==3.1.2
Flask-BasicAuth==0.2.0
blinker==1.4 blinker==1.4
pyexcel==0.7.0 pyexcel==0.7.0
+2
View File
@@ -71,6 +71,8 @@ flask-redis==0.4.0
# via notifications-utils # via notifications-utils
flask-wtf==1.0.1 flask-wtf==1.0.1
# via -r requirements.in # via -r requirements.in
Flask-BasicAuth==0.2.0
# via -r requirements.in
gds-metrics @ git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72 gds-metrics @ git+https://github.com/alphagov/gds_metrics_python.git@6f1840a57b6fb1ee40b7e84f2f18ec229de8aa72
# via -r requirements.in # via -r requirements.in
geojson==2.5.0 geojson==2.5.0
+2
View File
@@ -5,3 +5,5 @@ ROUTE_SECRET_KEY_1: asdf
ROUTE_SECRET_KEY_2: asdf ROUTE_SECRET_KEY_2: asdf
AWS_ACCESS_KEY_ID: asdf AWS_ACCESS_KEY_ID: asdf
AWS_SECRET_ACCESS_KEY: asdf AWS_SECRET_ACCESS_KEY: asdf
BASIC_AUTH_USERNAME: asdf
BASIC_AUTH_PASSWORD: asdf