@cccsaurora/howler-ui 2.15.0-dev.296 → 2.15.0-dev.307

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  import { hdelete, hpost, joinUri } from '@cccsaurora/howler-ui/api';
2
2
  import { uri as parentUri } from '@cccsaurora/howler-ui/api/auth';
3
3
  export const uri = (apiKeyName) => {
4
- return apiKeyName ? joinUri(joinUri(parentUri(), '@cccsaurora/howler-ui/apikey'), apiKeyName) : joinUri(parentUri(), '@cccsaurora/howler-ui/apikey');
4
+ return apiKeyName ? joinUri(joinUri(parentUri(), 'apikey'), apiKeyName) : joinUri(parentUri(), 'apikey');
5
5
  };
6
6
  export const post = (apiKeyName, priv, expiryDate) => {
7
7
  return hpost(uri(), { name: apiKeyName, priv, expiry_date: expiryDate });
@@ -61,6 +61,6 @@ const ApiKeyDrawer = ({ onCreated }) => {
61
61
  useEffect(() => {
62
62
  setExpiryDate(maxDate);
63
63
  }, [maxDate]);
64
- return (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "column", spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { sx: { maxWidth: '500px' }, children: _jsx(Trans, { i18nKey: "app.drawer.user.apikey.description" }) }), amount && unit && dayjs.duration(amount, unit).asSeconds() < dayjs.duration(7, 'days').asSeconds() && (_jsxs(Alert, { severity: "warning", variant: "outlined", sx: { maxWidth: '500px' }, children: [_jsx(AlertTitle, { children: t('app.drawer.user.apikey.limit.title') }), t('app.drawer.user.apikey.limit.description'), " (", maxDate?.format('MMM D HH:mm:ss'), ")"] })), _jsx(TextField, { label: t('app.drawer.user.apikey.field.name'), required: true, fullWidth: true, value: keyName, onChange: onChange }), _jsxs(FormControl, { required: true, children: [_jsx(FormLabel, { component: "legend", children: t('app.drawer.user.apikey.permissions') }), _jsxs(FormGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, children: [_jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('R') }), label: t('@cccsaurora/howler-ui/apikey.read') }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('W') }), label: t('@cccsaurora/howler-ui/apikey.write') }), _jsx(FormControlLabel, { disabled: privs.includes('E'), control: _jsx(Checkbox, { onChange: updatePrivs('I') }), label: t('@cccsaurora/howler-ui/apikey.impersonate') }), config.configuration.auth.allow_extended_apikeys && (_jsx(FormControlLabel, { disabled: privs.includes('I'), control: _jsx(Checkbox, { onChange: updatePrivs('E') }), label: t('@cccsaurora/howler-ui/apikey.extended') }))] })] }), _jsxs(FormControl, { required: !!maxDate, children: [_jsx(FormLabel, { children: t('app.drawer.user.apikey.expiry.date') }), _jsx(StaticDateTimePicker, { orientation: "landscape", ampm: false, value: expiryDate, onChange: newValue => setExpiryDate(newValue), disablePast: true, maxDate: maxDate, maxTime: maxDate, sx: { backgroundColor: 'transparent', '& > div:first-of-type': { maxWidth: '300px' } } })] }), _jsx(Button, { onClick: onSubmit, disabled: !keyName || (!privs.includes('R') && !privs.includes('W')) || (maxDate && !expiryDate), variant: "outlined", children: _jsx(Trans, { i18nKey: "button.create" }) }), createdKey && (_jsxs(_Fragment, { children: [_jsx(Divider, { orientation: "horizontal" }), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "stretch", children: [_jsx(TextField, { size: "small", value: createdKey, inputProps: { readOnly: true }, fullWidth: true }), _jsx(Button, { variant: "outlined", onClick: onCopy, disabled: !createdKey, children: _jsx(Trans, { i18nKey: "button.copy" }) })] })] }))] }) }));
64
+ return (_jsx(LocalizationProvider, { dateAdapter: AdapterDayjs, children: _jsxs(Stack, { direction: "column", spacing: 2, sx: { mt: 2 }, children: [_jsx(Typography, { sx: { maxWidth: '500px' }, children: _jsx(Trans, { i18nKey: "app.drawer.user.apikey.description" }) }), amount && unit && dayjs.duration(amount, unit).asSeconds() < dayjs.duration(7, 'days').asSeconds() && (_jsxs(Alert, { severity: "warning", variant: "outlined", sx: { maxWidth: '500px' }, children: [_jsx(AlertTitle, { children: t('app.drawer.user.apikey.limit.title') }), t('app.drawer.user.apikey.limit.description'), " (", maxDate?.format('MMM D HH:mm:ss'), ")"] })), _jsx(TextField, { label: t('app.drawer.user.apikey.field.name'), required: true, fullWidth: true, value: keyName, onChange: onChange }), _jsxs(FormControl, { required: true, children: [_jsx(FormLabel, { component: "legend", children: t('app.drawer.user.apikey.permissions') }), _jsxs(FormGroup, { sx: { display: 'grid', gridTemplateColumns: '1fr 1fr' }, children: [_jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('R') }), label: t('apikey.read') }), _jsx(FormControlLabel, { control: _jsx(Checkbox, { onChange: updatePrivs('W') }), label: t('apikey.write') }), _jsx(FormControlLabel, { disabled: privs.includes('E'), control: _jsx(Checkbox, { onChange: updatePrivs('I') }), label: t('apikey.impersonate') }), config.configuration.auth.allow_extended_apikeys && (_jsx(FormControlLabel, { disabled: privs.includes('I'), control: _jsx(Checkbox, { onChange: updatePrivs('E') }), label: t('apikey.extended') }))] })] }), _jsxs(FormControl, { required: !!maxDate, children: [_jsx(FormLabel, { children: t('app.drawer.user.apikey.expiry.date') }), _jsx(StaticDateTimePicker, { orientation: "landscape", ampm: false, value: expiryDate, onChange: newValue => setExpiryDate(newValue), disablePast: true, maxDate: maxDate, maxTime: maxDate, sx: { backgroundColor: 'transparent', '& > div:first-of-type': { maxWidth: '300px' } } })] }), _jsx(Button, { onClick: onSubmit, disabled: !keyName || (!privs.includes('R') && !privs.includes('W')) || (maxDate && !expiryDate), variant: "outlined", children: _jsx(Trans, { i18nKey: "button.create" }) }), createdKey && (_jsxs(_Fragment, { children: [_jsx(Divider, { orientation: "horizontal" }), _jsxs(Stack, { direction: "row", spacing: 1, alignItems: "stretch", children: [_jsx(TextField, { size: "small", value: createdKey, inputProps: { readOnly: true }, fullWidth: true }), _jsx(Button, { variant: "outlined", onClick: onCopy, disabled: !createdKey, children: _jsx(Trans, { i18nKey: "button.copy" }) })] })] }))] }) }));
65
65
  };
66
66
  export default ApiKeyDrawer;
@@ -9,12 +9,12 @@ import ViewProvider, { ViewContext } from './ViewProvider';
9
9
  let mockUser = {
10
10
  favourite_views: ['favourited_view_id']
11
11
  };
12
- vi.mock('@cccsaurora/howler-ui/api', { spy: true });
12
+ vi.mock('api', { spy: true });
13
13
  vi.mock('react-router-dom', () => ({
14
14
  useLocation: vi.fn(() => ({ pathname: '/views/searched_view_id' })),
15
15
  useParams: vi.fn(() => ({ id: 'searched_view_id' }))
16
16
  }));
17
- vi.mock('@cccsaurora/howler-ui/commons/components/app/hooks', () => ({
17
+ vi.mock('commons/components/app/hooks', () => ({
18
18
  useAppUser: () => ({
19
19
  user: mockUser,
20
20
  setUser: _user => (mockUser = _user)
@@ -22,7 +22,7 @@ const useMyUserFunctions = () => {
22
22
  return {
23
23
  editName: useCallback(async (user, name) => {
24
24
  await dispatchApi(api.user.put(user.username, { name }), { throwError: true, showError: true });
25
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.name.updated'));
25
+ showSuccessMessage(t('api.user.name.updated'));
26
26
  return {
27
27
  ...user,
28
28
  name
@@ -32,7 +32,7 @@ const useMyUserFunctions = () => {
32
32
  // eslint-disable-next-line @typescript-eslint/naming-convention
33
33
  const api_quota = parseInt(quota);
34
34
  await dispatchApi(api.user.put(user.username, { api_quota }), { throwError: true });
35
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.quota.updated'));
35
+ showSuccessMessage(t('api.user.quota.updated'));
36
36
  return {
37
37
  ...user,
38
38
  api_quota
@@ -54,7 +54,7 @@ const useMyUserFunctions = () => {
54
54
  throwError: true,
55
55
  showError: true
56
56
  });
57
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.role.updated'));
57
+ showSuccessMessage(t('api.user.role.updated'));
58
58
  return {
59
59
  ...user,
60
60
  roles: newRoles
@@ -68,7 +68,7 @@ const useMyUserFunctions = () => {
68
68
  ...currentUser,
69
69
  apikeys: [...currentUser.apikeys, [newKeyName, privs, expiryDate]]
70
70
  });
71
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.apikey.updated'));
71
+ showSuccessMessage(t('api.user.apikey.updated'));
72
72
  } }))
73
73
  });
74
74
  }, [currentUser, drawer, setUser, showSuccessMessage, t]),
@@ -78,7 +78,7 @@ const useMyUserFunctions = () => {
78
78
  throwError: true,
79
79
  showError: true
80
80
  });
81
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.role.updated'));
81
+ showSuccessMessage(t('api.user.role.updated'));
82
82
  return {
83
83
  ...user,
84
84
  roles: newRoles
@@ -89,7 +89,7 @@ const useMyUserFunctions = () => {
89
89
  showModal(_jsx(ConfirmDeleteModal, { onConfirm: res }));
90
90
  });
91
91
  await dispatchApi(api.auth.apikey.del(apiKey[0]), { throwError: true });
92
- showSuccessMessage(t('@cccsaurora/howler-ui/api.user.apikey.removed'));
92
+ showSuccessMessage(t('api.user.apikey.removed'));
93
93
  return {
94
94
  ...user,
95
95
  apikeys: user.apikeys.filter(([name, _]) => name !== apiKey[0])
@@ -8,7 +8,7 @@ import { sanitizeLuceneQuery } from '@cccsaurora/howler-ui/utils/stringUtils';
8
8
  import AnalyticHitComments from './AnalyticHitComments';
9
9
  // Mock the API
10
10
  const mockApiSearchHitPost = vi.fn();
11
- vi.mock('@cccsaurora/howler-ui/api', () => ({
11
+ vi.mock('api', () => ({
12
12
  default: {
13
13
  search: {
14
14
  hit: {
@@ -24,7 +24,7 @@ Object.defineProperty(window, 'localStorage', {
24
24
  writable: true
25
25
  });
26
26
  // Mock hooks
27
- vi.mock('@cccsaurora/howler-ui/components/hooks/useMyLocalStorage', () => ({
27
+ vi.mock('components/hooks/useMyLocalStorage', () => ({
28
28
  useMyLocalStorageItem: (key, defaultValue) => {
29
29
  const storageKey = `${MY_LOCAL_STORAGE_PREFIX}.${key}`;
30
30
  const storedValue = mockLocalStorage.getItem(storageKey);
@@ -32,7 +32,7 @@ vi.mock('@cccsaurora/howler-ui/components/hooks/useMyLocalStorage', () => ({
32
32
  return [value, vi.fn()];
33
33
  }
34
34
  }));
35
- vi.mock('@cccsaurora/howler-ui/components/hooks/useMyUserList', () => ({
35
+ vi.mock('components/hooks/useMyUserList', () => ({
36
36
  default: (userIds) => {
37
37
  const userMap = {};
38
38
  Array.from(userIds).forEach(id => {
@@ -42,7 +42,7 @@ vi.mock('@cccsaurora/howler-ui/components/hooks/useMyUserList', () => ({
42
42
  }
43
43
  }));
44
44
  // Mock Comment component
45
- vi.mock('@cccsaurora/howler-ui/components/elements/Comment', () => ({
45
+ vi.mock('components/elements/Comment', () => ({
46
46
  default: ({ comment, onClick, extra }) => (_jsxs("div", { id: `comment-${comment.id}`, onClick: onClick, children: [_jsxs("span", { id: `comment-user-${comment.id}`, children: ['Comment by ', comment.user] }), _jsx("span", { id: `comment-text-${comment.id}`, children: comment.text }), extra] }))
47
47
  }));
48
48
  // Mock MUI components
@@ -53,7 +53,7 @@ vi.mock('@mui/material', () => ({
53
53
  Stack: ({ children, ...props }) => (_jsx("div", { id: "stack", ...omit(props, ['flexItem', 'sx']), children: children }))
54
54
  }));
55
55
  // Mock utils
56
- vi.mock('@cccsaurora/howler-ui/utils/utils', () => ({
56
+ vi.mock('utils/utils', () => ({
57
57
  compareTimestamp: (a, b) => new Date(b).getTime() - new Date(a).getTime()
58
58
  }));
59
59
  // Mock react-router-dom
@@ -11,10 +11,10 @@ import { useEffect, useState } from 'react';
11
11
  import { Trans, useTranslation } from 'react-i18next';
12
12
  import { Fragment } from 'react/jsx-runtime';
13
13
  const APIKEY_LABELS = {
14
- R: '@cccsaurora/howler-ui/apikey.read',
15
- W: '@cccsaurora/howler-ui/apikey.write',
16
- E: '@cccsaurora/howler-ui/apikey.extended',
17
- I: '@cccsaurora/howler-ui/apikey.impersonate'
14
+ R: 'apikey.read',
15
+ W: 'apikey.write',
16
+ E: 'apikey.extended',
17
+ I: 'apikey.impersonate'
18
18
  };
19
19
  const ApiDocumentation = () => {
20
20
  const { t } = useTranslation();
@@ -1 +1 @@
1
- export default "# Howler Client Documentation\n\nThis documentation will outline how to interact with the howler API using the howler client in both Java and python development environments. We will outline the basic process of creating a new hit in each environment as well as searching howler for hits matching your query.\n\n## Getting started\n\n### Installation\n\nIn order to use the howler client, you need to list it as a dependency in your project.\n\n#### **Python**\n\nSimply install through pip:\n\n```bash\npip install howler-client\n```\n\nYou can also add it to your requirements.txt, or whatever dependency management system you use.\n\n### Authentication\n\nAs outlined in the [Authentication Documentation](/help/auth), there's a number of ways users can choose to authenticate. In order to interface with the howler client, however, the suggested flow is to use an API key. So before we start, let's generate a key.\n\n1. Open the Howler UI you'd like to interface with.\n2. Log in, then click your profile in the top right.\n3. Under user menu, click Settings.\n4. Under User Security, press the (+) icon on the API Keys row.\n5. Name your key, and give it the requisite permissions.\n6. Press Create, and copy the supplied string somewhere safe. **You will not see this string again.**\n\nThis API Key will be supplied to your code later on.\n\n## Python Client\n\nIn order to connect with howler using the python client, there is a fairly simple process to follow:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtain this from the user settings page of the Howler UI\nAPIKEY = '@cccsaurora/howler-ui/apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\n```alert\nYou can skip generating an API Key and providing it if you're executing this code within HOGWARTS (i.e., on jupyterhub or airflow). OBO will handle authentication for you!\n```\n\nThat's it! You can now use the `howler` object to interact with the server. So what does that actually look like?\n\n### Creating hits in Python\n\nFor the python client, you can create hits using either the `howler.hit.create` or `howler.hit.create_from_map` functions.\n\n#### `create`\n\nThis function takes in a single argument - either a single hit, or a list of them, conforming to the [Howler Schema](/help/hit?tab=schema). Here is a simple example:\n\n```python\n# Some bogus data in the Howler Schema format\nexample_hit = {\n \"howler\": {\n \"analytic\": \"example\",\n \"score\": 10.0\n },\n \"event\": {\n \"reason\": \"Example hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nYou can also ingest data in a flat format:\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: The name of the analytic creating the hit\n- `map`: A mapping between the raw data you have and the howler schema\n - The format is a dictionary where the keys are the flattened path of the raw data, and the values are a list of flattened paths for Howler's fields where the data will be copied into.\n- `documents`: The raw data you want to add to howler\n\nHere is a simple example:\n\n```python\n# The mapping from our data to howler's schema\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Some bogus data in a custom format we want to add to howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Note that the third argument is of type list!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Querying Hits\n\nQuerying hits using the howler python client is done using the `howler.search.hit` function. It has a number of required and optional arguments:\n\n- Required:\n - `query`: lucene query (string)\n- Optional:\n - `filters`: Additional lucene queries used to filter the data (list of strings)\n - `fl`: List of fields to return (comma separated string of fields)\n - `offset`: Offset at which the query items should start (integer)\n - `rows`: Number of records to return (integer)\n - `sort`: Field used for sorting with direction (string: ex. 'id desc')\n - `timeout`: Max amount of milliseconds the query will run (integer)\n - `use_archive`: Also query the archive\n - `track_total_hits`: Number of hits to track (default: 10k)\n\nHere are some example queries:\n\n```python\n# Search for all hits created by assemblyline, show the first 50, and return only their ids\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Search for all resolved hits created in the last five days, returning their id and the analytic that created them. Show only ten, offset by 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Search for all hits, timeout if the query takes more than 100ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100, use_archive=True)\n```\n\n### Updating Hits\n\nIn order to update hits, there are a number of supported functions:\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nIf you want to update a hit in a transactional way, you can use the following code:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nThe following operations can be run to update a hit.\n\n**List Operations:**\n\n- `UPDATE_APPEND`: Used to append a value to a given list\n- `UPDATE_APPEND_IF_MISSING`: Used to append a value to a given list if the value isn't already in the list\n- `UPDATE_REMOVE`: Will remove a given value from a list\n\n**Numeric Operations:**\n\n- `UPDATE_DEC`: Decrement a numeric value by the specified amount\n- `UPDATE_INC`: Increment a numeric value by the specified amount\n- `UPDATE_MAX`: Will set a numeric value to the maximum of the existing value and the specified value\n- `UPDATE_MIN`: Will set a numeric value to the minimum of the existing value and the specified value\n\n**Multipurpose Operations:**\n\n- `UPDATE_SET`: Set a field's value to the given value\n- `UPDATE_DELETE`: Will delete a given field's value\n\n#### `update_by_query()`\n\nThis function allows you to update a large number of hits by a query:\n\n```python\nclient.hit.update_by_query(f'howler.analytic:\"Example Alert\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nThe same operations as in `update()` can be used.\n\n### `overwrite()`\n\nThis function allows you to directly overwrite a hit with a partial hit object. This is the most easy to use, but loses some of the validation and additional processing of the update functions.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\": \"127.0.0.1\", \"destination.ip\": \"8.8.8.8\"})\n```\n"
1
+ export default "# Howler Client Documentation\n\nThis documentation will outline how to interact with the howler API using the howler client in both Java and python development environments. We will outline the basic process of creating a new hit in each environment as well as searching howler for hits matching your query.\n\n## Getting started\n\n### Installation\n\nIn order to use the howler client, you need to list it as a dependency in your project.\n\n#### **Python**\n\nSimply install through pip:\n\n```bash\npip install howler-client\n```\n\nYou can also add it to your requirements.txt, or whatever dependency management system you use.\n\n### Authentication\n\nAs outlined in the [Authentication Documentation](/help/auth), there's a number of ways users can choose to authenticate. In order to interface with the howler client, however, the suggested flow is to use an API key. So before we start, let's generate a key.\n\n1. Open the Howler UI you'd like to interface with.\n2. Log in, then click your profile in the top right.\n3. Under user menu, click Settings.\n4. Under User Security, press the (+) icon on the API Keys row.\n5. Name your key, and give it the requisite permissions.\n6. Press Create, and copy the supplied string somewhere safe. **You will not see this string again.**\n\nThis API Key will be supplied to your code later on.\n\n## Python Client\n\nIn order to connect with howler using the python client, there is a fairly simple process to follow:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtain this from the user settings page of the Howler UI\nAPIKEY = 'apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\n```alert\nYou can skip generating an API Key and providing it if you're executing this code within HOGWARTS (i.e., on jupyterhub or airflow). OBO will handle authentication for you!\n```\n\nThat's it! You can now use the `howler` object to interact with the server. So what does that actually look like?\n\n### Creating hits in Python\n\nFor the python client, you can create hits using either the `howler.hit.create` or `howler.hit.create_from_map` functions.\n\n#### `create`\n\nThis function takes in a single argument - either a single hit, or a list of them, conforming to the [Howler Schema](/help/hit?tab=schema). Here is a simple example:\n\n```python\n# Some bogus data in the Howler Schema format\nexample_hit = {\n \"howler\": {\n \"analytic\": \"example\",\n \"score\": 10.0\n },\n \"event\": {\n \"reason\": \"Example hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nYou can also ingest data in a flat format:\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: The name of the analytic creating the hit\n- `map`: A mapping between the raw data you have and the howler schema\n - The format is a dictionary where the keys are the flattened path of the raw data, and the values are a list of flattened paths for Howler's fields where the data will be copied into.\n- `documents`: The raw data you want to add to howler\n\nHere is a simple example:\n\n```python\n# The mapping from our data to howler's schema\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Some bogus data in a custom format we want to add to howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Note that the third argument is of type list!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Querying Hits\n\nQuerying hits using the howler python client is done using the `howler.search.hit` function. It has a number of required and optional arguments:\n\n- Required:\n - `query`: lucene query (string)\n- Optional:\n - `filters`: Additional lucene queries used to filter the data (list of strings)\n - `fl`: List of fields to return (comma separated string of fields)\n - `offset`: Offset at which the query items should start (integer)\n - `rows`: Number of records to return (integer)\n - `sort`: Field used for sorting with direction (string: ex. 'id desc')\n - `timeout`: Max amount of milliseconds the query will run (integer)\n - `use_archive`: Also query the archive\n - `track_total_hits`: Number of hits to track (default: 10k)\n\nHere are some example queries:\n\n```python\n# Search for all hits created by assemblyline, show the first 50, and return only their ids\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Search for all resolved hits created in the last five days, returning their id and the analytic that created them. Show only ten, offset by 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Search for all hits, timeout if the query takes more than 100ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100, use_archive=True)\n```\n\n### Updating Hits\n\nIn order to update hits, there are a number of supported functions:\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nIf you want to update a hit in a transactional way, you can use the following code:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nThe following operations can be run to update a hit.\n\n**List Operations:**\n\n- `UPDATE_APPEND`: Used to append a value to a given list\n- `UPDATE_APPEND_IF_MISSING`: Used to append a value to a given list if the value isn't already in the list\n- `UPDATE_REMOVE`: Will remove a given value from a list\n\n**Numeric Operations:**\n\n- `UPDATE_DEC`: Decrement a numeric value by the specified amount\n- `UPDATE_INC`: Increment a numeric value by the specified amount\n- `UPDATE_MAX`: Will set a numeric value to the maximum of the existing value and the specified value\n- `UPDATE_MIN`: Will set a numeric value to the minimum of the existing value and the specified value\n\n**Multipurpose Operations:**\n\n- `UPDATE_SET`: Set a field's value to the given value\n- `UPDATE_DELETE`: Will delete a given field's value\n\n#### `update_by_query()`\n\nThis function allows you to update a large number of hits by a query:\n\n```python\nclient.hit.update_by_query(f'howler.analytic:\"Example Alert\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nThe same operations as in `update()` can be used.\n\n### `overwrite()`\n\nThis function allows you to directly overwrite a hit with a partial hit object. This is the most easy to use, but loses some of the validation and additional processing of the update functions.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\": \"127.0.0.1\", \"destination.ip\": \"8.8.8.8\"})\n```\n"
@@ -1 +1 @@
1
- export default "# Retention in Howler\n\nIn order to comply with organizational policies, Howler is configured to purge stale alerts after a specific amount of time. On this instance, that duration is `duration`.\n\nHowler calculates whether it is time for the removal of an alert by the `event.created` date - once this surpasses the confgured deadline, a nightly automated job will remove the alert.\n\nIn order to communicate this to the user, see the example alert below:\n\n`alert`\n\nIn the top right, hovering over the timestamp will outline how long users have before the alert is removed. In order to ensure compliance with policy, ensure that `event.created` matches the date the underlying data was collected, allowing howler to ensure data is purged in time.\n\n```alert\nThis will soon change - there will be a dedicated field to set that will override this approach.\n```\n"
1
+ export default "# Retention in Howler\n\nIn order to comply with organizational policies, Howler is configured to purge stale alerts after a specific amount of\ntime. On this instance, that duration is `duration`.\n\n## How Retention Works\n\nHowler uses an automated retention job that runs on a configurable schedule (typically nightly) to remove\nalerts that have exceeded their retention period. The system evaluates two criteria for deletion:\n\n1. **Standard Retention**: Alerts are deleted when `event.created` exceeds the configured retention period\n2. **Custom Expiry**: Alerts are deleted when the `howler.expiry` field indicates the alert should expire\n\nAn alert will be removed when **either** condition is met - whichever comes first.\n\n## Custom Expiry (`howler.expiry`)\n\nThe `howler.expiry` field allows detection engineers to set custom retention periods for specific alerts\nduring ingestion. This field overrides the standard retention calculation and is commonly used when:\n\n- Clients have requested shorter data retention periods than the deployment default\n- Specific operations require time-limited data storage (e.g., a cybersecurity operation where data can\n only be retained for two weeks after ingest)\n- Regulatory requirements mandate earlier deletion for certain types of data\n\n```alert\nThe howler.expiry field can only shorten retention periods, not extend them. No matter\nwhat, alerts cannot be retained longer than the system-wide retention cutoff based on event.created.\n```\n\n## Configuration\n\nAdministrators can configure retention settings in the system configuration:\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Retention period duration\n limit_unit: days # Time unit (days, hours, etc.)\n crontab: \"0 0 * * *\" # Schedule (nightly at midnight)\n enabled: true # Whether retention is active\n```\n\n## User Interface\n\nTo communicate retention timing to users, see the example alert below:\n\n`alert`\n\nIn the top right, hovering over the timestamp will outline how long users have before the alert is\nremoved. In order to ensure compliance with policy, ensure that `event.created` matches the date the\nunderlying data was collected, allowing Howler to ensure data is purged in time.\n"
@@ -1 +1 @@
1
- export default "<!-- docs/ingestion/authentication.fr.md -->\n\n# Authentification de Howler\n\nL'API de Howler prend en charge un certain nombre d'approches d'authentification lors de l'acc\u00e8s \u00e0 l'API. Ce document pr\u00e9sente les\ndiff\u00e9rentes approches et explique comment utiliser chacune d'entre elles.\n\n## Types d'authentification\n\nIl existe quatre m\u00e9thodes d'authentification que l'on peut utiliser pour se connecter \u00e0 l'API howler:\n\n1. Nom d'utilisateur et mot de passe\n2. Nom d'utilisateur et cl\u00e9 API\n3. Nom d'utilisateur et cl\u00e9 d'application (apr\u00e8s connexion)\n4. Jeton d'acc\u00e8s OAuth\n\nNous allons maintenant pr\u00e9senter les cas d'utilisation de chaque type d'authentification.\n\n### Authentification par nom d'utilisateur/mot de passe\n\nL'authentification par nom d'utilisateur et mot de passe est la m\u00e9thode d'authentification la plus simple et la moins s\u00fbre. Il est peu probable qu'elle soit activ\u00e9e\ndans un environnement de production, elle permet aux utilisateurs de se connecter facilement \u00e0 l'API Howler et d'effectuer des modifications en tant qu'utilisateur donn\u00e9,\nsans avoir \u00e0 se soucier de cr\u00e9er des cl\u00e9s API ou d'utiliser un fournisseur OAuth comme Keycloak ou Azure. Les utilisateurs peuvent se connecter\nen utilisant l'authentification par nom d'utilisateur et mot de passe de l'une des deux mani\u00e8res suivantes :\n\n#### Appel direct au point de terminaison requis (mot de passe)\n\nC'est de loin la m\u00e9thode la plus simple. Il suffit d'ajouter un en-t\u00eate d'autorisation de base \u00e0 la requ\u00eate HTTP que vous souhaitez effectuer, et tout est pris en charge.\nque vous voulez faire, et tout est pris en charge:\n\n```bash\necho -n \"user:user\" | base64 -w0\n# -> dXNlcjp1c2Vy\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic dXNlcjp1c2Vy\n```\n\n#### \u00c9change contre un jeton d'application (mot de passe)\n\nIl s'agit d'une approche un peu plus complexe, mais qui pr\u00e9sente l'avantage de ne pas exposer le nom d'utilisateur et le mot de passe \u00e0 chaque demande.\n\u00e0 chaque requ\u00eate. Vous pouvez utiliser le point de terminaison `v1/auth/login` pour \u00e9changer votre nom d'utilisateur et votre mot de passe contre un jeton d'application. Le jeton\nfonctionne de la m\u00eame mani\u00e8re qu'un jeton d'acc\u00e8s OAuth - vous le fournissez \u00e0 chaque requ\u00eate ult\u00e9rieure, et il vous authentifie jusqu'\u00e0 ce que le jeton expire.\njusqu'\u00e0 ce que le jeton expire.\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"user\": \"user\",\n \"password\": \"user\"\n}\n```\n\nLe r\u00e9sultat sera quelque chose comme:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"app_token\": \"user:5791a142067745c3af51d6596da7da8f86357a9fa92ad78d1ce118ea7d89d34e\",\n \"provider\": null,\n \"refresh_token\": null\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nUtilisation de ce jeton dans un autre appel d'API:\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer user:5791a142067745c3af51d6596da7da8f86357a9fa92ad78d1ce118ea7d89d34e\n```\n\n### Authentification par nom d'utilisateur/cl\u00e9 API\n\nL'authentification par nom d'utilisateur et cl\u00e9 API fonctionne en grande partie de la m\u00eame mani\u00e8re que l'authentification par nom d'utilisateur/mot de passe du point de vue du client.\nDu c\u00f4t\u00e9 du serveur, cependant, les cl\u00e9s API pr\u00e9sentent plusieurs avantages essentiels. Tout d'abord, elles peuvent \u00eatre facilement r\u00e9voqu\u00e9es par l'utilisateur.\nDeuxi\u00e8mement, leurs privil\u00e8ges peuvent \u00eatre limit\u00e9s, n'autorisant qu'un sous-ensemble de permissions.\n\nIl existe deux m\u00e9thodes d'authentification, refl\u00e9tant le nom d'utilisateur et le mot de passe:\n\n#### Appel direct au point de terminaison requis (cl\u00e9 API)\n\nIl suffit d'ajouter un en-t\u00eate d'autorisation de base contenant le nom d'utilisateur et la cl\u00e9 API \u00e0 la requ\u00eate HTTP que vous souhaitez effectuer, et\ntout est pris en charge:\n\n```bash\n# note the format is <username>:<apikeyname>:<secret>\necho -n \"user:devkey:user\" | base64 -w0\n# -> dXNlcjpkZXZrZXk6dXNlcg==\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic dXNlcjpkZXZrZXk6dXNlcg==\n```\n\n#### \u00c9change contre un jeton d'application (cl\u00e9 API)\n\nVous pouvez \u00e9galement utiliser le point de terminaison `v1/auth/login` pour \u00e9changer votre nom d'utilisateur et votre cl\u00e9 API contre un jeton d'application. Le jeton\nfonctionne de la m\u00eame mani\u00e8re qu'un jeton d'acc\u00e8s OAuth - vous le fournissez \u00e0 chaque demande ult\u00e9rieure, et il vous authentifie jusqu'\u00e0 ce que le jeton expire.\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"user\": \"user\",\n \"apikey\": \"devkey:user\"\n}\n```\n\nLe r\u00e9sultat sera quelque chose comme:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"app_token\": \"user:f220eb76ff8404abfece8c0c2f3368c7d89618c776bedcd3a506843dc4a952e4\",\n \"provider\": null,\n \"refresh_token\": null\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nUtilisation de ce jeton dans un autre appel d'API:\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer user:f220eb76ff8404abfece8c0c2f3368c7d89618c776bedcd3a506843dc4a952e4\n```\n\nNotez que lorsque vous vous connectez \u00e0 l'aide d'une cl\u00e9 d'@cccsaurora/howler-ui/api, les autorisations de la cl\u00e9 d'@cccsaurora/howler-ui/api continuent de s'appliquer. Ainsi, si vous essayez d'acc\u00e9der \u00e0\n\u00e0 un point d'acc\u00e8s qui requiert l'autorisation \"write\" avec une cl\u00e9 API qui n'a que l'autorisation \"read\", cela provoquera une erreur 403\nForbidden (Interdit).\n\n### Authentification par jeton d'acc\u00e8s\n\nEn plus des m\u00e9thodes d'authentification fournies en interne par Howler, vous pouvez \u00e9galement vous authentifier avec un fournisseur OAuth externe comme Azure ou Keycloak.\nPour ce faire, vous devez obtenir un jeton d'acc\u00e8s aupr\u00e8s de l'un de ces fournisseurs.\nPour obtenir un jeton d'acc\u00e8s, il y a g\u00e9n\u00e9ralement deux flux - On-Behalf-Of, ou le flux de connexion de l'utilisateur (voir [ici](https://www.rfc-editor.org/rfc/rfc6749#section-4.1) pour un r\u00e9capitulatif\nde cela). Dans le cas de Howler, le point de terminaison `v1/auth/login` est utilis\u00e9 pour les \u00e9tapes D & E du flux de connexion de l'utilisateur, et\nil renvoie ce qui suit:\n\n```json\n{\n \"api_response\": {\n \"app_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIn0\",\n \"provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNoIn0\"\n }\n}\n```\n\nDans ce cas, le `app_token` est le Web Token JSON que l'application utilise comme jeton d'acc\u00e8s. De la m\u00eame mani\u00e8re, le\n`refresh_token` est un autre JWT qui est utilis\u00e9 pour rafra\u00eechir le jeton d'acc\u00e8s si n\u00e9cessaire. Enfin, le champ `provider` (fournisseur)\nindique \u00e0 quel fournisseur correspond ce jeton d'acc\u00e8s.\n\nUne fois que vous avez ce jeton d'acc\u00e8s, vous pouvez simplement le transmettre dans l'en-t\u00eate Authorization :\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIn0\n```\n\nHowler d\u00e9tectera automatiquement qu'il s'agit d'un JWT et le traitera comme tel. Si le jeton est valide, l'utilisateur sera\nauthentifi\u00e9.\n\nAfin de rafra\u00eechir un jeton d'acc\u00e8s expir\u00e9, vous pouvez utiliser l'appel API suivant:\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"oauth_provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNoIn0\"\n}\n```\n\nEt vous obtiendrez quelque chose comme ceci en retour:\n\n```json\n{\n \"api_response\": {\n \"app_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMiJ9\",\n \"provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNodG9rZW4yIn0\"\n }\n}\n```\n\n## Impersonation\n\nLe dernier \u00e9l\u00e9ment important du flux d'authentification est la fonctionnalit\u00e9 d'usurpation d'identit\u00e9. Pour qu'un compte de service\nou un autre utilisateur puisse se faire passer pour vous (c'est-\u00e0-dire cr\u00e9er des alertes en votre nom), Howler permet aux utilisateurs de fournir une deuxi\u00e8me cl\u00e9 API\nafin de s'authentifier en tant qu'autre utilisateur.\n\nAvant d'utiliser la cl\u00e9 API d'un autre utilisateur pour vous authentifier en tant que lui, il est important que la cl\u00e9 API que vous utilisez ait \u00e9t\u00e9\nmarqu\u00e9e comme valide pour l'usurpation d'identit\u00e9 - v\u00e9rifiez aupr\u00e8s de l'utilisateur qui l'a fournie. Si c'est le cas, vous pouvez l'utiliser en formulant votre requ\u00eate\ncomme suit:\n\n```bash\n# Your credentials\necho -n \"admin:devkey:admin\" | base64 -w0\n# -> YWRtaW46ZGV2a2V5OmFkbWlu\n\n# Their credentials\necho -n \"user:impersonate_admin:user\" | base64 -w0\n# -> dXNlcjppbXBlcnNvbmF0ZV9hZG1pbjp1c2Vy\n```\n\n```alert\nToute forme d'authentification peut \u00eatre utilis\u00e9e par l'usurpateur, mais les cl\u00e9s API valid\u00e9es sont la seule m\u00e9thode d'authentification autoris\u00e9e pour la personne dont vous usurpez l'identit\u00e9.\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic YWRtaW46ZGV2a2V5OmFkbWlu\nX-Impersonating: Basic dXNlcjppbXBlcnNvbmF0ZV9hZG1pbjp1c2Vy\n```\n\nSi la configuration est correcte, le r\u00e9sultat sera le suivant:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"avatar\": null,\n \"classification\": \"TLP:W\",\n \"email\": \"user@howler.cyber.gc.ca\",\n \"groups\": [\"USERS\"],\n \"is_active\": true,\n \"is_admin\": false,\n \"name\": \"User\",\n \"roles\": [\"user\"],\n \"username\": \"user\"\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nNotez que le serveur a renvoy\u00e9 cette requ\u00eate comme si vous \u00e9tiez `user`, PAS `admin`. Il est cependant clairement indiqu\u00e9 dans les logs que c'est vous qui faites la requ\u00eate:\n\n```log\n22/12/05 14:15:02 INFO howler.api.security | Authenticating user for path /api/v1/user/whoami/\n22/12/05 14:15:03 WARNING howler.api.security | admin is impersonating user\n22/12/05 14:15:03 INFO howler.api.security | Logged in as user from 127.0.0.1\n22/12/05 14:15:03 INFO howler.api | GET /api/v1/user/whoami/ - 200\n```\n"
1
+ export default "<!-- docs/ingestion/authentication.fr.md -->\n\n# Authentification de Howler\n\nL'API de Howler prend en charge un certain nombre d'approches d'authentification lors de l'acc\u00e8s \u00e0 l'API. Ce document pr\u00e9sente les\ndiff\u00e9rentes approches et explique comment utiliser chacune d'entre elles.\n\n## Types d'authentification\n\nIl existe quatre m\u00e9thodes d'authentification que l'on peut utiliser pour se connecter \u00e0 l'API howler:\n\n1. Nom d'utilisateur et mot de passe\n2. Nom d'utilisateur et cl\u00e9 API\n3. Nom d'utilisateur et cl\u00e9 d'application (apr\u00e8s connexion)\n4. Jeton d'acc\u00e8s OAuth\n\nNous allons maintenant pr\u00e9senter les cas d'utilisation de chaque type d'authentification.\n\n### Authentification par nom d'utilisateur/mot de passe\n\nL'authentification par nom d'utilisateur et mot de passe est la m\u00e9thode d'authentification la plus simple et la moins s\u00fbre. Il est peu probable qu'elle soit activ\u00e9e\ndans un environnement de production, elle permet aux utilisateurs de se connecter facilement \u00e0 l'API Howler et d'effectuer des modifications en tant qu'utilisateur donn\u00e9,\nsans avoir \u00e0 se soucier de cr\u00e9er des cl\u00e9s API ou d'utiliser un fournisseur OAuth comme Keycloak ou Azure. Les utilisateurs peuvent se connecter\nen utilisant l'authentification par nom d'utilisateur et mot de passe de l'une des deux mani\u00e8res suivantes :\n\n#### Appel direct au point de terminaison requis (mot de passe)\n\nC'est de loin la m\u00e9thode la plus simple. Il suffit d'ajouter un en-t\u00eate d'autorisation de base \u00e0 la requ\u00eate HTTP que vous souhaitez effectuer, et tout est pris en charge.\nque vous voulez faire, et tout est pris en charge:\n\n```bash\necho -n \"user:user\" | base64 -w0\n# -> dXNlcjp1c2Vy\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic dXNlcjp1c2Vy\n```\n\n#### \u00c9change contre un jeton d'application (mot de passe)\n\nIl s'agit d'une approche un peu plus complexe, mais qui pr\u00e9sente l'avantage de ne pas exposer le nom d'utilisateur et le mot de passe \u00e0 chaque demande.\n\u00e0 chaque requ\u00eate. Vous pouvez utiliser le point de terminaison `v1/auth/login` pour \u00e9changer votre nom d'utilisateur et votre mot de passe contre un jeton d'application. Le jeton\nfonctionne de la m\u00eame mani\u00e8re qu'un jeton d'acc\u00e8s OAuth - vous le fournissez \u00e0 chaque requ\u00eate ult\u00e9rieure, et il vous authentifie jusqu'\u00e0 ce que le jeton expire.\njusqu'\u00e0 ce que le jeton expire.\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"user\": \"user\",\n \"password\": \"user\"\n}\n```\n\nLe r\u00e9sultat sera quelque chose comme:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"app_token\": \"user:5791a142067745c3af51d6596da7da8f86357a9fa92ad78d1ce118ea7d89d34e\",\n \"provider\": null,\n \"refresh_token\": null\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nUtilisation de ce jeton dans un autre appel d'API:\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer user:5791a142067745c3af51d6596da7da8f86357a9fa92ad78d1ce118ea7d89d34e\n```\n\n### Authentification par nom d'utilisateur/cl\u00e9 API\n\nL'authentification par nom d'utilisateur et cl\u00e9 API fonctionne en grande partie de la m\u00eame mani\u00e8re que l'authentification par nom d'utilisateur/mot de passe du point de vue du client.\nDu c\u00f4t\u00e9 du serveur, cependant, les cl\u00e9s API pr\u00e9sentent plusieurs avantages essentiels. Tout d'abord, elles peuvent \u00eatre facilement r\u00e9voqu\u00e9es par l'utilisateur.\nDeuxi\u00e8mement, leurs privil\u00e8ges peuvent \u00eatre limit\u00e9s, n'autorisant qu'un sous-ensemble de permissions.\n\nIl existe deux m\u00e9thodes d'authentification, refl\u00e9tant le nom d'utilisateur et le mot de passe:\n\n#### Appel direct au point de terminaison requis (cl\u00e9 API)\n\nIl suffit d'ajouter un en-t\u00eate d'autorisation de base contenant le nom d'utilisateur et la cl\u00e9 API \u00e0 la requ\u00eate HTTP que vous souhaitez effectuer, et\ntout est pris en charge:\n\n```bash\n# note the format is <username>:<apikeyname>:<secret>\necho -n \"user:devkey:user\" | base64 -w0\n# -> dXNlcjpkZXZrZXk6dXNlcg==\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic dXNlcjpkZXZrZXk6dXNlcg==\n```\n\n#### \u00c9change contre un jeton d'application (cl\u00e9 API)\n\nVous pouvez \u00e9galement utiliser le point de terminaison `v1/auth/login` pour \u00e9changer votre nom d'utilisateur et votre cl\u00e9 API contre un jeton d'application. Le jeton\nfonctionne de la m\u00eame mani\u00e8re qu'un jeton d'acc\u00e8s OAuth - vous le fournissez \u00e0 chaque demande ult\u00e9rieure, et il vous authentifie jusqu'\u00e0 ce que le jeton expire.\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"user\": \"user\",\n \"apikey\": \"devkey:user\"\n}\n```\n\nLe r\u00e9sultat sera quelque chose comme:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"app_token\": \"user:f220eb76ff8404abfece8c0c2f3368c7d89618c776bedcd3a506843dc4a952e4\",\n \"provider\": null,\n \"refresh_token\": null\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nUtilisation de ce jeton dans un autre appel d'API:\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer user:f220eb76ff8404abfece8c0c2f3368c7d89618c776bedcd3a506843dc4a952e4\n```\n\nNotez que lorsque vous vous connectez \u00e0 l'aide d'une cl\u00e9 d'api, les autorisations de la cl\u00e9 d'api continuent de s'appliquer. Ainsi, si vous essayez d'acc\u00e9der \u00e0\n\u00e0 un point d'acc\u00e8s qui requiert l'autorisation \"write\" avec une cl\u00e9 API qui n'a que l'autorisation \"read\", cela provoquera une erreur 403\nForbidden (Interdit).\n\n### Authentification par jeton d'acc\u00e8s\n\nEn plus des m\u00e9thodes d'authentification fournies en interne par Howler, vous pouvez \u00e9galement vous authentifier avec un fournisseur OAuth externe comme Azure ou Keycloak.\nPour ce faire, vous devez obtenir un jeton d'acc\u00e8s aupr\u00e8s de l'un de ces fournisseurs.\nPour obtenir un jeton d'acc\u00e8s, il y a g\u00e9n\u00e9ralement deux flux - On-Behalf-Of, ou le flux de connexion de l'utilisateur (voir [ici](https://www.rfc-editor.org/rfc/rfc6749#section-4.1) pour un r\u00e9capitulatif\nde cela). Dans le cas de Howler, le point de terminaison `v1/auth/login` est utilis\u00e9 pour les \u00e9tapes D & E du flux de connexion de l'utilisateur, et\nil renvoie ce qui suit:\n\n```json\n{\n \"api_response\": {\n \"app_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIn0\",\n \"provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNoIn0\"\n }\n}\n```\n\nDans ce cas, le `app_token` est le Web Token JSON que l'application utilise comme jeton d'acc\u00e8s. De la m\u00eame mani\u00e8re, le\n`refresh_token` est un autre JWT qui est utilis\u00e9 pour rafra\u00eechir le jeton d'acc\u00e8s si n\u00e9cessaire. Enfin, le champ `provider` (fournisseur)\nindique \u00e0 quel fournisseur correspond ce jeton d'acc\u00e8s.\n\nUne fois que vous avez ce jeton d'acc\u00e8s, vous pouvez simplement le transmettre dans l'en-t\u00eate Authorization :\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyIn0\n```\n\nHowler d\u00e9tectera automatiquement qu'il s'agit d'un JWT et le traitera comme tel. Si le jeton est valide, l'utilisateur sera\nauthentifi\u00e9.\n\nAfin de rafra\u00eechir un jeton d'acc\u00e8s expir\u00e9, vous pouvez utiliser l'appel API suivant:\n\n```http\nPOST $CURRENT_URL/api/v1/auth/login/\nContent-Type: application/json\n\n{\n \"oauth_provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNoIn0\"\n}\n```\n\nEt vous obtiendrez quelque chose comme ceci en retour:\n\n```json\n{\n \"api_response\": {\n \"app_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyMiJ9\",\n \"provider\": \"keycloak\",\n \"refresh_token\": \"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJyZWZyZXNodG9rZW4yIn0\"\n }\n}\n```\n\n## Impersonation\n\nLe dernier \u00e9l\u00e9ment important du flux d'authentification est la fonctionnalit\u00e9 d'usurpation d'identit\u00e9. Pour qu'un compte de service\nou un autre utilisateur puisse se faire passer pour vous (c'est-\u00e0-dire cr\u00e9er des alertes en votre nom), Howler permet aux utilisateurs de fournir une deuxi\u00e8me cl\u00e9 API\nafin de s'authentifier en tant qu'autre utilisateur.\n\nAvant d'utiliser la cl\u00e9 API d'un autre utilisateur pour vous authentifier en tant que lui, il est important que la cl\u00e9 API que vous utilisez ait \u00e9t\u00e9\nmarqu\u00e9e comme valide pour l'usurpation d'identit\u00e9 - v\u00e9rifiez aupr\u00e8s de l'utilisateur qui l'a fournie. Si c'est le cas, vous pouvez l'utiliser en formulant votre requ\u00eate\ncomme suit:\n\n```bash\n# Your credentials\necho -n \"admin:devkey:admin\" | base64 -w0\n# -> YWRtaW46ZGV2a2V5OmFkbWlu\n\n# Their credentials\necho -n \"user:impersonate_admin:user\" | base64 -w0\n# -> dXNlcjppbXBlcnNvbmF0ZV9hZG1pbjp1c2Vy\n```\n\n```alert\nToute forme d'authentification peut \u00eatre utilis\u00e9e par l'usurpateur, mais les cl\u00e9s API valid\u00e9es sont la seule m\u00e9thode d'authentification autoris\u00e9e pour la personne dont vous usurpez l'identit\u00e9.\n```\n\n```http\nGET $CURRENT_URL/api/v1/user/whoami\nAuthorization: Basic YWRtaW46ZGV2a2V5OmFkbWlu\nX-Impersonating: Basic dXNlcjppbXBlcnNvbmF0ZV9hZG1pbjp1c2Vy\n```\n\nSi la configuration est correcte, le r\u00e9sultat sera le suivant:\n\n```json\n{\n \"api_error_message\": \"\",\n \"api_response\": {\n \"avatar\": null,\n \"classification\": \"TLP:W\",\n \"email\": \"user@howler.cyber.gc.ca\",\n \"groups\": [\"USERS\"],\n \"is_active\": true,\n \"is_admin\": false,\n \"name\": \"User\",\n \"roles\": [\"user\"],\n \"username\": \"user\"\n },\n \"api_server_version\": \"0.0.0.dev0\",\n \"api_status_code\": 200\n}\n```\n\nNotez que le serveur a renvoy\u00e9 cette requ\u00eate comme si vous \u00e9tiez `user`, PAS `admin`. Il est cependant clairement indiqu\u00e9 dans les logs que c'est vous qui faites la requ\u00eate:\n\n```log\n22/12/05 14:15:02 INFO howler.api.security | Authenticating user for path /api/v1/user/whoami/\n22/12/05 14:15:03 WARNING howler.api.security | admin is impersonating user\n22/12/05 14:15:03 INFO howler.api.security | Logged in as user from 127.0.0.1\n22/12/05 14:15:03 INFO howler.api | GET /api/v1/user/whoami/ - 200\n```\n"
@@ -1 +1 @@
1
- export default "# Documentation du client Howler\n\nCette documentation explique comment interagir avec l'API howler en utilisant le client howler dans les environnements de d\u00e9veloppement Java et Python. Nous d\u00e9crirons le processus de base de la cr\u00e9ation d'un nouveau hit dans chaque environnement ainsi que la recherche dans howler des hits correspondant \u00e0 votre requ\u00eate.\n\n## Mise en route\n\n### Installation\n\nAfin d'utiliser le client howler, vous devez le lister comme une d\u00e9pendance dans votre projet.\n\n#### **Python**\n\nIl suffit de l'installer \u00e0 l'aide de pip:\n\n```bash\npip install howler-client\n```\n\nVous pouvez \u00e9galement l'ajouter \u00e0 votre requirements.txt, ou \u00e0 tout autre syst\u00e8me de gestion des d\u00e9pendances que vous utilisez.\n\n### Authentification\n\nComme indiqu\u00e9 dans la [Documentation sur l'authentification](/help/auth), les utilisateurs peuvent s'authentifier de diff\u00e9rentes mani\u00e8res. Pour interfacer avec le client howler, cependant, le flux sugg\u00e9r\u00e9 est d'utiliser une cl\u00e9 API. Avant de commencer, g\u00e9n\u00e9rons une cl\u00e9.\n\n1. Ouvrez l'interface utilisateur Howler avec laquelle vous souhaitez vous interfacer.\n2. Connectez-vous, puis cliquez sur votre profil en haut \u00e0 droite.\n3. Dans le menu utilisateur, cliquez sur Param\u00e8tres.\n4. Sous S\u00e9curit\u00e9 de l'utilisateur, appuyez sur l'ic\u00f4ne (+) sur la ligne Cl\u00e9s API.\n5. Nommez votre cl\u00e9 et donnez-lui les autorisations n\u00e9cessaires.\n6. Appuyez sur Create (Cr\u00e9er) et copiez la cha\u00eene fournie dans un endroit s\u00fbr. \\*\\*Vous ne reverrez plus cette cha\u00eene.\n\nCette cl\u00e9 API sera fournie \u00e0 votre code par la suite.\n\n## Client Python\n\nPour se connecter \u00e0 howler en utilisant le client python, il y a un processus assez simple \u00e0 suivre:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtenez-le \u00e0 partir de la page des param\u00e8tres de l'utilisateur de l'interface utilisateur de Howler.\nAPIKEY = '@cccsaurora/howler-ui/apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\nVoil\u00e0, c'est fait ! Vous pouvez maintenant utiliser l'objet `howler` pour interagir avec le serveur. A quoi cela ressemble-t-il en r\u00e9alit\u00e9?\n\n### Cr\u00e9er des hits en Python\n\nPour le client python, vous pouvez cr\u00e9er des hits en utilisant les fonctions `howler.hit.create` ou `howler.hit.create_from_map`.\n\n#### `create`\n\nCette fonction prend un seul argument - soit un hit unique, soit une liste de hits, conforme au [Howler Schema] (/help/hit?tab=schema). Voici un exemple simple :\n\n```python\n# Quelques donn\u00e9es bidons au format Howler Schema\nexemple_hit = {\n \"howler\" : {\n \"analytic\" : \"exemple\",\n \"score\" : 10.0\n },\n \"event\" : {\n \"reason\" : \"Exemple hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nVous pouvez \u00e9galement ing\u00e9rer des donn\u00e9es dans un format plat :\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: Le nom de l'outil d'analyse qui cr\u00e9e le hit\n- `map`: Une correspondance entre les donn\u00e9es brutes que vous avez et le sch\u00e9ma howler\n - Le format est un dictionnaire o\u00f9 les cl\u00e9s sont le chemin aplati des donn\u00e9es brutes, et les valeurs sont une liste de chemins aplatis pour les champs de Howler dans lesquels les donn\u00e9es seront copi\u00e9es.\n- `documents`: Les donn\u00e9es brutes que vous voulez ajouter \u00e0 Howler.\n\nVoici un exemple simple:\n\n```python\n# La correspondance entre nos donn\u00e9es et le sch\u00e9ma de Howler\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Quelques fausses donn\u00e9es dans un format personnalis\u00e9 que nous voulons ajouter \u00e0 howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Notez que le troisi\u00e8me argument est de type liste!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Interroger les hits en Python\n\nL'interrogation des hits avec le client python howler se fait en utilisant la fonction `howler.search.hit`. Elle poss\u00e8de un certain nombre d'arguments obligatoires et optionnels:\n\n- Obligatoire:\n - `query`: requ\u00eate lucene (cha\u00eene)\n- Facultatif: `filters`: requ\u00eate lucene (cha\u00eene):\n - `filters`: Requ\u00eates lucene additionnelles utilis\u00e9es pour filtrer les donn\u00e9es (liste de cha\u00eenes)\n - `fl`: Liste des champs \u00e0 retourner (cha\u00eene de champs s\u00e9par\u00e9s par des virgules)\n - `offset`: Offset auquel les \u00e9l\u00e9ments de la requ\u00eate doivent commencer (entier)\n - `rows`: Nombre d'enregistrements \u00e0 retourner (entier)\n - `sort`: Champ utilis\u00e9 pour le tri avec direction (cha\u00eene: ex. 'id desc')\n - `timeout`: Nombre maximum de millisecondes d'ex\u00e9cution de la requ\u00eate (entier)\n - `use_archive`: Interroge \u00e9galement l'archive\n - `track_total_hits`: Nombre de hits \u00e0 suivre (par d\u00e9faut: 10k)\n\nVoici quelques exemples de requ\u00eates:\n\n```python\n# Rechercher tous les hits cr\u00e9\u00e9s par assemblyline, afficher les 50 premiers et ne renvoyer que leurs identifiants.\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Recherche de toutes les occurrences r\u00e9solues cr\u00e9\u00e9es au cours des cinq derniers jours, avec indication de leur identifiant et de l'analyste qui les a cr\u00e9\u00e9es. N'en afficher que dix, d\u00e9cal\u00e9s de 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Recherche de tous les r\u00e9sultats, d\u00e9lai d'attente si la requ\u00eate prend plus de 100 ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100, use_archive=True)\n```\n\n### Mise \u00e0 jour des r\u00e9sultats\n\nAfin de mettre \u00e0 jour les hits, il existe un certain nombre de fonctions support\u00e9es :\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nSi vous souhaitez mettre \u00e0 jour un hit de mani\u00e8re transactionnelle, vous pouvez utiliser le code suivant:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nLes op\u00e9rations suivantes peuvent \u00eatre ex\u00e9cut\u00e9es pour mettre \u00e0 jour un r\u00e9sultat.\n\n**Op\u00e9rations de liste:**\n\n- `UPDATE_APPEND` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e\n- `UPDATE_APPEND_IF_MISSING` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e si la valeur n'est pas d\u00e9j\u00e0 dans la liste.\n- `UPDATE_REMOVE` : Supprime une valeur donn\u00e9e d'une liste\n\n**Op\u00e9rations num\u00e9riques:**\n\n- `UPDATE_DEC` : Diminue une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_INC` : Incr\u00e9mente une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_MAX` : Fixe une valeur num\u00e9rique au maximum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n- `UPDATE_MIN` : Fixe une valeur num\u00e9rique au minimum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n\n**Op\u00e9rations polyvalentes:**\n\n- `UPDATE_SET` : Fixe la valeur d'un champ \u00e0 la valeur donn\u00e9e.\n- `UPDATE_DELETE` : Supprime la valeur d'un champ donn\u00e9\n\n#### `update_by_query()`\n\nCette fonction vous permet de mettre \u00e0 jour un grand nombre d'occurrences \u00e0 l'aide d'une requ\u00eate :\n\n```python\nclient.hit.update_by_query(f'howler.analytic : \"Exemple d\\'analytic\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nLes m\u00eames op\u00e9rations que dans `update()` peuvent \u00eatre utilis\u00e9es.\n\n### `overwrite()`\n\nCette fonction vous permet d'\u00e9craser directement un hit avec un objet hit partiel. C'est la plus facile \u00e0 utiliser, mais elle perd une partie de la validation et du traitement suppl\u00e9mentaire des fonctions de mise \u00e0 jour.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\" : \"127.0.0.1\", \u201cdestination.ip\u201d : \"8.8.8.8\"})\n```\n"
1
+ export default "# Documentation du client Howler\n\nCette documentation explique comment interagir avec l'API howler en utilisant le client howler dans les environnements de d\u00e9veloppement Java et Python. Nous d\u00e9crirons le processus de base de la cr\u00e9ation d'un nouveau hit dans chaque environnement ainsi que la recherche dans howler des hits correspondant \u00e0 votre requ\u00eate.\n\n## Mise en route\n\n### Installation\n\nAfin d'utiliser le client howler, vous devez le lister comme une d\u00e9pendance dans votre projet.\n\n#### **Python**\n\nIl suffit de l'installer \u00e0 l'aide de pip:\n\n```bash\npip install howler-client\n```\n\nVous pouvez \u00e9galement l'ajouter \u00e0 votre requirements.txt, ou \u00e0 tout autre syst\u00e8me de gestion des d\u00e9pendances que vous utilisez.\n\n### Authentification\n\nComme indiqu\u00e9 dans la [Documentation sur l'authentification](/help/auth), les utilisateurs peuvent s'authentifier de diff\u00e9rentes mani\u00e8res. Pour interfacer avec le client howler, cependant, le flux sugg\u00e9r\u00e9 est d'utiliser une cl\u00e9 API. Avant de commencer, g\u00e9n\u00e9rons une cl\u00e9.\n\n1. Ouvrez l'interface utilisateur Howler avec laquelle vous souhaitez vous interfacer.\n2. Connectez-vous, puis cliquez sur votre profil en haut \u00e0 droite.\n3. Dans le menu utilisateur, cliquez sur Param\u00e8tres.\n4. Sous S\u00e9curit\u00e9 de l'utilisateur, appuyez sur l'ic\u00f4ne (+) sur la ligne Cl\u00e9s API.\n5. Nommez votre cl\u00e9 et donnez-lui les autorisations n\u00e9cessaires.\n6. Appuyez sur Create (Cr\u00e9er) et copiez la cha\u00eene fournie dans un endroit s\u00fbr. \\*\\*Vous ne reverrez plus cette cha\u00eene.\n\nCette cl\u00e9 API sera fournie \u00e0 votre code par la suite.\n\n## Client Python\n\nPour se connecter \u00e0 howler en utilisant le client python, il y a un processus assez simple \u00e0 suivre:\n\n```python\nfrom howler_client import get_client\n\nUSERNAME = 'user' # Obtenez-le \u00e0 partir de la page des param\u00e8tres de l'utilisateur de l'interface utilisateur de Howler.\nAPIKEY = 'apikey_name:apikey_data'\n\napikey = (USERNAME, APIKEY)\n\nhowler = get_client(\"$CURRENT_URL\", apikey=apikey)\n```\n\nVoil\u00e0, c'est fait ! Vous pouvez maintenant utiliser l'objet `howler` pour interagir avec le serveur. A quoi cela ressemble-t-il en r\u00e9alit\u00e9?\n\n### Cr\u00e9er des hits en Python\n\nPour le client python, vous pouvez cr\u00e9er des hits en utilisant les fonctions `howler.hit.create` ou `howler.hit.create_from_map`.\n\n#### `create`\n\nCette fonction prend un seul argument - soit un hit unique, soit une liste de hits, conforme au [Howler Schema] (/help/hit?tab=schema). Voici un exemple simple :\n\n```python\n# Quelques donn\u00e9es bidons au format Howler Schema\nexemple_hit = {\n \"howler\" : {\n \"analytic\" : \"exemple\",\n \"score\" : 10.0\n },\n \"event\" : {\n \"reason\" : \"Exemple hit\"\n }\n}\n\nhowler.hit.create(example_hit)\n```\n\nVous pouvez \u00e9galement ing\u00e9rer des donn\u00e9es dans un format plat :\n\n```python\nexample_hit = {\n \"howler.analytic\": \"example\",\n \"howler.score\": 10.0,\n \"event.reason\": \"Example hit\"\n}\n\nhowler.hit.create(example_hit)\n```\n\n#### `create_from_map`\n\nThis function takes in three arguments:\n\n- `tool name`: Le nom de l'outil d'analyse qui cr\u00e9e le hit\n- `map`: Une correspondance entre les donn\u00e9es brutes que vous avez et le sch\u00e9ma howler\n - Le format est un dictionnaire o\u00f9 les cl\u00e9s sont le chemin aplati des donn\u00e9es brutes, et les valeurs sont une liste de chemins aplatis pour les champs de Howler dans lesquels les donn\u00e9es seront copi\u00e9es.\n- `documents`: Les donn\u00e9es brutes que vous voulez ajouter \u00e0 Howler.\n\nVoici un exemple simple:\n\n```python\n# La correspondance entre nos donn\u00e9es et le sch\u00e9ma de Howler\nhwl_map = {\n \"file.sha256\": [\"file.hash.sha256\", \"howler.hash\"],\n \"file.name\": [\"file.name\"],\n \"src_ip\": [\"source.ip\", \"related.ip\"],\n \"dest_ip\": [\"destination.ip\", \"related.ip\"],\n \"time.created\": [\"event.start\"],\n}\n\n# Quelques fausses donn\u00e9es dans un format personnalis\u00e9 que nous voulons ajouter \u00e0 howler\nexample_hit = {\n \"src_ip\": \"0.0.0.0\",\n \"dest_ip\": \"8.8.8.8\",\n \"file\": {\n \"name\": \"hello.exe\",\n \"sha256\": sha256(str(\"hello.exe\").encode()).hexdigest()\n },\n \"time\": {\n \"created\": datetime.now().isoformat()\n },\n}\n\n# Notez que le troisi\u00e8me argument est de type liste!\nhowler.hit.create_from_map(\"example_ingestor\", hwl_map, [example_hit])\n```\n\n### Interroger les hits en Python\n\nL'interrogation des hits avec le client python howler se fait en utilisant la fonction `howler.search.hit`. Elle poss\u00e8de un certain nombre d'arguments obligatoires et optionnels:\n\n- Obligatoire:\n - `query`: requ\u00eate lucene (cha\u00eene)\n- Facultatif: `filters`: requ\u00eate lucene (cha\u00eene):\n - `filters`: Requ\u00eates lucene additionnelles utilis\u00e9es pour filtrer les donn\u00e9es (liste de cha\u00eenes)\n - `fl`: Liste des champs \u00e0 retourner (cha\u00eene de champs s\u00e9par\u00e9s par des virgules)\n - `offset`: Offset auquel les \u00e9l\u00e9ments de la requ\u00eate doivent commencer (entier)\n - `rows`: Nombre d'enregistrements \u00e0 retourner (entier)\n - `sort`: Champ utilis\u00e9 pour le tri avec direction (cha\u00eene: ex. 'id desc')\n - `timeout`: Nombre maximum de millisecondes d'ex\u00e9cution de la requ\u00eate (entier)\n - `use_archive`: Interroge \u00e9galement l'archive\n - `track_total_hits`: Nombre de hits \u00e0 suivre (par d\u00e9faut: 10k)\n\nVoici quelques exemples de requ\u00eates:\n\n```python\n# Rechercher tous les hits cr\u00e9\u00e9s par assemblyline, afficher les 50 premiers et ne renvoyer que leurs identifiants.\nhowler.search.hit(\"howler.analytic:assemblyline\", fl=\"howler.id\", rows=50)\n\n# Recherche de toutes les occurrences r\u00e9solues cr\u00e9\u00e9es au cours des cinq derniers jours, avec indication de leur identifiant et de l'analyste qui les a cr\u00e9\u00e9es. N'en afficher que dix, d\u00e9cal\u00e9s de 40\nhowler.search.hit(\"howler.status:resolved\", filters=['event.created:[now-5d TO now]'] fl=\"howler.id,howler.analytic\", rows=10, offset=40)\n\n# Recherche de tous les r\u00e9sultats, d\u00e9lai d'attente si la requ\u00eate prend plus de 100 ms\nhowler.search.hit(\"howler.id:*\", track_total_hits=100000000, timeout=100, use_archive=True)\n```\n\n### Mise \u00e0 jour des r\u00e9sultats\n\nAfin de mettre \u00e0 jour les hits, il existe un certain nombre de fonctions support\u00e9es :\n\n- `howler.hit.update(...)`\n- `howler.hit.update_by_query(...)`\n- `howler.hit.overwrite(...)`\n\n#### `update()`\n\nSi vous souhaitez mettre \u00e0 jour un hit de mani\u00e8re transactionnelle, vous pouvez utiliser le code suivant:\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.update(hit_to_update[\"howler\"][\"id\"], [(UPDATE_SET, \"howler.score\", hit_to_update[\"howler\"][\"score\"] + 100)])\n```\n\nLes op\u00e9rations suivantes peuvent \u00eatre ex\u00e9cut\u00e9es pour mettre \u00e0 jour un r\u00e9sultat.\n\n**Op\u00e9rations de liste:**\n\n- `UPDATE_APPEND` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e\n- `UPDATE_APPEND_IF_MISSING` : Utilis\u00e9 pour ajouter une valeur \u00e0 une liste donn\u00e9e si la valeur n'est pas d\u00e9j\u00e0 dans la liste.\n- `UPDATE_REMOVE` : Supprime une valeur donn\u00e9e d'une liste\n\n**Op\u00e9rations num\u00e9riques:**\n\n- `UPDATE_DEC` : Diminue une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_INC` : Incr\u00e9mente une valeur num\u00e9rique de la quantit\u00e9 sp\u00e9cifi\u00e9e.\n- `UPDATE_MAX` : Fixe une valeur num\u00e9rique au maximum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n- `UPDATE_MIN` : Fixe une valeur num\u00e9rique au minimum de la valeur existante et de la valeur sp\u00e9cifi\u00e9e.\n\n**Op\u00e9rations polyvalentes:**\n\n- `UPDATE_SET` : Fixe la valeur d'un champ \u00e0 la valeur donn\u00e9e.\n- `UPDATE_DELETE` : Supprime la valeur d'un champ donn\u00e9\n\n#### `update_by_query()`\n\nCette fonction vous permet de mettre \u00e0 jour un grand nombre d'occurrences \u00e0 l'aide d'une requ\u00eate :\n\n```python\nclient.hit.update_by_query(f'howler.analytic : \"Exemple d\\'analytic\"', [(UPDATE_INC, \"howler.score\", 100)])\n```\n\nLes m\u00eames op\u00e9rations que dans `update()` peuvent \u00eatre utilis\u00e9es.\n\n### `overwrite()`\n\nCette fonction vous permet d'\u00e9craser directement un hit avec un objet hit partiel. C'est la plus facile \u00e0 utiliser, mais elle perd une partie de la validation et du traitement suppl\u00e9mentaire des fonctions de mise \u00e0 jour.\n\n```python\nhit_to_update = client.search.hit(\"howler.id:*\", rows=1, sort=\"event.created desc\")[\"items\"][0]\n\nresult = client.hit.overwrite(hit_to_update[\"howler\"][\"id\"], {\"source.ip\" : \"127.0.0.1\", \u201cdestination.ip\u201d : \"8.8.8.8\"})\n```\n"
@@ -1 +1 @@
1
- export default "# R\u00e9tention dans Howler\n\nAfin de se conformer au r\u00e8glement, Howler est configur\u00e9 pour purger les alertes p\u00e9rim\u00e9es apr\u00e8s une p\u00e9riode de temps sp\u00e9cifique. Dans cette instance, cette dur\u00e9e est `duration`.\n\nHowler calcule s'il est temps de supprimer une alerte en fonction de la date `event.created` - une fois que celle-ci d\u00e9passe la date limite configur\u00e9e, un travail automatis\u00e9 nocturne supprimera l'alerte.\n\nAfin de communiquer cela \u00e0 l'utilisateur, voir l'exemple d'alerte ci-dessous :\n\n`alert`\n\nEn haut \u00e0 droite, le survol de l'horodatage indique le temps dont dispose l'utilisateur avant que l'alerte ne soit supprim\u00e9e. Afin de se conformer au r\u00e8glement, assurez-vous que `event.created` correspond \u00e0 la date \u00e0 laquelle les donn\u00e9es sous-jacentes ont \u00e9t\u00e9 collect\u00e9es, ce qui permet \u00e0 howler de s'assurer que les donn\u00e9es sont purg\u00e9es \u00e0 temps.\n\n```alert\nCela va bient\u00f4t changer - il y aura un champ d\u00e9di\u00e9 \u00e0 d\u00e9finir qui remplacera cette approche.\n```\n"
1
+ export default "# R\u00e9tention dans Howler\n\nAfin de se conformer aux politiques organisationnelles, Howler est configur\u00e9 pour purger les alertes\np\u00e9rim\u00e9es apr\u00e8s une p\u00e9riode de temps sp\u00e9cifique. Dans cette instance, cette dur\u00e9e est `duration`.\n\n## Comment fonctionne la r\u00e9tention\n\nHowler utilise un travail de r\u00e9tention automatis\u00e9 qui s'ex\u00e9cute selon un calendrier configurable\n(g\u00e9n\u00e9ralement nocturne) pour supprimer les alertes qui ont d\u00e9pass\u00e9 leur p\u00e9riode de r\u00e9tention. Le syst\u00e8me\n\u00e9value deux crit\u00e8res de suppression :\n\n1. **R\u00e9tention standard** : Les alertes sont supprim\u00e9es lorsque `event.created` d\u00e9passe la p\u00e9riode de\n r\u00e9tention configur\u00e9e\n2. **Expiration personnalis\u00e9e** : Les alertes sont supprim\u00e9es lorsque le champ `howler.expiry` indique\n que l'alerte doit expirer\n\nUne alerte sera supprim\u00e9e lorsque **l'une ou l'autre** condition est remplie - selon celle qui arrive en\npremier.\n\n## Expiration personnalis\u00e9e (`howler.expiry`)\n\nLe champ `howler.expiry` permet aux ing\u00e9nieurs de d\u00e9tection de d\u00e9finir des p\u00e9riodes de r\u00e9tention\npersonnalis\u00e9es pour des alertes sp\u00e9cifiques lors de l'ingestion. Ce champ remplace le calcul de\nr\u00e9tention standard et est couramment utilis\u00e9 quand :\n\n- Les clients ont demand\u00e9 des p\u00e9riodes de r\u00e9tention de donn\u00e9es plus courtes que la valeur par d\u00e9faut\n du d\u00e9ploiement\n- Des op\u00e9rations sp\u00e9cifiques n\u00e9cessitent un stockage de donn\u00e9es \u00e0 dur\u00e9e limit\u00e9e (par ex., une op\u00e9ration\n de cybers\u00e9curit\u00e9 o\u00f9 les donn\u00e9es ne peuvent \u00eatre conserv\u00e9es que deux semaines apr\u00e8s ingestion)\n- Les exigences r\u00e9glementaires imposent une suppression plus pr\u00e9coce pour certains types de donn\u00e9es\n\n```alert\nLe champ howler.expiry ne peut que raccourcir les p\u00e9riodes de r\u00e9tention, pas les\nprolonger. Quoi qu'il arrive, les alertes ne peuvent pas \u00eatre conserv\u00e9es plus longtemps que la limite de\nr\u00e9tention syst\u00e8me bas\u00e9e sur event.created.\n```\n\n## Configuration\n\nLes administrateurs peuvent configurer les param\u00e8tres de r\u00e9tention dans la configuration syst\u00e8me :\n\n```yaml\nsystem:\n type: staging\n retention:\n limit_amount: 120 # Dur\u00e9e de la p\u00e9riode de r\u00e9tention\n limit_unit: days # Unit\u00e9 de temps (days, hours, etc.)\n crontab: \"0 0 * * *\" # Calendrier (nocturne \u00e0 minuit)\n enabled: true # Si la r\u00e9tention est active\n```\n\n## Interface utilisateur\n\nAfin de communiquer le d\u00e9lai de r\u00e9tention aux utilisateurs, voir l'exemple d'alerte ci-dessous :\n\n`alert`\n\nEn haut \u00e0 droite, le survol de l'horodatage indique le temps dont dispose l'utilisateur avant que\nl'alerte ne soit supprim\u00e9e. Afin de se conformer aux politiques, assurez-vous que `event.created`\ncorrespond \u00e0 la date \u00e0 laquelle les donn\u00e9es sous-jacentes ont \u00e9t\u00e9 collect\u00e9es, permettant \u00e0 Howler de\ns'assurer que les donn\u00e9es sont purg\u00e9es \u00e0 temps.\n"
@@ -12,10 +12,10 @@ import { StorageKey } from '@cccsaurora/howler-ui/utils/constants';
12
12
  import EditRow from '../../elements/EditRow';
13
13
  import SettingsSection from './SettingsSection';
14
14
  const APIKEY_LABELS = {
15
- R: '@cccsaurora/howler-ui/apikey.read',
16
- W: '@cccsaurora/howler-ui/apikey.write',
17
- E: '@cccsaurora/howler-ui/apikey.extended',
18
- I: '@cccsaurora/howler-ui/apikey.impersonate'
15
+ R: 'apikey.read',
16
+ W: 'apikey.write',
17
+ E: 'apikey.extended',
18
+ I: 'apikey.impersonate'
19
19
  };
20
20
  const SecuritySection = ({ user, editPassword, addApiKey, removeApiKey, editQuota }) => {
21
21
  const { t } = useTranslation();
package/package.json CHANGED
@@ -96,7 +96,7 @@
96
96
  "internal-slot": "1.0.7"
97
97
  },
98
98
  "type": "module",
99
- "version": "2.15.0-dev.296",
99
+ "version": "2.15.0-dev.307",
100
100
  "exports": {
101
101
  "./i18n": "./i18n.js",
102
102
  "./index.css": "./index.css",