Skip to content

Commit

Permalink
Enable data source audit log to file (opensearch-project#2215)
Browse files Browse the repository at this point in the history
Signed-off-by: Kristen Tian <tyarong@amazon.com>

Signed-off-by: Kristen Tian <tyarong@amazon.com>
  • Loading branch information
kristenTian committed Sep 14, 2022
1 parent 3eec6a3 commit 9a40b46
Show file tree
Hide file tree
Showing 5 changed files with 156 additions and 14 deletions.
42 changes: 42 additions & 0 deletions src/plugins/data_source/audit_config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { schema } from '@osd/config-schema';
// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { DateConversion } from '../../../src/core/server/logging/layouts/conversions';

const patternSchema = schema.string({
validate: (string) => {
DateConversion.validate!(string);
},
});

const patternLayout = schema.object({
highlight: schema.maybe(schema.boolean()),
kind: schema.literal('pattern'),
pattern: schema.maybe(patternSchema),
});

const jsonLayout = schema.object({
kind: schema.literal('json'),
});

export const fileAppenderSchema = schema.object(
{
kind: schema.literal('file'),
layout: schema.oneOf([patternLayout, jsonLayout]),
path: schema.string(),
},
{
defaultValue: {
kind: 'file',
layout: {
kind: 'pattern',
highlight: true,
},
path: '/tmp/opensearch-dashboards-data-source-audit.log',
},
}
);
5 changes: 5 additions & 0 deletions src/plugins/data_source/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

import { schema, TypeOf } from '@osd/config-schema';
import { fileAppenderSchema } from './audit_config';

const KEY_NAME_MIN_LENGTH: number = 1;
const KEY_NAME_MAX_LENGTH: number = 100;
Expand Down Expand Up @@ -32,6 +33,10 @@ export const configSchema = schema.object({
clientPool: schema.object({
size: schema.number({ defaultValue: 5 }),
}),
audit: schema.object({
enabled: schema.boolean({ defaultValue: true }),
appender: fileAppenderSchema,
}),
});

export type DataSourcePluginConfigType = TypeOf<typeof configSchema>;
23 changes: 23 additions & 0 deletions src/plugins/data_source/server/audit/logging_auditor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { AuditableEvent, Auditor, Logger, OpenSearchDashboardsRequest } from 'src/core/server';

export class LoggingAuditor implements Auditor {
constructor(
private readonly request: OpenSearchDashboardsRequest,
private readonly logger: Logger
) {}

public withAuditScope(name: string) {}

public add(event: AuditableEvent) {
const message = event.message;
const meta = {
type: event.type,
};
this.logger.info(message, meta);
}
}
9 changes: 7 additions & 2 deletions src/plugins/data_source/server/data_source_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,14 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { Logger, OpenSearchClient, SavedObjectsClientContract } from '../../../../src/core/server';
import {
Auditor,
Logger,
OpenSearchClient,
SavedObjectsClientContract,
} from '../../../../src/core/server';
import { DataSourcePluginConfigType } from '../config';
import { OpenSearchClientPool, configureClient } from './client';
import { configureClient, OpenSearchClientPool } from './client';
import { CryptographyClient } from './cryptography';
export interface DataSourceServiceSetup {
getDataSourceClient: (
Expand Down
91 changes: 79 additions & 12 deletions src/plugins/data_source/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,39 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { first } from 'rxjs/operators';
import { OpenSearchClientError } from '@opensearch-project/opensearch/lib/errors';
import { dataSource, credential, CredentialSavedObjectsClientWrapper } from './saved_objects';
import { DataSourcePluginConfigType } from '../config';
import { Observable } from 'rxjs';
import { first, map } from 'rxjs/operators';
import {
PluginInitializerContext,
Auditor,
AuditorFactory,
CoreSetup,
CoreStart,
Plugin,
Logger,
IContextProvider,
Logger,
LoggerContextConfigInput,
OpenSearchDashboardsRequest,
Plugin,
PluginInitializerContext,
RequestHandler,
} from '../../../../src/core/server';
import { DataSourcePluginConfigType } from '../config';
import { LoggingAuditor } from './audit/logging_auditor';
import { CryptographyClient } from './cryptography';
import { DataSourceService, DataSourceServiceSetup } from './data_source_service';
import { credential, CredentialSavedObjectsClientWrapper, dataSource } from './saved_objects';
import { DataSourcePluginSetup, DataSourcePluginStart } from './types';
import { CryptographyClient } from './cryptography';

// eslint-disable-next-line @osd/eslint/no-restricted-paths
import { ensureRawRequest } from '../../../../src/core/server/http/router';
export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourcePluginStart> {
private readonly logger: Logger;
private readonly dataSourceService: DataSourceService;
private readonly config$: Observable<DataSourcePluginConfigType>;

constructor(private initializerContext: PluginInitializerContext<DataSourcePluginConfigType>) {
this.logger = this.initializerContext.logger.get();
this.dataSourceService = new DataSourceService(this.logger.get('data-source-service'));
this.config$ = this.initializerContext.config.create<DataSourcePluginConfigType>();
}

public async setup(core: CoreSetup) {
Expand All @@ -38,8 +47,7 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
// Register data source saved object type
core.savedObjects.registerType(dataSource);

const config$ = this.initializerContext.config.create<DataSourcePluginConfigType>();
const config: DataSourcePluginConfigType = await config$.pipe(first()).toPromise();
const config: DataSourcePluginConfigType = await this.config$.pipe(first()).toPromise();

// Fetch configs used to create credential saved objects client wrapper
const { wrappingKeyName, wrappingKeyNamespace, wrappingKey } = config.encryption;
Expand All @@ -63,17 +71,48 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc

const dataSourceService: DataSourceServiceSetup = await this.dataSourceService.setup(config);

core.logging.configure(
this.config$.pipe<LoggerContextConfigInput>(
map((dataSourceConfig) => ({
appenders: {
auditTrailAppender: dataSourceConfig.audit.appender,
},
loggers: [
{
context: 'audit',
level: dataSourceConfig.audit.enabled ? 'info' : 'off',
appenders: ['auditTrailAppender'],
},
],
}))
)
);

const auditorFactory: AuditorFactory = {
asScoped: (request: OpenSearchDashboardsRequest) => {
return new LoggingAuditor(request, this.logger.get('audit'));
},
};
core.auditTrail.register(auditorFactory);
const auditTrailPromise = core.getStartServices().then(([coreStart]) => coreStart.auditTrail);

// Register data source plugin context to route handler context
core.http.registerRouteHandlerContext(
'dataSource',
this.createDataSourceRouteHandlerContext(dataSourceService, cryptographyClient, this.logger)
this.createDataSourceRouteHandlerContext(
dataSourceService,
cryptographyClient,
this.logger,
auditTrailPromise
)
);

return {};
}

public start(core: CoreStart) {
this.logger.debug('data_source: Started');

return {};
}

Expand All @@ -84,13 +123,17 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
private createDataSourceRouteHandlerContext = (
dataSourceService: DataSourceServiceSetup,
cryptographyClient: CryptographyClient,
logger: Logger
logger: Logger,
auditTrailPromise: Promise<AuditorFactory>
): IContextProvider<RequestHandler<unknown, unknown, unknown>, 'dataSource'> => {
return (context, req) => {
return {
opensearch: {
getClient: (dataSourceId: string) => {
try {
const auditor = auditTrailPromise.then((auditTrail) => auditTrail.asScoped(req));
this.logAuditMessage(auditor, dataSourceId, req);

return dataSourceService.getDataSourceClient(
dataSourceId,
context.core.savedObjects.client,
Expand All @@ -107,4 +150,28 @@ export class DataSourcePlugin implements Plugin<DataSourcePluginSetup, DataSourc
};
};
};

private async logAuditMessage(
auditorPromise: Promise<Auditor>,
dataSourceId: string,
request: OpenSearchDashboardsRequest
) {
const auditor = await auditorPromise;
const auditMessage = this.getAuditMessage(request, dataSourceId);

auditor.add({
message: auditMessage,
type: 'opensearch.dataSourceClient.fetchClient',
});
}

private getAuditMessage(request: OpenSearchDashboardsRequest, dataSourceId: string) {
const rawRequest = ensureRawRequest(request);
const remoteAddress = rawRequest?.info?.remoteAddress;
const xForwardFor = request.headers['x-forwarded-for'];

return xForwardFor
? `${remoteAddress} attempted accessing through ${xForwardFor} on data source: ${dataSourceId}`
: `${remoteAddress} attempted accessing on data source: ${dataSourceId}`;
}
}

0 comments on commit 9a40b46

Please sign in to comment.