def main(args): if args.yes: maybe_changed = partial(maybe_changed_extended, use_default=True) else: maybe_changed = maybe_changed_extended generate_key('server', gpg_user_ids['server']) generate_key('client', gpg_user_ids['client']) import_key('server', gpg_user_ids['client']) import_key('client', gpg_user_ids['server']) default = not (get_client_setting('loaded') and get_server_setting('loaded')) do_config = default if args.yes else \ get_bool('Do you want to configure things interactively?', default) server_changed = client_changed = False if do_config: if isinstance(get_server_setting('port'), int): # Otherwise, the settings file has been edited to make the port # either a list of ports or a mapping, and we don't want to try to # configure it here. server_changed |= maybe_changed( 'server', 'port', get_int, 'What port should the server listen on?') server_changed |= maybe_changed( 'server', 'local_port', get_int, 'What local-only port should the server use?') configure_ssl = True port = get_server_setting('port') if isinstance(port, dict): for port_number, port_settings in port.items(): if 'ssl' in port_settings: # If there are already port-specific SSL settings, then # don't try to configure SSL in this script. configure_ssl = False if configure_ssl: default = bool( get_server_setting('ssl:certificate', None) or get_server_setting('ssl:key', None)) configure_ssl = maybe_get_bool( 'Do you want the server to use SSL?', default, args.yes) if not configure_ssl: if get_server_setting('ssl:certificate', None): set_server_setting('ssl:certificate', None) server_changed = True if get_server_setting('ssl:key', None): set_server_setting('ssl:key', None) server_changed = True else: while True: server_changed |= maybe_changed('server', 'ssl:certificate', get_string, 'SSL certificate file path:') if os.path.exists(get_server_setting('ssl:certificate')): break print('That file does not exist.') while True: server_changed |= maybe_changed('server', 'ssl:key', get_string, 'SSL key file path:') if os.path.exists(get_server_setting('ssl:key')): break print('That file does not exist.') server_changed |= maybe_changed('server', 'database:host', get_string_or_list, 'Database host:port:') if get_server_setting('database:host'): server_changed |= maybe_changed('server', 'database:replicaset', get_string_none, 'Replicaset name:', empty_ok=True) server_changed |= maybe_changed('server', 'database:name', get_string, 'Database name:') server_changed |= maybe_changed('server', 'database:username', get_string_none, 'Database username:'******'database:username'): server_changed |= maybe_changed('server', 'database:password', get_string, 'Database password:'******'Server', maybe_changed) server_changed |= maybe_changed( 'server', 'audit_cron:enabled', get_bool, 'Do you want to enable the audit cron job?') if get_server_setting('audit_cron:enabled'): server_changed |= maybe_changed( 'server', 'audit_cron:email', get_string, 'What email address should get the audit output?') port = get_server_setting('port') if port == 443: sample_url = 'https://hostname' elif port == 80: sample_url = 'http://hostname' else: sample_url = 'http://hostname:{}'.format(port) prompt = 'URL base, e.g., {}, for clients to reach server:'.format( sample_url) client_changed |= maybe_changed('client', 'server_url', get_string, prompt) client_changed |= maybe_changed('client', 'geolocation_api_key', get_string, 'Google geolocation API key, if any:', empty_ok=True) prompter = partial(get_int, minimum=1) client_changed |= maybe_changed( 'client', 'schedule:collect_interval', prompter, 'How often (minutes) do you want to collect data?') client_changed |= maybe_changed( 'client', 'schedule:submit_interval', prompter, 'How often (minutes) do you want re-try submits?') client_changed |= configure_logging('Client', maybe_changed) save_server_settings() if server_changed: print('Saved server settings.') save_client_settings() if client_changed: print('Saved client settings.') service_file = '/etc/systemd/system/penguindome-server.service' service_exists = os.path.exists(service_file) default = not service_exists if service_exists: prompt = ("Do you want to replace the server's systemd " "configuration?") else: prompt = 'Do you want to add the server to systemd?' do_service = maybe_get_bool(prompt, default, args.yes) if do_service: with NamedTemporaryFile('w+') as temp_service_file: temp_service_file.write( dedent('''\ [Unit] Description=PenguinDome Server After=network.target [Service] Type=simple ExecStart={server_exe} [Install] WantedBy=multi-user.target '''.format(server_exe=os.path.join(top_dir, 'bin', 'server')))) temp_service_file.flush() os.chmod(temp_service_file.name, 0o644) shutil.copy(temp_service_file.name, service_file) subprocess.check_output(('systemctl', 'daemon-reload'), stderr=subprocess.STDOUT) service_exists = True if service_exists: try: subprocess.check_output( ('systemctl', 'is-enabled', 'penguindome-server'), stderr=subprocess.STDOUT) except Exception: if maybe_get_bool('Do you want to enable the server?', True, args.yes): subprocess.check_output( ('systemctl', 'enable', 'penguindome-server'), stderr=subprocess.STDOUT) is_enabled = True else: is_enabled = False else: is_enabled = True if is_enabled: try: subprocess.check_output( ('systemctl', 'status', 'penguindome-server'), stderr=subprocess.STDOUT) except Exception: if maybe_get_bool('Do you want to start the server?', True, args.yes): subprocess.check_output( ('systemctl', 'start', 'penguindome-server'), stderr=subprocess.STDOUT) else: if maybe_get_bool('Do you want to restart the server?', server_changed, args.yes): subprocess.check_output( ('systemctl', 'restart', 'penguindome-server'), stderr=subprocess.STDOUT) if get_server_setting('audit_cron:enabled'): cron_file = '/etc/cron.d/penguindome-audit' cron_exists = os.path.exists(cron_file) if cron_exists: prompt = 'Do you want to replace the audit crontab?' else: prompt = 'Do you want to install the audit crontab?' do_crontab = maybe_get_bool(prompt, args.audit_crontab or not cron_exists, args.audit_crontab or args.yes) if do_crontab: email = get_server_setting('audit_cron:email') minute = int(random.random() * 60) minute2 = (minute + 1) % 60 crontab = dedent('''\ MAILTO={email} {minute2} * * * * root "{top_dir}/bin/issues" audit --cron '''.format(minute2=minute2, email=email, top_dir=top_dir)) with NamedTemporaryFile('w+') as temp_cron_file: temp_cron_file.write(crontab) temp_cron_file.flush() os.chmod(temp_cron_file.name, 0o644) shutil.copy(temp_cron_file.name, cron_file) print('Installed {}'.format(cron_file)) if client_changed or not glob.glob(os.path.join(releases_dir, '*.tar.asc')): if client_changed: prompt = ('Do you want to build a release with the new client ' 'settings?') else: prompt = 'Do you want to build a client release?' if maybe_get_bool(prompt, True, args.yes): # Sometimes sign fails the first time because of GnuPG weirdness. # The client_release script will call sign as well, but we call it # first just in case it fails the first time. try: subprocess.check_output((os.path.join('bin', 'sign'), ), stderr=subprocess.STDOUT) except Exception: pass subprocess.check_output((os.path.join('bin', 'client_release'), ), stderr=subprocess.STDOUT) print('Done!')
def enable_handler(args): if get_server_setting('secret_keeping:enabled'): sys.exit('Secret-keeping is already enabled.') if get_server_setting('secret_keeping:key_id'): if not (args.replace or args.preserve): sys.exit('Must specify --replace or --preserve.') else: args.replace = True args.shares = args.shares or \ get_server_setting('secret_keeping:num_shares') if args.shares < 2: sys.exit('--num-shares must be at least 2.') args.combine_threshold = args.combine_threshold or \ get_server_setting('secret_keeping:combine_threshold') if args.combine_threshold < 2: sys.exit('--combine-threshold must be at least 2.') if args.combine_threshold > args.shares: sys.exit( '--combine-threshold must be less than {}.'.format(args.shares + 1)) if args.replace: key_name = 'penguindome-secret-keeping-' + uuid.uuid4().hex output = gpg_command('--passphrase', '', '--quick-gen-key', key_name, with_trustdb=True, quiet=False) match = re.search(r'key (.*) marked as ultimately trusted', output) key_id = match.group(1) match = re.search(r'/([0-9A-F]+)\.rev', output) key_fingerprint = match.group(1) split_dir = os.path.join(var_dir, key_name) key_file = os.path.join(split_dir, 'private_key.asc') os.makedirs(split_dir) gpg_command('--export-secret-key', '--armor', '-o', key_file, key_id) subprocess.check_output(('gfsplit', '-n', str( args.combine_threshold), '-m', str(args.shares), key_file), stderr=subprocess.STDOUT) try: gpg_command('--delete-secret-keys', key_fingerprint) except subprocess.CalledProcessError as e: sys.exit('Failed to delete secret key:\n{}'.format( e.output.decode('utf8'))) subprocess.check_output(('shred', '-u', key_file), stderr=subprocess.STDOUT) with NamedTemporaryFile() as public_key_file: gpg_command('--export', '-o', public_key_file.name, key_id) set_gpg('client') try: gpg_command('--import', public_key_file.name) finally: set_gpg('server') set_server_setting('secret_keeping:key_name', key_name) set_server_setting('secret_keeping:key_id', key_id) set_server_setting('secret_keeping:key_fingerprint', key_fingerprint) set_client_setting('secret_keeping:key_id', key_id) set_server_setting('secret_keeping:num_shares', args.shares) set_server_setting('secret_keeping:combine_threshold', args.combine_threshold) set_server_setting('secret_keeping:enabled', True) save_server_settings() set_client_setting('secret_keeping:enabled', True) save_client_settings() print( distribute_secrets_note.format(m=args.shares, n=args.combine_threshold, split_dir=split_dir)) print(restart_note) log.info('Enabled secret-keeping')
def configure_client(args): changed = False url = get_client_setting('server_url') if url: url = list(urlparse(url)) else: url = ['', '', '', '', '', ''] if ':' in url[1]: hostname, port = url[1].split(':') port = int(port) else: hostname = url[1] port = 443 if url[0] == 'https' else 80 if args.hostname: if hostname != args.hostname: hostname = args.hostname changed = True if args.port: if port != args.port: port = args.port changed = True if port == 443 and args.ssl is None: args.ssl = True if args.ssl: if url[0] != 'https': url[0] = 'https' changed = True elif args.ssl is False: if url[0] != 'http': url[0] = 'http' changed = True if port == 443 and url[0] != 'https': print("\n" "WARNING: Are you sure you don't want to use SSL on port 443?\n") verbose_port = port or 80 try: server_port_ssl = get_port_setting( verbose_port, 'ssl:enabled', bool(get_port_setting(verbose_port, 'ssl:certificate'))) except: server_port_ssl = False if server_port_ssl and url[0] != 'https': print('\n' 'WARNING: Port {} on the server is using SSL.\n' ' Does the client need to?\n'.format(verbose_port)) elif not server_port_ssl and url[0] != 'http': print('\n' 'WARNING: Port {} on the server is not using SSL.\n' ' Are you sure the client should?\n'.format( verbose_port)) if not hostname: sys.exit('You must specify hostname.') if url[0] not in ('http', 'https'): changed = True url[0] = 'https' if port == 443 else 80 if port and ((url[0] == 'http' and port != 80) or (url[0] == 'https' and port != 443)): loc = '{}:{}'.format(hostname, port) if url[1] != loc: changed = True url[1] = loc else: url[1] = hostname if args.ssl_ca_file is False: if get_client_setting('ssl:ca_path'): set_client_setting('ssl:ca_path', None) changed = True elif args.ssl_ca_file: try: server_port = int(args.ssl_ca_file) except: pass else: args.ssl_ca_file = get_port_setting(server_port, 'ssl:certificate') if not args.ssl_ca_file: sys.exit('Server port {} does not have an SSL certificate.' .format(server_port)) client_file = os.path.join('client', 'cacert.pem') if not os.path.exists(args.ssl_ca_file): sys.exit('The file {} does not exist.'.format(args.ssl_ca_file)) if not (os.path.exists(client_file) and filecmp.cmp(args.ssl_ca_file, client_file)): shutil.copy(args.ssl_ca_file, client_file) changed = True if get_client_setting('ssl:ca_path') != client_file: set_client_setting('ssl:ca_path', client_file) changed = True url = urlunparse(url) url = re.sub(r'/+$', '', url) if changed: set_client_setting('server_url', url) save_client_settings() print('Updated client configuration.') show_configuration(args) print("\n" "WARNING: Don't forget to build a new client release.\n") else: print('Client configuration unchanged.')