Skip to content

Commit

Permalink
Merge branch 'main' into 135887-discover-unsaved-changes
Browse files Browse the repository at this point in the history
  • Loading branch information
jughosta committed Nov 1, 2023
2 parents 38fb7c9 + 10f7865 commit 35be168
Show file tree
Hide file tree
Showing 30 changed files with 1,311 additions and 834 deletions.
22 changes: 18 additions & 4 deletions packages/kbn-profiling-utils/common/callee.ts
Expand Up @@ -6,6 +6,7 @@
* Side Public License, v 1.
*/

import { sum } from 'lodash';
import { createFrameGroupID, FrameGroupID } from './frame_group';
import {
emptyExecutable,
Expand Down Expand Up @@ -51,6 +52,9 @@ export interface CalleeTree {
CountInclusive: number[];
/** self cpu */
CountExclusive: number[];
TotalSamples: number;
TotalCPU: number;
SelfCPU: number;
}

/**
Expand Down Expand Up @@ -83,14 +87,17 @@ export function createCalleeTree(
FunctionOffset: new Array(totalFrames),
SourceFilename: new Array(totalFrames),
SourceLine: new Array(totalFrames),

CountInclusive: new Array(totalFrames),
CountExclusive: new Array(totalFrames),
TotalSamples: 0,
SelfCPU: 0,
TotalCPU: 0,
};

// The inverse of the sampling rate is the number with which to multiply the number of
// samples to get an estimate of the actual number of samples the backend received.
const scalingFactor = 1.0 / samplingRate;
let totalSamples = 0;
tree.Edges[0] = new Map<FrameGroupID, NodeID>();

tree.FileID[0] = '';
Expand Down Expand Up @@ -127,7 +134,7 @@ export function createCalleeTree(
const stackTrace = stackTraces.get(stackTraceID) ?? emptyStackTrace;
const lenStackTrace = stackTrace.FrameIDs.length;
const samples = Math.floor((events.get(stackTraceID) ?? 0) * scalingFactor);

totalSamples += samples;
let currentNode = 0;

// Increment the count by the number of samples observed, multiplied with the inverse of the
Expand Down Expand Up @@ -182,6 +189,13 @@ export function createCalleeTree(
currentNode = node;
}
}

return tree;
const sumSelfCPU = sum(tree.CountExclusive);
const sumTotalCPU = sum(tree.CountInclusive);

return {
...tree,
TotalSamples: totalSamples,
SelfCPU: sumSelfCPU,
TotalCPU: sumTotalCPU,
};
}
9 changes: 9 additions & 0 deletions packages/kbn-profiling-utils/common/flamegraph.ts
Expand Up @@ -45,6 +45,9 @@ export interface BaseFlameGraph {
TotalSeconds: number;
/** sampling rate */
SamplingRate: number;
TotalSamples: number;
TotalCPU: number;
SelfCPU: number;
}

/**
Expand Down Expand Up @@ -78,6 +81,9 @@ export function createBaseFlameGraph(
CountExclusive: tree.CountExclusive.slice(0, tree.Size),

TotalSeconds: totalSeconds,
TotalSamples: tree.TotalSamples,
SelfCPU: tree.SelfCPU,
TotalCPU: tree.TotalCPU,
};

for (let i = 0; i < tree.Size; i++) {
Expand Down Expand Up @@ -132,6 +138,9 @@ export function createFlameGraph(base: BaseFlameGraph): ElasticFlameGraph {
Label: new Array<string>(base.Size),

TotalSeconds: base.TotalSeconds,
TotalSamples: base.TotalSamples,
SelfCPU: base.SelfCPU,
TotalCPU: base.TotalCPU,
};

const rootFrameGroupID = createFrameGroupID(
Expand Down
Expand Up @@ -20,7 +20,7 @@ export class BlobStorageService {
/**
* The number of uploads per Kibana instance that can be running simultaneously
*/
private readonly concurrentUploadsToES = 20;
private readonly concurrentUploadsToES = 5;

/**
* The number of downloads per Kibana instance that can be running simultaneously
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 104 additions & 0 deletions x-pack/plugins/fleet/dev_docs/secrets.md
@@ -0,0 +1,104 @@
### Secrets

Fleet users often need to provide sensitive information as part of their output and integration config, for example API keys or credentials. Originally these were value were not treated in any special way, but the requirement cae to store these more securely.

## Solution Design

When it came to designing secrets storage, our goals were:

- 1. Kibana System and Fleet users should not be able to read any secrets once they are written for the first time
- 2. A malicious actor who gains authorization access to Kibana or Elasticsearch, or exploits an RCE in Kibana should not be able to read plaintext secret values.
- 3. Secrets in integration policies and outputs should be able to utilize secrets
- 4. When upgrading an integration policy, the policies secrets should be mapped across to the new version without user intervention
- 5. The design will use elastic as the secret store, however it should not prevent new secret stores from being added in the future.


## Architecture

![secret storage architecture](./diagrams/secrets/secret_storage_architecture.png)

Here is an overview of the integration policy flow:
- 1. A package identifies a field as secret using a new package spec
- 2. On creating an integration policy with a secret field, the secret is sent from the UI in plaintext
- 3. Kibana extracts secrets from the package policy using the package manifest to know which fields are secret
- 4. Kibana stores the secrets in the new `.secrets` system index
- 5. Kibana stores the policy with references to the secrets instead of the secrets
- 6. Fleet server reads the policy and retrieves the secrets
- 7. The fleet server inserts the secrets into the policy and passes the policy to the agent

### What is a secret reference?
A secret reference replaces a plain text value, and refers to a record in the .fleet-secrets system index (or other secret store in the future). In the .fleet-secrets system index we only store the value itself.

_example document from the .fleet-secrets index_
```jsonc
{
"_id": "abcd1234",
"_source": {
"value": "abcd12345678",
}
}
```

Kibana system user only has write and delete privileges for the .fleet-secrets system index. Fleet server user has read access.

The secret reference is then stored in the package policy in a format to let us know it is a secret reference:

_ingest-package-policies saved object in inputs.0.vars for example.._
```jsonc
"password": {
"type": "password",
"value": {
"isSecretRef": true
"id": "12345678"
}
}
```

We then track all secrets contained in a policy at the top level of the package policy saved object, this removes the need for us to ever recurse over a policy to find all the secrets it uses:
_ingest-package-policies saved object at the top level_
```jsonc
secret_refs: [{
id: "abcd1234",
// in the future we may also have
// encrypted: true
// source: "elastic"
}]
```

We then need to have the secret ID in the compiled package policy for the fleet server to replace with the value. For this we have a known scheme to refer to a secret reference. We use “$co.elastic.secret\{\<secret id\>\}” scheme. For example the above secret would then be stored in the compiled package policy as follows:

```jsonc
{
"password": "$co.elastic.secret{abcd1234}",
// or in the case of a secret nested within a field...
"header": "Authorization Basic: $co.elastic.secret{abcd1234}",
}
```

### How does Fleet Server replace the secret references with the secrets?
Fleet server has read access to the .fleet-secrets index. Upon receiving a new policy update, Fleet Server first retrieves the secrets referenced in the policy, to accommodate this we have a block at the top of the agent policy that lists all secrets used within the policy:
```jsonc
{
//at the top level of the policy sent to fleet server
secret_refs: [{
id: "abcd12345",
// this is an object so that we can add attributes to the secret in the future, e.g a source or if it is encrypted
},
{
// this may be an integration secret or an output secret
id: "defg56789"
}]
}
```

Having gathered the secrets, fleet server then performs templating on the policy replacing the integration or output secret references with the secret value.

### How is Fleet server backwards compatibility handled?

Integration secrets are only supported by Fleet server 8.10.0 or greater, output secrets 8.12.0.

For integration secrets, when performing CRUD on a package policy, we first check the global settings saved object to see if secrets are enabled, if so then secret storage is used.

If secret storage s not enabled, we check all flee tserver versions, if they meet the minimum spec, then we enable secret storage on the global settings saved object and the check is never performed again.

For output secrets, we do not perform this check as outputs offer a plain text alternative for all secret fields. Instead we warn the user about the version requirements and let them decide.
Expand Up @@ -8,10 +8,9 @@
import React from 'react';
import { EuiBasicTable } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
import { NoData } from '../../../../components/empty_states';
import { EuiEmptyPrompt } from '@elastic/eui';
import { HostNodeRow, useHostsTableContext } from '../hooks/use_hosts_table';
import { useHostsViewContext } from '../hooks/use_hosts_view';
import { useUnifiedSearchContext } from '../hooks/use_unified_search';
import { FlyoutWrapper } from './host_details_flyout/flyout_wrapper';
import { DEFAULT_PAGE_SIZE } from '../constants';
import { FilterAction } from './table/filter_action';
Expand All @@ -20,7 +19,6 @@ const PAGE_SIZE_OPTIONS = [5, 10, 20];

export const HostsTable = () => {
const { loading } = useHostsViewContext();
const { onSubmit } = useUnifiedSearchContext();

const {
columns,
Expand Down Expand Up @@ -75,18 +73,21 @@ export const HostsTable = () => {
defaultMessage: 'Loading data',
})
) : (
<NoData
titleText={i18n.translate('xpack.infra.waffle.noDataTitle', {
defaultMessage: 'There is no data to display.',
})}
bodyText={i18n.translate('xpack.infra.waffle.noDataDescription', {
<EuiEmptyPrompt
body={i18n.translate('xpack.infra.waffle.noDataDescription', {
defaultMessage: 'Try adjusting your time or filter.',
})}
refetchText={i18n.translate('xpack.infra.waffle.checkNewDataButtonLabel', {
defaultMessage: 'Check for new data',
})}
onRefetch={() => onSubmit()}
testString="noMetricsDataPrompt"
data-test-subj="hostsViewTableNoData"
layout="vertical"
title={
<h2>
{i18n.translate('xpack.infra.waffle.noDataTitle', {
defaultMessage: 'There is no data to display.',
})}
</h2>
}
hasBorder={false}
titleSize="m"
/>
)
}
Expand Down
86 changes: 46 additions & 40 deletions x-pack/plugins/infra/public/plugin.ts
Expand Up @@ -250,46 +250,52 @@ export class Plugin implements InfraClientPluginClass {
}: {
hostsEnabled: boolean;
metricsExplorerEnabled: boolean;
}): AppDeepLink[] => [
{
id: 'inventory',
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
defaultMessage: 'Inventory',
}),
path: '/inventory',
navLinkStatus: AppNavLinkStatus.visible,
},
...(hostsEnabled
? [
{
id: 'hosts',
title: i18n.translate('xpack.infra.homePage.metricsHostsTabTitle', {
defaultMessage: 'Hosts',
}),
path: '/hosts',
navLinkStatus: AppNavLinkStatus.visible,
},
]
: []),
...(metricsExplorerEnabled
? [
{
id: 'metrics-explorer',
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
defaultMessage: 'Metrics Explorer',
}),
path: '/explorer',
},
]
: []),
{
id: 'settings',
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
defaultMessage: 'Settings',
}),
path: '/settings',
},
];
}): AppDeepLink[] => {
const serverlessNavLinkStatus = this.isServerlessEnv
? AppNavLinkStatus.visible
: AppNavLinkStatus.hidden;

return [
{
id: 'inventory',
title: i18n.translate('xpack.infra.homePage.inventoryTabTitle', {
defaultMessage: 'Inventory',
}),
path: '/inventory',
navLinkStatus: serverlessNavLinkStatus,
},
...(hostsEnabled
? [
{
id: 'hosts',
title: i18n.translate('xpack.infra.homePage.metricsHostsTabTitle', {
defaultMessage: 'Hosts',
}),
path: '/hosts',
navLinkStatus: serverlessNavLinkStatus,
},
]
: []),
...(metricsExplorerEnabled
? [
{
id: 'metrics-explorer',
title: i18n.translate('xpack.infra.homePage.metricsExplorerTabTitle', {
defaultMessage: 'Metrics Explorer',
}),
path: '/explorer',
},
]
: []),
{
id: 'settings',
title: i18n.translate('xpack.infra.homePage.settingsTabTitle', {
defaultMessage: 'Settings',
}),
path: '/settings',
},
];
};

core.application.register({
id: 'metrics',
Expand Down

0 comments on commit 35be168

Please sign in to comment.