zscams 2.0.1__py2.py3-none-any.whl → 2.0.3__py2.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/__main__.py +10 -68
- zscams/agent/__init__.py +93 -0
- zscams/agent/config.yaml +71 -89
- zscams/agent/configuration/config.j2 +103 -0
- zscams/agent/configuration/service.j2 +12 -0
- zscams/agent/src/core/backend/bootstrap.py +73 -0
- zscams/agent/src/core/{api/backend → backend}/client.py +45 -6
- zscams/agent/src/core/{api/backend → backend}/update_machine_info.py +1 -1
- zscams/agent/src/support/cli.py +50 -0
- zscams/agent/src/support/configuration.py +35 -2
- zscams/agent/src/support/filesystem.py +22 -0
- zscams/agent/src/support/logger.py +62 -14
- zscams/agent/src/support/mac.py +1 -1
- zscams/agent/src/support/openssl.py +57 -43
- zscams/agent/src/support/os.py +85 -0
- zscams/agent/src/support/ssh.py +24 -0
- zscams/agent/src/support/yaml.py +37 -0
- zscams/lib/.gitkeep +0 -0
- {zscams-2.0.1.dist-info → zscams-2.0.3.dist-info}/METADATA +2 -1
- zscams-2.0.3.dist-info/RECORD +44 -0
- zscams/agent/src/core/api/backend/bootstrap.py +0 -21
- zscams-2.0.1.dist-info/RECORD +0 -37
- /zscams/agent/src/core/{api/backend → backend}/exceptions.py +0 -0
- /zscams/{libs → lib}/getmac/__init__.py +0 -0
- /zscams/{libs → lib}/getmac/__main__.py +0 -0
- /zscams/{libs → lib}/getmac/getmac.py +0 -0
- /zscams/{libs → lib}/getmac/shutilwhich.py +0 -0
- {zscams-2.0.1.dist-info → zscams-2.0.3.dist-info}/WHEEL +0 -0
- {zscams-2.0.1.dist-info → zscams-2.0.3.dist-info}/entry_points.txt +0 -0
|
@@ -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
|
|
@@ -5,8 +5,9 @@ Configuration loader module
|
|
|
5
5
|
import os
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
from typing import TypedDict
|
|
8
|
-
|
|
8
|
+
import json
|
|
9
9
|
import yaml
|
|
10
|
+
from zscams.agent.src.support.yaml import YamlIndentedListsDumper, resolve_placeholders
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
config = {}
|
|
@@ -26,6 +27,20 @@ class RemoteConfig(TypedDict):
|
|
|
26
27
|
ca_cert: str
|
|
27
28
|
ca_chain: str
|
|
28
29
|
|
|
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
|
+
|
|
29
44
|
|
|
30
45
|
def load_config():
|
|
31
46
|
"""
|
|
@@ -34,7 +49,7 @@ def load_config():
|
|
|
34
49
|
Returns:
|
|
35
50
|
dict: Configuration dictionary containing remote settings and forwards.
|
|
36
51
|
"""
|
|
37
|
-
with open(CONFIG_PATH, "r") as f:
|
|
52
|
+
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
|
38
53
|
config = yaml.safe_load(f)
|
|
39
54
|
return config
|
|
40
55
|
|
|
@@ -51,3 +66,21 @@ def get_config():
|
|
|
51
66
|
return load_config()
|
|
52
67
|
|
|
53
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
|
+
)
|
|
@@ -3,8 +3,11 @@ Path utilities for TLS Tunnel Client
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
+
from pathlib import Path
|
|
6
7
|
from typing import Optional
|
|
8
|
+
from zscams.agent.src.support.logger import get_logger
|
|
7
9
|
|
|
10
|
+
logger = get_logger("FileSystem")
|
|
8
11
|
|
|
9
12
|
def resolve_path(path: Optional[str], base_dir: Optional[str] = None) -> Optional[str]:
|
|
10
13
|
"""
|
|
@@ -39,3 +42,22 @@ def is_file_exists(path, logger):
|
|
|
39
42
|
|
|
40
43
|
logger.error(f"File not found: {path}")
|
|
41
44
|
return False
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def append_to_file(path: str | Path, content: str):
|
|
48
|
+
try:
|
|
49
|
+
if isinstance(path, str):
|
|
50
|
+
path = Path(path)
|
|
51
|
+
|
|
52
|
+
if not path.parent.exists():
|
|
53
|
+
os.mkdir(path.parent,0o700)
|
|
54
|
+
|
|
55
|
+
with open(path, "a", encoding="utf-8") as file:
|
|
56
|
+
file.write(content)
|
|
57
|
+
except Exception as exception:
|
|
58
|
+
logger.error(exception)
|
|
59
|
+
raise exception
|
|
60
|
+
|
|
61
|
+
def write_to_file(path: str | Path, content: str):
|
|
62
|
+
with open(path, "w", encoding="utf-8") as f:
|
|
63
|
+
f.write(content)
|
|
@@ -2,39 +2,87 @@
|
|
|
2
2
|
Logger module for TLS Tunnel Client
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
"""Singleton logger handlers for local logging only"""
|
|
6
6
|
import sys
|
|
7
|
+
import logging
|
|
8
|
+
import os
|
|
9
|
+
import platform
|
|
10
|
+
from typing import Dict
|
|
11
|
+
from logging import Logger
|
|
7
12
|
|
|
8
13
|
from zscams.agent.src.support.configuration import get_config
|
|
9
14
|
|
|
15
|
+
loggers: Dict[str, Logger] = {}
|
|
10
16
|
|
|
11
|
-
|
|
17
|
+
# -------------------- COLOR FORMATTER --------------------
|
|
12
18
|
|
|
13
19
|
|
|
14
|
-
|
|
15
|
-
"""
|
|
20
|
+
class ColorFormatter(logging.Formatter):
|
|
21
|
+
"""Formatter adding ANSI colors to console logs only."""
|
|
16
22
|
|
|
17
|
-
|
|
18
|
-
|
|
23
|
+
COLORS = {
|
|
24
|
+
"DEBUG": "\033[93m", # Yellow
|
|
25
|
+
"INFO": "\033[92m", # Green
|
|
26
|
+
"WARNING": "\033[95m", # Magenta
|
|
27
|
+
"ERROR": "\033[91m", # Red
|
|
28
|
+
"CRITICAL": "\033[41m", # Red background
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
RESET = "\033[0m"
|
|
32
|
+
|
|
33
|
+
def format(self, record):
|
|
34
|
+
color = self.COLORS.get(record.levelname, self.RESET)
|
|
35
|
+
msg = super().format(record)
|
|
36
|
+
return f"{color}{msg}{self.RESET}"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# -------------------- SYSTEM LOG PATH --------------------
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_default_system_log_path():
|
|
43
|
+
"""Returns the system default logging path"""
|
|
44
|
+
system = platform.system()
|
|
19
45
|
|
|
20
|
-
|
|
46
|
+
if system == "Windows":
|
|
47
|
+
return os.path.join(
|
|
48
|
+
os.environ.get("WINDIR", "C:\\Windows"), "System32", "winevt", "Logs"
|
|
49
|
+
)
|
|
21
50
|
|
|
22
|
-
|
|
51
|
+
elif system == "Linux":
|
|
52
|
+
return "/var/log"
|
|
23
53
|
|
|
24
|
-
|
|
25
|
-
|
|
54
|
+
else:
|
|
55
|
+
return None # unsupported
|
|
26
56
|
|
|
57
|
+
|
|
58
|
+
# -------------------- LOGGER FACTORY --------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def get_logger(name: str) -> Logger:
|
|
62
|
+
"""Create a singleton instance for that logger name"""
|
|
63
|
+
if name in loggers:
|
|
64
|
+
return loggers[name]
|
|
65
|
+
|
|
66
|
+
logger = logging.getLogger(
|
|
67
|
+
f"ZSCAMs - {name}" if name.lower().find("zscams") == -1 else name
|
|
68
|
+
)
|
|
69
|
+
logger.setLevel(get_config().get("logging", {}).get("level", 10))
|
|
70
|
+
logger.propagate = False # Prevent duplicate logs
|
|
71
|
+
|
|
72
|
+
# Cleanup handlers if any
|
|
27
73
|
if logger.hasHandlers():
|
|
28
74
|
logger.handlers.clear()
|
|
29
75
|
|
|
76
|
+
# -------- Console Handler -------- #
|
|
30
77
|
console_handler = logging.StreamHandler(sys.stdout)
|
|
31
|
-
console_handler.setLevel(level)
|
|
78
|
+
console_handler.setLevel(get_config().get("logging", {}).get("level", 10))
|
|
32
79
|
|
|
33
|
-
console_formatter =
|
|
34
|
-
"%(asctime)s [%(levelname)s] %(name)s
|
|
80
|
+
console_formatter = ColorFormatter(
|
|
81
|
+
fmt="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
|
|
82
|
+
datefmt="%Y-%m-%d %H:%M:%S",
|
|
35
83
|
)
|
|
36
84
|
console_handler.setFormatter(console_formatter)
|
|
37
85
|
logger.addHandler(console_handler)
|
|
38
|
-
|
|
86
|
+
# Cache singleton
|
|
39
87
|
loggers[name] = logger
|
|
40
88
|
return logger
|
zscams/agent/src/support/mac.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
This module provides a simple interface for generating RSA key pairs
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
+
import os
|
|
6
|
+
|
|
5
7
|
from cryptography import x509
|
|
6
8
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
7
9
|
from cryptography.x509.oid import NameOID
|
|
@@ -26,6 +28,8 @@ def generate_private_key(key_path: str):
|
|
|
26
28
|
with open(key_path, "wb") as f:
|
|
27
29
|
f.write(pem_private_key)
|
|
28
30
|
|
|
31
|
+
os.chmod(key_path, 0o700)
|
|
32
|
+
|
|
29
33
|
return pem_private_key
|
|
30
34
|
|
|
31
35
|
|
|
@@ -51,50 +55,60 @@ def generate_csr_from_private_key(
|
|
|
51
55
|
)
|
|
52
56
|
|
|
53
57
|
csr_builder = x509.CertificateSigningRequestBuilder()
|
|
54
|
-
csr_builder =
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
x509.BasicConstraints(ca=False, path_length=None),
|
|
65
|
-
critical=False,).add_extension(
|
|
66
|
-
x509.UnrecognizedExtension(
|
|
67
|
-
x509.ObjectIdentifier("2.16.840.1.113730.1.1"),
|
|
68
|
-
b"SSL Client, S/MIME",
|
|
69
|
-
),
|
|
70
|
-
critical=False,).add_extension(
|
|
71
|
-
x509.UnrecognizedExtension(
|
|
72
|
-
x509.ObjectIdentifier("2.16.840.1.113730.1.13"),
|
|
73
|
-
b"Generated by OBS/OCD",
|
|
74
|
-
),
|
|
75
|
-
critical=False,
|
|
76
|
-
).add_extension(
|
|
77
|
-
x509.KeyUsage(
|
|
78
|
-
digital_signature=True,
|
|
79
|
-
crl_sign=False,
|
|
80
|
-
key_encipherment=True,
|
|
81
|
-
content_commitment=True,
|
|
82
|
-
data_encipherment=False,
|
|
83
|
-
key_agreement=False,
|
|
84
|
-
key_cert_sign=False,
|
|
85
|
-
encipher_only=False,
|
|
86
|
-
decipher_only=False,
|
|
87
|
-
),
|
|
88
|
-
critical=True,
|
|
89
|
-
).add_extension(
|
|
90
|
-
x509.ExtendedKeyUsage(
|
|
91
|
-
[
|
|
92
|
-
x509.ExtendedKeyUsageOID.CLIENT_AUTH,
|
|
93
|
-
x509.ExtendedKeyUsageOID.EMAIL_PROTECTION,
|
|
94
|
-
]
|
|
95
|
-
),
|
|
96
|
-
critical=False,
|
|
58
|
+
csr_builder = (
|
|
59
|
+
csr_builder.subject_name(
|
|
60
|
+
x509.Name(
|
|
61
|
+
[
|
|
62
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
|
|
63
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state),
|
|
64
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, locality),
|
|
65
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
|
|
66
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
67
|
+
]
|
|
97
68
|
)
|
|
69
|
+
)
|
|
70
|
+
.add_extension(
|
|
71
|
+
x509.BasicConstraints(ca=False, path_length=None),
|
|
72
|
+
critical=False,
|
|
73
|
+
)
|
|
74
|
+
.add_extension(
|
|
75
|
+
x509.UnrecognizedExtension(
|
|
76
|
+
x509.ObjectIdentifier("2.16.840.1.113730.1.1"),
|
|
77
|
+
b"SSL Client, S/MIME",
|
|
78
|
+
),
|
|
79
|
+
critical=False,
|
|
80
|
+
)
|
|
81
|
+
.add_extension(
|
|
82
|
+
x509.UnrecognizedExtension(
|
|
83
|
+
x509.ObjectIdentifier("2.16.840.1.113730.1.13"),
|
|
84
|
+
b"Generated by OBS/OCD",
|
|
85
|
+
),
|
|
86
|
+
critical=False,
|
|
87
|
+
)
|
|
88
|
+
.add_extension(
|
|
89
|
+
x509.KeyUsage(
|
|
90
|
+
digital_signature=True,
|
|
91
|
+
crl_sign=False,
|
|
92
|
+
key_encipherment=True,
|
|
93
|
+
content_commitment=True,
|
|
94
|
+
data_encipherment=False,
|
|
95
|
+
key_agreement=False,
|
|
96
|
+
key_cert_sign=False,
|
|
97
|
+
encipher_only=False,
|
|
98
|
+
decipher_only=False,
|
|
99
|
+
),
|
|
100
|
+
critical=True,
|
|
101
|
+
)
|
|
102
|
+
.add_extension(
|
|
103
|
+
x509.ExtendedKeyUsage(
|
|
104
|
+
[
|
|
105
|
+
x509.ExtendedKeyUsageOID.CLIENT_AUTH,
|
|
106
|
+
x509.ExtendedKeyUsageOID.EMAIL_PROTECTION,
|
|
107
|
+
]
|
|
108
|
+
),
|
|
109
|
+
critical=False,
|
|
110
|
+
)
|
|
111
|
+
)
|
|
98
112
|
|
|
99
113
|
csr = csr_builder.sign(private_key, hashes.SHA256())
|
|
100
114
|
return csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import subprocess
|
|
3
|
+
|
|
4
|
+
from zscams.agent.src.support.logger import get_logger
|
|
5
|
+
|
|
6
|
+
from .filesystem import write_to_file
|
|
7
|
+
|
|
8
|
+
logger = get_logger("os_support")
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def is_linux():
|
|
12
|
+
if sys.platform.startswith("linux"):
|
|
13
|
+
return True
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def system_user_exists(username: str):
|
|
18
|
+
try:
|
|
19
|
+
subprocess.run(
|
|
20
|
+
["id", username],
|
|
21
|
+
check=True,
|
|
22
|
+
stdout=subprocess.DEVNULL,
|
|
23
|
+
stderr=subprocess.DEVNULL,
|
|
24
|
+
)
|
|
25
|
+
logger.debug("found the system user %s", username)
|
|
26
|
+
return True
|
|
27
|
+
except subprocess.CalledProcessError:
|
|
28
|
+
return False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def create_system_user(username: str):
|
|
32
|
+
if not is_linux():
|
|
33
|
+
logger.error(
|
|
34
|
+
"Error creating system user: This script is intended to run on Linux systems."
|
|
35
|
+
)
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
if not username:
|
|
39
|
+
logger.error("Error creating system user: Username must be provided.")
|
|
40
|
+
return
|
|
41
|
+
|
|
42
|
+
if system_user_exists(username):
|
|
43
|
+
logger.warning("User '%s' already exists.", username)
|
|
44
|
+
return
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
subprocess.run(["sudo", "useradd","-m", "-s", "/bin/bash", username], check=True)
|
|
48
|
+
logger.info("System user '%s' created successfully.", username)
|
|
49
|
+
except subprocess.CalledProcessError as e:
|
|
50
|
+
logger.error("Failed to create user '%s': %s", username, e)
|
|
51
|
+
|
|
52
|
+
|
|
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
|
|
59
|
+
|
|
60
|
+
service_path = f"/etc/systemd/system/{service_name}"
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
logger.debug("Installing service '%s' content", service_name)
|
|
64
|
+
write_to_file(service_path, content)
|
|
65
|
+
logger.debug(f"Installed {service_name}")
|
|
66
|
+
except PermissionError:
|
|
67
|
+
logger.warning("Permission denied: trying to write with sudo...")
|
|
68
|
+
echo_cmd = f"echo '{content}' | sudo tee {service_path}"
|
|
69
|
+
subprocess.run(echo_cmd, shell=True, check=True, stdout=subprocess.DEVNULL)
|
|
70
|
+
logger.debug("Wrote the service")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.error("Failed to install service %s. %s", service_name, e)
|
|
73
|
+
return
|
|
74
|
+
|
|
75
|
+
try:
|
|
76
|
+
logger.debug("Enabling %s...", service_name)
|
|
77
|
+
subprocess.run(["sudo", "systemctl", "enable", service_name], check=True)
|
|
78
|
+
logger.debug("Starting %s...", service_name)
|
|
79
|
+
subprocess.run(["sudo", "systemctl", "start", service_name], check=True)
|
|
80
|
+
except subprocess.CalledProcessError:
|
|
81
|
+
logger.error(
|
|
82
|
+
"Failed to enable/restart zscams service, You might need to do that manually by running\nsudo systemctl enable %s && sudo systemctl start %s",
|
|
83
|
+
service_name,
|
|
84
|
+
service_name,
|
|
85
|
+
)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
from zscams.agent.src.support.configuration import get_config
|
|
2
|
+
from zscams.agent.src.support.filesystem import append_to_file
|
|
3
|
+
from zscams.agent.src.support.logger import get_logger
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
logger = get_logger("ssh_support")
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def add_to_known_hosts(hostname: str, pub_key: str):
|
|
10
|
+
logger.debug("Appending '%s' to known hosts...", pub_key)
|
|
11
|
+
append_to_file(
|
|
12
|
+
get_config().get("ssh", {}).get("known_hosts_file_path"),
|
|
13
|
+
f"{hostname} {pub_key}\n",
|
|
14
|
+
)
|
|
15
|
+
logger.debug("Appended key to known hosts")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def add_to_authorized_keys(user, pub_key):
|
|
19
|
+
logger.debug(f"Appending to public key to {user}")
|
|
20
|
+
key = pub_key.split(' ')[1] if len(pub_key.split(' ')) >= 2 else pub_key
|
|
21
|
+
append_to_file(
|
|
22
|
+
f"/home/{user}/.ssh/authorized_keys",
|
|
23
|
+
f"ssh-rsa {key} zscams@orangecyberdefense\n",
|
|
24
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import yaml
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class YamlIndentedListsDumper(yaml.Dumper):
|
|
5
|
+
def increase_indent(self, flow=False, indentless=False):
|
|
6
|
+
return super(YamlIndentedListsDumper, self).increase_indent(flow, False)
|
|
7
|
+
|
|
8
|
+
def resolve_placeholders( obj, values: dict[str, int]):
|
|
9
|
+
"""
|
|
10
|
+
Recursively replace '{key}' strings with values[key]
|
|
11
|
+
in dicts, lists, and strings.
|
|
12
|
+
"""
|
|
13
|
+
if isinstance(obj, dict):
|
|
14
|
+
for k, v in obj.items():
|
|
15
|
+
obj[k] = resolve_placeholders(v, values)
|
|
16
|
+
|
|
17
|
+
elif isinstance(obj, list):
|
|
18
|
+
for i, v in enumerate(obj):
|
|
19
|
+
obj[i] = resolve_placeholders(v, values)
|
|
20
|
+
|
|
21
|
+
elif isinstance(obj, str):
|
|
22
|
+
for key, value in values.items():
|
|
23
|
+
placeholder = f"{{{key}}}"
|
|
24
|
+
if obj == placeholder:
|
|
25
|
+
return value
|
|
26
|
+
return obj
|
|
27
|
+
|
|
28
|
+
return obj
|
|
29
|
+
def assert_no_placeholders_left(obj):
|
|
30
|
+
if isinstance(obj, dict):
|
|
31
|
+
for v in obj.values():
|
|
32
|
+
assert_no_placeholders_left(v)
|
|
33
|
+
elif isinstance(obj, list):
|
|
34
|
+
for v in obj:
|
|
35
|
+
assert_no_placeholders_left(v)
|
|
36
|
+
elif isinstance(obj, str) and obj.startswith("{") and obj.endswith("}"):
|
|
37
|
+
raise ValueError(f"Unresolved placeholder: {obj}")
|
zscams/lib/.gitkeep
ADDED
|
File without changes
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: zscams
|
|
3
|
-
Version: 2.0.
|
|
3
|
+
Version: 2.0.3
|
|
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
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
zscams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
zscams/__main__.py,sha256=J3ArryUM5B-nVwMMNPmDGRHctehDF9ZaGGHfLHiP1jI,1055
|
|
3
|
+
zscams/agent/__init__.py,sha256=CKNRkV_frJyFrnFU0Nrf6xp4Ja4a_w7j7qrja_h8VUU,2976
|
|
4
|
+
zscams/agent/certificates/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
zscams/agent/config.yaml,sha256=S50bDUpyNg8gqM338TlYbX4d27mR2RrFajWINpkSliE,2555
|
|
6
|
+
zscams/agent/configuration/config.j2,sha256=DLkSd-opbPzFLvNipnPLx-OsUpISlZprPzabYjPF1-E,2659
|
|
7
|
+
zscams/agent/configuration/service.j2,sha256=B8t9eCmMbDOEu5kS3pIRdYb7Eh65Vacq0qSqoILgeC8,212
|
|
8
|
+
zscams/agent/keys/autoport.key,sha256=hZBmtw_nLsZwe11LYlwLL-P_blQ_qpUDpFwvqOZDZFE,1679
|
|
9
|
+
zscams/agent/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
zscams/agent/src/core/__init__.py,sha256=CEDwvbxojtNZOfOOFBj-URg4Q0KB0cq9AqIiD0uzPic,24
|
|
11
|
+
zscams/agent/src/core/backend/bootstrap.py,sha256=rsfGbZuvpt299_oWrQQHbJeXz4swwzoS54pr0nwBpvo,2340
|
|
12
|
+
zscams/agent/src/core/backend/client.py,sha256=xZw2GuOVw6Ek-QI9OaUO4rqzIrs4xGwiPizBLCH89fo,10123
|
|
13
|
+
zscams/agent/src/core/backend/exceptions.py,sha256=osMbVb_ZGvrGbw5cOCMG1s4yBLukJl7T8TITCcVPyXA,383
|
|
14
|
+
zscams/agent/src/core/backend/update_machine_info.py,sha256=9chBdvsLeLVf5DsvSHiUO9xQpXSbDgqhdnrUwxyoKUM,474
|
|
15
|
+
zscams/agent/src/core/prerequisites.py,sha256=LNPjDj2AQhZ3xBiLDNurG2osBTjepcU6tzDIm5PT_GA,1159
|
|
16
|
+
zscams/agent/src/core/service_health_check.py,sha256=9VUWQitXcDEwLcHTTeequi6om98OXN-JIIMZCCH5y4A,1733
|
|
17
|
+
zscams/agent/src/core/services.py,sha256=GvAYODh1Pg_FatnZO_8iqReiIeBfq7Hfwj9zxlXYm-0,2840
|
|
18
|
+
zscams/agent/src/core/tunnel/__init__.py,sha256=BvJmqtjliO-UvmEguOwky8KSGLY_w8xqM67Q3v2_jc0,4658
|
|
19
|
+
zscams/agent/src/core/tunnel/tls.py,sha256=EIRR7aLq6BkW6jUVseM1YCqm7E_UDVSQ9CffQri2U6U,2006
|
|
20
|
+
zscams/agent/src/core/tunnels.py,sha256=FwYi9cV3V7c_su5cEgXmyNdr8VyfCBKzU5olvi2MzBw,1736
|
|
21
|
+
zscams/agent/src/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
22
|
+
zscams/agent/src/services/reverse_ssh.py,sha256=-q9RJwyUvzSDftP7NNSmfHJHbx-2vmYrMy9KEjUmlyU,2081
|
|
23
|
+
zscams/agent/src/services/ssh_forwarder.py,sha256=BZ7-9i2YxdxtVfaxu5JDkwhFyNKDruIkph10bT6Ms-g,2223
|
|
24
|
+
zscams/agent/src/services/system_monitor.py,sha256=7VeSYHbr1Lv8tGVg0AMMd5_o7EgMGuRDlxJjxpGM27g,7529
|
|
25
|
+
zscams/agent/src/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
26
|
+
zscams/agent/src/support/cli.py,sha256=6Y0zokhwhWcsmjEosGqq_kMqF3tn4juPpsBmQax1Oyc,1221
|
|
27
|
+
zscams/agent/src/support/configuration.py,sha256=hnJhvJLdL5ElZITDFLybTOHVACBHttWgy8sXhmu75Wc,2025
|
|
28
|
+
zscams/agent/src/support/filesystem.py,sha256=jSVsnBFFBx8dFT3LzuVkGkK9CF1PScLxbyC_0vPkrRg,1587
|
|
29
|
+
zscams/agent/src/support/logger.py,sha256=rKjAVO6vDeXYTM9__q2lcc1PBiJoZUqLzry8yMf71vY,2347
|
|
30
|
+
zscams/agent/src/support/mac.py,sha256=EVlcxlgfPybH6S2IcC93MRNj9Dqc6_d6J14r8pW0PF0,464
|
|
31
|
+
zscams/agent/src/support/network.py,sha256=VwVVNqykZxvrTPwPYQ3sSVMc_Z2XUwASlo_kd_wdGDs,1453
|
|
32
|
+
zscams/agent/src/support/openssl.py,sha256=jLSv8ajIw1YfNdBhz4KSvNp-cARLXY9-7qdzne9Zca4,3429
|
|
33
|
+
zscams/agent/src/support/os.py,sha256=j7BtZ3D1pEEezZBw9eT8lWUBojMhIuovFbvNV0nLsdg,2766
|
|
34
|
+
zscams/agent/src/support/ssh.py,sha256=J9-XsVc6fGdcTN9CsfmgDRaMnfaWluDUaPGug7BVl6Q,812
|
|
35
|
+
zscams/agent/src/support/yaml.py,sha256=bKsQzXHAgjCxkGzPR8bgaUPB-QHMR3AMEVuvn4RRpnA,1188
|
|
36
|
+
zscams/lib/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
37
|
+
zscams/lib/getmac/__init__.py,sha256=iunkDFEtpGaRw1Y0lzr9OZ7LsZ-GH81E836ZJQC25Vg,80
|
|
38
|
+
zscams/lib/getmac/__main__.py,sha256=PTkcTMZSbvJ-qwMyVuo-Bf-RKI47y7Kwxqroo92EVS4,3547
|
|
39
|
+
zscams/lib/getmac/getmac.py,sha256=bFYx0WNBojEtNbIWKfNMm4mWI1Q2YLOx67Qluhs-fWU,64504
|
|
40
|
+
zscams/lib/getmac/shutilwhich.py,sha256=cTd3vGTZqKB44FOQhk7UG5M-Vb3qKg9HmuIy6hxkus0,2627
|
|
41
|
+
zscams-2.0.3.dist-info/METADATA,sha256=ULtW-vGMYs52TLIm3oCyxGOTtO1I5dxmIWeCnKwgqtI,7180
|
|
42
|
+
zscams-2.0.3.dist-info/WHEEL,sha256=iAMR_6Qh95yyjYIwRxyjpiFq4FhDPemrEV-SyWIQB3U,92
|
|
43
|
+
zscams-2.0.3.dist-info/entry_points.txt,sha256=IXiMYjEq4q0tUiD9O7eCWhqKBuOssXrMW42siTBAgG8,47
|
|
44
|
+
zscams-2.0.3.dist-info/RECORD,,
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
from zscams.agent.src.core.api.backend.client import backend_client
|
|
2
|
-
from zscams.agent.src.support.logger import get_logger
|
|
3
|
-
|
|
4
|
-
logger = get_logger("agent_bootstrap")
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def bootstrap():
|
|
8
|
-
"""Ensure the agent is bootstrapped with the backend."""
|
|
9
|
-
|
|
10
|
-
if backend_client.is_bootstrapped():
|
|
11
|
-
logger.info("Agent ID %s is already bootstrapped.", backend_client.agent_id)
|
|
12
|
-
return
|
|
13
|
-
|
|
14
|
-
username = input("LDAP Username: ")
|
|
15
|
-
totp = input("TOTP: ")
|
|
16
|
-
|
|
17
|
-
backend_client.login(username, totp)
|
|
18
|
-
|
|
19
|
-
equipment_name = input("Equipment Name: ")
|
|
20
|
-
enforced_id = input("Enforced Agent ID (Optional): ")
|
|
21
|
-
backend_client.bootstrap(equipment_name, enforced_id or None)
|
zscams-2.0.1.dist-info/RECORD
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
zscams/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
zscams/__main__.py,sha256=gawDyVPTo-ef-7IStyvS93Ch9cCmGxdhkvc-rBxq8sg,3105
|
|
3
|
-
zscams/agent/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
-
zscams/agent/certificates/.gitkeep,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
zscams/agent/config.yaml,sha256=6_nYDe0CuOxcCdYWWnoA7kZiPLLhK504QyoaaVuZ7z4,3556
|
|
6
|
-
zscams/agent/keys/autoport.key,sha256=hZBmtw_nLsZwe11LYlwLL-P_blQ_qpUDpFwvqOZDZFE,1679
|
|
7
|
-
zscams/agent/src/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
8
|
-
zscams/agent/src/core/__init__.py,sha256=CEDwvbxojtNZOfOOFBj-URg4Q0KB0cq9AqIiD0uzPic,24
|
|
9
|
-
zscams/agent/src/core/api/backend/bootstrap.py,sha256=MJ1ceskHmlRpan1Oj6nN2HQdMuwwSxNWCUPPsCXcmNs,667
|
|
10
|
-
zscams/agent/src/core/api/backend/client.py,sha256=0vjLOUXEMrrPERPKGsan8LzjQICIDsTKDbcGVrPBWGY,8542
|
|
11
|
-
zscams/agent/src/core/api/backend/exceptions.py,sha256=osMbVb_ZGvrGbw5cOCMG1s4yBLukJl7T8TITCcVPyXA,383
|
|
12
|
-
zscams/agent/src/core/api/backend/update_machine_info.py,sha256=9s2lmTwXqSJPrOq46ZUFHQ3dcOGZ45wzk11gLYJKA20,478
|
|
13
|
-
zscams/agent/src/core/prerequisites.py,sha256=LNPjDj2AQhZ3xBiLDNurG2osBTjepcU6tzDIm5PT_GA,1159
|
|
14
|
-
zscams/agent/src/core/service_health_check.py,sha256=9VUWQitXcDEwLcHTTeequi6om98OXN-JIIMZCCH5y4A,1733
|
|
15
|
-
zscams/agent/src/core/services.py,sha256=GvAYODh1Pg_FatnZO_8iqReiIeBfq7Hfwj9zxlXYm-0,2840
|
|
16
|
-
zscams/agent/src/core/tunnel/__init__.py,sha256=BvJmqtjliO-UvmEguOwky8KSGLY_w8xqM67Q3v2_jc0,4658
|
|
17
|
-
zscams/agent/src/core/tunnel/tls.py,sha256=EIRR7aLq6BkW6jUVseM1YCqm7E_UDVSQ9CffQri2U6U,2006
|
|
18
|
-
zscams/agent/src/core/tunnels.py,sha256=FwYi9cV3V7c_su5cEgXmyNdr8VyfCBKzU5olvi2MzBw,1736
|
|
19
|
-
zscams/agent/src/services/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
20
|
-
zscams/agent/src/services/reverse_ssh.py,sha256=-q9RJwyUvzSDftP7NNSmfHJHbx-2vmYrMy9KEjUmlyU,2081
|
|
21
|
-
zscams/agent/src/services/ssh_forwarder.py,sha256=BZ7-9i2YxdxtVfaxu5JDkwhFyNKDruIkph10bT6Ms-g,2223
|
|
22
|
-
zscams/agent/src/services/system_monitor.py,sha256=7VeSYHbr1Lv8tGVg0AMMd5_o7EgMGuRDlxJjxpGM27g,7529
|
|
23
|
-
zscams/agent/src/support/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
zscams/agent/src/support/configuration.py,sha256=gipsCDDjHH-ncxEURcu5b_ws4wmq9R58sRL-Fgx2O_Y,939
|
|
25
|
-
zscams/agent/src/support/filesystem.py,sha256=Cts8Sx1mr5Ndd15K9OQpvgAMgylBtZDUZIQtGibK7qI,962
|
|
26
|
-
zscams/agent/src/support/logger.py,sha256=lDkj-eTCdOlhyYO-ySH9UT_C4xh9S_jyMWM51N4i0xI,931
|
|
27
|
-
zscams/agent/src/support/mac.py,sha256=tCn6kB4m0VfrrQ04JEZrgOPEngLEZd5vDPdVdtFtvcg,465
|
|
28
|
-
zscams/agent/src/support/network.py,sha256=VwVVNqykZxvrTPwPYQ3sSVMc_Z2XUwASlo_kd_wdGDs,1453
|
|
29
|
-
zscams/agent/src/support/openssl.py,sha256=_Rd76Y2168hkLjeQlC7alvjUoWbiUWBum4yVpEfmVfw,3399
|
|
30
|
-
zscams/libs/getmac/__init__.py,sha256=iunkDFEtpGaRw1Y0lzr9OZ7LsZ-GH81E836ZJQC25Vg,80
|
|
31
|
-
zscams/libs/getmac/__main__.py,sha256=PTkcTMZSbvJ-qwMyVuo-Bf-RKI47y7Kwxqroo92EVS4,3547
|
|
32
|
-
zscams/libs/getmac/getmac.py,sha256=bFYx0WNBojEtNbIWKfNMm4mWI1Q2YLOx67Qluhs-fWU,64504
|
|
33
|
-
zscams/libs/getmac/shutilwhich.py,sha256=cTd3vGTZqKB44FOQhk7UG5M-Vb3qKg9HmuIy6hxkus0,2627
|
|
34
|
-
zscams-2.0.1.dist-info/METADATA,sha256=DX3VqzEC6Z32uJbOocIgiyEBYQg3k-YSn_CoF9uJt14,7156
|
|
35
|
-
zscams-2.0.1.dist-info/WHEEL,sha256=iAMR_6Qh95yyjYIwRxyjpiFq4FhDPemrEV-SyWIQB3U,92
|
|
36
|
-
zscams-2.0.1.dist-info/entry_points.txt,sha256=IXiMYjEq4q0tUiD9O7eCWhqKBuOssXrMW42siTBAgG8,47
|
|
37
|
-
zscams-2.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|