diff --git a/.flaskenv b/.flaskenv deleted file mode 100644 index 3ac4157..0000000 --- a/.flaskenv +++ /dev/null @@ -1,12 +0,0 @@ -CONTACT_EMAIL=[contact email] -CONTACT_PHONE=[contact phone] -DEPARTMENT_NAME=[name of department] -DEPARTMENT_URL=[url of department] -FLASK_APP=govuk-frontend-flask.py -FLASK_DEBUG=True -FLASK_RUN_PORT=5000 -REDIS_URL=memory:// -SECRET_KEY=3e48b821144547db5f22c7357431a093489450fcc4aad992ab9e1dd1a9d3b40d -SERVICE_NAME=[name of service] -SERVICE_PHASE=[phase] -SERVICE_URL=[url of service] \ No newline at end of file diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 93941b8..04d6ccb 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,17 +13,23 @@ name: "CodeQL" on: push: - branches: [ "main" ] + branches: [ "main", "develop" ] pull_request: # The branches below must be a subset of the branches above branches: [ "main" ] schedule: - - cron: '17 2 * * 4' + - cron: '40 16 * * 6' jobs: analyze: name: Analyze - runs-on: ubuntu-latest + # Runner size impacts CodeQL analysis time. To learn more, please see: + # - https://gh.io/recommended-hardware-resources-for-running-codeql + # - https://gh.io/supported-runners-and-hardware-resources + # - https://gh.io/using-larger-runners + # Consider using larger runners for possible analysis time improvements. + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} permissions: actions: read contents: read @@ -32,8 +38,10 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'javascript', 'python' ] - # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ] + 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: @@ -48,12 +56,12 @@ jobs: # 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. - - # 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 + + # 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#, or Java). + + # 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 @@ -61,12 +69,14 @@ jobs: # ℹī¸ 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. + # 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 + # 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}}" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index fe461b4..b0dedc4 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,4 +17,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@v2 + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml index b45ab00..4c9361d 100644 --- a/.github/workflows/python-app.yml +++ b/.github/workflows/python-app.yml @@ -1,38 +1,44 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + name: Python application on: push: - branches: [main] + branches: [ "main" ] pull_request: - branches: [main] + branches: [ "main" ] + +permissions: + contents: read jobs: build: runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] - steps: - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} + - name: Set up Python 3.11 uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} + python-version: 3.11 - name: Install dependencies run: | python -m pip install --upgrade pip - pip install safety flake8 black bandit + pip install -r requirements_dev.txt pip install -r requirements.txt - name: Check dependencies for known security vulnerabilities run: safety check -r requirements.txt - name: Check code for potential security vulnerabilities - run: bandit -r . + run: bandit -r . -x /tests - name: Check code formatting - run: black . -l 120 --check + run: | + black . -t py311 -l 120 --check + isort . -c - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics # exit-zero treats all errors as warnings. flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics + # - name: Test with pytest + # run: python -m pytest --cov=app --cov-report=term-missing --cov-branch diff --git a/.gitignore b/.gitignore index 10b4b63..6a27a5c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ -# Created by https://www.toptal.com/developers/gitignore/api/windows,macos,osx,linux,jetbrains+all,visualstudio,visualstudiocode,python,flask,git,venv -# Edit at https://www.toptal.com/developers/gitignore?templates=windows,macos,osx,linux,jetbrains+all,visualstudio,visualstudiocode,python,flask,git,venv +# Created by https://www.toptal.com/developers/gitignore/api/git,osx,venv,flask,linux,macos,python,windows,certificates,visualstudio,jetbrains+all,visualstudiocode +# Edit at https://www.toptal.com/developers/gitignore?templates=git,osx,venv,flask,linux,macos,python,windows,certificates,visualstudio,jetbrains+all,visualstudiocode ### GOV.UK Frontend ### app/static/fonts* @@ -8,6 +8,14 @@ app/static/images* app/static/VERSION.txt govuk_components* +### certificates ### +*.pem +*.key +*.crt +*.cer +*.der +*.priv + ### Flask ### instance/* !instance/.gitignore @@ -872,4 +880,4 @@ FodyWeavers.xsd ### VisualStudio Patch ### # Additional files built by Visual Studio -# End of https://www.toptal.com/developers/gitignore/api/windows,macos,osx,linux,jetbrains+all,visualstudio,visualstudiocode,python,flask,git,venv +# End of https://www.toptal.com/developers/gitignore/api/git,osx,venv,flask,linux,macos,python,windows,certificates,visualstudio,jetbrains+all,visualstudiocode diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..d08219e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,21 @@ +FROM python:3.11-slim + +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 + +EXPOSE 8000 +ENTRYPOINT ["./docker-entrypoint.sh"] \ No newline at end of file diff --git a/README.md b/README.md index ca8f3ca..fe50e01 100644 --- a/README.md +++ b/README.md @@ -15,11 +15,7 @@ The app is provided intentionally bare, with just the essential parts that all s ### Required -- Python 3.8.x or higher - -### Optional - -- Redis 4.0.x or higher (for rate limiting, otherwise in-memory storage is used) +- Docker ## Getting started @@ -27,25 +23,9 @@ The app is provided intentionally bare, with just the essential parts that all s [Create a new repository](https://github.com/LandRegistry/govuk-frontend-flask/generate) using this template, with the same directory structure and files. Then clone a local copy of your newly created repository. -### Create venv and install requirements - -```shell -python3 -m venv venv -source venv/bin/activate -pip3 install -r requirements.txt ; pip3 install -r requirements_dev.txt -``` - -### Get GOV.UK Frontend assets - -For convenience a shell script has been provided to download and extract the GOV.UK Frontend distribution assets - -```shell -./build.sh -``` - ### Set local environment variables -In the `.flaskenv` file you will find a number of environment variables. These are injected as global variables into the app and pre-populated into page templates as appropriate. Enter your specific information for the following: +In the `compose.yml` file you will find a number of environment variables. These are injected as global variables into the app and pre-populated into page templates as appropriate. Enter your specific service information for the following: - CONTACT_EMAIL - CONTACT_PHONE @@ -55,13 +35,25 @@ In the `.flaskenv` file you will find a number of environment variables. These a - SERVICE_PHASE - SERVICE_URL -### Run app +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: + +```shell +python -c 'import secrets; print(secrets.token_hex())' +``` + +### Get the latest GOV.UK Frontend assets + +```shell +./build.sh +``` + +### Run containers ```shell -flask run +docker compose up --build ``` -You should now have the app running on +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/app/demos/forms.py b/app/demos/forms.py index 1580eb0..5e77265 100644 --- a/app/demos/forms.py +++ b/app/demos/forms.py @@ -350,20 +350,29 @@ class ConditionalRevealForm(FlaskForm): class AutocompleteForm(FlaskForm): # Manually added list here, but could be dynamically assigned in server route countries = [ + "Argentina", + "Australia", + "Brazil", "Canada", "China", "France", "Germany", "India", + "Indonesia", "Italy", "Japan", + "Mexico", + "Russia", + "Saudi Arabia", + "South Africa", "South Korea", + "Turkey", "United Kingdom", "United States", ] country = StringField( - "Country", + "G20 Countries", widget=GovTextInput(), validators=[InputRequired(message="Enter a country")], description="Start typing and select a suggestion", diff --git a/compose.yml b/compose.yml new file mode 100644 index 0000000..39957dc --- /dev/null +++ b/compose.yml @@ -0,0 +1,28 @@ +services: + web: + container_name: govuk-frontend-flask + build: . + 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 + - SECRET_KEY=4f378500459bb58fecf903ea3c113069f11f150b33388f56fc89f7edce0e6a84 + - SERVICE_NAME=[name of service] + - SERVICE_PHASE=[phase] + - SERVICE_URL=[url of service] + ports: + - "8000:8000" + volumes: + - .:/home/containeruser + depends_on: + - cache + cache: + container_name: redis + image: redis:7.0-alpine + restart: always + ports: + - 6379:6379 + \ No newline at end of file diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh new file mode 100755 index 0000000..5f961b1 --- /dev/null +++ b/docker-entrypoint.sh @@ -0,0 +1,3 @@ +#!/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 :8000 --access-logfile - --error-logfile - govuk-frontend-flask:app \ No newline at end of file diff --git a/requirements.in b/requirements.in index a85de70..15b2d57 100644 --- a/requirements.in +++ b/requirements.in @@ -1,13 +1,11 @@ cssmin email_validator +flask flask-assets flask-compress -flask-limiter +flask-limiter[redis] flask-talisman -flask govuk-frontend-jinja govuk-frontend-wtf gunicorn jsmin -python-dotenv -redis diff --git a/requirements.txt b/requirements.txt index ac85327..7f80f69 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ # # pip-compile requirements.in # -blinker==1.6.2 +blinker==1.6.3 # via flask brotli==1.1.0 # via flask-compress @@ -18,7 +18,7 @@ deprecated==1.2.14 # via limits dnspython==2.4.2 # via email-validator -email-validator==2.0.0.post2 +email-validator==2.1.0.post1 # via -r requirements.in flask==3.0.0 # via @@ -28,11 +28,11 @@ flask==3.0.0 # flask-limiter # flask-wtf # govuk-frontend-wtf -flask-assets==2.0 +flask-assets==2.1.0 # via -r requirements.in flask-compress==1.14 # via -r requirements.in -flask-limiter==3.5.0 +flask-limiter[redis]==3.5.0 # via -r requirements.in flask-talisman==1.1.0 # via -r requirements.in @@ -61,7 +61,7 @@ jinja2==3.1.2 # govuk-frontend-wtf jsmin==3.0.1 # via -r requirements.in -limits==3.6.0 +limits[redis]==3.6.0 # via flask-limiter markdown-it-py==3.0.0 # via rich @@ -80,10 +80,8 @@ packaging==23.2 # limits pygments==2.16.1 # via rich -python-dotenv==1.0.0 - # via -r requirements.in redis==5.0.1 - # via -r requirements.in + # via limits rich==13.6.0 # via flask-limiter typing-extensions==4.8.0 @@ -92,11 +90,11 @@ typing-extensions==4.8.0 # limits webassets==2.0 # via flask-assets -werkzeug==3.0.0 +werkzeug==3.0.1 # via flask wrapt==1.15.0 # via deprecated -wtforms==3.0.1 +wtforms==3.1.0 # via # flask-wtf # govuk-frontend-wtf diff --git a/requirements_dev.txt b/requirements_dev.txt index f030411..a7f7dd1 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -8,13 +8,13 @@ attrs==23.1.0 # via flake8-bugbear bandit==1.7.5 # via -r requirements_dev.in -black==23.9.1 +black==23.10.1 # via -r requirements_dev.in build==1.0.3 # via pip-tools certifi==2023.7.22 # via requests -charset-normalizer==3.3.0 +charset-normalizer==3.3.1 # via requests click==8.1.7 # via @@ -23,7 +23,9 @@ click==8.1.7 # pur # safety coverage[toml]==7.3.2 - # via pytest-cov + # via + # coverage + # pytest-cov dparse==0.6.3 # via safety flake8==6.1.0 @@ -32,9 +34,9 @@ flake8==6.1.0 # pep8-naming flake8-bugbear==23.9.16 # via -r requirements_dev.in -gitdb==4.0.10 +gitdb==4.0.11 # via gitpython -gitpython==3.1.37 +gitpython==3.1.40 # via bandit idna==3.4 # via requests @@ -71,7 +73,7 @@ pluggy==1.3.0 # via pytest pur==7.3.1 # via -r requirements_dev.in -pycodestyle==2.11.0 +pycodestyle==2.11.1 # via flake8 pyflakes==3.1.0 # via flake8 @@ -79,7 +81,7 @@ pygments==2.16.1 # via rich pyproject-hooks==1.0.0 # via build -pytest==7.4.2 +pytest==7.4.3 # via pytest-cov pytest-cov==4.1.0 # via -r requirements_dev.in @@ -89,9 +91,9 @@ requests==2.31.0 # via safety rich==13.6.0 # via bandit -ruamel-yaml==0.17.33 +ruamel-yaml==0.18.2 # via safety -ruamel-yaml-clib==0.2.7 +ruamel-yaml-clib==0.2.8 # via ruamel-yaml safety==2.3.4 # via -r requirements_dev.in @@ -99,7 +101,7 @@ smmap==5.0.1 # via gitdb stevedore==5.1.0 # via bandit -urllib3==2.0.6 +urllib3==2.0.7 # via requests wheel==0.41.2 # via pip-tools