mirror of
https://github.com/SpecterOps/Nemesis
synced 2026-06-08 12:36:42 +00:00
1130 lines
47 KiB
Python
Executable File
1130 lines
47 KiB
Python
Executable File
#!/usr/bin/python3
|
|
|
|
import argparse
|
|
import logging
|
|
import os
|
|
import secrets
|
|
import string
|
|
import subprocess
|
|
import sys
|
|
|
|
# Setup logging
|
|
logger = logging.getLogger("nemesis")
|
|
logger.setLevel(logging.INFO)
|
|
ch = logging.StreamHandler()
|
|
ch.setLevel(logging.INFO)
|
|
|
|
formatter = logging.Formatter("[{levelname}] {message}", style="{")
|
|
ch.setFormatter(formatter)
|
|
|
|
logger.addHandler(ch)
|
|
|
|
|
|
if os.geteuid() == 0:
|
|
logger.error("Please do not run this script as root")
|
|
sys.exit(1)
|
|
|
|
# Check dependencies
|
|
try:
|
|
import boto3
|
|
from passlib.hash import apr_md5_crypt
|
|
from vyper import v
|
|
except ModuleNotFoundError:
|
|
logger.error("Please run `pip3 install boto3 vyper-config passlib`")
|
|
sys.exit(1)
|
|
|
|
version = "v0.1.0a"
|
|
|
|
|
|
######################################################
|
|
#
|
|
# Helpers
|
|
#
|
|
######################################################
|
|
|
|
|
|
def print_logo():
|
|
print(
|
|
f"""
|
|
_ _ _ _____ _ _____
|
|
| \\ | | (_) / ____| | |_ _|
|
|
| \\| | ___ _ __ ___ ___ ___ _ ___ ______| | | | | |
|
|
| . ` |/ _ \\ '_ ` _ \\ / _ \\/ __| / __|______| | | | | |
|
|
| |\\ | __/ | | | | | __/\\__ \\ \\__ \\ | |____| |____ _| |_
|
|
|_| \\_|\\___|_| |_| |_|\\___||___/_|___/ \\_____|______|_____|
|
|
{version}
|
|
|
|
"""
|
|
)
|
|
|
|
|
|
def get_random_password(length=24):
|
|
"""Gets a random password of the specified length."""
|
|
|
|
return "".join(secrets.choice(string.ascii_letters + string.digits) for i in range(length))
|
|
|
|
|
|
def run_cmd(cmd, show_error=False):
|
|
"""Small helper that returns program output or None if execution fails."""
|
|
|
|
exitcode, output = subprocess.getstatusoutput(cmd)
|
|
if "(NotFound)" in output or output.lower() == "none":
|
|
return None
|
|
elif exitcode == 0:
|
|
if output == "<no value>":
|
|
return None
|
|
else:
|
|
return output
|
|
elif exitcode != 0 and show_error:
|
|
logger.error(f"\Exit code '{exitcode}' running command '{cmd}' : {output}")
|
|
return None
|
|
else:
|
|
return None
|
|
|
|
|
|
######################################################
|
|
#
|
|
# Kubectl functions
|
|
#
|
|
######################################################
|
|
|
|
|
|
def get_kubectl_value(key):
|
|
"""Gets a specific aws configmap/key value that is already set in kubectl."""
|
|
|
|
if key == "aws_region":
|
|
return run_cmd("kubectl get configmaps aws-config -o=go-template='{{index .data \"aws-default-region\"}}'")
|
|
|
|
elif key == "aws_bucket":
|
|
return run_cmd("kubectl get configmaps aws-config -o=go-template='{{index .data \"aws-bucket\"}}'")
|
|
|
|
elif key == "aws_kms_key_alias":
|
|
return run_cmd("kubectl get configmaps aws-config -o=go-template='{{index .data \"aws-kms-key-alias\"}}'")
|
|
|
|
elif key == "aws_access_key_id":
|
|
return run_cmd("kubectl get secret aws-creds -o=go-template='{{index .data \"aws_access_key_id\"}}' | base64 -d")
|
|
|
|
elif key == "aws_secret_key":
|
|
return run_cmd("kubectl get secret aws-creds -o=go-template='{{index .data \"aws_secret_key\"}}' | base64 -d")
|
|
|
|
elif key == "minio_root_user":
|
|
return run_cmd("kubectl get secret minio-creds -o=go-template='{{index .data \"minio_root_user\"}}' | base64 -d")
|
|
|
|
elif key == "minio_root_password":
|
|
return run_cmd("kubectl get secret minio-creds -o=go-template='{{index .data \"minio_root_password\"}}' | base64 -d")
|
|
|
|
elif key == "minio_storage_size":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"minio_storage_size\"}}'")
|
|
|
|
elif key == "storage_provider":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"storage_provider\"}}'")
|
|
|
|
elif key == "assessment_id":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"assessment-id\"}}'")
|
|
|
|
elif key == "log_level":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"log-level\"}}'")
|
|
|
|
elif key == "data_expiration_days":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"data-expiration-days\"}}'")
|
|
|
|
elif key == "nemesis_http_server":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"nemesis-http-server\"}}'")
|
|
|
|
elif key == "environment":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"environment\"}}'")
|
|
|
|
elif key == "disable_slack_alerting":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"disable-slack-alerting\"}}'")
|
|
|
|
elif key == "slack_channel":
|
|
return run_cmd("kubectl get configmaps operation-config -o=go-template='{{index .data \"slack-alert-channel\"}}'")
|
|
|
|
elif key == "slack_webhook":
|
|
return run_cmd("kubectl get secret operation-creds -o=go-template='{{index .data \"slack_web_hook\"}}' | base64 -d")
|
|
|
|
elif key == "basic_auth_password":
|
|
return run_cmd("kubectl get secret operation-creds -o=go-template='{{index .data \"basic-auth-password\"}}' | base64 -d")
|
|
|
|
elif key == "basic_auth_user":
|
|
return run_cmd("kubectl get secret operation-creds -o=go-template='{{index .data \"basic-auth-user\"}}' | base64 -d")
|
|
|
|
elif key == "elasticsearch_password":
|
|
return run_cmd("kubectl get secret elasticsearch-users -o=go-template='{{index .data \"password\"}}' | base64 -d")
|
|
|
|
elif key == "elasticsearch_user":
|
|
return run_cmd("kubectl get secret elasticsearch-users -o=go-template='{{index .data \"username\"}}' | base64 -d")
|
|
|
|
elif key == "grafana_password":
|
|
return run_cmd("kubectl get secret grafana-creds --namespace=monitoring -o=go-template='{{index .data \"username\"}}' | base64 -d")
|
|
|
|
elif key == "grafana_user":
|
|
return run_cmd("kubectl get secret grafana-creds --namespace=monitoring -o=go-template='{{index .data \"password\"}}' | base64 -d")
|
|
|
|
elif key == "postgres_user":
|
|
return run_cmd("kubectl get secret postgres-creds -o=go-template='{{index .data \"postgres-user\"}}' | base64 -d")
|
|
|
|
elif key == "postgres_password":
|
|
return run_cmd("kubectl get secret postgres-creds -o=go-template='{{index .data \"postgres-password\"}}' | base64 -d")
|
|
|
|
elif key == "dashboard_user":
|
|
return run_cmd("kubectl get secret dashboard-creds -o=go-template='{{index .data \"dashboard-user\"}}' | base64 -d")
|
|
|
|
elif key == "dashboard_password":
|
|
return run_cmd("kubectl get secret dashboard-creds -o=go-template='{{index .data \"dashboard-password\"}}' | base64 -d")
|
|
|
|
elif key == "dashboard_user":
|
|
return run_cmd("kubectl get secret dashboard-creds -o=go-template='{{index .data \"dashboard-user\"}}' | base64 -d")
|
|
|
|
elif key == "dashboard_password":
|
|
return run_cmd("kubectl get secret dashboard-creds -o=go-template='{{index .data \"dashboard-password\"}}' | base64 -d")
|
|
|
|
elif key == "pgadmin_email":
|
|
return run_cmd("kubectl get secret postgres-creds -o=go-template='{{index .data \"pgadmin-email\"}}' | base64 -d")
|
|
|
|
elif key == "pgadmin_password":
|
|
return run_cmd("kubectl get secret postgres-creds -o=go-template='{{index .data \"pgadmin-password\"}}' | base64 -d")
|
|
|
|
elif key == "rabbitmq_admin_user":
|
|
return run_cmd("kubectl get secret rabbitmq-creds -o=go-template='{{index .data \"rabbitmq-admin-user\"}}' | base64 -d")
|
|
|
|
elif key == "rabbitmq_admin_password":
|
|
return run_cmd("kubectl get secret rabbitmq-creds -o=go-template='{{index .data \"rabbitmq-admin-password\"}}' | base64 -d")
|
|
|
|
elif key == "rabbitmq_connectionuri":
|
|
return run_cmd("kubectl get secret rabbitmq-creds -o=go-template='{{index .data \"rabbitmq-connectionuri\"}}' | base64 -d")
|
|
|
|
elif key == "rabbitmq_erlang_cookie":
|
|
return run_cmd("kubectl get secret rabbitmq-creds -o=go-template='{{index .data \"rabbitmq-erlang-cookie\"}}' | base64 -d")
|
|
|
|
else:
|
|
logger.error(f"Invalid config key: {key}")
|
|
return None
|
|
|
|
|
|
def set_config_values(config_values):
|
|
"""Deletes all applicable secret/configmap values and resets them using the new values."""
|
|
|
|
if any(v.is_set(config_value) for config_value in config_values):
|
|
logger.info("New configuration:\n")
|
|
|
|
for config_value in config_values:
|
|
if v.is_set(config_value):
|
|
logger.info(" {0:25} {1}".format(config_value, v.get(config_value)))
|
|
|
|
continue_set_values = False
|
|
if v.get("force"):
|
|
continue_set_values = True
|
|
else:
|
|
prompt_input = input("\n[*] Continue? [Y/n] : ").strip().lower()
|
|
if prompt_input == "y" or prompt_input == "":
|
|
continue_set_values = True
|
|
print()
|
|
|
|
if continue_set_values:
|
|
run_cmd("kubectl delete configmap aws-config")
|
|
run_cmd(
|
|
"kubectl create configmap aws-config" + f" --from-literal=aws-bucket={v.get('aws_bucket')}" + f" --from-literal=aws-default-region={v.get('aws_region')}" + f" --from-literal=aws-kms-key-alias={v.get('aws_kms_key_alias')}",
|
|
True,
|
|
)
|
|
|
|
run_cmd("kubectl delete secret aws-creds")
|
|
run_cmd(
|
|
"kubectl create secret generic aws-creds" + f" --from-literal=aws_access_key_id={v.get('aws_access_key_id')}" + f" --from-literal=aws_secret_key={v.get('aws_secret_key')}",
|
|
True,
|
|
)
|
|
|
|
run_cmd("kubectl delete secret minio-creds")
|
|
run_cmd(
|
|
"kubectl create secret generic minio-creds" + f" --from-literal=minio_root_user={v.get('minio_root_user')}" + f" --from-literal=minio_root_password={v.get('minio_root_password')}",
|
|
True,
|
|
)
|
|
|
|
run_cmd("kubectl delete configmap operation-config")
|
|
run_cmd(
|
|
"kubectl create configmap operation-config"
|
|
+ f" --from-literal=slack-alert-channel={v.get('slack_channel')}"
|
|
+ f" --from-literal=disable-slack-alerting={v.get('disable_slack_alerting')}"
|
|
+ f" --from-literal=log-level={v.get('log_level')}"
|
|
+ f" --from-literal=assessment-id={v.get('assessment_id')}"
|
|
+ f" --from-literal=storage_provider={v.get('storage_provider')}"
|
|
+ f" --from-literal=minio_storage_size={v.get('minio_storage_size')}"
|
|
+ f" --from-literal=nemesis-http-server={v.get('nemesis_http_server')}"
|
|
+ f" --from-literal=environment={v.get('environment')}"
|
|
+ f" --from-literal=data-expiration-days={v.get('data_expiration_days')}",
|
|
True,
|
|
)
|
|
|
|
run_cmd("kubectl create namespace monitoring")
|
|
|
|
run_cmd("kubectl delete configmap operation-config --namespace=monitoring")
|
|
run_cmd(
|
|
"kubectl create configmap operation-config --namespace=monitoring"
|
|
+ f" --from-literal=slack-alert-channel={v.get('slack_channel')}"
|
|
+ f" --from-literal=disable-slack-alerting={v.get('disable_slack_alerting')}"
|
|
+ f" --from-literal=log-level={v.get('log_level')}"
|
|
+ f" --from-literal=assessment-id={v.get('assessment_id')}"
|
|
+ f" --from-literal=storage_provider={v.get('storage_provider')}"
|
|
+ f" --from-literal=minio_storage_size={v.get('minio_storage_size')}"
|
|
+ f" --from-literal=nemesis-http-server={v.get('nemesis_http_server')}"
|
|
+ f" --from-literal=environment={v.get('environment')}"
|
|
+ f" --from-literal=data-expiration-days={v.get('data_expiration_days')}",
|
|
True,
|
|
)
|
|
|
|
run_cmd("kubectl delete secret operation-creds")
|
|
run_cmd(
|
|
"kubectl create secret generic operation-creds"
|
|
+ f" --from-literal=slack_web_hook={v.get('slack_webhook')}"
|
|
+ f" --from-literal=basic-auth-user={v.get('basic_auth_user')}"
|
|
+ f" --from-literal=basic-auth-password={v.get('basic_auth_password')}"
|
|
)
|
|
|
|
run_cmd("kubectl delete secret basic-auth")
|
|
encrypted = apr_md5_crypt.hash(v.get("basic_auth_password"))
|
|
run_cmd("kubectl create secret generic basic-auth" + f" --from-literal=auth='{v.get('basic_auth_user')}:{encrypted}'")
|
|
run_cmd("kubectl delete secret basic-auth -n monitoring")
|
|
run_cmd("kubectl create secret generic basic-auth -n monitoring" + f" --from-literal=auth='{v.get('basic_auth_user')}:{encrypted}'")
|
|
|
|
run_cmd("kubectl delete secret operation-creds --namespace=monitoring")
|
|
run_cmd(
|
|
"kubectl create secret generic operation-creds --namespace=monitoring"
|
|
+ f" --from-literal=slack_web_hook={v.get('slack_webhook')}"
|
|
+ f" --from-literal=basic-auth-user={v.get('basic_auth_user')}"
|
|
+ f" --from-literal=basic-auth-password={v.get('basic_auth_password')}"
|
|
)
|
|
|
|
run_cmd("kubectl delete secret grafana-creds --namespace=monitoring")
|
|
run_cmd("kubectl create secret generic grafana-creds --namespace=monitoring" + f" --from-literal=username={v.get('grafana_user')}" + f" --from-literal=password={v.get('grafana_password')}")
|
|
|
|
run_cmd("kubectl delete secret elasticsearch-users")
|
|
run_cmd("kubectl create secret generic elasticsearch-users" + f" --from-literal=username={v.get('elasticsearch_user')}" + f" --from-literal=password={v.get('elasticsearch_password')}" + f" --from-literal=roles=superuser")
|
|
|
|
run_cmd("kubectl delete secret postgres-creds")
|
|
run_cmd(
|
|
"kubectl create secret generic postgres-creds"
|
|
+ f" --from-literal=postgres-user={v.get('postgres_user')}"
|
|
+ f" --from-literal=postgres-password={v.get('postgres_password')}"
|
|
+ f" --from-literal=pgadmin-email={v.get('pgadmin_email')}"
|
|
+ f" --from-literal=pgadmin-password={v.get('pgadmin_password')}"
|
|
)
|
|
|
|
run_cmd("kubectl delete secret dashboard-creds")
|
|
run_cmd("kubectl create secret generic dashboard-creds" + f" --from-literal=dashboard-user={v.get('dashboard_user')}" + f" --from-literal=dashboard-password={v.get('dashboard_password')}")
|
|
|
|
run_cmd("kubectl delete secret fluentd-creds --namespace=kube-system")
|
|
run_cmd(
|
|
"kubectl create secret generic fluentd-creds --namespace=kube-system"
|
|
+ f" --from-literal=elasticsearch_username={v.get('elasticsearch_user')}"
|
|
+ f" --from-literal=elasticsearch_password={v.get('elasticsearch_password')}"
|
|
+ f" --from-literal=roles=superuser"
|
|
)
|
|
|
|
run_cmd("kubectl delete secret rabbitmq-creds")
|
|
run_cmd(
|
|
"kubectl create secret generic rabbitmq-creds"
|
|
+ f" --from-literal=rabbitmq-admin-user={v.get('rabbitmq_admin_user')}"
|
|
+ f" --from-literal=rabbitmq-admin-password={v.get('rabbitmq_admin_password')}"
|
|
+ f" --from-literal=rabbitmq-connectionuri={v.get('rabbitmq_connectionuri')}"
|
|
+ f" --from-literal=rabbitmq-erlang-cookie={v.get('rabbitmq_erlang_cookie')}"
|
|
)
|
|
|
|
# hack, but application not working through skaffold
|
|
run_cmd(f"kubectl apply --server-side=true -f ./monitoring/grafana-dashboards.yaml")
|
|
|
|
|
|
######################################################
|
|
#
|
|
# Validation functions
|
|
#
|
|
######################################################
|
|
def ensure_command(command: str):
|
|
logger.info("Checking for command: %s", command)
|
|
exitcode, output = subprocess.getstatusoutput(command)
|
|
|
|
if exitcode == 127:
|
|
logger.error(f"'{command}' command not found. Please install 'kubectl' and try again.")
|
|
sys.exit(1)
|
|
|
|
|
|
def validate_kubernetes():
|
|
"""Checks if kubernetes is running, and start minikube it if it's not."""
|
|
|
|
logger.info("Validating k8s commands are available...")
|
|
ensure_command("kubectl")
|
|
ensure_command("helm")
|
|
ensure_command("openssl version")
|
|
|
|
exitcode, output = subprocess.getstatusoutput("kubectl cluster-info")
|
|
if exitcode == 0:
|
|
logger.info("kubectl configured to use existing cluster")
|
|
return
|
|
else:
|
|
logger.info("No Kubernetes cluster found using kubectl, attempting to start Minikube")
|
|
start_minikube()
|
|
|
|
|
|
def start_minikube():
|
|
ensure_command("minikube")
|
|
|
|
minikube_running = False
|
|
exitcode, output = subprocess.getstatusoutput("minikube status")
|
|
|
|
if "host: Running" in output[1]:
|
|
minikube_running = True
|
|
|
|
if not minikube_running:
|
|
logger.info("Starting Minikube...")
|
|
output = subprocess.getstatusoutput("minikube start --network-plugin=cni --cni=calico")
|
|
else:
|
|
logger.info("Minikube is already running")
|
|
|
|
|
|
def validate_config_values(config_keys):
|
|
"""
|
|
If a value is not set in Vyper, this will attempt to pull the value
|
|
from kubectl and set that in the Vyper config.
|
|
|
|
If values are not set in either, an error message will display and the script
|
|
will exist with code 1, with the exception of:
|
|
- If "basic_auth_user" is not set, it will be set to "nemesis".
|
|
- If "basic_auth_password" is not set, it will be set to 24 random characaters.
|
|
- If "elasticsearch_user" is not set, it will be set to "nemesis".
|
|
- If "elasticsearch_password" is not set, it will be set to 24 random characaters.
|
|
- If "postgres_auth_user" is not set, it will be set to "nemesis".
|
|
- If "postgres_auth_password" is not set, it will be set to 24 random characaters.
|
|
- If "dashboard_auth_user" is not set, it will be set to "nemesis".
|
|
- If "dashboard_auth_password" is not set, it will be set to 24 random characaters.
|
|
- If "data_expiration_days" is not set, it will default to 100.
|
|
- If "minio_root_user" is not set, it will default to "nemesis".
|
|
- If "minio_root_password" is not set, it will be set to 24 random characaters.
|
|
- If "storage_provider" is not set or is set to "minio", aws_* variables will be filled in with "not-applicable"
|
|
- If "minio_storage_size" is not set it will be set to 30Gi.
|
|
|
|
The end result should be a Vyper config populated with existing values where explictly supplied
|
|
values are overwritten in the config. This is because we can't modify configmaps/secrets in
|
|
kubectl directly, we first have to delete them, so we want to preserve existing values if possible
|
|
in case we're overwriting just one value.
|
|
"""
|
|
|
|
# Ensure that nemesis_http_server ends with a "/"
|
|
if v.get("nemesis_http_server") and v.get("nemesis_http_server") != "<no value>":
|
|
nemesis_http_server = v.get("nemesis_http_server")
|
|
if not nemesis_http_server.endswith("/"):
|
|
v.set("nemesis_http_server", f"{nemesis_http_server}/")
|
|
|
|
# if a storage provider is not set, assume minio
|
|
if not v.get("storage_provider") or v.get("storage_provider") == "<no value>":
|
|
storage_provider = get_kubectl_value("storage_provider")
|
|
if storage_provider:
|
|
v.set("storage_provider", storage_provider)
|
|
else:
|
|
v.set("storage_provider", "minio")
|
|
|
|
if v.get("storage_provider") == "minio":
|
|
if not v.get("aws_access_key_id"):
|
|
v.set("aws_access_key_id", "not-applicable")
|
|
if not v.get("aws_bucket"):
|
|
v.set("aws_bucket", "not-applicable")
|
|
if not v.get("aws_kms_key_alias"):
|
|
v.set("aws_kms_key_alias", "not-applicable")
|
|
if not v.get("aws_region"):
|
|
v.set("aws_region", "not-applicable")
|
|
if not v.get("aws_secret_key"):
|
|
v.set("aws_secret_key", "not-applicable")
|
|
if not v.get("minio_storage_size") or v.get("minio_storage_size") == "<no value>":
|
|
v.set("minio_storage_size", "30Gi")
|
|
if not v.get("minio_root_user") or v.get("minio_root_user") == "<no value>":
|
|
minio_root_user = get_kubectl_value("minio_root_user")
|
|
if minio_root_user:
|
|
v.set("minio_root_user", minio_root_user)
|
|
else:
|
|
# set default username if not supplied or already set
|
|
v.set("minio_root_user", "nemesis")
|
|
if not v.get("minio_root_password") or v.get("minio_root_password") == "<no value>":
|
|
minio_root_password = get_kubectl_value("minio_root_password")
|
|
if minio_root_password:
|
|
v.set("minio_root_password", minio_root_password)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("minio_root_password", get_random_password(24))
|
|
else:
|
|
if not v.get("minio_root_user"):
|
|
v.set("minio_root_user", "not-applicable")
|
|
if not v.get("minio_root_password"):
|
|
v.set("minio_root_password", "not-applicable")
|
|
if not v.get("minio_storage_size"):
|
|
v.set("minio_storage_size", "30Gi")
|
|
|
|
if not v.get("force"):
|
|
for config_key in config_keys:
|
|
not_required_args = [
|
|
"basic_auth_password",
|
|
"data_expiration_days",
|
|
"log_level",
|
|
"pgadmin_email",
|
|
"disable_slack_alerting",
|
|
"rabbitmq_erlang_cookie",
|
|
]
|
|
if config_key not in not_required_args:
|
|
if config_key.startswith("slack_") and str(v.get("disable_slack_alerting")).lower() == "true":
|
|
continue
|
|
|
|
if not v.get(config_key):
|
|
# set the value for to config key if it already exists in Kubectl
|
|
config_value = get_kubectl_value(config_key)
|
|
if not config_value:
|
|
# otherwise prompt
|
|
config_value = input(f"\n[*] Please enter a value for '{config_key}' or <enter> for the default: ")
|
|
v.set(config_key, config_value)
|
|
|
|
if not v.get("log_level") or v.get("log_level") == "<no value>":
|
|
log_level = get_kubectl_value("log_level")
|
|
if log_level:
|
|
v.set("log_level", log_level)
|
|
else:
|
|
v.set("log_level", "INFO")
|
|
|
|
if not v.get("basic_auth_user") or v.get("basic_auth_user") == "<no value>":
|
|
basic_auth_kubectl = get_kubectl_value("basic_auth_user")
|
|
if basic_auth_kubectl:
|
|
v.set("basic_auth_user", basic_auth_kubectl)
|
|
else:
|
|
v.set("basic_auth_user", "nemesis")
|
|
|
|
if not v.get("basic_auth_password") or v.get("basic_auth_password") == "<no value>":
|
|
basic_auth_kubectl = get_kubectl_value("basic_auth_password")
|
|
if basic_auth_kubectl:
|
|
v.set("basic_auth_password", basic_auth_kubectl)
|
|
else:
|
|
# set a random basic auth password if not supplied or already set
|
|
v.set("basic_auth_password", get_random_password(24))
|
|
|
|
if not v.get("elasticsearch_user") or v.get("elasticsearch_user") == "<no value>":
|
|
elasticsearch_user_kubectl = get_kubectl_value("elasticsearch_user")
|
|
if elasticsearch_user_kubectl:
|
|
v.set("elasticsearch_user", elasticsearch_user_kubectl)
|
|
else:
|
|
v.set("elasticsearch_user", "nemesis")
|
|
|
|
if not v.get("elasticsearch_password") or v.get("elasticsearch_password") == "<no value>":
|
|
elasticsearch_password_kubectl = get_kubectl_value("elasticsearch_password")
|
|
if elasticsearch_password_kubectl:
|
|
v.set("elasticsearch_password", elasticsearch_password_kubectl)
|
|
else:
|
|
# set a random elasticsearch password if not supplied or already set
|
|
v.set("elasticsearch_password", get_random_password(24))
|
|
|
|
if not v.get("postgres_user") or v.get("postgres_user") == "<no value>":
|
|
postgres_user_kubectl = get_kubectl_value("postgres_user")
|
|
if postgres_user_kubectl:
|
|
v.set("postgres_user", postgres_user_kubectl)
|
|
else:
|
|
v.set("postgres_user", "nemesis")
|
|
|
|
if not v.get("postgres_password") or v.get("postgres_password") == "<no value>":
|
|
postgres_password_kubectl = get_kubectl_value("postgres_password")
|
|
if postgres_password_kubectl:
|
|
v.set("postgres_password", postgres_password_kubectl)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("postgres_password", get_random_password(24))
|
|
|
|
if not v.get("dashboard_user") or v.get("dashboard_user") == "<no value>":
|
|
dashboard_user_kubectl = get_kubectl_value("dashboard_user")
|
|
if dashboard_user_kubectl:
|
|
v.set("dashboard_user", dashboard_user_kubectl)
|
|
else:
|
|
v.set("dashboard_user", "nemesis")
|
|
|
|
if not v.get("dashboard_password") or v.get("dashboard_password") == "<no value>":
|
|
dashboard_password_kubectl = get_kubectl_value("dashboard_password")
|
|
if dashboard_password_kubectl:
|
|
v.set("dashboard_password", dashboard_password_kubectl)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("dashboard_password", get_random_password(24))
|
|
|
|
if not v.get("pgadmin_email") or v.get("pgadmin_email") == "<no value>":
|
|
pgadmin_email_kubectl = get_kubectl_value("pgadmin_email")
|
|
if pgadmin_email_kubectl:
|
|
v.set("pgadmin_email", pgadmin_email_kubectl)
|
|
else:
|
|
v.set("pgadmin_email", "nemesis@nemesis.com")
|
|
|
|
if not v.get("pgadmin_password") or v.get("pgadmin_password") == "<no value>":
|
|
pgadmin_password_kubectl = get_kubectl_value("pgadmin_password")
|
|
if pgadmin_password_kubectl:
|
|
v.set("pgadmin_password", pgadmin_password_kubectl)
|
|
else:
|
|
# set a random pgadmin password if not supplied or already set
|
|
v.set("pgadmin_password", get_random_password(24))
|
|
|
|
if not v.get("grafana_user") or v.get("grafana_user") == "<no value>":
|
|
grafana_user_kubectl = get_kubectl_value("grafana_user")
|
|
if grafana_user_kubectl:
|
|
v.set("grafana_user", grafana_user_kubectl)
|
|
else:
|
|
v.set("grafana_user", "nemesis")
|
|
|
|
if not v.get("grafana_password") or v.get("grafana_password") == "<no value>":
|
|
grafana_password_kubectl = get_kubectl_value("grafana_password")
|
|
if grafana_password_kubectl:
|
|
v.set("grafana_password", grafana_password_kubectl)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("grafana_password", get_random_password(24))
|
|
|
|
if not v.get("data_expiration_days") or v.get("data_expiration_days") == "<no value>":
|
|
data_expiration_days_kubectl = get_kubectl_value("data_expiration_days")
|
|
if data_expiration_days_kubectl:
|
|
v.set("data_expiration_days", data_expiration_days_kubectl)
|
|
else:
|
|
v.set("data_expiration_days", "100")
|
|
|
|
if not v.get("rabbitmq_admin_user") or v.get("rabbitmq_admin_user") == "<no value>":
|
|
rabbitmq_admin_user_kubectl = get_kubectl_value("rabbitmq_admin_user")
|
|
if rabbitmq_admin_user_kubectl:
|
|
v.set("rabbitmq_admin_user", rabbitmq_admin_user_kubectl)
|
|
else:
|
|
v.set("rabbitmq_admin_user", "nemesis")
|
|
|
|
if not v.get("rabbitmq_admin_password") or v.get("rabbitmq_admin_password") == "<no value>":
|
|
rabbitmq_admin_password_kubectl = get_kubectl_value("rabbitmq_admin_password")
|
|
if rabbitmq_admin_password_kubectl:
|
|
v.set("rabbitmq_admin_password", rabbitmq_admin_password_kubectl)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("rabbitmq_admin_password", get_random_password(24))
|
|
|
|
if not v.get("rabbitmq_connectionuri") or v.get("rabbitmq_connectionuri") == "<no value>":
|
|
rabbitmq_connectionuri_kubectl = get_kubectl_value("rabbitmq_connectionuri")
|
|
if rabbitmq_connectionuri_kubectl:
|
|
v.set("rabbitmq_connectionuri", rabbitmq_connectionuri_kubectl)
|
|
else:
|
|
# set a uri from provided user/pass
|
|
rabbitmq_user = v.get("rabbitmq_admin_user")
|
|
rabbitmq_password = v.get("rabbitmq_admin_password")
|
|
v.set(
|
|
"rabbitmq_connectionuri",
|
|
f"amqp://{rabbitmq_user}:{rabbitmq_password}@nemesis-rabbitmq-svc:5672/",
|
|
)
|
|
|
|
if not v.get("rabbitmq_erlang_cookie") or v.get("rabbitmq_erlang_cookie") == "<no value>":
|
|
rabbitmq_erlang_cookie_kubectl = get_kubectl_value("rabbitmq_erlang_cookie")
|
|
if rabbitmq_erlang_cookie_kubectl:
|
|
v.set("rabbitmq_erlang_cookie", rabbitmq_erlang_cookie_kubectl)
|
|
else:
|
|
# set a random password if not supplied or already set
|
|
v.set("rabbitmq_erlang_cookie", get_random_password(24))
|
|
|
|
if not v.get("disable_slack_alerting") or v.get("disable_slack_alerting") == "<no value>":
|
|
disable_slack_alerting = get_kubectl_value("disable_slack_alerting")
|
|
if disable_slack_alerting:
|
|
disable_slack_alerting = bool(disable_slack_alerting)
|
|
v.set("disable_slack_alerting", disable_slack_alerting)
|
|
else:
|
|
v.set("disable_slack_alerting", "False")
|
|
disable_slack_alerting = str(v.get("disable_slack_alerting"))
|
|
if disable_slack_alerting.lower() not in ["true", "false"]:
|
|
logger.error(f"The disable_slack_alerting argument must be either 'True' or 'False'. Supplied value: {disable_slack_alerting}")
|
|
sys.exit(1)
|
|
disable_slack_alerting = disable_slack_alerting.lower() == "true"
|
|
|
|
if not v.get("slack_channel") or v.get("slack_channel") == "<no value>":
|
|
slack_channel_kubectl = get_kubectl_value("slack_channel")
|
|
if slack_channel_kubectl:
|
|
v.set("slack_channel", slack_channel_kubectl)
|
|
else:
|
|
v.set("slack_channel", None)
|
|
|
|
slack_channel = v.get("slack_channel")
|
|
if not disable_slack_alerting:
|
|
if slack_channel:
|
|
if slack_channel[0] != "#":
|
|
logger.error(f"The slack_channel argument must start with a '#'. Supplied value: {slack_channel}")
|
|
sys.exit(1)
|
|
else:
|
|
logger.error(f"The slack_channel argument must be set if slack alerting is enabled.")
|
|
sys.exit(1)
|
|
|
|
if not v.get("slack_webhook") or v.get("slack_webhook") == "<no value>":
|
|
slack_webhook_kubectl = get_kubectl_value("slack_webhook")
|
|
if slack_webhook_kubectl:
|
|
v.set("slack_webhook", slack_webhook_kubectl)
|
|
else:
|
|
v.set("slack_webhook", None)
|
|
|
|
slack_webhook = v.get("slack_webhook")
|
|
if not disable_slack_alerting and slack_webhook and slack_webhook[0:8] != "https://":
|
|
logger.error(f"The slack_webhook argument must start with a 'https://'. Supplied value: {slack_webhook}")
|
|
sys.exit(1)
|
|
|
|
# make sure we have everything set
|
|
all_values_set = True
|
|
for config_key in config_keys:
|
|
if not v.get(config_key) and not get_kubectl_value(config_key):
|
|
if config_key == "slack_webhook" or config_key == "slack_channel":
|
|
continue
|
|
|
|
logger.error(f"\nRequired configuration key value '{config_key}' not supplied and not already present!\n")
|
|
all_values_set = True
|
|
|
|
if not all_values_set:
|
|
sys.exit(1)
|
|
|
|
|
|
def validate_aws_resources():
|
|
"""Checks for the presence of the proper AWS resources, creating them if they're not present."""
|
|
|
|
aws_access_key_id = v.get("aws_access_key_id")
|
|
if not aws_access_key_id:
|
|
logger.error("aws_access_key_id not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
aws_secret_key = v.get("aws_secret_key")
|
|
if not aws_secret_key:
|
|
logger.error("aws_secret_key not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
aws_region = v.get("aws_region")
|
|
if not aws_region:
|
|
logger.error("aws_region not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
aws_bucket = v.get("aws_bucket")
|
|
if not aws_bucket:
|
|
logger.error("aws_bucket not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
aws_kms_key_alias = v.get("aws_kms_key_alias")
|
|
if not aws_kms_key_alias:
|
|
logger.error("aws_kms_key_alias not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
assessment_id = v.get("assessment_id")
|
|
if not assessment_id:
|
|
logger.error("assessment_id not set, aws validation can't proceed\n")
|
|
sys.exit(1)
|
|
|
|
kms_client = boto3.client(
|
|
"kms",
|
|
aws_access_key_id=aws_access_key_id,
|
|
aws_secret_access_key=aws_secret_key,
|
|
region_name=aws_region,
|
|
)
|
|
|
|
s3_client = boto3.client(
|
|
"s3",
|
|
aws_access_key_id=aws_access_key_id,
|
|
aws_secret_access_key=aws_secret_key,
|
|
region_name=aws_region,
|
|
)
|
|
|
|
try:
|
|
s3_client.head_bucket(Bucket=aws_bucket)
|
|
logger.info(f"S3 bucket '{aws_bucket}' exists")
|
|
except:
|
|
if not v.get("force"):
|
|
output = input(f"\n[*] S3 bucket '{aws_bucket}' does not exist, do you want to create it? [Y/n] ")
|
|
|
|
if v.get("force") or output == "" or output.lower() == "y":
|
|
# create the bucket with a 'private' ACL
|
|
try:
|
|
response = s3_client.create_bucket(
|
|
ACL="private",
|
|
Bucket=aws_bucket,
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error creating S3 bucket '{aws_bucket}' (bucket name is likely already taken) : {e}\n")
|
|
sys.exit(1)
|
|
|
|
# ensure the bucket/objects can't be public
|
|
response = s3_client.put_public_access_block(
|
|
Bucket=aws_bucket,
|
|
PublicAccessBlockConfiguration={
|
|
"IgnorePublicAcls": True,
|
|
},
|
|
)
|
|
# BlockPublicAcls / BlockPublicPolicy / RestrictPublicBuckets ?
|
|
|
|
# ensure server side bucket encyption is enabled
|
|
response = s3_client.put_bucket_encryption(
|
|
Bucket=aws_bucket,
|
|
ServerSideEncryptionConfiguration={
|
|
"Rules": [
|
|
{
|
|
"ApplyServerSideEncryptionByDefault": {
|
|
"SSEAlgorithm": "AES256",
|
|
},
|
|
},
|
|
]
|
|
},
|
|
)
|
|
|
|
logger.info(f"S3 bucket key '{aws_bucket}' created!\n")
|
|
|
|
else:
|
|
logger.error(f"S3 bucket '{aws_bucket}' doesn't exist, file storage will not work!\n")
|
|
sys.exit(1)
|
|
|
|
# check if the KMS key already exists
|
|
aws_kms_key_exists = False
|
|
try:
|
|
output = kms_client.describe_key(KeyId=f"alias/{aws_kms_key_alias}")
|
|
aws_kms_key_state = output["KeyMetadata"]["KeyState"]
|
|
aws_kms_key_exists = True
|
|
logger.info(f"KMS key '{aws_kms_key_alias}' exists")
|
|
except:
|
|
pass
|
|
|
|
if not aws_kms_key_exists:
|
|
if not v.get("force"):
|
|
output = input(f"\n[*] KMS key '{aws_kms_key_alias}' does not exist, do you want to create it? [Y/n] ")
|
|
|
|
if v.get("force") or output == "" or output.lower() == "y":
|
|
response = kms_client.create_key(Description=f"key for {assessment_id}")
|
|
keyId = response["KeyMetadata"]["KeyId"]
|
|
kms_client.create_alias(AliasName=f"alias/{aws_kms_key_alias}", TargetKeyId=keyId)
|
|
logger.info(f"KMS key '{aws_kms_key_alias}' (ID {keyId}) created!\n")
|
|
else:
|
|
logger.error(f"KMS key '{aws_kms_key_alias}' doesn't exist, file encryption will not work!\n")
|
|
sys.exit(1)
|
|
elif aws_kms_key_state != "Enabled":
|
|
logger.error(f"Key state for KMS key '{aws_kms_key_alias}' is '{aws_kms_key_state}', encryption will not work!")
|
|
sys.exit(1)
|
|
|
|
|
|
def create_ingress_controller():
|
|
# Some jank to check if the nginx controller is installed in Kubernetes
|
|
exitcode, output = subprocess.getstatusoutput("kubectl get --raw /apis/admissionregistration.k8s.io/v1/validatingwebhookconfigurations | grep 'validate.nginx.ingress.kubernetes.io'")
|
|
if exitcode == 0:
|
|
logger.info("Ingress controller already installed, skipping")
|
|
return
|
|
|
|
# create local self-signed certs and store them as a secret for ingress auth
|
|
logger.info("Creating self-signed SSL certificates")
|
|
run_cmd(
|
|
'openssl req -x509 -newkey rsa:4096 -sha256 -nodes -keyout /tmp/tls.key -out /tmp/tls.crt -subj "/CN=nemesis.local" -days 365 -addext "subjectAltName = DNS:nemesis.local"',
|
|
show_error=True,
|
|
)
|
|
run_cmd("kubectl create secret tls nemesis-ingress-tls --cert=/tmp/tls.crt --key=/tmp/tls.key")
|
|
run_cmd("kubectl create secret tls nemesis-ingress-tls --cert=/tmp/tls.crt --key=/tmp/tls.key -n monitoring")
|
|
|
|
logger.info("Installing ingress-nginx controller using helm")
|
|
run_cmd(
|
|
"helm upgrade --install ingress-nginx ingress-nginx"
|
|
" --repo https://kubernetes.github.io/ingress-nginx"
|
|
" --namespace ingress-nginx"
|
|
" --create-namespace"
|
|
" --set prometheus.create=true"
|
|
" --set prometheus.port=9113"
|
|
' --set tcp.5044="default/nemesis-ls-beats:5044"',
|
|
show_error=True,
|
|
)
|
|
|
|
|
|
def create_minio():
|
|
# check if the chart is already installed
|
|
exitcode, output = subprocess.getstatusoutput("helm status minio")
|
|
if exitcode == 0:
|
|
logger.info("Minio chart already installed, skipping")
|
|
return
|
|
|
|
logger.info("Installing the Minio operator using helm")
|
|
run_cmd("helm repo add elastic https://helm.elastic.co")
|
|
|
|
# minio_storage_size = v.get("minio_storage_size")
|
|
minio_root_user = v.get("minio_root_user")
|
|
minio_root_password = v.get("minio_root_password")
|
|
minio_storage_size = v.get("minio_storage_size")
|
|
# nemesis_http_server = v.get("nemesis_http_server").rstrip("/")
|
|
|
|
run_cmd("helm repo add bitnami https://charts.bitnami.com/bitnami")
|
|
run_cmd("helm repo update")
|
|
run_cmd(
|
|
"helm install minio bitnami/minio "
|
|
"--set 'extraEnvVars[0].name=MINIO_BROWSER_LOGIN_ANIMATION' --set 'extraEnvVars[0].value=\"off\"' "
|
|
f"--set persistence.size={minio_storage_size} "
|
|
f"--set auth.rootUser={minio_root_user} "
|
|
f"--set auth.rootPassword='{minio_root_password}' "
|
|
)
|
|
# "--set 'extraEnvVars[0].name=MINIO_CONSOLE_SUBPATH' --set 'extraEnvVars[0].value=\"/minio/\"' "
|
|
# f"--set 'extraEnvVars[1].name=MINIO_BROWSER_REDIRECT_URL' --set 'extraEnvVars[1].value=\"{nemesis_http_server}/minio/\"'",)
|
|
|
|
|
|
def create_elastic_operator():
|
|
# Check if the elastic operator is already installed
|
|
exitcode, output = subprocess.getstatusoutput("kubectl get crds | grep 'k8s.elastic.co'")
|
|
if exitcode == 0:
|
|
logger.info("ECK operator already installed, skipping")
|
|
return
|
|
|
|
logger.info("Installing the ECK operator using helm")
|
|
run_cmd("helm repo add elastic https://helm.elastic.co")
|
|
run_cmd("helm repo update")
|
|
|
|
run_cmd("helm install elastic-operator elastic/eck-operator" " --namespace elastic-system" " --create-namespace" " --set managedNamespaces='{default}'")
|
|
|
|
|
|
def create_metrics_server():
|
|
exitcode, output = subprocess.getstatusoutput("kubectl get pods -A | grep 'metrics-server'")
|
|
if exitcode == 0:
|
|
logger.info("Metrics Server already installed, skipping")
|
|
return
|
|
|
|
# create local self-signed certs and store them as a secret for ingress auth
|
|
logger.info("Creating metrics server")
|
|
dir = os.path.realpath(os.path.dirname(__file__))
|
|
run_cmd(f"kubectl apply -f '{dir}/kubernetes/metrics-server/metrics-server.yaml'")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
print_logo()
|
|
|
|
# 1. parse any environment variables first
|
|
config_values = [
|
|
"aws_region",
|
|
"aws_bucket",
|
|
"aws_kms_key_alias",
|
|
"aws_access_key_id",
|
|
"aws_secret_key",
|
|
"minio_root_user",
|
|
"minio_root_password",
|
|
"minio_storage_size",
|
|
"storage_provider",
|
|
"assessment_id",
|
|
"nemesis_http_server",
|
|
"environment",
|
|
"data_expiration_days",
|
|
"log_level",
|
|
"disable_slack_alerting",
|
|
"slack_channel",
|
|
"slack_webhook",
|
|
"basic_auth_password",
|
|
"basic_auth_user",
|
|
"elasticsearch_password",
|
|
"elasticsearch_user",
|
|
"postgres_user",
|
|
"postgres_password",
|
|
"dashboard_user",
|
|
"dashboard_password",
|
|
"pgadmin_email",
|
|
"pgadmin_password",
|
|
"grafana_password",
|
|
"grafana_user",
|
|
"rabbitmq_admin_user",
|
|
"rabbitmq_admin_password",
|
|
"rabbitmq_erlang_cookie",
|
|
]
|
|
|
|
extra_settings = ["nemesis_config", "force"]
|
|
|
|
env_vars = config_values + extra_settings
|
|
|
|
for env_var in env_vars:
|
|
v.bind_env(env_var)
|
|
v.automatic_env()
|
|
|
|
parser = argparse.ArgumentParser(description="Nemesis settings")
|
|
|
|
# args specific to this program
|
|
parser.add_argument(
|
|
"--config",
|
|
"-c",
|
|
default="nemesis.config",
|
|
type=str,
|
|
help="Nemesis config (default: nemesis.config)",
|
|
)
|
|
parser.add_argument(
|
|
"--force",
|
|
"-f",
|
|
action=argparse.BooleanOptionalAction,
|
|
help="Don't prompt for any input",
|
|
)
|
|
|
|
# aws configs
|
|
parser.add_argument(
|
|
"--aws_region",
|
|
"--aws-region",
|
|
"--region",
|
|
type=str,
|
|
help="AWS region (default: us-east-1)",
|
|
)
|
|
parser.add_argument("--aws_bucket", "--aws-bucket", "--bucket", type=str, help="AWS S3 bucket name")
|
|
parser.add_argument(
|
|
"--aws_kms_key_alias",
|
|
"--aws-kms-key-alias",
|
|
"--kms",
|
|
type=str,
|
|
help="AWS KMS key alias",
|
|
)
|
|
parser.add_argument(
|
|
"--aws_access_key_id",
|
|
"--aws-access-key-id",
|
|
"--key_id",
|
|
"--key-id",
|
|
type=str,
|
|
help="AWS access key ID",
|
|
)
|
|
parser.add_argument(
|
|
"--aws_secret_key",
|
|
"--aws-secret-key",
|
|
"--secret_key",
|
|
"--secret-key",
|
|
type=str,
|
|
help="AWS secret key value",
|
|
)
|
|
|
|
# minio configs
|
|
parser.add_argument("--minio_root_user", "--minio-root-user", type=str, help="Minio root user")
|
|
parser.add_argument(
|
|
"--minio_root_password",
|
|
"--minio-root-password",
|
|
type=str,
|
|
help="Minio root password",
|
|
)
|
|
parser.add_argument(
|
|
"--minio_storage_size",
|
|
"--minio-storage-size",
|
|
type=str,
|
|
help="Size of Minio persistent storage",
|
|
)
|
|
parser.add_argument(
|
|
"--storage_provider",
|
|
"--storage-provider",
|
|
type=str,
|
|
choices=["s3", "mimio"],
|
|
help="Storage provider to use",
|
|
)
|
|
|
|
# operation configs
|
|
parser.add_argument("--assessment_id", "--assessment-id", type=str, help="Asessment ID")
|
|
parser.add_argument(
|
|
"--nemesis_http_server",
|
|
"--ip",
|
|
type=str,
|
|
help="Nemesis frontend HTTP server endpoint. Format: http://<SERVER>:<PORT>",
|
|
)
|
|
parser.add_argument(
|
|
"--environment",
|
|
"--env",
|
|
type=str,
|
|
help="Environment the application is running in (default: development)",
|
|
default="production",
|
|
choices=["development", "production", "testing"],
|
|
)
|
|
parser.add_argument(
|
|
"--data_expiration_days",
|
|
"--exp",
|
|
type=int,
|
|
help="Days after ingestion to set data to expire (default: 100)",
|
|
)
|
|
parser.add_argument("--log_level", "--log", type=str, help="Level of logging (default: info)")
|
|
parser.add_argument(
|
|
"--disable_slack_alerting",
|
|
type=str,
|
|
help="Should slack alerting be disabled? Values: True/False",
|
|
required=False,
|
|
)
|
|
parser.add_argument(
|
|
"--slack_channel",
|
|
"--channel",
|
|
type=str,
|
|
help="Slack channel name for alerting.",
|
|
required=False,
|
|
)
|
|
parser.add_argument(
|
|
"--slack_webhook",
|
|
"--webhook",
|
|
type=str,
|
|
help="Slack https://... webhook for alerting.",
|
|
required=False,
|
|
)
|
|
parser.add_argument(
|
|
"--basic_auth_user",
|
|
"--user",
|
|
type=str,
|
|
help="User to use for basic auth to the web-api",
|
|
)
|
|
parser.add_argument(
|
|
"--basic_auth_password",
|
|
"--password",
|
|
type=str,
|
|
help="Password to use for basic auth to the web-api",
|
|
)
|
|
parser.add_argument(
|
|
"--elasticsearch_user",
|
|
"--es_user",
|
|
type=str,
|
|
help="Username for Elasticsearch/Kibana",
|
|
)
|
|
parser.add_argument(
|
|
"--elasticsearch_password",
|
|
"--es_password",
|
|
type=str,
|
|
help="Password for Elasticsearch/Kibana",
|
|
)
|
|
parser.add_argument("--postgres_user", "--pg_user", type=str, help="Username for Postgres")
|
|
parser.add_argument("--postgres_password", "--pg_password", type=str, help="Password for Postgres")
|
|
parser.add_argument("--dashboard_user", type=str, help="Username for the Nemesis dashboard")
|
|
parser.add_argument("--dashboard_password", type=str, help="Password for the Nemesis dashboard")
|
|
parser.add_argument("--pgadmin_email", "--pgemail", type=str, help="Email (username) for pgAdmin")
|
|
parser.add_argument("--pgadmin_password", "--pgpassword", type=str, help="Password for pgAdmin")
|
|
parser.add_argument("--grafana_user", "--guser", type=str, help="Username for Grafana")
|
|
parser.add_argument("--grafana_password", "--gpassword", type=str, help="Password for Grafana")
|
|
parser.add_argument("--rabbitmq_admin_user", "--ruser", type=str, help="Admin username for RabbitMQ")
|
|
parser.add_argument(
|
|
"--rabbitmq_admin_password",
|
|
"--rpassword",
|
|
type=str,
|
|
help="Admin password for RabbitMQ",
|
|
)
|
|
parser.add_argument(
|
|
"--rabbitmq_erlang_cookie",
|
|
"--rcookie",
|
|
type=str,
|
|
help="Erlang cookie for RabbitMQ clusters",
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
# 2. next parse values from a local yaml config (if present and non-empty)
|
|
# the config location can be set with --config=X, -c=X, or the NEMESIS_CONFIG env variable
|
|
nemesis_config = v.get("nemesis_config") if v.get("nemesis_config") else args.config
|
|
if os.path.exists(nemesis_config) and os.path.getsize(nemesis_config) > 0:
|
|
v.set_config_type("yaml")
|
|
with open(nemesis_config) as f:
|
|
try:
|
|
v.read_config(f.read())
|
|
logger.info(f"Parsed config file: {nemesis_config}")
|
|
except Exception as e:
|
|
logger.error(f"Excepting parsing config '{nemesis_config}' : {e}\n")
|
|
sys.exit(1)
|
|
|
|
# 3. parse cli arguments override and have them take precedence
|
|
v.bind_args(parser)
|
|
|
|
# make sure kubernetes is running
|
|
validate_kubernetes()
|
|
validate_config_values(config_values)
|
|
set_config_values(config_values)
|
|
if v.get("storage_provider") == "s3":
|
|
validate_aws_resources()
|
|
# ensure minio is removed
|
|
run_cmd("helm uninstall minio")
|
|
elif v.get("storage_provider") == "minio":
|
|
create_minio()
|
|
create_ingress_controller()
|
|
create_elastic_operator()
|
|
create_metrics_server()
|
|
|
|
logger.info("Configuration set")
|
|
|
|
logger.info(f"Nemesis basic auth credentials: `{get_kubectl_value('basic_auth_user')}:{get_kubectl_value('basic_auth_password')}`")
|
|
|
|
logger.info("If settings were changed, you may need to restart minikube with: `minikube stop && minikube start`")
|
|
logger.info("You can start the backend infrastructure in development mode with `./scripts/infra_start.sh`")
|
|
logger.info("You can start the main processing services in development mode with `./scripts/services_start.sh`")
|
|
logger.info("For non-development execution, run `skaffold run -m nemesis --port-forward=user`\n")
|