mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
Exposes security audit logging from core (#181644)
Fix https://github.com/elastic/kibana/issues/178934 Exposes the audit service from core's security service. --------- Co-authored-by: pgayvallet <pierre.gayvallet@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
2360af9ec6
commit
991170bb19
25 changed files with 255 additions and 18 deletions
|
@ -367,6 +367,7 @@ export function createPluginStartContext<TPlugin, TPluginDependencies>({
|
|||
},
|
||||
security: {
|
||||
authc: deps.security.authc,
|
||||
audit: deps.security.audit,
|
||||
},
|
||||
userProfile: deps.userProfile,
|
||||
};
|
||||
|
|
|
@ -10,12 +10,13 @@ import type { KibanaRequest } from '@kbn/core-http-server';
|
|||
import type {
|
||||
SecurityRequestHandlerContext,
|
||||
AuthcRequestHandlerContext,
|
||||
AuditRequestHandlerContext,
|
||||
} from '@kbn/core-security-server';
|
||||
import type { InternalSecurityServiceStart } from './internal_contracts';
|
||||
|
||||
export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerContext {
|
||||
#authc?: AuthcRequestHandlerContext;
|
||||
|
||||
#audit?: AuditRequestHandlerContext;
|
||||
constructor(
|
||||
private readonly securityStart: InternalSecurityServiceStart,
|
||||
private readonly request: KibanaRequest
|
||||
|
@ -29,4 +30,13 @@ export class CoreSecurityRouteHandlerContext implements SecurityRequestHandlerCo
|
|||
}
|
||||
return this.#authc;
|
||||
}
|
||||
|
||||
public get audit() {
|
||||
if (this.#audit == null) {
|
||||
this.#audit = {
|
||||
logger: this.securityStart.audit.asScoped(this.request),
|
||||
};
|
||||
}
|
||||
return this.#audit;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import { AuditLogger } from '@kbn/core-security-server';
|
||||
|
||||
export type MockedAuditLogger = jest.Mocked<AuditLogger>;
|
||||
|
||||
export const createAuditLoggerMock = {
|
||||
create(): MockedAuditLogger {
|
||||
return {
|
||||
log: jest.fn(),
|
||||
enabled: true,
|
||||
};
|
||||
},
|
||||
};
|
|
@ -8,11 +8,22 @@
|
|||
|
||||
import type { CoreSecurityDelegateContract } from '@kbn/core-security-server';
|
||||
import { convertSecurityApi } from './convert_security_api';
|
||||
import { createAuditLoggerMock } from '../test_helpers/create_audit_logger.mock';
|
||||
|
||||
describe('convertSecurityApi', () => {
|
||||
it('returns the API from the source', () => {
|
||||
const source: CoreSecurityDelegateContract = { authc: { getCurrentUser: jest.fn() } };
|
||||
const source: CoreSecurityDelegateContract = {
|
||||
authc: {
|
||||
getCurrentUser: jest.fn(),
|
||||
},
|
||||
audit: {
|
||||
asScoped: jest.fn().mockReturnValue(createAuditLoggerMock.create()),
|
||||
withoutRequest: createAuditLoggerMock.create(),
|
||||
},
|
||||
};
|
||||
const output = convertSecurityApi(source);
|
||||
expect(output.authc.getCurrentUser).toBe(source.authc.getCurrentUser);
|
||||
expect(output.audit.asScoped).toBe(source.audit.asScoped);
|
||||
expect(output.audit.withoutRequest).toBe(source.audit.withoutRequest);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -22,4 +22,19 @@ describe('getDefaultSecurityImplementation', () => {
|
|||
expect(user).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('audit.asScoped', () => {
|
||||
it('returns null', async () => {
|
||||
const logger = implementation.audit.asScoped({} as any);
|
||||
expect(logger.log({ message: 'something' })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('audit.withoutRequest', () => {
|
||||
it('does not log', async () => {
|
||||
const logger = implementation.audit.withoutRequest;
|
||||
expect(logger.enabled).toBe(false);
|
||||
expect(logger.log({ message: 'no request' })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,5 +13,14 @@ export const getDefaultSecurityImplementation = (): CoreSecurityDelegateContract
|
|||
authc: {
|
||||
getCurrentUser: () => null,
|
||||
},
|
||||
audit: {
|
||||
asScoped: () => {
|
||||
return { log: () => undefined, enabled: false };
|
||||
},
|
||||
withoutRequest: {
|
||||
log: () => undefined,
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
|
|
@ -7,3 +7,5 @@
|
|||
*/
|
||||
|
||||
export { securityServiceMock } from './src/security_service.mock';
|
||||
export type { InternalSecurityStartMock, SecurityStartMock } from './src/security_service.mock';
|
||||
export { auditLoggerMock } from './src/audit.mock';
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import type { AuditLogger } from '@kbn/core-security-server';
|
||||
|
||||
export type MockedAuditLogger = jest.Mocked<AuditLogger>;
|
||||
|
||||
export const auditLoggerMock = {
|
||||
create(): MockedAuditLogger {
|
||||
return {
|
||||
log: jest.fn(),
|
||||
enabled: true,
|
||||
};
|
||||
},
|
||||
};
|
||||
|
||||
export interface MockedAuditService {
|
||||
asScoped: (request: KibanaRequest) => MockedAuditLogger;
|
||||
withoutRequest: MockedAuditLogger;
|
||||
}
|
||||
|
||||
export const auditServiceMock = {
|
||||
create(): MockedAuditService {
|
||||
return {
|
||||
asScoped: jest.fn().mockReturnValue(auditLoggerMock.create()),
|
||||
withoutRequest: auditLoggerMock.create(),
|
||||
};
|
||||
},
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type {
|
||||
import {
|
||||
SecurityServiceSetup,
|
||||
SecurityServiceStart,
|
||||
SecurityRequestHandlerContext,
|
||||
|
@ -15,6 +15,7 @@ import type {
|
|||
InternalSecurityServiceSetup,
|
||||
InternalSecurityServiceStart,
|
||||
} from '@kbn/core-security-server-internal';
|
||||
import { auditServiceMock, type MockedAuditService } from './audit.mock';
|
||||
|
||||
const createSetupMock = () => {
|
||||
const mock: jest.Mocked<SecurityServiceSetup> = {
|
||||
|
@ -24,11 +25,16 @@ const createSetupMock = () => {
|
|||
return mock;
|
||||
};
|
||||
|
||||
const createStartMock = () => {
|
||||
const mock: jest.MockedObjectDeep<SecurityServiceStart> = {
|
||||
export type SecurityStartMock = jest.MockedObjectDeep<Omit<SecurityServiceStart, 'audit'>> & {
|
||||
audit: MockedAuditService;
|
||||
};
|
||||
|
||||
const createStartMock = (): SecurityStartMock => {
|
||||
const mock = {
|
||||
authc: {
|
||||
getCurrentUser: jest.fn(),
|
||||
},
|
||||
audit: auditServiceMock.create(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
@ -42,11 +48,18 @@ const createInternalSetupMock = () => {
|
|||
return mock;
|
||||
};
|
||||
|
||||
const createInternalStartMock = () => {
|
||||
const mock: jest.MockedObjectDeep<InternalSecurityServiceStart> = {
|
||||
export type InternalSecurityStartMock = jest.MockedObjectDeep<
|
||||
Omit<InternalSecurityServiceStart, 'audit'>
|
||||
> & {
|
||||
audit: MockedAuditService;
|
||||
};
|
||||
|
||||
const createInternalStartMock = (): InternalSecurityStartMock => {
|
||||
const mock = {
|
||||
authc: {
|
||||
getCurrentUser: jest.fn(),
|
||||
},
|
||||
audit: auditServiceMock.create(),
|
||||
};
|
||||
|
||||
return mock;
|
||||
|
@ -67,6 +80,12 @@ const createRequestHandlerContextMock = () => {
|
|||
authc: {
|
||||
getCurrentUser: jest.fn(),
|
||||
},
|
||||
audit: {
|
||||
logger: {
|
||||
log: jest.fn(),
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
};
|
||||
return mock;
|
||||
};
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"kbn_references": [
|
||||
"@kbn/core-security-server",
|
||||
"@kbn/core-security-server-internal",
|
||||
"@kbn/core-http-server",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -8,11 +8,21 @@
|
|||
|
||||
export type { SecurityServiceSetup, SecurityServiceStart } from './src/contracts';
|
||||
export type { CoreAuthenticationService } from './src/authc';
|
||||
export type { CoreAuditService } from './src/audit';
|
||||
export type {
|
||||
CoreSecurityDelegateContract,
|
||||
AuthenticationServiceContract,
|
||||
AuditServiceContract,
|
||||
} from './src/api_provider';
|
||||
export type {
|
||||
SecurityRequestHandlerContext,
|
||||
AuthcRequestHandlerContext,
|
||||
AuditRequestHandlerContext,
|
||||
} from './src/request_handler_context';
|
||||
export type {
|
||||
AuditEvent,
|
||||
AuditHttp,
|
||||
AuditKibana,
|
||||
AuditRequest,
|
||||
} from './src/audit_logging/audit_events';
|
||||
export type { AuditLogger } from './src/audit_logging/audit_logger';
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { CoreAuditService } from './audit';
|
||||
import type { CoreAuthenticationService } from './authc';
|
||||
|
||||
/**
|
||||
|
@ -16,9 +17,12 @@ import type { CoreAuthenticationService } from './authc';
|
|||
*/
|
||||
export interface CoreSecurityDelegateContract {
|
||||
authc: AuthenticationServiceContract;
|
||||
audit: AuditServiceContract;
|
||||
}
|
||||
|
||||
/**
|
||||
* @public
|
||||
*/
|
||||
export type AuthenticationServiceContract = CoreAuthenticationService;
|
||||
|
||||
export type AuditServiceContract = CoreAuditService;
|
||||
|
|
40
packages/core/security/core-security-server/src/audit.ts
Normal file
40
packages/core/security/core-security-server/src/audit.ts
Normal file
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
|
||||
import type { AuditLogger } from './audit_logging/audit_logger';
|
||||
|
||||
export interface CoreAuditService {
|
||||
/**
|
||||
* Creates an {@link AuditLogger} scoped to the current request.
|
||||
*
|
||||
* This audit logger logs events with all required user and session info and should be used for
|
||||
* all user-initiated actions.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const auditLogger = securitySetup.audit.asScoped(request);
|
||||
* auditLogger.log(event);
|
||||
* ```
|
||||
*/
|
||||
asScoped: (request: KibanaRequest) => AuditLogger;
|
||||
|
||||
/**
|
||||
* {@link AuditLogger} for background tasks only.
|
||||
*
|
||||
* This audit logger logs events without any user or session info and should never be used to log
|
||||
* user-initiated actions.
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* securitySetup.audit.withoutRequest.log(event);
|
||||
* ```
|
||||
*/
|
||||
withoutRequest: AuditLogger;
|
||||
}
|
|
@ -1,11 +1,12 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { LogMeta } from '@kbn/core/server';
|
||||
import type { LogMeta } from '@kbn/logging';
|
||||
|
||||
/**
|
||||
* Audit kibana schema using ECS format
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||
* or more contributor license agreements. Licensed under the Elastic License
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
* 2.0 and the Server Side Public License, v 1; you may not use this file except
|
||||
* in compliance with, at your election, the Elastic License 2.0 or the Server
|
||||
* Side Public License, v 1.
|
||||
*/
|
||||
|
||||
import type { AuditEvent } from './audit_events';
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import type { CoreAuthenticationService } from './authc';
|
||||
import type { CoreSecurityDelegateContract } from './api_provider';
|
||||
|
||||
import type { CoreAuditService } from './audit';
|
||||
/**
|
||||
* Setup contract for Core's security service.
|
||||
*
|
||||
|
@ -33,4 +33,8 @@ export interface SecurityServiceStart {
|
|||
* The {@link CoreAuthenticationService | authentication service}
|
||||
*/
|
||||
authc: CoreAuthenticationService;
|
||||
/**
|
||||
* The {@link CoreAuditService | audit service}
|
||||
*/
|
||||
audit: CoreAuditService;
|
||||
}
|
||||
|
|
|
@ -7,11 +7,17 @@
|
|||
*/
|
||||
|
||||
import type { AuthenticatedUser } from '@kbn/core-security-common';
|
||||
import { AuditLogger } from './audit_logging/audit_logger';
|
||||
|
||||
export interface SecurityRequestHandlerContext {
|
||||
authc: AuthcRequestHandlerContext;
|
||||
audit: AuditRequestHandlerContext;
|
||||
}
|
||||
|
||||
export interface AuthcRequestHandlerContext {
|
||||
getCurrentUser(): AuthenticatedUser | null;
|
||||
}
|
||||
|
||||
export interface AuditRequestHandlerContext {
|
||||
logger: AuditLogger;
|
||||
}
|
||||
|
|
|
@ -16,5 +16,6 @@
|
|||
"kbn_references": [
|
||||
"@kbn/core-security-common",
|
||||
"@kbn/core-http-server",
|
||||
"@kbn/logging",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -122,6 +122,12 @@ export type {
|
|||
SecurityServiceSetup,
|
||||
SecurityServiceStart,
|
||||
CoreAuthenticationService,
|
||||
CoreAuditService,
|
||||
AuditEvent,
|
||||
AuditHttp,
|
||||
AuditKibana,
|
||||
AuditRequest,
|
||||
AuditLogger,
|
||||
} from '@kbn/core-security-server';
|
||||
export type {
|
||||
User,
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import type { KibanaRequest } from '@kbn/core/server';
|
||||
|
||||
import type { AuditLogger } from './audit_logger';
|
||||
import type { AuditLogger } from '@kbn/core-security-server';
|
||||
|
||||
export interface AuditServiceSetup {
|
||||
/**
|
||||
|
|
|
@ -6,5 +6,10 @@
|
|||
*/
|
||||
|
||||
export type { AuditServiceSetup } from './audit_service';
|
||||
export type { AuditEvent, AuditHttp, AuditKibana, AuditRequest } from './audit_events';
|
||||
export type { AuditLogger } from './audit_logger';
|
||||
export type {
|
||||
AuditEvent,
|
||||
AuditHttp,
|
||||
AuditKibana,
|
||||
AuditLogger,
|
||||
AuditRequest,
|
||||
} from '@kbn/core-security-server';
|
||||
|
|
|
@ -14,5 +14,6 @@
|
|||
"@kbn/core",
|
||||
"@kbn/security-plugin-types-common",
|
||||
"@kbn/core-user-profile-server",
|
||||
"@kbn/core-security-server",
|
||||
]
|
||||
}
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
*/
|
||||
|
||||
import { httpServerMock } from '@kbn/core-http-server-mocks';
|
||||
import type { CoreSecurityDelegateContract } from '@kbn/core-security-server';
|
||||
import type { AuditLogger, CoreSecurityDelegateContract } from '@kbn/core-security-server';
|
||||
import type { CoreUserProfileDelegateContract } from '@kbn/core-user-profile-server';
|
||||
|
||||
import { auditServiceMock } from './audit/mocks';
|
||||
import { authenticationServiceMock } from './authentication/authentication_service.mock';
|
||||
import { buildSecurityApi, buildUserProfileApi } from './build_delegate_apis';
|
||||
import { securityMock } from './mocks';
|
||||
|
@ -16,11 +17,13 @@ import { userProfileServiceMock } from './user_profile/user_profile_service.mock
|
|||
|
||||
describe('buildSecurityApi', () => {
|
||||
let authc: ReturnType<typeof authenticationServiceMock.createStart>;
|
||||
let auditService: ReturnType<typeof auditServiceMock.create>;
|
||||
let api: CoreSecurityDelegateContract;
|
||||
|
||||
beforeEach(() => {
|
||||
authc = authenticationServiceMock.createStart();
|
||||
api = buildSecurityApi({ getAuthc: () => authc });
|
||||
auditService = auditServiceMock.create();
|
||||
api = buildSecurityApi({ getAuthc: () => authc, audit: auditService });
|
||||
});
|
||||
|
||||
describe('authc.getCurrentUser', () => {
|
||||
|
@ -43,6 +46,25 @@ describe('buildSecurityApi', () => {
|
|||
expect(currentUser).toBe(delegateReturn);
|
||||
});
|
||||
});
|
||||
|
||||
describe('audit.asScoped', () => {
|
||||
let auditLogger: AuditLogger;
|
||||
it('properly delegates to the service', () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
auditLogger = api.audit.asScoped(request);
|
||||
auditLogger.log({ message: 'an event' });
|
||||
expect(auditService.asScoped).toHaveBeenCalledTimes(1);
|
||||
expect(auditService.asScoped).toHaveBeenCalledWith(request);
|
||||
});
|
||||
|
||||
it('returns the result from the service', async () => {
|
||||
const request = httpServerMock.createKibanaRequest();
|
||||
auditLogger = api.audit.asScoped(request);
|
||||
auditLogger.log({ message: 'an event' });
|
||||
expect(auditService.asScoped(request).log).toHaveBeenCalledTimes(1);
|
||||
expect(auditService.asScoped(request).log).toHaveBeenCalledWith({ message: 'an event' });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('buildUserProfileApi', () => {
|
||||
|
|
|
@ -7,14 +7,17 @@
|
|||
|
||||
import type { CoreSecurityDelegateContract } from '@kbn/core-security-server';
|
||||
import type { CoreUserProfileDelegateContract } from '@kbn/core-user-profile-server';
|
||||
import type { AuditServiceSetup } from '@kbn/security-plugin-types-server';
|
||||
|
||||
import type { InternalAuthenticationServiceStart } from './authentication';
|
||||
import type { UserProfileServiceStartInternal } from './user_profile';
|
||||
|
||||
export const buildSecurityApi = ({
|
||||
getAuthc,
|
||||
audit,
|
||||
}: {
|
||||
getAuthc: () => InternalAuthenticationServiceStart;
|
||||
audit: AuditServiceSetup;
|
||||
}): CoreSecurityDelegateContract => {
|
||||
return {
|
||||
authc: {
|
||||
|
@ -22,6 +25,15 @@ export const buildSecurityApi = ({
|
|||
return getAuthc().getCurrentUser(request);
|
||||
},
|
||||
},
|
||||
audit: {
|
||||
asScoped(request) {
|
||||
return audit.asScoped(request);
|
||||
},
|
||||
withoutRequest: {
|
||||
log: audit.withoutRequest.log,
|
||||
enabled: audit.withoutRequest.enabled,
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -298,6 +298,7 @@ export class SecurityPlugin
|
|||
core.security.registerSecurityDelegate(
|
||||
buildSecurityApi({
|
||||
getAuthc: this.getAuthentication.bind(this),
|
||||
audit: this.auditSetup,
|
||||
})
|
||||
);
|
||||
core.userProfile.registerUserProfileDelegate(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue