zscams 2.0.1__py2.py3-none-any.whl → 2.0.2__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.2.dist-info}/METADATA +2 -1
- zscams-2.0.2.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.2.dist-info}/WHEEL +0 -0
- {zscams-2.0.1.dist-info → zscams-2.0.2.dist-info}/entry_points.txt +0 -0
zscams/__main__.py
CHANGED
|
@@ -2,84 +2,26 @@
|
|
|
2
2
|
Main entry point for ZSCAMs Agent
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
import os
|
|
6
5
|
import asyncio
|
|
7
6
|
import sys
|
|
8
|
-
import
|
|
9
|
-
from zscams.agent.src.core.
|
|
10
|
-
from zscams.agent.src.core.
|
|
11
|
-
from zscams.agent.src.support.configuration import get_config, CONFIG_PATH
|
|
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
|
|
12
10
|
from zscams.agent.src.support.logger import get_logger
|
|
13
|
-
from zscams.agent
|
|
14
|
-
from zscams.agent.src.core.tunnels import start_all_tunnels
|
|
15
|
-
from zscams.agent.src.core.services import start_all_services
|
|
16
|
-
from zscams.agent.src.core.service_health_check import monitor_services
|
|
17
|
-
from zscams.agent.src.core.api.backend.client import backend_client
|
|
18
|
-
|
|
19
|
-
logger = get_logger("tls_tunnel_main")
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def ensure_bootstrapped():
|
|
23
|
-
"""Ensure the agent is bootstrapped with the backend."""
|
|
24
|
-
|
|
25
|
-
bootstrapped = backend_client.is_bootstrapped()
|
|
26
|
-
logger.debug("Agent bootstrapped: %s", bootstrapped)
|
|
27
|
-
if not bootstrapped:
|
|
28
|
-
logger.error(
|
|
29
|
-
"Agent is not bootstrapped. Please run `zscams --bootstrap` to start the bootstrap process.",
|
|
30
|
-
)
|
|
31
|
-
sys.exit(1)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
async def async_main():
|
|
35
|
-
"""Asynchronous main function to start tunnels and services."""
|
|
36
|
-
|
|
37
|
-
config = get_config()
|
|
38
|
-
config_dir = os.path.dirname(CONFIG_PATH)
|
|
39
|
-
ssl_context = create_ssl_context(config["remote"], config_dir=config_dir)
|
|
40
|
-
remote_host = config["remote"]["host"]
|
|
41
|
-
remote_port = config["remote"]["port"]
|
|
42
|
-
|
|
43
|
-
# Start tunnels and wait for readiness
|
|
44
|
-
tunnel_tasks = await start_all_tunnels(
|
|
45
|
-
config.get("forwards", []), remote_host, remote_port, ssl_context
|
|
46
|
-
)
|
|
47
|
-
|
|
48
|
-
# Start services that depend on tunnels
|
|
49
|
-
service_tasks = asyncio.create_task(
|
|
50
|
-
start_all_services(config.get("services", []), config_dir=config_dir)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
# Start health checks
|
|
54
|
-
monitor_task = asyncio.create_task(
|
|
55
|
-
monitor_services(
|
|
56
|
-
config.get("services", []),
|
|
57
|
-
config_dir=config_dir,
|
|
58
|
-
interval=config.get("general", {}).get("health_check_interval", 300),
|
|
59
|
-
)
|
|
60
|
-
)
|
|
11
|
+
from zscams.agent import init_parser, ensure_bootstrapped, run
|
|
61
12
|
|
|
62
|
-
|
|
63
|
-
await asyncio.gather(*tunnel_tasks, service_tasks, monitor_task)
|
|
13
|
+
logger = get_logger("ZSCAMs")
|
|
64
14
|
|
|
65
15
|
|
|
66
16
|
def main():
|
|
67
17
|
"""Main function to run the asynchronous main."""
|
|
68
|
-
|
|
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()
|
|
18
|
+
args = init_parser().parse_args()
|
|
80
19
|
|
|
81
20
|
if args.bootstrap:
|
|
82
21
|
try:
|
|
22
|
+
if os.geteuid() != 0:
|
|
23
|
+
logger.error("You are NOT running as root.")
|
|
24
|
+
sys.exit(1)
|
|
83
25
|
bootstrap()
|
|
84
26
|
sys.exit(0)
|
|
85
27
|
except Exception as exception:
|
|
@@ -92,7 +34,7 @@ def main():
|
|
|
92
34
|
|
|
93
35
|
try:
|
|
94
36
|
ensure_bootstrapped()
|
|
95
|
-
asyncio.run(
|
|
37
|
+
asyncio.run(run())
|
|
96
38
|
except KeyboardInterrupt:
|
|
97
39
|
logger.info("Exiting TLS Tunnel Client")
|
|
98
40
|
|
zscams/agent/__init__.py
CHANGED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
import sys
|
|
4
|
+
import argparse
|
|
5
|
+
from zscams.agent.src.support.configuration import get_config, CONFIG_PATH
|
|
6
|
+
from zscams.agent.src.support.filesystem import is_file_exists, resolve_path
|
|
7
|
+
from zscams.agent.src.core.tunnel.tls import create_ssl_context
|
|
8
|
+
from zscams.agent.src.core.tunnels import start_all_tunnels
|
|
9
|
+
from zscams.agent.src.core.services import start_all_services
|
|
10
|
+
from zscams.agent.src.core.service_health_check import monitor_services
|
|
11
|
+
from zscams.agent.src.core.backend.client import backend_client
|
|
12
|
+
from zscams.agent.src.support.logger import get_logger
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
logger = get_logger("tls_tunnel_main")
|
|
16
|
+
|
|
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
|
+
|
|
33
|
+
def ensure_bootstrapped():
|
|
34
|
+
"""Ensure the agent is bootstrapped with the backend."""
|
|
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
|
+
|
|
53
|
+
bootstrapped = backend_client.is_bootstrapped()
|
|
54
|
+
|
|
55
|
+
logger.debug("Agent has entry on server: %s", bootstrapped)
|
|
56
|
+
|
|
57
|
+
if not bootstrapped or has_missing_file:
|
|
58
|
+
logger.error(
|
|
59
|
+
"Agent is not bootstrapped. Please run `zscams --bootstrap` to start the bootstrap process.",
|
|
60
|
+
)
|
|
61
|
+
sys.exit(1)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
async def run():
|
|
65
|
+
"""Asynchronous main function to start tunnels and services."""
|
|
66
|
+
|
|
67
|
+
config = get_config()
|
|
68
|
+
config_dir = os.path.dirname(CONFIG_PATH)
|
|
69
|
+
ssl_context = create_ssl_context(config["remote"], config_dir=config_dir)
|
|
70
|
+
remote_host = config["remote"]["host"]
|
|
71
|
+
remote_port = config["remote"]["port"]
|
|
72
|
+
|
|
73
|
+
# Start tunnels and wait for readiness
|
|
74
|
+
tunnel_tasks = await start_all_tunnels(
|
|
75
|
+
config.get("forwards", []), remote_host, remote_port, ssl_context
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
# Start services that depend on tunnels
|
|
79
|
+
service_tasks = asyncio.create_task(
|
|
80
|
+
start_all_services(config.get("services", []), config_dir=config_dir)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Start health checks
|
|
84
|
+
monitor_task = asyncio.create_task(
|
|
85
|
+
monitor_services(
|
|
86
|
+
config.get("services", []),
|
|
87
|
+
config_dir=config_dir,
|
|
88
|
+
interval=config.get("general", {}).get("health_check_interval", 300),
|
|
89
|
+
)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
logger.info("[*] All tunnels and services started. Press Ctrl+C to stop.")
|
|
93
|
+
await asyncio.gather(*tunnel_tasks, service_tasks, monitor_task)
|
zscams/agent/config.yaml
CHANGED
|
@@ -1,121 +1,103 @@
|
|
|
1
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
|
|
2
23
|
logging:
|
|
3
24
|
level: 10
|
|
4
|
-
|
|
5
|
-
general:
|
|
6
|
-
health_check_interval: 30 # seconds between health checks for services
|
|
7
|
-
|
|
8
25
|
remote:
|
|
9
|
-
host: 81.255.240.214 # stunnel server IP or hostname
|
|
10
|
-
port: 443 # Single TLS port on stunnel server
|
|
11
|
-
verify_cert: false # set false for self-signed certs
|
|
12
26
|
ca_cert: certificates/ca_chain.pem
|
|
27
|
+
ca_chain: certificates/ca_chain.pem
|
|
13
28
|
client_cert: certificates/client.crt
|
|
14
29
|
client_key: certificates/client.key
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
- local_port: 4422
|
|
19
|
-
sni_hostname: ssh-tunnel
|
|
20
|
-
description: "Forward to SSH backend"
|
|
21
|
-
- local_port: 4423
|
|
22
|
-
sni_hostname: monitor-tunnel
|
|
23
|
-
description: "Forward to Seculogs"
|
|
24
|
-
|
|
25
|
-
backend:
|
|
26
|
-
base_url: "http://22.0.122.40:5000/api"
|
|
27
|
-
cache_dir: ".cache"
|
|
28
|
-
|
|
29
|
-
bootstrap_info:
|
|
30
|
-
csr:
|
|
31
|
-
country: "FR"
|
|
32
|
-
state: "Ile-de-France"
|
|
33
|
-
locality: "Paris"
|
|
34
|
-
common_name: "zscams-agent.example.com"
|
|
35
|
-
organization: "zscams-agent.orangecyberdefense.com"
|
|
36
|
-
cert_issuer: "ZSCAMs"
|
|
37
|
-
blacklist_ports: []
|
|
38
|
-
|
|
30
|
+
host: 81.255.240.214
|
|
31
|
+
port: 443
|
|
32
|
+
verify_cert: false
|
|
39
33
|
services:
|
|
40
|
-
-
|
|
41
|
-
|
|
42
|
-
# Its just for reference in logs but not used within the service as the
|
|
43
|
-
# actual port is passed via params (Remote Port). Im setting it to 22 as
|
|
44
|
-
# its the eventual port that will be connected to
|
|
45
|
-
port: 22
|
|
46
|
-
args: []
|
|
34
|
+
- args: []
|
|
35
|
+
custom_port_name: reverse_port
|
|
47
36
|
health:
|
|
48
37
|
host: localhost
|
|
49
|
-
# port that should be open if service is healthy
|
|
50
38
|
port: 22
|
|
51
|
-
|
|
52
|
-
# -R [remote_listen_address:]remote_listen_port:local_host:local_port
|
|
39
|
+
name: Reverse SSH
|
|
53
40
|
params:
|
|
54
|
-
remote_host: "localhost"
|
|
55
|
-
# The forwarding port on ZSCAMs server to connect to in order to create
|
|
56
|
-
# the reverse tunnel, this should match the local_port of one of the
|
|
57
|
-
# forwards above
|
|
58
41
|
local_port: 4422
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
reverse_port: 53612
|
|
64
|
-
# Path to private key for authentication
|
|
65
|
-
private_key: 'zscams/agent/keys/autoport.key'
|
|
42
|
+
private_key: zscams/agent/keys/autoport.key
|
|
43
|
+
remote_host: localhost
|
|
44
|
+
reverse_port: 10000
|
|
45
|
+
server_ssh_user: ssh_user
|
|
66
46
|
ssh_options:
|
|
67
|
-
-
|
|
68
|
-
-
|
|
69
|
-
-
|
|
70
|
-
-
|
|
71
|
-
-
|
|
47
|
+
- -o LogLevel=error
|
|
48
|
+
- -o UserKnownHostsFile=/dev/null
|
|
49
|
+
- -o StrictHostKeyChecking=no
|
|
50
|
+
- -o ServerAliveInterval=30
|
|
51
|
+
- -o ServerAliveCountMax=2
|
|
52
|
+
port: 22
|
|
72
53
|
prerequisites:
|
|
73
54
|
files:
|
|
74
|
-
-
|
|
75
|
-
services: []
|
|
55
|
+
- zscams/agent/keys/autoport.key
|
|
76
56
|
ports:
|
|
77
57
|
- 22
|
|
78
|
-
|
|
79
|
-
script:
|
|
80
|
-
|
|
81
|
-
|
|
58
|
+
services: []
|
|
59
|
+
script: src/services/reverse_ssh.py
|
|
60
|
+
- args: []
|
|
61
|
+
custom_port_name: forwarder_port
|
|
82
62
|
health:
|
|
83
63
|
host: localhost
|
|
84
|
-
|
|
85
|
-
|
|
64
|
+
port: 10001
|
|
65
|
+
name: Monitor Forwarder
|
|
86
66
|
params:
|
|
87
|
-
|
|
67
|
+
forwarder_port: 10001
|
|
68
|
+
local_port: 4423
|
|
69
|
+
private_key: zscams/agent/keys/autoport.key
|
|
70
|
+
remote_host: 194.51.14.159
|
|
88
71
|
remote_port: 530
|
|
89
|
-
|
|
90
|
-
forwarder_port: 44514 #Port the forwarder will listen on and forward to remote host and port
|
|
91
|
-
server_ssh_user: "ssh_user"
|
|
92
|
-
private_key: 'zscams/agent/keys/autoport.key'
|
|
72
|
+
server_ssh_user: ssh_user
|
|
93
73
|
ssh_options:
|
|
94
|
-
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
-
|
|
74
|
+
- -o LogLevel=error
|
|
75
|
+
- -o UserKnownHostsFile=/dev/null
|
|
76
|
+
- -o StrictHostKeyChecking=no
|
|
77
|
+
- -o ServerAliveInterval=30
|
|
78
|
+
- -o ServerAliveCountMax=2
|
|
79
|
+
port: 530
|
|
99
80
|
prerequisites:
|
|
100
81
|
files:
|
|
101
|
-
-
|
|
102
|
-
services: []
|
|
82
|
+
- zscams/agent/keys/autoport.key
|
|
103
83
|
ports: []
|
|
104
|
-
|
|
105
|
-
script:
|
|
106
|
-
|
|
107
|
-
args: []
|
|
84
|
+
services: []
|
|
85
|
+
script: src/services/ssh_forwarder.py
|
|
86
|
+
- args: []
|
|
108
87
|
health:
|
|
109
88
|
host: localhost
|
|
110
|
-
port:
|
|
89
|
+
port: 10001
|
|
90
|
+
name: Monitor Service
|
|
111
91
|
params:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
service_name:
|
|
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
|
|
117
98
|
prerequisites:
|
|
118
99
|
files: []
|
|
119
|
-
services: []
|
|
120
100
|
ports:
|
|
121
|
-
-
|
|
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)
|
|
@@ -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()
|