Esempio n. 1
0
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')
Esempio n. 3
0
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.')