diff --git a/README.md b/README.md index 1bade24bf..39980a512 100644 --- a/README.md +++ b/README.md @@ -317,6 +317,19 @@ To provisioning multi-cloud infrastructures with CB-TB, it is necessary to regis ClientSecret: ... ``` + - Encrypt `credentials.yaml` into `credentials.yaml.enc` + To protect sensitive information, `credentials.yaml` is not used directly. Instead, it must be encrypted using `encCredential.sh`. The encrypted file `credentials.yaml.enc` is then used by `init.py`. This approach ensures that sensitive credentials are not stored in plain text. + + If you need to update your credentials, decrypt the encrypted file using `decCredential.sh`, make the necessary changes to `credentials.yaml`, and then re-encrypt it. + - Encrypting Credentials + ```bash + scripts/init/encCredential.sh + ``` + - Decrypting Credentials + ```bash + scripts/init/decCredential.sh + ``` + - Register all multi-cloud connection information and common resources - How to register diff --git a/scripts/init/README.md b/scripts/init/README.md index c8e98dfd7..12d63975f 100644 --- a/scripts/init/README.md +++ b/scripts/init/README.md @@ -15,6 +15,18 @@ The `init.py` script is designed to automate the process of registering credenti - The `python3-venv` package should be installed for running the script using `init.sh`. ## Usage + +### Encrypting Credentials +Before running `init.py`, you must encrypt your `credentials.yaml` file to ensure the security of your sensitive information. + +1. Use the `encCredential.sh` script to encrypt your `credentials.yaml` file: +```bash +scripts/init/encCredential.sh +``` + +The `init.py` script will decrypt the `credentials.yaml.enc` file as needed to read the credentials. You may need to provide a password if the decryption key is not stored. + + ### Direct Execution ```bash pip3 install -r requirements.txt @@ -42,9 +54,25 @@ Before running the script, ensure the following environment variables are set ac - `API_USERNAME`: Username for API authentication. - `API_PASSWORD`: Password for API authentication. +## Security Considerations +To protect sensitive information, `credentials.yaml` is not used directly. Instead, it must be encrypted using `encCredential.sh`. The encrypted file `credentials.yaml.enc` is then used by `init.py`. This approach ensures that sensitive credentials are not stored in plain text. + +If you need to update your credentials, decrypt the encrypted file using `decCredential.sh`, make the necessary changes to `credentials.yaml`, and then re-encrypt it. + +### Encrypting Credentials +```bash +scripts/init/encCredential.sh +``` + +### Decrypting Credentials +```bash +scripts/init/decCredential.sh +``` ## Related Files - `init.py`: Main Python script. - `requirements.txt`: Contains all Python dependencies. - `init.sh`: Bash script for setting up a Python virtual environment and running `init.py`. - `credentials.yaml`: Contains the credentials data to be registered with the Tumblebug server. +- `encCredential.sh`: Script to encrypt `credentials.yaml`. +- `decCredential.sh`: Script to decrypt `credentials.yaml.enc`. \ No newline at end of file diff --git a/scripts/init/decCredential.sh b/scripts/init/decCredential.sh new file mode 100755 index 000000000..01b1f98d1 --- /dev/null +++ b/scripts/init/decCredential.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Define colors for output +RED='\033[0;31m' +LGREEN='\033[1;32m' +PURPLE='\033[0;35m' +NC='\033[0m' # No Color +CYAN='\033[0;36m' +YELLOW='\033[1;33m' + +CRED_FILE_NAME="credentials.yaml" +CRED_PATH="$HOME/.cloud-barista" +FILE_PATH="$CRED_PATH/$CRED_FILE_NAME" +ENCRYPTED_FILE="$FILE_PATH.enc" +TEMP_DECRYPTED_FILE="$FILE_PATH.tmp" +KEY_FILE="$CRED_PATH/.tmp_enc_key" + +# Check if OpenSSL is installed +if ! command -v openssl &> /dev/null; then + echo -e "\n${RED}OpenSSL is not installed. Installation guide:${NC}" + echo -e "${LGREEN}Ubuntu/Debian:${NC} sudo apt-get install openssl" + echo -e "${LGREEN}CentOS/RHEL:${NC} sudo yum install openssl" + echo -e "${LGREEN}Fedora:${NC} sudo dnf install openssl" + echo -e "${LGREEN}Arch Linux:${NC} sudo pacman -S openssl\n" + exit 1 +fi + +# Check if the file is already decrypted +if [ -f "$FILE_PATH" ]; then + echo -e "\n${RED}The file is already decrypted.${NC}\n" + exit 0 +fi + +# Check if the encrypted file exists +if [ ! -f "$ENCRYPTED_FILE" ]; then + echo -e "\n${RED}The encrypted file does not exist: ${CYAN}$ENCRYPTED_FILE${NC}\n" + exit 1 +fi + +# Prompt for password or use the key file +if [ -f "$KEY_FILE" ]; then + TB_CRED_DECRYPT_KEY=$(cat "$KEY_FILE") + echo -e "\n${YELLOW}Using the temporary key file for decryption: ${CYAN}$KEY_FILE${NC}" + echo -e "${RED}Warning: It is not recommended to use temporary key file continuously. Please manage the key securely and delete the file after use.${NC}" +else + read -sp "Enter the password: " PASSWORD + echo + + if [ -z "$PASSWORD" ]; then + echo -e "\n${RED}Password is required.${NC}\n" + exit 1 + fi + + # Use the entered password + TB_CRED_DECRYPT_KEY=$PASSWORD +fi + +# Decrypt the file to a temporary file, suppressing OpenSSL error messages +DECRYPT_OUTPUT=$(openssl enc -aes-256-cbc -d -pbkdf2 -in "$ENCRYPTED_FILE" -out "$TEMP_DECRYPTED_FILE" -pass pass:"$TB_CRED_DECRYPT_KEY" 2>&1) + +# Check if decryption was successful +if [ $? -eq 0 ]; then + mv "$TEMP_DECRYPTED_FILE" "$FILE_PATH" + rm "$ENCRYPTED_FILE" + echo -e "\n${LGREEN}File successfully decrypted: ${CYAN}$FILE_PATH${NC}" + echo -e "(Encrypted file deleted: $ENCRYPTED_FILE)\n" +else + echo -e "\n${RED}Failed to decrypt the file. Exiting.${NC}\n" + if [ -f "$KEY_FILE" ]; then + echo -e "${RED}Failed to decrypt the file using the key file. Please delete the key file and retry with manual password input.${NC}" + fi + echo -e "${RED}log: ${DECRYPT_OUTPUT}${NC}\n" + rm -f "$TEMP_DECRYPTED_FILE" # Remove the temporary file if decryption failed + exit 1 +fi + diff --git a/scripts/init/encCredential.sh b/scripts/init/encCredential.sh new file mode 100755 index 000000000..4c12be1e6 --- /dev/null +++ b/scripts/init/encCredential.sh @@ -0,0 +1,132 @@ +#!/bin/bash + +# Define colors for output +RED='\033[0;31m' +LGREEN='\033[1;32m' +PURPLE='\033[0;35m' +NC='\033[0m' # No Color +CYAN='\033[0;36m' +YELLOW='\033[1;33m' + +CRED_FILE_NAME="credentials.yaml" +CRED_PATH="$HOME/.cloud-barista" +FILE_PATH="$CRED_PATH/$CRED_FILE_NAME" +ENCRYPTED_FILE="$FILE_PATH.enc" +TEMP_DECRYPTED_FILE="$FILE_PATH.tmp.dec" +KEY_FILE="$CRED_PATH/.tmp_enc_key" +SCRIPT_DIR=$(dirname "$(realpath "$0")") +DECRYPT_SCRIPT_PATH="$SCRIPT_DIR/decCredential.sh" + +# Check if OpenSSL is installed +if ! command -v openssl &> /dev/null; then + echo -e "\n${RED}OpenSSL is not installed. Installation guide:${NC}" + echo -e "${LGREEN}Ubuntu/Debian:${NC} sudo apt-get install openssl" + echo -e "${LGREEN}CentOS/RHEL:${NC} sudo yum install openssl" + echo -e "${LGREEN}Fedora:${NC} sudo dnf install openssl" + echo -e "${LGREEN}Arch Linux:${NC} sudo pacman -S openssl\n" + exit 1 +fi + +# Check if the file is already encrypted +if [ -f "$ENCRYPTED_FILE" ]; then + echo -e "\n${RED}The file is already encrypted.${NC}\n" + exit 0 +fi + +# Check if the file to be encrypted exists +if [ ! -f "$FILE_PATH" ]; then + echo -e "\n${RED}The file to be encrypted does not exist: ${CYAN}$FILE_PATH${NC}\n" + exit 1 +fi + +# Prompt to proceed with encryption +while true; do + echo -e "\nDo you want to encrypt the file ${CYAN}$FILE_PATH${NC}? (y/n): \c" + read -e CONFIRM + case $CONFIRM in + [Yy]* ) + break + ;; + [Nn]* ) + echo -e "\n${RED}Encryption process aborted.${NC}\n" + exit 0 + ;; + * ) + echo -e "\n${RED}Please answer yes or no.${NC}\n" + ;; + esac +done + +# Prompt for password +echo -e "Enter a password (press ${YELLOW}enter${NC} to generate a random key): \c" +read -sp "" PASSWORD +echo +if [ -n "$PASSWORD" ]; then + read -sp "Confirm the password: " PASSWORD_CONFIRM + echo + if [ "$PASSWORD" != "$PASSWORD_CONFIRM" ]; then + echo -e "\n${RED}Passwords do not match. Encryption aborted.${NC}\n" + exit 1 + fi + TB_CRED_DECRYPT_KEY=$PASSWORD + # Delete the existing key file if any + if [ -f "$KEY_FILE" ]; then + rm "$KEY_FILE" + fi + echo -e "\n${YELLOW}Remember the password you have entered. You will need it to decrypt the file.${NC}\n" +else + # Generate a random key + TB_CRED_DECRYPT_KEY=$(openssl rand -base64 64 | tr -d '\n') + echo -e "${YELLOW}A random key has been generated for encryption.${NC}\n" + while true; do + echo -e "Do you want to ${YELLOW}save${NC} the key to a temporary file or ${LGREEN}print${NC} it to stdout? (${YELLOW}s${NC}/${LGREEN}p${NC}): \c" + read -e OUTPUT_OPTION + case $OUTPUT_OPTION in + s ) + echo "$TB_CRED_DECRYPT_KEY" > "$KEY_FILE" + echo -e "\n${LGREEN}The encryption key has been saved to: ${CYAN}$KEY_FILE${NC}" + echo -e "${RED}Warning: It is not recommended to use this temporary file continuously. Please manage the key securely and delete the file after use.${NC}" + break + ;; + p ) + echo -e "\n${LGREEN}Encryption Key: ${CYAN}$TB_CRED_DECRYPT_KEY${NC}" + echo -e "${RED}Warning: Please copy and manage the key securely. This key will not be shown again.${NC}" + # Delete the existing key file if any + if [ -f "$KEY_FILE" ]; then + rm "$KEY_FILE" + fi + break + ;; + * ) + echo -e "${RED}Please answer 's' for save or 'p' for print.${NC}" + ;; + esac + done +fi + +# Encrypt the file +openssl enc -aes-256-cbc -salt -pbkdf2 -in "$FILE_PATH" -out "$ENCRYPTED_FILE" -pass pass:"$TB_CRED_DECRYPT_KEY" + +if [ $? -eq 0 ]; then + # Verify encryption by decrypting the file to a temporary file + openssl enc -aes-256-cbc -d -pbkdf2 -in "$ENCRYPTED_FILE" -out "$TEMP_DECRYPTED_FILE" -pass pass:"$TB_CRED_DECRYPT_KEY" + if [ $? -eq 0 ] && cmp -s "$FILE_PATH" "$TEMP_DECRYPTED_FILE"; then + rm "$TEMP_DECRYPTED_FILE" + rm "$FILE_PATH" + echo -e "\n${YELLOW}File successfully encrypted${NC}: ${CYAN}$ENCRYPTED_FILE${NC}" + echo -e "(Original file deleted: ${CYAN}$FILE_PATH${NC})\n" + echo -e "${YELLOW}To edit the credentials,${NC}" + echo -e "Use ${CYAN}$DECRYPT_SCRIPT_PATH${NC} to decrypt the file" + echo -e "Then edit ${CYAN}$FILE_PATH${NC}\n" + else + echo -e "\n${RED}Encryption verification failed.${NC}\n" + if [ $? -ne 0 ]; then + echo -e "${RED}Decryption failed during verification.${NC}\n" + else + echo -e "${RED}File comparison failed during verification.${NC}\n" + fi + rm "$TEMP_DECRYPTED_FILE" + fi +else + echo -e "\n${RED}Failed to encrypt the file.${NC}\n" +fi diff --git a/scripts/init/init.py b/scripts/init/init.py index bc2b5e2ad..882c25383 100755 --- a/scripts/init/init.py +++ b/scripts/init/init.py @@ -7,11 +7,12 @@ import argparse import threading from concurrent.futures import ThreadPoolExecutor, as_completed - +import subprocess import requests import yaml from tqdm import tqdm from colorama import Fore, init +from getpass import getpass parser = argparse.ArgumentParser(description="Automatically proceed without confirmation.") parser.add_argument('-y', '--yes', action='store_true', help='Automatically answer yes to prompts and proceed.') @@ -21,14 +22,16 @@ init(autoreset=True) # Configuration -TUMBLEBUG_SERVER = os.getenv('TUMBLEBUG_SERVER', 'localhost:1323') +TUMBLEBUG_SERVER = os.getenv('TUMBLEBUG_SERVER', 'localhost:1323') API_USERNAME = os.getenv('API_USERNAME', 'default') API_PASSWORD = os.getenv('API_PASSWORD', 'default') AUTH = f"Basic {base64.b64encode(f'{API_USERNAME}:{API_PASSWORD}'.encode()).decode()}" HEADERS = {'Authorization': AUTH, 'Content-Type': 'application/json'} -CRED_FILE_NAME = "credentials.yaml" +CRED_FILE_NAME_ENC = "credentials.yaml.enc" CRED_PATH = os.path.join(os.path.expanduser('~'), '.cloud-barista') +ENC_FILE_PATH = os.path.join(CRED_PATH, CRED_FILE_NAME_ENC) +KEY_FILE = os.path.join(CRED_PATH, ".tmp_enc_key") expected_completion_time_seconds = 240 @@ -36,8 +39,47 @@ if not os.path.exists(CRED_PATH): print(Fore.RED + "Error: CRED_PATH does not exist. Please run scripts/genCredential.sh first.") sys.exit(1) -elif not os.path.isfile(os.path.join(CRED_PATH, CRED_FILE_NAME)): - print(Fore.RED + "Error: CRED_FILE_NAME does not exist. Please check if it has been generated.") +elif not os.path.isfile(ENC_FILE_PATH): + print(Fore.RED + f"Error: {CRED_FILE_NAME_ENC} does not exist. Please check if it has been generated.") + print(Fore.RED + f"- This script does not accept 'credentials.yaml'. For your security, it only accepts an encrypted file.") + print(Fore.RED + f"- Please generate '{CRED_FILE_NAME_ENC}' using 'scripts/init/encCredential.sh'.") + sys.exit(1) + + +# Decrypt credentials.yaml.enc +def decrypt_credentials(enc_file_path, key): + try: + result = subprocess.run( + ['openssl', 'enc', '-aes-256-cbc', '-d', '-pbkdf2', '-in', enc_file_path, '-pass', f'pass:{key}'], + check=True, + capture_output=True + ) + if result.returncode != 0: + return None, "Decryption failed." + return result.stdout.decode('utf-8'), None + except subprocess.CalledProcessError as e: + return None, f"Decryption error: {e.stderr.decode('utf-8')}" + +def get_decryption_key(): + # Try using the key from the key file + if os.path.isfile(KEY_FILE): + with open(KEY_FILE, 'r') as kf: + key = kf.read().strip() + print(Fore.YELLOW + f"Using key from {KEY_FILE} to decrypt the credentials file.") + decrypted_content, error = decrypt_credentials(ENC_FILE_PATH, key) + if error is None: + return decrypted_content + print(Fore.RED + error) + + # Prompt for password up to 3 times if the key file is not used or fails + for attempt in range(3): + password = getpass(f"Enter the password to decrypt the credentials file (attempt {attempt + 1}/3): ") + decrypted_content, error = decrypt_credentials(ENC_FILE_PATH, password) + if error is None: + return decrypted_content + print(Fore.RED + error) + + print(Fore.RED + "Failed to decrypt the file after 3 attempts. Exiting.") sys.exit(1) # Print the current configuration @@ -46,7 +88,7 @@ print(" - " + Fore.CYAN + "API_USERNAME:" + Fore.RESET + f" {API_USERNAME[0]}**********") print(" - " + Fore.CYAN + "API_PASSWORD:" + Fore.RESET + f" {API_PASSWORD[0]}**********") print(" - " + Fore.CYAN + "CRED_PATH:" + Fore.RESET + f" {CRED_PATH}") -print(" - " + Fore.CYAN + "CRED_FILE_NAME:" + Fore.RESET + f" {CRED_FILE_NAME}") +print(" - " + Fore.CYAN + "CRED_FILE_NAME:" + Fore.RESET + f" {CRED_FILE_NAME_ENC}") print(" - " + Fore.CYAN + "expected completion time:" + Fore.RESET + f" {expected_completion_time_seconds} seconds\n") # Check server health before proceeding @@ -63,7 +105,6 @@ print(Fore.RED + f"Failed to connect to server. Check the server address and try again.") sys.exit(1) - # Wait for user input to proceed print(Fore.YELLOW + "Registering credentials and Loading common Specs and Images takes time") if not args.yes: @@ -72,10 +113,9 @@ print(Fore.GREEN + "See you soon. :)") sys.exit(0) -# Load credentials from YAML file -with open(os.path.join(CRED_PATH, CRED_FILE_NAME), 'r') as file: - cred_data = yaml.safe_load(file)['credentialholder']['admin'] - +# Get the decryption key and decrypt the credentials file +decrypted_content = get_decryption_key() +cred_data = yaml.safe_load(decrypted_content)['credentialholder']['admin'] print(Fore.YELLOW + f"\nRegistering all valid credentials for all cloud regions...") @@ -123,7 +163,6 @@ def load_resources(): finally: event.set() # Signal that the request is complete regardless of success or failure - # Start time start_time = time.time() @@ -144,7 +183,6 @@ def load_resources(): # Wait for the thread to complete thread.join() - # Calculate duration end_time = time.time() duration = end_time - start_time @@ -178,4 +216,4 @@ def load_resources(): print(Fore.RESET + "Registered Common images") print(Fore.GREEN + f"- Successful: {successful_images}" + Fore.RESET + f", Failed: {failed_images}") else: - print(Fore.RED + "No data returned from the API.") \ No newline at end of file + print(Fore.RED + "No data returned from the API.")