Skip to content
This repository has been archived by the owner on Aug 9, 2022. It is now read-only.

[Not ready to merge] forward extra needed headers to headless browser #369

Draft
wants to merge 2 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 1 commit
Commits
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
35 changes: 5 additions & 30 deletions kibana-reports/server/routes/lib/createReport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
REPORT_TYPE,
REPORT_STATE,
DELIVERY_TYPE,
SECURITY_CONSTANTS,
EXTRA_HEADERS,
} from '../utils/constants';

import {
Expand All @@ -30,12 +30,12 @@ import { createSavedSearchReport } from '../utils/savedSearchReportHelper';
import { ReportSchemaType } from '../../model';
import { CreateReportResultType } from '../utils/types';
import { createVisualReport } from '../utils/visual_report/visualReportHelper';
import { SetCookie, Headers } from 'puppeteer-core';
import { deliverReport } from './deliverReport';
import { updateReportState } from './updateReportState';
import { saveReport } from './saveReport';
import { SemaphoreInterface } from 'async-mutex';
import { AccessInfoType } from 'server';
import _ from 'lodash';

export const createReport = async (
request: KibanaRequest,
Expand Down Expand Up @@ -98,40 +98,15 @@ export const createReport = async (
? report.query_url
: `${basePath}${report.query_url}`;
const completeQueryUrl = `${protocol}://${hostname}:${port}${relativeUrl}`;
// Check if security is enabled. TODO: is there a better way to check?
let cookieObject: SetCookie | undefined;
if (request.headers.cookie) {
const cookies = request.headers.cookie.split(';');
cookies.map((item: string) => {
const cookie = item.trim().split('=');
if (cookie[0] === SECURITY_CONSTANTS.AUTH_COOKIE_NAME) {
cookieObject = {
name: cookie[0],
value: cookie[1],
url: completeQueryUrl,
path: basePath,
};
}
});
}
// If header exists assuming that it needs forwarding
let additionalHeaders: Headers | undefined;
if (request.headers[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER]) {
additionalHeaders = {}
additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_USER_HEADER];
additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_IP_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_IP_HEADER];
if (request.headers[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER]) {
additionalHeaders[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER] = request.headers[SECURITY_CONSTANTS.PROXY_AUTH_ROLES_HEADER]
}
}
const extraHeaders = _.pick(request.headers, EXTRA_HEADERS);

const [value, release] = await semaphore.acquire();
try {
createReportResult = await createVisualReport(
reportParams,
completeQueryUrl,
logger,
cookieObject,
additionalHeaders,
extraHeaders,
timezone
);
} finally {
Expand Down
12 changes: 8 additions & 4 deletions kibana-reports/server/routes/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,13 +76,17 @@ export const DEFAULT_MAX_SIZE = 10000;
export const DEFAULT_REPORT_HEADER = '<h1>Open Distro Kibana Reports</h1>';

export const SECURITY_CONSTANTS = {
AUTH_COOKIE_NAME: 'security_authentication',
TENANT_LOCAL_STORAGE_KEY: 'opendistro::security::tenant::show_popup',
PROXY_AUTH_USER_HEADER: 'x-proxy-user',
PROXY_AUTH_ROLES_HEADER: 'x-proxy-roles',
PROXY_AUTH_IP_HEADER: 'x-forwarded-for',
};

export const EXTRA_HEADERS = [
'cookie',
'x-amzn-aes-auth-required',
'x-forward-proto',
'x-proxy-user',
'x-proxy-roles',
];

export const CHROMIUM_PATH = `${__dirname}/../../../.chromium/headless_shell`;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* permissions and limitations under the License.
*/

import puppeteer, { ElementHandle, SetCookie, Headers } from 'puppeteer-core';
import puppeteer, { Headers } from 'puppeteer-core';
import createDOMPurify from 'dompurify';
import { JSDOM } from 'jsdom';
import { Logger } from '../../../../../../src/core/server';
Expand All @@ -30,13 +30,13 @@ import { CreateReportResultType } from '../types';
import { ReportParamsSchemaType, VisualReportSchemaType } from 'server/model';
import fs from 'fs';
import cheerio from 'cheerio';
import _ from 'lodash';

export const createVisualReport = async (
reportParams: ReportParamsSchemaType,
queryUrl: string,
logger: Logger,
cookie?: SetCookie,
additionalheaders?: Headers,
extraHeaders: Headers,
timezone?: string
): Promise<CreateReportResultType> => {
const {
Expand All @@ -60,24 +60,34 @@ export const createVisualReport = async (
? DOMPurify.sanitize(header)
: DEFAULT_REPORT_HEADER;
const reportFooter = footer ? DOMPurify.sanitize(footer) : '';

// add waitForDynamicContent function
const waitForDynamicContent = async(page, timeout = 30000, interval = 1000, checks = 5) => {
const maxChecks = timeout / interval;
let passedChecks = 0;
const waitForDynamicContent = async (
page,
timeout = 30000,
interval = 1000,
checks = 5
) => {
const maxChecks = timeout / interval;
let passedChecks = 0;
let previousLength = 0;

let i=0; while(i++ <= maxChecks){
let pageContent = await page.content();

let i = 0;
while (i++ <= maxChecks) {
let pageContent = await page.content();
let currentLength = pageContent.length;

(previousLength === 0 || previousLength != currentLength) ? passedChecks = 0 : passedChecks++;
if (passedChecks >= checks) { break; }


previousLength === 0 || previousLength != currentLength
? (passedChecks = 0)
: passedChecks++;
if (passedChecks >= checks) {
break;
}

previousLength = currentLength;
await page.waitFor(interval);
}
}
};

// set up puppeteer
const browser = await puppeteer.launch({
Expand All @@ -102,14 +112,13 @@ export const createVisualReport = async (
const page = await browser.newPage();
page.setDefaultNavigationTimeout(0);
page.setDefaultTimeout(60000); // use 60s timeout instead of default 30s
if (cookie) {
logger.info('domain enables security, use session cookie to access');
await page.setCookie(cookie);
}
if (additionalheaders) {
logger.info('domain passed proxy auth headers, passing to backend');
await page.setExtraHTTPHeaders(additionalheaders);

// Set extra headers that are needed
if (!_.isEmpty(extraHeaders)) {
await page.setExtraHTTPHeaders(extraHeaders);
logger.info('forwarded extra headers to headless browser');
}

logger.info(`original queryUrl ${queryUrl}`);
await page.goto(queryUrl, { waitUntil: 'networkidle0' });
// should add to local storage after page.goto, then access the page again - browser must have an url to register local storage item on it
Expand Down Expand Up @@ -172,7 +181,7 @@ export const createVisualReport = async (
`report source can only be one of [Dashboard, Visualization]`
);
}

// wait for dynamic page content to render
await waitForDynamicContent(page);

Expand Down