zscams 2.0.1__tar.gz → 2.0.2__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.
Files changed (48) hide show
  1. {zscams-2.0.1 → zscams-2.0.2}/PKG-INFO +2 -1
  2. {zscams-2.0.1 → zscams-2.0.2}/pyproject.toml +2 -1
  3. zscams-2.0.2/zscams/__main__.py +43 -0
  4. zscams-2.0.1/zscams/__main__.py → zscams-2.0.2/zscams/agent/__init__.py +41 -49
  5. zscams-2.0.2/zscams/agent/config.yaml +103 -0
  6. zscams-2.0.2/zscams/agent/configuration/config.j2 +103 -0
  7. zscams-2.0.2/zscams/agent/configuration/service.j2 +12 -0
  8. zscams-2.0.2/zscams/agent/src/core/backend/bootstrap.py +73 -0
  9. {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.2/zscams/agent/src/core}/backend/client.py +45 -6
  10. {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.2/zscams/agent/src/core}/backend/update_machine_info.py +1 -1
  11. zscams-2.0.2/zscams/agent/src/support/cli.py +50 -0
  12. zscams-2.0.2/zscams/agent/src/support/configuration.py +86 -0
  13. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/support/filesystem.py +22 -0
  14. zscams-2.0.2/zscams/agent/src/support/logger.py +88 -0
  15. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/support/mac.py +1 -1
  16. zscams-2.0.2/zscams/agent/src/support/openssl.py +114 -0
  17. zscams-2.0.2/zscams/agent/src/support/os.py +85 -0
  18. zscams-2.0.2/zscams/agent/src/support/ssh.py +24 -0
  19. zscams-2.0.2/zscams/agent/src/support/yaml.py +37 -0
  20. zscams-2.0.1/zscams/agent/config.yaml +0 -121
  21. zscams-2.0.1/zscams/agent/src/core/api/backend/bootstrap.py +0 -21
  22. zscams-2.0.1/zscams/agent/src/support/configuration.py +0 -53
  23. zscams-2.0.1/zscams/agent/src/support/logger.py +0 -40
  24. zscams-2.0.1/zscams/agent/src/support/openssl.py +0 -100
  25. {zscams-2.0.1 → zscams-2.0.2}/README.md +0 -0
  26. {zscams-2.0.1 → zscams-2.0.2}/zscams/__init__.py +0 -0
  27. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/certificates/.gitkeep +0 -0
  28. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/keys/autoport.key +0 -0
  29. {zscams-2.0.1/zscams/agent → zscams-2.0.2/zscams/agent/src}/__init__.py +0 -0
  30. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/__init__.py +0 -0
  31. {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.2/zscams/agent/src/core}/backend/exceptions.py +0 -0
  32. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/prerequisites.py +0 -0
  33. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/service_health_check.py +0 -0
  34. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/services.py +0 -0
  35. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/tunnel/__init__.py +0 -0
  36. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/tunnel/tls.py +0 -0
  37. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/core/tunnels.py +0 -0
  38. {zscams-2.0.1/zscams/agent/src → zscams-2.0.2/zscams/agent/src/services}/__init__.py +0 -0
  39. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/services/reverse_ssh.py +0 -0
  40. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/services/ssh_forwarder.py +0 -0
  41. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/services/system_monitor.py +0 -0
  42. {zscams-2.0.1/zscams/agent/src/services → zscams-2.0.2/zscams/agent/src/support}/__init__.py +0 -0
  43. {zscams-2.0.1 → zscams-2.0.2}/zscams/agent/src/support/network.py +0 -0
  44. /zscams-2.0.1/zscams/agent/src/support/__init__.py → /zscams-2.0.2/zscams/lib/.gitkeep +0 -0
  45. {zscams-2.0.1/zscams/libs → zscams-2.0.2/zscams/lib}/getmac/__init__.py +0 -0
  46. {zscams-2.0.1/zscams/libs → zscams-2.0.2/zscams/lib}/getmac/__main__.py +0 -0
  47. {zscams-2.0.1/zscams/libs → zscams-2.0.2/zscams/lib}/getmac/getmac.py +0 -0
  48. {zscams-2.0.1/zscams/libs → zscams-2.0.2/zscams/lib}/getmac/shutilwhich.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscams
3
- Version: 2.0.1
3
+ Version: 2.0.2
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
@@ -20,6 +20,7 @@ Classifier: Programming Language :: Python :: 3.13
20
20
  Requires-Dist: PyYAML
21
21
  Requires-Dist: cryptography
22
22
  Requires-Dist: psutil
23
+ Requires-Dist: requests
23
24
  Description-Content-Type: text/markdown
24
25
 
25
26
  # Agent
@@ -4,7 +4,7 @@ requires-python = "^3.9.2"
4
4
 
5
5
  [tool.poetry]
6
6
  name = "zscams"
7
- version = "2.0.1"
7
+ version = "2.0.2"
8
8
  description = "Async TLS tunnel client with SNI routing, auto-reconnect, and health checks"
9
9
  authors = ["OCD - Cairo Software Team"]
10
10
  maintainers = ["OCD - Cairo Software Team"]
@@ -15,6 +15,7 @@ packages = [ { include = "zscams"}]
15
15
  PyYAML = "*"
16
16
  cryptography = "*"
17
17
  psutil = "*"
18
+ requests = "*"
18
19
 
19
20
  [tool.poetry.scripts]
20
21
  zscams = "zscams.__main__:main"
@@ -0,0 +1,43 @@
1
+ """
2
+ Main entry point for ZSCAMs Agent
3
+ """
4
+
5
+ import asyncio
6
+ import sys
7
+ import os
8
+ from zscams.agent.src.core.backend.bootstrap import bootstrap
9
+ from zscams.agent.src.core.backend.update_machine_info import update_machine_info
10
+ from zscams.agent.src.support.logger import get_logger
11
+ from zscams.agent import init_parser, ensure_bootstrapped, run
12
+
13
+ logger = get_logger("ZSCAMs")
14
+
15
+
16
+ def main():
17
+ """Main function to run the asynchronous main."""
18
+ args = init_parser().parse_args()
19
+
20
+ if args.bootstrap:
21
+ try:
22
+ if os.geteuid() != 0:
23
+ logger.error("You are NOT running as root.")
24
+ sys.exit(1)
25
+ bootstrap()
26
+ sys.exit(0)
27
+ except Exception as exception:
28
+ logger.error(exception)
29
+ sys.exit(1)
30
+
31
+ if args.update_machine_info:
32
+ update_machine_info()
33
+ sys.exit(0)
34
+
35
+ try:
36
+ ensure_bootstrapped()
37
+ asyncio.run(run())
38
+ except KeyboardInterrupt:
39
+ logger.info("Exiting TLS Tunnel Client")
40
+
41
+
42
+ if __name__ == "__main__":
43
+ main()
@@ -1,37 +1,67 @@
1
- """
2
- Main entry point for ZSCAMs Agent
3
- """
4
-
5
1
  import os
6
2
  import asyncio
7
3
  import sys
8
4
  import argparse
9
- from zscams.agent.src.core.api.backend.bootstrap import bootstrap
10
- from zscams.agent.src.core.api.backend.update_machine_info import update_machine_info
11
5
  from zscams.agent.src.support.configuration import get_config, CONFIG_PATH
12
- from zscams.agent.src.support.logger import get_logger
6
+ from zscams.agent.src.support.filesystem import is_file_exists, resolve_path
13
7
  from zscams.agent.src.core.tunnel.tls import create_ssl_context
14
8
  from zscams.agent.src.core.tunnels import start_all_tunnels
15
9
  from zscams.agent.src.core.services import start_all_services
16
10
  from zscams.agent.src.core.service_health_check import monitor_services
17
- from zscams.agent.src.core.api.backend.client import backend_client
11
+ from zscams.agent.src.core.backend.client import backend_client
12
+ from zscams.agent.src.support.logger import get_logger
13
+
18
14
 
19
15
  logger = get_logger("tls_tunnel_main")
20
16
 
21
17
 
18
+ def init_parser():
19
+ parser = argparse.ArgumentParser(description="ZSCAMs Agent")
20
+ parser.add_argument(
21
+ "--bootstrap",
22
+ action="store_true",
23
+ help="Run bootstrap process and exit",
24
+ )
25
+ parser.add_argument(
26
+ "--update-machine-info",
27
+ action="store_true",
28
+ help="Run bootstrap process and exit",
29
+ )
30
+ return parser
31
+
32
+
22
33
  def ensure_bootstrapped():
23
34
  """Ensure the agent is bootstrapped with the backend."""
24
35
 
36
+ config = get_config()
37
+ remote_cfg = config["remote"]
38
+ config_dir = os.path.dirname(CONFIG_PATH)
39
+
40
+ files_to_ensure = [
41
+ resolve_path(remote_cfg.get("ca_cert"), config_dir),
42
+ resolve_path(remote_cfg.get("client_cert"), config_dir),
43
+ resolve_path(remote_cfg.get("client_key"), config_dir),
44
+ ]
45
+
46
+ has_missing_file = False
47
+
48
+ for file in files_to_ensure:
49
+ if not is_file_exists(file, logger):
50
+ has_missing_file = True
51
+ break
52
+
25
53
  bootstrapped = backend_client.is_bootstrapped()
26
- logger.debug("Agent bootstrapped: %s", bootstrapped)
27
- if not bootstrapped:
54
+
55
+ logger.debug("Agent has entry on server: %s", bootstrapped)
56
+
57
+ if not bootstrapped or has_missing_file:
28
58
  logger.error(
29
59
  "Agent is not bootstrapped. Please run `zscams --bootstrap` to start the bootstrap process.",
30
60
  )
31
61
  sys.exit(1)
32
62
 
33
63
 
34
- async def async_main():
64
+ async def run():
35
65
  """Asynchronous main function to start tunnels and services."""
36
66
 
37
67
  config = get_config()
@@ -61,41 +91,3 @@ async def async_main():
61
91
 
62
92
  logger.info("[*] All tunnels and services started. Press Ctrl+C to stop.")
63
93
  await asyncio.gather(*tunnel_tasks, service_tasks, monitor_task)
64
-
65
-
66
- def main():
67
- """Main function to run the asynchronous main."""
68
- parser = argparse.ArgumentParser(description="ZSCAMs Agent")
69
- parser.add_argument(
70
- "--bootstrap",
71
- action="store_true",
72
- help="Run bootstrap process and exit",
73
- )
74
- parser.add_argument(
75
- "--update-machine-info",
76
- action="store_true",
77
- help="Run bootstrap process and exit",
78
- )
79
- args = parser.parse_args()
80
-
81
- if args.bootstrap:
82
- try:
83
- bootstrap()
84
- sys.exit(0)
85
- except Exception as exception:
86
- logger.error(exception)
87
- sys.exit(1)
88
-
89
- if args.update_machine_info:
90
- update_machine_info()
91
- sys.exit(0)
92
-
93
- try:
94
- ensure_bootstrapped()
95
- asyncio.run(async_main())
96
- except KeyboardInterrupt:
97
- logger.info("Exiting TLS Tunnel Client")
98
-
99
-
100
- if __name__ == "__main__":
101
- main()
@@ -0,0 +1,103 @@
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: zscams/agent/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
@@ -0,0 +1,103 @@
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
+ health:
36
+ host: localhost
37
+ port: 22
38
+ name: Reverse SSH
39
+ custom_port_name: "reverse_port"
40
+ params:
41
+ local_port: 4422
42
+ private_key: zscams/agent/keys/autoport.key
43
+ remote_host: localhost
44
+ reverse_port: "{reverse_port}"
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
+ health:
62
+ host: localhost
63
+ port: "{forwarder_port}"
64
+ name: Monitor Forwarder
65
+ custom_port_name: "forwarder_port"
66
+ params:
67
+ forwarder_port: "{forwarder_port}"
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: "{forwarder_port}"
90
+ name: Monitor Service
91
+ params:
92
+ equipment_name: "{equipment_name}"
93
+ equipment_type: "{equipment_type}"
94
+ remote_host: localhost
95
+ remote_port: "{forwarder_port}"
96
+ service_name: Zscaler-AppConnector
97
+ port: "{forwarder_port}"
98
+ prerequisites:
99
+ files: []
100
+ ports:
101
+ - "{forwarder_port}"
102
+ services: []
103
+ script: src/services/system_monitor.py
@@ -0,0 +1,12 @@
1
+ [Unit]
2
+ Description=ZSCAMs Service
3
+ After=network.target
4
+
5
+ [Service]
6
+ ExecStart={python_exec} -m zscams
7
+ Restart=always
8
+ User={user_to_run_as}
9
+ Environment="PYTHONPATH={pythonpath}"
10
+
11
+ [Install]
12
+ WantedBy=multi-user.target
@@ -0,0 +1,73 @@
1
+ import os
2
+ from pathlib import Path
3
+ import sysconfig
4
+ import sys
5
+ from typing import cast
6
+ from zscams.agent.src.core.backend.client import backend_client
7
+ from zscams.agent.src.support.configuration import reinitialize
8
+ from zscams.agent.src.support.logger import get_logger
9
+ from zscams.agent.src.support.os import create_system_user, install_systemd_service
10
+ from zscams.agent.src.support.ssh import add_to_authorized_keys
11
+ from zscams.agent.src.support.cli import prompt
12
+
13
+ logger = get_logger("bootstrap")
14
+
15
+
16
+ def bootstrap():
17
+ """Ensure the agent is bootstrapped with the backend."""
18
+
19
+ if backend_client.is_bootstrapped():
20
+ logger.info("Agent ID %s is already bootstrapped.", backend_client.agent_id)
21
+ return
22
+
23
+ username = input("Username: ")
24
+ totp = input("One Time Password (OTP): ")
25
+
26
+ backend_client.login(username, totp)
27
+
28
+ customer_name = prompt(
29
+ "Customer Name",
30
+ "Customer Name",
31
+ required=True,
32
+ startswith="",
33
+ )
34
+ connector_name = prompt(
35
+ "Connector Name",
36
+ "Connector Name [Unique name for this connector]",
37
+ required=True,
38
+ startswith="",
39
+ )
40
+ equipment_type = prompt(
41
+ "Equipment Type",
42
+ "Equipment Type [Can be ZPA|VZEN|PSE]",
43
+ required=True,
44
+ startswith="",
45
+ )
46
+ equipment_name = f"{equipment_type.lower()}-{customer_name.lower()}-{connector_name.lower()}"
47
+ enforced_id = prompt("Enforced ID", "Enforced Agent ID")
48
+ reinitialize(equipment_name=equipment_name,equipment_type=equipment_type)
49
+ cm_info = backend_client.bootstrap(equipment_name, enforced_id or None)
50
+
51
+ sys_user = cast(str, cm_info.get("ssh_user"))
52
+
53
+ create_system_user(sys_user)
54
+ add_to_authorized_keys(sys_user, cm_info.get("server_ssh_pub_key"))
55
+ install_zscams_systemd_service(sys_user)
56
+
57
+
58
+ def install_zscams_systemd_service(user_to_run_as: str):
59
+
60
+ data = {
61
+ "pythonpath": sysconfig.get_paths()["purelib"],
62
+ "python_exec": sys.executable,
63
+ "user_to_run_as": user_to_run_as,
64
+ }
65
+ import zscams
66
+ BASE_DIR = Path(zscams.__file__).resolve().parent
67
+ template_path = f"{BASE_DIR}/agent/configuration/service.j2"
68
+ with open(template_path) as f:
69
+ template = f.read()
70
+
71
+ rendered_config = template.format(**data)
72
+
73
+ install_systemd_service("zscams.service", rendered_config)
@@ -7,7 +7,13 @@ from typing import Optional, cast
7
7
 
8
8
  from .exceptions import AgentBootstrapError
9
9
 
10
- from zscams.agent.src.support.configuration import CONFIG_PATH, ROOT_PATH, get_config
10
+ from zscams.agent.src.support.configuration import (
11
+ CONFIG_PATH,
12
+ ROOT_PATH,
13
+ get_config,
14
+ override_config,
15
+ )
16
+ from zscams.agent.src.support.yaml import resolve_placeholders, assert_no_placeholders_left
11
17
  from zscams.agent.src.support.mac import get_mac_address
12
18
  from zscams.agent.src.support.filesystem import resolve_path, ensure_dir, is_file_exists
13
19
  from zscams.agent.src.support.network import get_local_hostname, get_local_ip_address
@@ -68,6 +74,12 @@ class BackendClient:
68
74
 
69
75
  self.logger.info("Generated the private key at %s", private_key_path)
70
76
 
77
+ common_name = (
78
+ f"{equipment_name}.orangecyberdefense.com"
79
+ if equipment_name
80
+ else csr_info.get("default_common_name")
81
+ )
82
+
71
83
  ip = get_local_ip_address()
72
84
  csr = generate_csr_from_private_key(
73
85
  private_key_content,
@@ -75,7 +87,7 @@ class BackendClient:
75
87
  csr_info.get("state"),
76
88
  csr_info.get("locality"),
77
89
  csr_info.get("organization"),
78
- csr_info.get("common_name"),
90
+ common_name,
79
91
  )
80
92
 
81
93
  payload = {
@@ -85,7 +97,7 @@ class BackendClient:
85
97
  "equipment_name": equipment_name,
86
98
  "csr": csr,
87
99
  "enforced_id": enforced_id,
88
- "services": [service.get("name") for service in self.services_config],
100
+ "services": [service.get("name") for service in self.services_config if service.get("custom_port_name", None)],
89
101
  "blacklist_ports": self.bootstrap_info.get("blacklist_ports", []),
90
102
  "cert_issuer": self.bootstrap_info.get("cert_issuer", None),
91
103
  }
@@ -111,14 +123,17 @@ class BackendClient:
111
123
  self.logger.info("Signed the cert")
112
124
  self.logger.debug("Signed cert response: %s", json.dumps(res_json, indent=2))
113
125
 
114
- ca_chain: list[str] = res_json.get("response", {}).get("ca_chain", [])
115
- signed_cert: str = res_json.get("response", {}).get("signed_certificate", [])
126
+ customer_machine = res_json.get("response", {})
127
+ ca_chain: list[str] = customer_machine.get("ca_chain", [])
128
+ signed_cert: str = customer_machine.get("signed_certificate", [])
116
129
  self._write_certificates(ca_chain, signed_cert)
117
130
 
118
131
  # To cache the machine info
119
132
  self.get_machine_info(ignore_cache=True)
120
133
 
121
- return res_json
134
+ self.__override_config_services_ports(customer_machine.get("services", []))
135
+
136
+ return customer_machine
122
137
 
123
138
  def get_machine_info(self, ignore_cache: bool = False):
124
139
  """Get machine information from the backend."""
@@ -237,6 +252,30 @@ class BackendClient:
237
252
 
238
253
  def __prepare_path(self, path):
239
254
  return f"{self.url}/{path.lstrip('/')}"
255
+
256
+ def __override_config_services_ports(self, cm_services: list[dict]):
257
+ self.logger.debug("Overriding configuration services ports...")
258
+
259
+ config = get_config()
260
+ custom_ports = {}
261
+ for cm_service in cm_services:
262
+ for cfg_service_idx, cfg_service in enumerate(config.get("services", [])):
263
+ if cm_service.get("name") == cfg_service.get("name"):
264
+
265
+ if not config["services"][cfg_service_idx]["params"]:
266
+ config["services"][cfg_service_idx]["params"] = {}
267
+
268
+ port_field_name = cfg_service.get("custom_port_name", None)
269
+ if port_field_name:
270
+ 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)
276
+ self.logger.debug("Done overriding the configurations")
277
+
278
+ return override_config(config)
240
279
 
241
280
 
242
281
  backend_client = BackendClient()
@@ -1,4 +1,4 @@
1
- from zscams.agent.src.core.api.backend.client import backend_client
1
+ from zscams.agent.src.core.backend.client import backend_client
2
2
  from zscams.agent.src.support.logger import get_logger
3
3
 
4
4
  logger = get_logger("agent_machine_info")
@@ -0,0 +1,50 @@
1
+ from typing import Optional, cast
2
+ from zscams.agent.src.support.logger import get_logger
3
+
4
+ logger = get_logger("bootstrap")
5
+
6
+
7
+ class RequiredFieldException(Exception):
8
+ pass
9
+
10
+
11
+ class InvalidFieldException(Exception):
12
+ pass
13
+
14
+
15
+ def prompt(
16
+ name: str,
17
+ message: str,
18
+ required=False,
19
+ startswith: Optional[str] = None,
20
+ fail_on_error=False,
21
+ retries_count=3,
22
+ ):
23
+ _message = message
24
+ if not required:
25
+ _message += " (Optional)"
26
+
27
+ _message += ": "
28
+
29
+ val = input(_message)
30
+
31
+ if required and not val:
32
+ if fail_on_error:
33
+ raise RequiredFieldException(f"Missing {name}")
34
+ logger.error(f"{name} is required..")
35
+ retries_count -= 1
36
+ return prompt(
37
+ name, message, required, startswith, fail_on_error=retries_count <= 0
38
+ )
39
+
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
+ )
49
+
50
+ return val