diff --git a/src/config.ts b/src/config.ts index 3fe691f9..c8d123f3 100644 --- a/src/config.ts +++ b/src/config.ts @@ -56,6 +56,7 @@ type StorageConfigType = { tenantId: string requestUrlLengthLimit: number requestXForwardedHostRegExp?: string + requestAllowXForwardedPath?: boolean logLevel?: string logflareEnabled?: boolean logflareApiKey?: string @@ -194,6 +195,8 @@ export function getConfig(options?: { reload?: boolean }): StorageConfigType { 'REQUEST_X_FORWARDED_HOST_REGEXP', 'X_FORWARDED_HOST_REGEXP' ), + requestAllowXForwardedPath: + getOptionalConfigFromEnv('REQUEST_ALLOW_X_FORWARDED_PATH') === 'true', requestUrlLengthLimit: Number(getOptionalConfigFromEnv('REQUEST_URL_LENGTH_LIMIT', 'URL_LENGTH_LIMIT')) || 7_500, requestTraceHeader: getOptionalConfigFromEnv('REQUEST_TRACE_HEADER', 'REQUEST_ID_HEADER'), diff --git a/src/http/plugins/signature-v4.ts b/src/http/plugins/signature-v4.ts index 7e698acf..62439245 100644 --- a/src/http/plugins/signature-v4.ts +++ b/src/http/plugins/signature-v4.ts @@ -14,6 +14,7 @@ const { serviceKey, storageS3Region, isMultitenant, + requestAllowXForwardedPath, s3ProtocolPrefix, s3ProtocolAllowForwardedHeader, s3ProtocolEnforceRegion, @@ -37,13 +38,18 @@ export const signatureV4 = fastifyPlugin( token, } = await createServerSignature(request.tenantId, clientSignature) + let storagePrefix = s3ProtocolPrefix + if (requestAllowXForwardedPath && typeof request.headers['x-forwarded-prefix'] === 'string') { + storagePrefix = request.headers['x-forwarded-prefix'] + } + const isVerified = signatureV4.verify(clientSignature, { url: request.url, body: request.body as string | ReadableStream | Buffer, headers: request.headers as Record, method: request.method, query: request.query as Record, - prefix: s3ProtocolPrefix, + prefix: storagePrefix, }) if (!isVerified && !sessionToken) { diff --git a/src/http/routes/tus/lifecycle.ts b/src/http/routes/tus/lifecycle.ts index 5769d995..3d41a45f 100644 --- a/src/http/routes/tus/lifecycle.ts +++ b/src/http/routes/tus/lifecycle.ts @@ -10,7 +10,7 @@ import { UploadId } from '@storage/protocols/tus' import { getConfig } from '../../../config' -const { storageS3Bucket, tusPath } = getConfig() +const { storageS3Bucket, tusPath, requestAllowXForwardedPath } = getConfig() const reExtractFileID = /([^/]+)\/?$/ export const SIGNED_URL_SUFFIX = '/sign' @@ -92,8 +92,15 @@ export function generateUrl( } proto = process.env.NODE_ENV === 'production' ? 'https' : proto + let basePath = path + + const forwardedPath = req.headers['x-forwarded-prefix'] + if (requestAllowXForwardedPath && typeof forwardedPath === 'string') { + basePath = forwardedPath + path + } + const isSigned = req.url?.endsWith(SIGNED_URL_SUFFIX) - const fullPath = isSigned ? `${path}${SIGNED_URL_SUFFIX}` : path + const fullPath = isSigned ? `${basePath}${SIGNED_URL_SUFFIX}` : basePath if (req.headers['x-forwarded-host']) { const port = req.headers['x-forwarded-port'] diff --git a/src/internal/monitoring/logger.ts b/src/internal/monitoring/logger.ts index 540245bd..d95152ab 100644 --- a/src/internal/monitoring/logger.ts +++ b/src/internal/monitoring/logger.ts @@ -139,6 +139,7 @@ const whitelistHeaders = (headers: Record) => { 'x-forwarded-proto', 'x-forwarded-host', 'x-forwarded-port', + 'x-forwarded-prefix', 'referer', 'content-length', 'x-real-ip', diff --git a/src/storage/protocols/s3/signature-v4.ts b/src/storage/protocols/s3/signature-v4.ts index 944d2817..77b0d51f 100644 --- a/src/storage/protocols/s3/signature-v4.ts +++ b/src/storage/protocols/s3/signature-v4.ts @@ -184,7 +184,7 @@ export class SignatureV4 { const serverSignature = this.sign(clientSignature, request) return crypto.timingSafeEqual( Buffer.from(clientSignature.signature), - Buffer.from(serverSignature) + Buffer.from(serverSignature.signature) ) } @@ -223,7 +223,7 @@ export class SignatureV4 { serverCredentials.service ) - return this.hmac(signingKey, stringToSign).toString('hex') + return { signature: this.hmac(signingKey, stringToSign).toString('hex'), canonicalRequest } } protected getPayloadHash(clientSignature: ClientSignature, request: SignatureRequest) {