zscams 2.0.1__tar.gz → 2.0.3__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.
- {zscams-2.0.1 → zscams-2.0.3}/PKG-INFO +2 -1
- {zscams-2.0.1 → zscams-2.0.3}/pyproject.toml +2 -2
- zscams-2.0.3/zscams/__main__.py +43 -0
- zscams-2.0.1/zscams/__main__.py → zscams-2.0.3/zscams/agent/__init__.py +41 -49
- zscams-2.0.3/zscams/agent/config.yaml +103 -0
- zscams-2.0.3/zscams/agent/configuration/config.j2 +103 -0
- zscams-2.0.3/zscams/agent/configuration/service.j2 +12 -0
- zscams-2.0.3/zscams/agent/src/core/backend/bootstrap.py +73 -0
- {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.3/zscams/agent/src/core}/backend/client.py +45 -6
- {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.3/zscams/agent/src/core}/backend/update_machine_info.py +1 -1
- zscams-2.0.3/zscams/agent/src/support/cli.py +50 -0
- zscams-2.0.3/zscams/agent/src/support/configuration.py +86 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/support/filesystem.py +22 -0
- zscams-2.0.3/zscams/agent/src/support/logger.py +88 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/support/mac.py +1 -1
- zscams-2.0.3/zscams/agent/src/support/openssl.py +114 -0
- zscams-2.0.3/zscams/agent/src/support/os.py +85 -0
- zscams-2.0.3/zscams/agent/src/support/ssh.py +24 -0
- zscams-2.0.3/zscams/agent/src/support/yaml.py +37 -0
- zscams-2.0.1/zscams/agent/config.yaml +0 -121
- zscams-2.0.1/zscams/agent/src/core/api/backend/bootstrap.py +0 -21
- zscams-2.0.1/zscams/agent/src/support/configuration.py +0 -53
- zscams-2.0.1/zscams/agent/src/support/logger.py +0 -40
- zscams-2.0.1/zscams/agent/src/support/openssl.py +0 -100
- {zscams-2.0.1 → zscams-2.0.3}/README.md +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/__init__.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/certificates/.gitkeep +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/keys/autoport.key +0 -0
- {zscams-2.0.1/zscams/agent → zscams-2.0.3/zscams/agent/src}/__init__.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/__init__.py +0 -0
- {zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.3/zscams/agent/src/core}/backend/exceptions.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/prerequisites.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/service_health_check.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/services.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/tunnel/__init__.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/tunnel/tls.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/core/tunnels.py +0 -0
- {zscams-2.0.1/zscams/agent/src → zscams-2.0.3/zscams/agent/src/services}/__init__.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/services/reverse_ssh.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/services/ssh_forwarder.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/services/system_monitor.py +0 -0
- {zscams-2.0.1/zscams/agent/src/services → zscams-2.0.3/zscams/agent/src/support}/__init__.py +0 -0
- {zscams-2.0.1 → zscams-2.0.3}/zscams/agent/src/support/network.py +0 -0
- /zscams-2.0.1/zscams/agent/src/support/__init__.py → /zscams-2.0.3/zscams/lib/.gitkeep +0 -0
- {zscams-2.0.1/zscams/libs → zscams-2.0.3/zscams/lib}/getmac/__init__.py +0 -0
- {zscams-2.0.1/zscams/libs → zscams-2.0.3/zscams/lib}/getmac/__main__.py +0 -0
- {zscams-2.0.1/zscams/libs → zscams-2.0.3/zscams/lib}/getmac/getmac.py +0 -0
- {zscams-2.0.1/zscams/libs → zscams-2.0.3/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.
|
|
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
|
|
@@ -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.
|
|
6
|
+
version = "2.0.3"
|
|
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"]
|
|
@@ -15,6 +14,7 @@ packages = [ { include = "zscams"}]
|
|
|
15
14
|
PyYAML = "*"
|
|
16
15
|
cryptography = "*"
|
|
17
16
|
psutil = "*"
|
|
17
|
+
requests = "*"
|
|
18
18
|
|
|
19
19
|
[tool.poetry.scripts]
|
|
20
20
|
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.
|
|
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.
|
|
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
|
-
|
|
27
|
-
|
|
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
|
|
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,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)
|
{zscams-2.0.1/zscams/agent/src/core/api → zscams-2.0.3/zscams/agent/src/core}/backend/client.py
RENAMED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
115
|
-
|
|
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
|
-
|
|
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()
|
|
@@ -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
|