diff --git a/cf_remote/commands.py b/cf_remote/commands.py index 90c499d..c21dfff 100644 --- a/cf_remote/commands.py +++ b/cf_remote/commands.py @@ -14,6 +14,7 @@ transfer_file, deploy_masterfiles, ) +import cf_remote.demo as demo_lib from cf_remote.packages import Releases from cf_remote.web import download_package from cf_remote.paths import ( @@ -229,12 +230,18 @@ def install( "\n".join(bootstrap + [""]), ) + hub_passwords = {} hub_jobs = [] if hubs: show_host_info = len(hubs) == 1 if type(hubs) is str: hubs = [hubs] for index, hub in enumerate(hubs): + if demo: + password, salt, sha = demo_lib.generate_password() + hub_passwords[hub] = password + else: + salt, sha = None, None hub_jobs.append( HostInstaller( hub, @@ -249,6 +256,8 @@ def install( remote_download=remote_download, trust_keys=trust_keys, insecure=insecure, + demo_salt=salt, + demo_sha=sha, ) ) @@ -293,8 +302,8 @@ def install( if demo and hubs: for hub in hubs: print( - "Your demo hub is ready: https://{}/ (Username: admin, Password: password)".format( - strip_user(hub) + "Your demo hub is ready: https://{}/ (Username: admin, Password: {})".format( + strip_user(hub), hub_passwords[hub] ) ) diff --git a/cf_remote/demo.py b/cf_remote/demo.py index cdfc7c1..5a7fc70 100644 --- a/cf_remote/demo.py +++ b/cf_remote/demo.py @@ -1,5 +1,10 @@ import os import json +import hashlib +import secrets +import shutil +import string +import tempfile from posixpath import dirname, join from cf_remote import log @@ -22,18 +27,47 @@ def agent_run(data, *, connection=None): log.debug(output) +def generate_password(): + """Generate credentials for the demo admin user. + + Returns (password, salt, sha) where sha is the hex SHA-256 of + salt + password concatenated with no separator. The password is meant + to be shown to the user; only the salt and sha are sent to the host. + """ + password = "".join(secrets.choice(string.ascii_letters) for _ in range(14)) + salt = "".join(secrets.choice(string.ascii_letters) for _ in range(10)) + sha = hashlib.sha256((salt + password).encode("utf-8")).hexdigest() + return password, salt, sha + + @auto_connect -def disable_password_dialog(host, *, connection=None): - print("Disabling password change on hub: '{}'".format(host)) +def setup_demo_admin_user(host, salt, sha, *, connection=None): + print("Setting up demo admin user on hub: '{}'".format(host)) - query_path = join(dirname(__file__), "demo.sql") - scp(query_path, host, connection=connection) + template_path = join(dirname(__file__), "demo.sql") + with open(template_path, "r") as f: + sql = f.read() + sql = sql.replace("__CF_REMOTE_SHA__", sha).replace("__CF_REMOTE_SALT__", salt) - query = os.path.basename(query_path) - ssh_sudo( - connection, - '/var/cfengine/bin/psql cfsettings -f "{}"'.format(query), - ) + # The SQL file contains the password salt and SHA. mkdtemp creates the + # directory with 0700 perms, so anything inside is protected from other + # local users. + tmp_dir = tempfile.mkdtemp(prefix="cf-remote-demo-") + try: + rendered_path = os.path.join(tmp_dir, "demo.sql") + with open(rendered_path, "w") as f: + f.write(sql) + scp(rendered_path, host, connection=connection) + query = os.path.basename(rendered_path) + try: + ssh_sudo( + connection, + '/var/cfengine/bin/psql cfsettings -f "{}"'.format(query), + ) + finally: + ssh_cmd(connection, 'rm -f "{}"'.format(query)) + finally: + shutil.rmtree(tmp_dir, ignore_errors=True) def def_json(call_collect=False): diff --git a/cf_remote/demo.sql b/cf_remote/demo.sql index ed6a087..edfd215 100644 --- a/cf_remote/demo.sql +++ b/cf_remote/demo.sql @@ -13,8 +13,8 @@ INSERT INTO "users" ("username", "roles", "changetimestamp") SELECT 'admin', - 'SHA=7f062dc2ef82d2b87f012fc17d70c372aa4e2883d9b6c5c1cc7382a5c868b724', - 'eWAbKQmxNP', + 'SHA=__CF_REMOTE_SHA__', + '__CF_REMOTE_SALT__', 'admin', 'admin@organisation.com', FALSE, @@ -23,5 +23,5 @@ SELECT 'admin', now() ON CONFLICT (username, EXTERNAL) DO UPDATE -SET password = 'SHA=7f062dc2ef82d2b87f012fc17d70c372aa4e2883d9b6c5c1cc7382a5c868b724', - salt = 'eWAbKQmxNP'; +SET password = 'SHA=__CF_REMOTE_SHA__', + salt = '__CF_REMOTE_SALT__'; diff --git a/cf_remote/remote.py b/cf_remote/remote.py index b8aee65..1ca89bd 100644 --- a/cf_remote/remote.py +++ b/cf_remote/remote.py @@ -661,7 +661,9 @@ def install_host( show_info=True, remote_download=False, trust_keys=None, - insecure=False + insecure=False, + demo_salt=None, + demo_sha=None, ): data = get_info(host, connection=connection) if show_info: @@ -758,7 +760,9 @@ def install_host( host, connection=connection, call_collect=call_collect ) demo_lib.agent_run(data, connection=connection) - demo_lib.disable_password_dialog(host, connection=connection) + demo_lib.setup_demo_admin_user( + host, demo_salt, demo_sha, connection=connection + ) demo_lib.agent_run(data, connection=connection) return 0