zscams 2.0.1__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/__init__.py +0 -0
- zscams/__main__.py +101 -0
- zscams/agent/__init__.py +0 -0
- zscams/agent/certificates/.gitkeep +0 -0
- zscams/agent/config.yaml +121 -0
- zscams/agent/keys/autoport.key +27 -0
- zscams/agent/src/__init__.py +0 -0
- zscams/agent/src/core/__init__.py +1 -0
- zscams/agent/src/core/api/backend/bootstrap.py +21 -0
- zscams/agent/src/core/api/backend/client.py +242 -0
- zscams/agent/src/core/api/backend/exceptions.py +10 -0
- zscams/agent/src/core/api/backend/update_machine_info.py +16 -0
- zscams/agent/src/core/prerequisites.py +36 -0
- zscams/agent/src/core/service_health_check.py +49 -0
- zscams/agent/src/core/services.py +86 -0
- zscams/agent/src/core/tunnel/__init__.py +144 -0
- zscams/agent/src/core/tunnel/tls.py +56 -0
- zscams/agent/src/core/tunnels.py +55 -0
- zscams/agent/src/services/__init__.py +0 -0
- zscams/agent/src/services/reverse_ssh.py +73 -0
- zscams/agent/src/services/ssh_forwarder.py +75 -0
- zscams/agent/src/services/system_monitor.py +264 -0
- zscams/agent/src/support/__init__.py +0 -0
- zscams/agent/src/support/configuration.py +53 -0
- zscams/agent/src/support/filesystem.py +41 -0
- zscams/agent/src/support/logger.py +40 -0
- zscams/agent/src/support/mac.py +18 -0
- zscams/agent/src/support/network.py +49 -0
- zscams/agent/src/support/openssl.py +100 -0
- zscams/libs/getmac/__init__.py +3 -0
- zscams/libs/getmac/__main__.py +123 -0
- zscams/libs/getmac/getmac.py +1900 -0
- zscams/libs/getmac/shutilwhich.py +67 -0
- zscams-2.0.1.dist-info/METADATA +118 -0
- zscams-2.0.1.dist-info/RECORD +37 -0
- zscams-2.0.1.dist-info/WHEEL +4 -0
- zscams-2.0.1.dist-info/entry_points.txt +3 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import datetime
|
|
5
|
+
import psutil
|
|
6
|
+
import logging
|
|
7
|
+
import logging.handlers
|
|
8
|
+
import json
|
|
9
|
+
import socket
|
|
10
|
+
import platform
|
|
11
|
+
from zscams.agent.src.support.logger import get_logger
|
|
12
|
+
from http.client import BadStatusLine, HTTPConnection, HTTPException
|
|
13
|
+
logger = get_logger("system_monitor")
|
|
14
|
+
|
|
15
|
+
# Load service-specific params from environment
|
|
16
|
+
params_env = os.environ.get("SERVICE_PARAMS")
|
|
17
|
+
if not params_env:
|
|
18
|
+
logger.error("SERVICE_PARAMS environment variable not set")
|
|
19
|
+
sys.exit(1)
|
|
20
|
+
|
|
21
|
+
params = json.loads(params_env)
|
|
22
|
+
# -----------------------------
|
|
23
|
+
# Configuration
|
|
24
|
+
# -----------------------------
|
|
25
|
+
SYSLOG_HOST = params.get("remote_host", "localhost") # remote rsyslog host
|
|
26
|
+
SYSLOG_PORT = params.get("remote_port", 514) # TCP syslog port
|
|
27
|
+
EQUIPMENT_NAME = params.get("equipment_name", "connector")
|
|
28
|
+
EQUIPMENT_TYPE = params.get("equipment_type", "zpa")
|
|
29
|
+
SERVICE_NAME = params.get("service_name", "Zscaler-AppConnector")
|
|
30
|
+
HOSTNAME = platform.node()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def network():
|
|
36
|
+
"""
|
|
37
|
+
Collect network interfaces and their statistics.
|
|
38
|
+
Returns a dictionary with per-interface
|
|
39
|
+
"""
|
|
40
|
+
ifaces = {}
|
|
41
|
+
for iface, stats in psutil.net_io_counters(pernic=True).items():
|
|
42
|
+
ifaces[iface] = {
|
|
43
|
+
"in": {
|
|
44
|
+
"bytes": stats.bytes_recv,
|
|
45
|
+
"packets": stats.packets_recv,
|
|
46
|
+
"errors": getattr(stats, "errin", 0),
|
|
47
|
+
"dropped": getattr(stats, "dropin", 0),
|
|
48
|
+
},
|
|
49
|
+
"out": {
|
|
50
|
+
"bytes": stats.bytes_sent,
|
|
51
|
+
"packets": stats.packets_sent,
|
|
52
|
+
"errors": getattr(stats, "errout", 0),
|
|
53
|
+
"dropped": getattr(stats, "dropout", 0),
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
if "lo" in ifaces:
|
|
57
|
+
del ifaces["lo"]
|
|
58
|
+
return ifaces
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def memory():
|
|
62
|
+
"""
|
|
63
|
+
Get memory and swap usage
|
|
64
|
+
Returns a dictionary with total, used, free, and swap info.
|
|
65
|
+
"""
|
|
66
|
+
mem = psutil.virtual_memory()
|
|
67
|
+
swap = psutil.swap_memory()
|
|
68
|
+
mem_remain = mem.total - mem.available
|
|
69
|
+
output_memory = {
|
|
70
|
+
"actual": {
|
|
71
|
+
"free": mem.available,
|
|
72
|
+
"used": {
|
|
73
|
+
"pct": round(mem_remain / mem.total, 4),
|
|
74
|
+
"bytes": mem_remain,
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
"total": mem.total,
|
|
78
|
+
"used": {
|
|
79
|
+
"pct": round(mem.percent / 100, 4),
|
|
80
|
+
"bytes": mem.total - mem.available,
|
|
81
|
+
},
|
|
82
|
+
"free": mem.available,
|
|
83
|
+
"swap": {
|
|
84
|
+
"total": swap.total,
|
|
85
|
+
"used": {
|
|
86
|
+
"pct": swap.percent / 100,
|
|
87
|
+
"bytes": swap.used,
|
|
88
|
+
},
|
|
89
|
+
"free": swap.free,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
return output_memory
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def cpu():
|
|
96
|
+
"""
|
|
97
|
+
Get CPU usage
|
|
98
|
+
Returns a dictionary with total, user, system, idle, and core count.
|
|
99
|
+
"""
|
|
100
|
+
times = psutil.cpu_times()
|
|
101
|
+
total = sum(times)
|
|
102
|
+
used = total - times.idle
|
|
103
|
+
cpu_total_pct = round(used / total, 4)
|
|
104
|
+
return {
|
|
105
|
+
"total": {"pct": cpu_total_pct},
|
|
106
|
+
"system": {"pct": round(times.system / total, 4)},
|
|
107
|
+
"user": {"pct": round(times.user / total, 4)},
|
|
108
|
+
"idle": {"pct": round(times.idle / total, 4)},
|
|
109
|
+
"cores": psutil.cpu_count(),
|
|
110
|
+
"iowait": {"pct": round(getattr(times, "iowait", 0) / total, 4)},
|
|
111
|
+
"irq": {"pct": round(getattr(times, "irq", 0) / total, 4)},
|
|
112
|
+
"softirq": {"pct": round(getattr(times, "softirq", 0) / total, 4)},
|
|
113
|
+
"nice": {"pct": round(getattr(times, "nice", 0) / total, 4)},
|
|
114
|
+
"steal": {"pct": round(getattr(times, "steal", 0) / total, 4)},
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def load():
|
|
119
|
+
"""
|
|
120
|
+
Get CPU load averages over 1, 5, and 15 minutes.
|
|
121
|
+
Returns a dictionary with normalized load per CPU core.
|
|
122
|
+
"""
|
|
123
|
+
load1, load5, load15 = psutil.getloadavg()
|
|
124
|
+
cores = psutil.cpu_count()
|
|
125
|
+
cores = float(cores) if cores else 1.0
|
|
126
|
+
return {
|
|
127
|
+
"1": load1,
|
|
128
|
+
"5": load5,
|
|
129
|
+
"15": load15,
|
|
130
|
+
"cores": cores,
|
|
131
|
+
"norm": {
|
|
132
|
+
"1": load1 / cores,
|
|
133
|
+
"5": load5 / cores,
|
|
134
|
+
"15": load15 / cores,
|
|
135
|
+
},
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def uptime():
|
|
140
|
+
"""
|
|
141
|
+
Return system boot time in seconds since the epoch.
|
|
142
|
+
"""
|
|
143
|
+
return {"duration": {"seconds": int(psutil.boot_time())}}
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def disk():
|
|
147
|
+
"""
|
|
148
|
+
Get disk usage metrics for the root filesystem.
|
|
149
|
+
Returns a dictionary with total, used, and free bytes.
|
|
150
|
+
"""
|
|
151
|
+
disk = psutil.disk_usage("/")
|
|
152
|
+
return {"total": disk.total, "used": disk.used, "free": disk.free}
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def process(process_names=None):
|
|
156
|
+
"""
|
|
157
|
+
Get metrics about running processes by name.
|
|
158
|
+
Default: ["zpa-connector", "zpa-service-edge"]
|
|
159
|
+
Returns a list of dictionaries with process info.
|
|
160
|
+
"""
|
|
161
|
+
if process_names is None:
|
|
162
|
+
process_names = ["zpa-connector", "zpa-service-edge"]
|
|
163
|
+
|
|
164
|
+
output = []
|
|
165
|
+
for proc in psutil.process_iter(
|
|
166
|
+
[
|
|
167
|
+
"name",
|
|
168
|
+
"pid",
|
|
169
|
+
"ppid",
|
|
170
|
+
"username",
|
|
171
|
+
"memory_info",
|
|
172
|
+
"cpu_times",
|
|
173
|
+
"cmdline",
|
|
174
|
+
"status",
|
|
175
|
+
"create_time",
|
|
176
|
+
]
|
|
177
|
+
):
|
|
178
|
+
try:
|
|
179
|
+
if proc.info["name"] in process_names:
|
|
180
|
+
mem = proc.info["memory_info"]
|
|
181
|
+
cpu_pct = proc.cpu_percent(interval=0.1)
|
|
182
|
+
output.append(
|
|
183
|
+
{
|
|
184
|
+
"name": proc.info["name"],
|
|
185
|
+
"pid": proc.info["pid"],
|
|
186
|
+
"ppid": proc.info["ppid"],
|
|
187
|
+
"username": proc.info["username"],
|
|
188
|
+
"memory": {
|
|
189
|
+
"rss": mem.rss,
|
|
190
|
+
"vms": mem.vms,
|
|
191
|
+
"percent": round(proc.memory_percent(), 2),
|
|
192
|
+
},
|
|
193
|
+
"cpu": {
|
|
194
|
+
"percent": cpu_pct,
|
|
195
|
+
"start_time": datetime.datetime.fromtimestamp(
|
|
196
|
+
proc.info["create_time"]
|
|
197
|
+
).isoformat(),
|
|
198
|
+
},
|
|
199
|
+
"cmdline": proc.info["cmdline"],
|
|
200
|
+
"status": proc.info["status"],
|
|
201
|
+
}
|
|
202
|
+
)
|
|
203
|
+
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
|
204
|
+
continue
|
|
205
|
+
return output
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def collect_all():
|
|
209
|
+
"""
|
|
210
|
+
Collect all metrics in a single dictionary.
|
|
211
|
+
"""
|
|
212
|
+
return {
|
|
213
|
+
"network": network(),
|
|
214
|
+
"memory": memory(),
|
|
215
|
+
"cpu": cpu(),
|
|
216
|
+
"load": load(),
|
|
217
|
+
"uptime": uptime(),
|
|
218
|
+
"disk": disk(),
|
|
219
|
+
"processes": process(),
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def log():
|
|
224
|
+
return {
|
|
225
|
+
"system": collect_all(),
|
|
226
|
+
"beat": {
|
|
227
|
+
"name": EQUIPMENT_NAME,
|
|
228
|
+
"type": EQUIPMENT_TYPE,
|
|
229
|
+
"hostname": HOSTNAME,
|
|
230
|
+
},
|
|
231
|
+
"host": HOSTNAME,
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
# -----------------------------
|
|
237
|
+
# Helper function to log JSON
|
|
238
|
+
# -----------------------------
|
|
239
|
+
async def send_json_log(payload: dict):
|
|
240
|
+
"""
|
|
241
|
+
Send a JSON-formatted log to syslog via TCP
|
|
242
|
+
"""
|
|
243
|
+
conn = HTTPConnection(SYSLOG_HOST, SYSLOG_PORT, timeout=10)
|
|
244
|
+
conn.request(
|
|
245
|
+
"POST", "/", json.dumps(payload), headers={"Content-type": "application/json"}
|
|
246
|
+
)
|
|
247
|
+
conn.close()
|
|
248
|
+
logger.info("Metrices collected and sent successfully.")
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
async def schedule_task(interval):
|
|
252
|
+
while (True):
|
|
253
|
+
try:
|
|
254
|
+
await send_json_log(log())
|
|
255
|
+
except Exception as exception:
|
|
256
|
+
print(exception)
|
|
257
|
+
await asyncio.sleep(interval)
|
|
258
|
+
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
try:
|
|
261
|
+
asyncio.run(schedule_task(30))
|
|
262
|
+
except KeyboardInterrupt:
|
|
263
|
+
logger.info("Exiting system_monitor.py")
|
|
264
|
+
sys.exit(0)
|
|
File without changes
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Configuration loader module
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import TypedDict
|
|
8
|
+
|
|
9
|
+
import yaml
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
config = {}
|
|
13
|
+
|
|
14
|
+
ROOT_PATH = Path(__file__).parent.parent.parent
|
|
15
|
+
|
|
16
|
+
CONFIG_PATH = os.path.join(ROOT_PATH.absolute(), "config.yaml")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class RemoteConfig(TypedDict):
|
|
20
|
+
"""Type definition for remote configuration."""
|
|
21
|
+
|
|
22
|
+
host: str
|
|
23
|
+
port: str
|
|
24
|
+
verify_cert: bool
|
|
25
|
+
client_key: str
|
|
26
|
+
ca_cert: str
|
|
27
|
+
ca_chain: str
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def load_config():
|
|
31
|
+
"""
|
|
32
|
+
Load and parse the YAML configuration file.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
dict: Configuration dictionary containing remote settings and forwards.
|
|
36
|
+
"""
|
|
37
|
+
with open(CONFIG_PATH, "r") as f:
|
|
38
|
+
config = yaml.safe_load(f)
|
|
39
|
+
return config
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def get_config():
|
|
43
|
+
"""
|
|
44
|
+
Get the loaded configuration.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
dict: Configuration dictionary containing remote settings and forwards.
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
if not config:
|
|
51
|
+
return load_config()
|
|
52
|
+
|
|
53
|
+
return config
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Path utilities for TLS Tunnel Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def resolve_path(path: Optional[str], base_dir: Optional[str] = None) -> Optional[str]:
|
|
10
|
+
"""
|
|
11
|
+
Resolve a file path relative to a base directory.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
path (str): Path to resolve (absolute or relative)
|
|
15
|
+
base_dir (str, optional): Directory to resolve relative paths from.
|
|
16
|
+
Defaults to None (current working directory)
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
str: Absolute path
|
|
20
|
+
"""
|
|
21
|
+
if path is None:
|
|
22
|
+
return None
|
|
23
|
+
if not os.path.isabs(path) and base_dir:
|
|
24
|
+
return os.path.join(base_dir, path)
|
|
25
|
+
return path
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def ensure_dir(path: str):
|
|
29
|
+
"""Ensure a directory exists."""
|
|
30
|
+
|
|
31
|
+
os.makedirs(path, exist_ok=True)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_file_exists(path, logger):
|
|
35
|
+
"""Check if file exists."""
|
|
36
|
+
if os.path.exists(path):
|
|
37
|
+
logger.debug(f"File exists: {path}")
|
|
38
|
+
return True
|
|
39
|
+
|
|
40
|
+
logger.error(f"File not found: {path}")
|
|
41
|
+
return False
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Logger module for TLS Tunnel Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from zscams.agent.src.support.configuration import get_config
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
loggers: dict[str, logging.Logger] = {}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def get_logger(name: str = "tls_tunnel") -> logging.Logger:
|
|
15
|
+
"""Create a singleton instance for that logger name"""
|
|
16
|
+
|
|
17
|
+
if name in loggers:
|
|
18
|
+
return loggers[name]
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(name)
|
|
21
|
+
|
|
22
|
+
level = get_config().get("logging", {}).get("level", 10)
|
|
23
|
+
|
|
24
|
+
logger.setLevel(level)
|
|
25
|
+
logger.propagate = False
|
|
26
|
+
|
|
27
|
+
if logger.hasHandlers():
|
|
28
|
+
logger.handlers.clear()
|
|
29
|
+
|
|
30
|
+
console_handler = logging.StreamHandler(sys.stdout)
|
|
31
|
+
console_handler.setLevel(level)
|
|
32
|
+
|
|
33
|
+
console_formatter = logging.Formatter(
|
|
34
|
+
"%(asctime)s [%(levelname)s] %(name)s: %(message)s", "%Y-%m-%d %H:%M:%S"
|
|
35
|
+
)
|
|
36
|
+
console_handler.setFormatter(console_formatter)
|
|
37
|
+
logger.addHandler(console_handler)
|
|
38
|
+
|
|
39
|
+
loggers[name] = logger
|
|
40
|
+
return logger
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Module to retrieve the MAC address of the primary network interface."""
|
|
2
|
+
|
|
3
|
+
from zscams.libs.getmac import get_mac_address as gma
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class MacAddressError(Exception):
|
|
7
|
+
"""Exception raised when MAC address cannot be retrieved."""
|
|
8
|
+
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def get_mac_address() -> str:
|
|
13
|
+
"""Get the MAC address of the primary network interface."""
|
|
14
|
+
|
|
15
|
+
mac_addr = gma()
|
|
16
|
+
if not mac_addr:
|
|
17
|
+
raise MacAddressError("Could not retrieve MAC address.")
|
|
18
|
+
return mac_addr
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Network utilities for TLS Tunnel Client
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import socket
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def is_port_open(host, port, logger):
|
|
9
|
+
"""Check if TCP port is open."""
|
|
10
|
+
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
|
|
11
|
+
sock.settimeout(1)
|
|
12
|
+
try:
|
|
13
|
+
sock.connect((host, port))
|
|
14
|
+
logger.debug(f"Port {port} on {host} is open.")
|
|
15
|
+
return True
|
|
16
|
+
except (ConnectionRefusedError, OSError):
|
|
17
|
+
logger.error(f"Port {port} on {host} is not open.")
|
|
18
|
+
return False
|
|
19
|
+
except Exception as err:
|
|
20
|
+
logger.error("Port check failed for %s:%s. Error: %s", host, port, err)
|
|
21
|
+
return False
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def is_service_running(service_name, running_services, logger):
|
|
25
|
+
"""Wait for another service to be marked as running."""
|
|
26
|
+
if service_name in running_services:
|
|
27
|
+
logger.debug(f"Service {service_name} is running.")
|
|
28
|
+
return True
|
|
29
|
+
|
|
30
|
+
logger.error(f"Service {service_name} is not running.")
|
|
31
|
+
return False
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_local_ip_address():
|
|
35
|
+
"""Get the local IP address of the machine."""
|
|
36
|
+
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
|
37
|
+
try:
|
|
38
|
+
# The IP used here doesn't need to be reachable
|
|
39
|
+
s.connect(("8.8.8.8", 80))
|
|
40
|
+
ip = s.getsockname()[0]
|
|
41
|
+
except Exception:
|
|
42
|
+
ip = "127.0.0.1"
|
|
43
|
+
return ip
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_local_hostname():
|
|
47
|
+
"""Get the local hostname of the machine."""
|
|
48
|
+
|
|
49
|
+
return socket.gethostname()
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides a simple interface for generating RSA key pairs
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from cryptography import x509
|
|
6
|
+
from cryptography.hazmat.primitives.asymmetric import rsa
|
|
7
|
+
from cryptography.x509.oid import NameOID
|
|
8
|
+
from cryptography.hazmat.primitives import hashes, serialization
|
|
9
|
+
from cryptography.hazmat.backends import default_backend
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def generate_private_key(key_path: str):
|
|
13
|
+
"""Generate a new RSA private key and save it to a file."""
|
|
14
|
+
|
|
15
|
+
private_key = rsa.generate_private_key(
|
|
16
|
+
public_exponent=65537,
|
|
17
|
+
key_size=4096,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
pem_private_key = private_key.private_bytes(
|
|
21
|
+
encoding=serialization.Encoding.PEM,
|
|
22
|
+
format=serialization.PrivateFormat.PKCS8,
|
|
23
|
+
encryption_algorithm=serialization.NoEncryption(),
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
with open(key_path, "wb") as f:
|
|
27
|
+
f.write(pem_private_key)
|
|
28
|
+
|
|
29
|
+
return pem_private_key
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
# pylint: disable=too-many-arguments,too-many-positional-arguments
|
|
33
|
+
def generate_csr_from_private_key(
|
|
34
|
+
private_key_content: bytes,
|
|
35
|
+
country: str,
|
|
36
|
+
state: str,
|
|
37
|
+
locality: str,
|
|
38
|
+
organization: str,
|
|
39
|
+
common_name: str,
|
|
40
|
+
) -> str:
|
|
41
|
+
"""
|
|
42
|
+
Generate a CSR (Certificate Signing Request) from an existing private key.
|
|
43
|
+
Returns:
|
|
44
|
+
str: The generated CSR in PEM format.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
private_key = serialization.load_pem_private_key(
|
|
48
|
+
private_key_content,
|
|
49
|
+
password=None,
|
|
50
|
+
backend=default_backend(),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
csr_builder = x509.CertificateSigningRequestBuilder()
|
|
54
|
+
csr_builder = csr_builder.subject_name(
|
|
55
|
+
x509.Name(
|
|
56
|
+
[
|
|
57
|
+
x509.NameAttribute(NameOID.COUNTRY_NAME, country),
|
|
58
|
+
x509.NameAttribute(NameOID.STATE_OR_PROVINCE_NAME, state),
|
|
59
|
+
x509.NameAttribute(NameOID.LOCALITY_NAME, locality),
|
|
60
|
+
x509.NameAttribute(NameOID.ORGANIZATION_NAME, organization),
|
|
61
|
+
x509.NameAttribute(NameOID.COMMON_NAME, common_name),
|
|
62
|
+
]
|
|
63
|
+
)).add_extension(
|
|
64
|
+
x509.BasicConstraints(ca=False, path_length=None),
|
|
65
|
+
critical=False,).add_extension(
|
|
66
|
+
x509.UnrecognizedExtension(
|
|
67
|
+
x509.ObjectIdentifier("2.16.840.1.113730.1.1"),
|
|
68
|
+
b"SSL Client, S/MIME",
|
|
69
|
+
),
|
|
70
|
+
critical=False,).add_extension(
|
|
71
|
+
x509.UnrecognizedExtension(
|
|
72
|
+
x509.ObjectIdentifier("2.16.840.1.113730.1.13"),
|
|
73
|
+
b"Generated by OBS/OCD",
|
|
74
|
+
),
|
|
75
|
+
critical=False,
|
|
76
|
+
).add_extension(
|
|
77
|
+
x509.KeyUsage(
|
|
78
|
+
digital_signature=True,
|
|
79
|
+
crl_sign=False,
|
|
80
|
+
key_encipherment=True,
|
|
81
|
+
content_commitment=True,
|
|
82
|
+
data_encipherment=False,
|
|
83
|
+
key_agreement=False,
|
|
84
|
+
key_cert_sign=False,
|
|
85
|
+
encipher_only=False,
|
|
86
|
+
decipher_only=False,
|
|
87
|
+
),
|
|
88
|
+
critical=True,
|
|
89
|
+
).add_extension(
|
|
90
|
+
x509.ExtendedKeyUsage(
|
|
91
|
+
[
|
|
92
|
+
x509.ExtendedKeyUsageOID.CLIENT_AUTH,
|
|
93
|
+
x509.ExtendedKeyUsageOID.EMAIL_PROTECTION,
|
|
94
|
+
]
|
|
95
|
+
),
|
|
96
|
+
critical=False,
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
csr = csr_builder.sign(private_key, hashes.SHA256())
|
|
100
|
+
return csr.public_bytes(serialization.Encoding.PEM).decode("utf-8")
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env python
|
|
2
|
+
# -*- coding: utf-8 -*-
|
|
3
|
+
|
|
4
|
+
from __future__ import print_function
|
|
5
|
+
|
|
6
|
+
import argparse
|
|
7
|
+
import logging
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
from . import getmac
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def main(): # type: () -> None
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
"getmac",
|
|
16
|
+
description="Get the MAC address of system network "
|
|
17
|
+
"interfaces or remote hosts on the LAN",
|
|
18
|
+
)
|
|
19
|
+
parser.add_argument(
|
|
20
|
+
"--version", action="version", version="getmac %s" % getmac.__version__
|
|
21
|
+
)
|
|
22
|
+
parser.add_argument(
|
|
23
|
+
"-v", "--verbose", action="store_true", help="Enable output messages"
|
|
24
|
+
)
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"-d",
|
|
27
|
+
"--debug",
|
|
28
|
+
action="count",
|
|
29
|
+
help="Enable debugging output. Add characters to "
|
|
30
|
+
"increase verbosity of output, e.g. '-dd'.",
|
|
31
|
+
)
|
|
32
|
+
parser.add_argument(
|
|
33
|
+
"-N",
|
|
34
|
+
"--no-net",
|
|
35
|
+
"--no-network-requests",
|
|
36
|
+
action="store_true",
|
|
37
|
+
dest="NO_NET",
|
|
38
|
+
help="Do not use arping or send a UDP packet to refresh the ARP table",
|
|
39
|
+
)
|
|
40
|
+
parser.add_argument(
|
|
41
|
+
"--override-port",
|
|
42
|
+
type=int,
|
|
43
|
+
metavar="PORT",
|
|
44
|
+
help="Override the default UDP port used to refresh the ARP table "
|
|
45
|
+
"if network requests are enabled and arping is unavailable",
|
|
46
|
+
)
|
|
47
|
+
parser.add_argument(
|
|
48
|
+
"--override-platform",
|
|
49
|
+
type=str,
|
|
50
|
+
default=None,
|
|
51
|
+
metavar="PLATFORM",
|
|
52
|
+
help="Override the platform detection with the given value "
|
|
53
|
+
"(e.g. 'linux', 'windows', 'freebsd', etc.'). "
|
|
54
|
+
"Any values returned by platform.system() are valid.",
|
|
55
|
+
)
|
|
56
|
+
parser.add_argument(
|
|
57
|
+
"--force-method",
|
|
58
|
+
type=str,
|
|
59
|
+
default=None,
|
|
60
|
+
metavar="METHOD",
|
|
61
|
+
help="Force a specific method to be used, e.g. 'IpNeighborShow'. "
|
|
62
|
+
"This will be used regardless of it's method type or platform "
|
|
63
|
+
"compatibility, and Method.test() will NOT be checked!",
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
group = parser.add_mutually_exclusive_group(required=False)
|
|
67
|
+
group.add_argument(
|
|
68
|
+
"-i",
|
|
69
|
+
"--interface",
|
|
70
|
+
type=str,
|
|
71
|
+
default=None,
|
|
72
|
+
help="Name of a network interface on the system",
|
|
73
|
+
)
|
|
74
|
+
group.add_argument(
|
|
75
|
+
"-4", "--ip", type=str, default=None, help="IPv4 address of a remote host"
|
|
76
|
+
)
|
|
77
|
+
group.add_argument(
|
|
78
|
+
"-6", "--ip6", type=str, default=None, help="IPv6 address of a remote host"
|
|
79
|
+
)
|
|
80
|
+
group.add_argument(
|
|
81
|
+
"-n", "--hostname", type=str, default=None, help="Hostname of a remote host"
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
args = parser.parse_args()
|
|
85
|
+
|
|
86
|
+
if args.debug or args.verbose:
|
|
87
|
+
logging.basicConfig(
|
|
88
|
+
format="%(levelname)-8s %(message)s", level=logging.DEBUG, stream=sys.stderr
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
if args.debug:
|
|
92
|
+
getmac.DEBUG = args.debug
|
|
93
|
+
|
|
94
|
+
if args.override_port:
|
|
95
|
+
port = int(args.override_port)
|
|
96
|
+
getmac.log.debug(
|
|
97
|
+
"Using UDP port %d (overriding the default port %d)", port, getmac.PORT
|
|
98
|
+
)
|
|
99
|
+
getmac.PORT = port
|
|
100
|
+
|
|
101
|
+
if args.override_platform:
|
|
102
|
+
getmac.OVERRIDE_PLATFORM = args.override_platform.strip().lower()
|
|
103
|
+
|
|
104
|
+
if args.force_method:
|
|
105
|
+
getmac.FORCE_METHOD = args.force_method.strip().lower()
|
|
106
|
+
|
|
107
|
+
mac = getmac.get_mac_address(
|
|
108
|
+
interface=args.interface,
|
|
109
|
+
ip=args.ip,
|
|
110
|
+
ip6=args.ip6,
|
|
111
|
+
hostname=args.hostname,
|
|
112
|
+
network_request=not args.NO_NET,
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
if mac is not None:
|
|
116
|
+
print(mac) # noqa: T001, T201
|
|
117
|
+
sys.exit(0) # Exit success!
|
|
118
|
+
else:
|
|
119
|
+
sys.exit(1) # Exit with error since it failed to find a MAC
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if __name__ == "__main__":
|
|
123
|
+
main()
|