From 5feec7f00856b0d5d768526e33ad783ebb53c84d Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Fri, 31 May 2024 16:16:06 +0100 Subject: [PATCH 01/66] WIP --- Dockerfile | 14 +++++--------- README.md | 2 +- compose.yml | 24 +++++++++++++----------- docker-entrypoint.sh | 3 --- nginx/Dockerfile | 4 ++++ nginx/nginx.conf | 14 ++++++++++++++ 6 files changed, 37 insertions(+), 24 deletions(-) delete mode 100755 docker-entrypoint.sh create mode 100644 nginx/Dockerfile create mode 100644 nginx/nginx.conf diff --git a/Dockerfile b/Dockerfile index ca27f4f..a99a9f5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,18 +4,14 @@ RUN useradd containeruser WORKDIR /home/containeruser -COPY app app -COPY govuk-frontend-flask.py config.py docker-entrypoint.sh requirements.txt ./ -RUN pip install -r requirements.txt \ - && chmod +x docker-entrypoint.sh \ - && chown -R containeruser:containeruser ./ - # Set environment variables ENV FLASK_APP=govuk-frontend-flask.py \ PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 -USER containeruser +COPY app app +COPY govuk-frontend-flask.py config.py requirements.txt ./ +RUN pip install -r requirements.txt \ + && chown -R containeruser:containeruser ./ -EXPOSE 9876 -ENTRYPOINT ["./docker-entrypoint.sh"] \ No newline at end of file +USER containeruser \ No newline at end of file diff --git a/README.md b/README.md index 0c71321..9ab28dd 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ python -c 'import secrets; print(secrets.token_hex())' docker compose up --build ``` -You should now have the app running on . Accept the browsers security warning due to the self-signed HTTPS certificate to continue. +You should now have the app running on . Accept the browsers security warning due to the self-signed HTTPS certificate to continue. ## Demos diff --git a/compose.yml b/compose.yml index 8e44d6c..bb1dba0 100644 --- a/compose.yml +++ b/compose.yml @@ -1,28 +1,30 @@ services: web: - container_name: govuk-frontend-flask build: . + command: gunicorn --bind 0.0.0.0:5000 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 + expose: + - 5000 depends_on: - - cache - cache: - container_name: redis - image: redis:7.0-alpine + - redis + redis: + image: redis:7-alpine restart: always ports: - 6379:6379 - \ No newline at end of file + nginx: + build: ./nginx + ports: + - 1337:80 + depends_on: + - web 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..814c5ba --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,4 @@ +FROM nginx:stable + +RUN rm /etc/nginx/conf.d/default.conf +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..d9fd3d2 --- /dev/null +++ b/nginx/nginx.conf @@ -0,0 +1,14 @@ +upstream hello_flask { + server web:5000; +} + +server { + listen 80; + + location / { + proxy_pass http://hello_flask; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_redirect off; + } +} \ No newline at end of file From 18d58560b44a5af915e4652381b4f62f1bde1a42 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 09:30:36 +0100 Subject: [PATCH 02/66] environment config --- README.md | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/README.md b/README.md index 9ab28dd..327ef5d 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,46 @@ To run the tests: python -m pytest --cov=app --cov-report=term-missing --cov-branch ``` +## Environment + +```mermaid +flowchart LR + cache1(Redis):::CACHE + Client + db1[(Postgres)]:::DB + prox1(NGINX):::PROXY + web1(Flask):::WEB + web2[/Static/]:::WEB + + Client <-- https:443 --> prox1 <-- http:5000 --> web1 + prox1 -- Read --> web2 + web1 -- Write --> web2 + web1 <-- postgresql:5432 --> db1 + web1 <-- redis:6379 --> cache1 + + subgraph Proxy + prox1 + end + + subgraph Web + web1 + web2 + end + + subgraph Database + db1 + end + + subgraph Cache + cache1 + end + + classDef PROXY fill:#D5E8D4,stroke:#82B366,stroke-width:2px + classDef WEB fill:#FFF2CC,stroke:#D6B656,stroke-width:2px + classDef CACHE fill:#F8CECC,stroke:#B85450,stroke-width:2px + classDef DB fill:#DAE8FC,stroke:#6C8EBF,stroke-width:2px +``` + ## Features Please refer to the specific packages documentation for more details. From f9c9cc41befa6e0b68f3a683159f9ea421f62355 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 10:08:16 +0100 Subject: [PATCH 03/66] WIP --- compose.yml | 2 +- nginx/nginx.conf | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/compose.yml b/compose.yml index bb1dba0..66b9eb0 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,7 @@ services: web: build: . - command: gunicorn --bind 0.0.0.0:5000 govuk-frontend-flask:app + command: gunicorn --bind localhost:5000 govuk-frontend-flask:app restart: always environment: - CONTACT_EMAIL=[contact email] diff --git a/nginx/nginx.conf b/nginx/nginx.conf index d9fd3d2..a105f0c 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,14 +1,13 @@ -upstream hello_flask { - server web:5000; -} - server { + # listen on port 80 (http) listen 80; + server_name _; location / { - proxy_pass http://hello_flask; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; + # forward application requests to the gunicorn server + proxy_pass http://localhost:5000; proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } \ No newline at end of file From e9b64ff9b4aaee505a481f7013325680c1da8761 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 10:39:39 +0100 Subject: [PATCH 04/66] upgrade requirements --- requirements.txt | 2 +- requirements_dev.txt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index 84f6be1..a92b0e6 100644 --- a/requirements.txt +++ b/requirements.txt @@ -86,7 +86,7 @@ redis==5.0.4 # via limits rich==13.7.1 # via flask-limiter -typing-extensions==4.12.0 +typing-extensions==4.12.1 # via # flask-limiter # limits diff --git a/requirements_dev.txt b/requirements_dev.txt index d91d780..7f7bef7 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -16,7 +16,7 @@ black==24.4.2 # via -r requirements_dev.in build==1.2.1 # via pip-tools -certifi==2024.2.2 +certifi==2024.6.2 # via requests cffi==1.16.0 # via cryptography @@ -133,7 +133,7 @@ stevedore==5.2.0 # via bandit typer==0.12.3 # via safety -typing-extensions==4.12.0 +typing-extensions==4.12.1 # via # pydantic # pydantic-core From d1b7db99cd0c86babb1b0387e4c45ae536ee7e65 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 10:42:36 +0100 Subject: [PATCH 05/66] containers --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 327ef5d..e81335e 100644 --- a/README.md +++ b/README.md @@ -84,20 +84,20 @@ flowchart LR web1 <-- postgresql:5432 --> db1 web1 <-- redis:6379 --> cache1 - subgraph Proxy + subgraph Proxy container prox1 end - subgraph Web + subgraph Web container web1 web2 end - subgraph Database + subgraph Database container db1 end - subgraph Cache + subgraph Cache container cache1 end From 1b814d7fc33b7a524d6a79d39e0ac91a5330c72f Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 10:49:03 +0100 Subject: [PATCH 06/66] WIP --- nginx/nginx.conf | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index a105f0c..a73eaef 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,3 +1,7 @@ +upstream app { + server web:5000; +} + server { # listen on port 80 (http) listen 80; @@ -5,7 +9,7 @@ server { location / { # forward application requests to the gunicorn server - proxy_pass http://localhost:5000; + proxy_pass http://app; proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; From c900240f049b9ea96133260f9a34bac5056becab Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 13:09:34 +0100 Subject: [PATCH 07/66] wip --- compose.yml | 10 +++++++--- nginx.conf | 29 +++++++++++++++++++++++++++++ nginx/Dockerfile | 4 ---- nginx/nginx.conf | 17 ----------------- 4 files changed, 36 insertions(+), 24 deletions(-) create mode 100644 nginx.conf delete mode 100644 nginx/Dockerfile delete mode 100644 nginx/nginx.conf diff --git a/compose.yml b/compose.yml index 66b9eb0..d0aa2e4 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,7 @@ services: web: build: . - command: gunicorn --bind localhost:5000 govuk-frontend-flask:app + command: gunicorn --bind 0.0.0.0:5000 govuk-frontend-flask:app restart: always environment: - CONTACT_EMAIL=[contact email] @@ -23,8 +23,12 @@ services: ports: - 6379:6379 nginx: - build: ./nginx + image: nginx:stable + volumes: + - ./nginx.conf:/etc/nginx/conf.d/default.conf + - ./cert.pem:/root/ssl/cert.pem + - ./key.pem:/root/ssl/key.pem ports: - - 1337:80 + - 443:443 depends_on: - web diff --git a/nginx.conf b/nginx.conf new file mode 100644 index 0000000..72d8f9c --- /dev/null +++ b/nginx.conf @@ -0,0 +1,29 @@ +server { + # listen on port 80 (http) + listen 80; + server_name _; + + location / { + # redirect any requests to the same URL but on https + return 301 https://$host$request_uri; + } +} + +server { + # listen on port 443 (https) + listen 443 ssl; + server_name _; + + # location of the self-signed SSL certificate + ssl_certificate /root/ssl/cert.pem; + ssl_certificate_key /root/ssl/key.pem; + + 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-Real-IP $remote_addr; + } +} \ No newline at end of file diff --git a/nginx/Dockerfile b/nginx/Dockerfile deleted file mode 100644 index 814c5ba..0000000 --- a/nginx/Dockerfile +++ /dev/null @@ -1,4 +0,0 @@ -FROM nginx:stable - -RUN rm /etc/nginx/conf.d/default.conf -COPY nginx.conf /etc/nginx/conf.d \ No newline at end of file diff --git a/nginx/nginx.conf b/nginx/nginx.conf deleted file mode 100644 index a73eaef..0000000 --- a/nginx/nginx.conf +++ /dev/null @@ -1,17 +0,0 @@ -upstream app { - server web:5000; -} - -server { - # listen on port 80 (http) - listen 80; - server_name _; - - location / { - # forward application requests to the gunicorn server - proxy_pass http://app; - proxy_redirect off; - proxy_set_header Host $host; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - } -} \ No newline at end of file From c8fadf039526a56e7b5cd8fc327ce7fb91a686b5 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 13:21:00 +0100 Subject: [PATCH 08/66] add cert generation --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index e81335e..dcb9e8f 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,12 @@ python -c 'import secrets; print(secrets.token_hex())' ./build.sh ``` +### Generate self-signed certificates + +```shell +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" +``` + ### Run containers ```shell From 38e4246d83e529d8ab14dce04f2d05eb00128a1b Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 14:50:08 +0100 Subject: [PATCH 09/66] shared static assets --- compose.yml | 5 +++++ nginx.conf | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/compose.yml b/compose.yml index d0aa2e4..22b4546 100644 --- a/compose.yml +++ b/compose.yml @@ -13,6 +13,8 @@ services: - SERVICE_NAME=[name of service] - SERVICE_PHASE=[phase] - SERVICE_URL=[url of service] + volumes: + - static_volume:/home/containeruser/app/static expose: - 5000 depends_on: @@ -28,7 +30,10 @@ services: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./cert.pem:/root/ssl/cert.pem - ./key.pem:/root/ssl/key.pem + - static_volume:/home/containeruser/app/static ports: - 443:443 depends_on: - web +volumes: + static_volume: diff --git a/nginx.conf b/nginx.conf index 72d8f9c..c308ab4 100644 --- a/nginx.conf +++ b/nginx.conf @@ -26,4 +26,8 @@ server { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; } + + location /assets/ { + alias /home/containeruser/app/static/; + } } \ No newline at end of file From b1253115c4a00f616e97f58b87ca662976d346e2 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 14:52:53 +0100 Subject: [PATCH 10/66] update readme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dcb9e8f..9eeb933 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ openssl req -new -x509 -newkey rsa:4096 -nodes -out ./cert.pem -keyout ./key.pem docker compose up --build ``` -You should now have the app running on . Accept the browsers security warning due to the self-signed HTTPS certificate to continue. +You should now have the app running on . Accept the browsers security warning due to the self-signed HTTPS certificate to continue. ## Demos From 36fdb00001de52bfcfeff09167961242307e82db Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 15:19:08 +0100 Subject: [PATCH 11/66] add cache control and expires header to static assets --- nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nginx.conf b/nginx.conf index c308ab4..8489ba2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -28,6 +28,8 @@ server { } location /assets/ { + # handle static files directly, without forwarding to the application alias /home/containeruser/app/static/; + expires 30d; } } \ No newline at end of file From 47058be50c5993c915f53a8cd92bad0fb43a53a8 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 15:24:41 +0100 Subject: [PATCH 12/66] send gunicorn logs to stdout --- compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yml b/compose.yml index 22b4546..010885f 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,7 @@ services: web: build: . - command: gunicorn --bind 0.0.0.0:5000 govuk-frontend-flask:app + command: gunicorn --bind 0.0.0.0:5000 --access-logfile - --error-logfile - govuk-frontend-flask:app restart: always environment: - CONTACT_EMAIL=[contact email] From eef1c294659ba6a58b6d801e00899a60c3c1c3de Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 15:44:00 +0100 Subject: [PATCH 13/66] stop talisman forcing https redirects --- app/__init__.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index a67c326..4401bd3 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -95,7 +95,12 @@ def create_app(config_class=Config): compress.init_app(app) csrf.init_app(app) limiter.init_app(app) - talisman.init_app(app, content_security_policy=csp, permissions_policy=permissions_policy) + talisman.init_app( + app, + force_https=False, + content_security_policy=csp, + permissions_policy=permissions_policy, + ) WTFormsHelpers(app) # Create static asset bundles From 363c2a0599ab53d0308cf98107a4c136480441e3 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 16:21:54 +0100 Subject: [PATCH 14/66] set file permissions for static volume --- compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index 010885f..056d3be 100644 --- a/compose.yml +++ b/compose.yml @@ -14,7 +14,7 @@ services: - SERVICE_PHASE=[phase] - SERVICE_URL=[url of service] volumes: - - static_volume:/home/containeruser/app/static + - static_volume:/home/containeruser/app/static:rw expose: - 5000 depends_on: @@ -30,7 +30,7 @@ services: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./cert.pem:/root/ssl/cert.pem - ./key.pem:/root/ssl/key.pem - - static_volume:/home/containeruser/app/static + - static_volume:/home/containeruser/app/static:ro ports: - 443:443 depends_on: From 9d320c87d0587e5774642a52430ee29910d285bf Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 19:53:06 +0100 Subject: [PATCH 15/66] TB diagram --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9eeb933..26f7cb8 100644 --- a/README.md +++ b/README.md @@ -76,12 +76,12 @@ python -m pytest --cov=app --cov-report=term-missing --cov-branch ## Environment ```mermaid -flowchart LR +flowchart TB cache1(Redis):::CACHE Client db1[(Postgres)]:::DB prox1(NGINX):::PROXY - web1(Flask):::WEB + web1(Flask app):::WEB web2[/Static/]:::WEB Client <-- https:443 --> prox1 <-- http:5000 --> web1 From b842214c1459962d290bf5bbae560fb2b6bcda49 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 21:12:22 +0100 Subject: [PATCH 16/66] gzip static assets --- nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nginx.conf b/nginx.conf index 8489ba2..cd87d05 100644 --- a/nginx.conf +++ b/nginx.conf @@ -31,5 +31,7 @@ server { # handle static files directly, without forwarding to the application alias /home/containeruser/app/static/; expires 30d; + gzip on; + gzip_types text/css text/javascript image/png font/woff2 image/svg+xml; } } \ No newline at end of file From 87f5514e5f1de57b95514bd035763d49f74a52aa Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 21:13:27 +0100 Subject: [PATCH 17/66] reorder --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 26f7cb8..e10b6f7 100644 --- a/README.md +++ b/README.md @@ -107,10 +107,10 @@ flowchart TB cache1 end - classDef PROXY fill:#D5E8D4,stroke:#82B366,stroke-width:2px - classDef WEB fill:#FFF2CC,stroke:#D6B656,stroke-width:2px classDef CACHE fill:#F8CECC,stroke:#B85450,stroke-width:2px classDef DB fill:#DAE8FC,stroke:#6C8EBF,stroke-width:2px + classDef PROXY fill:#D5E8D4,stroke:#82B366,stroke-width:2px + classDef WEB fill:#FFF2CC,stroke:#D6B656,stroke-width:2px ``` ## Features From 86755c9f7cbb93ba771265b1ecca36e6b9a94c13 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 21:16:39 +0100 Subject: [PATCH 18/66] add comment --- nginx.conf | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nginx.conf b/nginx.conf index cd87d05..3e01e0a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -31,6 +31,8 @@ server { # handle static files directly, without forwarding to the application alias /home/containeruser/app/static/; expires 30d; + + # enable gzip compression of static assets gzip on; gzip_types text/css text/javascript image/png font/woff2 image/svg+xml; } From c1b0d1cc23dcfe591a5c71180066fc292af83b68 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 3 Jun 2024 23:32:24 +0100 Subject: [PATCH 19/66] move to server --- nginx.conf | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/nginx.conf b/nginx.conf index 3e01e0a..e9ace4a 100644 --- a/nginx.conf +++ b/nginx.conf @@ -17,6 +17,10 @@ server { # location of the self-signed SSL certificate ssl_certificate /root/ssl/cert.pem; ssl_certificate_key /root/ssl/key.pem; + + # enable gzip compression + gzip on; + gzip_types text/html text/css text/javascript image/png image/svg+xml font/woff2; location / { # forward application requests to the gunicorn server @@ -31,9 +35,5 @@ server { # handle static files directly, without forwarding to the application alias /home/containeruser/app/static/; expires 30d; - - # enable gzip compression of static assets - gzip on; - gzip_types text/css text/javascript image/png font/woff2 image/svg+xml; } } \ No newline at end of file From 8fd8f020aeb7caee7907205e17624b4d945325b9 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 09:24:38 +0100 Subject: [PATCH 20/66] add proxyfix --- app/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/__init__.py b/app/__init__.py index 4401bd3..dd2d130 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -7,6 +7,7 @@ from flask_wtf.csrf import CSRFProtect from govuk_frontend_wtf.main import WTFormsHelpers from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader +from werkzeug.middleware.proxy_fix import ProxyFix from config import Config @@ -33,6 +34,7 @@ def create_app(config_class=Config): ), ] ) + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) # Set content security policy csp = { From 7d825fb46cb0b4d3556440731723cf622476622f Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 09:24:47 +0100 Subject: [PATCH 21/66] remove duplicate mimetype --- nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index e9ace4a..45bb511 100644 --- a/nginx.conf +++ b/nginx.conf @@ -20,7 +20,7 @@ server { # enable gzip compression gzip on; - gzip_types text/html text/css text/javascript image/png image/svg+xml font/woff2; + gzip_types text/css text/javascript image/png image/svg+xml font/woff2; location / { # forward application requests to the gunicorn server From ce36033a1126adae58e4d77f9db03d5bb8e8ec58 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 10:00:01 +0100 Subject: [PATCH 22/66] 1 year cache length --- nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index 45bb511..c340210 100644 --- a/nginx.conf +++ b/nginx.conf @@ -34,6 +34,6 @@ server { location /assets/ { # handle static files directly, without forwarding to the application alias /home/containeruser/app/static/; - expires 30d; + expires 1y; } } \ No newline at end of file From ed32ff3a7845ddfe72c48683d372d9809b99be8e Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 10:03:45 +0100 Subject: [PATCH 23/66] upgrade requirements --- requirements_dev.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 7f7bef7..7019f71 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -84,17 +84,17 @@ 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 # via flake8 pycparser==2.22 # via cffi -pydantic==2.7.2 +pydantic==2.7.3 # via # safety # safety-schemas -pydantic-core==2.18.3 +pydantic-core==2.18.4 # via pydantic pyflakes==3.2.0 # via flake8 From 5a8818fce111b3e21df87b7ff970c7eef87300dc Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 10:15:16 +0100 Subject: [PATCH 24/66] remove db --- README.md | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index e10b6f7..0cd2877 100644 --- a/README.md +++ b/README.md @@ -79,15 +79,13 @@ python -m pytest --cov=app --cov-report=term-missing --cov-branch flowchart TB cache1(Redis):::CACHE Client - db1[(Postgres)]:::DB prox1(NGINX):::PROXY web1(Flask app):::WEB - web2[/Static/]:::WEB + web2[/Static files/]:::WEB Client <-- https:443 --> prox1 <-- http:5000 --> web1 - prox1 -- Read --> web2 + prox1 -- Read only --> web2 web1 -- Write --> web2 - web1 <-- postgresql:5432 --> db1 web1 <-- redis:6379 --> cache1 subgraph Proxy container @@ -99,16 +97,11 @@ flowchart TB web2 end - subgraph Database container - db1 - end - subgraph Cache container cache1 end classDef CACHE fill:#F8CECC,stroke:#B85450,stroke-width:2px - classDef DB fill:#DAE8FC,stroke:#6C8EBF,stroke-width:2px classDef PROXY fill:#D5E8D4,stroke:#82B366,stroke-width:2px classDef WEB fill:#FFF2CC,stroke:#D6B656,stroke-width:2px ``` From 94f7d3dd862bddbd211c28e9859cd0551291626a Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 4 Jun 2024 21:42:24 +0100 Subject: [PATCH 25/66] 4 workers --- compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yml b/compose.yml index 056d3be..917edf2 100644 --- a/compose.yml +++ b/compose.yml @@ -1,7 +1,7 @@ services: web: build: . - command: gunicorn --bind 0.0.0.0:5000 --access-logfile - --error-logfile - govuk-frontend-flask:app + 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] From f5350fed96b4c8735e0a783551d5968295274459 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 08:44:58 +0100 Subject: [PATCH 26/66] cache control --- nginx.conf | 1 + 1 file changed, 1 insertion(+) diff --git a/nginx.conf b/nginx.conf index c340210..7afa8ad 100644 --- a/nginx.conf +++ b/nginx.conf @@ -21,6 +21,7 @@ server { # enable gzip compression gzip on; gzip_types text/css text/javascript image/png image/svg+xml font/woff2; + gzip_proxied no-cache no-store private expired auth; location / { # forward application requests to the gunicorn server From 11f63fa2e7a17dae76a713898b0e6b2ce1511a31 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 10:12:12 +0100 Subject: [PATCH 27/66] upgrade requirements --- requirements_dev.txt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 7019f71..7d5de46 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -8,7 +8,7 @@ annotated-types==0.7.0 # via pydantic attrs==23.2.0 # via flake8-bugbear -authlib==1.3.0 +authlib==1.3.1 # via safety bandit==1.7.8 # via -r requirements_dev.in @@ -31,7 +31,7 @@ click==8.1.7 # typer coverage[toml]==7.5.3 # via pytest-cov -cryptography==42.0.7 +cryptography==42.0.8 # via authlib dparse==0.6.4b0 # via @@ -104,7 +104,7 @@ pyproject-hooks==1.1.0 # via # build # pip-tools -pytest==8.2.1 +pytest==8.2.2 # via pytest-cov pytest-cov==5.0.0 # via -r requirements_dev.in @@ -123,7 +123,7 @@ ruamel-yaml==0.18.6 # safety-schemas ruamel-yaml-clib==0.2.8 # via ruamel-yaml -safety==3.2.0 +safety==3.2.1 # via -r requirements_dev.in safety-schemas==0.0.2 # via safety From 779b58b3d833943f1bab454780885b4066f9175a Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 10:43:24 +0100 Subject: [PATCH 28/66] remove flask talisman --- README.md | 6 ++--- app/__init__.py | 64 ------------------------------------------------ config.py | 1 + nginx.conf | 14 +++++++++-- requirements.in | 1 - requirements.txt | 2 -- 6 files changed, 15 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index 0cd2877..c2032fc 100644 --- a/README.md +++ b/README.md @@ -140,9 +140,7 @@ CSRF errors are handled by creating a [flash message](#flash-messages) notificat ### HTTP security headers -Uses [Flask Talisman](https://github.com/GoogleCloudPlatform/flask-talisman) to set HTTP headers that can help protect against a few common web application security issues. - -- Forces all connections to `https`, unless running with debug enabled. +- Forces all connections to `https`. - Enables [HTTP Strict Transport Security](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security). - Sets Flask's session cookie to `secure`, so it will never be set if your application is somehow accessed via a non-secure connection. - Sets Flask's session cookie to `httponly`, preventing JavaScript from being able to access its content. @@ -153,7 +151,7 @@ Uses [Flask Talisman](https://github.com/GoogleCloudPlatform/flask-talisman) to ### Content Security Policy -A strict default [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) is set using [Flask Talisman](https://github.com/GoogleCloudPlatform/flask-talisman) to mitigate [Cross Site Scripting](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) (XSS) and packet sniffing attacks. This prevents loading any resources that are not in the same domain as the application. +A strict default [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) is set to mitigate [Cross Site Scripting](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) (XSS) and packet sniffing attacks. This prevents loading any resources that are not in the same domain as the application. ### Response compression diff --git a/app/__init__.py b/app/__init__.py index dd2d130..ffaf017 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -3,7 +3,6 @@ from flask_compress import Compress from flask_limiter import Limiter from flask_limiter.util import get_remote_address -from flask_talisman import Talisman from flask_wtf.csrf import CSRFProtect from govuk_frontend_wtf.main import WTFormsHelpers from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader @@ -15,7 +14,6 @@ compress = Compress() csrf = CSRFProtect() limiter = Limiter(get_remote_address, default_limits=["2 per second", "60 per minute"]) -talisman = Talisman() def create_app(config_class=Config): @@ -36,73 +34,11 @@ def create_app(config_class=Config): ) app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) - # Set content security policy - csp = { - "default-src": "'self'", - "script-src": [ - "'self'", - "'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw='", - "'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='", - ], - } - - # Set permissions policy - 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-management": "()", - "vertical-scroll": "()", - } - # Initialise app extensions assets.init_app(app) compress.init_app(app) csrf.init_app(app) limiter.init_app(app) - talisman.init_app( - app, - force_https=False, - content_security_policy=csp, - permissions_policy=permissions_policy, - ) WTFormsHelpers(app) # Create static asset bundles 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/nginx.conf b/nginx.conf index 7afa8ad..c043a4d 100644 --- a/nginx.conf +++ b/nginx.conf @@ -20,9 +20,19 @@ server { # enable gzip compression gzip on; - gzip_types text/css text/javascript image/png image/svg+xml font/woff2; gzip_proxied no-cache no-store private expired auth; - + gzip_types text/css text/javascript image/png image/svg+xml font/woff2 application/json; + + # set security policies + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'" 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=()"; + + # set security headers + add_header Referrer-Policy "strict-origin-when-cross-origin"; + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; + add_header X-Content-Type-Options "nosniff"; + add_header X-Frame-Options "SAMEORIGIN"; + location / { # forward application requests to the gunicorn server proxy_pass http://web:5000; diff --git a/requirements.in b/requirements.in index 1a97612..d2c990d 100644 --- a/requirements.in +++ b/requirements.in @@ -4,7 +4,6 @@ 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 a92b0e6..ed804fe 100644 --- a/requirements.txt +++ b/requirements.txt @@ -34,8 +34,6 @@ flask-compress==1.15 # via -r requirements.in flask-limiter[redis]==3.7.0 # via -r requirements.in -flask-talisman==1.1.0 - # via -r requirements.in flask-wtf==1.2.1 # via govuk-frontend-wtf govuk-frontend-jinja==3.1.0 From 560a80a9109f7170f06454115f75ebb1b5a20693 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 10:58:27 +0100 Subject: [PATCH 29/66] add forwarded protocol --- app/__init__.py | 2 +- nginx.conf | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/__init__.py b/app/__init__.py index ffaf017..7dadf7c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -32,7 +32,7 @@ def create_app(config_class=Config): ), ] ) - app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1) + app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1) # Initialise app extensions assets.init_app(app) diff --git a/nginx.conf b/nginx.conf index c043a4d..bd89ae6 100644 --- a/nginx.conf +++ b/nginx.conf @@ -39,6 +39,7 @@ server { 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; } From 066ea5aaf9b3b85f156487b1f20273afcf71f9ad Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 10:58:39 +0100 Subject: [PATCH 30/66] use alpine slim version --- compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compose.yml b/compose.yml index 917edf2..5ccac70 100644 --- a/compose.yml +++ b/compose.yml @@ -25,7 +25,7 @@ services: ports: - 6379:6379 nginx: - image: nginx:stable + image: nginx:stable-alpine-slim volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./cert.pem:/root/ssl/cert.pem From cb8b76e9a1a36fb9db2653ed411c2bf4c6a495a2 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 5 Jun 2024 12:10:08 +0100 Subject: [PATCH 31/66] rename user --- Dockerfile | 8 ++++---- compose.yml | 4 ++-- nginx.conf | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Dockerfile b/Dockerfile index a99a9f5..189b2fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ FROM python:3.12-slim -RUN useradd containeruser +RUN useradd appuser -WORKDIR /home/containeruser +WORKDIR /home/appuser # Set environment variables ENV FLASK_APP=govuk-frontend-flask.py \ @@ -12,6 +12,6 @@ ENV FLASK_APP=govuk-frontend-flask.py \ COPY app app COPY govuk-frontend-flask.py config.py requirements.txt ./ RUN pip install -r requirements.txt \ - && chown -R containeruser:containeruser ./ + && chown -R appuser:appuser ./ -USER containeruser \ No newline at end of file +USER appuser \ No newline at end of file diff --git a/compose.yml b/compose.yml index 5ccac70..329b7d7 100644 --- a/compose.yml +++ b/compose.yml @@ -14,7 +14,7 @@ services: - SERVICE_PHASE=[phase] - SERVICE_URL=[url of service] volumes: - - static_volume:/home/containeruser/app/static:rw + - static_volume:/home/appuser/app/static:rw expose: - 5000 depends_on: @@ -30,7 +30,7 @@ services: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./cert.pem:/root/ssl/cert.pem - ./key.pem:/root/ssl/key.pem - - static_volume:/home/containeruser/app/static:ro + - static_volume:/home/appuser/app/static:ro ports: - 443:443 depends_on: diff --git a/nginx.conf b/nginx.conf index bd89ae6..9d5fc54 100644 --- a/nginx.conf +++ b/nginx.conf @@ -45,7 +45,7 @@ server { location /assets/ { # handle static files directly, without forwarding to the application - alias /home/containeruser/app/static/; + alias /home/appuser/app/static/; expires 1y; } } \ No newline at end of file From 89e7ca32e4033240860bc2894cec2e225795e5bb Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 6 Jun 2024 20:25:45 +0100 Subject: [PATCH 32/66] update docs --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c2032fc..56e2871 100644 --- a/README.md +++ b/README.md @@ -118,7 +118,7 @@ CSS is [minified](https://en.wikipedia.org/wiki/Minification_(programming)) usin ### Cache busting -Merged and compressed assets are browser cache busted on update by modifying their URL with their MD5 hash using [Flask Assets](https://flask-assets.readthedocs.io/en/latest/) and [Webassets](https://webassets.readthedocs.io/en/latest/). The MD5 hash is appended to the file name, for example `custom-d41d8cd9.css` instead of a query string, to support certain older browsers and proxies that ignore the querystring in their caching behaviour. +Merged and minified assets are browser cache busted on update by modifying the filename with their MD5 hash using [Flask Assets](https://flask-assets.readthedocs.io/en/latest/) and [Webassets](https://webassets.readthedocs.io/en/latest/). The MD5 hash is appended to the file name, for example `custom-d41d8cd9.css` instead of a query string, to support certain older browsers and proxies that ignore the querystring in their caching behaviour. ### Forms generation and validation @@ -145,13 +145,16 @@ CSRF errors are handled by creating a [flash message](#flash-messages) notificat - Sets Flask's session cookie to `secure`, so it will never be set if your application is somehow accessed via a non-secure connection. - Sets Flask's session cookie to `httponly`, preventing JavaScript from being able to access its content. - Sets [X-Frame-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options) to `SAMEORIGIN` to avoid [clickjacking](https://en.wikipedia.org/wiki/Clickjacking). -- Sets [X-XSS-Protection](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-XSS-Protection) to enable a cross site scripting filter for IE and Safari (note Chrome has removed this and Firefox never supported it). - Sets [X-Content-Type-Options](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Content-Type-Options) to prevent content type sniffing. - Sets a strict [Referrer-Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Referrer-Policy) of `strict-origin-when-cross-origin` that governs which referrer information should be included with requests made. ### Content Security Policy -A strict default [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) is set to mitigate [Cross Site Scripting](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) (XSS) and packet sniffing attacks. This prevents loading any resources that are not in the same domain as the application. +A strict [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP) (CSP) is set to mitigate [Cross Site Scripting](https://developer.mozilla.org/en-US/docs/Web/Security/Types_of_attacks#cross-site_scripting_xss) (XSS) and packet sniffing attacks. This prevents loading any resources that are not in the same domain as the application by default. + +### Permissions Policy + +A strict [Permissions Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy) is set to deny the use of browser features by default. ### Response compression From ed2b8e54399db00c59621b80720e13825c287f67 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Fri, 7 Jun 2024 12:40:35 +0100 Subject: [PATCH 33/66] move govuk components into demos --- app/demos/routes.py | 4 ++-- build.sh | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/demos/routes.py b/app/demos/routes.py index cc4a67a..474658e 100644 --- a/app/demos/routes.py +++ b/app/demos/routes.py @@ -10,7 +10,7 @@ @bp.route("/components", methods=["GET"]) def components(): - components = os.listdir("govuk_components") + components = os.listdir("app/demos/govuk_components") components.sort() return render_template("components.html", components=components) @@ -19,7 +19,7 @@ def components(): @bp.route("/components/", methods=["GET"]) def component(component): try: - with open(f"govuk_components/{component}/{component}.yaml") as yaml_file: + with open(f"app/demos/govuk_components/{component}/{component}.yaml") as yaml_file: fixtures = yaml.safe_load(yaml_file) except FileNotFoundError: raise NotFound diff --git a/build.sh b/build.sh index a644e43..b97beac 100755 --- a/build.sh +++ b/build.sh @@ -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 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.4.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 From c12c71d2a65f7b5350a1a71bfca9e5b5686eec16 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 12 Jun 2024 11:45:07 +0100 Subject: [PATCH 34/66] update openssl command and cert outputs --- README.md | 2 +- compose.yml | 2 +- nginx.conf | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 56e2871..f173a5d 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,7 @@ python -c 'import secrets; print(secrets.token_hex())' ### Generate self-signed certificates ```shell -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" +openssl req -x509 -noenc -newkey rsa:4096 -keyout ./key.pem -out ./req.pem -days 365 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost" ``` ### Run containers diff --git a/compose.yml b/compose.yml index 329b7d7..41b27c4 100644 --- a/compose.yml +++ b/compose.yml @@ -28,7 +28,7 @@ services: image: nginx:stable-alpine-slim volumes: - ./nginx.conf:/etc/nginx/conf.d/default.conf - - ./cert.pem:/root/ssl/cert.pem + - ./req.pem:/root/ssl/req.pem - ./key.pem:/root/ssl/key.pem - static_volume:/home/appuser/app/static:ro ports: diff --git a/nginx.conf b/nginx.conf index 9d5fc54..d577ac3 100644 --- a/nginx.conf +++ b/nginx.conf @@ -15,7 +15,7 @@ server { server_name _; # location of the self-signed SSL certificate - ssl_certificate /root/ssl/cert.pem; + ssl_certificate /root/ssl/req.pem; ssl_certificate_key /root/ssl/key.pem; # enable gzip compression From b6ba5f482bd831451c158da7c86dbe0a19b2e97a Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 12 Jun 2024 14:53:23 +0100 Subject: [PATCH 35/66] use mozilla guideline ssl config --- README.md | 4 +++- compose.yml | 1 + nginx.conf | 41 ++++++++++++++++++++++++----------------- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index f173a5d..b94c3bc 100644 --- a/README.md +++ b/README.md @@ -50,7 +50,9 @@ python -c 'import secrets; print(secrets.token_hex())' ### Generate self-signed certificates ```shell -openssl req -x509 -noenc -newkey rsa:4096 -keyout ./key.pem -out ./req.pem -days 365 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost" +openssl req -x509 -noenc -newkey rsa:2048 -keyout ./key.pem -out ./req.pem -days 90 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost" + +openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 ``` ### Run containers diff --git a/compose.yml b/compose.yml index 41b27c4..4edcf8c 100644 --- a/compose.yml +++ b/compose.yml @@ -30,6 +30,7 @@ services: - ./nginx.conf:/etc/nginx/conf.d/default.conf - ./req.pem:/root/ssl/req.pem - ./key.pem:/root/ssl/key.pem + - ./dhparam.pem:/root/ssl/dhparam.pem - static_volume:/home/appuser/app/static:ro ports: - 443:443 diff --git a/nginx.conf b/nginx.conf index d577ac3..ea1ecb0 100644 --- a/nginx.conf +++ b/nginx.conf @@ -1,38 +1,45 @@ +# generated 2024-06-12, Mozilla Guideline v5.7, nginx 1.26, OpenSSL 3.0.11, intermediate configuration, no OCSP +# https://ssl-config.mozilla.org/#server=nginx&version=1.26&config=intermediate&openssl=3.0.11&ocsp=false&guideline=5.7 server { - # listen on port 80 (http) - listen 80; - server_name _; + listen 80 default_server; + listen [::]:80 default_server; location / { - # redirect any requests to the same URL but on https return 301 https://$host$request_uri; } } server { - # listen on port 443 (https) listen 443 ssl; - server_name _; + listen [::]:443 ssl; + http2 on; - # location of the self-signed SSL certificate ssl_certificate /root/ssl/req.pem; ssl_certificate_key /root/ssl/key.pem; + ssl_session_timeout 1d; + ssl_session_cache shared:MozSSL:10m; # about 40000 sessions + ssl_session_tickets off; + ssl_dhparam /root/ssl/dhparam.pem; + + # intermediate configuration + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + ssl_prefer_server_ciphers off; + + # add security headers + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'" 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 "SAMEORIGIN" always; + add_header X-Xss-Protection "1; mode=block" always; # enable gzip compression gzip on; gzip_proxied no-cache no-store private expired auth; gzip_types text/css text/javascript image/png image/svg+xml font/woff2 application/json; - # set security policies - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'" 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=()"; - - # set security headers - add_header Referrer-Policy "strict-origin-when-cross-origin"; - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains; preload"; - add_header X-Content-Type-Options "nosniff"; - add_header X-Frame-Options "SAMEORIGIN"; - location / { # forward application requests to the gunicorn server proxy_pass http://web:5000; From 600ca549b4fff03cd4294d062a0c952df11bdfb7 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 12 Jun 2024 23:27:42 +0100 Subject: [PATCH 36/66] correct path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b94c3bc..4f23860 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ python -c 'import secrets; print(secrets.token_hex())' ```shell openssl req -x509 -noenc -newkey rsa:2048 -keyout ./key.pem -out ./req.pem -days 90 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost" -openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 +openssl dhparam -out ./dhparam.pem 2048 ``` ### Run containers From 16217a894ac4eb4640eb7374d497c9c087669587 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 12 Jun 2024 23:35:25 +0100 Subject: [PATCH 37/66] upgrade requirements --- requirements_dev.txt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 7d5de46..cb943af 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -10,7 +10,7 @@ attrs==23.2.0 # via flake8-bugbear authlib==1.3.1 # via safety -bandit==1.7.8 +bandit==1.7.9 # via -r requirements_dev.in black==24.4.2 # via -r requirements_dev.in @@ -55,7 +55,7 @@ markdown-it-py==3.0.0 # via rich markupsafe==2.1.5 # via jinja2 -marshmallow==3.21.2 +marshmallow==3.21.3 # via safety mccabe==0.7.0 # via flake8 @@ -63,7 +63,7 @@ mdurl==0.1.2 # via markdown-it-py mypy-extensions==1.0.0 # via black -packaging==24.0 +packaging==24.1 # via # black # build @@ -90,7 +90,7 @@ pycodestyle==2.11.1 # via flake8 pycparser==2.22 # via cffi -pydantic==2.7.3 +pydantic==2.7.4 # via # safety # safety-schemas @@ -123,7 +123,7 @@ ruamel-yaml==0.18.6 # safety-schemas ruamel-yaml-clib==0.2.8 # via ruamel-yaml -safety==3.2.1 +safety==3.2.3 # via -r requirements_dev.in safety-schemas==0.0.2 # via safety @@ -133,7 +133,7 @@ stevedore==5.2.0 # via bandit typer==0.12.3 # via safety -typing-extensions==4.12.1 +typing-extensions==4.12.2 # via # pydantic # pydantic-core From 80dd7ee75723eaedb880aec9169f407d40ed309a Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 12 Jun 2024 23:39:16 +0100 Subject: [PATCH 38/66] remove flask compress --- app/__init__.py | 3 --- requirements.in | 1 - requirements.txt | 13 +++---------- 3 files changed, 3 insertions(+), 14 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 7dadf7c..96ccf98 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,6 +1,5 @@ from flask import Flask from flask_assets import Bundle, Environment -from flask_compress import Compress from flask_limiter import Limiter from flask_limiter.util import get_remote_address from flask_wtf.csrf import CSRFProtect @@ -11,7 +10,6 @@ from config import Config assets = Environment() -compress = Compress() csrf = CSRFProtect() limiter = Limiter(get_remote_address, default_limits=["2 per second", "60 per minute"]) @@ -36,7 +34,6 @@ def create_app(config_class=Config): # Initialise app extensions assets.init_app(app) - compress.init_app(app) csrf.init_app(app) limiter.init_app(app) WTFormsHelpers(app) diff --git a/requirements.in b/requirements.in index d2c990d..52cdcd7 100644 --- a/requirements.in +++ b/requirements.in @@ -2,7 +2,6 @@ cssmin email_validator flask flask-assets -flask-compress flask-limiter[redis] govuk-frontend-jinja govuk-frontend-wtf diff --git a/requirements.txt b/requirements.txt index ed804fe..06fde0e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,8 +6,6 @@ # blinker==1.8.2 # via flask -brotli==1.1.0 - # via flask-compress click==8.1.7 # via flask cssmin==0.2.0 @@ -24,14 +22,11 @@ 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-wtf==1.2.1 @@ -72,7 +67,7 @@ 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 @@ -80,11 +75,11 @@ pygments==2.18.0 # via rich pyyaml==6.0.1 # via -r requirements.in -redis==5.0.4 +redis==5.0.5 # via limits rich==13.7.1 # via flask-limiter -typing-extensions==4.12.1 +typing-extensions==4.12.2 # via # flask-limiter # limits @@ -98,5 +93,3 @@ wtforms==3.1.2 # via # flask-wtf # govuk-frontend-wtf -zstandard==0.22.0 - # via flask-compress From 688858cabe6e91ab6cbbb89f62b418f520406af1 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 08:19:28 +0100 Subject: [PATCH 39/66] update x frame options header --- nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx.conf b/nginx.conf index ea1ecb0..bd501b2 100644 --- a/nginx.conf +++ b/nginx.conf @@ -32,7 +32,7 @@ server { 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 "SAMEORIGIN" always; + add_header X-Frame-Options "DENY" always; add_header X-Xss-Protection "1; mode=block" always; # enable gzip compression From 53ab869e889da3847b4953555c92e3daa0eef5c4 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 09:13:40 +0100 Subject: [PATCH 40/66] move cert generation into dockerfile --- README.md | 10 +--------- compose.yml | 6 +----- nginx/Dockerfile | 8 ++++++++ nginx.conf => nginx/nginx.conf | 6 +++--- 4 files changed, 13 insertions(+), 17 deletions(-) create mode 100644 nginx/Dockerfile rename nginx.conf => nginx/nginx.conf (95%) diff --git a/README.md b/README.md index 4f23860..e81ee11 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,6 @@ python -c 'import secrets; print(secrets.token_hex())' ./build.sh ``` -### Generate self-signed certificates - -```shell -openssl req -x509 -noenc -newkey rsa:2048 -keyout ./key.pem -out ./req.pem -days 90 -subj "/C=GB/ST=Devon/L=Plymouth/O=HM Land Registry/OU=DDaT/CN=localhost" - -openssl dhparam -out ./dhparam.pem 2048 -``` - ### Run containers ```shell @@ -114,7 +106,7 @@ Please refer to the specific packages documentation for more details. ### Asset management -Custom CSS and JavaScript files are merged and compressed using [Flask Assets](https://flask-assets.readthedocs.io/en/latest/) and [Webassets](https://webassets.readthedocs.io/en/latest/). This takes all `*.css` files in `app/static/src/css` and all `*.js` files in `app/static/src/js` and outputs a single compressed file to both `app/static/dist/css` and `app/static/dist/js` respectively. +Custom CSS and JavaScript files are merged and minified using [Flask Assets](https://flask-assets.readthedocs.io/en/latest/) and [Webassets](https://webassets.readthedocs.io/en/latest/). This takes all `*.css` files in `app/static/src/css` and all `*.js` files in `app/static/src/js` and outputs a single minified file to both `app/static/dist/css` and `app/static/dist/js` respectively. CSS is [minified](https://en.wikipedia.org/wiki/Minification_(programming)) using [CSSMin](https://github.com/zacharyvoase/cssmin) and JavaScript is minified using [JSMin](https://github.com/tikitu/jsmin/). This removes all whitespace characters, comments and line breaks to reduce the size of the source code, making its transmission over a network more efficient. diff --git a/compose.yml b/compose.yml index 4edcf8c..f443151 100644 --- a/compose.yml +++ b/compose.yml @@ -25,12 +25,8 @@ services: ports: - 6379:6379 nginx: - image: nginx:stable-alpine-slim + build: ./nginx volumes: - - ./nginx.conf:/etc/nginx/conf.d/default.conf - - ./req.pem:/root/ssl/req.pem - - ./key.pem:/root/ssl/key.pem - - ./dhparam.pem:/root/ssl/dhparam.pem - static_volume:/home/appuser/app/static:ro ports: - 443:443 diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..81e12e6 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,8 @@ +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=DDaT/CN=localhost" && \ + openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 + +COPY nginx.conf /etc/nginx/conf.d \ No newline at end of file diff --git a/nginx.conf b/nginx/nginx.conf similarity index 95% rename from nginx.conf rename to nginx/nginx.conf index bd501b2..c4b7415 100644 --- a/nginx.conf +++ b/nginx/nginx.conf @@ -14,12 +14,12 @@ server { listen [::]:443 ssl; http2 on; - ssl_certificate /root/ssl/req.pem; - ssl_certificate_key /root/ssl/key.pem; + 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; - ssl_dhparam /root/ssl/dhparam.pem; + ssl_dhparam /etc/nginx/ssl/dhparam.pem; # intermediate configuration ssl_protocols TLSv1.2 TLSv1.3; From a0aa0afcafc9b42bbb1c4e00119ad23e2999e51c Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 10:26:02 +0100 Subject: [PATCH 41/66] remove absent field check --- app/demos/forms.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/demos/forms.py b/app/demos/forms.py index 6059857..0594fc7 100644 --- a/app/demos/forms.py +++ b/app/demos/forms.py @@ -268,10 +268,6 @@ class KitchenSinkForm(FlaskForm): widget=GovPasswordInput(), validators=[ InputRequired("Password is required"), - EqualTo( - "password_retype_field", - message="Please ensure both password fields match", - ), ], description="PasswordField rendered using a GovPasswordInput widget.", ) From e257f7ab4ddd5a8b0e287aa6643a8c002f1b1702 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 10:26:12 +0100 Subject: [PATCH 42/66] add base-uri to csp --- nginx/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index c4b7415..4688264 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -27,7 +27,7 @@ server { ssl_prefer_server_ciphers off; # add security headers - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'" always; + add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'; base-uri 'self'" 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; From 06c17fa3920d9662d1d85f4bf832653266f9fcc9 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 21:55:36 +0100 Subject: [PATCH 43/66] modern config --- nginx/nginx.conf | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 4688264..5dac187 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,5 +1,5 @@ -# generated 2024-06-12, Mozilla Guideline v5.7, nginx 1.26, OpenSSL 3.0.11, intermediate configuration, no OCSP -# https://ssl-config.mozilla.org/#server=nginx&version=1.26&config=intermediate&openssl=3.0.11&ocsp=false&guideline=5.7 +# generated 2024-06-13, Mozilla Guideline v5.7, nginx 1.26.1, OpenSSL 3.0.11, modern configuration, no OCSP +# https://ssl-config.mozilla.org/#server=nginx&version=1.26.1&config=modern&openssl=3.0.11&ocsp=false&guideline=5.7 server { listen 80 default_server; listen [::]:80 default_server; @@ -19,11 +19,9 @@ server { ssl_session_timeout 1d; ssl_session_cache shared:MozSSL:10m; # about 40000 sessions ssl_session_tickets off; - ssl_dhparam /etc/nginx/ssl/dhparam.pem; - # intermediate configuration - ssl_protocols TLSv1.2 TLSv1.3; - ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-CHACHA20-POLY1305; + # modern configuration + ssl_protocols TLSv1.3; ssl_prefer_server_ciphers off; # add security headers From 4040adf1182a585b28131a00a784994205b2d9d2 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 13 Jun 2024 21:55:49 +0100 Subject: [PATCH 44/66] remove dhparam --- nginx/Dockerfile | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index 81e12e6..ae604bf 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -2,7 +2,6 @@ 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=DDaT/CN=localhost" && \ - openssl dhparam -out /etc/nginx/ssl/dhparam.pem 2048 + 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=DDaT/CN=localhost" COPY nginx.conf /etc/nginx/conf.d \ No newline at end of file From ebaff80a854d78e57fc64148ff52c2b66a7d4fb6 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Fri, 14 Jun 2024 10:04:05 +0100 Subject: [PATCH 45/66] 10 year expires header --- nginx/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 5dac187..71f00e3 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -51,6 +51,6 @@ server { location /assets/ { # handle static files directly, without forwarding to the application alias /home/appuser/app/static/; - expires 1y; + expires 10y; } } \ No newline at end of file From d01c3cb671d6070f14bbc91a174906b4fdf77a3c Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Fri, 14 Jun 2024 18:32:31 +0100 Subject: [PATCH 46/66] increase bullet spacing --- app/templates/demos/component.html | 2 +- app/templates/demos/components.html | 2 +- app/templates/demos/forms.html | 2 +- app/templates/main/index.html | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/templates/demos/component.html b/app/templates/demos/component.html index 61ca9b7..6879a47 100644 --- a/app/templates/demos/component.html +++ b/app/templates/demos/component.html @@ -65,7 +65,7 @@

{{component | replace("-", " ") | capitalize}}

Examples:

-
    +
      {% for fixture in fixtures.examples if not fixture.hidden %}
    • {{fixture.name | capitalize}} diff --git a/app/templates/demos/components.html b/app/templates/demos/components.html index 9841936..8b8a8e0 100644 --- a/app/templates/demos/components.html +++ b/app/templates/demos/components.html @@ -23,7 +23,7 @@
      Demo

      Components

      -
        +
          {% for component in components %}
        • {{component | replace("-", " ") | capitalize}} diff --git a/app/templates/demos/forms.html b/app/templates/demos/forms.html index 29b1b98..62d4ec9 100644 --- a/app/templates/demos/forms.html +++ b/app/templates/demos/forms.html @@ -25,7 +25,7 @@ {{ super() }} Demo

          Forms

          -
            +
            • Autocomplete
            • Bank details
            • Conditional reveal
            • diff --git a/app/templates/main/index.html b/app/templates/main/index.html index 2fab0bf..dc6483c 100644 --- a/app/templates/main/index.html +++ b/app/templates/main/index.html @@ -14,7 +14,7 @@

              Hello, World!

              get a new project started quicker.

              It is also the reference implementation of two core packages:

              -
                +
                • GOV.UK Frontend Jinja which provides Jinja macros of GOV.UK components
                • 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

                    -
                      + From 670a25ad18fb8314378f30769b42cc548c01a303 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Fri, 14 Jun 2024 18:33:43 +0100 Subject: [PATCH 47/66] update csp --- nginx/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 71f00e3..8f14d58 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -25,7 +25,7 @@ server { ssl_prefer_server_ciphers off; # add security headers - add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'; base-uri 'self'" always; + add_header Content-Security-Policy "script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'; base-uri 'none'" 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; From 46d372d279f1a96aada52d74b175776414f79449 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 18 Jun 2024 17:06:23 +0100 Subject: [PATCH 48/66] gzip more mimetypes --- nginx/nginx.conf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 8f14d58..6f2a0c5 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -36,7 +36,7 @@ server { # enable gzip compression gzip on; gzip_proxied no-cache no-store private expired auth; - gzip_types text/css text/javascript image/png image/svg+xml font/woff2 application/json; + gzip_types application/javascript application/json font/otf font/ttf font/woff font/woff2 image/gif image/jpeg image/png image/svg+xml image/webp text/css text/javascript; location / { # forward application requests to the gunicorn server From f2e993eedee0d6f5ab3ed774d70a1b9598ba820c Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 18 Jun 2024 23:21:32 +0100 Subject: [PATCH 49/66] upgrade components --- requirements.txt | 4 ++-- requirements_dev.txt | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/requirements.txt b/requirements.txt index 06fde0e..fe541e2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ deprecated==1.2.14 # via limits dnspython==2.6.1 # via email-validator -email-validator==2.1.1 +email-validator==2.1.2 # via -r requirements.in flask==3.0.3 # via @@ -75,7 +75,7 @@ pygments==2.18.0 # via rich pyyaml==6.0.1 # via -r requirements.in -redis==5.0.5 +redis==5.0.6 # via limits rich==13.7.1 # via flask-limiter diff --git a/requirements_dev.txt b/requirements_dev.txt index cb943af..97daf79 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -37,7 +37,7 @@ dparse==0.6.4b0 # via # safety # safety-schemas -flake8==7.0.0 +flake8==7.1.0 # via # flake8-bugbear # pep8-naming @@ -86,7 +86,7 @@ pluggy==1.5.0 # via pytest pur==7.3.2 # via -r requirements_dev.in -pycodestyle==2.11.1 +pycodestyle==2.12.0 # via flake8 pycparser==2.22 # via cffi @@ -140,7 +140,7 @@ typing-extensions==4.12.2 # safety # safety-schemas # typer -urllib3==2.2.1 +urllib3==2.2.2 # via # requests # safety From 5177b4c48153a8ecd350e842bb22fbd137aede73 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 19 Jun 2024 10:11:06 +0100 Subject: [PATCH 50/66] more gzip tuning --- nginx/nginx.conf | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 6f2a0c5..d376399 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -35,8 +35,9 @@ server { # enable gzip compression gzip on; - gzip_proxied no-cache no-store private expired auth; - gzip_types application/javascript application/json font/otf font/ttf font/woff font/woff2 image/gif image/jpeg image/png image/svg+xml image/webp text/css text/javascript; + 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 From 63a99fe9730e0fe5590de24e70862183070eb01e Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 24 Jun 2024 08:55:46 +0100 Subject: [PATCH 51/66] upgrade requirements --- requirements.txt | 4 ++-- requirements_dev.txt | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/requirements.txt b/requirements.txt index fe541e2..b688fcd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,7 +16,7 @@ deprecated==1.2.14 # via limits dnspython==2.6.1 # via email-validator -email-validator==2.1.2 +email-validator==2.2.0 # via -r requirements.in flask==3.0.3 # via @@ -54,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 diff --git a/requirements_dev.txt b/requirements_dev.txt index 97daf79..ccb874c 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -29,7 +29,7 @@ click==8.1.7 # pur # safety # typer -coverage[toml]==7.5.3 +coverage[toml]==7.5.4 # via pytest-cov cryptography==42.0.8 # via authlib From 31097650f44cbb7f320efee411b9c1662c8c73d9 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Mon, 24 Jun 2024 08:56:15 +0100 Subject: [PATCH 52/66] add sec headers, tweak static serving --- nginx/nginx.conf | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index d376399..91eab39 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -25,7 +25,10 @@ server { ssl_prefer_server_ciphers off; # add security headers - add_header Content-Security-Policy "script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'; base-uri 'none'" always; + add_header Content-Security-Policy "script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; 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; @@ -50,8 +53,13 @@ server { } location /assets/ { - # handle static files directly, without forwarding to the application + # 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 From 2bc6aa43caa0710f0938aaecfc29d03d5fc619df Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 26 Jun 2024 10:09:10 +0100 Subject: [PATCH 53/66] dont need to map to host port --- compose.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compose.yml b/compose.yml index f443151..1a7323f 100644 --- a/compose.yml +++ b/compose.yml @@ -22,8 +22,8 @@ services: redis: image: redis:7-alpine restart: always - ports: - - 6379:6379 + expose: + - 6379 nginx: build: ./nginx volumes: From 8953c90b569a535af64435976bc9c837c0b89e79 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 31 Jul 2024 09:53:51 +0100 Subject: [PATCH 54/66] upgrade requirements --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index b688fcd..724d6b5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -27,7 +27,7 @@ flask==3.0.3 # govuk-frontend-wtf flask-assets==2.1.0 # via -r requirements.in -flask-limiter[redis]==3.7.0 +flask-limiter[redis]==3.8.0 # via -r requirements.in flask-wtf==1.2.1 # via govuk-frontend-wtf @@ -75,7 +75,7 @@ pygments==2.18.0 # via rich pyyaml==6.0.1 # via -r requirements.in -redis==5.0.6 +redis==5.0.8 # via limits rich==13.7.1 # via flask-limiter From f4dbf3e3de85569c0b544e5c49d7472560323508 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 31 Jul 2024 09:54:07 +0100 Subject: [PATCH 55/66] upgrade dev reqs, remove safety --- .github/workflows/python-app.yml | 2 +- requirements_dev.in | 3 +- requirements_dev.txt | 116 ++++++++++++++----------------- 3 files changed, 57 insertions(+), 64 deletions(-) diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index de80497..9ef8f51 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -27,7 +27,7 @@ jobs: pip install -r requirements_dev.txt pip install -r requirements.txt - name: Check dependencies for known security vulnerabilities - run: safety check -r requirements.txt + run: pip-audit -r requirements.txt - name: Check code for potential security vulnerabilities run: bandit -r . -x /tests - name: Check code formatting 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 ccb874c..9a8a98e 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 # via flake8-bugbear -authlib==1.3.1 - # via safety bandit==1.7.9 # via -r requirements_dev.in black==24.4.2 # via -r requirements_dev.in +boolean-py==4.0 + # via license-expression build==1.2.1 # via pip-tools -certifi==2024.6.2 +cachecontrol[filecache]==0.14.0 + # via + # cachecontrol + # pip-audit +certifi==2024.7.4 # via requests -cffi==1.16.0 - # via cryptography charset-normalizer==3.3.2 # via requests click==8.1.7 @@ -27,57 +27,65 @@ click==8.1.7 # black # pip-tools # pur - # safety - # typer -coverage[toml]==7.5.4 +coverage[toml]==7.6.0 # via pytest-cov -cryptography==42.0.8 - # via authlib -dparse==0.6.4b0 - # via - # safety - # safety-schemas +cyclonedx-python-lib==7.5.1 + # via pip-audit +defusedxml==0.7.1 + # via py-serializable +filelock==3.15.4 + # via cachecontrol flake8==7.1.0 # via # flake8-bugbear # pep8-naming flake8-bugbear==24.4.26 # via -r requirements_dev.in +html5lib==1.1 + # via pip-audit idna==3.7 # 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.0 + # via cyclonedx-python-lib markdown-it-py==3.0.0 # via rich -markupsafe==2.1.5 - # via jinja2 -marshmallow==3.21.3 - # 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.1 + # via -r requirements_dev.in mypy-extensions==1.0.0 - # via black + # 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 # 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 @@ -86,64 +94,48 @@ pluggy==1.5.0 # via pytest pur==7.3.2 # via -r requirements_dev.in +py-serializable==1.1.0 + # via cyclonedx-python-lib pycodestyle==2.12.0 # via flake8 -pycparser==2.22 - # via cffi -pydantic==2.7.4 - # via - # safety - # safety-schemas -pydantic-core==2.18.4 - # via pydantic pyflakes==3.2.0 # via flake8 pygments==2.18.0 # via rich +pyparsing==3.1.2 + # via pip-requirements-parser pyproject-hooks==1.1.0 # via # build # pip-tools -pytest==8.2.2 +pytest==8.3.2 # via pytest-cov pytest-cov==5.0.0 # via -r requirements_dev.in pyyaml==6.0.1 # via bandit requests==2.32.3 - # via safety + # via + # cachecontrol + # pip-audit rich==13.7.1 # via # bandit - # safety - # typer -ruamel-yaml==0.18.6 - # via - # safety - # safety-schemas -ruamel-yaml-clib==0.2.8 - # via ruamel-yaml -safety==3.2.3 - # via -r requirements_dev.in -safety-schemas==0.0.2 - # via safety -shellingham==1.5.4 - # via typer + # pip-audit +six==1.16.0 + # via html5lib +sortedcontainers==2.4.0 + # via cyclonedx-python-lib stevedore==5.2.0 # via bandit -typer==0.12.3 - # via safety +toml==0.10.2 + # via pip-audit typing-extensions==4.12.2 - # via - # pydantic - # pydantic-core - # safety - # safety-schemas - # typer + # via mypy urllib3==2.2.2 - # via - # requests - # safety + # via requests +webencodings==0.5.1 + # via html5lib wheel==0.43.0 # via pip-tools From b06de8b26afc9fbbbea3ed33cbc108da9c7f1900 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Wed, 31 Jul 2024 09:54:13 +0100 Subject: [PATCH 56/66] formatting --- app/__init__.py | 17 +++++++++--- app/demos/custom_validators.py | 8 +++++- app/demos/forms.py | 48 +++++++++++++++++++++++++++------- app/demos/routes.py | 6 ++++- app/main/routes.py | 6 ++++- 5 files changed, 70 insertions(+), 15 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 96ccf98..7d75258 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -11,7 +11,10 @@ assets = Environment() csrf = CSRFProtect() -limiter = Limiter(get_remote_address, default_limits=["2 per second", "60 per minute"]) +limiter = Limiter( + get_remote_address, + default_limits=["2 per second", "60 per minute"], +) def create_app(config_class=Config): @@ -39,8 +42,16 @@ def create_app(config_class=Config): WTFormsHelpers(app) # Create static asset bundles - css = Bundle("src/css/*.css", filters="cssmin", output="dist/css/custom-%(version)s.min.css") - js = Bundle("src/js/*.js", filters="jsmin", output="dist/js/custom-%(version)s.min.js") + css = Bundle( + "src/css/*.css", + filters="cssmin", + output="dist/css/custom-%(version)s.min.css", + ) + js = Bundle( + "src/js/*.js", + filters="jsmin", + output="dist/js/custom-%(version)s.min.js", + ) if "css" not in assets: assets.register("css", css) if "js" not in assets: diff --git a/app/demos/custom_validators.py b/app/demos/custom_validators.py index c05667e..08390b1 100644 --- a/app/demos/custom_validators.py +++ b/app/demos/custom_validators.py @@ -2,7 +2,13 @@ class RequiredIf(InputRequired): - def __init__(self, other_field_name, other_field_value, *args, **kwargs): + def __init__( + self, + other_field_name, + other_field_value, + *args, + **kwargs, + ): self.other_field_name = other_field_name self.other_field_value = other_field_value diff --git a/app/demos/forms.py b/app/demos/forms.py index 0594fc7..9448ff5 100644 --- a/app/demos/forms.py +++ b/app/demos/forms.py @@ -45,7 +45,10 @@ class BankDetailsForm(FlaskForm): widget=GovTextInput(), validators=[ InputRequired(message="Enter a sort code"), - Regexp(regex=r"\d{6}", message="Enter a valid sort code like 309430"), + Regexp( + regex=r"\d{6}", + message="Enter a valid sort code like 309430", + ), ], description="Must be 6 digits long", ) @@ -54,8 +57,15 @@ class BankDetailsForm(FlaskForm): widget=GovTextInput(), validators=[ InputRequired(message="Enter an account number"), - Regexp(regex=r"\d{6,8}", message="Enter a valid account number like 00733445"), - Length(min=6, max=8, message="Account number must be between 6 and 8 digits"), + Regexp( + regex=r"\d{6,8}", + message="Enter a valid account number like 00733445", + ), + Length( + min=6, + max=8, + message="Account number must be between 6 and 8 digits", + ), ], description="Must be between 6 and 8 digits long", ) @@ -118,7 +128,10 @@ class CreateAccountForm(FlaskForm): widget=GovTextInput(), validators=[ InputRequired(message="Enter an email address"), - Length(max=256, message="Email address must be 256 characters or fewer"), + Length( + max=256, + message="Email address must be 256 characters or fewer", + ), Email(message="Enter an email address in the correct format, like name@example.com"), ], description="You'll need this email address to sign in to your account", @@ -139,7 +152,10 @@ class CreateAccountForm(FlaskForm): widget=GovPasswordInput(), validators=[ InputRequired(message="Enter a password"), - Length(min=8, message="Password must be at least 8 characters"), + Length( + min=8, + message="Password must be at least 8 characters", + ), ], description="Must be at least 8 characters", ) @@ -170,7 +186,10 @@ class KitchenSinkForm(FlaskForm): email_field = StringField( "EmailField", widget=GovTextInput(), - validators=[InputRequired(message="EmailField is required"), Email()], + validators=[ + InputRequired(message="EmailField is required"), + Email(), + ], description="StringField rendered using a GovTextInput widget.", ) @@ -207,7 +226,10 @@ class KitchenSinkForm(FlaskForm): widget=GovCharacterCount(), validators=[ InputRequired(message="CharacterCountField is required"), - Length(max=200, message="CharacterCountField must be 200 characters or fewer "), + Length( + max=200, + message="CharacterCountField must be 200 characters or fewer ", + ), ], description="TextAreaField rendered using a GovCharacterCount widget.", ) @@ -237,7 +259,11 @@ class KitchenSinkForm(FlaskForm): "SelectMultipleField", widget=GovCheckboxesInput(), validators=[InputRequired(message="Please select an option")], - choices=[("one", "One"), ("two", "Two"), ("three", "Three")], + choices=[ + ("one", "One"), + ("two", "Two"), + ("three", "Three"), + ], description="SelectMultipleField rendered using a GovCheckboxesInput widget.", ) @@ -245,7 +271,11 @@ class KitchenSinkForm(FlaskForm): "RadioField", widget=GovRadioInput(), validators=[InputRequired(message="Please select an option")], - choices=[("one", "One"), ("two", "Two"), ("three", "Three")], + choices=[ + ("one", "One"), + ("two", "Two"), + ("three", "Three"), + ], description="RadioField rendered using a GovRadioInput widget.", ) diff --git a/app/demos/routes.py b/app/demos/routes.py index 474658e..8ff30a6 100644 --- a/app/demos/routes.py +++ b/app/demos/routes.py @@ -24,7 +24,11 @@ def component(component): except FileNotFoundError: raise NotFound - return render_template("component.html", component=component, fixtures=fixtures) + return render_template( + "component.html", + component=component, + fixtures=fixtures, + ) @bp.route("/forms", methods=["GET"]) diff --git a/app/main/routes.py b/app/main/routes.py index 70bee6d..ccf9f40 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -34,7 +34,11 @@ def cookies(): response = make_response(render_template("cookies.html", form=form)) # Set cookies policy for one year - response.set_cookie("cookies_policy", json.dumps(cookies_policy), max_age=31557600) + response.set_cookie( + "cookies_policy", + json.dumps(cookies_policy), + max_age=31557600, + ) return response elif request.method == "GET": if request.cookies.get("cookies_policy"): From 5aa793aba5d1c3265b0771f03e1474d65675d44b Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Thu, 29 Aug 2024 15:59:41 +0100 Subject: [PATCH 57/66] govuk-frontend v5.6.0 --- README.md | 2 +- app/templates/base.html | 10 +++++----- app/templates/demos/component.html | 3 +++ build.sh | 6 +++--- requirements.txt | 14 ++++++------- requirements_dev.txt | 32 +++++++++++++++--------------- runtime.txt | 2 +- 7 files changed, 36 insertions(+), 33 deletions(-) diff --git a/README.md b/README.md index e81ee11..b3f9a57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GOV.UK Frontend Flask -![govuk-frontend 5.4.0](https://img.shields.io/badge/govuk--frontend%20version-5.4.0-005EA5?logo=gov.uk&style=flat) +![govuk-frontend 5.6.0](https://img.shields.io/badge/govuk--frontend%20version-5.6.0-005EA5?logo=gov.uk&style=flat) **GOV.UK Frontend Flask is a [community tool](https://design-system.service.gov.uk/community/resources-and-tools/) of the [GOV.UK Design System](https://design-system.service.gov.uk/). The Design System team is not responsible for it and cannot support you with using it. Contact the [maintainers](#contributors) directly if you need [help](#support) or you want to request a feature.** diff --git a/app/templates/base.html b/app/templates/base.html index 2a94022..071e1d3 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -13,7 +13,7 @@ - + {% assets "css" %}{% endassets %} {% endblock %} @@ -33,7 +33,7 @@ {% endset %} {{ govukCookieBanner({ - 'ariaLabel': "Cookies on " + config['SERVICE_NAME'], + 'ariaLabel': "Cookies on " ~ config['SERVICE_NAME'], 'attributes': { 'id': "cookie-banner" }, @@ -42,7 +42,7 @@ 'attributes': { 'id': "default-message" }, - 'headingText': "Cookies on " + config['SERVICE_NAME'], + 'headingText': "Cookies on " ~ config['SERVICE_NAME'], 'html': html, 'actions': [ { @@ -161,9 +161,9 @@ {% endblock %} {% block bodyEnd %} - + {% assets "js" %}{% endassets %} diff --git a/app/templates/demos/component.html b/app/templates/demos/component.html index 6879a47..2e982b9 100644 --- a/app/templates/demos/component.html +++ b/app/templates/demos/component.html @@ -27,6 +27,7 @@ {%- from 'govuk_frontend_jinja/components/phase-banner/macro.html' import govukPhaseBanner -%} {%- from 'govuk_frontend_jinja/components/radios/macro.html' import govukRadios -%} {%- from 'govuk_frontend_jinja/components/select/macro.html' import govukSelect -%} +{%- from 'govuk_frontend_jinja/components/service-navigation/macro.html' import govukServiceNavigation -%} {%- from 'govuk_frontend_jinja/components/skip-link/macro.html' import govukSkipLink -%} {%- from 'govuk_frontend_jinja/components/summary-list/macro.html' import govukSummaryList -%} {%- from 'govuk_frontend_jinja/components/table/macro.html' import govukTable -%} @@ -133,6 +134,8 @@

                      {{fixture. {{ govukRadios(fixture.options)}} {% elif component == 'select' %} {{ govukSelect(fixture.options)}} + {% elif component == 'service-navigation' %} + {{ govukServiceNavigation(fixture.options)}} {% elif component == 'skip-link' %} {{ govukSkipLink(fixture.options)}} {% elif component == 'summary-list' %} diff --git a/build.sh b/build.sh index b97beac..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 @@ -21,10 +21,10 @@ rm -rf govuk_frontend.zip 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 app/demos/govuk_components -mv govuk_frontend_source/govuk-frontend-5.4.0/packages/govuk-frontend/src/govuk/components/** 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 app/demos/govuk_components -type f ! -name '*.yaml' -delete diff --git a/requirements.txt b/requirements.txt index 724d6b5..e7aaeed 100644 --- a/requirements.txt +++ b/requirements.txt @@ -31,17 +31,17 @@ 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 @@ -73,11 +73,11 @@ packaging==24.1 # limits pygments==2.18.0 # via rich -pyyaml==6.0.1 +pyyaml==6.0.2 # via -r requirements.in redis==5.0.8 # via limits -rich==13.7.1 +rich==13.8.0 # via flask-limiter typing-extensions==4.12.2 # via @@ -85,7 +85,7 @@ typing-extensions==4.12.2 # limits webassets==2.0 # via flask-assets -werkzeug==3.0.3 +werkzeug==3.0.4 # via flask wrapt==1.16.0 # via deprecated diff --git a/requirements_dev.txt b/requirements_dev.txt index 9a8a98e..3328925 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -4,11 +4,11 @@ # # pip-compile requirements_dev.in # -attrs==23.2.0 +attrs==24.2.0 # via flake8-bugbear 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 @@ -27,29 +27,29 @@ click==8.1.7 # black # pip-tools # pur -coverage[toml]==7.6.0 +coverage[toml]==7.6.1 # via pytest-cov -cyclonedx-python-lib==7.5.1 +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.0 +flake8==7.1.1 # via # flake8-bugbear # pep8-naming -flake8-bugbear==24.4.26 +flake8-bugbear==24.8.19 # via -r requirements_dev.in html5lib==1.1 # via pip-audit -idna==3.7 +idna==3.8 # via requests iniconfig==2.0.0 # via pytest isort==5.13.2 # via -r requirements_dev.in -license-expression==30.3.0 +license-expression==30.3.1 # via cyclonedx-python-lib markdown-it-py==3.0.0 # via rich @@ -59,7 +59,7 @@ mdurl==0.1.2 # via markdown-it-py msgpack==1.0.8 # via cachecontrol -mypy==1.11.1 +mypy==1.11.2 # via -r requirements_dev.in mypy-extensions==1.0.0 # via @@ -76,7 +76,7 @@ packaging==24.1 # pytest 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 @@ -96,13 +96,13 @@ pur==7.3.2 # via -r requirements_dev.in py-serializable==1.1.0 # via cyclonedx-python-lib -pycodestyle==2.12.0 +pycodestyle==2.12.1 # via flake8 pyflakes==3.2.0 # via flake8 pygments==2.18.0 # via rich -pyparsing==3.1.2 +pyparsing==3.1.4 # via pip-requirements-parser pyproject-hooks==1.1.0 # via @@ -112,13 +112,13 @@ 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 # cachecontrol # pip-audit -rich==13.7.1 +rich==13.8.0 # via # bandit # pip-audit @@ -126,7 +126,7 @@ six==1.16.0 # via html5lib sortedcontainers==2.4.0 # via cyclonedx-python-lib -stevedore==5.2.0 +stevedore==5.3.0 # via bandit toml==0.10.2 # via pip-audit @@ -136,7 +136,7 @@ urllib3==2.2.2 # via requests webencodings==0.5.1 # via html5lib -wheel==0.43.0 +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 From f5731a76536064e288c7afb109328b8c78e19ab1 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 09:03:18 +0100 Subject: [PATCH 58/66] upgrade requirements --- requirements_dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements_dev.txt b/requirements_dev.txt index 3328925..362865a 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -18,7 +18,7 @@ cachecontrol[filecache]==0.14.0 # via # cachecontrol # pip-audit -certifi==2024.7.4 +certifi==2024.8.30 # via requests charset-normalizer==3.3.2 # via requests From a18106bb90638c1eeffa5c05dddbccc8a277a690 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 09:03:27 +0100 Subject: [PATCH 59/66] upgrade requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index e7aaeed..5d1cfee 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,7 +10,7 @@ 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 From 495deed7eb347305a2e86e72892185ca6d728618 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 12:21:57 +0100 Subject: [PATCH 60/66] change OU --- nginx/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nginx/Dockerfile b/nginx/Dockerfile index ae604bf..b7c491a 100644 --- a/nginx/Dockerfile +++ b/nginx/Dockerfile @@ -2,6 +2,6 @@ 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=DDaT/CN=localhost" + 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 From 716c5b64b8aebe251531ff9b550f244342c6d763 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 12:22:14 +0100 Subject: [PATCH 61/66] update script hash --- nginx/nginx.conf | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/nginx/nginx.conf b/nginx/nginx.conf index 91eab39..cb07a61 100644 --- a/nginx/nginx.conf +++ b/nginx/nginx.conf @@ -1,5 +1,5 @@ -# generated 2024-06-13, Mozilla Guideline v5.7, nginx 1.26.1, OpenSSL 3.0.11, modern configuration, no OCSP -# https://ssl-config.mozilla.org/#server=nginx&version=1.26.1&config=modern&openssl=3.0.11&ocsp=false&guideline=5.7 +# 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; @@ -25,7 +25,7 @@ server { ssl_prefer_server_ciphers off; # add security headers - add_header Content-Security-Policy "script-src 'self' 'sha256-GUQ5ad8JK5KmEWmROf3LZd9ge94daqNvd8xy9YS1iDw=' 'sha256-xvC5hOpINthj2xzP7qkRGmqR3SpU8ZVw1sEMKbsOS/4='; object-src 'none'; base-uri 'none';" always; + 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; From 8c2f0b050d7dc35facb81a670ef81fae6ec1571a Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 13:10:39 +0100 Subject: [PATCH 62/66] set https only cookie --- app/main/routes.py | 1 + 1 file changed, 1 insertion(+) diff --git a/app/main/routes.py b/app/main/routes.py index ccf9f40..5aa173a 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -38,6 +38,7 @@ def cookies(): "cookies_policy", json.dumps(cookies_policy), max_age=31557600, + secure=True, ) return response elif request.method == "GET": From 0ef69a42262a6064c5129351c0398faa12cdcf5b Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 3 Sep 2024 16:04:15 +0100 Subject: [PATCH 63/66] follow service navigation patterns --- app/templates/base.html | 12 +++++++++-- app/templates/demos/autocomplete.html | 22 --------------------- app/templates/demos/bank_details.html | 22 --------------------- app/templates/demos/component.html | 20 ------------------- app/templates/demos/components.html | 18 ----------------- app/templates/demos/conditional_reveal.html | 22 --------------------- app/templates/demos/create_account.html | 22 --------------------- app/templates/demos/forms.html | 19 ------------------ app/templates/demos/kitchen_sink.html | 22 --------------------- app/templates/main/index.html | 6 ------ 10 files changed, 10 insertions(+), 175 deletions(-) diff --git a/app/templates/base.html b/app/templates/base.html index 071e1d3..08868ff 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -4,6 +4,7 @@ {%- from 'govuk_frontend_jinja/components/error-summary/macro.html' import govukErrorSummary-%} {%- from 'govuk_frontend_jinja/components/notification-banner/macro.html' import govukNotificationBanner -%} {%- from 'govuk_frontend_jinja/components/phase-banner/macro.html' import govukPhaseBanner -%} +{%- from 'govuk_frontend_jinja/components/service-navigation/macro.html' import govukServiceNavigation -%} {% set assetPath = url_for('static', filename='').rstrip('/') %} @@ -108,10 +109,17 @@ {% block header %} {{ govukHeader({ - 'homepageUrl': url_for('main.index'), + 'classes': 'govuk-header--full-width-border', + 'homepageUrl': 'https://www.gov.uk/' + }) }} + + {{ govukServiceNavigation({ 'serviceName': config['SERVICE_NAME'], 'serviceUrl': url_for('main.index'), - 'useTudorCrown': true + 'navigation': [ + {'href': url_for('demos.components'), 'text': 'Components'}, + {'href': url_for('demos.forms'), 'text': 'Forms'} + ] }) }} {% endblock %} diff --git a/app/templates/demos/autocomplete.html b/app/templates/demos/autocomplete.html index daf2927..5856de2 100644 --- a/app/templates/demos/autocomplete.html +++ b/app/templates/demos/autocomplete.html @@ -1,29 +1,7 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - {% block pageTitle %}{%- if form.errors %}Error: {% endif -%}Autocomplete – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms", - 'href': url_for('demos.forms') - }, - { - 'text': "Autocomplete" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/bank_details.html b/app/templates/demos/bank_details.html index d47562a..ded06a4 100644 --- a/app/templates/demos/bank_details.html +++ b/app/templates/demos/bank_details.html @@ -1,29 +1,7 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - {% block pageTitle %}{%- if form.errors %}Error: {% endif -%}Bank details – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms", - 'href': url_for('demos.forms') - }, - { - 'text': "Bank details" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/component.html b/app/templates/demos/component.html index 2e982b9..6782922 100644 --- a/app/templates/demos/component.html +++ b/app/templates/demos/component.html @@ -39,26 +39,6 @@ {% block pageTitle %}{{component | replace("-", " ") | capitalize}} – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Components", - 'href': url_for('demos.components') - }, - { - 'text': component | replace("-", " ") | capitalize - } - ] - }) }} -{% endblock %} - {% block content %} Components

                      {{component | replace("-", " ") | capitalize}}

                      diff --git a/app/templates/demos/components.html b/app/templates/demos/components.html index 8b8a8e0..0b879bd 100644 --- a/app/templates/demos/components.html +++ b/app/templates/demos/components.html @@ -1,23 +1,5 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Components" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/conditional_reveal.html b/app/templates/demos/conditional_reveal.html index 1c5779c..d4cff36 100644 --- a/app/templates/demos/conditional_reveal.html +++ b/app/templates/demos/conditional_reveal.html @@ -1,29 +1,7 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - {% block pageTitle %}{%- if form.errors %}Error: {% endif -%}Conditionally revealed inputs – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms", - 'href': url_for('demos.forms') - }, - { - 'text': "Conditional reveal" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/create_account.html b/app/templates/demos/create_account.html index 8c530f0..70aa93a 100644 --- a/app/templates/demos/create_account.html +++ b/app/templates/demos/create_account.html @@ -1,29 +1,7 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - {% block pageTitle %}{%- if form.errors %}Error: {% endif -%}Create an account – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms", - 'href': url_for('demos.forms') - }, - { - 'text': "Create account" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/forms.html b/app/templates/demos/forms.html index 62d4ec9..f1f9ae6 100644 --- a/app/templates/demos/forms.html +++ b/app/templates/demos/forms.html @@ -1,24 +1,5 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - -{% block beforeContent %} - {{ super() }} - - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/demos/kitchen_sink.html b/app/templates/demos/kitchen_sink.html index 6e18367..91c231e 100644 --- a/app/templates/demos/kitchen_sink.html +++ b/app/templates/demos/kitchen_sink.html @@ -1,29 +1,7 @@ {% extends "base.html" %} -{%- from 'govuk_frontend_jinja/components/breadcrumbs/macro.html' import govukBreadcrumbs -%} - {% block pageTitle %}{%- if form.errors %}Error: {% endif -%}Kitchen sink – GOV.UK{% endblock %} -{% block beforeContent %} - {{ super() }} - {{ govukBreadcrumbs({ - 'collapseOnMobile': True, - 'items': [ - { - 'text': "Home", - 'href': url_for('main.index') - }, - { - 'text': "Forms", - 'href': url_for('demos.forms') - }, - { - 'text': "Kitchen sink" - } - ] - }) }} -{% endblock %} - {% block content %}
                      diff --git a/app/templates/main/index.html b/app/templates/main/index.html index dc6483c..9dc7d5e 100644 --- a/app/templates/main/index.html +++ b/app/templates/main/index.html @@ -39,12 +39,6 @@

                      Features

                    • Rate limiting
                    -

                    Demos

                    - -

                    Documentation

                    Detailed documentation on the features listed above and the next steps to start building out your app on top of this template is on GitHub

                    From b0e632c168d595ba456ec5e6a46b6700747e0719 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 15 Oct 2024 15:43:29 +0100 Subject: [PATCH 64/66] govuk 5.7.1 --- README.md | 6 +++--- app/templates/base.html | 6 +++--- build.sh | 6 +++--- requirements.txt | 16 ++++++++-------- requirements_dev.txt | 34 +++++++++++++++++----------------- 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index b3f9a57..301b8ca 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GOV.UK Frontend Flask -![govuk-frontend 5.6.0](https://img.shields.io/badge/govuk--frontend%20version-5.6.0-005EA5?logo=gov.uk&style=flat) +![govuk-frontend 5.7.1](https://img.shields.io/badge/govuk--frontend%20version-5.7.1-005EA5?logo=gov.uk&style=flat) **GOV.UK Frontend Flask is a [community tool](https://design-system.service.gov.uk/community/resources-and-tools/) of the [GOV.UK Design System](https://design-system.service.gov.uk/). The Design System team is not responsible for it and cannot support you with using it. Contact the [maintainers](#contributors) directly if you need [help](#support) or you want to request a feature.** @@ -35,7 +35,7 @@ In the `compose.yml` file you will find a number of environment variables. These - SERVICE_PHASE - SERVICE_URL -You must also set a new unique `SECRET_KEY`, which is used to securely sign the session cookie and CSRF tokens. It should be a long random `bytes` or `str`. You can use the output of this Python comand to generate a new key: +You must also set a new unique `SECRET_KEY`, which is used to securely sign the session cookie and CSRF tokens. It should be a long random `bytes` or `str`. You can use the output of this Python command to generate a new key: ```shell python -c 'import secrets; print(secrets.token_hex())' @@ -108,7 +108,7 @@ Please refer to the specific packages documentation for more details. Custom CSS and JavaScript files are merged and minified using [Flask Assets](https://flask-assets.readthedocs.io/en/latest/) and [Webassets](https://webassets.readthedocs.io/en/latest/). This takes all `*.css` files in `app/static/src/css` and all `*.js` files in `app/static/src/js` and outputs a single minified file to both `app/static/dist/css` and `app/static/dist/js` respectively. -CSS is [minified](https://en.wikipedia.org/wiki/Minification_(programming)) using [CSSMin](https://github.com/zacharyvoase/cssmin) and JavaScript is minified using [JSMin](https://github.com/tikitu/jsmin/). This removes all whitespace characters, comments and line breaks to reduce the size of the source code, making its transmission over a network more efficient. +CSS is [minified]() using [CSSMin](https://github.com/zacharyvoase/cssmin) and JavaScript is minified using [JSMin](https://github.com/tikitu/jsmin/). This removes all whitespace characters, comments and line breaks to reduce the size of the source code, making its transmission over a network more efficient. ### Cache busting diff --git a/app/templates/base.html b/app/templates/base.html index 08868ff..5c567d8 100644 --- a/app/templates/base.html +++ b/app/templates/base.html @@ -14,7 +14,7 @@ - + {% assets "css" %}{% endassets %} {% endblock %} @@ -169,9 +169,9 @@ {% endblock %} {% block bodyEnd %} - + {% assets "js" %}{% endassets %} diff --git a/build.sh b/build.sh index 447e178..86d87da 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.6.0/release-v5.6.0.zip > govuk_frontend.zip +curl -L https://github.com/alphagov/govuk-frontend/releases/download/v5.7.1/release-v5.7.1.zip > govuk_frontend.zip unzip -o govuk_frontend.zip -d app/static mv app/static/assets/* app/static @@ -21,10 +21,10 @@ rm -rf govuk_frontend.zip 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.6.0.zip > govuk_frontend_source.zip +curl -L https://github.com/alphagov/govuk-frontend/archive/refs/tags/v5.7.1.zip > govuk_frontend_source.zip unzip -o govuk_frontend_source.zip -d govuk_frontend_source mkdir app/demos/govuk_components -mv govuk_frontend_source/govuk-frontend-5.6.0/packages/govuk-frontend/src/govuk/components/** app/demos/govuk_components +mv govuk_frontend_source/govuk-frontend-5.7.1/packages/govuk-frontend/src/govuk/components/** app/demos/govuk_components # Remove all files apart from test fixtures find app/demos/govuk_components -type f ! -name '*.yaml' -delete diff --git a/requirements.txt b/requirements.txt index 5d1cfee..e6a497b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,7 +14,7 @@ deepmerge==2.0 # via govuk-frontend-wtf deprecated==1.2.14 # via limits -dnspython==2.6.1 +dnspython==2.7.0 # via email-validator email-validator==2.2.0 # via -r requirements.in @@ -31,17 +31,17 @@ flask-limiter[redis]==3.8.0 # via -r requirements.in flask-wtf==1.2.1 # via govuk-frontend-wtf -govuk-frontend-jinja==3.3.0 +govuk-frontend-jinja==3.4.0 # via # -r requirements.in # govuk-frontend-wtf -govuk-frontend-wtf==3.1.0 +govuk-frontend-wtf==3.2.0 # via -r requirements.in gunicorn==23.0.0 # via -r requirements.in -idna==3.8 +idna==3.10 # via email-validator -importlib-resources==6.4.4 +importlib-resources==6.4.5 # via limits itsdangerous==2.2.0 # via @@ -58,7 +58,7 @@ limits[redis]==3.13.0 # via flask-limiter markdown-it-py==3.0.0 # via rich -markupsafe==2.1.5 +markupsafe==3.0.1 # via # jinja2 # werkzeug @@ -75,9 +75,9 @@ pygments==2.18.0 # via rich pyyaml==6.0.2 # via -r requirements.in -redis==5.0.8 +redis==5.1.1 # via limits -rich==13.8.0 +rich==13.9.2 # via flask-limiter typing-extensions==4.12.2 # via diff --git a/requirements_dev.txt b/requirements_dev.txt index 362865a..4f5a038 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -6,13 +6,13 @@ # attrs==24.2.0 # via flake8-bugbear -bandit==1.7.9 +bandit==1.7.10 # via -r requirements_dev.in -black==24.8.0 +black==24.10.0 # via -r requirements_dev.in boolean-py==4.0 # via license-expression -build==1.2.1 +build==1.2.2.post1 # via pip-tools cachecontrol[filecache]==0.14.0 # via @@ -20,20 +20,20 @@ cachecontrol[filecache]==0.14.0 # pip-audit certifi==2024.8.30 # via requests -charset-normalizer==3.3.2 +charset-normalizer==3.4.0 # via requests click==8.1.7 # via # black # pip-tools # pur -coverage[toml]==7.6.1 +coverage[toml]==7.6.3 # via pytest-cov -cyclonedx-python-lib==7.6.0 +cyclonedx-python-lib==7.6.2 # via pip-audit defusedxml==0.7.1 # via py-serializable -filelock==3.15.4 +filelock==3.16.1 # via cachecontrol flake8==7.1.1 # via @@ -43,7 +43,7 @@ flake8-bugbear==24.8.19 # via -r requirements_dev.in html5lib==1.1 # via pip-audit -idna==3.8 +idna==3.10 # via requests iniconfig==2.0.0 # via pytest @@ -57,9 +57,9 @@ mccabe==0.7.0 # via flake8 mdurl==0.1.2 # via markdown-it-py -msgpack==1.0.8 +msgpack==1.1.0 # via cachecontrol -mypy==1.11.2 +mypy==1.12.0 # via -r requirements_dev.in mypy-extensions==1.0.0 # via @@ -88,13 +88,13 @@ pip-requirements-parser==32.0.1 # via pip-audit pip-tools==7.4.1 # via -r requirements_dev.in -platformdirs==4.2.2 +platformdirs==4.3.6 # via black pluggy==1.5.0 # via pytest pur==7.3.2 # via -r requirements_dev.in -py-serializable==1.1.0 +py-serializable==1.1.2 # via cyclonedx-python-lib pycodestyle==2.12.1 # via flake8 @@ -102,13 +102,13 @@ pyflakes==3.2.0 # via flake8 pygments==2.18.0 # via rich -pyparsing==3.1.4 +pyparsing==3.2.0 # via pip-requirements-parser -pyproject-hooks==1.1.0 +pyproject-hooks==1.2.0 # via # build # pip-tools -pytest==8.3.2 +pytest==8.3.3 # via pytest-cov pytest-cov==5.0.0 # via -r requirements_dev.in @@ -118,7 +118,7 @@ requests==2.32.3 # via # cachecontrol # pip-audit -rich==13.8.0 +rich==13.9.2 # via # bandit # pip-audit @@ -132,7 +132,7 @@ toml==0.10.2 # via pip-audit typing-extensions==4.12.2 # via mypy -urllib3==2.2.2 +urllib3==2.2.3 # via requests webencodings==0.5.1 # via html5lib From 9bbec370ab977b7c429b85a5addad16d511dd833 Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 15 Oct 2024 15:43:35 +0100 Subject: [PATCH 65/66] yaml formatting --- .github/workflows/codeql.yml | 63 ++++++++++++------------- .github/workflows/dependency-review.yml | 8 ++-- .github/workflows/python-app.yml | 4 +- 3 files changed, 37 insertions(+), 38 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 7c62a41..0fc8096 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,12 +13,12 @@ name: "CodeQL" on: push: - branches: [ "main", "develop" ] + branches: ["main", "develop"] pull_request: # The branches below must be a subset of the branches above - branches: [ "main" ] + branches: ["main"] schedule: - - cron: '40 16 * * 6' + - cron: "40 16 * * 6" jobs: analyze: @@ -38,45 +38,44 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript-typescript', 'python' ] + language: ["javascript-typescript", "python"] # CodeQL supports [ 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'swift' ] # Use only 'java-kotlin' to analyze code written in Java, Kotlin or both # Use only 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support steps: - - name: Checkout repository - uses: actions/checkout@v4 + - name: Checkout repository + uses: actions/checkout@v4 - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@v2 - with: - languages: ${{ matrix.language }} - # If you wish to specify custom queries, you can do so here or in a config file. - # By default, queries listed here will override any specified in a config file. - # Prefix the list here with "+" to use these queries and those in the config file. + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. - # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs - # queries: security-extended,security-and-quality + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 - # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). - # If this step fails, then you should remove it and run the build manually (see below) - - name: Autobuild - uses: github/codeql-action/autobuild@v2 + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun - # ℹ️ Command-line programs to run using the OS shell. - # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. - # If the Autobuild fails above, remove it and uncomment the following three lines. - # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh - # - run: | - # echo "Run, Build Application using script" - # ./location_of_script_within_repo/buildscript.sh - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 - with: - category: "/language:${{matrix.language}}" + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index d19e21b..e71af6d 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -7,10 +7,10 @@ # # Source repository: https://github.com/actions/dependency-review-action # Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement -name: 'Dependency review' +name: "Dependency review" on: pull_request: - branches: [ "main" ] + branches: ["main"] # If using a dependency submission action in this workflow this permission will need to be set to: # @@ -27,9 +27,9 @@ jobs: dependency-review: runs-on: ubuntu-latest steps: - - name: 'Checkout repository' + - name: "Checkout repository" uses: actions/checkout@v4 - - name: 'Dependency Review' + - name: "Dependency Review" uses: actions/dependency-review-action@v4 # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. with: diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index 9ef8f51..58fe45a 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -5,9 +5,9 @@ name: Python application on: push: - branches: [ "main" ] + branches: ["main"] pull_request: - branches: [ "main" ] + branches: ["main"] permissions: contents: read From 2d4a9f07291a1ff9979b3fb5e4c5d8567e30daea Mon Sep 17 00:00:00 2001 From: Matt Shaw Date: Tue, 15 Oct 2024 15:53:10 +0100 Subject: [PATCH 66/66] deprecated runtime file --- runtime.txt | 1 - 1 file changed, 1 deletion(-) delete mode 100644 runtime.txt diff --git a/runtime.txt b/runtime.txt deleted file mode 100644 index 14a2d25..0000000 --- a/runtime.txt +++ /dev/null @@ -1 +0,0 @@ -python-3.12.5 \ No newline at end of file