The following issues were found

certbot/certbot/achallenges.py
6 issues
Unable to import 'josepy'
Error

Line: 23 Column: 1

              import logging
from typing import Type

import josepy as jose

from acme import challenges
from acme.challenges import Challenge

logger = logging.getLogger(__name__)

            

Reported by Pylint.

Unable to import 'acme'
Error

Line: 25 Column: 1

              
import josepy as jose

from acme import challenges
from acme.challenges import Challenge

logger = logging.getLogger(__name__)



            

Reported by Pylint.

Unable to import 'acme.challenges'
Error

Line: 26 Column: 1

              import josepy as jose

from acme import challenges
from acme.challenges import Challenge

logger = logging.getLogger(__name__)


class AnnotatedChallenge(jose.ImmutableMap):

            

Reported by Pylint.

Too few public methods (1/2)
Error

Line: 31 Column: 1

              logger = logging.getLogger(__name__)


class AnnotatedChallenge(jose.ImmutableMap):
    """Client annotated challenge.

    Wraps around server provided challenge and annotates with data
    useful for the client.


            

Reported by Pylint.

Too few public methods (1/2)
Error

Line: 47 Column: 1

                      return getattr(self.challb, name)


class KeyAuthorizationAnnotatedChallenge(AnnotatedChallenge):
    """Client annotated `KeyAuthorizationChallenge` challenge."""
    __slots__ = ('challb', 'domain', 'account_key')

    def response_and_validation(self, *args, **kwargs):
        """Generate response and validation."""

            

Reported by Pylint.

Too few public methods (0/2)
Error

Line: 57 Column: 1

                          self.account_key, *args, **kwargs)


class DNS(AnnotatedChallenge):
    """Client annotated "dns" ACME challenge."""
    __slots__ = ('challb', 'domain')
    acme_type = challenges.DNS

            

Reported by Pylint.

certbot/certbot/plugins/storage.py
6 issues
Consider explicitly re-raising using the 'from' keyword
Error

Line: 67 Column: 17

                              errmsg = "PluginStorage file {0} is corrupted.".format(
                    self._storagepath)
                logger.error(errmsg)
                raise errors.PluginStorageError(errmsg)
        self._data = data

    def save(self):
        """Saves PluginStorage content to disk


            

Reported by Pylint.

Variable name "fh" doesn't conform to snake_case naming style
Error

Line: 48 Column: 50

                      data: Dict[str, Any] = {}
        filedata = ""
        try:
            with open(self._storagepath, 'r') as fh:
                filedata = fh.read()
        except IOError as e:
            errmsg = "Could not read PluginStorage data file: {0} : {1}".format(
                self._storagepath, str(e))
            if os.path.isfile(self._storagepath):

            

Reported by Pylint.

Variable name "e" doesn't conform to snake_case naming style
Error

Line: 50 Column: 9

                      try:
            with open(self._storagepath, 'r') as fh:
                filedata = fh.read()
        except IOError as e:
            errmsg = "Could not read PluginStorage data file: {0} : {1}".format(
                self._storagepath, str(e))
            if os.path.isfile(self._storagepath):
                # Only error out if file exists, but cannot be read
                logger.error(errmsg)

            

Reported by Pylint.

Variable name "e" doesn't conform to snake_case naming style
Error

Line: 83 Column: 9

              
        try:
            serialized = json.dumps(self._data)
        except TypeError as e:
            errmsg = "Could not serialize PluginStorage data: {0}".format(
                str(e))
            logger.error(errmsg)
            raise errors.PluginStorageError(errmsg)
        try:

            

Reported by Pylint.

Variable name "fh" doesn't conform to snake_case naming style
Error

Line: 92 Column: 37

                          with os.fdopen(filesystem.open(
                    self._storagepath,
                    os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
                    0o600), 'w') as fh:
                fh.write(serialized)
        except IOError as e:
            errmsg = "Could not write PluginStorage data to file {0} : {1}".format(
                self._storagepath, str(e))
            logger.error(errmsg)

            

Reported by Pylint.

Variable name "e" doesn't conform to snake_case naming style
Error

Line: 94 Column: 9

                                  os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
                    0o600), 'w') as fh:
                fh.write(serialized)
        except IOError as e:
            errmsg = "Could not write PluginStorage data to file {0} : {1}".format(
                self._storagepath, str(e))
            logger.error(errmsg)
            raise errors.PluginStorageError(errmsg)


            

Reported by Pylint.

certbot-compatibility-test/certbot_compatibility_test/validator.py
6 issues
Unable to import 'acme'
Error

Line: 7 Column: 1

              
import requests

from acme import crypto_util
from acme import errors as acme_errors

logger = logging.getLogger(__name__)



            

Reported by Pylint.

Unable to import 'acme'
Error

Line: 8 Column: 1

              import requests

from acme import crypto_util
from acme import errors as acme_errors

logger = logging.getLogger(__name__)


class Validator:

            

Reported by Pylint.

Method could be a function
Error

Line: 16 Column: 5

              class Validator:
    """Collection of functions to test a live webserver's configuration"""

    def certificate(self, cert, name, alt_host=None, port=443):
        """Verifies the certificate presented at name is cert"""
        if alt_host is None:
            host = socket.gethostbyname(name).encode()
        elif isinstance(alt_host, bytes):
            host = alt_host

            

Reported by Pylint.

Method could be a function
Error

Line: 34 Column: 5

              
        return presented_cert.digest("sha256") == cert.digest("sha256")

    def redirect(self, name, port=80, headers=None):
        """Test whether webserver redirects to secure connection."""
        url = "http://{0}:{1}".format(name, port)
        if headers:
            response = requests.get(url, headers=headers, allow_redirects=False)
        else:

            

Reported by Pylint.

Method could be a function
Error

Line: 55 Column: 5

              
        return True

    def any_redirect(self, name, port=80, headers=None):
        """Test whether webserver redirects."""
        url = "http://{0}:{1}".format(name, port)
        if headers:
            response = requests.get(url, headers=headers, allow_redirects=False)
        else:

            

Reported by Pylint.

Method could be a function
Error

Line: 65 Column: 5

              
        return response.status_code in range(300, 309)

    def hsts(self, name):
        """Test for HTTP Strict Transport Security header"""
        headers = requests.get("https://" + name).headers
        hsts_header = headers.get("strict-transport-security")

        if not hsts_header:

            

Reported by Pylint.

certbot-nginx/certbot_nginx/_internal/parser.py
6 issues
Unable to import 'certbot'
Error

Line: 17 Column: 1

              
import pyparsing

from certbot import errors
from certbot.compat import os
from certbot_nginx._internal import nginxparser
from certbot_nginx._internal import obj
from certbot_nginx._internal.nginxparser import UnspacedList


            

Reported by Pylint.

Unable to import 'certbot.compat'
Error

Line: 18 Column: 1

              import pyparsing

from certbot import errors
from certbot.compat import os
from certbot_nginx._internal import nginxparser
from certbot_nginx._internal import obj
from certbot_nginx._internal.nginxparser import UnspacedList

logger = logging.getLogger(__name__)

            

Reported by Pylint.

TODO: Check sites-available/ as well. For now, the configurator does
Error

Line: 41 Column: 3

                      self.config_root = self._find_config_root()

        # Parse nginx.conf and included files.
        # TODO: Check sites-available/ as well. For now, the configurator does
        # not enable sites from there.
        self.load()

    def load(self):
        """Loads Nginx files into a parsed tree.

            

Reported by Pylint.

XXX Windows nginx uses FindFirstFile, and
Error

Line: 201 Column: 3

              
        """
        files = glob.glob(filepath) # nginx on unix calls glob(3) for this
                                    # XXX Windows nginx uses FindFirstFile, and
                                    # should have a narrower call here
        trees = []
        for item in files:
            if item in self.parsed and not override:
                continue

            

Reported by Pylint.

TODO: https://github.com/certbot/certbot/issues/5185
Error

Line: 380 Column: 3

                      :returns: A vhost object for the newly created vhost
        :rtype: :class:`~certbot_nginx._internal.obj.VirtualHost`
        """
        # TODO: https://github.com/certbot/certbot/issues/5185
        # put it in the same file as the template, at the same level
        new_vhost = copy.deepcopy(vhost_template)

        enclosing_block = self.parsed[vhost_template.filep]
        for index in vhost_template.path[:-1]:

            

Reported by Pylint.

Method could be a function
Error

Line: 269 Column: 5

                      _apply_global_addr_ssl(addr_to_ssl, parsed_server)
        return parsed_server

    def has_ssl_on_directive(self, vhost):
        """Does vhost have ssl on for all ports?

        :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost in question

        :returns: True if 'ssl on' directive is included

            

Reported by Pylint.

certbot/certbot/_internal/log.py
6 issues
Unable to import 'acme'
Error

Line: 34 Column: 1

              from types import TracebackType
from typing import IO

from acme import messages
from certbot import errors
from certbot import util
from certbot._internal import constants
from certbot.compat import os


            

Reported by Pylint.

TODO: logs might contain sensitive data such as contents of the
Error

Line: 151 Column: 3

                  :rtype: tuple

    """
    # TODO: logs might contain sensitive data such as contents of the
    # private key! #525
    util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions)
    log_file_path = os.path.join(config.logs_dir, logfile)
    try:
        handler = logging.handlers.RotatingFileHandler(

            

Reported by Pylint.

TODO: creates empty letsencrypt.log.1 file
Error

Line: 164 Column: 3

                  # rotate on each invocation, rollover only possible when maxBytes
    # is nonzero and backupCount is nonzero, so we set maxBytes as big
    # as possible not to overrun in single CLI invocation (1MB).
    handler.doRollover()  # TODO: creates empty letsencrypt.log.1 file
    handler.setLevel(logging.DEBUG)
    handler_formatter = logging.Formatter(fmt=fmt)
    handler.setFormatter(handler_formatter)
    return handler, log_file_path


            

Reported by Pylint.

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Security

Line: 111
Suggestion: https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html

                      elif isinstance(handler, MemoryHandler):
            memory_handler = handler
    msg = 'Previously configured logging handlers have been removed!'
    assert memory_handler is not None and stderr_handler is not None, msg

    root_logger.addHandler(file_handler)
    root_logger.removeHandler(memory_handler)
    temp_handler = getattr(memory_handler, 'target', None)
    memory_handler.setTarget(file_handler)  # pylint: disable=no-member

            

Reported by Bandit.

Too many arguments (6/5)
Error

Line: 322 Column: 1

                      memory_handler.flush(force=True)


def post_arg_parse_except_hook(exc_type: type, exc_value: BaseException, trace: TracebackType,
                               debug: bool, quiet: bool, log_path: str):
    """Logs fatal exceptions and reports them to the user.

    If debug is True, the full exception and traceback is shown to the
    user, otherwise, it is suppressed. sys.exit is always called with a

            

Reported by Pylint.

Use of assert detected. The enclosed code will be removed when compiling to optimised byte code.
Security

Line: 345
Suggestion: https://bandit.readthedocs.io/en/latest/plugins/b101_assert_used.html

                  # display message the user, otherwise, a lower level like
    # logger.DEBUG should be used
    if debug or not issubclass(exc_type, Exception):
        assert constants.QUIET_LOGGING_LEVEL <= logging.ERROR
        if exc_type is KeyboardInterrupt:
            logger.error('Exiting due to user request.')
            sys.exit(1)
        logger.error('Exiting abnormally:', exc_info=exc_info)
    else:

            

Reported by Bandit.

certbot-apache/certbot_apache/_internal/augeasparser.py
5 issues
Unable to import 'certbot'
Error

Line: 69 Column: 1

              """
from typing import Set

from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import assertions
from certbot_apache._internal import interfaces
from certbot_apache._internal import parser

            

Reported by Pylint.

Unable to import 'certbot.compat'
Error

Line: 70 Column: 1

              from typing import Set

from certbot import errors
from certbot.compat import os
from certbot_apache._internal import apache_util
from certbot_apache._internal import assertions
from certbot_apache._internal import interfaces
from certbot_apache._internal import parser
from certbot_apache._internal import parsernode_util as util

            

Reported by Pylint.

Consider explicitly re-raising using the 'from' keyword
Error

Line: 97 Column: 13

                                  )
                )
        except KeyError:
            raise errors.PluginError("Augeas path is required")

    def save(self, msg):
        self.parser.save(msg)

    def find_ancestors(self, name):

            

Reported by Pylint.

Method could be a function
Error

Line: 149 Column: 5

                                             filepath=apache_util.get_file_path(path),
                               metadata=metadata)

    def _aug_get_name(self, path):
        """
        Helper function to get name of a configuration block or variable from path.
        """

        # Remove the ending slash if any

            

Reported by Pylint.

Variable name "pi" doesn't conform to snake_case naming style
Error

Line: 222 Column: 13

                          param_path = "{}/arg[1]".format(self.metadata["augeaspath"])
            self.parser.aug.remove(param_path)
        # Insert new ones
        for pi, param in enumerate(parameters):
            param_path = "{}/arg[{}]".format(self.metadata["augeaspath"], pi+1)
            self.parser.aug.set(param_path, param)

    @property
    def parameters(self):

            

Reported by Pylint.

certbot/certbot/plugins/dns_common.py
5 issues
Unable to import 'acme'
Error

Line: 9 Column: 1

              
import configobj

from acme import challenges
from certbot import errors
from certbot import interfaces
from certbot.compat import filesystem
from certbot.compat import os
from certbot.display import ops

            

Reported by Pylint.

Bad option value 'unused-private-member'
Error

Line: 170 Column: 1

                          indicate any issue.
        """

        def __validator(filename): # pylint: disable=unused-private-member
            configuration = CredentialsConfiguration(filename, self.dest)

            if required_variables:
                configuration.require(required_variables)


            

Reported by Pylint.

Bad option value 'unused-private-member'
Error

Line: 200 Column: 1

                      :rtype: str
        """

        def __validator(i): # pylint: disable=unused-private-member
            if not i:
                raise errors.PluginError('Please enter your {0}.'.format(label))

        code, response = ops.validated_input(
            __validator,

            

Reported by Pylint.

Bad option value 'unused-private-member'
Error

Line: 226 Column: 1

                      :rtype: str
        """

        def __validator(filename): # pylint: disable=unused-private-member
            if not filename:
                raise errors.PluginError('Please enter a valid path to your {0}.'.format(label))

            filename = os.path.expanduser(filename)


            

Reported by Pylint.

Variable name "e" doesn't conform to snake_case naming style
Error

Line: 260 Column: 9

              
        try:
            self.confobj = configobj.ConfigObj(filename)
        except configobj.ConfigObjError as e:
            logger.debug("Error parsing credentials configuration: %s", e, exc_info=True)
            raise errors.PluginError("Error parsing credentials configuration: {0}".format(e))

        self.mapper = mapper


            

Reported by Pylint.

acme/acme/standalone.py
5 issues
TODO: c.f. #858
Error

Line: 52 Column: 3

              
class ACMEServerMixin:
    """ACME server common settings mixin."""
    # TODO: c.f. #858
    server_version = "ACME client standalone challenge solver"
    allow_reuse_address = True


class BaseDualNetworkedServers:

            

Reported by Pylint.

TODO: We would like to serve challenge cert only if asked for it via
Error

Line: 149 Column: 3

                      self.challenge_certs = challenge_certs

    def _cert_selection(self, connection):
        # TODO: We would like to serve challenge cert only if asked for it via
        # ALPN. To do this, we need to retrieve the list of protos from client
        # hello, but this is currently impossible with openssl [0], and ALPN
        # negotiation is done after cert selection.
        # Therefore, currently we always return challenge cert, and terminate
        # handshake in alpn_selection() if ALPN protos are not what we expect.

            

Reported by Pylint.

Too few public methods (0/2)
Error

Line: 50 Column: 1

                      return socketserver.TCPServer.server_bind(self)


class ACMEServerMixin:
    """ACME server common settings mixin."""
    # TODO: c.f. #858
    server_version = "ACME client standalone challenge solver"
    allow_reuse_address = True


            

Reported by Pylint.

Variable name "e" doesn't conform to snake_case naming style
Error

Line: 89 Column: 13

                              logger.debug(
                    "Successfully bound to %s:%s using %s", new_address[0],
                    new_address[1], "IPv6" if ip_version else "IPv4")
            except socket.error as e:
                last_socket_err = e
                if self.servers:
                    # Already bound using IPv6.
                    logger.debug(
                        "Certbot wasn't able to bind to %s:%s using %s, this "

            

Reported by Pylint.

Unnecessary "else" after "raise"
Error

Line: 109 Column: 13

                              # bind to the same port for both servers.
                port = server.socket.getsockname()[1]
        if not self.servers:
            if last_socket_err:
                raise last_socket_err
            else: # pragma: no cover
                raise socket.error("Could not bind to IPv4 or IPv6.")

    def serve_forever(self):

            

Reported by Pylint.

tools/install_and_test.py
5 issues
subprocess call with shell=True identified, security issue.
Security injection

Line: 15
Suggestion: https://bandit.readthedocs.io/en/latest/plugins/b602_subprocess_popen_with_shell_equals_true.html

              
def call_with_print(command):
    print(command)
    subprocess.check_call(command, shell=True)


def main(args):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    command = [sys.executable, os.path.join(script_dir, 'pip_install_editable.py')]

            

Reported by Bandit.

Missing module docstring
Error

Line: 1 Column: 1

              #!/usr/bin/env python
# pip installs the requested packages in editable mode and runs unit tests on
# them. Each package is installed and tested in the order they are provided
# before the script moves on to the next package. If CERTBOT_NO_PIN is set not
# set to 1, packages are installed using pinned versions of all of our
# dependencies. See pip_install.py for more information on the versions pinned
# to.
import os
import re

            

Reported by Pylint.

Consider possible security implications associated with subprocess module.
Security blacklist

Line: 10
Suggestion: https://bandit.readthedocs.io/en/latest/blacklists/blacklist_imports.html#b404-import-subprocess

              # to.
import os
import re
import subprocess
import sys

def call_with_print(command):
    print(command)
    subprocess.check_call(command, shell=True)

            

Reported by Bandit.

Missing function or method docstring
Error

Line: 13 Column: 1

              import subprocess
import sys

def call_with_print(command):
    print(command)
    subprocess.check_call(command, shell=True)


def main(args):

            

Reported by Pylint.

Missing function or method docstring
Error

Line: 18 Column: 1

                  subprocess.check_call(command, shell=True)


def main(args):
    script_dir = os.path.dirname(os.path.abspath(__file__))
    command = [sys.executable, os.path.join(script_dir, 'pip_install_editable.py')]

    for requirement in args:
        current_command = command[:]

            

Reported by Pylint.

certbot/certbot/_internal/plugins/selection.py
5 issues
Unnecessary "elif" after "return"
Error

Line: 109 Column: 5

                  verified.prepare()
    prepared = verified.available()

    if len(prepared) > 1:
        logger.debug("Multiple candidate plugins: %s", prepared)
        plugin_ep = choose_plugin(list(prepared.values()), question)
        if plugin_ep is None:
            return None
        return plugin_ep.init()

            

Reported by Pylint.

Too many branches (13/12)
Error

Line: 168 Column: 1

                       config.authenticator, config.installer)


def choose_configurator_plugins(config: configuration.NamespaceConfig,
                                plugins: disco.PluginsRegistry,
                                verb: str) -> Tuple[Optional[interfaces.Installer],
                                                    Optional[interfaces.Authenticator]]:
    """
    Figure out which configurator we're going to use, modifies

            

Reported by Pylint.

Import outside toplevel (certbot._internal.cli.cli_command)
Error

Line: 193 Column: 9

                  # Which plugins do we need?
    if verb == "run":
        need_inst = need_auth = True
        from certbot._internal.cli import cli_command
        if req_auth in noninstaller_plugins and not req_inst:
            msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}'
                   '{1}    {2} certonly --{0}{1}{1}'
                   '(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins'
                   '{1} and "--help plugins" for more information.)'.format(

            

Reported by Pylint.

Too many branches (19/12)
Error

Line: 250 Column: 1

                  return now


def cli_plugin_requests(config):
    """
    Figure out which plugins the user requested with CLI and config options

    :returns: (requested authenticator string or None, requested installer string or None)
    :rtype: tuple

            

Reported by Pylint.

Import outside toplevel (certbot._internal.cli.cli_command)
Error

Line: 324 Column: 9

                                 "your existing configuration.\nThe error was: {1!r}"
                   .format(requested, plugins[requested].problem))
    elif cfg_type == "installer":
        from certbot._internal.cli import cli_command
        msg = ('Certbot doesn\'t know how to automatically configure the web '
          'server on this system. However, it can still get a certificate for '
          'you. Please run "{0} certonly" to do so. You\'ll need to '
          'manually configure your web server to use the resulting '
          'certificate.').format(cli_command)

            

Reported by Pylint.