mirror of
https://github.com/FAUSheppy/athq-vm-management
synced 2025-12-06 13:51:35 +01:00
Compare commits
12 Commits
bfd1c20c2b
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d4b720cee | |||
| c8640eb035 | |||
| 2a9559642b | |||
| ac2fbfdef2 | |||
| 9cb5204fec | |||
| cd76f3fe6b | |||
|
|
9634f35a1e | ||
| 73106f6d57 | |||
|
|
3222b4b437 | ||
|
|
e99e729a83 | ||
|
|
84cb2f9fb2 | ||
|
|
f71269d14b |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,3 +8,4 @@ password.txt
|
|||||||
ssh_config_for_clients
|
ssh_config_for_clients
|
||||||
virsh_backup
|
virsh_backup
|
||||||
.wireguard_keys
|
.wireguard_keys
|
||||||
|
master-address.txt
|
||||||
|
|||||||
10
backup.py
10
backup.py
@@ -3,6 +3,7 @@ import functools
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import json
|
import json
|
||||||
|
import sys
|
||||||
|
|
||||||
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="./templates"))
|
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="./templates"))
|
||||||
|
|
||||||
@@ -20,6 +21,15 @@ def createBackupScriptStructure(backupList, baseDomain="", icingaOnly=False, bac
|
|||||||
asyncIcingaConf = {}
|
asyncIcingaConf = {}
|
||||||
for backup in backupList:
|
for backup in backupList:
|
||||||
|
|
||||||
|
if not backup:
|
||||||
|
print("Warning: Empty backup mapping in List", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
if type(backup) == str:
|
||||||
|
print(f"Warning: Backup Entry is a stirng instead of a dict-object ({backup})", file=sys.stderr)
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
if backup.get("disabled"):
|
if backup.get("disabled"):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
|||||||
45
helper_scripts/build_ssh_config_on_workstation.py
Normal file
45
helper_scripts/build_ssh_config_on_workstation.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
HOSTS = [
|
||||||
|
"root@atlantishq.de",
|
||||||
|
"root@katzencluster.atlantishq.de",
|
||||||
|
"root@atlantis-helsinki.atlantishq.de"
|
||||||
|
]
|
||||||
|
|
||||||
|
BASE_FILE = "~/.ssh/base_config"
|
||||||
|
MAIN_CONFIG = "~/.ssh/config"
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
contents = ""
|
||||||
|
for target in HOSTS:
|
||||||
|
|
||||||
|
RUN_CMD = ["ssh", "-t", target , "cd /root/athq-vm-management/; python3 main.py"]
|
||||||
|
COPY_CMD = ["ssh", "-t", target, "cat /root/athq-vm-management/ssh_config_for_clients"]
|
||||||
|
|
||||||
|
print("Doing", target, file=sys.stderr)
|
||||||
|
out = subprocess.run(RUN_CMD, capture_output=True, universal_newlines=True)
|
||||||
|
if out.returncode != 0:
|
||||||
|
print("failed (run command)!")
|
||||||
|
print(out.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
out = subprocess.run(COPY_CMD, capture_output=True, universal_newlines=True)
|
||||||
|
if out.returncode != 0:
|
||||||
|
print("failed (cat command)!")
|
||||||
|
print(out.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
contents += out.stdout
|
||||||
|
contents += "\n"
|
||||||
|
|
||||||
|
with open(os.path.expanduser(BASE_FILE)) as f:
|
||||||
|
with open(os.path.expanduser(MAIN_CONFIG), "w") as fout:
|
||||||
|
fout.write(f.read())
|
||||||
|
fout.write("\n")
|
||||||
|
fout.write(contents)
|
||||||
12
helper_scripts/qemu-hooks/qemu
Normal file
12
helper_scripts/qemu-hooks/qemu
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
if [[ "$2" == "started" ]]; then
|
||||||
|
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWI 1 -o virbr0 -i atlantishq -d 192.168.123.0/24 -j ACCEPT
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWI 1 -o virbr0 -i at_helsinki -d 192.168.123.0/24 -j ACCEPT
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWI 1 -o virbr0 -i hc_worker_1 -d 192.168.123.0/24 -j ACCEPT
|
||||||
|
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWO 1 -i virbr0 -s 192.168.123.0/24 -o atlantishq -j ACCEPT
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWO 1 -i virbr0 -s 192.168.123.0/24 -o hc_worker_1 -j ACCEPT
|
||||||
|
/usr/sbin/iptables -I LIBVIRT_FWO 1 -i virbr0 -s 192.168.123.0/24 -o at_helsinki -j ACCEPT
|
||||||
|
|
||||||
|
fi
|
||||||
107
helper_scripts/wireguard-config-helper.py
Normal file
107
helper_scripts/wireguard-config-helper.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import configparser
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
LINK_BASE = f"10.0.{len(EXISTING_HOSTS)+10}.1/32"
|
||||||
|
LINK_BASE_PEER = f"10.0.{len(EXISTING_HOSTS)+10}.2/32"
|
||||||
|
PORT_BASE = 51820 + len(EXISTING_HOSTS)
|
||||||
|
|
||||||
|
MASTER_CONFIG = """[Interface]
|
||||||
|
PrivateKey = {local_private}
|
||||||
|
Address = {local_address}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
Endpoint = {peer_endpoint}
|
||||||
|
PublicKey = {peer_public}
|
||||||
|
AllowedIPs = {", ".join(peer_allowed_ips)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PEER_CONFIG = f"""[Interface]
|
||||||
|
PrivateKey = {peer_private}
|
||||||
|
Address = {peer_address}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = {local_public}
|
||||||
|
AllowedIPs = {", ".join(peer_allowed_ips)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def create_wireguard_config_pair(
|
||||||
|
new_host: str,
|
||||||
|
old_host: str,
|
||||||
|
new_host_allowed_ips: list[str],
|
||||||
|
old_host_allowed_ips,
|
||||||
|
old_host_public_key,
|
||||||
|
old_host_private_key,
|
||||||
|
output_dir: str = "./tmp"
|
||||||
|
):
|
||||||
|
|
||||||
|
# Validate input subnet
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Interface(local_address)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"Invalid IP address: {local_address}") from e
|
||||||
|
|
||||||
|
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Generate key pairs
|
||||||
|
local_private, local_public = generate_keypair()
|
||||||
|
peer_private, peer_public = generate_keypair()
|
||||||
|
|
||||||
|
new_host = MASTER_CONFIG.format(
|
||||||
|
local_private = local_private,
|
||||||
|
local_address = LINK_BASE,
|
||||||
|
local_public = local_public,
|
||||||
|
peer_endpoint = old_host,
|
||||||
|
peer_public = old_host_public_key,
|
||||||
|
peer_allowed_ips = [LINK_BASE_PEER, old_host_allowed_ips],
|
||||||
|
|
||||||
|
)
|
||||||
|
old_host = PEER_CONFIG.format(
|
||||||
|
peer_private = old_host_private_key,
|
||||||
|
peer_address = LINK_BASE_PEER,
|
||||||
|
local_public= local_public,
|
||||||
|
peer_allowed_ips = [LINK_BASE, new_host_subnet]
|
||||||
|
)
|
||||||
|
|
||||||
|
old_host_file = Path(output_dir) / f"{new_host}_local.conf"
|
||||||
|
new_host_file = Path(output_dir) / f"{new_host}_peer.conf"
|
||||||
|
|
||||||
|
new_host_file.write_text(new_host)
|
||||||
|
old_host_file.write_text(old_host)
|
||||||
|
|
||||||
|
return str(local_file), str(peer_file)
|
||||||
|
|
||||||
|
def generate_wireguard_keys():
|
||||||
|
|
||||||
|
private_key = subprocess.check_output(["wg", "genkey"]).strip()
|
||||||
|
public_key = subprocess.check_output(["wg", "pubkey"], input=private_key).strip()
|
||||||
|
|
||||||
|
return private_key.decode(), public_key.decode()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="WireGuard utility script")
|
||||||
|
|
||||||
|
parser.add_argument("--new-host", help="Hostname or IP of the new host")
|
||||||
|
parser.add_argument("--new-host-subnet", type=ipaddress.IPv4Network, help="Subnet of new host")
|
||||||
|
parser.add_argument("--write", action=argparse.BooleanOptionalAction, default=False)
|
||||||
|
parser.add_argument("--allow-overwrite", action=argparse.BooleanOptionalAction, default=False)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
configs = {}
|
||||||
|
|
||||||
|
for host in EXISTING_HOSTS:
|
||||||
|
fetch_conf_files(host)
|
||||||
|
configs |= load_all_confs(host)
|
||||||
|
|
||||||
|
# output current state #
|
||||||
|
print(json.dumps(configs, indent=2))
|
||||||
|
|
||||||
|
if args.new_host and args.new_host_subnet:
|
||||||
|
|
||||||
|
# TODO get keys from old host
|
||||||
|
create_wireguard_config_pair(LINK_BASE, new_host) #TODO
|
||||||
159
helper_scripts/wireguard-extender.py
Normal file
159
helper_scripts/wireguard-extender.py
Normal file
@@ -0,0 +1,159 @@
|
|||||||
|
import subprocess
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
import configparser
|
||||||
|
import ipaddress
|
||||||
|
|
||||||
|
# FIXME/WARNING: dont change this order without checking the output
|
||||||
|
EXISTING_HOSTS = [
|
||||||
|
"atlantishq.de",
|
||||||
|
"katzencluster.atlantishq.de",
|
||||||
|
"atlantis-helsinki.atlantishq.de",
|
||||||
|
]
|
||||||
|
|
||||||
|
USER = "root"
|
||||||
|
REMOTE_DIR = "/etc/wireguard"
|
||||||
|
LOCAL_TMP_DIR = "./tmp"
|
||||||
|
|
||||||
|
LINK_BASE = f"10.0.{len(EXISTING_HOSTS)+10}.1/32"
|
||||||
|
LINK_BASE_PEER = f"10.0.{len(EXISTING_HOSTS)+10}.2/32"
|
||||||
|
PORT_BASE = 51820 + len(EXISTING_HOSTS)
|
||||||
|
|
||||||
|
def fetch_conf_files(host):
|
||||||
|
|
||||||
|
print(f"Doing {host}\n")
|
||||||
|
os.makedirs(LOCAL_TMP_DIR, exist_ok=True)
|
||||||
|
remote_path = f"{USER}@{host}:{REMOTE_DIR}/*.conf"
|
||||||
|
|
||||||
|
subprocess.run(["scp", remote_path, LOCAL_TMP_DIR], check=True)
|
||||||
|
print(f"\n{host} retrieved successfully.")
|
||||||
|
|
||||||
|
def parse_conf_file(filepath, host):
|
||||||
|
|
||||||
|
config = {}
|
||||||
|
current_section = "Interface"
|
||||||
|
config[current_section] = {}
|
||||||
|
|
||||||
|
with open(filepath) as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if not line or line.startswith("#"):
|
||||||
|
continue
|
||||||
|
if line.startswith("[") and line.endswith("]"):
|
||||||
|
current_section = line.strip("[]")
|
||||||
|
config[current_section] = {}
|
||||||
|
else:
|
||||||
|
if "=" in line:
|
||||||
|
key, val = map(str.strip, line.split("=", 1))
|
||||||
|
config[current_section][key] = val
|
||||||
|
|
||||||
|
return config
|
||||||
|
|
||||||
|
def load_all_confs(host):
|
||||||
|
|
||||||
|
all_configs = {}
|
||||||
|
|
||||||
|
for conf_file in Path(LOCAL_TMP_DIR).glob("*.conf"):
|
||||||
|
conf_name = conf_file.stem
|
||||||
|
all_configs[conf_name] = parse_conf_file(conf_file, host)
|
||||||
|
os.remove(conf_file)
|
||||||
|
|
||||||
|
return { host : all_configs }
|
||||||
|
|
||||||
|
|
||||||
|
def create_wireguard_config_pair(
|
||||||
|
new_host: str,
|
||||||
|
old_host: str,
|
||||||
|
new_host_allowed_ips: list[str],
|
||||||
|
old_host_allowed_ips,
|
||||||
|
old_host_public_key,
|
||||||
|
old_host_private_key,
|
||||||
|
output_dir: str = "./tmp"
|
||||||
|
):
|
||||||
|
|
||||||
|
# Validate input subnet
|
||||||
|
try:
|
||||||
|
ipaddress.IPv4Interface(local_address)
|
||||||
|
except ValueError as e:
|
||||||
|
raise ValueError(f"Invalid IP address: {local_address}") from e
|
||||||
|
|
||||||
|
Path(output_dir).mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# Generate key pairs
|
||||||
|
local_private, local_public = generate_keypair()
|
||||||
|
peer_private, peer_public = generate_keypair()
|
||||||
|
|
||||||
|
MASTER_CONFIG = """[Interface]
|
||||||
|
PrivateKey = {local_private}
|
||||||
|
Address = {local_address}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
Endpoint = {peer_endpoint}
|
||||||
|
PublicKey = {peer_public}
|
||||||
|
AllowedIPs = {", ".join(peer_allowed_ips)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
PEER_CONFIG = f"""[Interface]
|
||||||
|
PrivateKey = {peer_private}
|
||||||
|
Address = {peer_address}
|
||||||
|
|
||||||
|
[Peer]
|
||||||
|
PublicKey = {local_public}
|
||||||
|
AllowedIPs = {", ".join(peer_allowed_ips)}
|
||||||
|
"""
|
||||||
|
|
||||||
|
new_host = MASTER_CONFIG.format(
|
||||||
|
local_private = local_private,
|
||||||
|
local_address = LINK_BASE,
|
||||||
|
local_public = local_public,
|
||||||
|
peer_endpoint = old_host,
|
||||||
|
peer_public = old_host_public_key,
|
||||||
|
peer_allowed_ips = [LINK_BASE_PEER, old_host_allowed_ips],
|
||||||
|
|
||||||
|
)
|
||||||
|
old_host = PEER_CONFIG.format(
|
||||||
|
peer_private = old_host_private_key,
|
||||||
|
peer_address = LINK_BASE_PEER,
|
||||||
|
local_public= local_public,
|
||||||
|
peer_allowed_ips = [LINK_BASE, new_host_subnet]
|
||||||
|
)
|
||||||
|
|
||||||
|
old_host_file = Path(output_dir) / f"{new_host}_local.conf"
|
||||||
|
new_host_file = Path(output_dir) / f"{new_host}_peer.conf"
|
||||||
|
|
||||||
|
new_host_file.write_text(new_host)
|
||||||
|
old_host_file.write_text(old_host)
|
||||||
|
|
||||||
|
return str(local_file), str(peer_file)
|
||||||
|
|
||||||
|
def generate_wireguard_keys():
|
||||||
|
|
||||||
|
private_key = subprocess.check_output(["wg", "genkey"]).strip()
|
||||||
|
public_key = subprocess.check_output(["wg", "pubkey"], input=private_key).strip()
|
||||||
|
|
||||||
|
return private_key.decode(), public_key.decode()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
|
||||||
|
parser = argparse.ArgumentParser(description="WireGuard utility script")
|
||||||
|
|
||||||
|
parser.add_argument("--new-host", help="Hostname or IP of the new host")
|
||||||
|
parser.add_argument("--new-host-subnet", type=ipaddress.IPv4Network, help="Subnet of new host")
|
||||||
|
parser.add_argument("--write", action=argparse.BooleanOptionalAction, default=False)
|
||||||
|
parser.add_argument("--allow-overwrite", action=argparse.BooleanOptionalAction, default=False)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
configs = {}
|
||||||
|
|
||||||
|
for host in EXISTING_HOSTS:
|
||||||
|
fetch_conf_files(host)
|
||||||
|
configs |= load_all_confs(host)
|
||||||
|
|
||||||
|
# output current state #
|
||||||
|
print(json.dumps(configs, indent=2))
|
||||||
|
|
||||||
|
if args.new_host and args.new_host_subnet:
|
||||||
|
pub, priv = generate_wireguard_keys()
|
||||||
|
create_wireguard_config_pair(LINK_BASE, new_host
|
||||||
4
main.py
4
main.py
@@ -68,10 +68,14 @@ if __name__ == "__main__":
|
|||||||
f.write("\n")
|
f.write("\n")
|
||||||
|
|
||||||
# backup #
|
# backup #
|
||||||
|
try:
|
||||||
with open("./config/backup.json") as f:
|
with open("./config/backup.json") as f:
|
||||||
backup.createBackupScriptStructure(json.load(f), baseDomain=MASTER_ADDRESS,
|
backup.createBackupScriptStructure(json.load(f), baseDomain=MASTER_ADDRESS,
|
||||||
icingaOnly=not args.backup,
|
icingaOnly=not args.backup,
|
||||||
backup_no_async_icinga=args.backup_no_async_icinga)
|
backup_no_async_icinga=args.backup_no_async_icinga)
|
||||||
|
except json.decoder.JSONDecodeError as e:
|
||||||
|
print("WARNING: Failed loading backup.json - either empty or invalid json!", file=sys.stderr)
|
||||||
|
print(e, file=sys.stderr)
|
||||||
|
|
||||||
# copy nginx maps #
|
# copy nginx maps #
|
||||||
if not args.backup and args.do_nginx_map_cert_manager:
|
if not args.backup and args.do_nginx_map_cert_manager:
|
||||||
|
|||||||
11
nginx.py
11
nginx.py
@@ -32,14 +32,15 @@ def dump_config(vmList, masterAddress):
|
|||||||
for vmo in vmList:
|
for vmo in vmList:
|
||||||
relevant_subdomains = filter(lambda x: x.get("no-terminate-ssl"), vmo.subdomains)
|
relevant_subdomains = filter(lambda x: x.get("no-terminate-ssl"), vmo.subdomains)
|
||||||
for s in relevant_subdomains:
|
for s in relevant_subdomains:
|
||||||
print(s)
|
# print(s, "ssl_target_port", s.get("ssl_target_port"))
|
||||||
# build the map contents #
|
# build the map contents #
|
||||||
if s.get("include-subdomains"):
|
if s.get("include-subdomains"):
|
||||||
match = "~.*{}".format(s.get("name"))
|
match = "~.*{}".format(s.get("name"))
|
||||||
else:
|
else:
|
||||||
match = s.get("name")
|
match = s.get("name")
|
||||||
|
|
||||||
ssl_passthrough_map.append("{} {}:443;".format(match, vmo.ip))
|
ssl_target_port = s.get("ssl_target_port") or 443
|
||||||
|
ssl_passthrough_map.append("{} {}:{};".format(match, vmo.ip, ssl_target_port))
|
||||||
|
|
||||||
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="./templates"))
|
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath="./templates"))
|
||||||
template = environment.get_template("nginx_stream_ssl_map.conf.j2")
|
template = environment.get_template("nginx_stream_ssl_map.conf.j2")
|
||||||
@@ -65,7 +66,7 @@ def dump_config(vmList, masterAddress):
|
|||||||
for vmo in vmList:
|
for vmo in vmList:
|
||||||
for subdomain in vmo.subdomains:
|
for subdomain in vmo.subdomains:
|
||||||
if vmo.noTerminateACME:
|
if vmo.noTerminateACME:
|
||||||
print("Not terminating ACME for: {}".format(subdomain))
|
print("Not terminating ACME for: {}".format(subdomain.get("name")))
|
||||||
continue
|
continue
|
||||||
if type(subdomain) == dict:
|
if type(subdomain) == dict:
|
||||||
domains.append(subdomain["name"])
|
domains.append(subdomain["name"])
|
||||||
@@ -94,10 +95,10 @@ def dump_config(vmList, masterAddress):
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
|
|
||||||
def check_transparent_proxy_loader():
|
def check_transparent_proxy_loader():
|
||||||
retcode = os.system("systemctl is-enabled nginx-iptables.service")
|
retcode = os.system("systemctl -q is-enabled nginx-iptables.service")
|
||||||
if retcode != 0:
|
if retcode != 0:
|
||||||
print("############################ WARNING ###############################")
|
print("############################ WARNING ###############################")
|
||||||
print("+++ You may have transparent proxy rules but the service to load +++")
|
print("+++ You may have transparent proxy rules but the service to load +++")
|
||||||
print("+++ them is not enabled or missing, a restart WILL break your +++")
|
print("+++ them is not enabled or missing, a restart WILL break your +++")
|
||||||
print("+++ setup! Add see nginx-iptables.service in the project root +++")
|
print("+++ setup! Look at nginx-iptables.service in the project root +++")
|
||||||
print("############################ WARNING ###############################")
|
print("############################ WARNING ###############################")
|
||||||
|
|||||||
40
provisioning/mac.py
Normal file
40
provisioning/mac.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
import libvirt
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
|
def get_mac_address(domain):
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Connect to the libvirt daemon
|
||||||
|
conn = libvirt.open()
|
||||||
|
if conn is None:
|
||||||
|
raise RuntimeError("Failed to open connection to the hypervisor.")
|
||||||
|
|
||||||
|
# Lookup the domain by name
|
||||||
|
vm = conn.lookupByName(domain)
|
||||||
|
if vm is None:
|
||||||
|
raise ValueError(f"Domain '{domain}' not found.")
|
||||||
|
|
||||||
|
# Get the XML description of the domain
|
||||||
|
xml_desc = vm.XMLDesc()
|
||||||
|
|
||||||
|
# Parse the XML to extract the MAC address
|
||||||
|
root = ET.fromstring(xml_desc)
|
||||||
|
mac_element = root.find(".//mac")
|
||||||
|
if mac_element is not None:
|
||||||
|
mac_address = mac_element.attrib.get('address')
|
||||||
|
return mac_address
|
||||||
|
else:
|
||||||
|
raise ValueError("MAC address not found in XML.")
|
||||||
|
|
||||||
|
except libvirt.libvirtError as e:
|
||||||
|
return f"A libvirt error occurred: {e}"
|
||||||
|
except Exception as e:
|
||||||
|
return f"An error occurred: {e}"
|
||||||
|
finally:
|
||||||
|
if conn:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
# Replace 'debian' with your domain name
|
||||||
|
domain_name = "debian"
|
||||||
|
mac_address = get_mac_address(domain_name)
|
||||||
|
print(f"MAC Address: {mac_address}")
|
||||||
26
provisioning/master.py
Normal file
26
provisioning/master.py
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
# get new domain name
|
||||||
|
|
||||||
|
# create a VM and clone disk
|
||||||
|
|
||||||
|
# set CPU & RAM
|
||||||
|
|
||||||
|
# get mac
|
||||||
|
virsh_vm.get_mac(domain)
|
||||||
|
|
||||||
|
# change network
|
||||||
|
network.set_ip_for_mac_domain(domain, mac)
|
||||||
|
|
||||||
|
# net destory net start ?
|
||||||
|
|
||||||
|
# start vm
|
||||||
|
|
||||||
|
# change hostname
|
||||||
|
|
||||||
|
# add vm subdomains
|
||||||
|
|
||||||
|
# run python script
|
||||||
|
|
||||||
|
# run cert script
|
||||||
|
|
||||||
|
# reload nginx
|
||||||
|
|
||||||
@@ -74,7 +74,11 @@ server{
|
|||||||
{% else %}
|
{% else %}
|
||||||
location / {
|
location / {
|
||||||
{{ proxy_pass_blob }}
|
{{ proxy_pass_blob }}
|
||||||
|
{% if http_target_port %}
|
||||||
|
proxy_pass http://{{ targetip }}:{{ http_target_port }};
|
||||||
|
{% else %}
|
||||||
proxy_pass http://{{ targetip }}:80;
|
proxy_pass http://{{ targetip }}:80;
|
||||||
|
{% endif %}
|
||||||
}
|
}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
|||||||
19
vm.py
19
vm.py
@@ -1,4 +1,5 @@
|
|||||||
import libvirt
|
import libvirt
|
||||||
|
import json
|
||||||
import jinja2
|
import jinja2
|
||||||
|
|
||||||
class VM:
|
class VM:
|
||||||
@@ -36,6 +37,8 @@ class VM:
|
|||||||
network = con.networkLookupByName(self.network)
|
network = con.networkLookupByName(self.network)
|
||||||
leases = network.DHCPLeases()
|
leases = network.DHCPLeases()
|
||||||
for l in leases:
|
for l in leases:
|
||||||
|
if not l.get("type") == 0: # FIXME: only ipv4 for now
|
||||||
|
continue
|
||||||
if l.get("hostname") == self.hostname:
|
if l.get("hostname") == self.hostname:
|
||||||
return l
|
return l
|
||||||
|
|
||||||
@@ -69,6 +72,7 @@ class VM:
|
|||||||
isUDP = proto == "udp"
|
isUDP = proto == "udp"
|
||||||
proxy_timeout = portStruct.get("proxy_timeout") or "10s"
|
proxy_timeout = portStruct.get("proxy_timeout") or "10s"
|
||||||
extra_content = portStruct.get("extra-content")
|
extra_content = portStruct.get("extra-content")
|
||||||
|
targetportoverwrite = portStruct.get("targetportoverwrite")
|
||||||
|
|
||||||
compositeName = "-".join((self.hostname, name, portstring, proto))
|
compositeName = "-".join((self.hostname, name, portstring, proto))
|
||||||
|
|
||||||
@@ -78,6 +82,7 @@ class VM:
|
|||||||
component = template.render(targetip=self.ip, udp=isUDP, portstring=portstring,
|
component = template.render(targetip=self.ip, udp=isUDP, portstring=portstring,
|
||||||
transparent=transparent, proxy_timeout=proxy_timeout,
|
transparent=transparent, proxy_timeout=proxy_timeout,
|
||||||
comment=compositeName, extra_content=extra_content,
|
comment=compositeName, extra_content=extra_content,
|
||||||
|
targetportoverwrite=targetportoverwrite,
|
||||||
port_interfaces=port_interfaces)
|
port_interfaces=port_interfaces)
|
||||||
|
|
||||||
components.append(component)
|
components.append(component)
|
||||||
@@ -89,7 +94,14 @@ class VM:
|
|||||||
components = []
|
components = []
|
||||||
template = self.environment.get_template("nginx_stream_block.conf.j2")
|
template = self.environment.get_template("nginx_stream_block.conf.j2")
|
||||||
if not self.isExternal:
|
if not self.isExternal:
|
||||||
|
|
||||||
|
try:
|
||||||
self.sshOutsidePort = 7000 + int(self.ip.split(".")[-1])
|
self.sshOutsidePort = 7000 + int(self.ip.split(".")[-1])
|
||||||
|
except ValueError as e:
|
||||||
|
print(f"Warning: {self.hostname} Invalid IP (IPv6 is not supported) {e}",
|
||||||
|
file=sys.stderr)
|
||||||
|
return []
|
||||||
|
|
||||||
component = template.render(targetip=self.ip, udp=False,
|
component = template.render(targetip=self.ip, udp=False,
|
||||||
portstring=self.sshOutsidePort,
|
portstring=self.sshOutsidePort,
|
||||||
targetportoverwrite=7000,
|
targetportoverwrite=7000,
|
||||||
@@ -134,7 +146,7 @@ class VM:
|
|||||||
for subdomain in self.subdomains:
|
for subdomain in self.subdomains:
|
||||||
|
|
||||||
if subdomain.get("no-terminate-ssl"):
|
if subdomain.get("no-terminate-ssl"):
|
||||||
print("Not terminating TLS for: {}".format(subdomain))
|
print("Not terminating TLS for: {}".format(subdomain.get("name")))
|
||||||
|
|
||||||
if type(subdomain) != dict:
|
if type(subdomain) != dict:
|
||||||
raise ValueError("Subdomain must be object containing 'name' ")
|
raise ValueError("Subdomain must be object containing 'name' ")
|
||||||
@@ -156,6 +168,10 @@ class VM:
|
|||||||
if subdomain.get("include-subdomains") and not subdomain.get("no-terminate-ssl"):
|
if subdomain.get("include-subdomains") and not subdomain.get("no-terminate-ssl"):
|
||||||
raise ValueError("Wildcard Subdomain not supported with SSL Termination")
|
raise ValueError("Wildcard Subdomain not supported with SSL Termination")
|
||||||
|
|
||||||
|
if "port" in subdomain and "no-terminate-ssl" in subdomain:
|
||||||
|
print(json.dumps(subdomain, indent=2))
|
||||||
|
raise ValueError("'port' is not allowed with no-terminate-ssl subdomain, use http_target_port and ssl_target_port")
|
||||||
|
|
||||||
component = template.render(targetip=self.ip, targetport=targetport,
|
component = template.render(targetip=self.ip, targetport=targetport,
|
||||||
servernames=[subdomain["name"]], comment=compositeName,
|
servernames=[subdomain["name"]], comment=compositeName,
|
||||||
proxy_pass_blob=self.proxy_pass_blob,
|
proxy_pass_blob=self.proxy_pass_blob,
|
||||||
@@ -166,6 +182,7 @@ class VM:
|
|||||||
include_subdomains=subdomain.get("include-subdomains"),
|
include_subdomains=subdomain.get("include-subdomains"),
|
||||||
cert_optional=cert_optional,
|
cert_optional=cert_optional,
|
||||||
cert_non_optional=cert_non_optional,
|
cert_non_optional=cert_non_optional,
|
||||||
|
http_target_port=subdomain.get("http_target_port"),
|
||||||
cert_header_line=header_line)
|
cert_header_line=header_line)
|
||||||
components.append(component)
|
components.append(component)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user