diff --git a/docs/user/security/api-keys/images/create-api-key.png b/docs/user/security/api-keys/images/create-api-key.png index c763dcbfa53f8..b0041f69b05b6 100644 Binary files a/docs/user/security/api-keys/images/create-api-key.png and b/docs/user/security/api-keys/images/create-api-key.png differ diff --git a/x-pack/plugins/security/common/model/api_key.ts b/x-pack/plugins/security/common/model/api_key.ts index f2467468f8069..ed622bf0dc87e 100644 --- a/x-pack/plugins/security/common/model/api_key.ts +++ b/x-pack/plugins/security/common/model/api_key.ts @@ -15,6 +15,7 @@ export interface ApiKey { creation: number; expiration: number; invalidated: boolean; + metadata: Record; } export interface ApiKeyToInvalidate { diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts index 65540fd7ebfc1..96f4506e09b71 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_api_client.ts @@ -28,6 +28,7 @@ export interface CreateApiKeyRequest { name: string; expiration?: string; role_descriptors?: ApiKeyRoleDescriptors; + metadata?: Record; } export interface CreateApiKeyResponse { diff --git a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx index 27385e4b29b00..e1ffc3b4b3515 100644 --- a/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx +++ b/x-pack/plugins/security/public/management/api_keys/api_keys_grid/create_api_key_flyout.tsx @@ -45,7 +45,9 @@ export interface ApiKeyFormValues { expiration: string; customExpiration: boolean; customPrivileges: boolean; + includeMetadata: boolean; role_descriptors: string; + metadata: string; } export interface CreateApiKeyFlyoutProps { @@ -59,30 +61,9 @@ const defaultDefaultValues: ApiKeyFormValues = { expiration: '', customExpiration: false, customPrivileges: false, - role_descriptors: JSON.stringify( - { - 'role-a': { - cluster: ['all'], - indices: [ - { - names: ['index-a*'], - privileges: ['read'], - }, - ], - }, - 'role-b': { - cluster: ['all'], - indices: [ - { - names: ['index-b*'], - privileges: ['all'], - }, - ], - }, - }, - null, - 2 - ), + includeMetadata: false, + role_descriptors: '{}', + metadata: '{}', }; export const CreateApiKeyFlyout: FunctionComponent = ({ @@ -227,7 +208,6 @@ export const CreateApiKeyFlyout: FunctionComponent = ({ = ({ = ({ )} + + + form.setValue('includeMetadata', e.target.checked)} + /> + {form.values.includeMetadata && ( + <> + + + + + } + error={form.errors.metadata} + isInvalid={form.touched.metadata && !!form.errors.metadata} + > + form.setValue('metadata', value)} + languageId="xjson" + height={200} + /> + + + + )} + + {/* Hidden submit button is required for enter key to trigger form submission */} @@ -363,6 +384,28 @@ export function validate(values: ApiKeyFormValues) { } } + if (values.includeMetadata) { + if (!values.metadata) { + errors.metadata = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.metadataRequired', + { + defaultMessage: 'Enter metadata or disable this option.', + } + ); + } else { + try { + JSON.parse(values.metadata); + } catch (e) { + errors.metadata = i18n.translate( + 'xpack.security.management.apiKeys.createApiKey.invalidJsonError', + { + defaultMessage: 'Enter valid JSON.', + } + ); + } + } + } + return errors; } @@ -374,5 +417,6 @@ export function mapValues(values: ApiKeyFormValues): CreateApiKeyRequest { values.customPrivileges && values.role_descriptors ? JSON.parse(values.role_descriptors) : undefined, + metadata: values.includeMetadata && values.metadata ? JSON.parse(values.metadata) : undefined, }; } diff --git a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts index 83bc5a26ead3f..026de4a978428 100644 --- a/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts +++ b/x-pack/plugins/security/server/authentication/api_keys/api_keys.ts @@ -30,15 +30,21 @@ export interface CreateAPIKeyParams { name: string; role_descriptors: Record; expiration?: string; + metadata?: Record; } -interface GrantAPIKeyParams { - api_key: CreateAPIKeyParams; - grant_type: 'password' | 'access_token'; - username?: string; - password?: string; - access_token?: string; -} +type GrantAPIKeyParams = + | { + api_key: CreateAPIKeyParams; + grant_type: 'password'; + username: string; + password: string; + } + | { + api_key: CreateAPIKeyParams; + grant_type: 'access_token'; + access_token: string; + }; /** * Represents the params for invalidating multiple API keys diff --git a/x-pack/plugins/security/server/routes/api_keys/create.test.ts b/x-pack/plugins/security/server/routes/api_keys/create.test.ts index 8e34ededdd8fa..502a7cb1246c4 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.test.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.test.ts @@ -85,6 +85,9 @@ describe('Create API Key route', () => { role_descriptors: { role_1: {}, }, + metadata: { + foo: 'bar', + }, }; const request = httpServerMock.createKibanaRequest({ diff --git a/x-pack/plugins/security/server/routes/api_keys/create.ts b/x-pack/plugins/security/server/routes/api_keys/create.ts index a309d3a0e3edb..b9e927592a492 100644 --- a/x-pack/plugins/security/server/routes/api_keys/create.ts +++ b/x-pack/plugins/security/server/routes/api_keys/create.ts @@ -29,6 +29,7 @@ export function defineCreateApiKeyRoutes({ defaultValue: {}, } ), + metadata: schema.maybe(schema.object({}, { unknowns: 'allow' })), }), }, }, diff --git a/x-pack/test/api_integration/apis/security/api_keys.ts b/x-pack/test/api_integration/apis/security/api_keys.ts index c6513fa800c1c..c79c4f3eaa88e 100644 --- a/x-pack/test/api_integration/apis/security/api_keys.ts +++ b/x-pack/test/api_integration/apis/security/api_keys.ts @@ -46,6 +46,23 @@ export default function ({ getService }: FtrProviderContext) { expect(name).to.eql('test_api_key'); }); }); + + it('should allow an API Key to be created with metadata', async () => { + await supertest + .post('/internal/security/api_key') + .set('kbn-xsrf', 'xxx') + .send({ + name: 'test_api_key_with_metadata', + metadata: { + foo: 'bar', + }, + }) + .expect(200) + .then((response: Record) => { + const { name } = response.body; + expect(name).to.eql('test_api_key_with_metadata'); + }); + }); }); }); }