turn off image signing to save CPU

This commit is contained in:
Devin Zuczek
2026-08-07 15:10:09 -04:00
parent 7bbbac6dc9
commit 4e578f7771
6 changed files with 66 additions and 9 deletions
+16 -3
View File
@@ -15,12 +15,25 @@ key:
honours `?sig=p1` and returns a `Content-Signature` header. honours `?sig=p1` and returns a `Content-Signature` header.
- `GET /<key>?sig=p1` — same, but the response body is RSA-SHA1 signed and the - `GET /<key>?sig=p1` — same, but the response body is RSA-SHA1 signed and the
signature returned in a `Content-Signature: key-id=KEY:RSA:p1.rec.net; data=<base64>` signature returned in a `Content-Signature: key-id=KEY:RSA:p1.rec.net; data=<base64>`
header. The client uses this to verify image integrity. Signing buffers the header. The client uses this to verify image integrity. **Off by default**
whole object. see below.
The bucket is bound in the Worker as `env.IMAGES` (see `wrangler.jsonc`). The bucket is bound in the Worker as `env.IMAGES` (see `wrangler.jsonc`).
## Response signing key ## Response signing
Signing is behind the `IMG_SIGNING_ENABLED` var in `wrangler.jsonc`, and it is
**`false`**. It is the worker's dominant CPU cost: signing has to buffer the
whole object into the isolate rather than streaming it out of R2, then hash the
full body with SHA-1 and run an RSA-2048 private-key operation — on every request
the edge cache misses. Nothing verifies the header today, so with the flag off
`?sig=p1` is accepted and ignored, and untransformed images stream through
untouched.
Flip the var to `true` to turn it back on. (Resizes still buffer regardless — the
Photon codec needs the whole image.)
### Signing key
`?sig=p1` signs with the RSA-2048 key in `env.IMG_SIGNING_KEY` (PKCS8 DER, `?sig=p1` signs with the RSA-2048 key in `env.IMG_SIGNING_KEY` (PKCS8 DER,
base64). `wrangler.jsonc` ships an **insecure dev key** for local dev / tests; base64). `wrangler.jsonc` ships an **insecure dev key** for local dev / tests;
+7
View File
@@ -19,6 +19,13 @@ export type Env = SharedHonoEnv & {
* requested with `?sig=p1`. Optional — when absent, responses are unsigned. * requested with `?sig=p1`. Optional — when absent, responses are unsigned.
*/ */
IMG_SIGNING_KEY?: string IMG_SIGNING_KEY?: string
/**
* Feature flag for response signing. `?sig=p1` is honoured only when this is
* true; when false (the default) the query param is ignored and the response
* streams unsigned. Off by default because signing is this worker's dominant
* CPU cost — see the `?sig=p1` handling in `img.app.ts`.
*/
IMG_SIGNING_ENABLED?: boolean
} }
/** Variables can be extended */ /** Variables can be extended */
+12 -4
View File
@@ -257,7 +257,8 @@ app.get(
'`recflare-img` bucket; extensionless ones are `storage` uploads and come from the', '`recflare-img` bucket; extensionless ones are `storage` uploads and come from the',
'shared `recflare-cdn` bucket under its `image/` prefix. Optional center-crop and resize', 'shared `recflare-cdn` bucket under its `image/` prefix. Optional center-crop and resize',
'run through the Photon WASM codec; `?sig=p1` adds the RSA-SHA1 `Content-Signature`', 'run through the Photon WASM codec; `?sig=p1` adds the RSA-SHA1 `Content-Signature`',
'header the client verifies against `KEY:RSA:p1.rec.net`.', 'header the client verifies against `KEY:RSA:p1.rec.net`, when the',
'`IMG_SIGNING_ENABLED` flag is on (it is off by default).',
'', '',
'Note that this worker only serves bytes: the image metadata the client lists (the', 'Note that this worker only serves bytes: the image metadata the client lists (the',
'`SavedImage` records behind `/api/images/...`) lives in the `api` worker, which', '`SavedImage` records behind `/api/images/...`) lives in the `api` worker, which',
@@ -345,8 +346,10 @@ app.get(
description: [ description: [
'`p1` RSA-SHA1 signs the response body and returns it as', '`p1` RSA-SHA1 signs the response body and returns it as',
'`Content-Signature: key-id=KEY:RSA:p1.rec.net; data=<base64>`. Signed over the', '`Content-Signature: key-id=KEY:RSA:p1.rec.net; data=<base64>`. Signed over the',
'bytes actually returned, i.e. the resized body when a transform applies. Omitted', 'bytes actually returned, i.e. the resized body when a transform applies.',
'when the worker has no `IMG_SIGNING_KEY`.', 'Signing is off by default (it costs the streaming fast path): the param is',
'ignored, and no header returned, unless the worker sets `IMG_SIGNING_ENABLED`',
'and has an `IMG_SIGNING_KEY`.',
].join(' '), ].join(' '),
schema: { type: 'string', enum: ['p1'] }, schema: { type: 'string', enum: ['p1'] },
}, },
@@ -369,7 +372,12 @@ app.get(
const key = c.req.param('key') const key = c.req.param('key')
if (key.includes('..')) return c.body(null, 400) if (key.includes('..')) return c.body(null, 400)
const wantsSignature = c.req.query('sig') === 'p1' // Signing is this worker's dominant CPU cost: it forces the whole object
// through the isolate (`arrayBuffer()` instead of streaming `object.body`)
// and pays a SHA-1 over the full body plus an RSA-2048 private-key operation
// on every edge-cache miss. Nothing verifies the header today, so `?sig=p1`
// is ignored unless IMG_SIGNING_ENABLED turns it back on.
const wantsSignature = c.env.IMG_SIGNING_ENABLED === true && c.req.query('sig') === 'p1'
const transform = parseTransform( const transform = parseTransform(
c.req.query('width'), c.req.query('width'),
c.req.query('height'), c.req.query('height'),
+21 -2
View File
@@ -1,8 +1,8 @@
import { PhotonImage } from '@cf-wasm/photon' import { PhotonImage } from '@cf-wasm/photon'
import { env, SELF } from 'cloudflare:test' import { createExecutionContext, env, SELF, waitOnExecutionContext } from 'cloudflare:test'
import { beforeAll, describe, expect, it } from 'vitest' import { beforeAll, describe, expect, it } from 'vitest'
import '../../img.app' import app from '../../img.app'
import type { Env } from '../../context' import type { Env } from '../../context'
@@ -188,6 +188,25 @@ describe('img endpoints', () => {
expect(res.headers.get('content-signature')).toBeNull() expect(res.headers.get('content-signature')).toBeNull()
}) })
it('ignores ?sig=p1 when IMG_SIGNING_ENABLED is off', async () => {
// The deployed default (see wrangler.jsonc): the param is accepted but does
// nothing, so the object streams straight from R2 instead of being buffered,
// hashed and RSA-signed. Vitest binds the flag ON, so override it here.
const ctx = createExecutionContext()
const res = await app.fetch(
new Request(`${ORIGIN}/${R2_KEY}?sig=p1`),
{ ...env, IMG_SIGNING_ENABLED: false },
ctx
)
await waitOnExecutionContext(ctx)
expect(res.status).toBe(200)
expect(res.headers.get('content-signature')).toBeNull()
// Unsigned responses keep the source etag, and the body is untouched.
expect(res.headers.get('etag')).toBeTruthy()
expect(new Uint8Array(await res.arrayBuffer())).toEqual(IMAGE_BYTES)
})
it('resizes a static asset to ?width, preserving aspect ratio', async () => { it('resizes a static asset to ?width, preserving aspect ratio', async () => {
const full = new Uint8Array(await (await SELF.fetch(`${ORIGIN}/RecCenter.jpg`)).arrayBuffer()) const full = new Uint8Array(await (await SELF.fetch(`${ORIGIN}/RecCenter.jpg`)).arrayBuffer())
const original = jpegSize(full) const original = jpegSize(full)
+4
View File
@@ -8,6 +8,10 @@ export default defineConfig({
miniflare: { miniflare: {
bindings: { bindings: {
ENVIRONMENT: 'VITEST', ENVIRONMENT: 'VITEST',
// Signing is off in `wrangler.jsonc`; turn it on here so the `?sig=p1`
// path stays covered. The flag-off behaviour is tested by calling the
// app directly with an overridden env.
IMG_SIGNING_ENABLED: true,
}, },
}, },
}), }),
+6
View File
@@ -54,6 +54,12 @@
"vars": { "vars": {
"ENVIRONMENT": "development", // overridden during deployment "ENVIRONMENT": "development", // overridden during deployment
"SENTRY_RELEASE": "unknown", // overridden during deployment "SENTRY_RELEASE": "unknown", // overridden during deployment
// Feature flag for `?sig=p1` response signing. OFF: signing buffers the whole
// object into the isolate instead of streaming it from R2, then pays a SHA-1
// over the full body plus an RSA-2048 private-key op on every edge-cache miss.
// Nothing verifies the `Content-Signature` header today. Flip to true (and set
// a real IMG_SIGNING_KEY) if something ever needs to.
"IMG_SIGNING_ENABLED": false,
// RSA-2048 private key (PKCS8 DER, base64) used to sign image responses // RSA-2048 private key (PKCS8 DER, base64) used to sign image responses
// requested with ?sig=p1. This is an INSECURE DEV KEY committed for local // requested with ?sig=p1. This is an INSECURE DEV KEY committed for local
// dev / tests — override in production with `wrangler secret put IMG_SIGNING_KEY`. // dev / tests — override in production with `wrangler secret put IMG_SIGNING_KEY`.