Skip to content

Commit

Permalink
Introduce telemetry for security features (elastic#74530) (elastic#77199
Browse files Browse the repository at this point in the history
)

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>

Co-authored-by: Joe Portner <5295965+jportner@users.noreply.github.com>
  • Loading branch information
legrego and jportner authored Sep 10, 2020
1 parent 1249ae5 commit d767b03
Show file tree
Hide file tree
Showing 14 changed files with 706 additions and 4 deletions.
1 change: 1 addition & 0 deletions x-pack/plugins/security/common/licensing/index.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { SecurityLicense } from '.';

export const licenseMock = {
create: (): jest.Mocked<SecurityLicense> => ({
isLicenseAvailable: jest.fn(),
isEnabled: jest.fn().mockReturnValue(true),
getFeatures: jest.fn(),
features$: of(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(undefined as any),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(false);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: false,
Expand All @@ -34,6 +35,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(rawLicenseMock),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(false);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: false,
Expand All @@ -60,6 +62,7 @@ describe('license features', function () {
const subscriptionHandler = jest.fn();
const subscription = serviceSetup.license.features$.subscribe(subscriptionHandler);
try {
expect(serviceSetup.license.isLicenseAvailable()).toEqual(false);
expect(subscriptionHandler).toHaveBeenCalledTimes(1);
expect(subscriptionHandler.mock.calls[0]).toMatchInlineSnapshot(`
Array [
Expand All @@ -80,6 +83,7 @@ describe('license features', function () {
`);

rawLicense$.next(licenseMock.createLicenseMock());
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(subscriptionHandler).toHaveBeenCalledTimes(2);
expect(subscriptionHandler.mock.calls[1]).toMatchInlineSnapshot(`
Array [
Expand Down Expand Up @@ -112,6 +116,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(mockRawLicense),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: true,
Expand All @@ -136,6 +141,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(mockRawLicense),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: false,
allowLogin: false,
Expand All @@ -159,6 +165,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(mockRawLicense),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: true,
Expand All @@ -182,6 +189,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(mockRawLicense),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: true,
Expand All @@ -205,6 +213,7 @@ describe('license features', function () {
const serviceSetup = new SecurityLicenseService().setup({
license$: of(mockRawLicense),
});
expect(serviceSetup.license.isLicenseAvailable()).toEqual(true);
expect(serviceSetup.license.getFeatures()).toEqual({
showLogin: true,
allowLogin: true,
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/security/common/licensing/license_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ILicense } from '../../../licensing/common/types';
import { SecurityLicenseFeatures } from './license_features';

export interface SecurityLicense {
isLicenseAvailable(): boolean;
isEnabled(): boolean;
getFeatures(): SecurityLicenseFeatures;
features$: Observable<SecurityLicenseFeatures>;
Expand All @@ -31,6 +32,8 @@ export class SecurityLicenseService {

return {
license: Object.freeze({
isLicenseAvailable: () => rawLicense?.isAvailable ?? false,

isEnabled: () => this.isSecurityEnabledFromRawLicense(rawLicense),

getFeatures: () => this.calculateFeaturesFromRawLicense(rawLicense),
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/security/kibana.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"kibanaVersion": "kibana",
"configPath": ["xpack", "security"],
"requiredPlugins": ["data", "features", "licensing", "taskManager"],
"optionalPlugins": ["home", "management"],
"optionalPlugins": ["home", "management", "usageCollection"],
"server": true,
"ui": true,
"requiredBundles": [
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions x-pack/plugins/security/public/plugin.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ describe('Security Plugin', () => {
__legacyCompat: { logoutUrl: '/some-base-path/logout', tenant: '/some-base-path' },
authc: { getCurrentUser: expect.any(Function), areAPIKeysEnabled: expect.any(Function) },
license: {
isLicenseAvailable: expect.any(Function),
isEnabled: expect.any(Function),
getFeatures: expect.any(Function),
features$: expect.any(Observable),
Expand All @@ -67,6 +68,7 @@ describe('Security Plugin', () => {
expect(setupManagementServiceMock).toHaveBeenCalledWith({
authc: { getCurrentUser: expect.any(Function), areAPIKeysEnabled: expect.any(Function) },
license: {
isLicenseAvailable: expect.any(Function),
isEnabled: expect.any(Function),
getFeatures: expect.any(Function),
features$: expect.any(Observable),
Expand Down
64 changes: 64 additions & 0 deletions x-pack/plugins/security/server/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1034,11 +1034,13 @@ describe('createConfig()', () => {
},
"sortedProviders": Array [
Object {
"hasAccessAgreement": false,
"name": "saml",
"order": 0,
"type": "saml",
},
Object {
"hasAccessAgreement": false,
"name": "basic",
"order": 1,
"type": "basic",
Expand Down Expand Up @@ -1112,6 +1114,63 @@ describe('createConfig()', () => {
).toBe(true);
});

it('indicates which providers have the access agreement enabled', () => {
expect(
createConfig(
ConfigSchema.validate({
authc: {
providers: {
basic: { basic1: { order: 3 } },
saml: {
saml1: { order: 2, realm: 'saml1', accessAgreement: { message: 'foo' } },
saml2: { order: 1, realm: 'saml2' },
},
oidc: {
oidc1: { order: 0, realm: 'oidc1', accessAgreement: { message: 'foo' } },
oidc2: { order: 4, realm: 'oidc2' },
},
},
},
}),
loggingSystemMock.create().get(),
{ isTLSEnabled: true }
).authc.sortedProviders
).toMatchInlineSnapshot(`
Array [
Object {
"hasAccessAgreement": true,
"name": "oidc1",
"order": 0,
"type": "oidc",
},
Object {
"hasAccessAgreement": false,
"name": "saml2",
"order": 1,
"type": "saml",
},
Object {
"hasAccessAgreement": true,
"name": "saml1",
"order": 2,
"type": "saml",
},
Object {
"hasAccessAgreement": false,
"name": "basic1",
"order": 3,
"type": "basic",
},
Object {
"hasAccessAgreement": false,
"name": "oidc2",
"order": 4,
"type": "oidc",
},
]
`);
});

it('correctly sorts providers based on the `order`', () => {
expect(
createConfig(
Expand All @@ -1130,26 +1189,31 @@ describe('createConfig()', () => {
).toMatchInlineSnapshot(`
Array [
Object {
"hasAccessAgreement": false,
"name": "oidc1",
"order": 0,
"type": "oidc",
},
Object {
"hasAccessAgreement": false,
"name": "saml2",
"order": 1,
"type": "saml",
},
Object {
"hasAccessAgreement": false,
"name": "saml1",
"order": 2,
"type": "saml",
},
Object {
"hasAccessAgreement": false,
"name": "basic1",
"order": 3,
"type": "basic",
},
Object {
"hasAccessAgreement": false,
"name": "oidc2",
"order": 4,
"type": "oidc",
Expand Down
10 changes: 8 additions & 2 deletions x-pack/plugins/security/server/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -260,13 +260,19 @@ export function createConfig(
type: keyof ProvidersConfigType;
name: string;
order: number;
hasAccessAgreement: boolean;
}> = [];
for (const [type, providerGroup] of Object.entries(providers)) {
for (const [name, { enabled, order }] of Object.entries(providerGroup ?? {})) {
for (const [name, { enabled, order, accessAgreement }] of Object.entries(providerGroup ?? {})) {
if (!enabled) {
delete providerGroup![name];
} else {
sortedProviders.push({ type: type as any, name, order });
sortedProviders.push({
type: type as any,
name,
order,
hasAccessAgreement: !!accessAgreement?.message,
});
}
}
}
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/security/server/plugin.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ describe('Security Plugin', () => {
},
"getFeatures": [Function],
"isEnabled": [Function],
"isLicenseAvailable": [Function],
},
"registerSpacesService": [Function],
}
Expand Down
7 changes: 6 additions & 1 deletion x-pack/plugins/security/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import { combineLatest } from 'rxjs';
import { first, map } from 'rxjs/operators';
import { TypeOf } from '@kbn/config-schema';
import { UsageCollectionSetup } from 'src/plugins/usage_collection/server';
import {
deepFreeze,
CoreSetup,
Expand All @@ -32,6 +33,7 @@ import { AuditService, SecurityAuditLogger, AuditServiceSetup } from './audit';
import { SecurityFeatureUsageService, SecurityFeatureUsageServiceStart } from './feature_usage';
import { ElasticsearchService } from './elasticsearch';
import { SessionManagementService } from './session_management';
import { registerSecurityUsageCollector } from './usage_collector';

export type SpacesService = Pick<
SpacesPluginSetup['spacesService'],
Expand Down Expand Up @@ -74,6 +76,7 @@ export interface PluginSetupDependencies {
features: FeaturesPluginSetup;
licensing: LicensingPluginSetup;
taskManager: TaskManagerSetupContract;
usageCollection?: UsageCollectionSetup;
}

export interface PluginStartDependencies {
Expand Down Expand Up @@ -123,7 +126,7 @@ export class Plugin {

public async setup(
core: CoreSetup<PluginStartDependencies>,
{ features, licensing, taskManager }: PluginSetupDependencies
{ features, licensing, taskManager, usageCollection }: PluginSetupDependencies
) {
const [config, legacyConfig] = await combineLatest([
this.initializerContext.config.create<TypeOf<typeof ConfigSchema>>().pipe(
Expand Down Expand Up @@ -151,6 +154,8 @@ export class Plugin {

this.featureUsageService.setup({ featureUsage: licensing.featureUsage });

registerSecurityUsageCollector({ usageCollection, config, license });

const audit = this.auditService.setup({ license, config: config.audit });
const auditLogger = new SecurityAuditLogger(audit.getLogger());

Expand Down
7 changes: 7 additions & 0 deletions x-pack/plugins/security/server/usage_collector/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/*
* 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.
*/

export { registerSecurityUsageCollector } from './security_usage_collector';
Loading

0 comments on commit d767b03

Please sign in to comment.