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

Add frontend http client #7000

Open
wants to merge 13 commits into
base: master
Choose a base branch
from
17 changes: 17 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"diffEditor.ignoreTrimWhitespace": true,
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnPaste": true,
"editor.formatOnSave": true,
"editor.inlineSuggest.enabled": true,
"editor.insertSpaces": true,
"editor.minimap.enabled": true,
"editor.rulers": [80, 100],
"editor.tabSize": 2,
"editor.trimAutoWhitespace": true,
"editor.wordWrap": "on",
"explorer.confirmDelete": true,
"files.autoSave": "off",
"javascript.updateImportsOnFileMove.enabled": "always",
"typescript.updateImportsOnFileMove.enabled": "always"
}
13 changes: 3 additions & 10 deletions plugins/main/server/controllers/wazuh-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,16 +76,9 @@ export class WazuhApiCtrl {
}
}
}
let token;
if (context.wazuh_core.manageHosts.isEnabledAuthWithRunAs(idHost)) {
token = await context.wazuh.api.client.asCurrentUser.authenticate(
idHost,
);
} else {
token = await context.wazuh.api.client.asInternalUser.authenticate(
idHost,
);
}
const token = await context.wazuh.api.client.asCurrentUser.authenticate(
idHost,
);

let textSecure = '';
if (context.wazuh.server.info.protocol === 'https') {
Expand Down
4 changes: 2 additions & 2 deletions plugins/wazuh-core/common/services/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { cloneDeep } from 'lodash';
import { formatLabelValuePair } from './settings';
import { formatBytes } from './file-size';

export interface ILogger {
export interface Logger {
debug(message: string): void;
info(message: string): void;
warn(message: string): void;
Expand Down Expand Up @@ -180,7 +180,7 @@ export class Configuration implements IConfiguration {
store: IConfigurationStore | null = null;
_settings: Map<string, { [key: string]: TConfigurationSetting }>;
_categories: Map<string, { [key: string]: any }>;
constructor(private logger: ILogger, store: IConfigurationStore) {
constructor(private logger: Logger, store: IConfigurationStore) {
this._settings = new Map();
this._categories = new Map();
this.setStore(store);
Expand Down
26 changes: 25 additions & 1 deletion plugins/wazuh-core/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
} from '../common/constants';
import { DashboardSecurity } from './utils/dashboard-security';
import * as hooks from './hooks';
import { CoreHTTPClient } from './services/http/http-client';

export class WazuhCorePlugin
implements Plugin<WazuhCorePluginSetup, WazuhCorePluginStart>
Expand All @@ -19,12 +20,21 @@ export class WazuhCorePlugin
services: { [key: string]: any } = {};
public async setup(core: CoreSetup): Promise<WazuhCorePluginSetup> {
const noop = () => {};
const logger = {
// Debug logger
const consoleLogger = {
info: console.log,
error: console.error,
debug: console.debug,
warn: console.warn,
};
// No operation logger
const noopLogger = {
Desvelao marked this conversation as resolved.
Show resolved Hide resolved
info: noop,
error: noop,
debug: noop,
warn: noop,
};
const logger = noopLogger;
this._internal.configurationStore = new ConfigurationStore(
logger,
core.http,
Expand All @@ -44,9 +54,22 @@ export class WazuhCorePlugin
this.services.configuration.registerCategory({ ...value, id: key });
});

// Create dashboardSecurity
this.services.dashboardSecurity = new DashboardSecurity(logger, core.http);

// Create http
this.services.http = new CoreHTTPClient(logger, {
getTimeout: async () =>
(await this.services.configuration.get('timeout')) as number,
getURL: (path: string) => core.http.basePath.prepend(path),
getServerAPI: () => 'api-host-id', // TODO: implement
getIndexPatternTitle: async () => 'wazuh-alerts-*', // TODO: implement
http: core.http,
});

// Setup services
await this.services.dashboardSecurity.setup();
await this.services.http.setup();

return {
...this.services,
Expand All @@ -60,6 +83,7 @@ export class WazuhCorePlugin
setCore(core);
setUiSettings(core.uiSettings);

// Start services
await this.services.configuration.start({ http: core.http });

return {
Expand Down
105 changes: 105 additions & 0 deletions plugins/wazuh-core/public/services/http/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# HTTPClient

The `HTTPClient` provides a custom mechanim to do an API request to the backend side.

This defines a request interceptor that disables the requests when `core.http` returns a response with status code 401, avoiding a problem in the login flow (with SAML).

The request interceptor is used in the clients:

- generic
- server

## Generic

This client provides a method to run the request that injects some properties related to an index pattern and selected server API host in the headers of the API request that could be used for some backend endpoints

### Usage

#### Request

```ts
plugins.wazuhCore.http.request('GET', '/api/check-api', {});
```

## Server

This client provides:

- some methods to communicate with the Wazuh server API
- manage authentication with Wazuh server API
- store the login data

### Usage

#### Authentication

```ts
plugins.wazuhCore.http.auth();
```

#### Unauthentication

```ts
plugins.wazuhCore.http.unauth();
```

#### Request

```ts
plugins.wazuhCore.http.request('GET', '/agents', {});
```

#### CSV

```ts
plugins.wazuhCore.http.csv('GET', '/agents', {});
```

#### Check API id

```ts
plugins.wazuhCore.http.checkApiById('api-host-id');
```

#### Check API

```ts
plugins.wazuhCore.http.checkApi(apiHostData);
```

#### Get user data

```ts
plugins.wazuhCore.http.getUserData();
```

The changes in the user data can be retrieved thourgh the `userData$` observable.

```ts
plugins.wazuhCore.http.userData$.subscribe(userData => {
// do something with the data
});
```

### Register interceptor

In each application when this is mounted through the `mount` method, the request interceptor must be registered and when the application is unmounted must be unregistered.

> We should research about the possibility to register/unregister the interceptor once in the `wazuh-core` plugin instead of registering/unregisting in each mount of application.

```ts
// setup lifecycle plugin method

// Register an application
core.application.register({
// rest of registration properties
mount: () => {
// Register the interceptor
plugins.wazuhCore.http.register();
return () => {
// Unregister the interceptor
plugins.wazuhCore.http.unregister();
};
},
});
```
5 changes: 5 additions & 0 deletions plugins/wazuh-core/public/services/http/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export const PLUGIN_PLATFORM_REQUEST_HEADERS = {
'osd-xsrf': 'kibana',
};

export const HTTP_CLIENT_DEFAULT_TIMEOUT = 20000;
127 changes: 127 additions & 0 deletions plugins/wazuh-core/public/services/http/generic-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { PLUGIN_PLATFORM_REQUEST_HEADERS } from './constants';
import { Logger } from '../../../common/services/configuration';
import {
HTTPClientGeneric,
HTTPClientRequestInterceptor,
HTTPVerb,
} from './types';

interface GenericRequestServices {
request: HTTPClientRequestInterceptor['request'];
getURL(path: string): string;
getTimeout(): Promise<number>;
getIndexPatternTitle(): Promise<string>;
getServerAPI(): string;
checkAPIById(apiId: string): Promise<any>;
}

export class GenericRequest implements HTTPClientGeneric {
onErrorInterceptor?: (error: any) => Promise<void>;
constructor(
private logger: Logger,
private services: GenericRequestServices,
) {}
async request(
method: HTTPVerb,
path: string,
payload = null,
returnError = false,
) {
try {
if (!method || !path) {
throw new Error('Missing parameters');
}
const timeout = await this.services.getTimeout();
const requestHeaders = {
...PLUGIN_PLATFORM_REQUEST_HEADERS,
'content-type': 'application/json',
};
const url = this.services.getURL(path);

try {
requestHeaders.pattern = await this.services.getIndexPatternTitle();
} catch (error) {}

try {
requestHeaders.id = this.services.getServerAPI();
} catch (error) {
// Intended
}
var options = {};

if (method === 'GET') {
options = {
method: method,
headers: requestHeaders,
url: url,
timeout: timeout,
};
}
if (method === 'PUT') {
options = {
method: method,
headers: requestHeaders,
data: payload,
url: url,
timeout: timeout,
};
}
if (method === 'POST') {
options = {
method: method,
headers: requestHeaders,
data: payload,
url: url,
timeout: timeout,
};
}
if (method === 'DELETE') {
options = {
method: method,
headers: requestHeaders,
data: payload,
url: url,
timeout: timeout,
};
}

const data = await this.services.request(options);
if (!data) {
throw new Error(`Error doing a request to ${url}, method: ${method}.`);
}

return data;
} catch (error) {
//if the requests fails, we need to check if the API is down
const currentApi = this.services.getServerAPI(); //JSON.parse(AppState.getCurrentAPI() || '{}');
if (currentApi) {
try {
await this.services.checkAPIById(currentApi);
} catch (err) {
// const wzMisc = new WzMisc();
// wzMisc.setApiIsDown(true);
// if (
// ['/settings', '/health-check', '/blank-screen'].every(
// pathname =>
// !NavigationService.getInstance()
// .getPathname()
// .startsWith(pathname),
// )
// ) {
// NavigationService.getInstance().navigate('/health-check');
// }
}
}
// if(this.onErrorInterceptor){
// await this.onErrorInterceptor(error)
// }
if (returnError) return Promise.reject(error);
return (((error || {}).response || {}).data || {}).message || false
? Promise.reject(new Error(error.response.data.message))
: Promise.reject(error || new Error('Server did not respond'));
}
}
setOnErrorInterceptor(onErrorInterceptor: (error: any) => Promise<void>) {
this.onErrorInterceptor = onErrorInterceptor;
}
}
Loading
Loading