This commit is contained in:
Karsten Gorskowski 2023-11-29 23:19:18 +01:00
parent c5ee561bbd
commit 40128c57b5
6 changed files with 193 additions and 37 deletions

View File

@ -7,8 +7,8 @@ setup(
packages=find_packages(), packages=find_packages(),
install_requires=[], install_requires=[],
entry_points={ entry_points={
'console_scripts': [ "console_scripts": [
'tenant=tenant.main:main', "tenant=tenant.main:main",
], ],
}, },
) )

View File

@ -1,40 +1,32 @@
# tenant/commands/init.py # tenant/commands/init.py
import os 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): def add_subparser(subparsers):
init_parser = subparsers.add_parser("init", help="Initialize a new tenant") 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( init_parser.add_argument(
"--target", default=".", help="Target directory (default: current directory)" "--target", default=".", help="Target directory (default: current directory)"
) )
def execute(args): def execute(args):
if args.tenant_name is not None: tenant_name = os.environ.get("TENANT_NAME")
tenant_name = args.tenant_name if not tenant_name:
# If tenant_name is not provided and TENANT_NAME is not set, prompt the user tenant_name = input("Please enter the desired tenant name: ")
if args.tenant_name is None and os.getenv("TENANT_NAME") is None: else:
user_confirmation = input(
f"Current tenant name is {tenant_name}. Is this correct? (y/n): "
)
if user_confirmation != "y":
tenant_name = input("Please enter the tenant name: ") tenant_name = input("Please enter the tenant name: ")
# Ask the user to confirm the tenant name if the environment variable is set ingress = input(
if os.getenv("TENANT_NAME") == args.tenant_name: "Please enter the FQDN of the Kibana ingress, without the 'kibana' prefix: "
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: ")
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")
target_directory = args.target target_directory = args.target
@ -42,25 +34,42 @@ def execute(args):
# Check if the tenant directory already exists # Check if the tenant directory already exists
if os.path.exists(tenant_directory): 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 return
# Prompt the user for the GitSync password securely # Prompt the user for the GitSync password securely
git_sync_password = get_secure_password( 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") # define and create necessary folder structure
kubernetes_directory = os.path.join(tenant_directory, "kubernetes") terraform_directory = os.path.join(tenant_directory, "00-terraform")
certificates_directory = os.path.join(tenant_directory, "certificates") 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(terraform_directory)
os.makedirs(kubernetes_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") 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): for filename in os.listdir(source_tf_dir):
if filename.endswith(".tf"): if filename.endswith(".tf"):
@ -74,6 +83,22 @@ def execute(args):
print( print(
f"Warning: Source file '{filename}' not found in '{source_tf_dir}'." 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}'.")

View File

@ -1,7 +1,62 @@
# tenant/utils/common.py # tenant/utils/common.py
import getpass 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: "): def get_secure_password(prompt="Enter password: "):
# Use getpass to securely input a password without displaying it # Use getpass to securely input a password without displaying it
return getpass.getpass(prompt=prompt) 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"

View File

@ -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,
)

View File

@ -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')

View File

@ -5,6 +5,7 @@ from unittest.mock import patch
from argparse import Namespace # Import Namespace for creating args object from argparse import Namespace # Import Namespace for creating args object
from tenant.commands import init from tenant.commands import init
class TestInitCommand(unittest.TestCase): class TestInitCommand(unittest.TestCase):
def setUp(self): def setUp(self):
# Set up any necessary configurations or resources # Set up any necessary configurations or resources
@ -25,7 +26,9 @@ class TestInitCommand(unittest.TestCase):
os.rmdir(tenant_directory) os.rmdir(tenant_directory)
# Create an args object # 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 # Call the init command with the args object
init.execute(args) init.execute(args)
@ -42,5 +45,6 @@ class TestInitCommand(unittest.TestCase):
os.rmdir(os.path.join(tenant_directory, "certificates")) os.rmdir(os.path.join(tenant_directory, "certificates"))
os.rmdir(tenant_directory) os.rmdir(tenant_directory)
if __name__ == "__main__": if __name__ == "__main__":
unittest.main() unittest.main()