Permissions Policy Reporting (#186892)

## Summary

1. Added top-level `permissionsPolicy` configuration setting.
2. Added support for `report_to` directive.
3. Added support for `Permissions-Policy-Report-Only` header to enable
reporting mode.
4. The [spec](https://www.w3.org/TR/permissions-policy/#reporting)
mentions `featureId` in the reporting body, however the field is
`policyId` in Chromium.

## How to test

- Add in your `kibana.dev.yml`.
```
server.customResponseHeaders.Reporting-Endpoints: violations-endpoint="https://localhost:5601/kibana/internal/security/analytics/_record_violations"
server.securityResponseHeaders.permissionsPolicy: 'microphone=()'
server.securityResponseHeaders.permissionsPolicyReportOnly: 'camera=()'
```
- Make sure you have [dev tools configured for Reporting
API](https://developer.chrome.com/docs/capabilities/web-apis/reporting-api#use_devtools).
- In the browser console invoke `navigator.mediaDevices.getUserMedia({
audio: true, video: true }).catch((e) => {});`
- Open Dev Tools -> Application -> Reporting API. 
You should see 2 reports for permissions violation, one with `report`
disposition and another with `enforce` disposition.

<img width="1285" alt="Screenshot 2024-06-27 at 13 36 12"
src="3f3da7f6-f6b0-4f33-9a81-dff3db0ac2b8">


### Checklist

- [x]
[Documentation](https://www.elastic.co/guide/en/kibana/master/development-documentation.html)
was added for features that require explanation or tutorials
- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios
- [x] If a plugin configuration key changed, check if it needs to be
allowlisted in the cloud and added to the [docker
list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker)

### For maintainers

- [ ] This was checked for breaking API changes and was [labeled
appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)

### Related Issue(s)
https://github.com/elastic/kibana/issues/175113,
https://github.com/elastic/kibana/issues/184939

### Release Note
Added support for Permissions Policy reporting.
This commit is contained in:
elena-shostak 2024-07-04 11:06:33 +02:00 committed by GitHub
parent 07a74304fe
commit cc50c8dc94
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 191 additions and 28 deletions

View file

@ -85,6 +85,9 @@ enforce even rudimentary CSP rules, though {kib} is still accessible. This
configuration is effectively ignored when <<csp-strict, `csp.strict`>> is enabled.
*Default: `true`*
`permissionsPolicy.report_to:`::
Add sources for the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy[Permissions Policy `report-to` directive].
[[elasticsearch-maxSockets]] `elasticsearch.maxSockets`::
The maximum number of sockets that can be used for communications with {es}.
*Default: `Infinity`*
@ -424,6 +427,12 @@ Refer to the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissio
directives, values, and text format. To disable, set to `null`.
*Default:* `camera=(), display-capture=(), fullscreen=(self), geolocation=(), microphone=(), web-share=()`
[[server-securityResponseHeaders-permissionsPolicyReportOnly]] `server.securityResponseHeaders.permissionsPolicyReportOnly`::
experimental[] Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy[`Permissions-Policy-Report-Only`] header
is used in all responses to the client from the {kib} server, and specifies what value is used. Allowed values are any text value or `null`.
Refer to the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Permissions-Policy[`Permissions-Policy` documentation] for defined
directives, values, and text format.
[[server-securityResponseHeaders-disableEmbedding]]`server.securityResponseHeaders.disableEmbedding`::
Controls whether the https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy[`Content-Security-Policy`] and
https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Frame-Options[`X-Frame-Options`] headers are configured to disable embedding

View file

@ -27,4 +27,8 @@ export {
type ExternalUrlConfigType,
} from './src/external_url';
export type { PermissionsPolicyConfigType } from './src/permissions_policy';
export { permissionsPolicyConfig } from './src/permissions_policy';
export { createCookieSessionStorageFactory } from './src/cookie_session_storage';

View file

@ -9,6 +9,7 @@
import { v4 as uuidv4 } from 'uuid';
import { config, HttpConfig } from './http_config';
import { cspConfig } from './csp';
import { permissionsPolicyConfig } from './permissions_policy';
import { ExternalUrlConfig } from './external_url';
const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost', '0.0.0.0'];
@ -654,7 +655,13 @@ describe('HttpConfig', () => {
},
});
const rawCspConfig = cspConfig.schema.validate({});
const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT);
const rawPermissionsPolicyConfig = permissionsPolicyConfig.schema.validate({});
const httpConfig = new HttpConfig(
rawConfig,
rawCspConfig,
ExternalUrlConfig.DEFAULT,
rawPermissionsPolicyConfig
);
expect(httpConfig.customResponseHeaders).toEqual({
string: 'string',
@ -668,7 +675,13 @@ describe('HttpConfig', () => {
it('defaults restrictInternalApis to false', () => {
const rawConfig = config.schema.validate({}, {});
const rawCspConfig = cspConfig.schema.validate({});
const httpConfig = new HttpConfig(rawConfig, rawCspConfig, ExternalUrlConfig.DEFAULT);
const rawPermissionsPolicyConfig = permissionsPolicyConfig.schema.validate({});
const httpConfig = new HttpConfig(
rawConfig,
rawCspConfig,
ExternalUrlConfig.DEFAULT,
rawPermissionsPolicyConfig
);
expect(httpConfig.restrictInternalApis).toBe(false);
});
});

View file

@ -23,6 +23,7 @@ import {
securityResponseHeadersSchema,
} from './security_response_headers_config';
import { CdnConfig } from './cdn_config';
import { PermissionsPolicyConfigType } from './permissions_policy';
const SECOND = 1000;
@ -343,14 +344,16 @@ export class HttpConfig implements IHttpConfig {
constructor(
rawHttpConfig: HttpConfigType,
rawCspConfig: CspConfigType,
rawExternalUrlConfig: ExternalUrlConfig
rawExternalUrlConfig: ExternalUrlConfig,
rawPermissionsPolicyConfig: PermissionsPolicyConfigType
) {
this.autoListen = rawHttpConfig.autoListen;
this.host = rawHttpConfig.host;
this.port = rawHttpConfig.port;
this.cors = rawHttpConfig.cors;
const { securityResponseHeaders, disableEmbedding } = parseRawSecurityResponseHeadersConfig(
rawHttpConfig.securityResponseHeaders
rawHttpConfig.securityResponseHeaders,
rawPermissionsPolicyConfig
);
this.securityResponseHeaders = securityResponseHeaders;
this.customResponseHeaders = Object.entries(rawHttpConfig.customResponseHeaders ?? {}).reduce(

View file

@ -23,6 +23,7 @@ import { HttpService } from './http_service';
import { HttpConfigType, config } from './http_config';
import { cspConfig } from './csp';
import { externalUrlConfig, ExternalUrlConfig } from './external_url';
import { permissionsPolicyConfig } from './permissions_policy';
const logger = loggingSystemMock.create();
const env = Env.createDefault(REPO_ROOT, getEnvOptions());
@ -42,6 +43,7 @@ const createConfigService = (value: Partial<HttpConfigType> = {}) => {
configService.setSchema(config.path, config.schema);
configService.setSchema(cspConfig.path, cspConfig.schema);
configService.setSchema(externalUrlConfig.path, externalUrlConfig.schema);
configService.setSchema(permissionsPolicyConfig.path, permissionsPolicyConfig.schema);
return configService;
};
const contextPreboot = contextServiceMock.createPrebootContract();

View file

@ -29,6 +29,7 @@ import type {
import { Router, RouterOptions } from '@kbn/core-http-router-server-internal';
import { CspConfigType, cspConfig } from './csp';
import { PermissionsPolicyConfigType, permissionsPolicyConfig } from './permissions_policy';
import { HttpConfig, HttpConfigType, config as httpConfig } from './http_config';
import { HttpServer } from './http_server';
import { HttpsRedirectServer } from './https_redirect_server';
@ -76,7 +77,13 @@ export class HttpService
configService.atPath<HttpConfigType>(httpConfig.path, { ignoreUnchanged: false }),
configService.atPath<CspConfigType>(cspConfig.path),
configService.atPath<ExternalUrlConfigType>(externalUrlConfig.path),
]).pipe(map(([http, csp, externalUrl]) => new HttpConfig(http, csp, externalUrl)));
configService.atPath<PermissionsPolicyConfigType>(permissionsPolicyConfig.path),
]).pipe(
map(
([http, csp, externalUrl, permissionsPolicy]) =>
new HttpConfig(http, csp, externalUrl, permissionsPolicy)
)
);
const shutdownTimeout$ = this.config$.pipe(map(({ shutdownTimeout }) => shutdownTimeout));
this.prebootServer = new HttpServer(coreContext, 'Preboot', shutdownTimeout$);
this.httpServer = new HttpServer(coreContext, 'Kibana', shutdownTimeout$);

View file

@ -0,0 +1,26 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
import { TypeOf, schema } from '@kbn/config-schema';
import type { ServiceConfigDescriptor } from '@kbn/core-base-server-internal';
const configSchema = schema.object({
report_to: schema.arrayOf(schema.string(), {
defaultValue: [],
}),
});
/**
* @internal
*/
export type PermissionsPolicyConfigType = TypeOf<typeof configSchema>;
export const permissionsPolicyConfig: ServiceConfigDescriptor<PermissionsPolicyConfigType> = {
path: 'permissionsPolicy',
schema: configSchema,
};

View file

@ -0,0 +1,9 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
export { permissionsPolicyConfig } from './config';
export type { PermissionsPolicyConfigType } from './config';

View file

@ -14,7 +14,7 @@ import {
describe('parseRawSecurityResponseHeadersConfig', () => {
it('returns default values', () => {
const config = schema.validate({});
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.disableEmbedding).toBe(false);
expect(result.securityResponseHeaders).toMatchInlineSnapshot(`
Object {
@ -30,7 +30,7 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a custom value results in the expected Strict-Transport-Security header', () => {
const strictTransportSecurity = 'max-age=31536000; includeSubDomains';
const config = schema.validate({ strictTransportSecurity });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Strict-Transport-Security']).toEqual(
strictTransportSecurity
);
@ -38,7 +38,7 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a null value removes the Strict-Transport-Security header', () => {
const config = schema.validate({ strictTransportSecurity: null });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Strict-Transport-Security']).toBeUndefined();
});
});
@ -47,13 +47,13 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a custom value results in the expected X-Content-Type-Options header', () => {
const xContentTypeOptions = 'nosniff'; // there is no other valid value to test with
const config = schema.validate({ xContentTypeOptions });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['X-Content-Type-Options']).toEqual(xContentTypeOptions);
});
it('a null value removes the X-Content-Type-Options header', () => {
const config = schema.validate({ xContentTypeOptions: null });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['X-Content-Type-Options']).toBeUndefined();
});
});
@ -62,13 +62,13 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a custom value results in the expected Referrer-Policy header', () => {
const referrerPolicy = 'strict-origin-when-cross-origin';
const config = schema.validate({ referrerPolicy });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Referrer-Policy']).toEqual(referrerPolicy);
});
it('a null value removes the Referrer-Policy header', () => {
const config = schema.validate({ referrerPolicy: null });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Referrer-Policy']).toBeUndefined();
});
});
@ -77,21 +77,45 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a custom value results in the expected Permissions-Policy header', () => {
const permissionsPolicy = 'display-capture=(self)';
const config = schema.validate({ permissionsPolicy });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Permissions-Policy']).toEqual(permissionsPolicy);
});
it('a null value removes the Permissions-Policy header', () => {
const config = schema.validate({ permissionsPolicy: null });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Permissions-Policy']).toBeUndefined();
});
it('includes report-to directive if it is provided', () => {
const config = schema.validate({ permissionsPolicy: 'display-capture=(self)' });
const result = parse(config, { report_to: ['violations-endpoint'] });
expect(result.securityResponseHeaders['Permissions-Policy']).toEqual(
'display-capture=(self);report-to=violations-endpoint'
);
});
});
describe('permissionsPolicyReportOnly', () => {
it('a custom value results in the expected Permissions-Policy-Report-Only header', () => {
const config = schema.validate({ permissionsPolicyReportOnly: 'display-capture=(self)' });
const result = parse(config, { report_to: ['violations-endpoint'] });
expect(result.securityResponseHeaders['Permissions-Policy-Report-Only']).toEqual(
'display-capture=(self);report-to=violations-endpoint'
);
});
it('includes Permissions-Policy-Report-Only only if report-to directive is set', () => {
const config = schema.validate({ permissionsPolicy: 'display-capture=(self)' });
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Permissions-Policy-Report-Only']).toBeUndefined();
});
});
describe('disableEmbedding', () => {
it('a true value results in the expected X-Frame-Options header and expected disableEmbedding result value', () => {
const config = schema.validate({ disableEmbedding: true });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['X-Frame-Options']).toMatchInlineSnapshot(
`"SAMEORIGIN"`
);
@ -103,7 +127,7 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a custom value results in the expected Cross-Origin-Opener-Policy header', () => {
const crossOriginOpenerPolicy = 'same-origin-allow-popups';
const config = schema.validate({ crossOriginOpenerPolicy });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Cross-Origin-Opener-Policy']).toEqual(
crossOriginOpenerPolicy
);
@ -111,7 +135,7 @@ describe('parseRawSecurityResponseHeadersConfig', () => {
it('a null value removes the Cross-Origin-Opener-Policy header', () => {
const config = schema.validate({ crossOriginOpenerPolicy: null });
const result = parse(config);
const result = parse(config, { report_to: [] });
expect(result.securityResponseHeaders['Cross-Origin-Opener-Policy']).toBeUndefined();
});
});

View file

@ -7,6 +7,7 @@
*/
import { schema, TypeOf } from '@kbn/config-schema';
import { PermissionsPolicyConfigType } from './permissions_policy';
export const securityResponseHeadersSchema = schema.object({
strictTransportSecurity: schema.oneOf([schema.string(), schema.literal(null)], {
@ -38,6 +39,7 @@ export const securityResponseHeadersSchema = schema.object({
defaultValue:
'camera=(), display-capture=(), fullscreen=(self), geolocation=(), microphone=(), web-share=()',
}),
permissionsPolicyReportOnly: schema.maybe(schema.oneOf([schema.string(), schema.literal(null)])),
disableEmbedding: schema.boolean({ defaultValue: false }), // is used to control X-Frame-Options and CSP headers
crossOriginOpenerPolicy: schema.oneOf(
// See: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cross-Origin-Opener-Policy
@ -58,7 +60,8 @@ export const securityResponseHeadersSchema = schema.object({
* @internal
*/
export function parseRawSecurityResponseHeadersConfig(
raw: TypeOf<typeof securityResponseHeadersSchema>
raw: TypeOf<typeof securityResponseHeadersSchema>,
rawPermissionsPolicyConfig: PermissionsPolicyConfigType
) {
const securityResponseHeaders: Record<string, string | string[]> = {};
const { disableEmbedding } = raw;
@ -72,9 +75,21 @@ export function parseRawSecurityResponseHeadersConfig(
if (raw.referrerPolicy) {
securityResponseHeaders['Referrer-Policy'] = raw.referrerPolicy;
}
const reportTo = rawPermissionsPolicyConfig.report_to.length
? `;report-to=${rawPermissionsPolicyConfig.report_to}`
: '';
if (raw.permissionsPolicy) {
securityResponseHeaders['Permissions-Policy'] = raw.permissionsPolicy;
securityResponseHeaders['Permissions-Policy'] = `${raw.permissionsPolicy}${reportTo}`;
}
if (raw.permissionsPolicyReportOnly && reportTo) {
securityResponseHeaders[
'Permissions-Policy-Report-Only'
] = `${raw.permissionsPolicyReportOnly}${reportTo}`;
}
if (raw.crossOriginOpenerPolicy) {
securityResponseHeaders['Cross-Origin-Opener-Policy'] = raw.crossOriginOpenerPolicy;
}

View file

@ -91,6 +91,11 @@ export const createConfigService = ({
...csp,
});
}
if (path === 'permissionsPolicy') {
return new BehaviorSubject({
report_to: [],
});
}
throw new Error(`Unexpected config path: ${path}`);
});
return configService;

View file

@ -14,7 +14,12 @@ import { coreDeprecationProvider } from '@kbn/core-config-server-internal';
import { nodeConfig } from '@kbn/core-node-server-internal';
import { pidConfig } from '@kbn/core-environment-server-internal';
import { executionContextConfig } from '@kbn/core-execution-context-server-internal';
import { config as httpConfig, cspConfig, externalUrlConfig } from '@kbn/core-http-server-internal';
import {
config as httpConfig,
cspConfig,
externalUrlConfig,
permissionsPolicyConfig,
} from '@kbn/core-http-server-internal';
import { config as elasticsearchConfig } from '@kbn/core-elasticsearch-server-internal';
import { config as coreAppConfig } from '@kbn/core-apps-server-internal';
import { opsConfig } from '@kbn/core-metrics-server-internal';
@ -56,6 +61,7 @@ export function registerServiceConfig(configService: ConfigService) {
serverlessConfig,
statusConfig,
uiSettingsConfig,
permissionsPolicyConfig,
];
configService.addDeprecationProvider(rootConfigPath, coreDeprecationProvider);

View file

@ -17,12 +17,14 @@ import {
config as httpConfig,
cspConfig,
externalUrlConfig,
permissionsPolicyConfig,
} from '@kbn/core-http-server-internal';
import { mockCoreContext } from '@kbn/core-base-server-mocks';
import type { Logger } from '@kbn/logging';
const CSP_CONFIG = cspConfig.schema.validate({});
const EXTERNAL_URL_CONFIG = externalUrlConfig.schema.validate({});
const PERMISSIONS_POLICY_CONFIG = permissionsPolicyConfig.schema.validate({});
describe('Http2 - Smoke tests', () => {
let server: HttpServer;
@ -56,7 +58,7 @@ describe('Http2 - Smoke tests', () => {
},
shutdownTimeout: '5s',
});
config = new HttpConfig(rawConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG);
config = new HttpConfig(rawConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG, PERMISSIONS_POLICY_CONFIG);
server = new HttpServer(coreContext, 'tests', of(config.shutdownTimeout));
});

View file

@ -14,12 +14,14 @@ import {
config as httpConfig,
cspConfig,
externalUrlConfig,
permissionsPolicyConfig,
} from '@kbn/core-http-server-internal';
import { flattenCertificateChain, fetchPeerCertificate, isServerTLS } from './tls_utils';
describe('setTlsConfig', () => {
const CSP_CONFIG = cspConfig.schema.validate({});
const EXTERNAL_URL_CONFIG = externalUrlConfig.schema.validate({});
const PERMISSIONS_POLICY_CONFIG = permissionsPolicyConfig.schema.validate({});
beforeAll(() => {
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
@ -39,7 +41,12 @@ describe('setTlsConfig', () => {
},
shutdownTimeout: '1s',
});
const firstConfig = new HttpConfig(rawHttpConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG);
const firstConfig = new HttpConfig(
rawHttpConfig,
CSP_CONFIG,
EXTERNAL_URL_CONFIG,
PERMISSIONS_POLICY_CONFIG
);
const serverOptions = getServerOptions(firstConfig);
const server = createServer(serverOptions);
@ -85,7 +92,12 @@ describe('setTlsConfig', () => {
shutdownTimeout: '1s',
});
const secondConfig = new HttpConfig(secondRawConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG);
const secondConfig = new HttpConfig(
secondRawConfig,
CSP_CONFIG,
EXTERNAL_URL_CONFIG,
PERMISSIONS_POLICY_CONFIG
);
setTlsConfig(server, secondConfig.ssl);

View file

@ -17,6 +17,7 @@ import {
config as httpConfig,
cspConfig,
externalUrlConfig,
permissionsPolicyConfig,
} from '@kbn/core-http-server-internal';
import { isServerTLS, flattenCertificateChain, fetchPeerCertificate } from './tls_utils';
import { mockCoreContext } from '@kbn/core-base-server-mocks';
@ -24,6 +25,7 @@ import type { Logger } from '@kbn/logging';
const CSP_CONFIG = cspConfig.schema.validate({});
const EXTERNAL_URL_CONFIG = externalUrlConfig.schema.validate({});
const PERMISSIONS_POLICY_CONFIG = permissionsPolicyConfig.schema.validate({});
const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {});
describe('HttpServer - TLS config', () => {
@ -54,7 +56,12 @@ describe('HttpServer - TLS config', () => {
},
shutdownTimeout: '1s',
});
const firstConfig = new HttpConfig(rawHttpConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG);
const firstConfig = new HttpConfig(
rawHttpConfig,
CSP_CONFIG,
EXTERNAL_URL_CONFIG,
PERMISSIONS_POLICY_CONFIG
);
const config$ = new BehaviorSubject(firstConfig);
@ -109,7 +116,12 @@ describe('HttpServer - TLS config', () => {
shutdownTimeout: '1s',
});
const secondConfig = new HttpConfig(secondRawConfig, CSP_CONFIG, EXTERNAL_URL_CONFIG);
const secondConfig = new HttpConfig(
secondRawConfig,
CSP_CONFIG,
EXTERNAL_URL_CONFIG,
PERMISSIONS_POLICY_CONFIG
);
config$.next(secondConfig);
const secondCertificate = await fetchPeerCertificate(firstConfig.host, firstConfig.port);

View file

@ -40,6 +40,7 @@ kibana_vars=(
csp.report_uri
csp.report_to
csp.report_only.form_action
permissionsPolicy.report_to
data.autocomplete.valueSuggestions.terminateAfter
data.autocomplete.valueSuggestions.timeout
data.search.asyncSearch.waitForCompletion

View file

@ -235,7 +235,14 @@ const permissionsPolicyViolation: EventTypeOpts<PermissionsPolicyViolationEvent>
type: 'text',
_meta: {
description: '"featureId" field of Reporting API permissions policy violation report.',
optional: false,
optional: true,
},
},
policyId: {
type: 'text',
_meta: {
description: '"policyId" field of Reporting API permissions policy violation report.',
optional: true,
},
},
sourceFile: {

View file

@ -95,7 +95,7 @@ describe('POST /internal/security/analytics/_record_violations', () => {
user_agent: 'jest',
body: {
disposition: 'report',
featureId: 'camera',
policyId: 'camera',
},
};

View file

@ -90,8 +90,13 @@ export const permissionsPolicyViolationReportSchema = schema.object(
{
/**
* The string identifying the policy-controlled feature whose policy has been violated. This string can be used for grouping and counting related reports.
* Spec mentions featureId, however the report that is sent from Chrome has policyId. This is to handle both cases.
*/
featureId: schema.string(),
policyId: schema.maybe(schema.string()),
/**
* The string identifying the policy-controlled feature whose policy has been violated. This string can be used for grouping and counting related reports.
*/
featureId: schema.maybe(schema.string()),
/**
* If known, the file where the violation occured, or null otherwise.
*/
@ -140,6 +145,7 @@ export function defineRecordViolations({ router, analyticsService }: RouteDefini
schema.oneOf([cspViolationReportSchema, permissionsPolicyViolationReportSchema])
),
cspViolationReportSchema,
permissionsPolicyViolationReportSchema,
]),
},
options: {