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
.flake8
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
.gitignore vendored
View File

@@ -109,6 +109,8 @@ environment.sh
.env .env
.env* .env*
varsfile varsfile
*.log.json
logs/**
# CloudFoundry # CloudFoundry
.cf .cf

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)

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,13 +63,17 @@ 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'
@@ -191,6 +200,18 @@ class Live(Config):
CHECK_PROXY_HEADER = False CHECK_PROXY_HEADER = False
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):

15
app/custom_auth.py Normal file
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()

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):

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

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": [

View File

@@ -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
@@ -36,4 +39,6 @@ fi
npm run build npm run build
# run flask # run flask
# make run # make run
echo "FINISHED ENTRYPOINT SCRIPT"

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: []

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

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

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