zscams 2.0.2__tar.gz → 2.0.4__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 (45) hide show
  1. {zscams-2.0.2 → zscams-2.0.4}/PKG-INFO +6 -11
  2. {zscams-2.0.2 → zscams-2.0.4}/pyproject.toml +7 -6
  3. zscams-2.0.4/zscams/__init__.py +3 -0
  4. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/backend/bootstrap.py +7 -4
  5. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/mac.py +1 -1
  6. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/os.py +64 -11
  7. zscams-2.0.4/zscams/deps.py +86 -0
  8. zscams-2.0.2/zscams/agent/src/support/__init__.py +0 -0
  9. zscams-2.0.2/zscams/lib/getmac/__init__.py +0 -3
  10. zscams-2.0.2/zscams/lib/getmac/__main__.py +0 -123
  11. zscams-2.0.2/zscams/lib/getmac/getmac.py +0 -1900
  12. zscams-2.0.2/zscams/lib/getmac/shutilwhich.py +0 -67
  13. {zscams-2.0.2 → zscams-2.0.4}/README.md +0 -0
  14. {zscams-2.0.2 → zscams-2.0.4}/zscams/__main__.py +0 -0
  15. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/__init__.py +0 -0
  16. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/certificates/.gitkeep +0 -0
  17. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/config.yaml +0 -0
  18. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/configuration/config.j2 +0 -0
  19. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/configuration/service.j2 +0 -0
  20. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/keys/autoport.key +0 -0
  21. {zscams-2.0.2/zscams → zscams-2.0.4/zscams/agent/src}/__init__.py +0 -0
  22. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/__init__.py +0 -0
  23. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/backend/client.py +0 -0
  24. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/backend/exceptions.py +0 -0
  25. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/backend/update_machine_info.py +0 -0
  26. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/prerequisites.py +0 -0
  27. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/service_health_check.py +0 -0
  28. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/services.py +0 -0
  29. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/tunnel/__init__.py +0 -0
  30. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/tunnel/tls.py +0 -0
  31. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/core/tunnels.py +0 -0
  32. {zscams-2.0.2/zscams/agent/src → zscams-2.0.4/zscams/agent/src/services}/__init__.py +0 -0
  33. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/services/reverse_ssh.py +0 -0
  34. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/services/ssh_forwarder.py +0 -0
  35. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/services/system_monitor.py +0 -0
  36. {zscams-2.0.2/zscams/agent/src/services → zscams-2.0.4/zscams/agent/src/support}/__init__.py +0 -0
  37. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/cli.py +0 -0
  38. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/configuration.py +0 -0
  39. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/filesystem.py +0 -0
  40. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/logger.py +0 -0
  41. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/network.py +0 -0
  42. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/openssl.py +0 -0
  43. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/ssh.py +0 -0
  44. {zscams-2.0.2 → zscams-2.0.4}/zscams/agent/src/support/yaml.py +0 -0
  45. {zscams-2.0.2 → zscams-2.0.4}/zscams/lib/.gitkeep +0 -0
@@ -1,25 +1,20 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: zscams
3
- Version: 2.0.2
3
+ Version: 2.0.4
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
7
- Classifier: Programming Language :: Python :: 2
8
- Classifier: Programming Language :: Python :: 2.7
7
+ Requires-Python: >=3.9
9
8
  Classifier: Programming Language :: Python :: 3
10
- Classifier: Programming Language :: Python :: 3.4
11
- Classifier: Programming Language :: Python :: 3.5
12
- Classifier: Programming Language :: Python :: 3.6
13
- Classifier: Programming Language :: Python :: 3.7
14
- Classifier: Programming Language :: Python :: 3.8
15
9
  Classifier: Programming Language :: Python :: 3.9
16
10
  Classifier: Programming Language :: Python :: 3.10
17
11
  Classifier: Programming Language :: Python :: 3.11
18
12
  Classifier: Programming Language :: Python :: 3.12
19
13
  Classifier: Programming Language :: Python :: 3.13
20
- Requires-Dist: PyYAML
21
- Requires-Dist: cryptography
22
- Requires-Dist: psutil
14
+ Requires-Dist: PyYAML ; sys_platform != "freebsd"
15
+ Requires-Dist: cryptography ; sys_platform != "freebsd"
16
+ Requires-Dist: getmac
17
+ Requires-Dist: psutil ; sys_platform != "freebsd"
23
18
  Requires-Dist: requests
24
19
  Description-Content-Type: text/markdown
25
20
 
@@ -1,10 +1,9 @@
1
1
  [project]
2
2
  name = "zscams_agent"
3
- requires-python = "^3.9.2"
4
3
 
5
4
  [tool.poetry]
6
5
  name = "zscams"
7
- version = "2.0.2"
6
+ version = "2.0.4"
8
7
  description = "Async TLS tunnel client with SNI routing, auto-reconnect, and health checks"
9
8
  authors = ["OCD - Cairo Software Team"]
10
9
  maintainers = ["OCD - Cairo Software Team"]
@@ -12,10 +11,12 @@ readme = "README.md"
12
11
  packages = [ { include = "zscams"}]
13
12
 
14
13
  [tool.poetry.dependencies]
15
- PyYAML = "*"
16
- cryptography = "*"
17
- psutil = "*"
18
- requests = "*"
14
+ python = ">=3.9"
15
+ requests = "*"
16
+ getmac = "*"
17
+ PyYAML = { version = "*", markers = "sys_platform != 'freebsd'" }
18
+ cryptography = { version = "*", markers = "sys_platform != 'freebsd'" }
19
+ psutil = { version = "*", markers = "sys_platform != 'freebsd'" }
19
20
 
20
21
  [tool.poetry.scripts]
21
22
  zscams = "zscams.__main__:main"
@@ -0,0 +1,3 @@
1
+ from .deps import ensure_native_deps
2
+
3
+ ensure_native_deps()
@@ -6,7 +6,7 @@ from typing import cast
6
6
  from zscams.agent.src.core.backend.client import backend_client
7
7
  from zscams.agent.src.support.configuration import reinitialize
8
8
  from zscams.agent.src.support.logger import get_logger
9
- from zscams.agent.src.support.os import create_system_user, install_systemd_service
9
+ from zscams.agent.src.support.os import create_system_user, install_service
10
10
  from zscams.agent.src.support.ssh import add_to_authorized_keys
11
11
  from zscams.agent.src.support.cli import prompt
12
12
 
@@ -43,9 +43,11 @@ def bootstrap():
43
43
  required=True,
44
44
  startswith="",
45
45
  )
46
- equipment_name = f"{equipment_type.lower()}-{customer_name.lower()}-{connector_name.lower()}"
46
+ equipment_name = (
47
+ f"{equipment_type.lower()}-{customer_name.lower()}-{connector_name.lower()}"
48
+ )
47
49
  enforced_id = prompt("Enforced ID", "Enforced Agent ID")
48
- reinitialize(equipment_name=equipment_name,equipment_type=equipment_type)
50
+ reinitialize(equipment_name=equipment_name, equipment_type=equipment_type)
49
51
  cm_info = backend_client.bootstrap(equipment_name, enforced_id or None)
50
52
 
51
53
  sys_user = cast(str, cm_info.get("ssh_user"))
@@ -63,6 +65,7 @@ def install_zscams_systemd_service(user_to_run_as: str):
63
65
  "user_to_run_as": user_to_run_as,
64
66
  }
65
67
  import zscams
68
+
66
69
  BASE_DIR = Path(zscams.__file__).resolve().parent
67
70
  template_path = f"{BASE_DIR}/agent/configuration/service.j2"
68
71
  with open(template_path) as f:
@@ -70,4 +73,4 @@ def install_zscams_systemd_service(user_to_run_as: str):
70
73
 
71
74
  rendered_config = template.format(**data)
72
75
 
73
- install_systemd_service("zscams.service", rendered_config)
76
+ install_service("zscams.service", rendered_config)
@@ -1,6 +1,6 @@
1
1
  """Module to retrieve the MAC address of the primary network interface."""
2
2
 
3
- from zscams.lib.getmac import get_mac_address as gma
3
+ from getmac import get_mac_address as gma
4
4
 
5
5
 
6
6
  class MacAddressError(Exception):
@@ -1,6 +1,7 @@
1
1
  import sys
2
2
  import subprocess
3
-
3
+ import platform
4
+ import subprocess
4
5
  from zscams.agent.src.support.logger import get_logger
5
6
 
6
7
  from .filesystem import write_to_file
@@ -9,7 +10,7 @@ logger = get_logger("os_support")
9
10
 
10
11
 
11
12
  def is_linux():
12
- if sys.platform.startswith("linux"):
13
+ if sys.platform.lower().startswith("linux"):
13
14
  return True
14
15
  return False
15
16
 
@@ -28,10 +29,18 @@ def system_user_exists(username: str):
28
29
  return False
29
30
 
30
31
 
32
+ def is_freebsd():
33
+ return (
34
+ platform.system().lower() == "freebsd"
35
+ or platform.system().lower() == "zscaleros"
36
+ )
37
+
38
+
31
39
  def create_system_user(username: str):
32
- if not is_linux():
40
+ # Support both Linux and FreeBSD
41
+ if not (platform.system() == "Linux" or is_freebsd()):
33
42
  logger.error(
34
- "Error creating system user: This script is intended to run on Linux systems."
43
+ "Error creating system user: This script is intended to run on Linux or FreeBSD systems."
35
44
  )
36
45
  return
37
46
 
@@ -39,24 +48,68 @@ def create_system_user(username: str):
39
48
  logger.error("Error creating system user: Username must be provided.")
40
49
  return
41
50
 
51
+ # Assuming system_user_exists is already updated to handle both
42
52
  if system_user_exists(username):
43
53
  logger.warning("User '%s' already exists.", username)
44
54
  return
45
55
 
46
56
  try:
47
- subprocess.run(["sudo", "useradd","-m", "-s", "/bin/bash", username], check=True)
57
+ if is_freebsd():
58
+ cmd = ["sudo", "pw", "useradd", "-n", username, "-m", "-s", "/bin/sh"]
59
+ else:
60
+ # Standard Linux useradd
61
+ cmd = ["sudo", "useradd", "-m", "-s", "/bin/bash", username]
62
+
63
+ subprocess.run(cmd, check=True)
48
64
  logger.info("System user '%s' created successfully.", username)
65
+
49
66
  except subprocess.CalledProcessError as e:
50
67
  logger.error("Failed to create user '%s': %s", username, e)
51
68
 
52
69
 
53
- def install_systemd_service(service_name: str, content: str):
54
- if not is_linux():
55
- logger.error(
56
- "Error installing systemd service: This script is intended to run on Linux systems."
57
- )
58
- return
70
+ def install_service(service_name: str, content: str):
71
+ """
72
+ Main entry point to install services.
73
+ Redirects to systemd for Linux and rc.d for FreeBSD.
74
+ """
75
+ if is_linux():
76
+ install_systemd_service(service_name, content)
77
+ elif is_freebsd():
78
+ install_rc_service(service_name, content)
79
+ else:
80
+ logger.error("Unsupported OS for service installation.")
81
+
59
82
 
83
+ def install_rc_service(service_name: str, content: str):
84
+ """
85
+ Installs a FreeBSD rc.d script.
86
+ Note: 'content' for FreeBSD should be a valid rc.subr shell script.
87
+ """
88
+ service_path = f"/usr/local/etc/rc.d/{service_name}"
89
+
90
+ try:
91
+ # 1. Write the rc script
92
+ logger.debug("Installing FreeBSD rc script: %s", service_path)
93
+ # Using sudo tee to ensure write permissions on restricted paths
94
+ echo_cmd = f"printf '%s' '{content}' | sudo tee {service_path}"
95
+ subprocess.run(echo_cmd, shell=True, check=True, stdout=subprocess.DEVNULL)
96
+
97
+ # 2. Make it executable (Crucial for FreeBSD)
98
+ subprocess.run(["sudo", "chmod", "+x", service_path], check=True)
99
+
100
+ # 3. Enable and Start
101
+ # In FreeBSD, enabling adds 'service_name_enable="YES"' to /etc/rc.conf
102
+ logger.debug("Enabling and starting FreeBSD service %s...", service_name)
103
+ subprocess.run(["sudo", "sysrc", f"{service_name}_enable=YES"], check=True)
104
+ subprocess.run(["sudo", "service", service_name, "start"], check=True)
105
+
106
+ logger.info("Service %s installed and started successfully.", service_name)
107
+
108
+ except Exception as e:
109
+ logger.error("Failed to install FreeBSD service %s: %s", service_name, e)
110
+
111
+
112
+ def install_systemd_service(service_name: str, content: str):
60
113
  service_path = f"/etc/systemd/system/{service_name}"
61
114
 
62
115
  try:
@@ -0,0 +1,86 @@
1
+ import subprocess
2
+ import sys
3
+ import platform
4
+ import os
5
+ import importlib.util
6
+
7
+
8
+ def is_installed(name):
9
+ return importlib.util.find_spec(name) is not None
10
+
11
+
12
+ def ensure_native_deps():
13
+ # Only run this logic on FreeBSD
14
+ if (
15
+ platform.system().lower() != "freebsd"
16
+ and platform.system().lower() != "zscaleros"
17
+ ):
18
+ return
19
+
20
+ # 1. Define what we need and how FreeBSD names them
21
+ py_ver = f"py{sys.version_info.major}{sys.version_info.minor}"
22
+ deps_map = {
23
+ "cryptography": f"{py_ver}-cryptography",
24
+ "yaml": f"{py_ver}-pyyaml",
25
+ "psutil": f"{py_ver}-psutil",
26
+ }
27
+
28
+ # Identify which modules are actually missing
29
+ missing_mods = [mod for mod in deps_map if not is_installed(mod)]
30
+
31
+ if not missing_mods:
32
+ return # Everything is already installed
33
+
34
+ print(
35
+ f"--> ZscalerOS/FreeBSD detected. Missing requirements: {', '.join(missing_mods)}"
36
+ )
37
+
38
+ # 2. Ensure the FreeBSD Mirror is configured
39
+ # We use a custom local file so we don't overwrite Zscaler's system configs
40
+ repo_conf_dir = "/usr/local/etc/pkg/repos"
41
+ repo_conf_file = f"{repo_conf_dir}/FreeBSD.conf"
42
+
43
+ if not os.path.exists(repo_conf_file):
44
+ print("--> Mirror not found. Configuring official FreeBSD repository...")
45
+ # Hardcoding the ABI to 13 because ZscalerOS identifies as 42-RELEASE
46
+ mirror_config = (
47
+ "FreeBSD: { "
48
+ 'url: "pkg+http://pkg.FreeBSD.org/FreeBSD:13:amd64/latest", '
49
+ 'mirror_type: "srv", '
50
+ 'signature_type: "fingerprints", '
51
+ 'fingerprints: "/usr/share/keys/pkg", '
52
+ "enabled: yes "
53
+ "}"
54
+ )
55
+
56
+ try:
57
+ subprocess.run(["sudo", "mkdir", "-p", repo_conf_dir], check=True)
58
+ # Use printf/tee to handle the sudo write to a restricted path
59
+ subprocess.run(
60
+ f"printf '{mirror_config}' | sudo tee {repo_conf_file}",
61
+ shell=True,
62
+ check=True,
63
+ capture_output=True,
64
+ )
65
+ print(
66
+ "--> Mirror configured. Updating package database (this may take a moment)..."
67
+ )
68
+ subprocess.run(["sudo", "pkg", "update", "-f"], check=True)
69
+ except subprocess.CalledProcessError as e:
70
+ print(f"--> Failed to configure mirror. Error: {e}")
71
+ sys.exit(1)
72
+
73
+ # 3. Install the missing packages
74
+ targets = [deps_map[m] for m in missing_mods]
75
+ print(f"--> Attempting auto-install of: {', '.join(targets)}")
76
+
77
+ try:
78
+ # Install via pkg
79
+ subprocess.run(["sudo", "pkg", "install", "-y"] + targets, check=True)
80
+ print("--> Dependencies installed successfully!")
81
+ print("--> Please restart your command to apply changes.")
82
+ sys.exit(0)
83
+ except subprocess.CalledProcessError:
84
+ print("--> Error: Failed to install packages. Check your internet connection.")
85
+ print(f"--> Manual command: sudo pkg install {' '.join(targets)}")
86
+ sys.exit(1)
File without changes
@@ -1,3 +0,0 @@
1
- from .getmac import __version__, get_mac_address
2
-
3
- __all__ = ["get_mac_address"]
@@ -1,123 +0,0 @@
1
- #!/usr/bin/env python
2
- # -*- coding: utf-8 -*-
3
-
4
- from __future__ import print_function
5
-
6
- import argparse
7
- import logging
8
- import sys
9
-
10
- from . import getmac
11
-
12
-
13
- def main(): # type: () -> None
14
- parser = argparse.ArgumentParser(
15
- "getmac",
16
- description="Get the MAC address of system network "
17
- "interfaces or remote hosts on the LAN",
18
- )
19
- parser.add_argument(
20
- "--version", action="version", version="getmac %s" % getmac.__version__
21
- )
22
- parser.add_argument(
23
- "-v", "--verbose", action="store_true", help="Enable output messages"
24
- )
25
- parser.add_argument(
26
- "-d",
27
- "--debug",
28
- action="count",
29
- help="Enable debugging output. Add characters to "
30
- "increase verbosity of output, e.g. '-dd'.",
31
- )
32
- parser.add_argument(
33
- "-N",
34
- "--no-net",
35
- "--no-network-requests",
36
- action="store_true",
37
- dest="NO_NET",
38
- help="Do not use arping or send a UDP packet to refresh the ARP table",
39
- )
40
- parser.add_argument(
41
- "--override-port",
42
- type=int,
43
- metavar="PORT",
44
- help="Override the default UDP port used to refresh the ARP table "
45
- "if network requests are enabled and arping is unavailable",
46
- )
47
- parser.add_argument(
48
- "--override-platform",
49
- type=str,
50
- default=None,
51
- metavar="PLATFORM",
52
- help="Override the platform detection with the given value "
53
- "(e.g. 'linux', 'windows', 'freebsd', etc.'). "
54
- "Any values returned by platform.system() are valid.",
55
- )
56
- parser.add_argument(
57
- "--force-method",
58
- type=str,
59
- default=None,
60
- metavar="METHOD",
61
- help="Force a specific method to be used, e.g. 'IpNeighborShow'. "
62
- "This will be used regardless of it's method type or platform "
63
- "compatibility, and Method.test() will NOT be checked!",
64
- )
65
-
66
- group = parser.add_mutually_exclusive_group(required=False)
67
- group.add_argument(
68
- "-i",
69
- "--interface",
70
- type=str,
71
- default=None,
72
- help="Name of a network interface on the system",
73
- )
74
- group.add_argument(
75
- "-4", "--ip", type=str, default=None, help="IPv4 address of a remote host"
76
- )
77
- group.add_argument(
78
- "-6", "--ip6", type=str, default=None, help="IPv6 address of a remote host"
79
- )
80
- group.add_argument(
81
- "-n", "--hostname", type=str, default=None, help="Hostname of a remote host"
82
- )
83
-
84
- args = parser.parse_args()
85
-
86
- if args.debug or args.verbose:
87
- logging.basicConfig(
88
- format="%(levelname)-8s %(message)s", level=logging.DEBUG, stream=sys.stderr
89
- )
90
-
91
- if args.debug:
92
- getmac.DEBUG = args.debug
93
-
94
- if args.override_port:
95
- port = int(args.override_port)
96
- getmac.log.debug(
97
- "Using UDP port %d (overriding the default port %d)", port, getmac.PORT
98
- )
99
- getmac.PORT = port
100
-
101
- if args.override_platform:
102
- getmac.OVERRIDE_PLATFORM = args.override_platform.strip().lower()
103
-
104
- if args.force_method:
105
- getmac.FORCE_METHOD = args.force_method.strip().lower()
106
-
107
- mac = getmac.get_mac_address(
108
- interface=args.interface,
109
- ip=args.ip,
110
- ip6=args.ip6,
111
- hostname=args.hostname,
112
- network_request=not args.NO_NET,
113
- )
114
-
115
- if mac is not None:
116
- print(mac) # noqa: T001, T201
117
- sys.exit(0) # Exit success!
118
- else:
119
- sys.exit(1) # Exit with error since it failed to find a MAC
120
-
121
-
122
- if __name__ == "__main__":
123
- main()