Skip to content

Commit

Permalink
fix(kibana-security-health-check): improve "Dashboard" tracker view
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin committed Dec 13, 2023
1 parent 93e17b3 commit 65f5d12
Showing 2 changed files with 141 additions and 17 deletions.
137 changes: 120 additions & 17 deletions kibana-security-health-check/src/dashboard.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import type { KibanaMetadata } from './index';
import type {
Credentials,
KibanaMetadata,
SecurityResponseHeaders,
SecutilsGetContentSecurityPolicyResponse,
} from './index';

interface Meta {
lastRevisionId: string;
@@ -8,8 +13,16 @@ const META_REGEX = /\(https:\/\/meta.secutils.dev\/(.+)\)/gm;

interface Params {
targetContentTrackerId: string;
targetCspShareId: string;
credentials: { username: string; password: string };
credentials: Credentials;
expected: {
contentSecurityPolicyId: string;
crossOriginOpenerPolicy: string;
permissionsPolicy: string;
referrerPolicy: string;
strictTransportSecurity: string;
xContentTypeOptions: string;
xFrameOptions: string;
};
}

interface WebPageContentRevision {
@@ -38,27 +51,84 @@ export async function run(previousContent: string | undefined, params: Params):
}

const lastRevision = revisions[revisions.length - 1];
const kibanaMetadata = (JSON.parse(lastRevision.data) as { injectedMetadata: KibanaMetadata }).injectedMetadata;
if (previousContent && lastRevision.id === previousMeta?.lastRevisionId) {
return previousContent;
}

const expectedCsp = await getExpectedContentSecurityPolicy(
params.credentials,
params.expected.contentSecurityPolicyId,
);

const { injectedMetadata, headers: responseHeaders } = JSON.parse(lastRevision.data) as {
headers: SecurityResponseHeaders;
injectedMetadata: KibanaMetadata;
};
const state = `
# Project information
|||
| ------ | ----------- |
| **Environment** | ${kibanaMetadata.env.mode.name} |
| **Branch** | ${kibanaMetadata.env.packageInfo.branch} |
| **Project ID / Cluster Name** | ${kibanaMetadata.clusterInfo?.cluster_name ?? '?'} |
| **Build Flavour** | ${kibanaMetadata.env.packageInfo.buildFlavor} |
| **Build Date** | ${kibanaMetadata.env.packageInfo.buildDate} |
| **Build Number** | ${kibanaMetadata.env.packageInfo.buildNum} |
| **Build Commit** | [${kibanaMetadata.env.packageInfo.buildSha.slice(
| **Environment** | ${injectedMetadata.env.mode.name} |
| **Branch** | ${injectedMetadata.env.packageInfo.branch} |
| **Project ID / Cluster Name** | ${injectedMetadata.clusterInfo?.cluster_name ?? '?'} |
| **Build Flavour** | ${injectedMetadata.env.packageInfo.buildFlavor} |
| **Build Date** | ${injectedMetadata.env.packageInfo.buildDate} |
| **Build Number** | ${injectedMetadata.env.packageInfo.buildNum} |
| **Build Commit** | [${injectedMetadata.env.packageInfo.buildSha.slice(
6,
)}](https://github.com/elastic/kibana/commit/${kibanaMetadata.env.packageInfo.buildSha}) |
| **Version** | ${kibanaMetadata.env.packageInfo.version}|
)}](https://github.com/elastic/kibana/commit/${injectedMetadata.env.packageInfo.buildSha}) |
| **Version** | ${injectedMetadata.env.packageInfo.version}|
# Security headers
## Content Security Policy
Status: ${
lastRevision.id === previousMeta?.lastRevisionId || !previousMeta ? ':white_check_mark:' : ':red_circle:'
} [view policy](${location.origin}/ws/web_security__csp__policies?x-user-share-id=${params.targetCspShareId})
## ${
responseHeaders['content-security-policy'] === expectedCsp.policyText ? ':white_check_mark:' : ':red_circle:'
} Content Security Policy
\`\`\`
${responseHeaders['content-security-policy']}
\`\`\`
[**:mag_right: Inspect**](${location.origin}/ws/web_security__csp__policies?x-user-share-id=${expectedCsp.userShareId})
## ${
responseHeaders['cross-origin-opener-policy'] === params.expected.crossOriginOpenerPolicy
? ':white_check_mark:'
: ':red_circle:'
} Cross Origin Opener Policy
\`\`\`
${responseHeaders['cross-origin-opener-policy']}
\`\`\`
## ${
responseHeaders['permissions-policy'] === params.expected.permissionsPolicy ? ':white_check_mark:' : ':red_circle:'
} Permissions Policy
\`\`\`
${responseHeaders['permissions-policy']}
\`\`\`
## ${
responseHeaders['referrer-policy'] === params.expected.referrerPolicy ? ':white_check_mark:' : ':red_circle:'
} Referrer Policy
\`\`\`
${responseHeaders['referrer-policy']}
\`\`\`
## ${
responseHeaders['strict-transport-security'] === params.expected.strictTransportSecurity
? ':white_check_mark:'
: ':red_circle:'
} Strict Transport Security Policy
\`\`\`
${responseHeaders['strict-transport-security']}
\`\`\`
## ${
responseHeaders['x-content-type-options'] === params.expected.xContentTypeOptions
? ':white_check_mark:'
: ':red_circle:'
} Content Type Options
\`\`\`
${responseHeaders['x-content-type-options']}
\`\`\`
## ${
responseHeaders['x-frame-options'] === params.expected.xFrameOptions ? ':white_check_mark:' : ':red_circle:'
} Frame Options
\`\`\`
${responseHeaders['x-frame-options']}
\`\`\`
`;

return prependMeta(state, {
@@ -77,3 +147,36 @@ function prependMeta(markdownState: string, meta: Meta): string {
function extractMeta(markdownState: string): Meta {
return JSON.parse(decodeURIComponent(Array.from(markdownState.matchAll(META_REGEX))[0][1])) as Meta;
}

async function getExpectedContentSecurityPolicy(
credentials: Credentials,
contentSecurityPolicyId: string,
): Promise<{ userShareId: string; policyText: string }> {
const authorizationHeader = `Basic ${btoa(`${credentials.username}:${credentials.password}`)}`;
// Retrieve policy to get user share ID.
const getResponse = (await (
await fetch(`${location.origin}/api/utils/web_security/csp/${contentSecurityPolicyId}`, {
credentials: 'omit',
headers: { Authorization: authorizationHeader, Accept: 'application/json' },
})
).json()) as SecutilsGetContentSecurityPolicyResponse;

if (!getResponse.userShare) {
throw new Error(`Could not find user share for policy ${contentSecurityPolicyId}`);
}

// Fetch serialized policy
const serializeResponse = await (
await fetch('https://dev.secutils.dev/api/utils/web_security/csp/018c5b81-2908-7583-99e5-0c03fc9f827e/serialize', {
credentials: 'omit',
headers: { Authorization: authorizationHeader, Accept: 'text/plain, */*', 'Content-Type': 'application/json' },
body: JSON.stringify({ source: 'enforcingHeader' }),
method: 'POST',
})
).text();

return {
userShareId: getResponse.userShare.id,
policyText: serializeResponse,
};
}
21 changes: 21 additions & 0 deletions kibana-security-health-check/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
export { run as dashboardRun } from './dashboard';
export { run as trackerRun } from './tracker';

export interface Credentials {
username: string;
password: string;
}

export interface WebPageResource {
url: string;
data: string;
@@ -40,3 +45,19 @@ export interface KibanaMetadata {
};
};
}

export interface SecutilsGetContentSecurityPolicyResponse {
policy: SecutilsContentSecurityPolicy;
userShare?: SecutilsUserShare;
}

export interface SecutilsContentSecurityPolicy {
id: string;
name: string;
createdAt: number;
}

export interface SecutilsUserShare {
id: string;
createdAt: number;
}

0 comments on commit 65f5d12

Please sign in to comment.