Skip to content

Commit 585e199

Browse files
committed
refactor: Use code signature for build info
1 parent 4c31fe4 commit 585e199

File tree

18 files changed

+316
-282
lines changed

18 files changed

+316
-282
lines changed

.github/workflows/ci-cd.yml

-1
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,6 @@ jobs:
210210
context: .
211211
file: packages/server/Dockerfile
212212
build-args: |
213-
BUILD_URL=https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
214213
SCEAU_VERIFICATION_MODE=--strict
215214
labels: "${{ steps.docker-labels-tags.outputs.labels }}"
216215
tags: "${{ steps.docker-labels-tags.outputs.tags }}"

examples/fullstack/contact-forms/docker-compose.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ services:
7575
ports:
7676
- '4003:3000'
7777
environment:
78+
- DEPLOYMENT_TAG=local
7879
- DEPLOYMENT_URL=http://localhost:4003
7980
- POSTGRESQL_URL=postgres://postgres:password@e2esdk-db:5432/e2esdk
8081
- SIGNATURE_PUBLIC_KEY=gsE7B63ETtNDIzAwXEp3X1Hv12WCKGH6h7brV3U9NKE
8182
- SIGNATURE_PRIVATE_KEY=___examples-server-signkey__NOT-FOR-PROD__yCwTsHrcRO00MjMDBcSndfUe_XZYIoYfqHtutXdT00oQ
8283
- CORS_ALLOWED_ORIGINS=http://localhost:4000
83-
- RELEASE_TAG=local
8484
links:
8585
- e2esdk-db
8686
depends_on:

examples/fullstack/contact-forms/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,13 @@
1313
"@chakra-ui/react": "^2.4.4",
1414
"@emotion/react": "^11.10.5",
1515
"@emotion/styled": "^11.10.5",
16-
"@socialgouv/e2esdk-client": "^1.0.0-beta.5",
17-
"@socialgouv/e2esdk-crypto": "^1.0.0-beta.5",
18-
"@socialgouv/e2esdk-devtools": "^1.0.0-beta.10",
19-
"@socialgouv/e2esdk-react": "^1.0.0-beta.5",
16+
"@socialgouv/e2esdk-client": "^1.0.0-beta.6",
17+
"@socialgouv/e2esdk-crypto": "^1.0.0-beta.6",
18+
"@socialgouv/e2esdk-devtools": "^1.0.0-beta.11",
19+
"@socialgouv/e2esdk-react": "^1.0.0-beta.6",
2020
"@tanstack/react-query": "^4.20.4",
2121
"@tanstack/react-query-devtools": "^4.20.4",
22-
"framer-motion": "^7.10.2",
22+
"framer-motion": "^7.10.3",
2323
"graphql": "^16.6.0",
2424
"graphql-request": "^5.1.0",
2525
"next": "13.0.5",

examples/with-vue/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,6 @@
2323
"npm-run-all": "^4.1.5",
2424
"typescript": "~4.7.4",
2525
"vite": "^3.2.5",
26-
"vue-tsc": "^1.0.14"
26+
"vue-tsc": "^1.0.16"
2727
}
2828
}

packages/api/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"@socialgouv/e2esdk-config-typescript": "workspace:*",
5050
"@types/node": "^18.11.17",
5151
"jest": "^29.3.1",
52-
"sceau": "^1.1.0",
52+
"sceau": "^1.2.0",
5353
"tsup": "^6.5.0",
5454
"typescript": "^4.9.4"
5555
}

packages/client/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
},
4949
"devDependencies": {
5050
"@socialgouv/e2esdk-config-typescript": "workspace:*",
51-
"sceau": "^1.1.0",
51+
"sceau": "^1.2.0",
5252
"tsup": "^6.5.0",
5353
"typescript": "^4.9.4"
5454
}

packages/crypto/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"@types/libsodium-wrappers": "^0.7.10",
5555
"@types/node": "^18.11.17",
5656
"jest": "^29.3.1",
57-
"sceau": "^1.1.0",
57+
"sceau": "^1.2.0",
5858
"tsup": "^6.5.0",
5959
"typescript": "^4.9.4"
6060
}

packages/devtools/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"@socialgouv/e2esdk-react": "workspace:^",
5656
"@tanstack/react-query": "^4.20.4",
5757
"@tanstack/react-query-devtools": "^4.20.4",
58-
"framer-motion": "^7.10.2",
58+
"framer-motion": "^7.10.3",
5959
"react-focus-lock": "^2.9.2",
6060
"react-hook-form": "^7.41.0",
6161
"react-icons": "^4.7.1",
@@ -73,7 +73,7 @@
7373
"react": "^18.2.0",
7474
"react-dom": "^18.2.0",
7575
"rollup-plugin-visualizer": "^5.8.3",
76-
"sceau": "^1.1.0",
76+
"sceau": "^1.2.0",
7777
"tsup": "^6.5.0",
7878
"typescript": "^4.9.4",
7979
"vite": "^4.0.2"

packages/keygen/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
},
4747
"devDependencies": {
4848
"@socialgouv/e2esdk-config-typescript": "workspace:*",
49-
"sceau": "^1.1.0",
49+
"sceau": "^1.2.0",
5050
"tsup": "^6.5.0",
5151
"typescript": "^4.9.4"
5252
}

packages/react/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"@types/node": "^18.11.17",
5050
"@types/react": "^18.0.26",
5151
"react": "^18.2.0",
52-
"sceau": "^1.1.0",
52+
"sceau": "^1.2.0",
5353
"tsup": "^6.5.0",
5454
"typescript": "^4.9.4"
5555
}

packages/server/Dockerfile

-4
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,7 @@ RUN apk add --no-cache curl
5454

5555
RUN corepack enable
5656

57-
ARG BUILD_URL=local
5857
ARG SCEAU_VERIFICATION_MODE
59-
ENV BUILD_URL $BUILD_URL
6058
ENV NODE_ENV production
6159
ENV PORT 3000
6260

@@ -69,8 +67,6 @@ COPY --from=builder --chown=e2esdk:nodejs /app/ .
6967

7068
RUN packages/server/node_modules/.bin/sceau verify --packageDir packages/server $SCEAU_VERIFICATION_MODE
7169

72-
LABEL org.opencontainers.image.url="$BUILD_URL"
73-
7470
EXPOSE 3000
7571

7672
HEALTHCHECK \

packages/server/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"fastify-plugin": "^4.4.0",
4949
"mitt": "^3.0.0",
5050
"postgres": "^3.3.2",
51-
"sceau": "^1.1.0",
51+
"sceau": "^1.2.0",
5252
"zod": "^3.20.2",
5353
"zod-to-json-schema": "^3.20.1",
5454
"zx": "^7.1.1"

packages/server/src/env.ts

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ const envSchema = z.object({
1414
NODE_ENV: z.enum(['development', 'production', 'test'] as const),
1515
POSTGRESQL_URL: z.string().url(),
1616
DEPLOYMENT_URL: z.string().url(),
17-
RELEASE_TAG: z.string().optional().default('local'),
18-
BUILD_URL: z.string().url().optional().default('unknown://local'),
17+
DEPLOYMENT_TAG: z.string().optional().default('local'),
1918
SIGNATURE_PUBLIC_KEY: z.string().regex(/^[\w-]{43}$/),
2019
SIGNATURE_PRIVATE_KEY: z.string().regex(/^[\w-]{86}$/),
2120
CORS_ALLOWED_ORIGINS: z
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import type { FastifyPluginAsync } from 'fastify'
2+
import fp from 'fastify-plugin'
3+
import fs from 'node:fs/promises'
4+
import path from 'node:path'
5+
import {
6+
sceauSchema,
7+
SceauVerificationSuccess,
8+
SCEAU_FILE_NAME,
9+
verify,
10+
} from 'sceau'
11+
import { fileURLToPath } from 'url'
12+
import { env } from '../env.js'
13+
import type { App } from '../types'
14+
15+
type Decoration = Omit<SceauVerificationSuccess, 'outcome'>
16+
17+
declare module 'fastify' {
18+
interface FastifyInstance {
19+
codeSignature: Decoration
20+
}
21+
}
22+
23+
const codeSignaturePlugin: FastifyPluginAsync = async (app: App) => {
24+
if (env.NODE_ENV !== 'production') {
25+
const decoration: Decoration = {
26+
timestamp: 'unknown',
27+
buildURL: 'local',
28+
sourceURL: 'local',
29+
}
30+
app.decorate('codeSignature', decoration)
31+
return
32+
}
33+
const rootDir = path.resolve(
34+
path.dirname(fileURLToPath(import.meta.url)),
35+
'../..'
36+
)
37+
const sceauFilePath = path.resolve(rootDir, SCEAU_FILE_NAME)
38+
const sceauFileContents = await fs
39+
.readFile(sceauFilePath, { encoding: 'utf8' })
40+
.catch(error => {
41+
app.log.fatal({ msg: 'Failed to read code signature file', error })
42+
process.exit(1)
43+
})
44+
const sceau = sceauSchema.parse(JSON.parse(sceauFileContents))
45+
const result = await verify(
46+
app.sodium,
47+
sceau,
48+
rootDir,
49+
app.sodium.from_hex(sceau.publicKey)
50+
)
51+
if (result.outcome === 'failure') {
52+
app.log.fatal({
53+
msg: 'Invalid code signature',
54+
manifestErrors: result.manifestErrors,
55+
signatureVerified: result.signatureVerified,
56+
})
57+
process.exit(0)
58+
}
59+
app.log.info({
60+
msg: 'Code signature verified',
61+
signedOn: result.timestamp,
62+
sources: result.sourceURL,
63+
build: result.buildURL,
64+
})
65+
const { outcome: _, ...decoration } = result
66+
app.decorate('codeSignature', decoration)
67+
}
68+
69+
export default fp(codeSignaturePlugin, {
70+
fastify: '4.x',
71+
name: 'codeSignature',
72+
dependencies: ['sodium'],
73+
decorators: {
74+
fastify: ['sodium'],
75+
},
76+
})

packages/server/src/plugins/swagger.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,15 @@ const swaggerPlugin: FastifyPluginAsync = async (app: App) => {
1919
],
2020
info: {
2121
title: pkg.name,
22-
version: `${pkg.version} (${env.RELEASE_TAG})`,
22+
version: `${pkg.version} (${env.DEPLOYMENT_TAG})`,
2323
description: pkg.description,
2424
license: {
2525
name: pkg.license,
2626
url: 'https://github.com/SocialGouv/e2esdk/blob/main/LICENSE',
2727
},
2828
},
2929
externalDocs: {
30+
// todo: Use source URL in sceau
3031
description: 'GitHub repository',
3132
url: 'https://github.com/SocialGouv/e2esdk',
3233
},

packages/server/src/routes/info.ts

+7-44
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import fs from 'node:fs/promises'
22
import path from 'node:path'
33
import { fileURLToPath } from 'node:url'
4-
import { sceauSchema, SCEAU_FILE_NAME, verify } from 'sceau'
54
import { z } from 'zod'
65
import { zodToJsonSchema } from 'zod-to-json-schema'
76
import { env } from '../env.js'
@@ -11,8 +10,10 @@ export const prefixOverride = ''
1110

1211
const infoResponseBody = z.object({
1312
version: z.string(),
14-
release: z.string(),
13+
builtAt: z.string(),
1514
buildURL: z.string(),
15+
sourceURL: z.string(),
16+
deploymentTag: z.string(),
1617
deploymentURL: z.string(),
1718
signaturePublicKey: z.string(),
1819
})
@@ -33,52 +34,14 @@ async function readVersion() {
3334

3435
// --
3536

36-
async function verifyCodeSignature(app: App) {
37-
const rootDir = path.resolve(
38-
path.dirname(fileURLToPath(import.meta.url)),
39-
'../..'
40-
)
41-
const sceauFilePath = path.resolve(rootDir, SCEAU_FILE_NAME)
42-
const sceauFileContents = await fs
43-
.readFile(sceauFilePath, { encoding: 'utf8' })
44-
.catch(error => {
45-
app.log.fatal({ msg: 'Failed to read code signature file', error })
46-
process.exit(1)
47-
})
48-
const sceau = sceauSchema.parse(JSON.parse(sceauFileContents))
49-
const result = await verify(
50-
app.sodium,
51-
sceau,
52-
rootDir,
53-
app.sodium.from_hex(sceau.publicKey)
54-
)
55-
if (result.outcome === 'failure') {
56-
app.log.fatal({
57-
msg: 'Invalid code signature',
58-
manifestErrors: result.manifestErrors,
59-
signatureVerified: result.signatureVerified,
60-
})
61-
process.exit(0)
62-
}
63-
app.log.info({
64-
msg: 'Code signature verified',
65-
signedOn: result.timestamp,
66-
sources: result.sourceURL,
67-
build: result.buildURL,
68-
})
69-
}
70-
71-
// --
72-
7337
export default async function infoRoutes(app: App) {
74-
if (env.NODE_ENV === 'production') {
75-
await verifyCodeSignature(app)
76-
}
7738
const version = await readVersion()
7839
const serverInfo: InfoResponseBody = {
7940
version,
80-
release: env.RELEASE_TAG,
81-
buildURL: env.BUILD_URL,
41+
builtAt: app.codeSignature.timestamp,
42+
buildURL: app.codeSignature.buildURL,
43+
sourceURL: app.codeSignature.sourceURL,
44+
deploymentTag: env.DEPLOYMENT_TAG,
8245
deploymentURL: env.DEPLOYMENT_URL,
8346
signaturePublicKey: env.SIGNATURE_PUBLIC_KEY,
8447
}

packages/server/src/server.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ type HealthCheckReply = z.infer<typeof healthCheckReply>
3232
export function createServer() {
3333
const __PROD__ = env.NODE_ENV === 'production'
3434
const app = createFastifyServer({
35-
name: ['e2esdk', env.RELEASE_TAG].join(':'),
35+
name: ['e2esdk', env.DEPLOYMENT_TAG].join(':'),
3636
redactEnv: __PROD__ ? ['POSTGRESQL_URL', 'SIGNATURE_PRIVATE_KEY'] : [],
3737
redactLogPaths: env.DEBUG
3838
? []
@@ -58,7 +58,7 @@ export function createServer() {
5858
},
5959
printRoutes: __PROD__ ? 'logger' : false,
6060
sentry: {
61-
release: env.RELEASE_TAG,
61+
release: env.DEPLOYMENT_TAG,
6262
getUser(_app, request) {
6363
return Promise.resolve({
6464
id: request.identity?.userId,

0 commit comments

Comments
 (0)