-
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
[http] Prevent access to internal-only APIs when running in serverless #151940
Comments
Pinging @elastic/kibana-core (Team:Core) |
++ to that! For versioned routers, are you thinking something along the lines of
where Disclaimer: the implementation needs discussion and (possibly) a design proposal |
@lukeelmers is restricting access to internal-only APIs a hard requirement or is it more of a desired mechanism? We want to know if we should build that into the current versioned API design now. |
It's not a hard requirement, however if we don't have a solution like this it opens us up to the risk that internal APIs are discovered/used, and then we get stuck supporting them if they become widely adopted. Explicitly documenting these APIs as not supported is fine as an interim solution (vs leaving them undocumented and hoping nobody uses them). I just thought if we were already introducing a versioned router & asking all teams to move to it, it would be an ideal time to also ask them to flag their APIs as internal/public in a way that's more formal than a pathname convention. Even if we don't choose to enforce this until later, it'd probably make our lives easier than chasing folks down to revisit all of their APIs again later. But whether it's worth taking that tradeoff is up to the team 🙂 |
I'm fully on board with adding an optional internal flag as a recommended mechanism to "protect" APIs not intended for external use. |
Thinking aloud here, we could take the approach of making no assumptions as to how a domain reacts to requests in internal APIs, but we'd need to decide pretty soon on an approach.
Since there's already a mechanism, why not implement something similar, maybe even reuse that exact same header? |
Two questions to consider if we decide to do this:
|
good questions! For 1, we should default to internal and leave public APIs as those that are opted in to make them public. Of course, we'd need to flesh out and agree on a design pretty soon, ideally before teams start their migration. There's a potential repercussion to opting into a public API: Do we need to implement a modified version for those? Once teams consciously decide to make an API public, they're signing a contract to maintain said API for a specified amount of time (AFAIK, that's 18 months). @jloleysens how would that affect the design? For 2: That would be a lovely feature! It's probably the easiest way and safer option than introducing a whole new API migration strategy. |
@jloleysens the The example then becomes something like this:
|
++ to being explicit about an app being internal or public! However, should it be a flag For instance:
For WDYT? |
For the |
++ I was trying to use names to make it super-obvious in the comment. I think for the MVP, we should be fine with |
So it will also be stripped from browser requests.
If possible to send from browsers we should use this. Otherwise we should pick something else.
Yeah, I am curious about how we want to treat the distinction between serverless and on-prem/cloud. For serverless we can definitely be more aggressive in reducing the HTTP APIs we make available, but it seems we will need to continue as we have done for on-prem/cloud for a while. This probably means some kind of on/off switch based on whether we are running serverless (assuming Kibana has the final say about whether a request goes through).
It is related, but the concept of internal/public does not only apply to versioned routes. It applies to ALL routes. The internal/public concept could be middleware run by our server on all requests. The design work here should be to decide where to expose this kind of setting: existing
From an API perspective, I think a binary setting of |
++ |
That's a great point, I copied that here because it is what ES will likely do, but this wouldn't work for browser traffic from Kibana. Regardless I don't think it matters too much - even if folks can circumvent it, they'd have to really go out of their way to do so (vs just copying an endpoint that happens to have
Agreed, we should be able to inject it via the client-side
Yeah we'd need to conditionally enforce this on serverless only for awhile, but perhaps in the future could roll it out consistently for self-managed as well.
What types of non-versioned routes are you thinking of? Static assets? It seems to me that pretty much all of our http APIs will need to be versioned in some way. To support downtimeless upgrades we'll even need to have a concept of versioning on our static assets eventually, whether it be handled on the Kibana side or on a CDN.
++ I don't think we need to get super granular about this. Ultimately this proposal isn't a security feature, it's about protecting users from foot guns and protecting us from unintended maintenance overhead.
This is the main thing I wanted to propose we consider now. I don't want to blow up the scope of the versioned router project, and we don't even need to enforce anything at this point, but it'd be worth considering whether to start collecting this info since we're already asking teams to migrate their APIs. |
Having a route default to internal makes it easier to introduce the mechanism later, leaving us some room to think through this more. I want to keep it simple and we need to get it right. |
My main question here is, do we need to make a distinction between 'Kibana-internal' (APIs that should only be accessed from the Kibana UI, or for -TBD- inter-node communcations) and 'Stack-internal' (APIs callable from other components of the stack but not from our customers), or are we fine with just the My feeling is that we're good with only two options, as we're supposed to be in control of internal calls, and therefor adding an isolation layer between Kibana and the rest of the 'internal' components doesn't seem to bring much value. So, if we go with an
Adding the option as mandatory would mean adapting all the routes definition in the whole codebase in a single PR, which doesn't seem realistic, so I think we don't have any choice that having it optional for the current unversioned router. For the versioned router, I would just introduce the option in the initial implementation, which would allow us to have it defined as mandatory. Now regarding the appropriate default, to avoid the risk of introducing breaking changes, I think we're kinda forced to default to
I don't really see any value in this. I do see value in doing it the other way around though, to set the default
TLDR: Proposal summaryAllow to define Kibana HTTP APIs either as being 1. Adapting the current server-side route declarationAdapt RouteConfigOptions to add a new optional This parameter can set to either When unspecified, the value will be inferred from the 2. Adapting the versioned router specificationSame idea. note that the Given the API isn't exposed / implemented yet, we don't need to have the option optional. For versioned routes, 3. Adapting Core's internal router to handle the
|
Overall: great summary and breakdown of a path forward. I am happy to move ahead with the proposed tasking. I only have a few comments for now.
I like this idea!
+1 to this idea too
One interesting case is Console. We would need to make sure that Kibana <-> ES traffic is not flagged as internal for the proxied requests. Might not require any changes, just another instance of the
I know something like this is strictly necessary to avoid removing these APIs for cloud/on-prem, but I am a bit concerned about the the scale of this change and the impact it may have on tests and QA. Do we need specialised tests for Kibana in "restricted internal APIs" mode? Not saying we need a specific solution to this, rather just think about how we will ensure quality of the serverless variant. |
What do you mean here exactly? Are you thinking about the eventual need to have a full FTR coverage that all 'internal' APIs are not accessible without the header when this 'restrict' config option is enabled? Or is this something else? |
I had in mind that Kibana UI still works with Kibana server as intended (has the same APIs available). But perhaps this is as simple as ensuring that we are sending the needed header via the http client. |
That's probably an argument in favor of tackling this sooner than later, that way folks can hit this barrier early and adapt their code (vs us needing to go back and chase them retroactively after we've broken stuff by enabling the setting).
FWIW it looks like ES is treating it as a Perhaps in our case it'd be more appropriate to do something like a 400 with |
Personally, I prefer a 403 since it's communicating a restricted-access situation whereas a 400 implies that the problem can be fixed. We know that API customers would try to "fix" their call but they can't (assuming that the |
I'll go ahead and create issues for each step in the proposal and @jloleysens and I can work on them in parallel. We don't really have the luxury of spending too much time debating names. The error code, however, we should settle on (even though we can still change it in PR review). If we want to be consistent with Elasticsearch, then yes, we should go with a 400 but as @pgayvallet points out, kibana's use case is slightly different. I'd rather go with a 403, directly implying restricted access but we can also debate that. |
Can I just give a strong +1 here, but not just for serverless. We are introducing saveable event annotation groups in Lens. The plan was to mark that SO type as hidden so that customers are not able to automate the creation of annotations via the global SO APIs. However, #149098 directs us to create an HTTP API of our own. This opens up the problem again since external users could again use it to automate the creation of these objects. |
Having created issues for each step, I think that we could only realistically handle the config option for restricting API access and ensuring that intra-stack components are updated as a separate item of work. The Versioned Router will inherit from the Core implementation (as will the configuration adaption for specific environments). For now, we should communicate the intent to add access control in the guide and update the design once the core part is done. |
This control will be available on serverless initially (not cloud or on-prem). One of the conventions for HTTP APIs in Kibana (there are not many) is to prefix your path with |
@jloleysens we absolutely will. But, as has already been discussed in this thread, if enough customers use the internal APIs they can use business pressure to force us away from breaking changes. As an extra deterrent, I will probably end up using some heuristics in the body of the handler to check if the request originates in Kibana UI. I really like the idea of a universal boolean flag I can just set. 👍 |
The team discussed this and decided to postpone the full implementation until other priorities are met. For now, we'll lay the groundwork (steps 1, 2 and possibly 3) and add the config option, inferring it for routes that don't explicitly declare the Versioned routes must declare |
@lukeelmers We keep "promising" internal APIs will have restricted access and it would be great to have that in place before folks start migrating to versioned routes. Is there anything more we can use to motivate jumping on the rest of the implementation here? |
I think the main motivation is making sure we have these restrictions in place before any customers are using the system in the MVP -- otherwise we risk internal APIs being discovered and used, or folks migrating existing integrations which rely on internal APIs from self-managed to serverless, where they wouldn't have any protections in place and might give the impression that these APIs are still fine to use. IMO there isn't much risk in communicating to teams how to set the |
Closed by #156935 |
Currently Kibana only uses a naming convention to distinguish between internal (
/internal
) and public (/api
) routes. We've seen many instances in the past of internal APIs being (ab)used, even when they are undocumented. The introduction of a serverless offering presents us with an opportunity to better enforce this separation and ensure we don't get stuck maintaining internal APIs just because they've been picked up.Elasticsearch recently introduced serverless API protection, where calls to internal HTTP APIs are rejected if an
x-elastic-internal-origin
header is not present: elastic/elasticsearch#93607.Presumably these headers could eventually be stripped from incoming requests at the control plane level to ensure the protection can't be circumvented.We should consider doing the same for Kibana, that is, formalize a way to flag routes as
internal
, and reject incoming requests to these routes if they are missing the relevant header. We could use the same header as ES or consider leveraging the existingx-elastic-product-origin
header (which serves a similar purpose, though it wasn't designed with this use case in mind).This would mean all requests from the client-side
http
service would need to have the header injected. We wouldn't be able to enforce this boundary for self-managed just yet, but could consider doing so in a future major version upgrade.This is potentially related to the versioned router discussion -- depending on how that's implemented, we could also use it as an opportunity to incorporate a concept of
internal
to our APIs.The text was updated successfully, but these errors were encountered: