Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 68 additions & 36 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,45 +4,47 @@ imaged can be configured via CLI flags or environment variables. CLI flags take

## CLI Options

| Flag | Description | Default |
| ------------------------------- | ------------------------------------------- | ----------- |
| `-p, --port <number>` | HTTP port to listen on | 8000 |
| `-H, --host <address>` | HTTP host to bind to | - |
| `-u, --unix <path>` | Unix socket path (overrides port/host) | - |
| `-c, --concurrency <number>` | Max concurrent image operations | CPU cores |
| `-b, --body-limit <bytes>` | Max request body size in bytes | 16,777,216 |
| `-x, --pixel-limit <pixels>` | Max input image pixels | 100,000,000 |
| `-d, --dimension-limit <px>` | Max output width/height in pixels | 16,384 |
| `-f, --enable-fetch` | Enable GET endpoints that fetch remote URLs | false |
| `-a, --allowed-hosts <regex>` | Regex pattern for allowed fetch hosts | - |
| ` --disable-ssrf-protection` | Disable SSRF protection for fetch requests | false |
| `-P, --enable-pipeline` | Enable the /pipeline endpoint (Bun only) | false |
| ` --max-pipeline-tasks <n>` | Max tasks per pipeline request | 10 |
| `-l, --log-format <format>` | Log format: `json` or `text` | text |
| `-L, --log-level <level>` | Log level: `debug`, `info`, `warn`, `error` | info |
| ` --tls-cert <path>` | Path to TLS certificate file | - |
| ` --tls-key <path>` | Path to TLS private key file | - |
| Flag | Description | Default |
| ------------------------------- | ------------------------------------------------------------- | ----------- |
| `-p, --port <number>` | HTTP port to listen on | 8000 |
| `-H, --host <address>` | HTTP host to bind to | - |
| `-u, --unix <path>` | Unix socket path (overrides port/host) | - |
| `-c, --concurrency <number>` | Max concurrent image operations | CPU cores |
| `-b, --body-limit <bytes>` | Max request body size in bytes | 16,777,216 |
| `-x, --pixel-limit <pixels>` | Max input image pixels | 100,000,000 |
| `-d, --dimension-limit <px>` | Max output width/height in pixels | 16,384 |
| `-f, --enable-fetch` | Enable GET endpoints that fetch remote URLs | false |
| `-a, --allowed-hosts <regex>` | Regex pattern for allowed fetch hosts | - |
| ` --disable-ssrf-protection` | Disable SSRF protection for fetch requests | false |
| `-P, --enable-pipeline` | Enable the /pipeline endpoint (Bun only) | false |
| ` --max-pipeline-tasks <n>` | Max tasks per pipeline request | 10 |
| `-l, --log-format <format>` | Log format: `json` or `text` | text |
| `-L, --log-level <level>` | Log level: `debug`, `info`, `warn`, `error` | info |
| ` --tls-cert <path>` | Path to TLS certificate file | - |
| ` --tls-key <path>` | Path to TLS private key file | - |
| ` --mtls-ca <path>` | Path to CA certificate for client verification (enables mTLS) | - |

## Environment Variables

| Environment Variable | CLI Equivalent | Description |
| ------------------------- | --------------------------- | ------------------------------------------- |
| `PORT` | `--port` | HTTP port to listen on |
| `HOST` | `--host` | HTTP host to bind to |
| `UNIX_SOCKET` | `--unix` | Unix socket path |
| `CONCURRENCY` | `--concurrency` | Max concurrent image operations |
| `BODY_LIMIT` | `--body-limit` | Max request body size in bytes |
| `PIXEL_LIMIT` | `--pixel-limit` | Max input image pixels |
| `DIMENSION_LIMIT` | `--dimension-limit` | Max output width/height in pixels |
| `ENABLE_FETCH` | `--enable-fetch` | Enable GET endpoints (`true`/`false`) |
| `ALLOWED_HOSTS` | `--allowed-hosts` | Regex pattern for allowed fetch hosts |
| `DISABLE_SSRF_PROTECTION` | `--disable-ssrf-protection` | Disable SSRF protection (`true`/`false`) |
| `ENABLE_PIPELINE` | `--enable-pipeline` | Enable pipeline endpoint (`true`/`false`) |
| `MAX_PIPELINE_TASKS` | `--max-pipeline-tasks` | Max tasks per pipeline request |
| `LOG_FORMAT` | `--log-format` | Log format: `json` or `text` |
| `LOG_LEVEL` | `--log-level` | Log level: `debug`, `info`, `warn`, `error` |
| `TLS_CERT` | `--tls-cert` | Path to TLS certificate file |
| `TLS_KEY` | `--tls-key` | Path to TLS private key file |
| Environment Variable | CLI Equivalent | Description |
| ------------------------- | --------------------------- | ---------------------------------------------- |
| `PORT` | `--port` | HTTP port to listen on |
| `HOST` | `--host` | HTTP host to bind to |
| `UNIX_SOCKET` | `--unix` | Unix socket path |
| `CONCURRENCY` | `--concurrency` | Max concurrent image operations |
| `BODY_LIMIT` | `--body-limit` | Max request body size in bytes |
| `PIXEL_LIMIT` | `--pixel-limit` | Max input image pixels |
| `DIMENSION_LIMIT` | `--dimension-limit` | Max output width/height in pixels |
| `ENABLE_FETCH` | `--enable-fetch` | Enable GET endpoints (`true`/`false`) |
| `ALLOWED_HOSTS` | `--allowed-hosts` | Regex pattern for allowed fetch hosts |
| `DISABLE_SSRF_PROTECTION` | `--disable-ssrf-protection` | Disable SSRF protection (`true`/`false`) |
| `ENABLE_PIPELINE` | `--enable-pipeline` | Enable pipeline endpoint (`true`/`false`) |
| `MAX_PIPELINE_TASKS` | `--max-pipeline-tasks` | Max tasks per pipeline request |
| `LOG_FORMAT` | `--log-format` | Log format: `json` or `text` |
| `LOG_LEVEL` | `--log-level` | Log level: `debug`, `info`, `warn`, `error` |
| `TLS_CERT` | `--tls-cert` | Path to TLS certificate file |
| `TLS_KEY` | `--tls-key` | Path to TLS private key file |
| `MTLS_CA` | `--mtls-ca` | Path to CA certificate for client verification |

Boolean environment variables accept `true`, `1`, `false`, or `0`.

Expand Down Expand Up @@ -84,3 +86,33 @@ docker run -p 8443:8000 \
ghcr.io/ryanfowler/imaged:latest \
--tls-cert /certs/cert.pem --tls-key /certs/key.pem
```

## mTLS (Mutual TLS)

Enable client certificate verification by providing a CA certificate. When mTLS is enabled, clients must present a valid certificate signed by the specified CA.

```bash
bun run index.ts \
--tls-cert server.pem \
--tls-key server-key.pem \
--mtls-ca ca.pem
```

The `--mtls-ca` option requires TLS to be enabled (`--tls-cert` and `--tls-key`). Clients without valid certificates will be rejected.

**Testing with curl:**

```bash
curl --cert client.pem --key client-key.pem --cacert ca.pem https://localhost:8000/healthz
```

**Docker with mTLS:**

```bash
docker run -p 8443:8000 \
-v /path/to/certs:/certs:ro \
ghcr.io/ryanfowler/imaged:latest \
--tls-cert /certs/server.pem \
--tls-key /certs/server-key.pem \
--mtls-ca /certs/ca.pem
```
6 changes: 5 additions & 1 deletion index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ const url = await server.serve({
enableFetch: opts.enableFetch,
tlsCert: opts.tlsCert,
tlsKey: opts.tlsKey,
mtlsCa: opts.mtlsCa,
pipelineExecutor,
});
logger.info({ url }, "server listening");
logger.info(
{ tls: !!opts.tlsCert && !!opts.tlsKey, mtls: !!opts.mtlsCa, url },
"server listening",
);
21 changes: 21 additions & 0 deletions lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export interface CLIOptions {
logLevel: LogLevel;
tlsCert?: string;
tlsKey?: string;
mtlsCa?: string;
enablePipeline: boolean;
maxPipelineTasks: number;
s3Config?: S3Config;
Expand All @@ -58,6 +59,7 @@ interface RawOptions {
logLevel: string;
tlsCert?: string;
tlsKey?: string;
mtlsCa?: string;
enablePipeline: boolean;
maxPipelineTasks: string;
}
Expand Down Expand Up @@ -146,6 +148,12 @@ export function parseArgs(): CLIOptions {
.addOption(
new Option("--tls-key <path>", "Path to TLS private key file").env("TLS_KEY"),
)
.addOption(
new Option(
"--mtls-ca <path>",
"Path to CA certificate for client verification (enables mTLS)",
).env("MTLS_CA"),
)
.addOption(
new Option("-u, --unix <path>", "Unix socket path (overrides port/host)").env(
"UNIX_SOCKET",
Expand Down Expand Up @@ -248,6 +256,18 @@ export function parseArgs(): CLIOptions {
}
}

const mtlsCa = opts.mtlsCa;
if (mtlsCa) {
if (!fs.existsSync(mtlsCa)) {
logger.fatal({ path: mtlsCa }, "mTLS CA certificate file not found");
process.exit(1);
}
if (!tlsCert || !tlsKey) {
logger.fatal("--mtls-ca requires TLS to be enabled (--tls-cert and --tls-key)");
process.exit(1);
}
}

const maxPipelineTasks = parseInt(opts.maxPipelineTasks, 10);
if (!Number.isInteger(maxPipelineTasks) || maxPipelineTasks <= 0) {
logger.fatal({ value: opts.maxPipelineTasks }, "Invalid max-pipeline-tasks");
Expand Down Expand Up @@ -288,6 +308,7 @@ export function parseArgs(): CLIOptions {
logLevel,
tlsCert,
tlsKey,
mtlsCa,
enablePipeline: opts.enablePipeline,
maxPipelineTasks,
s3Config,
Expand Down
6 changes: 6 additions & 0 deletions lib/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export interface ServeOptions {
enableFetch: boolean;
tlsCert?: string;
tlsKey?: string;
mtlsCa?: string;
pipelineExecutor?: PipelineExecutor;
}

Expand Down Expand Up @@ -87,6 +88,11 @@ export class Server {
https: {
cert: fs.readFileSync(options.tlsCert),
key: fs.readFileSync(options.tlsKey),
...(options.mtlsCa && {
ca: fs.readFileSync(options.mtlsCa),
requestCert: true,
rejectUnauthorized: true,
}),
},
}),
});
Expand Down