Update the Database and make it easier to gen

This commit is contained in:
TheophileDiot 2022-11-08 17:14:33 +01:00
parent 375776e7de
commit eec00ba2bf
25 changed files with 560 additions and 676 deletions

View file

@ -12,12 +12,18 @@ class Config(ConfigCaller):
self.__ctrl_type = ctrl_type self.__ctrl_type = ctrl_type
self.__lock = lock self.__lock = lock
self.__logger = setup_logger("Config", getenv("LOG_LEVEL", "INFO")) self.__logger = setup_logger("Config", getenv("LOG_LEVEL", "INFO"))
self._db = None
self.__instances = [] self.__instances = []
self.__services = [] self.__services = []
self.__configs = [] self.__configs = []
self.__config = {} self.__config = {}
self._db = Database(self.__logger)
while not self._db.is_initialized():
self.__logger.warning(
"Database is not initialized, retrying in 5 seconds ...",
)
sleep(5)
def __get_full_env(self) -> dict: def __get_full_env(self) -> dict:
env_instances = {} env_instances = {}
for instance in self.__instances: for instance in self.__instances:
@ -55,21 +61,6 @@ class Config(ConfigCaller):
self.__configs = configs self.__configs = configs
self.__config = self.__get_full_env() self.__config = self.__get_full_env()
if self._db is None:
self._db = Database(
self.__logger,
sqlalchemy_string=self.__config.get("DATABASE_URI", None),
bw_integration="Kubernetes"
if self.__config.get("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
)
while not self._db.is_initialized():
self.__logger.warning(
"Database is not initialized, retrying in 5 seconds ...",
)
sleep(5)
custom_configs = [] custom_configs = []
for config_type in self.__configs: for config_type in self.__configs:
for file, data in self.__configs[config_type].items(): for file, data in self.__configs[config_type].items():

View file

@ -2,7 +2,6 @@
from os import _exit, getenv from os import _exit, getenv
from signal import SIGINT, SIGTERM, signal from signal import SIGINT, SIGTERM, signal
from subprocess import DEVNULL, STDOUT, run
from sys import exit as sys_exit, path as sys_path from sys import exit as sys_exit, path as sys_path
from traceback import format_exc from traceback import format_exc

View file

@ -54,9 +54,6 @@ logger = setup_logger("BLACKLIST", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -19,9 +19,6 @@ logger = setup_logger("BUNKERNET", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -19,9 +19,6 @@ logger = setup_logger("BUNKERNET", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -17,9 +17,6 @@ logger = setup_logger("CUSTOM-CERT", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )

View file

@ -54,9 +54,6 @@ logger = setup_logger("GREYLIST", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -21,9 +21,6 @@ logger = setup_logger("JOBS", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -21,9 +21,6 @@ logger = setup_logger("JOBS", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -14,7 +14,6 @@ sys_path.append("/opt/bunkerweb/utils")
sys_path.append("/opt/bunkerweb/api") sys_path.append("/opt/bunkerweb/api")
from docker import DockerClient from docker import DockerClient
from docker.errors import DockerException
from logger import setup_logger from logger import setup_logger
from API import API from API import API
@ -88,12 +87,9 @@ try:
# Docker or Linux case # Docker or Linux case
elif bw_integration == "Docker": elif bw_integration == "Docker":
try: docker_client = DockerClient(
docker_client = DockerClient(base_url="tcp://docker-proxy:2375") base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
except DockerException: )
docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
)
apis = [] apis = []
for instance in docker_client.containers.list( for instance in docker_client.containers.list(

View file

@ -36,9 +36,6 @@ logger = setup_logger("REALIP", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -19,9 +19,6 @@ logger = setup_logger("self-signed", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )

View file

@ -54,9 +54,6 @@ logger = setup_logger("WHITELIST", getenv("LOG_LEVEL", "INFO"))
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration="Kubernetes"
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
status = 0 status = 0

View file

@ -13,14 +13,19 @@ class Configurator:
def __init__( def __init__(
self, self,
settings: str, settings: str,
core: str, core: Union[str, dict],
plugins: str, plugins: str,
variables: Union[str, dict], variables: Union[str, dict],
logger: Logger, logger: Logger,
): ):
self.__logger = logger self.__logger = logger
self.__settings = self.__load_settings(settings) self.__settings = self.__load_settings(settings)
self.__core = core
if isinstance(core, str):
self.__core = self.__load_plugins(core)
else:
self.__core = core
self.__plugins_settings = [] self.__plugins_settings = []
self.__plugins = self.__load_plugins(plugins, "plugins") self.__plugins = self.__load_plugins(plugins, "plugins")

View file

@ -2,17 +2,14 @@
from argparse import ArgumentParser from argparse import ArgumentParser
from glob import glob from glob import glob
from itertools import chain
from json import loads from json import loads
from os import R_OK, W_OK, X_OK, access, environ, getenv, path, remove, unlink from os import R_OK, W_OK, X_OK, access, getenv, path, remove, unlink
from os.path import exists, isdir, isfile, islink from os.path import exists, isdir, isfile, islink
from re import compile as re_compile
from shutil import rmtree from shutil import rmtree
from subprocess import DEVNULL, STDOUT, run from subprocess import DEVNULL, STDOUT, run
from sys import exit as sys_exit, path as sys_path from sys import exit as sys_exit, path as sys_path
from time import sleep from time import sleep
from traceback import format_exc from traceback import format_exc
from typing import Any
sys_path.append("/opt/bunkerweb/deps/python") sys_path.append("/opt/bunkerweb/deps/python")
@ -20,18 +17,14 @@ sys_path.append("/opt/bunkerweb/utils")
sys_path.append("/opt/bunkerweb/api") sys_path.append("/opt/bunkerweb/api")
sys_path.append("/opt/bunkerweb/db") sys_path.append("/opt/bunkerweb/db")
from docker import DockerClient
from kubernetes import client as kube_client
from logger import setup_logger from logger import setup_logger
from Database import Database from Database import Database
from Configurator import Configurator from Configurator import Configurator
from Templator import Templator from Templator import Templator
from API import API
if __name__ == "__main__": if __name__ == "__main__":
logger = setup_logger("Generator", environ.get("LOG_LEVEL", "INFO")) logger = setup_logger("Generator", getenv("LOG_LEVEL", "INFO"))
wait_retry_interval = int(getenv("WAIT_RETRY_INTERVAL", "5")) wait_retry_interval = int(getenv("WAIT_RETRY_INTERVAL", "5"))
try: try:
@ -78,17 +71,6 @@ if __name__ == "__main__":
type=str, type=str,
help="path to the file containing environment variables", help="path to the file containing environment variables",
) )
parser.add_argument(
"--method",
default="scheduler",
type=str,
help="The method that is used in the database",
)
parser.add_argument(
"--init",
action="store_true",
help="Only initialize the database",
)
args = parser.parse_args() args = parser.parse_args()
logger.info("Generator started ...") logger.info("Generator started ...")
@ -99,439 +81,60 @@ if __name__ == "__main__":
logger.info(f"Output : {args.output}") logger.info(f"Output : {args.output}")
logger.info(f"Target : {args.target}") logger.info(f"Target : {args.target}")
logger.info(f"Variables : {args.variables}") logger.info(f"Variables : {args.variables}")
logger.info(f"Method : {args.method}")
logger.info(f"Init : {args.init}")
custom_confs_rx = re_compile(
r"^([0-9a-z\.\-]*)_?CUSTOM_CONF_(HTTP|DEFAULT_SERVER_HTTP|SERVER_HTTP|MODSEC|MODSEC_CRS)_(.+)$"
)
# Check existences and permissions
logger.info("Checking arguments ...")
files = [args.settings] + ([args.variables] if args.variables else [])
paths_rx = [args.core, args.plugins, args.templates]
paths_rwx = [args.output]
for file in files:
if not path.exists(file):
logger.error(f"Missing file : {file}")
sys_exit(1)
if not access(file, R_OK):
logger.error(f"Can't read file : {file}")
sys_exit(1)
for _path in paths_rx + paths_rwx:
if not path.isdir(_path):
logger.error(f"Missing directory : {_path}")
sys_exit(1)
if not access(_path, R_OK | X_OK):
logger.error(
f"Missing RX rights on directory : {_path}",
)
sys_exit(1)
for _path in paths_rwx:
if not access(_path, W_OK):
logger.error(
f"Missing W rights on directory : {_path}",
)
sys_exit(1)
# Check core plugins orders
logger.info("Checking core plugins orders ...")
core_plugins = {}
files = glob(f"{args.core}/*/plugin.json")
for file in files:
try:
with open(file) as f:
core_plugin = loads(f.read())
if core_plugin["order"] not in core_plugins:
core_plugins[core_plugin["order"]] = []
core_plugins[core_plugin["order"]].append(core_plugin)
except:
logger.error(
f"Exception while loading JSON from {file} : {format_exc()}",
)
core_settings = {}
for order in core_plugins:
if len(core_plugins[order]) > 1 and order != 999:
logger.warning(
f"Multiple plugins have the same order ({order}) : {', '.join(plugin['id'] for plugin in core_plugins[order])}. Therefor, the execution order will be random.",
)
for plugin in core_plugins[order]:
core_settings.update(plugin["settings"])
integration = "Linux" integration = "Linux"
if exists("/opt/bunkerweb/INTEGRATION"): if getenv("KUBERNETES_MODE", "no") == "yes":
integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
integration = "Autoconf"
elif exists("/opt/bunkerweb/INTEGRATION"):
with open("/opt/bunkerweb/INTEGRATION", "r") as f: with open("/opt/bunkerweb/INTEGRATION", "r") as f:
integration = f.read().strip() integration = f.read().strip()
if args.variables or args.init: if args.variables:
# Compute the config # Check existences and permissions
logger.info("Computing config ...") logger.info("Checking arguments ...")
config = Configurator( files = [args.settings, args.variables]
args.settings, core_settings, args.plugins, args.variables, logger paths_rx = [args.core, args.plugins, args.templates]
) paths_rwx = [args.output]
config_files = config.get_config() for file in files:
if not path.exists(file):
if config_files.get("LOG_LEVEL", logger.level) != logger.level: logger.error(f"Missing file : {file}")
logger = setup_logger("Generator", config_files["LOG_LEVEL"]) sys_exit(1)
if not access(file, R_OK):
bw_integration = "Local" logger.error(f"Can't read file : {file}")
if getenv("KUBERNETES_MODE", "no") == "yes": sys_exit(1)
bw_integration = "Kubernetes" for _path in paths_rx + paths_rwx:
elif ( if not path.isdir(_path):
integration == "Docker" logger.error(f"Missing directory : {_path}")
or getenv("SWARM_MODE", getenv("AUTOCONF_MODE", "no")) == "yes" sys_exit(1)
): if not access(_path, R_OK | X_OK):
bw_integration = "Cluster"
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration=bw_integration,
)
is_initialized = db.is_initialized()
if not is_initialized:
ret, err = db.init_tables(
[
config.get_settings(),
list(chain.from_iterable(core_plugins.values())),
config.get_plugins_settings(),
]
)
# Initialize database tables
if err:
logger.error( logger.error(
f"Exception while initializing database : {err}", f"Missing RX rights on directory : {_path}",
) )
sys_exit(1) sys_exit(1)
elif ret is False: for _path in paths_rwx:
logger.info( if not access(_path, W_OK):
"Database tables are already initialized, skipping creation ...",
)
else:
logger.info("Database tables initialized")
logger.info(
"Database not initialized, initializing ...",
)
custom_confs = [
{"value": v, "exploded": custom_confs_rx.search(k).groups()}
for k, v in environ.items()
if custom_confs_rx.match(k)
]
with open("/opt/bunkerweb/VERSION", "r") as f:
bw_version = f.read().strip()
if bw_integration == "Local":
err = db.save_config(config_files, args.method)
if not err:
err1 = db.save_custom_configs(custom_confs, args.method)
else:
err = None
err1 = None
integration = "Linux"
if config_files.get("KUBERNETES_MODE", "no") == "yes":
integration = "Kubernetes"
elif config_files.get("SWARM_MODE", "no") == "yes":
integration = "Swarm"
elif config_files.get("AUTOCONF_MODE", "no") == "yes":
integration = "Autoconf"
elif exists("/opt/bunkerweb/INTEGRATION"):
with open("/opt/bunkerweb/INTEGRATION", "r") as f:
integration = f.read().strip()
err2 = db.initialize_db(version=bw_version, integration=integration)
if err or err1 or err2:
logger.error( logger.error(
f"Can't Initialize database : {err or err1 or err2}", f"Missing W rights on directory : {_path}",
) )
sys_exit(1) sys_exit(1)
else:
logger.info("Database initialized")
if args.init:
sys_exit(0)
elif is_initialized:
logger.info(
"Database is already initialized, skipping ...",
)
config = db.get_config()
elif integration == "Docker":
bw_integration = "Cluster"
docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
)
def get_instance_configs_and_apis(instance: Any, db, _type="Docker"):
api_http_port = None
api_server_name = None
tmp_config = {}
custom_confs = []
apis = []
for var in (
instance.attrs["Config"]["Env"]
if _type == "Docker"
else instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"]["Env"]
):
splitted = var.split("=", 1)
if custom_confs_rx.match(splitted[0]):
custom_confs.append(
{
"value": splitted[1],
"exploded": custom_confs_rx.search(
splitted[0]
).groups(),
}
)
else:
tmp_config[splitted[0]] = splitted[1]
if db is None and splitted[0] == "DATABASE_URI":
db = Database(
logger,
sqlalchemy_string=splitted[1],
)
elif splitted[0] == "API_HTTP_PORT":
api_http_port = splitted[1]
elif splitted[0] == "API_SERVER_NAME":
api_server_name = splitted[1]
apis.append(
API(
f"http://{instance.name}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
return tmp_config, custom_confs, apis, db
tmp_config = {}
custom_confs = []
apis = []
db = None
for instance in docker_client.containers.list(
filters={"label": "bunkerweb.INSTANCE"}
):
conf, cstm_confs, tmp_apis, tmp_db = get_instance_configs_and_apis(
instance, db
)
tmp_config.update(conf)
custom_confs.extend(cstm_confs)
apis.extend(tmp_apis)
if db is None:
db = tmp_db
is_swarm = True
try:
docker_client.swarm.version
except:
is_swarm = False
if is_swarm:
for instance in docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
conf, cstm_confs, tmp_apis, tmp_db = get_instance_configs_and_apis(
instance, db, "Swarm"
)
tmp_config.update(conf)
custom_confs.extend(cstm_confs)
apis.extend(tmp_apis)
if db is None:
db = tmp_db
if db is None:
db = Database(logger)
# Compute the config # Compute the config
logger.info("Computing config ...") logger.info("Computing config ...")
config = Configurator( config = Configurator(
args.settings, core_settings, args.plugins, tmp_config, logger args.settings, args.core, args.plugins, args.variables, logger
) )
config_files = config.get_config() config = config.get_config()
if config_files.get("LOG_LEVEL", logger.level) != logger.level:
logger = setup_logger("Generator", config_files["LOG_LEVEL"])
err = db.save_config(config_files, args.method)
if not err:
err1 = db.save_custom_configs(custom_confs, args.method)
else:
err = None
err1 = None
if err or err1:
logger.error(
f"Can't save config to database : {err or err1}",
)
sys_exit(1)
else:
logger.info("Config successfully saved to database")
config = db.get_config()
elif integration == "Kubernetes":
bw_integration = "Kubernetes"
corev1 = kube_client.CoreV1Api()
tmp_config = {}
apis = []
db = None
for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if (
pod.metadata.annotations != None
and "bunkerweb.io/INSTANCE" in pod.metadata.annotations
):
api_http_port = None
api_server_name = None
for pod_env in pod.spec.containers[0].env:
tmp_config[pod_env.name] = pod_env.value
if db is None and pod_env.name == "DATABASE_URI":
db = Database(
logger,
sqlalchemy_string=pod_env.value,
)
elif pod_env.name == "API_HTTP_PORT":
api_http_port = pod_env.value
elif pod_env.name == "API_SERVER_NAME":
api_server_name = pod_env.value
apis.append(
API(
f"http://{pod.status.pod_ip}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
if db is None:
db = Database(logger)
# Compute the config
logger.info("Computing config ...")
config = Configurator(
args.settings, core_settings, args.plugins, tmp_config, logger
)
config_files = config.get_config()
if config_files.get("LOG_LEVEL", logger.level) != logger.level:
logger = setup_logger("Generator", config_files["LOG_LEVEL"])
err = db.save_config(config_files, args.method)
if not err:
supported_config_types = [
"http",
"stream",
"server-http",
"server-stream",
"default-server-http",
"modsec",
"modsec-crs",
]
custom_confs = []
for configmap in corev1.list_config_map_for_all_namespaces(
watch=False
).items:
if (
configmap.metadata.annotations is None
or "bunkerweb.io/CONFIG_TYPE"
not in configmap.metadata.annotations
):
continue
config_type = configmap.metadata.annotations[
"bunkerweb.io/CONFIG_TYPE"
]
if config_type not in supported_config_types:
logger.warning(
f"Ignoring unsupported CONFIG_TYPE {config_type} for ConfigMap {configmap.metadata.name}",
)
continue
elif not configmap.data:
logger.warning(
f"Ignoring blank ConfigMap {configmap.metadata.name}",
)
continue
config_site = ""
if "bunkerweb.io/CONFIG_SITE" in configmap.metadata.annotations:
config_site = f"{configmap.metadata.annotations['bunkerweb.io/CONFIG_SITE']}/"
for config_name, config_data in configmap.data.items():
custom_confs.append(
{
"value": config_data,
"exploded": (config_site, config_type, config_name),
}
)
err1 = db.save_custom_configs(custom_confs, args.method)
else:
err = None
err1 = None
if err or err1:
logger.error(
f"Can't save config to database : {err or err1}",
)
sys_exit(1)
else:
logger.info("Config successfully saved to database")
config = db.get_config()
else: else:
db = Database( db = Database(
logger, logger,
bw_integration="Kubernetes" sqlalchemy_string=getenv("DATABASE_URI", None),
if getenv("KUBERNETES_MODE", "no") == "yes"
else "Cluster",
) )
config = db.get_config() config = db.get_config()
bw_integration = "Local"
if config.get("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif (
config.get("SWARM_MODE", "no") == "yes"
or config.get("AUTOCONF_MODE", "no") == "yes"
):
bw_integration = "Cluster"
logger = setup_logger("Generator", config.get("LOG_LEVEL", "INFO"))
if bw_integration == "Local":
retries = 0
while not exists("/opt/bunkerweb/tmp/nginx.pid"):
if retries == 5:
logger.error(
"BunkerWeb's nginx didn't start in time.",
)
sys_exit(1)
logger.warning(
"Waiting for BunkerWeb's nginx to start, retrying in 5 seconds ...",
)
retries += 1
sleep(5)
# Remove old files # Remove old files
logger.info("Removing old files ...") logger.info("Removing old files ...")
files = glob(f"{args.output}/*") files = glob(f"{args.output}/*")
@ -555,7 +158,21 @@ if __name__ == "__main__":
) )
templator.render() templator.render()
if bw_integration == "Local": if integration == "Linux":
retries = 0
while not exists("/opt/bunkerweb/tmp/nginx.pid"):
if retries == 5:
logger.error(
"BunkerWeb's nginx didn't start in time.",
)
sys_exit(1)
logger.warning(
"Waiting for BunkerWeb's nginx to start, retrying in 5 seconds ...",
)
retries += 1
sleep(5)
cmd = "/usr/sbin/nginx -s reload" cmd = "/usr/sbin/nginx -s reload"
proc = run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT) proc = run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT)
if proc.returncode != 0: if proc.returncode != 0:

398
bw/gen/save_config.py Normal file
View file

@ -0,0 +1,398 @@
#!/usr/bin/python3
from argparse import ArgumentParser
from glob import glob
from itertools import chain
from json import loads
from os import R_OK, W_OK, X_OK, access, environ, getenv, path
from os.path import exists
from re import compile as re_compile
from sys import exit as sys_exit, path as sys_path
from traceback import format_exc
from typing import Any
sys_path.append("/opt/bunkerweb/deps/python")
sys_path.append("/opt/bunkerweb/utils")
sys_path.append("/opt/bunkerweb/api")
sys_path.append("/opt/bunkerweb/db")
from docker import DockerClient
from kubernetes import client as kube_client
from logger import setup_logger
from Database import Database
from Configurator import Configurator
from API import API
custom_confs_rx = re_compile(
r"^([0-9a-z\.\-]*)_?CUSTOM_CONF_(HTTP|DEFAULT_SERVER_HTTP|SERVER_HTTP|MODSEC|MODSEC_CRS)_(.+)$"
)
def get_instance_configs_and_apis(instance: Any, db, _type="Docker"):
api_http_port = None
api_server_name = None
tmp_config = {}
custom_confs = []
apis = []
for var in (
instance.attrs["Config"]["Env"]
if _type == "Docker"
else instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"]["Env"]
):
splitted = var.split("=", 1)
if custom_confs_rx.match(splitted[0]):
custom_confs.append(
{
"value": splitted[1],
"exploded": custom_confs_rx.search(splitted[0]).groups(),
}
)
else:
tmp_config[splitted[0]] = splitted[1]
if db is None and splitted[0] == "DATABASE_URI":
db = Database(
logger,
sqlalchemy_string=splitted[1],
)
elif splitted[0] == "API_HTTP_PORT":
api_http_port = splitted[1]
elif splitted[0] == "API_SERVER_NAME":
api_server_name = splitted[1]
apis.append(
API(
f"http://{instance.name}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
return tmp_config, custom_confs, apis, db
if __name__ == "__main__":
logger = setup_logger("Generator", getenv("LOG_LEVEL", "INFO"))
wait_retry_interval = int(getenv("WAIT_RETRY_INTERVAL", "5"))
try:
# Parse arguments
parser = ArgumentParser(description="BunkerWeb config saver")
parser.add_argument(
"--settings",
default="/opt/bunkerweb/settings.json",
type=str,
help="file containing the main settings",
)
parser.add_argument(
"--core",
default="/opt/bunkerweb/core",
type=str,
help="directory containing the core plugins",
)
parser.add_argument(
"--plugins",
default="/opt/bunkerweb/plugins",
type=str,
help="directory containing the external plugins",
)
parser.add_argument(
"--variables",
type=str,
help="path to the file containing environment variables",
)
parser.add_argument(
"--init",
action="store_true",
help="Only initialize the database",
)
args = parser.parse_args()
logger.info("First gen started ...")
logger.info(f"Settings : {args.settings}")
logger.info(f"Core : {args.core}")
logger.info(f"Plugins : {args.plugins}")
logger.info(f"Init : {args.init}")
integration = "Linux"
if getenv("KUBERNETES_MODE", "no") == "yes":
integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
integration = "Autoconf"
elif exists("/opt/bunkerweb/INTEGRATION"):
with open("/opt/bunkerweb/INTEGRATION", "r") as f:
integration = f.read().strip()
logger.info(f"Detected {integration} integration")
if args.variables:
logger.info(f"Variables : {args.variables}")
# Check existences and permissions
logger.info("Checking arguments ...")
files = [args.settings, args.variables]
paths_rx = [args.core, args.plugins]
for file in files:
if not path.exists(file):
logger.error(f"Missing file : {file}")
sys_exit(1)
if not access(file, R_OK):
logger.error(f"Can't read file : {file}")
sys_exit(1)
for _path in paths_rx:
if not path.isdir(_path):
logger.error(f"Missing directory : {_path}")
sys_exit(1)
if not access(_path, R_OK | X_OK):
logger.error(
f"Missing RX rights on directory : {_path}",
)
sys_exit(1)
# Check core plugins orders
logger.info("Checking core plugins orders ...")
core_plugins = {}
files = glob(f"{args.core}/*/plugin.json")
for file in files:
try:
with open(file) as f:
core_plugin = loads(f.read())
if core_plugin["order"] not in core_plugins:
core_plugins[core_plugin["order"]] = []
core_plugins[core_plugin["order"]].append(core_plugin)
except:
logger.error(
f"Exception while loading JSON from {file} : {format_exc()}",
)
core_settings = {}
for order in core_plugins:
if len(core_plugins[order]) > 1 and order != 999:
logger.warning(
f"Multiple plugins have the same order ({order}) : {', '.join(plugin['id'] for plugin in core_plugins[order])}. Therefor, the execution order will be random.",
)
for plugin in core_plugins[order]:
core_settings.update(plugin["settings"])
# Compute the config
logger.info("Computing config ...")
config = Configurator(
args.settings, core_settings, args.plugins, args.variables, logger
)
config_files = config.get_config()
db = Database(
logger,
sqlalchemy_string=getenv("DATABASE_URI", None),
)
is_initialized = db.is_initialized()
if not is_initialized:
ret, err = db.init_tables(
[
config.get_settings(),
list(chain.from_iterable(core_plugins.values())),
config.get_plugins_settings(),
]
)
# Initialize database tables
if err:
logger.error(
f"Exception while initializing database : {err}",
)
sys_exit(1)
elif ret is False:
logger.info(
"Database tables are already initialized, skipping creation ...",
)
else:
logger.info("Database tables initialized")
logger.info(
"Database not initialized, initializing ...",
)
custom_confs = [
{"value": v, "exploded": custom_confs_rx.search(k).groups()}
for k, v in environ.items()
if custom_confs_rx.match(k)
]
with open("/opt/bunkerweb/VERSION", "r") as f:
bw_version = f.read().strip()
err = db.save_config(config_files, "scheduler")
if not err:
err1 = db.save_custom_configs(custom_confs, "scheduler")
if not err1:
err2 = db.initialize_db(
version=bw_version, integration=integration
)
if err or err1 or err2:
logger.error(
f"Can't Initialize database : {err or err1 or err2}",
)
sys_exit(1)
else:
logger.info("Database initialized")
if args.init:
sys_exit(0)
elif is_initialized:
logger.info(
"Database is already initialized, skipping ...",
)
sys_exit(0)
elif integration == "Kubernetes":
corev1 = kube_client.CoreV1Api()
tmp_config = {}
apis = []
db = None
for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if (
pod.metadata.annotations != None
and "bunkerweb.io/INSTANCE" in pod.metadata.annotations
):
api_http_port = None
api_server_name = None
for pod_env in pod.spec.containers[0].env:
tmp_config[pod_env.name] = pod_env.value
if db is None and pod_env.name == "DATABASE_URI":
db = Database(
logger,
sqlalchemy_string=pod_env.value,
)
elif pod_env.name == "API_HTTP_PORT":
api_http_port = pod_env.value
elif pod_env.name == "API_SERVER_NAME":
api_server_name = pod_env.value
apis.append(
API(
f"http://{pod.status.pod_ip}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
supported_config_types = [
"http",
"stream",
"server-http",
"server-stream",
"default-server-http",
"modsec",
"modsec-crs",
]
custom_confs = []
for configmap in corev1.list_config_map_for_all_namespaces(
watch=False
).items:
if (
configmap.metadata.annotations is None
or "bunkerweb.io/CONFIG_TYPE" not in configmap.metadata.annotations
):
continue
config_type = configmap.metadata.annotations["bunkerweb.io/CONFIG_TYPE"]
if config_type not in supported_config_types:
logger.warning(
f"Ignoring unsupported CONFIG_TYPE {config_type} for ConfigMap {configmap.metadata.name}",
)
continue
elif not configmap.data:
logger.warning(
f"Ignoring blank ConfigMap {configmap.metadata.name}",
)
continue
config_site = ""
if "bunkerweb.io/CONFIG_SITE" in configmap.metadata.annotations:
config_site = (
f"{configmap.metadata.annotations['bunkerweb.io/CONFIG_SITE']}/"
)
for config_name, config_data in configmap.data.items():
custom_confs.append(
{
"value": config_data,
"exploded": (config_site, config_type, config_name),
}
)
else:
docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
)
tmp_config = {}
custom_confs = []
apis = []
db = None
for instance in (
docker_client.containers.list(filters={"label": "bunkerweb.INSTANCE"})
if integration == "Docker"
else docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
)
):
conf, cstm_confs, tmp_apis, tmp_db = get_instance_configs_and_apis(
instance, db, integration
)
tmp_config.update(conf)
custom_confs.extend(cstm_confs)
apis.extend(tmp_apis)
if db is None:
db = tmp_db
if db is None:
db = Database(logger)
# Compute the config
logger.info("Computing config ...")
config = Configurator(
args.settings, args.core, args.plugins, tmp_config, logger
)
config_files = config.get_config()
err = db.save_config(config_files, "scheduler")
if not err:
err1 = db.save_custom_configs(custom_confs, "scheduler")
else:
err = None
err1 = None
if err or err1:
logger.error(
f"Can't save config to database : {err or err1}",
)
sys_exit(1)
else:
logger.info("Config successfully saved to database")
except SystemExit as e:
sys_exit(e)
except:
logger.error(
f"Exception while executing config saver : {format_exc()}",
)
sys_exit(1)
# We're done
logger.info("Config saver successfully executed !")

View file

@ -23,12 +23,7 @@ from jobs import file_hash
class Database: class Database:
def __init__( def __init__(self, logger: Logger, sqlalchemy_string: str = None) -> None:
self,
logger: Logger,
sqlalchemy_string: str = None,
bw_integration: str = "Local",
) -> None:
"""Initialize the database""" """Initialize the database"""
self.__logger = logger self.__logger = logger
self.__sql_session = None self.__sql_session = None
@ -38,60 +33,6 @@ class Database:
logger.level if logger.level != INFO else WARNING logger.level if logger.level != INFO else WARNING
) )
if sqlalchemy_string is None and bw_integration != "Local":
if bw_integration == "Kubernetes":
from kubernetes import client as kube_client
corev1 = kube_client.CoreV1Api()
for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if (
pod.metadata.annotations != None
and "bunkerweb.io/INSTANCE" in pod.metadata.annotations
):
for pod_env in pod.spec.containers[0].env:
if pod_env.name == "DATABASE_URI":
sqlalchemy_string = pod_env.value
break
if sqlalchemy_string:
break
else:
from docker import DockerClient
docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
)
for instance in docker_client.containers.list(
filters={"label": "bunkerweb.INSTANCE"}
):
for var in instance.attrs["Config"]["Env"]:
if var.startswith("DATABASE_URI="):
sqlalchemy_string = var.replace("DATABASE_URI=", "", 1)
break
if sqlalchemy_string:
break
is_swarm = True
try:
docker_client.swarm.version
except:
is_swarm = False
if not sqlalchemy_string and is_swarm:
for instance in docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
for var in instance.attrs["Spec"]["TaskTemplate"][
"ContainerSpec"
]["Env"]:
if var.startswith("DATABASE_URI="):
sqlalchemy_string = var.replace("DATABASE_URI=", "", 1)
break
if sqlalchemy_string:
break
if not sqlalchemy_string: if not sqlalchemy_string:
sqlalchemy_string = getenv("DATABASE_URI", "sqlite:////data/db.sqlite3") sqlalchemy_string = getenv("DATABASE_URI", "sqlite:////data/db.sqlite3")
@ -376,8 +317,9 @@ class Database:
) )
if service_setting is None: if service_setting is None:
if value == setting.default or ( if key != "SERVER_NAME" and (
key in config and value == config[key] value == setting.default
or (key in config and value == config[key])
): ):
continue continue
@ -391,8 +333,9 @@ class Database:
) )
) )
elif method == "autoconf": elif method == "autoconf":
if value == setting.default or ( if key != "SERVER_NAME" and (
key in config and value == config[key] value == setting.default
or (key in config and value == config[key])
): ):
session.query(Services_settings).filter( session.query(Services_settings).filter(
Services_settings.service_id == server_name, Services_settings.service_id == server_name,

View file

@ -29,13 +29,12 @@ CUSTOM_CONFIGS_TYPES = Enum(
) )
LOG_LEVELS_ENUM = Enum("DEBUG", "INFO", "WARNING", "ERROR") LOG_LEVELS_ENUM = Enum("DEBUG", "INFO", "WARNING", "ERROR")
INTEGRATIONS_ENUM = Enum( INTEGRATIONS_ENUM = Enum(
"Docker",
"Linux", "Linux",
"Docker",
"Swarm", "Swarm",
"Kubernetes", "Kubernetes",
"Autoconf", "Autoconf",
"Ansible", "Windows",
"Vagrant",
"Unknown", "Unknown",
) )

View file

@ -38,7 +38,7 @@ RUN apk add --no-cache bash libgcc libstdc++ openssl git && \
find /opt/bunkerweb -type f -exec chmod 0740 {} \; && \ find /opt/bunkerweb -type f -exec chmod 0740 {} \; && \
find /opt/bunkerweb -type d -exec chmod 0750 {} \; && \ find /opt/bunkerweb -type d -exec chmod 0750 {} \; && \
chmod 770 /opt/bunkerweb/tmp && \ chmod 770 /opt/bunkerweb/tmp && \
chmod 750 /opt/bunkerweb/gen/main.py /opt/bunkerweb/scheduler/main.py /opt/bunkerweb/scheduler/entrypoint.sh /opt/bunkerweb/helpers/*.sh /opt/bunkerweb/deps/python/bin/* && \ chmod 750 /opt/bunkerweb/gen/*.py /opt/bunkerweb/scheduler/main.py /opt/bunkerweb/scheduler/entrypoint.sh /opt/bunkerweb/helpers/*.sh /opt/bunkerweb/deps/python/bin/* && \
find /opt/bunkerweb/core/*/jobs/* -type f -exec chmod 750 {} \; && \ find /opt/bunkerweb/core/*/jobs/* -type f -exec chmod 750 {} \; && \
mkdir /etc/nginx && \ mkdir /etc/nginx && \
chown -R scheduler:scheduler /etc/nginx && \ chown -R scheduler:scheduler /etc/nginx && \

View file

@ -1,7 +1,7 @@
from glob import glob from glob import glob
from json import loads from json import loads
from logging import Logger from logging import Logger
from os import environ from os import environ, getenv
from subprocess import DEVNULL, PIPE, STDOUT, run from subprocess import DEVNULL, PIPE, STDOUT, run
from schedule import ( from schedule import (
clear as schedule_clear, clear as schedule_clear,
@ -25,17 +25,13 @@ class JobScheduler(ApiCaller):
env={}, env={},
lock=None, lock=None,
apis=[], apis=[],
logger: Logger = setup_logger("Scheduler", environ.get("LOG_LEVEL", "INFO")), logger: Logger = setup_logger("Scheduler", getenv("LOG_LEVEL", "INFO")),
bw_integration: str = "Local", integration: str = "Linux",
): ):
super().__init__(apis) super().__init__(apis)
self.__logger = logger self.__logger = logger
self.__bw_integration = bw_integration self.__integration = integration
self.__db = Database( self.__db = Database(self.__logger)
self.__logger,
sqlalchemy_string=env.get("DATABASE_URI", None),
bw_integration=self.__bw_integration,
)
self.__env = env self.__env = env
self.__env.update(environ) self.__env.update(environ)
self.__jobs = self.__get_jobs() self.__jobs = self.__get_jobs()
@ -75,7 +71,7 @@ class JobScheduler(ApiCaller):
def __reload(self): def __reload(self):
reload = True reload = True
if self.__bw_integration == "Local": if self.__integration == "Linux":
self.__logger.info("Reloading nginx ...") self.__logger.info("Reloading nginx ...")
proc = run( proc = run(
["/usr/sbin/nginx", "-s", "reload"], ["/usr/sbin/nginx", "-s", "reload"],

View file

@ -39,7 +39,7 @@ fi
# Init database # Init database
get_env > "/tmp/variables.env" get_env > "/tmp/variables.env"
/opt/bunkerweb/gen/main.py --variables /tmp/variables.env --method scheduler --init /opt/bunkerweb/gen/save_config.py --variables /tmp/variables.env --init
if [ "$?" -ne 0 ] ; then if [ "$?" -ne 0 ] ; then
log "ENTRYPOINT" "❌" "Scheduler generator failed" log "ENTRYPOINT" "❌" "Scheduler generator failed"
exit 1 exit 1

View file

@ -118,18 +118,11 @@ if __name__ == "__main__":
) )
args = parser.parse_args() args = parser.parse_args()
generate = args.generate == "yes" generate = args.generate == "yes"
integration = "Linux"
api_caller = ApiCaller()
logger.info("Scheduler started ...") logger.info("Scheduler started ...")
bw_integration = (
"Local"
if not isfile("/usr/sbin/nginx")
and not isfile("/opt/bunkerweb/tmp/nginx.pid")
else "Cluster"
)
api_caller = ApiCaller()
if args.variables: if args.variables:
logger.info(f"Variables : {args.variables}") logger.info(f"Variables : {args.variables}")
@ -137,45 +130,25 @@ if __name__ == "__main__":
env = dotenv_values(args.variables) env = dotenv_values(args.variables)
else: else:
# Read from database # Read from database
bw_integration = (
"Kubernetes" if getenv("KUBERNETES_MODE", "no") == "yes" else "Cluster"
)
integration = "Docker" integration = "Docker"
if exists("/opt/bunkerweb/INTEGRATION"): if exists("/opt/bunkerweb/INTEGRATION"):
with open("/opt/bunkerweb/INTEGRATION", "r") as f: with open("/opt/bunkerweb/INTEGRATION", "r") as f:
integration = f.read().strip() integration = f.read().strip()
api_caller.auto_setup(bw_integration=bw_integration) api_caller.auto_setup(bw_integration=integration)
if integration == "Docker" and generate is True: if integration == "Docker" and generate is True:
# run the generator # run the config saver
cmd = f"python /opt/bunkerweb/gen/main.py --settings /opt/bunkerweb/settings.json --templates /opt/bunkerweb/confs --output /etc/nginx{f' --variables {args.variables}' if args.variables else ''} --method scheduler" cmd = f"python /opt/bunkerweb/gen/save_config.py --settings /opt/bunkerweb/settings.json"
proc = subprocess_run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT) proc = subprocess_run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT)
if proc.returncode != 0: if proc.returncode != 0:
logger.error( logger.error(
"Config generator failed, configuration will not work as expected...", "Config saver failed, configuration will not work as expected...",
) )
# Fix permissions for the nginx folder
for root, dirs, files in walk("/etc/nginx", topdown=False):
for name in files + dirs:
chown(join(root, name), "scheduler", "scheduler")
chmod(join(root, name), 0o770)
if len(api_caller._get_apis()) > 0:
# send nginx configs
logger.info("Sending /etc/nginx folder ...")
ret = api_caller._send_files("/etc/nginx", "/confs")
if not ret:
logger.error(
"Sending nginx configs failed, configuration will not work as expected...",
)
db = Database( db = Database(
logger, logger,
sqlalchemy_string=getenv("DATABASE_URI", None), sqlalchemy_string=getenv("DATABASE_URI", None),
bw_integration=bw_integration,
) )
while not db.is_initialized(): while not db.is_initialized():
@ -184,7 +157,7 @@ if __name__ == "__main__":
) )
sleep(5) sleep(5)
if bw_integration == "Kubernetes" or integration in ( if integration in (
"Swarm", "Swarm",
"Kubernetes", "Kubernetes",
"Autoconf", "Autoconf",
@ -265,7 +238,7 @@ if __name__ == "__main__":
if isfile(join(root, name)): if isfile(join(root, name)):
chmod(join(root, name), 0o740) chmod(join(root, name), 0o740)
if bw_integration != "Local": if integration != "Linux":
logger.info("Sending custom configs to BunkerWeb") logger.info("Sending custom configs to BunkerWeb")
ret = api_caller._send_files("/data/configs", "/custom_configs") ret = api_caller._send_files("/data/configs", "/custom_configs")
@ -281,7 +254,7 @@ if __name__ == "__main__":
env=deepcopy(env), env=deepcopy(env),
apis=api_caller._get_apis(), apis=api_caller._get_apis(),
logger=logger, logger=logger,
bw_integration=bw_integration, integration=integration,
) )
# Only run jobs once # Only run jobs once
@ -292,7 +265,7 @@ if __name__ == "__main__":
if generate is True: if generate is True:
# run the generator # run the generator
cmd = f"python /opt/bunkerweb/gen/main.py --settings /opt/bunkerweb/settings.json --templates /opt/bunkerweb/confs --output /etc/nginx{f' --variables {args.variables}' if args.variables else ''} --method scheduler" cmd = f"python /opt/bunkerweb/gen/main.py --settings /opt/bunkerweb/settings.json --templates /opt/bunkerweb/confs --output /etc/nginx{f' --variables {args.variables}' if args.variables else ''}"
proc = subprocess_run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT) proc = subprocess_run(cmd.split(" "), stdin=DEVNULL, stderr=STDOUT)
if proc.returncode != 0: if proc.returncode != 0:
logger.error( logger.error(
@ -334,7 +307,7 @@ if __name__ == "__main__":
logger.info("Successfuly sent /data/cache folder") logger.info("Successfuly sent /data/cache folder")
# reload nginx # reload nginx
if bw_integration == "Local": if integration == "Linux":
logger.info("Reloading nginx ...") logger.info("Reloading nginx ...")
proc = run( proc = run(
["/usr/sbin/nginx", "-s", "reload"], ["/usr/sbin/nginx", "-s", "reload"],
@ -410,7 +383,7 @@ if __name__ == "__main__":
if isfile(join(root, name)): if isfile(join(root, name)):
chmod(join(root, name), 0o740) chmod(join(root, name), 0o740)
if bw_integration != "Local": if integration != "Linux":
logger.info("Sending custom configs to BunkerWeb") logger.info("Sending custom configs to BunkerWeb")
ret = api_caller._send_files("/data/configs", "/custom_configs") ret = api_caller._send_files("/data/configs", "/custom_configs")

View file

@ -118,29 +118,31 @@ PLUGIN_KEYS = [
"settings", "settings",
] ]
bw_integration = "Local" integration = "Linux"
if getenv("KUBERNETES_MODE", "no") == "yes": if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes" integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes" or getenv("AUTOCONF_MODE", "no") == "yes": elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Cluster" integration = "Swarm"
elif getenv("AUTOCONF_MODE", "no") == "yes":
integration = "Autoconf"
try: try:
docker_client: DockerClient = DockerClient( docker_client: DockerClient = DockerClient(
base_url=vars.get("DOCKER_HOST", "unix:///var/run/docker.sock") base_url=vars.get("DOCKER_HOST", "unix:///var/run/docker.sock")
) )
bw_integration = "Cluster" integration = "Cluster"
except (docker_APIError, DockerException): except (docker_APIError, DockerException):
logger.warning("No docker host found") logger.warning("No docker host found")
docker_client = None docker_client = None
db = Database(logger, bw_integration=bw_integration) db = Database(logger)
try: try:
app.config.update( app.config.update(
DEBUG=True, DEBUG=True,
SECRET_KEY=vars["FLASK_SECRET"], SECRET_KEY=vars["FLASK_SECRET"],
ABSOLUTE_URI=vars["ABSOLUTE_URI"], ABSOLUTE_URI=vars["ABSOLUTE_URI"],
INSTANCES=Instances(docker_client, bw_integration), INSTANCES=Instances(docker_client, integration),
CONFIG=Config(logger, db), CONFIG=Config(logger, db),
CONFIGFILES=ConfigFiles(logger, db), CONFIGFILES=ConfigFiles(logger, db),
SESSION_COOKIE_DOMAIN=vars["ABSOLUTE_URI"] SESSION_COOKIE_DOMAIN=vars["ABSOLUTE_URI"]

View file

@ -59,9 +59,9 @@ class Instance:
class Instances: class Instances:
def __init__(self, docker_client, bw_integration: str): def __init__(self, docker_client, integration: str):
self.__docker = docker_client self.__docker = docker_client
self.__bw_integration = bw_integration self.__integration = integration
def __instance_from_id(self, _id) -> Instance: def __instance_from_id(self, _id) -> Instance:
instances: list[Instance] = self.get_instances() instances: list[Instance] = self.get_instances()
@ -104,35 +104,28 @@ class Instances:
apiCaller, apiCaller,
) )
) )
elif self.__integration == "Swarm":
for instance in self.__docker.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
status = "down"
desired_tasks = instance.attrs["ServiceStatus"]["DesiredTasks"]
running_tasks = instance.attrs["ServiceStatus"]["RunningTasks"]
if desired_tasks > 0 and (desired_tasks == running_tasks):
status = "up"
is_swarm = True instances.append(
try: Instance(
self.__docker.swarm.version instance.id,
except: instance.name,
is_swarm = False instance.name,
"service",
if is_swarm: status,
for instance in self.__docker.services.list( instance,
filters={"label": "bunkerweb.INSTANCE"} apiCaller,
):
status = "down"
desired_tasks = instance.attrs["ServiceStatus"]["DesiredTasks"]
running_tasks = instance.attrs["ServiceStatus"]["RunningTasks"]
if desired_tasks > 0 and (desired_tasks == running_tasks):
status = "up"
instances.append(
Instance(
instance.id,
instance.name,
instance.name,
"service",
status,
instance,
apiCaller,
)
) )
elif self.__bw_integration == "Kubernetes": )
elif self.__integration == "Kubernetes":
corev1 = kube_client.CoreV1Api() corev1 = kube_client.CoreV1Api()
for pod in corev1.list_pod_for_all_namespaces(watch=False).items: for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if ( if (

View file

@ -1,10 +1,20 @@
from io import BytesIO from io import BytesIO
from os import environ, getenv from os import environ, getenv
from sys import path as sys_path
from tarfile import open as taropen from tarfile import open as taropen
if "/opt/bunkerweb/utils" not in sys_path:
sys_path.append("/opt/bunkerweb/utils")
from logger import setup_logger from logger import setup_logger
from API import API from API import API
if "/opt/bunkerweb/deps/python" not in sys_path:
sys_path.append("/opt/bunkerweb/deps/python")
from kubernetes import client as kube_client
from docker import DockerClient
class ApiCaller: class ApiCaller:
def __init__(self, apis=[]): def __init__(self, apis=[]):
@ -12,12 +22,13 @@ class ApiCaller:
self.__logger = setup_logger("Api", environ.get("LOG_LEVEL", "INFO")) self.__logger = setup_logger("Api", environ.get("LOG_LEVEL", "INFO"))
def auto_setup(self, bw_integration: str = None): def auto_setup(self, bw_integration: str = None):
if bw_integration is None and getenv("KUBERNETES_MODE", "no") == "yes": if bw_integration is None:
bw_integration = "Kubernetes" if getenv("KUBERNETES_MODE", "no") == "yes":
bw_integration = "Kubernetes"
elif getenv("SWARM_MODE", "no") == "yes":
bw_integration = "Swarm"
if bw_integration == "Kubernetes": if bw_integration == "Kubernetes":
from kubernetes import client as kube_client
corev1 = kube_client.CoreV1Api() corev1 = kube_client.CoreV1Api()
for pod in corev1.list_pod_for_all_namespaces(watch=False).items: for pod in corev1.list_pod_for_all_namespaces(watch=False).items:
if ( if (
@ -39,9 +50,28 @@ class ApiCaller:
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"), host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
) )
) )
else: elif bw_integration == "Swarm":
from docker import DockerClient for instance in docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
api_http_port = None
api_server_name = None
for var in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][
"Env"
]:
if var.startswith("API_HTTP_PORT="):
api_http_port = var.replace("API_HTTP_PORT=", "", 1)
elif var.startswith("API_SERVER_NAME="):
api_server_name = var.replace("API_SERVER_NAME=", "", 1)
self.__apis.append(
API(
f"http://{instance.name}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
else:
docker_client = DockerClient( docker_client = DockerClient(
base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock") base_url=getenv("DOCKER_HOST", "unix:///var/run/docker.sock")
) )
@ -64,34 +94,6 @@ class ApiCaller:
) )
) )
is_swarm = True
try:
docker_client.swarm.version
except:
is_swarm = False
if is_swarm:
for instance in docker_client.services.list(
filters={"label": "bunkerweb.INSTANCE"}
):
api_http_port = None
api_server_name = None
for var in instance.attrs["Spec"]["TaskTemplate"]["ContainerSpec"][
"Env"
]:
if var.startswith("API_HTTP_PORT="):
api_http_port = var.replace("API_HTTP_PORT=", "", 1)
elif var.startswith("API_SERVER_NAME="):
api_server_name = var.replace("API_SERVER_NAME=", "", 1)
self.__apis.append(
API(
f"http://{instance.name}:{api_http_port or getenv('API_HTTP_PORT', '5000')}",
host=api_server_name or getenv("API_SERVER_NAME", "bwapi"),
)
)
def _set_apis(self, apis): def _set_apis(self, apis):
self.__apis = apis self.__apis = apis