Coverage for src/meshadmin/common/utils.py: 65%
109 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-10 16:08 +0200
« prev ^ index » next coverage.py v7.6.12, created at 2025-04-10 16:08 +0200
1import json
2import os
3import platform
4import stat
5import subprocess
6import tempfile
7from datetime import datetime, timedelta, timezone
8from pathlib import Path
10import httpx
11import structlog
13logger = structlog.get_logger(__name__)
16def get_nebula_install_path():
17 os_name = platform.system()
19 if os_name == "Darwin":
20 base_path = Path(os.path.expanduser("~/Library/Application Support/nebula"))
21 elif os_name == "Linux":
22 base_path = Path("/opt/nebula")
23 else:
24 raise NotImplementedError(f"Unsupported operating system: {os_name}")
26 if not base_path.exists():
27 base_path.mkdir(parents=True, exist_ok=True)
29 return base_path
32def download_nebula_binaries(mesh_admin_endpoint):
33 os_name = platform.system()
34 if os_name not in ["Linux", "Darwin"]:
35 raise NotImplementedError(
36 f"Unsupported operating system: {os_name}. Only Linux and Darwin are supported."
37 )
39 architecture = platform.machine()
41 binaries = ["nebula", "nebula-cert"]
42 install_path = get_nebula_install_path()
44 for binary in binaries:
45 binary_path = install_path / binary
47 if binary_path.exists():
48 logger.info(f"{binary} binary already exists", path=str(binary_path))
49 continue
51 url = f"{mesh_admin_endpoint}/api/v1/nebula/download/{os_name}/{architecture}/{binary}"
52 logger.info(f"Downloading {binary}", url=url)
54 try:
55 with httpx.stream("GET", url) as response:
56 response.raise_for_status()
57 with open(binary_path, "wb") as f:
58 for chunk in response.iter_bytes():
59 f.write(chunk)
61 # Make the binary executable
62 binary_path.chmod(binary_path.stat().st_mode | stat.S_IEXEC)
63 logger.info(f"{binary} downloaded and installed", path=str(binary_path))
65 except httpx.HTTPStatusError as e:
66 logger.error(
67 f"Failed to download {binary}", status_code=e.response.status_code
68 )
69 raise RuntimeError(f"Failed to download {binary}: {e}")
71 return install_path
74def get_nebula_path():
75 return get_nebula_binary_path()
78def get_nebula_binary_path():
79 install_path = get_nebula_install_path()
80 return install_path / "nebula"
83def get_nebula_cert_binary_path():
84 install_path = get_nebula_install_path()
85 return install_path / "nebula-cert"
88def create_keys():
89 with tempfile.TemporaryDirectory() as tmpdir:
90 res = subprocess.run(
91 [
92 get_nebula_cert_binary_path(),
93 "keygen",
94 "-out-key",
95 "private.key",
96 "-out-pub",
97 "public.key",
98 ],
99 capture_output=True,
100 text=True,
101 cwd=tmpdir,
102 )
103 if res.returncode != 0:
104 raise RuntimeError(f"Failed to create keys: {res.stderr}")
106 private_key = (Path(tmpdir) / "private.key").read_text()
107 public_key = (Path(tmpdir) / "public.key").read_text()
109 return private_key, public_key
112def sign_keys(
113 ca_key: str,
114 ca_crt: str,
115 public_key: str,
116 name: str,
117 ip: str,
118 groups: frozenset[str],
119):
120 with tempfile.TemporaryDirectory() as tmpdir:
121 temp_path = Path(tmpdir)
123 ca_crt_path = temp_path / "ca.crt"
124 ca_crt_path.write_text(ca_crt)
125 ca_key_path = temp_path / "ca.key"
126 ca_key_path.write_text(ca_key)
128 public_key_file = temp_path / "public.key"
129 public_key_file.write_text(public_key)
131 command = [
132 str(get_nebula_cert_binary_path()),
133 "sign",
134 "-in-pub",
135 "public.key",
136 "-name",
137 name,
138 "-ip",
139 ip,
140 "-groups",
141 " " + ",".join(groups) + " ",
142 "-out-crt",
143 "public.crt",
144 ]
145 print(f"cd {temp_path}")
146 print(" ".join(command))
148 res = subprocess.run(
149 command,
150 capture_output=True,
151 text=True,
152 cwd=tmpdir,
153 )
154 assert res.returncode == 0, res.stderr
155 cert = (Path(tmpdir) / "public.crt").read_text()
156 return cert
159def print_ca(cert: str):
160 with tempfile.TemporaryDirectory() as tmpdir:
161 temp_path = Path(tmpdir)
163 cert_path = temp_path / "cert.crt"
164 cert_path.write_text(cert)
166 command = [
167 get_nebula_cert_binary_path(),
168 "print",
169 "-path",
170 "cert.crt",
171 "-json",
172 ]
173 res = subprocess.run(
174 command,
175 capture_output=True,
176 text=True,
177 cwd=tmpdir,
178 )
180 assert res.returncode == 0, res.stderr
181 output = res.stdout
182 json_output = json.loads(output)
183 return json_output
186def create_ca(ca_name):
187 with tempfile.TemporaryDirectory() as tmpdir:
188 res = subprocess.run(
189 [str(get_nebula_cert_binary_path()), "ca", "-name", ca_name],
190 capture_output=True,
191 text=True,
192 cwd=tmpdir,
193 )
194 assert res.returncode == 0
195 cert = (Path(tmpdir) / "ca.crt").read_text()
196 key = (Path(tmpdir) / "ca.key").read_text()
198 return cert, key
201def create_expiration_date(minutes=60) -> int:
202 return int(
203 (
204 datetime.utcnow().replace(tzinfo=timezone.utc) + timedelta(minutes)
205 ).timestamp()
206 )
209def get_public_ip():
210 res = httpx.get("https://checkip.amazonaws.com/")
211 assert res.status_code == 200
212 public_ip = res.text.strip()
213 return public_ip
216def get_default_config_path() -> Path:
217 os_name = platform.system()
218 if os_name == "Darwin":
219 return Path(os.path.expanduser("~/Library/Application Support/meshadmin"))
220 else:
221 return Path("/etc/meshadmin")