Skip to content

Commit

Permalink
refactor: Use code signature for build info
Browse files Browse the repository at this point in the history
  • Loading branch information
franky47 committed Dec 20, 2022
1 parent 4c31fe4 commit 585e199
Show file tree
Hide file tree
Showing 18 changed files with 316 additions and 282 deletions.
1 change: 0 additions & 1 deletion .github/workflows/ci-cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,6 @@ jobs:
context: .
file: packages/server/Dockerfile
build-args: |
BUILD_URL=https://github.com/${{github.repository}}/actions/runs/${{github.run_id}}
SCEAU_VERIFICATION_MODE=--strict
labels: "${{ steps.docker-labels-tags.outputs.labels }}"
tags: "${{ steps.docker-labels-tags.outputs.tags }}"
Expand Down
2 changes: 1 addition & 1 deletion examples/fullstack/contact-forms/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ services:
ports:
- '4003:3000'
environment:
- DEPLOYMENT_TAG=local
- DEPLOYMENT_URL=http://localhost:4003
- POSTGRESQL_URL=postgres://postgres:password@e2esdk-db:5432/e2esdk
- SIGNATURE_PUBLIC_KEY=gsE7B63ETtNDIzAwXEp3X1Hv12WCKGH6h7brV3U9NKE
- SIGNATURE_PRIVATE_KEY=___examples-server-signkey__NOT-FOR-PROD__yCwTsHrcRO00MjMDBcSndfUe_XZYIoYfqHtutXdT00oQ
- CORS_ALLOWED_ORIGINS=http://localhost:4000
- RELEASE_TAG=local
links:
- e2esdk-db
depends_on:
Expand Down
10 changes: 5 additions & 5 deletions examples/fullstack/contact-forms/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
"@chakra-ui/react": "^2.4.4",
"@emotion/react": "^11.10.5",
"@emotion/styled": "^11.10.5",
"@socialgouv/e2esdk-client": "^1.0.0-beta.5",
"@socialgouv/e2esdk-crypto": "^1.0.0-beta.5",
"@socialgouv/e2esdk-devtools": "^1.0.0-beta.10",
"@socialgouv/e2esdk-react": "^1.0.0-beta.5",
"@socialgouv/e2esdk-client": "^1.0.0-beta.6",
"@socialgouv/e2esdk-crypto": "^1.0.0-beta.6",
"@socialgouv/e2esdk-devtools": "^1.0.0-beta.11",
"@socialgouv/e2esdk-react": "^1.0.0-beta.6",
"@tanstack/react-query": "^4.20.4",
"@tanstack/react-query-devtools": "^4.20.4",
"framer-motion": "^7.10.2",
"framer-motion": "^7.10.3",
"graphql": "^16.6.0",
"graphql-request": "^5.1.0",
"next": "13.0.5",
Expand Down
2 changes: 1 addition & 1 deletion examples/with-vue/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@
"npm-run-all": "^4.1.5",
"typescript": "~4.7.4",
"vite": "^3.2.5",
"vue-tsc": "^1.0.14"
"vue-tsc": "^1.0.16"
}
}
2 changes: 1 addition & 1 deletion packages/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@socialgouv/e2esdk-config-typescript": "workspace:*",
"@types/node": "^18.11.17",
"jest": "^29.3.1",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
},
"devDependencies": {
"@socialgouv/e2esdk-config-typescript": "workspace:*",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/crypto/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
"@types/libsodium-wrappers": "^0.7.10",
"@types/node": "^18.11.17",
"jest": "^29.3.1",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
}
Expand Down
4 changes: 2 additions & 2 deletions packages/devtools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
"@socialgouv/e2esdk-react": "workspace:^",
"@tanstack/react-query": "^4.20.4",
"@tanstack/react-query-devtools": "^4.20.4",
"framer-motion": "^7.10.2",
"framer-motion": "^7.10.3",
"react-focus-lock": "^2.9.2",
"react-hook-form": "^7.41.0",
"react-icons": "^4.7.1",
Expand All @@ -73,7 +73,7 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"rollup-plugin-visualizer": "^5.8.3",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4",
"vite": "^4.0.2"
Expand Down
2 changes: 1 addition & 1 deletion packages/keygen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
},
"devDependencies": {
"@socialgouv/e2esdk-config-typescript": "workspace:*",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
}
Expand Down
2 changes: 1 addition & 1 deletion packages/react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
"@types/node": "^18.11.17",
"@types/react": "^18.0.26",
"react": "^18.2.0",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"tsup": "^6.5.0",
"typescript": "^4.9.4"
}
Expand Down
4 changes: 0 additions & 4 deletions packages/server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,7 @@ RUN apk add --no-cache curl

RUN corepack enable

ARG BUILD_URL=local
ARG SCEAU_VERIFICATION_MODE
ENV BUILD_URL $BUILD_URL
ENV NODE_ENV production
ENV PORT 3000

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

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

LABEL org.opencontainers.image.url="$BUILD_URL"

EXPOSE 3000

HEALTHCHECK \
Expand Down
2 changes: 1 addition & 1 deletion packages/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"fastify-plugin": "^4.4.0",
"mitt": "^3.0.0",
"postgres": "^3.3.2",
"sceau": "^1.1.0",
"sceau": "^1.2.0",
"zod": "^3.20.2",
"zod-to-json-schema": "^3.20.1",
"zx": "^7.1.1"
Expand Down
3 changes: 1 addition & 2 deletions packages/server/src/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test'] as const),
POSTGRESQL_URL: z.string().url(),
DEPLOYMENT_URL: z.string().url(),
RELEASE_TAG: z.string().optional().default('local'),
BUILD_URL: z.string().url().optional().default('unknown://local'),
DEPLOYMENT_TAG: z.string().optional().default('local'),
SIGNATURE_PUBLIC_KEY: z.string().regex(/^[\w-]{43}$/),
SIGNATURE_PRIVATE_KEY: z.string().regex(/^[\w-]{86}$/),
CORS_ALLOWED_ORIGINS: z
Expand Down
76 changes: 76 additions & 0 deletions packages/server/src/plugins/codeSignature.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import type { FastifyPluginAsync } from 'fastify'
import fp from 'fastify-plugin'
import fs from 'node:fs/promises'
import path from 'node:path'
import {
sceauSchema,
SceauVerificationSuccess,
SCEAU_FILE_NAME,
verify,
} from 'sceau'
import { fileURLToPath } from 'url'
import { env } from '../env.js'
import type { App } from '../types'

type Decoration = Omit<SceauVerificationSuccess, 'outcome'>

declare module 'fastify' {
interface FastifyInstance {
codeSignature: Decoration
}
}

const codeSignaturePlugin: FastifyPluginAsync = async (app: App) => {
if (env.NODE_ENV !== 'production') {
const decoration: Decoration = {
timestamp: 'unknown',
buildURL: 'local',
sourceURL: 'local',
}
app.decorate('codeSignature', decoration)
return
}
const rootDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../..'
)
const sceauFilePath = path.resolve(rootDir, SCEAU_FILE_NAME)
const sceauFileContents = await fs
.readFile(sceauFilePath, { encoding: 'utf8' })
.catch(error => {
app.log.fatal({ msg: 'Failed to read code signature file', error })
process.exit(1)
})
const sceau = sceauSchema.parse(JSON.parse(sceauFileContents))
const result = await verify(
app.sodium,
sceau,
rootDir,
app.sodium.from_hex(sceau.publicKey)
)
if (result.outcome === 'failure') {
app.log.fatal({
msg: 'Invalid code signature',
manifestErrors: result.manifestErrors,
signatureVerified: result.signatureVerified,
})
process.exit(0)
}
app.log.info({
msg: 'Code signature verified',
signedOn: result.timestamp,
sources: result.sourceURL,
build: result.buildURL,
})
const { outcome: _, ...decoration } = result
app.decorate('codeSignature', decoration)
}

export default fp(codeSignaturePlugin, {
fastify: '4.x',
name: 'codeSignature',
dependencies: ['sodium'],
decorators: {
fastify: ['sodium'],
},
})
3 changes: 2 additions & 1 deletion packages/server/src/plugins/swagger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ const swaggerPlugin: FastifyPluginAsync = async (app: App) => {
],
info: {
title: pkg.name,
version: `${pkg.version} (${env.RELEASE_TAG})`,
version: `${pkg.version} (${env.DEPLOYMENT_TAG})`,
description: pkg.description,
license: {
name: pkg.license,
url: 'https://github.com/SocialGouv/e2esdk/blob/main/LICENSE',
},
},
externalDocs: {
// todo: Use source URL in sceau
description: 'GitHub repository',
url: 'https://github.com/SocialGouv/e2esdk',
},
Expand Down
51 changes: 7 additions & 44 deletions packages/server/src/routes/info.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import fs from 'node:fs/promises'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import { sceauSchema, SCEAU_FILE_NAME, verify } from 'sceau'
import { z } from 'zod'
import { zodToJsonSchema } from 'zod-to-json-schema'
import { env } from '../env.js'
Expand All @@ -11,8 +10,10 @@ export const prefixOverride = ''

const infoResponseBody = z.object({
version: z.string(),
release: z.string(),
builtAt: z.string(),
buildURL: z.string(),
sourceURL: z.string(),
deploymentTag: z.string(),
deploymentURL: z.string(),
signaturePublicKey: z.string(),
})
Expand All @@ -33,52 +34,14 @@ async function readVersion() {

// --

async function verifyCodeSignature(app: App) {
const rootDir = path.resolve(
path.dirname(fileURLToPath(import.meta.url)),
'../..'
)
const sceauFilePath = path.resolve(rootDir, SCEAU_FILE_NAME)
const sceauFileContents = await fs
.readFile(sceauFilePath, { encoding: 'utf8' })
.catch(error => {
app.log.fatal({ msg: 'Failed to read code signature file', error })
process.exit(1)
})
const sceau = sceauSchema.parse(JSON.parse(sceauFileContents))
const result = await verify(
app.sodium,
sceau,
rootDir,
app.sodium.from_hex(sceau.publicKey)
)
if (result.outcome === 'failure') {
app.log.fatal({
msg: 'Invalid code signature',
manifestErrors: result.manifestErrors,
signatureVerified: result.signatureVerified,
})
process.exit(0)
}
app.log.info({
msg: 'Code signature verified',
signedOn: result.timestamp,
sources: result.sourceURL,
build: result.buildURL,
})
}

// --

export default async function infoRoutes(app: App) {
if (env.NODE_ENV === 'production') {
await verifyCodeSignature(app)
}
const version = await readVersion()
const serverInfo: InfoResponseBody = {
version,
release: env.RELEASE_TAG,
buildURL: env.BUILD_URL,
builtAt: app.codeSignature.timestamp,
buildURL: app.codeSignature.buildURL,
sourceURL: app.codeSignature.sourceURL,
deploymentTag: env.DEPLOYMENT_TAG,
deploymentURL: env.DEPLOYMENT_URL,
signaturePublicKey: env.SIGNATURE_PUBLIC_KEY,
}
Expand Down
4 changes: 2 additions & 2 deletions packages/server/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ type HealthCheckReply = z.infer<typeof healthCheckReply>
export function createServer() {
const __PROD__ = env.NODE_ENV === 'production'
const app = createFastifyServer({
name: ['e2esdk', env.RELEASE_TAG].join(':'),
name: ['e2esdk', env.DEPLOYMENT_TAG].join(':'),
redactEnv: __PROD__ ? ['POSTGRESQL_URL', 'SIGNATURE_PRIVATE_KEY'] : [],
redactLogPaths: env.DEBUG
? []
Expand All @@ -58,7 +58,7 @@ export function createServer() {
},
printRoutes: __PROD__ ? 'logger' : false,
sentry: {
release: env.RELEASE_TAG,
release: env.DEPLOYMENT_TAG,
getUser(_app, request) {
return Promise.resolve({
id: request.identity?.userId,
Expand Down
Loading

0 comments on commit 585e199

Please sign in to comment.