Skip to content

Commit a56735d

Browse files
authored
Merge pull request #92 from NHSDigital/age
Add encrypted secret storage
2 parents b4efd3e + af7a266 commit a56735d

File tree

17 files changed

+207
-98
lines changed

17 files changed

+207
-98
lines changed

.dockerignore

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.git
2+
.github
3+
.version
4+
*.code-workspace
5+
.DS_Store
6+
.env
7+
mise.local.toml
8+
mise.*.local.toml
9+
.venv/
10+
__pycache__/
11+
.ruff_cache/
12+
node_modules/
13+
.idea/
14+
.vscode/
15+
mavis/reporting/static/css/*
16+
!mavis/reporting/static/css/.keep
17+
mavis/reporting/static/js/*
18+
!mavis/reporting/static/js/.keep
19+
mavis/reporting/static/favicons/*
20+
!mavis/reporting/static/favicons/.keep
21+
.coverage
22+
htmlcov/
23+
coverage.svg
24+
coverage.xml
25+
.pytest_cache/
26+
tmp/
27+
!tmp/.keep
28+
config/credentials/*.key
29+
!config/credentials/development.key
30+
*.pyc
31+
*.pyo
32+
*.pyd
33+
*.log
34+
*.swp
35+
*.swo
36+
*~
37+
Dockerfile
38+
.dockerignore
39+
README.md
40+
docs/
41+
tests/

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@ name: CI
22

33
on: [push]
44

5+
env:
6+
MISE_ENV: development
7+
58
jobs:
69
gitleaks:
710
runs-on: ubuntu-latest

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,6 @@ coverage.xml
3232
.pytest_cache
3333
tmp
3434
!tmp/.keep
35+
36+
config/credentials/*.key
37+
!config/credentials/development.key

.gitleaks.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[[allowlists]]
2+
description = "Allow development key"
3+
paths = ["config/credentials/development.key"]

Dockerfile

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,41 @@
1-
FROM python:3.13.7-alpine AS builder
2-
1+
FROM python:3.13.7-alpine AS base
32
WORKDIR /app
43

5-
ADD package.json package-lock.json pyproject.toml uv.lock /app/
4+
ARG MISE_ENV=staging
5+
ENV MISE_ENV=${MISE_ENV} \
6+
MISE_TASK_RUN_AUTO_INSTALL=false \
7+
PATH="/root/.local/share/mise/shims:$PATH"
8+
9+
RUN apk add --no-cache mise sops uv
610

7-
RUN apk add build-base libffi-dev npm bash curl
11+
# Builder image
12+
FROM base AS builder
813

9-
RUN pip install uv
14+
RUN apk add --no-cache build-base libffi-dev bash npm
1015

11-
ADD ./mavis/reporting /app/mavis/reporting
12-
ADD README.md /app/
16+
ADD mise.toml /app/
17+
RUN mise trust
1318

14-
RUN uv sync --frozen
15-
RUN npm install
19+
ADD package.json package-lock.json pyproject.toml uv.lock /app/
20+
ADD mavis/reporting /app/mavis/reporting
21+
RUN mise build
1622

17-
FROM builder
23+
# Runtime image
24+
FROM base
1825

19-
WORKDIR /app
26+
COPY --from=builder /app/.venv /app/.venv
27+
COPY --from=builder /app/mavis /app/mavis
28+
COPY --from=builder /app/pyproject.toml /app/pyproject.toml
2029

21-
RUN mkdir -p mavis/reporting/static/favicons && \
22-
cp -r node_modules/nhsuk-frontend/dist/nhsuk/assets/images/* mavis/reporting/static/favicons/ && \
23-
npm run build:scss && npm run build:js
30+
ADD config/credentials/*.enc.yaml /app/config/credentials/
31+
ADD mise.*.toml /app/
2432

25-
RUN addgroup --gid 1000 app
26-
RUN adduser app -h /app -u 1000 -G app -DH
27-
RUN mkdir -p /app/.cache/uv && chown -R app:app /app/.cache
28-
RUN chown -R app:app /app/.venv
33+
RUN addgroup --gid 1000 app && \
34+
adduser app -h /app -u 1000 -G app -DH && \
35+
chown -R app:app /app
2936

3037
USER 1000
38+
RUN mise trust --all
3139

32-
VOLUME ["/tmp", "/var/tmp", "/usr/tmp", "/app/.cache", "/app/.venv"]
33-
34-
# pass through additional arguments like --workers=5 via GUNICORN_CMD_ARGS
35-
CMD ["uv", "run", "gunicorn", "--bind", "0.0.0.0:5000", "mavis.reporting:create_app()"]
40+
ENV PORT=5000
41+
CMD ["sh", "-c", "mise exec -- uv run --no-sync gunicorn --bind 0.0.0.0:${PORT} 'mavis.reporting:create_app()'"]

README.md

Lines changed: 27 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,8 @@ Please see the main Mavis repository for [how to install
88
mise](https://github.com/nhsuk/manage-vaccinations-in-schools?tab=readme-ov-file#mise).
99

1010
```sh
11-
mise install # Install dev tools
12-
cp mise.local.toml.example mise.local.toml # Fill in shared secrets
13-
mise dev # Run dev server
14-
mise ci # Run CI tests
11+
mise dev --env development # Run dev server
12+
mise ci --env development # Run CI tests
1513
```
1614

1715
The application will be available at <http://localhost:4001>.
@@ -23,8 +21,8 @@ below for details.
2321
## Other tasks
2422

2523
```sh
26-
mise tasks # See all available tasks
27-
mise env # See all available environment variables
24+
mise tasks # See all available tasks
25+
mise env --env development # See env vars and dev secrets
2826
```
2927

3028
### Docker
@@ -36,44 +34,33 @@ environment:
3634
mise docker
3735
```
3836

39-
Different environment variables can be overwritten in `mise.local.toml`.
37+
## Runtime dependencies
4038

41-
### Gunicorn arguments
39+
This application authenticates with the main Mavis application using the [OAuth
40+
2.0 Authorization Code
41+
flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1).
4242

43-
Additional parameters to the `gunicorn` executable (for instance, the number of
44-
workers) can be passed through with the `GUNICORN_CMD_ARGS` environment
45-
variable.
43+
Mavis should already have the development secrets set up in the development
44+
environment. Make sure to turn on the `reporting_api` feature flag.
4645

47-
Example:
46+
## Secrets
4847

49-
```bash
50-
% HOST_PORT=5555 GUNICORN_CMD_ARGS="--workers=5" mise docker:run
51-
docker run --rm -p 5555:5000 -e GUNICORN_CMD_ARGS=--workers=5 mavis-reporting:latest
52-
[2025-07-17 10:32:01 +0000] [1] [INFO] Starting gunicorn 23.0.0
53-
[2025-07-17 10:32:01 +0000] [1] [INFO] Listening at: http://0.0.0.0:5000 (1)
54-
[2025-07-17 10:32:01 +0000] [1] [INFO] Using worker: sync
55-
[2025-07-17 10:32:01 +0000] [10] [INFO] Booting worker with pid: 10
56-
[2025-07-17 10:32:01 +0000] [11] [INFO] Booting worker with pid: 11
57-
[2025-07-17 10:32:01 +0000] [12] [INFO] Booting worker with pid: 12
58-
[2025-07-17 10:32:01 +0000] [13] [INFO] Booting worker with pid: 13
59-
[2025-07-17 10:32:01 +0000] [14] [INFO] Booting worker with pid: 14
60-
```
48+
This project uses encrypted secrets stored in `config/credentials`, using the
49+
[mise secrets](https://mise.jdx.dev/environments/secrets.html) integration with
50+
`age` and `sops`.
6151

62-
## Runtime dependencies
52+
```sh
53+
age-keygen -o config/credentials/staging.key # Generate a new keypair
54+
age-keygen -y config/credentials/staging.key # View the public key
6355

64-
This application authenticates with the main Mavis application using the [OAuth
65-
2.0 Authorization Code
66-
flow](https://datatracker.ietf.org/doc/html/rfc6749#section-4.1).
56+
echo "FOO: bar" > config/credentials/staging.enc.yaml # Create a secret file
57+
sops encrypt -i --age $(age-keygen -y config/credentials/staging.key) \
58+
config/credentials/staging.enc.yaml # Encrypt the file
59+
git add config/credentials/staging.enc.yaml # It's now safe to commit
60+
61+
mise credentials:show --env staging # Show secrets
62+
mise credentials:edit --env staging # Edit secrets
63+
```
6764

68-
To do this, it requires:
69-
70-
1. A copy of the main Mavis app must be running and available at the URL given
71-
in the `MAVIS_ROOT_URL` env var
72-
2. That copy of Mavis must:
73-
- have the `reporting_api` feature flag enabled
74-
- have a value for `Settings.reporting_api.client_app.client_id` (..which can
75-
also be set via the `MAVIS__REPORTING_API__CLIENT_APP__CLIENT_ID`
76-
environment variable) which matches this application's `CLIENT_ID` value
77-
- have a value for `Settings.reporting_api.client_app.secret` (..which can
78-
also be set via the `MAVIS__REPORTING_API__CLIENT_APP__SECRET` environment
79-
variable) which matches this application's `CLIENT_SECRET` value
65+
To view and edit staging/production secrets, you need to obtain the
66+
`config/credentials/staging.key` (or `production.key`) from a colleague.

config/container_variables.yml

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
# This file specifies environment-specific variables that will be added to or override
66
# the environment variables extracted from terraform configuration
77

8-
98
environments:
109
production:
1110

@@ -18,9 +17,6 @@ environments:
1817
training:
1918

2019
sandbox-alpha:
21-
ROOT_URL: https://sandbox-alpha.mavistesting.com/reporting/
22-
MAVIS_ROOT_URL: https://sandbox-alpha.mavistesting.com/
23-
FLASK_ENV: staging
2420

2521
sandbox-beta:
2622

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
SECRET_KEY: ENC[AES256_GCM,data:u6doFxRRBBU=,iv:oTHu76s9l443FqBEQU1UxpY1T4RoiUAnrS+Gd2sG21A=,tag:nWRiLS1cyZVYneTzdhl+kQ==,type:str]
2+
CLIENT_ID: ENC[AES256_GCM,data:IA+KSvbO9cE=,iv:WcnAaROf+PDi7A13ihSLIrd7oUxK/TCR4Gqa+SIj8EQ=,tag:pJ6MzbUqSaoTPdRpmh2vtw==,type:str]
3+
CLIENT_SECRET: ENC[AES256_GCM,data:cFdjBDvOIk4=,iv:kcgs+mwzRJccW1pql9VlKQ4nPqPwdO8tBoc+Mv2M6Wo=,tag:tSWtF1RwSmxdLecm0dFfIA==,type:str]
4+
sops:
5+
age:
6+
- recipient: age1hkeyzs454rgc59xgluayhhqrp3e66zkn02ax7etr9tzlpuh0vqtqnz44h6
7+
enc: |
8+
-----BEGIN AGE ENCRYPTED FILE-----
9+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBCa1pFS0RLUC9tTXV2c3RY
10+
VlJwd3V2dlQxNllJbDBpcEJ3eElLdy9mbndRCjN2eFlyeFFFa3Z5dTIzMVJiV0Ro
11+
YzEvVTFIRWpLK2UvRW9LSkZkekU3SUEKLS0tIGw1eTJpNFZHVmlvVlp4UnQ3Nkw4
12+
L1ozeHFWVjFHS0E4aWlVbTFyWjRQYTQKNxGr3dTtvqozv2relz0ttqxu7apT6Rl2
13+
fRruhGAbWM8JpdwhC6gt4b0k0JLPlKrFtmKe+5fhEgZP2KT8b8wiXg==
14+
-----END AGE ENCRYPTED FILE-----
15+
lastmodified: "2025-09-10T02:02:05Z"
16+
mac: ENC[AES256_GCM,data:bOYhpatQI0/funZdLa35k4BOrH6MbWETeXF2vozwgpgcYaPYzdMGYMV2BtQoDW5Gn7tPWvP/+uYd1cXRiQYp9Tm/hznI2cJzW4rnd/U5mTr7BwUc1V0afbSG32KmOWXGp0aREfCs86IqEYBtdHB9+Wm2Uf/ZzFunegIb0h3L7ww=,iv:/iThyCv6IesKuNI8Dw1ThOOuzCe1FzFFlYCXgylRRYc=,tag:2tdqHRp0wWB+13pYxS4AIQ==,type:str]
17+
unencrypted_suffix: _unencrypted
18+
version: 3.10.2

config/credentials/development.key

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGE-SECRET-KEY-127S3XG77RYGXVZ8GWSH5ULYVPYYQLCX8HJJLCN72GQWDNT4LJ2GS9CEYPT
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
SECRET_KEY: ENC[AES256_GCM,data:kYn6JjDLk/ZC5D7tPmqP/nisCPuJ2xxqplRQ46R/r83UeDtfnaB/9I0mRQ==,iv:JVtmKpBrLbbJPgXcXDHgfe0CLZZ+T3xD7xV6v5OfGh0=,tag:6pE5xnbV32GMegDB0VlfBg==,type:str]
2+
CLIENT_ID: ENC[AES256_GCM,data:RfS0zTSiY3T6ikzWobhfznE1SjubGKXvgtrRazlzc9JsR7FlTmHDxZA=,iv:/U1D/tTT+sEN1v9/cUSzuyF8oRU6vrO5WUCKKBMTW/M=,tag:diFqiYa5Q+DxWigMAk28Pw==,type:str]
3+
CLIENT_SECRET: ENC[AES256_GCM,data:OQjfGTY6144QX0c+v3ZUKQRhazcRre6kC9ollpt9vGA=,iv:WzE2Ubtas6aRB7468N50GIToEvRWpr89cSd8UR1u0dE=,tag:yHTT9FMm0788dyt4eG9YgQ==,type:str]
4+
sops:
5+
age:
6+
- recipient: age1msgf79zw73wv0ljjqw5evjk8hst6y82e5vu6rws8jcntmfk24ulq69erw8
7+
enc: |
8+
-----BEGIN AGE ENCRYPTED FILE-----
9+
YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSBsczJhMnd3U1diUGpiVzFS
10+
RmRYY0M4QlY2akppb2RXUWZZb09FMWZMTDJBCnExbmVUN0s0L2p3allLNk0rUDdM
11+
eUZDQXdKUHd6d0wzSnU2OTdXbitKQncKLS0tIFAyWUQrUzRnRXNsVWhQU2NwWlFk
12+
T1JrUVVCUlVMNjZiM3ZBaG5tNU1hSU0K2vbWc/1yUI+NUI1d4i4vUN/25vwbBKcc
13+
D8e97cMWjV23t7RGXmsMgDtec1AvtQMN80i7d4K1lBYTSjbhB/gO1A==
14+
-----END AGE ENCRYPTED FILE-----
15+
lastmodified: "2025-09-10T12:00:37Z"
16+
mac: ENC[AES256_GCM,data:Tq2X6gSW56EvbWBxeXX82FCELdHOC37HWdBpujKtZBPioLUhcOiGjSBVoQVS+pxkLLAaZ4TUkZXZZ6r5Rmn6wh0be4ke+hy+hE5NlBU9H9e2jRULiafIGINDn6FF6USYgLuTqCZZ6OoukHpkNb7WsagfaXZZtF/SNn5tsL7ovv8=,iv:iilEzoZnkAzEEvb+SMCQB10W4aafzAm7kkUze1neoeg=,tag:BjersIyT/0OnmY7+o5vvTA==,type:str]
17+
unencrypted_suffix: _unencrypted
18+
version: 3.10.2

0 commit comments

Comments
 (0)