zscams 2.0.10__py3-none-any.whl → 2.0.12__py3-none-any.whl

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.
zscams/__init__.py CHANGED
@@ -1,3 +0,0 @@
1
- from .deps import ensure_native_deps
2
-
3
- ensure_native_deps()
zscams/__main__.py CHANGED
@@ -2,14 +2,14 @@
2
2
  Main entry point for ZSCAMs Agent
3
3
  """
4
4
 
5
- import asyncio
6
5
  import sys
7
- import os
6
+ import asyncio
8
7
  from zscams.agent.src.core.backend.bootstrap import bootstrap
9
8
  from zscams.agent.src.core.backend.unbootstrap import unbootstrap
10
9
  from zscams.agent.src.core.backend.update_machine_info import update_machine_info
11
10
  from zscams.agent.src.support.logger import get_logger
12
11
  from zscams.agent import init_parser, ensure_bootstrapped, run
12
+ from zscams.agent.src.support.os import running_as_root
13
13
 
14
14
  logger = get_logger("ZSCAMs")
15
15
 
@@ -20,7 +20,7 @@ def main():
20
20
 
21
21
  if args.bootstrap:
22
22
  try:
23
- if os.geteuid() != 0:
23
+ if not running_as_root():
24
24
  logger.error("You are NOT running as root.")
25
25
  sys.exit(1)
26
26
  bootstrap()
@@ -35,7 +35,7 @@ def main():
35
35
 
36
36
  if args.unbootstrap:
37
37
  try:
38
- if os.geteuid() != 0:
38
+ if not running_as_root():
39
39
  logger.error("You are NOT running as root.")
40
40
  sys.exit(1)
41
41
  unbootstrap()
zscams/agent/__init__.py CHANGED
@@ -2,7 +2,7 @@ import os
2
2
  import asyncio
3
3
  import sys
4
4
  import argparse
5
- from zscams.agent.src.support.configuration import get_config, CONFIG_PATH
5
+ from zscams.agent.src.support.configuration import RemoteConfig, config, CONFIG_PATH
6
6
  from zscams.agent.src.support.filesystem import is_file_exists, resolve_path
7
7
  from zscams.agent.src.core.tunnel.tls import create_ssl_context
8
8
  from zscams.agent.src.core.tunnels import start_all_tunnels
@@ -38,8 +38,7 @@ def init_parser():
38
38
  def ensure_bootstrapped():
39
39
  """Ensure the agent is bootstrapped with the backend."""
40
40
 
41
- config = get_config()
42
- remote_cfg = config["remote"]
41
+ remote_cfg = config.get("remote", valtyp=dict)
43
42
  config_dir = os.path.dirname(CONFIG_PATH)
44
43
 
45
44
  files_to_ensure = [
@@ -69,11 +68,12 @@ def ensure_bootstrapped():
69
68
  async def run():
70
69
  """Asynchronous main function to start tunnels and services."""
71
70
 
72
- config = get_config()
73
71
  config_dir = os.path.dirname(CONFIG_PATH)
74
- ssl_context = create_ssl_context(config["remote"], config_dir=config_dir)
75
- remote_host = config["remote"]["host"]
76
- remote_port = config["remote"]["port"]
72
+ ssl_context = create_ssl_context(
73
+ config.get("remote", valtyp=RemoteConfig), config_dir=config_dir
74
+ )
75
+ remote_host = config.get("remote.host")
76
+ remote_port = config.get("remote.port")
77
77
 
78
78
  # Start tunnels and wait for readiness
79
79
  tunnel_tasks = await start_all_tunnels(
@@ -1,8 +1,7 @@
1
1
  ---
2
- backend:
2
+ bootstrap:
3
3
  base_url: http://22.0.122.40:5000/api
4
4
  cache_dir: .cache
5
- bootstrap_info:
6
5
  blacklist_ports: []
7
6
  cert_issuer: ZSCAMs
8
7
  csr:
@@ -19,6 +18,7 @@ forwards:
19
18
  local_port: 4423
20
19
  sni_hostname: monitor-tunnel
21
20
  general:
21
+ install_project_deps: false
22
22
  health_check_interval: 30
23
23
  logging:
24
24
  level: 10
@@ -10,7 +10,7 @@ name="zscams"
10
10
  rcvar="zscams_enable"
11
11
 
12
12
  # Execution command and arguments
13
- command="{ python_exec }"
13
+ command="{python_exec}"
14
14
  command_args="-m zscams"
15
15
 
16
16
  # User to run the process
@@ -1,14 +1,19 @@
1
1
  import os
2
- from pathlib import Path
3
2
  import sysconfig
4
3
  import sys
5
4
  from typing import cast
6
5
  from zscams.agent.src.core.backend.client import backend_client
7
- from zscams.agent.src.support.configuration import reinitialize, CONFIG_PATH
6
+ from zscams.agent.src.support.configuration import ROOT_PATH, config, CONFIG_PATH
8
7
  from zscams.agent.src.support.logger import get_logger
9
- from zscams.agent.src.support.os import create_system_user, install_service, is_freebsd
8
+ from zscams.agent.src.support.os import (
9
+ create_system_user,
10
+ install_service,
11
+ is_freebsd,
12
+ is_linux,
13
+ )
10
14
  from zscams.agent.src.support.ssh import add_to_authorized_keys
11
- from zscams.agent.src.support.cli import prompt
15
+ from zscams.agent.src.support.cli import ensure_config_value, prompt, prompt_auth_info
16
+ from zscams.deps import ensure_native_deps
12
17
 
13
18
  logger = get_logger("bootstrap")
14
19
 
@@ -16,32 +21,46 @@ logger = get_logger("bootstrap")
16
21
  def bootstrap():
17
22
  """Ensure the agent is bootstrapped with the backend."""
18
23
 
24
+ if config.get("general.install_project_deps", default=True):
25
+ ensure_native_deps()
26
+
27
+ ensure_config_value(
28
+ config_key_path="bootstrap.base_url",
29
+ prompt_message="Bootstrap server origin",
30
+ prompt_desc="(E.g. http://10.0.100.100:5000)",
31
+ process_value=lambda v: (
32
+ v.rstrip("/") + "/api"
33
+ if not v.rstrip("/").endswith("api")
34
+ else v.rstrip("/")
35
+ ),
36
+ )
37
+
38
+ ensure_config_value(
39
+ config_key_path="remote.host",
40
+ prompt_message="Remote host IP",
41
+ prompt_desc="(E.g. 10.0.100.100)",
42
+ )
43
+
19
44
  if backend_client.is_bootstrapped():
20
45
  logger.info("Agent ID %s is already bootstrapped.", backend_client.agent_id)
21
46
  return
22
47
 
23
- username = input("Username: ")
24
- totp = input("One Time Password (OTP): ")
25
-
48
+ username, totp = prompt_auth_info()
26
49
  backend_client.login(username, totp)
27
50
 
28
51
  customer_name = prompt(
29
- "Customer Name",
30
52
  "Customer Name",
31
53
  required=True,
32
- startswith="",
33
54
  )
34
55
  connector_name = prompt(
35
56
  "Connector Name",
36
- "Connector Name [Unique name for this connector]",
57
+ "[Unique name for this connector]",
37
58
  required=True,
38
- startswith="",
39
59
  )
40
60
  equipment_type = prompt(
41
61
  "Equipment Type",
42
- "Equipment Type [Can be ZPA|VZEN|PSE]",
43
62
  required=True,
44
- startswith="",
63
+ one_of=["ZPA", "VZEN", "PSE"],
45
64
  )
46
65
  equipment_name = (
47
66
  f"{equipment_type.lower()}-{customer_name.lower()}-{connector_name.lower()}"
@@ -49,7 +68,7 @@ def bootstrap():
49
68
  enforced_id = prompt("Enforced ID", "Enforced Agent ID")
50
69
  if not os.path.exists(CONFIG_PATH):
51
70
  os.remove(CONFIG_PATH)
52
- reinitialize(equipment_name=equipment_name, equipment_type=equipment_type)
71
+ config.reinitialize(equipment_name=equipment_name, equipment_type=equipment_type)
53
72
  cm_info = backend_client.bootstrap(equipment_name, enforced_id or None)
54
73
 
55
74
  sys_user = cast(str, cm_info.get("ssh_user"))
@@ -60,18 +79,19 @@ def bootstrap():
60
79
 
61
80
 
62
81
  def install_zscams_systemd_service(user_to_run_as: str):
82
+ if not is_freebsd() and not is_linux():
83
+ logger.error("Unsupported OS for service installation.")
84
+ return
63
85
 
64
86
  data = {
65
87
  "pythonpath": sysconfig.get_paths()["purelib"],
66
88
  "python_exec": sys.executable,
67
89
  "user_to_run_as": user_to_run_as,
68
90
  }
69
- import zscams
70
91
 
71
- BASE_DIR = Path(zscams.__file__).resolve().parent
72
- filename = "freebsd_service" if is_freebsd() else "linux_service"
73
- template_path = f"{BASE_DIR}/agent/configuration/{filename}.j2"
74
- with open(template_path) as f:
92
+ file_prefix = "freebsd" if is_freebsd() else "linux"
93
+ template_path = ROOT_PATH.joinpath(f"configuration/{file_prefix}_service.j2")
94
+ with open(template_path, encoding="utf-8") as f:
75
95
  template = f.read()
76
96
 
77
97
  rendered_config = template.format(**data)
@@ -5,16 +5,19 @@ import requests
5
5
 
6
6
  from typing import Optional, cast
7
7
 
8
+ from zscams.agent.src.support.os import get_machine_id
9
+
8
10
  from .exceptions import AgentBootstrapError
9
11
 
10
12
  from zscams.agent.src.support.configuration import (
11
13
  CONFIG_PATH,
12
14
  ROOT_PATH,
13
- get_config,
14
- override_config,
15
+ config,
16
+ )
17
+ from zscams.agent.src.support.yaml import (
18
+ resolve_placeholders,
19
+ assert_no_placeholders_left,
15
20
  )
16
- from zscams.agent.src.support.yaml import resolve_placeholders, assert_no_placeholders_left
17
- from zscams.agent.src.support.mac import get_mac_address
18
21
  from zscams.agent.src.support.filesystem import resolve_path, ensure_dir, is_file_exists
19
22
  from zscams.agent.src.support.network import get_local_hostname, get_local_ip_address
20
23
  from zscams.agent.src.support.openssl import (
@@ -30,22 +33,17 @@ class BackendClient:
30
33
  MACHINE_INFO_FILE_NAME = "machine_info.json"
31
34
 
32
35
  def __init__(self):
33
- config = get_config()
34
- self.services_config = config.get("services", [])
35
- self.backend_config = config.get("backend", {})
36
- self.bootstrap_info = config.get("bootstrap_info", {})
37
- self.remote_config = config.get("remote", {})
38
- self.url = self.backend_config.get("base_url")
36
+ self.services_config = config.get("services", [], valtyp=list)
37
+ self.bootstrap_config = config.get("bootstrap", {}, valtyp=dict)
38
+ self.remote_config = config.get("remote", {}, valtyp=dict)
39
+ self.url = self.bootstrap_config.get("base_url")
39
40
  self.session = requests.session()
40
- self.agent_id = get_mac_address()
41
+ self.agent_id = get_machine_id()
41
42
  self.logger = get_logger("backend_client")
42
43
 
43
- self.__machine_cache_path = Path(
44
- os.path.join(
45
- ROOT_PATH.parent,
46
- self.backend_config.get("cache_dir"),
47
- self.MACHINE_INFO_FILE_NAME,
48
- )
44
+ self.__machine_cache_path = ROOT_PATH.parent.parent.joinpath(
45
+ self.bootstrap_config.get("cache_dir", ".cache"),
46
+ self.MACHINE_INFO_FILE_NAME,
49
47
  )
50
48
 
51
49
  def bootstrap(self, equipment_name: str, enforced_id: Optional[str] = None):
@@ -59,8 +57,8 @@ class BackendClient:
59
57
 
60
58
  self.logger.info("bootstrapping agent ID %s", self.agent_id)
61
59
 
62
- csr_info = self.bootstrap_info.get("csr", {})
63
- remote_configs = get_config().get("remote", {})
60
+ csr_info = self.bootstrap_config.get("csr", {})
61
+ remote_configs = config.get("remote", {}, valtyp=dict)
64
62
 
65
63
  private_key_path = cast(
66
64
  str,
@@ -97,9 +95,13 @@ class BackendClient:
97
95
  "equipment_name": equipment_name,
98
96
  "csr": csr,
99
97
  "enforced_id": enforced_id,
100
- "services": [service.get("name") for service in self.services_config if service.get("custom_port_name", None)],
101
- "blacklist_ports": self.bootstrap_info.get("blacklist_ports", []),
102
- "cert_issuer": self.bootstrap_info.get("cert_issuer", None),
98
+ "services": [
99
+ service.get("name")
100
+ for service in self.services_config
101
+ if service.get("custom_port_name", None)
102
+ ],
103
+ "blacklist_ports": self.bootstrap_config.get("blacklist_ports", []),
104
+ "cert_issuer": self.bootstrap_config.get("cert_issuer", None),
103
105
  }
104
106
 
105
107
  self.logger.debug(
@@ -135,6 +137,18 @@ class BackendClient:
135
137
 
136
138
  return customer_machine
137
139
 
140
+ def unbootstrap(self):
141
+ """This will remove the machine from the db and revoke the agent's certificate"""
142
+
143
+ if not self.is_authenticated():
144
+ self.logger.error("Couldn't authenticate")
145
+ raise AgentBootstrapError(
146
+ "Client is not authenticated to unbootstrap. Please login first."
147
+ )
148
+
149
+ res = self._delete(f"agent-bootstrap/{self.agent_id}")
150
+ return res.json().get("response")
151
+
138
152
  def get_machine_info(self, ignore_cache: bool = False):
139
153
  """Get machine information from the backend."""
140
154
 
@@ -170,7 +184,9 @@ class BackendClient:
170
184
 
171
185
  def _write_certificates(self, ca_chain: list[str], cert: str):
172
186
  if cert:
173
- cert_path = os.path.join(ROOT_PATH, self.remote_config.get("client_cert"))
187
+ cert_path = os.path.join(
188
+ str(ROOT_PATH), self.remote_config.get("client_cert")
189
+ )
174
190
 
175
191
  self.logger.info("Writing signed certificate to %s", cert_path)
176
192
  with open(cert_path, "w", encoding="utf-8") as cert_file:
@@ -251,31 +267,35 @@ class BackendClient:
251
267
  return self.session.headers.get("Authorization") is not None
252
268
 
253
269
  def __prepare_path(self, path):
270
+ if self.url is None:
271
+ self.url = self.bootstrap_config.get("base_url")
254
272
  return f"{self.url}/{path.lstrip('/')}"
255
-
273
+
256
274
  def __override_config_services_ports(self, cm_services: list[dict]):
257
275
  self.logger.debug("Overriding configuration services ports...")
258
276
 
259
- config = get_config()
277
+ _config = config.to_dict()
260
278
  custom_ports = {}
261
279
  for cm_service in cm_services:
262
- for cfg_service_idx, cfg_service in enumerate(config.get("services", [])):
280
+ for cfg_service_idx, cfg_service in enumerate(
281
+ config.get("services", [], valtyp=list[dict])
282
+ ):
263
283
  if cm_service.get("name") == cfg_service.get("name"):
264
284
 
265
- if not config["services"][cfg_service_idx]["params"]:
266
- config["services"][cfg_service_idx]["params"] = {}
285
+ if not _config["services"][cfg_service_idx]["params"]:
286
+ _config["services"][cfg_service_idx]["params"] = {}
267
287
 
268
288
  port_field_name = cfg_service.get("custom_port_name", None)
269
289
  if port_field_name:
270
290
  custom_ports[port_field_name] = cm_service.get("port")
271
- config["services"][cfg_service_idx]["params"][port_field_name] = (
272
- cm_service.get("port")
273
- )
274
- resolve_placeholders(config, custom_ports)
275
- assert_no_placeholders_left(config)
291
+ _config["services"][cfg_service_idx]["params"][
292
+ port_field_name
293
+ ] = cm_service.get("port")
294
+ resolve_placeholders(_config, custom_ports)
295
+ assert_no_placeholders_left(_config)
276
296
  self.logger.debug("Done overriding the configurations")
277
297
 
278
- return override_config(config)
298
+ return config.override_config(_config)
279
299
 
280
300
 
281
301
  backend_client = BackendClient()
@@ -1,31 +1,50 @@
1
1
  import os
2
+ from typing import cast
3
+
4
+ from zscams.agent.src.support.cli import prompt_auth_info
5
+
6
+ from .client import backend_client
2
7
  from zscams.agent.src.support.filesystem import resolve_path
3
- from zscams.agent.src.support.configuration import get_config, CONFIG_PATH, ROOT_PATH
8
+ from zscams.agent.src.support.configuration import config, CONFIG_PATH, ROOT_PATH
4
9
  from zscams.agent.src.core.backend.client import BackendClient
5
10
  from zscams.agent.src.support.logger import get_logger
6
- from zscams.agent.src.support.os import remove_service, is_freebsd
11
+ from zscams.agent.src.support.os import remove_service
7
12
 
8
13
  logger = get_logger("Unbootstrap")
9
14
 
10
15
 
11
16
  def unbootstrap():
12
- remote_configs = get_config().get("remote", {})
13
- backend_config = get_config().get("backend", {})
14
- private_key_path = resolve_path(
15
- remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
17
+ username, totp = prompt_auth_info()
18
+ backend_client.login(username, totp)
19
+
20
+ backend_client.unbootstrap()
21
+ logger.info(
22
+ "Unbootstrapped from backend and revoked the certificate.",
23
+ )
24
+
25
+ remove_local_data()
26
+
27
+
28
+ def remove_local_data():
29
+ remote_configs = config.get("remote", default={}, valtyp=dict)
30
+ private_key_path = cast(
31
+ str,
32
+ resolve_path(remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)),
16
33
  )
17
34
 
18
- cert_path = resolve_path(
19
- remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
35
+ cert_path = cast(
36
+ str,
37
+ resolve_path(remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)),
20
38
  )
21
39
 
22
- ca_chain_path = resolve_path(
23
- remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)
40
+ ca_chain_path = cast(
41
+ str,
42
+ resolve_path(remote_configs.get("client_key"), os.path.dirname(CONFIG_PATH)),
24
43
  )
25
44
 
26
45
  cache_path = os.path.join(
27
46
  ROOT_PATH.parent,
28
- backend_config.get("cache_dir"),
47
+ config.get("bootstrap.cache_dir", must_resolve=True, valtyp=str),
29
48
  BackendClient.MACHINE_INFO_FILE_NAME,
30
49
  )
31
50
 
@@ -45,5 +64,6 @@ def unbootstrap():
45
64
  os.remove(cache_path)
46
65
  logger.debug("Removed Machine info")
47
66
 
48
- remove_service("zscams" if is_freebsd() else "zscams.service")
49
- logger.debug("Removed ZSCAMs service")
67
+ removed_service = remove_service("zscams")
68
+ if removed_service:
69
+ logger.debug("Removed ZSCAMs service")
@@ -1,4 +1,5 @@
1
- from typing import Optional, cast
1
+ from typing import Optional, Callable
2
+ from zscams.agent.src.support.configuration import config
2
3
  from zscams.agent.src.support.logger import get_logger
3
4
 
4
5
  logger = get_logger("bootstrap")
@@ -14,13 +15,24 @@ class InvalidFieldException(Exception):
14
15
 
15
16
  def prompt(
16
17
  name: str,
17
- message: str,
18
+ description: Optional[str] = None,
18
19
  required=False,
19
- startswith: Optional[str] = None,
20
+ startswith: Optional[list[str]] = None,
21
+ one_of: Optional[list[str]] = None,
20
22
  fail_on_error=False,
21
23
  retries_count=3,
22
24
  ):
23
- _message = message
25
+ _message = name
26
+
27
+ if description:
28
+ _message += f" {description}"
29
+
30
+ if startswith:
31
+ _message += f" [Must start with {'|'.join(startswith)}]"
32
+
33
+ if one_of:
34
+ _message += f" [Can be {'|'.join(one_of)}]"
35
+
24
36
  if not required:
25
37
  _message += " (Optional)"
26
38
 
@@ -31,20 +43,90 @@ def prompt(
31
43
  if required and not val:
32
44
  if fail_on_error:
33
45
  raise RequiredFieldException(f"Missing {name}")
34
- logger.error(f"{name} is required..")
46
+ logger.error("%s is required..", name)
35
47
  retries_count -= 1
36
48
  return prompt(
37
- name, message, required, startswith, fail_on_error=retries_count <= 0
49
+ name, description, required, startswith, fail_on_error=retries_count <= 0
38
50
  )
39
51
 
40
- if startswith is not None and not val.startswith(startswith):
41
- error_mesagge = f"The value has to start with {startswith}"
42
- if fail_on_error:
43
- raise InvalidFieldException(error_mesagge)
44
- logger.error(error_mesagge)
45
- retries_count -= 1
46
- return prompt(
47
- name, message, required, startswith, fail_on_error=retries_count <= 0
48
- )
52
+ if startswith:
53
+ matches = next((start for start in startswith if val.startswith(start)), False)
54
+ if not matches:
55
+ error_mesagge = f"The value has to start with one of {startswith}"
56
+ if fail_on_error:
57
+ raise InvalidFieldException(error_mesagge)
58
+ logger.error(error_mesagge)
59
+ retries_count -= 1
60
+ return prompt(
61
+ name,
62
+ description,
63
+ required,
64
+ one_of=one_of,
65
+ startswith=startswith,
66
+ fail_on_error=retries_count <= 0,
67
+ )
68
+
69
+ if one_of:
70
+ matches = next((opt for opt in one_of if val == opt), False)
71
+ if not matches:
72
+ error_mesagge = f"The value has to be one of {one_of}"
73
+ if fail_on_error:
74
+ raise InvalidFieldException(error_mesagge)
75
+ logger.error(error_mesagge)
76
+ retries_count -= 1
77
+ return prompt(
78
+ name,
79
+ description,
80
+ required,
81
+ one_of=one_of,
82
+ startswith=startswith,
83
+ fail_on_error=retries_count <= 0,
84
+ )
49
85
 
50
86
  return val
87
+
88
+
89
+ def prompt_auth_info():
90
+ """Prompt for username and totp"""
91
+
92
+ username = prompt("Username", required=True)
93
+ totp = prompt("One Time Password (OTP)", required=True)
94
+ return username, totp
95
+
96
+
97
+ def ensure_config_value(
98
+ config_key_path: str,
99
+ prompt_message: str,
100
+ prompt_desc: Optional[str] = None,
101
+ process_value: Optional[Callable] = None,
102
+ ):
103
+ """
104
+ Ensures a configuration value is set. If not, prompts the user and sets it.
105
+
106
+ Args:
107
+ config_key_path (list): List of keys representing the path in the config dict.
108
+ prompt_message (str): Message to display in the prompt.
109
+ default_value (str, optional): Default value for the prompt.
110
+ process_value (callable, optional): Function to process the input value.
111
+ append_path_segment (str, optional): Path segment to append if needed.
112
+ """
113
+ if config.has(config_key_path):
114
+ return
115
+
116
+ value = prompt(prompt_message, prompt_desc, required=True)
117
+
118
+ if process_value:
119
+ value = process_value(value)
120
+
121
+ _config = config.to_dict()
122
+
123
+ d = _config
124
+ keys_list = config_key_path.split(".")
125
+ for key in keys_list[:-1]:
126
+ if key not in d:
127
+ d[key] = {}
128
+ d = d[key]
129
+
130
+ d[keys_list[-1]] = value
131
+
132
+ config.override_config(_config)
@@ -3,19 +3,119 @@ Configuration loader module
3
3
  """
4
4
 
5
5
  import os
6
+ import sys
6
7
  from pathlib import Path
7
- from typing import TypedDict
8
- import json
8
+ from typing import Optional, Type, TypeVar, TypedDict, cast
9
9
  import yaml
10
- from zscams.agent.src.support.yaml import YamlIndentedListsDumper, resolve_placeholders
11
10
 
11
+ import zscams
12
+ from zscams.agent.src.support.yaml import YamlIndentedListsDumper, resolve_placeholders
12
13
 
13
- config = {}
14
-
15
- ROOT_PATH = Path(__file__).parent.parent.parent
16
-
14
+ ROOT_PATH = Path(zscams.__file__).resolve().parent.joinpath("agent")
17
15
  CONFIG_PATH = os.path.join(ROOT_PATH.absolute(), "config.yaml")
18
16
 
17
+ GetReturnT = TypeVar("T")
18
+
19
+
20
+ class MissingConfiguration(BaseException):
21
+ """Error when the requisted key is not found"""
22
+
23
+
24
+ class Configuration:
25
+ """Class to get/set configurations and handle the configurations file."""
26
+
27
+ __instance = None
28
+ __config = {}
29
+
30
+ def __init__(self):
31
+ self.__load_config()
32
+
33
+ def __new__(cls):
34
+ if cls.__instance is None:
35
+ cls.__instance = super().__new__(cls)
36
+ return cls.__instance
37
+
38
+ def __load_config(self):
39
+ try:
40
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
41
+ self.__config = yaml.safe_load(f)
42
+ except FileNotFoundError:
43
+ print(
44
+ f"Can't find configurations file. Make sure you have it by running `cp '{ROOT_PATH.joinpath('configuration/config.j2')}' '{CONFIG_PATH}'`",
45
+ )
46
+ sys.exit(1)
47
+
48
+ def override_config(self, new_config: dict):
49
+ """
50
+ Override the existing configuration with a new one.
51
+ Args:
52
+ new_config (dict): New configuration dictionary to override the existing one.
53
+ """
54
+ self.__config = new_config
55
+
56
+ with open(CONFIG_PATH, "w", encoding="utf-8") as file:
57
+ yaml.dump(
58
+ self.__config,
59
+ file,
60
+ Dumper=YamlIndentedListsDumper,
61
+ default_flow_style=False,
62
+ explicit_start=True,
63
+ )
64
+
65
+ def to_dict(self):
66
+ return self.__config
67
+
68
+ def get(
69
+ self,
70
+ key: str,
71
+ default: Optional[GetReturnT] = None,
72
+ must_resolve=False,
73
+ valtyp: Type[GetReturnT] = str,
74
+ ) -> GetReturnT:
75
+ """
76
+ Returns the configuration value.
77
+
78
+ Args:
79
+ key (str): The configurations dotnotation key to get the value of.
80
+ default (Optional[T]): The default value if the configurations wasn't found.
81
+ must_resolve (bool): Wheither to throw an error if the value wasn't found and if there was no default.
82
+ valtyp (Type[T]): The expected type of the return value.
83
+ """
84
+
85
+ keys = key.split(".")
86
+ val = self.__config
87
+
88
+ for k in keys:
89
+ if isinstance(val, dict) and k in val:
90
+ val = val.get(k, default)
91
+ elif isinstance(val, list) and int(k) < len(val):
92
+ val = val[int(k)]
93
+ elif default is not None:
94
+ val = default
95
+ break
96
+ elif must_resolve:
97
+ raise MissingConfiguration
98
+ else:
99
+ val = None
100
+ break
101
+
102
+ return cast(GetReturnT, val)
103
+
104
+ def has(self, key: str):
105
+ try:
106
+ self.get(key, must_resolve=True)
107
+ return True
108
+ except MissingConfiguration:
109
+ return False
110
+
111
+ def reinitialize(self, **kwargs):
112
+ """Regenerate the config template with new values"""
113
+ template_path = ROOT_PATH.joinpath("configuration/config.j2")
114
+ with open(template_path, encoding="utf-8") as f:
115
+ template = yaml.safe_load(f)
116
+ resolve_placeholders(template, kwargs)
117
+ self.override_config(template)
118
+
19
119
 
20
120
  class RemoteConfig(TypedDict):
21
121
  """Type definition for remote configuration."""
@@ -27,60 +127,5 @@ class RemoteConfig(TypedDict):
27
127
  ca_cert: str
28
128
  ca_chain: str
29
129
 
30
- def reinitialize(**kwargs):
31
- import zscams
32
- BASE_DIR = Path(zscams.__file__).resolve().parent
33
- template_path = f"{BASE_DIR}/agent/configuration/config.j2"
34
- with open(template_path) as f:
35
- template = yaml.safe_load(f)
36
- resolve_placeholders(template, kwargs)
37
- override_config(template)
38
-
39
- def save_config(data):
40
- """Save the YAML config back to disk."""
41
- with open(CONFIG_PATH, "w", encoding="utf-8") as f:
42
- yaml.safe_dump(data, f, default_flow_style=False)
43
-
44
-
45
- def load_config():
46
- """
47
- Load and parse the YAML configuration file.
48
-
49
- Returns:
50
- dict: Configuration dictionary containing remote settings and forwards.
51
- """
52
- with open(CONFIG_PATH, "r", encoding="utf-8") as f:
53
- config = yaml.safe_load(f)
54
- return config
55
-
56
-
57
- def get_config():
58
- """
59
- Get the loaded configuration.
60
-
61
- Returns:
62
- dict: Configuration dictionary containing remote settings and forwards.
63
- """
64
-
65
- if not config:
66
- return load_config()
67
-
68
- return config
69
-
70
-
71
- def override_config(new_config: dict):
72
- """
73
- Override the existing configuration with a new one.
74
- Args:
75
- new_config (dict): New configuration dictionary to override the existing one.
76
- """
77
- config = new_config
78
-
79
- with open(CONFIG_PATH, "w", encoding="utf-8") as file:
80
- yaml.dump(
81
- config,
82
- file,
83
- Dumper=YamlIndentedListsDumper,
84
- default_flow_style=False,
85
- explicit_start=True,
86
- )
130
+
131
+ config = Configuration()
@@ -10,7 +10,7 @@ import platform
10
10
  from typing import Dict
11
11
  from logging import Logger
12
12
 
13
- from zscams.agent.src.support.configuration import get_config
13
+ from zscams.agent.src.support.configuration import config
14
14
 
15
15
  loggers: Dict[str, Logger] = {}
16
16
 
@@ -66,7 +66,8 @@ def get_logger(name: str) -> Logger:
66
66
  logger = logging.getLogger(
67
67
  f"ZSCAMs - {name}" if name.lower().find("zscams") == -1 else name
68
68
  )
69
- logger.setLevel(get_config().get("logging", {}).get("level", 10))
69
+ logging_level = config.get("logging.level", default=10, valtyp=int)
70
+ logger.setLevel(logging_level)
70
71
  logger.propagate = False # Prevent duplicate logs
71
72
 
72
73
  # Cleanup handlers if any
@@ -75,7 +76,7 @@ def get_logger(name: str) -> Logger:
75
76
 
76
77
  # -------- Console Handler -------- #
77
78
  console_handler = logging.StreamHandler(sys.stdout)
78
- console_handler.setLevel(get_config().get("logging", {}).get("level", 10))
79
+ console_handler.setLevel(logging_level)
79
80
 
80
81
  console_formatter = ColorFormatter(
81
82
  fmt="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
@@ -1,9 +1,10 @@
1
1
  import os
2
+ from pathlib import Path
2
3
  import sys
3
4
  import subprocess
4
5
  import platform
5
- import subprocess
6
6
  from zscams.agent.src.support.logger import get_logger
7
+ from zscams.agent.src.support.mac import get_mac_address
7
8
 
8
9
  from .filesystem import write_to_file
9
10
 
@@ -11,9 +12,14 @@ logger = get_logger("os_support")
11
12
 
12
13
 
13
14
  def is_linux():
14
- if sys.platform.lower().startswith("linux"):
15
- return True
16
- return False
15
+ return sys.platform.lower().startswith("linux")
16
+
17
+
18
+ def is_freebsd():
19
+ return (
20
+ platform.system().lower() == "freebsd"
21
+ or platform.system().lower() == "zscaleros"
22
+ )
17
23
 
18
24
 
19
25
  def system_user_exists(username: str):
@@ -30,16 +36,9 @@ def system_user_exists(username: str):
30
36
  return False
31
37
 
32
38
 
33
- def is_freebsd():
34
- return (
35
- platform.system().lower() == "freebsd"
36
- or platform.system().lower() == "zscaleros"
37
- )
38
-
39
-
40
39
  def create_system_user(username: str):
41
40
  # Support both Linux and FreeBSD
42
- if not (platform.system() == "Linux" or is_freebsd()):
41
+ if not (is_linux() or is_freebsd()):
43
42
  logger.error(
44
43
  "Error creating system user: This script is intended to run on Linux or FreeBSD systems."
45
44
  )
@@ -146,12 +145,15 @@ def remove_service(service_name: str):
146
145
  # Standardize name (remove .service suffix if present for consistency)
147
146
  clean_name = service_name.replace(".service", "")
148
147
 
148
+ if not is_linux() or not is_freebsd():
149
+ logger.error("Unsupported OS for service removal.")
150
+ return False
151
+
149
152
  if is_linux():
150
153
  _remove_systemd_service(clean_name)
151
154
  elif is_freebsd():
152
155
  _remove_rc_service(clean_name)
153
- else:
154
- logger.error("Unsupported OS for service removal.")
156
+ return True
155
157
 
156
158
 
157
159
  def _remove_systemd_service(name: str):
@@ -187,3 +189,16 @@ def _remove_rc_service(name: str):
187
189
 
188
190
  except subprocess.CalledProcessError as e:
189
191
  logger.error("Failed to remove FreeBSD service: %s", e)
192
+
193
+
194
+ def running_as_root():
195
+ if not is_linux() and not is_freebsd():
196
+ return True
197
+ return os.geteuid() == 0
198
+
199
+
200
+ def get_machine_id():
201
+ try:
202
+ return Path("/etc/machine-id").read_text("utf-8").strip()
203
+ except FileNotFoundError:
204
+ return get_mac_address()
@@ -1,4 +1,3 @@
1
- from zscams.agent.src.support.configuration import get_config
2
1
  from zscams.agent.src.support.filesystem import append_to_file
3
2
  from zscams.agent.src.support.logger import get_logger
4
3
 
@@ -6,18 +5,18 @@ from zscams.agent.src.support.logger import get_logger
6
5
  logger = get_logger("ssh_support")
7
6
 
8
7
 
9
- def add_to_known_hosts(hostname: str, pub_key: str):
8
+ def add_to_known_hosts(user: str, hostname: str, pub_key: str):
10
9
  logger.debug("Appending '%s' to known hosts...", pub_key)
11
10
  append_to_file(
12
- get_config().get("ssh", {}).get("known_hosts_file_path"),
11
+ f"/home/{user}/.ssh/known_hosts",
13
12
  f"{hostname} {pub_key}\n",
14
13
  )
15
14
  logger.debug("Appended key to known hosts")
16
15
 
17
16
 
18
- def add_to_authorized_keys(user, pub_key):
17
+ def add_to_authorized_keys(user: str, pub_key: str):
19
18
  logger.debug(f"Appending to public key to {user}")
20
- key = pub_key.split(' ')[1] if len(pub_key.split(' ')) >= 2 else pub_key
19
+ key = pub_key.split(" ")[1] if len(pub_key.split(" ")) >= 2 else pub_key
21
20
  append_to_file(
22
21
  f"/home/{user}/.ssh/authorized_keys",
23
22
  f"ssh-rsa {key} zscams@orangecyberdefense\n",
@@ -5,7 +5,8 @@ class YamlIndentedListsDumper(yaml.Dumper):
5
5
  def increase_indent(self, flow=False, indentless=False):
6
6
  return super(YamlIndentedListsDumper, self).increase_indent(flow, False)
7
7
 
8
- def resolve_placeholders( obj, values: dict[str, int]):
8
+
9
+ def resolve_placeholders(obj, values: dict[str, int]):
9
10
  """
10
11
  Recursively replace '{key}' strings with values[key]
11
12
  in dicts, lists, and strings.
@@ -26,6 +27,8 @@ def resolve_placeholders( obj, values: dict[str, int]):
26
27
  return obj
27
28
 
28
29
  return obj
30
+
31
+
29
32
  def assert_no_placeholders_left(obj):
30
33
  if isinstance(obj, dict):
31
34
  for v in obj.values():
@@ -34,4 +37,5 @@ def assert_no_placeholders_left(obj):
34
37
  for v in obj:
35
38
  assert_no_placeholders_left(v)
36
39
  elif isinstance(obj, str) and obj.startswith("{") and obj.endswith("}"):
37
- raise ValueError(f"Unresolved placeholder: {obj}")
40
+ raise ValueError(f"Unresolved placeholder: {obj}")
41
+
zscams/deps.py CHANGED
@@ -1,9 +1,10 @@
1
1
  import subprocess
2
2
  import sys
3
- import platform
4
3
  import os
5
4
  import importlib.util
6
5
 
6
+ from zscams.agent.src.support.os import is_freebsd
7
+
7
8
 
8
9
  def is_installed(name):
9
10
  return importlib.util.find_spec(name) is not None
@@ -11,10 +12,7 @@ def is_installed(name):
11
12
 
12
13
  def ensure_native_deps():
13
14
  # Only run this logic on FreeBSD
14
- if (
15
- platform.system().lower() != "freebsd"
16
- and platform.system().lower() != "zscaleros"
17
- ):
15
+ if not is_freebsd():
18
16
  # --- Linux/Standard Path ---
19
17
  # On Linux, we just use pip to install the missing pieces
20
18
  # We use 'PyYAML' instead of 'yaml' for pip
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscams
3
- Version: 2.0.10
3
+ Version: 2.0.12
4
4
  Summary: Async TLS tunnel client with SNI routing, auto-reconnect, and health checks
5
5
  Author: OCD - Cairo Software Team
6
6
  Maintainer: OCD - Cairo Software Team
@@ -1,18 +1,17 @@
1
- zscams/__init__.py,sha256=xAeDJU2cSBrdspt5aVRTYIzAbGSGvn-CtGV4qc7moQ0,59
2
- zscams/__main__.py,sha256=-vjCPBc7n_M3Gq7GRu5TWhdxJrPcU_NV1sZEXvUZU58,1432
3
- zscams/agent/__init__.py,sha256=zGhrb1drq8BP9uvuXeq-L8HhtfWW7s-_1VjVJ-jaykY,3105
1
+ zscams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ zscams/__main__.py,sha256=TcHhPfv7zHxD1orIDbv5Tr5WB10wq7nGNnifVr7SqPg,1486
3
+ zscams/agent/__init__.py,sha256=F1GZKevu-XdgWdT5mP-PnjWDdbgAnsaCDFtoPS7RtEo,3121
4
4
  zscams/agent/certificates/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
- zscams/agent/config.yaml,sha256=0DuIENHlSoo6nRg4Xa0ZljHGgDGQiWSEX8b_uRMtpS0,2542
6
- zscams/agent/configuration/config.j2,sha256=95lUo1oo_EExGCHIhlBnaeK0cwmr9gUXfh7TYP9WYdg,2633
7
- zscams/agent/configuration/freebsd_service.j2,sha256=d0Nh6WJFGw7PhO_dfUjECx2NXTGagalvuIE0aulXIYE,309
5
+ zscams/agent/configuration/config.j2,sha256=znK-UvRB3-PiEOUPrFuAYDbkAA-zog-Js-et4-97rY4,2649
6
+ zscams/agent/configuration/freebsd_service.j2,sha256=LHW1bEz0ky2aasYBy1JpreAzplzqJ53z8SNbDElD4y0,307
8
7
  zscams/agent/configuration/linux_service.j2,sha256=UOrGrXvBK2mFit_b3QxUAKgZhG-dmKV8-45bksxL4NE,225
9
8
  zscams/agent/keys/autoport.key,sha256=hZBmtw_nLsZwe11LYlwLL-P_blQ_qpUDpFwvqOZDZFE,1679
10
9
  zscams/agent/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
11
10
  zscams/agent/src/core/__init__.py,sha256=CEDwvbxojtNZOfOOFBj-URg4Q0KB0cq9AqIiD0uzPic,24
12
- zscams/agent/src/core/backend/bootstrap.py,sha256=ELkDtvNa_Bn4JO4JWgW9Y0pu0NVvAM6bEkgFyXJMon0,2541
13
- zscams/agent/src/core/backend/client.py,sha256=xZw2GuOVw6Ek-QI9OaUO4rqzIrs4xGwiPizBLCH89fo,10123
11
+ zscams/agent/src/core/backend/bootstrap.py,sha256=oeSERVRo6CqXWraDm2z-JUpasjmObeBDhEQqlSVE_FQ,3145
12
+ zscams/agent/src/core/backend/client.py,sha256=QFMqG58EUzYJ1ODsqU6I4fxhQAGvgS1xywA30EbKpq4,10727
14
13
  zscams/agent/src/core/backend/exceptions.py,sha256=osMbVb_ZGvrGbw5cOCMG1s4yBLukJl7T8TITCcVPyXA,383
15
- zscams/agent/src/core/backend/unbootstrap.py,sha256=ULY78KGuv-Pcz69L6PyhU_4_xRaMZCrrln1TdejjzBE,1554
14
+ zscams/agent/src/core/backend/unbootstrap.py,sha256=PZAN_Bgf26iEJoljCIs0cftCyC0lqPkgThjpaK_i0zU,1978
16
15
  zscams/agent/src/core/backend/update_machine_info.py,sha256=9chBdvsLeLVf5DsvSHiUO9xQpXSbDgqhdnrUwxyoKUM,474
17
16
  zscams/agent/src/core/prerequisites.py,sha256=5OlXBEg8FaYp6LXjJHtbdcpRaMywR-DBDyvDr_OiVdA,1286
18
17
  zscams/agent/src/core/service_health_check.py,sha256=9VUWQitXcDEwLcHTTeequi6om98OXN-JIIMZCCH5y4A,1733
@@ -25,18 +24,18 @@ zscams/agent/src/services/reverse_ssh.py,sha256=LUbl7FwKltN2irQ-lAsECm-JMr0PRlgb
25
24
  zscams/agent/src/services/ssh_forwarder.py,sha256=vl3afyWxvYu114o5PTQpg3aok9oaLdVfIwydVxE5bUo,2446
26
25
  zscams/agent/src/services/system_monitor.py,sha256=caGexjOD0eH7GVDdQQIBdTlMpYema_YIUEo3F-fG1vM,7526
27
26
  zscams/agent/src/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
28
- zscams/agent/src/support/cli.py,sha256=6Y0zokhwhWcsmjEosGqq_kMqF3tn4juPpsBmQax1Oyc,1221
29
- zscams/agent/src/support/configuration.py,sha256=hnJhvJLdL5ElZITDFLybTOHVACBHttWgy8sXhmu75Wc,2025
27
+ zscams/agent/src/support/cli.py,sha256=QexHTsEFSRy3AY-29m0Q-0msL4fxooRUJdIN0vAT_WI,3641
28
+ zscams/agent/src/support/configuration.py,sha256=3jGtXdGBPE2zKfMb2T85837YJOHTALhP_r8W__ej7qs,3761
30
29
  zscams/agent/src/support/filesystem.py,sha256=e2p2xWxitLkTclyVgmDC-2DGROBwowves7dlm0S47Hw,1719
31
- zscams/agent/src/support/logger.py,sha256=rKjAVO6vDeXYTM9__q2lcc1PBiJoZUqLzry8yMf71vY,2347
30
+ zscams/agent/src/support/logger.py,sha256=cKmCqy2dSOJk7kivs9QPyop7bLa71619ODNylS27z6M,2345
32
31
  zscams/agent/src/support/mac.py,sha256=XVKc5YAYLu4a-5VrMhcwgkMNnP2u6itK3cx-Oxnx4IA,453
33
32
  zscams/agent/src/support/network.py,sha256=VwVVNqykZxvrTPwPYQ3sSVMc_Z2XUwASlo_kd_wdGDs,1453
34
33
  zscams/agent/src/support/openssl.py,sha256=jLSv8ajIw1YfNdBhz4KSvNp-cARLXY9-7qdzne9Zca4,3429
35
- zscams/agent/src/support/os.py,sha256=QxAfj8nhC6EIpN14snIsjF5c2i8tWhtCS4DzWIZBSJc,6691
36
- zscams/agent/src/support/ssh.py,sha256=J9-XsVc6fGdcTN9CsfmgDRaMnfaWluDUaPGug7BVl6Q,812
37
- zscams/agent/src/support/yaml.py,sha256=bKsQzXHAgjCxkGzPR8bgaUPB-QHMR3AMEVuvn4RRpnA,1188
38
- zscams/deps.py,sha256=D2WEI7dLoMnPl_OL7NOSXe-k25p1FFeeGsY1Mh7IIEw,3630
39
- zscams-2.0.10.dist-info/METADATA,sha256=JoWXxVqzag06DUBG1LpxVPg2eQrC28jyMZj5l6zAOTM,6806
40
- zscams-2.0.10.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
41
- zscams-2.0.10.dist-info/entry_points.txt,sha256=IXiMYjEq4q0tUiD9O7eCWhqKBuOssXrMW42siTBAgG8,47
42
- zscams-2.0.10.dist-info/RECORD,,
34
+ zscams/agent/src/support/os.py,sha256=EhDy5mMyZsDFK_eL_qot5l2e94r3RODVLh2eX2BvONg,7054
35
+ zscams/agent/src/support/ssh.py,sha256=5qJpKIIiidG1r9AMeAIfb4c4eGOV4MExSVmQUgAuVzs,747
36
+ zscams/agent/src/support/yaml.py,sha256=7NXPqj-v_RUif3fLfErNwSUJ-Y-so0GCFZ5aIiU96GQ,1192
37
+ zscams/deps.py,sha256=9xbpgq77oTch-Nv_99QQtkyO3a96JxqFjUH_2d5zt4Q,3575
38
+ zscams-2.0.12.dist-info/METADATA,sha256=ChVB7wedYEAGnl3H8u2Qzor8OxKStTpov5oVeWuyFZY,6806
39
+ zscams-2.0.12.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
40
+ zscams-2.0.12.dist-info/entry_points.txt,sha256=IXiMYjEq4q0tUiD9O7eCWhqKBuOssXrMW42siTBAgG8,47
41
+ zscams-2.0.12.dist-info/RECORD,,
zscams/agent/config.yaml DELETED
@@ -1,103 +0,0 @@
1
- ---
2
- backend:
3
- base_url: http://22.0.122.40:5000/api
4
- cache_dir: .cache
5
- bootstrap_info:
6
- blacklist_ports: []
7
- cert_issuer: ZSCAMs
8
- csr:
9
- country: FR
10
- default_common_name: zscams-agent.orangecyberdefense.com
11
- locality: Paris
12
- organization: zscams-agent.orangecyberdefense.com
13
- state: Ile-de-France
14
- forwards:
15
- - description: Forward to SSH backend
16
- local_port: 4422
17
- sni_hostname: ssh-tunnel
18
- - description: Forward to Seculogs
19
- local_port: 4423
20
- sni_hostname: monitor-tunnel
21
- general:
22
- health_check_interval: 30
23
- logging:
24
- level: 10
25
- remote:
26
- ca_cert: certificates/ca_chain.pem
27
- ca_chain: certificates/ca_chain.pem
28
- client_cert: certificates/client.crt
29
- client_key: certificates/client.key
30
- host: 81.255.240.214
31
- port: 443
32
- verify_cert: false
33
- services:
34
- - args: []
35
- custom_port_name: reverse_port
36
- health:
37
- host: localhost
38
- port: 22
39
- name: Reverse SSH
40
- params:
41
- local_port: 4422
42
- private_key: keys/autoport.key
43
- remote_host: localhost
44
- reverse_port: 10000
45
- server_ssh_user: ssh_user
46
- ssh_options:
47
- - -o LogLevel=error
48
- - -o UserKnownHostsFile=/dev/null
49
- - -o StrictHostKeyChecking=no
50
- - -o ServerAliveInterval=30
51
- - -o ServerAliveCountMax=2
52
- port: 22
53
- prerequisites:
54
- files:
55
- - zscams/agent/keys/autoport.key
56
- ports:
57
- - 22
58
- services: []
59
- script: src/services/reverse_ssh.py
60
- - args: []
61
- custom_port_name: forwarder_port
62
- health:
63
- host: localhost
64
- port: 10001
65
- name: Monitor Forwarder
66
- params:
67
- forwarder_port: 10001
68
- local_port: 4423
69
- private_key: zscams/agent/keys/autoport.key
70
- remote_host: 194.51.14.159
71
- remote_port: 530
72
- server_ssh_user: ssh_user
73
- ssh_options:
74
- - -o LogLevel=error
75
- - -o UserKnownHostsFile=/dev/null
76
- - -o StrictHostKeyChecking=no
77
- - -o ServerAliveInterval=30
78
- - -o ServerAliveCountMax=2
79
- port: 530
80
- prerequisites:
81
- files:
82
- - zscams/agent/keys/autoport.key
83
- ports: []
84
- services: []
85
- script: src/services/ssh_forwarder.py
86
- - args: []
87
- health:
88
- host: localhost
89
- port: 10001
90
- name: Monitor Service
91
- params:
92
- equipment_name: zpa-orange-swec-dev-01
93
- equipment_type: zpa
94
- remote_host: localhost
95
- remote_port: 10001
96
- service_name: Zscaler-AppConnector
97
- port: 10001
98
- prerequisites:
99
- files: []
100
- ports:
101
- - 10001
102
- services: []
103
- script: src/services/system_monitor.py