Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
azasypkin committed Feb 7, 2020
1 parent a7be72c commit e0f2a25
Show file tree
Hide file tree
Showing 19 changed files with 454 additions and 163 deletions.
9 changes: 9 additions & 0 deletions x-pack/plugins/login_selector/kibana.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"id": "loginSelector",
"version": "8.0.0",
"kibanaVersion": "kibana",
"configPath": ["xpack", "loginSelector"],
"requiredPlugins": ["security", "licensing"],
"server": true,
"ui": false
}
11 changes: 11 additions & 0 deletions x-pack/plugins/login_selector/server/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { PluginInitializerContext } from '../../../../src/core/server';
import { Plugin } from './plugin';

export const plugin = (initializerContext: PluginInitializerContext) =>
new Plugin(initializerContext);
57 changes: 57 additions & 0 deletions x-pack/plugins/login_selector/server/plugin.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { ByteSizeValue } from '@kbn/config-schema';
import { CoreSetup, Logger, PluginInitializerContext } from '../../../../src/core/server';
import { LicensingPluginSetup } from '../../licensing/server';
import { PluginSetupContract as SecurityPluginSetup } from '../../security/server';
import { defineRoutes } from './routes';

export interface PluginSetupDependencies {
licensing: LicensingPluginSetup;
security: SecurityPluginSetup;
}

/**
* Represents Login Selector Plugin instance that will be managed by the Kibana plugin system.
*/
export class Plugin {
private readonly logger: Logger;

constructor(private readonly initializerContext: PluginInitializerContext) {
this.logger = this.initializerContext.logger.get();
}

public async setup(core: CoreSetup, { security }: PluginSetupDependencies) {
if (!security.authc.isProviderEnabled('saml')) {
security.authc.enableProvider('saml', {
realm: '__fake-realm__',
maxRedirectURLSize: new ByteSizeValue(2048),
});
}

if (!security.authc.isProviderEnabled('oidc')) {
security.authc.enableProvider('oidc', {
realm: '__fake-realm__',
});
}

defineRoutes({
router: core.http.createRouter(),
basePath: core.http.basePath,
logger: this.initializerContext.logger.get('routes'),
security,
});
}

public start() {
this.logger.debug('Starting plugin');
}

public stop() {
this.logger.debug('Stopping plugin');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { schema } from '@kbn/config-schema';
import { OIDCLogin, SAMLLogin } from '../../../../security/server';
import { RouteDefinitionParams } from '..';

export function defineAuthenticationRoutes({
basePath,
logger,
router,
security,
}: RouteDefinitionParams) {
router.get(
{
path: '/login-selector/quick-sso-login/{provider}/{realm}',
validate: {
params: schema.object({
provider: schema.oneOf([schema.literal('saml'), schema.literal('oidc')]),
realm: schema.string({ minLength: 1 }),
}),
},
options: { authRequired: false },
},
async (context, request, response) => {
try {
logger.info(`Logging with ${request.params.provider}/${request.params.realm}`);
const authenticationResult = await security.authc.login(request, {
provider: request.params.provider,
value: {
type:
request.params.provider === 'saml'
? SAMLLogin.LoginWithRealm
: OIDCLogin.LoginWithRealm,
realm: request.params.realm,
redirectURL: `${basePath.get(request)}/`,
},
});

// When authenticating using SAML we _expect_ to redirect to the SAML Identity provider.
if (authenticationResult.redirected()) {
return response.redirected({ headers: { location: authenticationResult.redirectURL! } });
}

return response.unauthorized();
} catch (err) {
logger.error(err);
return response.internalError();
}
}
);
}
24 changes: 24 additions & 0 deletions x-pack/plugins/login_selector/server/routes/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { CoreSetup, IRouter, Logger } from '../../../../../src/core/server';
import { PluginSetupContract as SecurityPluginSetup } from '../../../security/server';

import { defineAuthenticationRoutes } from './authentication';

/**
* Describes parameters used to define HTTP routes.
*/
export interface RouteDefinitionParams {
router: IRouter;
basePath: CoreSetup['http']['basePath'];
logger: Logger;
security: SecurityPluginSetup;
}

export function defineRoutes(params: RouteDefinitionParams) {
defineAuthenticationRoutes(params);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { RequestHandler } from 'src/core/server';
import { ObjectType } from '@kbn/config-schema';
import { LICENSE_CHECK_STATE } from '../../../licensing/server';

export const createLicensedRouteHandler = <
P extends ObjectType<any>,
Q extends ObjectType<any>,
B extends ObjectType<any>
>(
handler: RequestHandler<P, Q, B>
) => {
const licensedRouteHandler: RequestHandler<P, Q, B> = (context, request, responseToolkit) => {
const { license } = context.licensing;
const licenseCheck = license.check('security', 'basic');
if (
licenseCheck.state === LICENSE_CHECK_STATE.Unavailable ||
licenseCheck.state === LICENSE_CHECK_STATE.Invalid
) {
return responseToolkit.forbidden({ body: { message: licenseCheck.message! } });
}

return handler(context, request, responseToolkit);
};

return licensedRouteHandler;
};
101 changes: 65 additions & 36 deletions x-pack/plugins/security/server/authentication/authenticator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,12 @@ export interface AuthenticatorOptions {
isSystemAPIRequest: (request: KibanaRequest) => boolean;
}

type KnownProviderType = ConfigType['authc']['providers'] extends Array<infer P> ? P : never;

// Mapping between provider key defined in the config and authentication
// provider class that can handle specific authentication mechanism.
const providerMap = new Map<
string,
KnownProviderType,
new (
options: AuthenticationProviderOptions,
providerSpecificOptions?: AuthenticationProviderSpecificOptions
Expand All @@ -120,25 +122,6 @@ function assertLoginAttempt(attempt: ProviderLoginAttempt) {
}
}

/**
* Instantiates authentication provider based on the provider key from config.
* @param providerType Provider type key.
* @param options Options to pass to provider's constructor.
* @param providerSpecificOptions Options that are specific to {@param providerType}.
*/
function instantiateProvider(
providerType: string,
options: AuthenticationProviderOptions,
providerSpecificOptions?: AuthenticationProviderSpecificOptions
) {
const ProviderClassName = providerMap.get(providerType);
if (!ProviderClassName) {
throw new Error(`Unsupported authentication provider name: ${providerType}.`);
}

return new ProviderClassName(options, providerSpecificOptions);
}

/**
* Authenticator is responsible for authentication of the request using chain of
* authentication providers. The chain is essentially a prioritized list of configured
Expand Down Expand Up @@ -178,21 +161,18 @@ export class Authenticator {
*/
private readonly logger: Logger;

private readonly tokens: PublicMethodsOf<Tokens>;

/**
* Instantiates Authenticator and bootstrap configured providers.
* @param options Authenticator options.
*/
constructor(private readonly options: Readonly<AuthenticatorOptions>) {
this.logger = options.loggers.get('authenticator');

const providerCommonOptions = {
this.tokens = new Tokens({
client: this.options.clusterClient,
basePath: this.options.basePath,
tokens: new Tokens({
client: this.options.clusterClient,
logger: this.options.loggers.get('tokens'),
}),
};
logger: this.options.loggers.get('tokens'),
});

const authProviders = this.options.config.authc.providers;
if (authProviders.length === 0) {
Expand All @@ -207,14 +187,10 @@ export class Authenticator {
? (this.options.config.authc as Record<string, any>)[providerType]
: undefined;

return [
providerType,
instantiateProvider(
providerType,
Object.freeze({ ...providerCommonOptions, logger: options.loggers.get(providerType) }),
providerSpecificOptions
),
] as [string, BaseAuthenticationProvider];
return [providerType, this.createProvider(providerType, providerSpecificOptions)] as [
string,
BaseAuthenticationProvider
];
})
);
this.serverBasePath = this.options.basePath.serverBasePath || '/';
Expand Down Expand Up @@ -386,6 +362,59 @@ export class Authenticator {
return null;
}

isProviderEnabled(providerType: KnownProviderType) {
return this.providers.has(providerType);
}

enableProvider(providerType: KnownProviderType, options?: Record<string, unknown>) {
if (!providerMap.has(providerType)) {
throw new Error(`Provider "${providerType}" is not supported!`);
}

if (this.isProviderEnabled(providerType)) {
throw new Error(`Provider "${providerType}" is already enabled!`);
}

this.providers.set(providerType, this.createProvider(providerType, options));
}

disableProvider(providerType: KnownProviderType) {
if (!providerMap.has(providerType)) {
throw new Error(`Provider "${providerType}" is not supported!`);
}

if (!this.isProviderEnabled(providerType)) {
return;
}

this.providers.delete(providerType);
}

/**
* Instantiates authentication provider based on the provider key from config.
* @param providerType Provider type key.
* @param providerSpecificOptions Options that are specific to {@param providerType}.
*/
private createProvider(
providerType: KnownProviderType,
providerSpecificOptions?: Record<string, unknown>
) {
const ProviderClassName = providerMap.get(providerType);
if (!ProviderClassName) {
throw new Error(`Unsupported authentication provider name: ${providerType}.`);
}

return new ProviderClassName(
Object.freeze({
client: this.options.clusterClient,
basePath: this.options.basePath,
tokens: this.tokens,
logger: this.options.loggers.get(providerType),
}),
providerSpecificOptions || (this.options.config.authc as Record<string, any>)[providerType]
);
}

/**
* Returns provider iterator where providers are sorted in the order of priority (based on the session ownership).
* @param sessionValue Current session value.
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/security/server/authentication/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export { canRedirectRequest } from './can_redirect_request';
export { Authenticator, ProviderLoginAttempt } from './authenticator';
export { AuthenticationResult } from './authentication_result';
export { DeauthenticationResult } from './deauthentication_result';
export { OIDCAuthenticationFlow, SAMLLoginStep } from './providers';
export { OIDCLogin, SAMLLogin } from './providers';
export {
CreateAPIKeyResult,
InvalidateAPIKeyResult,
Expand Down Expand Up @@ -173,6 +173,9 @@ export async function setupAuthentication({
login: authenticator.login.bind(authenticator),
logout: authenticator.logout.bind(authenticator),
getSessionInfo: authenticator.getSessionInfo.bind(authenticator),
isProviderEnabled: authenticator.isProviderEnabled.bind(authenticator),
enableProvider: authenticator.enableProvider.bind(authenticator),
disableProvider: authenticator.disableProvider.bind(authenticator),
getCurrentUser,
createAPIKey: (request: KibanaRequest, params: CreateAPIKeyParams) =>
apiKeys.create(request, params),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ export {
} from './base';
export { BasicAuthenticationProvider } from './basic';
export { KerberosAuthenticationProvider } from './kerberos';
export { SAMLAuthenticationProvider, isSAMLRequestQuery, SAMLLoginStep } from './saml';
export { SAMLAuthenticationProvider, isSAMLRequestQuery, SAMLLogin } from './saml';
export { TokenAuthenticationProvider } from './token';
export { OIDCAuthenticationProvider, OIDCAuthenticationFlow } from './oidc';
export { OIDCAuthenticationProvider, OIDCLogin } from './oidc';
export { PKIAuthenticationProvider } from './pki';
Loading

0 comments on commit e0f2a25

Please sign in to comment.