def deselect_handler(args): selectors = get_server_setting(selectors_setting) if isinstance(selectors, str): selectors = [selectors] set_server_setting(selectors_setting, selectors) if selectors is None: selectors = [] set_server_setting(selectors_setting, selectors) errors = False for selector in args.selector: try: selectors.remove(selector) except ValueError: sys.stderr.write( 'Selector {} is not currently selected.\n'.format(selector)) errors = True if errors: sys.exit(1) for selector in args.selector: log.info('Removing secret-keeping selector {}', selector) save_server_settings() set_client_setting(selectors_setting, selectors) save_client_settings() print(restart_note)
def disable_handler(args): if not get_server_setting('secret_keeping:enabled'): sys.exit('Secret-keeping is not enabled.') set_server_setting('secret_keeping:enabled', False) save_server_settings() set_client_setting('secret_keeping:enabled', False) save_client_settings() print(restart_note) log.info('Disabled secret-keeping')
def remove_port(args): port = args.port ports = get_server_setting('port') if not ports: sys.exit('There are no ports to remove!') if isinstance(ports, int) or len(ports) == 1: sys.exit("You can't remove the only configured port!") if port not in ports: sys.exit('Port {} is not currently configured'.format(port)) if isinstance(ports, list): ports.remove(port) else: ports.pop(port) set_server_setting('port', ports) save_server_settings() print('Removed port {}.'.format(port)) show_configuration(args)
def select_handler(args): selectors = get_server_setting(selectors_setting) if isinstance(selectors, str): selectors = [selectors] set_server_setting(selectors_setting, selectors) if selectors is None: selectors = [] set_server_setting(selectors_setting, selectors) errors = False for selector in args.selector: if selector in selectors: sys.stderr.write( 'Selector {} is already added.\n'.format(selector)) errors = True if errors: sys.exit(1) if not args.force: db = get_db() for selector in args.selector: if not db.clients.find_one({selector: { '$exists': True }}, projection=[]): sys.stderr.write( 'Selector {} does not match anything.\n' 'Specify --force to save anyway.\n'.format(selector)) errors = True if errors: sys.exit(1) for selector in args.selector: log.info('Adding secret-keeping selector {}', selector) selectors.extend(args.selector) save_server_settings() set_client_setting(selectors_setting, selectors) save_client_settings() print(restart_note)
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 and not args.skipsystemctl: 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 and not args.skipsystemctl: 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')) and not args.skipbuildrelease): 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_port(args, add=False): if args.ssl_self_signed and (args.certificate or args.key): sys.exit('--certificate and --key are incompatible with ' '--ssl-self-signed.') changed = False port = args.port ports = get_server_setting('port', None) if not ports: ports = [] elif isinstance(ports, int): ports = [ports] if isinstance(ports, list): ports = {port: {} for port in ports} set_server_setting('port', ports) if port in ports: if add: sys.exit('Port {} is already present.'.format(args.port)) which = 'Configured' else: ports[port] = {} changed = True which = 'Added' if not ports[port]: ports[port] = {} port_settings = ports[port] gps = partial(get_port_setting, port) def ss(setting, value): nonlocal changed set_setting(port_settings, setting, value) changed = True if args.deprecated is not None: if bool(gps('deprecated')) != args.deprecated: ss('deprecated', args.deprecated) if args.ssl_self_signed: cert_file, key_file = make_self_signed_cert(args.ssl_self_signed) ss('ssl:certificate', cert_file) ss('ssl:key', key_file) if args.ssl is not False: args.ssl = True if args.certificate and gps('ssl:certificate') != args.certificate: if not (args.key or gps('ssl:key')): sys.exit('You must specify both a certificate and a key.') if not os.path.exists(args.certificate): sys.exit('The certificate file {} does not exist'.format( args.certificate)) ss('ssl:certificate', args.certificate) if args.key and gps('ssl:key') != args.key: if not gps('ssl:certificate'): sys.exit('You must specify both a certificate and a key.') if not os.path.exists(args.key): sys.exit('The key file {} does not exist'.format( args.key)) ss('ssl:key', args.key) if args.ssl: if not (gps('ssl:certificate') and gps('ssl:key')): sys.exit('You must specify a certificate and key to enable SSL.') if not gps('ssl:enabled', bool(gps('ssl:certificate'))): ss('ssl:enabled', True) elif args.ssl is False: if gps('ssl:enabled', bool(gps('ssl:certificate'))): ss('ssl:enabled', False) if changed: save_server_settings() url = get_client_setting('server_url') if url: url = urlparse(url) if ':' in url[1]: _, client_port = url[1].split(':') client_port = int(client_port) else: client_port = {'http': 80, 'https': 443}[url[0]] client_ssl = {'http': False, 'https': True}[url[0]] if port == client_port: if client_ssl != gps( 'ssl:enabled', bool(gps('ssl:certificate'))): print('\n' 'WARNING: Client is configured to use port {} and {}' 'using SSL.\n' ' Should it be?\n'.format( port, '' if client_ssl else 'not ')) if gps('deprecated'): print('\n' 'WARNING: Client is configured to use deprecated ' 'port {}.\n' ' Do you need to change the client port?'. format(port)) print('{} port {}.'.format(which, port)) print("\n" "WARNING: Don't forget to restart the server.\n") if args.ssl_self_signed and args.ssl: print("\n" "WARNING: Don't forget to configure client CA file\n" " (see help for 'configure-client').\n") show_configuration(args) else: print('No changes.')