diff --git a/.gitlab/preview-prefix/kubernetes-resources.yaml b/.gitlab/preview-prefix/kubernetes-resources.yaml index d85a733e68082785048b06e88667686e3459ca42..e1586fce5508af078c607b01d9bdbfa8734f104a 100644 --- a/.gitlab/preview-prefix/kubernetes-resources.yaml +++ b/.gitlab/preview-prefix/kubernetes-resources.yaml @@ -177,6 +177,8 @@ metadata: data: config.yaml: | baseUrls: - - http://preview-app-core-ui - - http://preview-app-office-web + - http://preview-app-core-ui/manifest.json + - http://preview-app-office-web/manifest.json + - http://preview-app-guard-ui/manifest.json + - http://preview-app-core-guidedtours/manifest.json --- diff --git a/.gitlab/preview-sentinel/templates/kubernetes-resources.yaml b/.gitlab/preview-sentinel/templates/kubernetes-resources.yaml index a43a0d0cd5503ed91895161f772eaa23bae06a7c..70c9d2c8df791ba06ec775117eff496340c6c3b9 100644 --- a/.gitlab/preview-sentinel/templates/kubernetes-resources.yaml +++ b/.gitlab/preview-sentinel/templates/kubernetes-resources.yaml @@ -169,3 +169,15 @@ spec: host: preview-sentinel-core-ui-middleware port: number: 80 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: preview-core-ui-middleware-sentinel +data: + config.yaml: | + baseUrls: + - http://preview-app-core-ui/manifest.json + - http://preview-app-office-web/manifest.json + - http://preview-app-guard-ui/manifest.json + - http://preview-app-core-guidedtours/manifest.json diff --git a/.gitlab/preview-sentinel/values.yaml b/.gitlab/preview-sentinel/values.yaml index 85abe938421cade6eee3afa8a37fb47b84d01ca0..a59fce4512bff25c4a56da4985d992c79f2daee5 100644 --- a/.gitlab/preview-sentinel/values.yaml +++ b/.gitlab/preview-sentinel/values.yaml @@ -31,7 +31,8 @@ core-ui-middleware: mode: sentinel hosts: - preview-sentinel-redis:26379 - existingConfigMap: preview-core-ui-middleware + existingConfigMap: preview-core-ui-middleware-sentinel + logLevel: info defaultRegistry: "" diff --git a/.gitlab/preview/templates/kubernetes-resources.yaml b/.gitlab/preview/templates/kubernetes-resources.yaml index 503ebd29e620ee631b7a1aeff0a41db6db6211ca..c02ded2736c1ab092ab2305ecd845be31689cf01 100644 --- a/.gitlab/preview/templates/kubernetes-resources.yaml +++ b/.gitlab/preview/templates/kubernetes-resources.yaml @@ -173,8 +173,10 @@ metadata: data: config.yaml: | baseUrls: - - http://preview-app-core-ui - - http://preview-app-office-web + - http://preview-app-core-ui/manifest.json + - http://preview-app-office-web/manifest.json + - http://preview-app-guard-ui/manifest.json + - http://preview-app-core-guidedtours/manifest.json --- apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor diff --git a/.gitlab/preview/values.yaml b/.gitlab/preview/values.yaml index 6cf0f874a4280c8ed8034316931e4c82c6215dd1..e2afbb5dbb85ed04af3c6042a1f24470edec930c 100644 --- a/.gitlab/preview/values.yaml +++ b/.gitlab/preview/values.yaml @@ -5,6 +5,7 @@ global: core-ui-middleware: replicaCount: 1 existingConfigMap: preview-core-ui-middleware + logLevel: info defaultRegistry: "" @@ -111,10 +112,10 @@ appsuite: memory: "32Mi" core-guidedtours: - enabled: false + enabled: true guard-ui: - enabled: false + enabled: true core-documents-collaboration: enabled: false diff --git a/integration/caching_test.js b/integration/caching_test.js index 4634526f19385020890b5de32a55611d14faaca8..bddd12cee2f23b5ce7113e889d874d3a48005170 100644 --- a/integration/caching_test.js +++ b/integration/caching_test.js @@ -95,4 +95,9 @@ describe('File caching service', function () { const response2 = await app.inject({ url: '/demo.js', headers: { version } }) expect(response2.statusCode).to.equal(200) }) + + it('does not fetch from origins not defined in baseUrls', async function () { + const response = await app.inject({ url: '//t989be0.netlify.app/xss.html' }) + expect(response.statusCode).to.equal(400) + }) }) diff --git a/spec/file_caching_test.js b/spec/file_caching_test.js index e9e68e9cd50453da4438c92d9a31edc08673b64c..fc82976267e8fa3e3a6ff6711751ff63a77251e3 100644 --- a/spec/file_caching_test.js +++ b/spec/file_caching_test.js @@ -273,4 +273,9 @@ describe('File caching service', function () { // check for files in redis expect(await redis.client.getBuffer('ui-middleware:554855300:/file.svg:body')).to.deep.equal(response.rawPayload) }) + + it('does not fetch from origins not defined in baseUrls', async function () { + const response = await app.inject({ url: '//t989be0.netlify.app/xss.html' }) + expect(response.statusCode).to.equal(400) + }) }) diff --git a/src/config_map.js b/src/config_map.js index ba35124b96cb92e32a8aeb343297cfb4b5fc8212..8cb34d4016f09a1ced14c859d14e20abad15be24 100644 --- a/src/config_map.js +++ b/src/config_map.js @@ -31,6 +31,7 @@ export const configMap = { try { const doc = yaml.load(await fs.readFile('./config/config.yaml', 'utf8')) this.urls = doc.baseUrls || [] + this.origins = doc.baseUrls.map((baseUrl) => (new URL(baseUrl)).origin) this.salt = doc.salt logger.debug('[Config] Config has been loaded') } catch (error) { diff --git a/src/errors.js b/src/errors.js index 5b63080636027b67e7942d5ccf5ae9de95eac329..ad483dce0a6882ab3bc885ae33a5f7a34990fe21 100644 --- a/src/errors.js +++ b/src/errors.js @@ -29,6 +29,13 @@ export class NotFoundError extends Error { export class VersionMismatchError extends Error {} +export class NotAllowedOriginError extends Error { + constructor (message, options = {}) { + super(message, options) + this.status = options.status + } +} + /** * Returns true, if the error is a VersionMismatchError or if the error is an aggregate error containing a VersionMismatchError * @param {AggregateError | Error} err @@ -48,3 +55,8 @@ export function isNotFoundError (err) { const errors = err instanceof AggregateError ? err.errors : [err] return errors.some(error => error instanceof NotFoundError) } + +export function isNotAllowedOriginError (err) { + const errors = err instanceof AggregateError ? err.errors : [err] + return errors.some(error => error instanceof NotAllowedOriginError) +} diff --git a/src/files.js b/src/files.js index 41e47eee5ab1d0ce2eae7275524d32df8dbe7012..701e7d8212c350e64f8e00d6fdfb29077d02cdf7 100644 --- a/src/files.js +++ b/src/files.js @@ -25,7 +25,7 @@ import { promisify } from 'node:util' import zlib from 'node:zlib' import * as cache from './cache.js' import { configMap } from './config_map.js' -import { NotFoundError, VersionMismatchError, isVersionMismatchError } from './errors.js' +import { NotAllowedOriginError, NotFoundError, VersionMismatchError, isVersionMismatchError } from './errors.js' import logger from './logger.js' import { getCSSDependenciesFor, getViteManifests } from './manifests.js' import { getVersionInfo } from './version.js' @@ -38,8 +38,13 @@ const compressionMimeTypes = (process.env.COMPRESS_FILE_TYPES || '').replace(/([ const compressionWhitelistRegex = new RegExp(`^(${compressionMimeTypes.join('|')})($|;)`, 'i') export async function fetchFileWithHeadersFromBaseUrl ({ path, baseUrl, version }) { + const upstreamUrl = new URL(path, baseUrl) + if (configMap.origins.includes(upstreamUrl.origin) === false) { + logger.debug(`"${upstreamUrl.origin}" does not match valid baseUrl: "${baseUrl}".`) + throw new NotAllowedOriginError('This origin is not allowed', { status: 400 }) + } const [response, dependencies] = await Promise.all([ - fetch(new URL(path, baseUrl), { cache: 'no-store' }), + fetch(upstreamUrl, { cache: 'no-store' }), nodePath.extname(path) === '.js' && getCSSDependenciesFor({ file: path.substr(1), version }) ]) diff --git a/src/routes/serve-files.js b/src/routes/serve-files.js index fd8cfb7cd42df4e30fd2157d3bc40c33f87c2b54..12f9544f9d468442b963f7e173b3d0bc8cb10c0a 100644 --- a/src/routes/serve-files.js +++ b/src/routes/serve-files.js @@ -21,7 +21,7 @@ */ import { getFile } from '../files.js' -import { isNotFoundError, isVersionMismatchError } from '../errors.js' +import { isNotAllowedOriginError, isNotFoundError, isVersionMismatchError } from '../errors.js' export default async function serveFilePlugin (fastify, options) { fastify.get('*', async (req, reply) => { @@ -35,6 +35,7 @@ export default async function serveFilePlugin (fastify, options) { reply.send(body) } catch (err) { if (isNotFoundError(err) || isVersionMismatchError(err)) throw fastify.httpErrors.createError(404, `File "${req.urlData('path')}" does not exist.`, err) + if (isNotAllowedOriginError(err)) throw fastify.httpErrors.createError(400, err) throw fastify.httpErrors.createError(err.statusCode || 500, err) } })