Merge remote-tracking branch 'upstream/master' into feature-secops
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"upstream": "elastic/kibana",
|
||||
"branches": [{ "name": "7.x", "checked": true }, "7.0", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"],
|
||||
"branches": [{ "name": "7.x", "checked": true }, "7.1", "7.0", "6.8", "6.7", "6.6", "6.5", "6.4", "6.3", "6.2", "6.1", "6.0", "5.6"],
|
||||
"labels": ["backport"]
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@ bower_components
|
|||
/packages/*/target
|
||||
/packages/eslint-config-kibana
|
||||
/packages/kbn-es-query/src/kuery/ast/kuery.js
|
||||
/packages/kbn-es-query/src/kuery/ast/legacy_kuery.js
|
||||
/packages/kbn-pm/dist
|
||||
/packages/kbn-plugin-generator/sao_template/template
|
||||
/packages/kbn-ui-framework/dist
|
||||
|
|
5
.github/CODEOWNERS
vendored
|
@ -11,6 +11,11 @@
|
|||
# Canvas
|
||||
/x-pack/plugins/canvas/ @elastic/kibana-canvas
|
||||
|
||||
# Code
|
||||
/x-pack/plugins/code/ @teams/code
|
||||
/x-pack/test/functional/apps/code/ @teams/code
|
||||
/x-pack/test/api_integration/apis/code/ @teams/code
|
||||
|
||||
# Machine Learning
|
||||
/x-pack/plugins/ml/ @elastic/ml-ui
|
||||
|
||||
|
|
2
.gitignore
vendored
|
@ -31,7 +31,7 @@ webpackstats.json
|
|||
!/config/kibana.yml
|
||||
coverage
|
||||
selenium
|
||||
.babelcache.json
|
||||
.babel_register_cache.json
|
||||
.webpack.babelcache
|
||||
*.swp
|
||||
*.swo
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"paths": {
|
||||
"common.ui": "src/legacy/ui",
|
||||
"data": "src/legacy/core_plugins/data",
|
||||
"server": "src/legacy/server",
|
||||
"console": "src/legacy/core_plugins/console",
|
||||
"core": "src/core",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"timelion": "src/legacy/core_plugins/timelion",
|
||||
"tagCloud": "src/legacy/core_plugins/tagcloud",
|
||||
"tsvb": "src/legacy/core_plugins/metrics",
|
||||
"kbnESQuery": "packages/kbn-es-query",
|
||||
"xpack.apm": "x-pack/plugins/apm",
|
||||
"xpack.beatsManagement": "x-pack/plugins/beats_management",
|
||||
"xpack.crossClusterReplication": "x-pack/plugins/cross_cluster_replication",
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
--
|
||||
The *Dev Tools* page contains development tools that you can use to interact
|
||||
with your data in Kibana.
|
||||
|
||||
* <<console-kibana, Console>>
|
||||
* <<xpack-profiler, Search Profiler>>
|
||||
* <<xpack-grokdebugger, Grok Debugger>>
|
||||
|
||||
|
||||
--
|
||||
|
||||
include::dev-tools/console/console.asciidoc[]
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
[[auto-formatting]]
|
||||
=== Auto Formatting
|
||||
=== Auto formatting
|
||||
|
||||
Console allows you to auto format messy requests. To do so, position the cursor on the request you would like to format
|
||||
and select Auto Indent from the action menu:
|
||||
Console can help you format requests. Select one or more requests that you
|
||||
want to format, click the action icon (image:dev-tools/console/images/wrench.png[]),
|
||||
and select *Auto indent*.
|
||||
|
||||
.Auto Indent a request
|
||||
image::images/auto_format_before.png["Auto format before",width=500,align="center"]
|
||||
For example, you might have a request that is formatted like this:
|
||||
|
||||
Console will adjust the JSON body of the request and it will now look like this:
|
||||
[role="screenshot"]
|
||||
image::dev-tools/console/images/copy-curl.png["Console close-up"]
|
||||
|
||||
.A formatted request
|
||||
image::images/auto_format_after.png["Auto format after",width=500,align="center"]
|
||||
Console adjusts the JSON body of the request to apply the indents.
|
||||
|
||||
If you select Auto Indent on a request that is already perfectly formatted, Console will collapse the
|
||||
request body to a single line per document. This is very handy when working with Elasticsearch's bulk APIs:
|
||||
[role="screenshot"]
|
||||
image::dev-tools/console/images/request.png["Console close-up"]
|
||||
|
||||
.One doc per line
|
||||
image::images/auto_format_bulk.png["Auto format bulk",width=550,align="center"]
|
||||
If you select *Auto indent* on a request that is already well formatted,
|
||||
Console collapses the request body to a single line per document.
|
||||
This is helpful when working with {es}'s {ref}/docs-bulk.html[bulk APIs].
|
||||
|
|
|
@ -1,6 +1,26 @@
|
|||
[[configuring-console]]
|
||||
=== Configuring Console
|
||||
|
||||
You can add the following options in the `config/kibana.yml` file:
|
||||
You can configure Console to your preferences.
|
||||
|
||||
[float]
|
||||
==== Configuring settings
|
||||
|
||||
*Settings* allows you to modify the font size and set the fileds for
|
||||
autocomplete.
|
||||
|
||||
[role="screenshot"]
|
||||
image::dev-tools/console/images/console-settings.png["Console settings"]
|
||||
|
||||
|
||||
[float]
|
||||
[[console-settings]]
|
||||
==== Disabling Console
|
||||
|
||||
If you don’t want to use Console, you can disable it by setting `console.enabled`
|
||||
to false in your `kibana.yml` configuration file. Changing this setting
|
||||
causes the server to regenerate assets on the next startup,
|
||||
which might cause a delay before pages start being served.
|
||||
|
||||
|
||||
|
||||
`console.enabled`:: *Default: true* Set to false to disable Console. Toggling this will cause the server to regenerate assets on the next startup, which may cause a delay before pages start being served.
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
[[console-kibana]]
|
||||
== Console
|
||||
|
||||
The Console plugin provides a UI to interact with the REST API of Elasticsearch. Console has two main areas: the *editor*,
|
||||
where you compose requests to Elasticsearch, and the *response* pane, which displays the responses to the request.
|
||||
Console enables you to interact with the REST API of {es}. *Note:* You cannot
|
||||
interact with {kib} API endpoints via Console.
|
||||
|
||||
NOTE: You cannot interact with Kibana API endpoints via the Console.
|
||||
Go to *Dev Tools > Console* to get started.
|
||||
|
||||
.The Console UI
|
||||
image::dev-tools/console/images/introduction_screen.png[Screenshot]
|
||||
Console has two main areas:
|
||||
|
||||
Console understands commands in a cURL-like syntax. For example the following Console command
|
||||
* The *editor*, where you compose requests to send to {es}.
|
||||
* The *response* pane, which displays the responses to the request.
|
||||
|
||||
[role="screenshot"]
|
||||
image::dev-tools/console/images/console.png["Console"]
|
||||
|
||||
[float]
|
||||
[[console-api]]
|
||||
=== Writing requests
|
||||
|
||||
Console understands commands in a cURL-like syntax.
|
||||
For example, the following is a `GET` request to the {es} `_search` API.
|
||||
|
||||
[source,js]
|
||||
----------------------------------
|
||||
|
@ -21,7 +31,7 @@ GET /_search
|
|||
}
|
||||
----------------------------------
|
||||
|
||||
is a simple `GET` request to Elasticsearch's `_search API`. Here is the equivalent command in cURL.
|
||||
Here is the equivalent command in cURL:
|
||||
|
||||
[source,bash]
|
||||
----------------------------------
|
||||
|
@ -33,38 +43,55 @@ curl -XGET "http://localhost:9200/_search" -d'
|
|||
}'
|
||||
----------------------------------
|
||||
|
||||
In fact, you can paste the above command into Console and it will automatically be converted into the Console syntax.
|
||||
If you paste the above command into Console, {kib} automatically converts it
|
||||
to Console syntax. Alternatively, if you want to want to see Console syntax in cURL,
|
||||
click the action icon (image:dev-tools/console/images/wrench.png[]) and select *Copy as cURL*.
|
||||
|
||||
When typing a command, Console will make context sensitive <<suggestions,suggestions>>. These suggestions can help
|
||||
you explore parameters for each API, or to just speed up typing. Console will suggest APIs, indexes and field
|
||||
names.
|
||||
For help with formatting requests, you can use Console's <<auto-formatting, auto formatting>>
|
||||
feature.
|
||||
|
||||
[[suggestions]]
|
||||
.API suggestions
|
||||
image::dev-tools/console/images/introduction_suggestion.png["Suggestions",width=400,align="center"]
|
||||
|
||||
Once you have typed a command in to the left pane, you can submit it to Elasticsearch by clicking the little green
|
||||
triangle that appears next to the URL line of the request. Notice that as you move the cursor around, the little
|
||||
triangle and wrench icons follow you around. We call this the <<action_menu,Action Menu>>. You can also select
|
||||
multiple requests and submit them all at once.
|
||||
[float]
|
||||
[[console-request]]
|
||||
=== Submitting requests
|
||||
|
||||
[[action_menu]]
|
||||
.The Action Menu
|
||||
image::dev-tools/console/images/introduction_action_menu.png["The Action Menu",width=400,align="center"]
|
||||
Once you enter a command in the editor, click the
|
||||
green triangle to submit the request to {es}.
|
||||
|
||||
When the response come back, you should see it in the right hand panel:
|
||||
You can select multiple requests and submit them together.
|
||||
Console sends the requests to {es} one by one and shows the output
|
||||
in the response pane. Submitting multiple request is helpful when you're debugging an issue or trying query
|
||||
combinations in multiple scenarios.
|
||||
|
||||
[float]
|
||||
[[console-autocomplete]]
|
||||
=== Using autocomplete
|
||||
|
||||
When typing a command, Console makes context-sensitive suggestions.
|
||||
These suggestions can help you explore parameters for each API and speed up typing.
|
||||
To configure your preferences for autocomplete, go to
|
||||
<<configuring-console, Settings>>.
|
||||
|
||||
[float]
|
||||
[[console-view-api]]
|
||||
=== Viewing API docs
|
||||
|
||||
You can view the documentation for an API endpoint by clicking
|
||||
the action icon (image:dev-tools/console/images/wrench.png[]) and selecting
|
||||
*Open documentation*.
|
||||
|
||||
[float]
|
||||
[[console-history]]
|
||||
=== Getting your request history
|
||||
|
||||
Console maintains a list of the last 500 requests that {es} successfully executed.
|
||||
To view your most recent requests, click *History*. If you select a request
|
||||
and click *Apply*, {kib} adds it to the editor at the current cursor position.
|
||||
|
||||
.The Output Pane
|
||||
image::dev-tools/console/images/introduction_output.png[Screenshot]
|
||||
|
||||
include::multi-requests.asciidoc[]
|
||||
|
||||
include::auto-formatting.asciidoc[]
|
||||
|
||||
include::keyboard-shortcuts.asciidoc[]
|
||||
|
||||
include::history.asciidoc[]
|
||||
|
||||
include::settings.asciidoc[]
|
||||
|
||||
include::configuring-console.asciidoc[]
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
[[history]]
|
||||
=== History
|
||||
|
||||
Console maintains a list of the last 500 requests that were successfully executed by Elasticsearch. The history
|
||||
is available by clicking the clock icon on the top right side of the window. The icons opens the history panel
|
||||
where you can see the old requests. You can also select a request here and it will be added to the editor at
|
||||
the current cursor position.
|
||||
|
||||
.History Panel
|
||||
image::images/history.png["History Panel"]
|
BIN
docs/dev-tools/console/images/console-settings.png
Normal file
After Width: | Height: | Size: 116 KiB |
BIN
docs/dev-tools/console/images/console.png
Normal file
After Width: | Height: | Size: 200 KiB |
BIN
docs/dev-tools/console/images/copy-curl.png
Normal file
After Width: | Height: | Size: 34 KiB |
Before Width: | Height: | Size: 23 KiB |
Before Width: | Height: | Size: 49 KiB |
Before Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 38 KiB |
BIN
docs/dev-tools/console/images/request.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/dev-tools/console/images/wrench.png
Normal file
After Width: | Height: | Size: 729 B |
|
@ -1,21 +1,22 @@
|
|||
[[keyboard-shortcuts]]
|
||||
=== Keyboard shortcuts
|
||||
|
||||
Console comes with a set of nifty keyboard shortcuts making working with it even more efficient. Here is an overview:
|
||||
The keyboard shortcuts below can help you move quickly through Console. You can
|
||||
also view these shortcuts by clicking *Help* in Console.
|
||||
|
||||
[float]
|
||||
==== General editing
|
||||
|
||||
Ctrl/Cmd + I:: Auto indent current request.
|
||||
Ctrl + Space:: Open Auto complete (even if not typing).
|
||||
Ctrl + Space:: Open Autocomplete (even if not typing).
|
||||
Ctrl/Cmd + Enter:: Submit request.
|
||||
Ctrl/Cmd + Up/Down:: Jump to the previous/next request start or end.
|
||||
Ctrl/Cmd + Alt + L:: Collapse/expand current scope.
|
||||
Ctrl/Cmd + Option + 0:: Collapse all scopes but the current one. Expand by adding a shift.
|
||||
|
||||
[float]
|
||||
==== When auto-complete is visible
|
||||
==== When autocomplete is visible
|
||||
|
||||
Down arrow:: Switch focus to auto-complete menu. Use arrows to further select a term.
|
||||
Enter/Tab:: Select the currently selected or the top most term in auto-complete menu.
|
||||
Esc:: Close auto-complete menu.
|
||||
Down arrow:: Switch focus to autocomplete menu. Use arrows to further select a term.
|
||||
Enter/Tab:: Select the currently selected or the top most term in autocomplete menu.
|
||||
Esc:: Close autocomplete menu.
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
[[multi-requests]]
|
||||
=== Multiple Requests Support
|
||||
|
||||
The Console editor allows writing multiple requests below each other. As shown in the <<console-kibana>> section, you
|
||||
can submit a request to Elasticsearch by positioning the cursor and using the <<action_menu,Action Menu>>. Similarly
|
||||
you can select multiple requests in one go:
|
||||
|
||||
.Selecting Multiple Requests
|
||||
image::images/multiple_requests.png[Multiple Requests]
|
||||
|
||||
Console will send the request one by one to Elasticsearch and show the output on the right pane as Elasticsearch responds.
|
||||
This is very handy when debugging an issue or trying query combinations in multiple scenarios.
|
||||
|
||||
Selecting multiple requests also allows you to auto format and copy them as cURL in one go.
|
|
@ -1,8 +0,0 @@
|
|||
[[console-settings]]
|
||||
=== Settings
|
||||
|
||||
Console has multiple settings you can set. All of them are available in the Settings panel. To open the panel
|
||||
click on the cog icon on the top right.
|
||||
|
||||
.Settings Panel
|
||||
image::images/settings.png["Setting Panel"]
|
|
@ -0,0 +1,9 @@
|
|||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeBadge](./kibana-plugin-public.chromebadge.md) > [iconType](./kibana-plugin-public.chromebadge.icontype.md)
|
||||
|
||||
## ChromeBadge.iconType property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
iconType?: IconType;
|
||||
```
|
|
@ -0,0 +1,19 @@
|
|||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeBadge](./kibana-plugin-public.chromebadge.md)
|
||||
|
||||
## ChromeBadge interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ChromeBadge
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [iconType](./kibana-plugin-public.chromebadge.icontype.md) | <code>IconType</code> | |
|
||||
| [text](./kibana-plugin-public.chromebadge.text.md) | <code>string</code> | |
|
||||
| [tooltip](./kibana-plugin-public.chromebadge.tooltip.md) | <code>string</code> | |
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeBadge](./kibana-plugin-public.chromebadge.md) > [text](./kibana-plugin-public.chromebadge.text.md)
|
||||
|
||||
## ChromeBadge.text property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
text: string;
|
||||
```
|
|
@ -0,0 +1,9 @@
|
|||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md) > [ChromeBadge](./kibana-plugin-public.chromebadge.md) > [tooltip](./kibana-plugin-public.chromebadge.tooltip.md)
|
||||
|
||||
## ChromeBadge.tooltip property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
tooltip: string;
|
||||
```
|
|
@ -1,45 +1,46 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md)
|
||||
|
||||
## kibana-plugin-public package
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [FlyoutRef](./kibana-plugin-public.flyoutref.md) | A FlyoutRef is a reference to an opened flyout panel. It offers methods to close the flyout panel again. If you open a flyout panel you should make sure you call <code>close()</code> when it should be closed. Since a flyout could also be closed by a user or from another flyout being opened, you must bind to the <code>onClose</code> Promise on the FlyoutRef instance. The Promise will resolve whenever the flyout was closed at which point you should discard the FlyoutRef. |
|
||||
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | |
|
||||
| [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [BasePathSetup](./kibana-plugin-public.basepathsetup.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
|
||||
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [CapabilitiesSetup](./kibana-plugin-public.capabilitiessetup.md) | Capabilities Setup. |
|
||||
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
|
||||
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
|
||||
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the start lifecycle |
|
||||
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [I18nSetup](./kibana-plugin-public.i18nsetup.md) | I18nSetup.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [InjectedMetadataSetup](./kibana-plugin-public.injectedmetadatasetup.md) | Provides access to the metadata injected by the server into the page |
|
||||
| [OverlaySetup](./kibana-plugin-public.overlaysetup.md) | |
|
||||
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a <code>PluginInitializer</code> |
|
||||
| [PluginSetupContext](./kibana-plugin-public.pluginsetupcontext.md) | The available core services passed to a plugin's <code>Plugin#setup</code> method. |
|
||||
| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
|
||||
| [ChromeSetup](./kibana-plugin-public.chromesetup.md) | |
|
||||
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
|
||||
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
|
||||
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
|
||||
| [ToastInput](./kibana-plugin-public.toastinput.md) | |
|
||||
| [UiSettingsSetup](./kibana-plugin-public.uisettingssetup.md) | |
|
||||
|
||||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-public](./kibana-plugin-public.md)
|
||||
|
||||
## kibana-plugin-public package
|
||||
|
||||
## Classes
|
||||
|
||||
| Class | Description |
|
||||
| --- | --- |
|
||||
| [FlyoutRef](./kibana-plugin-public.flyoutref.md) | A FlyoutRef is a reference to an opened flyout panel. It offers methods to close the flyout panel again. If you open a flyout panel you should make sure you call <code>close()</code> when it should be closed. Since a flyout could also be closed by a user or from another flyout being opened, you must bind to the <code>onClose</code> Promise on the FlyoutRef instance. The Promise will resolve whenever the flyout was closed at which point you should discard the FlyoutRef. |
|
||||
| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | |
|
||||
| [UiSettingsClient](./kibana-plugin-public.uisettingsclient.md) | |
|
||||
|
||||
## Interfaces
|
||||
|
||||
| Interface | Description |
|
||||
| --- | --- |
|
||||
| [BasePathSetup](./kibana-plugin-public.basepathsetup.md) | Provides access to the 'server.basePath' configuration option in kibana.yml |
|
||||
| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. |
|
||||
| [CapabilitiesSetup](./kibana-plugin-public.capabilitiessetup.md) | Capabilities Setup. |
|
||||
| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | |
|
||||
| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | |
|
||||
| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | |
|
||||
| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the start lifecycle |
|
||||
| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. |
|
||||
| [I18nSetup](./kibana-plugin-public.i18nsetup.md) | I18nSetup.Context is required by any localizable React component from @<!-- -->kbn/i18n and @<!-- -->elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. |
|
||||
| [InjectedMetadataSetup](./kibana-plugin-public.injectedmetadatasetup.md) | Provides access to the metadata injected by the server into the page |
|
||||
| [OverlaySetup](./kibana-plugin-public.overlaysetup.md) | |
|
||||
| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a <code>PluginInitializer</code>. |
|
||||
| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a <code>PluginInitializer</code> |
|
||||
| [PluginSetupContext](./kibana-plugin-public.pluginsetupcontext.md) | The available core services passed to a plugin's <code>Plugin#setup</code> method. |
|
||||
| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
| --- | --- |
|
||||
| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | |
|
||||
| [ChromeSetup](./kibana-plugin-public.chromesetup.md) | |
|
||||
| [HttpSetup](./kibana-plugin-public.httpsetup.md) | |
|
||||
| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | |
|
||||
| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The <code>plugin</code> export at the root of a plugin's <code>public</code> directory should conform to this interface. |
|
||||
| [ToastInput](./kibana-plugin-public.toastinput.md) | |
|
||||
| [UiSettingsSetup](./kibana-plugin-public.uisettingssetup.md) | |
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md)
|
||||
|
||||
## AuthenticationHandler type
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md)
|
||||
|
||||
## AuthToolkit.authenticated property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md)
|
||||
|
||||
## AuthToolkit interface
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [redirected](./kibana-plugin-server.authtoolkit.redirected.md)
|
||||
|
||||
## AuthToolkit.redirected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [AuthToolkit](./kibana-plugin-server.authtoolkit.md) > [rejected](./kibana-plugin-server.authtoolkit.rejected.md)
|
||||
|
||||
## AuthToolkit.rejected property
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md) > [http](./kibana-plugin-server.corestart.http.md)
|
||||
|
||||
## CoreStart.http property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
http: HttpServiceStart;
|
||||
```
|
|
@ -0,0 +1,18 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [CoreStart](./kibana-plugin-server.corestart.md)
|
||||
|
||||
## CoreStart interface
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface CoreStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [http](./kibana-plugin-server.corestart.http.md) | <code>HttpServiceStart</code> | |
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceSetup](./kibana-plugin-server.httpservicesetup.md)
|
||||
|
||||
## HttpServiceSetup type
|
||||
|
@ -6,5 +8,5 @@
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare type HttpServiceSetup = HttpServerInfo;
|
||||
export declare type HttpServiceSetup = HttpServerSetup;
|
||||
```
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) > [isListening](./kibana-plugin-server.httpservicestart.islistening.md)
|
||||
|
||||
## HttpServiceStart.isListening property
|
||||
|
||||
Indicates if http server is listening on a port
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
isListening: () => boolean;
|
||||
```
|
|
@ -0,0 +1,19 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [HttpServiceStart](./kibana-plugin-server.httpservicestart.md)
|
||||
|
||||
## HttpServiceStart interface
|
||||
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface HttpServiceStart
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| [isListening](./kibana-plugin-server.httpservicestart.islistening.md) | <code>() => boolean</code> | Indicates if http server is listening on a port |
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [body](./kibana-plugin-server.kibanarequest.body.md)
|
||||
|
||||
## KibanaRequest.body property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [from](./kibana-plugin-server.kibanarequest.from.md)
|
||||
|
||||
## KibanaRequest.from() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [getFilteredHeaders](./kibana-plugin-server.kibanarequest.getfilteredheaders.md)
|
||||
|
||||
## KibanaRequest.getFilteredHeaders() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [headers](./kibana-plugin-server.kibanarequest.headers.md)
|
||||
|
||||
## KibanaRequest.headers property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md)
|
||||
|
||||
## KibanaRequest class
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [params](./kibana-plugin-server.kibanarequest.params.md)
|
||||
|
||||
## KibanaRequest.params property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [path](./kibana-plugin-server.kibanarequest.path.md)
|
||||
|
||||
## KibanaRequest.path property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [KibanaRequest](./kibana-plugin-server.kibanarequest.md) > [query](./kibana-plugin-server.kibanarequest.query.md)
|
||||
|
||||
## KibanaRequest.query property
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
| [AuthToolkit](./kibana-plugin-server.authtoolkit.md) | A tool set defining an outcome of Auth interceptor for incoming request. |
|
||||
| [CallAPIOptions](./kibana-plugin-server.callapioptions.md) | The set of options that defines how API call should be made and result be processed. |
|
||||
| [CoreSetup](./kibana-plugin-server.coresetup.md) | |
|
||||
| [CoreStart](./kibana-plugin-server.corestart.md) | |
|
||||
| [ElasticsearchServiceSetup](./kibana-plugin-server.elasticsearchservicesetup.md) | |
|
||||
| [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) | |
|
||||
| [Logger](./kibana-plugin-server.logger.md) | Logger exposes all the necessary methods to log any type of information and this is the interface used by the logging consumers including plugins. |
|
||||
| [LoggerFactory](./kibana-plugin-server.loggerfactory.md) | The single purpose of <code>LoggerFactory</code> interface is to define a way to retrieve a context-based logger instance. |
|
||||
| [LogMeta](./kibana-plugin-server.logmeta.md) | Contextual metadata |
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md)
|
||||
|
||||
## OnRequestHandler type
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md)
|
||||
|
||||
## OnRequestToolkit interface
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [next](./kibana-plugin-server.onrequesttoolkit.next.md)
|
||||
|
||||
## OnRequestToolkit.next property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md)
|
||||
|
||||
## OnRequestToolkit.redirected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) > [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md)
|
||||
|
||||
## OnRequestToolkit.rejected property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) > [http](./kibana-plugin-server.pluginsetupcontext.http.md)
|
||||
|
||||
## PluginSetupContext.http property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [delete](./kibana-plugin-server.router.delete.md)
|
||||
|
||||
## Router.delete() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [get](./kibana-plugin-server.router.get.md)
|
||||
|
||||
## Router.get() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [getRoutes](./kibana-plugin-server.router.getroutes.md)
|
||||
|
||||
## Router.getRoutes() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md)
|
||||
|
||||
## Router class
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [path](./kibana-plugin-server.router.path.md)
|
||||
|
||||
## Router.path property
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [post](./kibana-plugin-server.router.post.md)
|
||||
|
||||
## Router.post() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [put](./kibana-plugin-server.router.put.md)
|
||||
|
||||
## Router.put() method
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index) > [kibana-plugin-server](./kibana-plugin-server.md) > [Router](./kibana-plugin-server.router.md) > [routes](./kibana-plugin-server.router.routes.md)
|
||||
|
||||
## Router.routes property
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
`xpack.infra.enabled`:: Set to `false` to disable the Logs and Infrastructure UI plugin {kib}. Defaults to `true`.
|
||||
|
||||
`xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`.
|
||||
`xpack.infra.sources.default.logAlias`:: Index pattern for matching indices that contain log data. Defaults to `filebeat-*,kibana_sample_data_logs*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`.
|
||||
|
||||
`xpack.infra.sources.default.metricAlias`:: Index pattern for matching indices that contain Metricbeat data. Defaults to `metricbeat-*`.
|
||||
`xpack.infra.sources.default.metricAlias`:: Index pattern for matching indices that contain Metricbeat data. Defaults to `metricbeat-*`. To match multiple wildcard patterns, use a comma to separate the names, with no space after the comma. For example, `logstash-app1-*,default-logs-*`.
|
||||
|
||||
`xpack.infra.sources.default.fields.timestamp`:: Timestamp used to sort log entries. Defaults to `@timestamp`.
|
||||
|
||||
|
|
6
github_checks_reporter.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"appId": 26774,
|
||||
"envVars": {
|
||||
"appKey": "KIBANA_CI_REPORTER_KEY"
|
||||
}
|
||||
}
|
29
package.json
|
@ -29,7 +29,8 @@
|
|||
"build",
|
||||
"optimize",
|
||||
"built_assets",
|
||||
".eslintcache"
|
||||
".eslintcache",
|
||||
".node_binaries"
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -79,7 +80,8 @@
|
|||
"resolutions": {
|
||||
"**/@types/node": "10.12.27",
|
||||
"**/@types/hapi": "^17.0.18",
|
||||
"**/typescript": "^3.3.3333"
|
||||
"**/typescript": "^3.3.3333",
|
||||
"**/@elastic/eui/**/core-js": "2.5.3"
|
||||
},
|
||||
"workspaces": {
|
||||
"packages": [
|
||||
|
@ -102,7 +104,7 @@
|
|||
"@babel/polyfill": "^7.2.5",
|
||||
"@babel/register": "^7.0.0",
|
||||
"@elastic/datemath": "5.0.2",
|
||||
"@elastic/eui": "10.1.0",
|
||||
"@elastic/eui": "10.3.1",
|
||||
"@elastic/filesaver": "1.1.2",
|
||||
"@elastic/good": "8.1.1-kibana2",
|
||||
"@elastic/numeral": "2.3.3",
|
||||
|
@ -122,7 +124,7 @@
|
|||
"@types/recompose": "^0.30.5",
|
||||
"JSONStream": "1.1.1",
|
||||
"abortcontroller-polyfill": "^1.1.9",
|
||||
"angular": "1.6.9",
|
||||
"angular": "npm:@elastic/angular@1.6.9-kibana.0",
|
||||
"angular-aria": "1.6.6",
|
||||
"angular-elastic": "2.5.0",
|
||||
"angular-recursion": "^1.0.5",
|
||||
|
@ -134,7 +136,7 @@
|
|||
"bluebird": "3.5.3",
|
||||
"boom": "^7.2.0",
|
||||
"brace": "0.11.1",
|
||||
"cache-loader": "1.2.2",
|
||||
"cache-loader": "^3.0.0",
|
||||
"chalk": "^2.4.1",
|
||||
"color": "1.0.3",
|
||||
"commander": "2.8.1",
|
||||
|
@ -146,8 +148,8 @@
|
|||
"d3-cloud": "1.2.1",
|
||||
"del": "^3.0.0",
|
||||
"dragula": "3.7.0",
|
||||
"elasticsearch": "^15.4.1",
|
||||
"elasticsearch-browser": "^15.4.1",
|
||||
"elasticsearch": "^15.5.0",
|
||||
"elasticsearch-browser": "^15.5.0",
|
||||
"encode-uri-query": "1.0.0",
|
||||
"execa": "^1.0.0",
|
||||
"expiry-js": "0.1.7",
|
||||
|
@ -159,7 +161,7 @@
|
|||
"globby": "^8.0.1",
|
||||
"good-squeeze": "2.1.0",
|
||||
"h2o2": "^8.1.2",
|
||||
"handlebars": "4.0.13",
|
||||
"handlebars": "4.0.14",
|
||||
"hapi": "^17.5.3",
|
||||
"hapi-auth-cookie": "^9.0.0",
|
||||
"hjson": "3.1.0",
|
||||
|
@ -168,7 +170,7 @@
|
|||
"https-proxy-agent": "^2.2.1",
|
||||
"inert": "^5.1.0",
|
||||
"joi": "^13.5.2",
|
||||
"jquery": "^3.4.0",
|
||||
"jquery": "^3.4.1",
|
||||
"js-yaml": "3.4.1",
|
||||
"json-stable-stringify": "^1.0.1",
|
||||
"json-stringify-pretty-compact": "1.0.4",
|
||||
|
@ -204,7 +206,6 @@
|
|||
"raw-loader": "0.5.1",
|
||||
"react": "^16.8.0",
|
||||
"react-addons-shallow-compare": "15.6.2",
|
||||
"react-anything-sortable": "^1.7.4",
|
||||
"react-color": "^2.13.8",
|
||||
"react-dom": "^16.8.0",
|
||||
"react-grid-layout": "^0.16.2",
|
||||
|
@ -227,8 +228,9 @@
|
|||
"script-loader": "0.7.2",
|
||||
"semver": "^5.5.0",
|
||||
"stream-stream": "^1.2.6",
|
||||
"style-it": "^2.1.3",
|
||||
"style-loader": "0.23.1",
|
||||
"tar": "2.2.0",
|
||||
"tar": "4.4.8",
|
||||
"terser-webpack-plugin": "^1.1.0",
|
||||
"thread-loader": "^2.1.2",
|
||||
"tinygradient": "0.3.0",
|
||||
|
@ -258,6 +260,7 @@
|
|||
"@babel/parser": "^7.3.4",
|
||||
"@babel/types": "^7.3.4",
|
||||
"@elastic/eslint-config-kibana": "0.15.0",
|
||||
"@elastic/github-checks-reporter": "0.0.11",
|
||||
"@elastic/makelogs": "^4.4.0",
|
||||
"@kbn/es": "1.0.0",
|
||||
"@kbn/eslint-import-resolver-kibana": "2.0.0",
|
||||
|
@ -365,7 +368,7 @@
|
|||
"exit-hook": "^2.1.0",
|
||||
"faker": "1.1.0",
|
||||
"fetch-mock": "7.3.0",
|
||||
"geckodriver": "^1.16.1",
|
||||
"geckodriver": "^1.16.2",
|
||||
"getopts": "^2.2.4",
|
||||
"grunt": "1.0.3",
|
||||
"grunt-cli": "^1.2.0",
|
||||
|
@ -431,4 +434,4 @@
|
|||
"node": "10.15.2",
|
||||
"yarn": "^1.10.1"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -76,8 +76,6 @@ Creates a filter (`RangeFilter`) where the value for the given field is in the g
|
|||
|
||||
This folder contains the code corresponding to generating Elasticsearch queries using the Kibana query language.
|
||||
|
||||
It also contains code corresponding to the original implementation of Kuery (released in 6.0) which should be removed at some point (see legacy_kuery.js, legacy_kuery.peg).
|
||||
|
||||
In general, you will only need to worry about the following functions from the `ast` folder:
|
||||
|
||||
```javascript
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"lodash": "npm:@elastic/lodash@3.10.1-kibana1",
|
||||
"moment-timezone": "^0.5.14"
|
||||
"moment-timezone": "^0.5.14",
|
||||
"@kbn/i18n": "1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.2.3",
|
||||
|
|
|
@ -52,15 +52,6 @@ describe('build query', function () {
|
|||
expect(result.filter).to.eql(expectedESQueries);
|
||||
});
|
||||
|
||||
it('should throw a useful error if it looks like query is using an old, unsupported syntax', function () {
|
||||
const oldQuery = { query: 'is(foo, bar)', language: 'kuery' };
|
||||
|
||||
expect(buildQueryFromKuery).withArgs(indexPattern, [oldQuery], true).to.throwError(
|
||||
/OutdatedKuerySyntaxError/
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
it('should accept a specific date format for a kuery query into an ES query in the bool\'s filter clause', function () {
|
||||
const queries = [{ query: '@timestamp:"2018-04-03T19:04:17"', language: 'kuery' }];
|
||||
|
||||
|
|
|
@ -17,20 +17,15 @@
|
|||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromLegacyKueryExpression, fromKueryExpression, toElasticsearchQuery, nodeTypes } from '../kuery';
|
||||
import {
|
||||
fromKueryExpression,
|
||||
toElasticsearchQuery,
|
||||
nodeTypes,
|
||||
} from '../kuery';
|
||||
|
||||
export function buildQueryFromKuery(indexPattern, queries = [], allowLeadingWildcards, dateFormatTZ = null) {
|
||||
const queryASTs = queries.map(query => {
|
||||
try {
|
||||
return fromKueryExpression(query.query, { allowLeadingWildcards });
|
||||
} catch (parseError) {
|
||||
try {
|
||||
fromLegacyKueryExpression(query.query);
|
||||
} catch (legacyParseError) {
|
||||
throw parseError;
|
||||
}
|
||||
throw Error('OutdatedKuerySyntaxError');
|
||||
}
|
||||
return fromKueryExpression(query.query, { allowLeadingWildcards });
|
||||
});
|
||||
return buildQuery(indexPattern, queryASTs, { dateFormatTZ });
|
||||
}
|
||||
|
|
|
@ -22,13 +22,6 @@ import expect from '@kbn/expect';
|
|||
import { nodeTypes } from '../../node_types/index';
|
||||
import indexPatternResponse from '../../../__fixtures__/index_pattern_response.json';
|
||||
|
||||
// Helpful utility allowing us to test the PEG parser by simply checking for deep equality between
|
||||
// the nodes the parser generates and the nodes our constructor functions generate.
|
||||
|
||||
function fromLegacyKueryExpressionNoMeta(text) {
|
||||
return ast.fromLegacyKueryExpression(text, { includeMetadata: false });
|
||||
}
|
||||
|
||||
let indexPattern;
|
||||
|
||||
describe('kuery AST API', function () {
|
||||
|
@ -38,153 +31,6 @@ describe('kuery AST API', function () {
|
|||
indexPattern = indexPatternResponse;
|
||||
});
|
||||
|
||||
describe('fromLegacyKueryExpression', function () {
|
||||
|
||||
it('should return location and text metadata for each AST node', function () {
|
||||
const notNode = ast.fromLegacyKueryExpression('!foo:bar');
|
||||
expect(notNode).to.have.property('text', '!foo:bar');
|
||||
expect(notNode.location).to.eql({ min: 0, max: 8 });
|
||||
|
||||
const isNode = notNode.arguments[0];
|
||||
expect(isNode).to.have.property('text', 'foo:bar');
|
||||
expect(isNode.location).to.eql({ min: 1, max: 8 });
|
||||
|
||||
const { arguments: [ argNode1, argNode2 ] } = isNode;
|
||||
expect(argNode1).to.have.property('text', 'foo');
|
||||
expect(argNode1.location).to.eql({ min: 1, max: 4 });
|
||||
|
||||
expect(argNode2).to.have.property('text', 'bar');
|
||||
expect(argNode2.location).to.eql({ min: 5, max: 8 });
|
||||
});
|
||||
|
||||
it('should return a match all "is" function for whitespace', function () {
|
||||
const expected = nodeTypes.function.buildNode('is', '*', '*');
|
||||
const actual = fromLegacyKueryExpressionNoMeta(' ');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should return an "and" function for single literals', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [nodeTypes.literal.buildNode('foo')]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should ignore extraneous whitespace at the beginning and end of the query', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [nodeTypes.literal.buildNode('foo')]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta(' foo ');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('literals and queries separated by whitespace should be joined by an implicit "and"', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo bar');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should also support explicit "and"s as a binary operator', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo and bar');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should also support "and" as a function', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
], 'function');
|
||||
const actual = fromLegacyKueryExpressionNoMeta('and(foo, bar)');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support "or" as a binary operator', function () {
|
||||
const expected = nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo or bar');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support "or" as a function', function () {
|
||||
const expected = nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('or(foo, bar)');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support negation of queries with a "!" prefix', function () {
|
||||
const expected = nodeTypes.function.buildNode('not',
|
||||
nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]));
|
||||
const actual = fromLegacyKueryExpressionNoMeta('!or(foo, bar)');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('"and" should have a higher precedence than "or"', function () {
|
||||
const expected = nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.function.buildNode('and', [
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
nodeTypes.literal.buildNode('baz'),
|
||||
]),
|
||||
nodeTypes.literal.buildNode('qux'),
|
||||
])
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo or bar and baz or qux');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support grouping to override default precedence', function () {
|
||||
const expected = nodeTypes.function.buildNode('and', [
|
||||
nodeTypes.function.buildNode('or', [
|
||||
nodeTypes.literal.buildNode('foo'),
|
||||
nodeTypes.literal.buildNode('bar'),
|
||||
]),
|
||||
nodeTypes.literal.buildNode('baz'),
|
||||
]);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('(foo or bar) and baz');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support a shorthand operator syntax for "is" functions', function () {
|
||||
const expected = nodeTypes.function.buildNode('is', 'foo', 'bar', true);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('foo:bar');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support a shorthand operator syntax for inclusive "range" functions', function () {
|
||||
const argumentNodes = [
|
||||
nodeTypes.literal.buildNode('bytes'),
|
||||
nodeTypes.literal.buildNode(1000),
|
||||
nodeTypes.literal.buildNode(8000),
|
||||
];
|
||||
const expected = nodeTypes.function.buildNodeWithArgumentNodes('range', argumentNodes);
|
||||
const actual = fromLegacyKueryExpressionNoMeta('bytes:[1000 to 8000]');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should support functions with named arguments', function () {
|
||||
const expected = nodeTypes.function.buildNode('range', 'bytes', { gt: 1000, lt: 8000 });
|
||||
const actual = fromLegacyKueryExpressionNoMeta('range(bytes, gt=1000, lt=8000)');
|
||||
expect(actual).to.eql(expected);
|
||||
});
|
||||
|
||||
it('should throw an error for unknown functions', function () {
|
||||
expect(ast.fromLegacyKueryExpression).withArgs('foo(bar)').to.throwException(/Unknown function "foo"/);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fromKueryExpression', function () {
|
||||
|
||||
it('should return a match all "is" function for whitespace', function () {
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
import { nodeTypes } from '../node_types/index';
|
||||
import { parse as parseKuery } from './kuery';
|
||||
import { parse as parseLegacyKuery } from './legacy_kuery';
|
||||
import { KQLSyntaxError } from '../errors';
|
||||
|
||||
export function fromLiteralExpression(expression, parseOptions) {
|
||||
parseOptions = {
|
||||
|
@ -31,12 +31,16 @@ export function fromLiteralExpression(expression, parseOptions) {
|
|||
return fromExpression(expression, parseOptions, parseKuery);
|
||||
}
|
||||
|
||||
export function fromLegacyKueryExpression(expression, parseOptions) {
|
||||
return fromExpression(expression, parseOptions, parseLegacyKuery);
|
||||
}
|
||||
|
||||
export function fromKueryExpression(expression, parseOptions) {
|
||||
return fromExpression(expression, parseOptions, parseKuery);
|
||||
try {
|
||||
return fromExpression(expression, parseOptions, parseKuery);
|
||||
} catch (error) {
|
||||
if (error.name === 'SyntaxError') {
|
||||
throw new KQLSyntaxError(error, expression);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function fromExpression(expression, parseOptions = {}, parse = parseKuery) {
|
||||
|
@ -46,11 +50,12 @@ function fromExpression(expression, parseOptions = {}, parse = parseKuery) {
|
|||
|
||||
parseOptions = {
|
||||
...parseOptions,
|
||||
helpers: { nodeTypes }
|
||||
helpers: { nodeTypes },
|
||||
};
|
||||
|
||||
return parse(expression, parseOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @params {String} indexPattern
|
||||
* @params {Object} config - contains the dateFormatTZ
|
||||
|
|
|
@ -66,8 +66,11 @@ Expression
|
|||
/ FieldValueExpression
|
||||
/ ValueExpression
|
||||
|
||||
Field "fieldName"
|
||||
= Literal
|
||||
|
||||
FieldRangeExpression
|
||||
= field:Literal Space* operator:RangeOperator Space* value:Literal {
|
||||
= field:Field Space* operator:RangeOperator Space* value:Literal {
|
||||
if (value.type === 'cursor') {
|
||||
return {
|
||||
...value,
|
||||
|
@ -79,7 +82,7 @@ FieldRangeExpression
|
|||
}
|
||||
|
||||
FieldValueExpression
|
||||
= field:Literal Space* ':' Space* partial:ListOfValues {
|
||||
= field:Field Space* ':' Space* partial:ListOfValues {
|
||||
if (partial.type === 'cursor') {
|
||||
return {
|
||||
...partial,
|
||||
|
@ -154,7 +157,7 @@ NotListOfValues
|
|||
}
|
||||
/ ListOfValues
|
||||
|
||||
Value
|
||||
Value "value"
|
||||
= value:QuotedString {
|
||||
if (value.type === 'cursor') return value;
|
||||
const isPhrase = buildLiteralNode(true);
|
||||
|
@ -171,19 +174,19 @@ Value
|
|||
return (field) => buildFunctionNode('is', [field, value, isPhrase]);
|
||||
}
|
||||
|
||||
Or
|
||||
Or "OR"
|
||||
= Space+ 'or'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneOr
|
||||
|
||||
And
|
||||
And "AND"
|
||||
= Space+ 'and'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneAnd
|
||||
|
||||
Not
|
||||
Not "NOT"
|
||||
= 'not'i Space+
|
||||
/ &{ return errorOnLuceneSyntax; } LuceneNot
|
||||
|
||||
Literal
|
||||
Literal "literal"
|
||||
= QuotedString / UnquotedLiteral
|
||||
|
||||
QuotedString
|
||||
|
@ -277,7 +280,7 @@ RangeOperator
|
|||
/ '<' { return 'lt'; }
|
||||
/ '>' { return 'gt'; }
|
||||
|
||||
Space
|
||||
Space "whitespace"
|
||||
= [\ \t\r\n]
|
||||
|
||||
Cursor
|
||||
|
|
|
@ -1,153 +0,0 @@
|
|||
/*
|
||||
* Kuery parser
|
||||
*
|
||||
* To generate the parsing module (legacy_kuery.js), run `grunt peg`
|
||||
* To watch changes and generate on file change, run `grunt watch:peg`
|
||||
*/
|
||||
|
||||
/*
|
||||
* Initialization block
|
||||
*/
|
||||
{
|
||||
var nodeTypes = options.helpers.nodeTypes;
|
||||
|
||||
if (options.includeMetadata === undefined) {
|
||||
options.includeMetadata = true;
|
||||
}
|
||||
|
||||
function addMeta(source, text, location) {
|
||||
if (options.includeMetadata) {
|
||||
return Object.assign(
|
||||
{},
|
||||
source,
|
||||
{
|
||||
text: text,
|
||||
location: simpleLocation(location),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
function simpleLocation(location) {
|
||||
// Returns an object representing the position of the function within the expression,
|
||||
// demarcated by the position of its first character and last character. We calculate these values
|
||||
// using the offset because the expression could span multiple lines, and we don't want to deal
|
||||
// with column and line values.
|
||||
return {
|
||||
min: location.start.offset,
|
||||
max: location.end.offset
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
start
|
||||
= space? query:OrQuery space? {
|
||||
if (query.type === 'literal') {
|
||||
return addMeta(nodeTypes.function.buildNode('and', [query]), text(), location());
|
||||
}
|
||||
return query;
|
||||
}
|
||||
/ whitespace:[\ \t\r\n]* {
|
||||
return addMeta(nodeTypes.function.buildNode('is', '*', '*', false), text(), location());
|
||||
}
|
||||
|
||||
OrQuery
|
||||
= left:AndQuery space 'or'i space right:OrQuery {
|
||||
return addMeta(nodeTypes.function.buildNode('or', [left, right]), text(), location());
|
||||
}
|
||||
/ AndQuery
|
||||
|
||||
AndQuery
|
||||
= left:NegatedClause space 'and'i space right:AndQuery {
|
||||
return addMeta(nodeTypes.function.buildNode('and', [left, right]), text(), location());
|
||||
}
|
||||
/ left:NegatedClause space !'or'i right:AndQuery {
|
||||
return addMeta(nodeTypes.function.buildNode('and', [left, right]), text(), location());
|
||||
}
|
||||
/ NegatedClause
|
||||
|
||||
NegatedClause
|
||||
= [!] clause:Clause {
|
||||
return addMeta(nodeTypes.function.buildNode('not', clause), text(), location());
|
||||
}
|
||||
/ Clause
|
||||
|
||||
Clause
|
||||
= '(' subQuery:start ')' {
|
||||
return subQuery;
|
||||
}
|
||||
/ Term
|
||||
|
||||
Term
|
||||
= field:literal_arg_type ':' value:literal_arg_type {
|
||||
return addMeta(nodeTypes.function.buildNodeWithArgumentNodes('is', [field, value, nodeTypes.literal.buildNode(true)]), text(), location());
|
||||
}
|
||||
/ field:literal_arg_type ':[' space? gt:literal_arg_type space 'to'i space lt:literal_arg_type space? ']' {
|
||||
return addMeta(nodeTypes.function.buildNodeWithArgumentNodes('range', [field, gt, lt]), text(), location());
|
||||
}
|
||||
/ function
|
||||
/ !Keywords literal:literal_arg_type { return literal; }
|
||||
|
||||
function_name
|
||||
= first:[a-zA-Z]+ rest:[.a-zA-Z0-9_-]* { return first.join('') + rest.join('') }
|
||||
|
||||
function "function"
|
||||
= name:function_name space? '(' space? arg_list:arg_list? space? ')' {
|
||||
return addMeta(nodeTypes.function.buildNodeWithArgumentNodes(name, arg_list || []), text(), location());
|
||||
}
|
||||
|
||||
arg_list
|
||||
= first:argument rest:(space? ',' space? arg:argument {return arg})* space? ','? {
|
||||
return [first].concat(rest);
|
||||
}
|
||||
|
||||
argument
|
||||
= name:function_name space? '=' space? value:arg_type {
|
||||
return addMeta(nodeTypes.namedArg.buildNode(name, value), text(), location());
|
||||
}
|
||||
/ element:arg_type {return element}
|
||||
|
||||
arg_type
|
||||
= OrQuery
|
||||
/ literal_arg_type
|
||||
|
||||
literal_arg_type
|
||||
= literal:literal {
|
||||
var result = addMeta(nodeTypes.literal.buildNode(literal), text(), location());
|
||||
return result;
|
||||
}
|
||||
|
||||
Keywords
|
||||
= 'and'i / 'or'i
|
||||
|
||||
/* ----- Core types ----- */
|
||||
|
||||
literal "literal"
|
||||
= '"' chars:dq_char* '"' { return chars.join(''); } // double quoted string
|
||||
/ "'" chars:sq_char* "'" { return chars.join(''); } // single quoted string
|
||||
/ 'true' { return true; } // unquoted literals from here down
|
||||
/ 'false' { return false; }
|
||||
/ 'null' { return null; }
|
||||
/ string:[^\[\]()"',:=\ \t]+ { // this also matches numbers via Number()
|
||||
var result = string.join('');
|
||||
// Sort of hacky, but PEG doesn't have backtracking so
|
||||
// a number rule is hard to read, and performs worse
|
||||
if (isNaN(Number(result))) return result;
|
||||
return Number(result)
|
||||
}
|
||||
|
||||
space
|
||||
= [\ \t\r\n]+
|
||||
|
||||
dq_char
|
||||
= "\\" sequence:('"' / "\\") { return sequence; }
|
||||
/ [^"] // everything except "
|
||||
|
||||
sq_char
|
||||
= "\\" sequence:("'" / "\\") { return sequence; }
|
||||
/ [^'] // everything except '
|
||||
|
||||
integer
|
||||
= digits:[0-9]+ {return parseInt(digits.join(''))}
|
69
packages/kbn-es-query/src/kuery/errors/index.js
Normal file
|
@ -0,0 +1,69 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { repeat } from 'lodash';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const endOfInputText = i18n.translate('kbnESQuery.kql.errors.endOfInputText', {
|
||||
defaultMessage: 'end of input',
|
||||
});
|
||||
|
||||
export class KQLSyntaxError extends Error {
|
||||
|
||||
constructor(error, expression) {
|
||||
const grammarRuleTranslations = {
|
||||
fieldName: i18n.translate('kbnESQuery.kql.errors.fieldNameText', {
|
||||
defaultMessage: 'field name',
|
||||
}),
|
||||
value: i18n.translate('kbnESQuery.kql.errors.valueText', {
|
||||
defaultMessage: 'value',
|
||||
}),
|
||||
literal: i18n.translate('kbnESQuery.kql.errors.literalText', {
|
||||
defaultMessage: 'literal',
|
||||
}),
|
||||
whitespace: i18n.translate('kbnESQuery.kql.errors.whitespaceText', {
|
||||
defaultMessage: 'whitespace',
|
||||
}),
|
||||
};
|
||||
|
||||
const translatedExpectations = error.expected.map((expected) => {
|
||||
return grammarRuleTranslations[expected.description] || expected.description;
|
||||
});
|
||||
|
||||
const translatedExpectationText = translatedExpectations.join(', ');
|
||||
|
||||
const message = i18n.translate('kbnESQuery.kql.errors.syntaxError', {
|
||||
defaultMessage: 'Expected {expectedList} but {foundInput} found.',
|
||||
values: {
|
||||
expectedList: translatedExpectationText,
|
||||
foundInput: error.found ? `"${error.found}"` : endOfInputText,
|
||||
},
|
||||
});
|
||||
|
||||
const fullMessage = [
|
||||
message,
|
||||
expression,
|
||||
repeat('-', error.location.start.offset) + '^',
|
||||
].join('\n');
|
||||
|
||||
super(fullMessage);
|
||||
this.name = 'KQLSyntaxError';
|
||||
this.shortMessage = message;
|
||||
}
|
||||
}
|
105
packages/kbn-es-query/src/kuery/errors/index.test.js
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { fromKueryExpression } from '../ast';
|
||||
|
||||
|
||||
describe('kql syntax errors', () => {
|
||||
|
||||
it('should throw an error for a field query missing a value', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('response:');
|
||||
}).toThrow('Expected "(", value, whitespace but end of input found.\n' +
|
||||
'response:\n' +
|
||||
'---------^');
|
||||
});
|
||||
|
||||
it('should throw an error for an OR query missing a right side sub-query', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('response:200 or ');
|
||||
}).toThrow('Expected "(", NOT, field name, value but end of input found.\n' +
|
||||
'response:200 or \n' +
|
||||
'----------------^');
|
||||
});
|
||||
|
||||
it('should throw an error for an OR list of values missing a right side sub-query', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('response:(200 or )');
|
||||
}).toThrow('Expected "(", NOT, value but ")" found.\n' +
|
||||
'response:(200 or )\n' +
|
||||
'-----------------^');
|
||||
});
|
||||
|
||||
it('should throw an error for a NOT query missing a sub-query', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('response:200 and not ');
|
||||
}).toThrow('Expected "(", field name, value but end of input found.\n' +
|
||||
'response:200 and not \n' +
|
||||
'---------------------^');
|
||||
});
|
||||
|
||||
it('should throw an error for a NOT list missing a sub-query', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('response:(200 and not )');
|
||||
}).toThrow('Expected "(", value but ")" found.\n' +
|
||||
'response:(200 and not )\n' +
|
||||
'----------------------^');
|
||||
});
|
||||
|
||||
it('should throw an error for unbalanced quotes', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('foo:"ba ');
|
||||
}).toThrow('Expected "(", value, whitespace but "\"" found.\n' +
|
||||
'foo:"ba \n' +
|
||||
'----^');
|
||||
});
|
||||
|
||||
it('should throw an error for unescaped quotes in a quoted string', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('foo:"ba "r"');
|
||||
}).toThrow('Expected AND, OR, end of input, whitespace but "r" found.\n' +
|
||||
'foo:"ba "r"\n' +
|
||||
'---------^');
|
||||
});
|
||||
|
||||
it('should throw an error for unescaped special characters in literals', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('foo:ba:r');
|
||||
}).toThrow('Expected AND, OR, end of input, whitespace but ":" found.\n' +
|
||||
'foo:ba:r\n' +
|
||||
'------^');
|
||||
});
|
||||
|
||||
it('should throw an error for range queries missing a value', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('foo > ');
|
||||
}).toThrow('Expected literal, whitespace but end of input found.\n' +
|
||||
'foo > \n' +
|
||||
'------^');
|
||||
});
|
||||
|
||||
it('should throw an error for range queries missing a field', () => {
|
||||
expect(() => {
|
||||
fromKueryExpression('< 1000');
|
||||
}).toThrow('Expected "(", NOT, end of input, field name, value, whitespace but "<" found.\n' +
|
||||
'< 1000\n' +
|
||||
'^');
|
||||
});
|
||||
|
||||
});
|
|
@ -20,3 +20,4 @@
|
|||
export * from './ast';
|
||||
export * from './filter_migration';
|
||||
export * from './node_types';
|
||||
export * from './errors';
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
"license": "Apache-2.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@elastic/elasticsearch": "^7.0.0-rc.2",
|
||||
"@kbn/dev-utils": "1.0.0",
|
||||
"abort-controller": "^2.0.3",
|
||||
"chalk": "^2.4.1",
|
||||
|
|
|
@ -32,10 +32,11 @@ exports.help = (defaults = {}) => {
|
|||
return dedent`
|
||||
Options:
|
||||
|
||||
--base-path Path containing cache/installations [default: ${basePath}]
|
||||
--install-path Installation path, defaults to 'source' within base-path
|
||||
--password Sets password for elastic user [default: ${password}]
|
||||
-E Additional key=value settings to pass to Elasticsearch
|
||||
--base-path Path containing cache/installations [default: ${basePath}]
|
||||
--install-path Installation path, defaults to 'source' within base-path
|
||||
--password Sets password for elastic user [default: ${password}]
|
||||
--password.[user] Sets password for native realm user [default: ${password}]
|
||||
-E Additional key=value settings to pass to Elasticsearch
|
||||
|
||||
Example:
|
||||
|
||||
|
|
|
@ -29,14 +29,15 @@ exports.help = (defaults = {}) => {
|
|||
return dedent`
|
||||
Options:
|
||||
|
||||
--license Run with a 'oss', 'basic', or 'trial' license [default: ${license}]
|
||||
--version Version of ES to download [default: ${defaults.version}]
|
||||
--base-path Path containing cache/installations [default: ${basePath}]
|
||||
--install-path Installation path, defaults to 'source' within base-path
|
||||
--data-archive Path to zip or tarball containing an ES data directory to seed the cluster with.
|
||||
--password Sets password for elastic user [default: ${password}]
|
||||
-E Additional key=value settings to pass to Elasticsearch
|
||||
--download-only Download the snapshot but don't actually start it
|
||||
--license Run with a 'oss', 'basic', or 'trial' license [default: ${license}]
|
||||
--version Version of ES to download [default: ${defaults.version}]
|
||||
--base-path Path containing cache/installations [default: ${basePath}]
|
||||
--install-path Installation path, defaults to 'source' within base-path
|
||||
--data-archive Path to zip or tarball containing an ES data directory to seed the cluster with.
|
||||
--password Sets password for elastic user [default: ${password}]
|
||||
--password.[user] Sets password for native realm user [default: ${password}]
|
||||
-E Additional key=value settings to pass to Elasticsearch
|
||||
--download-only Download the snapshot but don't actually start it
|
||||
|
||||
Example:
|
||||
|
||||
|
@ -69,6 +70,6 @@ exports.run = async (defaults = {}) => {
|
|||
await cluster.extractDataDirectory(installPath, options.dataArchive);
|
||||
}
|
||||
|
||||
await cluster.run(installPath, { esArgs: options.esArgs });
|
||||
await cluster.run(installPath, options);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -22,7 +22,13 @@ const chalk = require('chalk');
|
|||
const path = require('path');
|
||||
const { downloadSnapshot, installSnapshot, installSource, installArchive } = require('./install');
|
||||
const { ES_BIN } = require('./paths');
|
||||
const { log: defaultLog, parseEsLog, extractConfigFiles, decompress } = require('./utils');
|
||||
const {
|
||||
log: defaultLog,
|
||||
parseEsLog,
|
||||
extractConfigFiles,
|
||||
decompress,
|
||||
NativeRealm,
|
||||
} = require('./utils');
|
||||
const { createCliError } = require('./errors');
|
||||
const { promisify } = require('util');
|
||||
const treeKillAsync = promisify(require('tree-kill'));
|
||||
|
@ -215,7 +221,7 @@ exports.Cluster = class Cluster {
|
|||
* @property {Array} options.esArgs
|
||||
* @return {undefined}
|
||||
*/
|
||||
_exec(installPath, { esArgs = [] }) {
|
||||
_exec(installPath, options = {}) {
|
||||
if (this._process || this._outcome) {
|
||||
throw new Error('ES has already been started');
|
||||
}
|
||||
|
@ -223,7 +229,7 @@ exports.Cluster = class Cluster {
|
|||
this._log.info(chalk.bold('Starting'));
|
||||
this._log.indent(4);
|
||||
|
||||
const args = extractConfigFiles(esArgs, installPath, {
|
||||
const args = extractConfigFiles(options.esArgs || [], installPath, {
|
||||
log: this._log,
|
||||
}).reduce((acc, cur) => acc.concat(['-E', cur]), []);
|
||||
|
||||
|
@ -236,7 +242,23 @@ exports.Cluster = class Cluster {
|
|||
|
||||
this._process.stdout.on('data', data => {
|
||||
const lines = parseEsLog(data.toString());
|
||||
lines.forEach(line => this._log.info(line.formattedMessage));
|
||||
lines.forEach(line => {
|
||||
this._log.info(line.formattedMessage);
|
||||
|
||||
// once we have the port we can stop checking for it
|
||||
if (this.httpPort) {
|
||||
return;
|
||||
}
|
||||
|
||||
const httpAddressMatch = line.message.match(
|
||||
/HttpServer.+publish_address {[0-9.]+:([0-9]+)/
|
||||
);
|
||||
|
||||
if (httpAddressMatch) {
|
||||
this.httpPort = httpAddressMatch[1];
|
||||
new NativeRealm(options.password, this.httpPort, this._log).setPasswords(options);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this._process.stderr.on('data', data => this._log.error(chalk.red(data.toString())));
|
||||
|
|
|
@ -23,3 +23,4 @@ exports.parseEsLog = require('./parse_es_log').parseEsLog;
|
|||
exports.findMostRecentlyChanged = require('./find_most_recently_changed').findMostRecentlyChanged;
|
||||
exports.extractConfigFiles = require('./extract_config_files').extractConfigFiles;
|
||||
exports.decompress = require('./decompress').decompress;
|
||||
exports.NativeRealm = require('./native_realm').NativeRealm;
|
||||
|
|
82
packages/kbn-es/src/utils/native_realm.js
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch');
|
||||
const chalk = require('chalk');
|
||||
|
||||
const { log: defaultLog } = require('./log');
|
||||
|
||||
exports.NativeRealm = class NativeRealm {
|
||||
constructor(elasticPassword, port, log = defaultLog) {
|
||||
this._client = new Client({ node: `http://elastic:${elasticPassword}@localhost:${port}` });
|
||||
this._elasticPassword = elasticPassword;
|
||||
this._log = log;
|
||||
}
|
||||
|
||||
async setPassword(username, password = this._elasticPassword) {
|
||||
this._log.info(`setting ${chalk.bold(username)} password to ${chalk.bold(password)}`);
|
||||
|
||||
try {
|
||||
await this._client.security.changePassword({
|
||||
username,
|
||||
refresh: 'wait_for',
|
||||
body: {
|
||||
password,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
this._log.error(
|
||||
chalk.red(`unable to set password for ${chalk.bold(username)}: ${e.message}`)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
async setPasswords(options) {
|
||||
if (!(await this.isSecurityEnabled())) {
|
||||
this._log.info('security is not enabled, unable to set native realm passwords');
|
||||
return;
|
||||
}
|
||||
|
||||
(await this.getReservedUsers()).forEach(user => {
|
||||
this.setPassword(user, options[`password.${user}`]);
|
||||
});
|
||||
}
|
||||
|
||||
async getReservedUsers() {
|
||||
const users = await this._client.security.getUser();
|
||||
|
||||
return Object.keys(users.body).reduce((acc, user) => {
|
||||
if (users.body[user].metadata._reserved === true) {
|
||||
acc.push(user);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
async isSecurityEnabled() {
|
||||
try {
|
||||
const {
|
||||
body: { features },
|
||||
} = await this._client.xpack.info({ categories: 'features' });
|
||||
return features.security && features.security.enabled && features.security.available;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
221
packages/kbn-es/src/utils/native_realm.test.js
Normal file
|
@ -0,0 +1,221 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
const { NativeRealm } = require('./native_realm');
|
||||
|
||||
jest.genMockFromModule('@elastic/elasticsearch');
|
||||
jest.mock('@elastic/elasticsearch');
|
||||
|
||||
const { Client } = require('@elastic/elasticsearch');
|
||||
|
||||
const mockClient = {
|
||||
xpack: {
|
||||
info: jest.fn(),
|
||||
},
|
||||
security: {
|
||||
changePassword: jest.fn(),
|
||||
getUser: jest.fn(),
|
||||
},
|
||||
};
|
||||
Client.mockImplementation(() => mockClient);
|
||||
|
||||
const log = {
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
};
|
||||
|
||||
let nativeRealm;
|
||||
|
||||
beforeEach(() => {
|
||||
nativeRealm = new NativeRealm('changeme', '9200', log);
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
function mockXPackInfo(available, enabled) {
|
||||
mockClient.xpack.info.mockImplementation(() => ({
|
||||
body: {
|
||||
features: {
|
||||
security: {
|
||||
available,
|
||||
enabled,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
describe('isSecurityEnabled', () => {
|
||||
test('returns true if enabled and available', async () => {
|
||||
mockXPackInfo(true, true);
|
||||
expect(await nativeRealm.isSecurityEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
test('returns false if not available', async () => {
|
||||
mockXPackInfo(false, true);
|
||||
expect(await nativeRealm.isSecurityEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
test('returns false if not enabled', async () => {
|
||||
mockXPackInfo(true, false);
|
||||
expect(await nativeRealm.isSecurityEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
test('logs exception and returns false', async () => {
|
||||
mockClient.xpack.info.mockImplementation(() => {
|
||||
throw new Error('ResponseError');
|
||||
});
|
||||
expect(await nativeRealm.isSecurityEnabled()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPasswords', () => {
|
||||
it('uses provided passwords', async () => {
|
||||
mockXPackInfo(true, true);
|
||||
|
||||
mockClient.security.getUser.mockImplementation(() => ({
|
||||
body: {
|
||||
kibana: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
non_native: {
|
||||
metadata: {
|
||||
_reserved: false,
|
||||
},
|
||||
},
|
||||
logstash_system: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
elastic: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
beats_system: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
await nativeRealm.setPasswords({
|
||||
'password.kibana': 'bar',
|
||||
});
|
||||
|
||||
expect(mockClient.security.changePassword.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"password": "bar",
|
||||
},
|
||||
"refresh": "wait_for",
|
||||
"username": "kibana",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"password": "changeme",
|
||||
},
|
||||
"refresh": "wait_for",
|
||||
"username": "logstash_system",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"password": "changeme",
|
||||
},
|
||||
"refresh": "wait_for",
|
||||
"username": "elastic",
|
||||
},
|
||||
],
|
||||
Array [
|
||||
Object {
|
||||
"body": Object {
|
||||
"password": "changeme",
|
||||
},
|
||||
"refresh": "wait_for",
|
||||
"username": "beats_system",
|
||||
},
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getReservedUsers', () => {
|
||||
it('returns array of reserved usernames', async () => {
|
||||
mockClient.security.getUser.mockImplementation(() => ({
|
||||
body: {
|
||||
kibana: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
non_native: {
|
||||
metadata: {
|
||||
_reserved: false,
|
||||
},
|
||||
},
|
||||
logstash_system: {
|
||||
metadata: {
|
||||
_reserved: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
expect(await nativeRealm.getReservedUsers()).toEqual(['kibana', 'logstash_system']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setPassword', () => {
|
||||
it('sets password for provided user', async () => {
|
||||
await nativeRealm.setPassword('kibana', 'foo');
|
||||
expect(mockClient.security.changePassword).toHaveBeenCalledWith({
|
||||
body: { password: 'foo' },
|
||||
refresh: 'wait_for',
|
||||
username: 'kibana',
|
||||
});
|
||||
});
|
||||
|
||||
it('logs error', async () => {
|
||||
mockClient.security.changePassword.mockImplementation(() => {
|
||||
throw new Error('SomeError');
|
||||
});
|
||||
|
||||
await nativeRealm.setPassword('kibana', 'foo');
|
||||
expect(log.error.mock.calls).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Array [
|
||||
"[31munable to set password for [1mkibana[22m: SomeError[39m",
|
||||
],
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
|
@ -61,7 +61,7 @@ export default function (kibana) {
|
|||
api: [],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
|
@ -69,7 +69,7 @@ export default function (kibana) {
|
|||
api: [],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: ['config'],
|
||||
read: [],
|
||||
},
|
||||
ui: ['show'],
|
||||
},
|
||||
|
|
|
@ -22,7 +22,7 @@ import { format as formatUrl } from 'url';
|
|||
import request from 'request';
|
||||
import { delay } from 'bluebird';
|
||||
|
||||
export const DEFAULT_SUPERUSER_PASS = 'iamsuperuser';
|
||||
export const DEFAULT_SUPERUSER_PASS = 'changeme';
|
||||
|
||||
async function updateCredentials(port, auth, username, password, retries = 10) {
|
||||
const result = await new Promise((resolve, reject) =>
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
"html": "1.0.0",
|
||||
"html-loader": "^0.5.5",
|
||||
"imports-loader": "^0.8.0",
|
||||
"jquery": "^3.4.0",
|
||||
"jquery": "^3.4.1",
|
||||
"keymirror": "0.1.1",
|
||||
"moment": "^2.20.1",
|
||||
"node-sass": "^4.9.4",
|
||||
|
|
252
rfcs/text/0002_encrypted_attributes.md
Normal file
|
@ -0,0 +1,252 @@
|
|||
- Start Date: 2019-03-22
|
||||
- RFC PR: [#33740](https://github.com/elastic/kibana/pull/33740)
|
||||
- Kibana Issue: (leave this empty)
|
||||
|
||||
# Summary
|
||||
|
||||
In order to support the action service we need a way to encrypt/decrypt
|
||||
attributes on saved objects that works with security and spaces filtering as
|
||||
well as performing audit logging. Sufficiently hides the private key used and
|
||||
removes encrypted attributes from being exposed through regular means.
|
||||
|
||||
# Basic example
|
||||
|
||||
Register saved object type with the `encrypted_saved_objects` plugin:
|
||||
|
||||
```typescript
|
||||
server.plugins.encrypted_saved_objects.registerType({
|
||||
type: 'server-action',
|
||||
attributesToEncrypt: new Set(['credentials', 'apiKey']),
|
||||
});
|
||||
```
|
||||
|
||||
Use the same API to create saved objects with encrypted attributes as for any other saved object type:
|
||||
|
||||
```typescript
|
||||
const savedObject = await server.savedObjects
|
||||
.getScopedSavedObjectsClient(request)
|
||||
.create('server-action', {
|
||||
name: 'my-server-action',
|
||||
data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
|
||||
credentials: { username: 'some-user', password: 'some-password' },
|
||||
apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb'
|
||||
});
|
||||
|
||||
// savedObject = {
|
||||
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
|
||||
// type: 'server-action',
|
||||
// name: 'my-server-action',
|
||||
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
|
||||
// };
|
||||
|
||||
```
|
||||
|
||||
Use dedicated method to retrieve saved object with decrypted attributes on behalf of Kibana internal user:
|
||||
|
||||
```typescript
|
||||
const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser(
|
||||
'server-action',
|
||||
'dd9750b9-ef0a-444c-8405-4dfcc2e9d670'
|
||||
);
|
||||
|
||||
// savedObject = {
|
||||
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
|
||||
// type: 'server-action',
|
||||
// name: 'my-server-action',
|
||||
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
|
||||
// credentials: { username: 'some-user', password: 'some-password' },
|
||||
// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb',
|
||||
// };
|
||||
```
|
||||
|
||||
# Motivation
|
||||
|
||||
Main motivation is the storage and usage of third-party credentials for use with
|
||||
the action service to do notifications. Also perform other types integrations,
|
||||
call webhooks using tokens.
|
||||
|
||||
# Detailed design
|
||||
|
||||
In order for this to be in basic it needs to be done as a wrapper around the
|
||||
saved object client. This can be added from the `x-pack` plugin.
|
||||
|
||||
## General
|
||||
|
||||
To be able to manage saved objects with encrypted attributes from any plugin one should
|
||||
do the following:
|
||||
|
||||
1. Define `encrypted_saved_objects` plugin as a dependency.
|
||||
2. Add attributes to be encrypted in `mappings.json` file for the respective saved object type. These attributes should
|
||||
always have a `binary` type since they'll contain encrypted content as a `Base64` encoded string and should never be
|
||||
searchable or analyzed. This makes defining of attributes that require encryption explicit and auditable, and significantly
|
||||
simplifies implementation:
|
||||
```json
|
||||
{
|
||||
"server-action": {
|
||||
"properties": {
|
||||
"name": { "type": "keyword" },
|
||||
"data": {
|
||||
"properties": {
|
||||
"location": { "type": "geo_shape" },
|
||||
"email": { "type": "text" }
|
||||
}
|
||||
},
|
||||
"credentials": { "type": "binary" },
|
||||
"apiKey": { "type": "binary" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
3. Register saved object type and attributes that should be encrypted with `encrypted_saved_objects` plugin:
|
||||
```typescript
|
||||
server.plugins.encrypted_saved_objects.registerType({
|
||||
type: 'server-action',
|
||||
attributesToEncrypt: new Set(['credentials', 'apiKey']),
|
||||
attributesToExcludeFromAAD: new Set(['data']),
|
||||
});
|
||||
```
|
||||
|
||||
Notice the optional `attributesToExcludeFromAAD` property, it allows one to exclude some of the saved object attributes
|
||||
from Additional authenticated data (AAD), read more about that below in `Encryption and decryption` section.
|
||||
|
||||
Since `encrypted_saved_objects` adds its own wrapper (`EncryptedSavedObjectsClientWrapper`) into `SavedObjectsClient`
|
||||
wrapper chain consumers will be able to create, update, delete and retrieve saved objects using standard Saved Objects API.
|
||||
Two main responsibilities of the wrapper are:
|
||||
|
||||
* It encrypts attributes that are supposed to be encrypted during `create`, `bulkCreate` and `update` operations
|
||||
* It strips encrypted attributes from **any** saved object returned from the Saved Objects API
|
||||
|
||||
As noted above the wrapper is stripping encrypted attributes from saved objects returned from the API methods, that means
|
||||
that there is no way at all to retrieve encrypted attributes using standard Saved Objects API unless `encrypted_saved_objects`
|
||||
plugin is disabled. This potentially can lead to the situation when consumer retrieves saved object, updates its non-encrypted
|
||||
properties and passes that same object to the `update` Saved Objects API method without re-defining encrypted attributes. In
|
||||
this case only specified attributes will be updated and encrypted attributes will stay untouched. And if these updated
|
||||
attributes are included into AAD, that is true by default for all attributes unless they are specifically excluded via
|
||||
`attributesToExcludeFromAAD`, then it will be no longer possible to decrypt encrypted attributes. At this stage we consider
|
||||
this as a developer mistake and don't prevent it from happening in any way apart from logging this type of event. Partial
|
||||
update of only attributes that are not the part of AAD will not cause this issue.
|
||||
|
||||
Saved object ID is an essential part of AAD used during encryption process and hence should be as hard to guess as possible.
|
||||
To fulfil this requirement wrapper generates highly random IDs (UUIDv4) for the saved objects that contain encrypted
|
||||
attributes and hence consumers are not allowed to specify ID when calling `create` or `bulkCreate` method and if they try
|
||||
to do so the error will be thrown.
|
||||
|
||||
To reduce the risk of unintentional decryption and consequent leaking of the sensitive information there is only one way
|
||||
to retrieve saved object and decrypt its encrypted attributes and it's exposed only through `encrypted_saved_objects` plugin:
|
||||
|
||||
```typescript
|
||||
const savedObject = await server.plugins.encrypted_saved_objects.getDecryptedAsInternalUser(
|
||||
'server-action',
|
||||
'dd9750b9-ef0a-444c-8405-4dfcc2e9d670'
|
||||
);
|
||||
|
||||
// savedObject = {
|
||||
// id: 'dd9750b9-ef0a-444c-8405-4dfcc2e9d670',
|
||||
// type: 'server-action',
|
||||
// name: 'my-server-action',
|
||||
// data: { location: 'BBOX (100.0, ..., 0.0)', email: '<html>...</html>' },
|
||||
// credentials: { username: 'some-user', password: 'some-password' },
|
||||
// apiKey: 'dGhpcyBpcyBub3QgYSByZWFsIHRva2VuIGJ1dCBpdCBpcyBvb',
|
||||
// };
|
||||
```
|
||||
|
||||
As can be seen from the method name, the request to retrieve saved object and decrypt its attributes is performed on
|
||||
behalf of the internal Kibana user and hence isn't supposed to be called within user request context.
|
||||
|
||||
**Note:** the fact that saved object with encrypted attributes is created using standard Saved Objects API within a
|
||||
particular user and space context, but retrieved out of any context makes it unclear how consumers are supposed to
|
||||
provide that context and retrieve saved object from a particular space. Current plan for `getDecryptedAsInternalUser`
|
||||
method is to accept a third `BaseOptions` argument that allows consumers to specify `namespace` that they can retrieve
|
||||
from the request using public `spaces` plugin API.
|
||||
|
||||
## Encryption and decryption
|
||||
|
||||
Saved object attributes are encrypted using [@elastic/node-crypto](https://github.com/elastic/node-crypto) library. Please
|
||||
take a look at the source code of this library to know how encryption is performed exactly, what algorithm and encryption
|
||||
parameters are used, but in short it's AES Encryption with AES-256-GCM that uses random initialization vector and salt.
|
||||
|
||||
As with encryption key for Kibana's session cookie, master encryption key used by `encrypted_saved_objects` plugin can be
|
||||
defined as a configuration value (`xpack.encrypted_saved_objects.encryptionKey`) via `kibana.yml`, but it's **highly
|
||||
recommended** to define this key in the [Kibana Keystore](https://www.elastic.co/guide/en/kibana/current/secure-settings.html)
|
||||
instead. The master key should be cryptographically safe and be equal or greater than 32 bytes.
|
||||
|
||||
To prevent certain vectors of attacks where raw content of encrypted attributes of one saved object is copied to another
|
||||
saved object which would unintentionally allow it to decrypt content that was not supposed to be decrypted we rely on Additional
|
||||
authenticated data (AAD) during encryption and decryption. AAD consists of the following components:
|
||||
|
||||
* Saved object ID
|
||||
* Saved object type
|
||||
* Saved object attributes
|
||||
|
||||
AAD does not include encrypted attributes themselves and attributes defined in optional `attributesToExcludeFromAAD`
|
||||
parameter provided during saved object type registration with `encrypted_saved_objects` plugin. There are a number of
|
||||
reasons why one would want to exclude certain attributes from AAD:
|
||||
|
||||
* if attribute contains large amount of data that can significantly slow down encryption and decryption, especially during
|
||||
bulk operations (e.g. large geo shape or arbitrary HTML document)
|
||||
* if attribute contains data that is supposed to be updated separately from encrypted attributes or attributes included
|
||||
into AAD (e.g some user defined content associated with the email action or alert)
|
||||
|
||||
## Audit
|
||||
|
||||
Encrypted attributes will most likely contain sensitive information and any attempt to access these should be properly
|
||||
logged to allow any further audit procedures. The following events will be logged with Kibana audit log functionality:
|
||||
|
||||
* Successful attempt to encrypt attributes (incl. saved object ID, type and attributes names)
|
||||
* Failed attempt to encrypt attribute (incl. saved object ID, type and attribute name)
|
||||
* Successful attempt to decrypt attributes (incl. saved object ID, type and attributes names)
|
||||
* Failed attempt to decrypt attribute (incl. saved object ID, type and attribute name)
|
||||
|
||||
In addition to audit log events we'll issue ordinary log events for any attempts to save, update or decrypt saved objects
|
||||
with missing attributes that were supposed to be encrypted/decrypted based on the registration parameters.
|
||||
|
||||
# Benefits
|
||||
|
||||
* None of the registered types will expose their encrypted details. The saved
|
||||
objects with their unencrypted attributes could still be obtained and searched
|
||||
on. The wrapper will follow all the security and spaces filtering of saved
|
||||
objects so that only users with appropriate permissions will be able to obtain
|
||||
the scrubbed objects or _save_ objects with encrypted attributes.
|
||||
|
||||
* No explicit access to a method that takes in an encrypted string exists. If the
|
||||
type was not registered no decryption is possible. No need to handle the saved object
|
||||
with the encrypted attributes reducing the risk of accidentally returning it in a
|
||||
handler.
|
||||
|
||||
# Drawbacks
|
||||
|
||||
* It isn't possible to decrypt existing encrypted attributes once encryption key changes
|
||||
* Possibly have a performance impact on Saved Objects API operations that require encryption/decryption
|
||||
* Will require non trivial tests to test functionality along with spaces and security
|
||||
* The attributes that are encrypted have to be defined and if they change they need to be migrated
|
||||
|
||||
# Out of scope
|
||||
|
||||
* Encryption key rotation mechanism, either regular or emergency
|
||||
* Mechanism that would detect and warn when Kibana does not use keystore to store encryption key
|
||||
|
||||
# Alternatives
|
||||
|
||||
Only allow this to be used within the Actions service itself where the details
|
||||
of the saved object are handled there directly. And the saved objects are
|
||||
`hidden` but still use the security and spaces wrappers.
|
||||
|
||||
# Adoption strategy
|
||||
|
||||
Integration should be pretty easy which would include depending on the plugin, registering the desired saved object type
|
||||
with it and defining encrypted attributes in the `mappings.json`.
|
||||
|
||||
# How we teach this
|
||||
|
||||
The `encrypted_saved_objects` as the name of the `thing` where it's seen as a separate
|
||||
extension on top of the saved object service.
|
||||
|
||||
Provide a README.md in the plugin directory with the usage examples.
|
||||
|
||||
# Unresolved questions
|
||||
|
||||
* Is it acceptable to have this plugin in Basic?
|
||||
* Are there any other use-cases that are not served with that interface?
|
||||
* How would this work with Saved Objects Export\Import API?
|
||||
* How would this work with migrations, if the attribute names wanted to be
|
||||
changed, a decrypt context would need to be created for migration?
|
|
@ -75,7 +75,7 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) {
|
|||
set('optimize.watch', true);
|
||||
|
||||
if (!has('elasticsearch.username')) {
|
||||
set('elasticsearch.username', 'elastic');
|
||||
set('elasticsearch.username', 'kibana');
|
||||
}
|
||||
|
||||
if (!has('elasticsearch.password')) {
|
||||
|
|
|
@ -17,7 +17,13 @@
|
|||
* under the License.
|
||||
*/
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { ChromeBrand, ChromeBreadcrumb, ChromeService, ChromeSetup } from './chrome_service';
|
||||
import {
|
||||
ChromeBadge,
|
||||
ChromeBrand,
|
||||
ChromeBreadcrumb,
|
||||
ChromeService,
|
||||
ChromeSetup,
|
||||
} from './chrome_service';
|
||||
|
||||
const createSetupContractMock = () => {
|
||||
const setupContract: jest.Mocked<ChromeSetup> = {
|
||||
|
@ -30,6 +36,8 @@ const createSetupContractMock = () => {
|
|||
addApplicationClass: jest.fn(),
|
||||
removeApplicationClass: jest.fn(),
|
||||
getApplicationClasses$: jest.fn(),
|
||||
getBadge$: jest.fn(),
|
||||
setBadge: jest.fn(),
|
||||
getBreadcrumbs$: jest.fn(),
|
||||
setBreadcrumbs: jest.fn(),
|
||||
getHelpExtension$: jest.fn(),
|
||||
|
@ -39,6 +47,7 @@ const createSetupContractMock = () => {
|
|||
setupContract.getIsVisible$.mockReturnValue(new BehaviorSubject(false));
|
||||
setupContract.getIsCollapsed$.mockReturnValue(new BehaviorSubject(false));
|
||||
setupContract.getApplicationClasses$.mockReturnValue(new BehaviorSubject(['class-name']));
|
||||
setupContract.getBadge$.mockReturnValue(new BehaviorSubject({} as ChromeBadge));
|
||||
setupContract.getBreadcrumbs$.mockReturnValue(new BehaviorSubject([{} as ChromeBreadcrumb]));
|
||||
setupContract.getHelpExtension$.mockReturnValue(new BehaviorSubject(undefined));
|
||||
return setupContract;
|
||||
|
|
|
@ -246,6 +246,37 @@ Array [
|
|||
});
|
||||
});
|
||||
|
||||
describe('badge', () => {
|
||||
it('updates/emits the current badge', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
const setup = service.setup(defaultSetupDeps());
|
||||
const promise = setup
|
||||
.getBadge$()
|
||||
.pipe(toArray())
|
||||
.toPromise();
|
||||
|
||||
setup.setBadge({ text: 'foo', tooltip: `foo's tooltip` });
|
||||
setup.setBadge({ text: 'bar', tooltip: `bar's tooltip` });
|
||||
setup.setBadge(undefined);
|
||||
service.stop();
|
||||
|
||||
await expect(promise).resolves.toMatchInlineSnapshot(`
|
||||
Array [
|
||||
undefined,
|
||||
Object {
|
||||
"text": "foo",
|
||||
"tooltip": "foo's tooltip",
|
||||
},
|
||||
Object {
|
||||
"text": "bar",
|
||||
"tooltip": "bar's tooltip",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('breadcrumbs', () => {
|
||||
it('updates/emits the current set of breadcrumbs', async () => {
|
||||
const service = new ChromeService({ browserSupportsCsp: true });
|
||||
|
|
|
@ -22,6 +22,7 @@ import * as Url from 'url';
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import * as Rx from 'rxjs';
|
||||
import { map, takeUntil } from 'rxjs/operators';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import { InjectedMetadataSetup } from '../injected_metadata';
|
||||
import { NotificationsSetup } from '../notifications';
|
||||
|
||||
|
@ -32,6 +33,13 @@ function isEmbedParamInHash() {
|
|||
return Boolean(query.embed);
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ChromeBadge {
|
||||
text: string;
|
||||
tooltip: string;
|
||||
iconType?: IconType;
|
||||
}
|
||||
|
||||
/** @public */
|
||||
export interface ChromeBrand {
|
||||
logo?: string;
|
||||
|
@ -75,6 +83,7 @@ export class ChromeService {
|
|||
const applicationClasses$ = new Rx.BehaviorSubject<Set<string>>(new Set());
|
||||
const helpExtension$ = new Rx.BehaviorSubject<ChromeHelpExtension | undefined>(undefined);
|
||||
const breadcrumbs$ = new Rx.BehaviorSubject<ChromeBreadcrumb[]>([]);
|
||||
const badge$ = new Rx.BehaviorSubject<ChromeBadge | undefined>(undefined);
|
||||
|
||||
if (!this.browserSupportsCsp && injectedMetadata.getCspConfig().warnLegacyBrowsers) {
|
||||
notifications.toasts.addWarning(
|
||||
|
@ -175,6 +184,18 @@ export class ChromeService {
|
|||
takeUntil(this.stop$)
|
||||
),
|
||||
|
||||
/**
|
||||
* Get an observable of the current badge
|
||||
*/
|
||||
getBadge$: () => badge$.pipe(takeUntil(this.stop$)),
|
||||
|
||||
/**
|
||||
* Override the current badge
|
||||
*/
|
||||
setBadge: (badge: ChromeBadge | undefined) => {
|
||||
badge$.next(badge);
|
||||
},
|
||||
|
||||
/**
|
||||
* Get an observable of the current list of breadcrumbs
|
||||
*/
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
*/
|
||||
|
||||
export {
|
||||
ChromeBadge,
|
||||
ChromeBreadcrumb,
|
||||
ChromeService,
|
||||
ChromeSetup,
|
||||
|
|
|
@ -36,6 +36,9 @@ exports[`#setup() returns \`Context\` component 1`] = `
|
|||
"euiPagination.pageOfTotal": [Function],
|
||||
"euiPagination.previousPage": "Previous page",
|
||||
"euiPopover.screenReaderAnnouncement": "You are in a popup. To exit this popup, hit Escape.",
|
||||
"euiSelectable.loadingOptions": "Loading options",
|
||||
"euiSelectable.noAvailableOptions": "There aren't any options available",
|
||||
"euiSelectable.noMatchingOptions": [Function],
|
||||
"euiStep.completeStep": "Step",
|
||||
"euiStep.incompleteStep": "Incomplete Step",
|
||||
"euiStepHorizontal.buttonTitle": [Function],
|
||||
|
@ -95,6 +98,9 @@ exports[`#start() returns \`Context\` component 1`] = `
|
|||
"euiPagination.pageOfTotal": [Function],
|
||||
"euiPagination.previousPage": "Previous page",
|
||||
"euiPopover.screenReaderAnnouncement": "You are in a popup. To exit this popup, hit Escape.",
|
||||
"euiSelectable.loadingOptions": "Loading options",
|
||||
"euiSelectable.noAvailableOptions": "There aren't any options available",
|
||||
"euiSelectable.noMatchingOptions": [Function],
|
||||
"euiStep.completeStep": "Step",
|
||||
"euiStep.incompleteStep": "Incomplete Step",
|
||||
"euiStepHorizontal.buttonTitle": [Function],
|
||||
|
|
|
@ -185,6 +185,20 @@ export class I18nService {
|
|||
defaultMessage: 'You are in a popup. To exit this popup, hit Escape.',
|
||||
}
|
||||
),
|
||||
'euiSelectable.loadingOptions': i18n.translate('core.euiSelectable.loadingOptions', {
|
||||
defaultMessage: 'Loading options',
|
||||
description: 'Placeholder message while data is asynchronously loaded',
|
||||
}),
|
||||
'euiSelectable.noAvailableOptions': i18n.translate('core.euiSelectable.noAvailableOptions', {
|
||||
defaultMessage: "There aren't any options available",
|
||||
}),
|
||||
'euiSelectable.noMatchingOptions': ({ searchValue }: EuiValues) => (
|
||||
<FormattedMessage
|
||||
id="core.euiSelectable.noMatchingOptions"
|
||||
defaultMessage="{searchValue} doesn't match any options"
|
||||
values={{ searchValue }}
|
||||
/>
|
||||
),
|
||||
'euiStep.completeStep': i18n.translate('core.euiStep.completeStep', {
|
||||
defaultMessage: 'Step',
|
||||
description:
|
||||
|
|
|
@ -19,7 +19,13 @@
|
|||
|
||||
import { BasePathSetup } from './base_path';
|
||||
import { Capabilities, CapabilitiesStart } from './capabilities';
|
||||
import { ChromeBrand, ChromeBreadcrumb, ChromeHelpExtension, ChromeSetup } from './chrome';
|
||||
import {
|
||||
ChromeBadge,
|
||||
ChromeBrand,
|
||||
ChromeBreadcrumb,
|
||||
ChromeHelpExtension,
|
||||
ChromeSetup,
|
||||
} from './chrome';
|
||||
import { FatalErrorsSetup } from './fatal_errors';
|
||||
import { HttpSetup } from './http';
|
||||
import { I18nSetup, I18nStart } from './i18n';
|
||||
|
@ -89,6 +95,7 @@ export {
|
|||
Capabilities,
|
||||
CapabilitiesStart,
|
||||
ChromeSetup,
|
||||
ChromeBadge,
|
||||
ChromeBreadcrumb,
|
||||
ChromeBrand,
|
||||
ChromeHelpExtension,
|
||||
|
|
|
@ -77,6 +77,7 @@ export class LegacyPlatformService {
|
|||
require('ui/chrome/api/controls').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/api/help_extension').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/api/theme').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/api/badge').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/api/breadcrumbs').__newPlatformSetup__(chrome);
|
||||
require('ui/chrome/services/global_nav_state').__newPlatformSetup__(chrome);
|
||||
|
||||
|
|
|
@ -37,6 +37,9 @@ export type PluginsServiceStartDeps = CoreStart;
|
|||
export interface PluginsServiceSetup {
|
||||
contracts: Map<string, unknown>;
|
||||
}
|
||||
export interface PluginsServiceStart {
|
||||
contracts: Map<string, unknown>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Service responsible for loading plugin bundles, initializing plugins, and managing the lifecycle
|
||||
|
@ -44,7 +47,7 @@ export interface PluginsServiceSetup {
|
|||
*
|
||||
* @internal
|
||||
*/
|
||||
export class PluginsService implements CoreService<PluginsServiceSetup> {
|
||||
export class PluginsService implements CoreService<PluginsServiceSetup, PluginsServiceStart> {
|
||||
/** Plugin wrappers in topological order. */
|
||||
private readonly plugins: Map<
|
||||
PluginName,
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
import * as CSS from 'csstype';
|
||||
import { default } from 'react';
|
||||
import { IconType } from '@elastic/eui';
|
||||
import * as PropTypes from 'prop-types';
|
||||
import * as Rx from 'rxjs';
|
||||
import { Toast } from '@elastic/eui';
|
||||
|
@ -32,6 +33,16 @@ export interface CapabilitiesStart {
|
|||
getCapabilities: () => Capabilities;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ChromeBadge {
|
||||
// (undocumented)
|
||||
iconType?: IconType;
|
||||
// (undocumented)
|
||||
text: string;
|
||||
// (undocumented)
|
||||
tooltip: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
export interface ChromeBrand {
|
||||
// (undocumented)
|
||||
|
|
|
@ -84,6 +84,7 @@ export async function bootstrap({
|
|||
|
||||
try {
|
||||
await root.setup();
|
||||
await root.start();
|
||||
} catch (err) {
|
||||
await shutdown(err);
|
||||
}
|
||||
|
|
|
@ -39,6 +39,7 @@ type ElasticsearchServiceContract = PublicMethodsOf<ElasticsearchService>;
|
|||
const createMock = () => {
|
||||
const mocked: jest.Mocked<ElasticsearchServiceContract> = {
|
||||
setup: jest.fn(),
|
||||
start: jest.fn(),
|
||||
stop: jest.fn(),
|
||||
};
|
||||
mocked.setup.mockResolvedValue(createSetupContractMock());
|
||||
|
|