-
Notifications
You must be signed in to change notification settings - Fork 8.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Expose ability to check if API Keys are enabled #63454
Conversation
return false; | ||
} | ||
|
||
const id = `kibana-api-key-service-test-${uuid.v4()}`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: we don't want to accidentally invalidate a key that actually exists, but we also want the ID to be descriptive in case someone inspecting the ES audit logs wants to trace this back. By combining descriptive text with a UUID, we can guarantee uniqueness while still being descriptive.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated note: after I wrote this comment, I realized that the key id
is generated by ES, and not under the user's control (unlike the key's name
). Therefore, there is no need to generate a unique id using UUID. I've updated this to always just use kibana-api-key-service-test
instead.
… into security/api-keys-enabled
@elasticmachine merge upstream |
Pinging @elastic/kibana-security (Team:Security) |
@@ -10,6 +10,7 @@ import { ApiKey, ApiKeyToInvalidate } from '../../../common/model'; | |||
interface CheckPrivilegesResponse { | |||
areApiKeysEnabled: boolean; | |||
isAdmin: boolean; | |||
canManage: boolean; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note previously, the API Keys app had a dual purpose for areApiKeysEnabled
: It was true if and only if both of the following were true:
- User was authorized to retrieve their own API Keys
- API Keys were in fact enabled
This separates those two concerns into dedicated properties
ACK: can review tomorrow if no one does it today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM! Tested locally, everything works as expected. Just left a few questions/nits, but nothing substantial (except for error.body.
that we can potentially use instead of JSON.parse).
this.props.notifications.toasts.addDanger( | ||
i18n.translate('xpack.security.management.apiKeys.table.fetchingApiKeysErrorMessage', { | ||
defaultMessage: 'Error checking privileges: {message}', | ||
values: { message: _.get(e, 'body.message', '') }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: while you're, let's finally remove lodash from this file:
values: { message: _.get(e, 'body.message', '') }, | |
values: { message: e.body?.message || '' }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good idea!
@@ -10,7 +10,7 @@ import { wrapIntoCustomErrorResponse } from '../../errors'; | |||
import { createLicensedRouteHandler } from '../licensed_route_handler'; | |||
import { RouteDefinitionParams } from '..'; | |||
|
|||
export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { | |||
export function defineGetApiKeysRoutes({ router, clusterClient, authc }: RouteDefinitionParams) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: looks like a leftover
export function defineGetApiKeysRoutes({ router, clusterClient, authc }: RouteDefinitionParams) { | |
export function defineGetApiKeysRoutes({ router, clusterClient }: RouteDefinitionParams) { |
|
||
export function defineEnabledApiKeysRoutes({ | ||
router, | ||
clusterClient, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
clusterClient, |
id, | ||
}, | ||
}); | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what does it mean if ES replies with 200
to such request? Does Elasticsearch just ignores unknown API keys? It seems ES works with security tokens differently then elastic/elasticsearch#54532 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: what does it mean if ES replies with
200
to such request? Does Elasticsearch just ignores unknown API keys? It seems ES works with security tokens differently then elastic/elasticsearch#54532 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, thanks for confirming. Just out of curiosity, @jkakavas is there any valid reason to return different status codes for "unknown" api keys and access tokens during invalidation or we just didn't have chance to unify that yet?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's the latter. I just opened elastic/elasticsearch#55671 to keep track of the work needed for the invalidate api keys API
@@ -247,6 +276,20 @@ export class APIKeys { | |||
return result; | |||
} | |||
|
|||
private doesErrorIndicateAPIKeysAreDisabled(e: Record<string, any>) { | |||
const responseText = e.response ?? '{}'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: can't we just use e.body?.error?.['disabled.feature']
and eliminate try/catch
here completely?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, you are absolutely right! I don't know why I thought I had to manually parse response
to get at this metadata 🤦
@@ -70,16 +109,21 @@ describe('Check API keys privileges', () => { | |||
|
|||
const error = Boom.notAcceptable('test not acceptable message'); | |||
getPrivilegesTest('returns error from cluster client', { | |||
apiResponses: [ | |||
callAsCurrrentUserResponses: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo:
callAsCurrrentUserResponses: [ | |
callAsCurrentUserResponses: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
gah! I hate my broken keyboard!
{ body: { cluster: ['manage_security', 'manage_api_key', 'manage_own_api_key'] } }, | ||
], | ||
], | ||
callAsInternalUserAPIArguments: [ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
question: any reason you didn't want to mock ApiKeys
instead to not expose internal impl detail (the fact that we use invalidate key
under the hood)? I'm fine with keeping it as is, just curious what is main motivation here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My only motivation here was to follow the pattern that already existed in this test suite. The changes started out small, but it might have been easier to rewrite in hindsight
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, makes sense to me.
@@ -13,6 +13,7 @@ export default async function({ readConfigFile }) { | |||
config.esTestCluster.serverArgs = [ | |||
'xpack.license.self_generated.type=basic', | |||
'xpack.security.enabled=true', | |||
'xpack.security.authc.api_key.enabled=true', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note: it's too bad we don't have an easy way to reconfigure ES during tests and jest_integration tests are not available in xpack either. Ideally we'd like to check the case when keys are disabled so that we can catch the breaking changes in ES error format, but having a dedicated config just to test this is too much as well. No need to change/improve anything here, just grumbling about imperfect test system we live with.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree! I almost created a dedicated test suite for the disabled case, but like you mentioned, it's not worth the effort/overhead
@elasticmachine merge upstream |
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
* expose ability to check if API Keys are enabled * fix mock * Fix typo in test name * simplify key check * fix privilege check * remove unused variable * address PR feedback Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
* expose ability to check if API Keys are enabled * fix mock * Fix typo in test name * simplify key check * fix privilege check * remove unused variable * address PR feedback Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com> Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
* master: (70 commits) KQL removes leading zero and breaks query (elastic#62748) [FieldFormats] Cleanup: rename IFieldFormatType -> FieldFormatInstanceType (elastic#64193) [ML] Changes transforms wizard UI text (elastic#64150) [Alerting] change server log action type .log to .server-log in README (elastic#64124) [Metrics UI] Design Refresh: Inventory View, Episode 1 (elastic#64026) chore(NA): reduce siem bundle size using babel-plugin-transfor… (elastic#63269) chore(NA): use core-js instead of babel-polyfill on canvas sha… (elastic#63486) skip flaky suite (elastic#61173) skip flaky suite (elastic#62497) Renamed ilm policy for event log so it is not prefixed with dot (elastic#64262) [eslint] no_restricted_paths config cleanup (elastic#63741) Add Oil Rig Icon from @elastic/maki (elastic#64364) [Maps] Migrate Maps embeddables to NP (elastic#63976) [Ingest] Data streams list page (elastic#64134) chore(NA): add file-loader into jest moduleNameMapper (elastic#64330) [DOCS] Added images to automating report generation (elastic#64333) [SIEM][CASE] Api Integration Tests: Configuration (elastic#63948) Expose ability to check if API Keys are enabled (elastic#63454) [DOCS] Fixes formatting in alerting doc (elastic#64338) [data.search.aggs]: Create agg types function for terms agg. (elastic#63541) ...
Summary
Exposes the ability to check if API Keys are enabled in Elasticsearch:
await setup.authc.areAPIKeysEnabled()
await setup.authc.areAPIKeysEnabled()
Updates existing
/internal/security/api_key/privileges
route to use ES exception metadata to check if API Keys are enabled (instead of checking error message text).Resolves #59576