GOV.UK Frontend WTForms
@@ -26,7 +26,7 @@ Hello, World!
Features
A number of other packages are used to provide the features listed below with sensible and
best-practice defaults:
-
+
- Asset management
- Cache busting
- Form generation and validation
@@ -40,7 +40,7 @@ Features
Demos
-
+
diff --git a/build.sh b/build.sh
index a644e43..447e178 100755
--- a/build.sh
+++ b/build.sh
@@ -4,7 +4,7 @@ rm -rf app/static/images
rm -rf app/static/govuk-frontend*
# Get new release distribution assets and move to static directory
-curl -L https://github.com/alphagov/govuk-frontend/releases/download/v5.4.0/release-v5.4.0.zip > govuk_frontend.zip
+curl -L https://github.com/alphagov/govuk-frontend/releases/download/v5.6.0/release-v5.6.0.zip > govuk_frontend.zip
unzip -o govuk_frontend.zip -d app/static
mv app/static/assets/* app/static
@@ -18,16 +18,16 @@ rm -rf govuk_frontend.zip
#####################################################################
# Remove existing GOV.UK Frontend test fixtures
-rm -rf govuk_components
+rm -rf app/demos/govuk_components
# Get new release source code and move to a directory
-curl -L https://github.com/alphagov/govuk-frontend/archive/refs/tags/v5.4.0.zip > govuk_frontend_source.zip
+curl -L https://github.com/alphagov/govuk-frontend/archive/refs/tags/v5.6.0.zip > govuk_frontend_source.zip
unzip -o govuk_frontend_source.zip -d govuk_frontend_source
-mkdir govuk_components
-mv govuk_frontend_source/govuk-frontend-5.4.0/packages/govuk-frontend/src/govuk/components/** govuk_components
+mkdir app/demos/govuk_components
+mv govuk_frontend_source/govuk-frontend-5.6.0/packages/govuk-frontend/src/govuk/components/** app/demos/govuk_components
# Remove all files apart from test fixtures
-find govuk_components -type f ! -name '*.yaml' -delete
+find app/demos/govuk_components -type f ! -name '*.yaml' -delete
# Tidy up
rm -rf govuk_frontend_source
diff --git a/compose.yml b/compose.yml
index 8e44d6c..1a7323f 100644
--- a/compose.yml
+++ b/compose.yml
@@ -1,28 +1,36 @@
services:
web:
- container_name: govuk-frontend-flask
build: .
+ command: gunicorn --bind 0.0.0.0:5000 -w 4 --access-logfile - --error-logfile - govuk-frontend-flask:app
restart: always
environment:
- CONTACT_EMAIL=[contact email]
- CONTACT_PHONE=[contact phone]
- DEPARTMENT_NAME=[name of department]
- DEPARTMENT_URL=[url of department]
- - REDIS_URL=redis://cache:6379
+ - REDIS_URL=redis://redis:6379
- SECRET_KEY=4f378500459bb58fecf903ea3c113069f11f150b33388f56fc89f7edce0e6a84
- SERVICE_NAME=[name of service]
- SERVICE_PHASE=[phase]
- SERVICE_URL=[url of service]
- ports:
- - "9876:9876"
volumes:
- - .:/home/containeruser
+ - static_volume:/home/appuser/app/static:rw
+ expose:
+ - 5000
depends_on:
- - cache
- cache:
- container_name: redis
- image: redis:7.0-alpine
+ - redis
+ redis:
+ image: redis:7-alpine
restart: always
+ expose:
+ - 6379
+ nginx:
+ build: ./nginx
+ volumes:
+ - static_volume:/home/appuser/app/static:ro
ports:
- - 6379:6379
-
\ No newline at end of file
+ - 443:443
+ depends_on:
+ - web
+volumes:
+ static_volume:
diff --git a/config.py b/config.py
index 8d24d82..f2f5388 100644
--- a/config.py
+++ b/config.py
@@ -13,4 +13,5 @@ class Config(object):
SERVICE_PHASE = os.environ.get("SERVICE_PHASE")
SERVICE_URL = os.environ.get("SERVICE_URL")
SESSION_COOKIE_HTTPONLY = True
+ SESSION_COOKIE_SAMESITE = "Lax"
SESSION_COOKIE_SECURE = True
diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh
deleted file mode 100755
index 93d14e4..0000000
--- a/docker-entrypoint.sh
+++ /dev/null
@@ -1,3 +0,0 @@
-#!/bin/bash
-openssl req -new -x509 -newkey rsa:4096 -nodes -out cert.pem -keyout key.pem -days 365 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost"
-exec gunicorn --reload --certfile cert.pem --keyfile key.pem -b :9876 --access-logfile - --error-logfile - govuk-frontend-flask:app
\ No newline at end of file
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
new file mode 100644
index 0000000..b7c491a
--- /dev/null
+++ b/nginx/Dockerfile
@@ -0,0 +1,7 @@
+FROM nginx:stable
+
+RUN rm /etc/nginx/conf.d/default.conf && \
+ mkdir /etc/nginx/ssl && \
+ openssl req -x509 -noenc -newkey rsa:2048 -keyout /etc/nginx/ssl/key.pem -out /etc/nginx/ssl/req.pem -days 90 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=Digital/CN=localhost"
+
+COPY nginx.conf /etc/nginx/conf.d
\ No newline at end of file
diff --git a/nginx/nginx.conf b/nginx/nginx.conf
new file mode 100644
index 0000000..cb07a61
--- /dev/null
+++ b/nginx/nginx.conf
@@ -0,0 +1,65 @@
+# generated 2024-09-03, Mozilla Guideline v5.7, nginx 1.26.2, OpenSSL 3.0.13, modern configuration, no OCSP
+# https://ssl-config.mozilla.org/#server=nginx&version=1.26.2&config=modern&openssl=3.0.13&ocsp=false&guideline=5.7
+server {
+ listen 80 default_server;
+ listen [::]:80 default_server;
+
+ location / {
+ return 301 https://$host$request_uri;
+ }
+}
+
+server {
+ listen 443 ssl;
+ listen [::]:443 ssl;
+ http2 on;
+
+ ssl_certificate /etc/nginx/ssl/req.pem;
+ ssl_certificate_key /etc/nginx/ssl/key.pem;
+ ssl_session_timeout 1d;
+ ssl_session_cache shared:MozSSL:10m; # about 40000 sessions
+ ssl_session_tickets off;
+
+ # modern configuration
+ ssl_protocols TLSv1.3;
+ ssl_prefer_server_ciphers off;
+
+ # add security headers
+ add_header Content-Security-Policy "script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-3t81BEe/IfrPieOkVojxAPxOujfIBkzGt+HP2GeblR4='; object-src 'none'; base-uri 'none';" always;
+ add_header Cross-Origin-Embedder-Policy "require-corp" always;
+ add_header Cross-Origin-Opener-Policy "same-origin" always;
+ add_header Cross-Origin-Resource-Policy "same-origin" always;
+ add_header Permissions-Policy "accelerometer=(), ambient-light-sensor=(), autoplay=(), battery=(), camera=(), cross-origin-isolated=(), display-capture=(), document-domain=(), encrypted-media=(), execution-while-not-rendered=(), execution-while-out-of-viewport=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), navigation-override=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), speaker-selection=(), conversion-measurement=(), focus-without-user-activation=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), sync-script=(), trust-token-redemption=(), unload=(), window-placement=(), vertical-scroll=()" always;
+ add_header Referrer-Policy "strict-origin-when-cross-origin" always;
+ add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload" always;
+ add_header X-Content-Type-Options "nosniff" always;
+ add_header X-Frame-Options "DENY" always;
+ add_header X-Xss-Protection "1; mode=block" always;
+
+ # enable gzip compression
+ gzip on;
+ gzip_comp_level 6;
+ gzip_proxied any;
+ gzip_types application/javascript application/json application/xml font/otf font/ttf font/woff font/woff2 image/gif image/jpeg image/png image/svg+xml image/webp text/css text/csv text/javascript text/xml;
+
+ location / {
+ # forward application requests to the gunicorn server
+ proxy_pass http://web:5000;
+ proxy_redirect off;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $http_x_forwarded_proto;
+ proxy_set_header X-Real-IP $remote_addr;
+ }
+
+ location /assets/ {
+ # serve static files directly, without forwarding to the application
+ alias /home/appuser/app/static/;
+
+ sendfile on;
+ tcp_nopush on;
+
+ # set far future expires header
+ expires 10y;
+ }
+}
\ No newline at end of file
diff --git a/requirements.in b/requirements.in
index 1a97612..52cdcd7 100644
--- a/requirements.in
+++ b/requirements.in
@@ -2,9 +2,7 @@ cssmin
email_validator
flask
flask-assets
-flask-compress
flask-limiter[redis]
-flask-talisman
govuk-frontend-jinja
govuk-frontend-wtf
gunicorn
diff --git a/requirements.txt b/requirements.txt
index 84f6be1..5d1cfee 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -6,49 +6,42 @@
#
blinker==1.8.2
# via flask
-brotli==1.1.0
- # via flask-compress
click==8.1.7
# via flask
cssmin==0.2.0
# via -r requirements.in
-deepmerge==1.1.1
+deepmerge==2.0
# via govuk-frontend-wtf
deprecated==1.2.14
# via limits
dnspython==2.6.1
# via email-validator
-email-validator==2.1.1
+email-validator==2.2.0
# via -r requirements.in
flask==3.0.3
# via
# -r requirements.in
# flask-assets
- # flask-compress
# flask-limiter
# flask-wtf
# govuk-frontend-wtf
flask-assets==2.1.0
# via -r requirements.in
-flask-compress==1.15
- # via -r requirements.in
-flask-limiter[redis]==3.7.0
- # via -r requirements.in
-flask-talisman==1.1.0
+flask-limiter[redis]==3.8.0
# via -r requirements.in
flask-wtf==1.2.1
# via govuk-frontend-wtf
-govuk-frontend-jinja==3.1.0
+govuk-frontend-jinja==3.3.0
# via
# -r requirements.in
# govuk-frontend-wtf
govuk-frontend-wtf==3.1.0
# via -r requirements.in
-gunicorn==22.0.0
+gunicorn==23.0.0
# via -r requirements.in
-idna==3.7
+idna==3.8
# via email-validator
-importlib-resources==6.4.0
+importlib-resources==6.4.4
# via limits
itsdangerous==2.2.0
# via
@@ -61,7 +54,7 @@ jinja2==3.1.4
# govuk-frontend-wtf
jsmin==3.0.1
# via -r requirements.in
-limits[redis]==3.12.0
+limits[redis]==3.13.0
# via flask-limiter
markdown-it-py==3.0.0
# via rich
@@ -74,25 +67,25 @@ mdurl==0.1.2
# via markdown-it-py
ordered-set==4.1.0
# via flask-limiter
-packaging==24.0
+packaging==24.1
# via
# gunicorn
# limits
pygments==2.18.0
# via rich
-pyyaml==6.0.1
+pyyaml==6.0.2
# via -r requirements.in
-redis==5.0.4
+redis==5.0.8
# via limits
-rich==13.7.1
+rich==13.8.0
# via flask-limiter
-typing-extensions==4.12.0
+typing-extensions==4.12.2
# via
# flask-limiter
# limits
webassets==2.0
# via flask-assets
-werkzeug==3.0.3
+werkzeug==3.0.4
# via flask
wrapt==1.16.0
# via deprecated
@@ -100,5 +93,3 @@ wtforms==3.1.2
# via
# flask-wtf
# govuk-frontend-wtf
-zstandard==0.22.0
- # via flask-compress
diff --git a/requirements_dev.in b/requirements_dev.in
index a9fc1b4..70d88cb 100644
--- a/requirements_dev.in
+++ b/requirements_dev.in
@@ -2,8 +2,9 @@ bandit
black
flake8-bugbear
isort
+mypy
pep8-naming
+pip-audit
pip-tools
pur
pytest-cov
-safety
diff --git a/requirements_dev.txt b/requirements_dev.txt
index d91d780..362865a 100644
--- a/requirements_dev.txt
+++ b/requirements_dev.txt
@@ -4,22 +4,22 @@
#
# pip-compile requirements_dev.in
#
-annotated-types==0.7.0
- # via pydantic
-attrs==23.2.0
+attrs==24.2.0
# via flake8-bugbear
-authlib==1.3.0
- # via safety
-bandit==1.7.8
+bandit==1.7.9
# via -r requirements_dev.in
-black==24.4.2
+black==24.8.0
# via -r requirements_dev.in
+boolean-py==4.0
+ # via license-expression
build==1.2.1
# via pip-tools
-certifi==2024.2.2
+cachecontrol[filecache]==0.14.0
+ # via
+ # cachecontrol
+ # pip-audit
+certifi==2024.8.30
# via requests
-cffi==1.16.0
- # via cryptography
charset-normalizer==3.3.2
# via requests
click==8.1.7
@@ -27,124 +27,116 @@ click==8.1.7
# black
# pip-tools
# pur
- # safety
- # typer
-coverage[toml]==7.5.3
+coverage[toml]==7.6.1
# via pytest-cov
-cryptography==42.0.7
- # via authlib
-dparse==0.6.4b0
- # via
- # safety
- # safety-schemas
-flake8==7.0.0
+cyclonedx-python-lib==7.6.0
+ # via pip-audit
+defusedxml==0.7.1
+ # via py-serializable
+filelock==3.15.4
+ # via cachecontrol
+flake8==7.1.1
# via
# flake8-bugbear
# pep8-naming
-flake8-bugbear==24.4.26
+flake8-bugbear==24.8.19
# via -r requirements_dev.in
-idna==3.7
+html5lib==1.1
+ # via pip-audit
+idna==3.8
# via requests
iniconfig==2.0.0
# via pytest
isort==5.13.2
# via -r requirements_dev.in
-jinja2==3.1.4
- # via safety
+license-expression==30.3.1
+ # via cyclonedx-python-lib
markdown-it-py==3.0.0
# via rich
-markupsafe==2.1.5
- # via jinja2
-marshmallow==3.21.2
- # via safety
mccabe==0.7.0
# via flake8
mdurl==0.1.2
# via markdown-it-py
+msgpack==1.0.8
+ # via cachecontrol
+mypy==1.11.2
+ # via -r requirements_dev.in
mypy-extensions==1.0.0
- # via black
-packaging==24.0
+ # via
+ # black
+ # mypy
+packageurl-python==0.15.6
+ # via cyclonedx-python-lib
+packaging==24.1
# via
# black
# build
- # dparse
- # marshmallow
+ # pip-audit
+ # pip-requirements-parser
# pytest
- # safety
- # safety-schemas
pathspec==0.12.1
# via black
-pbr==6.0.0
+pbr==6.1.0
# via stevedore
pep8-naming==0.14.1
# via -r requirements_dev.in
+pip-api==0.0.34
+ # via pip-audit
+pip-audit==2.7.3
+ # via -r requirements_dev.in
+pip-requirements-parser==32.0.1
+ # via pip-audit
pip-tools==7.4.1
# via -r requirements_dev.in
platformdirs==4.2.2
# via black
pluggy==1.5.0
# via pytest
-pur==7.3.1
+pur==7.3.2
# via -r requirements_dev.in
-pycodestyle==2.11.1
+py-serializable==1.1.0
+ # via cyclonedx-python-lib
+pycodestyle==2.12.1
# via flake8
-pycparser==2.22
- # via cffi
-pydantic==2.7.2
- # via
- # safety
- # safety-schemas
-pydantic-core==2.18.3
- # via pydantic
pyflakes==3.2.0
# via flake8
pygments==2.18.0
# via rich
+pyparsing==3.1.4
+ # via pip-requirements-parser
pyproject-hooks==1.1.0
# via
# build
# pip-tools
-pytest==8.2.1
+pytest==8.3.2
# via pytest-cov
pytest-cov==5.0.0
# via -r requirements_dev.in
-pyyaml==6.0.1
+pyyaml==6.0.2
# via bandit
requests==2.32.3
- # via safety
-rich==13.7.1
# via
- # bandit
- # safety
- # typer
-ruamel-yaml==0.18.6
+ # cachecontrol
+ # pip-audit
+rich==13.8.0
# via
- # safety
- # safety-schemas
-ruamel-yaml-clib==0.2.8
- # via ruamel-yaml
-safety==3.2.0
- # via -r requirements_dev.in
-safety-schemas==0.0.2
- # via safety
-shellingham==1.5.4
- # via typer
-stevedore==5.2.0
+ # bandit
+ # pip-audit
+six==1.16.0
+ # via html5lib
+sortedcontainers==2.4.0
+ # via cyclonedx-python-lib
+stevedore==5.3.0
# via bandit
-typer==0.12.3
- # via safety
-typing-extensions==4.12.0
- # via
- # pydantic
- # pydantic-core
- # safety
- # safety-schemas
- # typer
-urllib3==2.2.1
- # via
- # requests
- # safety
-wheel==0.43.0
+toml==0.10.2
+ # via pip-audit
+typing-extensions==4.12.2
+ # via mypy
+urllib3==2.2.2
+ # via requests
+webencodings==0.5.1
+ # via html5lib
+wheel==0.44.0
# via pip-tools
# The following packages are considered to be unsafe in a requirements file:
diff --git a/runtime.txt b/runtime.txt
index b884b0f..14a2d25 100644
--- a/runtime.txt
+++ b/runtime.txt
@@ -1 +1 @@
-python-3.12.2
\ No newline at end of file
+python-3.12.5
\ No newline at end of file