Skip to content

Commit fe0ffab

Browse files
jeramysoucykibanamachineazasypkin
authored
Prepare the Security domain HTTP APIs for Serverless (elastic#162087)
Closes elastic#161337 ## Summary Uses build flavor(see elastic#161930) to disable specific Kibana security, spaces, and encrypted saved objects HTTP API routes in serverless (see details in elastic#161337). HTTP APIs that will be public in serverless have been handled in elastic#162523. **IMPORTANT: This PR leaves login, user, and role routes enabled. The primary reason for this is due to several testing mechanisms that rely on basic authentication and custom roles (UI, Cypress). These tests will be modified to use SAML authentication and serverless roles in the immediate future. Once this occurs, we will disable these routes.** ### Testing This PR also implements testing API access in serverless. - The testing strategy for disabled routes in serverless is to verify a `404 not found `response. - The testing strategy for internal access routes in serverless is to verify that without the internal request header (`x-elastic-internal-origin`), a `400 bad request response` is received, then verify that with the internal request header, a `200 ok response` is received. - The strategy for public routes in serverless is to verify a `200 ok` or `203 redirect` is received. ~~blocked by elastic#161930~~ ~~blocked by elastic#162149 for test implementation~~ --------- Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com> Co-authored-by: Aleh Zasypkin <aleh.zasypkin@gmail.com> Co-authored-by: Aleh Zasypkin <aleh.zasypkin@elastic.co>
1 parent f4f286f commit fe0ffab

56 files changed

Lines changed: 1581 additions & 186 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

config/serverless.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ xpack.cloud_integrations.data_migration.enabled: false
4848
data.search.sessions.enabled: false
4949
advanced_settings.enabled: false
5050

51+
# Disable the browser-side functionality that depends on SecurityCheckupGetStateRoutes
52+
xpack.security.showInsecureClusterWarning: false
53+
5154
# Disable UI of security management plugins
5255
xpack.security.ui.userManagementEnabled: false
5356
xpack.security.ui.roleManagementEnabled: false

x-pack/plugins/encrypted_saved_objects/server/plugin.ts

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -95,19 +95,23 @@ export class EncryptedSavedObjectsPlugin
9595
getStartServices: core.getStartServices,
9696
});
9797

98-
defineRoutes({
99-
router: core.http.createRouter(),
100-
logger: this.initializerContext.logger.get('routes'),
101-
encryptionKeyRotationService: Object.freeze(
102-
new EncryptionKeyRotationService({
103-
logger: this.logger.get('key-rotation-service'),
104-
service,
105-
getStartServices: core.getStartServices,
106-
security: deps.security,
107-
})
108-
),
109-
config,
110-
});
98+
// In the serverless environment, the encryption keys for saved objects is managed internally and never
99+
// exposed to users and administrators, eliminating the need for any public Encrypted Saved Objects HTTP APIs
100+
if (this.initializerContext.env.packageInfo.buildFlavor !== 'serverless') {
101+
defineRoutes({
102+
router: core.http.createRouter(),
103+
logger: this.initializerContext.logger.get('routes'),
104+
encryptionKeyRotationService: Object.freeze(
105+
new EncryptionKeyRotationService({
106+
logger: this.logger.get('key-rotation-service'),
107+
service,
108+
getStartServices: core.getStartServices,
109+
security: deps.security,
110+
})
111+
),
112+
config,
113+
});
114+
}
111115

112116
return {
113117
canEncrypt,

x-pack/plugins/security/server/plugin.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ export class SecurityPlugin
359359
getAnonymousAccessService: this.getAnonymousAccess,
360360
getUserProfileService: this.getUserProfileService,
361361
analyticsService: this.analyticsService.setup({ analytics: core.analytics }),
362+
buildFlavor: this.initializerContext.env.packageInfo.buildFlavor,
362363
});
363364

364365
return Object.freeze<SecurityPluginSetup>({

x-pack/plugins/security/server/routes/authentication/common.ts

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,14 @@ export function defineCommonRoutes({
3232
basePath,
3333
license,
3434
logger,
35+
buildFlavor,
3536
}: RouteDefinitionParams) {
3637
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
37-
for (const path of ['/api/security/logout', '/api/security/v1/logout']) {
38+
// For a serverless build, do not register deprecated versioned routes
39+
for (const path of [
40+
'/api/security/logout',
41+
...(buildFlavor !== 'serverless' ? ['/api/security/v1/logout'] : []),
42+
]) {
3843
router.get(
3944
{
4045
path,
@@ -79,7 +84,11 @@ export function defineCommonRoutes({
7984
}
8085

8186
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
82-
for (const path of ['/internal/security/me', '/api/security/v1/me']) {
87+
// For a serverless build, do not register deprecated versioned routes
88+
for (const path of [
89+
'/internal/security/me',
90+
...(buildFlavor !== 'serverless' ? ['/api/security/v1/me'] : []),
91+
]) {
8392
router.get(
8493
{ path, validate: false },
8594
createLicensedRouteHandler((context, request, response) => {
@@ -123,6 +132,8 @@ export function defineCommonRoutes({
123132
return undefined;
124133
}
125134

135+
// Register the login route for serverless for the time being. Note: This route will move into the buildFlavor !== 'serverless' block below. See next line.
136+
// ToDo: In the serverless environment, we do not support API login - the only valid authentication methodology (or maybe just method or mechanism?) is SAML
126137
router.post(
127138
{
128139
path: '/internal/security/login',
@@ -169,20 +180,23 @@ export function defineCommonRoutes({
169180
})
170181
);
171182

172-
router.post(
173-
{ path: '/internal/security/access_agreement/acknowledge', validate: false },
174-
createLicensedRouteHandler(async (context, request, response) => {
175-
// If license doesn't allow access agreement we shouldn't handle request.
176-
if (!license.getFeatures().allowAccessAgreement) {
177-
logger.warn(`Attempted to acknowledge access agreement when license doesn't allow it.`);
178-
return response.forbidden({
179-
body: { message: `Current license doesn't support access agreement.` },
180-
});
181-
}
183+
if (buildFlavor !== 'serverless') {
184+
// In the serverless offering, the access agreement functionality isn't available.
185+
router.post(
186+
{ path: '/internal/security/access_agreement/acknowledge', validate: false },
187+
createLicensedRouteHandler(async (context, request, response) => {
188+
// If license doesn't allow access agreement we shouldn't handle request.
189+
if (!license.getFeatures().allowAccessAgreement) {
190+
logger.warn(`Attempted to acknowledge access agreement when license doesn't allow it.`);
191+
return response.forbidden({
192+
body: { message: `Current license doesn't support access agreement.` },
193+
});
194+
}
182195

183-
await getAuthenticationService().acknowledgeAccessAgreement(request);
196+
await getAuthenticationService().acknowledgeAccessAgreement(request);
184197

185-
return response.noContent();
186-
})
187-
);
198+
return response.noContent();
199+
})
200+
);
201+
}
188202
}

x-pack/plugins/security/server/routes/authentication/saml.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@ export function defineSAMLRoutes({
1919
getAuthenticationService,
2020
basePath,
2121
logger,
22+
buildFlavor,
2223
}: RouteDefinitionParams) {
2324
// Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used.
24-
for (const path of ['/api/security/saml/callback', '/api/security/v1/saml']) {
25+
// For a serverless build, do not register deprecated versioned routes
26+
for (const path of [
27+
'/api/security/saml/callback',
28+
...(buildFlavor !== 'serverless' ? ['/api/security/v1/saml'] : []),
29+
]) {
2530
router.post(
2631
{
2732
path,

x-pack/plugins/security/server/routes/authorization/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,14 @@ import { defineShareSavedObjectPermissionRoutes } from './spaces';
1212
import type { RouteDefinitionParams } from '..';
1313

1414
export function defineAuthorizationRoutes(params: RouteDefinitionParams) {
15-
defineRolesRoutes(params);
16-
definePrivilegesRoutes(params);
15+
// The reset session endpoint is registered with httpResources and should remain public in serverless
1716
resetSessionPageRoutes(params);
18-
defineShareSavedObjectPermissionRoutes(params);
17+
defineRolesRoutes(params); // Temporarily allow role APIs (ToDo: move to non-serverless block below)
18+
19+
// In the serverless environment, roles, privileges, and permissions are managed internally and only
20+
// exposed to users and administrators via control plane UI, eliminating the need for any public HTTP APIs.
21+
if (params.buildFlavor !== 'serverless') {
22+
definePrivilegesRoutes(params);
23+
defineShareSavedObjectPermissionRoutes(params);
24+
}
1925
}

x-pack/plugins/security/server/routes/index.ts

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
import type { Observable } from 'rxjs';
99

10+
import type { BuildFlavor } from '@kbn/config/src/types';
1011
import type { HttpResources, IBasePath, Logger } from '@kbn/core/server';
1112
import type { KibanaFeature } from '@kbn/features-plugin/server';
1213
import type { PublicMethodsOf } from '@kbn/utility-types';
@@ -54,20 +55,26 @@ export interface RouteDefinitionParams {
5455
getUserProfileService: () => UserProfileServiceStartInternal;
5556
getAnonymousAccessService: () => AnonymousAccessServiceStart;
5657
analyticsService: AnalyticsServiceSetup;
58+
buildFlavor: BuildFlavor;
5759
}
5860

5961
export function defineRoutes(params: RouteDefinitionParams) {
62+
defineAnalyticsRoutes(params);
63+
defineApiKeysRoutes(params);
6064
defineAuthenticationRoutes(params);
6165
defineAuthorizationRoutes(params);
6266
defineSessionManagementRoutes(params);
63-
defineApiKeysRoutes(params);
64-
defineIndicesRoutes(params);
65-
defineUsersRoutes(params);
6667
defineUserProfileRoutes(params);
67-
defineRoleMappingRoutes(params);
68+
defineUsersRoutes(params); // Temporarily allow user APIs (ToDo: move to non-serverless block below)
6869
defineViewRoutes(params);
69-
defineDeprecationsRoutes(params);
70-
defineAnonymousAccessRoutes(params);
71-
defineSecurityCheckupGetStateRoutes(params);
72-
defineAnalyticsRoutes(params);
70+
71+
// In the serverless environment...
72+
if (params.buildFlavor !== 'serverless') {
73+
defineAnonymousAccessRoutes(params); // anonymous access is disabled
74+
defineDeprecationsRoutes(params); // deprecated kibana user roles are not applicable, these HTTP APIs are not needed
75+
defineIndicesRoutes(params); // the ES privileges form used to help define roles (only consumer) is disabled, so there is no need for these HTTP APIs
76+
defineRoleMappingRoutes(params); // role mappings are managed internally, based on configurations in control plane, these HTTP APIs are not needed
77+
defineSecurityCheckupGetStateRoutes(params); // security checkup is not applicable, these HTTP APIs are not needed
78+
// defineUsersRoutes(params); // the native realm is not enabled (there is only Elastic cloud SAML), no user HTTP API routes are needed
79+
}
7380
}

x-pack/plugins/security/server/routes/session_management/index.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,12 @@ import type { RouteDefinitionParams } from '..';
1313
export function defineSessionManagementRoutes(params: RouteDefinitionParams) {
1414
defineSessionInfoRoutes(params);
1515
defineSessionExtendRoutes(params);
16-
defineInvalidateSessionsRoutes(params);
16+
17+
// The invalidate session API was introduced to address situations where the session index
18+
// could grow rapidly - when session timeouts are disabled, or with anonymous access.
19+
// In the serverless environment, sessions timeouts are always be enabled, and there is no
20+
// anonymous access. This eliminates the need for an invalidate session HTTP API.
21+
if (params.buildFlavor !== 'serverless') {
22+
defineInvalidateSessionsRoutes(params);
23+
}
1724
}

x-pack/plugins/security/server/routes/views/index.test.ts

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,19 +12,20 @@ describe('View routes', () => {
1212
it('does not register Login routes if both `basic` and `token` providers are disabled', () => {
1313
const routeParamsMock = routeDefinitionParamsMock.create({
1414
authc: { providers: { pki: { pki1: { order: 0 } } } },
15+
accessAgreement: { message: 'some-message' },
1516
});
1617

1718
defineViewRoutes(routeParamsMock);
1819

1920
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
2021
.toMatchInlineSnapshot(`
2122
Array [
22-
"/security/access_agreement",
2323
"/security/account",
24+
"/internal/security/capture-url",
2425
"/security/logged_out",
2526
"/logout",
2627
"/security/overwritten_session",
27-
"/internal/security/capture-url",
28+
"/security/access_agreement",
2829
]
2930
`);
3031
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
@@ -37,80 +38,108 @@ describe('View routes', () => {
3738
it('registers Login routes if `basic` provider is enabled', () => {
3839
const routeParamsMock = routeDefinitionParamsMock.create({
3940
authc: { providers: { basic: { basic1: { order: 0 } } } },
41+
accessAgreement: { message: 'some-message' },
4042
});
4143

4244
defineViewRoutes(routeParamsMock);
4345

4446
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
4547
.toMatchInlineSnapshot(`
4648
Array [
47-
"/login",
48-
"/security/access_agreement",
4949
"/security/account",
50+
"/internal/security/capture-url",
5051
"/security/logged_out",
5152
"/logout",
5253
"/security/overwritten_session",
53-
"/internal/security/capture-url",
54+
"/security/access_agreement",
55+
"/login",
5456
]
5557
`);
5658
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
5759
Array [
58-
"/internal/security/login_state",
5960
"/internal/security/access_agreement/state",
61+
"/internal/security/login_state",
6062
]
6163
`);
6264
});
6365

6466
it('registers Login routes if `token` provider is enabled', () => {
6567
const routeParamsMock = routeDefinitionParamsMock.create({
6668
authc: { providers: { token: { token1: { order: 0 } } } },
69+
accessAgreement: { message: 'some-message' },
6770
});
6871

6972
defineViewRoutes(routeParamsMock);
7073

7174
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
7275
.toMatchInlineSnapshot(`
7376
Array [
74-
"/login",
75-
"/security/access_agreement",
7677
"/security/account",
78+
"/internal/security/capture-url",
7779
"/security/logged_out",
7880
"/logout",
7981
"/security/overwritten_session",
80-
"/internal/security/capture-url",
82+
"/security/access_agreement",
83+
"/login",
8184
]
8285
`);
8386
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
8487
Array [
85-
"/internal/security/login_state",
8688
"/internal/security/access_agreement/state",
89+
"/internal/security/login_state",
8790
]
8891
`);
8992
});
9093

9194
it('registers Login routes if Login Selector is enabled even if both `token` and `basic` providers are not enabled', () => {
9295
const routeParamsMock = routeDefinitionParamsMock.create({
9396
authc: { selector: { enabled: true }, providers: { pki: { pki1: { order: 0 } } } },
97+
accessAgreement: { message: 'some-message' },
9498
});
9599

96100
defineViewRoutes(routeParamsMock);
97101

98102
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
99103
.toMatchInlineSnapshot(`
100104
Array [
101-
"/login",
102-
"/security/access_agreement",
103105
"/security/account",
106+
"/internal/security/capture-url",
104107
"/security/logged_out",
105108
"/logout",
106109
"/security/overwritten_session",
110+
"/security/access_agreement",
111+
"/login",
112+
]
113+
`);
114+
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
115+
Array [
116+
"/internal/security/access_agreement/state",
117+
"/internal/security/login_state",
118+
]
119+
`);
120+
});
121+
122+
it('does not register access agreement routes if access agreement is not enabled', () => {
123+
const routeParamsMock = routeDefinitionParamsMock.create({
124+
authc: { providers: { basic: { basic1: { order: 0 } } } },
125+
});
126+
127+
defineViewRoutes(routeParamsMock);
128+
129+
expect(routeParamsMock.httpResources.register.mock.calls.map(([{ path }]) => path))
130+
.toMatchInlineSnapshot(`
131+
Array [
132+
"/security/account",
107133
"/internal/security/capture-url",
134+
"/security/logged_out",
135+
"/logout",
136+
"/security/overwritten_session",
137+
"/login",
108138
]
109139
`);
110140
expect(routeParamsMock.router.get.mock.calls.map(([{ path }]) => path)).toMatchInlineSnapshot(`
111141
Array [
112142
"/internal/security/login_state",
113-
"/internal/security/access_agreement/state",
114143
]
115144
`);
116145
});

x-pack/plugins/security/server/routes/views/index.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,23 @@ import { defineOverwrittenSessionRoutes } from './overwritten_session';
1515
import type { RouteDefinitionParams } from '..';
1616

1717
export function defineViewRoutes(params: RouteDefinitionParams) {
18+
defineAccountManagementRoutes(params);
19+
defineCaptureURLRoutes(params);
20+
defineLoggedOutRoutes(params);
21+
defineLogoutRoutes(params);
22+
defineOverwrittenSessionRoutes(params);
23+
24+
if (
25+
params.config.accessAgreement?.message ||
26+
params.config.authc.sortedProviders.some(({ hasAccessAgreement }) => hasAccessAgreement)
27+
) {
28+
defineAccessAgreementRoutes(params);
29+
}
30+
1831
if (
1932
params.config.authc.selector.enabled ||
2033
params.config.authc.sortedProviders.some(({ type }) => type === 'basic' || type === 'token')
2134
) {
2235
defineLoginRoutes(params);
2336
}
24-
25-
defineAccessAgreementRoutes(params);
26-
defineAccountManagementRoutes(params);
27-
defineLoggedOutRoutes(params);
28-
defineLogoutRoutes(params);
29-
defineOverwrittenSessionRoutes(params);
30-
defineCaptureURLRoutes(params);
3137
}

0 commit comments

Comments
 (0)