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 +0 -3
- zscams/__main__.py +4 -4
- zscams/agent/__init__.py +7 -7
- zscams/agent/configuration/config.j2 +2 -2
- zscams/agent/configuration/freebsd_service.j2 +1 -1
- zscams/agent/src/core/backend/bootstrap.py +39 -19
- zscams/agent/src/core/backend/client.py +54 -34
- zscams/agent/src/core/backend/unbootstrap.py +33 -13
- zscams/agent/src/support/cli.py +97 -15
- zscams/agent/src/support/configuration.py +109 -64
- zscams/agent/src/support/logger.py +4 -3
- zscams/agent/src/support/os.py +29 -14
- zscams/agent/src/support/ssh.py +4 -5
- zscams/agent/src/support/yaml.py +6 -2
- zscams/deps.py +3 -5
- {zscams-2.0.10.dist-info → zscams-2.0.12.dist-info}/METADATA +1 -1
- {zscams-2.0.10.dist-info → zscams-2.0.12.dist-info}/RECORD +19 -20
- zscams/agent/config.yaml +0 -103
- {zscams-2.0.10.dist-info → zscams-2.0.12.dist-info}/WHEEL +0 -0
- {zscams-2.0.10.dist-info → zscams-2.0.12.dist-info}/entry_points.txt +0 -0
zscams/__init__.py
CHANGED
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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(
|
|
75
|
-
|
|
76
|
-
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
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
|
|
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 =
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
template_path = f
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
34
|
-
self.
|
|
35
|
-
self.
|
|
36
|
-
self.
|
|
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 =
|
|
41
|
+
self.agent_id = get_machine_id()
|
|
41
42
|
self.logger = get_logger("backend_client")
|
|
42
43
|
|
|
43
|
-
self.__machine_cache_path =
|
|
44
|
-
|
|
45
|
-
|
|
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.
|
|
63
|
-
remote_configs =
|
|
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": [
|
|
101
|
-
|
|
102
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
|
266
|
-
|
|
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
|
-
|
|
272
|
-
|
|
273
|
-
)
|
|
274
|
-
resolve_placeholders(
|
|
275
|
-
assert_no_placeholders_left(
|
|
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(
|
|
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
|
|
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
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 =
|
|
19
|
-
|
|
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 =
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
49
|
-
|
|
67
|
+
removed_service = remove_service("zscams")
|
|
68
|
+
if removed_service:
|
|
69
|
+
logger.debug("Removed ZSCAMs service")
|
zscams/agent/src/support/cli.py
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
from typing import Optional,
|
|
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
|
-
|
|
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 =
|
|
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(
|
|
46
|
+
logger.error("%s is required..", name)
|
|
35
47
|
retries_count -= 1
|
|
36
48
|
return prompt(
|
|
37
|
-
name,
|
|
49
|
+
name, description, required, startswith, fail_on_error=retries_count <= 0
|
|
38
50
|
)
|
|
39
51
|
|
|
40
|
-
if startswith
|
|
41
|
-
|
|
42
|
-
if
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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
|
-
|
|
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
|
-
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
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(
|
|
79
|
+
console_handler.setLevel(logging_level)
|
|
79
80
|
|
|
80
81
|
console_formatter = ColorFormatter(
|
|
81
82
|
fmt="[%(asctime)s] [%(levelname)s] [%(name)s] %(message)s",
|
zscams/agent/src/support/os.py
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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 (
|
|
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
|
-
|
|
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()
|
zscams/agent/src/support/ssh.py
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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",
|
zscams/agent/src/support/yaml.py
CHANGED
|
@@ -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
|
-
|
|
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,18 +1,17 @@
|
|
|
1
|
-
zscams/__init__.py,sha256=
|
|
2
|
-
zscams/__main__.py,sha256
|
|
3
|
-
zscams/agent/__init__.py,sha256=
|
|
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.
|
|
6
|
-
zscams/agent/configuration/
|
|
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=
|
|
13
|
-
zscams/agent/src/core/backend/client.py,sha256=
|
|
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=
|
|
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=
|
|
29
|
-
zscams/agent/src/support/configuration.py,sha256=
|
|
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=
|
|
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=
|
|
36
|
-
zscams/agent/src/support/ssh.py,sha256=
|
|
37
|
-
zscams/agent/src/support/yaml.py,sha256=
|
|
38
|
-
zscams/deps.py,sha256=
|
|
39
|
-
zscams-2.0.
|
|
40
|
-
zscams-2.0.
|
|
41
|
-
zscams-2.0.
|
|
42
|
-
zscams-2.0.
|
|
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
|
|
File without changes
|
|
File without changes
|