Skip to content

Commit

Permalink
[7.x] Added ability to create API keys (elastic#92610) (elastic#96943)
Browse files Browse the repository at this point in the history
* Added ability to create API keys (elastic#92610)

* Added ability to create API keys

* Remove hard coded colours

* Added unit tests

* Fix linting errors

* Display full base64 encoded API key

* Fix linting errors

* Fix more linting error and unit tests

* Added suggestions from code review

* fix unit tests

* move code editor field into separate component

* fixed tests

* fixed test

* Fixed functional tests

* replaced theme hook with eui import

* Revert to manual theme detection

* added storybook

* Additional unit and functional tests

* Added suggestions from code review

* Remove unused translations

* Updated docs and added detailed error description

* Removed unused messages

* Updated unit test

Co-authored-by: Kibana Machine <[email protected]>
Co-authored-by: Larry Gregory <[email protected]>
# Conflicts:
#	x-pack/plugins/security/public/management/api_keys/api_keys_management_app.test.tsx
#	x-pack/plugins/security/public/management/api_keys/api_keys_management_app.tsx

* Updated translations
  • Loading branch information
thomheymann authored Apr 13, 2021
1 parent a2cfeb8 commit f143c7d
Show file tree
Hide file tree
Showing 45 changed files with 1,872 additions and 946 deletions.
Binary file not shown.
Binary file modified docs/user/security/api-keys/images/api-keys.png
100755 → 100644
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
57 changes: 15 additions & 42 deletions docs/user/security/api-keys/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@


API keys enable you to create secondary credentials so that you can send
requests on behalf of the user. Secondary credentials have
requests on behalf of a user. Secondary credentials have
the same or lower access rights.

For example, if you extract data from an {es} cluster on a daily
Expand All @@ -14,8 +14,7 @@ and then put the API credentials into a cron job.
Or, you might create API keys to automate ingestion of new data from
remote sources, without a live user interaction.

You can create API keys from the {kib} Console. To view and invalidate
API keys, open the main menu, then click *Stack Management > API Keys*.
To manage API keys, open the main menu, then click *Stack Management > API Keys*.

[role="screenshot"]
image:user/security/api-keys/images/api-keys.png["API Keys UI"]
Expand Down Expand Up @@ -46,58 +45,32 @@ cluster privileges to use API keys in {kib}. To manage roles, open the main menu
[float]
[[create-api-key]]
=== Create an API key
You can {ref}/security-api-create-api-key.html[create an API key] from
the {kib} Console. This example shows how to create an API key
to authenticate to a <<api, Kibana API>>.

[source,js]
POST /_security/api_key
{
"name": "kibana_api_key"
}

This creates an API key with the
name `kibana_api_key`. API key
names must be globally unique.
An expiration date is optional and follows
{ref}/common-options.html#time-units[{es} time unit format].
When an expiration is not provided, the API key does not expire.

The response should look something like this:

[source,js]
{
"id" : "XFcbCnIBnbwqt2o79G4q",
"name" : "kibana_api_key",
"api_key" : "FD6P5UA4QCWlZZQhYF3YGw"
}

Now, you can use the API key to request {kib} roles. You'll need to send a request with a
`Authorization` header with a value having the prefix `ApiKey` followed by the credentials,
where credentials is the base64 encoding of `id` and `api_key` joined by a colon. For example:

[source,js]

To create an API key, open the main menu, then click *Stack Management > API Keys > Create API key*.

[role="screenshot"]
image:user/security/api-keys/images/create-api-key.png["Create API Key UI"]

Once created, you can copy the API key (Base64 encoded) and use it to send requests to {es} on your behalf. For example:

[source,bash]
curl --location --request GET 'http://localhost:5601/api/security/role' \
--header 'Content-Type: application/json;charset=UTF-8' \
--header 'kbn-xsrf: true' \
--header 'Authorization: ApiKey aVZlLUMzSUJuYndxdDJvN0k1bU46aGxlYUpNS2lTa2FKeVZua1FnY1VEdw==' \

[float]
[[view-api-keys]]
=== View and invalidate API keys
The *API Keys* feature in Kibana lists your API keys, including the name, date created,
and expiration date. If an API key expires, its status changes from `Active` to `Expired`.
=== View and delete API keys

The *API Keys* feature in Kibana lists your API keys, including the name, date created, and status. If an API key expires, its status changes from `Active` to `Expired`.

If you have `manage_security` or `manage_api_key` permissions,
you can view the API keys of all users, and see which API key was
created by which user in which realm.
If you have only the `manage_own_api_key` permission, you see only a list of your own keys.

You can invalidate API keys individually or in bulk.
Invalidated keys are deleted in batch after seven days.

[role="screenshot"]
image:user/security/api-keys/images/api-key-invalidate.png["API Keys invalidate"]
You can delete API keys individually or in bulk.

You cannot modify an API key. If you need additional privileges,
you must create a new key with the desired configuration and invalidate the old key.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,25 @@ storiesOf('CodeEditor', module)
},
}
)
.add(
'transparent background',
() => (
<div>
<CodeEditor
languageId="plaintext"
height={250}
value="Hello!"
onChange={action('onChange')}
transparentBackground
/>
</div>
),
{
info: {
text: 'Plaintext Monaco Editor',
},
}
)
.add(
'custom log language',
() => (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,8 @@ test('editor mount setup', () => {
// Verify our mount callback will be called
expect(editorWillMount.mock.calls.length).toBe(1);

// Verify our theme will be setup
expect((monaco.editor.defineTheme as jest.Mock).mock.calls.length).toBe(1);
// Verify that both, default and transparent theme will be setup
expect((monaco.editor.defineTheme as jest.Mock).mock.calls.length).toBe(2);

// Verify our language features have been registered
expect((monaco.languages.onLanguage as jest.Mock).mock.calls.length).toBe(1);
Expand Down
44 changes: 35 additions & 9 deletions src/plugins/kibana_react/public/code_editor/code_editor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,14 @@
import React from 'react';
import ReactResizeDetector from 'react-resize-detector';
import MonacoEditor from 'react-monaco-editor';

import { monaco } from '@kbn/monaco';

import { LIGHT_THEME, DARK_THEME } from './editor_theme';
import {
DARK_THEME,
LIGHT_THEME,
DARK_THEME_TRANSPARENT,
LIGHT_THEME_TRANSPARENT,
} from './editor_theme';

import './editor.scss';

Expand Down Expand Up @@ -86,6 +90,11 @@ export interface Props {
* Should the editor use the dark theme
*/
useDarkTheme?: boolean;

/**
* Should the editor use a transparent background
*/
transparentBackground?: boolean;
}

export class CodeEditor extends React.Component<Props, {}> {
Expand Down Expand Up @@ -132,8 +141,12 @@ export class CodeEditor extends React.Component<Props, {}> {
}
});

// Register the theme
// Register themes
monaco.editor.defineTheme('euiColors', this.props.useDarkTheme ? DARK_THEME : LIGHT_THEME);
monaco.editor.defineTheme(
'euiColorsTransparent',
this.props.useDarkTheme ? DARK_THEME_TRANSPARENT : LIGHT_THEME_TRANSPARENT
);
};

_editorDidMount = (editor: monaco.editor.IStandaloneCodeEditor, __monaco: unknown) => {
Expand All @@ -152,20 +165,33 @@ export class CodeEditor extends React.Component<Props, {}> {
const { languageId, value, onChange, width, height, options } = this.props;

return (
<React.Fragment>
<>
<MonacoEditor
theme="euiColors"
theme={this.props.transparentBackground ? 'euiColorsTransparent' : 'euiColors'}
language={languageId}
value={value}
onChange={onChange}
editorWillMount={this._editorWillMount}
editorDidMount={this._editorDidMount}
width={width}
height={height}
options={options}
editorWillMount={this._editorWillMount}
editorDidMount={this._editorDidMount}
options={{
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
minimap: {
enabled: false,
},
scrollbar: {
useShadows: false,
},
wordBasedSuggestions: false,
wordWrap: 'on',
wrappingIndent: 'indent',
...options,
}}
/>
<ReactResizeDetector handleWidth handleHeight onResize={this._updateDimensions} />
</React.Fragment>
</>
);
}

Expand Down
9 changes: 6 additions & 3 deletions src/plugins/kibana_react/public/code_editor/editor_theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import lightTheme from '@elastic/eui/dist/eui_theme_light.json';

export function createTheme(
euiTheme: typeof darkTheme | typeof lightTheme,
selectionBackgroundColor: string
selectionBackgroundColor: string,
backgroundColor?: string
): monaco.editor.IStandaloneThemeData {
return {
base: 'vs',
Expand Down Expand Up @@ -87,7 +88,7 @@ export function createTheme(
],
colors: {
'editor.foreground': euiTheme.euiColorDarkestShade,
'editor.background': euiTheme.euiFormBackgroundColor,
'editor.background': backgroundColor ?? euiTheme.euiFormBackgroundColor,
'editorLineNumber.foreground': euiTheme.euiColorDarkShade,
'editorLineNumber.activeForeground': euiTheme.euiColorDarkShade,
'editorIndentGuide.background': euiTheme.euiColorLightShade,
Expand All @@ -105,5 +106,7 @@ export function createTheme(

const DARK_THEME = createTheme(darkTheme, '#343551');
const LIGHT_THEME = createTheme(lightTheme, '#E3E4ED');
const DARK_THEME_TRANSPARENT = createTheme(darkTheme, '#343551', '#00000000');
const LIGHT_THEME_TRANSPARENT = createTheme(lightTheme, '#E3E4ED', '#00000000');

export { DARK_THEME, LIGHT_THEME };
export { DARK_THEME, LIGHT_THEME, DARK_THEME_TRANSPARENT, LIGHT_THEME_TRANSPARENT };
58 changes: 54 additions & 4 deletions src/plugins/kibana_react/public/code_editor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,14 @@
*/

import React from 'react';
import { EuiDelayRender, EuiLoadingContent } from '@elastic/eui';
import {
EuiDelayRender,
EuiErrorBoundary,
EuiLoadingContent,
EuiFormControlLayout,
} from '@elastic/eui';
import darkTheme from '@elastic/eui/dist/eui_theme_dark.json';
import lightTheme from '@elastic/eui/dist/eui_theme_light.json';
import { useUiSetting } from '../ui_settings';
import type { Props } from './code_editor';

Expand All @@ -19,11 +26,54 @@ const Fallback = () => (
</EuiDelayRender>
);

/**
* Renders a Monaco code editor with EUI color theme.
*
* @see CodeEditorField to render a code editor in the same style as other EUI form fields.
*/
export const CodeEditor: React.FunctionComponent<Props> = (props) => {
const darkMode = useUiSetting<boolean>('theme:darkMode');
return (
<React.Suspense fallback={<Fallback />}>
<LazyBaseEditor {...props} useDarkTheme={darkMode} />
</React.Suspense>
<EuiErrorBoundary>
<React.Suspense fallback={<Fallback />}>
<LazyBaseEditor {...props} useDarkTheme={darkMode} />
</React.Suspense>
</EuiErrorBoundary>
);
};

/**
* Renders a Monaco code editor in the same style as other EUI form fields.
*/
export const CodeEditorField: React.FunctionComponent<Props> = (props) => {
const { width, height, options } = props;
const darkMode = useUiSetting<boolean>('theme:darkMode');
const theme = darkMode ? darkTheme : lightTheme;
const style = {
width,
height,
backgroundColor: options?.readOnly
? theme.euiFormBackgroundReadOnlyColor
: theme.euiFormBackgroundColor,
};

return (
<EuiErrorBoundary>
<React.Suspense
fallback={
<EuiFormControlLayout
append={<div hidden />}
style={{ ...style, padding: theme.paddingSizes.m }}
readOnly={options?.readOnly}
>
<Fallback />
</EuiFormControlLayout>
}
>
<EuiFormControlLayout append={<div hidden />} style={style} readOnly={options?.readOnly}>
<LazyBaseEditor {...props} useDarkTheme={darkMode} transparentBackground />
</EuiFormControlLayout>
</React.Suspense>
</EuiErrorBoundary>
);
};
4 changes: 4 additions & 0 deletions x-pack/plugins/security/common/model/api_key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
* 2.0.
*/

import type { Role } from './role';

export interface ApiKey {
id: string;
name: string;
Expand All @@ -19,3 +21,5 @@ export interface ApiKeyToInvalidate {
id: string;
name: string;
}

export type ApiKeyRoleDescriptors = Record<string, Role['elasticsearch']>;
2 changes: 1 addition & 1 deletion x-pack/plugins/security/common/model/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
* 2.0.
*/

export { ApiKey, ApiKeyToInvalidate } from './api_key';
export { ApiKey, ApiKeyToInvalidate, ApiKeyRoleDescriptors } from './api_key';
export { User, EditUser, getUserDisplayName } from './user';
export { AuthenticatedUser, canUserChangePassword } from './authenticated_user';
export { AuthenticationProvider, shouldProviderUseLoginForm } from './authentication_provider';
Expand Down
20 changes: 18 additions & 2 deletions x-pack/plugins/security/public/components/breadcrumb.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import type { EuiBreadcrumb } from '@elastic/eui';
import type { FunctionComponent } from 'react';
import React, { createContext, useContext, useEffect, useRef } from 'react';

import type { ChromeStart } from 'src/core/public';

import { useKibana } from '../../../../../src/plugins/kibana_react/public';

interface BreadcrumbsContext {
Expand Down Expand Up @@ -81,8 +83,8 @@ export const BreadcrumbsProvider: FunctionComponent<BreadcrumbsProviderProps> =
if (onChange) {
onChange(breadcrumbs);
} else if (services.chrome) {
services.chrome.setBreadcrumbs(breadcrumbs);
services.chrome.docTitle.change(getDocTitle(breadcrumbs));
const setBreadcrumbs = createBreadcrumbsChangeHandler(services.chrome);
setBreadcrumbs(breadcrumbs);
}
};

Expand Down Expand Up @@ -138,3 +140,17 @@ export function getDocTitle(breadcrumbs: BreadcrumbProps[], maxBreadcrumbs = 2)
.reverse()
.map(({ text }) => text);
}

export function createBreadcrumbsChangeHandler(
chrome: Pick<ChromeStart, 'docTitle' | 'setBreadcrumbs'>,
setBreadcrumbs = chrome.setBreadcrumbs
) {
return (breadcrumbs: BreadcrumbProps[]) => {
setBreadcrumbs(breadcrumbs);
if (breadcrumbs.length === 0) {
chrome.docTitle.reset();
} else {
chrome.docTitle.change(getDocTitle(breadcrumbs));
}
};
}
Loading

0 comments on commit f143c7d

Please sign in to comment.