Kibana: Authentication for Enterprise Search APIs

Created on 25 Feb 2020  ·  12Comments  ·  Source: elastic/kibana

Problem

The Enterprise Search team is creating a plugin for Kibana which consumes APIs provided by the Enterprise Search product. They would like to have users who are logged into Kibana transparently authenticate as themselves against the Enterprise Search APIs. These requests to the Enterprise Search APIs are currently originating from the Kibana server.

Enterprise Search provides multiple modes of authenticating their own users:

Standard: The default method: Users are managed by App Search. Use this to keep user management coupled to App Search. Users are invited and administrated by an App Search account owner.
Elasticsearch Native Realm: Users are managed by the Elasticsearch native realm. If your Elasticsearch cluster is already managing users and their roles, then you can prevent duplication of effort. Whether you're using Kibana or automating user creation via Elasticsearch APIs, you can set App Search to inherit that configuration and then use Role Mapping to tie those existing roles to App Search users and their own permissions.
Elasticsearch SAML: Allow a third-party authentication provider like Auth0 or Okta to manage users within Elasticsearch. Inherits SAML settings from Elasticsearch. Role Mapping associates third-party governed roles with App Search users and their own permissions.

_Source: https://swiftype.com/documentation/app-search/self-managed/security_

Potential Solutions

callWithRequest/callAsInternalUser

This is an approach which @constancecchen came up with, and has working locally. The URL for the Enterprise Search API is configured in the kibana.yml, so we aren't worried about a SSRF because of that. This would work with all authentication modes, as the standard authentication mode also relies on users stored in the Elasticsearch native realm.

This would require that the Enterprise Search APIs support the Authorization: Basic <credentials> and Authorization: Bearer <credentials> headers for authenticating end-users.

The only issue that I can think of with this approach is that there's a race condition where the access token that we use to call the Enterprise Search APIs expires during this process. We currently have this same race condition when using callWithRequest/callAsInternalUser against Elasticsearch directly; however, we're adding one more "hop" here: Kibana -> Enterprise Search API -> Elasticsearch as opposed to just Kibana -> Elasticsearch.

HMAC signed header

This is an approach which @kovyrin came up with, but there's not a proof-of-concept for yet.

This would rely on a shared-secret to be configured in the kibana.yml and the app-search.yml. Before the Kibana server calls the Enterprise Search API, it would determine the username of the authenticated end-user, HMAC it using the shared-secret, and add both in the request header.

The biggest issue that I see with this approach is that if the shared-secret was compromised, an attacker would be able to impersonate any user they want when calling the Enterprise Search APIs. We've refrained from relying on the Elasticsearch run-as header for similar reasons. It should be noted that there is no race condition with access tokens expiring in this situation.

Recommended Solution: callWithRequest/callAsInternalUser

Using callWithRequest/callAsInternalUser is more secure, as there is no shared secret that when compromised allows an attacker an impersonate any arbitrary user. Additionally, there is already a race condition when using this directly against Elasticsearch, so adding one more "hop" is likely negligible. The race condition can be solved with additional effort by allowing callWithRequest/callAsInternalUser to initiate the access token refresh process.

Security discuss

Most helpful comment

@kobelb I think we have a clear path forward from now on and can mark this issue as closed. That being said if anyone has anything left to hash out or discuss, feel free to raise it anytime!

All 12 comments

Pinging @elastic/kibana-security (Team:Security)

/cc @elastic/kibana-security @elastic/app-search-eng

Hey hey! Sorry for the late response on this, was out yesterday. First thing I wanted to mention is that @orhantoy deserves all the credit for the first solution, definitely not me! 😁

Second thing I wanted to make sure I understood - we currently aren't using the callWithRequest/callAsInternalUser methods specifically. Our code looks more like this:

const response = await fetch(appSearchUrl, {
  headers: { Authorization: request.headers.authorization },
});

(where request is coming from Kibana's server handler(request).)

Is this something you'd like us to modify our current approach with to make use of callWithRequest/callAsInternalUser? Or would the current iteration pass security review?

I was originally concerned our use of request.headers.authorization wouldn't pass review due to the "Mishandling requests" section in Kibana's security review Google doc:

[...] Care should be taken when accessing information from requests and where they end up being consumed or sent. The request headers are particularly problematic because they directly contain the information that is needed to impersonate users. We should not be forwarding sensitive information to third-party resources.

But it sounds like due to the URL being set in config/kibana.yml, that that makes it fine - does that sound right?

Is this something you'd like us to modify our current approach with to make use of callWithRequest/callAsInternalUser? Or would the current iteration pass security review?

Now that you've brought it up, callWithRequest/callAsInternalUser only works with Elasticsearch... Apologies for overlooking this earlier. The code sample you've included is similar to what we do internally for callWithRequest/callAsInternalUser. The primary difference is there are additional settings which can be used to customize the headers which are sent to Elasticsearch: elasticsearch.customHeaders and elasticsearch.requestHeadersWhitelist. I don't think we need to worry about ensuring parity for the Enterprise search APIs at this time though.

TLDR; what you have is fine.

But it sounds like due to the URL being set in config/kibana.yml, that that makes it fine - does that sound right?

That's correct. Do you all have a default config value for the appSearchUrl? If so, we'll want to ensure it's for localhost on a non commonly used port. Using http://localhost:80 for the default is potentially problematic because it's common for a web server to be listening on this port, which may very well not be the Enterprise Search APIs.

Awesome, thanks a ton for the sanity checks Brandon! 🎉

Do you all have a default config value for the appSearchUrl?

We don't, and I don't think we plan on having one (feel free to double-check me @orhantoy / @kovyrin). That being said, our localhost port for Enterprise Search is typically 3002 (which is fairly non-standard I think), so we should hopefully be OK there either way!

The default one will be on localhost:3002, and on cloud it will be pre-populated with the actual App Search URL

@kovyrin Just curious, how do we plan on doing that logic (on cloud it will be pre-populated with the actual App Search URL)? Is there some way we can auto-write to config/kibana.yml? Or do we want to figure that out within the server-side logic of the Kibana plugin?

Personally, for non-Cloud users, I don't know if we should pre-populate a default config URL. That's currently the logic I'm using to send people to the Setup Guide (if their config URL is missing), and if we're pre-populating that field, the user skips the Setup Guide entirely and just goes to the "Something's wrong connecting to your App Search" error screen - not a great user experience. But let's take that convo offline / to another thread if needed :)

When provisioning the Kibana instance, Cloud will populate the configuration for it with whatever we want. They could conditionally add the URL for us if the deployment has an App Search instance within.

For non-cloud users, we could default to localhost:3000 and ask them to change it in their kibana.yml if needed.

@kobelb I think we have a clear path forward from now on and can mark this issue as closed. That being said if anyone has anything left to hash out or discuss, feel free to raise it anytime!

Could you point here the documentation related to what we have to set in kibana.yml related to the Enterprise Search plugin ? Thanks.

@flavienbwk Someone recently let us know that we totally missed update the Elastic Docs with our new kibana.yml config, so apologies, I don't have a handy link for you just yet 🤦‍♀️ That's our fault, and I'll update with a follow-up comment once we've updated the docs.

In the meanwhile, the config setting to use in kibana.yml looks like this:

enterpriseSearch.host: http://localhost:3002

... Or whatever the real/production URL of your Enterprise Search instance is.

Hope that helps!

Thank you very much for these news @constancecchen

Was this page helpful?
0 / 5 - 0 ratings