Skip to content

Commit deda7c0

Browse files
[CSP] add dynamic config tgo csp report only (#10877)
* add dynamic config tgo csp report only Signed-off-by: Justin Kim <[email protected]> * Changeset file for PR #10877 created/updated * mock dynamic config Signed-off-by: Justin Kim <[email protected]> * add try/catch Signed-off-by: Justin Kim <[email protected]> --------- Signed-off-by: Justin Kim <[email protected]> Co-authored-by: opensearch-changeset-bot[bot] <154024398+opensearch-changeset-bot[bot]@users.noreply.github.com>
1 parent ace1ca2 commit deda7c0

File tree

7 files changed

+62
-8
lines changed

7 files changed

+62
-8
lines changed

changelogs/fragments/10877.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
feat:
2+
- Add dynamic config support to csp report only ([#10877](https://github.com/opensearch-project/OpenSearch-Dashboards/pull/10877))

src/core/server/core_route_handler_context.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ class CoreUiSettingsRouteHandlerContext {
117117
class CoreDynamicConfigRouteHandlerContext {
118118
#client?: IDynamicConfigurationClient;
119119
#asyncLocalStore?: AsyncLocalStorageContext;
120+
#createStoreFromRequest?: (
121+
req: OpenSearchDashboardsRequest
122+
) => AsyncLocalStorageContext | undefined;
120123

121124
constructor(private readonly dynamicConfigServiceStart: InternalDynamicConfigServiceStart) {}
122125

@@ -129,6 +132,11 @@ class CoreDynamicConfigRouteHandlerContext {
129132
this.#asyncLocalStore = this.dynamicConfigServiceStart.getAsyncLocalStore();
130133
return this.#asyncLocalStore;
131134
}
135+
136+
public createStoreFromRequest(req: OpenSearchDashboardsRequest) {
137+
this.#createStoreFromRequest = this.dynamicConfigServiceStart.createStoreFromRequest;
138+
return this.#createStoreFromRequest(req);
139+
}
132140
}
133141

134142
export class CoreRouteHandlerContext {

src/core/server/http_resources/http_resources_service.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,17 @@ describe('HttpResources service', () => {
5454
};
5555
service = new HttpResourcesService(coreContext);
5656
router = httpServiceMock.createRouter();
57+
58+
// TODO? createStoreFromRequest was mocked to reject, hence we are overriding that mock
59+
// Mock dynamicConfig to prevent unhandled promise rejections
60+
const mockDynamicConfigClient = {
61+
getConfig: jest.fn().mockResolvedValue({}),
62+
};
63+
const mockDynamicConfig = {
64+
client: mockDynamicConfigClient,
65+
createStoreFromRequest: jest.fn().mockResolvedValue(null),
66+
};
67+
(context.core as any).dynamicConfig = mockDynamicConfig;
5768
});
5869

5970
describe('register', () => {

src/core/server/http_resources/http_resources_service.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -79,26 +79,39 @@ export class HttpResourcesService implements CoreService<InternalHttpResourcesSe
7979
route: RouteConfig<P, Q, B, 'get'>,
8080
handler: HttpResourcesRequestHandler<P, Q, B>
8181
) => {
82-
return router.get<P, Q, B>(route, (context, request, response) => {
82+
return router.get<P, Q, B>(route, async (context, request, response) => {
8383
return handler(context, request, {
8484
...response,
85-
...this.createResponseToolkit(deps, context, request, response),
85+
...(await this.createResponseToolkit(deps, context, request, response)),
8686
});
8787
});
8888
},
8989
};
9090
}
9191

92-
private createResponseToolkit(
92+
private async createResponseToolkit(
9393
deps: SetupDeps,
9494
context: RequestHandlerContext,
9595
request: OpenSearchDashboardsRequest,
9696
response: OpenSearchDashboardsResponseFactory
97-
): HttpResourcesServiceToolkit {
97+
): Promise<HttpResourcesServiceToolkit> {
9898
const cspHeader = deps.http.csp.header;
99-
10099
const cspReportOnly = deps.http.cspReportOnly;
101-
const cspReportOnlyHeaders = cspReportOnly.isEmitting
100+
101+
let cspReportOnlyIsEmitting: boolean;
102+
try {
103+
const dynamicConfigClient = context.core.dynamicConfig.client;
104+
const dynamicConfigStore = context.core.dynamicConfig.createStoreFromRequest(request);
105+
const cspReportOnlyDynamicConfig = await dynamicConfigClient.getConfig(
106+
{ pluginConfigPath: 'csp-report-only' },
107+
dynamicConfigStore ? { asyncLocalStorageContext: dynamicConfigStore } : undefined
108+
);
109+
cspReportOnlyIsEmitting = cspReportOnlyDynamicConfig?.isEmitting ?? cspReportOnly.isEmitting;
110+
} catch (e) {
111+
cspReportOnlyIsEmitting = cspReportOnly.isEmitting;
112+
}
113+
114+
const cspReportOnlyHeaders = cspReportOnlyIsEmitting
102115
? {
103116
'content-security-policy-report-only': cspReportOnly.cspReportOnlyHeader,
104117
'reporting-endpoints': cspReportOnly.reportingEndpointsHeader,

src/core/server/index.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ import {
5858
OpenSearchServiceStart,
5959
IScopedClusterClient,
6060
} from './opensearch';
61-
import { HttpServiceSetup, HttpServiceStart } from './http';
61+
import { HttpServiceSetup, HttpServiceStart, OpenSearchDashboardsRequest } from './http';
6262
import { HttpResources } from './http_resources';
6363

6464
import { PluginsServiceSetup, PluginsServiceStart, PluginOpaqueId } from './plugins';
@@ -451,6 +451,9 @@ export interface RequestHandlerContext {
451451
dynamicConfig: {
452452
client: IDynamicConfigurationClient;
453453
asyncLocalStore: AsyncLocalStorageContext | undefined;
454+
createStoreFromRequest: (
455+
req: OpenSearchDashboardsRequest
456+
) => AsyncLocalStorageContext | undefined;
454457
};
455458
auditor: Auditor;
456459
};

src/core/server/mocks.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,8 @@ function createCoreRequestHandlerContextMock() {
253253
dynamicConfig: {
254254
client: dynamicConfigServiceMock.createInternalStartContract().getClient(),
255255
asyncLocalStore: dynamicConfigServiceMock.createInternalStartContract().getAsyncLocalStore(),
256+
createStoreFromRequest: dynamicConfigServiceMock.createInternalStartContract()
257+
.createStoreFromRequest,
256258
},
257259
};
258260
}

src/legacy/ui/ui_render/ui_render_mixin.js

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ export function uiRenderMixin(osdServer, server, config) {
306306

307307
async function renderApp(h) {
308308
const { http } = osdServer.newPlatform.setup.core;
309+
const { dynamicConfig } = osdServer.newPlatform.start.core;
309310
const { savedObjects } = osdServer.newPlatform.start.core;
310311
const { rendering } = osdServer.newPlatform.__internals;
311312
const req = OpenSearchDashboardsRequest.from(h.request);
@@ -325,7 +326,21 @@ export function uiRenderMixin(osdServer, server, config) {
325326
.type('text/html')
326327
.header('content-security-policy', http.csp.header);
327328

328-
if (http.cspReportOnly.isEmitting) {
329+
let cspReportOnlyIsEmitting;
330+
try {
331+
const dynamicConfigClient = dynamicConfig.getClient();
332+
const dynamicConfigStore = dynamicConfig.createStoreFromRequest(req);
333+
const cspReportOnlyDynamicConfig = await dynamicConfigClient.getConfig(
334+
{ pluginConfigPath: 'csp-report-only' },
335+
dynamicConfigStore ? { asyncLocalStorageContext: dynamicConfigStore } : undefined
336+
);
337+
cspReportOnlyIsEmitting =
338+
cspReportOnlyDynamicConfig.isEmitting ?? http.cspReportOnly.isEmitting;
339+
} catch (e) {
340+
cspReportOnlyIsEmitting = http.cspReportOnly.isEmitting;
341+
}
342+
343+
if (cspReportOnlyIsEmitting) {
329344
output.header('content-security-policy-report-only', http.cspReportOnly.cspReportOnlyHeader);
330345

331346
if (http.cspReportOnly.reportingEndpointsHeader) {

0 commit comments

Comments
 (0)