From 8bf94876b9150931efdc3b4af3fc6f3d38b4e645 Mon Sep 17 00:00:00 2001 From: Jeramy Soucy Date: Tue, 19 Nov 2024 09:54:40 +0100 Subject: [PATCH] Surface Kibana security route deprecations in Upgrade Assistant (#199656) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #197389 ## Summary Uses the `deprecated` route configuration option on all Kibana Security "v1" endpoints. This will surface deprecation information in the Upgrade Assistant. ## Related PRs - https://github.com/elastic/kibana/pull/50695 - `7.6.0`, deprecated - `/api/security/v1/me` - `/api/security/v1/logout` - `/api/security/v1/oidc/implicit` - `/api/security/v1/oidc` (POST) - https://github.com/elastic/kibana/pull/53886 - `7.6.0`, deprecated `/api/security/v1/oidc` (GET) - https://github.com/elastic/kibana/pull/47929 - `8.0.0`, dropped `/api/security/v1/saml` (`breaking` release note) - https://github.com/elastic/kibana/pull/106665 - restored `/api/security/v1/saml` but warned as deprecated (no release note) ## Testing 1. Start ES & Kibana in trial license mode 2. Make several calls to one or more of the deprecated endpoints 3. Navigate to `Stack Management`->`Upgrade Assistant` 4. Click on Kibana warnings Screenshot 2024-11-18 at 10 01 10 AM 5. Confirm the called endpoints are displayed as warnings in the Upgrade Assistant Screenshot 2024-11-18 at 9 59 34 AM ## Previous release notes ### v7.6.0 https://www.elastic.co/guide/en/kibana/7.6/release-notes-7.6.0.html#deprecation-7.6.0 https://www.elastic.co/guide/en/kibana/7.6/breaking-changes-7.6.html The deprecations are not listed in the release notes or breaking changes notes. ### v8.0.0 https://www.elastic.co/guide/en/kibana/current/release-notes-8.0.0-alpha1.html#rest-api-changes SAML endpoint deprecation only appears in the 8.0.0-alpha1 release notes, and was reverted in 8.0.0-alpha2 # Release note See `docs/upgrade-notes.asciidoc` in file changes # Follow-up A follow-up PR must be created to create and backfill the docLinks. --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> --- docs/upgrade-notes.asciidoc | 38 ++++++---- packages/kbn-doc-links/src/get_doc_links.ts | 1 + packages/kbn-doc-links/src/types.ts | 1 + x-pack/plugins/security/server/plugin.ts | 1 + .../server/routes/authentication/common.ts | 43 +++++++++-- .../server/routes/authentication/oidc.ts | 71 ++++++++++++++++--- .../server/routes/authentication/saml.ts | 23 +++++- .../security/server/routes/index.mock.ts | 3 + .../plugins/security/server/routes/index.ts | 3 +- x-pack/plugins/security/tsconfig.json | 1 + 10 files changed, 157 insertions(+), 28 deletions(-) diff --git a/docs/upgrade-notes.asciidoc b/docs/upgrade-notes.asciidoc index c2d866f90eed3..4d4208b2253f7 100644 --- a/docs/upgrade-notes.asciidoc +++ b/docs/upgrade-notes.asciidoc @@ -49,6 +49,32 @@ For Elastic Security release information, refer to {security-guide}/release-note [float] ==== Kibana APIs +[discrete] +[[breaking-199656]] +.Removed all security v1 endpoints (9.0.0) +[%collapsible] +==== +*Details* + +All `v1` Kibana security HTTP endpoints have been removed. + +`GET /api/security/v1/logout` has been replaced by `GET /api/security/logout` +`GET /api/security/v1/oidc/implicit` has been replaced by `GET /api/security/oidc/implicit` +`GET /api/security/v1/oidc` has been replaced by GET `/api/security/oidc/callback` +`POST /api/security/v1/oidc` has been replaced by POST `/api/security/oidc/initiate_login` +`POST /api/security/v1/saml` has been replaced by POST `/api/security/saml/callback` +`GET /api/security/v1/me` has been removed with no replacement. + +For more information, refer to {kibana-pull}199656[#199656]. + +*Impact* + +Any HTTP API calls to the `v1` Kibana security endpoints will fail with a 404 status code starting from version 9.0.0. +Third party OIDC and SAML identity providers configured with `v1` endpoints will no longer work. + +*Action* + +Update any OIDC and SAML identity providers to reference the corresponding replacement endpoint listed above. +Remove references to the `/api/security/v1/me` endpoint from any automations, applications, tooling, and scripts. +==== + [discrete] [[breaking-193792]] .Access to all internal APIs is blocked (9.0.0) @@ -814,18 +840,6 @@ The legacy audit logger has been removed. For more information, refer to {kibana Audit logs will be written to the default location in the new ECS format. To change the output file, filter events, and more, use the <>. ==== -[discrete] -[[breaking-47929]] -.[Security] Removed `/api/security/v1/saml` route. (8.0) -[%collapsible] -==== -*Details* + -The `/api/security/v1/saml` route has been removed and is reflected in the kibana.yml `server.xsrf.whitelist` setting, {es}, and the Identity Provider SAML settings. For more information, refer to {kibana-pull}47929[#47929] - -*Impact* + -Use the `/api/security/saml/callback` route, or wait to upgrade to 8.0.0-alpha2 when the `/api/security/saml/callback` route breaking change is reverted. -==== - [discrete] [[breaking-41700]] .[Security] Legacy browsers rejected by default. (8.0) diff --git a/packages/kbn-doc-links/src/get_doc_links.ts b/packages/kbn-doc-links/src/get_doc_links.ts index 52a3e7b7427e7..c7d714ebfbbb7 100644 --- a/packages/kbn-doc-links/src/get_doc_links.ts +++ b/packages/kbn-doc-links/src/get_doc_links.ts @@ -717,6 +717,7 @@ export const getDocLinks = ({ kibanaBranch, buildFlavor }: GetDocLinkOptions): D mappingRoles: `${ELASTICSEARCH_DOCS}mapping-roles.html`, mappingRolesFieldRules: `${ELASTICSEARCH_DOCS}role-mapping-resources.html#mapping-roles-rule-field`, runAsPrivilege: `${ELASTICSEARCH_DOCS}security-privileges.html#_run_as_privilege`, + deprecatedV1Endpoints: `${KIBANA_DOCS}breaking-changes-summary.html#breaking-199656`, }, spaces: { kibanaLegacyUrlAliases: `${KIBANA_DOCS}legacy-url-aliases.html`, diff --git a/packages/kbn-doc-links/src/types.ts b/packages/kbn-doc-links/src/types.ts index d3ece5b61daa1..ac0f66d83b705 100644 --- a/packages/kbn-doc-links/src/types.ts +++ b/packages/kbn-doc-links/src/types.ts @@ -505,6 +505,7 @@ export interface DocLinks { mappingRoles: string; mappingRolesFieldRules: string; runAsPrivilege: string; + deprecatedV1Endpoints: string; }>; readonly spaces: Readonly<{ kibanaLegacyUrlAliases: string; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index 3007973d59b47..afd21a83712ae 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -338,6 +338,7 @@ export class SecurityPlugin getUserProfileService: this.getUserProfileService, analyticsService: this.analyticsService.setup({ analytics: core.analytics }), buildFlavor: this.initializerContext.env.packageInfo.buildFlavor, + docLinks: core.docLinks, }); return Object.freeze({ diff --git a/x-pack/plugins/security/server/routes/authentication/common.ts b/x-pack/plugins/security/server/routes/authentication/common.ts index 0c91a6c7f3858..4ee2e57a33517 100644 --- a/x-pack/plugins/security/server/routes/authentication/common.ts +++ b/x-pack/plugins/security/server/routes/authentication/common.ts @@ -7,6 +7,7 @@ import type { TypeOf } from '@kbn/config-schema'; import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; import { parseNextURL } from '@kbn/std'; import type { RouteDefinitionParams } from '..'; @@ -33,6 +34,7 @@ export function defineCommonRoutes({ license, logger, buildFlavor, + docLinks, }: RouteDefinitionParams) { // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. // For a serverless build, do not register deprecated versioned routes @@ -40,6 +42,7 @@ export function defineCommonRoutes({ '/api/security/logout', ...(buildFlavor !== 'serverless' ? ['/api/security/v1/logout'] : []), ]) { + const isDeprecated = path === '/api/security/v1/logout'; router.get( { path, @@ -57,13 +60,29 @@ export function defineCommonRoutes({ excludeFromOAS: true, authRequired: false, tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.logoutRouteMessage', { + defaultMessage: + 'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/logout" instead.', + values: { path }, + }), + reason: { + type: 'migrate', + newApiMethod: 'GET', + newApiPath: '/api/security/logout', + }, + }, + }), }, }, async (context, request, response) => { const serverBasePath = basePath.serverBasePath; - if (path === '/api/security/v1/logout') { + if (isDeprecated) { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/logout" URL instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/logout" URL instead.`, { tags: ['deprecation'] } ); } @@ -96,7 +115,7 @@ export function defineCommonRoutes({ '/internal/security/me', ...(buildFlavor !== 'serverless' ? ['/api/security/v1/me'] : []), ]) { - const deprecated = path === '/api/security/v1/me'; + const isDeprecated = path === '/api/security/v1/me'; router.get( { path, @@ -107,10 +126,24 @@ export function defineCommonRoutes({ }, }, validate: false, - options: { access: deprecated ? 'public' : 'internal' }, + options: { + access: isDeprecated ? 'public' : 'internal', + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.meRouteMessage', { + defaultMessage: + 'The "{path}" endpoint is deprecated and will be removed in the next major version.', + values: { path }, + }), + reason: { type: 'remove' }, + }, + }), + }, }, createLicensedRouteHandler(async (context, request, response) => { - if (deprecated) { + if (isDeprecated) { logger.warn( `The "${basePath.serverBasePath}${path}" endpoint is deprecated and will be removed in the next major version.`, { tags: ['deprecation'] } diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts index bb1ed6959e690..d1d31f4c49a69 100644 --- a/x-pack/plugins/security/server/routes/authentication/oidc.ts +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -25,9 +25,11 @@ export function defineOIDCRoutes({ logger, getAuthenticationService, basePath, + docLinks, }: RouteDefinitionParams) { // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. for (const path of ['/api/security/oidc/implicit', '/api/security/v1/oidc/implicit']) { + const isDeprecated = path === '/api/security/v1/oidc/implicit'; /** * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow * is used, so that we can extract authentication response from URL fragment and send it to @@ -37,13 +39,32 @@ export function defineOIDCRoutes({ { path, validate: false, - options: { authRequired: false, excludeFromOAS: true }, + options: { + authRequired: false, + excludeFromOAS: true, + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.oidcImplicitRouteMessage', { + defaultMessage: + 'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/implicit" instead.', + values: { path }, + }), + reason: { + type: 'migrate', + newApiMethod: 'GET', + newApiPath: '/api/security/oidc/implicit', + }, + }, + }), + }, }, (context, request, response) => { const serverBasePath = basePath.serverBasePath; - if (path === '/api/security/v1/oidc/implicit') { + if (isDeprecated) { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/implicit" URL instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/implicit" URL instead.`, { tags: ['deprecation'] } ); } @@ -84,6 +105,7 @@ export function defineOIDCRoutes({ // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. for (const path of ['/api/security/oidc/callback', '/api/security/v1/oidc']) { + const isDeprecated = path === '/api/security/v1/oidc'; router.get( { path, @@ -117,6 +139,22 @@ export function defineOIDCRoutes({ excludeFromOAS: true, authRequired: false, tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.oidcCallbackRouteMessage', { + defaultMessage: + 'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/callback" instead.', + values: { path }, + }), + reason: { + type: 'migrate', + newApiMethod: 'GET', + newApiPath: '/api/security/oidc/callback', + }, + }, + }), }, }, createLicensedRouteHandler(async (context, request, response) => { @@ -133,9 +171,9 @@ export function defineOIDCRoutes({ authenticationResponseURI: request.query.authenticationResponseURI, }; } else if (request.query.code || request.query.error) { - if (path === '/api/security/v1/oidc') { + if (isDeprecated) { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/callback" URL instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/callback" URL instead.`, { tags: ['deprecation'] } ); } @@ -150,7 +188,7 @@ export function defineOIDCRoutes({ }; } else if (request.query.iss) { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, { tags: ['deprecation'] } ); // An HTTP GET request with a query parameter named `iss` as part of a 3rd party initiated authentication. @@ -175,6 +213,7 @@ export function defineOIDCRoutes({ // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. for (const path of ['/api/security/oidc/initiate_login', '/api/security/v1/oidc']) { + const isDeprecated = path === '/api/security/v1/oidc'; /** * An HTTP POST request with the payload parameter named `iss` as part of a 3rd party initiated authentication. * See more details at https://openid.net/specs/openid-connect-core-1_0.html#ThirdPartyInitiatedLogin @@ -206,13 +245,29 @@ export function defineOIDCRoutes({ authRequired: false, xsrfRequired: false, tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.oidcInitiateRouteMessage', { + defaultMessage: + 'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/oidc/initiate_login" instead.', + values: { path }, + }), + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: '/api/security/oidc/initiate_login', + }, + }, + }), }, }, createLicensedRouteHandler(async (context, request, response) => { const serverBasePath = basePath.serverBasePath; - if (path === '/api/security/v1/oidc') { + if (isDeprecated) { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version. Use "${serverBasePath}/api/security/oidc/initiate_login" URL for Third-Party Initiated login instead.`, { tags: ['deprecation'] } ); } diff --git a/x-pack/plugins/security/server/routes/authentication/saml.ts b/x-pack/plugins/security/server/routes/authentication/saml.ts index 8cee1df2da88b..c45f1eed3affd 100644 --- a/x-pack/plugins/security/server/routes/authentication/saml.ts +++ b/x-pack/plugins/security/server/routes/authentication/saml.ts @@ -6,6 +6,7 @@ */ import { schema } from '@kbn/config-schema'; +import { i18n } from '@kbn/i18n'; import type { RouteDefinitionParams } from '..'; import { SAMLAuthenticationProvider, SAMLLogin } from '../../authentication'; @@ -20,6 +21,7 @@ export function defineSAMLRoutes({ basePath, logger, buildFlavor, + docLinks, }: RouteDefinitionParams) { // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. // For a serverless build, do not register deprecated versioned routes @@ -27,6 +29,7 @@ export function defineSAMLRoutes({ '/api/security/saml/callback', ...(buildFlavor !== 'serverless' ? ['/api/security/v1/saml'] : []), ]) { + const isDeprecated = path === '/api/security/v1/saml'; router.post( { path, @@ -48,14 +51,30 @@ export function defineSAMLRoutes({ authRequired: false, xsrfRequired: false, tags: [ROUTE_TAG_CAN_REDIRECT, ROUTE_TAG_AUTH_FLOW], + ...(isDeprecated && { + deprecated: { + documentationUrl: docLinks.links.security.deprecatedV1Endpoints, + severity: 'warning', + message: i18n.translate('xpack.security.deprecations.samlPostRouteMessage', { + defaultMessage: + 'The "{path}" URL is deprecated and will be removed in the next major version. Use "/api/security/saml/callback" instead.', + values: { path }, + }), + reason: { + type: 'migrate', + newApiMethod: 'POST', + newApiPath: '/api/security/saml/callback', + }, + }, + }), }, }, async (context, request, response) => { - if (path === '/api/security/v1/saml') { + if (isDeprecated) { const serverBasePath = basePath.serverBasePath; logger.warn( // When authenticating using SAML we _expect_ to redirect to the SAML Identity provider. - `The "${serverBasePath}${path}" URL is deprecated and might stop working in a future release. Please use "${serverBasePath}/api/security/saml/callback" URL instead.` + `The "${serverBasePath}${path}" URL is deprecated and might stop working in a future release. Use "${serverBasePath}/api/security/saml/callback" URL instead.` ); } diff --git a/x-pack/plugins/security/server/routes/index.mock.ts b/x-pack/plugins/security/server/routes/index.mock.ts index 910578a14789d..e73cd74daf300 100644 --- a/x-pack/plugins/security/server/routes/index.mock.ts +++ b/x-pack/plugins/security/server/routes/index.mock.ts @@ -13,6 +13,7 @@ import { httpServiceMock, loggingSystemMock, } from '@kbn/core/server/mocks'; +import { getDocLinks } from '@kbn/doc-links'; import { licensingMock } from '@kbn/licensing-plugin/server/mocks'; import type { DeeplyMockedKeys } from '@kbn/utility-types-jest'; @@ -50,6 +51,8 @@ export const routeDefinitionParamsMock = { getAnonymousAccessService: jest.fn(), getUserProfileService: jest.fn().mockReturnValue(userProfileServiceMock.createStart()), analyticsService: analyticsServiceMock.createSetup(), + buildFlavor: 'traditional', + docLinks: { links: getDocLinks({ kibanaBranch: 'main', buildFlavor: 'traditional' }) }, } as unknown as DeeplyMockedKeys; }, }; diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index 8b986cc4a3893..cbc1569d963cf 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -8,7 +8,7 @@ import type { Observable } from 'rxjs'; import type { BuildFlavor } from '@kbn/config/src/types'; -import type { HttpResources, IBasePath, Logger } from '@kbn/core/server'; +import type { DocLinksServiceSetup, HttpResources, IBasePath, Logger } from '@kbn/core/server'; import type { KibanaFeature } from '@kbn/features-plugin/server'; import type { SubFeaturePrivilegeIterator } from '@kbn/features-plugin/server/feature_privilege_iterator'; import type { PublicMethodsOf } from '@kbn/utility-types'; @@ -59,6 +59,7 @@ export interface RouteDefinitionParams { getAnonymousAccessService: () => AnonymousAccessServiceStart; analyticsService: AnalyticsServiceSetup; buildFlavor: BuildFlavor; + docLinks: DocLinksServiceSetup; } export function defineRoutes(params: RouteDefinitionParams) { diff --git a/x-pack/plugins/security/tsconfig.json b/x-pack/plugins/security/tsconfig.json index 2a0eabcd914d4..4837d3729e3f9 100644 --- a/x-pack/plugins/security/tsconfig.json +++ b/x-pack/plugins/security/tsconfig.json @@ -87,6 +87,7 @@ "@kbn/security-ui-components", "@kbn/core-http-router-server-mocks", "@kbn/security-authorization-core-common", + "@kbn/doc-links", ], "exclude": [ "target/**/*",