Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add auditor to coreServices #26372

Merged
merged 10 commits into from
Jan 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-backend-defaults.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/backend-defaults': minor
---

This change introduces the `auditor` service implementation details.
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-backend-plugin-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/backend-plugin-api': minor
---

This change introduces the `auditor` service definition.
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-backend-test-utils.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/backend-test-utils': minor
---

This change introduces mocks for the `auditor` service.
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-catalog-backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend': minor
---

This change integrates the `auditor` service into the Catalog plugin.
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-scaffolder-backend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend': minor
---

This change integrates the `auditor` service into the Scaffolder plugin.
5 changes: 5 additions & 0 deletions .changeset/olive-boxes-hide-scaffolder-node.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-node': minor
---

This change introduces an optional `taskId` property to `TaskContext`.
87 changes: 87 additions & 0 deletions docs/backend-system/core-services/auditor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
---
id: auditor
title: Auditor Service
sidebar_label: Auditor
description: Documentation for the Auditor service
---

## Overview

This document describes the Auditor Service, a software service designed to record and report on security-relevant events within an application. This service utilizes the `winston` library for logging and provides a flexible way to capture and format audit events.
Rugvip marked this conversation as resolved.
Show resolved Hide resolved

## Key Features

- Provides a standardized way to capture security events.
- Allows categorization of events by severity level.
- Supports detailed metadata for each event.
- Offers success/failure reporting for events.
- Integrates with authentication and plugin services for enhanced context.
- Uses `winston` for flexible log formatting and transport.
- Provides a service factory for easy integration with Backstage plugins.
- Supports configurable log transports (console, file).

## How it Works

The Auditor Service defines a core class, `Auditor`, which implements the `AuditorService` interface. This class uses `winston` to log audit events with varying levels of severity and associated metadata. It also integrates with authentication and plugin services to capture actor details and plugin context.

The `auditorServiceFactory` creates an `Auditor` instance for the root context and provides a factory function for creating child loggers for individual plugins. This allows each plugin to have its own logger with inherited and additional metadata.

## Usage Guidance

The Auditor Service is designed for recording security-relevant events that require special attention or are subject to compliance regulations. These events often involve actions like:

- User session management
- Data access and modification
- System configuration changes

For general application logging that is not security-critical, you should use the standard `LoggerService` provided by Backstage. This helps to keep your audit logs focused and relevant.

## Using the Service

The Auditor Service can be accessed via dependency injection in your Backstage plugin. Here's an example of how to access the service and create an audit event within an Express route handler:

```typescript
export async function createRouter(
options: RouterOptions,
): Promise<express.Router> {
const { auditor } = options;

const router = Router();
router.use(express.json());

router.post('/my-endpoint', async (req, res) => {
const auditorEvent = await auditor.createEvent({
eventId: 'my-endpoint-call',
request: req,
meta: {
// ... metadata about the request
},
});

try {
// ... process the request

await auditorEvent.success();
res.status(200).json({ message: 'Succeeded!' });
} catch (error) {
await auditorEvent.fail({ error });
res.status(500).json({ message: 'Failed!' });
throw error;
}
});

return router;
}
```

In this example, an audit event is created for each request to `/my-endpoint`. The `success` or `fail` methods are called based on the outcome of processing the request.

## Naming Conventions

When defining `eventId` and `subEventId` for your audit events, follow these guidelines:

- Use kebab-case (e.g., `user-login`, `file-download`, `fetch`, `entity-create`, `entity-update`).
- The `eventId` represents a logical group of similar events or operations. For example, "fetch" could be used as an `eventId` encompassing various fetch methods like `by-id` or `by-location`.
- Use `subEventId` to further categorize events within a logical group. For example, if the `eventId` is "fetch", the `subEventId` could be "by-id" or "by-location" to specify the method used for fetching.
- Avoid redundant prefixes related to the plugin ID, as that context is already provided.
- Choose names that clearly and concisely describe the event being audited.
4 changes: 4 additions & 0 deletions packages/backend-defaults/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"license": "Apache-2.0",
"exports": {
".": "./src/index.ts",
"./auditor": "./src/entrypoints/auditor/index.ts",
"./auth": "./src/entrypoints/auth/index.ts",
"./cache": "./src/entrypoints/cache/index.ts",
"./database": "./src/entrypoints/database/index.ts",
Expand All @@ -44,6 +45,9 @@
"types": "src/index.ts",
"typesVersions": {
"*": {
"auditor": [
"src/entrypoints/auditor/index.ts"
],
"auth": [
"src/entrypoints/auth/index.ts"
],
Expand Down
121 changes: 121 additions & 0 deletions packages/backend-defaults/report-auditor.api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
## API Report File for "@backstage/backend-defaults"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts
import { AuditorService } from '@backstage/backend-plugin-api';
import type { AuditorServiceCreateEventOptions } from '@backstage/backend-plugin-api';
import type { AuditorServiceEvent } from '@backstage/backend-plugin-api';
import type { AuditorServiceEventSeverityLevel } from '@backstage/backend-plugin-api';
import type { AuthService } from '@backstage/backend-plugin-api';
import type { Format } from 'logform';
import type { HttpAuthService } from '@backstage/backend-plugin-api';
import type { JsonObject } from '@backstage/types';
import type { PluginMetadataService } from '@backstage/backend-plugin-api';
import type { Request as Request_2 } from 'express';
import type { RootLoggerService } from '@backstage/backend-plugin-api';
import { ServiceFactory } from '@backstage/backend-plugin-api';
import * as winston from 'winston';

// @public
export type AuditorEvent = [
eventId: string,
meta: {
plugin: string;
severityLevel: AuditorServiceEventSeverityLevel;
actor: AuditorEventActorDetails;
meta?: JsonObject;
request?: AuditorEventRequest;
} & AuditorEventStatus,
];

// @public (undocumented)
export type AuditorEventActorDetails = {
actorId?: string;
ip?: string;
hostname?: string;
userAgent?: string;
};

// @public
export type AuditorEventOptions<TMeta extends JsonObject> = {
eventId: string;
severityLevel?: AuditorServiceEventSeverityLevel;
request?: Request_2<any, any, any, any, any>;
meta?: TMeta;
} & AuditorEventStatus;

// @public (undocumented)
export type AuditorEventRequest = {
url: string;
method: string;
};

// @public (undocumented)
export type AuditorEventStatus =
| {
status: 'initiated';
}
| {
status: 'succeeded';
}
| {
status: 'failed';
error: string;
};

// @public
export const auditorFieldFormat: Format;

// @public
export const auditorServiceFactory: ServiceFactory<
AuditorService,
'plugin',
'singleton'
>;

// @public
export class DefaultAuditorService implements AuditorService {
static create(
impl: DefaultRootAuditorService,
deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
},
): DefaultAuditorService;
// (undocumented)
createEvent(
options: AuditorServiceCreateEventOptions,
): Promise<AuditorServiceEvent>;
}

// @public (undocumented)
export const defaultFormatter: Format;

// @public (undocumented)
export class DefaultRootAuditorService {
static create(options?: RootAuditorOptions): DefaultRootAuditorService;
// (undocumented)
forPlugin(deps: {
auth: AuthService;
httpAuth: HttpAuthService;
plugin: PluginMetadataService;
}): AuditorService;
// (undocumented)
log(auditorEvent: AuditorEvent): Promise<void>;
}

// @public
export type RootAuditorOptions =
| {
meta?: JsonObject;
format?: Format;
transports?: winston.transport[];
}
| {
rootLogger: RootLoggerService;
};

// (No @packageDocumentation comment for this package)
```
2 changes: 2 additions & 0 deletions packages/backend-defaults/src/CreateBackend.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/

import { Backend, createSpecializedBackend } from '@backstage/backend-app-api';
import { auditorServiceFactory } from '@backstage/backend-defaults/auditor';
import { authServiceFactory } from '@backstage/backend-defaults/auth';
import { cacheServiceFactory } from '@backstage/backend-defaults/cache';
import { databaseServiceFactory } from '@backstage/backend-defaults/database';
Expand All @@ -36,6 +37,7 @@ import { userInfoServiceFactory } from '@backstage/backend-defaults/userInfo';
import { eventsServiceFactory } from '@backstage/plugin-events-node';

export const defaultServiceFactories = [
auditorServiceFactory,
authServiceFactory,
cacheServiceFactory,
rootConfigServiceFactory,
Expand Down
Loading
Loading