mirror of
https://github.com/Unleash/unleash.git
synced 2025-01-20 00:08:02 +01:00
feat: Allow extra CSP domains (#1610)
* feat: Allow extra CSP domains Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com> * fix: eslint: * fix: allow partial csp domains * fix: add option and config type * fix: snapshot Co-authored-by: Fredrik Oseberg <fredrik.no@gmail.com>
This commit is contained in:
parent
1cfb25fcb8
commit
606270d86a
@ -2,6 +2,13 @@
|
||||
|
||||
exports[`should create default config 1`] = `
|
||||
Object {
|
||||
"additionalCspAllowedDomains": Object {
|
||||
"defaultSrc": Array [],
|
||||
"fontSrc": Array [],
|
||||
"imgSrc": Array [],
|
||||
"scriptSrc": Array [],
|
||||
"styleSrc": Array [],
|
||||
},
|
||||
"authentication": Object {
|
||||
"createAdminUser": true,
|
||||
"customAuthHandler": [Function],
|
||||
|
@ -268,3 +268,100 @@ test('should yield an empty list when no environment overrides are specified', a
|
||||
|
||||
expect(config.environmentEnableOverrides).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('should yield all empty lists when no additionalCspAllowedDomains are set', async () => {
|
||||
const config = createConfig({});
|
||||
expect(config.additionalCspAllowedDomains).toBeDefined();
|
||||
expect(config.additionalCspAllowedDomains.defaultSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.fontSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.styleSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.scriptSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.imgSrc).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('If additionalCspAllowedDomains is set in config map, passes through', async () => {
|
||||
const config = createConfig({
|
||||
additionalCspAllowedDomains: {
|
||||
defaultSrc: ['googlefonts.com'],
|
||||
fontSrc: [],
|
||||
styleSrc: [],
|
||||
scriptSrc: [],
|
||||
imgSrc: [],
|
||||
},
|
||||
});
|
||||
expect(config.additionalCspAllowedDomains).toBeDefined();
|
||||
expect(config.additionalCspAllowedDomains.defaultSrc).toStrictEqual([
|
||||
'googlefonts.com',
|
||||
]);
|
||||
expect(config.additionalCspAllowedDomains.fontSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.styleSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.scriptSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.imgSrc).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test('Can set partial additionalCspDomains', () => {
|
||||
const config = createConfig({
|
||||
additionalCspAllowedDomains: {
|
||||
defaultSrc: ['googlefonts.com'],
|
||||
},
|
||||
});
|
||||
expect(config.additionalCspAllowedDomains).toBeDefined();
|
||||
expect(config.additionalCspAllowedDomains.defaultSrc).toStrictEqual([
|
||||
'googlefonts.com',
|
||||
]);
|
||||
expect(config.additionalCspAllowedDomains.fontSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.styleSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.scriptSrc).toStrictEqual([]);
|
||||
expect(config.additionalCspAllowedDomains.imgSrc).toStrictEqual([]);
|
||||
});
|
||||
|
||||
test.each([
|
||||
['CSP_ALLOWED_DEFAULT', 'googlefonts.com', 'defaultSrc'],
|
||||
['CSP_ALLOWED_FONT', 'googlefonts.com', 'fontSrc'],
|
||||
['CSP_ALLOWED_STYLE', 'googlefonts.com', 'styleSrc'],
|
||||
['CSP_ALLOWED_SCRIPT', 'googlefonts.com', 'scriptSrc'],
|
||||
['CSP_ALLOWED_IMG', 'googlefonts.com', 'imgSrc'],
|
||||
])(
|
||||
'When %s is set to %s. %s should include passed in domain',
|
||||
(env, domain, key) => {
|
||||
process.env[env] = domain;
|
||||
const config = createConfig({});
|
||||
expect(config.additionalCspAllowedDomains[key][0]).toBe(domain);
|
||||
Object.keys(config.additionalCspAllowedDomains)
|
||||
.filter((objKey) => objKey !== key)
|
||||
.forEach((otherKey) => {
|
||||
expect(
|
||||
config.additionalCspAllowedDomains[otherKey],
|
||||
).toStrictEqual([]);
|
||||
});
|
||||
delete process.env[env];
|
||||
},
|
||||
);
|
||||
|
||||
test('When multiple CSP environment variables are set, respects them all', () => {
|
||||
process.env.CSP_ALLOWED_DEFAULT = 'googlefonts.com';
|
||||
process.env.CSP_ALLOWED_IMG = 'googlefonts.com';
|
||||
process.env.CSP_ALLOWED_SCRIPT = 'plausible.getunleash.io';
|
||||
const config = createConfig({});
|
||||
expect(config.additionalCspAllowedDomains.imgSrc).toStrictEqual([
|
||||
'googlefonts.com',
|
||||
]);
|
||||
expect(config.additionalCspAllowedDomains.defaultSrc).toStrictEqual([
|
||||
'googlefonts.com',
|
||||
]);
|
||||
expect(config.additionalCspAllowedDomains.scriptSrc).toStrictEqual([
|
||||
'plausible.getunleash.io',
|
||||
]);
|
||||
delete process.env.CSP_ALLOWED_DEFAULT;
|
||||
delete process.env.CSP_ALLOWED_IMG;
|
||||
delete process.env.CSP_ALLOWED_SCRIPT;
|
||||
});
|
||||
|
||||
test('Supports multiple domains comma separated in environment variables', () => {
|
||||
process.env.CSP_ALLOWED_SCRIPT = 'plausible.getunleash.io,googlefonts.com';
|
||||
const config = createConfig({});
|
||||
expect(config.additionalCspAllowedDomains.scriptSrc).toStrictEqual([
|
||||
'plausible.getunleash.io',
|
||||
'googlefonts.com',
|
||||
]);
|
||||
});
|
||||
|
@ -15,6 +15,8 @@ import {
|
||||
IListeningPipe,
|
||||
IListeningHost,
|
||||
IUIConfig,
|
||||
ICspDomainConfig,
|
||||
ICspDomainOptions,
|
||||
} from './types/option';
|
||||
import { getDefaultLogProvider, LogLevel, validateLogProvider } from './logger';
|
||||
import { defaultCustomAuthDenyAll } from './default-custom-auth-deny-all';
|
||||
@ -230,6 +232,37 @@ const loadEnvironmentEnableOverrides = () => {
|
||||
return [];
|
||||
};
|
||||
|
||||
const parseCspConfig = (
|
||||
cspConfig: ICspDomainOptions,
|
||||
): ICspDomainConfig | undefined => {
|
||||
if (!cspConfig) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return {
|
||||
defaultSrc: cspConfig.defaultSrc || [],
|
||||
fontSrc: cspConfig.fontSrc || [],
|
||||
scriptSrc: cspConfig.scriptSrc || [],
|
||||
imgSrc: cspConfig.imgSrc || [],
|
||||
styleSrc: cspConfig.styleSrc || [],
|
||||
};
|
||||
};
|
||||
|
||||
const parseCspEnvironmentVariables = (): ICspDomainConfig => {
|
||||
const defaultSrc = process.env.CSP_ALLOWED_DEFAULT?.split(',') || [];
|
||||
const fontSrc = process.env.CSP_ALLOWED_FONT?.split(',') || [];
|
||||
const styleSrc = process.env.CSP_ALLOWED_STYLE?.split(',') || [];
|
||||
const scriptSrc = process.env.CSP_ALLOWED_SCRIPT?.split(',') || [];
|
||||
const imgSrc = process.env.CSP_ALLOWED_IMG?.split(',') || [];
|
||||
return {
|
||||
defaultSrc,
|
||||
fontSrc,
|
||||
styleSrc,
|
||||
scriptSrc,
|
||||
imgSrc,
|
||||
};
|
||||
};
|
||||
|
||||
export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
let extraDbOptions = {};
|
||||
|
||||
@ -318,6 +351,10 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
options.disableLegacyFeaturesApi ||
|
||||
safeBoolean(process.env.DISABLE_LEGACY_FEATURES_API, false);
|
||||
|
||||
const additionalCspAllowedDomains: ICspDomainConfig =
|
||||
parseCspConfig(options.additionalCspAllowedDomains) ||
|
||||
parseCspEnvironmentVariables();
|
||||
|
||||
return {
|
||||
db,
|
||||
session,
|
||||
@ -339,6 +376,7 @@ export function createConfig(options: IUnleashOptions): IUnleashConfig {
|
||||
enterpriseVersion: options.enterpriseVersion,
|
||||
eventBus: new EventEmitter(),
|
||||
environmentEnableOverrides,
|
||||
additionalCspAllowedDomains,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -13,12 +13,18 @@ const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
|
||||
},
|
||||
contentSecurityPolicy: {
|
||||
directives: {
|
||||
defaultSrc: ["'self'", 'cdn.getunleash.io', 'gravatar.com'],
|
||||
defaultSrc: [
|
||||
"'self'",
|
||||
'cdn.getunleash.io',
|
||||
'gravatar.com',
|
||||
...config.additionalCspAllowedDomains.defaultSrc,
|
||||
],
|
||||
fontSrc: [
|
||||
"'self'",
|
||||
'cdn.getunleash.io',
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
...config.additionalCspAllowedDomains.fontSrc,
|
||||
],
|
||||
styleSrc: [
|
||||
"'self'",
|
||||
@ -27,13 +33,19 @@ const secureHeaders: (config: IUnleashConfig) => RequestHandler = (config) => {
|
||||
'fonts.googleapis.com',
|
||||
'fonts.gstatic.com',
|
||||
'data:',
|
||||
...config.additionalCspAllowedDomains.styleSrc,
|
||||
],
|
||||
scriptSrc: [
|
||||
"'self'",
|
||||
'cdn.getunleash.io',
|
||||
...config.additionalCspAllowedDomains.scriptSrc,
|
||||
],
|
||||
scriptSrc: ["'self'", 'cdn.getunleash.io'],
|
||||
imgSrc: [
|
||||
"'self'",
|
||||
'data:',
|
||||
'cdn.getunleash.io',
|
||||
'gravatar.com',
|
||||
...config.additionalCspAllowedDomains.imgSrc,
|
||||
],
|
||||
},
|
||||
},
|
||||
|
@ -97,6 +97,7 @@ export interface IUnleashOptions {
|
||||
experimental?: IExperimentalOptions;
|
||||
email?: Partial<IEmailOption>;
|
||||
secureHeaders?: boolean;
|
||||
additionalCspAllowedDomains?: ICspDomainOptions;
|
||||
enableOAS?: boolean;
|
||||
preHook?: Function;
|
||||
preRouterHook?: Function;
|
||||
@ -137,6 +138,21 @@ export interface IUIConfig {
|
||||
},
|
||||
];
|
||||
}
|
||||
export interface ICspDomainOptions {
|
||||
defaultSrc?: string[];
|
||||
fontSrc?: string[];
|
||||
styleSrc?: string[];
|
||||
scriptSrc?: string[];
|
||||
imgSrc?: string[];
|
||||
}
|
||||
|
||||
export interface ICspDomainConfig {
|
||||
defaultSrc: string[];
|
||||
fontSrc: string[];
|
||||
styleSrc: string[];
|
||||
scriptSrc: string[];
|
||||
imgSrc: string[];
|
||||
}
|
||||
|
||||
export interface IUnleashConfig {
|
||||
db: IDBOption;
|
||||
@ -151,6 +167,7 @@ export interface IUnleashConfig {
|
||||
experimental?: IExperimentalOptions;
|
||||
email: IEmailOption;
|
||||
secureHeaders: boolean;
|
||||
additionalCspAllowedDomains: ICspDomainConfig;
|
||||
enableOAS: boolean;
|
||||
preHook?: Function;
|
||||
preRouterHook?: Function;
|
||||
|
@ -98,6 +98,12 @@ unleash.start(unleashOptions);
|
||||
- **preHook** (function) - this is a hook if you need to provide any middlewares to express before `unleash` adds any. Express app instance is injected as first argument.
|
||||
- **preRouterHook** (function) - use this to register custom express middlewares before the `unleash` specific routers are added.
|
||||
- **secureHeaders** (boolean) - use this to enable security headers (HSTS, CSP, etc) when serving Unleash from HTTPS. Can also be configured through the environment variable `SECURE_HEADERS`.
|
||||
- **additionalCspAllowedDomains** (CspAllowedDomains) - use this when you want to enable security headers but have additional domains you need to allow traffic to
|
||||
- You can set the environment variable CSP_ALLOWED_DEFAULT to allow new defaultSrc (comma separated list)
|
||||
- You can set the environment variable CSP_ALLOWED_FONT to allow new fontSrc (comma separated list)
|
||||
- You can set the environment variable CSP_ALLOWED_STYLE to allow new styleSrc (comma separated list)
|
||||
- You can set the environment variable CSP_ALLOWED_SCRIPT to allow new scriptSrc (comma separated list)
|
||||
- You can set the environment variable CSP_ALLOWED_IMG to allow new imgSrc (comma separated list)
|
||||
- **server** - The server config object taking the following properties
|
||||
- _port_ - which port the unleash-server should bind to. If port is omitted or is 0, the operating system will assign an arbitrary unused port. Will be ignored if pipe is specified. This value may also be set via the `HTTP_PORT` environment variable
|
||||
- _host_ - which host the unleash-server should bind to. If host is omitted, the server will accept connections on the unspecified IPv6 address (::) when IPv6 is available, or the unspecified IPv4 address (0.0.0.0) otherwise. This value may also be set via the `HTTP_HOST` environment variable
|
||||
|
Loading…
Reference in New Issue
Block a user