Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support dynamic CSP rules to mitigate clickjacking #5641

Merged
merged 59 commits into from
Mar 8, 2024
Merged
Changes from 1 commit
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
6376379
support dynamic csp rules to mitigate clickjacking
tianleh Dec 23, 2023
a6c0f6e
add unit tests for the provider class
tianleh Dec 27, 2023
c4481c1
move request handler to its own class
tianleh Dec 27, 2023
e97bef8
add license headers
tianleh Dec 27, 2023
3343fe4
fix failed unit tests
tianleh Dec 27, 2023
f634d39
add unit tests for the handler
tianleh Dec 28, 2023
4e03159
add content to read me
tianleh Dec 28, 2023
d9f9bac
fix test error
tianleh Dec 28, 2023
7a4a93f
update readme
tianleh Dec 28, 2023
5515cad
update CHANGELOG.md
tianleh Dec 28, 2023
f0a7bf0
update snap tests
tianleh Dec 28, 2023
352431a
update snapshots
tianleh Dec 28, 2023
8ac1824
fix a wrong import
tianleh Dec 29, 2023
8688156
undo changes in listing snap
tianleh Dec 29, 2023
d54f8e4
improve wording
tianleh Dec 29, 2023
9c6a8e9
set client after default client is created
tianleh Jan 2, 2024
f570ecc
update return value and add a unit test
tianleh Jan 3, 2024
9e0d78d
remove unnecessary dependency
tianleh Jan 6, 2024
d42ff1e
make the name of the index configurable
tianleh Jan 8, 2024
29211c9
expose APIs and update file structures
tianleh Jan 18, 2024
48ee280
add header
tianleh Jan 18, 2024
ce1b89a
fix link error
tianleh Jan 18, 2024
fe140d8
fix link error
tianleh Jan 18, 2024
bd9ff1d
add more unit tests
tianleh Jan 18, 2024
9e14248
add more unit tests
tianleh Jan 18, 2024
fca9738
update api path
tianleh Jan 19, 2024
6f2e0d4
remove logging
tianleh Jan 19, 2024
60c89b5
update path
tianleh Jan 25, 2024
e9bfdc6
rename index name
tianleh Jan 25, 2024
f4d9f63
update wording
tianleh Jan 25, 2024
e4efc0e
make the new plugin disabled by default
tianleh Jan 30, 2024
b8be888
do not update defaults to avoid breaking change
tianleh Jan 30, 2024
e588758
update readme to reflect new API path
tianleh Jan 31, 2024
6bc00ce
update handler to append frame-ancestors conditionally
tianleh Feb 3, 2024
8f68228
update readme
tianleh Feb 6, 2024
4dbcc21
clean up code to prepare for application config
tianleh Feb 29, 2024
22e5394
reset change log
tianleh Feb 29, 2024
27ce0b1
reset change log again
tianleh Feb 29, 2024
d55b459
update accordingly to new changes in applicationConfig
tianleh Mar 1, 2024
9f371e7
update changelog
tianleh Mar 1, 2024
54a5db1
rename to a new plugin name
tianleh Mar 1, 2024
0e9e146
rename
tianleh Mar 1, 2024
b434169
rename more
tianleh Mar 1, 2024
e67af3c
sync changelog from main
tianleh Mar 4, 2024
2d12575
onboard to app config
tianleh Mar 4, 2024
ef3a55d
fix comment
tianleh Mar 4, 2024
f2ac166
update yml
tianleh Mar 5, 2024
f8dc3b4
update readme
tianleh Mar 5, 2024
0c6a1cc
update change log
tianleh Mar 5, 2024
78004c9
call out single quotes in readme
tianleh Mar 5, 2024
3dc2024
update yml
tianleh Mar 5, 2024
140375f
update default
tianleh Mar 5, 2024
e31cc9b
add reference link
tianleh Mar 5, 2024
688f65a
update js doc
tianleh Mar 5, 2024
c99b02f
rename
tianleh Mar 5, 2024
68f3ea2
use new name
tianleh Mar 5, 2024
8c8eb0f
redo changelog update
tianleh Mar 6, 2024
78d8b32
remove link
tianleh Mar 6, 2024
fcc9875
better name
tianleh Mar 8, 2024
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
Next Next commit
support dynamic csp rules to mitigate clickjacking
Signed-off-by: Tianle Huang <tianleh@amazon.com>
tianleh authored and bandinib-amzn committed Mar 8, 2024
commit 6376379db96f320a7f9a228252f3a4462abeeb0e
1 change: 1 addition & 0 deletions src/core/server/csp/config.ts
Original file line number Diff line number Diff line change
@@ -45,6 +45,7 @@ export const config = {
`script-src 'unsafe-eval' 'self'`,
`worker-src blob: 'self'`,
`style-src 'unsafe-inline' 'self'`,
`frame-ancestors 'self'`,
],
}),
strict: schema.boolean({ defaultValue: false }),
7 changes: 7 additions & 0 deletions src/plugins/csp_configuration_provider/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = {
root: true,
extends: ['@elastic/eslint-config-kibana', 'plugin:@elastic/eui/recommended'],
rules: {
'@osd/eslint/require-license-header': 'off',
},
};
7 changes: 7 additions & 0 deletions src/plugins/csp_configuration_provider/.i18nrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"prefix": "cspConfigurationProvider",
"paths": {
"cspConfigurationProvider": "."
},
"translations": ["translations/ja-JP.json"]
}
11 changes: 11 additions & 0 deletions src/plugins/csp_configuration_provider/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# CspConfigurationProvider

A OpenSearch Dashboards plugin

---

## Development

See the [OpenSearch Dashboards contributing
guide](https://github.com/opensearch-project/OpenSearch-Dashboards/blob/main/CONTRIBUTING.md) for instructions
setting up your development environment.
2 changes: 2 additions & 0 deletions src/plugins/csp_configuration_provider/common/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const PLUGIN_ID = 'cspConfigurationProvider';
export const PLUGIN_NAME = 'CspConfigurationProvider';
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "cspConfigurationProvider",
tianleh marked this conversation as resolved.
Show resolved Hide resolved
"version": "1.0.0",
"opensearchDashboardsVersion": "opensearchDashboards",
"server": true,
"ui": false,
"requiredPlugins": ["navigation"],
tianleh marked this conversation as resolved.
Show resolved Hide resolved
"optionalPlugins": []
}
11 changes: 11 additions & 0 deletions src/plugins/csp_configuration_provider/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { PluginInitializerContext } from '../../../core/server';
import { CspConfigurationProviderPlugin } from './plugin';

// This exports static code and TypeScript types,
// as well as, OpenSearch Dashboards Platform `plugin()` initializer.

export function plugin(initializerContext: PluginInitializerContext) {
return new CspConfigurationProviderPlugin(initializerContext);
}

export { CspConfigurationProviderPluginSetup, CspConfigurationProviderPluginStart } from './types';
100 changes: 100 additions & 0 deletions src/plugins/csp_configuration_provider/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import {
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
OnPreResponseHandler,
OpenSearchClient,
} from '../../../core/server';

import {
CspClient,
CspConfigurationProviderPluginSetup,
CspConfigurationProviderPluginStart,
} from './types';
import { defineRoutes } from './routes';
import { OpenSearchCspClient } from './provider';

const OPENSEARCH_DASHBOARDS_CONFIG_INDEX_NAME = '.opensearch_dashboards_config';
const OPENSEARCH_DASHBOARDS_CONFIG_DOCUMENT_NAME = 'csp.rules';

export class CspConfigurationProviderPlugin
implements Plugin<CspConfigurationProviderPluginSetup, CspConfigurationProviderPluginStart> {
private readonly logger: Logger;
private cspClient: CspClient | undefined;

constructor(initializerContext: PluginInitializerContext) {
this.logger = initializerContext.logger.get();
}

private setCspClient(inputCspClient: CspClient) {
this.cspClient = inputCspClient;
}

private getCspClient(inputOpenSearchClient: OpenSearchClient) {
if (this.cspClient) {
return this.cspClient;
}

return new OpenSearchCspClient(inputOpenSearchClient);
tianleh marked this conversation as resolved.
Show resolved Hide resolved
}

public setup(core: CoreSetup) {
this.logger.debug('CspConfigurationProvider: Setup');
tianleh marked this conversation as resolved.
Show resolved Hide resolved
const router = core.http.createRouter();

// Register server side APIs
defineRoutes(router);

core.http.registerOnPreResponse(this.createCspRulesPreResponseHandler(core));

return {
setCspClient: this.setCspClient.bind(this),
};
}

public start(core: CoreStart) {
this.logger.debug('CspConfigurationProvider: Started');
return {};
}

public stop() {}

private createCspRulesPreResponseHandler(core: CoreSetup): OnPreResponseHandler {
return async (request, response, toolkit) => {
const shouldCheckDest = ['document', 'frame', 'iframe', 'embed', 'object'];

const currentDest = request.headers['sec-fetch-dest'];

if (!shouldCheckDest.includes(currentDest)) {
return toolkit.next({});
}

const [coreStart] = await core.getStartServices();

const myClient = this.getCspClient(coreStart.opensearch.client.asInternalUser);

const existsData = await myClient.exists(OPENSEARCH_DASHBOARDS_CONFIG_INDEX_NAME);

let header;
const defaultHeader = core.http.csp.header;

if (!existsData) {
header = defaultHeader;
} else {
const data = await myClient.get(
OPENSEARCH_DASHBOARDS_CONFIG_INDEX_NAME,
OPENSEARCH_DASHBOARDS_CONFIG_DOCUMENT_NAME
);
header = data || defaultHeader;
}

const additionalHeaders = {
['content-security-policy']: header,
};

return toolkit.next({ headers: additionalHeaders });
};
}
}
39 changes: 39 additions & 0 deletions src/plugins/csp_configuration_provider/server/provider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { OpenSearchClient } from '../../../../src/core/server';

import { CspClient } from './types';

export class OpenSearchCspClient implements CspClient {
client: OpenSearchClient;

constructor(inputOpenSearchClient: OpenSearchClient) {
this.client = inputOpenSearchClient;
}

async exists(configurationName: string): Promise<boolean> {
tianleh marked this conversation as resolved.
Show resolved Hide resolved
const exists = await this.client.indices.exists({
index: configurationName,
});

return exists.body;
}

async get(configurationName: string, cspRulesName: string): Promise<string> {
const query = {
query: {
match: {
_id: {
query: cspRulesName,
},
},
},
};

const data = await this.client.search({
index: configurationName,
_source: true,
body: query,
});

return data.body.hits.hits[0]?._source.value;
}
}
17 changes: 17 additions & 0 deletions src/plugins/csp_configuration_provider/server/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { IRouter } from '../../../../core/server';

export function defineRoutes(router: IRouter) {
router.get(
{
path: '/api/csp_configuration_provider/example',
validate: false,
},
async (context, request, response) => {
return response.ok({
body: {
time: new Date().toISOString(),
},
});
}
);
}
10 changes: 10 additions & 0 deletions src/plugins/csp_configuration_provider/server/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CspConfigurationProviderPluginSetup {}
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface CspConfigurationProviderPluginStart {}

export interface CspClient {
exists(configurationName: string): Promise<boolean>;

get(configurationName: string, cspRulesName: string): Promise<string>;
tianleh marked this conversation as resolved.
Show resolved Hide resolved
}