mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Resurrect deprecated and removed authentication settings. (#110835)
This commit is contained in:
parent
23fa1b4c07
commit
c42391ed3a
13 changed files with 436 additions and 89 deletions
|
@ -318,6 +318,52 @@ describe('AuthenticationService', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getServerBaseURL()', () => {
|
||||
let getServerBaseURL: () => string;
|
||||
beforeEach(() => {
|
||||
mockStartAuthenticationParams.http.getServerInfo.mockReturnValue({
|
||||
name: 'some-name',
|
||||
protocol: 'socket',
|
||||
hostname: 'test-hostname',
|
||||
port: 1234,
|
||||
});
|
||||
|
||||
service.setup(mockSetupAuthenticationParams);
|
||||
service.start(mockStartAuthenticationParams);
|
||||
|
||||
getServerBaseURL = jest.requireMock('./authenticator').Authenticator.mock.calls[0][0]
|
||||
.getServerBaseURL;
|
||||
});
|
||||
|
||||
it('falls back to legacy server config if `public` config is not specified', async () => {
|
||||
expect(getServerBaseURL()).toBe('socket://test-hostname:1234');
|
||||
});
|
||||
|
||||
it('respects `public` config if it is specified', async () => {
|
||||
mockStartAuthenticationParams.config.public = {
|
||||
protocol: 'https',
|
||||
} as ConfigType['public'];
|
||||
expect(getServerBaseURL()).toBe('https://test-hostname:1234');
|
||||
|
||||
mockStartAuthenticationParams.config.public = {
|
||||
hostname: 'elastic.co',
|
||||
} as ConfigType['public'];
|
||||
expect(getServerBaseURL()).toBe('socket://elastic.co:1234');
|
||||
|
||||
mockStartAuthenticationParams.config.public = {
|
||||
port: 4321,
|
||||
} as ConfigType['public'];
|
||||
expect(getServerBaseURL()).toBe('socket://test-hostname:4321');
|
||||
|
||||
mockStartAuthenticationParams.config.public = {
|
||||
protocol: 'https',
|
||||
hostname: 'elastic.co',
|
||||
port: 4321,
|
||||
} as ConfigType['public'];
|
||||
expect(getServerBaseURL()).toBe('https://elastic.co:4321');
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCurrentUser()', () => {
|
||||
let getCurrentUser: (r: KibanaRequest) => AuthenticatedUser | null;
|
||||
beforeEach(async () => {
|
||||
|
|
|
@ -41,7 +41,7 @@ interface AuthenticationServiceSetupParams {
|
|||
}
|
||||
|
||||
interface AuthenticationServiceStartParams {
|
||||
http: Pick<HttpServiceStart, 'auth' | 'basePath'>;
|
||||
http: Pick<HttpServiceStart, 'auth' | 'basePath' | 'getServerInfo'>;
|
||||
config: ConfigType;
|
||||
clusterClient: IClusterClient;
|
||||
legacyAuditLogger: SecurityAuditLogger;
|
||||
|
@ -234,6 +234,17 @@ export class AuthenticationService {
|
|||
license: this.license,
|
||||
});
|
||||
|
||||
/**
|
||||
* Retrieves server protocol name/host name/port and merges it with `xpack.security.public` config
|
||||
* to construct a server base URL (deprecated, used by the SAML provider only).
|
||||
*/
|
||||
const getServerBaseURL = () => {
|
||||
const { protocol, hostname, port } = http.getServerInfo();
|
||||
const serverConfig = { protocol, hostname, port, ...config.public };
|
||||
|
||||
return `${serverConfig.protocol}://${serverConfig.hostname}:${serverConfig.port}`;
|
||||
};
|
||||
|
||||
const getCurrentUser = (request: KibanaRequest) =>
|
||||
http.auth.get<AuthenticatedUser>(request).state ?? null;
|
||||
|
||||
|
@ -247,6 +258,7 @@ export class AuthenticationService {
|
|||
config: { authc: config.authc },
|
||||
getCurrentUser,
|
||||
featureUsageService,
|
||||
getServerBaseURL,
|
||||
license: this.license,
|
||||
session,
|
||||
});
|
||||
|
|
|
@ -55,6 +55,7 @@ function getMockOptions({
|
|||
basePath: httpServiceMock.createSetupContract().basePath,
|
||||
license: licenseMock.create(),
|
||||
loggers: loggingSystemMock.create(),
|
||||
getServerBaseURL: jest.fn(),
|
||||
config: createConfig(
|
||||
ConfigSchema.validate({ authc: { selector, providers, http } }),
|
||||
loggingSystemMock.create().get(),
|
||||
|
|
|
@ -87,6 +87,7 @@ export interface AuthenticatorOptions {
|
|||
loggers: LoggerFactory;
|
||||
clusterClient: IClusterClient;
|
||||
session: PublicMethodsOf<Session>;
|
||||
getServerBaseURL: () => string;
|
||||
}
|
||||
|
||||
// Mapping between provider key defined in the config and authentication
|
||||
|
@ -216,6 +217,7 @@ export class Authenticator {
|
|||
client: this.options.clusterClient.asInternalUser,
|
||||
logger: this.options.loggers.get('tokens'),
|
||||
}),
|
||||
getServerBaseURL: this.options.getServerBaseURL,
|
||||
};
|
||||
|
||||
this.providers = new Map(
|
||||
|
|
|
@ -17,6 +17,7 @@ export type MockAuthenticationProviderOptions = ReturnType<
|
|||
|
||||
export function mockAuthenticationProviderOptions(options?: { name: string }) {
|
||||
return {
|
||||
getServerBaseURL: () => 'test-protocol://test-hostname:1234',
|
||||
client: elasticsearchServiceMock.createClusterClient(),
|
||||
logger: loggingSystemMock.create().get(),
|
||||
basePath: httpServiceMock.createBasePath(),
|
||||
|
|
|
@ -26,6 +26,7 @@ import type { Tokens } from '../tokens';
|
|||
*/
|
||||
export interface AuthenticationProviderOptions {
|
||||
name: string;
|
||||
getServerBaseURL: () => string;
|
||||
basePath: HttpServiceSetup['basePath'];
|
||||
getRequestOriginalURL: (
|
||||
request: KibanaRequest,
|
||||
|
|
|
@ -39,23 +39,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
);
|
||||
mockOptions.client.asScoped.mockReturnValue(mockScopedClusterClient);
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
});
|
||||
|
||||
it('throws if `realm` option is not specified', () => {
|
||||
const providerOptions = mockAuthenticationProviderOptions();
|
||||
|
||||
expect(() => new SAMLAuthenticationProvider(providerOptions)).toThrowError(
|
||||
'Realm name must be specified'
|
||||
);
|
||||
expect(() => new SAMLAuthenticationProvider(providerOptions, {})).toThrowError(
|
||||
'Realm name must be specified'
|
||||
);
|
||||
expect(() => new SAMLAuthenticationProvider(providerOptions, { realm: '' })).toThrowError(
|
||||
'Realm name must be specified'
|
||||
);
|
||||
provider = new SAMLAuthenticationProvider(mockOptions);
|
||||
});
|
||||
|
||||
describe('`login` method', () => {
|
||||
|
@ -67,6 +51,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
access_token: 'some-token',
|
||||
refresh_token: 'some-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
|
@ -108,13 +93,13 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
access_token: 'some-token',
|
||||
refresh_token: 'some-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
useRelayStateDeepLink: true,
|
||||
});
|
||||
await expect(
|
||||
|
@ -169,6 +154,10 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
|
||||
it('fails if realm from state is different from the realm provider is configured with.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
const customMockOptions = mockAuthenticationProviderOptions({ name: 'saml' });
|
||||
provider = new SAMLAuthenticationProvider(customMockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
|
@ -184,7 +173,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
)
|
||||
);
|
||||
|
||||
expect(mockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled();
|
||||
expect(customMockOptions.client.asInternalUser.transport.request).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('redirects to the default location if state contains empty redirect URL.', async () => {
|
||||
|
@ -195,6 +184,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
access_token: 'user-initiated-login-token',
|
||||
refresh_token: 'user-initiated-login-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
|
@ -232,13 +222,13 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
access_token: 'user-initiated-login-token',
|
||||
refresh_token: 'user-initiated-login-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
useRelayStateDeepLink: true,
|
||||
});
|
||||
await expect(
|
||||
|
@ -275,6 +265,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
mockOptions.client.asInternalUser.transport.request.mockResolvedValue(
|
||||
securityMock.createApiResponse({
|
||||
body: {
|
||||
realm: 'test-realm',
|
||||
access_token: 'idp-initiated-login-token',
|
||||
refresh_token: 'idp-initiated-login-refresh-token',
|
||||
authentication: mockUser,
|
||||
|
@ -301,7 +292,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
|
||||
body: { ids: [], content: 'saml-response-xml' },
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -342,20 +333,19 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
username: 'user',
|
||||
access_token: 'valid-token',
|
||||
refresh_token: 'valid-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
useRelayStateDeepLink: true,
|
||||
});
|
||||
});
|
||||
|
||||
it('redirects to the home page if `useRelayStateDeepLink` is set to `false`.', async () => {
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
useRelayStateDeepLink: false,
|
||||
});
|
||||
|
||||
|
@ -454,10 +444,39 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
)
|
||||
);
|
||||
});
|
||||
|
||||
it('uses `realm` name instead of `acs` if it is specified for SAML authenticate request.', async () => {
|
||||
// Create new provider instance with additional `realm` option.
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
|
||||
await expect(
|
||||
provider.login(httpServerMock.createKibanaRequest({ headers: {} }), {
|
||||
type: SAMLLogin.LoginWithSAMLResponse,
|
||||
samlResponse: 'saml-response-xml',
|
||||
})
|
||||
).resolves.toEqual(
|
||||
AuthenticationResult.redirectTo(`${mockOptions.basePath.serverBasePath}/`, {
|
||||
state: {
|
||||
accessToken: 'valid-token',
|
||||
refreshToken: 'valid-refresh-token',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
user: mockUser,
|
||||
})
|
||||
);
|
||||
|
||||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('IdP initiated login with existing session', () => {
|
||||
it('returns `notHandled` if new SAML Response is rejected.', async () => {
|
||||
it('fails if new SAML Response is rejected and provider is not configured with specific realm.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({ headers: {} });
|
||||
const authorization = 'Bearer some-valid-token';
|
||||
|
||||
|
@ -466,6 +485,39 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
);
|
||||
mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason);
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
request,
|
||||
{ type: SAMLLogin.LoginWithSAMLResponse, samlResponse: 'saml-response-xml' },
|
||||
{
|
||||
accessToken: 'some-valid-token',
|
||||
refreshToken: 'some-valid-refresh-token',
|
||||
realm: 'test-realm',
|
||||
}
|
||||
)
|
||||
).resolves.toEqual(AuthenticationResult.failed(failureReason));
|
||||
|
||||
expect(mockOptions.client.asScoped).toHaveBeenCalledWith({ headers: { authorization } });
|
||||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml' },
|
||||
});
|
||||
});
|
||||
|
||||
it('returns `notHandled` if new SAML Response is rejected and provider is configured with specific realm.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({ headers: {} });
|
||||
const authorization = 'Bearer some-valid-token';
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
|
||||
const failureReason = new errors.ResponseError(
|
||||
securityMock.createApiResponse({ statusCode: 503, body: {} })
|
||||
);
|
||||
mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason);
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
request,
|
||||
|
@ -521,7 +573,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
|
||||
body: { ids: [], content: 'saml-response-xml' },
|
||||
});
|
||||
|
||||
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
|
||||
|
@ -543,7 +595,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
),
|
||||
],
|
||||
[
|
||||
'current session is is expired',
|
||||
'current session is expired',
|
||||
Promise.reject(
|
||||
new errors.ResponseError(securityMock.createApiResponse({ statusCode: 401, body: {} }))
|
||||
),
|
||||
|
@ -568,6 +620,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
username: 'user',
|
||||
access_token: 'new-valid-token',
|
||||
refresh_token: 'new-valid-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
|
@ -595,7 +648,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
|
||||
body: { ids: [], content: 'saml-response-xml' },
|
||||
});
|
||||
|
||||
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
|
||||
|
@ -624,6 +677,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
username: 'user',
|
||||
access_token: 'new-valid-token',
|
||||
refresh_token: 'new-valid-refresh-token',
|
||||
realm: 'test-realm',
|
||||
authentication: mockUser,
|
||||
},
|
||||
})
|
||||
|
@ -632,7 +686,6 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
mockOptions.tokens.invalidate.mockResolvedValue(undefined);
|
||||
|
||||
provider = new SAMLAuthenticationProvider(mockOptions, {
|
||||
realm: 'test-realm',
|
||||
useRelayStateDeepLink: true,
|
||||
});
|
||||
|
||||
|
@ -661,7 +714,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/authenticate',
|
||||
body: { ids: [], content: 'saml-response-xml', realm: 'test-realm' },
|
||||
body: { ids: [], content: 'saml-response-xml' },
|
||||
});
|
||||
|
||||
expect(mockOptions.tokens.invalidate).toHaveBeenCalledTimes(1);
|
||||
|
@ -699,19 +752,16 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
id: 'some-request-id',
|
||||
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
request,
|
||||
{
|
||||
type: SAMLLogin.LoginInitiatedByUser,
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
},
|
||||
{ realm: 'test-realm' }
|
||||
)
|
||||
provider.login(request, {
|
||||
type: SAMLLogin.LoginInitiatedByUser,
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
})
|
||||
).resolves.toEqual(
|
||||
AuthenticationResult.redirectTo(
|
||||
'https://idp-host/path/login?SAMLRequest=some%20request%20',
|
||||
|
@ -728,7 +778,9 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: 'test-realm' },
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockOptions.logger.warn).not.toHaveBeenCalled();
|
||||
|
@ -742,6 +794,7 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
body: {
|
||||
id: 'some-request-id',
|
||||
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
@ -771,12 +824,62 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: 'test-realm' },
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
|
||||
expect(mockOptions.logger.warn).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('uses `realm` name instead of `acs` if it is specified for SAML prepare request.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest({ path: '/s/foo/some-path' });
|
||||
|
||||
// Create new provider instance with additional `realm` option.
|
||||
const customMockOptions = mockAuthenticationProviderOptions();
|
||||
provider = new SAMLAuthenticationProvider(customMockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
|
||||
customMockOptions.client.asInternalUser.transport.request.mockResolvedValue(
|
||||
securityMock.createApiResponse({
|
||||
body: {
|
||||
id: 'some-request-id',
|
||||
redirect: 'https://idp-host/path/login?SAMLRequest=some%20request%20',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
})
|
||||
);
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
request,
|
||||
{
|
||||
type: SAMLLogin.LoginInitiatedByUser,
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
},
|
||||
{ realm: 'test-realm' }
|
||||
)
|
||||
).resolves.toEqual(
|
||||
AuthenticationResult.redirectTo(
|
||||
'https://idp-host/path/login?SAMLRequest=some%20request%20',
|
||||
{
|
||||
state: {
|
||||
requestId: 'some-request-id',
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
expect(customMockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: 'test-realm' },
|
||||
});
|
||||
});
|
||||
|
||||
it('fails if SAML request preparation fails.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
|
@ -786,20 +889,18 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
mockOptions.client.asInternalUser.transport.request.mockRejectedValue(failureReason);
|
||||
|
||||
await expect(
|
||||
provider.login(
|
||||
request,
|
||||
{
|
||||
type: SAMLLogin.LoginInitiatedByUser,
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
},
|
||||
{ realm: 'test-realm' }
|
||||
)
|
||||
provider.login(request, {
|
||||
type: SAMLLogin.LoginInitiatedByUser,
|
||||
redirectURL: '/test-base-path/some-path#some-fragment',
|
||||
})
|
||||
).resolves.toEqual(AuthenticationResult.failed(failureReason));
|
||||
|
||||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: 'test-realm' },
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -893,7 +994,6 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
state: {
|
||||
requestId: 'some-request-id',
|
||||
redirectURL: '/mock-server-basepath/s/foo/some-path#some-fragment',
|
||||
realm: 'test-realm',
|
||||
},
|
||||
}
|
||||
)
|
||||
|
@ -905,7 +1005,9 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: 'test-realm' },
|
||||
body: {
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1112,6 +1214,13 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
|
||||
it('fails if realm from state is different from the realm provider is configured with.', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
|
||||
// Create new provider instance with additional `realm` option.
|
||||
const customMockOptions = mockAuthenticationProviderOptions({ name: 'saml' });
|
||||
provider = new SAMLAuthenticationProvider(customMockOptions, {
|
||||
realm: 'test-realm',
|
||||
});
|
||||
|
||||
await expect(provider.authenticate(request, { realm: 'other-realm' })).resolves.toEqual(
|
||||
AuthenticationResult.failed(
|
||||
Boom.unauthorized(
|
||||
|
@ -1186,7 +1295,10 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/invalidate',
|
||||
body: { query_string: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1305,7 +1417,10 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/invalidate',
|
||||
body: { query_string: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1324,7 +1439,10 @@ describe('SAMLAuthenticationProvider', () => {
|
|||
expect(mockOptions.client.asInternalUser.transport.request).toHaveBeenCalledWith({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/invalidate',
|
||||
body: { query_string: 'SAMLRequest=xxx%20yyy', realm: 'test-realm' },
|
||||
body: {
|
||||
query_string: 'SAMLRequest=xxx%20yyy',
|
||||
acs: 'test-protocol://test-hostname:1234/mock-server-basepath/api/security/v1/saml',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -42,9 +42,10 @@ interface ProviderState extends Partial<TokenPair> {
|
|||
redirectURL?: string;
|
||||
|
||||
/**
|
||||
* The name of the SAML realm that was used to establish session.
|
||||
* The name of the SAML realm that was used to establish session (may not be known during URL
|
||||
* fragment capturing stage).
|
||||
*/
|
||||
realm: string;
|
||||
realm?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -105,9 +106,10 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
static readonly type = 'saml';
|
||||
|
||||
/**
|
||||
* Specifies Elasticsearch SAML realm name that Kibana should use.
|
||||
* Optionally specifies Elasticsearch SAML realm name that Kibana should use. If not specified
|
||||
* Kibana ACS URL is used for realm matching instead.
|
||||
*/
|
||||
private readonly realm: string;
|
||||
private readonly realm?: string;
|
||||
|
||||
/**
|
||||
* Indicates if we should treat non-empty `RelayState` as a deep link in Kibana we should redirect
|
||||
|
@ -121,12 +123,8 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
) {
|
||||
super(options);
|
||||
|
||||
if (!samlOptions || !samlOptions.realm) {
|
||||
throw new Error('Realm name must be specified');
|
||||
}
|
||||
|
||||
this.realm = samlOptions.realm;
|
||||
this.useRelayStateDeepLink = samlOptions.useRelayStateDeepLink ?? false;
|
||||
this.realm = samlOptions?.realm;
|
||||
this.useRelayStateDeepLink = samlOptions?.useRelayStateDeepLink ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -144,7 +142,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
|
||||
// It may happen that Kibana is re-configured to use different realm for the same provider name,
|
||||
// we should clear such session an log user out.
|
||||
if (state?.realm && state.realm !== this.realm) {
|
||||
if (state && this.realm && state.realm !== this.realm) {
|
||||
const message = `State based on realm "${state.realm}", but provider with the name "${this.options.name}" is configured to use realm "${this.realm}".`;
|
||||
this.logger.debug(message);
|
||||
return AuthenticationResult.failed(Boom.unauthorized(message));
|
||||
|
@ -215,7 +213,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
|
||||
// It may happen that Kibana is re-configured to use different realm for the same provider name,
|
||||
// we should clear such session an log user out.
|
||||
if (state?.realm && state.realm !== this.realm) {
|
||||
if (state && this.realm && state.realm !== this.realm) {
|
||||
const message = `State based on realm "${state.realm}", but provider with the name "${this.options.name}" is configured to use realm "${this.realm}".`;
|
||||
this.logger.debug(message);
|
||||
return AuthenticationResult.failed(Boom.unauthorized(message));
|
||||
|
@ -274,7 +272,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
// and state !== undefined). In this case case it'd be safer to trigger SP initiated logout
|
||||
// for the new session as well.
|
||||
const redirect = isIdPInitiatedSLORequest
|
||||
? await this.performIdPInitiatedSingleLogout(request)
|
||||
? await this.performIdPInitiatedSingleLogout(request, this.realm || state?.realm)
|
||||
: state
|
||||
? await this.performUserInitiatedSingleLogout(state.accessToken!, state.refreshToken!)
|
||||
: // Once Elasticsearch can consume logout response we'll be sending it here. See https://github.com/elastic/elasticsearch/issues/40901
|
||||
|
@ -331,9 +329,14 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
|
||||
// If we have a `SAMLResponse` and state, but state doesn't contain all the necessary information,
|
||||
// then something unexpected happened and we should fail.
|
||||
const { requestId: stateRequestId, redirectURL: stateRedirectURL } = state || {
|
||||
const {
|
||||
requestId: stateRequestId,
|
||||
redirectURL: stateRedirectURL,
|
||||
realm: stateRealm,
|
||||
} = state || {
|
||||
requestId: '',
|
||||
redirectURL: '',
|
||||
realm: '',
|
||||
};
|
||||
if (state && !stateRequestId) {
|
||||
const message = 'SAML response state does not have corresponding request id.';
|
||||
|
@ -349,7 +352,14 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
: 'Login has been initiated by Identity Provider.'
|
||||
);
|
||||
|
||||
let result: { access_token: string; refresh_token: string; authentication: AuthenticationInfo };
|
||||
const providerRealm = this.realm || stateRealm;
|
||||
|
||||
let result: {
|
||||
access_token: string;
|
||||
refresh_token: string;
|
||||
realm: string;
|
||||
authentication: AuthenticationInfo;
|
||||
};
|
||||
try {
|
||||
// This operation should be performed on behalf of the user with a privilege that normal
|
||||
// user usually doesn't have `cluster:admin/xpack/security/saml/authenticate`.
|
||||
|
@ -362,7 +372,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
body: {
|
||||
ids: !isIdPInitiatedLogin ? [stateRequestId] : [],
|
||||
content: samlResponse,
|
||||
realm: this.realm,
|
||||
...(providerRealm ? { realm: providerRealm } : {}),
|
||||
},
|
||||
})
|
||||
).body as any;
|
||||
|
@ -372,7 +382,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
// Since we don't know upfront what realm is targeted by the Identity Provider initiated login
|
||||
// there is a chance that it failed because of realm mismatch and hence we should return
|
||||
// `notHandled` and give other SAML providers a chance to properly handle it instead.
|
||||
return isIdPInitiatedLogin
|
||||
return isIdPInitiatedLogin && providerRealm
|
||||
? AuthenticationResult.notHandled()
|
||||
: AuthenticationResult.failed(err);
|
||||
}
|
||||
|
@ -404,7 +414,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
state: {
|
||||
accessToken: result.access_token,
|
||||
refreshToken: result.refresh_token,
|
||||
realm: this.realm,
|
||||
realm: result.realm,
|
||||
},
|
||||
user: this.authenticationInfoToAuthenticatedUser(result.authentication),
|
||||
}
|
||||
|
@ -545,7 +555,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
authHeaders: {
|
||||
authorization: new HTTPAuthorizationHeader('Bearer', accessToken).toString(),
|
||||
},
|
||||
state: { accessToken, refreshToken, realm: this.realm },
|
||||
state: { accessToken, refreshToken, realm: this.realm || state.realm },
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -559,15 +569,18 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
this.logger.debug('Trying to initiate SAML handshake.');
|
||||
|
||||
try {
|
||||
// Prefer realm name if it's specified, otherwise fallback to ACS.
|
||||
const preparePayload = this.realm ? { realm: this.realm } : { acs: this.getACS() };
|
||||
|
||||
// This operation should be performed on behalf of the user with a privilege that normal
|
||||
// user usually doesn't have `cluster:admin/xpack/security/saml/prepare`.
|
||||
// We can replace generic `transport.request` with a dedicated API method call once
|
||||
// https://github.com/elastic/elasticsearch/issues/67189 is resolved.
|
||||
const { id: requestId, redirect } = (
|
||||
const { id: requestId, redirect, realm } = (
|
||||
await this.options.client.asInternalUser.transport.request({
|
||||
method: 'POST',
|
||||
path: '/_security/saml/prepare',
|
||||
body: { realm: this.realm },
|
||||
body: preparePayload,
|
||||
})
|
||||
).body as any;
|
||||
|
||||
|
@ -575,7 +588,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
|
||||
// Store request id in the state so that we can reuse it once we receive `SAMLResponse`.
|
||||
return AuthenticationResult.redirectTo(redirect, {
|
||||
state: { requestId, redirectURL, realm: this.realm },
|
||||
state: { requestId, redirectURL, realm },
|
||||
});
|
||||
} catch (err) {
|
||||
this.logger.debug(`Failed to initiate SAML handshake: ${getDetailedErrorMessage(err)}`);
|
||||
|
@ -612,10 +625,14 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
* Calls `saml/invalidate` with the `SAMLRequest` query string parameter received from the Identity
|
||||
* Provider and redirects user back to the Identity Provider if needed.
|
||||
* @param request Request instance.
|
||||
* @param realm Configured SAML realm name.
|
||||
*/
|
||||
private async performIdPInitiatedSingleLogout(request: KibanaRequest) {
|
||||
private async performIdPInitiatedSingleLogout(request: KibanaRequest, realm?: string) {
|
||||
this.logger.debug('Single logout has been initiated by the Identity Provider.');
|
||||
|
||||
// Prefer realm name if it's specified, otherwise fallback to ACS.
|
||||
const invalidatePayload = realm ? { realm } : { acs: this.getACS() };
|
||||
|
||||
// This operation should be performed on behalf of the user with a privilege that normal
|
||||
// user usually doesn't have `cluster:admin/xpack/security/saml/invalidate`.
|
||||
// We can replace generic `transport.request` with a dedicated API method call once
|
||||
|
@ -627,7 +644,7 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
// Elasticsearch expects `query_string` without leading `?`, so we should strip it with `slice`.
|
||||
body: {
|
||||
query_string: request.url.search ? request.url.search.slice(1) : '',
|
||||
realm: this.realm,
|
||||
...invalidatePayload,
|
||||
},
|
||||
})
|
||||
).body as any;
|
||||
|
@ -637,6 +654,15 @@ export class SAMLAuthenticationProvider extends BaseAuthenticationProvider {
|
|||
return redirect;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs and returns Kibana's Assertion consumer service URL.
|
||||
*/
|
||||
private getACS() {
|
||||
return `${this.options.getServerBaseURL()}${
|
||||
this.options.basePath.serverBasePath
|
||||
}/api/security/v1/saml`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to initiate SAML authentication handshake. If the request already includes user URL hash fragment, we will
|
||||
* initiate handshake right away, otherwise we'll redirect user to a dedicated page where we capture URL hash fragment
|
||||
|
|
|
@ -58,6 +58,7 @@ describe('config schema', () => {
|
|||
"enabled": true,
|
||||
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"loginAssistanceMessage": "",
|
||||
"public": Object {},
|
||||
"secureCookies": false,
|
||||
"session": Object {
|
||||
"cleanupInterval": "PT1H",
|
||||
|
@ -109,6 +110,7 @@ describe('config schema', () => {
|
|||
"enabled": true,
|
||||
"encryptionKey": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
|
||||
"loginAssistanceMessage": "",
|
||||
"public": Object {},
|
||||
"secureCookies": false,
|
||||
"session": Object {
|
||||
"cleanupInterval": "PT1H",
|
||||
|
@ -159,6 +161,7 @@ describe('config schema', () => {
|
|||
"cookieName": "sid",
|
||||
"enabled": true,
|
||||
"loginAssistanceMessage": "",
|
||||
"public": Object {},
|
||||
"secureCookies": false,
|
||||
"session": Object {
|
||||
"cleanupInterval": "PT1H",
|
||||
|
@ -179,6 +182,109 @@ describe('config schema', () => {
|
|||
);
|
||||
});
|
||||
|
||||
describe('public', () => {
|
||||
it('properly validates `protocol`', async () => {
|
||||
expect(ConfigSchema.validate({ public: { protocol: 'http' } }).public).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"protocol": "http",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ConfigSchema.validate({ public: { protocol: 'https' } }).public)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"protocol": "https",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(() => ConfigSchema.validate({ public: { protocol: 'ftp' } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"[public.protocol]: types that failed validation:
|
||||
- [public.protocol.0]: expected value to equal [http]
|
||||
- [public.protocol.1]: expected value to equal [https]"
|
||||
`);
|
||||
|
||||
expect(() => ConfigSchema.validate({ public: { protocol: 'some-protocol' } }))
|
||||
.toThrowErrorMatchingInlineSnapshot(`
|
||||
"[public.protocol]: types that failed validation:
|
||||
- [public.protocol.0]: expected value to equal [http]
|
||||
- [public.protocol.1]: expected value to equal [https]"
|
||||
`);
|
||||
});
|
||||
|
||||
it('properly validates `hostname`', async () => {
|
||||
expect(ConfigSchema.validate({ public: { hostname: 'elastic.co' } }).public)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hostname": "elastic.co",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ConfigSchema.validate({ public: { hostname: '192.168.1.1' } }).public)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hostname": "192.168.1.1",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ConfigSchema.validate({ public: { hostname: '::1' } }).public).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"hostname": "::1",
|
||||
}
|
||||
`);
|
||||
|
||||
expect(() =>
|
||||
ConfigSchema.validate({ public: { hostname: 'http://elastic.co' } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[public.hostname]: value must be a valid hostname (see RFC 1123)."`
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ConfigSchema.validate({ public: { hostname: 'localhost:5601' } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[public.hostname]: value must be a valid hostname (see RFC 1123)."`
|
||||
);
|
||||
});
|
||||
|
||||
it('properly validates `port`', async () => {
|
||||
expect(ConfigSchema.validate({ public: { port: 1234 } }).public).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"port": 1234,
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ConfigSchema.validate({ public: { port: 0 } }).public).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"port": 0,
|
||||
}
|
||||
`);
|
||||
|
||||
expect(ConfigSchema.validate({ public: { port: 65535 } }).public).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"port": 65535,
|
||||
}
|
||||
`);
|
||||
|
||||
expect(() =>
|
||||
ConfigSchema.validate({ public: { port: -1 } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[public.port]: Value must be equal to or greater than [0]."`
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ConfigSchema.validate({ public: { port: 65536 } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[public.port]: Value must be equal to or lower than [65535]."`
|
||||
);
|
||||
|
||||
expect(() =>
|
||||
ConfigSchema.validate({ public: { port: '56x1' } })
|
||||
).toThrowErrorMatchingInlineSnapshot(
|
||||
`"[public.port]: expected value of type [number] but got [string]"`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('authc.oidc', () => {
|
||||
it(`returns a validation error when authc.providers is "['oidc']" and realm is unspecified`, async () => {
|
||||
expect(() => ConfigSchema.validate({ authc: { providers: ['oidc'] } })).toThrow(
|
||||
|
@ -255,14 +361,42 @@ describe('config schema', () => {
|
|||
});
|
||||
|
||||
describe('authc.saml', () => {
|
||||
it('fails if authc.providers includes `saml`, but `saml.realm` is not specified', async () => {
|
||||
expect(() => ConfigSchema.validate({ authc: { providers: ['saml'] } })).toThrow(
|
||||
'[authc.saml.realm]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
it('does not fail if authc.providers includes `saml`, but `saml.realm` is not specified', async () => {
|
||||
expect(ConfigSchema.validate({ authc: { providers: ['saml'] } }).authc)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"http": Object {
|
||||
"autoSchemesEnabled": true,
|
||||
"enabled": true,
|
||||
"schemes": Array [
|
||||
"apikey",
|
||||
],
|
||||
},
|
||||
"providers": Array [
|
||||
"saml",
|
||||
],
|
||||
"saml": Object {},
|
||||
"selector": Object {},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(() => ConfigSchema.validate({ authc: { providers: ['saml'], saml: {} } })).toThrow(
|
||||
'[authc.saml.realm]: expected value of type [string] but got [undefined]'
|
||||
);
|
||||
expect(ConfigSchema.validate({ authc: { providers: ['saml'], saml: {} } }).authc)
|
||||
.toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"http": Object {
|
||||
"autoSchemesEnabled": true,
|
||||
"enabled": true,
|
||||
"schemes": Array [
|
||||
"apikey",
|
||||
],
|
||||
},
|
||||
"providers": Array [
|
||||
"saml",
|
||||
],
|
||||
"saml": Object {},
|
||||
"selector": Object {},
|
||||
}
|
||||
`);
|
||||
|
||||
expect(
|
||||
ConfigSchema.validate({
|
||||
|
|
|
@ -228,6 +228,11 @@ export const ConfigSchema = schema.object({
|
|||
sameSiteCookies: schema.maybe(
|
||||
schema.oneOf([schema.literal('Strict'), schema.literal('Lax'), schema.literal('None')])
|
||||
),
|
||||
public: schema.object({
|
||||
protocol: schema.maybe(schema.oneOf([schema.literal('http'), schema.literal('https')])),
|
||||
hostname: schema.maybe(schema.string({ hostname: true })),
|
||||
port: schema.maybe(schema.number({ min: 0, max: 65535 })),
|
||||
}),
|
||||
authc: schema.object({
|
||||
selector: schema.object({ enabled: schema.maybe(schema.boolean()) }),
|
||||
providers: schema.oneOf([schema.arrayOf(schema.string()), providersConfigSchema], {
|
||||
|
@ -256,7 +261,7 @@ export const ConfigSchema = schema.object({
|
|||
saml: providerOptionsSchema(
|
||||
'saml',
|
||||
schema.object({
|
||||
realm: schema.string(),
|
||||
realm: schema.maybe(schema.string()),
|
||||
maxRedirectURLSize: schema.maybe(schema.byteSize()),
|
||||
})
|
||||
),
|
||||
|
|
|
@ -286,7 +286,7 @@ describe('Config Deprecations', () => {
|
|||
const { messages } = applyConfigDeprecations(cloneDeep(config));
|
||||
expect(messages).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
"\\"xpack.security.authc.providers.saml.<provider-name>.maxRedirectURLSize\\" is is no longer used.",
|
||||
"\\"xpack.security.authc.providers.saml.<provider-name>.maxRedirectURLSize\\" is no longer used.",
|
||||
]
|
||||
`);
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
unused,
|
||||
}) => [
|
||||
rename('sessionTimeout', 'session.idleTimeout'),
|
||||
rename('authProviders', 'authc.providers'),
|
||||
|
||||
rename('audit.appender.kind', 'audit.appender.type'),
|
||||
rename('audit.appender.layout.kind', 'audit.appender.layout.type'),
|
||||
|
@ -121,7 +122,7 @@ export const securityConfigDeprecationProvider: ConfigDeprecationProvider = ({
|
|||
}),
|
||||
message: i18n.translate('xpack.security.deprecations.maxRedirectURLSizeMessage', {
|
||||
defaultMessage:
|
||||
'"xpack.security.authc.providers.saml.<provider-name>.maxRedirectURLSize" is is no longer used.',
|
||||
'"xpack.security.authc.providers.saml.<provider-name>.maxRedirectURLSize" is no longer used.',
|
||||
}),
|
||||
correctiveActions: {
|
||||
manualSteps: [
|
||||
|
|
|
@ -50,7 +50,7 @@ export default async function ({ readConfigFile }: FtrConfigProviderContext) {
|
|||
serverArgs: [
|
||||
...xPackAPITestsConfig.get('kbnTestServer.serverArgs'),
|
||||
`--plugin-path=${plugin}`,
|
||||
`--xpack.security.authc.providers=${JSON.stringify(['oidc', 'basic'])}`,
|
||||
`--xpack.security.authProviders=${JSON.stringify(['oidc', 'basic'])}`,
|
||||
'--xpack.security.authc.oidc.realm="oidc1"',
|
||||
],
|
||||
},
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue