-
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
Adding authc.grantAPIKeyAsInternalUser
#60423
Conversation
const [scheme] = authorizationHeaderValue.split(/\s+/); | ||
const credentials = authorizationHeaderValue.substring(scheme.length + 1); | ||
|
||
return new HTTPAuthorizationHeader(scheme, credentials); |
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.
With the old implementation of getHTTPAuthenticationScheme
, .toLowerCase()
was being called when initially parsing the scheme from the request's headers. This felt inconsistent when I began to use the HTTPAuthorizationHeader
class and .toString()
to create the Authorization
headers themselves. It has the effect of requiring consumers to call .toLowerCase()
, or instead use .localeCompare
, to treat this as being case insensitive.
@azasypkin I'm interested in how you feel about this.
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 fine by me as long as we mention this in the property JS docs. I'm curious if there is any reason we cannot do toLowerCase()
for both scheme
and credentials
in HTTPAuthorizationHeader
constructor though?
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.
We definitely can do this in the constructor. When we were previously building the Authorization
header manually within the auth providers, we were using a capitalized version, for example: Bearer ${accessToken}
. Changing this behavior for the sake of making matching the scheme
easier felt wrong. However, I don't want to be introducing future bugs by being pedantic... I wish there was CaseInsensitiveString
in JavaScript which overrode equals
, etc. but AFAIK there is no such thing, or no ability to create one ourselves which would work with things like a Map
and a Set
.
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.
Changing this behavior for the sake of making matching the scheme easier felt wrong. However, I don't want to be introducing future bugs by being pedantic...
Yeah, I agree. We use and compare schemes just in a couple of places, I think the risk of accidentally breaking something in this area may be negligible.
x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts
Outdated
Show resolved
Hide resolved
…ion/http_authorization_header.ts Co-Authored-By: Aleh Zasypkin <[email protected]>
@tvernum I've been able to make the current ES Grant API key endpoint work, but it requires that Kibana parse the |
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.
Tested this locally. I changed some alerting code to make the alerting APIs use this new API and everything worked based on @kobelb temporary workaround. I tested with a user who didn't have any access to API keys and Kibana allowed the creation of alerts. Tested the API key the alert created to ensure it's usable, etc and it worked! LGTMike 👍
* Tries to grant an API key for the current user. | ||
* @param request Request instance. | ||
*/ | ||
async grant(request: KibanaRequest): Promise<GrantAPIKeyResult | null> { |
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.
We chatted a bit about the function naming. Did you decide to keep as is or go with something like grantAsInternalUser
?
ACK: will review today |
@elasticmachine merge upstream |
authc.grantAPIKey
authc.grantAPIKeyAsInternalUser
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.
Looks great, thanks! Tested locally with both SAML and Basic.
...plugins/security/server/authentication/http_authentication/http_authorization_header.test.ts
Outdated
Show resolved
Hide resolved
...ity/server/authentication/http_authentication/basic_http_authorization_header_credentials.ts
Outdated
Show resolved
Hide resolved
@@ -54,7 +57,10 @@ export class BasicAuthenticationProvider extends BaseAuthenticationProvider { | |||
this.logger.debug('Trying to perform a login.'); | |||
|
|||
const authHeaders = { | |||
authorization: `Basic ${Buffer.from(`${username}:${password}`).toString('base64')}`, | |||
authorization: new HTTPAuthorizationHeader( |
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: would you mind also updating https://github.com/elastic/kibana/blob/master/x-pack/plugins/security/server/routes/users/change_password.ts#L46 to use HTTPAuthorizationHeader
?
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.
You got it!
interface GrantAPIKeyParams { | ||
grant_type: 'password' | 'access_token'; | ||
username?: string; | ||
password?: string; | ||
access_token?: string; | ||
} |
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.
optional nit: how do you feel about something like this instead?
interface GrantAPIKeyParams { | |
grant_type: 'password' | 'access_token'; | |
username?: string; | |
password?: string; | |
access_token?: string; | |
} | |
type GrantAPIKeyParams = | |
| { grant_type: 'password'; username: string; password: string } | |
| { grant_type: 'access_token'; access_token: string }; |
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: hmm I'm surprised that Elasticsearch doesn't allow client to specify Api Key name like it does in create API key
API. Do you know why? Just out of curiosity.
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 don't, unfortunately. It also doesn't allow you to specify a role descriptor. It's not blocking Alerting's usage, so I :ostrich_head_in_sand:'ed it.
result = (await this.clusterClient | ||
.asScoped(request) | ||
.callAsInternalUser('shield.grantAPIKey', { body: params })) as GrantAPIKeyResult; |
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: I guess we don't need asScoped
here (as well as as GrantAPIKeyResult
)?
result = (await this.clusterClient | |
.asScoped(request) | |
.callAsInternalUser('shield.grantAPIKey', { body: params })) as GrantAPIKeyResult; | |
result = await this.clusterClient.callAsInternalUser('shield.grantAPIKey', { body: params }); |
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.
The conceptual equivalent to an asScoped
"grant" is just createAPIKey
. It's safe for us to always allow end-users to use the create API Key API in Elasticsearch and ES will enforce all of the necessary authorization for this to be safe. The ability to grant an API Key in this manner is a new "two credentials API" where the Kibana server should only be able to perform the operation when it has the user's credentials, and the result of the operation should be safe-guarded...
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.
Yeah, but I what I meant is that you're using callAsInternalUser
and callAsInternalUser
on ScopedClusterClient
is exactly the same as on non-scoped ClusterClient
and doesn't use request headers you scoped client with. So unless I'm missing something clusterClient.asScoped(request).callAsInternalUser
is a bit less performant equivalent of just clusterClient.callAsInternalUser
.
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'm with you now, apologies for being dense. Fix forthcoming.
} | ||
|
||
this.logger.debug('Trying to grant an API key'); | ||
const authorizationHeader = HTTPAuthorizationHeader.parseFromRequest(request); |
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: I'm not super happy that we should do that, but I can't think of a better solution for the time being as well. So +1.
Just for the records: my main concern is that the fact that we have Authorization
header within request.headers
is just "temporary" backward compatibility escape hatch for the places that still rely on legacy requests. The idea was to store authHeaders
returned from auth hook internally in the core so that it can add them automatically whenever authenticated requested is relayed to ES. And nothing else would have access to them.
However we (Security) register and own this hook, provide auth headers and hence should have an exclusive right to access these headers whenever we need them (e.g. via Core "auth headers accessor" returned as part of the auth hook registration). So even if Core decides to no longer populate request.headers
with Authorization
we'll still have access to them via some API. Migrating to this API will be an implementation detail that won't change public API we expose unless I'm missing something.
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.
What you've said makes sense. I'm also open to alternatives which require some changes to the authentication providers. I began exploring what it'd look like to allow the auth providers to store the parameters we need to grant an API key in a WeakMap<KibanaRequest, GrantAPIKeyParams>
that only the security plugin had access to, but then came on the necessity to parse the Authorization
header within the http
authentication provider and decided to just do the header parsing logic 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.
Yeah, I think what you have now is the best option for the time being. Parsing headers because of http
is where I ended up as well when we had initial discussion on that requirement.
It feels we're still missing some pieces either in Security plugin or in the Core that would have allowed us to come with a more future-proof approach, but I'm sure we'll get there and reuse the work you've done here anyway.
Co-Authored-By: Aleh Zasypkin <[email protected]> Co-Authored-By: Mike Côté <[email protected]>
💚 Build SucceededHistory
To update your PR or re-run it, just comment with: |
* master: [Uptime] Skip failing location test temporarily (elastic#60938) [ML] Disabling datafeed editing when job is running (elastic#60751) Adding `authc.invalidateAPIKeyAsInternalUser` (elastic#60717) [SIEM] Add license check to ML Rule form (elastic#60691) Adding `authc.grantAPIKeyAsInternalUser` (elastic#60423) Support Histogram Data Type (elastic#59387) [Upgrade Assistant] Fix edge case where reindex op can falsely be seen as stale (elastic#60770) [SIEM] [Cases] Update case icons (elastic#60812) [TSVB] Fix percentiles band mode (elastic#60741)
* Parsing the Authorization HTTP header to grant API keys * Using HTTPAuthorizationHeader and BasicHTTPAuthorizationHeaderCredentials * Adding tests for grantAPIKey * Adding http_authentication/ folder * Removing test route * Using new classes to create the headers we pass to ES * No longer .toLowerCase() when parsing the scheme from the request * Updating snapshots * Update x-pack/plugins/security/server/authentication/http_authentication/http_authorization_header.ts Co-Authored-By: Aleh Zasypkin <[email protected]> * Updating another inline snapshot * Adding JSDoc * Renaming `grant` to `grantAsInternalUser` * Adding forgotten test. Fixing snapshot * Fixing mock * Apply suggestions from code review Co-Authored-By: Aleh Zasypkin <[email protected]> Co-Authored-By: Mike Côté <[email protected]> * Using new classes for changing password * Removing unneeded asScoped call Co-authored-by: Aleh Zasypkin <[email protected]> Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Mike Côté <[email protected]> Co-authored-by: Aleh Zasypkin <[email protected]> Co-authored-by: Elastic Machine <[email protected]> Co-authored-by: Mike Côté <[email protected]>
* master: (26 commits) [Alerting] Fixes flaky test in Alert Instances Details page (elastic#60893) cleanup visualizations api (elastic#59958) Inline timezoneProvider function, remove ui/vis/lib/timezone (elastic#60475) [SIEM] Adds 'Open one signal' Cypress test (elastic#60484) [UA] Upgrade assistant migration meta data can become stale (elastic#60789) [Metrics Alerts] Remove metric field from doc count on backend (elastic#60679) [Uptime] Skip failing location test temporarily (elastic#60938) [ML] Disabling datafeed editing when job is running (elastic#60751) Adding `authc.invalidateAPIKeyAsInternalUser` (elastic#60717) [SIEM] Add license check to ML Rule form (elastic#60691) Adding `authc.grantAPIKeyAsInternalUser` (elastic#60423) Support Histogram Data Type (elastic#59387) [Upgrade Assistant] Fix edge case where reindex op can falsely be seen as stale (elastic#60770) [SIEM] [Cases] Update case icons (elastic#60812) [TSVB] Fix percentiles band mode (elastic#60741) Fix formatter on range aggregation (elastic#58651) Goodbye, legacy data plugin 👋 (elastic#60449) [Metrics UI] Alerting for metrics explorer and inventory (elastic#58779) [Remote clustersadopt changes to remote info API (elastic#60795) Only run xpack siem cypress in PRs when there are siem changes (elastic#60661) ...
I tried to find a way to prevent having to parse
KibanaRequest::headers
to use Elasticsearch's grant API Key API, but I was unable to find a way without breaking support for clients like cURL. So instead, I've attempted to parse theAuthorization
header in the "nicest way possible".The
kibana_system
role doesn't have the recently addedgrant_api
cluster privilege , so for the time being I've been creating a new role and creating a new user with thekibana_system
and the new role:And then changing my
elasticsearch.username
tokibana_system
.