diff --git a/setup.py b/setup.py index de4ba2d..17575af 100644 --- a/setup.py +++ b/setup.py @@ -7,8 +7,8 @@ setup( packages=find_packages(), install_requires=[], entry_points={ - 'console_scripts': [ - 'tenant=tenant.main:main', + "console_scripts": [ + "tenant=tenant.main:main", ], }, ) diff --git a/tenant/commands/init.py b/tenant/commands/init.py index 18c9851..d36c977 100644 --- a/tenant/commands/init.py +++ b/tenant/commands/init.py @@ -1,40 +1,32 @@ # tenant/commands/init.py import os -from tenant.utils.common import get_secure_password +from tenant.utils.common import get_secure_password, generate_key, generate_csr +from tenant.utils.terraform import create_tfvars_file +import tenant.utils.generatecsr +import tenant.utils.generate_secrets_file def add_subparser(subparsers): init_parser = subparsers.add_parser("init", help="Initialize a new tenant") - default_tenant_name = os.getenv("TENANT_NAME", None) - init_parser.add_argument( - "tenant_name", help="Name of the tenant", default=default_tenant_name, nargs="?" - ) init_parser.add_argument( "--target", default=".", help="Target directory (default: current directory)" ) def execute(args): - if args.tenant_name is not None: - tenant_name = args.tenant_name - # If tenant_name is not provided and TENANT_NAME is not set, prompt the user - if args.tenant_name is None and os.getenv("TENANT_NAME") is None: - tenant_name = input("Please enter the tenant name: ") - - # Ask the user to confirm the tenant name if the environment variable is set - if os.getenv("TENANT_NAME") == args.tenant_name: + tenant_name = os.environ.get("TENANT_NAME") + if not tenant_name: + tenant_name = input("Please enter the desired tenant name: ") + else: user_confirmation = input( - f"Use '{os.getenv('TENANT_NAME')}' as the tenant name? (yes/no): " - ).lower() - if user_confirmation not in ("yes", "y"): - tenant_name = input("Please enter the desired tenant name: ") + f"Current tenant name is {tenant_name}. Is this correct? (y/n): " + ) + if user_confirmation != "y": + tenant_name = input("Please enter the tenant name: ") - if os.getenv("TENANT_NAME") and args.tenant_name != os.getenv("TENANT_NAME"): - user_confirmation = input( - f"Both env var TENANT_NAME and tenant_name argument are set. Use '{os.getenv('TENANT_NAME')}' as the tenant name? (If no, '{tenant_name}' will be used) (yes/no): " - ).lower() - if user_confirmation in ("yes", "y"): - tenant_name = os.getenv("TENANT_NAME") + ingress = input( + "Please enter the FQDN of the Kibana ingress, without the 'kibana' prefix: " + ) target_directory = args.target @@ -42,25 +34,42 @@ def execute(args): # Check if the tenant directory already exists if os.path.exists(tenant_directory): - print(f"Error: Tenant directory '{tenant_directory}' already exists.") + print( + f"Error: Tenant directory '{tenant_directory}' already exists. Init aborted." + ) return # Prompt the user for the GitSync password securely git_sync_password = get_secure_password( - prompt="Please insert known password for GitSync: " + prompt="Please insert predefined password for GitSync: " ) - terraform_directory = os.path.join(tenant_directory, "terraform") - kubernetes_directory = os.path.join(tenant_directory, "kubernetes") - certificates_directory = os.path.join(tenant_directory, "certificates") + # define and create necessary folder structure + terraform_directory = os.path.join(tenant_directory, "00-terraform") + certificates_directory = os.path.join(tenant_directory, "01-certificates") + kubernetes_directory = os.path.join(tenant_directory, "02-kubernetes") + helm_directory = os.path.join(tenant_directory, "03-helm") + os.makedirs(certificates_directory) os.makedirs(terraform_directory) os.makedirs(kubernetes_directory) - os.makedirs(certificates_directory) + os.makedirs(helm_directory) - # Create symbolic links for *.tf files + # generate key and csr if not exist + keyfile = os.path.join(certificates_directory, ingress + ".key") + csrfile = os.path.join(certificates_directory, ingress + ".csr") + + if os.path.exists(keyfile): + print("Keyfile file already exists") + print(keyfile) + exit(1) + else: + generate_key(keyfile) + generate_csr(csrfile, ingress) + + # Create symbolic links for *.tf files in tenant directory source_tf_dir = os.path.join(target_directory, "terraform") - target_tf_dir = os.path.join(tenant_directory, "terraform") + target_tf_dir = terraform_directory for filename in os.listdir(source_tf_dir): if filename.endswith(".tf"): @@ -74,6 +83,22 @@ def execute(args): print( f"Warning: Source file '{filename}' not found in '{source_tf_dir}'." ) - print( - f"Tenant '{tenant_name}' initialized in '{tenant_directory}' with GitSync password provided." - ) + + variables = { + "tenant_name": tenant_name, + } + + tfvars_filepath = os.path.join(terraform_directory, tenant_name + ".tfvars") + create_tfvars_file(variables, tfvars_filepath) + + # generate secrets file if not already exist, not yet encrypted on the fly + secrets_file = os.path.join(helm_directory, tenant_name + ".secrets.yaml") + if os.path.exists(secrets_file): + print("Secrets file already exists") + print(secrets_file) + else: + tenant.utils.generate_secrets_file.generate_secrets_file( + secrets_file, git_sync_password + ) + + print(f"Tenant '{tenant_name}' initialized in '{tenant_directory}'.") diff --git a/tenant/utils/common.py b/tenant/utils/common.py index 418d3a0..220785a 100644 --- a/tenant/utils/common.py +++ b/tenant/utils/common.py @@ -1,7 +1,62 @@ # tenant/utils/common.py import getpass +from OpenSSL import crypto + +TYPE_RSA = crypto.TYPE_RSA +TYPE_DSA = crypto.TYPE_DSA +key = crypto.PKey() def get_secure_password(prompt="Enter password: "): # Use getpass to securely input a password without displaying it return getpass.getpass(prompt=prompt) + + +def generate_key(keyfile): + print("Generating Key: ") + key.generate_key(TYPE_RSA, 4096) + f = open(keyfile, "wb") + f.write(crypto.dump_privatekey(crypto.FILETYPE_PEM, key)) + + +def generate_csr(csrfile, ingress): + req = crypto.X509Req() + req.get_subject().CN = ingress + req.get_subject().C = "DE" + req.get_subject().ST = "Nordrhein-Westfalen" + req.get_subject().L = "Bonn" + req.get_subject().O = "Informationstechnikzentrum Bund (ITZBund)" + req.get_subject().OU = "Laas/DaaS" + req.get_subject().emailAddress = "logging@itzbund.de" + req.set_pubkey(key) + req.sign(key, "sha256") + f = open(csrfile, "wb") + f.write(crypto.dump_certificate_request(crypto.FILETYPE_PEM, req)) + f.close() + print("CSR file generated") + + +def calculate_java_settings(memory_limit): + # Use regex to extract the value and unit + match = re.match(r"(\d*\.?\d+)([GgMm])i?", memory_limit) + + if match: + value, unit = match.groups() + + # Convert the value to an integer + value = float(value) + + if unit.lower() == "g": + # Convert GiB to MiB + value *= 1024 + + # Calculate Xmx value + xmx_value = value * 0.75 + + # Build the Java Xmx settings string without decimal part if it's ".0" + value = f"{int(xmx_value) if xmx_value.is_integer() else xmx_value}m" + java_settings = "-Xmx" + value + " " + "-Xms" + value + return java_settings + else: + # If the unit is neither "Gi" nor "m", return an error message or handle accordingly + return "Unsupported memory unit" diff --git a/tenant/utils/generate_secrets_file.py b/tenant/utils/generate_secrets_file.py new file mode 100644 index 0000000..348134b --- /dev/null +++ b/tenant/utils/generate_secrets_file.py @@ -0,0 +1,67 @@ +import random +import os +import ruamel.yaml +import string + +yaml = ruamel.yaml.YAML() + + +def generate_random_string(length): + characters = string.ascii_letters + string.digits + return "".join(random.choice(characters) for i in range(length)) + + +def generate_secrets_file(secrets_file, git_sync_password): + with open(secrets_file, "w", encoding="utf-8") as file: + yaml.dump( + { + "elasticsearch": { + "config": { + "rbac": { + "builtinUsers": { + "apm_system": generate_random_string(8), + "beats_system": generate_random_string(8), + "elastic": generate_random_string(8), + "kibana_system": generate_random_string(8), + "logstash_system": generate_random_string(8), + "remote_monitoring_user": generate_random_string(8), + }, + "customUsers": { + "logstash_internal": { + "password": generate_random_string(8) + }, + "logstash_writer": { + "password": generate_random_string(8) + }, + "prometheus": {"password": "monitor"}, + }, + } + } + }, + "kibana": { + "config": { + "encryption": { + "common": generate_random_string(32), + "reporting": generate_random_string(32), + "savedObjects": generate_random_string(32), + } + } + }, + "logstash": { + "gitSync": {"password": git_sync_password}, + "password": generate_random_string(32), + }, + "oauthProxy": { + "clientSecret": generate_random_string(20), + "cookie_secret": generate_random_string(32), + }, + "tls": { + "externalCertificates": { + "kibana": {"tls_key": "ImportMeFromSopsFile"} + }, + "keystorePassword": generate_random_string(8), + "truststorePassword": generate_random_string(8), + }, + }, + file, + ) diff --git a/tenant/utils/terraform.py b/tenant/utils/terraform.py new file mode 100644 index 0000000..83bf3e9 --- /dev/null +++ b/tenant/utils/terraform.py @@ -0,0 +1,5 @@ +def create_tfvars_file(variables, filepath): + with open(filepath, "w") as tfvars_file: + for key, value in variables.items(): + # Writing variable assignments to the file + tfvars_file.write(f'{key} = "{value}"\n') diff --git a/tests/test_init.py b/tests/test_init.py index ede21c6..a7dae9e 100644 --- a/tests/test_init.py +++ b/tests/test_init.py @@ -5,6 +5,7 @@ from unittest.mock import patch from argparse import Namespace # Import Namespace for creating args object from tenant.commands import init + class TestInitCommand(unittest.TestCase): def setUp(self): # Set up any necessary configurations or resources @@ -25,7 +26,9 @@ class TestInitCommand(unittest.TestCase): os.rmdir(tenant_directory) # Create an args object - args = Namespace(command="init", tenant_name=tenant_name, target=target_directory) + args = Namespace( + command="init", tenant_name=tenant_name, target=target_directory + ) # Call the init command with the args object init.execute(args) @@ -42,5 +45,6 @@ class TestInitCommand(unittest.TestCase): os.rmdir(os.path.join(tenant_directory, "certificates")) os.rmdir(tenant_directory) + if __name__ == "__main__": unittest.main()