diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index 3907615f73..e5bc1afa51 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -174,6 +174,12 @@ id: 'aidx' # Whether disable HSTS #disableHsts: true +# Whether to enable HSTS preload +# Read these before enabling: +# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security +# - https://hstspreload.org/ +#hstsPreload: false + # Number of worker processes #clusterLimit: 1 diff --git a/.config/docker_example.yml b/.config/docker_example.yml index 3f8e5734ce..7d51ab0443 100644 --- a/.config/docker_example.yml +++ b/.config/docker_example.yml @@ -168,6 +168,12 @@ id: 'aidx' # Whether disable HSTS #disableHsts: true +# Whether to enable HSTS preload +# Read these before enabling: +# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security +# - https://hstspreload.org/ +#hstsPreload: false + # Number of worker processes #clusterLimit: 1 diff --git a/.config/example.yml b/.config/example.yml index 60a6a0aa71..ce336d9f75 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -250,6 +250,12 @@ id: 'aidx' # Whether disable HSTS #disableHsts: true +# Whether to enable HSTS preload +# Read these before enabling: +# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security +# - https://hstspreload.org/ +#hstsPreload: false + # Number of worker processes #clusterLimit: 1 diff --git a/.devcontainer/devcontainer.yml b/.devcontainer/devcontainer.yml index 3eb4fc2879..478642587a 100644 --- a/.devcontainer/devcontainer.yml +++ b/.devcontainer/devcontainer.yml @@ -161,6 +161,12 @@ id: 'aidx' # Whether disable HSTS #disableHsts: true +# Whether to enable HSTS preload +# Read these before enabling: +# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security +# - https://hstspreload.org/ +#hstsPreload: false + # Number of worker processes #clusterLimit: 1 diff --git a/chart/files/default.yml b/chart/files/default.yml index 4d17131c25..1802d987c7 100644 --- a/chart/files/default.yml +++ b/chart/files/default.yml @@ -182,6 +182,12 @@ id: "aidx" # Whether disable HSTS #disableHsts: true +# Whether to enable HSTS preload +# Read these before enabling: +# - https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Strict-Transport-Security#preloading_strict_transport_security +# - https://hstspreload.org/ +#hstsPreload: false + # Number of worker processes #clusterLimit: 1 diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 42f1033b9d..db5c9357e3 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -28,6 +28,7 @@ type Source = { socket?: string; chmodSocket?: string; disableHsts?: boolean; + hstsPreload?: boolean; db: { host: string; port: number; @@ -107,6 +108,7 @@ export type Config = { socket: string | undefined; chmodSocket: string | undefined; disableHsts: boolean | undefined; + hstsPreload: boolean | undefined; db: { host: string; port: number; @@ -241,6 +243,7 @@ export function loadConfig(): Config { socket: config.socket, chmodSocket: config.chmodSocket, disableHsts: config.disableHsts, + hstsPreload: config.hstsPreload ?? false, host, hostname, scheme, diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index fd2bd3267d..a733adbc47 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -31,6 +31,7 @@ import { HealthServerService } from './HealthServerService.js'; import { ClientServerService } from './web/ClientServerService.js'; import { OpenApiServerService } from './api/openapi/OpenApiServerService.js'; import { OAuth2ProviderService } from './oauth/OAuth2ProviderService.js'; +import { makeHstsHook } from './hsts.js'; const _dirname = fileURLToPath(new URL('.', import.meta.url)); @@ -81,12 +82,10 @@ export class ServerService implements OnApplicationShutdown { this.#fastify = fastify; // HSTS - // 6months (15552000sec) if (this.config.url.startsWith('https') && !this.config.disableHsts) { - fastify.addHook('onRequest', (request, reply, done) => { - reply.header('strict-transport-security', 'max-age=15552000; preload'); - done(); - }); + const preload = this.config.hstsPreload; + const host = new URL(this.config.url).host; + fastify.addHook('onRequest', makeHstsHook(host, preload)); } // Register raw-body parser for ActivityPub HTTP signature validation. diff --git a/packages/backend/src/server/hsts.ts b/packages/backend/src/server/hsts.ts new file mode 100644 index 0000000000..d8f606b8bb --- /dev/null +++ b/packages/backend/src/server/hsts.ts @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { onRequestHookHandler } from "fastify"; + +// 6months (15552000sec) by default, 1year (31536000sec) if preload is enabled +export function makeHstsHook(host: string, preload: boolean = false): onRequestHookHandler { + if (preload) { + return (request, reply, done) => { + // we must permanent redirect http to https for preload to be eligible + // however we do not want to do this if a reverse proxy is detected + if ( + (request.host === host || request.hostname === host) && + (request.headers['x-forwarded-proto'] ?? request.protocol) === 'http') { + reply.redirect(`https://${request.hostname}${request.url}`, 301); + } else { + // 1 year is mandatory for preload + reply.header('strict-transport-security', 'max-age=31536000; includeSubDomains; preload'); + } + done(); + } + } else { + return (request, reply, done) => { + reply.header('strict-transport-security', 'max-age=15552000'); + done(); + } + } +} \ No newline at end of file diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index dd7bb7823e..7abcebe397 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -52,6 +52,7 @@ import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify'; +import { makeHstsHook } from '../hsts.js'; const _filename = fileURLToPath(import.meta.url); const _dirname = dirname(_filename); @@ -207,6 +208,13 @@ export class ClientServerService { //#region Bull Dashboard const bullBoardPath = '/queue'; + // HSTS + if (this.config.url.startsWith('https') && !this.config.disableHsts) { + const preload = this.config.hstsPreload; + const host = new URL(this.config.url).host; + fastify.addHook('onRequest', makeHstsHook(host, preload)); + } + // Authenticate fastify.addHook('onRequest', async (request, reply) => { if (request.routeOptions.url == null) {