-
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
Enables preventing access to internal APIs #156935
Enables preventing access to internal APIs #156935
Conversation
… lifecycle handlers test
@elasticmachine merge upstream |
@@ -17,3 +17,6 @@ xpack.license_management.enabled: false | |||
# Other disabled plugins | |||
#xpack.canvas.enabled: false #only disabable in dev-mode | |||
xpack.reporting.enabled: false | |||
|
|||
# Enforce restring access to internal APIs see https://github.com/elastic/kibana/issues/151940 | |||
# server.restrictInternalApis: 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.
Added here for intended purpose. Once all intra-stack components and Kibana browser-side code not using Core's http service are updated, we can enable the restriction.
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.
Self review
@@ -160,6 +160,7 @@ describe('Fetch', () => { | |||
expect(fetchMock.lastOptions()!.headers).toMatchObject({ | |||
'content-type': 'application/json', | |||
'kbn-version': 'VERSION', | |||
'x-elastic-internal-origin': 'Kibana', |
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're not concerned about the content here, only that the header is present.
@@ -150,6 +150,7 @@ const configSchema = schema.object( | |||
}, | |||
} | |||
), | |||
restrictInternalApis: schema.boolean({ defaultValue: false }), // allow access to internal routes by default to prevent breaking changes in current offerings |
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.
Code comment to explain the intent.
@@ -39,6 +40,27 @@ export const createXsrfPostAuthHandler = (config: HttpConfig): OnPostAuthHandler | |||
}; | |||
}; | |||
|
|||
export const createRestrictInternalRoutesPostAuthHandler = ( |
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.
This takes a similar approach to createVersionCheckPostAuthHandler
.
@@ -86,7 +106,12 @@ export const registerCoreHandlers = ( | |||
config: HttpConfig, | |||
env: Env | |||
) => { | |||
// add headers based on config |
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 don't often update these and I added comments to more easily follow where they're applied.
@@ -157,7 +157,9 @@ export interface BulkGetItem { | |||
export function isKibanaRequest({ headers }: KibanaRequest) { | |||
// The presence of these two request headers gives us a good indication that this is a first-party request from the Kibana client. | |||
// We can't be 100% certain, but this is a reasonable attempt. | |||
return headers && headers['kbn-version'] && headers.referer; | |||
return ( | |||
headers && headers['kbn-version'] && headers.referer && headers['x-elastic-internal-origin'] |
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.
ATM, there are 4: core usage stats client, cases plugin, rule registry, and spaces usage stats client. I've added the header to core's implementation.
Are the concerns Joe raised still val a valid reason to have 4 implementations of isKibanaRequest
or can we add the extra header to that check?
A while back, Joe raised concerns about adding a header to all Kibana requests:
- other integrations might attempt to use this header
- adding a new header might present problems for users with reverse proxies that may strip headers not in an allow-list; this would cause false negative for first-party request detection
If these are still valid, then having different implementations is warrented. @response-ops @kibana-security would a single method fullfil your needs or are concerns around false negatives be an issue for you?
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.
IMO (1) and (2) should be mitigated because we will not be restricting access to internal endpoints by default so everything should continue working as-is for on-prem.
// We can't be 100% certain, but this is a reasonable attempt. | ||
return headers && headers['kbn-version'] && headers.referer; | ||
return ( | ||
headers && headers['kbn-version'] && headers.referer && headers['x-elastic-internal-origin'] |
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.
similar comment to above
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.
More self review comments
src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker
Show resolved
Hide resolved
|
||
const isApiRoute = | ||
route.options.tags.includes(ROUTE_TAG_API) || | ||
route.path.startsWith('/api/') || | ||
route.path.startsWith('/internal/'); | ||
const isAjaxRequest = hasVersionHeader || hasXsrfHeader; | ||
const isAjaxRequest = hasVersionHeader || hasXsrfHeader || hasIternalOriginHeader; |
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.
@elastic/kibana-security I took the initiative and applied the implementation. Feel free to suggest changes, I'm not too familiar with your domains.
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.
Thanks, @TinaHeiligers. Question for you: Under what conditions will requests be expected to have an internal origin header
, but not one of the version
or xsrf
headers?
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.
@legrego good question! I can't think of one TBH. Can you?
Maybe it's better not to add the internal header, it could become confusing down the line. I'll make the change.
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 don't think this change is necessary at this point, but thanks for taking this function into consideration!
Pinging @elastic/kibana-core (Team:Core) |
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 job @TinaHeiligers ! Thanks for responding to my questions.
x-pack/plugins/security/server/authentication/can_redirect_request.test.ts
Show resolved
Hide resolved
@@ -11,7 +11,7 @@ import { ROUTE_TAG_API, ROUTE_TAG_CAN_REDIRECT } from '../routes/tags'; | |||
import { canRedirectRequest } from './can_redirect_request'; | |||
|
|||
describe('can_redirect_request', () => { | |||
it('returns true if request does not have either a kbn-version or kbn-xsrf header', () => { | |||
it('returns true if request does not have either a kbn-version or kbn-xsrf header or x-elastic-internal-origin', () => { |
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('returns true if request does not have either a kbn-version or kbn-xsrf header or x-elastic-internal-origin', () => { | |
it('returns true if request does not have either a kbn-version or kbn-xsrf header', () => { |
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.
Bfetch changes LGTM
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.
bfetch changes LGTM
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.
bfetch changes LGTM
@elasticmachine merge upstream |
@elasticmachine merge upstream |
💚 Build Succeeded
Metrics [docs]Module Count
Public APIs missing comments
Page load bundle
Unknown metric groupsAPI count
ESLint disabled line counts
Total ESLint disabled count
History
To update your PR or re-run it, just comment with: |
fix #163654 This PR enforces internal API restrictions in our standard offering. Internal APIs are subject to rapid change and are intentionally not public. By restricting their access, we protect consumers from these rapid changes. This PR does not change any public APIs and they remain available for external consumption. ## Note to reviewers: I chose the most practical way of resolving the failures (add the header or disable the restriction). ## Details Requests to internal Kibana APIs will be restricted globally. This allows more flexibility in making breaking changes to internal APIs, without a risk to external consumers. ## Why are we doing this? The restriction is there to help mitigate the risk of breaking external integrations consuming APIs. Internal APIs are subject to frequent changes, necessary for feature development. ## What this means to plugin authors : Kibana core applies the restriction when enabled through HTTP config. ## What this means to Kibana consumers: Explicitly restricting access to internal APIs has advantages for external consumers: - Consumers can safely integrate with Kibana's stable, public APIs - Consumers are protected from Internal route development, which may involve breaking changes - Relevant information in Kibana's external documentation that is user-friendly and complete. KB article explaining the change (tracked as part of elastic/kibana-team#1044) ## Release note Starting with this release, requests to internal Kibana APIs are globally restricted by default. This change is designed to provide more flexibility in making breaking changes to internal APIs while protecting external consumers from unexpected disruptions. **Key Changes**: • _Internal API Access_: External consumers no longer have access to Kibana’s internal APIs, which are now strictly reserved for internal development and subject to frequent changes. This helps ensure that external integrations only interact with stable, public APIs. • _Error Handling_: When a request is made to an internal API without the proper internal identifier (header or query parameter), Kibana will respond with a 400 Bad Request error, indicating that the route exists but is not allowed under the current Kibana configuration. ## How to test this ### Running kibana 1. Set `server.restrictInternalApis: true` in `kibana.yml` 2. Start es with any license 3. Start kibana 4. Make an external request to an internal API: <details> <summary>curl request to get the config for 9.0:</summary> ```unset curl --location 'localhost:5601/abc/api/kibana/management/saved_objects/_bulk_get' \ --header 'Content-Type: application/json' \ --header 'kbn-xsrf: kibana' \ --header 'x-elastic-internal-origin: kibana' \ --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \ --data '[ { "type": "config", "id": "9.0.0" } ]' ``` </details> The request should be successful. 5. Make the same curl request without the internal origin header <details> <summary>curl:</summary> ```unset curl --location 'localhost:5601/abc/api/kibana/management/saved_objects/_bulk_get' \ --header 'Content-Type: application/json' \ --header 'kbn-xsrf: kibana' \ --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' \ --data '[ { "type": "config", "id": "9.0.0" } ]' ``` </details> The response should be an error similar to: `{"statusCode":400,"error":"Bad Request","message":"uri [/api/kibana/management/saved_objects/_bulk_get] with method [post] exists but is not available with the current configuration"}` 6. Remove `server.restrictInternalApis` from `kibana.yml` or set it to `false`. 7. Repeat both curl requests above. Both should respond without an error. ### Checklist Delete any items that are not applicable to this PR. - [X] [Documentation] was added for features that require explanation or tutorials (In PR #191943) - [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios (and PR #192407) - [x] If a plugin configuration key changed, check if it needs to be allowlisted in the cloud and added to the [docker list](https://github.com/elastic/kibana/blob/main/src/dev/build/tasks/os_packages/docker_generator/resources/base/bin/kibana-docker) (docker list updated in #156935, cloud stack packs for 9.0 kibana to follow) ### Risk Matrix | Risk | Probability | Severity | Mitigation/Notes | |---------------------------|-------------|----------|-------------------------| | The restriction is knowingly bypassed by end-users adding the required header to use `internal` APIs | Low | High | Kibana's internal APIs are not documented in the OAS specifications. External consumption will be prevented unless explicitly bypassed. | | Upstream services don't include the header and requests to `internal` APIs fail | Medium | Medium | The Core team needs to ensure intra-stack components are updated too | ### For maintainers - [x] This was checked for breaking API changes and was [labeled appropriately](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process) --------- Co-authored-by: Elastic Machine <[email protected]>
Part of #151940
fix #152282
fix #152283
fix #152293
partially address #152287
Summary
This PR follows on from the initial work done to more formally declare APIs as
internal
orpublic
:Details
Requests to internal Kibana APIs will be restricted for serverless. This allows us more flexibility in making breaking changes to internal APIs, without having to go through a lengthy deprecation period, as is required for changes to public APIs.
Why are we doing this?
Explicitly enforcing a difference between internal and public APIs has a few other advantages:
There's always the catch that our code is public and end-users can still get around the restriction by adding the
x-elastic-internal-origin
header. However, if and when they do so and something 'breaks', we're not held liable.What this means to plugin authors :
Core applies the restriction when enabled through HTTP config as
server.restrictInternalApis: <boolean>
.What this means is that once we "flip the switch", any requests to internal APIs will throw if they don't send the
x-elastic-internal-product
header.I've handled the known cases in this issue and opened #152287 asking teams to help out.
Checklist
jsdocs
. We explicitly don't intend to document the config option publically)Risk Matrix
x-elastic-internal-origin
header to accessinternal
APIsinternal
APIs must have theexperimental
warning and clearly state thatinternal
APIs are subject to frequent changesinternal
APIs failFor maintainers