diff options
author | Antoine A <> | 2024-03-26 15:32:18 +0100 |
---|---|---|
committer | Antoine A <> | 2024-03-26 15:32:18 +0100 |
commit | 5c0c85e20c074daab41a64bfd97b17814542c3fb (patch) | |
tree | 25014c2cde8bd9dd6bfd42375d7899e83eb70331 | |
parent | 56174c4dc7f0b8b3ec02fc165e473fd489ac66b2 (diff) | |
download | deployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.tar.gz deployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.tar.bz2 deployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.zip |
regio: encrypt CONFIG_PASSWORD
-rwxr-xr-x | regional-currency/config.py (renamed from regional-currency/ask_questions.py) | 162 | ||||
-rwxr-xr-x | regional-currency/install_packages.sh | 7 | ||||
-rwxr-xr-x | regional-currency/main.sh | 6 | ||||
-rwxr-xr-x | regional-currency/setup-libeufin.sh | 2 |
4 files changed, 143 insertions, 34 deletions
diff --git a/regional-currency/ask_questions.py b/regional-currency/config.py index 7b9b9b8..d00ff51 100755 --- a/regional-currency/ask_questions.py +++ b/regional-currency/config.py @@ -7,7 +7,17 @@ import re import subprocess import urllib.parse import uuid +import argon2 +from base64 import b64decode, b64encode from typing import Callable, Dict, TypeVar +from Crypto.Cipher import ChaCha20_Poly1305 +from Crypto.Hash import SHA512 +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Random import get_random_bytes + +# Early exit if already loaded +if os.environ.get("CONFIG_LOADED") == "y": + exit(0) log = open("setup.log", "ab", buffering=0) CONFIG_FILE = "config/user.conf" @@ -22,20 +32,30 @@ def load_conf() -> Dict[str, str]: for kv in f.read().splitlines(): if len(kv) != 0: [k, v] = [part.strip() for part in kv.split("=", 1)] - conf[k] = v.strip('"').replace('\\"', '"') + if v.startswith('"') and v.endswith('"'): + conf[k] = v.strip('"').replace('\\"', '"') + elif v.startswith("'") and v.endswith("'"): + conf[k] = v.strip("'").replace("\\'", "'") + else: + conf[k] = v return conf conf = load_conf() +result_conf = { + **conf, + "CONFIG_LOADED": "y" +} def add_conf(name: str, value: str): """Update a user configuration value and update the configuration file""" conf[name] = value + result_conf[name] = value content = "" for key, value in conf.items(): - escaped = value.replace('"', '\\"') - content += f'{key}="{escaped}"\n' + escaped = value.replace("'", "\\'") + content += f'{key}=\'{escaped}\'\n' with open(CONFIG_FILE, "w") as f: f.write(content) @@ -90,7 +110,7 @@ def conf_value( # Fetch current value if name is not None: - curr = conf.get(name, None) + curr = conf.get(name) if curr is not None: # Check the current value and ask again if invalid value = check(curr) @@ -244,51 +264,131 @@ def ask_yes_no(name: str | None, msg: str, default: bool | None = None) -> bool: return ask(name, msg, default, check_yes_no, lambda it: "y" if it else "n") +# ----- Crypto ----- # + + +def ask_config_password() -> str: + "Prompt the user to configure a password stored hashed with argon2id" + ph = argon2.PasswordHasher() + hash = conf.get("CONFIG_PASSWORD") + passwd = None + if hash is not None: + while True: + passwd = ask_str(None, "Enter the config password : ") + try: + ph.verify(hash, passwd) + break + except argon2.exceptions.VerifyMismatchError: + print("invalid password") + else: + passwd = ask_str(None, "1.1 Choose a config password : ") + + if hash is None or ph.check_needs_rehash(hash): + add_conf("CONFIG_PASSWORD", ph.hash(passwd)) + + return passwd + + +def ask_secret( + name: str, msg: str, passwd: str | None, default: str | None = None +) -> str: + "Prompt the user to configure a string stored encryped using pbkdf2_sha512 and chacha20_poly1305" + if passwd is None: + return ask_str(name, msg, default) + else: + raw = conf.get(name) + plaintext = None + if raw is not None: + method = "$pbkdf2_sha512_chacha20_poly1305$1000000$" + if raw.startswith(method): + salt, nonce, tag, ciphertext = [ + b64decode(it) for it in raw.removeprefix(method).split("$", 3) + ] + key = PBKDF2(passwd, salt, 32, count=1000000, hmac_hash_module=SHA512) + cipher = ChaCha20_Poly1305.new(key=key, nonce=nonce) + cipher.update(name.encode()) + plaintext = cipher.decrypt_and_verify(ciphertext, tag).decode() + else: + salt = get_random_bytes(16) + key = PBKDF2(passwd, salt, 32, count=1000000, hmac_hash_module=SHA512) + cipher = ChaCha20_Poly1305.new(key=key) + cipher.update(name.encode()) + ciphertext, tag = cipher.encrypt_and_digest(raw.encode()) + add_conf( + name, + f"$pbkdf2_sha512_chacha20_poly1305$1000000${base64.b64encode(salt).decode()}${base64.b64encode(cipher.nonce).decode()}${base64.b64encode(tag).decode()}${base64.b64encode(ciphertext).decode()}", + ) + else: + plaintext = ask_str(None, msg, default) + salt = get_random_bytes(16) + key = PBKDF2(passwd, salt, 32, count=1000000, hmac_hash_module=SHA512) + cipher = ChaCha20_Poly1305.new(key=key) + cipher.update(name.encode()) + ciphertext, tag = cipher.encrypt_and_digest(plaintext.encode()) + add_conf( + name, + f"$pbkdf2_sha512_chacha20_poly1305$1000000${base64.b64encode(salt).decode()}${base64.b64encode(cipher.nonce).decode()}${base64.b64encode(tag).decode()}${base64.b64encode(ciphertext).decode()}", + ) + result_conf[name] = plaintext + return plaintext + + +# ----- Prompt ----- # + +config_passwd = ( + ask_config_password() + if ask_yes_no( + "DO_CONFIG_ENCRYPTION", + "1. Do you want to encrypt sensitive config values (Y/n): ", + True, + ) + else None +) ask_currency( "CURRENCY", - "1. Enter the name of the regional currency (e.g. 'NETZBON'): ", + "2. Enter the name of the regional currency (e.g. 'NETZBON'): ", "NETZBON", ) do_conversion = ask_yes_no( "DO_CONVERSION", - "2. Do you want setup regional currency conversion to fiat currency (Y/n): ", + "3. Do you want setup regional currency conversion to fiat currency (Y/n): ", True, ) if do_conversion: ask_currency( "FIAT_CURRENCY", - "2.1. Enter the name of the fiat currency (e.g. 'CHF'): ", + "3.1. Enter the name of the fiat currency (e.g. 'CHF'): ", "CHF", ) iban = ask_iban( "FIAT_ACCOUNT_IBAN", - "2.2. Enter the IBAN of your fiat bank account (e.g. 'CH7789144474425692816'): ", + "3.2. Enter the IBAN of your fiat bank account (e.g. 'CH7789144474425692816'): ", ) bic = ask_bic( "FIAT_ACCOUNT_BIC", - "2.3. Enter the BIC of your fiat bank account (e.g. 'POFICHBEXXX'): ", + "3.3. Enter the BIC of your fiat bank account (e.g. 'POFICHBEXXX'): ", ) name = ask_str( - "FIAT_ACCOUNT_NAME", "2.4. Enter the legal name of your fiat bank account: " + "FIAT_ACCOUNT_NAME", "3.4. Enter the legal name of your fiat bank account: " ) params = urllib.parse.urlencode({"receiver-name": name}) add_conf("CONVERSION_PAYTO", f"payto://iban/{bic}/{iban}?{params}") bank_name = ask_str( "BANK_NAME", - "3. Enter the human-readable name of the bank (e.g. 'Taler Bank'): ", + "4. Enter the human-readable name of the bank (e.g. 'Taler Bank'): ", "Taler Bank", ) -ask_host("DOMAIN_NAME", "4. Enter the domain name (e.g. 'example.com'): ") -if ask_yes_no("ENABLE_TLS", "5. Setup TLS using Let's Encrypt? (Y/n): ", True): - ask_str("TLS_EMAIL", "5.1. Enter an email address for Let's Encrypt: ") +ask_host("DOMAIN_NAME", "5. Enter the domain name (e.g. 'example.com'): ") +if ask_yes_no("ENABLE_TLS", "6. Setup TLS using Let's Encrypt? (Y/n): ", True): + ask_str("TLS_EMAIL", "6.1. Enter an email address for Let's Encrypt: ") def ask_tos(): print( - "5.2. Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf." + "6.2. Please read the Terms of Service at https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf." ) if not ask_yes_no( None, - "5.2. You must agree in order to register with the ACME server. Do you agree? (y/n): ", + "6.2. You must agree in order to register with the ACME server. Do you agree? (y/n): ", False, ): print("You must agree in order to register with the ACME server") @@ -307,13 +407,13 @@ add_conf( if ask_yes_no( "DO_TELESIGN", - "6. Setup SMS two-factor authentication using Telesign https://www.telesign.com? (Y/n): ", + "7. Setup SMS two-factor authentication using Telesign https://www.telesign.com? (Y/n): ", True, ): def ask_telesign(): - customer_id = ask_str(None, "6.1. Enter your Telesign Customer ID: ") - api_key = ask_str(None, "6.2. Enter your Telesign API Key: ") + customer_id = ask_str(None, "7.1. Enter your Telesign Customer ID: ") + api_key = ask_str(None, "7.2. Enter your Telesign API Key: ") phone_number = ask_str( None, "6.3. Enter a phone number to test your API key (e.g. '+447911123456'): ", @@ -328,7 +428,7 @@ if ask_yes_no( "Failed to send an SMS using Telesign API, check your credentials and phone number" ) return None - code = ask_str(None, f"6.4. Enter the code received by {phone_number} : ") + code = ask_str(None, f"7.4. Enter the code received by {phone_number} : ") if code != "12345" and code != "T-12345": print( f"Wrong code got '{code}' expected '12345', check your credentials and phone number" @@ -337,30 +437,40 @@ if ask_yes_no( return auth_token conf_value("TELESIGN_AUTH_TOKEN", ask_telesign) -ask_str( +ask_secret( "BANK_ADMIN_PASSWORD", - "7. Enter the admin password for the bank (or press enter to autogenerate password): ", + "8. Enter the admin password for the bank (or press enter to autogenerate password): ", + config_passwd, str(uuid.uuid4()), ) if ask_yes_no( "DO_EXCHANGE_TERMS", - "8. Do you wish to configure terms of service (ToS) for the exchange? (Y/n): ", + "9. Do you wish to configure terms of service (ToS) for the exchange? (Y/n): ", True, ): ask_terms( "EXCHANGE_TERMS_FILE", - "8.1. Enter the filename of the ToS. Some available options are:\n", + "9.1. Enter the filename of the ToS. Some available options are:\n", "-tos-", ) if ask_yes_no( "DO_EXCHANGE_PRIVACY", - "9. Do you wish to configure a privacy policy for the exchange? (Y/n): ", + "10. Do you wish to configure a privacy policy for the exchange? (Y/n): ", True, ): ask_terms( "EXCHANGE_PRIVACY_FILE", - "9.1. Enter the filename of the privacy policy. Some available options are:\n", + "10.1. Enter the filename of the privacy policy. Some available options are:\n", "-pp-", ) + +# ----- Return conf ----- # + +content = "" +for key, value in result_conf.items(): + escaped = value.replace('\'', '\\\'') + content += f'export {key}=\'{escaped}\'\n' +with os.fdopen(3, "w") as f: + f.write(content) diff --git a/regional-currency/install_packages.sh b/regional-currency/install_packages.sh index 466f6d7..44e4377 100755 --- a/regional-currency/install_packages.sh +++ b/regional-currency/install_packages.sh @@ -4,8 +4,6 @@ set -eu source functions.sh -source config/user.conf -source config/internal.conf detect_distro @@ -38,7 +36,9 @@ apt install \ pip3 install --break-system-packages \ sphinx-markdown-builder \ - htmlark &>> setup.log + htmlark \ + argon2-cffi \ + pycryptodome &>> setup.log ## Add GNU Taler deb.taler.net to /etc/apt/sources.list @@ -48,6 +48,7 @@ say "Detected distro $DISTRO" case $DISTRO in debian) if test ${APT_NIGHTLY:-n} == y; then + say "Setup nightly packages" echo "deb [trusted=yes] https://deb.taler.net/apt-nightly bookworm main" >/etc/apt/sources.list.d/taler.list else echo "deb [signed-by=/etc/apt/keyrings/taler-systems.gpg] https://deb.taler.net/apt/debian bookworm main" >/etc/apt/sources.list.d/taler.list diff --git a/regional-currency/main.sh b/regional-currency/main.sh index 725549a..6793c4e 100755 --- a/regional-currency/main.sh +++ b/regional-currency/main.sh @@ -18,8 +18,6 @@ source functions.sh # include variables from configuration mkdir -p config/ touch config/user.conf config/internal.conf -# Values supplied by user -source config/user.conf # Values we generated source config/internal.conf @@ -44,8 +42,7 @@ say "Installing packages (step 1 of 6)" say "" say "Interactive configuration (step 2 of 6)" -./ask_questions.py -source config/user.conf +{ source <(./config.py 3>&1 >&4 4>&-); } 4>&1 # Remove when libeufin currencies.conf is in sync with exchange cat >>/usr/share/libeufin/config.d/netzbon.conf <<EOF @@ -66,6 +63,7 @@ fi if test -z "${BANK_PORT:-}"; then echo "BANK_PORT=8080" >>config/user.conf + export BANK_PORT=8080 fi say "" diff --git a/regional-currency/setup-libeufin.sh b/regional-currency/setup-libeufin.sh index 9b563e3..efbd714 100755 --- a/regional-currency/setup-libeufin.sh +++ b/regional-currency/setup-libeufin.sh @@ -7,7 +7,7 @@ set -eu source functions.sh -source config/user.conf +{ source <(./config.py 3>&1 >&4 4>&-); } 4>&1 source config/internal.conf say "Beginning LibEuFin setup" |