def setup_nodes(cp: ConfigParser) -> None:
    print('==== Nodes')
    print('To produce alerts, the alerter needs something to monitor! The list '
          'of nodes to be included in the monitoring will now be set up. This '
          'includes validators, sentries, and any full nodes that can be used '
          'as data sources to monitor from the network\'s perspective, together'
          ' with the Node Exporter URL to be used to monitor the system. You '
          'may include nodes from multiple networks in any order; PANIC '
          'will figure out which network they belong to when you run it. Node '
          'names must be set identical to the ones previously set in the API '
          'Server/s!')

    # Check if list already set up
    if len(cp.sections()) > 0 and \
            not yn_prompt('The list of nodes is already set up. Do you wish to '
                          'clear this list? You will then be asked to set up a '
                          'new list of nodes, if you wish to do so (Y/n)\n'):
        return

    # Clear config and initialise new list
    cp.clear()
    nodes = []

    # Ask if they want to set it up
    if not yn_prompt('Do you wish to set up the list of nodes? (Y/n)\n'):
        return

    # Get node details and append them to the list of nodes
    while True:
        # Check that API is running by retrieving some data which will be used.
        oasis_api_data_wrapper = OasisApiWrapper(DUMMY_LOGGER)
        node = get_node(nodes, oasis_api_data_wrapper)

        if node is not None:
            nodes.append(node)
            if node.node_is_validator:
                print('Successfully added validator node.')
            else:
                print('Successfully added full node.')

        if not yn_prompt('Do you want to add another node? (Y/n)\n'):
            break

    # Add nodes to config
    for i, node in enumerate(nodes):
        section = 'node_' + str(i)
        cp.add_section(section)
        cp[section]['node_name'] = node.node_name
        cp[section]['chain_name'] = node.chain_name
        cp[section]['node_api_url'] = node.node_api_url
        cp[section]['node_public_key'] = node.node_public_key
        cp[section]['node_is_validator'] = \
            'true' if node.node_is_validator else 'false'
        cp[section]['node_exporter_url'] = node.node_exporter_url
        cp[section]['monitor_node'] = \
            'true' if node.monitor_node else 'false'
        cp[section]['is_archive_node'] = \
            'true' if node.is_archive_node else 'false'
        cp[section]['use_as_data_source'] = \
            'true' if node.use_as_data_source else 'false'
Exemple #2
0
def get_node(nodes_so_far: List[NodeConfig]) -> Optional[NodeConfig]:
    # Get node's name
    node_names_so_far = [n.node_name for n in nodes_so_far]
    while True:
        node_name = input('Unique node name:\n')
        if node_name in node_names_so_far:
            print('Node name must be unique.')
        else:
            break

    # Get node's RPC url
    while True:
        rpc_url = input('Node\'s RPC url (typically http://NODE_IP:26657):\n')
        print('Trying to connect to endpoint {}/health'.format(rpc_url))
        try:
            get_json(rpc_url + '/health', DUMMY_LOGGER)
            print('Success.')
            break
        except Exception:
            if not yn_prompt('Failed to connect to endpoint. Do '
                             'you want to try again? (Y/n)\n'):
                return None

    # Ask if node is a validator
    node_is_validator = yn_prompt('Is this node a validator? (Y/n)\n')

    # Return node
    return NodeConfig(node_name, rpc_url, node_is_validator, True, True)
Exemple #3
0
def get_repo() -> Optional[RepoConfig]:
    # Get repo name
    repo_name = input('GitHub repository name (to know which repository '
                      'triggered an alert, example: Substrate):\n')

    # Get repo page
    while True:
        repo_page = input('Official GitHub repository (example: '
                          'w3f/substrate/):\n')
        if not repo_page.endswith('/'):
            repo_page = repo_page + '/'

        releases_page = InternalConf.github_releases_template.format(repo_page)
        print('Trying to connect to {}'.format(releases_page))
        try:
            releases = get_json(releases_page, DUMMY_LOGGER)
            if 'message' in releases and releases['message'] == 'Not Found':
                if not yn_prompt('Connection successful, but URL is not valid. '
                                 'Do you want to try again? (Y/n)\n'):
                    return None
            else:
                break  # success message left to setup_repos function
        except Exception:
            if not yn_prompt('Failed to connect to page. Do '
                             'you want to try again? (Y/n)\n'):
                return None

    # Return node
    return RepoConfig(repo_name, repo_page, True)
Exemple #4
0
def get_node(nodes_so_far: List[NodeConfig],
             polkadot_api_data_wrapper: PolkadotApiWrapper,
             web_sockets_connected_to_api: List) -> Optional[NodeConfig]:
    # Get node's name
    node_names_so_far = [n.node_name for n in nodes_so_far]
    while True:
        node_name = input('Unique node name:\n')
        if node_name in node_names_so_far:
            print('Node name must be unique.')
        else:
            break

    # Get node's WS url
    while True:
        ws_url = input('Node\'s WS url (typically ws://NODE_IP:9944):\n')
        if ws_url in web_sockets_connected_to_api:
            print('Testing connection with node {}'.format(ws_url))
            try:
                polkadot_api_data_wrapper.ping_node(ws_url)
                print('Success.')
                break
            except Exception:
                if not yn_prompt('Failed to connect with node {}. Do you want '
                                 'to try again? (Y/n)\n'.format(ws_url)):
                    return None
        else:
            if not yn_prompt(
                    'Could not connect with the API Server at web socket '
                    '{}. Please make sure that the node was added in the '
                    'API\'s config. Do you want to try again? (Y/n)\n'.format(
                        ws_url, polkadot_api_data_wrapper.api_endpoint,
                        polkadot_api_data_wrapper.api_endpoint)):
                return None

    # Ask if node is a validator
    node_is_validator = yn_prompt('Is this node a validator? (Y/n)\n')

    # Ask if node is an archive node.
    # Note: if the node is a validator, it must also be an archive node.
    #       However, it was done this way in case of changes in future updates.
    node_is_archive_node = yn_prompt('Is this node an archive node? (Y/n)\n')

    # Get validator's stash account address.
    if node_is_validator:
        while True:
            stash_account_address = input('Please enter the validator\'s stash '
                                          'account address:\n')
            if not stash_account_address.strip():
                if not yn_prompt('You cannot leave the stash_account_address '
                                 'field empty for a validator. Do you want to '
                                 'try again? (Y/n)\n'):
                    return None
            else:
                break
    else:
        stash_account_address = ''

    # Return node
    return NodeConfig(node_name, ws_url, node_is_validator,
                      node_is_archive_node, True, True, stash_account_address)
def setup_redis(cp: ConfigParser) -> None:
    print('==== Redis')
    print('Redis is used by the alerter to persist data every now and then, '
          'so that it can continue where it left off if it is restarted. It '
          'is also used to be able to get the status of the alerter and to '
          'have some control over it, such as to snooze Twilio phone calls.')

    if is_already_set_up(cp, 'redis') and \
            not yn_prompt('Redis is already set up. Do you wish '
                          'to clear the current config? (Y/n)\n'):
        return

    reset_section('redis', cp)
    cp['redis']['enabled'] = str(False)
    cp['redis']['host'] = ''
    cp['redis']['port'] = ''
    cp['redis']['password'] = ''

    if not yn_prompt('Do you wish to set up Redis? (Y/n)\n'):
        return

    while True:
        print('You will now be asked to input the IP of the Redis server.\n'
              'If you will be running PANIC using Docker, do not use '
              'localhost, instead use the full IP address (local or external) '
              'of the machine that the Redis container will be running on.')
        host = input('Please insert the Redis host IP: (default: localhost)\n')
        host = 'localhost' if host == '' else host

        print(
            'You will now be asked to input the port of the Redis server.\n'
            'If you will be running PANIC using Docker, you should leave the '
            'port as the default. Otherwise, you must run the Redis Docker '
            'using -p <port>:6379.')
        port = input('Please insert the Redis host port: (default: 6379)\n')
        port = '6379' if port == '' else port

        print('Note if you are going to be running PANIC in docker, this is '
              'mandatory.')
        password = input('Please insert the Redis password:\n')

        if yn_prompt('Do you wish to test Redis now? (Y/n)\n'):
            test = test_redis(host, port, password)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['redis']['enabled'] = str(True)
    cp['redis']['host'] = host
    cp['redis']['port'] = port
    cp['redis']['password'] = password
Exemple #6
0
def setup_telegram_commands(cp: ConfigParser) -> None:
    print('---- Telegram Commands')
    print(
        'Telegram is also used as a two-way interface with the alerter and '
        'as an assistant, allowing you to do things such as snooze phone '
        'call alerts and to get the alerter\'s current status from Telegram. '
        'Once again, this requires you to set up a Telegram bot, which is '
        'free and easy. You can reuse the Telegram bot set up for alerts.')

    already_set_up = is_already_set_up(cp, 'telegram_commands')
    if already_set_up and \
            not yn_prompt('Telegram commands are already set up. Do you '
                          'wish to clear the current config? (Y/n)\n'):
        return

    print('NOTE: If you are running more than one instance of the P.A.N.I.C. '
          'alerter, do not use the same telegram bot as the other instance/s.')

    reset_section('telegram_commands', cp)
    cp['telegram_commands']['enabled'] = str(False)
    cp['telegram_commands']['bot_token'] = ''
    cp['telegram_commands']['bot_chat_id'] = ''

    if not already_set_up and \
            not yn_prompt('Do you wish to set up Telegram commands? (Y/n)\n'):
        return

    while True:
        while True:
            bot_token = input(
                'Please insert your Telegram bot\'s API token:\n')
            bot_api = TelegramBotApi(bot_token, None)

            confirmation = bot_api.get_me()
            if not confirmation['ok']:
                print(str(confirmation))
            else:
                print('Successfully connected to Telegram bot.')
                break

        bot_chat_id = input('Please insert the authorised chat ID:\n')

        if yn_prompt('Do you wish to test Telegram commands now? (Y/n)\n'):
            test = test_telegram_commands(bot_token, bot_chat_id)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['telegram_commands']['enabled'] = str(True)
    cp['telegram_commands']['bot_token'] = bot_token
    cp['telegram_commands']['bot_chat_id'] = bot_chat_id
Exemple #7
0
def setup_nodes(cp: ConfigParser) -> None:
    print('==== Nodes')
    print('To produce alerts, the alerter needs something to monitor! The list '
          'of nodes to be included in the monitoring will now be set up. This '
          'includes validators, sentries, and any full nodes that can be used '
          'as a data source to monitor from the network\'s perspective. You '
          'may include nodes from multiple networks in any order; P.A.N.I.C. '
          'will figure out which network they belong to when you run it. Node '
          'names must be unique!')

    # Check if list already set up
    already_set_up = len(cp.sections()) > 0
    if already_set_up:
        if not yn_prompt(
                'The list of nodes is already set up. Do you wish '
                'to replace this list with a new one? (Y/n)\n'):
            return

    # Otherwise ask if they want to set it up
    if not already_set_up and \
            not yn_prompt('Do you wish to set up the list of nodes? (Y/n)\n'):
        return

    # Clear config and initialise new list
    cp.clear()
    nodes = []

    # Get node details and append them to the list of nodes
    while True:
        node = get_node(nodes)
        if node is not None:
            nodes.append(node)
            if node.node_is_validator:
                print('Successfully added validator node.')
            else:
                print('Successfully added full node.')

        if not yn_prompt('Do you want to add another node? (Y/n)\n'):
            break

    # Add nodes to config
    cp.clear()
    for i, node in enumerate(nodes):
        section = 'node_' + str(i)
        cp.add_section(section)
        cp[section]['node_name'] = node.node_name
        cp[section]['node_rpc_url'] = node.node_rpc_url
        cp[section]['node_is_validator'] = \
            'true' if node.node_is_validator else 'false'
        cp[section]['include_in_node_monitor'] = \
            'true' if node.include_in_node_monitor else 'false'
        cp[section]['include_in_network_monitor'] = \
            'true' if node.include_in_network_monitor else 'false'
Exemple #8
0
def test_telegram_commands(bot_token: str, bot_chat_id: str) -> TestOutcome:
    cmd_handler = TelegramCommandHandler(bot_token, bot_chat_id, None)
    cmd_handler.start_handling(run_in_background=True)
    print('Go ahead and send /ping to the Telegram bot.')
    input('Press ENTER once you are done sending commands...')
    print('Stopping the Telegram bot...')
    cmd_handler.stop()

    if yn_prompt('Was the testing successful? (Y/n)\n'):
        return TestOutcome.OK
    elif yn_prompt('Retry Telegram commands setup? (Y/n)\n'):
        return TestOutcome.RestartSetup
    else:
        return TestOutcome.SkipSetup
Exemple #9
0
def setup_telegram_alerts(cp: ConfigParser) -> None:
    print('---- Telegram Alerts')
    print('Alerts sent via Telegram are a fast and reliable means of alerting '
          'that we highly recommend setting up. This requires you to have a '
          'Telegram bot set up, which is a free and quick procedure.')

    already_set_up = is_already_set_up(cp, 'telegram_alerts')
    if already_set_up and \
            not yn_prompt('Telegram alerts are already set up. Do you '
                          'wish to clear the current config? (Y/n)\n'):
        return

    reset_section('telegram_alerts', cp)
    cp['telegram_alerts']['enabled'] = str(False)
    cp['telegram_alerts']['bot_token'] = ''
    cp['telegram_alerts']['bot_chat_id'] = ''

    if not already_set_up and \
            not yn_prompt('Do you wish to set up Telegram alerts? (Y/n)\n'):
        return

    while True:
        while True:
            bot_token = input(
                'Please insert your Telegram bot\'s API token:\n')
            bot_api = TelegramBotApi(bot_token, None)

            confirmation = bot_api.get_me()
            if not confirmation['ok']:
                print(str(confirmation))
            else:
                print('Successfully connected to Telegram bot.')
                break

        bot_chat_id = input('Please insert the chat ID for Telegram alerts:\n')
        bot_api = TelegramBotApi(bot_token, bot_chat_id)

        if yn_prompt('Do you wish to test Telegram alerts now? (Y/n)\n'):
            test = test_telegram_alerts(bot_api)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['telegram_alerts']['enabled'] = str(True)
    cp['telegram_alerts']['bot_token'] = bot_token
    cp['telegram_alerts']['bot_chat_id'] = bot_chat_id
Exemple #10
0
def test_telegram_alerts(bot_api: TelegramBotApi) -> TestOutcome:
    response = bot_api.send_message('*Test Alert*')
    if response['ok']:
        print('Test alert sent successfully.')
        if yn_prompt('Was the testing successful? (Y/n)\n'):
            return TestOutcome.OK
        elif yn_prompt('Retry Telegram alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
    else:
        print('Something went wrong: {}'.format(response))
        if yn_prompt('Retry Telegram alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
Exemple #11
0
def setup_general(cp: ConfigParser) -> None:
    print('==== General')
    print('The first step is to set a unique identifier for the alerter. This '
          'can be any word that uniquely describes the setup being monitored. '
          'Uniqueness is very important if you are running multiple instances '
          'of the PANIC alerter, to avoid any possible namespace clashes. The '
          'name will only be used internally and will not show up in alerts.')

    if is_already_set_up(cp, 'general'):
        identifier = cp['general']['unique_alerter_identifier']
        if not yn_prompt(
                'A unique alerter identifier \'{}\' is already set. Do you '
                'wish to change this identifier? (Y/n)\n'.format(identifier)):
            return

    reset_section('general', cp)
    cp['general']['unique_alerter_identifier'] = ''

    while True:
        identifier = input('Please insert the unique identifier:\n')
        if len(identifier) != 0:
            break
        else:
            print('The unique identifier cannot be blank.')

    cp['general']['unique_alerter_identifier'] = identifier
def get_repo(repos_so_far: List[RepoConfig]) -> Optional[RepoConfig]:
    # Get repo's name
    repo_names_so_far = [r.repo_name for r in repos_so_far]
    while True:
        repo_name = input(
            'Unique GitHub repository name (to know which repository '
            'triggered an alert, example: oasis-core):\n')
        if repo_name in repo_names_so_far:
            print('Repo name must be unique.')
        elif len(repo_name) == 0:
            print('Repo name cannot be empty.')
        else:
            break

    # Get repo page
    while True:
        repo_page = input('Official GitHub repository (example: '
                          'oasislabs/oasis-core/):\n')
        if not repo_page.endswith('/'):
            repo_page = repo_page + '/'

        releases_page = InternalConf.github_releases_template.format(repo_page)
        print('Trying to connect to {}'.format(releases_page))
        try:
            releases = get_json(releases_page, DUMMY_LOGGER)
            if 'message' in releases and releases['message'] == 'Not Found':
                if not yn_prompt(
                        'Connection successful, but URL is not valid. '
                        'Do you want to try again? (Y/n)\n'):
                    if not yn_prompt(
                            'Do you still want to add the repo? (Y/n)\n'):
                        return None
                    else:
                        break
            else:
                break  # success message left to setup_repos function
        except Exception:
            if not yn_prompt('Failed to connect to page. Do '
                             'you want to try again? (Y/n)\n'):

                if not yn_prompt('Do you still want to add the repo? (Y/n)\n'):
                    return None
                else:
                    break

    # Return node
    return RepoConfig(repo_name, repo_page, True)
Exemple #13
0
def setup_email_alerts(cp: ConfigParser) -> None:
    print('---- Email Alerts')
    print('Email alerts are more useful as a backup alerting channel rather '
          'than the main one, given that one is much more likely to notice a '
          'a message on Telegram or a phone call. Email alerts also require '
          'an SMTP server to be set up for the alerter to be able to send.')

    already_set_up = is_already_set_up(cp, 'email_alerts')
    if already_set_up and \
            not yn_prompt('Email alerts are already set up. Do you '
                          'wish to clear the current config? (Y/n)\n'):
        return

    reset_section('email_alerts', cp)
    cp['email_alerts']['enabled'] = str(False)
    cp['email_alerts']['smtp'] = ''
    cp['email_alerts']['from'] = ''
    cp['email_alerts']['to'] = ''

    if not already_set_up and \
            not yn_prompt('Do you wish to set up email alerts? (Y/n)\n'):
        return

    while True:
        email_smtp = input('Please insert the SMTP server\'s address:\n')

        email_from = input('Please specify the details of the sender in the '
                           'format shown below:\n\t'
                           '[email protected]\n')

        email_to = input('Please specify the email address where you wish to '
                         'receive email alerts:\n\t'
                         '[email protected]\n')

        if yn_prompt('Do you wish to test email alerts now? (Y/n)\n'):
            test = test_email_alerts(email_smtp, email_from, email_to)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['email_alerts']['enabled'] = str(True)
    cp['email_alerts']['smtp'] = email_smtp
    cp['email_alerts']['from'] = email_from
    cp['email_alerts']['to'] = email_to
Exemple #14
0
def setup_redis(cp: ConfigParser) -> None:
    print('==== Redis')
    print('Redis is used by the alerter to persist data every now and then, '
          'so that it can continue where it left off if it is restarted. It '
          'is also used to be able to get the status of the alerter and to '
          'have some control over it, such as to snooze Twilio phone calls.')

    already_set_up = is_already_set_up(cp, 'redis')
    if already_set_up and \
            not yn_prompt('Redis is already set up. Do you wish '
                          'to clear the current config? (Y/n)\n'):
        return

    reset_section('redis', cp)
    cp['redis']['enabled'] = str(False)
    cp['redis']['host'] = ''
    cp['redis']['port'] = ''
    cp['redis']['password'] = ''

    if not already_set_up and \
            not yn_prompt('Do you wish to set up Redis? (Y/n)\n'):
        return

    while True:
        host = input('Please insert the Redis host IP: (default: localhost)\n')
        host = 'localhost' if host == '' else host

        port = input('Please insert the Redis host port: (default: 6379)\n')
        port = '6379' if port == '' else port

        password = input('Please insert the Redis password:\n')

        if yn_prompt('Do you wish to test Redis now? (Y/n)\n'):
            test = test_redis(host, port, password)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['redis']['enabled'] = str(True)
    cp['redis']['host'] = host
    cp['redis']['port'] = port
    cp['redis']['password'] = password
Exemple #15
0
def test_email_alerts(email_smtp: str, email_from: str, email_to: str) \
        -> TestOutcome:
    email_sender = EmailSender(email_smtp, email_from)
    try:
        email_sender.send_email('Test Alert', 'Test Alert', email_to)
        print('Test email sent.')

        if yn_prompt('Was the testing successful? (Y/n)\n'):
            return TestOutcome.OK
        elif yn_prompt('Retry email alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
    except Exception as e:
        print('Something went wrong: {}'.format(e))
        if yn_prompt('Retry email alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
def setup_repos(cp: ConfigParser) -> None:
    print('==== GitHub Repositories')
    print(
        'The GitHub monitor alerts on new releases in repositories. The list '
        'of GitHub repositories to monitor will now be set up.')

    # Check if list already set up
    already_set_up = len(cp.sections()) > 0
    if already_set_up:
        if not yn_prompt('The list of repositories is already set up. Do you '
                         'wish to replace this list with a new one? (Y/n)\n'):
            return

    # Otherwise ask if they want to set it up
    if not already_set_up and \
            not yn_prompt('Do you wish to set up the list of repos? (Y/n)\n'):
        return

    # Clear config and initialise new list
    cp.clear()
    repos = []

    # Get repository details and append them to the list of repositories
    while True:
        repo = get_repo()
        if repo is not None:
            repos.append(repo)
            print('Successfully added repository.')

        if not yn_prompt('Do you want to add another repo? (Y/n)\n'):
            break

    # Add repos to config
    cp.clear()
    for i, repo in enumerate(repos):
        section = 'repo_' + str(i)
        cp.add_section(section)
        cp[section]['repo_name'] = repo.repo_name
        cp[section]['repo_page'] = repo.repo_page
        cp[section]['include_in_github_monitor'] = \
            'true' if repo.include_in_github_monitor else 'false'
Exemple #17
0
def test_twilio_alerts(twilio_no: str, to_dial: str, twilio_api: TwilioApi,
                       internal_conf: InternalConfig = InternalConf) \
        -> TestOutcome:
    try:
        twilio_api.dial_number(twilio_no, to_dial,
                               internal_conf.twiml_instructions_url)
        print('Test phone call requested successfully. Please wait a bit.')

        if yn_prompt('Was the testing successful? (Y/n)\n'):
            return TestOutcome.OK
        elif yn_prompt('Retry Twilio alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
    except Exception as e:
        print('Something went wrong: {}'.format(e))
        print('The Twilio details you provided might be incorrect.')
        if yn_prompt('Retry Twilio alerts setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
Exemple #18
0
def setup_api(cp: ConfigParser) -> None:
    print('==== Polkadot API Server')
    print(
        'The Polkadot API Server is used by the alerter to get data from the '
        'nodes. It is important that before running both the alerter and '
        'this setup, the Polkadot API Server is set up and running.')

    if is_already_set_up(cp, 'api') and \
            not yn_prompt('The Polkadot API Server is already set up. Do you '
                          'wish to replace the current config? (Y/n)\n'):
        return

    reset_section('api', cp)
    cp['api']['polkadot_api_endpoint'] = ''

    while True:
        print('You will now be asked to input the API Server\'s address\n'
              'If you will be running PANIC using Docker, do not use '
              'localhost, instead use the full IP address (local or external) '
              'of the machine that the API container will be running on.\n'
              'You should also set the port to 3000. Otherwise, you must run '
              'the API Docker using -p <port>:3000.')
        polkadot_api_endpoint = input(
            'Please insert the API Server\'s address:'
            ' (default: http://localhost:3000)\n')
        polkadot_api_endpoint = 'http://localhost:3000' if \
            polkadot_api_endpoint == '' else polkadot_api_endpoint
        polkadot_api = PolkadotApiWrapper(DUMMY_LOGGER, polkadot_api_endpoint)
        print('Testing connection with endpoint {}'.format(
            polkadot_api_endpoint))
        try:
            polkadot_api.ping_api()
            print('Success.')
            break
        except Exception:
            if not yn_prompt('Failed to connect to endpoint. Do '
                             'you want to try again? (Y/n)\n'):
                return None

    cp['api']['polkadot_api_endpoint'] = polkadot_api_endpoint
Exemple #19
0
def run() -> None:
    if not cp_ui.has_section('authentication'):
        raise InitialisationException(
            'You cannot change your UI password because UI authentication is '
            'not set up yet. Please set it up using the run_ui_setup.py script.'
        )

    if not cp_ui.has_option('authentication', 'hashed_password'):
        raise InitialisationException(
            'Missing key authentication.hashed_password in the '
            'config/user_config_ui.ini config file. Please set up UI '
            'authentication again using the run_ui_setup.py script as your '
            'config is invalid.')

    while True:
        new_pass = input('Please insert your new password.\n')

        # The salt is the first 64 characters
        salt = bytes.fromhex(cp_ui['authentication']['hashed_password'][:64])
        hashed_new_pass = hashlib.pbkdf2_hmac('sha256',
                                              new_pass.encode('utf-8'), salt,
                                              100000)

        # Ask user if he really wants to change the password if the new password
        # is the same as the old one.
        if hashed_new_pass == \
                bytes.fromhex(
                    cp_ui['authentication']['hashed_password'][64:128]):
            if not yn_prompt('The new password is exactly the same as the old '
                             'password. Do you want to insert a different '
                             'password instead? (Y/n)\n'):
                print("Password did not change.")
                return
        else:
            # Generate a new salt to avoid situations where the user is suddenly
            # logged in without authenticating himself. This can happen if the
            # stored password matches the new password and the user is currently
            # not authenticated.
            salt = os.urandom(32)

            # Generate another hash with the new salt
            hashed_new_pass = hashlib.pbkdf2_hmac('sha256',
                                                  new_pass.encode('utf-8'),
                                                  salt, 100000)

            # First 64 characters are the salt, and the remaining characters
            # are the hashed password.
            cp_ui['authentication']['hashed_password'] = \
                (salt + hashed_new_pass).hex()
            break

    print("Password changed successfully")
def test_redis(host: str, port: str, password: str) -> TestOutcome:
    redis = RedisApi(DUMMY_LOGGER, 0, host, int(port),
                     password=password, namespace='')
    try:
        redis.ping_unsafe()
        print('Test completed successfully.')
        return TestOutcome.OK
    except Exception as e:
        print('Something went wrong: {}'.format(e))
        if yn_prompt('Retry Redis setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
Exemple #21
0
def setup_repos(cp: ConfigParser) -> None:
    print('==== GitHub Repositories')
    print(
        'The GitHub monitor alerts on new releases in repositories. The list '
        'of GitHub repositories to monitor will now be set up.')

    # Check if list already set up
    if len(cp.sections()) > 0 and \
            not yn_prompt('The list of repositories is already set up. Do you '
                          'wish to clear this list? You will then be asked to '
                          'set up a new list, if you wish to do so (Y/n)\n'):
        return

    # Clear config and initialise new list
    cp.clear()
    repos = []

    # Ask if they want to set it up
    if not yn_prompt('Do you wish to set up the list of repos? (Y/n)\n'):
        return

    # Get repository details and append them to the list of repositories
    while True:
        repo = get_repo(repos)
        if repo is not None:
            repos.append(repo)
            print('Successfully added repository.')

        if not yn_prompt('Do you want to add another repo? (Y/n)\n'):
            break

    # Add repos to config
    for i, repo in enumerate(repos):
        section = 'repo_' + str(i)
        cp.add_section(section)
        cp[section]['repo_name'] = repo.repo_name
        cp[section]['repo_page'] = repo.repo_page
        cp[section]['monitor_repo'] = str(repo.monitor_repo)
def test_mongo(host: str, port: str, username: str, password: str) \
        -> TestOutcome:
    mongo = MongoApi(DUMMY_LOGGER, 'dummy_db_name', host, int(port),
                     username=username, password=password)
    try:
        mongo.ping_unsafe()
        if len(username) != 0:
            mongo.ping_auth(username, password)
        print('Test completed successfully.')
        return TestOutcome.OK
    except Exception as e:
        print('Something went wrong: {}'.format(e))
        if yn_prompt('Retry Mongo setup? (Y/n)\n'):
            return TestOutcome.RestartSetup
        else:
            return TestOutcome.SkipSetup
def setup_authentication(cp: ConfigParser) -> None:
    print('==== Authentication')
    print('For connections with the Web UI server to be secure, session-based '
          'authentication is used. You will now be asked to input a username, '
          'password, and a cookie secret (used to sign the cookie stored in '
          'the browser to avoid tampering). The inputted password will be '
          'hashed inside the config, so any future password changes should be '
          'made using the run_util_change_ui_auth_pass.py util. Note that if '
          'the authentication credentials are not inputted in the config, the '
          'UI server won\'t start. In addition to this, since the UI server is '
          'an HTTPS server, please also make sure that the key.pem and '
          'cert.pem files are included as presented in the documentation. This '
          'is important for maximum security.')

    if is_already_set_up(cp, 'authentication') and \
            not yn_prompt('Authentication for the UI is already set up. Do you '
                          'wish to replace the current config? (Y/n)\n'):
        return

    reset_section('authentication', cp)
    cp['authentication']['username'] = ''
    cp['authentication']['hashed_password'] = ''
    cp['authentication']['cookie_secret'] = ''

    auth_username = input('Please insert a username \n')
    print('Please insert a password')
    auth_password = getpass()
    salt = os.urandom(32)
    hashed_pass = hashlib.pbkdf2_hmac('sha256', auth_password.encode('utf-8'),
                                      salt, 100000)
    cookie_secret = input('Please insert a cookie secret. This should be '
                          'a string.\n')

    cp['authentication']['username'] = auth_username

    # First 64 characters are the salt, and the remaining characters
    # are the hashed password.
    cp['authentication']['hashed_password'] = (salt + hashed_pass).hex()
    cp['authentication']['cookie_secret'] = cookie_secret
Exemple #24
0
 def test_yn_prompt_returns_false_for_uppercase_n(self, _):
     self.assertFalse(
         yn_prompt(TestUserInput.DUMMY_INPUT))
Exemple #25
0
 def test_yn_prompt_keeps_looping_until_valid_input(self, _):
     self.assertTrue(
         yn_prompt(TestUserInput.DUMMY_INPUT))
Exemple #26
0
 def test_yn_prompt_returns_true_for_uppercase_y(self, _):
     self.assertTrue(
         yn_prompt(TestUserInput.DUMMY_INPUT))
Exemple #27
0
def setup_periodic_alive_reminder(cp: ConfigParser) -> None:
    print('---- Periodic alive reminder')
    print('The periodic alive reminder is a way for the alerter to inform its '
          'users that it is still running.')

    if is_already_set_up(cp, 'periodic_alive_reminder') and \
            not yn_prompt('The periodic alive reminder is already set up. '
                          'Do you wish to clear the current config? (Y/n)\n'):
        return

    reset_section('periodic_alive_reminder', cp)
    cp['periodic_alive_reminder']['enabled'] = str(False)
    cp['periodic_alive_reminder']['interval_seconds'] = ''
    cp['periodic_alive_reminder']['email_enabled'] = ''
    cp['periodic_alive_reminder']['telegram_enabled'] = ''
    cp['periodic_alive_reminder']['mongo_enabled'] = ''

    if not yn_prompt('Do you wish to set up the periodic alive reminder? '
                     '(Y/n)\n'):
        return

    interval = input("Please enter the amount of seconds you want to "
                     "pass for the periodic alive reminder. Make sure that "
                     "you insert a positive integer.\n")
    while True:
        try:
            interval_number_rep = int(interval)
        except ValueError:
            interval = input("Input is not a valid integer. Please enter "
                             "another value:\n")
            continue
        if interval_number_rep > 0:
            time = timedelta(seconds=int(interval_number_rep))
            time = strfdelta(time, "{hours}h {minutes}m {seconds}s")
            if yn_prompt(
                    'You will be reminded that the alerter is still running '
                    'every {}. Is this correct (Y/n) \n'.format(time)):
                break
            else:
                interval = input(
                    "Please enter the amount of seconds you want to "
                    "pass for the periodic alive reminder. Make sure that "
                    "you insert a positive integer.\n")
        else:
            interval = input("Input is not a positive integer. Please enter "
                             "another value:\n")

    if is_already_set_up(cp, 'email_alerts') and \
            cp['email_alerts']['enabled'] and \
            yn_prompt('Would you like the periodic alive reminder '
                      'to send alerts via e-mail? (Y/n)\n'):
        email_enabled = str(True)
    else:
        email_enabled = str(False)

    if is_already_set_up(cp, 'telegram_alerts') and \
            cp['telegram_alerts']['enabled'] and \
            yn_prompt('Would you like the periodic alive reminder '
                      'to send alerts via Telegram? (Y/n)\n'):
        telegram_enabled = str(True)
    else:
        telegram_enabled = str(False)

    if is_already_set_up(cp, 'mongo') and cp['mongo']['enabled'] and \
            yn_prompt('Would you like the periodic alive reminder '
                      'to save alerts to Mongo? (Y/n)\n'):
        mongo_enabled = str(True)
    else:
        mongo_enabled = str(False)

    cp['periodic_alive_reminder']['enabled'] = str(True)
    cp['periodic_alive_reminder']['interval_seconds'] = interval
    cp['periodic_alive_reminder']['email_enabled'] = email_enabled
    cp['periodic_alive_reminder']['telegram_enabled'] = telegram_enabled
    cp['periodic_alive_reminder']['mongo_enabled'] = mongo_enabled
Exemple #28
0
def setup_mongo(cp: ConfigParser) -> None:
    print('==== MongoDB')
    print('Mongo can be set up to persist any alert in a MongoDB collection.')

    if is_already_set_up(cp, 'mongo') and \
            not yn_prompt('Mongo is already set up. Do you wish '
                          'to clear the current config? (Y/n)\n'):
        return

    reset_section('mongo', cp)
    cp['mongo']['enabled'] = str(False)
    cp['mongo']['host'] = ''
    cp['mongo']['port'] = ''
    cp['mongo']['db_name'] = ''
    cp['mongo']['user'] = ''
    cp['mongo']['pass'] = ''

    if not yn_prompt('Do you wish to set up Mongo? (Y/n)\n'):
        return

    while True:
        print('You will now be asked to input the IP of the Mongo server.\n'
              'If you will be running PANIC using Docker, do not use '
              'localhost, instead use the full IP address (local or external) '
              'of the machine that the Mongo container will be running on.')
        host = input('Please insert the Mongo host IP: (default: localhost)\n')
        host = 'localhost' if host == '' else host

        print('You will now be asked to input the port of the Mongo server.\n'
              'If you will be running PANIC using Docker, you should leave the '
              'port as the default. If you wish to run the Mongo container on '
              'another port, please input this port number here and change the '
              '`MONGO_HOST_PORT` value inside the `panic_polkadot/.env` file.')
        port = input('Please insert the Mongo host port: (default: 27017)\n')
        port = '27017' if port == '' else port

        print('You will now be asked which database you wish to use to store '
              'the alerts. This will be auto-created if it does not exist. '
              'You can re-use the same database if another PANIC is installed.')
        while True:
            db_name = input('Please insert database name: (default: panic)\n')
            db_name = 'panic' if db_name == '' else db_name
            if ' ' in db_name:
                print('Database name cannot contain spaces.')
            else:
                break

        username = input('Please insert the username for authentication '
                         '(blank for no authentication):\n')
        if len(username) != 0:
            password = input('Please insert the password for authentication:\n')
        else:
            password = ''

        if yn_prompt('Do you wish to test Mongo now? (Y/n)\n'):
            test = test_mongo(host, port, username, password)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['mongo']['enabled'] = str(True)
    cp['mongo']['host'] = host
    cp['mongo']['port'] = port
    cp['mongo']['db_name'] = db_name
    cp['mongo']['user'] = username
    cp['mongo']['pass'] = password
Exemple #29
0
def setup_email_alerts(cp: ConfigParser) -> None:
    print('---- Email Alerts')
    print('Email alerts are more useful as a backup alerting channel rather '
          'than the main one, given that one is much more likely to notice a '
          'a message on Telegram or a phone call. Email alerts also require '
          'an SMTP server to be set up for the alerter to be able to send.')

    if is_already_set_up(cp, 'email_alerts') and \
            not yn_prompt('Email alerts are already set up. Do you '
                          'wish to clear the current config? (Y/n)\n'):
        return

    reset_section('email_alerts', cp)
    cp['email_alerts']['enabled'] = str(False)
    cp['email_alerts']['smtp'] = ''
    cp['email_alerts']['from'] = ''
    cp['email_alerts']['to'] = ''
    cp['email_alerts']['user'] = ''
    cp['email_alerts']['pass'] = ''

    if not yn_prompt('Do you wish to set up email alerts? (Y/n)\n'):
        return

    while True:
        email_smtp = input('Please insert the SMTP server\'s address:\n')

        email_user = input('Please insert the username for SMTP authentication '
                           '(blank for no authentication):\n')
        if len(email_user) != 0:
            email_pass = input('Please insert the password for SMTP '
                               'authentication:\n')
        else:
            email_pass = ''

        email_from = input('Please specify the details of the sender in the '
                           'format shown below:\n\t'
                           '[email protected]\n')

        email_to = input('Please specify the email address where you wish to '
                         'receive email alerts:\n\t'
                         '[email protected]\n')

        while yn_prompt('Do you wish to add another email address? (Y/n)\n'):
            email_to += ';' + input('Please insert the email address:\n')

        if yn_prompt('Do you wish to test email alerts now? The first '
                     'email address inserted will be used. (Y/n)\n'):
            test = test_email_alerts(email_smtp, email_from,
                                     email_to.split(';')[0],
                                     email_user, email_pass)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['email_alerts']['enabled'] = str(True)
    cp['email_alerts']['smtp'] = email_smtp
    cp['email_alerts']['from'] = email_from
    cp['email_alerts']['to'] = email_to
    cp['email_alerts']['user'] = email_user
    cp['email_alerts']['pass'] = email_pass
Exemple #30
0
def setup_twilio_alerts(cp: ConfigParser) -> None:
    print('---- Twilio Alerts')
    print('Twilio phone-call alerts are the most important alerts since they '
          'are the best at grabbing your attention, especially when you\'re '
          'asleep! To set these up, you have to have a Twilio account set up, '
          'with a registered Twilio phone number and a verified phone number.'
          'The timed trial version of Twilio is free.')

    if is_already_set_up(cp, 'twilio_alerts') and \
            not yn_prompt('Twilio alerts are already set up. Do you '
                          'wish to clear the current config? (Y/n)\n'):
        return

    reset_section('twilio_alerts', cp)
    cp['twilio_alerts']['enabled'] = str(False)
    cp['twilio_alerts']['account_sid'] = ''
    cp['twilio_alerts']['auth_token'] = ''
    cp['twilio_alerts']['twilio_phone_number'] = ''
    cp['twilio_alerts']['phone_numbers_to_dial'] = ''

    if not yn_prompt('Do you wish to set up Twilio alerts? (Y/n)\n'):
        return

    while True:

        while True:
            account_sid = input('Please insert your Twilio account SID:\n')
            auth_token = input('Please insert your Twilio account AuthToken:\n')

            try:
                twilio_api = TwilioApi(account_sid, auth_token)
                print('Successfully connected to Twilio.')
                break
            except Exception as e:
                print('Something went wrong: {}'.format(e))

        twilio_no = input('Please insert your registered Twilio phone number '
                          'in the format shown below:\n\t'
                          'E.164 format, for example: +12025551234\n')

        to_dial = input('Please insert the first phone number to dial for '
                        'alerting purposes in the format shown below:\n\t'
                        'E.164 format, for example: +12025551234\n')

        while yn_prompt('Do you wish to add another number? (Y/n)\n'):
            to_dial += ';' + input('Please insert the phone number:\n')

        if yn_prompt('Do you wish to test Twilio alerts now? The first '
                     'phone number inserted will be called. (Y/n)\n'):
            test = test_twilio_alerts(twilio_no, to_dial.split(';')[0],
                                      twilio_api)
            if test == TestOutcome.RestartSetup:
                continue
            elif test == TestOutcome.SkipSetup:
                return
        break

    cp['twilio_alerts']['enabled'] = str(True)
    cp['twilio_alerts']['account_sid'] = account_sid
    cp['twilio_alerts']['auth_token'] = auth_token
    cp['twilio_alerts']['twilio_phone_number'] = twilio_no
    cp['twilio_alerts']['phone_numbers_to_dial'] = to_dial