graylog-api-client 0.1.4__tar.gz → 0.2.0__tar.gz

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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graylog-api-client
3
- Version: 0.1.4
3
+ Version: 0.2.0
4
4
  Summary: A Python wrapper for the Graylog REST API
5
5
  License-Expression: MIT
6
6
  License-File: LICENSE
@@ -11,6 +11,7 @@ Requires-Python: >=3.13
11
11
  Classifier: Programming Language :: Python :: 3
12
12
  Classifier: Programming Language :: Python :: 3.13
13
13
  Classifier: Programming Language :: Python :: 3.14
14
+ Classifier: Programming Language :: Python :: 3.15
14
15
  Requires-Dist: requests (>=2.32.5,<3.0.0)
15
16
  Project-URL: Repository, https://github.com/Chrxxxxs/graylog_api_client
16
17
  Description-Content-Type: text/markdown
@@ -19,7 +20,7 @@ Description-Content-Type: text/markdown
19
20
  As there are a lot of endpoints not all of them are implemented by now. I started by implementing the GET requests first.
20
21
  Also the API responses all follow very different schemas and an implementation of that into Python is probably not worth the effort which is why the responses from this API are so low-level.
21
22
  Feel free to add endpoints or schemas in PRs if you need them :)
22
- #### Last tested Graylog Version is: 6.3.4
23
+ #### Last tested Graylog Major Version is: 7
23
24
 
24
25
  ### Example Usage
25
26
 
@@ -2,7 +2,7 @@
2
2
  As there are a lot of endpoints not all of them are implemented by now. I started by implementing the GET requests first.
3
3
  Also the API responses all follow very different schemas and an implementation of that into Python is probably not worth the effort which is why the responses from this API are so low-level.
4
4
  Feel free to add endpoints or schemas in PRs if you need them :)
5
- #### Last tested Graylog Version is: 6.3.4
5
+ #### Last tested Graylog Major Version is: 7
6
6
 
7
7
  ### Example Usage
8
8
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "graylog-api-client"
3
- version = "0.1.4"
3
+ version = "0.2.0"
4
4
  description = "A Python wrapper for the Graylog REST API"
5
5
  authors = [
6
6
  {name = "Chrxxxxs",email = "134285960+Chrxxxxs@users.noreply.github.com"}
@@ -23,4 +23,4 @@ class GraylogTimerange:
23
23
  def relative(range: int):
24
24
  ...
25
25
  def absolute(from_time: str, to_time: str):
26
- ...
26
+ ...
@@ -0,0 +1,27 @@
1
+ from ..data_structures import GraylogApiResult
2
+ from ..rest_adapter import RestAdapter
3
+ from typing import Dict
4
+
5
+ class Endpoint_AUTH:
6
+ def __init__(self, rest_adapter: RestAdapter):
7
+ self._rest_adapter = rest_adapter
8
+
9
+ def get_grants_overview(self) -> GraylogApiResult:
10
+ overview = self._rest_adapter.get("authz/grants-overview")
11
+ return overview
12
+
13
+ def get_roles(self, parameters: Dict = None) -> GraylogApiResult:
14
+ roles = self._rest_adapter.get("authz/roles", parameters)
15
+ return roles
16
+
17
+ def get_roles_of_user(self, username: str, parameters: Dict = None) -> GraylogApiResult:
18
+ roles = self._rest_adapter.get(f"authz/roles/user/{username}", parameters)
19
+ return roles
20
+
21
+ def get_role_by_id(self, role_id: str) -> GraylogApiResult:
22
+ roles = self._rest_adapter.get(f"authz/roles/{role_id}")
23
+ return roles
24
+
25
+ def get_assignees_of_role(self, role_id: str, parameters: Dict = None) -> GraylogApiResult:
26
+ assignees = self._rest_adapter.get(f"authz/roles/{role_id}/assignees", parameters)
27
+ return assignees
@@ -0,0 +1,101 @@
1
+ from ...data_structures import GraylogApiResult
2
+ from ...rest_adapter import RestAdapter
3
+ from typing import Union, Dict
4
+ from datetime import datetime
5
+
6
+ class SecurityApp:
7
+ def __init__(self, rest_adapter: RestAdapter):
8
+ self._rest_adapter = rest_adapter
9
+ self.Assets = Assets(rest_adapter)
10
+
11
+ class Assets:
12
+ def __init__(self, rest_adapter: RestAdapter):
13
+ self._rest_adapter = rest_adapter
14
+
15
+ def get_assets(self, *, sort: str = "name", page: int = 1, per_page: int = 15, query: str = "",\
16
+ filters: str = "", direction: str = "asc") -> GraylogApiResult:
17
+ parameters = {
18
+ "sort": sort,
19
+ "page": page,
20
+ "per_page": per_page,
21
+ "query": query,
22
+ "filters": filters,
23
+ "direction": direction,
24
+ }
25
+ result = self._rest_adapter.get("plugins/org.graylog.plugins.securityapp.asset/assets", parameters)
26
+ return result
27
+
28
+ # The API Browser is very confusing/wrong for creating assets
29
+ def create_user_asset(
30
+ self, *, name: str, usernames: list[str], user_ids: list[str] = [], email_addresses: list[str] = [], first_name: str = "", last_name: str = "", category: list[str] = [], priority: int = 1
31
+ ) -> GraylogApiResult:
32
+ """Create a user asset."""
33
+ data = {
34
+ "category": category,
35
+ "details": {
36
+ "type": "user",
37
+ "usernames": usernames,
38
+ "user_ids": user_ids,
39
+ "email_addresses": email_addresses,
40
+ "first_name": first_name,
41
+ "last_name": last_name,
42
+ },
43
+ "name": name,
44
+ "priority": priority,
45
+ }
46
+ result = self._rest_adapter.post("plugins/org.graylog.plugins.securityapp.asset/assets", data=data)
47
+ return result
48
+
49
+ def create_machine_asset(
50
+ self, *, name: str, hostnames: list[str], ip_addresses: list[str] = [], mac_addresses: list[str] = [], category: list[str] = [], custom_fields: dict[str, str] = {},
51
+ description: str = "", geo_info: dict[str, int|str] = {}, owner: str = "", priority: int = 1
52
+ ) -> GraylogApiResult:
53
+ """Create a machine asset."""
54
+ data = {
55
+ "category": category,
56
+ "details": {
57
+ "type": "machine",
58
+ "hostnames": hostnames,
59
+ "ip_addresses": ip_addresses,
60
+ "mac_addresses": mac_addresses,
61
+ "custom_fields": custom_fields,
62
+ "description": description,
63
+ "geo_info": geo_info,
64
+ "owner": owner,
65
+ },
66
+ "name": name,
67
+ "priority": priority,
68
+ }
69
+ result = self._rest_adapter.post("plugins/org.graylog.plugins.securityapp.asset/assets", data=data)
70
+ return result
71
+
72
+ def add_categories_to_multiple_assets(self, *, asset_ids: list[str], categories: list[str]) -> GraylogApiResult:
73
+ # Untested
74
+ data = {
75
+ "asset_ids": asset_ids,
76
+ "categories": categories,
77
+ }
78
+ result = self._rest_adapter.put("plugins/org.graylog.plugins.securityapp.asset/assets/bulk/categories_add", data=data)
79
+ return result
80
+
81
+ def get_asset_by_id_detailed(self, asset_id: str) -> GraylogApiResult:
82
+ result = self._rest_adapter.get(
83
+ f"plugins/org.graylog.plugins.securityapp.asset/assets/{asset_id}"
84
+ )
85
+ return result
86
+
87
+ def edit_asset_by_id(self, asset_id: str, **updates) -> GraylogApiResult:
88
+ """Edit the asset with the given ID by passing the updates as keyword arguments."""
89
+ current_state = self.get_asset_by_id_detailed(asset_id)
90
+ payload = current_state.data
91
+ payload.update(updates)
92
+ result = self._rest_adapter.put(
93
+ f"plugins/org.graylog.plugins.securityapp.asset/assets/{asset_id}", data=payload
94
+ )
95
+ return result
96
+
97
+ def delete_asset_by_id(self, asset_id: str) -> GraylogApiResult:
98
+ result = self._rest_adapter.delete(
99
+ f"plugins/org.graylog.plugins.securityapp.asset/assets/{asset_id}"
100
+ )
101
+ return result
@@ -0,0 +1,7 @@
1
+ from ...rest_adapter import RestAdapter
2
+ from .SecurityApp import SecurityApp
3
+
4
+ class Plugins:
5
+ def __init__(self, rest_adapter: RestAdapter):
6
+ self._rest_adapter = rest_adapter
7
+ self.SecurityApp = SecurityApp(rest_adapter)
@@ -1,8 +1,11 @@
1
1
  import logging
2
+ from datetime import datetime
2
3
  from typing import List, Literal, Union, Dict
3
4
 
4
5
  from .rest_adapter import RestAdapter
5
6
  from .data_structures import GraylogApiResult
7
+ from .endpoints.authz import Endpoint_AUTH
8
+ from .endpoints.plugins import Plugins
6
9
 
7
10
 
8
11
  class GraylogAPI:
@@ -15,27 +18,29 @@ class GraylogAPI:
15
18
  :param logger: The logger being used by the Adapter, will use a new one if none is given, defaults to None
16
19
  """
17
20
  self._rest_adapter = RestAdapter(host=host, api_key=api_key, ssl_verify=ssl_verify, logger=logger)
21
+ self.authz = Endpoint_AUTH(rest_adapter=self._rest_adapter)
22
+ self.plugins = Plugins(rest_adapter=self._rest_adapter)
18
23
 
19
24
  # The endpoints grouped like they are in the api-browser
20
25
  # /authz Authorization
21
26
  def get_auth_grants_overview(self) -> GraylogApiResult:
22
- overview = self._rest_adapter.get("authz/grants-overview")
27
+ overview = self.authz.get_grants_overview()
23
28
  return overview
24
29
 
25
30
  def get_auth_roles(self, parameters: Dict = None) -> GraylogApiResult:
26
- roles = self._rest_adapter.get("authz/roles", parameters)
31
+ roles = self.authz.get_roles(parameters)
27
32
  return roles
28
33
 
29
34
  def get_auth_roles_of_user(self, username: str, parameters: Dict = None) -> GraylogApiResult:
30
- roles = self._rest_adapter.get(f"authz/roles/user/{username}", parameters)
35
+ roles = self.authz.get_roles_of_user(username, parameters)
31
36
  return roles
32
37
 
33
38
  def get_auth_role_by_id(self, role_id: str) -> GraylogApiResult:
34
- roles = self._rest_adapter.get(f"authz/roles/{role_id}")
39
+ roles = self.authz.get_role_by_id(role_id)
35
40
  return roles
36
41
 
37
42
  def get_auth_assignees_of_role(self, role_id: str, parameters: Dict = None) -> GraylogApiResult:
38
- assignees = self._rest_adapter.get(f"authz/roles/{role_id}/assignees", parameters)
43
+ assignees = self.authz.get_assignees_of_role(role_id, parameters)
39
44
  return assignees
40
45
 
41
46
  # /ca
@@ -37,7 +37,7 @@ class RestAdapter:
37
37
  """
38
38
  url = self.host + endpoint
39
39
  log_line_pre = f"method={method}, url={url}, parameters={parameters}"
40
- log_line_post = ', '.join((log_line_pre, "success={}, status_code={}, message={}"))
40
+ log_line_post = "{}, success={}, status_code={}, message={}"
41
41
  try:
42
42
  self._logger.debug(msg=log_line_pre)
43
43
  response = self._session.request(method=method, url=url, verify=self._ssl_verify, params=parameters, json=data)
@@ -50,10 +50,10 @@ class RestAdapter:
50
50
  else:
51
51
  data_out = None
52
52
  except (ValueError, requests.exceptions.JSONDecodeError) as e:
53
- self._logger.error(msg=log_line_post.format(False, None, e))
53
+ self._logger.error(msg=log_line_post.format(log_line_pre, False, None, e))
54
54
  raise GraylogApiException("Bad JSON in response") from e
55
55
  is_success = 299 >= response.status_code >= 200 # OK
56
- log_line = log_line_post.format(is_success, response.status_code, response.reason)
56
+ log_line = log_line_post.format(log_line_pre, is_success, response.status_code, response.reason)
57
57
  if is_success:
58
58
  self._logger.debug(msg=log_line)
59
59
  return GraylogApiResult(status_code=response.status_code, message=response.reason, data=data_out)