summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-03-26 15:32:18 +0100
committerAntoine A <>2024-03-26 15:32:18 +0100
commit5c0c85e20c074daab41a64bfd97b17814542c3fb (patch)
tree25014c2cde8bd9dd6bfd42375d7899e83eb70331
parent56174c4dc7f0b8b3ec02fc165e473fd489ac66b2 (diff)
downloaddeployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.tar.gz
deployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.tar.bz2
deployment-5c0c85e20c074daab41a64bfd97b17814542c3fb.zip
regio: encrypt CONFIG_PASSWORD
-rwxr-xr-xregional-currency/config.py (renamed from regional-currency/ask_questions.py)162
-rwxr-xr-xregional-currency/install_packages.sh7
-rwxr-xr-xregional-currency/main.sh6
-rwxr-xr-xregional-currency/setup-libeufin.sh2
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"