Merge branch 'master' into apache-session-tix

This commit is contained in:
Erica Portnoy 2020-03-11 12:59:07 -07:00
commit 5fc288d29c
106 changed files with 2282 additions and 3686 deletions

View file

@ -0,0 +1,13 @@
# Advanced pipeline for running our full test suite on demand.
trigger:
# When changing these triggers, please ensure the documentation under
# "Running tests in CI" is still correct.
- azure-test-*
- test-*
pr: none
jobs:
# Any addition here should be reflected in the advanced and release pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View file

@ -1,12 +1,7 @@
# Advanced pipeline for isolated checks and release purpose
# Advanced pipeline for running our full test suite on protected branches.
trigger:
# When changing these triggers, please ensure the documentation under
# "Running tests in CI" is still correct.
- azure-test-*
- test-*
- '*.x'
pr:
- test-*
pr: none
# This pipeline is also nightly run on master
schedules:
- cron: "0 4 * * *"
@ -17,7 +12,7 @@ schedules:
always: true
jobs:
# Any addition here should be reflected in the release pipeline.
# Any addition here should be reflected in the advanced-test and release pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View file

@ -6,7 +6,7 @@ trigger:
pr: none
jobs:
# Any addition here should be reflected in the advanced pipeline.
# Any addition here should be reflected in the advanced and advanced-test pipelines.
# It is advised to declare all jobs here as templates to improve maintainability.
- template: templates/tests-suite.yml
- template: templates/installer-tests.yml

View file

@ -28,15 +28,13 @@ jobs:
imageName: windows-2019
win2016:
imageName: vs2017-win2016
win2012r2:
imageName: vs2015-win2012r2
pool:
vmImage: $(imageName)
steps:
- powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.1/python-3.8.1-amd64-webinstall.exe -OutFile C:\py3-setup.exe
displayName: Get Python
- script: C:\py3-setup.exe /quiet PrependPath=1 InstallAllUsers=1 Include_launcher=1 InstallLauncherAllUsers=1 Include_test=0 Include_doc=0 Include_dev=1 Include_debug=0 Include_tcltk=0 TargetDir=C:\py3
displayName: Install Python
- task: UsePythonVersion@0
inputs:
versionSpec: 3.8
addToPath: true
- task: DownloadPipelineArtifact@2
inputs:
artifact: windows-installer

View file

@ -1,22 +1,34 @@
jobs:
- job: test
pool:
vmImage: vs2017-win2016
strategy:
matrix:
py35:
macos-py27:
IMAGE_NAME: macOS-10.14
PYTHON_VERSION: 2.7
TOXENV: py27
macos-py38:
IMAGE_NAME: macOS-10.14
PYTHON_VERSION: 3.8
TOXENV: py38
windows-py35:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.5
TOXENV: py35
py37-cover:
windows-py37-cover:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7
TOXENV: py37-cover
integration-certbot:
windows-integration-certbot:
IMAGE_NAME: vs2017-win2016
PYTHON_VERSION: 3.7
TOXENV: integration-certbot
PYTEST_ADDOPTS: --numprocesses 4
variables:
- group: certbot-common
pool:
vmImage: $(IMAGE_NAME)
steps:
- bash: brew install augeas
condition: startswith(variables['IMAGE_NAME'], 'macOS')
displayName: Install Augeas
- task: UsePythonVersion@0
inputs:
versionSpec: $(PYTHON_VERSION)
@ -25,14 +37,3 @@ jobs:
displayName: Install dependencies
- script: python -m tox
displayName: Run tox
# We do not require codecov report upload to succeed. So to avoid to break the pipeline if
# something goes wrong, each command is suffixed with a command that hides any non zero exit
# codes and echoes an informative message instead.
- bash: |
curl -s https://codecov.io/bash -o codecov-bash || echo "Failed to download codecov-bash"
chmod +x codecov-bash || echo "Failed to apply execute permissions on codecov-bash"
./codecov-bash -F windows || echo "Codecov did not collect coverage reports"
condition: in(variables['TOXENV'], 'py37-cover', 'integration-certbot')
env:
CODECOV_TOKEN: $(codecov_token)
displayName: Publish coverage

View file

@ -1,18 +0,0 @@
coverage:
status:
project:
default: off
linux:
flags: linux
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.4
threshold: 0.1
base: auto
windows:
flags: windows
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.4
threshold: 0.1
base: auto

View file

@ -6,7 +6,6 @@ cache:
- $HOME/.cache/pip
before_script:
- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi'
# On Travis, the fastest parallelization for integration tests has proved to be 4.
- 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi'
# Use Travis retry feature for farm tests since they are flaky
@ -224,24 +223,6 @@ matrix:
packages: # don't install nginx and apache
- libaugeas0
<<: *extended-test-suite
- language: generic
env: TOXENV=py27
os: osx
addons:
homebrew:
packages:
- augeas
- python2
<<: *extended-test-suite
- language: generic
env: TOXENV=py3
os: osx
addons:
homebrew:
packages:
- augeas
- python3
<<: *extended-test-suite
# container-based infrastructure
sudo: false
@ -266,15 +247,13 @@ addons:
# version of virtualenv. The option "-I" is set so when CERTBOT_NO_PIN is also
# set, pip updates dependencies it thinks are already satisfied to avoid some
# problems with its lack of real dependency resolution.
install: 'tools/pip_install.py -I codecov tox virtualenv'
install: 'tools/pip_install.py -I tox virtualenv'
# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the
# script command. It is set only to `travis_retry` during farm tests, in
# order to trigger the Travis retry feature, and compensate the inherent
# flakiness of these specific tests.
script: '$TRAVIS_RETRY tox'
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
notifications:
email: false
irc:

View file

@ -54,7 +54,7 @@ class UnrecognizedChallenge(Challenge):
object.__setattr__(self, "jobj", jobj)
def to_partial_json(self):
return self.jobj
return self.jobj # pylint: disable=no-member
@classmethod
def from_json(cls, jobj):

View file

@ -1022,6 +1022,9 @@ class ClientNetwork(object):
"""
response_ct = response.headers.get('Content-Type')
# Strip parameters from the media-type (rfc2616#section-3.7)
if response_ct:
response_ct = response_ct.split(';')[0].strip()
try:
# TODO: response.json() is called twice, once here, and
# once in _get and _post clients

View file

@ -25,7 +25,7 @@ class Header(jose.Header):
class Signature(jose.Signature):
"""ACME-specific Signature. Uses ACME-specific Header for customer fields."""
__slots__ = jose.Signature._orig_slots
__slots__ = jose.Signature._orig_slots # pylint: disable=no-member
# TODO: decoder/encoder should accept cls? Otherwise, subclassing
# JSONObjectWithFields is tricky...

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -980,6 +980,35 @@ class ClientNetworkTest(unittest.TestCase):
self.assertEqual(
self.response, self.net._check_response(self.response))
@mock.patch('acme.client.logger')
def test_check_response_ok_ct_with_charset(self, mock_logger):
self.response.json.return_value = {}
self.response.headers['Content-Type'] = 'application/json; charset=utf-8'
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._check_response(
self.response, content_type='application/json'))
try:
mock_logger.debug.assert_called_with(
'Ignoring wrong Content-Type (%r) for JSON decodable response',
'application/json; charset=utf-8'
)
except AssertionError:
return
raise AssertionError('Expected Content-Type warning ' #pragma: no cover
'to not have been logged')
@mock.patch('acme.client.logger')
def test_check_response_ok_bad_ct(self, mock_logger):
self.response.json.return_value = {}
self.response.headers['Content-Type'] = 'text/plain'
# pylint: disable=protected-access
self.assertEqual(self.response, self.net._check_response(
self.response, content_type='application/json'))
mock_logger.debug.assert_called_with(
'Ignoring wrong Content-Type (%r) for JSON decodable response',
'text/plain'
)
def test_check_response_conflict(self):
self.response.ok = False
self.response.status_code = 409

View file

@ -2516,7 +2516,7 @@ class ApacheConfigurator(common.Installer):
:type _unused_lineage: certbot._internal.storage.RenewableCert
:param domains: List of domains in certificate to enhance
:type domains: str
:type domains: `list` of `str`
"""
self._autohsts_fetch_state()

View file

@ -1,5 +1,6 @@
"""A class that performs HTTP-01 challenges for Apache"""
import logging
import errno
from acme.magic_typing import List
from acme.magic_typing import Set
@ -168,7 +169,15 @@ class ApacheHttp01(common.ChallengePerformer):
def _set_up_challenges(self):
if not os.path.isdir(self.challenge_dir):
filesystem.makedirs(self.challenge_dir, 0o755)
old_umask = os.umask(0o022)
try:
filesystem.makedirs(self.challenge_dir, 0o755)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for http-01 challenge")
finally:
os.umask(old_umask)
responses = []
for achall in self.achalls:

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -1,5 +1,6 @@
"""Test for certbot_apache._internal.http_01."""
import unittest
import errno
import mock
@ -197,6 +198,12 @@ class ApacheHttp01Test(util.ApacheTest):
self.assertTrue(os.path.exists(challenge_dir))
@mock.patch("certbot_apache._internal.http_01.filesystem.makedirs")
def test_failed_makedirs(self, mock_makedirs):
mock_makedirs.side_effect = OSError(errno.EACCES, "msg")
self.http.add_chall(self.achalls[0])
self.assertRaises(errors.PluginError, self.http.perform)
def _test_challenge_conf(self):
with open(self.http.challenge_conf_pre) as f:
pre_conf_contents = f.read()

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="1.2.0"
LE_AUTO_VERSION="1.3.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==1.2.0 \
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
acme==1.2.0 \
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
certbot-apache==1.2.0 \
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
certbot-nginx==1.2.0 \
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
certbot==1.3.0 \
--hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \
--hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8
acme==1.3.0 \
--hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \
--hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d
certbot-apache==1.3.0 \
--hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \
--hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52
certbot-nginx==1.3.0 \
--hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \
--hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b
UNLIKELY_EOF
# -------------------------------------------------------------------------

View file

@ -595,6 +595,23 @@ def test_ocsp_status_live(context):
assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert)
def test_ocsp_renew(context):
"""Test that revoked certificates are renewed."""
# Obtain a certificate
certname = context.get_domain('ocsp-renew')
context.certbot(['--domains', certname])
# Test that "certbot renew" does not renew the certificate
assert_cert_count_for_lineage(context.config_dir, certname, 1)
context.certbot(['renew'], force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 1)
# Revoke the certificate and test that it does renew the certificate
context.certbot(['revoke', '--cert-name', certname, '--no-delete-after-revoke'])
context.certbot(['renew'], force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 2)
def test_dry_run_deactivate_authzs(context):
"""Test that Certbot deactivates authorizations when performing a dry run"""

View file

@ -31,7 +31,7 @@ COPY certbot-nginx /opt/certbot/src/certbot-nginx/
COPY certbot-compatibility-test /opt/certbot/src/certbot-compatibility-test/
COPY tools /opt/certbot/src/tools
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv && \
RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv -p python2 /opt/certbot/venv && \
/opt/certbot/venv/bin/pip install -U setuptools && \
/opt/certbot/venv/bin/pip install -U pip
ENV PATH /opt/certbot/venv/bin:$PATH

View file

@ -3,7 +3,7 @@ import sys
from setuptools import find_packages
from setuptools import setup
version = '1.3.0.dev0'
version = '1.4.0.dev0'
install_requires = [
'certbot',

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -5,7 +5,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View file

@ -4,7 +4,7 @@ from setuptools import find_packages
from setuptools import setup
from setuptools.command.test import test as TestCommand
version = '1.3.0.dev0'
version = '1.4.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View file

@ -2,14 +2,10 @@
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 1.3.0 - master
## 1.4.0 - master
### Added
* Added certbot.ocsp Certbot's API. The certbot.ocsp module can be used to
determine the OCSP status of certificates.
* Don't verify the existing certificate in HTTP01Response.simple_verify, for
compatibility with the real-world ACME challenge checks.
* Turn off session tickets for apache plugin by default when appropriate.
### Changed
@ -22,12 +18,35 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
More details about these changes can be found on our GitHub repo.
## 1.3.0 - 2020-03-03
### Added
* Added certbot.ocsp Certbot's API. The certbot.ocsp module can be used to
determine the OCSP status of certificates.
* Don't verify the existing certificate in HTTP01Response.simple_verify, for
compatibility with the real-world ACME challenge checks.
* Added support for `$hostname` in nginx `server_name` directive
### Changed
* Certbot will now renew certificates early if they have been revoked according
to OCSP.
* Fix acme module warnings when response Content-Type includes params (e.g. charset).
* Fixed issue where webroot plugin would incorrectly raise `Read-only file system`
error when creating challenge directories (issue #7165).
### Fixed
* Fix Apache plugin to use less restrictive umask for making the challenge directory when a restrictive umask was set when certbot was started.
More details about these changes can be found on our GitHub repo.
## 1.2.0 - 2020-02-04
### Added
* Added support for Cloudflare's limited-scope API Tokens
* Added support for `$hostname` in nginx `server_name` directive
### Changed

View file

@ -71,16 +71,12 @@ ACME spec: http://ietf-wg-acme.github.io/acme/
ACME working area in github: https://github.com/ietf-wg-acme/acme
|build-status| |coverage| |container|
|build-status| |container|
.. |build-status| image:: https://travis-ci.com/certbot/certbot.svg?branch=master
:target: https://travis-ci.com/certbot/certbot
:alt: Travis CI status
.. |coverage| image:: https://codecov.io/gh/certbot/certbot/branch/master/graph/badge.svg
:target: https://codecov.io/gh/certbot/certbot
:alt: Coverage status
.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status
:target: https://quay.io/repository/letsencrypt/letsencrypt
:alt: Docker Repository on Quay.io

View file

@ -1,4 +1,4 @@
"""Certbot client."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = '1.3.0.dev0'
__version__ = '1.4.0.dev0'

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,526 @@
"""Certbot command line argument & config processing."""
# pylint: disable=too-many-lines
from __future__ import print_function
import logging
import logging.handlers
import argparse
import sys
import certbot._internal.plugins.selection as plugin_selection
from certbot._internal.plugins import disco as plugins_disco
from acme.magic_typing import Optional
# pylint: disable=ungrouped-imports
import certbot
from certbot._internal import constants
import certbot.plugins.enhancements as enhancements
from certbot._internal.cli.cli_constants import (
LEAUTO,
old_path_fragment,
new_path_prefix,
cli_command,
SHORT_USAGE,
COMMAND_OVERVIEW,
HELP_AND_VERSION_USAGE,
ARGPARSE_PARAMS_TO_REMOVE,
EXIT_ACTIONS,
ZERO_ARG_ACTIONS,
VAR_MODIFIERS
)
from certbot._internal.cli.cli_utils import (
_Default,
read_file,
flag_default,
config_help,
HelpfulArgumentGroup,
CustomHelpFormatter,
_DomainsAction,
add_domains,
CaseInsensitiveList,
_user_agent_comment_type,
_EncodeReasonAction,
parse_preferred_challenges,
_PrefChallAction,
_DeployHookAction,
_RenewHookAction,
nonnegative_int
)
# These imports depend on cli_constants and cli_utils.
from certbot._internal.cli.report_config_interaction import report_config_interaction
from certbot._internal.cli.verb_help import VERB_HELP, VERB_HELP_MAP
from certbot._internal.cli.group_adder import _add_all_groups
from certbot._internal.cli.subparsers import _create_subparsers
from certbot._internal.cli.paths_parser import _paths_parser
from certbot._internal.cli.plugins_parsing import _plugins_parsing
# These imports depend on some or all of the submodules for cli.
from certbot._internal.cli.helpful import HelpfulArgumentParser
# pylint: enable=ungrouped-imports
logger = logging.getLogger(__name__)
# Global, to save us from a lot of argument passing within the scope of this module
helpful_parser = None # type: Optional[HelpfulArgumentParser]
def prepare_and_parse_args(plugins, args, detect_defaults=False):
"""Returns parsed command line arguments.
:param .PluginsRegistry plugins: available plugins
:param list args: command line arguments with the program name removed
:returns: parsed command line arguments
:rtype: argparse.Namespace
"""
helpful = HelpfulArgumentParser(args, plugins, detect_defaults)
_add_all_groups(helpful)
# --help is automatically provided by argparse
helpful.add(
None, "-v", "--verbose", dest="verbose_count", action="count",
default=flag_default("verbose_count"), help="This flag can be used "
"multiple times to incrementally increase the verbosity of output, "
"e.g. -vvv.")
helpful.add(
None, "-t", "--text", dest="text_mode", action="store_true",
default=flag_default("text_mode"), help=argparse.SUPPRESS)
helpful.add(
None, "--max-log-backups", type=nonnegative_int,
default=flag_default("max_log_backups"),
help="Specifies the maximum number of backup logs that should "
"be kept by Certbot's built in log rotation. Setting this "
"flag to 0 disables log rotation entirely, causing "
"Certbot to always append to the same log file.")
helpful.add(
[None, "automation", "run", "certonly", "enhance"],
"-n", "--non-interactive", "--noninteractive",
dest="noninteractive_mode", action="store_true",
default=flag_default("noninteractive_mode"),
help="Run without ever asking for user input. This may require "
"additional command line flags; the client will try to explain "
"which ones are required if it finds one missing")
helpful.add(
[None, "register", "run", "certonly", "enhance"],
constants.FORCE_INTERACTIVE_FLAG, action="store_true",
default=flag_default("force_interactive"),
help="Force Certbot to be interactive even if it detects it's not "
"being run in a terminal. This flag cannot be used with the "
"renew subcommand.")
helpful.add(
[None, "run", "certonly", "certificates", "enhance"],
"-d", "--domains", "--domain", dest="domains",
metavar="DOMAIN", action=_DomainsAction,
default=flag_default("domains"),
help="Domain names to apply. For multiple domains you can use "
"multiple -d flags or enter a comma separated list of domains "
"as a parameter. The first domain provided will be the "
"subject CN of the certificate, and all domains will be "
"Subject Alternative Names on the certificate. "
"The first domain will also be used in "
"some software user interfaces and as the file paths for the "
"certificate and related material unless otherwise "
"specified or you already have a certificate with the same "
"name. In the case of a name collision it will append a number "
"like 0001 to the file path name. (default: Ask)")
helpful.add(
[None, "run", "certonly", "register"],
"--eab-kid", dest="eab_kid",
metavar="EAB_KID",
help="Key Identifier for External Account Binding"
)
helpful.add(
[None, "run", "certonly", "register"],
"--eab-hmac-key", dest="eab_hmac_key",
metavar="EAB_HMAC_KEY",
help="HMAC key for External Account Binding"
)
helpful.add(
[None, "run", "certonly", "manage", "delete", "certificates",
"renew", "enhance"], "--cert-name", dest="certname",
metavar="CERTNAME", default=flag_default("certname"),
help="Certificate name to apply. This name is used by Certbot for housekeeping "
"and in file paths; it doesn't affect the content of the certificate itself. "
"To see certificate names, run 'certbot certificates'. "
"When creating a new certificate, specifies the new certificate's name. "
"(default: the first provided domain or the name of an existing "
"certificate on your system for the same domains)")
helpful.add(
[None, "testing", "renew", "certonly"],
"--dry-run", action="store_true", dest="dry_run",
default=flag_default("dry_run"),
help="Perform a test run of the client, obtaining test (invalid) certificates"
" but not saving them to disk. This can currently only be used"
" with the 'certonly' and 'renew' subcommands. \nNote: Although --dry-run"
" tries to avoid making any persistent changes on a system, it "
" is not completely side-effect free: if used with webserver authenticator plugins"
" like apache and nginx, it makes and then reverts temporary config changes"
" in order to obtain test certificates, and reloads webservers to deploy and then"
" roll back those changes. It also calls --pre-hook and --post-hook commands"
" if they are defined because they may be necessary to accurately simulate"
" renewal. --deploy-hook commands are not called.")
helpful.add(
["register", "automation"], "--register-unsafely-without-email", action="store_true",
default=flag_default("register_unsafely_without_email"),
help="Specifying this flag enables registering an account with no "
"email address. This is strongly discouraged, because in the "
"event of key loss or account compromise you will irrevocably "
"lose access to your account. You will also be unable to receive "
"notice about impending expiration or revocation of your "
"certificates. Updates to the Subscriber Agreement will still "
"affect you, and will be effective 14 days after posting an "
"update to the web site.")
helpful.add(
["register", "update_account", "unregister", "automation"], "-m", "--email",
default=flag_default("email"),
help=config_help("email"))
helpful.add(["register", "update_account", "automation"], "--eff-email", action="store_true",
default=flag_default("eff_email"), dest="eff_email",
help="Share your e-mail address with EFF")
helpful.add(["register", "update_account", "automation"], "--no-eff-email",
action="store_false", default=flag_default("eff_email"), dest="eff_email",
help="Don't share your e-mail address with EFF")
helpful.add(
["automation", "certonly", "run"],
"--keep-until-expiring", "--keep", "--reinstall",
dest="reinstall", action="store_true", default=flag_default("reinstall"),
help="If the requested certificate matches an existing certificate, always keep the "
"existing one until it is due for renewal (for the "
"'run' subcommand this means reinstall the existing certificate). (default: Ask)")
helpful.add(
"automation", "--expand", action="store_true", default=flag_default("expand"),
help="If an existing certificate is a strict subset of the requested names, "
"always expand and replace it with the additional names. (default: Ask)")
helpful.add(
"automation", "--version", action="version",
version="%(prog)s {0}".format(certbot.__version__),
help="show program's version number and exit")
helpful.add(
["automation", "renew"],
"--force-renewal", "--renew-by-default", dest="renew_by_default",
action="store_true", default=flag_default("renew_by_default"),
help="If a certificate "
"already exists for the requested domains, renew it now, "
"regardless of whether it is near expiry. (Often "
"--keep-until-expiring is more appropriate). Also implies "
"--expand.")
helpful.add(
"automation", "--renew-with-new-domains", dest="renew_with_new_domains",
action="store_true", default=flag_default("renew_with_new_domains"),
help="If a "
"certificate already exists for the requested certificate name "
"but does not match the requested domains, renew it now, "
"regardless of whether it is near expiry.")
helpful.add(
"automation", "--reuse-key", dest="reuse_key",
action="store_true", default=flag_default("reuse_key"),
help="When renewing, use the same private key as the existing "
"certificate.")
helpful.add(
["automation", "renew", "certonly"],
"--allow-subset-of-names", action="store_true",
default=flag_default("allow_subset_of_names"),
help="When performing domain validation, do not consider it a failure "
"if authorizations can not be obtained for a strict subset of "
"the requested domains. This may be useful for allowing renewals for "
"multiple domains to succeed even if some domains no longer point "
"at this system. This option cannot be used with --csr.")
helpful.add(
"automation", "--agree-tos", dest="tos", action="store_true",
default=flag_default("tos"),
help="Agree to the ACME Subscriber Agreement (default: Ask)")
helpful.add(
["unregister", "automation"], "--account", metavar="ACCOUNT_ID",
default=flag_default("account"),
help="Account ID to use")
helpful.add(
"automation", "--duplicate", dest="duplicate", action="store_true",
default=flag_default("duplicate"),
help="Allow making a certificate lineage that duplicates an existing one "
"(both can be renewed in parallel)")
helpful.add(
"automation", "--os-packages-only", action="store_true",
default=flag_default("os_packages_only"),
help="(certbot-auto only) install OS package dependencies and then stop")
helpful.add(
"automation", "--no-self-upgrade", action="store_true",
default=flag_default("no_self_upgrade"),
help="(certbot-auto only) prevent the certbot-auto script from"
" upgrading itself to newer released versions (default: Upgrade"
" automatically)")
helpful.add(
"automation", "--no-bootstrap", action="store_true",
default=flag_default("no_bootstrap"),
help="(certbot-auto only) prevent the certbot-auto script from"
" installing OS-level dependencies (default: Prompt to install "
" OS-wide dependencies, but exit if the user says 'No')")
helpful.add(
"automation", "--no-permissions-check", action="store_true",
default=flag_default("no_permissions_check"),
help="(certbot-auto only) skip the check on the file system"
" permissions of the certbot-auto script")
helpful.add(
["automation", "renew", "certonly", "run"],
"-q", "--quiet", dest="quiet", action="store_true",
default=flag_default("quiet"),
help="Silence all output except errors. Useful for automation via cron."
" Implies --non-interactive.")
# overwrites server, handled in HelpfulArgumentParser.parse_args()
helpful.add(["testing", "revoke", "run"], "--test-cert", "--staging",
dest="staging", action="store_true", default=flag_default("staging"),
help="Use the staging server to obtain or revoke test (invalid) certificates; equivalent"
" to --server " + constants.STAGING_URI)
helpful.add(
"testing", "--debug", action="store_true", default=flag_default("debug"),
help="Show tracebacks in case of errors, and allow certbot-auto "
"execution on experimental platforms")
helpful.add(
[None, "certonly", "run"], "--debug-challenges", action="store_true",
default=flag_default("debug_challenges"),
help="After setting up challenges, wait for user input before "
"submitting to CA")
helpful.add(
"testing", "--no-verify-ssl", action="store_true",
help=config_help("no_verify_ssl"),
default=flag_default("no_verify_ssl"))
helpful.add(
["testing", "standalone", "manual"], "--http-01-port", type=int,
dest="http01_port",
default=flag_default("http01_port"), help=config_help("http01_port"))
helpful.add(
["testing", "standalone"], "--http-01-address",
dest="http01_address",
default=flag_default("http01_address"), help=config_help("http01_address"))
helpful.add(
["testing", "nginx"], "--https-port", type=int,
default=flag_default("https_port"),
help=config_help("https_port"))
helpful.add(
"testing", "--break-my-certs", action="store_true",
default=flag_default("break_my_certs"),
help="Be willing to replace or renew valid certificates with invalid "
"(testing/staging) certificates")
helpful.add(
"security", "--rsa-key-size", type=int, metavar="N",
default=flag_default("rsa_key_size"), help=config_help("rsa_key_size"))
helpful.add(
"security", "--must-staple", action="store_true",
dest="must_staple", default=flag_default("must_staple"),
help=config_help("must_staple"))
helpful.add(
["security", "enhance"],
"--redirect", action="store_true", dest="redirect",
default=flag_default("redirect"),
help="Automatically redirect all HTTP traffic to HTTPS for the newly "
"authenticated vhost. (default: Ask)")
helpful.add(
"security", "--no-redirect", action="store_false", dest="redirect",
default=flag_default("redirect"),
help="Do not automatically redirect all HTTP traffic to HTTPS for the newly "
"authenticated vhost. (default: Ask)")
helpful.add(
["security", "enhance"],
"--hsts", action="store_true", dest="hsts", default=flag_default("hsts"),
help="Add the Strict-Transport-Security header to every HTTP response."
" Forcing browser to always use SSL for the domain."
" Defends against SSL Stripping.")
helpful.add(
"security", "--no-hsts", action="store_false", dest="hsts",
default=flag_default("hsts"), help=argparse.SUPPRESS)
helpful.add(
["security", "enhance"],
"--uir", action="store_true", dest="uir", default=flag_default("uir"),
help='Add the "Content-Security-Policy: upgrade-insecure-requests"'
' header to every HTTP response. Forcing the browser to use'
' https:// for every http:// resource.')
helpful.add(
"security", "--no-uir", action="store_false", dest="uir", default=flag_default("uir"),
help=argparse.SUPPRESS)
helpful.add(
"security", "--staple-ocsp", action="store_true", dest="staple",
default=flag_default("staple"),
help="Enables OCSP Stapling. A valid OCSP response is stapled to"
" the certificate that the server offers during TLS.")
helpful.add(
"security", "--no-staple-ocsp", action="store_false", dest="staple",
default=flag_default("staple"), help=argparse.SUPPRESS)
helpful.add(
"security", "--strict-permissions", action="store_true",
default=flag_default("strict_permissions"),
help="Require that all configuration files are owned by the current "
"user; only needed if your config is somewhere unsafe like /tmp/")
helpful.add(
["manual", "standalone", "certonly", "renew"],
"--preferred-challenges", dest="pref_challs",
action=_PrefChallAction, default=flag_default("pref_challs"),
help='A sorted, comma delimited list of the preferred challenge to '
'use during authorization with the most preferred challenge '
'listed first (Eg, "dns" or "http,dns"). '
'Not all plugins support all challenges. See '
'https://certbot.eff.org/docs/using.html#plugins for details. '
'ACME Challenges are versioned, but if you pick "http" rather '
'than "http-01", Certbot will select the latest version '
'automatically.')
helpful.add(
"renew", "--pre-hook",
help="Command to be run in a shell before obtaining any certificates."
" Intended primarily for renewal, where it can be used to temporarily"
" shut down a webserver that might conflict with the standalone"
" plugin. This will only be called if a certificate is actually to be"
" obtained/renewed. When renewing several certificates that have"
" identical pre-hooks, only the first will be executed.")
helpful.add(
"renew", "--post-hook",
help="Command to be run in a shell after attempting to obtain/renew"
" certificates. Can be used to deploy renewed certificates, or to"
" restart any servers that were stopped by --pre-hook. This is only"
" run if an attempt was made to obtain/renew a certificate. If"
" multiple renewed certificates have identical post-hooks, only"
" one will be run.")
helpful.add("renew", "--renew-hook",
action=_RenewHookAction, help=argparse.SUPPRESS)
helpful.add(
"renew", "--no-random-sleep-on-renew", action="store_false",
default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew",
help=argparse.SUPPRESS)
helpful.add(
"renew", "--deploy-hook", action=_DeployHookAction,
help='Command to be run in a shell once for each successfully'
' issued certificate. For this command, the shell variable'
' $RENEWED_LINEAGE will point to the config live subdirectory'
' (for example, "/etc/letsencrypt/live/example.com") containing'
' the new certificates and keys; the shell variable'
' $RENEWED_DOMAINS will contain a space-delimited list of'
' renewed certificate domains (for example, "example.com'
' www.example.com"')
helpful.add(
"renew", "--disable-hook-validation",
action="store_false", dest="validate_hooks",
default=flag_default("validate_hooks"),
help="Ordinarily the commands specified for"
" --pre-hook/--post-hook/--deploy-hook will be checked for"
" validity, to see if the programs being run are in the $PATH,"
" so that mistakes can be caught early, even when the hooks"
" aren't being run just yet. The validation is rather"
" simplistic and fails if you use more advanced shell"
" constructs, so you can use this switch to disable it."
" (default: False)")
helpful.add(
"renew", "--no-directory-hooks", action="store_false",
default=flag_default("directory_hooks"), dest="directory_hooks",
help="Disable running executables found in Certbot's hook directories"
" during renewal. (default: False)")
helpful.add(
"renew", "--disable-renew-updates", action="store_true",
default=flag_default("disable_renew_updates"), dest="disable_renew_updates",
help="Disable automatic updates to your server configuration that"
" would otherwise be done by the selected installer plugin, and triggered"
" when the user executes \"certbot renew\", regardless of if the certificate"
" is renewed. This setting does not apply to important TLS configuration"
" updates.")
helpful.add(
"renew", "--no-autorenew", action="store_false",
default=flag_default("autorenew"), dest="autorenew",
help="Disable auto renewal of certificates.")
# Populate the command line parameters for new style enhancements
enhancements.populate_cli(helpful.add)
_create_subparsers(helpful)
_paths_parser(helpful)
# _plugins_parsing should be the last thing to act upon the main
# parser (--help should display plugin-specific options last)
_plugins_parsing(helpful, plugins)
if not detect_defaults:
global helpful_parser # pylint: disable=global-statement
helpful_parser = helpful
return helpful.parse_args()
def set_by_cli(var):
"""
Return True if a particular config variable has been set by the user
(CLI or config file) including if the user explicitly set it to the
default. Returns False if the variable was assigned a default value.
"""
detector = set_by_cli.detector # type: ignore
if detector is None and helpful_parser is not None:
# Setup on first run: `detector` is a weird version of config in which
# the default value of every attribute is wrangled to be boolean-false
plugins = plugins_disco.PluginsRegistry.find_all()
# reconstructed_args == sys.argv[1:], or whatever was passed to main()
reconstructed_args = helpful_parser.args + [helpful_parser.verb]
detector = set_by_cli.detector = prepare_and_parse_args( # type: ignore
plugins, reconstructed_args, detect_defaults=True)
# propagate plugin requests: eg --standalone modifies config.authenticator
detector.authenticator, detector.installer = ( # type: ignore
plugin_selection.cli_plugin_requests(detector))
if not isinstance(getattr(detector, var), _Default):
logger.debug("Var %s=%s (set by user).", var, getattr(detector, var))
return True
for modifier in VAR_MODIFIERS.get(var, []):
if set_by_cli(modifier):
logger.debug("Var %s=%s (set by user).",
var, VAR_MODIFIERS.get(var, []))
return True
return False
# static housekeeping var
# functions attributed are not supported by mypy
# https://github.com/python/mypy/issues/2087
set_by_cli.detector = None # type: ignore
def has_default_value(option, value):
"""Does option have the default value?
If the default value of option is not known, False is returned.
:param str option: configuration variable being considered
:param value: value of the configuration variable named option
:returns: True if option has the default value, otherwise, False
:rtype: bool
"""
if helpful_parser is not None:
return (option in helpful_parser.defaults and
helpful_parser.defaults[option] == value)
return False
def option_was_set(option, value):
"""Was option set by the user or does it differ from the default?
:param str option: configuration variable being considered
:param value: value of the configuration variable named option
:returns: True if the option was set, otherwise, False
:rtype: bool
"""
return set_by_cli(option) or not has_default_value(option, value)
def argparse_type(variable):
"""Return our argparse type function for a config variable (default: str)"""
# pylint: disable=protected-access
if helpful_parser is not None:
for action in helpful_parser.parser._actions:
if action.type is not None and action.dest == variable:
return action.type
return str

View file

@ -0,0 +1,107 @@
"""Certbot command line constants"""
import sys
from certbot.compat import os
# For help strings, figure out how the user ran us.
# When invoked from letsencrypt-auto, sys.argv[0] is something like:
# "/home/user/.local/share/certbot/bin/certbot"
# Note that this won't work if the user set VENV_PATH or XDG_DATA_HOME before
# running letsencrypt-auto (and sudo stops us from seeing if they did), so it
# should only be used for purposes where inability to detect letsencrypt-auto
# fails safely
LEAUTO = "letsencrypt-auto"
if "CERTBOT_AUTO" in os.environ:
# if we're here, this is probably going to be certbot-auto, unless the
# user saved the script under a different name
LEAUTO = os.path.basename(os.environ["CERTBOT_AUTO"])
old_path_fragment = os.path.join(".local", "share", "letsencrypt")
new_path_prefix = os.path.abspath(os.path.join(os.sep, "opt",
"eff.org", "certbot", "venv"))
if old_path_fragment in sys.argv[0] or sys.argv[0].startswith(new_path_prefix):
cli_command = LEAUTO
else:
cli_command = "certbot"
# Argparse's help formatting has a lot of unhelpful peculiarities, so we want
# to replace as much of it as we can...
# This is the stub to include in help generated by argparse
SHORT_USAGE = """
{0} [SUBCOMMAND] [options] [-d DOMAIN] [-d DOMAIN] ...
Certbot can obtain and install HTTPS/TLS/SSL certificates. By default,
it will attempt to use a webserver both for obtaining and installing the
certificate. """.format(cli_command)
# This section is used for --help and --help all ; it needs information
# about installed plugins to be fully formatted
COMMAND_OVERVIEW = """The most common SUBCOMMANDS and flags are:
obtain, install, and renew certificates:
(default) run Obtain & install a certificate in your current webserver
certonly Obtain or renew a certificate, but do not install it
renew Renew all previously obtained certificates that are near expiry
enhance Add security enhancements to your existing configuration
-d DOMAINS Comma-separated list of domains to obtain a certificate for
%s
--standalone Run a standalone webserver for authentication
%s
--webroot Place files in a server's webroot folder for authentication
--manual Obtain certificates interactively, or using shell script hooks
-n Run non-interactively
--test-cert Obtain a test certificate from a staging server
--dry-run Test "renew" or "certonly" without saving any certificates to disk
manage certificates:
certificates Display information about certificates you have from Certbot
revoke Revoke a certificate (supply --cert-name or --cert-path)
delete Delete a certificate (supply --cert-name)
manage your account:
register Create an ACME account
unregister Deactivate an ACME account
update_account Update an ACME account
--agree-tos Agree to the ACME server's Subscriber Agreement
-m EMAIL Email address for important account notifications
"""
# This is the short help for certbot --help, where we disable argparse
# altogether
HELP_AND_VERSION_USAGE = """
More detailed help:
-h, --help [TOPIC] print this message, or detailed help on a topic;
the available TOPICS are:
all, automation, commands, paths, security, testing, or any of the
subcommands or plugins (certonly, renew, install, register, nginx,
apache, standalone, webroot, etc.)
-h all print a detailed help page including all topics
--version print the version number
"""
# These argparse parameters should be removed when detecting defaults.
ARGPARSE_PARAMS_TO_REMOVE = ("const", "nargs", "type",)
# These sets are used when to help detect options set by the user.
EXIT_ACTIONS = set(("help", "version",))
ZERO_ARG_ACTIONS = set(("store_const", "store_true",
"store_false", "append_const", "count",))
# Maps a config option to a set of config options that may have modified it.
# This dictionary is used recursively, so if A modifies B and B modifies C,
# it is determined that C was modified by the user if A was modified.
VAR_MODIFIERS = {"account": set(("server",)),
"renew_hook": set(("deploy_hook",)),
"server": set(("dry_run", "staging",)),
"webroot_map": set(("webroot_path",))}

View file

@ -0,0 +1,239 @@
"""Certbot command line util function"""
import argparse
import copy
import zope.interface.interface # pylint: disable=unused-import
from acme import challenges
from certbot import interfaces
from certbot import util
from certbot import errors
from certbot.compat import os
from certbot._internal import constants
class _Default(object):
"""A class to use as a default to detect if a value is set by a user"""
def __bool__(self):
return False
def __eq__(self, other):
return isinstance(other, _Default)
def __hash__(self):
return id(_Default)
def __nonzero__(self):
return self.__bool__()
def read_file(filename, mode="rb"):
"""Returns the given file's contents.
:param str filename: path to file
:param str mode: open mode (see `open`)
:returns: absolute path of filename and its contents
:rtype: tuple
:raises argparse.ArgumentTypeError: File does not exist or is not readable.
"""
try:
filename = os.path.abspath(filename)
with open(filename, mode) as the_file:
contents = the_file.read()
return filename, contents
except IOError as exc:
raise argparse.ArgumentTypeError(exc.strerror)
def flag_default(name):
"""Default value for CLI flag."""
# XXX: this is an internal housekeeping notion of defaults before
# argparse has been set up; it is not accurate for all flags. Call it
# with caution. Plugin defaults are missing, and some things are using
# defaults defined in this file, not in constants.py :(
return copy.deepcopy(constants.CLI_DEFAULTS[name])
def config_help(name, hidden=False):
"""Extract the help message for an `.IConfig` attribute."""
if hidden:
return argparse.SUPPRESS
field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute
return field.__doc__
class HelpfulArgumentGroup(object):
"""Emulates an argparse group for use with HelpfulArgumentParser.
This class is used in the add_group method of HelpfulArgumentParser.
Command line arguments can be added to the group, but help
suppression and default detection is applied by
HelpfulArgumentParser when necessary.
"""
def __init__(self, helpful_arg_parser, topic):
self._parser = helpful_arg_parser
self._topic = topic
def add_argument(self, *args, **kwargs):
"""Add a new command line argument to the argument group."""
self._parser.add(self._topic, *args, **kwargs)
class CustomHelpFormatter(argparse.HelpFormatter):
"""This is a clone of ArgumentDefaultsHelpFormatter, with bugfixes.
In particular we fix https://bugs.python.org/issue28742
"""
def _get_help_string(self, action):
helpstr = action.help
if '%(default)' not in action.help and '(default:' not in action.help:
if action.default != argparse.SUPPRESS:
defaulting_nargs = [argparse.OPTIONAL, argparse.ZERO_OR_MORE]
if action.option_strings or action.nargs in defaulting_nargs:
helpstr += ' (default: %(default)s)'
return helpstr
class _DomainsAction(argparse.Action):
"""Action class for parsing domains."""
def __call__(self, parser, namespace, domain, option_string=None):
"""Just wrap add_domains in argparseese."""
add_domains(namespace, domain)
def add_domains(args_or_config, domains):
"""Registers new domains to be used during the current client run.
Domains are not added to the list of requested domains if they have
already been registered.
:param args_or_config: parsed command line arguments
:type args_or_config: argparse.Namespace or
configuration.NamespaceConfig
:param str domain: one or more comma separated domains
:returns: domains after they have been normalized and validated
:rtype: `list` of `str`
"""
validated_domains = []
for domain in domains.split(","):
domain = util.enforce_domain_sanity(domain.strip())
validated_domains.append(domain)
if domain not in args_or_config.domains:
args_or_config.domains.append(domain)
return validated_domains
class CaseInsensitiveList(list):
"""A list that will ignore case when searching.
This class is passed to the `choices` argument of `argparse.add_arguments`
through the `helpful` wrapper. It is necessary due to special handling of
command line arguments by `set_by_cli` in which the `type_func` is not applied."""
def __contains__(self, element):
return super(CaseInsensitiveList, self).__contains__(element.lower())
def _user_agent_comment_type(value):
if "(" in value or ")" in value:
raise argparse.ArgumentTypeError("may not contain parentheses")
return value
class _EncodeReasonAction(argparse.Action):
"""Action class for parsing revocation reason."""
def __call__(self, parser, namespace, reason, option_string=None):
"""Encodes the reason for certificate revocation."""
code = constants.REVOCATION_REASONS[reason.lower()]
setattr(namespace, self.dest, code)
def parse_preferred_challenges(pref_challs):
"""Translate and validate preferred challenges.
:param pref_challs: list of preferred challenge types
:type pref_challs: `list` of `str`
:returns: validated list of preferred challenge types
:rtype: `list` of `str`
:raises errors.Error: if pref_challs is invalid
"""
aliases = {"dns": "dns-01", "http": "http-01"}
challs = [c.strip() for c in pref_challs]
challs = [aliases.get(c, c) for c in challs]
unrecognized = ", ".join(name for name in challs
if name not in challenges.Challenge.TYPES)
if unrecognized:
raise errors.Error(
"Unrecognized challenges: {0}".format(unrecognized))
return challs
class _PrefChallAction(argparse.Action):
"""Action class for parsing preferred challenges."""
def __call__(self, parser, namespace, pref_challs, option_string=None):
try:
challs = parse_preferred_challenges(pref_challs.split(","))
except errors.Error as error:
raise argparse.ArgumentError(self, str(error))
namespace.pref_challs.extend(challs)
class _DeployHookAction(argparse.Action):
"""Action class for parsing deploy hooks."""
def __call__(self, parser, namespace, values, option_string=None):
renew_hook_set = namespace.deploy_hook != namespace.renew_hook
if renew_hook_set and namespace.renew_hook != values:
raise argparse.ArgumentError(
self, "conflicts with --renew-hook value")
namespace.deploy_hook = namespace.renew_hook = values
class _RenewHookAction(argparse.Action):
"""Action class for parsing renew hooks."""
def __call__(self, parser, namespace, values, option_string=None):
deploy_hook_set = namespace.deploy_hook is not None
if deploy_hook_set and namespace.deploy_hook != values:
raise argparse.ArgumentError(
self, "conflicts with --deploy-hook value")
namespace.renew_hook = values
def nonnegative_int(value):
"""Converts value to an int and checks that it is not negative.
This function should used as the type parameter for argparse
arguments.
:param str value: value provided on the command line
:returns: integer representation of value
:rtype: int
:raises argparse.ArgumentTypeError: if value isn't a non-negative integer
"""
try:
int_value = int(value)
except ValueError:
raise argparse.ArgumentTypeError("value must be an integer")
if int_value < 0:
raise argparse.ArgumentTypeError("value must be non-negative")
return int_value

View file

@ -0,0 +1,19 @@
"""This module contains a function to add the groups of arguments for the help
display"""
from certbot._internal.cli import VERB_HELP
def _add_all_groups(helpful):
helpful.add_group("automation", description="Flags for automating execution & other tweaks")
helpful.add_group("security", description="Security parameters & server settings")
helpful.add_group("testing",
description="The following flags are meant for testing and integration purposes only.")
helpful.add_group("paths", description="Flags for changing execution paths & servers")
helpful.add_group("manage",
description="Various subcommands and flags are available for managing your certificates:",
verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"])
# VERBS
for verb, docs in VERB_HELP:
name = docs.get("realname", verb)
helpful.add_group(name, description=docs["opts"])

View file

@ -0,0 +1,468 @@
"""Certbot command line argument parser"""
from __future__ import print_function
import argparse
import copy
import glob
import sys
import configargparse
import six
import zope.component
import zope.interface
from zope.interface import interfaces as zope_interfaces
# pylint: disable=unused-import, no-name-in-module
from acme.magic_typing import Any, Dict, Optional
# pylint: enable=unused-import, no-name-in-module
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import os
from certbot._internal import constants
from certbot._internal import hooks
from certbot.display import util as display_util
from certbot._internal.cli import (
SHORT_USAGE,
CustomHelpFormatter,
flag_default,
VERB_HELP,
VERB_HELP_MAP,
COMMAND_OVERVIEW,
HELP_AND_VERSION_USAGE,
_Default,
add_domains,
EXIT_ACTIONS,
ZERO_ARG_ACTIONS,
ARGPARSE_PARAMS_TO_REMOVE,
HelpfulArgumentGroup
)
class HelpfulArgumentParser(object):
"""Argparse Wrapper.
This class wraps argparse, adding the ability to make --help less
verbose, and request help on specific subcategories at a time, eg
'certbot --help security' for security options.
"""
def __init__(self, args, plugins, detect_defaults=False):
from certbot._internal import main
self.VERBS = {
"auth": main.certonly,
"certonly": main.certonly,
"run": main.run,
"install": main.install,
"plugins": main.plugins_cmd,
"register": main.register,
"update_account": main.update_account,
"unregister": main.unregister,
"renew": main.renew,
"revoke": main.revoke,
"rollback": main.rollback,
"everything": main.run,
"update_symlinks": main.update_symlinks,
"certificates": main.certificates,
"delete": main.delete,
"enhance": main.enhance,
}
# Get notification function for printing
try:
self.notify = zope.component.getUtility(
interfaces.IDisplay).notification
except zope_interfaces.ComponentLookupError:
self.notify = display_util.NoninteractiveDisplay(
sys.stdout).notification
# List of topics for which additional help can be provided
HELP_TOPICS = ["all", "security", "paths", "automation", "testing"]
HELP_TOPICS += list(self.VERBS) + self.COMMANDS_TOPICS + ["manage"]
plugin_names = list(plugins)
self.help_topics = HELP_TOPICS + plugin_names + [None] # type: ignore
self.detect_defaults = detect_defaults
self.args = args
if self.args and self.args[0] == 'help':
self.args[0] = '--help'
self.determine_verb()
help1 = self.prescan_for_flag("-h", self.help_topics)
help2 = self.prescan_for_flag("--help", self.help_topics)
if isinstance(help1, bool) and isinstance(help2, bool):
self.help_arg = help1 or help2
else:
self.help_arg = help1 if isinstance(help1, six.string_types) else help2
short_usage = self._usage_string(plugins, self.help_arg)
self.visible_topics = self.determine_help_topics(self.help_arg)
# elements are added by .add_group()
self.groups = {} # type: Dict[str, argparse._ArgumentGroup]
# elements are added by .parse_args()
self.defaults = {} # type: Dict[str, Any]
self.parser = configargparse.ArgParser(
prog="certbot",
usage=short_usage,
formatter_class=CustomHelpFormatter,
args_for_setting_config_path=["-c", "--config"],
default_config_files=flag_default("config_files"),
config_arg_help_message="path to config file (default: {0})".format(
" and ".join(flag_default("config_files"))))
# This is the only way to turn off overly verbose config flag documentation
self.parser._add_config_file_help = False
# Help that are synonyms for --help subcommands
COMMANDS_TOPICS = ["command", "commands", "subcommand", "subcommands", "verbs"]
def _list_subcommands(self):
longest = max(len(v) for v in VERB_HELP_MAP)
text = "The full list of available SUBCOMMANDS is:\n\n"
for verb, props in sorted(VERB_HELP):
doc = props.get("short", "")
text += '{0:<{length}} {1}\n'.format(verb, doc, length=longest)
text += "\nYou can get more help on a specific subcommand with --help SUBCOMMAND\n"
return text
def _usage_string(self, plugins, help_arg):
"""Make usage strings late so that plugins can be initialised late
:param plugins: all discovered plugins
:param help_arg: False for none; True for --help; "TOPIC" for --help TOPIC
:rtype: str
:returns: a short usage string for the top of --help TOPIC)
"""
if "nginx" in plugins:
nginx_doc = "--nginx Use the Nginx plugin for authentication & installation"
else:
nginx_doc = "(the certbot nginx plugin is not installed)"
if "apache" in plugins:
apache_doc = "--apache Use the Apache plugin for authentication & installation"
else:
apache_doc = "(the certbot apache plugin is not installed)"
usage = SHORT_USAGE
if help_arg is True:
self.notify(usage + COMMAND_OVERVIEW % (apache_doc, nginx_doc) + HELP_AND_VERSION_USAGE)
sys.exit(0)
elif help_arg in self.COMMANDS_TOPICS:
self.notify(usage + self._list_subcommands())
sys.exit(0)
elif help_arg == "all":
# if we're doing --help all, the OVERVIEW is part of the SHORT_USAGE at
# the top; if we're doing --help someothertopic, it's OT so it's not
usage += COMMAND_OVERVIEW % (apache_doc, nginx_doc)
else:
custom = VERB_HELP_MAP.get(help_arg, {}).get("usage", None)
usage = custom if custom else usage
return usage
def remove_config_file_domains_for_renewal(self, parsed_args):
"""Make "certbot renew" safe if domains are set in cli.ini."""
# Works around https://github.com/certbot/certbot/issues/4096
if self.verb == "renew":
for source, flags in self.parser._source_to_settings.items(): # pylint: disable=protected-access
if source.startswith("config_file") and "domains" in flags:
parsed_args.domains = _Default() if self.detect_defaults else []
def parse_args(self):
"""Parses command line arguments and returns the result.
:returns: parsed command line arguments
:rtype: argparse.Namespace
"""
parsed_args = self.parser.parse_args(self.args)
parsed_args.func = self.VERBS[self.verb]
parsed_args.verb = self.verb
self.remove_config_file_domains_for_renewal(parsed_args)
if self.detect_defaults:
return parsed_args
self.defaults = dict((key, copy.deepcopy(self.parser.get_default(key)))
for key in vars(parsed_args))
# Do any post-parsing homework here
if self.verb == "renew":
if parsed_args.force_interactive:
raise errors.Error(
"{0} cannot be used with renew".format(
constants.FORCE_INTERACTIVE_FLAG))
parsed_args.noninteractive_mode = True
if parsed_args.force_interactive and parsed_args.noninteractive_mode:
raise errors.Error(
"Flag for non-interactive mode and {0} conflict".format(
constants.FORCE_INTERACTIVE_FLAG))
if parsed_args.staging or parsed_args.dry_run:
self.set_test_server(parsed_args)
if parsed_args.csr:
self.handle_csr(parsed_args)
if parsed_args.must_staple:
parsed_args.staple = True
if parsed_args.validate_hooks:
hooks.validate_hooks(parsed_args)
if parsed_args.allow_subset_of_names:
if any(util.is_wildcard_domain(d) for d in parsed_args.domains):
raise errors.Error("Using --allow-subset-of-names with a"
" wildcard domain is not supported.")
if parsed_args.hsts and parsed_args.auto_hsts:
raise errors.Error(
"Parameters --hsts and --auto-hsts cannot be used simultaneously.")
return parsed_args
def set_test_server(self, parsed_args):
"""We have --staging/--dry-run; perform sanity check and set config.server"""
# Flag combinations should produce these results:
# | --staging | --dry-run |
# ------------------------------------------------------------
# | --server acme-v02 | Use staging | Use staging |
# | --server acme-staging-v02 | Use staging | Use staging |
# | --server <other> | Conflict error | Use <other> |
default_servers = (flag_default("server"), constants.STAGING_URI)
if parsed_args.staging and parsed_args.server not in default_servers:
raise errors.Error("--server value conflicts with --staging")
if parsed_args.server in default_servers:
parsed_args.server = constants.STAGING_URI
if parsed_args.dry_run:
if self.verb not in ["certonly", "renew"]:
raise errors.Error("--dry-run currently only works with the "
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if glob.glob(os.path.join(parsed_args.config_dir, constants.ACCOUNTS_DIR, "*")):
# The user has a prod account, but might not have a staging
# one; we don't want to start trying to perform interactive registration
parsed_args.tos = True
parsed_args.register_unsafely_without_email = True
def handle_csr(self, parsed_args):
"""Process a --csr flag."""
if parsed_args.verb != "certonly":
raise errors.Error("Currently, a CSR file may only be specified "
"when obtaining a new or replacement "
"via the certonly command. Please try the "
"certonly command instead.")
if parsed_args.allow_subset_of_names:
raise errors.Error("--allow-subset-of-names cannot be used with --csr")
csrfile, contents = parsed_args.csr[0:2]
typ, csr, domains = crypto_util.import_csr_file(csrfile, contents)
# This is not necessary for webroot to work, however,
# obtain_certificate_from_csr requires parsed_args.domains to be set
for domain in domains:
add_domains(parsed_args, domain)
if not domains:
# TODO: add CN to domains instead:
raise errors.Error(
"Unfortunately, your CSR %s needs to have a SubjectAltName for every domain"
% parsed_args.csr[0])
parsed_args.actual_csr = (csr, typ)
csr_domains = {d.lower() for d in domains}
config_domains = set(parsed_args.domains)
if csr_domains != config_domains:
raise errors.ConfigurationError(
"Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}"
.format(", ".join(csr_domains), ", ".join(config_domains)))
def determine_verb(self):
"""Determines the verb/subcommand provided by the user.
This function works around some of the limitations of argparse.
"""
if "-h" in self.args or "--help" in self.args:
# all verbs double as help arguments; don't get them confused
self.verb = "help"
return
for i, token in enumerate(self.args):
if token in self.VERBS:
verb = token
if verb == "auth":
verb = "certonly"
if verb == "everything":
verb = "run"
self.verb = verb
self.args.pop(i)
return
self.verb = "run"
def prescan_for_flag(self, flag, possible_arguments):
"""Checks cli input for flags.
Check for a flag, which accepts a fixed set of possible arguments, in
the command line; we will use this information to configure argparse's
help correctly. Return the flag's argument, if it has one that matches
the sequence @possible_arguments; otherwise return whether the flag is
present.
"""
if flag not in self.args:
return False
pos = self.args.index(flag)
try:
nxt = self.args[pos + 1]
if nxt in possible_arguments:
return nxt
except IndexError:
pass
return True
def add(self, topics, *args, **kwargs):
"""Add a new command line argument.
:param topics: str or [str] help topic(s) this should be listed under,
or None for options that don't fit under a specific
topic which will only be shown in "--help all" output.
The first entry determines where the flag lives in the
"--help all" output (None -> "optional arguments").
:param list *args: the names of this argument flag
:param dict **kwargs: various argparse settings for this argument
"""
if isinstance(topics, list):
# if this flag can be listed in multiple sections, try to pick the one
# that the user has asked for help about
topic = self.help_arg if self.help_arg in topics else topics[0]
else:
topic = topics # there's only one
if self.detect_defaults:
kwargs = self.modify_kwargs_for_default_detection(**kwargs)
if self.visible_topics[topic]:
if topic in self.groups:
group = self.groups[topic]
group.add_argument(*args, **kwargs)
else:
self.parser.add_argument(*args, **kwargs)
else:
kwargs["help"] = argparse.SUPPRESS
self.parser.add_argument(*args, **kwargs)
def modify_kwargs_for_default_detection(self, **kwargs):
"""Modify an arg so we can check if it was set by the user.
Changes the parameters given to argparse when adding an argument
so we can properly detect if the value was set by the user.
:param dict kwargs: various argparse settings for this argument
:returns: a modified versions of kwargs
:rtype: dict
"""
action = kwargs.get("action", None)
if action not in EXIT_ACTIONS:
kwargs["action"] = ("store_true" if action in ZERO_ARG_ACTIONS else
"store")
kwargs["default"] = _Default()
for param in ARGPARSE_PARAMS_TO_REMOVE:
kwargs.pop(param, None)
return kwargs
def add_deprecated_argument(self, argument_name, num_args):
"""Adds a deprecated argument with the name argument_name.
Deprecated arguments are not shown in the help. If they are used
on the command line, a warning is shown stating that the
argument is deprecated and no other action is taken.
:param str argument_name: Name of deprecated argument.
:param int nargs: Number of arguments the option takes.
"""
util.add_deprecated_argument(
self.parser.add_argument, argument_name, num_args)
def add_group(self, topic, verbs=(), **kwargs):
"""Create a new argument group.
This method must be called once for every topic, however, calls
to this function are left next to the argument definitions for
clarity.
:param str topic: Name of the new argument group.
:param str verbs: List of subcommands that should be documented as part of
this help group / topic
:returns: The new argument group.
:rtype: `HelpfulArgumentGroup`
"""
if self.visible_topics[topic]:
self.groups[topic] = self.parser.add_argument_group(topic, **kwargs)
if self.help_arg:
for v in verbs:
self.groups[topic].add_argument(v, help=VERB_HELP_MAP[v]["short"])
return HelpfulArgumentGroup(self, topic)
def add_plugin_args(self, plugins):
"""
Let each of the plugins add its own command line arguments, which
may or may not be displayed as help topics.
"""
for name, plugin_ep in six.iteritems(plugins):
parser_or_group = self.add_group(name,
description=plugin_ep.long_description)
plugin_ep.plugin_cls.inject_parser_options(parser_or_group, name)
def determine_help_topics(self, chosen_topic):
"""
The user may have requested help on a topic, return a dict of which
topics to display. @chosen_topic has prescan_for_flag's return type
:returns: dict
"""
# topics maps each topic to whether it should be documented by
# argparse on the command line
if chosen_topic == "auth":
chosen_topic = "certonly"
if chosen_topic == "everything":
chosen_topic = "run"
if chosen_topic == "all":
# Addition of condition closes #6209 (removal of duplicate route53 option).
return {t: t != 'certbot-route53:auth' for t in self.help_topics}
elif not chosen_topic:
return {t: False for t in self.help_topics}
return {t: t == chosen_topic for t in self.help_topics}

View file

@ -0,0 +1,50 @@
"""This is a module that adds configuration to the argument parser regarding
paths for certificates"""
from certbot.compat import os
from certbot._internal.cli import (
read_file,
flag_default,
config_help
)
def _paths_parser(helpful):
add = helpful.add
verb = helpful.verb
if verb == "help":
verb = helpful.help_arg
cph = "Path to where certificate is saved (with auth --csr), installed from, or revoked."
sections = ["paths", "install", "revoke", "certonly", "manage"]
if verb == "certonly":
add(sections, "--cert-path", type=os.path.abspath,
default=flag_default("auth_cert_path"), help=cph)
elif verb == "revoke":
add(sections, "--cert-path", type=read_file, required=False, help=cph)
else:
add(sections, "--cert-path", type=os.path.abspath, help=cph)
section = "paths"
if verb in ("install", "revoke"):
section = verb
# revoke --key-path reads a file, install --key-path takes a string
add(section, "--key-path",
type=((verb == "revoke" and read_file) or os.path.abspath),
help="Path to private key for certificate installation "
"or revocation (if account key is missing)")
default_cp = None
if verb == "certonly":
default_cp = flag_default("auth_chain_path")
add(["paths", "install"], "--fullchain-path", default=default_cp, type=os.path.abspath,
help="Accompanying path to a full certificate chain (certificate plus chain).")
add("paths", "--chain-path", default=default_cp, type=os.path.abspath,
help="Accompanying path to a certificate chain.")
add("paths", "--config-dir", default=flag_default("config_dir"),
help=config_help("config_dir"))
add("paths", "--work-dir", default=flag_default("work_dir"),
help=config_help("work_dir"))
add("paths", "--logs-dir", default=flag_default("logs_dir"),
help="Logs directory.")
add("paths", "--server", default=flag_default("server"),
help=config_help("server"))

View file

@ -0,0 +1,97 @@
"""This is a module that handles parsing of plugins for the argument parser"""
from certbot._internal.cli import flag_default
def _plugins_parsing(helpful, plugins):
# It's nuts, but there are two "plugins" topics. Somehow this works
helpful.add_group(
"plugins", description="Plugin Selection: Certbot client supports an "
"extensible plugins architecture. See '%(prog)s plugins' for a "
"list of all installed plugins and their names. You can force "
"a particular plugin by setting options provided below. Running "
"--help <plugin_name> will list flags specific to that plugin.")
helpful.add("plugins", "--configurator", default=flag_default("configurator"),
help="Name of the plugin that is both an authenticator and an installer."
" Should not be used together with --authenticator or --installer. "
"(default: Ask)")
helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"),
help="Authenticator plugin name.")
helpful.add("plugins", "-i", "--installer", default=flag_default("installer"),
help="Installer plugin name (also used to find domains).")
helpful.add(["plugins", "certonly", "run", "install"],
"--apache", action="store_true", default=flag_default("apache"),
help="Obtain and install certificates using Apache")
helpful.add(["plugins", "certonly", "run", "install"],
"--nginx", action="store_true", default=flag_default("nginx"),
help="Obtain and install certificates using Nginx")
helpful.add(["plugins", "certonly"], "--standalone", action="store_true",
default=flag_default("standalone"),
help='Obtain certificates using a "standalone" webserver.')
helpful.add(["plugins", "certonly"], "--manual", action="store_true",
default=flag_default("manual"),
help="Provide laborious manual instructions for obtaining a certificate")
helpful.add(["plugins", "certonly"], "--webroot", action="store_true",
default=flag_default("webroot"),
help="Obtain certificates by placing files in a webroot directory.")
helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true",
default=flag_default("dns_cloudflare"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using Cloudflare for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-cloudxns", action="store_true",
default=flag_default("dns_cloudxns"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using CloudXNS for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-digitalocean", action="store_true",
default=flag_default("dns_digitalocean"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using DigitalOcean for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-dnsimple", action="store_true",
default=flag_default("dns_dnsimple"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using DNSimple for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-dnsmadeeasy", action="store_true",
default=flag_default("dns_dnsmadeeasy"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using DNS Made Easy for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-gehirn", action="store_true",
default=flag_default("dns_gehirn"),
help=("Obtain certificates using a DNS TXT record "
"(if you are using Gehirn Infrastructure Service for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-google", action="store_true",
default=flag_default("dns_google"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using Google Cloud DNS)."))
helpful.add(["plugins", "certonly"], "--dns-linode", action="store_true",
default=flag_default("dns_linode"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using Linode for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-luadns", action="store_true",
default=flag_default("dns_luadns"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using LuaDNS for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-nsone", action="store_true",
default=flag_default("dns_nsone"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using NS1 for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-ovh", action="store_true",
default=flag_default("dns_ovh"),
help=("Obtain certificates using a DNS TXT record (if you are "
"using OVH for DNS)."))
helpful.add(["plugins", "certonly"], "--dns-rfc2136", action="store_true",
default=flag_default("dns_rfc2136"),
help="Obtain certificates using a DNS TXT record (if you are using BIND for DNS).")
helpful.add(["plugins", "certonly"], "--dns-route53", action="store_true",
default=flag_default("dns_route53"),
help=("Obtain certificates using a DNS TXT record (if you are using Route53 for "
"DNS)."))
helpful.add(["plugins", "certonly"], "--dns-sakuracloud", action="store_true",
default=flag_default("dns_sakuracloud"),
help=("Obtain certificates using a DNS TXT record "
"(if you are using Sakura Cloud for DNS)."))
# things should not be reorder past/pre this comment:
# plugins_group should be displayed in --help before plugin
# specific groups (so that plugins_group.description makes sense)
helpful.add_plugin_args(plugins)

View file

@ -0,0 +1,27 @@
"""This is a module that reports config option interaction that should be
checked by set_by_cli"""
import six
from certbot._internal.cli import VAR_MODIFIERS
def report_config_interaction(modified, modifiers):
"""Registers config option interaction to be checked by set_by_cli.
This function can be called by during the __init__ or
add_parser_arguments methods of plugins to register interactions
between config options.
:param modified: config options that can be modified by modifiers
:type modified: iterable or str (string_types)
:param modifiers: config options that modify modified
:type modifiers: iterable or str (string_types)
"""
if isinstance(modified, six.string_types):
modified = (modified,)
if isinstance(modifiers, six.string_types):
modifiers = (modifiers,)
for var in modified:
VAR_MODIFIERS.setdefault(var, set()).update(modifiers)

View file

@ -0,0 +1,72 @@
"""This module creates subparsers for the argument parser"""
from certbot import interfaces
from certbot._internal import constants
from certbot._internal.cli import (
flag_default,
read_file,
CaseInsensitiveList,
_user_agent_comment_type,
_EncodeReasonAction
)
def _create_subparsers(helpful):
from certbot._internal.client import sample_user_agent # avoid import loops
helpful.add(
None, "--user-agent", default=flag_default("user_agent"),
help='Set a custom user agent string for the client. User agent strings allow '
'the CA to collect high level statistics about success rates by OS, '
'plugin and use case, and to know when to deprecate support for past Python '
"versions and flags. If you wish to hide this information from the Let's "
'Encrypt server, set this to "". '
'(default: {0}). The flags encoded in the user agent are: '
'--duplicate, --force-renew, --allow-subset-of-names, -n, and '
'whether any hooks are set.'.format(sample_user_agent()))
helpful.add(
None, "--user-agent-comment", default=flag_default("user_agent_comment"),
type=_user_agent_comment_type,
help="Add a comment to the default user agent string. May be used when repackaging Certbot "
"or calling it from another tool to allow additional statistical data to be collected."
" Ignored if --user-agent is set. (Example: Foo-Wrapper/1.0)")
helpful.add("certonly",
"--csr", default=flag_default("csr"), type=read_file,
help="Path to a Certificate Signing Request (CSR) in DER or PEM format."
" Currently --csr only works with the 'certonly' subcommand.")
helpful.add("revoke",
"--reason", dest="reason",
choices=CaseInsensitiveList(sorted(constants.REVOCATION_REASONS,
key=constants.REVOCATION_REASONS.get)),
action=_EncodeReasonAction, default=flag_default("reason"),
help="Specify reason for revoking certificate. (default: unspecified)")
helpful.add("revoke",
"--delete-after-revoke", action="store_true",
default=flag_default("delete_after_revoke"),
help="Delete certificates after revoking them, along with all previous and later "
"versions of those certificates.")
helpful.add("revoke",
"--no-delete-after-revoke", action="store_false",
dest="delete_after_revoke",
default=flag_default("delete_after_revoke"),
help="Do not delete certificates after revoking them. This "
"option should be used with caution because the 'renew' "
"subcommand will attempt to renew undeleted revoked "
"certificates.")
helpful.add("rollback",
"--checkpoints", type=int, metavar="N",
default=flag_default("rollback_checkpoints"),
help="Revert configuration N number of checkpoints.")
helpful.add("plugins",
"--init", action="store_true", default=flag_default("init"),
help="Initialize plugins.")
helpful.add("plugins",
"--prepare", action="store_true", default=flag_default("prepare"),
help="Initialize and prepare plugins.")
helpful.add("plugins",
"--authenticators", action="append_const", dest="ifaces",
default=flag_default("ifaces"),
const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.")
helpful.add("plugins",
"--installers", action="append_const", dest="ifaces",
default=flag_default("ifaces"),
const=interfaces.IInstaller, help="Limit to installer plugins only.")

View file

@ -0,0 +1,106 @@
"""This module contain help information for verbs supported by certbot"""
from certbot.compat import os
from certbot._internal.cli import (
SHORT_USAGE,
flag_default
)
# The attributes here are:
# short: a string that will be displayed by "certbot -h commands"
# opts: a string that heads the section of flags with which this command is documented,
# both for "certbot -h SUBCOMMAND" and "certbot -h all"
# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND"
VERB_HELP = [
("run (default)", {
"short": "Obtain/renew a certificate, and install it",
"opts": "Options for obtaining & installing certificates",
"usage": SHORT_USAGE.replace("[SUBCOMMAND]", ""),
"realname": "run"
}),
("certonly", {
"short": "Obtain or renew a certificate, but do not install it",
"opts": "Options for modifying how a certificate is obtained",
"usage": ("\n\n certbot certonly [options] [-d DOMAIN] [-d DOMAIN] ...\n\n"
"This command obtains a TLS/SSL certificate without installing it anywhere.")
}),
("renew", {
"short": "Renew all certificates (or one specified with --cert-name)",
"opts": ("The 'renew' subcommand will attempt to renew all"
" certificates (or more precisely, certificate lineages) you have"
" previously obtained if they are close to expiry, and print a"
" summary of the results. By default, 'renew' will reuse the options"
" used to create obtain or most recently successfully renew each"
" certificate lineage. You can try it with `--dry-run` first. For"
" more fine-grained control, you can renew individual lineages with"
" the `certonly` subcommand. Hooks are available to run commands"
" before and after renewal; see"
" https://certbot.eff.org/docs/using.html#renewal for more"
" information on these."),
"usage": "\n\n certbot renew [--cert-name CERTNAME] [options]\n\n"
}),
("certificates", {
"short": "List certificates managed by Certbot",
"opts": "List certificates managed by Certbot",
"usage": ("\n\n certbot certificates [options] ...\n\n"
"Print information about the status of certificates managed by Certbot.")
}),
("delete", {
"short": "Clean up all files related to a certificate",
"opts": "Options for deleting a certificate",
"usage": "\n\n certbot delete --cert-name CERTNAME\n\n"
}),
("revoke", {
"short": "Revoke a certificate specified with --cert-path or --cert-name",
"opts": "Options for revocation of certificates",
"usage": "\n\n certbot revoke [--cert-path /path/to/fullchain.pem | "
"--cert-name example.com] [options]\n\n"
}),
("register", {
"short": "Register for account with Let's Encrypt / other ACME server",
"opts": "Options for account registration",
"usage": "\n\n certbot register --email user@example.com [options]\n\n"
}),
("update_account", {
"short": "Update existing account with Let's Encrypt / other ACME server",
"opts": "Options for account modification",
"usage": "\n\n certbot update_account --email updated_email@example.com [options]\n\n"
}),
("unregister", {
"short": "Irrevocably deactivate your account",
"opts": "Options for account deactivation.",
"usage": "\n\n certbot unregister [options]\n\n"
}),
("install", {
"short": "Install an arbitrary certificate in a server",
"opts": "Options for modifying how a certificate is deployed",
"usage": "\n\n certbot install --cert-path /path/to/fullchain.pem "
" --key-path /path/to/private-key [options]\n\n"
}),
("rollback", {
"short": "Roll back server conf changes made during certificate installation",
"opts": "Options for rolling back server configuration changes",
"usage": "\n\n certbot rollback --checkpoints 3 [options]\n\n"
}),
("plugins", {
"short": "List plugins that are installed and available on your system",
"opts": 'Options for the "plugins" subcommand',
"usage": "\n\n certbot plugins [options]\n\n"
}),
("update_symlinks", {
"short": "Recreate symlinks in your /etc/letsencrypt/live/ directory",
"opts": ("Recreates certificate and key symlinks in {0}, if you changed them by hand "
"or edited a renewal configuration file".format(
os.path.join(flag_default("config_dir"), "live"))),
"usage": "\n\n certbot update_symlinks [options]\n\n"
}),
("enhance", {
"short": "Add security enhancements to your existing configuration",
"opts": ("Helps to harden the TLS configuration by adding security enhancements "
"to already existing configuration."),
"usage": "\n\n certbot enhance [options]\n\n"
}),
]
# VERB_HELP is a list in order to preserve order, but a dict is sometimes useful
VERB_HELP_MAP = dict(VERB_HELP)

View file

@ -394,7 +394,7 @@ def _find_domains_or_certname(config, installer, question=None):
:param installer: Installer object
:type installer: interfaces.IInstaller
:param `str` question: Overriding dialog question to ask the user if asked
:param `str` question: Overriding default question to ask the user if asked
to choose from domain names.
:returns: Two-part tuple of domains and certname

View file

@ -1,34 +0,0 @@
"""Send e-mail notification to system administrators."""
import email
import smtplib
import socket
import subprocess
def notify(subject, whom, what):
"""Send email notification.
Try to notify the addressee (``whom``) by e-mail, with Subject:
defined by ``subject`` and message body by ``what``.
"""
msg = email.message_from_string(what)
msg.add_header("From", "Certbot renewal agent <root>")
msg.add_header("To", whom)
msg.add_header("Subject", subject)
msg = msg.as_string()
try:
lmtp = smtplib.LMTP()
lmtp.connect()
lmtp.sendmail("root", [whom], msg)
except (smtplib.SMTPHeloError, smtplib.SMTPRecipientsRefused,
smtplib.SMTPSenderRefused, smtplib.SMTPDataError, socket.error):
# We should try using /usr/sbin/sendmail in this case
try:
proc = subprocess.Popen(["/usr/sbin/sendmail", "-t"],
stdin=subprocess.PIPE)
proc.communicate(msg)
except OSError:
return False
return True

View file

@ -1,7 +1,6 @@
"""Webroot plugin."""
import argparse
import collections
import errno
import json
import logging
@ -71,7 +70,7 @@ to serve all files under specified web root ({0})."""
super(Authenticator, self).__init__(*args, **kwargs)
self.full_roots = {} # type: Dict[str, str]
self.performed = collections.defaultdict(set) \
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
# type: DefaultDict[str, Set[achallenges.KeyAuthorizationAnnotatedChallenge]]
# stack of dirs successfully created by this authenticator
self._created_dirs = [] # type: List[str]
@ -137,7 +136,7 @@ to serve all files under specified web root ({0})."""
"webroot when using the webroot plugin.")
return None if index == 0 else known_webroots[index - 1] # code == display_util.OK
def _prompt_for_new_webroot(self, domain, allowraise=False):
def _prompt_for_new_webroot(self, domain, allowraise=False): # pylint: no-self-use
code, webroot = ops.validated_directory(
_validate_webroot,
"Input the webroot for {0}:".format(domain),
@ -170,6 +169,10 @@ to serve all files under specified web root ({0})."""
# We ignore the last prefix in the next iteration,
# as it does not correspond to a folder path ('/' or 'C:')
for prefix in sorted(util.get_prefixes(self.full_roots[name])[:-1], key=len):
if os.path.isdir(prefix):
# Don't try to create directory if it already exists, as some filesystems
# won't reliably raise EEXIST or EISDIR if directory exists.
continue
try:
# Set owner as parent directory if possible, apply mode for Linux/Windows.
# For Linux, this is coupled with the "umask" call above because
@ -184,14 +187,13 @@ to serve all files under specified web root ({0})."""
logger.info("Unable to change owner and uid of webroot directory")
logger.debug("Error was: %s", exception)
except OSError as exception:
if exception.errno not in (errno.EEXIST, errno.EISDIR):
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}".format(name, exception))
raise errors.PluginError(
"Couldn't create root for {0} http-01 "
"challenge responses: {1}".format(name, exception))
finally:
os.umask(old_umask)
def _get_validation_path(self, root_path, achall):
def _get_validation_path(self, root_path, achall): # pylint: no-self-use
return os.path.join(root_path, achall.chall.encode("token"))
def _perform_single(self, achall):

View file

@ -15,6 +15,7 @@ import certbot
from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import ocsp
from certbot import util
from certbot._internal import cli
from certbot._internal import constants
@ -882,27 +883,33 @@ class RenewableCert(interfaces.RenewableCert):
with open(target) as f:
return crypto_util.get_names_from_cert(f.read())
def ocsp_revoked(self, version=None):
# pylint: disable=unused-argument
def ocsp_revoked(self, version):
"""Is the specified cert version revoked according to OCSP?
Also returns True if the cert version is declared as intended
to be revoked according to Let's Encrypt OCSP extensions.
(If no version is specified, uses the current version.)
This method is not yet implemented and currently always returns
False.
Also returns True if the cert version is declared as revoked
according to OCSP. If OCSP status could not be determined, False
is returned.
:param int version: the desired version number
:returns: whether the certificate is or will be revoked
:returns: True if the certificate is revoked, otherwise, False
:rtype: bool
"""
# XXX: This query and its associated network service aren't
# implemented yet, so we currently return False (indicating that the
# certificate is not revoked).
return False
cert_path = self.version("cert", version)
chain_path = self.version("chain", version)
# While the RevocationChecker should return False if it failed to
# determine the OCSP status, let's ensure we don't crash Certbot by
# catching all exceptions here.
try:
return ocsp.RevocationChecker().ocsp_revoked_by_paths(cert_path,
chain_path)
except Exception as e: # pylint: disable=broad-except
logger.warning(
"An error occurred determining the OCSP status of %s.",
cert_path)
logger.debug(str(e))
return False
def autorenewal_is_enabled(self):
"""Is automatic renewal enabled for this cert?

View file

@ -107,7 +107,7 @@ def choose_names(installer, question=None):
:param installer: An installer object
:type installer: :class:`certbot.interfaces.IInstaller`
:param `str` question: Overriding dialog question to ask the user if asked
:param `str` question: Overriding default question to ask the user if asked
to choose from domain names.
:returns: List of selected names

View file

@ -68,8 +68,19 @@ class RevocationChecker(object):
:rtype: bool
"""
cert_path, chain_path = cert.cert_path, cert.chain_path
return self.ocsp_revoked_by_paths(cert.cert_path, cert.chain_path)
def ocsp_revoked_by_paths(self, cert_path, chain_path):
# type: (str, str) -> bool
"""Performs the OCSP revocation check
:param str cert_path: Certificate filepath
:param str chain_path: Certificate chain filepath
:returns: True if revoked; False if valid or the check failed or cert is expired.
:rtype: bool
"""
if self.broken:
return False

View file

@ -153,7 +153,7 @@ class AutoHSTSEnhancement(object):
:type lineage: certbot.interfaces.RenewableCert
:param domains: List of domains in certificate to enhance
:type domains: str
:type domains: `list` of `str`
"""
# This is used to configure internal new style enhancements in Certbot. These

View file

@ -25,11 +25,9 @@ from certbot._internal import lock
from certbot.compat import filesystem
from certbot.compat import os
if sys.platform.startswith('linux'):
_USE_DISTRO = sys.platform.startswith('linux')
if _USE_DISTRO:
import distro
_USE_DISTRO = True
else:
_USE_DISTRO = False
logger = logging.getLogger(__name__)

View file

@ -113,7 +113,7 @@ optional arguments:
case, and to know when to deprecate support for past
Python versions and flags. If you wish to hide this
information from the Let's Encrypt server, set this to
"". (default: CertbotACMEClient/1.2.0 (certbot(-auto);
"". (default: CertbotACMEClient/1.3.0 (certbot(-auto);
OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY
(SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel).
The flags encoded in the user agent are: --duplicate,

View file

@ -33,12 +33,16 @@ example: `v0.11.1`.
.. _`Semantic Versioning`: http://semver.org/
Our packages are cryptographically signed and their signature can be verified
using the PGP key ``A2CFB51FA275A7286234E7B24D17C995CD9775F2``. This key can be
found on major key servers and at https://dl.eff.org/certbot.pub.
Notes for package maintainers
=============================
0. Please use our tagged releases, not ``master``!
1. Do not package ``certbot-compatibility-test`` or ``letshelp-certbot`` - it's only used internally.
1. Do not package ``certbot-compatibility-test`` as it's only used internally.
2. To run tests on our packages, you should use ``python setup.py test``. Doing things like running ``pytest`` directly on our package files may not work because Certbot relies on setuptools to register and find its plugins.

View file

@ -485,43 +485,6 @@ If you want your hook to run only after a successful renewal, use
``certbot renew --deploy-hook /path/to/deploy-hook-script``
For example, if you have a daemon that does not read its certificates as the
root user, a deploy hook like this can copy them to the correct location and
apply appropriate file permissions.
/path/to/deploy-hook-script
.. code-block:: none
#!/bin/sh
set -e
for domain in $RENEWED_DOMAINS; do
case $domain in
example.com)
daemon_cert_root=/etc/some-daemon/certs
# Make sure the certificate and private key files are
# never world readable, even just for an instant while
# we're copying them into daemon_cert_root.
umask 077
cp "$RENEWED_LINEAGE/fullchain.pem" "$daemon_cert_root/$domain.cert"
cp "$RENEWED_LINEAGE/privkey.pem" "$daemon_cert_root/$domain.key"
# Apply the proper file ownership and permissions for
# the daemon to read its certificate and key.
chown some-daemon "$daemon_cert_root/$domain.cert" \
"$daemon_cert_root/$domain.key"
chmod 400 "$daemon_cert_root/$domain.cert" \
"$daemon_cert_root/$domain.key"
service some-daemon restart >/dev/null
;;
esac
done
You can also specify hooks by placing files in subdirectories of Certbot's
configuration directory. Assuming your configuration directory is
``/etc/letsencrypt``, any executable files found in
@ -686,6 +649,17 @@ your (web) server configuration directly to those files (or create
symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated
with the latest necessary files.
For historical reasons, the containing directories are created with
permissions of ``0700`` meaning that certificates are accessible only
to servers that run as the root user. **If you will never downgrade
to an older version of Certbot**, then you can safely fix this using
``chmod 0755 /etc/letsencrypt/{live,archive}``.
For servers that drop root privileges before attempting to read the
private key file, you will also need to use ``chgrp`` and ``chmod
0640`` to allow the server to read
``/etc/letsencrypt/live/$domain/privkey.pem``.
.. note:: ``/etc/letsencrypt/archive`` and ``/etc/letsencrypt/keys``
contain all previous keys and certificates, while
``/etc/letsencrypt/live`` symlinks to the latest versions.

View file

@ -564,14 +564,12 @@ class GetCertnameTest(unittest.TestCase):
"""Tests for certbot._internal.cert_manager."""
def setUp(self):
self.get_utility_patch = test_util.patch_get_utility()
self.mock_get_utility = self.get_utility_patch.start()
get_utility_patch = test_util.patch_get_utility()
self.mock_get_utility = get_utility_patch.start()
self.addCleanup(get_utility_patch.stop)
self.config = mock.MagicMock()
self.config.certname = None
def tearDown(self):
self.get_utility_patch.stop()
@mock.patch('certbot._internal.storage.renewal_conf_files')
@mock.patch('certbot._internal.storage.lineagename_for_filename')
def test_get_certnames(self, mock_name, mock_files):

View file

@ -30,15 +30,23 @@ class TestReadFile(TempDirTestCase):
# However a relative path between two different drives is invalid. So we move to
# self.tempdir to ensure that we stay on the same drive.
os.chdir(self.tempdir)
rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo'))
# The read-only filesystem introduced with macOS Catalina can break
# code using relative paths below. See
# https://bugs.python.org/issue38295 for another example of this.
# Eliminating any possible symlinks in self.tempdir before passing
# it to os.path.relpath solves the problem. This is done by calling
# filesystem.realpath which removes any symlinks in the path on
# POSIX systems.
real_path = filesystem.realpath(os.path.join(self.tempdir, 'foo'))
relative_path = os.path.relpath(real_path)
self.assertRaises(
argparse.ArgumentTypeError, cli.read_file, rel_test_path)
argparse.ArgumentTypeError, cli.read_file, relative_path)
test_contents = b'bar\n'
with open(rel_test_path, 'wb') as f:
with open(relative_path, 'wb') as f:
f.write(test_contents)
path, contents = cli.read_file(rel_test_path)
path, contents = cli.read_file(relative_path)
self.assertEqual(path, os.path.abspath(path))
self.assertEqual(contents, test_contents)
finally:
@ -93,7 +101,7 @@ class ParseTest(unittest.TestCase):
return output.getvalue()
@mock.patch("certbot._internal.cli.flag_default")
@mock.patch("certbot._internal.cli.helpful.flag_default")
def test_cli_ini_domains(self, mock_flag_default):
with tempfile.NamedTemporaryFile() as tmp_config:
tmp_config.close() # close now because of compatibility issues on Windows
@ -142,7 +150,6 @@ class ParseTest(unittest.TestCase):
self.assertTrue("how a certificate is deployed" in out)
self.assertTrue("--webroot-path" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)
self.assertTrue("--renew-hook" not in out)
@ -203,7 +210,6 @@ class ParseTest(unittest.TestCase):
self.assertTrue("how a certificate is deployed" in out)
self.assertTrue("--webroot-path" in out)
self.assertTrue("--text" not in out)
self.assertTrue("--dialog" not in out)
self.assertTrue("%s" not in out)
self.assertTrue("{0}" not in out)

View file

@ -0,0 +1,193 @@
"""Tests for certbot.helpful_parser"""
import unittest
from certbot import errors
from certbot._internal.cli import HelpfulArgumentParser
from certbot._internal.cli import _DomainsAction
from certbot._internal import constants
class TestScanningFlags(unittest.TestCase):
'''Test the prescan_for_flag method of HelpfulArgumentParser'''
def test_prescan_no_help_flag(self):
arg_parser = HelpfulArgumentParser(['run'], {})
detected_flag = arg_parser.prescan_for_flag('--help',
['all', 'certonly'])
self.assertFalse(detected_flag)
detected_flag = arg_parser.prescan_for_flag('-h',
['all, certonly'])
self.assertFalse(detected_flag)
def test_prescan_unvalid_topic(self):
arg_parser = HelpfulArgumentParser(['--help', 'all'], {})
detected_flag = arg_parser.prescan_for_flag('--help',
['potato'])
self.assertIs(detected_flag, True)
detected_flag = arg_parser.prescan_for_flag('-h',
arg_parser.help_topics)
self.assertFalse(detected_flag)
def test_prescan_valid_topic(self):
arg_parser = HelpfulArgumentParser(['-h', 'all'], {})
detected_flag = arg_parser.prescan_for_flag('-h',
arg_parser.help_topics)
self.assertEqual(detected_flag, 'all')
detected_flag = arg_parser.prescan_for_flag('--help',
arg_parser.help_topics)
self.assertFalse(detected_flag)
class TestDetermineVerbs(unittest.TestCase):
'''Tests for determine_verb methods of HelpfulArgumentParser'''
def test_determine_verb_wrong_verb(self):
arg_parser = HelpfulArgumentParser(['potato'], {})
self.assertEqual(arg_parser.verb, "run")
self.assertEqual(arg_parser.args, ["potato"])
def test_determine_verb_help(self):
arg_parser = HelpfulArgumentParser(['--help', 'everything'], {})
self.assertEqual(arg_parser.verb, "help")
self.assertEqual(arg_parser.args, ["--help", "everything"])
arg_parser = HelpfulArgumentParser(['-d', 'some_domain', '--help',
'all'], {})
self.assertEqual(arg_parser.verb, "help")
self.assertEqual(arg_parser.args, ['-d', 'some_domain', '--help',
'all'])
def test_determine_verb(self):
arg_parser = HelpfulArgumentParser(['certonly'], {})
self.assertEqual(arg_parser.verb, 'certonly')
self.assertEqual(arg_parser.args, [])
arg_parser = HelpfulArgumentParser(['auth'], {})
self.assertEqual(arg_parser.verb, 'certonly')
self.assertEqual(arg_parser.args, [])
arg_parser = HelpfulArgumentParser(['everything'], {})
self.assertEqual(arg_parser.verb, 'run')
self.assertEqual(arg_parser.args, [])
class TestAdd(unittest.TestCase):
'''Tests for add method in HelpfulArgumentParser'''
def test_add_trivial_argument(self):
arg_parser = HelpfulArgumentParser(['run'], {})
arg_parser.add(None, "--hello-world")
parsed_args = arg_parser.parser.parse_args(['--hello-world',
'Hello World!'])
self.assertIs(parsed_args.hello_world, 'Hello World!')
self.assertFalse(hasattr(parsed_args, 'potato'))
def test_add_expected_argument(self):
arg_parser = HelpfulArgumentParser(['--help', 'run'], {})
arg_parser.add(
[None, "run", "certonly", "register"],
"--eab-kid", dest="eab_kid", action="store",
metavar="EAB_KID",
help="Key Identifier for External Account Binding")
parsed_args = arg_parser.parser.parse_args(["--eab-kid", None])
self.assertIs(parsed_args.eab_kid, None)
self.assertTrue(hasattr(parsed_args, 'eab_kid'))
class TestAddGroup(unittest.TestCase):
'''Test add_group method of HelpfulArgumentParser'''
def test_add_group_no_input(self):
arg_parser = HelpfulArgumentParser(['run'], {})
self.assertRaises(TypeError, arg_parser.add_group)
def test_add_group_topic_not_visible(self):
# The user request help on run. A topic that given somewhere in the
# args won't be added to the groups in the parser.
arg_parser = HelpfulArgumentParser(['--help', 'run'], {})
arg_parser.add_group("auth",
description="description of auth")
self.assertEqual(arg_parser.groups, {})
def test_add_group_topic_requested_help(self):
arg_parser = HelpfulArgumentParser(['--help', 'run'], {})
arg_parser.add_group("run",
description="description of run")
self.assertTrue(arg_parser.groups["run"])
arg_parser.add_group("certonly", description="description of certonly")
with self.assertRaises(KeyError):
self.assertFalse(arg_parser.groups["certonly"])
class TestParseArgsErrors(unittest.TestCase):
'''Tests for errors that should be met for some cases in parse_args method
in HelpfulArgumentParser'''
def test_parse_args_renew_force_interactive(self):
arg_parser = HelpfulArgumentParser(['renew', '--force-interactive'],
{})
arg_parser.add(
None, constants.FORCE_INTERACTIVE_FLAG, action="store_true")
with self.assertRaises(errors.Error):
arg_parser.parse_args()
def test_parse_args_non_interactive_and_force_interactive(self):
arg_parser = HelpfulArgumentParser(['--force-interactive',
'--non-interactive'], {})
arg_parser.add(
None, constants.FORCE_INTERACTIVE_FLAG, action="store_true")
arg_parser.add(
None, "--non-interactive", dest="noninteractive_mode",
action="store_true"
)
with self.assertRaises(errors.Error):
arg_parser.parse_args()
def test_parse_args_subset_names_wildcard_domain(self):
arg_parser = HelpfulArgumentParser(['--domain',
'*.example.com,potato.example.com',
'--allow-subset-of-names'], {})
# The following arguments are added because they have to be defined
# in order for arg_parser to run completely. They are not used for the
# test.
arg_parser.add(
None, constants.FORCE_INTERACTIVE_FLAG, action="store_true")
arg_parser.add(
None, "--non-interactive", dest="noninteractive_mode",
action="store_true")
arg_parser.add(
None, "--staging"
)
arg_parser.add(None, "--dry-run")
arg_parser.add(None, "--csr")
arg_parser.add(None, "--must-staple")
arg_parser.add(None, "--validate-hooks")
arg_parser.add(None, "-d", "--domain", dest="domains",
metavar="DOMAIN", action=_DomainsAction)
arg_parser.add(None, "--allow-subset-of-names")
# with self.assertRaises(errors.Error):
# arg_parser.parse_args()
def test_parse_args_hosts_and_auto_hosts(self):
arg_parser = HelpfulArgumentParser(['--hsts', '--auto-hsts'], {})
arg_parser.add(
None, "--hsts", action="store_true", dest="hsts")
arg_parser.add(
None, "--auto-hsts", action="store_true", dest="auto_hsts")
# The following arguments are added because they have to be defined
# in order for arg_parser to run completely. They are not used for the
# test.
arg_parser.add(
None, constants.FORCE_INTERACTIVE_FLAG, action="store_true")
arg_parser.add(
None, "--non-interactive", dest="noninteractive_mode",
action="store_true")
arg_parser.add(None, "--staging")
arg_parser.add(None, "--dry-run")
arg_parser.add(None, "--csr")
arg_parser.add(None, "--must-staple")
arg_parser.add(None, "--validate-hooks")
arg_parser.add(None, "--allow-subset-of-names")
with self.assertRaises(errors.Error):
arg_parser.parse_args()
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -62,7 +62,7 @@ class RunTest(test_util.ConfigTestCase):
def setUp(self):
super(RunTest, self).setUp()
self.domain = 'example.org'
self.patches = [
patches = [
mock.patch('certbot._internal.main._get_and_save_cert'),
mock.patch('certbot._internal.main.display_ops.success_installation'),
mock.patch('certbot._internal.main.display_ops.success_renewal'),
@ -71,17 +71,15 @@ class RunTest(test_util.ConfigTestCase):
mock.patch('certbot._internal.main._report_new_cert'),
mock.patch('certbot._internal.main._find_cert')]
self.mock_auth = self.patches[0].start()
self.mock_success_installation = self.patches[1].start()
self.mock_success_renewal = self.patches[2].start()
self.mock_init = self.patches[3].start()
self.mock_suggest_donation = self.patches[4].start()
self.mock_report_cert = self.patches[5].start()
self.mock_find_cert = self.patches[6].start()
def tearDown(self):
for patch in self.patches:
patch.stop()
self.mock_auth = patches[0].start()
self.mock_success_installation = patches[1].start()
self.mock_success_renewal = patches[2].start()
self.mock_init = patches[3].start()
self.mock_suggest_donation = patches[4].start()
self.mock_report_cert = patches[5].start()
self.mock_find_cert = patches[6].start()
for patch in patches:
self.addCleanup(patch.stop)
def _call(self):
args = '-a webroot -i null -d {0}'.format(self.domain).split()
@ -243,16 +241,18 @@ class RevokeTest(test_util.TempDirTestCase):
with open(self.tmp_cert_path, 'r') as f:
self.tmp_cert = (self.tmp_cert_path, f.read())
self.patches = [
patches = [
mock.patch('acme.client.BackwardsCompatibleClientV2'),
mock.patch('certbot._internal.client.Client'),
mock.patch('certbot._internal.main._determine_account'),
mock.patch('certbot._internal.main.display_ops.success_revocation')
]
self.mock_acme_client = self.patches[0].start()
self.patches[1].start()
self.mock_determine_account = self.patches[2].start()
self.mock_success_revoke = self.patches[3].start()
self.mock_acme_client = patches[0].start()
patches[1].start()
self.mock_determine_account = patches[2].start()
self.mock_success_revoke = patches[3].start()
for patch in patches:
self.addCleanup(patch.stop)
from certbot._internal.account import Account
@ -265,12 +265,6 @@ class RevokeTest(test_util.TempDirTestCase):
self.mock_determine_account.return_value = (self.acc, None)
def tearDown(self):
super(RevokeTest, self).tearDown()
for patch in self.patches:
patch.stop()
def _call(self, args=None):
if not args:
args = 'revoke --cert-path={0} '

View file

@ -1,52 +0,0 @@
"""Tests for certbot._internal.notify."""
import socket
import unittest
import mock
class NotifyTests(unittest.TestCase):
"""Tests for the notifier."""
@mock.patch("certbot._internal.notify.smtplib.LMTP")
def test_smtp_success(self, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
self.assertTrue(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.connect.call_count, 1)
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
@mock.patch("certbot._internal.notify.smtplib.LMTP")
@mock.patch("certbot._internal.notify.subprocess.Popen")
def test_smtp_failure(self, mock_popen, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
lmtp_obj.sendmail.side_effect = socket.error(17)
proc = mock.MagicMock()
mock_popen.return_value = proc
self.assertTrue(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
self.assertEqual(proc.communicate.call_count, 1)
@mock.patch("certbot._internal.notify.smtplib.LMTP")
@mock.patch("certbot._internal.notify.subprocess.Popen")
def test_everything_fails(self, mock_popen, mock_lmtp):
from certbot._internal.notify import notify
lmtp_obj = mock.MagicMock()
mock_lmtp.return_value = lmtp_obj
lmtp_obj.sendmail.side_effect = socket.error(17)
proc = mock.MagicMock()
mock_popen.return_value = proc
proc.communicate.side_effect = OSError("What we have here is a "
"failure to communicate.")
self.assertFalse(notify("Goose", "auntrhody@example.com",
"The old grey goose is dead."))
self.assertEqual(lmtp_obj.sendmail.call_count, 1)
self.assertEqual(proc.communicate.call_count, 1)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -177,7 +177,7 @@ class InstallerTest(test_util.ConfigTestCase):
class AddrTest(unittest.TestCase):
"""Tests for certbot._internal.client.plugins.common.Addr."""
"""Tests for certbot.plugins.common.Addr."""
def setUp(self):
from certbot.plugins.common import Addr

View file

@ -672,10 +672,35 @@ class RenewableCertTests(BaseRenewableCertTest):
errors.CertStorageError,
self.test_rc._update_link_to, "elephant", 17)
def test_ocsp_revoked(self):
# XXX: This is currently hardcoded to False due to a lack of an
# OCSP server to test against.
self.assertFalse(self.test_rc.ocsp_revoked())
@mock.patch("certbot.ocsp.RevocationChecker.ocsp_revoked_by_paths")
def test_ocsp_revoked(self, mock_checker):
# Write out test files
for kind in ALL_FOUR:
self._write_out_kind(kind, 1)
version = self.test_rc.latest_common_version()
expected_cert_path = self.test_rc.version("cert", version)
expected_chain_path = self.test_rc.version("chain", version)
# Test with cert revoked
mock_checker.return_value = True
self.assertTrue(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
# Test with cert not revoked
mock_checker.return_value = False
self.assertFalse(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
# Test with error
mock_checker.side_effect = ValueError
with mock.patch("certbot._internal.storage.logger.warning") as logger:
self.assertFalse(self.test_rc.ocsp_revoked(version))
self.assertEqual(mock_checker.call_args[0][0], expected_cert_path)
self.assertEqual(mock_checker.call_args[0][1], expected_chain_path)
log_msg = logger.call_args[0][0]
self.assertIn("An error occurred determining the OCSP status", log_msg)
def test_add_time_interval(self):
from certbot._internal import storage

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="1.2.0"
LE_AUTO_VERSION="1.3.0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==1.2.0 \
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
acme==1.2.0 \
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
certbot-apache==1.2.0 \
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
certbot-nginx==1.2.0 \
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
certbot==1.3.0 \
--hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \
--hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8
acme==1.3.0 \
--hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \
--hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d
certbot-apache==1.3.0 \
--hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \
--hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52
certbot-nginx==1.3.0 \
--hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \
--hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b
UNLIKELY_EOF
# -------------------------------------------------------------------------

View file

@ -1,11 +1,11 @@
-----BEGIN PGP SIGNATURE-----
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl456ZoACgkQTRfJlc2X
dfJx8wf/addMw4kUlwu6poHqLvsifZzHAESgvq+qybgFvl5yTh2U+99PGBgxRYx+
bENIWBi6+XB+CiVuLzIXWw/VkXh+za99orRkkVK9PI33Xr7jBMZo5Oa3JviYjl3X
PcfjioRQCD+a9Tf9RO25LXQmxn87Ql9x3nxJuk//YeSpuImFmYjIBPE4n/LPEf7z
8WHU4oxxa/bgqGCPgv6O7ZBw7ipd3g+VHcDZcNQMP4tWYb6m7x/nN61yirid7q3M
uqQ1lbitN48ISyru6xPyE6WGTvfl1SIQd21FNRETpcoesx+MTv3ApWT4dqXjZvaX
FeM55IS65e7ci6yLV9qdAbqGKzhX0Q==
=uLcV
iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl5ewVUACgkQTRfJlc2X
dfJnZAf+KmxYl1YoP/FlTG5Npb64qaDdxm59SeEVJez6fZh15xq71tRPYR+4xszE
XTeyGt7uAxjYqeiBJU5xBvGC1Veprhj5AbflVOTP+5yiBr9iNWC35zmgaE63UlZ/
V94sfL0pkax7wLngil7a0OuzUjikzK3gXOqrY8LoUdr4mAA9AhSjajWHmyY3tpDR
84GKrVhybIt0sjy/172VuPPbXZKno/clztkKMZHXNrDeL5jgJ15Va4Ts5FK0j9VT
HQvuazbGkYVCuvlp8Np5ESDje69LCJfPZxl34htoa8WNJoVIOsQWZpoXp5B5huSP
vGrh4LabZ5UDsl+k11ikHBRUpO7E5w==
=IgRH
-----END PGP SIGNATURE-----

View file

@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="1.3.0.dev0"
LE_AUTO_VERSION="1.4.0.dev0"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@ -1540,18 +1540,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==1.2.0 \
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
acme==1.2.0 \
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
certbot-apache==1.2.0 \
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
certbot-nginx==1.2.0 \
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
certbot==1.3.0 \
--hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \
--hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8
acme==1.3.0 \
--hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \
--hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d
certbot-apache==1.3.0 \
--hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \
--hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52
certbot-nginx==1.3.0 \
--hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \
--hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b
UNLIKELY_EOF
# -------------------------------------------------------------------------

View file

@ -1,12 +1,12 @@
certbot==1.2.0 \
--hash=sha256:e25c17125c00b3398c8e9b9d54ef473c0e8f5aff53389f313a51b06cf472d335 \
--hash=sha256:95dcbae085f8e4eb18442fe7b12994b08964a9a6e8e352e556cdb4a8a625373c
acme==1.2.0 \
--hash=sha256:284d22fde75687a8ea72d737cac6bcbdc91f3c796221aa25378b8732ba6f6875 \
--hash=sha256:0630c740d49bda945e97bd35fc8d6f02d082c8cb9e18f8fec0dbb3d395ac26ab
certbot-apache==1.2.0 \
--hash=sha256:3f7493918353d3bd6067d446a2cf263e03831c4c10ec685b83d644b47767090d \
--hash=sha256:b46e9def272103a68108e48bf7e410ea46801529b1ea6954f6506b14dd9df9b3
certbot-nginx==1.2.0 \
--hash=sha256:efd32a2b32f2439279da446b6bf67684f591f289323c5f494ebfd86a566a28fd \
--hash=sha256:6fd7cf4f2545ad66e57000343227df9ccccaf04420e835e05cb3250fac1fa6db
certbot==1.3.0 \
--hash=sha256:979793b36151be26c159f1946d065a0cbbcaed3e9ac452c19a142b0d2d2b42e3 \
--hash=sha256:bc2091cbbc2f432872ed69309046e79771d9c81cd441bde3e6a6553ecd04b1d8
acme==1.3.0 \
--hash=sha256:b888757c750e393407a3cdf0eb5c2d06036951e10c41db4c83537617568561b6 \
--hash=sha256:c0de9e1fbcb4a28509825a4d19ab5455910862b23fa338acebc7bbe7c0abd20d
certbot-apache==1.3.0 \
--hash=sha256:1050cd262bcc598957c45a6fa1febdf5e41e87176c0aebad3a1ab7268b0d82d9 \
--hash=sha256:4a6bb818a7a70803127590a54bb25c1e79810761c9d4c92cf9f16a56b518bd52
certbot-nginx==1.3.0 \
--hash=sha256:46106b96429d1aaf3765635056352d2372941027a3bc26bbf964e4329202adc7 \
--hash=sha256:9aa0869c1250b7ea0a1eb1df6bdb5d0d6190d6ca0400da1033a8decc0df6f65b

View file

@ -1,190 +0,0 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View file

@ -1,4 +0,0 @@
include LICENSE.txt
include README.rst
recursive-include docs *
recursive-include letshelp_certbot/testdata *

View file

@ -1 +0,0 @@
Let's help Certbot client

View file

@ -1 +0,0 @@
/_build/

View file

@ -1,192 +0,0 @@
# Makefile for Sphinx documentation
#
# You can set these variables from the command line.
SPHINXOPTS =
SPHINXBUILD = sphinx-build
PAPER =
BUILDDIR = _build
# User-friendly check for sphinx-build
ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
endif
# Internal variables.
PAPEROPT_a4 = -D latex_paper_size=a4
PAPEROPT_letter = -D latex_paper_size=letter
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
# the i18n builder cannot share the environment and doctrees with the others
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext
help:
@echo "Please use \`make <target>' where <target> is one of"
@echo " html to make standalone HTML files"
@echo " dirhtml to make HTML files named index.html in directories"
@echo " singlehtml to make a single large HTML file"
@echo " pickle to make pickle files"
@echo " json to make JSON files"
@echo " htmlhelp to make HTML files and a HTML help project"
@echo " qthelp to make HTML files and a qthelp project"
@echo " applehelp to make an Apple Help Book"
@echo " devhelp to make HTML files and a Devhelp project"
@echo " epub to make an epub"
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
@echo " latexpdf to make LaTeX files and run them through pdflatex"
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
@echo " text to make text files"
@echo " man to make manual pages"
@echo " texinfo to make Texinfo files"
@echo " info to make Texinfo files and run them through makeinfo"
@echo " gettext to make PO message catalogs"
@echo " changes to make an overview of all changed/added/deprecated items"
@echo " xml to make Docutils-native XML files"
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
@echo " linkcheck to check all external links for integrity"
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
@echo " coverage to run coverage check of the documentation (if enabled)"
clean:
rm -rf $(BUILDDIR)/*
html:
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
dirhtml:
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
@echo
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
singlehtml:
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
@echo
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
pickle:
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
@echo
@echo "Build finished; now you can process the pickle files."
json:
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
@echo
@echo "Build finished; now you can process the JSON files."
htmlhelp:
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
@echo
@echo "Build finished; now you can run HTML Help Workshop with the" \
".hhp project file in $(BUILDDIR)/htmlhelp."
qthelp:
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
@echo
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/letshelp-certbot.qhcp"
@echo "To view the help file:"
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/letshelp-certbot.qhc"
applehelp:
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
@echo
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
@echo "N.B. You won't be able to view it unless you put it in" \
"~/Library/Documentation/Help or install it in your application" \
"bundle."
devhelp:
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
@echo
@echo "Build finished."
@echo "To view the help file:"
@echo "# mkdir -p $$HOME/.local/share/devhelp/letshelp-certbot"
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/letshelp-certbot"
@echo "# devhelp"
epub:
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
@echo
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
latex:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
@echo "Run \`make' in that directory to run these through (pdf)latex" \
"(use \`make latexpdf' here to do that automatically)."
latexpdf:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through pdflatex..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
latexpdfja:
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
@echo "Running LaTeX files through platex and dvipdfmx..."
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
text:
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
@echo
@echo "Build finished. The text files are in $(BUILDDIR)/text."
man:
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
@echo
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
texinfo:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
@echo "Run \`make' in that directory to run these through makeinfo" \
"(use \`make info' here to do that automatically)."
info:
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
@echo "Running Texinfo files through makeinfo..."
make -C $(BUILDDIR)/texinfo info
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
gettext:
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
@echo
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
changes:
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
@echo
@echo "The overview file is in $(BUILDDIR)/changes."
linkcheck:
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
@echo
@echo "Link check complete; look for any errors in the above output " \
"or in $(BUILDDIR)/linkcheck/output.txt."
doctest:
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
@echo "Testing of doctests in the sources finished, look at the " \
"results in $(BUILDDIR)/doctest/output.txt."
coverage:
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
@echo "Testing of coverage in the sources finished, look at the " \
"results in $(BUILDDIR)/coverage/python.txt."
xml:
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
@echo
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
pseudoxml:
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
@echo
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."

View file

@ -1,8 +0,0 @@
=================
API Documentation
=================
.. toctree::
:glob:
api/**

View file

@ -1,11 +0,0 @@
:mod:`letshelp_certbot`
---------------------------
.. automodule:: letshelp_certbot
:members:
:mod:`letshelp_certbot.apache`
==================================
.. automodule:: letshelp_certbot.apache
:members:

View file

@ -1,310 +0,0 @@
# -*- coding: utf-8 -*-
#
# letshelp-certbot documentation build configuration file, created by
# sphinx-quickstart on Sun Oct 18 13:40:19 2015.
#
# This file is execfile()d with the current directory set to its
# containing dir.
#
# Note that not all possible configuration values are present in this
# autogenerated file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.
import os
import shlex
import sys
here = os.path.abspath(os.path.dirname(__file__))
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
sys.path.insert(0, os.path.abspath(os.path.join(here, '..')))
# -- General configuration ------------------------------------------------
# If your documentation needs a minimal Sphinx version, state it here.
needs_sphinx = '1.0'
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.intersphinx',
'sphinx.ext.todo',
'sphinx.ext.coverage',
'sphinx.ext.viewcode',
]
autodoc_member_order = 'bysource'
autodoc_default_flags = ['show-inheritance']
# Add any paths that contain templates here, relative to this directory.
templates_path = ['_templates']
# The suffix(es) of source filenames.
# You can specify multiple suffix as a list of string:
# source_suffix = ['.rst', '.md']
source_suffix = '.rst'
# The encoding of source files.
#source_encoding = 'utf-8-sig'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'letshelp-certbot'
copyright = u'2014-2015, Let\'s Encrypt Project'
author = u'Certbot Project'
# The version info for the project you're documenting, acts as replacement for
# |version| and |release|, also used in various other places throughout the
# built documents.
#
# The short X.Y version.
version = '0'
# The full version, including alpha/beta/rc tags.
release = '0'
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#
# This is also used if you do content translation via gettext catalogs.
# Usually you set "language" from the command line for these cases.
language = 'en'
# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']
# The reST default role (used for this markup: `text`) to use for all
# documents.
default_role = 'py:obj'
# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True
# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []
# If true, keep warnings as "system message" paragraphs in the built documents.
#keep_warnings = False
# If true, `todo` and `todoList` produce output, else they produce nothing.
todo_include_todos = False
# -- Options for HTML output ----------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
# http://docs.readthedocs.org/en/latest/theme.html#how-do-i-use-this-locally-and-on-read-the-docs
# on_rtd is whether we are on readthedocs.org
on_rtd = os.environ.get('READTHEDOCS', None) == 'True'
if not on_rtd: # only import and set the theme if we're building docs locally
import sphinx_rtd_theme
html_theme = 'sphinx_rtd_theme'
html_theme_path = [sphinx_rtd_theme.get_html_theme_path()]
# otherwise, readthedocs.org uses their theme by default, so no need to specify it
# Theme options are theme-specific and customize the look and feel of a theme
# further. For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}
# Add any paths that contain custom themes here, relative to this directory.
#html_theme_path = []
# The name for this set of Sphinx documents. If None, it defaults to
# "<project> v<release> documentation".
#html_title = None
# A shorter title for the navigation bar. Default is the same as html_title.
#html_short_title = None
# The name of an image file (relative to this directory) to place at the top
# of the sidebar.
#html_logo = None
# The name of an image file (within the static path) to use as favicon of the
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.
#html_favicon = None
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
#html_extra_path = []
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
#html_last_updated_fmt = '%b %d, %Y'
# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True
# Custom sidebar templates, maps document names to template names.
#html_sidebars = {}
# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}
# If false, no module index is generated.
#html_domain_indices = True
# If false, no index is generated.
#html_use_index = True
# If true, the index is split into individual pages for each letter.
#html_split_index = False
# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True
# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it. The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''
# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None
# Language to be used for generating the HTML full-text search index.
# Sphinx supports the following languages:
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr'
#html_search_language = 'en'
# A dictionary with options for the search language support, empty by default.
# Now only 'ja' uses this config value
#html_search_options = {'type': 'default'}
# The name of a javascript file (relative to the configuration directory) that
# implements a search results scorer. If empty, the default will be used.
#html_search_scorer = 'scorer.js'
# Output file base name for HTML help builder.
htmlhelp_basename = 'letshelp-certbotdoc'
# -- Options for LaTeX output ---------------------------------------------
latex_elements = {
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',
# The font size ('10pt', '11pt' or '12pt').
#'pointsize': '10pt',
# Additional stuff for the LaTeX preamble.
#'preamble': '',
# Latex figure (float) alignment
#'figure_align': 'htbp',
}
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title,
# author, documentclass [howto, manual, or own class]).
latex_documents = [
(master_doc, 'letshelp-certbot.tex', u'letshelp-certbot Documentation',
u'Certbot Project', 'manual'),
]
# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None
# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
#latex_use_parts = False
# If true, show page references after internal links.
#latex_show_pagerefs = False
# If true, show URL addresses after external links.
#latex_show_urls = False
# Documents to append as an appendix to all manuals.
#latex_appendices = []
# If false, no module index is generated.
#latex_domain_indices = True
# -- Options for manual page output ---------------------------------------
# One entry per manual page. List of tuples
# (source start file, name, description, authors, manual section).
man_pages = [
(master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation',
[author], 1)
]
# If true, show URL addresses after external links.
#man_show_urls = False
# -- Options for Texinfo output -------------------------------------------
# Grouping the document tree into Texinfo files. List of tuples
# (source start file, target name, title, author,
# dir menu entry, description, category)
texinfo_documents = [
(master_doc, 'letshelp-certbot', u'letshelp-certbot Documentation',
author, 'letshelp-certbot', 'One line description of project.',
'Miscellaneous'),
]
# Documents to append as an appendix to all manuals.
#texinfo_appendices = []
# If false, no module index is generated.
#texinfo_domain_indices = True
# How to display URL addresses: 'footnote', 'no', or 'inline'.
#texinfo_show_urls = 'footnote'
# If true, do not generate a @detailmenu in the "Top" node's menu.
#texinfo_no_detailmenu = False
intersphinx_mapping = {
'python': ('https://docs.python.org/', None),
'acme': ('https://acme-python.readthedocs.org/en/latest/', None),
'certbot': ('https://certbot.eff.org/docs/', None),
}

View file

@ -1,27 +0,0 @@
.. letshelp-certbot documentation master file, created by
sphinx-quickstart on Sun Oct 18 13:40:19 2015.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
Welcome to letshelp-certbot's documentation!
================================================
Contents:
.. toctree::
:maxdepth: 2
.. toctree::
:maxdepth: 1
api
Indices and tables
==================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`

View file

@ -1,263 +0,0 @@
@ECHO OFF
REM Command file for Sphinx documentation
if "%SPHINXBUILD%" == "" (
set SPHINXBUILD=sphinx-build
)
set BUILDDIR=_build
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
set I18NSPHINXOPTS=%SPHINXOPTS% .
if NOT "%PAPER%" == "" (
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
)
if "%1" == "" goto help
if "%1" == "help" (
:help
echo.Please use `make ^<target^>` where ^<target^> is one of
echo. html to make standalone HTML files
echo. dirhtml to make HTML files named index.html in directories
echo. singlehtml to make a single large HTML file
echo. pickle to make pickle files
echo. json to make JSON files
echo. htmlhelp to make HTML files and a HTML help project
echo. qthelp to make HTML files and a qthelp project
echo. devhelp to make HTML files and a Devhelp project
echo. epub to make an epub
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
echo. text to make text files
echo. man to make manual pages
echo. texinfo to make Texinfo files
echo. gettext to make PO message catalogs
echo. changes to make an overview over all changed/added/deprecated items
echo. xml to make Docutils-native XML files
echo. pseudoxml to make pseudoxml-XML files for display purposes
echo. linkcheck to check all external links for integrity
echo. doctest to run all doctests embedded in the documentation if enabled
echo. coverage to run coverage check of the documentation if enabled
goto end
)
if "%1" == "clean" (
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
del /q /s %BUILDDIR%\*
goto end
)
REM Check if sphinx-build is available and fallback to Python version if any
%SPHINXBUILD% 2> nul
if errorlevel 9009 goto sphinx_python
goto sphinx_ok
:sphinx_python
set SPHINXBUILD=python -m sphinx.__init__
%SPHINXBUILD% 2> nul
if errorlevel 9009 (
echo.
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
echo.installed, then set the SPHINXBUILD environment variable to point
echo.to the full path of the 'sphinx-build' executable. Alternatively you
echo.may add the Sphinx directory to PATH.
echo.
echo.If you don't have Sphinx installed, grab it from
echo.http://sphinx-doc.org/
exit /b 1
)
:sphinx_ok
if "%1" == "html" (
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
goto end
)
if "%1" == "dirhtml" (
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
goto end
)
if "%1" == "singlehtml" (
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
goto end
)
if "%1" == "pickle" (
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the pickle files.
goto end
)
if "%1" == "json" (
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can process the JSON files.
goto end
)
if "%1" == "htmlhelp" (
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run HTML Help Workshop with the ^
.hhp project file in %BUILDDIR%/htmlhelp.
goto end
)
if "%1" == "qthelp" (
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished; now you can run "qcollectiongenerator" with the ^
.qhcp project file in %BUILDDIR%/qthelp, like this:
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\letshelp-certbot.qhcp
echo.To view the help file:
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\letshelp-certbot.ghc
goto end
)
if "%1" == "devhelp" (
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
if errorlevel 1 exit /b 1
echo.
echo.Build finished.
goto end
)
if "%1" == "epub" (
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The epub file is in %BUILDDIR%/epub.
goto end
)
if "%1" == "latex" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
if errorlevel 1 exit /b 1
echo.
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdf" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "latexpdfja" (
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
cd %BUILDDIR%/latex
make all-pdf-ja
cd %~dp0
echo.
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
goto end
)
if "%1" == "text" (
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The text files are in %BUILDDIR%/text.
goto end
)
if "%1" == "man" (
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The manual pages are in %BUILDDIR%/man.
goto end
)
if "%1" == "texinfo" (
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
goto end
)
if "%1" == "gettext" (
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
goto end
)
if "%1" == "changes" (
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
if errorlevel 1 exit /b 1
echo.
echo.The overview file is in %BUILDDIR%/changes.
goto end
)
if "%1" == "linkcheck" (
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
if errorlevel 1 exit /b 1
echo.
echo.Link check complete; look for any errors in the above output ^
or in %BUILDDIR%/linkcheck/output.txt.
goto end
)
if "%1" == "doctest" (
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
if errorlevel 1 exit /b 1
echo.
echo.Testing of doctests in the sources finished, look at the ^
results in %BUILDDIR%/doctest/output.txt.
goto end
)
if "%1" == "coverage" (
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
if errorlevel 1 exit /b 1
echo.
echo.Testing of coverage in the sources finished, look at the ^
results in %BUILDDIR%/coverage/python.txt.
goto end
)
if "%1" == "xml" (
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The XML files are in %BUILDDIR%/xml.
goto end
)
if "%1" == "pseudoxml" (
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
if errorlevel 1 exit /b 1
echo.
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
goto end
)
:end

View file

@ -1 +0,0 @@
"""Tools for submitting server configurations"""

View file

@ -1,314 +0,0 @@
#!/usr/bin/env python
"""Certbot Apache configuration submission script"""
from __future__ import print_function
import argparse
import atexit
import os
import re
import shutil
import subprocess
import sys
import tarfile
import tempfile
import textwrap
import six
from letshelp_certbot.magic_typing import List
_DESCRIPTION = """
Let's Help is a simple script you can run to help out the Certbot
project. Since Certbot will support automatically configuring HTTPS on
many servers, we want to test this functionality on as many configurations as
possible. This script will create a sanitized copy of your Apache
configuration, notifying you of the files that have been selected. If (and only
if) you approve this selection, these files will be sent to the Certbot
developers.
"""
_NO_APACHECTL = """
Unable to find `apachectl` which is required for this script to work. If it is
installed, please run this script again with the --apache-ctl command line
argument and the path to the binary.
"""
# Keywords likely to be found in filenames of sensitive files
_SENSITIVE_FILENAME_REGEX = re.compile(r"^(?!.*proxy_fdpass).*pass.*$|private|"
r"secret|^(?!.*certbot).*cert.*$|crt|"
r"key|rsa|dsa|pw|\.pem|\.der|\.p12|"
r"\.pfx|\.p7b")
def make_and_verify_selection(server_root, temp_dir):
"""Copies server_root to temp_dir and verifies selection with the user
:param str server_root: Path to the Apache server root
:param str temp_dir: Path to the temporary directory to copy files to
"""
copied_files, copied_dirs = copy_config(server_root, temp_dir)
print(textwrap.fill("A secure copy of the files that have been selected "
"for submission has been created under {0}. All "
"comments have been removed and the files are only "
"accessible by the current user. A list of the files "
"that have been included is shown below. Please make "
"sure that this selection does not contain private "
"keys, passwords, or any other sensitive "
"information.".format(temp_dir)))
print("\nFiles:")
for copied_file in copied_files:
print(copied_file)
print("Directories (including all contained files):")
for copied_dir in copied_dirs:
print(copied_dir)
sys.stdout.write("\nIs it safe to submit these files? ")
while True:
ans = six.moves.input("(Y)es/(N)o: ").lower()
if ans.startswith("y"):
return
if ans.startswith("n"):
sys.exit("Your files were not submitted")
def copy_config(server_root, temp_dir):
"""Safely copies server_root to temp_dir and returns copied files
:param str server_root: Absolute path to the Apache server root
:param str temp_dir: Path to the temporary directory to copy files to
:returns: List of copied files and a list of leaf directories where
all contained files were copied
:rtype: `tuple` of `list` of `str`
"""
copied_files = [] # type: List[str]
copied_dirs = [] # type: List[str]
dir_len = len(os.path.dirname(server_root))
for config_path, config_dirs, config_files in os.walk(server_root):
temp_path = os.path.join(temp_dir, config_path[dir_len + 1:])
os.mkdir(temp_path)
copied_all = True
copied_files_in_current_dir = []
for config_file in config_files:
config_file_path = os.path.join(config_path, config_file)
temp_file_path = os.path.join(temp_path, config_file)
if os.path.islink(config_file_path):
os.symlink(os.readlink(config_file_path), temp_file_path)
elif safe_config_file(config_file_path):
copy_file_without_comments(config_file_path, temp_file_path)
copied_files_in_current_dir.append(config_file_path)
else:
copied_all = False
# If copied all files in leaf directory
if copied_all and not config_dirs:
copied_dirs.append(config_path)
else:
copied_files += copied_files_in_current_dir
return copied_files, copied_dirs
def copy_file_without_comments(source, destination):
"""Copies source to destination, removing comments
:param str source: Path to the file to be copied
:param str destination: Path where source should be copied to
"""
with open(source, "r") as infile:
with open(destination, "w") as outfile:
for line in infile:
if not (line.isspace() or line.lstrip().startswith("#")):
outfile.write(line)
def safe_config_file(config_file):
"""Returns True if config_file can be safely copied
:param str config_file: Path to an Apache configuration file
:returns: True if config_file can be safely copied
:rtype: bool
"""
config_file_lower = config_file.lower()
if _SENSITIVE_FILENAME_REGEX.search(config_file_lower):
return False
proc = subprocess.Popen(["file", config_file],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
file_output, _ = proc.communicate()
if "ASCII" in file_output:
possible_password_file = empty_or_all_comments = True
with open(config_file) as config_fd:
for line in config_fd:
if not (line.isspace() or line.lstrip().startswith("#")):
empty_or_all_comments = False
if line.startswith("-----BEGIN"):
return False
if ":" not in line:
possible_password_file = False
# If file isn't empty or commented out and could be a password file,
# don't include it in selection. It is safe to include the file if
# it consists solely of comments because comments are removed before
# submission.
return empty_or_all_comments or not possible_password_file
return False
def setup_tempdir(args):
"""Creates a temporary directory and necessary files for config
:param argparse.Namespace args: Parsed command line arguments
:returns: Path to temporary directory
:rtype: str
"""
tempdir = tempfile.mkdtemp()
with open(os.path.join(tempdir, "config_file"), "w") as config_fd:
config_fd.write(args.config_file + "\n")
proc = subprocess.Popen([args.apache_ctl, "-v"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
with open(os.path.join(tempdir, "version"), "w") as version_fd:
version_fd.write(proc.communicate()[0])
proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f",
args.config_file, "-M"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
with open(os.path.join(tempdir, "modules"), "w") as modules_fd:
modules_fd.write(proc.communicate()[0])
proc = subprocess.Popen([args.apache_ctl, "-d", args.server_root, "-f",
args.config_file, "-t", "-D", "DUMP_VHOSTS"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
with open(os.path.join(tempdir, "vhosts"), "w") as vhosts_fd:
vhosts_fd.write(proc.communicate()[0])
return tempdir
def verify_config(args):
"""Verifies server_root and config_file specify a valid config
:param argparse.Namespace args: Parsed command line arguments
"""
with open(os.devnull, "w") as devnull:
try:
subprocess.check_call([args.apache_ctl, "-d", args.server_root,
"-f", args.config_file, "-t"],
stdout=devnull, stderr=subprocess.STDOUT)
except OSError:
sys.exit(_NO_APACHECTL)
except subprocess.CalledProcessError:
sys.exit("Syntax check from apachectl failed")
def locate_config(apache_ctl):
"""Uses the apachectl binary to find configuration files
:param str apache_ctl: Path to `apachectl` binary
:returns: Path to Apache server root and main configuration file
:rtype: `tuple` of `str`
"""
try:
proc = subprocess.Popen([apache_ctl, "-V"],
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
universal_newlines=True)
output, _ = proc.communicate()
except OSError:
sys.exit(_NO_APACHECTL)
server_root = config_file = ""
for line in output.splitlines():
# Relevant output lines are of the form: -D DIRECTIVE="VALUE"
if "HTTPD_ROOT" in line:
server_root = line[line.find('"') + 1:-1]
elif "SERVER_CONFIG_FILE" in line:
config_file = line[line.find('"') + 1:-1]
if not (server_root and config_file):
sys.exit("Unable to locate Apache configuration. Please run this "
"script again and specify --server-root and --config-file")
return server_root, config_file
def get_args():
"""Parses command line arguments
:returns: Parsed command line options
:rtype: argparse.Namespace
"""
parser = argparse.ArgumentParser(description=_DESCRIPTION)
parser.add_argument("-c", "--apache-ctl", default="apachectl",
help="path to the `apachectl` binary")
parser.add_argument("-d", "--server-root",
help=("location of the root directory of your Apache "
"configuration"))
parser.add_argument("-f", "--config-file",
help=("location of your main Apache configuration "
"file relative to the server root"))
args = parser.parse_args()
# args.server_root XOR args.config_file
if bool(args.server_root) != bool(args.config_file):
sys.exit("If either --server-root and --config-file are specified, "
"they both must be included")
elif args.server_root and args.config_file:
args.server_root = os.path.abspath(args.server_root)
args.config_file = os.path.abspath(args.config_file)
if args.config_file.startswith(args.server_root):
args.config_file = args.config_file[len(args.server_root) + 1:]
else:
sys.exit("This script expects the Apache configuration file to be "
"inside the server root")
return args
def main():
"""Main script execution"""
args = get_args()
if args.server_root is None:
args.server_root, args.config_file = locate_config(args.apache_ctl)
verify_config(args)
tempdir = setup_tempdir(args)
atexit.register(lambda: shutil.rmtree(tempdir))
make_and_verify_selection(args.server_root, tempdir)
tarpath = os.path.join(tempdir, "config.tar.gz")
with tarfile.open(tarpath, mode="w:gz") as tar:
tar.add(tempdir, arcname=".")
# TODO: Submit tarpath
if __name__ == "__main__":
main() # pragma: no cover

View file

@ -1,241 +0,0 @@
"""Tests for letshelp.letshelp_certbot_apache.py"""
import argparse
import functools
import os
import subprocess
import tarfile
import tempfile
import unittest
# six is used in mock.patch()
import mock
import pkg_resources
import six # pylint: disable=unused-import
import letshelp_certbot.apache as letshelp_le_apache
_PARTIAL_CONF_PATH = os.path.join("mods-available", "ssl.load")
_PARTIAL_LINK_PATH = os.path.join("mods-enabled", "ssl.load")
_CONFIG_FILE = pkg_resources.resource_filename(
__name__, os.path.join("testdata", _PARTIAL_CONF_PATH))
_PASSWD_FILE = pkg_resources.resource_filename(
__name__, os.path.join("testdata", "uncommonly_named_p4sswd"))
_KEY_FILE = pkg_resources.resource_filename(
__name__, os.path.join("testdata", "uncommonly_named_k3y"))
_SECRET_FILE = pkg_resources.resource_filename(
__name__, os.path.join("testdata", "super_secret_file.txt"))
_MODULE_NAME = "letshelp_certbot.apache"
_COMPILE_SETTINGS = """Server version: Apache/2.4.10 (Debian)
Server built: Mar 15 2015 09:51:43
Server's Module Magic Number: 20120211:37
Server loaded: APR 1.5.1, APR-UTIL 1.5.4
Compiled using: APR 1.5.1, APR-UTIL 1.5.4
Architecture: 64-bit
Server MPM: event
threaded: yes (fixed thread count)
forked: yes (variable process count)
Server compiled with....
-D APR_HAS_SENDFILE
-D APR_HAS_MMAP
-D APR_HAVE_IPV6 (IPv4-mapped addresses enabled)
-D APR_USE_SYSVSEM_SERIALIZE
-D APR_USE_PTHREAD_SERIALIZE
-D SINGLE_LISTEN_UNSERIALIZED_ACCEPT
-D APR_HAS_OTHER_CHILD
-D AP_HAVE_RELIABLE_PIPED_LOGS
-D DYNAMIC_MODULE_LIMIT=256
-D HTTPD_ROOT="/etc/apache2"
-D SUEXEC_BIN="/usr/lib/apache2/suexec"
-D DEFAULT_PIDLOG="/var/run/apache2.pid"
-D DEFAULT_SCOREBOARD="logs/apache_runtime_status"
-D DEFAULT_ERRORLOG="logs/error_log"
-D AP_TYPES_CONFIG_FILE="mime.types"
-D SERVER_CONFIG_FILE="apache2.conf"
"""
class LetsHelpApacheTest(unittest.TestCase):
@mock.patch(_MODULE_NAME + ".copy_config")
def test_make_and_verify_selection(self, mock_copy_config):
mock_copy_config.return_value = (["apache2.conf"], ["apache2"])
with mock.patch("six.moves.input") as mock_input:
with mock.patch(_MODULE_NAME + ".sys.stdout"):
mock_input.side_effect = ["Yes", "No"]
letshelp_le_apache.make_and_verify_selection("root", "temp")
self.assertRaises(
SystemExit, letshelp_le_apache.make_and_verify_selection,
"server_root", "temp_dir")
def test_copy_config(self):
tempdir = tempfile.mkdtemp()
server_root = pkg_resources.resource_filename(__name__, "testdata")
letshelp_le_apache.copy_config(server_root, tempdir)
temp_testdata = os.path.join(tempdir, "testdata")
self.assertFalse(os.path.exists(os.path.join(
temp_testdata, os.path.basename(_PASSWD_FILE))))
self.assertFalse(os.path.exists(os.path.join(
temp_testdata, os.path.basename(_KEY_FILE))))
self.assertFalse(os.path.exists(os.path.join(
temp_testdata, os.path.basename(_SECRET_FILE))))
self.assertTrue(os.path.exists(os.path.join(
temp_testdata, _PARTIAL_CONF_PATH)))
self.assertTrue(os.path.exists(os.path.join(
temp_testdata, _PARTIAL_LINK_PATH)))
def test_copy_file_without_comments(self):
dest = tempfile.mkstemp()[1]
letshelp_le_apache.copy_file_without_comments(_PASSWD_FILE, dest)
with open(_PASSWD_FILE) as original:
with open(dest) as copy:
for original_line, copied_line in zip(original, copy):
self.assertEqual(original_line, copied_line)
@mock.patch(_MODULE_NAME + ".subprocess.Popen")
def test_safe_config_file(self, mock_popen):
mock_popen().communicate.return_value = ("PEM RSA private key", None)
self.assertFalse(letshelp_le_apache.safe_config_file("filename"))
mock_popen().communicate.return_value = ("ASCII text", None)
self.assertFalse(letshelp_le_apache.safe_config_file(_PASSWD_FILE))
self.assertFalse(letshelp_le_apache.safe_config_file(_KEY_FILE))
self.assertFalse(letshelp_le_apache.safe_config_file(_SECRET_FILE))
self.assertTrue(letshelp_le_apache.safe_config_file(_CONFIG_FILE))
@mock.patch(_MODULE_NAME + ".subprocess.Popen")
def test_tempdir(self, mock_popen):
mock_popen().communicate.side_effect = [
("version", None), ("modules", None), ("vhosts", None)]
args = _get_args()
tempdir = letshelp_le_apache.setup_tempdir(args)
with open(os.path.join(tempdir, "config_file")) as config_fd:
self.assertEqual(config_fd.read(), args.config_file + "\n")
with open(os.path.join(tempdir, "version")) as version_fd:
self.assertEqual(version_fd.read(), "version")
with open(os.path.join(tempdir, "modules")) as modules_fd:
self.assertEqual(modules_fd.read(), "modules")
with open(os.path.join(tempdir, "vhosts")) as vhosts_fd:
self.assertEqual(vhosts_fd.read(), "vhosts")
@mock.patch(_MODULE_NAME + ".subprocess.check_call")
def test_verify_config(self, mock_check_call):
args = _get_args()
mock_check_call.side_effect = [
None, OSError, subprocess.CalledProcessError(1, "apachectl")]
letshelp_le_apache.verify_config(args)
self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args)
self.assertRaises(SystemExit, letshelp_le_apache.verify_config, args)
@mock.patch(_MODULE_NAME + ".subprocess.Popen")
def test_locate_config(self, mock_popen):
mock_popen().communicate.side_effect = [
OSError, ("bad_output", None), (_COMPILE_SETTINGS, None)]
self.assertRaises(
SystemExit, letshelp_le_apache.locate_config, "ctl")
self.assertRaises(
SystemExit, letshelp_le_apache.locate_config, "ctl")
server_root, config_file = letshelp_le_apache.locate_config("ctl")
self.assertEqual(server_root, "/etc/apache2")
self.assertEqual(config_file, "apache2.conf")
@mock.patch(_MODULE_NAME + ".argparse")
def test_get_args(self, mock_argparse):
argv = ["-d", "/etc/apache2"]
mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv)
self.assertRaises(SystemExit, letshelp_le_apache.get_args)
server_root = "/etc/apache2"
config_file = server_root + "/apache2.conf"
argv = ["-d", server_root, "-f", config_file]
mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv)
args = letshelp_le_apache.get_args()
self.assertEqual(args.apache_ctl, "apachectl")
self.assertEqual(args.server_root, server_root)
self.assertEqual(args.config_file, os.path.basename(config_file))
server_root = "/etc/apache2"
config_file = "/etc/httpd/httpd.conf"
argv = ["-d", server_root, "-f", config_file]
mock_argparse.ArgumentParser.return_value = _create_mock_parser(argv)
self.assertRaises(SystemExit, letshelp_le_apache.get_args)
def test_main_with_args(self):
with mock.patch(_MODULE_NAME + ".get_args"):
self._test_main_common()
def test_main_without_args(self):
with mock.patch(_MODULE_NAME + ".get_args") as get_args:
args = _get_args()
server_root, config_file = args.server_root, args.config_file
args.server_root = args.config_file = None
get_args.return_value = args
with mock.patch(_MODULE_NAME + ".locate_config") as locate:
locate.return_value = (server_root, config_file)
self._test_main_common()
def _test_main_common(self):
with mock.patch(_MODULE_NAME + ".verify_config"):
with mock.patch(_MODULE_NAME + ".setup_tempdir") as mock_setup:
tempdir_path = tempfile.mkdtemp()
mock_setup.return_value = tempdir_path
with mock.patch(_MODULE_NAME + ".make_and_verify_selection"):
testdir_basename = "test"
os.mkdir(os.path.join(tempdir_path, testdir_basename))
letshelp_le_apache.main()
tar = tarfile.open(os.path.join(
tempdir_path, "config.tar.gz"))
tempdir = tar.next()
if tempdir is None:
self.fail("Invalid tarball!") # pragma: no cover
else:
self.assertTrue(tempdir.isdir())
self.assertEqual(tempdir.name, ".")
testdir = tar.next()
if testdir is None:
self.fail("Invalid tarball!") # pragma: no cover
else:
self.assertTrue(testdir.isdir())
self.assertEqual(os.path.basename(testdir.name),
testdir_basename)
self.assertEqual(tar.next(), None)
def _create_mock_parser(argv):
parser = argparse.ArgumentParser()
mock_parser = mock.MagicMock()
mock_parser.add_argument = parser.add_argument
mock_parser.parse_args = functools.partial(parser.parse_args, argv)
return mock_parser
def _get_args():
args = argparse.Namespace()
args.apache_ctl = "apache_ctl"
args.config_file = "config_file"
args.server_root = "server_root"
return args
if __name__ == "__main__":
unittest.main() # pragma: no cover

View file

@ -1,16 +0,0 @@
"""Shim class to not have to depend on typing module in prod."""
import sys
class TypingClass(object):
"""Ignore import errors by getting anything"""
def __getattr__(self, name):
return None
try:
# mypy doesn't respect modifying sys.modules
from typing import * # pylint: disable=wildcard-import, unused-wildcard-import
from typing import Collection, IO # type: ignore
# pylint: enable=unused-import
except ImportError:
sys.modules[__name__] = TypingClass()

View file

@ -1,41 +0,0 @@
"""Tests for letshelp_certbot.magic_typing."""
import sys
import unittest
import mock
class MagicTypingTest(unittest.TestCase):
"""Tests for letshelp_certbot.magic_typing."""
def test_import_success(self):
try:
import typing as temp_typing
except ImportError: # pragma: no cover
temp_typing = None # pragma: no cover
typing_class_mock = mock.MagicMock()
text_mock = mock.MagicMock()
typing_class_mock.Text = text_mock
sys.modules['typing'] = typing_class_mock
if 'letshelp_certbot.magic_typing' in sys.modules:
del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover
from letshelp_certbot.magic_typing import Text
self.assertEqual(Text, text_mock)
del sys.modules['letshelp_certbot.magic_typing']
sys.modules['typing'] = temp_typing
def test_import_failure(self):
try:
import typing as temp_typing
except ImportError: # pragma: no cover
temp_typing = None # pragma: no cover
sys.modules['typing'] = None
if 'letshelp_certbot.magic_typing' in sys.modules:
del sys.modules['letshelp_certbot.magic_typing'] # pragma: no cover
from letshelp_certbot.magic_typing import Text
self.assertTrue(Text is None)
del sys.modules['letshelp_certbot.magic_typing']
sys.modules['typing'] = temp_typing
if __name__ == '__main__':
unittest.main() # pragma: no cover

View file

@ -1,2 +0,0 @@
# Depends: setenvif mime socache_shmcb
LoadModule ssl_module /usr/lib/apache2/modules/mod_ssl.so

View file

@ -1 +0,0 @@
../mods-available/ssl.load

View file

@ -1 +0,0 @@
hunter2

View file

@ -1,6 +0,0 @@
-----BEGIN RSA PRIVATE KEY-----
MIGrAgEAAiEAm2Fylv+Uz7trgTW8EBHP3FQSMeZs2GNQ6VRo1sIVJEkCAwEAAQIh
AJT0BA/xD01dFCAXzSNyj9nfSZa3NpqzJZZn/eOm7vghAhEAzUVNZn4lLLBD1R6N
E8TKNQIRAMHHyn3O5JeY36lwKwkUlEUCEAliRauN0L0+QZuYjfJ9aJECEGx4dru3
rTPCyighdqWNlHUCEQCiLjlwSRtWgmMBudCkVjzt
-----END RSA PRIVATE KEY-----

View file

@ -1 +0,0 @@
johntheripper:$apr1$fIGE9.JL$jTCwNWZy9Ak/yvOLuOyzQ1

View file

@ -1,10 +0,0 @@
# readthedocs.org gives no way to change the install command to "pip
# install -e certbot[docs]" (that would in turn install documentation
# dependencies), but it allows to specify a requirements.txt file at
# https://readthedocs.org/dashboard/letsencrypt/advanced/ (c.f. #259)
# Although ReadTheDocs certainly doesn't need to install the project
# in --editable mode (-e), just "pip install .[docs]" does not work as
# expected and "pip install -e certbot[docs]" must be used instead
-e letshelp-certbot[docs]

View file

@ -1,2 +0,0 @@
[bdist_wheel]
universal = 1

View file

@ -1,58 +0,0 @@
from setuptools import find_packages
from setuptools import setup
version = '0.7.0.dev0'
install_requires = [
'mock',
'setuptools', # pkg_resources
]
docs_extras = [
'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags
'sphinx_rtd_theme',
]
setup(
name='letshelp-certbot',
version=version,
description="Let's help Certbot client",
url='https://github.com/letsencrypt/letsencrypt',
author="Certbot Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
'Topic :: System :: Networking',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
extras_require={
'docs': docs_extras,
},
entry_points={
'console_scripts': [
'letshelp-certbot-apache = letshelp_certbot.apache:main',
],
},
test_suite='letshelp_certbot',
)

View file

@ -10,7 +10,7 @@ from pylint.checkers import BaseChecker
from pylint.interfaces import IAstroidChecker
# Modules in theses packages can import the os module.
WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'letshelp_certbot', 'lock_test']
WHITELIST_PACKAGES = ['acme', 'certbot_compatibility_test', 'lock_test']
class ForbidStandardOsModule(BaseChecker):

View file

@ -5,6 +5,3 @@ python_version = 2.7
[mypy-acme.magic_typing_test]
ignore_errors = True
[mypy-letshelp_certbot.magic_typing_test]
ignore_errors = True

View file

@ -1,25 +1,19 @@
asn1crypto==0.24.0
awscli==1.16.157
bcrypt==3.1.6
boto3==1.9.146
botocore==1.12.147
cffi==1.12.3
colorama==0.3.9
cryptography==2.4.2
docutils==0.14
enum34==1.1.6
bcrypt==3.1.7
boto3==1.12.7
botocore==1.15.7
cffi==1.14.0
cryptography==2.8
docutils==0.15.2
enum34==1.1.9
Fabric==1.14.1
futures==3.2.0
idna==2.8
ipaddress==1.0.22
jmespath==0.9.4
paramiko==2.4.2
pyasn1==0.4.5
futures==3.3.0
ipaddress==1.0.23
jmespath==0.9.5
paramiko==2.7.1
pycparser==2.19
PyNaCl==1.3.0
python-dateutil==2.8.0
PyYAML==3.10
rsa==3.4.2
s3transfer==0.2.0
six==1.12.0
urllib3==1.24.3
python-dateutil==2.8.1
PyYAML==5.3
s3transfer==0.3.3
six==1.14.0
urllib3==1.25.8

Some files were not shown because too many files have changed in this diff Show more