Merge remote-tracking branch 'upstream/master' into feature-secops

This commit is contained in:
FrankHassanabad 2019-05-02 15:13:53 -06:00
commit bb182c239a
No known key found for this signature in database
GPG key ID: 0BC9DC0E0023FA5D
1152 changed files with 27813 additions and 17954 deletions

View file

@ -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"]
}

View file

@ -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
View file

@ -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
View file

@ -31,7 +31,7 @@ webpackstats.json
!/config/kibana.yml
coverage
selenium
.babelcache.json
.babel_register_cache.json
.webpack.babelcache
*.swp
*.swo

View file

@ -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",

View file

@ -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[]

View file

@ -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].

View file

@ -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 dont 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.

View file

@ -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[]

View file

@ -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"]

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 200 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 729 B

View file

@ -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.

View file

@ -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.

View file

@ -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"]

View file

@ -0,0 +1,9 @@
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeBadge](./kibana-plugin-public.chromebadge.md) &gt; [iconType](./kibana-plugin-public.chromebadge.icontype.md)
## ChromeBadge.iconType property
<b>Signature:</b>
```typescript
iconType?: IconType;
```

View file

@ -0,0 +1,19 @@
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [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> | |

View file

@ -0,0 +1,9 @@
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeBadge](./kibana-plugin-public.chromebadge.md) &gt; [text](./kibana-plugin-public.chromebadge.text.md)
## ChromeBadge.text property
<b>Signature:</b>
```typescript
text: string;
```

View file

@ -0,0 +1,9 @@
[Home](./index) &gt; [kibana-plugin-public](./kibana-plugin-public.md) &gt; [ChromeBadge](./kibana-plugin-public.chromebadge.md) &gt; [tooltip](./kibana-plugin-public.chromebadge.tooltip.md)
## ChromeBadge.tooltip property
<b>Signature:</b>
```typescript
tooltip: string;
```

View file

@ -1,45 +1,46 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [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) &gt; [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) | |

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [AuthenticationHandler](./kibana-plugin-server.authenticationhandler.md)
## AuthenticationHandler type

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [AuthToolkit](./kibana-plugin-server.authtoolkit.md) &gt; [authenticated](./kibana-plugin-server.authtoolkit.authenticated.md)
## AuthToolkit.authenticated property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [AuthToolkit](./kibana-plugin-server.authtoolkit.md)
## AuthToolkit interface

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [AuthToolkit](./kibana-plugin-server.authtoolkit.md) &gt; [redirected](./kibana-plugin-server.authtoolkit.redirected.md)
## AuthToolkit.redirected property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [AuthToolkit](./kibana-plugin-server.authtoolkit.md) &gt; [rejected](./kibana-plugin-server.authtoolkit.rejected.md)
## AuthToolkit.rejected property

View file

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [CoreStart](./kibana-plugin-server.corestart.md) &gt; [http](./kibana-plugin-server.corestart.http.md)
## CoreStart.http property
<b>Signature:</b>
```typescript
http: HttpServiceStart;
```

View file

@ -0,0 +1,18 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [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> | |

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [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;
```

View file

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [HttpServiceStart](./kibana-plugin-server.httpservicestart.md) &gt; [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;
```

View file

@ -0,0 +1,19 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [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>() =&gt; boolean</code> | Indicates if http server is listening on a port |

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [body](./kibana-plugin-server.kibanarequest.body.md)
## KibanaRequest.body property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [from](./kibana-plugin-server.kibanarequest.from.md)
## KibanaRequest.from() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [getFilteredHeaders](./kibana-plugin-server.kibanarequest.getfilteredheaders.md)
## KibanaRequest.getFilteredHeaders() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [headers](./kibana-plugin-server.kibanarequest.headers.md)
## KibanaRequest.headers property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md)
## KibanaRequest class

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [params](./kibana-plugin-server.kibanarequest.params.md)
## KibanaRequest.params property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [path](./kibana-plugin-server.kibanarequest.path.md)
## KibanaRequest.path property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [KibanaRequest](./kibana-plugin-server.kibanarequest.md) &gt; [query](./kibana-plugin-server.kibanarequest.query.md)
## KibanaRequest.query property

View file

@ -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 |

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestHandler](./kibana-plugin-server.onrequesthandler.md)
## OnRequestHandler type

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md)
## OnRequestToolkit interface

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [next](./kibana-plugin-server.onrequesttoolkit.next.md)
## OnRequestToolkit.next property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [redirected](./kibana-plugin-server.onrequesttoolkit.redirected.md)
## OnRequestToolkit.redirected property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [OnRequestToolkit](./kibana-plugin-server.onrequesttoolkit.md) &gt; [rejected](./kibana-plugin-server.onrequesttoolkit.rejected.md)
## OnRequestToolkit.rejected property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [PluginSetupContext](./kibana-plugin-server.pluginsetupcontext.md) &gt; [http](./kibana-plugin-server.pluginsetupcontext.http.md)
## PluginSetupContext.http property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [delete](./kibana-plugin-server.router.delete.md)
## Router.delete() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [get](./kibana-plugin-server.router.get.md)
## Router.get() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [getRoutes](./kibana-plugin-server.router.getroutes.md)
## Router.getRoutes() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md)
## Router class

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [path](./kibana-plugin-server.router.path.md)
## Router.path property

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [post](./kibana-plugin-server.router.post.md)
## Router.post() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [put](./kibana-plugin-server.router.put.md)
## Router.put() method

View file

@ -1,3 +1,5 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index) &gt; [kibana-plugin-server](./kibana-plugin-server.md) &gt; [Router](./kibana-plugin-server.router.md) &gt; [routes](./kibana-plugin-server.router.routes.md)
## Router.routes property

View file

@ -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`.

View file

@ -0,0 +1,6 @@
{
"appId": 26774,
"envVars": {
"appKey": "KIBANA_CI_REPORTER_KEY"
}
}

View file

@ -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"
}
}
}

View file

@ -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

View file

@ -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",

View file

@ -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' }];

View file

@ -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 });
}

View file

@ -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 () {

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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

File diff suppressed because it is too large Load diff

View file

@ -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(''))}

View 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;
}
}

View 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' +
'^');
});
});

View file

@ -20,3 +20,4 @@
export * from './ast';
export * from './filter_migration';
export * from './node_types';
export * from './errors';

View file

@ -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",

View file

@ -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:

View file

@ -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);
}
};

View file

@ -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())));

View file

@ -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;

View 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;
}
}
};

View 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 [
"unable to set password for kibana: SomeError",
],
]
`);
});
});

View file

@ -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'],
},

View file

@ -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) =>

View file

@ -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",

View 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?

View file

@ -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')) {

View file

@ -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;

View file

@ -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 });

View file

@ -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
*/

View file

@ -18,6 +18,7 @@
*/
export {
ChromeBadge,
ChromeBreadcrumb,
ChromeService,
ChromeSetup,

View file

@ -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],

View file

@ -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:

View file

@ -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,

View file

@ -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);

View file

@ -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,

View file

@ -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)

View file

@ -84,6 +84,7 @@ export async function bootstrap({
try {
await root.setup();
await root.start();
} catch (err) {
await shutdown(err);
}

View file

@ -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());

Some files were not shown because too many files have changed in this diff Show more