1643 lines
96 KiB
Python
Executable File
1643 lines
96 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
# This script will use the libcloud api
|
|
# to manage vps instances from
|
|
# 1) Google Cloud Platform (GCE)
|
|
# 2) Microsoft Azure (AZURE_ARM)
|
|
# 3) Amazon Web Services (EC2)
|
|
# A user will be able to
|
|
# 1) create an instance
|
|
# 2) delete an instance or all of them across all or one cloud provider
|
|
# 3) list all instances across all cloud providers or a specific one
|
|
# 4) start a stopped instance or all of them across all or one cloud provider
|
|
# 5) stop a running instance or all of them across all or one cloud provider
|
|
# 6) reboot a running instance or all of them across all or one cloud provider
|
|
# 7) list images available for a provider
|
|
# 8) list sizes available for a provider
|
|
# 9) list locations available for a provider
|
|
# 10) run a script during the creation of a new instance to deploy docker services
|
|
# 11) ssh to an instance with a choice across all or one cloud provider
|
|
# all from the command line using flags
|
|
|
|
import os
|
|
import sys
|
|
import argparse
|
|
import shtab
|
|
import time
|
|
import paramiko
|
|
import socket
|
|
from rich import pretty
|
|
from rich.console import Console
|
|
from rich.prompt import Prompt
|
|
from rich.prompt import Confirm
|
|
from rich.progress import track
|
|
from rich.status import Status
|
|
from dotenv import load_dotenv
|
|
from libcloud.compute.types import Provider
|
|
from libcloud.compute.providers import get_driver
|
|
from libcloud.compute.base import NodeAuthSSHKey
|
|
from libcloud.compute.deployment import ScriptDeployment, MultiStepDeployment, ScriptFileDeployment
|
|
# FileDeployment not working for some reason
|
|
# from libcloud.compute.deployment import ScriptDeployment, MultiStepDeployment, ScriptFileDeployment, FileDeployment
|
|
from azure.identity import ClientSecretCredential
|
|
from azure.mgmt.resource import ResourceManagementClient
|
|
from azure.mgmt.network import NetworkManagementClient
|
|
from azure.mgmt.network.v2022_07_01.models import SecurityRule
|
|
console = Console() # Better and more flexible print
|
|
prompt = Prompt() # Better and more flexible input
|
|
pretty.install()
|
|
|
|
# Declare the ENV_FILE variable as such to always reside in the same directory as the script
|
|
# We use os.path.join to make sure the path is correct for every OS
|
|
# Also do the same for the ssh keys and the script to be used during deployment
|
|
# Finally we declare the docker-compose.yml file in case the user wants to use that in addition to the list of docker images
|
|
ENV_FILE = os.path.join(os.path.dirname(__file__), ".env")
|
|
SECDEP_SSH_PUBLIC_KEY = os.path.join(os.path.dirname(__file__), "secdep.pub")
|
|
SECDEP_SSH_PRIVATE_KEY = os.path.join(os.path.dirname(__file__), "secdep")
|
|
SECDEP_DEPLOY_SCRIPT = os.path.join(os.path.dirname(__file__), "harden")
|
|
SECDEP_DOCKER_COMPOSE = os.path.join(os.path.dirname(__file__), "docker-compose.yml")
|
|
|
|
# Available choices when the action flag is used
|
|
action_choices = ["delete","start","stop","reboot","deleteall","startall","stopall","rebootall"]
|
|
|
|
# If no arguements were given let the user know this is not how to use this program
|
|
if not len(sys.argv) > 1:
|
|
console.print("No arguments passed. Use [u]-h[/u] or [u]--help[/u] for help", style="bold red")
|
|
exit(0)
|
|
# Define the command line arguments
|
|
parser = argparse.ArgumentParser(
|
|
prog='secdep.py',
|
|
description='Manage cloud instances',
|
|
)
|
|
shtab.add_argument_to(parser, ["-comp", "--print-completion"])
|
|
parser.add_argument('-l', '--list', help='List all instances or with -P PROVIDER list a provider\'s instances', action='store_true')
|
|
parser.add_argument('-e', '--edit', help='Change credential values', action='store_true')
|
|
parser.add_argument('-v', '--version', help='Show secdep\'s version', action='store_true')
|
|
parser.add_argument('-P', '--provider', help='Cloud provider', choices=['gce', 'azure', 'aws'])
|
|
parser.add_argument('-a', '--action', help='Action to perform on a single provider with -P PROVIDER or all instances. Valid options are delete[all] start[all] stop[all] reboot[all]', choices=action_choices, metavar='ACTION')
|
|
parser.add_argument('-c', '--create', help='Create an instance', action='store_true')
|
|
# --docker_compose is not named --docker-compose because of the way argparse works
|
|
parser.add_argument('-dc', '--docker_compose', help='Run the docker-compose.yml file', action='store_true')
|
|
# action='append' is used to allow the user to check if --deploy was used even without any arguments
|
|
parser.add_argument('-dep', '--deploy', help='Docker images to deploy', type=str, nargs='*', default=None, required=False, action='append')
|
|
parser.add_argument('-I', '--listimages', help='List images', action='store_true')
|
|
parser.add_argument('-S', '--listsizes', help='List sizes', action='store_true')
|
|
parser.add_argument('-G', '--listlocations', help='List locations', action='store_true')
|
|
parser.add_argument('-i', '--image', help='Image to use')
|
|
parser.add_argument('-s', '--size', help='Size of instance')
|
|
parser.add_argument('-n', '--name', help='Name of instance')
|
|
parser.add_argument('-g', '--region', help='Region to use')
|
|
parser.add_argument('-y', '--yes', help='Do not ask for confirmation during creation', action='store_true')
|
|
parser.add_argument('-p', '--print', help='Also print node, image, location or size', action='store_true')
|
|
parser.add_argument('-port', '--port', help='Port to connect to when using ssh')
|
|
parser.add_argument('-awsregion', '--awsregion', help='Specify aws region to not have to go through all of them')
|
|
parser.add_argument('-ssh', '--ssh', help='Connect to an instance using ssh with the option to use -P PROVIDER to choose node from a specific provider', action='store_true')
|
|
parser.add_argument('-init', '--init', help='Initialize a specific provider\'s values' , choices=['gce', 'azure', 'aws'])
|
|
args = parser.parse_args()
|
|
|
|
def show_version():
|
|
console.print('''
|
|
____ ____
|
|
/ ___| ___ ___| _ \ ___ _ __
|
|
\___ \ / _ \/ __| | | |/ _ \ '_ \
|
|
___) | __/ (__| |_| | __/ |_) |
|
|
|____/ \___|\___|____/ \___| .__/
|
|
|_|
|
|
''',style="bold cyan")
|
|
console.print("[bold cyan]SecDep[/bold cyan] - Automated secure docker services deployment\n[bold cyan]Version[/bold cyan]: v1.0.0\n[bold cyan]Repo[/bold cyan]: [u]https://git.konsthol.eu/konsthol/SecDep[/u]", style="bold blue")
|
|
|
|
if args.version:
|
|
show_version()
|
|
exit(0)
|
|
|
|
# If one or both keys don't exist we create them
|
|
if not os.path.exists(SECDEP_SSH_PUBLIC_KEY) or not os.path.exists(SECDEP_SSH_PRIVATE_KEY):
|
|
# Generate a new SSH key pair
|
|
# The key is stored in the current directory and named secdep
|
|
# The public key is stored in the current directory and named secdep.pub
|
|
# The passphrase is an empty string
|
|
# The key is a 4096 bit RSA key
|
|
# The key's comment is secdep@hostname
|
|
key = paramiko.RSAKey.generate(4096)
|
|
key.write_private_key_file(SECDEP_SSH_PRIVATE_KEY)
|
|
with open(SECDEP_SSH_PUBLIC_KEY, 'w') as f:
|
|
f.write("%s %s secdep@%s" % (key.get_name(), key.get_base64(), socket.gethostname()))
|
|
|
|
# We check if the env file it exists already in order to avoid overwriting it
|
|
# and if it doesn't exist, we create it by inserting an empty string
|
|
|
|
# When using the with statement, the file is automatically
|
|
# closed at the end of the indented block
|
|
if not os.path.exists(ENV_FILE):
|
|
with open(ENV_FILE, 'w') as f:
|
|
f.write('')
|
|
|
|
# The required values for authentication are stored in the .env file in the form of KEY=VALUE
|
|
# These are
|
|
# 1) SECDEP_GCE_CLIENT_ID (the service account Email found in project's IAM & Admin section/Service Accounts)
|
|
# 2) SECDEP_GCE_CLIENT_SECRET (the service account's private Key ID found in project's IAM & Admin section/Service Accounts)
|
|
# 3) SECDEP_GCE_PROJECT_ID (the project ID found in project's dashboard)
|
|
# 4) SECDEP_AZURE_TENANT_ID (the tenant id found when viewing the azure subscription)
|
|
# 5) SECDEP_AZURE_SUB_ID (documented in the README)
|
|
# 6) SECDEP_AZURE_APP_ID (documented in the README)
|
|
# 7) SECDEP_AZURE_PASSWORD (documented in the README)
|
|
# 8) SECDEP_AWS_ACCESS_KEY (that we created in the aws IAM section)
|
|
# 9) SECDEP_AWS_SECRET_KEY (that we viewed only once when we created the key)
|
|
|
|
# For GCE we need to create a service account (with Owner Role from the IAM section) and download the json file (from
|
|
# the Service Account's manage keys section) in the same directory as the script
|
|
|
|
if os.stat(ENV_FILE).st_size != 0 and args.init:
|
|
console.print("[bold red]The init flag was only meant to be optionally run once and only in the first run if you knew you were going to be using only one provider.[/bold red] [bold white]If you need to change or populate a provider\'s needed values use the [u]--edit[/u] or [u]-e[/u] flag instead[/bold white]")
|
|
exit(0)
|
|
|
|
# We then check if the .env file is empty to determine if it's the first run of the script
|
|
if os.stat(ENV_FILE).st_size == 0:
|
|
if args.init:
|
|
match args.init:
|
|
case "gce":
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AZURE_TENANT_ID=\n')
|
|
f.write('SECDEP_AZURE_SUB_ID=\n')
|
|
f.write('SECDEP_AZURE_APP_ID=\n')
|
|
f.write('SECDEP_AZURE_PASSWORD=\n')
|
|
f.write('SECDEP_AWS_ACCESS_KEY=\n')
|
|
f.write('SECDEP_AWS_SECRET_KEY=\n')
|
|
case "azure":
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_GCE_CLIENT_ID=\n')
|
|
f.write('SECDEP_GCE_CLIENT_SECRET=\n')
|
|
f.write('SECDEP_GCE_PROJECT_ID=\n')
|
|
f.write('SECDEP_AWS_ACCESS_KEY=\n')
|
|
f.write('SECDEP_AWS_SECRET_KEY=\n')
|
|
case "aws":
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_GCE_CLIENT_ID=\n')
|
|
f.write('SECDEP_GCE_CLIENT_SECRET=\n')
|
|
f.write('SECDEP_GCE_PROJECT_ID=\n')
|
|
f.write('SECDEP_AZURE_TENANT_ID=\n')
|
|
f.write('SECDEP_AZURE_SUB_ID=\n')
|
|
f.write('SECDEP_AZURE_APP_ID=\n')
|
|
f.write('SECDEP_AZURE_PASSWORD=\n')
|
|
case _:
|
|
console.print("[u]Invalid[/u] provider", style="bold red")
|
|
else:
|
|
console.print('[bold white]You will be asked for each needed value\nIf you want to skip a provider press enter on each of their values because they are all needed for authentication\nIf at some point you delete the provider\'s value entry you will once again be asked to enter it\nIf you pressed enter by mistake or inserted an incorrect value just edit the file directly or delete the corresponding line\nThere is also the choice of using the [u]-e[/u] option to have that done interactively[/bold white]')
|
|
|
|
# We search for these values in the ENV_FILE and for each not found, we prompt the user to enter it
|
|
# We then write the values to the ENV_FILE
|
|
# An empty string is allowed in case the user does not want to use a particular cloud provider
|
|
# but he will be prompted for all values
|
|
with open(ENV_FILE, 'r') as f:
|
|
env_file_content = f.read()
|
|
if 'SECDEP_GCE_CLIENT_ID' not in env_file_content:
|
|
SECDEP_GCE_CLIENT_ID = prompt.ask("[bold white]Enter your [u]GCE_CLIENT_ID[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_GCE_CLIENT_ID={}\n'.format(SECDEP_GCE_CLIENT_ID))
|
|
if 'SECDEP_GCE_CLIENT_SECRET' not in env_file_content:
|
|
SECDEP_GCE_CLIENT_SECRET = prompt.ask("[bold white]Enter your [u]GCE_CLIENT_SECRET[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_GCE_CLIENT_SECRET={}\n'.format(SECDEP_GCE_CLIENT_SECRET))
|
|
if 'SECDEP_GCE_PROJECT_ID' not in env_file_content:
|
|
SECDEP_GCE_PROJECT_ID = prompt.ask("[bold white]Enter your [u]GCE_PROJECT_ID[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_GCE_PROJECT_ID={}\n'.format(SECDEP_GCE_PROJECT_ID))
|
|
if 'SECDEP_AZURE_TENANT_ID' not in env_file_content:
|
|
SECDEP_AZURE_TENANT_ID = prompt.ask("[bold white]Enter your [u]AZURE_TENANT_ID[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AZURE_TENANT_ID={}\n'.format(SECDEP_AZURE_TENANT_ID))
|
|
if 'SECDEP_AZURE_SUB_ID' not in env_file_content:
|
|
SECDEP_AZURE_SUB_ID = prompt.ask("[bold white]Enter your [u]AZURE_SUB_ID[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AZURE_SUB_ID={}\n'.format(SECDEP_AZURE_SUB_ID))
|
|
if 'SECDEP_AZURE_APP_ID' not in env_file_content:
|
|
SECDEP_AZURE_APP_ID = prompt.ask("[bold white]Enter your [u]AZURE_APP_ID[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AZURE_APP_ID={}\n'.format(SECDEP_AZURE_APP_ID))
|
|
if 'SECDEP_AZURE_PASSWORD' not in env_file_content:
|
|
SECDEP_AZURE_PASSWORD = prompt.ask("[bold white]Enter your [u]AZURE_PASSWORD[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AZURE_PASSWORD={}\n'.format(SECDEP_AZURE_PASSWORD))
|
|
if 'SECDEP_AWS_ACCESS_KEY' not in env_file_content:
|
|
SECDEP_AWS_ACCESS_KEY = prompt.ask("[bold white]Enter your [u]AWS_ACCESS_KEY[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AWS_ACCESS_KEY={}\n'.format(SECDEP_AWS_ACCESS_KEY))
|
|
if 'SECDEP_AWS_SECRET_KEY' not in env_file_content:
|
|
SECDEP_AWS_SECRET_KEY = prompt.ask("[bold white]Enter your [u]AWS_SECRET_KEY[/u] [/bold white]")
|
|
with open(ENV_FILE, 'a') as f:
|
|
f.write('SECDEP_AWS_SECRET_KEY={}\n'.format(SECDEP_AWS_SECRET_KEY))
|
|
|
|
# Load environment variables from ENV_FILE and temporarily store them
|
|
# along the other system environment variables
|
|
# If an environment variable is already set, it will not be overwritten
|
|
# but since the existence of the .env file is mandatory it is best if
|
|
# they are not already set.
|
|
# That is because some of them may be named something slightly different
|
|
# by the user and this program works with these exact value names
|
|
# That is also the reason they are prefixed with the SECDEP_ string
|
|
load_dotenv(ENV_FILE)
|
|
|
|
# We also store them in variables for ease of use inside the program
|
|
# We take for granted that these values exist either empty or not
|
|
|
|
def get_env_vars():
|
|
# Make them global so we can use them in other functions
|
|
global SECDEP_GCE_CLIENT_ID
|
|
global SECDEP_GCE_CLIENT_SECRET
|
|
global SECDEP_GCE_PROJECT_ID
|
|
global SECDEP_AZURE_TENANT_ID
|
|
global SECDEP_AZURE_SUB_ID
|
|
global SECDEP_AZURE_APP_ID
|
|
global SECDEP_AZURE_PASSWORD
|
|
global SECDEP_AWS_ACCESS_KEY
|
|
global SECDEP_AWS_SECRET_KEY
|
|
# GCE
|
|
SECDEP_GCE_CLIENT_ID = os.getenv('SECDEP_GCE_CLIENT_ID')
|
|
SECDEP_GCE_CLIENT_SECRET = os.getenv('SECDEP_GCE_CLIENT_SECRET')
|
|
SECDEP_GCE_PROJECT_ID = os.getenv('SECDEP_GCE_PROJECT_ID')
|
|
if SECDEP_GCE_CLIENT_SECRET !="" and SECDEP_GCE_PROJECT_ID !="":
|
|
SECDEP_GCE_CLIENT_SECRET = os.path.join(os.path.dirname(os.path.abspath(__file__)), SECDEP_GCE_PROJECT_ID+'-'+SECDEP_GCE_CLIENT_SECRET[:12]+'.json')
|
|
# Never mind the error after converting is not None to !="" because only then
|
|
# this variable doesnt end up as /home/konsthol/MyGitea/SecDep/-.json
|
|
# Azure
|
|
SECDEP_AZURE_TENANT_ID = os.getenv('SECDEP_AZURE_TENANT_ID')
|
|
SECDEP_AZURE_SUB_ID = os.getenv('SECDEP_AZURE_SUB_ID')
|
|
SECDEP_AZURE_APP_ID = os.getenv('SECDEP_AZURE_APP_ID')
|
|
SECDEP_AZURE_PASSWORD = os.getenv('SECDEP_AZURE_PASSWORD')
|
|
# AWS
|
|
SECDEP_AWS_ACCESS_KEY = os.getenv('SECDEP_AWS_ACCESS_KEY')
|
|
SECDEP_AWS_SECRET_KEY = os.getenv('SECDEP_AWS_SECRET_KEY')
|
|
|
|
get_env_vars()
|
|
|
|
# List all the entries in the .env file and ask the user which one he wants to update
|
|
# If the user enters an invalid value, the program will ask again
|
|
# If the user enters 0 the program will exit
|
|
def update_env_file():
|
|
with open(ENV_FILE, 'r') as f:
|
|
env_file_content = f.read()
|
|
# find all lines staring with SECDEP_
|
|
file_entries = list(filter(lambda x: x.startswith('SECDEP_'), env_file_content.split('\n')))
|
|
count = 0
|
|
for line in file_entries:
|
|
count += 1
|
|
console.print("[bold white]{}) {}[/bold white]".format(count, line))
|
|
console.print("Choosing [u]0[/u] will exit the function", style="bold white")
|
|
console.print("You will be asked to enter the new value [u]until it is valid[/u] or you enter [u]0[/u]", style="bold white")
|
|
choice = prompt.ask("[bold white]Choose the entry you want to update [/bold white]")
|
|
try:
|
|
choice = int(choice)
|
|
if choice > count or choice < 0:
|
|
raise ValueError
|
|
elif choice == 0:
|
|
return
|
|
except ValueError:
|
|
console.print("[u]Invalid[/u] choice", style="bold red")
|
|
update_env_file()
|
|
else:
|
|
entry = file_entries[choice - 1]
|
|
entry_name = entry.split('=')[0]
|
|
entry_value = entry.split('=')[1]
|
|
if entry_value == '':
|
|
entry_value = 'None'
|
|
console.print("[bold white]The current value for {} is {}[/bold white]".format(entry_name, entry_value))
|
|
new_value = prompt.ask("[bold white]Enter the new value [/bold white]")
|
|
with open(ENV_FILE, 'w') as f:
|
|
f.write(env_file_content.replace(entry, "{}={}".format(entry_name, new_value)))
|
|
console.print("[bold white]The value for {} was updated successfully[/bold white]".format(entry_name))
|
|
update_env_file()
|
|
# Reload the environment variables
|
|
# That was setup this way because the initial thought was exiting manually but it will stay that way just in case we do end up making it like so
|
|
load_dotenv(ENV_FILE)
|
|
get_env_vars()
|
|
|
|
# If -e or --edit is passed, call the update_env_file function
|
|
if args.edit:
|
|
update_env_file()
|
|
exit(0)
|
|
|
|
# AWS and AZURE have thousands of image choice so we hardcode the ones we want in order to not wait forever during the input validation
|
|
AWS_ubuntu22_04_images = {
|
|
"ap-northeast-1": "ami-0cd7ad8676931d727",
|
|
"ap-south-1": "ami-06984ea821ac0a879",
|
|
"ca-central-1": "ami-0dae3a932d090b3de",
|
|
"eu-central-1": "ami-03e08697c325f02ab",
|
|
"eu-north-1": "ami-00c70b245f5354c0a",
|
|
"eu-west-1": "ami-0333305f9719618c7",
|
|
"sa-east-1": "ami-015e30624fffff117",
|
|
"us-east-1": "ami-00874d747dde814fa",
|
|
"us-west-1": "ami-09b2a1e33ce552e68",
|
|
"ap-northeast-2": "ami-09eba584c30b7299f",
|
|
"ap-southeast-2": "ami-0c18f3cdeea1c220d",
|
|
"eu-west-2": "ami-0d09654d0a20d3ae2",
|
|
"us-east-2": "ami-0ab0629dba5ae551d",
|
|
"us-west-2": "ami-095413544ce52437d",
|
|
"ap-northeast-3": "ami-0ead712799090892e",
|
|
"eu-west-3": "ami-0afd55c0c8a52973a"
|
|
}
|
|
AWS_ubuntu22_10_images = {
|
|
"ap-northeast-1": "ami-04e096919731466e4",
|
|
"ap-south-1": "ami-01b60efb0632e8b18",
|
|
"ap-southeast-1": "ami-085ecfe4d567e153a",
|
|
"ca-central-1": "ami-010346a896f698f7d",
|
|
"eu-central-1": "ami-0f1cf34dcb4057a5f",
|
|
"eu-north-1": "ami-0e942149c97ff88bd",
|
|
"eu-west-1": "ami-015423a987dafce81",
|
|
"sa-east-1": "ami-06edb0fb80530aa8b",
|
|
"us-east-1": "ami-074f5b0bdbdc43210",
|
|
"us-west-1": "ami-0eb5f373317fb0ae4",
|
|
"ap-northeast-2": "ami-05493e234687bd083",
|
|
"ap-southeast-2": "ami-0bd29331dcb023a43",
|
|
"eu-west-2": "ami-0ae7948375d66d902",
|
|
"us-east-2": "ami-0de381f43eb6dbe39",
|
|
"us-west-2": "ami-0534f435d9dd0ece4",
|
|
"ap-northeast-3": "ami-05ede9a3a46934154",
|
|
"eu-west-3": "ami-0545575c963ed1276"
|
|
}
|
|
AWS_debian_10_images = {
|
|
"ap-northeast-1": "ami-041e370f47f9b619e",
|
|
"ap-northeast-2": "ami-0243bb2bec2d9707b",
|
|
"ap-northeast-3": "ami-0c1c0379b3534e7d8",
|
|
"ap-south-1": "ami-0eb2c4104acb437b2",
|
|
"ap-southeast-1": "ami-0ee39036464b9a87e",
|
|
"ap-southeast-2": "ami-0bb99834ae8c7471e",
|
|
"ca-central-1": "ami-0125327537ef16282",
|
|
"eu-central-1": "ami-0c984d7a384cafb51",
|
|
"eu-north-1": "ami-013fff3fbe1b44cd7",
|
|
"eu-west-1": "ami-00aa3f69e07141166",
|
|
"eu-west-2": "ami-073e61682f2137943",
|
|
"eu-west-3": "ami-0fa84148519465f59",
|
|
"sa-east-1": "ami-03405dcb7a98e974b",
|
|
"us-east-1": "ami-03d6e2aceefaa35b0",
|
|
"us-east-2": "ami-0246e87085c5c98e3",
|
|
"us-west-1": "ami-0809b44a732f37188",
|
|
"us-west-2": "ami-0164ab05efc075cbc"
|
|
}
|
|
AWS_debian_11_images = {
|
|
"ap-northeast-1": "ami-043fed2cdcba4027e",
|
|
"ap-northeast-2": "ami-0bb0231c184073ac1",
|
|
"ap-northeast-3": "ami-09968ae34a843a6cb",
|
|
"ap-south-1": "ami-079b117c1800d30f8",
|
|
"ap-southeast-1": "ami-07ac0a74d21a3174c",
|
|
"ap-southeast-2": "ami-0c7a13e86ce7afc80",
|
|
"ca-central-1": "ami-09b3e61273dd34f3b",
|
|
"eu-central-1": "ami-0c75b861029de4030",
|
|
"eu-north-1": "ami-08869bacfa1188ec9",
|
|
"eu-west-1": "ami-0591c8c8aa7d9b217",
|
|
"eu-west-2": "ami-0e789c3dd24faa0be",
|
|
"eu-west-3": "ami-040dc155c278da35a",
|
|
"sa-east-1": "ami-0cb45622734c55ed6",
|
|
"us-east-1": "ami-052465340e6b59fc0",
|
|
"us-east-2": "ami-06a7641d5bd7bdc65",
|
|
"us-west-1": "ami-0097d5326aebc68e0",
|
|
"us-west-2": "ami-05063446e767da4ff"
|
|
}
|
|
AWS_centos7_images = {
|
|
"eu-north-1": "ami-0e5125a0f19c52a2b",
|
|
"eu-west-2": "ami-0de2f45684e59282c",
|
|
"us-west-1": "ami-0bcd12d19d926f8e9",
|
|
"eu-central-1": "ami-0afcbcee3dfbce929",
|
|
"us-east-1": "ami-0aedf6b1cb669b4c7",
|
|
"ap-south-1": "ami-09f129ee53d3523c0",
|
|
"ap-northeast-1": "ami-06e6d2122baa563c4",
|
|
"ap-northeast-2": "ami-061f5322ab2662c82",
|
|
"eu-west-3": "ami-051806c39fa542e22",
|
|
"us-west-2": "ami-04f798ca92cc13f74",
|
|
"ap-southeast-1": "ami-03bfba2e75432064e",
|
|
"us-east-2": "ami-033adaf0b583374d4",
|
|
"ap-southeast-2": "ami-0264ead5294ad1773",
|
|
"ca-central-1": "ami-01ebef6e00efb2c20",
|
|
"sa-east-1": "ami-015f6bf0657816a2d",
|
|
"eu-west-1": "ami-00d464afa64e1fc69"
|
|
}
|
|
AWS_centos8_images = {
|
|
"sa-east-1": "ami-0ec17e5479978d435",
|
|
"ap-southeast-2": "ami-0cd15beeae86bd0b5",
|
|
"us-east-1": "ami-0c07df890a618c98a",
|
|
"eu-central-1": "ami-0b79da023fb461a12",
|
|
"us-west-2": "ami-09195cb76ab892888",
|
|
"eu-west-1": "ami-0819edf1cd94e83e8",
|
|
"ap-southeast-1": "ami-07ab649c51b1c14e7",
|
|
"eu-north-1": "ami-05eaebdafff627949",
|
|
"us-east-2": "ami-05cefb3ebaddc75f6",
|
|
"eu-west-3": "ami-05a3b9ccef6b9a4f4",
|
|
"ap-south-1": "ami-04ebf66b7be0500f6",
|
|
"eu-west-2": "ami-0495083a5dc6bc6a3",
|
|
"us-west-1": "ami-033e1fe6304139f4c",
|
|
"ap-northeast-2": "ami-014911a31aba94953",
|
|
"ap-northeast-1": "ami-0117f2fc9c6939327",
|
|
"ca-central-1": "ami-00723d0970fc53863"
|
|
}
|
|
AWS_centos9_images = {
|
|
"us-west-2": "ami-0e27c3746a0d5ecbf",
|
|
"sa-east-1": "ami-0c70afd15bcb126a9",
|
|
"eu-north-1": "ami-0a942e3eea51f9810",
|
|
"ap-southeast-2": "ami-09825374904820651",
|
|
"us-west-1": "ami-08e31531a8310da78",
|
|
"eu-west-1": "ami-08be94d0e177189de",
|
|
"eu-west-3": "ami-082f57b7c23b057df",
|
|
"ap-northeast-1": "ami-074800b0d58e64b24",
|
|
"ap-southeast-1": "ami-05a6e3d785f747c7d",
|
|
"eu-west-2": "ami-056117c19265dad25",
|
|
"ca-central-1": "ami-03f6ca47d93885ab9",
|
|
"ap-south-1": "ami-0383b9760ad3de192",
|
|
"us-east-1": "ami-027a785419f41ea0f",
|
|
"ap-northeast-2": "ami-025fab85b691896e8",
|
|
"us-east-2": "ami-0127d4ce241322a8e",
|
|
"eu-central-1": "ami-00cdc2b0769957e1c"
|
|
}
|
|
AWS_fedora37_images = {
|
|
"us-east-1": "ami-023fb534213ca41da",
|
|
"us-east-2": "ami-0d9ef71e892f861c6",
|
|
"us-west-2": "ami-019b893191a9ac44a",
|
|
"us-west-1": "ami-04e37334d4e907fab",
|
|
"eu-west-1": "ami-0f242738c5379ffc1",
|
|
"eu-central-1": "ami-0965c162c412da7ca",
|
|
"eu-west-2": "ami-0257e646e0a4b4987",
|
|
"ap-southeast-1": "ami-002185f8a7d5528e2",
|
|
"ap-northeast-1": "ami-0e010569d0798fa05",
|
|
"ap-southeast-2": "ami-028fb965bf530cd3e",
|
|
"sa-east-1": "ami-0390b84962fd284f8",
|
|
"ap-northeast-2": "ami-0902b5ee3ed4ebe17",
|
|
"ap-south-1": "ami-04e6a16b463b1dbe0",
|
|
"ca-central-1": "ami-057f927818ad393c5"
|
|
}
|
|
AWS_redhat9_images = {
|
|
"eu-north-1": "ami-0cd776c8201793f81",
|
|
"ap-south-1": "ami-063d0d3250553f017",
|
|
"eu-west-3": "ami-0b6b7402cc4708282",
|
|
"eu-west-2": "ami-03628db51da52eeaa",
|
|
"eu-west-1": "ami-028f9616b17ba1d53",
|
|
"ap-northeast-3": "ami-088c289c3f07fa115",
|
|
"ap-northeast-2": "ami-000847072b0b35502",
|
|
"ap-northeast-1": "ami-0b74caa65e36e4b38",
|
|
"sa-east-1": "ami-03f8004d2ba700e6c",
|
|
"ca-central-1": "ami-0c23a35603e3d8ba4",
|
|
"ap-southeast-1": "ami-07a79552200256856",
|
|
"ap-southeast-2": "ami-0fe791c0ace58322c",
|
|
"eu-central-1": "ami-025d24108be0a614c",
|
|
"us-east-1": "ami-0c41531b8d18cc72b",
|
|
"us-east-2": "ami-078cbc4c2d057c244",
|
|
"us-west-1": "ami-0fa0ed170a59f4917",
|
|
"us-west-2": "ami-04a616933df665b44"
|
|
}
|
|
AWS_redhat8_6_images = {
|
|
"eu-north-1": "ami-06a2a41d455060f8b",
|
|
"ap-south-1": "ami-05c8ca4485f8b138a",
|
|
"eu-west-3": "ami-0460bf124812bebfa",
|
|
"eu-west-2": "ami-035c5dc086849b5de",
|
|
"eu-west-1": "ami-0f0f1c02e5e4d9d9f",
|
|
"ap-northeast-3": "ami-044921b7897a7e0da",
|
|
"ap-northeast-2": "ami-06c568b08b5a431d5",
|
|
"ap-northeast-1": "ami-0f903fb156f24adbf",
|
|
"sa-east-1": "ami-0c1b8b886626f940c",
|
|
"ca-central-1": "ami-0c3d3a230b9668c02",
|
|
"ap-southeast-1": "ami-051f0947e420652a9",
|
|
"ap-southeast-2": "ami-0808460885ff81045",
|
|
"eu-central-1": "ami-0e7e134863fac4946",
|
|
"us-east-1": "ami-06640050dc3f556bb",
|
|
"us-east-2": "ami-092b43193629811af",
|
|
"us-west-1": "ami-0186e3fec9b0283ee",
|
|
"us-west-2": "ami-08970fb2e5767e3b8"
|
|
}
|
|
AWS_redhat7_9_images = {
|
|
"eu-north-1": "ami-003fb5b0ea327060c",
|
|
"ap-south-1": "ami-0b6d1128312a13b2a",
|
|
"eu-west-3": "ami-0f4643887b8afe9e2",
|
|
"eu-west-2": "ami-0e6c172f77df9f9c3",
|
|
"eu-west-1": "ami-020e14de09d1866b4",
|
|
"ap-northeast-3": "ami-00718a107dacde79f",
|
|
"ap-northeast-2": "ami-0c851e892c33af909",
|
|
"ap-northeast-1": "ami-0155fdd0956a0c7a0",
|
|
"sa-east-1": "ami-07eca9d9caaced495",
|
|
"ca-central-1": "ami-0de9a412a63b8f99d",
|
|
"ap-southeast-1": "ami-0f24fbd3cc8531844",
|
|
"ap-southeast-2": "ami-0fb87e863747a1610",
|
|
"eu-central-1": "ami-0f58468b80db2db66",
|
|
"us-east-1": "ami-005b7876121b7244d",
|
|
"us-east-2": "ami-0d2bf41df19c4aac7",
|
|
"us-west-1": "ami-015474e24281c803d",
|
|
"us-west-2": "ami-02d40d11bb3aaf3e5"
|
|
}
|
|
AWS_opensuseLeap15_3_images = {
|
|
"ap-south-1": "ami-0ade60d62f2a2e05c",
|
|
"eu-north-1": "ami-0ae983da6ffb9b1e7",
|
|
"eu-west-3": "ami-025ea6b220205eeda",
|
|
"eu-west-2": "ami-0c912bbb879286143",
|
|
"eu-west-1": "ami-0397bb96c927b3747",
|
|
"ap-northeast-3": "ami-073fffb8542446f02",
|
|
"ap-northeast-2": "ami-0a4130f336deb5d7d",
|
|
"ap-northeast-1": "ami-093be1d241fe4f400",
|
|
"ca-central-1": "ami-07b73495c9189e14c",
|
|
"sa-east-1": "ami-02f22e7a3a61b9ad4",
|
|
"ap-southeast-1": "ami-0e2676834fdb2722d",
|
|
"ap-southeast-2": "ami-0d75289309da084dd",
|
|
"eu-central-1": "ami-0ca8f0b74d5fe4b0c",
|
|
"us-east-1": "ami-0a21376f8d02ffa2d",
|
|
"us-east-2": "ami-0a3fdbf842659345d",
|
|
"us-west-1": "ami-0feb167c171099332",
|
|
"us-west-2": "ami-0eb50cf5b36982ab5",
|
|
}
|
|
AWS_opensuseLeap15_4_images = {
|
|
"ap-south-1": "ami-0c936741ae62acefd",
|
|
"eu-north-1": "ami-06f677915bcf57a3f",
|
|
"eu-west-3": "ami-0fcb2baaff359470d",
|
|
"eu-west-2": "ami-0d75aefcfe115e1b1",
|
|
"eu-west-1": "ami-04c349804a22e1a81",
|
|
"ap-northeast-3": "ami-0ce8958d99aded398",
|
|
"ap-northeast-2": "ami-0d6202fde5a0672cb",
|
|
"ap-northeast-1": "ami-0ad77d12fa9be7d26",
|
|
"ca-central-1": "ami-08d3a675ff259e79b",
|
|
"sa-east-1": "ami-05c679febf019e22b",
|
|
"ap-southeast-1": "ami-0eff8b51447b310c8",
|
|
"ap-southeast-2": "ami-0050fc1b597e7e5c7",
|
|
"eu-central-1": "ami-04050bb7554762bec",
|
|
"us-east-1": "ami-08159a7e0bbb30b75",
|
|
"us-east-2": "ami-028019dea1a954aa4",
|
|
"us-west-1": "ami-0685f2266425e1dec",
|
|
"us-west-2": "ami-02881e3769e7c9ab2"
|
|
}
|
|
|
|
AWS_images = {
|
|
"Ubuntu Server 22.04 LTS": AWS_ubuntu22_04_images,
|
|
"Ubuntu Server 22.10": AWS_ubuntu22_10_images,
|
|
"Debian 10": AWS_debian_10_images,
|
|
"Debian 11": AWS_debian_11_images,
|
|
"CentOS 7": AWS_centos7_images,
|
|
"CentOS 8": AWS_centos8_images,
|
|
"CentOS 9": AWS_centos9_images,
|
|
"Fedora 37": AWS_fedora37_images,
|
|
"Red Hat Enterprise Linux 7.9": AWS_redhat7_9_images,
|
|
"Red Hat Enterprise Linux 8.6": AWS_redhat8_6_images,
|
|
"Red Hat Enterprise Linux 9.0": AWS_redhat9_images,
|
|
"OpenSUSE Leap 15.3": AWS_opensuseLeap15_3_images,
|
|
"OpenSUSE Leap 15.4": AWS_opensuseLeap15_4_images
|
|
}
|
|
|
|
AZURE_images = {
|
|
"Ubuntu Server 22.04 LTS": "Canonical:0001-com-ubuntu-server-jammy:22_04-lts:22.04.202301050",
|
|
"Ubuntu Server 22.10": "Canonical:0001-com-ubuntu-server-kinetic:22_10:22.10.202301040",
|
|
"Debian 10": "Debian:debian-11:11:0.20221219.1234",
|
|
"Debian 11": "Debian:debian-10:10:0.20221205.1220",
|
|
"CentOS 8.4": "OpenLogic:CentOS:8_4:8.4.2021071900",
|
|
"CentOS 8.5": "OpenLogic:CentOS:8_5:8.5.2022101800",
|
|
"Fedora 36": "ntegralinc1586961136942:ntg_fedora_36:ntg_fedora_36:1.0.1",
|
|
"Fedora 37": "ntegralinc1586961136942:ntg_fedora_37:ntg_fedora_37:1.0.0",
|
|
"Red Hat Enterprise Linux 8.6": "RedHat:rhel-raw:8_6:8.6.2022052401",
|
|
"Red Hat Enterprise Linux 9.1": "RedHat:rhel-raw:9_1:9.1.2022112213",
|
|
"OpenSUSE Leap 15.3": "SUSE:opensuse-leap-15-3:gen1:2022.11.04",
|
|
"OpenSUSE Leap 15.4": "SUSE:opensuse-leap-15-4:gen1:2022.11.04"
|
|
}
|
|
|
|
# Declare the global variables for the drivers
|
|
global gce_driver
|
|
global azure_driver
|
|
global aws_driver
|
|
global providers_quantity
|
|
|
|
# Get GCE driver
|
|
def get_gce_driver():
|
|
if SECDEP_GCE_CLIENT_SECRET !="" and SECDEP_GCE_PROJECT_ID !="" and SECDEP_GCE_CLIENT_ID !="":
|
|
driver = get_driver(Provider.GCE)
|
|
console.print("Trying to authenticate with google...\n", style="bold white")
|
|
for step in track(range(1)):
|
|
pass
|
|
return driver(SECDEP_GCE_CLIENT_ID, SECDEP_GCE_CLIENT_SECRET, project=SECDEP_GCE_PROJECT_ID)
|
|
|
|
# Get Azure driver
|
|
def get_azure_driver():
|
|
if SECDEP_AZURE_TENANT_ID !="" and SECDEP_AZURE_SUB_ID !="" and SECDEP_AZURE_APP_ID !="" and SECDEP_AZURE_PASSWORD !="":
|
|
driver = get_driver(Provider.AZURE_ARM)
|
|
console.print("Trying to authenticate with azure...\n", style="bold white")
|
|
for step in track(range(1)):
|
|
pass
|
|
return driver(tenant_id=SECDEP_AZURE_TENANT_ID, subscription_id=SECDEP_AZURE_SUB_ID, key=SECDEP_AZURE_APP_ID, secret=SECDEP_AZURE_PASSWORD)
|
|
|
|
# Get AWS driver
|
|
def get_aws_driver():
|
|
if SECDEP_AWS_ACCESS_KEY !="" and SECDEP_AWS_SECRET_KEY !="":
|
|
driver = get_driver(Provider.EC2)
|
|
console.print("Trying to authenticate with amazon...\n", style="bold white")
|
|
for step in track(range(1)):
|
|
pass
|
|
return driver(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY)
|
|
|
|
# We need to know the quantity to print the loading percentage when getting the list of all the nodes
|
|
def get_providers_quantity():
|
|
providers_quantity = 0
|
|
if SECDEP_GCE_CLIENT_SECRET !="" and SECDEP_GCE_PROJECT_ID !="" and SECDEP_GCE_CLIENT_ID !="":
|
|
providers_quantity +=1
|
|
if SECDEP_AZURE_TENANT_ID !="" and SECDEP_AZURE_SUB_ID !="" and SECDEP_AZURE_APP_ID !="" and SECDEP_AZURE_PASSWORD !="":
|
|
providers_quantity +=1
|
|
if SECDEP_AWS_ACCESS_KEY !="" and SECDEP_AWS_SECRET_KEY !="":
|
|
providers_quantity +=1
|
|
return providers_quantity
|
|
|
|
providers_quantity = get_providers_quantity()
|
|
|
|
gce_driver = get_gce_driver()
|
|
azure_driver = get_azure_driver()
|
|
aws_driver = get_aws_driver()
|
|
|
|
# We call this function in almost every other function in order to not keep remaking the driver
|
|
def get_corresponding_driver(provider):
|
|
global driver
|
|
match provider:
|
|
case "gce":
|
|
driver = gce_driver
|
|
case "azure":
|
|
driver = azure_driver
|
|
case "aws":
|
|
driver = aws_driver
|
|
case _:
|
|
console.print("[u]Invalid[/u] provider", style="bold red")
|
|
assert driver is not None, "You need to set all {} environment variables first".format(provider.upper())
|
|
return driver
|
|
|
|
# This function takes a provider arguement and lists all the available sizes
|
|
def list_provider_sizes(provider):
|
|
console.print("Getting "+provider+" sizes...", style="bold white")
|
|
driver = get_corresponding_driver(provider)
|
|
if provider == "aws" or provider == "gce":
|
|
sizes = driver.list_sizes(location=None)
|
|
sizes = [size for size in sizes if size.ram < 16384]
|
|
else:
|
|
azlocation = driver.list_locations()[0]
|
|
sizes = driver.list_sizes(location=azlocation)
|
|
sizes = [size for size in sizes if size.ram < 16384]
|
|
count = 0
|
|
console.print("Available "+provider+" sizes", style="bold white")
|
|
if provider == "aws":
|
|
for size in sizes:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][bold blue]Ram: {}[/bold blue]\n[bold cyan]Disk: {}[/bold cyan]\n[bold magenta]Bandwidth: {}[/bold magenta]\n[bold yellow]Price: {}[/bold yellow]\n".format(count, size.name, size.ram, size.disk, size.bandwidth, size.price))
|
|
elif provider == "gce":
|
|
for size in sizes:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][italic white]{}[/italic white]\n[bold yellow]Price: {}[/bold yellow]\n".format(count, size.name, size.extra['description'], size.price))
|
|
else:
|
|
for size in sizes:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][bold blue]Ram: {}[/bold blue]\n[bold cyan]Disk: {}[/bold cyan]\n[bold yellow]Price: {}[/bold yellow]\n".format(count, size.name, size.ram, size.disk, size.price))
|
|
return sizes
|
|
|
|
# This function takes a provider arguement and lists all the available locations
|
|
def list_provider_locations(provider):
|
|
console.print("Getting "+provider+" locations...", style="bold white")
|
|
driver = get_corresponding_driver(provider)
|
|
locations = driver.list_locations()
|
|
count = 0
|
|
console.print("Available "+provider+" locations", style="bold white")
|
|
if provider == "aws":
|
|
status = Status("[bold white]Still loading...[/bold white]", spinner="dots")
|
|
status.start()
|
|
locations = []
|
|
awsLocations = ["ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2"]
|
|
for region in awsLocations:
|
|
awsdr = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
specificAwsLocations = awsdr.list_locations()
|
|
for item in specificAwsLocations:
|
|
locations.append(item)
|
|
status.stop()
|
|
for location in locations:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][bold cyan]Region name: {}[/bold cyan]\n[bold blue]Country: {}[/bold blue]\n".format(count, location.name, location.availability_zone.region_name, location.country))
|
|
else:
|
|
for location in locations:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][bold blue]Country: {}\n[/bold blue]".format(count, location.name, location.country))
|
|
return locations
|
|
|
|
def listAWSregions(list):
|
|
count = 0
|
|
console.print("Available aws regions:", style="bold white")
|
|
for item in list:
|
|
count += 1
|
|
console.print("[bold white]{}) {}[/bold white]".format(count, item))
|
|
return list
|
|
|
|
# This function lists all available images from the providers.
|
|
def list_provider_images(provider,images=None):
|
|
driver = get_corresponding_driver(provider)
|
|
console.print("Getting images from " +provider+"...", style="bold white")
|
|
if provider == "azure":
|
|
images = AZURE_images
|
|
elif provider == "aws":
|
|
images = AWS_images
|
|
else:
|
|
status = Status("[bold white]Still loading...[/bold white]", spinner="dots")
|
|
status.start()
|
|
images = driver.list_images()
|
|
status.stop()
|
|
# We filter out the images we do not care about
|
|
images = list(filter(lambda x: 'windows' not in x.name.lower() and 'cos' not in x.name.lower() and 'arm64' not in x.name.lower() and 'byos' not in x.name.lower() and 'sap' not in x.name.lower(), images))
|
|
count = 0
|
|
console.print("Available "+provider+" images", style="bold white")
|
|
if provider == "azure" or provider == "aws":
|
|
for image in images:
|
|
count += 1
|
|
console.print("[bold white]{}) {}[/bold white]".format(count, image))
|
|
else:
|
|
for image in images:
|
|
count += 1
|
|
console.print("[bold white]{}) {}\n\n[/bold white][italic white]{}[/italic white]\n".format(count, image.name, image.extra['description']))
|
|
return images
|
|
|
|
# This function gets called in every get function to create a menu for selection
|
|
# It takes a list and a list name as arguements to differentiate how we print each one
|
|
# It also validates the user input and keeps asking for a valid one unless the user enters 0 to exit
|
|
def choose_from_list(listFromlistFunction,listName):
|
|
if len(listFromlistFunction) == 0:
|
|
console.print("No items", style="bold white")
|
|
exit(0)
|
|
if listName == "awsLocation":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][bold cyan]Region name: {}[/bold cyan]\n[bold blue]Country: {}[/bold blue]\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.availability_zone.region_name, item.country))"
|
|
elif listName == "azureLocation" or listName == "gceLocation":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][bold blue]Country: {}\n[/bold blue]"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.country))"
|
|
elif listName == "awsSize":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][bold blue]Ram: {}[/bold blue]\n[bold cyan]Disk: {}[/bold cyan]\n[bold magenta]Bandwidth: {}[/bold magenta]\n[bold yellow]Price: {}[/bold yellow]\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.ram, item.disk, item.bandwidth, item.price))"
|
|
elif listName == "gceSize":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][italic white]{}[/italic white]\n[bold yellow]Price: {}[/bold yellow]\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.extra['description'], item.price))"
|
|
elif listName == "azureSize":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][bold blue]Ram: {}[/bold blue]\n[bold cyan]Disk: {}[/bold cyan]\n[bold yellow]Price: {}[/bold yellow]\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.ram, item.disk, item.price))"
|
|
elif listName == "awsImage" or listName == "azureImage" or listName == "awsRegion" or listName == "aws_region":
|
|
printFormat = "[bold white]{}) {}[/bold white]"
|
|
printstring = "console.print(printFormat.format(count, item))"
|
|
elif listName == "gceImage":
|
|
printFormat = "[bold white]{}) {}\n\n[/bold white][italic white]{}[/italic white]\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.extra['description']))"
|
|
elif listName == "node":
|
|
printFormat = "[bold white]{}) {}[/bold white]\n\n[bold cyan]State: {}[/bold cyan]\n[bold magenta]Public IPs: {}[/bold magenta]\n[bold blue]Private IPs: {}[/bold blue]\n[bold white]Driver: {}[/bold white]\n[bold cyan]Size: {}[/bold cyan]\n[bold magenta]Image: {}[/bold magenta]\n[bold blue]Creation Date: {}[/bold blue]\n[bold white]Extra: [/bold white]{}\n"
|
|
printstring = "console.print(printFormat.format(count, item.name, item.state, item.public_ips, item.private_ips, item.driver, item.size, item.image, item.created_at, item.extra))"
|
|
|
|
compiled_code = compile(printstring,"<string>","exec")
|
|
console.print("Choosing 0 will exit", style="bold white")
|
|
choice = prompt.ask("[bold white]Choose the "+listName+" you want to use [/bold white]")
|
|
while True:
|
|
try:
|
|
choice = int(choice)
|
|
if choice > len(listFromlistFunction) or choice < 0:
|
|
raise ValueError
|
|
elif choice == 0:
|
|
return
|
|
elif choice == '':
|
|
raise ValueError
|
|
else:
|
|
if listName == "azureImage":
|
|
item = list(listFromlistFunction.values())[choice-1]
|
|
elif listName == "awsImage":
|
|
item = list(listFromlistFunction.values())[choice-1]
|
|
print(list(listFromlistFunction)[choice-1])
|
|
elif listName == "awsRegion":
|
|
item1 = list(listFromlistFunction)[choice-1]
|
|
item2 = list(listFromlistFunction.values())[choice-1]
|
|
return item1,item2
|
|
else:
|
|
item = listFromlistFunction[int(choice) - 1]
|
|
return item
|
|
except ValueError:
|
|
count = 0
|
|
for item in listFromlistFunction:
|
|
count += 1
|
|
exec(compiled_code)
|
|
console.print("[u]Invalid[/u] choice", style="bold red")
|
|
console.print("Choosing 0 will exit", style="bold white")
|
|
choice = prompt.ask("[bold white]Choose the "+listName+" you want to use [/bold white]")
|
|
|
|
# This function gets a provider location and returns it
|
|
def get_provider_location(provider):
|
|
location = choose_from_list(list_provider_locations(provider),provider+"Location")
|
|
return location
|
|
|
|
# This function gets a provider size and returns it
|
|
def get_provider_size(provider):
|
|
size = choose_from_list(list_provider_sizes(provider),provider+"Size")
|
|
return size
|
|
|
|
# This function asks the user which image he wants to use and returns it
|
|
# If the user enters an invalid value, the program will ask again
|
|
# If the user enters 0 the program will exit
|
|
# For azure, after the user chooses one we take the value which is the image URN
|
|
# and use it to get the actual AzureImage
|
|
# For aws after we get the image we must select a region to get the ami because
|
|
# amis are region specific. Then we get the actual image
|
|
# Unlike GCE, the equivalent list_images() function requires NodeLocation as an argument so
|
|
# we choose the first available one.
|
|
# Without additional arguments like publisher, sku and version it takes rediculously
|
|
# long to execute because there are over 3000 images available and the code that retrieves them has about 3 nested for loops.
|
|
# That is why we use a dictionary to output the available images. The list does not get updated
|
|
# but since the options include very old releases as well it is safe to assume these will also
|
|
# be kept as available choices
|
|
# Same goes for aws
|
|
def get_provider_image(provider):
|
|
image = choose_from_list(list_provider_images(provider),provider+"Image")
|
|
if provider == "azure":
|
|
driver = get_corresponding_driver(provider)
|
|
azlocation = driver.list_locations()[0]
|
|
if image is not None:
|
|
image = driver.get_image(image,location=azlocation)
|
|
elif provider == "aws":
|
|
if image is not None:
|
|
region = choose_from_list(listAWSregions(image),provider+"Region")
|
|
if region is not None:
|
|
dr = get_driver(Provider.EC2)
|
|
correct_driver = dr(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region[0])
|
|
image = correct_driver.get_image(region[1])
|
|
else:
|
|
image = None
|
|
return image
|
|
|
|
# We need the blockPrint and enablePrint functions for when we use the list something ones during the input validation
|
|
# and don't actually need to see the lists
|
|
def blockPrint():
|
|
sys.stdout = open(os.devnull, 'w')
|
|
|
|
def enablePrint():
|
|
sys.stdout = sys.__stdout__
|
|
|
|
def getAWSRegionFromAmi(ami):
|
|
images = AWS_images.values()
|
|
for image in images:
|
|
if ami in image.values():
|
|
return list(image.keys())[list(image.values()).index(ami)]
|
|
|
|
# This is the most important function of all and uses all the previous ones to validate the input and get the actual objects
|
|
def create_node(provider, name=None, location=None, size=None, image=None, confirm=None, deploy=None):
|
|
# Get public ssh key value
|
|
with open(SECDEP_SSH_PUBLIC_KEY, 'r') as f:
|
|
pubkey = f.read()
|
|
if provider == "azure":
|
|
auth = NodeAuthSSHKey(pubkey)
|
|
# Check if name was given and if not prompt the user to give one
|
|
if name is None:
|
|
name = prompt.ask("[bold white]Enter the name of the node [/bold white]")
|
|
assert name != "", "Name is empty"
|
|
name = provider+"-"+name
|
|
else:
|
|
name = provider+"-"+name
|
|
# In the case of aws location has to be None because it is actually derived from the ami (image)
|
|
if provider == "aws":
|
|
location = None
|
|
# print("in aws you first have to choose an image before the location")
|
|
else:
|
|
# In other cases
|
|
# Check if location was given and if not prompt the user to choose
|
|
if location is None:
|
|
location = get_provider_location(provider)
|
|
assert location is not None, "Location is None"
|
|
# If it was given, get the list of valid values to compare the imput with
|
|
else:
|
|
blockPrint()
|
|
locations = list_provider_locations(provider)
|
|
enablePrint()
|
|
locationName = []
|
|
for loc in locations:
|
|
# gce and aws have the location name under the name tag and azure under the id.
|
|
if provider == "gce" or provider == "aws":
|
|
locationName.append(loc.name)
|
|
else:
|
|
locationName.append(loc.id)
|
|
# If it was not found prompt the user for selection
|
|
if location not in locationName:
|
|
console.print("[u]Invalid Location[/u]", style="bold red")
|
|
location = get_provider_location(provider)
|
|
assert location is not None, "Location is None"
|
|
else:
|
|
location = locations[locationName.index(location)]
|
|
# Check if size was given and if not prompt the user to choose
|
|
if size is None:
|
|
size = get_provider_size(provider)
|
|
assert size is not None, "Size is None"
|
|
# If it was given, get the list of valid values to compare the input with
|
|
else:
|
|
blockPrint()
|
|
sizes = list_provider_sizes(provider)
|
|
enablePrint()
|
|
sizeName = []
|
|
for siz in sizes:
|
|
sizeName.append(siz.name)
|
|
if size not in sizeName:
|
|
console.print("[u]Invalid[/u] Size", style="bold red")
|
|
size = get_provider_size(provider)
|
|
assert size is not None, "Size is None"
|
|
else:
|
|
size = sizes[sizeName.index(size)]
|
|
# Check if image was given and if not prompt the user to choose one
|
|
if image is None:
|
|
image = get_provider_image(provider)
|
|
assert image is not None, "Image is None"
|
|
if provider == "aws":
|
|
ami = image.id
|
|
region = getAWSRegionFromAmi(ami)
|
|
dr = get_corresponding_driver(provider)
|
|
assert dr is not None, "Driver is not set up correctly"
|
|
image = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region).get_image(ami)
|
|
else:
|
|
blockPrint()
|
|
# If provider was aws we must get the list of amis
|
|
if provider == "gce":
|
|
images = list_provider_images(provider)
|
|
elif provider == "azure":
|
|
images = AZURE_images.values()
|
|
else:
|
|
images = AWS_images.values()
|
|
enablePrint()
|
|
imageName = []
|
|
if provider == "gce":
|
|
for img in images:
|
|
imageName.append(img.name)
|
|
elif provider == "azure":
|
|
for img in images:
|
|
imageName.append(img)
|
|
else:
|
|
# In the case of aws this is how we get the list of amis
|
|
for img in images:
|
|
amis = img.values()
|
|
for ami in amis:
|
|
imageName.append(ami)
|
|
if image not in imageName:
|
|
console.print("[u]Invalid[/u] Image", style="bold red")
|
|
image = get_provider_image(provider)
|
|
# If the image given was not in the list of valid values we must get the actual image and the region from the ami to get the correct driver
|
|
if provider == "aws":
|
|
assert image is not None, "Image is None"
|
|
ami = image.id
|
|
region = getAWSRegionFromAmi(ami)
|
|
dr = get_corresponding_driver(provider)
|
|
assert dr is not None, "Driver is not set up correctly"
|
|
image = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region).get_image(ami)
|
|
assert image is not None, "Image is None"
|
|
else:
|
|
# If image was indeed in the list we choose it
|
|
if provider == "gce":
|
|
image = images[imageName.index(image)]
|
|
elif provider == "azure":
|
|
driver = get_corresponding_driver(provider)
|
|
azlocation = driver.list_locations()[0]
|
|
image = driver.get_image(image,location=azlocation)
|
|
else:
|
|
# but in the case of aws we still need to get the region first
|
|
region = getAWSRegionFromAmi(image)
|
|
dr = get_corresponding_driver(provider)
|
|
assert dr is not None, "Driver is not set up correctly"
|
|
image = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region).get_image(image)
|
|
# If we could use the docker python sdk to search for docker images without the need for the docker engine to be installed on the host system, we could now validate the image's names to be downloaded
|
|
# In the case of gce we need to give the sa_scopes and the ex_metadata parameters
|
|
if provider == "gce":
|
|
sa_scopes = [{"email": "default","scopes": ["cloud-platform"]}]
|
|
ex_metadata = metadata = { "items": [{"key": "ssh-keys", "value": "secdep: %s" % (pubkey)}] }
|
|
# For any other provider we just get the driver as defined in the get_corresponding_driver function
|
|
if provider != "aws":
|
|
driver = get_corresponding_driver(provider)
|
|
else:
|
|
# But for aws we need to get the one matching our derived region from the image selection
|
|
dr = get_corresponding_driver(provider)
|
|
assert dr is not None, "Driver is not set up correctly"
|
|
driver = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
# If the user did not input the -y or --yes flag then we output the current choices for a second though
|
|
if confirm is False:
|
|
console.print("[bold white]\nName: %s\n[/bold white]" % (name))
|
|
# There is a differentiation between aws and the other providers in region and location
|
|
if provider == "aws":
|
|
console.print("[bold white]\nLocation: %s\n[/bold white]" % (region))
|
|
else:
|
|
console.print("[bold white]\nLocation: %s\n[/bold white]" % (location))
|
|
console.print("[bold white]\nSize: [/bold white]%s\n" % (size))
|
|
console.print("[bold white]\nImage: [/bold white]%s\n" % (image))
|
|
console.print("Type yes if you want to confirm your choices", style="bold white")
|
|
confirm = Confirm.ask("[bold white]Continue? [/bold white]")
|
|
# Any input other than yes does not continue the node creation
|
|
assert confirm, "User did not confirm"
|
|
if provider == "gce":
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
gceNodes = driver.list_nodes()
|
|
for gceNode in gceNodes:
|
|
if gceNode.name == name:
|
|
console.print("A node with that name already exists under this project, please choose [u]another[/u] one", style="bold red")
|
|
exit(0)
|
|
existIn = False
|
|
firewalls = driver.ex_list_firewalls()
|
|
for firewall in firewalls:
|
|
if firewall.name == "allow-all-inbound":
|
|
existIn = True
|
|
break
|
|
if existIn == False:
|
|
driver.ex_create_firewall(name="allow-all-inbound", allowed=[{"IPProtocol": "tcp", "ports": ["0-65534"]},{"IPProtocol": "udp", "ports": ["0-65534"]}], network='default', direction='INGRESS', priority=1000, source_service_accounts=sa_scopes, target_service_accounts=sa_scopes)
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([sendDockerCompose, actualDeployScript])
|
|
node = driver.deploy_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
else:
|
|
node = driver.deploy_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata, deploy=actualDeployScript, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.create_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata)
|
|
elif provider == "azure":
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
console.print("Keep in mind azure node creation may take a while because we need to create all the needed resources first", style="bold white")
|
|
res_groups = driver.ex_list_resource_groups()
|
|
for res_group in res_groups:
|
|
if res_group.name == name+"-res_group":
|
|
console.print("A resource group with that name already exists, please try a [u]different[/u] virtual machine name to differentiate the resource group name", style="bold red")
|
|
exit(0)
|
|
credential = ClientSecretCredential(client_id=SECDEP_AZURE_APP_ID, client_secret=SECDEP_AZURE_PASSWORD, tenant_id=SECDEP_AZURE_TENANT_ID)
|
|
subscription_id = SECDEP_AZURE_SUB_ID
|
|
resource_client = ResourceManagementClient(credential, subscription_id)
|
|
network_client = NetworkManagementClient(credential, subscription_id)
|
|
# Create Resource group using azure sdk since libcloud does not offer that functionality
|
|
res_group = resource_client.resource_groups.create_or_update(name+"-res_group", {"location": location.id})
|
|
# Create Virtual Network using azure sdk since libcloud does not offer that functionality
|
|
poller = network_client.virtual_networks.begin_create_or_update(res_group.name, name+"-vir_net", { "location": location.id, "address_space": {"address_prefixes": ["10.0.0.0/16"]},},)
|
|
vir_net = poller.result()
|
|
# Create the default subnet using azure sdk since libcloud does not offer that functionality
|
|
poller = network_client.subnets.begin_create_or_update(res_group.name, vir_net.name, name+"-subnet", { "address_prefix": "10.0.0.0/24"},)
|
|
subnet = poller.result()
|
|
# Create Network Security Group
|
|
driver.ex_create_network_security_group(name=name+"-sec_group", resource_group=res_group.name, location=location)
|
|
# Get the created Virtual Network
|
|
networks = driver.ex_list_networks()
|
|
for network in networks:
|
|
if network.name == vir_net.name:
|
|
ex_network = network
|
|
break
|
|
else:
|
|
console.print("Could not find the virtual network. Maybe it was not created correctly?", style="bold red")
|
|
# Get Virtual Network's default subnet we created
|
|
subnet = driver.ex_list_subnets(network=ex_network)[0]
|
|
# Create public ip
|
|
public_ip = driver.ex_create_public_ip(name=name+"-ip", resource_group=res_group.name, location=location, public_ip_allocation_method="Static")
|
|
# Create a Virtual Network Interface
|
|
network_interface = driver.ex_create_network_interface(name=name+"-nic", subnet=subnet, resource_group=res_group.name, location=location, public_ip=public_ip)
|
|
# Get the created Virtual Network Interface
|
|
nic = driver.ex_list_nics(resource_group=res_group.name)[0]
|
|
# Get the created Network Security Group
|
|
sec_group = driver.ex_list_network_security_groups(res_group.name)[0]
|
|
# Parameters to associate the Network Security Group to the Virtual Network Interface
|
|
params = {"ipConfigurations":[{"name":"myip1","id":nic.id,"type":"Microsoft.Network/networkInterfaces/ipConfigurations","properties":{"provisioningState":"Succeeded","privateIPAddress":"10.0.0.4","privateIPAllocationMethod":"Dynamic","publicIPAddress":{"id":public_ip.id},"subnet":{"id":subnet.id},"primary":"true","privateIPAddressVersion":"IPv4"}}],"dnsSettings":{"dnsServers":[]},"enableAcceleratedNetworking":"false","enableIPForwarding":"false","disableTcpStateTracking":"false","networkSecurityGroup":{"id":sec_group.id},"nicType":"Standard"}
|
|
# New Virtual Network Interface associated with the Network Security Group
|
|
newnic = driver.ex_update_nic_properties(nic, res_group.name, params)
|
|
# Update the Network Security Group's rules to accept connections using azure sdk since libcloud does not offer that functionality
|
|
network_client.security_rules.begin_create_or_update(res_group.name, sec_group.name,"allowAllInbound", SecurityRule(protocol='*', source_address_prefix='*', destination_address_prefix='*', access='Allow', direction='Inbound', description='Allow all', source_port_range='*', destination_port_range='*', priority=4096, name="allowAll"))
|
|
network_client.security_rules.begin_create_or_update(res_group.name, sec_group.name,"allowAllOutbound", SecurityRule(protocol='*', source_address_prefix='*', destination_address_prefix='*', access='Allow', direction='Outbound', description='Allow all', source_port_range='*', destination_port_range='*', priority=4096, name="allowAll"))
|
|
# Create the node
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([sendDockerCompose, actualDeployScript])
|
|
node = driver.deploy_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
else:
|
|
node = driver.deploy_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True, deploy=actualDeployScript, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.create_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True)
|
|
else:
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
# If provider was aws
|
|
# Delete all keys since we are just going to upload the same one for the creation
|
|
# This doesn't affect already existing nodes because as we said, it it the same one used for the others
|
|
keys = driver.list_key_pairs()
|
|
for key in keys:
|
|
driver.delete_key_pair(key)
|
|
keyname="secdep@"+socket.gethostname()
|
|
driver.import_key_pair_from_string(keyname, pubkey)
|
|
driver.ex_authorize_security_group_permissive('default')
|
|
# since each ami decides on a different admin user name we can't use the create node
|
|
# to end up with a secdep user but we have to use the deploy_node function
|
|
SCRIPT = '''#!/usr/bin/env bash
|
|
sudo useradd -G sudo -s /bin/bash -m secdep
|
|
sudo echo "secdep:secdeppass" | sudo chpasswd
|
|
sudo mkdir -p /home/secdep/.ssh
|
|
[[ -e /root/.ssh/authorized_keys ]] && sudo cp /root/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -e /home/admin/.ssh/authorized_keys ]] && sudo cp /home/admin/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -e /home/ec2-user/.ssh/authorized_keys ]] && sudo cp /home/ec2-user/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -e /home/centos/.ssh/authorized_keys ]] && sudo cp /home/centos/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -e /home/fedora/.ssh/authorized_keys ]] && sudo cp /home/fedora/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -e /home/ubuntu/.ssh/authorized_keys ]] && sudo cp /home/ubuntu/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
sudo chmod 755 /home
|
|
sudo chown secdep:secdep /home/secdep -R
|
|
sudo chmod 700 /home/secdep /home/secdep/.ssh
|
|
sudo chmod 600 /home/secdep/.ssh/authorized_keys'''
|
|
deploy = ScriptDeployment(script=SCRIPT, name="initialization.sh", delete=True)
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([deploy, sendDockerCompose, actualDeployScript])
|
|
else:
|
|
msd = MultiStepDeployment([deploy, actualDeployScript])
|
|
node = driver.deploy_node(name=name, image=image, size=size, ex_keyname=keyname, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_alternate_usernames=["admin", "ec2-user", "centos", "fedora", "ubuntu"])
|
|
console.print('[bold white]deploy stdout: %s[/bold white]' % (deploy.stdout))
|
|
console.print('[bold red]deploy stderr: %s[/bold red]' % (deploy.stderr))
|
|
console.print('[bold white]deploy exit_code: %s[/bold white]' % (deploy.exit_status))
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.deploy_node(name=name, image=image, size=size, ex_keyname=keyname, deploy=deploy, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_alternate_usernames=["admin", "ec2-user", "centos", "fedora", "ubuntu"])
|
|
console.print('[bold white]deploy stdout: %s[/bold white]' % (deploy.stdout))
|
|
console.print('[bold red]deploy stderr: %s[/bold red]' % (deploy.stderr))
|
|
console.print('[bold white]deploy exit_code: %s[/bold white]' % (deploy.exit_status))
|
|
else:
|
|
# When the -y or --yes parameter is passed we go straight to the node creation
|
|
if provider == "gce":
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
gceNodes = driver.list_nodes()
|
|
for gceNode in gceNodes:
|
|
if gceNode.name == name:
|
|
console.print("A node with that name already exists under this project, please choose [u]another[/u] one", style="bold red")
|
|
exit(0)
|
|
existIn = False
|
|
firewalls = driver.ex_list_firewalls()
|
|
for firewall in firewalls:
|
|
if firewall.name == "allow-all-inbound":
|
|
existIn = True
|
|
break
|
|
if existIn == False:
|
|
driver.ex_create_firewall(name="allow-all-inbound", allowed=[{"IPProtocol": "tcp", "ports": ["0-65534"]},{"IPProtocol": "udp", "ports": ["0-65534"]}], network='default', direction='INGRESS', priority=1000, source_service_accounts=sa_scopes, target_service_accounts=sa_scopes)
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([sendDockerCompose, actualDeployScript])
|
|
node = driver.deploy_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
else:
|
|
node = driver.deploy_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata, deploy=actualDeployScript, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.create_node(name=name, image=image, size=size, location=location, ex_service_accounts=sa_scopes, ex_metadata=metadata)
|
|
elif provider == "azure":
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
console.print("Keep in mind azure node creation may take a while because we need to create all the needed resources first", style="bold white")
|
|
res_groups = driver.ex_list_resource_groups()
|
|
for res_group in res_groups:
|
|
if res_group.name == name+"-res_group":
|
|
console.print("A resource group with that name already exists, please try a [u]different[/u] virtual machine name to differentiate the resource group name", style="bold red")
|
|
exit(0)
|
|
credential = ClientSecretCredential(client_id=SECDEP_AZURE_APP_ID, client_secret=SECDEP_AZURE_PASSWORD, tenant_id=SECDEP_AZURE_TENANT_ID)
|
|
subscription_id = SECDEP_AZURE_SUB_ID
|
|
resource_client = ResourceManagementClient(credential, subscription_id)
|
|
network_client = NetworkManagementClient(credential, subscription_id)
|
|
# Create Resource group using azure sdk since libcloud does not offer that functionality
|
|
res_group = resource_client.resource_groups.create_or_update(name+"-res_group", {"location": location.id})
|
|
# Create Virtual Network using azure sdk since libcloud does not offer that functionality
|
|
poller = network_client.virtual_networks.begin_create_or_update(res_group.name, name+"-vir_net", { "location": location.id, "address_space": {"address_prefixes": ["10.0.0.0/16"]},},)
|
|
vir_net = poller.result()
|
|
# Create the default subnet using azure sdk since libcloud does not offer that functionality
|
|
poller = network_client.subnets.begin_create_or_update(res_group.name, vir_net.name, name+"-subnet", { "address_prefix": "10.0.0.0/24"},)
|
|
subnet = poller.result()
|
|
# Create Network Security Group
|
|
driver.ex_create_network_security_group(name=name+"-sec_group", resource_group=res_group.name, location=location)
|
|
# Get the created Virtual Network
|
|
networks = driver.ex_list_networks()
|
|
for network in networks:
|
|
if network.name == vir_net.name:
|
|
ex_network = network
|
|
break
|
|
else:
|
|
console.print("Could not find the virtual network. Maybe it was not created correctly?", style="bold red")
|
|
# Get Virtual Network's default subnet we created
|
|
subnet = driver.ex_list_subnets(network=ex_network)[0]
|
|
# Create public ip
|
|
public_ip = driver.ex_create_public_ip(name=name+"-ip", resource_group=res_group.name, location=location, public_ip_allocation_method="Static")
|
|
# Create a Virtual Network Interface
|
|
network_interface = driver.ex_create_network_interface(name=name+"-nic", subnet=subnet, resource_group=res_group.name, location=location, public_ip=public_ip)
|
|
# Get the created Virtual Network Interface
|
|
nic = driver.ex_list_nics(resource_group=res_group.name)[0]
|
|
# Get the created Network Security Group
|
|
sec_group = driver.ex_list_network_security_groups(res_group.name)[0]
|
|
# Parameters to associate the Network Security Group to the Virtual Network Interface
|
|
params = {"ipConfigurations":[{"name":"myip1","id":nic.id,"type":"Microsoft.Network/networkInterfaces/ipConfigurations","properties":{"provisioningState":"Succeeded","privateIPAddress":"10.0.0.4","privateIPAllocationMethod":"Dynamic","publicIPAddress":{"id":public_ip.id},"subnet":{"id":subnet.id},"primary":"true","privateIPAddressVersion":"IPv4"}}],"dnsSettings":{"dnsServers":[]},"enableAcceleratedNetworking":"false","enableIPForwarding":"false","disableTcpStateTracking":"false","networkSecurityGroup":{"id":sec_group.id},"nicType":"Standard"}
|
|
# New Virtual Network Interface associated with the Network Security Group
|
|
newnic = driver.ex_update_nic_properties(nic, res_group.name, params)
|
|
# Update the Network Security Group's rules to accept connections using azure sdk since libcloud does not offer that functionality
|
|
network_client.security_rules.begin_create_or_update(res_group.name, sec_group.name,"allowAllInbound", SecurityRule(protocol='*', source_address_prefix='*', destination_address_prefix='*', access='Allow', direction='Inbound', description='Allow all', source_port_range='*', destination_port_range='*', priority=4096, name="allowAll"))
|
|
network_client.security_rules.begin_create_or_update(res_group.name, sec_group.name,"allowAllOutbound", SecurityRule(protocol='*', source_address_prefix='*', destination_address_prefix='*', access='Allow', direction='Outbound', description='Allow all', source_port_range='*', destination_port_range='*', priority=4096, name="allowAll"))
|
|
# Create the node
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([sendDockerCompose, actualDeployScript])
|
|
node = driver.deploy_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
else:
|
|
node = driver.deploy_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True, deploy=actualDeployScript, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_username="secdep")
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.create_node(name=name, size=size, image=image, location=location, auth=auth, ex_user_name="secdep", ex_resource_group=res_group.name, ex_use_managed_disks=True, ex_nic=newnic, ex_os_disk_delete=True)
|
|
else:
|
|
status = Status("[bold white]Creating instance, please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
# If provider was aws
|
|
# Delete all keys since we are just going to upload the same one for the creation
|
|
# This doesn't affect already existing nodes because as we said, it it the same one used for the others
|
|
keys = driver.list_key_pairs()
|
|
for key in keys:
|
|
driver.delete_key_pair(key)
|
|
keyname="secdep@"+socket.gethostname()
|
|
driver.import_key_pair_from_string(keyname, pubkey)
|
|
driver.ex_authorize_security_group_permissive('default')
|
|
# since each ami decides on a different admin user name we can't use the create node
|
|
# to end up with a secdep user but we have to use the deploy_node function
|
|
SCRIPT = '''#!/usr/bin/env bash
|
|
sudo useradd -G sudo -s /bin/bash -m secdep
|
|
sudo echo "secdep:secdeppass" | sudo chpasswd
|
|
sudo mkdir -p /home/secdep/.ssh
|
|
[[ -f /root/.ssh/authorized_keys ]] && sudo cp /root/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -f /home/admin/.ssh/authorized_keys ]] && sudo cp /home/admin/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -f /home/ec2-user/.ssh/authorized_keys ]] && sudo cp /home/ec2-user/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -f /home/centos/.ssh/authorized_keys ]] && sudo cp /home/centos/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -f /home/fedora/.ssh/authorized_keys ]] && sudo cp /home/fedora/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
[[ -f /home/ubuntu/.ssh/authorized_keys ]] && sudo cp /home/ubuntu/.ssh/authorized_keys /home/secdep/.ssh/authorized_keys
|
|
sudo chmod 755 /home
|
|
sudo chown secdep:secdep /home/secdep -R
|
|
sudo chmod 700 /home/secdep /home/secdep/.ssh
|
|
sudo chmod 600 /home/secdep/.ssh/authorized_keys'''
|
|
deploy = ScriptDeployment(script=SCRIPT, name="initialization.sh", delete=True)
|
|
if args.deploy:
|
|
# After using action=append the args.deploy is a list of lists, so we need to get the first element of the first list
|
|
actualDeployScript = ScriptFileDeployment(script_file=SECDEP_DEPLOY_SCRIPT, args=args.deploy[0], name="harden", delete=True)
|
|
if os.path.exists(SECDEP_DOCKER_COMPOSE) and args.docker_compose:
|
|
# sendDockerCompose = FileDeployment(SECDEP_DOCKER_COMPOSE, target="/home/secdep")
|
|
sendDockerCompose = ScriptFileDeployment(script_file=SECDEP_DOCKER_COMPOSE, name="docker-compose.yml", delete=False)
|
|
msd = MultiStepDeployment([deploy, sendDockerCompose, actualDeployScript])
|
|
else:
|
|
msd = MultiStepDeployment([deploy, actualDeployScript])
|
|
node = driver.deploy_node(name=name, image=image, size=size, ex_keyname=keyname, deploy=msd, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_alternate_usernames=["admin", "ec2-user", "centos", "fedora", "ubuntu"])
|
|
console.print('[bold white]deploy stdout: %s[/bold white]' % (deploy.stdout))
|
|
console.print('[bold red]deploy stderr: %s[/bold red]' % (deploy.stderr))
|
|
console.print('[bold white]deploy exit_code: %s[/bold white]' % (deploy.exit_status))
|
|
# console.print('[bold white]harden stdout: %s[/bold white]' % (sendDockerCompose.stdout))
|
|
# console.print('[bold red]harden stderr: %s[/bold red]' % (sendDockerCompose.stderr))
|
|
# console.print('[bold white]harden exit_code: %s[/bold white]' % (sendDockerCompose.exit_status))
|
|
console.print('[bold white]harden stdout: %s[/bold white]' % (actualDeployScript.stdout))
|
|
console.print('[bold red]harden stderr: %s[/bold red]' % (actualDeployScript.stderr))
|
|
console.print('[bold white]harden exit_code: %s[/bold white]' % (actualDeployScript.exit_status))
|
|
else:
|
|
node = driver.deploy_node(name=name, image=image, size=size, ex_keyname=keyname, deploy=deploy, ssh_key=SECDEP_SSH_PRIVATE_KEY, ssh_alternate_usernames=["admin", "ec2-user", "centos", "fedora", "ubuntu"])
|
|
console.print('[bold white]deploy stdout: %s[/bold white]' % (deploy.stdout))
|
|
console.print('[bold red]deploy stderr: %s[/bold red]' % (deploy.stderr))
|
|
console.print('[bold white]deploy exit_code: %s[/bold white]' % (deploy.exit_status))
|
|
console.print(node.name + " created successfully", style="bold white")
|
|
console.print("Node is initializing, please wait...", style="bold white")
|
|
console.print("ip to connect to", style="bold white")
|
|
console.print("[bold white]\nIP: %s[/bold white]" % (node.public_ips[0]))
|
|
console.print("[u]ssh command:[/u]", style="bold white")
|
|
if args.deploy:
|
|
console.print("[bold white]\nssh -p 22100 -i %s secdep@%s\n[/bold white]" % (SECDEP_SSH_PRIVATE_KEY, node.public_ips[0]))
|
|
else:
|
|
console.print("[bold white]\nssh -i %s secdep@%s\n[/bold white]" % (SECDEP_SSH_PRIVATE_KEY, node.public_ips[0]))
|
|
status.stop()
|
|
return node
|
|
|
|
def list_all_nodes(provider, filterIn=None, awsRegion=None):
|
|
console.print("Getting all nodes...", style="bold white")
|
|
status = Status("[bold white]Please wait...[/bold white]", spinner="dots")
|
|
status.start()
|
|
console.print("Loading 0%...", style="bold white")
|
|
nodes = []
|
|
if provider is None:
|
|
if SECDEP_GCE_CLIENT_ID != "":
|
|
console.print("Getting GCE nodes...", style="bold white")
|
|
driver = get_corresponding_driver("gce")
|
|
gceNodes = driver.list_nodes()
|
|
if len(gceNodes) > 0:
|
|
for node in gceNodes:
|
|
nodes.append(node)
|
|
console.print("[bold white]Loading %s%%...[/bold white]" % (int((1/providers_quantity)*100)))
|
|
else:
|
|
console.print("Skipping gce", style="bold red")
|
|
if SECDEP_AZURE_APP_ID != "":
|
|
console.print("Getting AZURE nodes...", style="bold white")
|
|
driver2 = get_corresponding_driver("azure")
|
|
azureNodes = driver2.list_nodes()
|
|
if len(azureNodes) > 0:
|
|
for node in azureNodes:
|
|
nodes.append(node)
|
|
if providers_quantity < 3:
|
|
console.print("[bold white]Loading %s%%...[/bold white]" % (int(((2/providers_quantity)/2)*100)))
|
|
else:
|
|
console.print("[bold white]Loading %s%%...[/bold white]" % (int((2/providers_quantity)*100)))
|
|
else:
|
|
console.print("Skipping azure", style="bold red")
|
|
if SECDEP_AWS_ACCESS_KEY != "":
|
|
driver3 = get_corresponding_driver("aws")
|
|
console.print("Getting AWS nodes...", style="bold white")
|
|
awsLocations = ["ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2"]
|
|
for region in awsLocations:
|
|
driver3 = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
# make it so it tries all drivers
|
|
awsNodes = driver3.list_nodes()
|
|
if len(awsNodes) > 0:
|
|
for node in awsNodes:
|
|
nodes.append(node)
|
|
if providers_quantity < 3:
|
|
console.print("[bold white]Loading %s%%...[/bold white]" % (int((providers_quantity/providers_quantity)*100)))
|
|
else:
|
|
console.print("[bold white]Loading %s%%...[/bold white]" % (int((3/providers_quantity)*100)))
|
|
else:
|
|
console.print("Skipping aws", style="bold red")
|
|
status.stop()
|
|
elif provider == "gce":
|
|
if SECDEP_GCE_CLIENT_ID != "":
|
|
console.print("Getting GCE nodes...", style="bold white")
|
|
driver = get_corresponding_driver("gce")
|
|
gceNodes = driver.list_nodes()
|
|
if len(gceNodes) > 0:
|
|
for node in gceNodes:
|
|
nodes.append(node)
|
|
console.print("[bold white]Loading 100%...[/bold white]")
|
|
else:
|
|
console.print("Skipping gce", style="bold red")
|
|
status.stop()
|
|
elif provider == "azure":
|
|
if SECDEP_AZURE_APP_ID != "":
|
|
console.print("Getting AZURE nodes...", style="bold white")
|
|
driver2 = get_corresponding_driver("azure")
|
|
azureNodes = driver2.list_nodes()
|
|
if len(azureNodes) > 0:
|
|
for node in azureNodes:
|
|
nodes.append(node)
|
|
console.print("[bold white]Loading 100%...[/bold white]")
|
|
else:
|
|
console.print("Skipping azure", style="bold red")
|
|
status.stop()
|
|
elif provider == "aws":
|
|
if SECDEP_AWS_ACCESS_KEY != "":
|
|
driver3 = get_corresponding_driver("aws")
|
|
console.print("Getting AWS nodes...", style="bold white")
|
|
awsLocations = ["ap-northeast-1", "ap-northeast-2", "ap-northeast-3", "ap-south-1", "ap-southeast-1", "ap-southeast-2", "ca-central-1", "eu-central-1", "eu-north-1", "eu-west-1", "eu-west-2", "eu-west-3", "sa-east-1", "us-east-1", "us-east-2", "us-west-1", "us-west-2"]
|
|
if awsRegion is None:
|
|
for region in awsLocations:
|
|
driver3 = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
# make it so it tries all drivers
|
|
awsNodes = driver3.list_nodes()
|
|
if len(awsNodes) > 0:
|
|
for node in awsNodes:
|
|
nodes.append(node)
|
|
else:
|
|
status.stop()
|
|
if awsRegion not in awsLocations:
|
|
console.print("[u]Invalid[/u] region", style="bold red")
|
|
awsRegion = choose_from_list(listAWSregions(awsLocations),"aws_region")
|
|
assert awsRegion is not None, "You chose an invalid aws region so we can't continue unless you choose a correct one"
|
|
driver3 = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=awsRegion)
|
|
awsNodes = driver3.list_nodes()
|
|
if len(awsNodes) > 0:
|
|
for node in awsNodes:
|
|
nodes.append(node)
|
|
console.print("[bold white]Loading 100%...[/bold white]")
|
|
else:
|
|
console.print("Skipping aws", style="bold red")
|
|
status.stop()
|
|
count = 0
|
|
if len(nodes) == 0:
|
|
console.print("No nodes", style="bold white")
|
|
exit(0)
|
|
# available states: running, rebooting, terminated, pending, stopped, suspended, paused, erro, unknown
|
|
# for delete
|
|
if filterIn == "delete":
|
|
nodes = list(filter(lambda x: 'running' in x.state.lower() or 'rebooting' in x.state.lower() or 'stopped' in x.state.lower() or 'suspended' in x.state.lower() or 'paused' in x.state.lower(), nodes))
|
|
# for start
|
|
if filterIn == "start":
|
|
nodes = list(filter(lambda x: 'stopped' in x.state.lower() or 'suspended' in x.state.lower() or 'paused' in x.state.lower(), nodes))
|
|
# for stop
|
|
if filterIn == "stop":
|
|
nodes = list(filter(lambda x: 'running' in x.state.lower() or 'rebooting' in x.state.lower(), nodes))
|
|
# for reboot
|
|
if filterIn == "reboot":
|
|
nodes = list(filter(lambda x: 'running' in x.state.lower() or 'suspended' in x.state.lower() or 'paused' in x.state.lower(), nodes))
|
|
for node in nodes:
|
|
count += 1
|
|
console.print("[bold white]{}) {}[/bold white]\n\n[bold cyan]State: {}[/bold cyan]\n[bold magenta]Public IPs: {}[/bold magenta]\n[bold blue]Private IPs: {}[/bold blue]\n[bold white]Driver: {}[/bold white]\n[bold cyan]Size: {}[/bold cyan]\n[bold magenta]Image: {}[/bold magenta]\n[bold blue]Creation Date: {}[/bold blue]\n[bold white]Extra: [/bold white]{}\n".format(count, node.name, node.state, node.public_ips, node.private_ips, node.driver, node.size, node.image, node.created_at, node.extra))
|
|
return nodes
|
|
|
|
def get_node(provider, awsRegion=None):
|
|
node = choose_from_list(list_all_nodes(provider, None, awsRegion), "node")
|
|
return node
|
|
|
|
def node_action(action, provider, awsRegion=None):
|
|
node = choose_from_list(list_all_nodes(provider, action, awsRegion), "node")
|
|
if node is None:
|
|
console.print("Nothing was chosen", style="bold white")
|
|
exit(0)
|
|
providerName = node.name.split("-")[0]
|
|
if providerName == "gce":
|
|
driver = get_corresponding_driver("gce")
|
|
elif providerName == "azure":
|
|
driver = get_corresponding_driver("azure")
|
|
node_name = node.name
|
|
credential = ClientSecretCredential(client_id=SECDEP_AZURE_APP_ID, client_secret=SECDEP_AZURE_PASSWORD, tenant_id=SECDEP_AZURE_TENANT_ID)
|
|
subscription_id = SECDEP_AZURE_SUB_ID
|
|
resource_client = ResourceManagementClient(credential, subscription_id)
|
|
network_client = NetworkManagementClient(credential, subscription_id)
|
|
elif providerName == "aws":
|
|
driver = get_corresponding_driver("aws")
|
|
assert driver is not None, "Driver is not set up correctly"
|
|
region = getAWSRegionFromAmi(node.extra['image_id'])
|
|
driver = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
match action:
|
|
case "reboot":
|
|
succeded = driver.reboot_node(node)
|
|
case "stop":
|
|
succeded = driver.stop_node(node)
|
|
case "start":
|
|
succeded = driver.start_node(node)
|
|
case "delete":
|
|
succeded = driver.destroy_node(node)
|
|
case _:
|
|
console.print("[u]Invalid[/u] action command", style="bold red")
|
|
exit(0)
|
|
if(succeded):
|
|
console.print("[bold white]%s node %s -> successful[/bold white]" % (providerName.upper(), action))
|
|
else:
|
|
console.print("[bold red]%s node %s -> failed[/bold red]" % (providerName.upper(), action))
|
|
if providerName == "azure" and action == "delete":
|
|
console.print("Deleting the corresponding resource group may take a while", style="bold white")
|
|
poller = resource_client.resource_groups.begin_delete(node_name+"-res_group")
|
|
result = poller.result()
|
|
|
|
def node_action_all(action, provider, awsRegion=None):
|
|
string = action[:-3]
|
|
nodes = list_all_nodes(provider, string, awsRegion)
|
|
node_name = ""
|
|
for node in nodes:
|
|
providerName = node.name.split("-")[0]
|
|
if providerName == "gce":
|
|
driver = get_corresponding_driver("gce")
|
|
elif providerName == "azure":
|
|
driver = get_corresponding_driver("azure")
|
|
node_name = node.name
|
|
credential = ClientSecretCredential(client_id=SECDEP_AZURE_APP_ID, client_secret=SECDEP_AZURE_PASSWORD, tenant_id=SECDEP_AZURE_TENANT_ID)
|
|
subscription_id = SECDEP_AZURE_SUB_ID
|
|
resource_client = ResourceManagementClient(credential, subscription_id)
|
|
network_client = NetworkManagementClient(credential, subscription_id)
|
|
elif providerName == "aws":
|
|
driver = get_corresponding_driver("aws")
|
|
assert driver is not None, "Driver is not set up correctly"
|
|
region = getAWSRegionFromAmi(node.extra['image_id'])
|
|
driver = get_driver(Provider.EC2)(SECDEP_AWS_ACCESS_KEY, SECDEP_AWS_SECRET_KEY, region=region)
|
|
match action:
|
|
case "rebootall":
|
|
succeded = driver.reboot_node(node)
|
|
case "stopall":
|
|
succeded = driver.stop_node(node)
|
|
case "startall":
|
|
succeded = driver.start_node(node)
|
|
case "deleteall":
|
|
succeded = driver.destroy_node(node)
|
|
case _:
|
|
console.print("[u]Invalid[/u] action command", style="bold red")
|
|
exit(0)
|
|
if(succeded):
|
|
console.print("[bold white]%s node %s -> successful[/bold white]" % (node.name, string))
|
|
else:
|
|
console.print("[bold red]%s node %s -> failed[/bold red]" % (node.name, string))
|
|
if providerName == "azure" and action == "delete":
|
|
console.print("Deleting the corresponding resource group may take a while", style="bold white")
|
|
poller = resource_client.resource_groups.begin_delete(node_name+"-res_group")
|
|
result = poller.result()
|
|
|
|
def ssh(provider, port=None, awsRegion=None):
|
|
node = choose_from_list(list_all_nodes(provider,"stop",awsRegion), "node")
|
|
ip = node.public_ips[0]
|
|
if port is None:
|
|
port = 22
|
|
else:
|
|
port = port
|
|
username = "secdep"
|
|
sshkey = SECDEP_SSH_PRIVATE_KEY
|
|
ssh = paramiko.SSHClient()
|
|
ssh.load_system_host_keys()
|
|
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
|
ssh.connect(ip, port=port, username=username, key_filename=sshkey)
|
|
channel = ssh.get_transport()
|
|
if channel is not None:
|
|
channel = channel.open_session()
|
|
channel.get_pty()
|
|
channel.invoke_shell()
|
|
while True:
|
|
command = prompt.ask('[bold white]$> [/bold white]')
|
|
if command == 'exit': break
|
|
channel.send((command + "\n").encode())
|
|
while True:
|
|
if channel.recv_ready():
|
|
output = channel.recv(1024)
|
|
console.print(output.decode(), style="bold white")
|
|
else:
|
|
time.sleep(0.5)
|
|
if not(channel.recv_ready()):
|
|
break
|
|
ssh.close()
|
|
|
|
if args.action and args.ssh:
|
|
console.print("You can\'t use [u]--ssh[/u] when using [u]--action[/u]", style="bold red")
|
|
exit(0)
|
|
if args.list and args.ssh:
|
|
console.print("You can\'t use [u]--ssh[/u] when using [u]--list[/u]", style="bold red")
|
|
exit(0)
|
|
if args.awsregion and args.provider != "aws":
|
|
console.print("AWS region flag as the name suggests only goes with the aws provider", style="bold red")
|
|
exit(0)
|
|
# If -I -S or -G is passed, provider must be passed as well
|
|
if args.listimages or args.listsizes or args.listlocations:
|
|
assert args.provider is not None, "Provider must be passed if listing images, sizes or locations"
|
|
if args.listimages and args.provider:
|
|
# If -I or --listimages is passed, call the list_provider_images function
|
|
if args.print:
|
|
console.print(get_provider_image(args.provider))
|
|
else:
|
|
list_provider_images(args.provider)
|
|
exit(0)
|
|
if args.listsizes and args.provider:
|
|
# If -S or --listsizes is passed, call the list_provider_sizes function
|
|
if args.print:
|
|
console.print(get_provider_size(args.provider))
|
|
else:
|
|
list_provider_sizes(args.provider)
|
|
exit(0)
|
|
if args.listlocations and args.provider:
|
|
# If -G or --listlocations is passed, call the list_provider_locations function
|
|
if args.print:
|
|
console.print(get_provider_location(args.provider))
|
|
else:
|
|
list_provider_locations(args.provider)
|
|
exit(0)
|
|
if args.create:
|
|
assert args.provider is not None, "Provider must be specified for node creation"
|
|
# If -c or --create is passed, call the create_node function
|
|
create_node(args.provider, args.name, args.region, args.size, args.image, args.yes, args.deploy[0])
|
|
exit(0)
|
|
if args.list:
|
|
if args.print:
|
|
console.print(get_node(args.provider, args.awsregion))
|
|
else:
|
|
list_all_nodes(args.provider, None, args.awsregion)
|
|
exit(0)
|
|
# If args.action contains the word all execute the node_action_all function, otherwise the node_action function
|
|
if args.action:
|
|
if(args.action.endswith("all")):
|
|
node_action_all(args.action, args.provider, args.awsregion)
|
|
else:
|
|
node_action(args.action, args.provider, args.awsregion)
|
|
exit(0)
|
|
if args.ssh:
|
|
ssh(args.provider, args.port, args.awsregion)
|
|
exit(0)
|
|
if args.image or args.size or args.name or args.region or args.yes or args.deploy and not args.create:
|
|
console.print("Image, size, name, region, yes and deploy parameters [u]only[/u] go along with the create flag", style="bold red")
|
|
exit(0)
|
|
if args.docker_compose and not args.deploy:
|
|
console.print("Docker compose [u]only[/u] goes along with the deploy flag", style="bold red")
|
|
exit(0)
|
|
if args.print and not args.list or args.listimages or args.listsizes or args.listlocations:
|
|
console.print("The print flag [u]only[/u] goes together with the list, list images, list sizes or list locations", style="bold red")
|
|
exit(0)
|
|
if args.port and not args.ssh:
|
|
console.print("The port flag [u]only[/u] goes with the ssh flag", style="bold red")
|
|
exit(0)
|