diff --git a/.azure-pipelines/INSTALL.md b/.azure-pipelines/INSTALL.md new file mode 100644 index 000000000..9c1e4bff7 --- /dev/null +++ b/.azure-pipelines/INSTALL.md @@ -0,0 +1,119 @@ +# Configuring Azure Pipelines with Certbot + +Let's begin. All pipelines are defined in `.azure-pipelines`. Currently there are two: +* `.azure-pipelines/main.yml` is the main one, executed on PRs for master, and pushes to master, +* `.azure-pipelines/advanced.yml` add installer testing on top of the main pipeline, and is executed for `test-*` branches, release branches, and nightly run for master. + +Several templates are defined in `.azure-pipelines/templates`. These YAML files aggregate common jobs configuration that can be reused in several pipelines. + +Unlike Travis, where CodeCov is working without any action required, CodeCov supports Azure Pipelines +using the coverage-bash utility (not python-coverage for now) only if you provide the Codecov repo token +using the `CODECOV_TOKEN` environment variable. So `CODECOV_TOKEN` needs to be set as a secured +environment variable to allow the main pipeline to publish coverage reports to CodeCov. + +This INSTALL.md file explains how to configure Azure Pipelines with Certbot in order to execute the CI/CD logic defined in `.azure-pipelines` folder with it. +During this installation step, warnings describing user access and legal comitments will be displayed like this: +``` +!!! ACCESS REQUIRED !!! +``` + +This document suppose that the Azure DevOps organization is named _certbot_, and the Azure DevOps project is also _certbot_. + +## Useful links + +* https://docs.microsoft.com/en-us/azure/devops/pipelines/yaml-schema?view=azure-devops&tabs=schema +* https://www.azuredevopslabs.com/labs/azuredevops/github-integration/ +* https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/python?view=azure-devops + +## Prerequisites + +### Having a GitHub account + +Use your GitHub user for a normal GitHub account, or a user that has administrative rights to the GitHub organization if relevant. + +### Having an Azure DevOps account +- Go to https://dev.azure.com/, click "Start free with GitHub" +- Login to GitHub + +``` +!!! ACCESS REQUIRED !!! +Personal user data (email + profile info, in read-only) +``` + +- Microsoft will create a Live account using the email referenced for the GitHub account. This account is also linked to GitHub account (meaning you can log it using GitHub authentication) +- Proceed with account registration (birth date, country), add details about name and email contact + +``` +!!! ACCESS REQUIRED !!! +Microsoft proposes to send commercial links to this mail +Azure DevOps terms of service need to be accepted +``` + +_Logged to Azure DevOps, account is ready._ + +### Installing Azure Pipelines to GitHub + +- On GitHub, go to Marketplace +- Select Azure Pipeline, and "Set up a plan" +- Select Free, then "Install it for free" +- Click "Complete order and begin installation" + +``` +!!! ACCESS !!! +Azure Pipeline needs RW on code, RO on metadata, RW on checks, commit statuses, deployments, issues, pull requests. +RW access here is required to allow update of the pipelines YAML files from Azure DevOps interface, and to +update the status of builds and PRs on GitHub side when Azure Pipelines are triggered. +Note however that no admin access is defined here: this means that Azure Pipelines cannot do anything with +protected branches, like master, and cannot modify the security context around this on GitHub. +Access can be defined for all or only selected repositories, which is nice. +``` + +- Redirected to Azure DevOps, select the account created in _Having an Azure DevOps account_ section. +- Select the organization, and click "Create a new project" (let's name it the same than the targetted github repo) +- The Visibility is public, to profit from 10 parallel jobs + +``` +!!! ACCESS !!! +Azure Pipelines needs access to the GitHub account (in term of beeing able to check it is valid), and the Resources shared between the GitHub account and Azure Pipelines. +``` + +_Done. We can move to pipelines configuration._ + +## Import an existing pipelines from `.azure-pipelines` folder + +- On Azure DevOps, go to your organization (eg. _certbot_) then your project (eg. _certbot_) +- Click "Pipelines" tab +- Click "New pipeline" +- Where is your code?: select "__Use the classic editor__" + +__Warning: Do not choose the GitHub option in Where is your code? section. Indeed, this option will trigger an OAuth +grant permissions from Azure Pipelines to GitHub in order to setup a GitHub OAuth Application. The permissions asked +then are way too large (admin level on almost everything), while the classic approach does not add any more +permissions, and works perfectly well.__ + +- Select GitHub in "Select your repository section", choose certbot/certbot in Repository, master in default branch. +- Click on YAML option for "Select a template" +- Choose a name for the pipeline (eg. test-pipeline), and browse to the actual pipeline YAML definition in the + "YAML file path" input (eg. `.azure-pipelines/test-pipeline.yml`) +- Click "Save & queue", choose the master branch to build the first pipeline, and click "Save and run" button. + +_Done. Pipeline is operational. Repeat to add more pipelines from existing YAML files in `.azure-pipelines`._ + +## Add a secret variable to a pipeline (like `CODECOV_TOKEN`) + +__NB: Following steps suppose that you already setup the YAML pipeline file to +consume the secret variable that these steps will create as an environment variable. +For a variable named `CODECOV_TOKEN` consuming the variable `codecov_token`, +in the YAML file this setup would take the form of the following: +``` +steps: + - script: ./do_something_that_consumes_CODECOV_TOKEN # Eg. `codecov -F windows` + env: + CODECOV_TOKEN: $(codecov_token) +``` + +To set up a variable that is shared between pipelines, follow the instructions +at +https://docs.microsoft.com/en-us/azure/devops/pipelines/library/variable-groups. +When adding variables to a group, don't forget to tick "Keep this value secret" +if it shouldn't be shared publcily. diff --git a/.azure-pipelines/advanced.yml b/.azure-pipelines/advanced.yml new file mode 100644 index 000000000..44cdf5d54 --- /dev/null +++ b/.azure-pipelines/advanced.yml @@ -0,0 +1,20 @@ +# Advanced pipeline for isolated checks and release purpose +trigger: + - test-* + - '*.x' +pr: + - test-* +# This pipeline is also nightly run on master +schedules: + - cron: "0 4 * * *" + displayName: Nightly build + branches: + include: + - master + always: true + +jobs: + # Any addition here should be reflected in the release pipeline. + # It is advised to declare all jobs here as templates to improve maintainability. + - template: templates/tests-suite.yml + - template: templates/installer-tests.yml diff --git a/.azure-pipelines/main.yml b/.azure-pipelines/main.yml new file mode 100644 index 000000000..d9609037e --- /dev/null +++ b/.azure-pipelines/main.yml @@ -0,0 +1,12 @@ +trigger: + # apache-parser-v2 is a temporary branch for doing work related to + # rewriting the parser in the Apache plugin. + - apache-parser-v2 + - master +pr: + - apache-parser-v2 + - master + - '*.x' + +jobs: + - template: templates/tests-suite.yml diff --git a/.azure-pipelines/release.yml b/.azure-pipelines/release.yml new file mode 100644 index 000000000..aeb5ee327 --- /dev/null +++ b/.azure-pipelines/release.yml @@ -0,0 +1,13 @@ +# Release pipeline to build and deploy Certbot for Windows for GitHub release tags +trigger: + tags: + include: + - v* +pr: none + +jobs: + # Any addition here should be reflected in the advanced pipeline. + # It is advised to declare all jobs here as templates to improve maintainability. + - template: templates/tests-suite.yml + - template: templates/installer-tests.yml + - template: templates/changelog.yml diff --git a/.azure-pipelines/templates/changelog.yml b/.azure-pipelines/templates/changelog.yml new file mode 100644 index 000000000..4a65e2c2b --- /dev/null +++ b/.azure-pipelines/templates/changelog.yml @@ -0,0 +1,14 @@ +jobs: + - job: changelog + pool: + vmImage: vs2017-win2016 + steps: + - bash: | + CERTBOT_VERSION="$(cd certbot && python -c "import certbot; print(certbot.__version__)" && cd ~-)" + "${BUILD_REPOSITORY_LOCALPATH}\tools\extract_changelog.py" "${CERTBOT_VERSION}" >> "${BUILD_ARTIFACTSTAGINGDIRECTORY}/release_notes.md" + displayName: Prepare changelog + - task: PublishPipelineArtifact@1 + inputs: + path: $(Build.ArtifactStagingDirectory) + artifact: changelog + displayName: Publish changelog diff --git a/.azure-pipelines/templates/installer-tests.yml b/.azure-pipelines/templates/installer-tests.yml new file mode 100644 index 000000000..e3a005334 --- /dev/null +++ b/.azure-pipelines/templates/installer-tests.yml @@ -0,0 +1,54 @@ +jobs: + - job: installer_build + pool: + vmImage: vs2017-win2016 + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: 3.7 + architecture: x86 + addToPath: true + - script: python windows-installer/construct.py + displayName: Build Certbot installer + - task: CopyFiles@2 + inputs: + sourceFolder: $(System.DefaultWorkingDirectory)/windows-installer/build/nsis + contents: '*.exe' + targetFolder: $(Build.ArtifactStagingDirectory) + - task: PublishPipelineArtifact@1 + inputs: + path: $(Build.ArtifactStagingDirectory) + artifact: windows-installer + displayName: Publish Windows installer + - job: installer_run + dependsOn: installer_build + strategy: + matrix: + win2019: + imageName: windows-2019 + win2016: + imageName: vs2017-win2016 + win2012r2: + imageName: vs2015-win2012r2 + pool: + vmImage: $(imageName) + steps: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: windows-installer + path: $(Build.SourcesDirectory)/bin + displayName: Retrieve Windows installer + - script: $(Build.SourcesDirectory)\bin\certbot-beta-installer-win32.exe /S + displayName: Install Certbot + - powershell: Invoke-WebRequest https://www.python.org/ftp/python/3.8.0/python-3.8.0-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 + - script: | + py -3 -m venv venv + venv\Scripts\python tools\pip_install.py -e certbot-ci + displayName: Prepare Certbot-CI + - script: | + set PATH=%ProgramFiles(x86)%\Certbot\bin;%PATH% + venv\Scripts\python -m pytest certbot-ci\certbot_integration_tests\certbot_tests -n 4 + displayName: Run integration tests diff --git a/.azure-pipelines/templates/tests-suite.yml b/.azure-pipelines/templates/tests-suite.yml new file mode 100644 index 000000000..119f755a6 --- /dev/null +++ b/.azure-pipelines/templates/tests-suite.yml @@ -0,0 +1,38 @@ +jobs: + - job: test + pool: + vmImage: vs2017-win2016 + strategy: + matrix: + py35: + PYTHON_VERSION: 3.5 + TOXENV: py35 + py37-cover: + PYTHON_VERSION: 3.7 + TOXENV: py37-cover + integration-certbot: + PYTHON_VERSION: 3.7 + TOXENV: integration-certbot + PYTEST_ADDOPTS: --numprocesses 4 + variables: + - group: certbot-common + steps: + - task: UsePythonVersion@0 + inputs: + versionSpec: $(PYTHON_VERSION) + addToPath: true + - script: python tools/pip_install.py -U tox coverage + 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 diff --git a/.codecov.yml b/.codecov.yml index af67ea27f..0a97fffe3 100644 --- a/.codecov.yml +++ b/.codecov.yml @@ -6,13 +6,13 @@ coverage: flags: linux # Fixed target instead of auto set by #7173, can # be removed when flags in Codecov are added back. - target: 98.0 + 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: 96.9 + target: 97.4 threshold: 0.1 base: auto diff --git a/.coveragerc b/.coveragerc index 1a87ab2da..5d2a93148 100644 --- a/.coveragerc +++ b/.coveragerc @@ -1,2 +1,5 @@ +[run] +omit = */setup.py + [report] omit = */setup.py diff --git a/.github/stale.yml b/.github/stale.yml index 9c095ebd6..2e4106314 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -5,7 +5,8 @@ daysUntilStale: 365 # Number of days of inactivity before an Issue or Pull Request with the stale label is closed. # Set to false to disable. If disabled, issues still need to be closed manually, but will remain marked as stale. -daysUntilClose: 7 +# When changing this value, be sure to also update markComment below. +daysUntilClose: 30 # Ignore issues with an assignee (defaults to false) exemptAssignees: true @@ -18,8 +19,8 @@ markComment: > We've made a lot of changes to Certbot since this issue was opened. If you still have this issue with an up-to-date version of Certbot, can you please add a comment letting us know? This helps us to better see what issues are - still affecting our users. If there is no further activity, this issue will - be automatically closed. + still affecting our users. If there is no activity in the next 30 days, this + issue will be automatically closed. # Comment to post when closing a stale Issue or Pull Request. closeComment: > diff --git a/.gitignore b/.gitignore index 56ec23fbd..68762da6b 100644 --- a/.gitignore +++ b/.gitignore @@ -47,3 +47,5 @@ tests/letstest/venv/ # certbot tests .certbot_test_workspace +**/assets/pebble* +**/assets/challtestsrv* diff --git a/.isort.cfg b/.isort.cfg new file mode 100644 index 000000000..11c895f4d --- /dev/null +++ b/.isort.cfg @@ -0,0 +1,7 @@ +[settings] +skip_glob=venv* +skip=letsencrypt-auto-source +force_sort_within_sections=True +force_single_line=True +order_by_type=False +line_length=400 diff --git a/.pylintrc b/.pylintrc index bd4eab11a..0e78828bd 100644 --- a/.pylintrc +++ b/.pylintrc @@ -24,6 +24,11 @@ persistent=yes # usually to register additional checkers. load-plugins=linter_plugin +# A comma-separated list of package or module names from where C extensions may +# be loaded. Extensions are loading into the active Python interpreter and may +# run arbitrary code. +extension-pkg-whitelist=pywintypes,win32api,win32file,win32security + [MESSAGES CONTROL] @@ -41,10 +46,14 @@ load-plugins=linter_plugin # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use"--disable=all --enable=classes # --disable=W" -disable=fixme,locally-disabled,locally-enabled,abstract-class-not-used,abstract-class-little-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name,too-many-instance-attributes,cyclic-import,duplicate-code -# abstract-class-not-used cannot be disabled locally (at least in -# pylint 1.4.1), same for abstract-class-little-used - +# CERTBOT COMMENT +# 1) Once certbot codebase is claimed to be compatible exclusively with Python 3, +# the useless-object-inheritance check can be enabled again, and code fixed accordingly. +# 2) Check unsubscriptable-object tends to create a lot of false positives. Let's disable it. +# See https://github.com/PyCQA/pylint/issues/1498. +# 3) Same as point 2 for no-value-for-parameter. +# See https://github.com/PyCQA/pylint/issues/2820. +disable=fixme,locally-disabled,locally-enabled,bad-continuation,no-self-use,invalid-name,cyclic-import,duplicate-code,design,import-outside-toplevel,useless-object-inheritance,unsubscriptable-object,no-value-for-parameter,no-else-return,no-else-raise,no-else-break,no-else-continue [REPORTS] @@ -297,40 +306,6 @@ valid-classmethod-first-arg=cls valid-metaclass-classmethod-first-arg=mcs -[DESIGN] - -# Maximum number of arguments for function / method -max-args=6 - -# Argument names that match this expression will be ignored. Default to name -# with leading underscore -ignored-argument-names=(unused)?_.*|dummy - -# Maximum number of locals for function / method body -max-locals=15 - -# Maximum number of return / yield for function / method body -max-returns=6 - -# Maximum number of branch for function / method body -max-branches=12 - -# Maximum number of statements in function / method body -max-statements=50 - -# Maximum number of parents for a class (see R0901). -max-parents=12 - -# Maximum number of attributes for a class (see R0902). -max-attributes=7 - -# Minimum number of public methods for a class (see R0903). -min-public-methods=2 - -# Maximum number of public methods for a class (see R0904). -max-public-methods=20 - - [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to diff --git a/.travis.yml b/.travis.yml index d7d328c8b..63129c9b1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,5 @@ language: python +dist: xenial cache: directories: @@ -8,6 +9,8 @@ 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 + - 'if [[ "$TOXENV" == "travis-test-farm"* ]]; then export TRAVIS_RETRY=travis_retry; fi' - export TOX_TESTENV_PASSENV=TRAVIS # Only build pushes to the master branch, PRs, and branches beginning with @@ -16,6 +19,9 @@ before_script: # is a cap of on the number of simultaneous runs. branches: only: + # apache-parser-v2 is a temporary branch for doing work related to + # rewriting the parser in the Apache plugin. + - apache-parser-v2 - master - /^\d+\.\d+\.x$/ - /^test-.*$/ @@ -24,47 +30,43 @@ branches: not-on-master: ¬-on-master if: NOT (type = push AND branch = master) -# Jobs for the extended test suite are executed for cron jobs and pushes on non-master branches. +# Jobs for the extended test suite are executed for cron jobs and pushes to +# non-development branches. See the explanation for apache-parser-v2 above. extended-test-suite: &extended-test-suite - if: type = cron OR (type = push AND branch != master) + if: type = cron OR (type = push AND branch NOT IN (apache-parser-v2, master)) matrix: include: # Main test suite - python: "2.7" env: ACME_SERVER=pebble TOXENV=integration - sudo: required - services: docker <<: *not-on-master # This job is always executed, including on master - python: "2.7" env: TOXENV=py27-cover FYI="py27 tests + code coverage" - - python: "2.7" + - python: "3.7" env: TOXENV=lint <<: *not-on-master - - python: "3.4" - env: TOXENV=mypy - <<: *not-on-master - python: "3.5" env: TOXENV=mypy <<: *not-on-master - python: "2.7" + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest' - sudo: required - services: docker <<: *not-on-master - python: "3.4" env: TOXENV=py34 - sudo: required - services: docker <<: *not-on-master - python: "3.7" - dist: xenial env: TOXENV=py37 - sudo: required - services: docker + <<: *not-on-master + - python: "3.8" + env: TOXENV=py38 <<: *not-on-master - sudo: required env: TOXENV=apache_compat @@ -73,15 +75,11 @@ matrix: addons: <<: *not-on-master - sudo: required - env: TOXENV=le_auto_trusty + env: TOXENV=le_auto_xenial services: docker - before_install: - addons: <<: *not-on-master - python: "2.7" env: TOXENV=apacheconftest-with-pebble - sudo: required - services: docker <<: *not-on-master - python: "2.7" env: TOXENV=nginxroundtrip @@ -117,7 +115,6 @@ matrix: - secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw=" <<: *extended-test-suite - python: "3.7" - dist: xenial env: TOXENV=py37 CERTBOT_NO_PIN=1 <<: *extended-test-suite - python: "2.7" @@ -130,29 +127,39 @@ matrix: sudo: required services: docker <<: *extended-test-suite - - python: "2.7" - env: TOXENV=py27-certbot-oldest - <<: *extended-test-suite - - python: "2.7" - env: TOXENV=py27-nginx-oldest - <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v2 TOXENV=integration-certbot-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v1 TOXENV=integration-nginx-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite - python: "2.7" env: ACME_SERVER=boulder-v2 TOXENV=integration-nginx-oldest + # Ubuntu Trusty or older must be used because the oldest version of + # cryptography we support cannot be compiled against the version of + # OpenSSL in Xenial or newer. + dist: trusty sudo: required services: docker <<: *extended-test-suite @@ -166,9 +173,11 @@ matrix: env: TOXENV=py36 <<: *extended-test-suite - python: "3.7" - dist: xenial env: TOXENV=py37 <<: *extended-test-suite + - python: "3.8" + env: TOXENV=py38 + <<: *extended-test-suite - python: "3.4" env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required @@ -200,20 +209,20 @@ matrix: services: docker <<: *extended-test-suite - python: "3.7" - dist: xenial env: ACME_SERVER=boulder-v1 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - python: "3.7" - dist: xenial env: ACME_SERVER=boulder-v2 TOXENV=integration sudo: required services: docker <<: *extended-test-suite - - sudo: required - env: TOXENV=le_auto_xenial - services: docker + - python: "3.8" + env: ACME_SERVER=boulder-v1 TOXENV=integration + <<: *extended-test-suite + - python: "3.8" + env: ACME_SERVER=boulder-v2 TOXENV=integration <<: *extended-test-suite - sudo: required env: TOXENV=le_auto_jessie @@ -263,7 +272,6 @@ addons: apt: packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder. - python-dev - - python-virtualenv - gcc - libaugeas0 - libssl-dev @@ -273,8 +281,17 @@ addons: - nginx-light - openssl -install: "$(command -v pip || command -v pip3) install codecov tox" -script: tox +# tools/pip_install.py is used to pin packages to a known working version +# except in tests where the environment variable CERTBOT_NO_PIN is set. +# virtualenv is listed here explicitly to make sure it is upgraded when +# CERTBOT_NO_PIN is set to work around failures we've seen when using an older +# version of virtualenv. +install: 'tools/pip_install.py -U codecov 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' diff --git a/AUTHORS.md b/AUTHORS.md index 340a0a94b..d24c5be1d 100644 --- a/AUTHORS.md +++ b/AUTHORS.md @@ -15,8 +15,10 @@ Authors * [Alex Gaynor](https://github.com/alex) * [Alex Halderman](https://github.com/jhalderm) * [Alex Jordan](https://github.com/strugee) +* [Alex Zorin](https://github.com/alexzorin) * [Amjad Mashaal](https://github.com/TheNavigat) * [Andrew Murray](https://github.com/radarhere) +* [Andrzej Górski](https://github.com/andrzej3393) * [Anselm Levskaya](https://github.com/levskaya) * [Antoine Jacoutot](https://github.com/ajacoutot) * [asaph](https://github.com/asaph) @@ -126,6 +128,7 @@ Authors * [Joubin Jabbari](https://github.com/joubin) * [Juho Juopperi](https://github.com/jkjuopperi) * [Kane York](https://github.com/riking) +* [Kenichi Maehashi](https://github.com/kmaehashi) * [Kenneth Skovhede](https://github.com/kenkendk) * [Kevin Burke](https://github.com/kevinburke) * [Kevin London](https://github.com/kevinlondon) @@ -161,8 +164,10 @@ Authors * [Michael Schumacher](https://github.com/schumaml) * [Michael Strache](https://github.com/Jarodiv) * [Michael Sverdlin](https://github.com/sveder) +* [Michael Watters](https://github.com/blackknight36) * [Michal Moravec](https://github.com/https://github.com/Majkl578) * [Michal Papis](https://github.com/mpapis) +* [Mickaël Schoentgen](https://github.com/BoboTiG) * [Minn Soe](https://github.com/MinnSoe) * [Min RK](https://github.com/minrk) * [Miquel Ruiz](https://github.com/miquelruiz) @@ -226,6 +231,7 @@ Authors * [Stavros Korokithakis](https://github.com/skorokithakis) * [Stefan Weil](https://github.com/stweil) * [Steve Desmond](https://github.com/stevedesmond-ca) +* [sydneyli](https://github.com/sydneyli) * [Tan Jay Jun](https://github.com/jayjun) * [Tapple Gao](https://github.com/tapple) * [Telepenin Nikolay](https://github.com/telepenin) diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 4dfebb376..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,1692 +0,0 @@ -# Certbot change log - -Certbot adheres to [Semantic Versioning](https://semver.org/). - -## 0.36.0 - master - -### Added - -* Turn off session tickets for nginx plugin by default -* Added missing error types from RFC8555 to acme - -### Changed - -* Update the 'manage your account' help to be more generic. -* The error message when Certbot's Apache plugin is unable to modify your - Apache configuration has been improved. - -### Fixed - -* - -More details about these changes can be found on our GitHub repo. - -## 0.35.1 - 2019-06-10 - -### Fixed - -* Support for specifying an authoritative base domain in our dns-rfc2136 plugin - has been removed. This feature was added in our last release but had a bug - which caused the plugin to fail so the feature has been removed until it can - be added properly. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* certbot-dns-rfc2136 - -More details about these changes can be found on our GitHub repo. - -## 0.35.0 - 2019-06-05 - -### Added - -* dns_rfc2136 plugin now supports explicitly specifing an authorative - base domain for cases when the automatic method does not work (e.g. - Split horizon DNS) - -### Changed - -* - -### Fixed - -* Renewal parameter `webroot_path` is always saved, avoiding some regressions - when `webroot` authenticator plugin is invoked with no challenge to perform. -* Certbot now accepts OCSP responses when an explicit authorized - responder, different from the issuer, is used to sign OCSP - responses. -* Scripts in Certbot hook directories are no longer executed when their - filenames end in a tilde. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* certbot -* certbot-dns-rfc2136 - -More details about these changes can be found on our GitHub repo. - -## 0.34.2 - 2019-05-07 - -### Fixed - -* certbot-auto no longer writes a check_permissions.py script at the root - of the filesystem. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.34.1 - 2019-05-06 - -### Fixed - -* certbot-auto no longer prints a blank line when there are no permissions - problems. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.34.0 - 2019-05-01 - -### Changed - -* Apache plugin now tries to restart httpd on Fedora using systemctl if a - configuration test error is detected. This has to be done due to the way - Fedora now generates the self signed certificate files upon first - restart. -* Updated Certbot and its plugins to improve the handling of file system permissions - on Windows as a step towards adding proper Windows support to Certbot. -* Updated urllib3 to 1.24.2 in certbot-auto. -* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response - with a `keyAuthorization` if sending the response without this field caused a - `malformed` error to be received from the ACME server. -* Linode DNS plugin now supports api keys created from their new panel - at [cloud.linode.com](https://cloud.linode.com) - -### Fixed - -* Fixed Google DNS Challenge issues when private zones exist -* Adding a warning noting that future versions of Certbot will automatically configure the - webserver so that all requests redirect to secure HTTPS access. You can control this - behavior and disable this warning with the --redirect and --no-redirect flags. -* certbot-auto now prints warnings when run as root with insecure file system - permissions. If you see these messages, you should fix the problem by - following the instructions at - https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, - however, these warnings can be disabled as necessary with the flag - --no-permissions-check. -* `acme` module uses now a POST-as-GET request to retrieve the registration - from an ACME v2 server -* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to - all uppercase letters before validating. This makes the value in the config case - insensitive. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-dns-cloudflare -* certbot-dns-cloudxns -* certbot-dns-digitalocean -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-google -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-rfc2136 -* certbot-dns-route53 -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.33.1 - 2019-04-04 - -### Fixed - -* A bug causing certbot-auto to print warnings or crash on some RHEL based - systems has been resolved. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -changes in this release were to certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.33.0 - 2019-04-03 - -### Added - -* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation - path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. -* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces - `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while - setting up a new SSL vhost. By default the HTTPS port is 443. - -### Changed - -* Support for TLS-SNI-01 has been removed from all official Certbot plugins. -* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone` - modules are deprecated and will be removed soon. -* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will - generate a deprecation warning if used, and will be removed soon. -* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, - will generate a deprecation warning if used, and will be removed soon. -* CLI flag `--standalone-supported-challenges` has been removed. - -### Fixed - -* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 - is installed. We fixed a bug in Certbot causing it to interpret timestamps in - the OCSP response as being in the local timezone rather than UTC. -* Issue causing the default CentOS 6 TLS configuration to ignore some of the - HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main - http.conf for this environment where possible. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.32.0 - 2019-03-06 - -### Added - -* If possible, Certbot uses built-in support for OCSP from recent cryptography - versions instead of the OpenSSL binary: as a consequence Certbot does not need - the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. - -### Changed - -* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the - warnings described at https://github.com/certbot/josepy/issues/13. -* Apache plugin now respects CERTBOT_DOCS environment variable when adding - command line defaults. -* The running of manual plugin hooks is now always included in Certbot's log - output. -* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. -* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as - specified in the ACME protocol, to indicate when the next polling should occur. Certbot now - reads this header if set and respect its value. -* The `acme` module avoids sending the `keyAuthorization` field in the JWS - payload when responding to a challenge as the field is not included in the - current ACME protocol. To ease the migration path for ACME CA servers, - Certbot and its `acme` module will first try the request without the - `keyAuthorization` field but will temporarily retry the request with the - field included if a `malformed` error is received. This fallback will be - removed in version 0.34.0. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-nginx - -More details about these changes can be found on our GitHub repo. - -## 0.31.0 - 2019-02-07 - -### Added - -* Avoid reprocessing challenges that are already validated - when a certificate is issued. -* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges - with the `acme` module. - -### Changed - -* Certbot's official Docker images are now based on Alpine Linux 3.9 rather - than 3.7. The new version comes with OpenSSL 1.1.1. -* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support - on 2.x branch is maintained). -* Apache plugin now attempts to configure all VirtualHosts matching requested - domain name instead of only a single one when answering the HTTP-01 challenge. - -### Fixed - -* Fixed accessing josepy contents through acme.jose when the full acme.jose - path is used. -* Clarify behavior for deleting certs as part of revocation. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-dns-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-sakuracloud - -More details about these changes can be found on our GitHub repo. - -## 0.30.2 - 2019-01-25 - -### Fixed - -* Update the version of setuptools pinned in certbot-auto to 40.6.3 to - solve installation problems on newer OSes. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, this -release only affects certbot-auto. - -More details about these changes can be found on our GitHub repo. - -## 0.30.1 - 2019-01-24 - -### Fixed - -* Always download the pinned version of pip in pipstrap to address breakages -* Rename old,default.conf to old-and-default.conf to address commas in filenames - breaking recent versions of pip. -* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages - from venv downloading the latest pip - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* certbot-apache - -More details about these changes can be found on our GitHub repo. - -## 0.30.0 - 2019-01-02 - -### Added - -* Added the `update_account` subcommand for account management commands. - -### Changed - -* Copied account management functionality from the `register` subcommand - to the `update_account` subcommand. -* Marked usage `register --update-registration` for deprecation and - removal in a future release. - -### Fixed - -* Older modules in the josepy library can now be accessed through acme.jose - like it could in previous versions of acme. This is only done to preserve - backwards compatibility and support for doing this with new modules in josepy - will not be added. Users of the acme library should switch to using josepy - directly if they haven't done so already. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme - -More details about these changes can be found on our GitHub repo. - -## 0.29.1 - 2018-12-05 - -### Added - -* - -### Changed - -* - -### Fixed - -* The default work and log directories have been changed back to - /var/lib/letsencrypt and /var/log/letsencrypt respectively. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* certbot - -More details about these changes can be found on our GitHub repo. - -## 0.29.0 - 2018-12-05 - -### Added - -* Noninteractive renewals with `certbot renew` (those not started from a - terminal) now randomly sleep 1-480 seconds before beginning work in - order to spread out load spikes on the server side. -* Added External Account Binding support in cli and acme library. - Command line arguments --eab-kid and --eab-hmac-key added. - -### Changed - -* Private key permissioning changes: Renewal preserves existing group mode - & gid of previous private key material. Private keys for new - lineages (i.e. new certs, not renewed) default to 0o600. - -### Fixed - -* Update code and dependencies to clean up Resource and Deprecation Warnings. -* Only depend on imgconverter extension for Sphinx >= 1.6 - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-dns-cloudflare -* certbot-dns-digitalocean -* certbot-dns-google -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/62?closed=1 - -## 0.28.0 - 2018-11-7 - -### Added - -* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`. -* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory. - -### Changed - -* Removed documentation mentions of `#letsencrypt` IRC on Freenode. -* Write README to the base of (config-dir)/live directory -* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges. -* Warn when using deprecated acme.challenges.TLSSNI01 -* Log warning about TLS-SNI deprecation in Certbot -* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins -* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies -* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. - -### Fixed - -* Match Nginx parser update in allowing variable names to start with `${`. -* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first -* Correct OVH integration tests on machines without internet access. -* Stop caching the results of ipv6_info in http01.py -* Test fix for Route53 plugin to prevent boto3 making outgoing connections. -* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors. -* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and - Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-dns-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-ovh -* certbot-dns-route53 -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/59?closed=1 - -## 0.27.1 - 2018-09-06 - -### Fixed - -* Fixed parameter name in OpenSUSE overrides for default parameters in the - Apache plugin. Certbot on OpenSUSE works again. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* certbot-apache - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/60?closed=1 - -## 0.27.0 - 2018-09-05 - -### Added - -* The Apache plugin now accepts the parameter --apache-ctl which can be - used to configure the path to the Apache control script. - -### Changed - -* When using `acme.client.ClientV2` (or - `acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a - newer version of the ACME protocol), an `acme.errors.ConflictError` will be - raised if you try to create an ACME account with a key that has already been - used. Previously, a JSON parsing error was raised in this scenario when using - the library with Let's Encrypt's ACMEv2 endpoint. - -### Fixed - -* When Apache is not installed, Certbot's Apache plugin no longer prints - messages about being unable to find apachectl to the terminal when the plugin - is not selected. -* If you're using the Apache plugin with the --apache-vhost-root flag set to a - directory containing a disabled virtual host for the domain you're requesting - a certificate for, the virtual host will now be temporarily enabled if - necessary to pass the HTTP challenge. -* The documentation for the Certbot package can now be built using Sphinx 1.6+. -* You can now call `query_registration` without having to first call - `new_account` on `acme.client.ClientV2` objects. -* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`. -* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura - Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -package with changes other than its version number was: - -* acme -* certbot -* certbot-apache -* certbot-dns-ovh -* certbot-dns-sakuracloud - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/57?closed=1 - -## 0.26.1 - 2018-07-17 - -### Fixed - -* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs. - -Despite us having broken lockstep, we are continuing to release new versions of all Certbot components during releases for the time being, however, the only package with changes other than its version number was: - -* certbot - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/58?closed=1 - -## 0.26.0 - 2018-07-11 - -### Added - -* A new security enhancement which we're calling AutoHSTS has been added to - Certbot's Apache plugin. This enhancement configures your webserver to send a - HTTP Strict Transport Security header with a low max-age value that is slowly - increased over time. The max-age value is not increased to a large value - until you've successfully managed to renew your certificate. This enhancement - can be requested with the --auto-hsts flag. -* New official DNS plugins have been created for Gehirn Infrastracture Service, - Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub - page at https://hub.docker.com/u/certbot and on PyPI. -* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on - Let's Encrypt's ACMEv2 endpoint has been added. -* Certbot and its components now support Python 3.7. -* Certbot's install subcommand now allows you to interactively choose which - certificate to install from the list of certificates managed by Certbot. -* Certbot now accepts the flag `--no-autorenew` which causes any obtained - certificates to not be automatically renewed when it approaches expiration. -* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme - library. - -### Changed - -* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2 - endpoint. By default, this server will now be used for both new certificate - lineages and renewals. -* The Nginx plugin is no longer marked labeled as an "Alpha" version. -* The `prepare` method of Certbot's plugins is no longer called before running - "Updater" enhancements that are run on every invocation of `certbot renew`. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with functional changes were: - -* acme -* certbot -* certbot-apache -* certbot-dns-gehirn -* certbot-dns-linode -* certbot-dns-ovh -* certbot-dns-sakuracloud -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/55?closed=1 - -## 0.25.1 - 2018-06-13 - -### Fixed - -* TLS-ALPN-01 support has been removed from our acme library. Using our current - dependencies, we are unable to provide a correct implementation of this - challenge so we decided to remove it from the library until we can provide - proper support. -* Issues causing test failures when running the tests in the acme package with - pytest<3.0 has been resolved. -* certbot-nginx now correctly depends on acme>=0.25.0. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with changes other than their version number were: - -* acme -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/56?closed=1 - -## 0.25.0 - 2018-06-06 - -### Added - -* Support for the ready status type was added to acme. Without this change, - Certbot and acme users will begin encountering errors when using Let's - Encrypt's ACMEv2 API starting on June 19th for the staging environment and - July 5th for production. See - https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more - information. -* Certbot now accepts the flag --reuse-key which will cause the same key to be - used in the certificate when the lineage is renewed rather than generating a - new key. -* You can now add multiple email addresses to your ACME account with Certbot by - providing a comma separated list of emails to the --email flag. -* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme. - For more information, see - https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1. -* acme now supports specifying the source address to bind to when sending - outgoing connections. You still cannot specify this address using Certbot. -* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't - already have an account registered at that server URL, Certbot will - automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint - if it exists. -* Interfaces were added to Certbot allowing plugins to be called at additional - points. The `GenericUpdater` interface allows plugins to perform actions - every time `certbot renew` is run, regardless of whether any certificates are - due for renewal, and the `RenewDeployer` interface allows plugins to perform - actions when a certificate is renewed. See `certbot.interfaces` for more - information. - -### Changed - -* When running Certbot with --dry-run and you don't already have a staging - account, the created account does not contain an email address even if one - was provided to avoid expiration emails from Let's Encrypt's staging server. -* certbot-nginx does a better job of automatically detecting the location of - Nginx's configuration files when run on BSD based systems. -* acme now requires and uses pytest when running tests with setuptools with - `python setup.py test`. -* `certbot config_changes` no longer waits for user input before exiting. - -### Fixed - -* Misleading log output that caused users to think that Certbot's standalone - plugin failed to bind to a port when performing a challenge has been - corrected. -* An issue where certbot-nginx would fail to enable HSTS if the server block - already had an `add_header` directive has been resolved. -* certbot-nginx now does a better job detecting the server block to base the - configuration for TLS-SNI challenges on. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with functional changes were: - -* acme -* certbot -* certbot-apache -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/54?closed=1 - -## 0.24.0 - 2018-05-02 - -### Added - -* certbot now has an enhance subcommand which allows you to configure security - enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without - reinstalling a certificate. -* certbot-dns-rfc2136 now allows the user to specify the port to use to reach - the DNS server in its credentials file. -* acme now parses the wildcard field included in authorizations so it can be - used by users of the library. - -### Changed - -* certbot-dns-route53 used to wait for each DNS update to propagate before - sending the next one, but now it sends all updates before waiting which - speeds up issuance for multiple domains dramatically. -* Certbot's official Docker images are now based on Alpine Linux 3.7 rather - than 3.4 because 3.4 has reached its end-of-life. -* We've doubled the time Certbot will spend polling authorizations before - timing out. -* The level of the message logged when Certbot is being used with - non-standard paths warning that crontabs for renewal included in Certbot - packages from OS package managers may not work has been reduced. This stops - the message from being written to stderr every time `certbot renew` runs. - -### Fixed - -* certbot-auto now works with Python 3.6. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with changes other than their version number were: - -* acme -* certbot -* certbot-apache -* certbot-dns-digitalocean (only style improvements to tests) -* certbot-dns-rfc2136 - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/52?closed=1 - -## 0.23.0 - 2018-04-04 - -### Added - -* Support for OpenResty was added to the Nginx plugin. - -### Changed - -* The timestamps in Certbot's logfiles now use the system's local time zone - rather than UTC. -* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able - to create and delete multiple TXT records on a single domain. -* certbot-dns-google's test suite now works without an internet connection. - -### Fixed - -* Removed a small window that if during which an error occurred, Certbot - wouldn't clean up performed challenges. -* The parameters `default` and `ipv6only` are now removed from `listen` - directives when creating a new server block in the Nginx plugin. -* `server_name` directives enclosed in quotation marks in Nginx are now properly - supported. -* Resolved an issue preventing the Apache plugin from starting Apache when it's - not currently running on RHEL and Gentoo based systems. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with changes other than their version number were: - -* certbot -* certbot-apache -* certbot-dns-cloudxns -* certbot-dns-dnsimple -* certbot-dns-dnsmadeeasy -* certbot-dns-google -* certbot-dns-luadns -* certbot-dns-nsone -* certbot-dns-rfc2136 -* certbot-nginx - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/50?closed=1 - -## 0.22.2 - 2018-03-19 - -### Fixed - -* A type error introduced in 0.22.1 that would occur during challenge cleanup - when a Certbot plugin raises an exception while trying to complete the - challenge was fixed. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with changes other than their version number were: - -* certbot - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/53?closed=1 - -## 0.22.1 - 2018-03-19 - -### Changed - -* The ACME server used with Certbot's --dry-run and --staging flags is now - Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2 - features with these flags. - -### Fixed - -* The HTTP Content-Type header is now set to the correct value during - certificate revocation with new versions of the ACME protocol. -* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank - line to the top of chain.pem and between the certificates in fullchain.pem - for each lineage. These blank lines have been removed. -* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to - work. -* Fixed a regression in acme.client.Client that caused the class to not work - when it was initialized without a ClientNetwork which is done by some of the - other projects using our ACME library. - -Despite us having broken lockstep, we are continuing to release new versions of -all Certbot components during releases for the time being, however, the only -packages with changes other than their version number were: - -* acme -* certbot - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/51?closed=1 - -## 0.22.0 - 2018-03-07 - -### Added - -* Support for obtaining wildcard certificates and a newer version of the ACME - protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2 - endpoint was added to Certbot and its ACME library. Certbot still works with - older ACME versions and will automatically change the version of the protocol - used based on the version the ACME CA implements. -* The Apache and Nginx plugins are now able to automatically install a wildcard - certificate to multiple virtual hosts that you select from your server - configuration. -* The `certbot install` command now accepts the `--cert-name` flag for - selecting a certificate. -* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library - which automatically handles most of the differences between new and old ACME - versions. `acme.client.ClientV2` is also available for people who only want - to support one version of the protocol or want to handle the differences - between versions themselves. -* certbot-auto now supports the flag --install-only which has the script - install Certbot and its dependencies and exit without invoking Certbot. -* Support for issuing a single certificate for a wildcard and base domain was - added to our Google Cloud DNS plugin. To do this, we now require your API - credentials have additional permissions, however, your credentials will - already have these permissions unless you defined a custom role with fewer - permissions than the standard DNS administrator role provided by Google. - These permissions are also only needed for the case described above so it - will continue to work for existing users. For more information about the - permissions changes, see the documentation in the plugin. - -### Changed - -* We have broken lockstep between our ACME library, Certbot, and its plugins. - This means that the different components do not need to be the same version - to work together like they did previously. This makes packaging easier - because not every piece of Certbot needs to be repackaged to ship a change to - a subset of its components. -* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot, - Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL - 6 based system, it will walk you through the process of installing Certbot - with Python 3 and refuse to upgrade to a newer version of Certbot until you - have done so. -* Certbot's components now work with older versions of setuptools to simplify - packaging for EPEL 7. - -### Fixed - -* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives - has been resolved. -* A problem where Certbot's Apache plugin would add redundant include - directives for the TLS configuration managed by Certbot has been fixed. -* Certbot's webroot plugin now properly deletes any directories it creates. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/48?closed=1 - -## 0.21.1 - 2018-01-25 - -### Fixed - -* When creating an HTTP to HTTPS redirect in Nginx, we now ensure the Host - header of the request is set to an expected value before redirecting users to - the domain found in the header. The previous way Certbot configured Nginx - redirects was a potential security issue which you can read more about at - https://community.letsencrypt.org/t/security-issue-with-redirects-added-by-certbots-nginx-plugin/51493. -* Fixed a problem where Certbot's Apache plugin could fail HTTP-01 challenges - if basic authentication is configured for the domain you request a - certificate for. -* certbot-auto --no-bootstrap now properly tries to use Python 3.4 on RHEL 6 - based systems rather than Python 2.6. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/49?closed=1 - -## 0.21.0 - 2018-01-17 - -### Added - -* Support for the HTTP-01 challenge type was added to our Apache and Nginx - plugins. For those not aware, Let's Encrypt disabled the TLS-SNI-01 challenge - type which was what was previously being used by our Apache and Nginx plugins - last week due to a security issue. For more information about Let's Encrypt's - change, click - [here](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188). - Our Apache and Nginx plugins will automatically switch to use HTTP-01 so no - changes need to be made to your Certbot configuration, however, you should - make sure your server is accessible on port 80 and isn't behind an external - proxy doing things like redirecting all traffic from HTTP to HTTPS. HTTP to - HTTPS redirects inside Apache and Nginx are fine. -* IPv6 support was added to the Nginx plugin. -* Support for automatically creating server blocks based on the default server - block was added to the Nginx plugin. -* The flags --delete-after-revoke and --no-delete-after-revoke were added - allowing users to control whether the revoke subcommand also deletes the - certificates it is revoking. - -### Changed - -* We deprecated support for Python 2.6 and Python 3.3 in Certbot and its ACME - library. Support for these versions of Python will be removed in the next - major release of Certbot. If you are using certbot-auto on a RHEL 6 based - system, it will guide you through the process of installing Python 3. -* We split our implementation of JOSE (Javascript Object Signing and - Encryption) out of our ACME library and into a separate package named josepy. - This package is available on [PyPI](https://pypi.python.org/pypi/josepy) and - on [GitHub](https://github.com/certbot/josepy). -* We updated the ciphersuites used in Apache to the new [values recommended by - Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29). - The major change here is adding ChaCha20 to the list of supported - ciphersuites. - -### Fixed - -* An issue with our Apache plugin on Gentoo due to differences in their - apache2ctl command have been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/47?closed=1 - -## 0.20.0 - 2017-12-06 - -### Added - -* Certbot's ACME library now recognizes URL fields in challenge objects in - preparation for Let's Encrypt's new ACME endpoint. The value is still - accessible in our ACME library through the name "uri". - -### Changed - -* The Apache plugin now parses some distro specific Apache configuration files - on non-Debian systems allowing it to get a clearer picture on the running - configuration. Internally, these changes were structured so that external - contributors can easily write patches to make the plugin work in new Apache - configurations. -* Certbot better reports network failures by removing information about - connection retries from the error output. -* An unnecessary question when using Certbot's webroot plugin interactively has - been removed. - -### Fixed - -* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was - unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a - redirect for multiple domains. -* Problems where the Apache plugin was failing to find directives and - duplicating existing directives on openSUSE have been resolved. -* An issue running the test shipped with Certbot and some our DNS plugins with - older versions of mock have been resolved. -* On some systems, users reported strangely interleaved output depending on - when stdout and stderr were flushed. This problem was resolved by having - Certbot regularly flush these streams. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/44?closed=1 - -## 0.19.0 - 2017-10-04 - -### Added - -* Certbot now has renewal hook directories where executable files can be placed - for Certbot to run with the renew subcommand. Pre-hooks, deploy-hooks, and - post-hooks can be specified in the renewal-hooks/pre, renewal-hooks/deploy, - and renewal-hooks/post directories respectively in Certbot's configuration - directory (which is /etc/letsencrypt by default). Certbot will automatically - create these directories when it is run if they do not already exist. -* After revoking a certificate with the revoke subcommand, Certbot will offer - to delete the lineage associated with the certificate. When Certbot is run - with --non-interactive, it will automatically try to delete the associated - lineage. -* When using Certbot's Google Cloud DNS plugin on Google Compute Engine, you no - longer have to provide a credential file to Certbot if you have configured - sufficient permissions for the instance which Certbot can automatically - obtain using Google's metadata service. - -### Changed - -* When deleting certificates interactively using the delete subcommand, Certbot - will now allow you to select multiple lineages to be deleted at once. -* Certbot's Apache plugin no longer always parses Apache's sites-available on - Debian based systems and instead only parses virtual hosts included in your - Apache configuration. You can provide an additional directory for Certbot to - parse using the command line flag --apache-vhost-root. - -### Fixed - -* The plugins subcommand can now be run without root access. -* certbot-auto now includes a timeout when updating itself so it no longer - hangs indefinitely when it is unable to connect to the external server. -* An issue where Certbot's Apache plugin would sometimes fail to deploy a - certificate on Debian based systems if mod_ssl wasn't already enabled has - been resolved. -* A bug in our Docker image where the certificates subcommand could not report - if certificates maintained by Certbot had been revoked has been fixed. -* Certbot's RFC 2136 DNS plugin (for use with software like BIND) now properly - performs DNS challenges when the domain being verified contains a CNAME - record. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/43?closed=1 - -## 0.18.2 - 2017-09-20 - -### Fixed - -* An issue where Certbot's ACME module would raise an AttributeError trying to - create self-signed certificates when used with pyOpenSSL 17.3.0 has been - resolved. For Certbot users with this version of pyOpenSSL, this caused - Certbot to crash when performing a TLS SNI challenge or when the Nginx plugin - tried to create an SSL server block. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/46?closed=1 - -## 0.18.1 - 2017-09-08 - -### Fixed - -* If certbot-auto was running as an unprivileged user and it upgraded from - 0.17.0 to 0.18.0, it would crash with a permissions error and would need to - be run again to successfully complete the upgrade. This has been fixed and - certbot-auto should upgrade cleanly to 0.18.1. -* Certbot usually uses "certbot-auto" or "letsencrypt-auto" in error messages - and the User-Agent string instead of "certbot" when you are using one of - these wrapper scripts. Proper detection of this was broken with Certbot's new - installation path in /opt in 0.18.0 but this problem has been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/45?closed=1 - -## 0.18.0 - 2017-09-06 - -### Added - -* The Nginx plugin now configures Nginx to use 2048-bit Diffie-Hellman - parameters. Java 6 clients do not support Diffie-Hellman parameters larger - than 1024 bits, so if you need to support these clients you will need to - manually modify your Nginx configuration after using the Nginx installer. - -### Changed - -* certbot-auto now installs Certbot in directories under `/opt/eff.org`. If you - had an existing installation from certbot-auto, a symlink is created to the - new directory. You can configure certbot-auto to use a different path by - setting the environment variable VENV_PATH. -* The Nginx plugin can now be selected in Certbot's interactive output. -* Output verbosity of renewal failures when running with `--quiet` has been - reduced. -* The default revocation reason shown in Certbot help output now is a human - readable string instead of a numerical code. -* Plugin selection is now included in normal terminal output. - -### Fixed - -* A newer version of ConfigArgParse is now installed when using certbot-auto - causing values set to false in a Certbot INI configuration file to be handled - intuitively. Setting a boolean command line flag to false is equivalent to - not including it in the configuration file at all. -* New naming conventions preventing certbot-auto from installing OS - dependencies on Fedora 26 have been resolved. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/42?closed=1 - -## 0.17.0 - 2017-08-02 - -### Added - -* Support in our nginx plugin for modifying SSL server blocks that do - not contain certificate or key directives. -* A `--max-log-backups` flag to allow users to configure or even completely - disable Certbot's built in log rotation. -* A `--user-agent-comment` flag to allow people who build tools around Certbot - to differentiate their user agent string by adding a comment to its default - value. - -### Changed - -* Due to some awesome work by - [cryptography project](https://github.com/pyca/cryptography), compilation can - now be avoided on most systems when using certbot-auto. This eliminates many - problems people have had in the past such as running out of memory, having - invalid headers/libraries, and changes to the OS packages on their system - after compilation breaking Certbot. -* The `--renew-hook` flag has been hidden in favor of `--deploy-hook`. This new - flag works exactly the same way except it is always run when a certificate is - issued rather than just when it is renewed. -* We have started printing deprecation warnings in certbot-auto for - experimentally supported systems with OS packages available. -* A certificate lineage's name is included in error messages during renewal. - -### Fixed - -* Encoding errors that could occur when parsing error messages from the ACME - server containing Unicode have been resolved. -* certbot-auto no longer prints misleading messages about there being a newer - pip version available when installation fails. -* Certbot's ACME library now properly extracts domains from critical SAN - extensions. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.17.0+is%3Aclosed - -## 0.16.0 - 2017-07-05 - -### Added - -* A plugin for performing DNS challenges using dynamic DNS updates as defined - in RFC 2316. This plugin is packaged separately from Certbot and is available - at https://pypi.python.org/pypi/certbot-dns-rfc2136. It supports Python 2.6, - 2.7, and 3.3+. At this time, there isn't a good way to install this plugin - when using certbot-auto, but this should change in the near future. -* Plugins for performing DNS challenges for the providers - [DNS Made Easy](https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy) and - [LuaDNS](https://pypi.python.org/pypi/certbot-dns-luadns). These plugins are - packaged separately from Certbot and support Python 2.7 and 3.3+. Currently, - there isn't a good way to install these plugins when using certbot-auto, - but that should change soon. -* Support for performing TLS-SNI-01 challenges when using the manual plugin. -* Automatic detection of Arch Linux in the Apache plugin providing better - default settings for the plugin. - -### Changed - -* The text of the interactive question about whether a redirect from HTTP to - HTTPS should be added by Certbot has been rewritten to better explain the - choices to the user. -* Simplified HTTP challenge instructions in the manual plugin. - -### Fixed - -* Problems performing a dry run when using the Nginx plugin have been fixed. -* Resolved an issue where certbot-dns-digitalocean's test suite would sometimes - fail when ran using Python 3. -* On some systems, previous versions of certbot-auto would error out with a - message about a missing hash for setuptools. This has been fixed. -* A bug where Certbot would sometimes not print a space at the end of an - interactive prompt has been resolved. -* Nonfatal tracebacks are no longer shown in rare cases where Certbot - encounters an exception trying to close its TCP connection with the ACME - server. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.16.0+is%3Aclosed - -## 0.15.0 - 2017-06-08 - -### Added - -* Plugins for performing DNS challenges for popular providers. Like the Apache - and Nginx plugins, these plugins are packaged separately and not included in - Certbot by default. So far, we have plugins for - [Amazon Route 53](https://pypi.python.org/pypi/certbot-dns-route53), - [Cloudflare](https://pypi.python.org/pypi/certbot-dns-cloudflare), - [DigitalOcean](https://pypi.python.org/pypi/certbot-dns-digitalocean), and - [Google Cloud](https://pypi.python.org/pypi/certbot-dns-google) which all - work on Python 2.6, 2.7, and 3.3+. Additionally, we have plugins for - [CloudXNS](https://pypi.python.org/pypi/certbot-dns-cloudxns), - [DNSimple](https://pypi.python.org/pypi/certbot-dns-dnsimple), - [NS1](https://pypi.python.org/pypi/certbot-dns-nsone) which work on Python - 2.7 and 3.3+ (and not 2.6). Currently, there isn't a good way to install - these plugins when using `certbot-auto`, but that should change soon. -* IPv6 support in the standalone plugin. When performing a challenge, the - standalone plugin automatically handles listening for IPv4/IPv6 traffic based - on the configuration of your system. -* A mechanism for keeping your Apache and Nginx SSL/TLS configuration up to - date. When the Apache or Nginx plugins are used, they place SSL/TLS - configuration options in the root of Certbot's config directory - (`/etc/letsencrypt` by default). Now when a new version of these plugins run - on your system, they will automatically update the file to the newest - version if it is unmodified. If you manually modified the file, Certbot will - display a warning giving you a path to the updated file which you can use as - a reference to manually update your modified copy. -* `--http-01-address` and `--tls-sni-01-address` flags for controlling the - address Certbot listens on when using the standalone plugin. -* The command `certbot certificates` that lists certificates managed by Certbot - now performs additional validity checks to notify you if your files have - become corrupted. - -### Changed - -* Messages custom hooks print to `stdout` are now displayed by Certbot when not - running in `--quiet` mode. -* `jwk` and `alg` fields in JWS objects have been moved into the protected - header causing Certbot to more closely follow the latest version of the ACME - spec. - -### Fixed - -* Permissions on renewal configuration files are now properly preserved when - they are updated. -* A bug causing Certbot to display strange defaults in its help output when - using Python <= 2.7.4 has been fixed. -* Certbot now properly handles mixed case domain names found in custom CSRs. -* A number of poorly worded prompts and error messages. - -### Removed - -* Support for OpenSSL 1.0.0 in `certbot-auto` has been removed as we now pin a - newer version of `cryptography` which dropped support for this version. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.15.0+is%3Aclosed - -## 0.14.2 - 2017-05-25 - -### Fixed - -* Certbot 0.14.0 included a bug where Certbot would create a temporary log file -(usually in /tmp) if the program exited during argument parsing. If a user -provided -h/--help/help, --version, or an invalid command line argument, -Certbot would create this temporary log file. This was especially bothersome to -certbot-auto users as certbot-auto runs `certbot --version` internally to see -if the script needs to upgrade causing it to create at least one of these files -on every run. This problem has been resolved. - -More details about this change can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed - -## 0.14.1 - 2017-05-16 - -### Fixed - -* Certbot now works with configargparse 0.12.0. -* Issues with the Apache plugin and Augeas 1.7+ have been resolved. -* A problem where the Nginx plugin would fail to install certificates on -systems that had the plugin's SSL/TLS options file from 7+ months ago has been -fixed. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.1+is%3Aclosed - -## 0.14.0 - 2017-05-04 - -### Added - -* Python 3.3+ support for all Certbot packages. `certbot-auto` still currently -only supports Python 2, but the `acme`, `certbot`, `certbot-apache`, and -`certbot-nginx` packages on PyPI now fully support Python 2.6, 2.7, and 3.3+. -* Certbot's Apache plugin now handles multiple virtual hosts per file. -* Lockfiles to prevent multiple versions of Certbot running simultaneously. - -### Changed - -* When converting an HTTP virtual host to HTTPS in Apache, Certbot only copies -the virtual host rather than the entire contents of the file it's contained -in. -* The Nginx plugin now includes SSL/TLS directives in a separate file located -in Certbot's configuration directory rather than copying the contents of the -file into every modified `server` block. - -### Fixed - -* Ensure logging is configured before parts of Certbot attempt to log any -messages. -* Support for the `--quiet` flag in `certbot-auto`. -* Reverted a change made in a previous release to make the `acme` and `certbot` -packages always depend on `argparse`. This dependency is conditional again on -the user's Python version. -* Small bugs in the Nginx plugin such as properly handling empty `server` -blocks and setting `server_names_hash_bucket_size` during challenges. - -As always, a more complete list of changes can be found on GitHub: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.0+is%3Aclosed - -## 0.13.0 - 2017-04-06 - -### Added - -* `--debug-challenges` now pauses Certbot after setting up challenges for debugging. -* The Nginx parser can now handle all valid directives in configuration files. -* Nginx ciphersuites have changed to Mozilla Intermediate. -* `certbot-auto --no-bootstrap` provides the option to not install OS dependencies. - -### Fixed - -* `--register-unsafely-without-email` now respects `--quiet`. -* Hyphenated renewal parameters are now saved in renewal config files. -* `--dry-run` no longer persists keys and csrs. -* Certbot no longer hangs when trying to start Nginx in Arch Linux. -* Apache rewrite rules no longer double-encode characters. - -A full list of changes is available on GitHub: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.13.0%20is%3Aclosed%20 - -## 0.12.0 - 2017-03-02 - -### Added - -* Certbot now allows non-camelcase Apache VirtualHost names. -* Certbot now allows more log messages to be silenced. - -### Fixed - -* Fixed a regression around using `--cert-name` when getting new certificates - -More information about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0 - -## 0.11.1 - 2017-02-01 - -### Fixed - -* Resolved a problem where Certbot would crash while parsing command line -arguments in some cases. -* Fixed a typo. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed - -## 0.11.0 - 2017-02-01 - -### Added - -* When using the standalone plugin while running Certbot interactively -and a required port is bound by another process, Certbot will give you -the option to retry to grab the port rather than immediately exiting. -* You are now able to deactivate your account with the Let's Encrypt -server using the `unregister` subcommand. -* When revoking a certificate using the `revoke` subcommand, you now -have the option to provide the reason the certificate is being revoked -to Let's Encrypt with `--reason`. - -### Changed - -* Providing `--quiet` to `certbot-auto` now silences package manager output. - -### Removed - -* Removed the optional `dnspython` dependency in our `acme` package. -Now the library does not support client side verification of the DNS -challenge. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed - -## 0.10.2 - 2017-01-25 - -### Added - -* If Certbot receives a request with a `badNonce` error, it now -automatically retries the request. Since nonces from Let's Encrypt expire, -this helps people performing the DNS challenge with the `manual` plugin -who may have to wait an extended period of time for their DNS changes to -propagate. - -### Fixed - -* Certbot now saves the `--preferred-challenges` values for renewal. Previously -these values were discarded causing a different challenge type to be used when -renewing certs in some cases. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed - -## 0.10.1 - 2017-01-13 - -### Fixed - -* Resolve problems where when asking Certbot to update a certificate at -an existing path to include different domain names, the old names would -continue to be used. -* Fix issues successfully running our unit test suite on some systems. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed - -## 0.10.0 - 2017-01-11 - -## Added - -* Added the ability to customize and automatically complete DNS and HTTP -domain validation challenges with the manual plugin. The flags -`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided -when using the manual plugin to execute commands provided by the user to -perform and clean up challenges provided by the CA. This is best used in -complicated setups where the DNS challenge must be used or Certbot's -existing plugins cannot be used to perform HTTP challenges. For more -information on how this works, see `certbot --help manual`. -* Added a `--cert-name` flag for specifying the name to use for the -certificate in Certbot's configuration directory. Using this flag in -combination with `-d/--domains`, a user can easily request a new -certificate with different domains and save it with the name provided by -`--cert-name`. Additionally, `--cert-name` can be used to select a -certificate with the `certonly` and `run` subcommands so a full list of -domains in the certificate does not have to be provided. -* Added subcommand `certificates` for listing the certificates managed by -Certbot and their properties. -* Added the `delete` subcommand for removing certificates managed by Certbot -from the configuration directory. -* Certbot now supports requesting internationalized domain names (IDNs). -* Hooks provided to Certbot are now saved to be reused during renewal. -If you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook` -flags when obtaining a certificate, the provided commands will -automatically be saved and executed again when renewing the certificate. -A pre-hook and/or post-hook can also be given to the `certbot renew` -command either on the command line or in a [configuration -file](https://certbot.eff.org/docs/using.html#configuration-file) to run -an additional command before/after any certificate is renewed. Hooks -will only be run if a certificate is renewed. -* Support Busybox in certbot-auto. - -### Changed - -* Recategorized `-h/--help` output to improve documentation and -discoverability. - -### Removed - -* Removed the ncurses interface. This change solves problems people -were having on many systems, reduces the number of Certbot -dependencies, and simplifies our code. Certbot's only interface now is -the text interface which was available by providing `-t/--text` to -earlier versions of Certbot. - -### Fixed - -* Many small bug fixes. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed - -## 0.9.3 - 2016-10-13 - -### Added - -* The Apache plugin uses information about your OS to help determine the -layout of your Apache configuration directory. We added a patch to -ensure this code behaves the same way when testing on different systems -as the tests were failing in some cases. - -### Changed - -* Certbot adopted more conservative behavior about reporting a needed port as -unavailable when using the standalone plugin. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/27?closed=1 - -## 0.9.2 - 2016-10-12 - -### Added - -* Certbot stopped requiring that all possibly required ports are available when -using the standalone plugin. It now only verifies that the ports are available -when they are necessary. - -### Fixed - -* Certbot now verifies that our optional dependencies version matches what is -required by Certbot. -* Certnot now properly copies the `ssl on;` directives as necessary when -performing domain validation in the Nginx plugin. -* Fixed problem where symlinks were becoming files when they were -packaged, causing errors during testing and OS packaging. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/26?closed=1 - -## 0.9.1 - 2016-10-06 - -### Fixed - -* Fixed a bug that was introduced in version 0.9.0 where the command -line flag -q/--quiet wasn't respected in some cases. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/milestone/25?closed=1 - -## 0.9.0 - 2016-10-05 - -### Added - -* Added an alpha version of the Nginx plugin. This plugin fully automates the -process of obtaining and installing certificates with Nginx. -Additionally, it is able to automatically configure security -enhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use -this plugin, you must have the `certbot-nginx` package installed (which -is installed automatically when using `certbot-auto`) and provide -`--nginx` on the command line. This plugin is still in its early stages -so we recommend you use it with some caution and make sure you have a -backup of your Nginx configuration. -* Added support for the `DNS` challenge in the `acme` library and `DNS` in -Certbot's `manual` plugin. This allows you to create DNS records to -prove to Let's Encrypt you control the requested domain name. To use -this feature, include `--manual --preferred-challenges dns` on the -command line. -* Certbot now helps with enabling Extra Packages for Enterprise Linux (EPEL) on -CentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6, -the EPEL repository has to be enabled. `certbot-auto` will now prompt -users asking them if they would like the script to enable this for them -automatically. This is done without prompting users when using -`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is -included on the command line. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed - -## 0.8.1 - 2016-06-14 - -### Added - -* Certbot now preserves a certificate's common name when using `renew`. -* Certbot now saves webroot values for renewal when they are entered interactively. -* Certbot now gracefully reports that the Apache plugin isn't usable when Augeas is not installed. -* Added experimental support for Mageia has been added to `certbot-auto`. - -### Fixed - -* Fixed problems with an invalid user-agent string on OS X. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+ - -## 0.8.0 - 2016-06-02 - -### Added - -* Added the `register` subcommand which can be used to register an account -with the Let's Encrypt CA. -* You can now run `certbot register --update-registration` to -change the e-mail address associated with your registration. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+ - -## 0.7.0 - 2016-05-27 - -### Added - -* Added `--must-staple` to request certificates from Let's Encrypt -with the OCSP must staple extension. -* Certbot now automatically configures OSCP stapling for Apache. -* Certbot now allows requesting certificates for domains found in the common name -of a custom CSR. - -### Fixed - -* Fixed a number of miscellaneous bugs - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue - -## 0.6.0 - 2016-05-12 - -### Added - -* Versioned the datetime dependency in setup.py. - -### Changed - -* Renamed the client from `letsencrypt` to `certbot`. - -### Fixed - -* Fixed a small json deserialization error. -* Certbot now preserves domain order in generated CSRs. -* Fixed some minor bugs. - -More details about these changes can be found on our GitHub repo: -https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20 - -## 0.5.0 - 2016-04-05 - -### Added - -* Added the ability to use the webroot plugin interactively. -* Added the flags --pre-hook, --post-hook, and --renew-hook which can be used with -the renew subcommand to register shell commands to run in response to -renewal events. Pre-hook commands will be run before any certs are -renewed, post-hook commands will be run after any certs are renewed, -and renew-hook commands will be run after each cert is renewed. If no -certs are due for renewal, no command is run. -* Added a -q/--quiet flag which silences all output except errors. -* Added an --allow-subset-of-domains flag which can be used with the renew -command to prevent renewal failures for a subset of the requested -domains from causing the client to exit. - -### Changed - -* Certbot now uses renewal configuration files. In /etc/letsencrypt/renewal -by default, these files can be used to control what parameters are -used when renewing a specific certificate. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue - -## 0.4.2 - 2016-03-03 - -### Fixed - -* Resolved problems encountered when compiling letsencrypt -against the new OpenSSL release. -* Fixed problems encountered when using `letsencrypt renew` with configuration files -from the private beta. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2 - -## 0.4.1 - 2016-02-29 - -### Fixed - -* Fixed Apache parsing errors encountered with some configurations. -* Fixed Werkzeug dependency problems encountered on some Red Hat systems. -* Fixed bootstrapping failures when using letsencrypt-auto with --no-self-upgrade. -* Fixed problems with parsing renewal config files from private beta. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1 - -## 0.4.0 - 2016-02-10 - -### Added - -* Added the verb/subcommand `renew` which can be used to renew your existing -certificates as they approach expiration. Running `letsencrypt renew` -will examine all existing certificate lineages and determine if any are -less than 30 days from expiration. If so, the client will use the -settings provided when you previously obtained the certificate to renew -it. The subcommand finishes by printing a summary of which renewals were -successful, failed, or not yet due. -* Added a `--dry-run` flag to help with testing configuration -without affecting production rate limits. Currently supported by the -`renew` and `certonly` subcommands, providing `--dry-run` on the command -line will obtain certificates from the staging server without saving the -resulting certificates to disk. -* Added major improvements to letsencrypt-auto. This script -has been rewritten to include full support for Python 2.6, the ability -for letsencrypt-auto to update itself, and improvements to the -stability, security, and performance of the script. -* Added support for Apache 2.2 to the Apache plugin. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0 - -## 0.3.0 - 2016-01-27 - -### Added - -* Added a non-interactive mode which can be enabled by including `-n` or -`--non-interactive` on the command line. This can be used to guarantee -the client will not prompt when run automatically using cron/systemd. -* Added preparation for the new letsencrypt-auto script. Over the past -couple months, we've been working on increasing the reliability and -security of letsencrypt-auto. A number of changes landed in this -release to prepare for the new version of this script. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0 - -## 0.2.0 - 2016-01-14 - -### Added - -* Added Apache plugin support for non-Debian based systems. Support has been -added for modern Red Hat based systems such as Fedora 23, Red Hat 7, -and CentOS 7 running Apache 2.4. In theory, this plugin should be -able to be configured to run on any Unix-like OS running Apache 2.4. -* Relaxed PyOpenSSL version requirements. This adds support for systems -with PyOpenSSL versions 0.13 or 0.14. -* Improved error messages from the client. - -### Fixed - -* Resolved issues with the Apache plugin enabling an HTTP to HTTPS -redirect on some systems. - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0 - -## 0.1.1 - 2015-12-15 - -### Added - -* Added a check that avoids attempting to issue for unqualified domain names like -"localhost". - -### Fixed - -* Fixed a confusing UI path that caused some users to repeatedly renew -their certs while experimenting with the client, in some cases hitting -issuance rate limits. -* Fixed numerous Apache configuration parser problems -* Fixed --webroot permission handling for non-root users - -More details about these changes can be found on our GitHub repo: -https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1 diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 120000 index 000000000..ba7396f24 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1 @@ +certbot/CHANGELOG.md \ No newline at end of file diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index 828f5ec94..000000000 --- a/Dockerfile +++ /dev/null @@ -1,35 +0,0 @@ -FROM python:2-alpine3.9 - -ENTRYPOINT [ "certbot" ] -EXPOSE 80 443 -VOLUME /etc/letsencrypt /var/lib/letsencrypt -WORKDIR /opt/certbot - -COPY CHANGELOG.md README.rst setup.py src/ - -# Generate constraints file to pin dependency versions -COPY letsencrypt-auto-source/pieces/dependency-requirements.txt . -COPY tools /opt/certbot/tools -RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt' -RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt' - -COPY acme src/acme -COPY certbot src/certbot - -RUN apk add --no-cache --virtual .certbot-deps \ - libffi \ - libssl1.1 \ - openssl \ - ca-certificates \ - binutils -RUN apk add --no-cache --virtual .build-deps \ - gcc \ - linux-headers \ - openssl-dev \ - musl-dev \ - libffi-dev \ - && pip install -r /opt/certbot/dependency-requirements.txt \ - && pip install --no-cache-dir --no-deps \ - --editable /opt/certbot/src/acme \ - --editable /opt/certbot/src \ - && apk del .build-deps diff --git a/Dockerfile-dev b/Dockerfile-dev index 1ab56e081..ae197b1cb 100644 --- a/Dockerfile-dev +++ b/Dockerfile-dev @@ -1,21 +1,20 @@ # This Dockerfile builds an image for development. -FROM ubuntu:xenial +FROM debian:buster # Note: this only exposes the port to other docker containers. EXPOSE 80 443 WORKDIR /opt/certbot/src -# TODO: Install Apache/Nginx for plugin development. COPY . . RUN apt-get update && \ - apt-get install apache2 git nginx-light -y && \ - letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \ + apt-get install apache2 git python3-dev python3-venv gcc libaugeas0 \ + libssl-dev libffi-dev ca-certificates openssl nginx-light -y && \ apt-get clean && \ rm -rf /var/lib/apt/lists/* \ /tmp/* \ /var/tmp/* -RUN VENV_NAME="../venv" python tools/venv.py +RUN VENV_NAME="../venv3" python3 tools/venv3.py -ENV PATH /opt/certbot/venv/bin:$PATH +ENV PATH /opt/certbot/venv3/bin:$PATH diff --git a/Dockerfile-old b/Dockerfile-old deleted file mode 100644 index c52a9937b..000000000 --- a/Dockerfile-old +++ /dev/null @@ -1,75 +0,0 @@ -# https://github.com/letsencrypt/letsencrypt/pull/431#issuecomment-103659297 -# it is more likely developers will already have ubuntu:trusty rather -# than e.g. debian:jessie and image size differences are negligible -FROM ubuntu:trusty -MAINTAINER Jakub Warmuz -MAINTAINER William Budington - -# Note: this only exposes the port to other docker containers. You -# still have to bind to 443@host at runtime, as per the ACME spec. -EXPOSE 443 - -# TODO: make sure --config-dir and --work-dir cannot be changed -# through the CLI (certbot-docker wrapper that uses standalone -# authenticator and text mode only?) -VOLUME /etc/letsencrypt /var/lib/letsencrypt - -WORKDIR /opt/certbot - -# no need to mkdir anything: -# https://docs.docker.com/reference/builder/#copy -# If doesn't exist, it is created along with all missing -# directories in its path. - -ENV DEBIAN_FRONTEND=noninteractive - -COPY letsencrypt-auto-source/letsencrypt-auto /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto -RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* \ - /tmp/* \ - /var/tmp/* - -# the above is not likely to change, so by putting it further up the -# Dockerfile we make sure we cache as much as possible - - -COPY setup.py README.rst CHANGELOG.md MANIFEST.in letsencrypt-auto-source/pieces/pipstrap.py /opt/certbot/src/ - -# all above files are necessary for setup.py and venv setup, however, -# package source code directory has to be copied separately to a -# subdirectory... -# https://docs.docker.com/reference/builder/#copy: "If is a -# directory, the entire contents of the directory are copied, -# including filesystem metadata. Note: The directory itself is not -# copied, just its contents." Order again matters, three files are far -# more likely to be cached than the whole project directory - -COPY certbot /opt/certbot/src/certbot/ -COPY acme /opt/certbot/src/acme/ -COPY certbot-apache /opt/certbot/src/certbot-apache/ -COPY certbot-nginx /opt/certbot/src/certbot-nginx/ - - -RUN VIRTUALENV_NO_DOWNLOAD=1 virtualenv --no-site-packages -p python2 /opt/certbot/venv - -# PATH is set now so pipstrap upgrades the correct (v)env -ENV PATH /opt/certbot/venv/bin:$PATH -RUN /opt/certbot/venv/bin/python /opt/certbot/src/pipstrap.py && \ - /opt/certbot/venv/bin/pip install \ - -e /opt/certbot/src/acme \ - -e /opt/certbot/src \ - -e /opt/certbot/src/certbot-apache \ - -e /opt/certbot/src/certbot-nginx - -# install in editable mode (-e) to save space: it's not possible to -# "rm -rf /opt/certbot/src" (it's stays in the underlaying image); -# this might also help in debugging: you can "docker run --entrypoint -# bash" and investigate, apply patches, etc. - -# set up certbot/letsencrypt wrapper to warn people about Dockerfile changes -COPY tools/docker-warning.sh /opt/certbot/bin/certbot -RUN ln -s /opt/certbot/bin/certbot /opt/certbot/bin/letsencrypt -ENV PATH /opt/certbot/bin:$PATH - -ENTRYPOINT [ "certbot" ] diff --git a/README.rst b/README.rst deleted file mode 100644 index 5f5ea17a1..000000000 --- a/README.rst +++ /dev/null @@ -1,131 +0,0 @@ -.. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin - -Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server. - -Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment. - -How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide `_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access `_ to your web server to run Certbot. - -Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt. - -Certbot is a fully-featured, extensible client for the Let's -Encrypt CA (or any other CA that speaks the `ACME -`_ -protocol) that can automate the tasks of obtaining certificates and -configuring webservers to use them. This client runs on Unix-based operating -systems. - -To see the changes made to Certbot between versions please refer to our -`changelog `_. - -Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``, -depending on install method. Instructions on the Internet, and some pieces of the -software, may still refer to this older name. - -Contributing ------------- - -If you'd like to contribute to this project please read `Developer Guide -`_. - -This project is governed by `EFF's Public Projects Code of Conduct `_. - -.. _installation: - -How to run the client ---------------------- - -The easiest way to install and run Certbot is by visiting `certbot.eff.org`_, -where you can find the correct instructions for many web server and OS -combinations. For more information, see `Get Certbot -`_. - -.. _certbot.eff.org: https://certbot.eff.org/ - -Understanding the client in more depth --------------------------------------- - -To understand what the client is doing in detail, it's important to -understand the way it uses plugins. Please see the `explanation of -plugins `_ in -the User Guide. - -Links -===== - -.. Do not modify this comment unless you know what you're doing. tag:links-begin - -Documentation: https://certbot.eff.org/docs - -Software project: https://github.com/certbot/certbot - -Notes for developers: https://certbot.eff.org/docs/contributing.html - -Main Website: https://certbot.eff.org - -Let's Encrypt Website: https://letsencrypt.org - -Community: https://community.letsencrypt.org - -ACME spec: http://ietf-wg-acme.github.io/acme/ - -ACME working area in github: https://github.com/ietf-wg-acme/acme - -|build-status| |coverage| |docs| |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 - -.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ - :target: https://readthedocs.org/projects/letsencrypt/ - :alt: Documentation status - -.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status - :target: https://quay.io/repository/letsencrypt/letsencrypt - :alt: Docker Repository on Quay.io - -.. Do not modify this comment unless you know what you're doing. tag:links-end - -System Requirements -=================== - -See https://certbot.eff.org/docs/install.html#system-requirements. - -.. Do not modify this comment unless you know what you're doing. tag:intro-end - -.. Do not modify this comment unless you know what you're doing. tag:features-begin - -Current Features -===================== - -* Supports multiple web servers: - - - apache/2.x - - nginx/0.8.48+ - - webroot (adds files to webroot directories in order to prove control of - domains and obtain certs) - - standalone (runs its own simple webserver to prove you control a domain) - - other server software via `third party plugins `_ - -* The private key is generated locally on your system. -* Can talk to the Let's Encrypt CA or optionally to other ACME - compliant services. -* Can get domain-validated (DV) certificates. -* Can revoke certificates. -* Adjustable RSA key bit-length (2048 (default), 4096, ...). -* Can optionally install a http -> https redirect, so your site effectively - runs https only (Apache only) -* Fully automated. -* Configuration changes are logged and can be reverted. -* Supports an interactive text UI, or can be driven entirely from the - command line. -* Free and Open Source Software, made with Python. - -.. Do not modify this comment unless you know what you're doing. tag:features-end - -For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide `_. diff --git a/README.rst b/README.rst new file mode 120000 index 000000000..645fd4c78 --- /dev/null +++ b/README.rst @@ -0,0 +1 @@ +certbot/README.rst \ No newline at end of file diff --git a/acme/MANIFEST.in b/acme/MANIFEST.in index 1619bef69..de254250e 100644 --- a/acme/MANIFEST.in +++ b/acme/MANIFEST.in @@ -3,4 +3,6 @@ include README.rst include pytest.ini recursive-include docs * recursive-include examples * -recursive-include acme/testdata * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/acme/acme/__init__.py b/acme/acme/__init__.py index 20c008d64..d1679fcad 100644 --- a/acme/acme/__init__.py +++ b/acme/acme/__init__.py @@ -13,7 +13,6 @@ import warnings # # It is based on # https://github.com/requests/requests/blob/1278ecdf71a312dc2268f3bfc0aabfab3c006dcf/requests/packages.py - import josepy as jose for mod in list(sys.modules): @@ -21,30 +20,3 @@ for mod in list(sys.modules): # preserved (acme.jose.* is josepy.*) if mod == 'josepy' or mod.startswith('josepy.'): sys.modules['acme.' + mod.replace('josepy', 'jose', 1)] = sys.modules[mod] - - -# This class takes a similar approach to the cryptography project to deprecate attributes -# in public modules. See the _ModuleWithDeprecation class here: -# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 -class _TLSSNI01DeprecationModule(object): - """ - Internal class delegating to a module, and displaying warnings when - attributes related to TLS-SNI-01 are accessed. - """ - def __init__(self, module): - self.__dict__['_module'] = module - - def __getattr__(self, attr): - if 'TLSSNI01' in attr: - warnings.warn('{0} attribute is deprecated, and will be removed soon.'.format(attr), - DeprecationWarning, stacklevel=2) - return getattr(self._module, attr) - - def __setattr__(self, attr, value): # pragma: no cover - setattr(self._module, attr, value) - - def __delattr__(self, attr): # pragma: no cover - delattr(self._module, attr) - - def __dir__(self): # pragma: no cover - return ['_module'] + dir(self._module) diff --git a/acme/acme/challenges.py b/acme/acme/challenges.py index 78991608a..8a0366301 100644 --- a/acme/acme/challenges.py +++ b/acme/acme/challenges.py @@ -3,19 +3,13 @@ import abc import functools import hashlib import logging -import socket -import sys from cryptography.hazmat.primitives import hashes # type: ignore import josepy as jose -import OpenSSL import requests import six -from acme import errors -from acme import crypto_util from acme import fields -from acme import _TLSSNI01DeprecationModule logger = logging.getLogger(__name__) @@ -60,8 +54,7 @@ class UnrecognizedChallenge(Challenge): object.__setattr__(self, "jobj", jobj) def to_partial_json(self): - # pylint: disable=no-member - return self.jobj + return self.jobj # pylint: disable=no-member @classmethod def from_json(cls, jobj): @@ -119,7 +112,7 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse): :rtype: bool """ - parts = self.key_authorization.split('.') # pylint: disable=no-member + parts = self.key_authorization.split('.') if len(parts) != 2: logger.debug("Key authorization (%r) is not well formed", self.key_authorization) @@ -237,7 +230,7 @@ class DNS01Response(KeyAuthorizationChallengeResponse): return verified -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class DNS01(KeyAuthorizationChallenge): """ACME dns-01 challenge.""" response_cls = DNS01Response @@ -327,7 +320,7 @@ class HTTP01Response(KeyAuthorizationChallengeResponse): return True -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class HTTP01(KeyAuthorizationChallenge): """ACME http-01 challenge.""" response_cls = HTTP01Response @@ -367,148 +360,6 @@ class HTTP01(KeyAuthorizationChallenge): return self.key_authorization(account_key) -@ChallengeResponse.register -class TLSSNI01Response(KeyAuthorizationChallengeResponse): - """ACME tls-sni-01 challenge response.""" - typ = "tls-sni-01" - - DOMAIN_SUFFIX = b".acme.invalid" - """Domain name suffix.""" - - PORT = 443 - """Verification port as defined by the protocol. - - You can override it (e.g. for testing) by passing ``port`` to - `simple_verify`. - - """ - - @property - def z(self): # pylint: disable=invalid-name - """``z`` value used for verification. - - :rtype bytes: - - """ - return hashlib.sha256( - self.key_authorization.encode("utf-8")).hexdigest().lower().encode() - - @property - def z_domain(self): - """Domain name used for verification, generated from `z`. - - :rtype bytes: - - """ - return self.z[:32] + b'.' + self.z[32:] + self.DOMAIN_SUFFIX - - def gen_cert(self, key=None, bits=2048): - """Generate tls-sni-01 certificate. - - :param OpenSSL.crypto.PKey key: Optional private key used in - certificate generation. If not provided (``None``), then - fresh key will be generated. - :param int bits: Number of bits for newly generated key. - - :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey` - - """ - if key is None: - key = OpenSSL.crypto.PKey() - key.generate_key(OpenSSL.crypto.TYPE_RSA, bits) - return crypto_util.gen_ss_cert(key, [ - # z_domain is too big to fit into CN, hence first dummy domain - 'dummy', self.z_domain.decode()], force_san=True), key - - def probe_cert(self, domain, **kwargs): - """Probe tls-sni-01 challenge certificate. - - :param unicode domain: - - """ - # TODO: domain is not necessary if host is provided - if "host" not in kwargs: - host = socket.gethostbyname(domain) - logger.debug('%s resolved to %s', domain, host) - kwargs["host"] = host - - kwargs.setdefault("port", self.PORT) - kwargs["name"] = self.z_domain - # TODO: try different methods? - return crypto_util.probe_sni(**kwargs) - - def verify_cert(self, cert): - """Verify tls-sni-01 challenge certificate. - - :param OpensSSL.crypto.X509 cert: Challenge certificate. - - :returns: Whether the certificate was successfully verified. - :rtype: bool - - """ - # pylint: disable=protected-access - sans = crypto_util._pyopenssl_cert_or_req_san(cert) - logger.debug('Certificate %s. SANs: %s', cert.digest('sha256'), sans) - return self.z_domain.decode() in sans - - def simple_verify(self, chall, domain, account_public_key, - cert=None, **kwargs): - """Simple verify. - - Verify ``validation`` using ``account_public_key``, optionally - probe tls-sni-01 certificate and check using `verify_cert`. - - :param .challenges.TLSSNI01 chall: Corresponding challenge. - :param str domain: Domain name being validated. - :param JWK account_public_key: - :param OpenSSL.crypto.X509 cert: Optional certificate. If not - provided (``None``) certificate will be retrieved using - `probe_cert`. - :param int port: Port used to probe the certificate. - - - :returns: ``True`` iff client's control of the domain has been - verified. - :rtype: bool - - """ - if not self.verify(chall, account_public_key): - logger.debug("Verification of key authorization in response failed") - return False - - if cert is None: - try: - cert = self.probe_cert(domain=domain, **kwargs) - except errors.Error as error: - logger.debug(str(error), exc_info=True) - return False - - return self.verify_cert(cert) - - -@Challenge.register # pylint: disable=too-many-ancestors -class TLSSNI01(KeyAuthorizationChallenge): - """ACME tls-sni-01 challenge.""" - response_cls = TLSSNI01Response - typ = response_cls.typ - - # boulder#962, ietf-wg-acme#22 - #n = jose.Field("n", encoder=int, decoder=int) - - def validation(self, account_key, **kwargs): - """Generate validation. - - :param JWK account_key: - :param OpenSSL.crypto.PKey cert_key: Optional private key used - in certificate generation. If not provided (``None``), then - fresh key will be generated. - - :rtype: `tuple` of `OpenSSL.crypto.X509` and `OpenSSL.crypto.PKey` - - """ - return self.response(account_key).gen_cert(key=kwargs.get('cert_key')) - - @ChallengeResponse.register class TLSALPN01Response(KeyAuthorizationChallengeResponse): """ACME TLS-ALPN-01 challenge response. @@ -520,7 +371,7 @@ class TLSALPN01Response(KeyAuthorizationChallengeResponse): typ = "tls-alpn-01" -@Challenge.register # pylint: disable=too-many-ancestors +@Challenge.register class TLSALPN01(KeyAuthorizationChallenge): """ACME tls-alpn-01 challenge. @@ -617,7 +468,3 @@ class DNSResponse(ChallengeResponse): """ return chall.check_validation(self.validation, account_public_key) - - -# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. -sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/acme/acme/client.py b/acme/acme/client.py index e28accd9b..b4089f83a 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -5,25 +5,26 @@ import datetime from email.utils import parsedate_tz import heapq import logging -import time import re import sys +import time -import six -from six.moves import http_client # pylint: disable=import-error import josepy as jose import OpenSSL import requests from requests.adapters import HTTPAdapter from requests_toolbelt.adapters.source import SourceAddressAdapter +import six +from six.moves import http_client # pylint: disable=import-error from acme import crypto_util from acme import errors from acme import jws from acme import messages -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List, Set, Text - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Text # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) @@ -33,7 +34,6 @@ logger = logging.getLogger(__name__) # https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning if sys.version_info < (2, 7, 9): # pragma: no cover try: - # pylint: disable=no-member requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() # type: ignore except AttributeError: import urllib3.contrib.pyopenssl # pylint: disable=import-error @@ -44,7 +44,7 @@ DEFAULT_NETWORK_TIMEOUT = 45 DER_CONTENT_TYPE = 'application/pkix-cert' -class ClientBase(object): # pylint: disable=too-many-instance-attributes +class ClientBase(object): """ACME client base object. :ivar messages.Directory directory: @@ -123,6 +123,22 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes """ return self.update_registration(regr, update={'status': 'deactivated'}) + def deactivate_authorization(self, authzr): + # type: (messages.AuthorizationResource) -> messages.AuthorizationResource + """Deactivate authorization. + + :param messages.AuthorizationResource authzr: The Authorization resource + to be deactivated. + + :returns: The Authorization resource that was deactivated. + :rtype: `.AuthorizationResource` + + """ + body = messages.UpdateAuthorization(status='deactivated') + response = self._post(authzr.uri, body) + return self._authzr_from_response(response, + authzr.body.identifier, authzr.uri) + def _authzr_from_response(self, response, identifier=None, uri=None): authzr = messages.AuthorizationResource( body=messages.Authorization.from_json(response.json()), @@ -238,7 +254,6 @@ class Client(ClientBase): URI from which the resource will be downloaded. """ - # pylint: disable=too-many-arguments self.key = key if net is None: net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) @@ -264,7 +279,6 @@ class Client(ClientBase): assert response.status_code == http_client.CREATED # "Instance of 'Field' has no key/contact member" bug: - # pylint: disable=no-member return self._regr_from_response(response) def query_registration(self, regr): @@ -419,7 +433,6 @@ class Client(ClientBase): was marked by the CA as invalid """ - # pylint: disable=too-many-locals assert max_attempts > 0 attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int] exhausted = set() @@ -450,7 +463,6 @@ class Client(ClientBase): updated[authzr] = updated_authzr attempts[authzr] += 1 - # pylint: disable=no-member if updated_authzr.body.status not in ( messages.STATUS_VALID, messages.STATUS_INVALID): if attempts[authzr] < max_attempts: @@ -591,7 +603,6 @@ class ClientV2(ClientBase): if response.status_code == 200 and 'Location' in response.headers: raise errors.ConflictError(response.headers.get('Location')) # "Instance of 'Field' has no key/contact member" bug: - # pylint: disable=no-member regr = self._regr_from_response(response) self.net.account = regr return regr @@ -715,7 +726,7 @@ class ClientV2(ClientBase): for authzr in responses: if authzr.body.status != messages.STATUS_VALID: for chall in authzr.body.challenges: - if chall.error != None: + if chall.error is not None: failed.append(authzr) if failed: raise errors.ValidationError(failed) @@ -931,7 +942,7 @@ class BackwardsCompatibleClientV2(object): return self.client.external_account_required() -class ClientNetwork(object): # pylint: disable=too-many-instance-attributes +class ClientNetwork(object): """Wrapper around requests that signs POSTs for authentication. Also adds user agent, and handles Content-Type. @@ -957,7 +968,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes def __init__(self, key, account=None, alg=jose.RS256, verify_ssl=True, user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT, source_address=None): - # pylint: disable=too-many-arguments self.key = key self.account = account self.alg = alg @@ -1065,7 +1075,6 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes return response def _send_request(self, method, url, *args, **kwargs): - # pylint: disable=too-many-locals """Send HTTP request. Makes sure that `verify_ssl` is respected. Logs request and @@ -1112,10 +1121,9 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes err_regex = r".*host='(\S*)'.*Max retries exceeded with url\: (\/\w*).*(\[Errno \d+\])([A-Za-z ]*)" m = re.match(err_regex, str(e)) if m is None: - raise # pragma: no cover - else: - host, path, _err_no, err_msg = m.groups() - raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg)) + raise # pragma: no cover + host, path, _err_no, err_msg = m.groups() + raise ValueError("Requesting {0}{1}:{2}".format(host, path, err_msg)) # If content is DER, log the base64 of it instead of raw bytes, to keep # binary data out of the logs. @@ -1181,8 +1189,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes if error.code == 'badNonce': logger.debug('Retrying request after error:\n%s', error) return self._post_once(*args, **kwargs) - else: - raise + raise def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, acme_version=1, **kwargs): diff --git a/acme/acme/crypto_util.py b/acme/acme/crypto_util.py index 6a319d94e..66dfc738c 100644 --- a/acme/acme/crypto_util.py +++ b/acme/acme/crypto_util.py @@ -6,15 +6,15 @@ import os import re import socket -from OpenSSL import crypto -from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 import josepy as jose +from OpenSSL import crypto +from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052 from acme import errors -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Union, Tuple, Optional -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) @@ -28,7 +28,7 @@ logger = logging.getLogger(__name__) _DEFAULT_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore -class SSLSocket(object): # pylint: disable=too-few-public-methods +class SSLSocket(object): """SSL wrapper for sockets. :ivar socket sock: Original wrapped socket. @@ -74,7 +74,7 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods class FakeConnection(object): """Fake OpenSSL.SSL.Connection.""" - # pylint: disable=too-few-public-methods,missing-docstring + # pylint: disable=missing-docstring def __init__(self, connection): self._wrapped = connection diff --git a/acme/acme/errors.py b/acme/acme/errors.py index 3a0f8c596..806657940 100644 --- a/acme/acme/errors.py +++ b/acme/acme/errors.py @@ -29,7 +29,12 @@ class NonceError(ClientError): class BadNonce(NonceError): """Bad nonce error.""" def __init__(self, nonce, error, *args, **kwargs): - super(BadNonce, self).__init__(*args, **kwargs) + # MyPy complains here that there is too many arguments for BaseException constructor. + # This is an error fixed in typeshed, see https://github.com/python/mypy/issues/4183 + # The fix is included in MyPy>=0.740, but upgrading it would bring dozen of errors due to + # new types definitions. So we ignore the error until the code base is fixed to match + # with MyPy>=0.740 referential. + super(BadNonce, self).__init__(*args, **kwargs) # type: ignore self.nonce = nonce self.error = error @@ -48,7 +53,8 @@ class MissingNonce(NonceError): """ def __init__(self, response, *args, **kwargs): - super(MissingNonce, self).__init__(*args, **kwargs) + # See comment in BadNonce constructor above for an explanation of type: ignore here. + super(MissingNonce, self).__init__(*args, **kwargs) # type: ignore self.response = response def __str__(self): @@ -83,6 +89,7 @@ class PollError(ClientError): return '{0}(exhausted={1!r}, updated={2!r})'.format( self.__class__.__name__, self.exhausted, self.updated) + class ValidationError(Error): """Error for authorization failures. Contains a list of authorization resources, each of which is invalid and should have an error field. @@ -91,9 +98,11 @@ class ValidationError(Error): self.failed_authzrs = failed_authzrs super(ValidationError, self).__init__() -class TimeoutError(Error): + +class TimeoutError(Error): # pylint: disable=redefined-builtin """Error for when polling an authorization or an order times out.""" + class IssuanceError(Error): """Error sent by the server after requesting issuance of a certificate.""" @@ -105,6 +114,7 @@ class IssuanceError(Error): self.error = error super(IssuanceError, self).__init__() + class ConflictError(ClientError): """Error for when the server returns a 409 (Conflict) HTTP status. diff --git a/acme/acme/fields.py b/acme/acme/fields.py index d7ec78403..3b5672283 100644 --- a/acme/acme/fields.py +++ b/acme/acme/fields.py @@ -4,7 +4,6 @@ import logging import josepy as jose import pyrfc3339 - logger = logging.getLogger(__name__) diff --git a/acme/acme/jws.py b/acme/acme/jws.py index d1f4a5606..8efbadfbe 100644 --- a/acme/acme/jws.py +++ b/acme/acme/jws.py @@ -44,10 +44,10 @@ class Signature(jose.Signature): class JWS(jose.JWS): """ACME-specific JWS. Includes none, url, and kid in protected header.""" signature_cls = Signature - __slots__ = jose.JWS._orig_slots # pylint: disable=no-member + __slots__ = jose.JWS._orig_slots @classmethod - # pylint: disable=arguments-differ,too-many-arguments + # pylint: disable=arguments-differ def sign(cls, payload, key, alg, nonce, url=None, kid=None): # Per ACME spec, jwk and kid are mutually exclusive, so only include a # jwk field if kid is not provided. diff --git a/acme/acme/magic_typing.py b/acme/acme/magic_typing.py index 471b8dfa9..5a6358c69 100644 --- a/acme/acme/magic_typing.py +++ b/acme/acme/magic_typing.py @@ -1,6 +1,7 @@ """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): diff --git a/acme/acme/messages.py b/acme/acme/messages.py index df96b5f2b..96a1ed7c0 100644 --- a/acme/acme/messages.py +++ b/acme/acme/messages.py @@ -1,18 +1,21 @@ """ACME protocol messages.""" import json + +import josepy as jose import six + +from acme import challenges +from acme import errors +from acme import fields +from acme import jws +from acme import util + try: from collections.abc import Hashable # pylint: disable=no-name-in-module except ImportError: # pragma: no cover from collections import Hashable -import josepy as jose -from acme import challenges -from acme import errors -from acme import fields -from acme import util -from acme import jws OLD_ERROR_PREFIX = "urn:acme:error:" ERROR_PREFIX = "urn:ietf:params:acme:error:" @@ -143,7 +146,7 @@ class _Constant(jose.JSONDeSerializable, Hashable): # type: ignore if jobj not in cls.POSSIBLE_NAMES: # pylint: disable=unsupported-membership-test raise jose.DeserializationError( '{0} not recognized'.format(cls.__name__)) - return cls.POSSIBLE_NAMES[jobj] # pylint: disable=unsubscriptable-object + return cls.POSSIBLE_NAMES[jobj] def __repr__(self): return '{0}({1})'.format(self.__class__.__name__, self.name) @@ -168,6 +171,7 @@ STATUS_VALID = Status('valid') STATUS_INVALID = Status('invalid') STATUS_REVOKED = Status('revoked') STATUS_READY = Status('ready') +STATUS_DEACTIVATED = Status('deactivated') class IdentifierType(_Constant): @@ -471,7 +475,7 @@ class Authorization(ResourceBody): :ivar datetime.datetime expires: """ - identifier = jose.Field('identifier', decoder=Identifier.from_json) + identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True) challenges = jose.Field('challenges', omitempty=True) combinations = jose.Field('combinations', omitempty=True) @@ -501,6 +505,12 @@ class NewAuthorization(Authorization): resource = fields.Resource(resource_type) +class UpdateAuthorization(Authorization): + """Update authorization.""" + resource_type = 'authz' + resource = fields.Resource(resource_type) + + class AuthorizationResource(ResourceWithURI): """Authorization Resource. diff --git a/acme/acme/standalone.py b/acme/acme/standalone.py index 844960b2f..cf0da4e86 100644 --- a/acme/acme/standalone.py +++ b/acme/acme/standalone.py @@ -1,29 +1,22 @@ """Support for standalone client challenge solvers. """ -import argparse import collections import functools import logging -import os import socket -import sys import threading from six.moves import BaseHTTPServer # type: ignore # pylint: disable=import-error from six.moves import http_client # pylint: disable=import-error from six.moves import socketserver # type: ignore # pylint: disable=import-error -import OpenSSL - from acme import challenges from acme import crypto_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module -from acme import _TLSSNI01DeprecationModule - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) # six.moves.* | pylint: disable=no-member,attribute-defined-outside-init -# pylint: disable=too-few-public-methods,no-init +# pylint: disable=no-init class TLSServer(socketserver.TCPServer): @@ -51,7 +44,7 @@ class TLSServer(socketserver.TCPServer): return socketserver.TCPServer.server_bind(self) -class ACMEServerMixin: # pylint: disable=old-style-class +class ACMEServerMixin: """ACME server common settings mixin.""" # TODO: c.f. #858 server_version = "ACME client standalone challenge solver" @@ -112,7 +105,6 @@ class BaseDualNetworkedServers(object): """Wraps socketserver.TCPServer.serve_forever""" for server in self.servers: thread = threading.Thread( - # pylint: disable=no-member target=server.serve_forever) thread.start() self.threads.append(thread) @@ -132,35 +124,6 @@ class BaseDualNetworkedServers(object): self.threads = [] -class TLSSNI01Server(TLSServer, ACMEServerMixin): - """TLSSNI01 Server.""" - - def __init__(self, server_address, certs, ipv6=False): - TLSServer.__init__( - self, server_address, BaseRequestHandlerWithLogging, certs=certs, ipv6=ipv6) - - -class TLSSNI01DualNetworkedServers(BaseDualNetworkedServers): - """TLSSNI01Server Wrapper. Tries everything for both. Failures for one don't - affect the other.""" - - def __init__(self, *args, **kwargs): - BaseDualNetworkedServers.__init__(self, TLSSNI01Server, *args, **kwargs) - - -class BaseRequestHandlerWithLogging(socketserver.BaseRequestHandler): - """BaseRequestHandler with logging.""" - - def log_message(self, format, *args): # pylint: disable=redefined-builtin - """Log arbitrary message.""" - logger.debug("%s - - %s", self.client_address[0], format % args) - - def handle(self): - """Handle request.""" - self.log_message("Incoming request") - socketserver.BaseRequestHandler.handle(self) - - class HTTPServer(BaseHTTPServer.HTTPServer): """Generic HTTP Server.""" @@ -263,43 +226,3 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler): """ return functools.partial( cls, simple_http_resources=simple_http_resources) - - -def simple_tls_sni_01_server(cli_args, forever=True): - """Run simple standalone TLSSNI01 server.""" - logging.basicConfig(level=logging.DEBUG) - - parser = argparse.ArgumentParser() - parser.add_argument( - "-p", "--port", default=0, help="Port to serve at. By default " - "picks random free port.") - args = parser.parse_args(cli_args[1:]) - - certs = {} - - _, hosts, _ = next(os.walk('.')) # type: ignore # https://github.com/python/mypy/issues/465 - for host in hosts: - with open(os.path.join(host, "cert.pem")) as cert_file: - cert_contents = cert_file.read() - with open(os.path.join(host, "key.pem")) as key_file: - key_contents = key_file.read() - certs[host.encode()] = ( - OpenSSL.crypto.load_privatekey( - OpenSSL.crypto.FILETYPE_PEM, key_contents), - OpenSSL.crypto.load_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert_contents)) - - server = TLSSNI01Server(('', int(args.port)), certs=certs) - logger.info("Serving at https://%s:%s...", *server.socket.getsockname()[:2]) - if forever: # pragma: no cover - server.serve_forever() - else: - server.handle_request() - - -# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. -sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) - - -if __name__ == "__main__": - sys.exit(simple_tls_sni_01_server(sys.argv)) # pragma: no cover diff --git a/acme/docs/conf.py b/acme/docs/conf.py index e70651648..01029a81f 100644 --- a/acme/docs/conf.py +++ b/acme/docs/conf.py @@ -12,10 +12,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os import shlex - +import sys here = os.path.abspath(os.path.dirname(__file__)) diff --git a/acme/examples/http01_example.py b/acme/examples/http01_example.py index 79508f1b4..2dc197d09 100644 --- a/acme/examples/http01_example.py +++ b/acme/examples/http01_example.py @@ -26,8 +26,10 @@ Workflow: - Deactivate Account """ from contextlib import contextmanager + from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import rsa +import josepy as jose import OpenSSL from acme import challenges @@ -36,7 +38,6 @@ from acme import crypto_util from acme import errors from acme import messages from acme import standalone -import josepy as jose # Constants: diff --git a/acme/readthedocs.org.requirements.txt b/acme/readthedocs.org.requirements.txt index 65e6c7cf3..168af8013 100644 --- a/acme/readthedocs.org.requirements.txt +++ b/acme/readthedocs.org.requirements.txt @@ -1,10 +1,10 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e acme[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install acme[docs]" does not work as +# expected and "pip install -e acme[docs]" must be used instead -e acme[docs] diff --git a/acme/setup.py b/acme/setup.py index 9fca57e01..6da5fe519 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -1,9 +1,10 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys -version = '0.36.0.dev0' +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand + +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ @@ -14,8 +15,8 @@ install_requires = [ # 1.1.0+ is required to avoid the warnings described at # https://github.com/certbot/josepy/issues/13. 'josepy>=1.1.0', - # Connection.set_tlsext_host_name (>=0.13) 'mock', + # Connection.set_tlsext_host_name (>=0.13) 'PyOpenSSL>=0.13.1', 'pyrfc3339', 'pytz', @@ -73,6 +74,7 @@ setup( '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', ], diff --git a/acme/acme/challenges_test.py b/acme/tests/challenges_test.py similarity index 71% rename from acme/acme/challenges_test.py rename to acme/tests/challenges_test.py index 9d3a92fa5..490caadc2 100644 --- a/acme/acme/challenges_test.py +++ b/acme/tests/challenges_test.py @@ -3,13 +3,10 @@ import unittest import josepy as jose import mock -import OpenSSL import requests +from six.moves.urllib import parse as urllib_parse -from six.moves.urllib import parse as urllib_parse # pylint: disable=relative-import - -from acme import errors -from acme import test_util +import test_util CERT = test_util.load_comparable_cert('cert.pem') KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem')) @@ -21,7 +18,6 @@ class ChallengeTest(unittest.TestCase): from acme.challenges import Challenge from acme.challenges import UnrecognizedChallenge chall = UnrecognizedChallenge({"type": "foo"}) - # pylint: disable=no-member self.assertEqual(chall, Challenge.from_json(chall.jobj)) @@ -77,7 +73,6 @@ class KeyAuthorizationChallengeResponseTest(unittest.TestCase): class DNS01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import DNS01Response @@ -149,7 +144,6 @@ class DNS01Test(unittest.TestCase): class HTTP01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import HTTP01Response @@ -259,152 +253,7 @@ class HTTP01Test(unittest.TestCase): self.msg.update(token=b'..').good_token) -class TLSSNI01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes - - def setUp(self): - from acme.challenges import TLSSNI01 - self.chall = TLSSNI01( - token=jose.b64decode(b'a82d5ff8ef740d12881f6d3c2277ab2e')) - - self.response = self.chall.response(KEY) - self.jmsg = { - 'resource': 'challenge', - 'type': 'tls-sni-01', - 'keyAuthorization': self.response.key_authorization, - } - - # pylint: disable=invalid-name - label1 = b'dc38d9c3fa1a4fdcc3a5501f2d38583f' - label2 = b'b7793728f084394f2a1afd459556bb5c' - self.z = label1 + label2 - self.z_domain = label1 + b'.' + label2 + b'.acme.invalid' - self.domain = 'foo.com' - - def test_z_and_domain(self): - self.assertEqual(self.z, self.response.z) - self.assertEqual(self.z_domain, self.response.z_domain) - - def test_to_partial_json(self): - self.assertEqual({k: v for k, v in self.jmsg.items() if k != 'keyAuthorization'}, - self.response.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSSNI01Response - self.assertEqual(self.response, TLSSNI01Response.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSSNI01Response - hash(TLSSNI01Response.from_json(self.jmsg)) - - @mock.patch('acme.challenges.socket.gethostbyname') - @mock.patch('acme.challenges.crypto_util.probe_sni') - def test_probe_cert(self, mock_probe_sni, mock_gethostbyname): - mock_gethostbyname.return_value = '127.0.0.1' - self.response.probe_cert('foo.com') - mock_gethostbyname.assert_called_once_with('foo.com') - mock_probe_sni.assert_called_once_with( - host='127.0.0.1', port=self.response.PORT, - name=self.z_domain) - - self.response.probe_cert('foo.com', host='8.8.8.8') - mock_probe_sni.assert_called_with( - host='8.8.8.8', port=mock.ANY, name=mock.ANY) - - self.response.probe_cert('foo.com', port=1234) - mock_probe_sni.assert_called_with( - host=mock.ANY, port=1234, name=mock.ANY) - - self.response.probe_cert('foo.com', bar='baz') - mock_probe_sni.assert_called_with( - host=mock.ANY, port=mock.ANY, name=mock.ANY, bar='baz') - - self.response.probe_cert('foo.com', name=b'xxx') - mock_probe_sni.assert_called_with( - host=mock.ANY, port=mock.ANY, - name=self.z_domain) - - def test_gen_verify_cert(self): - key1 = test_util.load_pyopenssl_private_key('rsa512_key.pem') - cert, key2 = self.response.gen_cert(key1) - self.assertEqual(key1, key2) - self.assertTrue(self.response.verify_cert(cert)) - - def test_gen_verify_cert_gen_key(self): - cert, key = self.response.gen_cert() - self.assertTrue(isinstance(key, OpenSSL.crypto.PKey)) - self.assertTrue(self.response.verify_cert(cert)) - - def test_verify_bad_cert(self): - self.assertFalse(self.response.verify_cert( - test_util.load_cert('cert.pem'))) - - def test_simple_verify_bad_key_authorization(self): - key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem')) - self.response.simple_verify(self.chall, "local", key2.public_key()) - - @mock.patch('acme.challenges.TLSSNI01Response.verify_cert', autospec=True) - def test_simple_verify(self, mock_verify_cert): - mock_verify_cert.return_value = mock.sentinel.verification - self.assertEqual( - mock.sentinel.verification, self.response.simple_verify( - self.chall, self.domain, KEY.public_key(), - cert=mock.sentinel.cert)) - mock_verify_cert.assert_called_once_with( - self.response, mock.sentinel.cert) - - @mock.patch('acme.challenges.TLSSNI01Response.probe_cert') - def test_simple_verify_false_on_probe_error(self, mock_probe_cert): - mock_probe_cert.side_effect = errors.Error - self.assertFalse(self.response.simple_verify( - self.chall, self.domain, KEY.public_key())) - - -class TLSSNI01Test(unittest.TestCase): - - def setUp(self): - self.jmsg = { - 'type': 'tls-sni-01', - 'token': 'a82d5ff8ef740d12881f6d3c2277ab2e', - } - from acme.challenges import TLSSNI01 - self.msg = TLSSNI01( - token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e')) - - def test_to_partial_json(self): - self.assertEqual(self.jmsg, self.msg.to_partial_json()) - - def test_from_json(self): - from acme.challenges import TLSSNI01 - self.assertEqual(self.msg, TLSSNI01.from_json(self.jmsg)) - - def test_from_json_hashable(self): - from acme.challenges import TLSSNI01 - hash(TLSSNI01.from_json(self.jmsg)) - - def test_from_json_invalid_token_length(self): - from acme.challenges import TLSSNI01 - self.jmsg['token'] = jose.encode_b64jose(b'abcd') - self.assertRaises( - jose.DeserializationError, TLSSNI01.from_json, self.jmsg) - - @mock.patch('acme.challenges.TLSSNI01Response.gen_cert') - def test_validation(self, mock_gen_cert): - mock_gen_cert.return_value = ('cert', 'key') - self.assertEqual(('cert', 'key'), self.msg.validation( - KEY, cert_key=mock.sentinel.cert_key)) - mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key) - - def test_deprecation_message(self): - with mock.patch('acme.warnings.warn') as mock_warn: - from acme.challenges import TLSSNI01 - assert TLSSNI01 - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - - class TLSALPN01ResponseTest(unittest.TestCase): - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.challenges import TLSALPN01Response diff --git a/acme/acme/client_test.py b/acme/tests/client_test.py similarity index 98% rename from acme/acme/client_test.py rename to acme/tests/client_test.py index 406201751..192cd2949 100644 --- a/acme/acme/client_test.py +++ b/acme/tests/client_test.py @@ -5,21 +5,19 @@ import datetime import json import unittest -from six.moves import http_client # pylint: disable=import-error - import josepy as jose import mock import OpenSSL import requests +from six.moves import http_client # pylint: disable=import-error from acme import challenges from acme import errors from acme import jws as acme_jws from acme import messages -from acme import messages_test -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +import messages_test +import test_util CERT_DER = test_util.load_vector('cert.der') CERT_SAN_PEM = test_util.load_vector('cert-san.pem') @@ -63,7 +61,7 @@ class ClientTestBase(unittest.TestCase): self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212') reg = messages.Registration( contact=self.contact, key=KEY.public_key()) - the_arg = dict(reg) # type: Dict + the_arg = dict(reg) # type: Dict self.new_reg = messages.NewRegistration(**the_arg) self.regr = messages.RegistrationResource( body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1') @@ -318,7 +316,6 @@ class BackwardsCompatibleClientV2Test(ClientTestBase): class ClientTest(ClientTestBase): """Tests for acme.client.Client.""" - # pylint: disable=too-many-instance-attributes,too-many-public-methods def setUp(self): super(ClientTest, self).setUp() @@ -637,6 +634,14 @@ class ClientTest(ClientTestBase): errors.PollError, self.client.poll_and_request_issuance, csr, authzrs, mintime=mintime, max_attempts=2) + def test_deactivate_authorization(self): + authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED) + self.response.json.return_value = authzb.to_json() + authzr = self.client.deactivate_authorization(self.authzr) + self.assertEqual(authzb, authzr.body) + self.assertEqual(self.client.net.post.call_count, 1) + self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0]) + def test_check_cert(self): self.response.headers['Location'] = self.certr.uri self.response.content = CERT_DER @@ -880,7 +885,7 @@ class ClientV2Test(ClientTestBase): new_nonce_url='https://www.letsencrypt-demo.org/acme/new-nonce') self.client.net.get.assert_not_called() - class FakeError(messages.Error): # pylint: disable=too-many-ancestors + class FakeError(messages.Error): """Fake error to reproduce a malformed request ACME error""" def __init__(self): # pylint: disable=super-init-not-called pass @@ -909,7 +914,6 @@ class MockJSONDeSerializable(jose.JSONDeSerializable): class ClientNetworkTest(unittest.TestCase): """Tests for acme.client.ClientNetwork.""" - # pylint: disable=too-many-public-methods def setUp(self): self.verify_ssl = mock.MagicMock() @@ -959,8 +963,8 @@ class ClientNetworkTest(unittest.TestCase): def test_check_response_not_ok_jobj_error(self): self.response.ok = False - self.response.json.return_value = messages.Error( - detail='foo', typ='serverInternal', title='some title').to_json() + self.response.json.return_value = messages.Error.with_code( + 'serverInternal', detail='foo', title='some title').to_json() # pylint: disable=protected-access self.assertRaises( messages.Error, self.net._check_response, self.response) @@ -985,7 +989,7 @@ class ClientNetworkTest(unittest.TestCase): self.response.json.side_effect = ValueError for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter + # pylint: disable=protected-access self.assertEqual( self.response, self.net._check_response(self.response)) @@ -999,7 +1003,7 @@ class ClientNetworkTest(unittest.TestCase): self.response.json.return_value = {} for response_ct in [self.net.JSON_CONTENT_TYPE, 'foo']: self.response.headers['Content-Type'] = response_ct - # pylint: disable=protected-access,no-value-for-parameter + # pylint: disable=protected-access self.assertEqual( self.response, self.net._check_response(self.response)) @@ -1115,7 +1119,6 @@ class ClientNetworkTest(unittest.TestCase): class ClientNetworkWithMockedResponseTest(unittest.TestCase): """Tests for acme.client.ClientNetwork which mock out response.""" - # pylint: disable=too-many-instance-attributes def setUp(self): from acme.client import ClientNetwork @@ -1125,8 +1128,8 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase): self.response.headers = {} self.response.links = {} self.response.checked = False - self.acmev1_nonce_response = mock.MagicMock(ok=False, - status_code=http_client.METHOD_NOT_ALLOWED) + self.acmev1_nonce_response = mock.MagicMock( + ok=False, status_code=http_client.METHOD_NOT_ALLOWED) self.acmev1_nonce_response.headers = {} self.obj = mock.MagicMock() self.wrapped_obj = mock.MagicMock() diff --git a/acme/acme/crypto_util_test.py b/acme/tests/crypto_util_test.py similarity index 96% rename from acme/acme/crypto_util_test.py rename to acme/tests/crypto_util_test.py index 44b245bbe..41640ed60 100644 --- a/acme/acme/crypto_util_test.py +++ b/acme/tests/crypto_util_test.py @@ -5,15 +5,14 @@ import threading import time import unittest -import six -from six.moves import socketserver #type: ignore # pylint: disable=import-error - import josepy as jose import OpenSSL +import six +from six.moves import socketserver # type: ignore # pylint: disable=import-error from acme import errors -from acme import test_util -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +import test_util class SSLSocketAndProbeSNITest(unittest.TestCase): @@ -30,7 +29,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): class _TestServer(socketserver.TCPServer): - # pylint: disable=too-few-public-methods # six.moves.* | pylint: disable=attribute-defined-outside-init,no-init def server_bind(self): # pylint: disable=missing-docstring @@ -40,7 +38,6 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): self.server = _TestServer(('', 0), socketserver.BaseRequestHandler) self.port = self.server.socket.getsockname()[1] self.server_thread = threading.Thread( - # pylint: disable=no-member target=self.server.handle_request) def tearDown(self): @@ -67,7 +64,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase): def test_probe_connection_error(self): # pylint has a hard time with six - self.server.server_close() # pylint: disable=no-member + self.server.server_close() original_timeout = socket.getdefaulttimeout() try: socket.setdefaulttimeout(1) diff --git a/acme/acme/errors_test.py b/acme/tests/errors_test.py similarity index 100% rename from acme/acme/errors_test.py rename to acme/tests/errors_test.py diff --git a/acme/acme/fields_test.py b/acme/tests/fields_test.py similarity index 100% rename from acme/acme/fields_test.py rename to acme/tests/fields_test.py diff --git a/acme/acme/jose_test.py b/acme/tests/jose_test.py similarity index 87% rename from acme/acme/jose_test.py rename to acme/tests/jose_test.py index 340624a4f..e008cb6fc 100644 --- a/acme/acme/jose_test.py +++ b/acme/tests/jose_test.py @@ -2,6 +2,7 @@ import importlib import unittest + class JoseTest(unittest.TestCase): """Tests for acme.jose shim.""" @@ -20,11 +21,10 @@ class JoseTest(unittest.TestCase): # We use the imports below with eval, but pylint doesn't # understand that. - # pylint: disable=eval-used,unused-variable - import acme - import josepy - acme_jose_mod = eval(acme_jose_path) - josepy_mod = eval(josepy_path) + import acme # pylint: disable=unused-import + import josepy # pylint: disable=unused-import + acme_jose_mod = eval(acme_jose_path) # pylint: disable=eval-used + josepy_mod = eval(josepy_path) # pylint: disable=eval-used self.assertIs(acme_jose_mod, josepy_mod) self.assertIs(getattr(acme_jose_mod, attribute), getattr(josepy_mod, attribute)) diff --git a/acme/acme/jws_test.py b/acme/tests/jws_test.py similarity index 99% rename from acme/acme/jws_test.py rename to acme/tests/jws_test.py index 743589937..dd730f2e0 100644 --- a/acme/acme/jws_test.py +++ b/acme/tests/jws_test.py @@ -3,8 +3,7 @@ import unittest import josepy as jose -from acme import test_util - +import test_util KEY = jose.JWKRSA.load(test_util.load_vector('rsa512_key.pem')) diff --git a/acme/acme/magic_typing_test.py b/acme/tests/magic_typing_test.py similarity index 100% rename from acme/acme/magic_typing_test.py rename to acme/tests/magic_typing_test.py diff --git a/acme/acme/messages_test.py b/acme/tests/messages_test.py similarity index 95% rename from acme/acme/messages_test.py rename to acme/tests/messages_test.py index 7efaaa1a3..b9b70266b 100644 --- a/acme/acme/messages_test.py +++ b/acme/tests/messages_test.py @@ -5,9 +5,8 @@ import josepy as jose import mock from acme import challenges -from acme import test_util -from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +import test_util CERT = test_util.load_comparable_cert('cert.der') CSR = test_util.load_comparable_csr('csr.der') @@ -19,8 +18,7 @@ class ErrorTest(unittest.TestCase): def setUp(self): from acme.messages import Error, ERROR_PREFIX - self.error = Error( - detail='foo', typ=ERROR_PREFIX + 'malformed', title='title') + self.error = Error.with_code('malformed', detail='foo', title='title') self.jobj = { 'detail': 'foo', 'title': 'some title', @@ -28,7 +26,6 @@ class ErrorTest(unittest.TestCase): } self.error_custom = Error(typ='custom', detail='bar') self.empty_error = Error() - self.jobj_custom = {'type': 'custom', 'detail': 'bar'} def test_default_typ(self): from acme.messages import Error @@ -43,8 +40,7 @@ class ErrorTest(unittest.TestCase): hash(Error.from_json(self.error.to_json())) def test_description(self): - self.assertEqual( - 'The request message was malformed', self.error.description) + self.assertEqual('The request message was malformed', self.error.description) self.assertTrue(self.error_custom.description is None) def test_code(self): @@ -54,17 +50,17 @@ class ErrorTest(unittest.TestCase): self.assertEqual(None, Error().code) def test_is_acme_error(self): - from acme.messages import is_acme_error + from acme.messages import is_acme_error, Error self.assertTrue(is_acme_error(self.error)) self.assertFalse(is_acme_error(self.error_custom)) + self.assertFalse(is_acme_error(Error())) self.assertFalse(is_acme_error(self.empty_error)) self.assertFalse(is_acme_error("must pet all the {dogs|rabbits}")) def test_unicode_error(self): - from acme.messages import Error, ERROR_PREFIX, is_acme_error - arabic_error = Error( - detail=u'\u0639\u062f\u0627\u0644\u0629', typ=ERROR_PREFIX + 'malformed', - title='title') + from acme.messages import Error, is_acme_error + arabic_error = Error.with_code( + 'malformed', detail=u'\u0639\u062f\u0627\u0644\u0629', title='title') self.assertTrue(is_acme_error(arabic_error)) def test_with_code(self): @@ -305,8 +301,7 @@ class ChallengeBodyTest(unittest.TestCase): from acme.messages import Error from acme.messages import STATUS_INVALID self.status = STATUS_INVALID - error = Error(typ='urn:ietf:params:acme:error:serverInternal', - detail='Unable to communicate with DNS server') + error = Error.with_code('serverInternal', detail='Unable to communicate with DNS server') self.challb = ChallengeBody( uri='http://challb', chall=self.chall, status=self.status, error=error) diff --git a/acme/acme/standalone_test.py b/acme/tests/standalone_test.py similarity index 60% rename from acme/acme/standalone_test.py rename to acme/tests/standalone_test.py index 86ceaa316..83ced12b0 100644 --- a/acme/acme/standalone_test.py +++ b/acme/tests/standalone_test.py @@ -1,26 +1,17 @@ """Tests for acme.standalone.""" -import multiprocessing -import os -import shutil import socket import threading -import tempfile import unittest -import time -from contextlib import closing - -from six.moves import http_client # pylint: disable=import-error -from six.moves import socketserver # type: ignore # pylint: disable=import-error import josepy as jose import mock import requests +from six.moves import http_client # pylint: disable=import-error +from six.moves import socketserver # type: ignore # pylint: disable=import-error from acme import challenges -from acme import crypto_util -from acme import errors -from acme import test_util -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +import test_util class TLSServerTest(unittest.TestCase): @@ -41,32 +32,6 @@ class TLSServerTest(unittest.TestCase): server.server_close() -class TLSSNI01ServerTest(unittest.TestCase): - """Test for acme.standalone.TLSSNI01Server.""" - - - def setUp(self): - self.certs = {b'localhost': ( - test_util.load_pyopenssl_private_key('rsa2048_key.pem'), - test_util.load_cert('rsa2048_cert.pem'), - )} - from acme.standalone import TLSSNI01Server - self.server = TLSSNI01Server(('localhost', 0), certs=self.certs) - self.thread = threading.Thread(target=self.server.serve_forever) - self.thread.start() - - def tearDown(self): - self.server.shutdown() - self.thread.join() - - def test_it(self): - host, port = self.server.socket.getsockname()[:2] - cert = crypto_util.probe_sni( - b'localhost', host=host, port=port, timeout=1) - self.assertEqual(jose.ComparableX509(cert), - jose.ComparableX509(self.certs[b'localhost'][1])) - - class HTTP01ServerTest(unittest.TestCase): """Tests for acme.standalone.HTTP01Server.""" @@ -170,33 +135,6 @@ class BaseDualNetworkedServersTest(unittest.TestCase): prev_port = port -class TLSSNI01DualNetworkedServersTest(unittest.TestCase): - """Test for acme.standalone.TLSSNI01DualNetworkedServers.""" - - - def setUp(self): - self.certs = {b'localhost': ( - test_util.load_pyopenssl_private_key('rsa2048_key.pem'), - test_util.load_cert('rsa2048_cert.pem'), - )} - from acme.standalone import TLSSNI01DualNetworkedServers - self.servers = TLSSNI01DualNetworkedServers(('localhost', 0), certs=self.certs) - self.servers.serve_forever() - - def tearDown(self): - self.servers.shutdown_and_server_close() - - def test_connect(self): - socknames = self.servers.getsocknames() - # connect to all addresses - for sockname in socknames: - host, port = sockname[:2] - cert = crypto_util.probe_sni( - b'localhost', host=host, port=port, timeout=1) - self.assertEqual(jose.ComparableX509(cert), - jose.ComparableX509(self.certs[b'localhost'][1])) - - class HTTP01DualNetworkedServersTest(unittest.TestCase): """Tests for acme.standalone.HTTP01DualNetworkedServers.""" @@ -247,60 +185,5 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase): self.assertFalse(self._test_http01(add=False)) -class TestSimpleTLSSNI01Server(unittest.TestCase): - """Tests for acme.standalone.simple_tls_sni_01_server.""" - - - def setUp(self): - # mirror ../examples/standalone - self.test_cwd = tempfile.mkdtemp() - localhost_dir = os.path.join(self.test_cwd, 'localhost') - os.makedirs(localhost_dir) - shutil.copy(test_util.vector_path('rsa2048_cert.pem'), - os.path.join(localhost_dir, 'cert.pem')) - shutil.copy(test_util.vector_path('rsa2048_key.pem'), - os.path.join(localhost_dir, 'key.pem')) - - with closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as sock: - sock.bind(('', 0)) - sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - self.port = sock.getsockname()[1] - - from acme.standalone import simple_tls_sni_01_server - self.process = multiprocessing.Process(target=simple_tls_sni_01_server, - args=(['path', '-p', str(self.port)],)) - self.old_cwd = os.getcwd() - os.chdir(self.test_cwd) - - def tearDown(self): - os.chdir(self.old_cwd) - if self.process.is_alive(): - self.process.terminate() - self.process.join(timeout=5) - # Check that we didn't timeout waiting for the process to - # terminate. - self.assertNotEqual(self.process.exitcode, None) - shutil.rmtree(self.test_cwd) - - @mock.patch('acme.standalone.TLSSNI01Server.handle_request') - def test_mock(self, handle): - from acme.standalone import simple_tls_sni_01_server - simple_tls_sni_01_server(cli_args=['path', '-p', str(self.port)], forever=False) - self.assertEqual(handle.call_count, 1) - - def test_live(self): - self.process.start() - cert = None - for _ in range(50): - time.sleep(0.1) - try: - cert = crypto_util.probe_sni(b'localhost', b'127.0.0.1', self.port) - break - except errors.Error: # pragma: no cover - pass - self.assertEqual(jose.ComparableX509(cert), - test_util.load_comparable_cert('rsa2048_cert.pem')) - - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/acme/acme/test_util.py b/acme/tests/test_util.py similarity index 68% rename from acme/acme/test_util.py rename to acme/tests/test_util.py index f04829deb..d4a45272d 100644 --- a/acme/acme/test_util.py +++ b/acme/tests/test_util.py @@ -4,19 +4,12 @@ """ import os -import unittest -import pkg_resources from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization import josepy as jose from OpenSSL import crypto - - -def vector_path(*names): - """Path to a test vector.""" - return pkg_resources.resource_filename( - __name__, os.path.join('testdata', *names)) +import pkg_resources def load_vector(*names): @@ -32,8 +25,7 @@ def _guess_loader(filename, loader_pem, loader_der): return loader_pem elif ext.lower() == '.der': return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") + raise ValueError("Loader could not be recognized based on extension") # pragma: no cover def load_cert(*names): @@ -73,23 +65,3 @@ def load_pyopenssl_private_key(*names): loader = _guess_loader( names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1) return crypto.load_privatekey(loader, load_vector(*names)) - - -def skip_unless(condition, reason): # pragma: no cover - """Skip tests unless a condition holds. - - This implements the basic functionality of unittest.skipUnless - which is only available on Python 2.7+. - - :param bool condition: If ``False``, the test will be skipped - :param str reason: the reason for skipping the test - - :rtype: callable - :returns: decorator that hides tests unless condition is ``True`` - - """ - if hasattr(unittest, "skipUnless"): - return unittest.skipUnless(condition, reason) - elif condition: - return lambda cls: cls - return lambda cls: None diff --git a/acme/acme/testdata/README b/acme/tests/testdata/README similarity index 100% rename from acme/acme/testdata/README rename to acme/tests/testdata/README diff --git a/acme/acme/testdata/cert-100sans.pem b/acme/tests/testdata/cert-100sans.pem similarity index 100% rename from acme/acme/testdata/cert-100sans.pem rename to acme/tests/testdata/cert-100sans.pem diff --git a/acme/acme/testdata/cert-idnsans.pem b/acme/tests/testdata/cert-idnsans.pem similarity index 100% rename from acme/acme/testdata/cert-idnsans.pem rename to acme/tests/testdata/cert-idnsans.pem diff --git a/acme/acme/testdata/cert-nocn.der b/acme/tests/testdata/cert-nocn.der similarity index 100% rename from acme/acme/testdata/cert-nocn.der rename to acme/tests/testdata/cert-nocn.der diff --git a/acme/acme/testdata/cert-san.pem b/acme/tests/testdata/cert-san.pem similarity index 100% rename from acme/acme/testdata/cert-san.pem rename to acme/tests/testdata/cert-san.pem diff --git a/acme/acme/testdata/cert.der b/acme/tests/testdata/cert.der similarity index 100% rename from acme/acme/testdata/cert.der rename to acme/tests/testdata/cert.der diff --git a/acme/acme/testdata/cert.pem b/acme/tests/testdata/cert.pem similarity index 100% rename from acme/acme/testdata/cert.pem rename to acme/tests/testdata/cert.pem diff --git a/acme/acme/testdata/critical-san.pem b/acme/tests/testdata/critical-san.pem similarity index 100% rename from acme/acme/testdata/critical-san.pem rename to acme/tests/testdata/critical-san.pem diff --git a/acme/acme/testdata/csr-100sans.pem b/acme/tests/testdata/csr-100sans.pem similarity index 100% rename from acme/acme/testdata/csr-100sans.pem rename to acme/tests/testdata/csr-100sans.pem diff --git a/acme/acme/testdata/csr-6sans.pem b/acme/tests/testdata/csr-6sans.pem similarity index 100% rename from acme/acme/testdata/csr-6sans.pem rename to acme/tests/testdata/csr-6sans.pem diff --git a/acme/acme/testdata/csr-idnsans.pem b/acme/tests/testdata/csr-idnsans.pem similarity index 100% rename from acme/acme/testdata/csr-idnsans.pem rename to acme/tests/testdata/csr-idnsans.pem diff --git a/acme/acme/testdata/csr-nosans.pem b/acme/tests/testdata/csr-nosans.pem similarity index 100% rename from acme/acme/testdata/csr-nosans.pem rename to acme/tests/testdata/csr-nosans.pem diff --git a/acme/acme/testdata/csr-san.pem b/acme/tests/testdata/csr-san.pem similarity index 100% rename from acme/acme/testdata/csr-san.pem rename to acme/tests/testdata/csr-san.pem diff --git a/acme/acme/testdata/csr.der b/acme/tests/testdata/csr.der similarity index 100% rename from acme/acme/testdata/csr.der rename to acme/tests/testdata/csr.der diff --git a/acme/acme/testdata/csr.pem b/acme/tests/testdata/csr.pem similarity index 100% rename from acme/acme/testdata/csr.pem rename to acme/tests/testdata/csr.pem diff --git a/acme/acme/testdata/dsa512_key.pem b/acme/tests/testdata/dsa512_key.pem similarity index 100% rename from acme/acme/testdata/dsa512_key.pem rename to acme/tests/testdata/dsa512_key.pem diff --git a/acme/acme/testdata/rsa1024_key.pem b/acme/tests/testdata/rsa1024_key.pem similarity index 100% rename from acme/acme/testdata/rsa1024_key.pem rename to acme/tests/testdata/rsa1024_key.pem diff --git a/acme/acme/testdata/rsa2048_cert.pem b/acme/tests/testdata/rsa2048_cert.pem similarity index 100% rename from acme/acme/testdata/rsa2048_cert.pem rename to acme/tests/testdata/rsa2048_cert.pem diff --git a/acme/acme/testdata/rsa2048_key.pem b/acme/tests/testdata/rsa2048_key.pem similarity index 100% rename from acme/acme/testdata/rsa2048_key.pem rename to acme/tests/testdata/rsa2048_key.pem diff --git a/acme/acme/testdata/rsa256_key.pem b/acme/tests/testdata/rsa256_key.pem similarity index 100% rename from acme/acme/testdata/rsa256_key.pem rename to acme/tests/testdata/rsa256_key.pem diff --git a/acme/acme/testdata/rsa512_key.pem b/acme/tests/testdata/rsa512_key.pem similarity index 100% rename from acme/acme/testdata/rsa512_key.pem rename to acme/tests/testdata/rsa512_key.pem diff --git a/acme/acme/util_test.py b/acme/tests/util_test.py similarity index 100% rename from acme/acme/util_test.py rename to acme/tests/util_test.py diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index ed3e87c6c..000000000 --- a/appveyor.yml +++ /dev/null @@ -1,40 +0,0 @@ -image: Visual Studio 2015 - -environment: - matrix: - - TOXENV: py35 - - TOXENV: py37-cover - -branches: - only: - - master - - /^\d+\.\d+\.x$/ # Version branches like X.X.X - - /^test-.*$/ - -init: - # Since master can receive only commits from PR that have already been tested, following - # condition avoid to launch all jobs except the coverage one for commits pushed to master. - - ps: | - if (-Not $Env:APPVEYOR_PULL_REQUEST_NUMBER -And $Env:APPVEYOR_REPO_BRANCH -Eq 'master' ` - -And -Not ($Env:TOXENV -Like '*-cover')) - { $Env:APPVEYOR_SKIP_FINALIZE_ON_EXIT = 'true'; Exit-AppVeyorBuild } - -install: - # Use Python 3.7 by default - - "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%" - # Check env - - "python --version" - # Upgrade pip to avoid warnings - - "python -m pip install --upgrade pip" - # Ready to install tox and coverage - - "pip install tox codecov" - -build: off - -test_script: - - set TOX_TESTENV_PASSENV=APPVEYOR - # Test env is set by TOXENV env variable - - tox - -on_success: - - if exist .coverage codecov -F windows diff --git a/certbot-apache/MANIFEST.in b/certbot-apache/MANIFEST.in index 3e594a953..fa15504e7 100644 --- a/certbot-apache/MANIFEST.in +++ b/certbot-apache/MANIFEST.in @@ -1,7 +1,8 @@ include LICENSE.txt include README.rst -recursive-include docs * -recursive-include certbot_apache/tests/testdata * -include certbot_apache/centos-options-ssl-apache.conf -include certbot_apache/options-ssl-apache.conf -recursive-include certbot_apache/augeas_lens *.aug +recursive-include tests * +include certbot_apache/_internal/centos-options-ssl-apache.conf +include certbot_apache/_internal/options-ssl-apache.conf +recursive-include certbot_apache/_internal/augeas_lens *.aug +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-apache/certbot_apache/_internal/__init__.py b/certbot-apache/certbot_apache/_internal/__init__.py new file mode 100644 index 000000000..9c195ccc7 --- /dev/null +++ b/certbot-apache/certbot_apache/_internal/__init__.py @@ -0,0 +1 @@ +"""Certbot Apache plugin.""" diff --git a/certbot-apache/certbot_apache/apache_util.py b/certbot-apache/certbot_apache/_internal/apache_util.py similarity index 100% rename from certbot-apache/certbot_apache/apache_util.py rename to certbot-apache/certbot_apache/_internal/apache_util.py diff --git a/certbot-apache/certbot_apache/augeas_lens/README b/certbot-apache/certbot_apache/_internal/augeas_lens/README similarity index 100% rename from certbot-apache/certbot_apache/augeas_lens/README rename to certbot-apache/certbot_apache/_internal/augeas_lens/README diff --git a/certbot-apache/certbot_apache/augeas_lens/httpd.aug b/certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug similarity index 100% rename from certbot-apache/certbot_apache/augeas_lens/httpd.aug rename to certbot-apache/certbot_apache/_internal/augeas_lens/httpd.aug diff --git a/certbot-apache/certbot_apache/centos-options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf similarity index 100% rename from certbot-apache/certbot_apache/centos-options-ssl-apache.conf rename to certbot-apache/certbot_apache/_internal/centos-options-ssl-apache.conf diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/_internal/configurator.py similarity index 91% rename from certbot-apache/certbot_apache/configurator.py rename to certbot-apache/certbot_apache/_internal/configurator.py index 37f8ba289..20c225e04 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/_internal/configurator.py @@ -1,5 +1,6 @@ -"""Apache Configuration based off of Augeas Configurator.""" +"""Apache Configurator.""" # pylint: disable=too-many-lines +from collections import defaultdict import copy import fnmatch import logging @@ -7,34 +8,32 @@ import re import socket import time -from collections import defaultdict - import pkg_resources import six - import zope.component import zope.interface from acme import challenges -from acme.magic_typing import DefaultDict, Dict, List, Set, Union # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util - from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import +from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common -from certbot.plugins.util import path_surgery from certbot.plugins.enhancements import AutoHSTSEnhancement - -from certbot_apache import apache_util -from certbot_apache import augeas_configurator -from certbot_apache import constants -from certbot_apache import display_ops -from certbot_apache import http_01 -from certbot_apache import obj -from certbot_apache import parser +from certbot.plugins.util import path_surgery +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants +from certbot_apache._internal import display_ops +from certbot_apache._internal import http_01 +from certbot_apache._internal import obj +from certbot_apache._internal import parser logger = logging.getLogger(__name__) @@ -70,22 +69,18 @@ logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) -class ApacheConfigurator(augeas_configurator.AugeasConfigurator): - # pylint: disable=too-many-instance-attributes,too-many-public-methods +class ApacheConfigurator(common.Installer): """Apache configurator. - State of Configurator: This code has been been tested and built for Ubuntu - 14.04 Apache 2.4 and it works for Ubuntu 12.04 Apache 2.2 - :ivar config: Configuration. :type config: :class:`~certbot.interfaces.IConfig` :ivar parser: Handles low level parsing - :type parser: :class:`~certbot_apache.parser` + :type parser: :class:`~certbot_apache._internal.parser` :ivar tup version: version of Apache :ivar list vhosts: All vhosts found in the configuration - (:class:`list` of :class:`~certbot_apache.obj.VirtualHost`) + (:class:`list` of :class:`~certbot_apache._internal.obj.VirtualHost`) :ivar dict assoc: Mapping between domains and vhosts @@ -114,7 +109,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): handle_sites=False, challenge_location="/etc/apache2", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def option(self, key): @@ -177,8 +172,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "(Only Ubuntu/Debian currently)") add("ctl", default=DEFAULTS["ctl"], help="Full path to Apache control script") - util.add_deprecated_argument( - add, argument_name="init-script", nargs=1) def __init__(self, *args, **kwargs): """Initialize an Apache Configurator. @@ -201,6 +194,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self._enhanced_vhosts = defaultdict(set) # type: DefaultDict[str, Set[obj.VirtualHost]] # Temporary state for AutoHSTS enhancement self._autohsts = {} # type: Dict[str, Dict[str, Union[int, float]]] + # Reverter save notes + self.save_notes = "" # These will be set in the prepare function self._prepared = False @@ -231,12 +226,6 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :raises .errors.PluginError: If there is any other error """ - # Perform the actual Augeas initialization to be able to react - try: - self.init_augeas() - except ImportError: - raise errors.NoInstallationError("Problem in Augeas installation") - self._prepare_options() # Verify Apache is installed @@ -254,16 +243,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.NotSupportedError( "Apache Version {0} not supported.".format(str(self.version))) - if not self._check_aug_version(): - raise errors.NotSupportedError( - "Apache plugin support requires libaugeas0 and augeas-lenses " - "version 1.2.0 or higher, please make sure you have you have " - "those installed.") - + # Recover from previous crash before Augeas initialization to have the + # correct parse tree from the get go. + self.recovery_routine() + # Perform the actual Augeas initialization to be able to react self.parser = self.get_parser() # Check for errors in parsing files with Augeas - self.check_parsing_errors("httpd.aug") + self.parser.check_parsing_errors("httpd.aug") # Get all of the available vhosts self.vhosts = self.get_virtual_hosts() @@ -282,6 +269,67 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): " Apache configuration?".format(self.option("server_root"))) self._prepared = True + def save(self, title=None, temporary=False): + """Saves all changes to the configuration files. + + This function first checks for save errors, if none are found, + all configuration changes made will be saved. According to the + function parameters. If an exception is raised, a new checkpoint + was not created. + + :param str title: The title of the save. If a title is given, the + configuration will be saved as a new checkpoint and put in a + timestamped directory. + + :param bool temporary: Indicates whether the changes made will + be quickly reversed in the future (ie. challenges) + + """ + save_files = self.parser.unsaved_files() + if save_files: + self.add_to_checkpoint(save_files, + self.save_notes, temporary=temporary) + # Handle the parser specific tasks + self.parser.save(save_files) + if title and not temporary: + self.finalize_checkpoint(title) + + def recovery_routine(self): + """Revert all previously modified files. + + Reverts all modified files that have not been saved as a checkpoint + + :raises .errors.PluginError: If unable to recover the configuration + + """ + super(ApacheConfigurator, self).recovery_routine() + # Reload configuration after these changes take effect if needed + # ie. ApacheParser has been initialized. + if self.parser: + # TODO: wrap into non-implementation specific parser interface + self.parser.aug.load() + + def revert_challenge_config(self): + """Used to cleanup challenge configurations. + + :raises .errors.PluginError: If unable to revert the challenge config. + + """ + self.revert_temporary_config() + self.parser.aug.load() + + def rollback_checkpoints(self, rollback=1): + """Rollback saved checkpoints. + + :param int rollback: Number of checkpoints to revert + + :raises .errors.PluginError: If there is a problem with the input or + the function is unable to correctly revert the configuration + + """ + super(ApacheConfigurator, self).rollback_checkpoints(rollback) + self.parser.aug.load() + def _verify_exe_availability(self, exe): """Checks availability of Apache executable""" if not util.exe_exists(exe): @@ -289,26 +337,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.NoInstallationError( 'Cannot find Apache executable {0}'.format(exe)) - def _check_aug_version(self): - """ Checks that we have recent enough version of libaugeas. - If augeas version is recent enough, it will support case insensitive - regexp matching""" - - self.aug.set("/test/path/testing/arg", "aRgUMeNT") - try: - matches = self.aug.match( - "/test//*[self::arg=~regexp('argument', 'i')]") - except RuntimeError: - self.aug.remove("/test/path") - return False - self.aug.remove("/test/path") - return matches - def get_parser(self): """Initializes the ApacheParser""" # If user provided vhost_root value in command line, use it return parser.ApacheParser( - self.aug, self.option("server_root"), self.conf("vhost-root"), + self.option("server_root"), self.conf("vhost-root"), self.version, configurator=self) def _wildcard_domain(self, domain): @@ -357,7 +390,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): counterpart, should one get created :returns: List of VirtualHosts or None - :rtype: `list` of :class:`~certbot_apache.obj.VirtualHost` + :rtype: `list` of :class:`~certbot_apache._internal.obj.VirtualHost` """ if self._wildcard_domain(domain): @@ -416,7 +449,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): filtered_vhosts[name] = vhost # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + dialog_input = set(filtered_vhosts.values()) # Ask the user which of names to enable, expect list of names back dialog_output = display_ops.select_vhost_multiple(list(dialog_input)) @@ -487,8 +520,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # install SSLCertificateFile, SSLCertificateKeyFile, # and SSLCertificateChainFile directives set_cert_path = cert_path - self.aug.set(path["cert_path"][-1], cert_path) - self.aug.set(path["cert_key"][-1], key_path) + self.parser.aug.set(path["cert_path"][-1], cert_path) + self.parser.aug.set(path["cert_key"][-1], key_path) if chain_path is not None: self.parser.add_dir(vhost.path, "SSLCertificateChainFile", chain_path) @@ -500,8 +533,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): raise errors.PluginError("Please provide the --fullchain-path " "option pointing to your full chain file") set_cert_path = fullchain_path - self.aug.set(path["cert_path"][-1], fullchain_path) - self.aug.set(path["cert_key"][-1], key_path) + self.parser.aug.set(path["cert_path"][-1], fullchain_path) + self.parser.aug.set(path["cert_key"][-1], key_path) # Enable the new vhost if needed if not vhost.enabled: @@ -532,7 +565,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): counterpart, should one get created :returns: vhost associated with name - :rtype: :class:`~certbot_apache.obj.VirtualHost` + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` :raises .errors.PluginError: If no vhost is available or chosen @@ -567,9 +600,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "in the Apache config.", target_name) raise errors.PluginError("No vhost selected") - elif temp: + if temp: return vhost - elif not vhost.ssl: + if not vhost.ssl: addrs = self._get_proposed_addrs(vhost, "443") # TODO: Conflicts is too conservative if not any(vhost.enabled and vhost.conflicts(addrs) for @@ -635,7 +668,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str target_name: domain handled by the desired vhost :param vhosts: vhosts to consider - :type vhosts: `collections.Iterable` of :class:`~certbot_apache.obj.VirtualHost` + :type vhosts: `collections.Iterable` of :class:`~certbot_apache._internal.obj.VirtualHost` :param bool filter_defaults: whether a vhost with a _default_ addr is acceptable @@ -777,7 +810,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Helper function for get_virtual_hosts(). :param host: In progress vhost whose names will be added - :type host: :class:`~certbot_apache.obj.VirtualHost` + :type host: :class:`~certbot_apache._internal.obj.VirtualHost` """ @@ -796,12 +829,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str path: Augeas path to virtual host :returns: newly created vhost - :rtype: :class:`~certbot_apache.obj.VirtualHost` + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` """ addrs = set() try: - args = self.aug.match(path + "/arg") + args = self.parser.aug.match(path + "/arg") except RuntimeError: logger.warning("Encountered a problem while parsing file: %s, skipping", path) return None @@ -819,7 +852,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): is_ssl = True filename = apache_util.get_file_path( - self.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) + self.parser.aug.get("/augeas/files%s/path" % apache_util.get_file_path(path))) if filename is None: return None @@ -837,7 +870,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def get_virtual_hosts(self): """Returns list of virtual hosts found in the Apache configuration. - :returns: List of :class:`~certbot_apache.obj.VirtualHost` + :returns: List of :class:`~certbot_apache._internal.obj.VirtualHost` objects found in configuration :rtype: list @@ -849,7 +882,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Make a list of parser paths because the parser_paths # dictionary may be modified during the loop. for vhost_path in list(self.parser.parser_paths): - paths = self.aug.match( + paths = self.parser.aug.match( ("/files%s//*[label()=~regexp('%s')]" % (vhost_path, parser.case_i("VirtualHost")))) paths = [path for path in paths if @@ -859,7 +892,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if not new_vhost: continue internal_path = apache_util.get_internal_aug_path(new_vhost.path) - realpath = os.path.realpath(new_vhost.filep) + realpath = filesystem.realpath(new_vhost.filep) if realpath not in file_paths: file_paths[realpath] = new_vhost.filep internal_paths[realpath].add(internal_path) @@ -894,7 +927,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): now NameVirtualHosts. If version is earlier than 2.4, check if addr has a NameVirtualHost directive in the Apache config - :param certbot_apache.obj.Addr target_addr: vhost address + :param certbot_apache._internal.obj.Addr target_addr: vhost address :returns: Success :rtype: bool @@ -912,19 +945,18 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Adds NameVirtualHost directive for given address. :param addr: Address that will be added as NameVirtualHost directive - :type addr: :class:`~certbot_apache.obj.Addr` + :type addr: :class:`~certbot_apache._internal.obj.Addr` """ loc = parser.get_aug_path(self.parser.loc["name"]) if addr.get_port() == "443": - path = self.parser.add_dir_to_ifmodssl( + self.parser.add_dir_to_ifmodssl( loc, "NameVirtualHost", [str(addr)]) else: - path = self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) + self.parser.add_dir(loc, "NameVirtualHost", [str(addr)]) - msg = ("Setting %s to be NameBasedVirtualHost\n" - "\tDirective added to %s\n" % (addr, path)) + msg = "Setting {0} to be NameBasedVirtualHost\n".format(addr) logger.debug(msg) self.save_notes += msg @@ -1081,7 +1113,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if "ssl_module" not in self.parser.modules: self.enable_mod("ssl", temp=temp) - def make_vhost_ssl(self, nonssl_vhost): # pylint: disable=too-many-locals + def make_vhost_ssl(self, nonssl_vhost): """Makes an ssl_vhost version of a nonssl_vhost. Duplicates vhost and adds default ssl options @@ -1091,10 +1123,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param nonssl_vhost: Valid VH that doesn't have SSLEngine on - :type nonssl_vhost: :class:`~certbot_apache.obj.VirtualHost` + :type nonssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :returns: SSL vhost - :rtype: :class:`~certbot_apache.obj.VirtualHost` + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` :raises .errors.PluginError: If more than one virtual host is in the file or if plugin is unable to write/read vhost files. @@ -1103,16 +1135,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): avail_fp = nonssl_vhost.filep ssl_fp = self._get_ssl_vhost_path(avail_fp) - orig_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" % + orig_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) self._copy_create_ssl_vhost_skeleton(nonssl_vhost, ssl_fp) # Reload augeas to take into account the new vhost - self.aug.load() + self.parser.aug.load() # Get Vhost augeas path for new vhost - new_matches = self.aug.match("/files%s//* [label()=~regexp('%s')]" % + new_matches = self.parser.aug.match("/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) @@ -1123,7 +1155,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Make Augeas aware of the new vhost self.parser.parse_file(ssl_fp) # Try to search again - new_matches = self.aug.match( + new_matches = self.parser.aug.match( "/files%s//* [label()=~regexp('%s')]" % (self._escape(ssl_fp), parser.case_i("VirtualHost"))) @@ -1185,11 +1217,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")): - fp = os.path.join(os.path.realpath(self.option("vhost_root")), + fp = os.path.join(filesystem.realpath(self.option("vhost_root")), os.path.basename(non_ssl_vh_fp)) else: # Use non-ssl filepath - fp = os.path.realpath(non_ssl_vh_fp) + fp = filesystem.realpath(non_ssl_vh_fp) if fp.endswith(".conf"): return fp[:-(len(".conf"))] + self.option("le_vhost_ext") @@ -1273,8 +1305,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): "vhost for your HTTPS site located at {1} because they have " "the potential to create redirection loops.".format( vhost.filep, ssl_fp), reporter.MEDIUM_PRIORITY) - self.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") - self.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(ssl_fp)), "0") + self.parser.aug.set("/augeas/files%s/mtime" % (self._escape(vhost.filep)), "0") def _sift_rewrite_rules(self, contents): """ Helper function for _copy_create_ssl_vhost_skeleton to prepare the @@ -1332,12 +1364,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): result.append(comment) sift = True - result.append('\n'.join( - ['# ' + l for l in chunk])) - continue + result.append('\n'.join(['# ' + l for l in chunk])) else: result.append('\n'.join(chunk)) - continue return result, sift def _get_vhost_block(self, vhost): @@ -1349,7 +1378,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ try: - span_val = self.aug.span(vhost.path) + span_val = self.parser.aug.span(vhost.path) except ValueError: logger.critical("Error while reading the VirtualHost %s from " "file %s", vhost.name, vhost.filep, exc_info=True) @@ -1384,13 +1413,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _update_ssl_vhosts_addrs(self, vh_path): ssl_addrs = set() - ssl_addr_p = self.aug.match(vh_path + "/arg") + ssl_addr_p = self.parser.aug.match(vh_path + "/arg") for addr in ssl_addr_p: old_addr = obj.Addr.fromstring( str(self.parser.get_arg(addr))) ssl_addr = old_addr.get_addr_obj("443") - self.aug.set(addr, str(ssl_addr)) + self.parser.aug.set(addr, str(ssl_addr)) ssl_addrs.add(ssl_addr) return ssl_addrs @@ -1409,14 +1438,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): vh_path, False)) > 1: directive_path = self.parser.find_dir(directive, None, vh_path, False) - self.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) def _remove_directives(self, vh_path, directives): for directive in directives: while self.parser.find_dir(directive, None, vh_path, False): directive_path = self.parser.find_dir(directive, None, vh_path, False) - self.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) + self.parser.aug.remove(re.sub(r"/\w*$", "", directive_path[0])) def _add_dummy_ssl_directives(self, vh_path): self.parser.add_dir(vh_path, "SSLCertificateFile", @@ -1455,7 +1484,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ matches = self.parser.find_dir( "ServerAlias", start=vh_path, exclude=False) - aliases = (self.aug.get(match) for match in matches) + aliases = (self.parser.aug.get(match) for match in matches) return self.domain_in_names(aliases, target_name) def _add_name_vhost_if_necessary(self, vhost): @@ -1465,7 +1494,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): https://httpd.apache.org/docs/2.2/mod/core.html#namevirtualhost :param vhost: New virtual host that was recently created. - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` """ need_to_save = False @@ -1500,7 +1529,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str id_str: Id string for matching :returns: The matched VirtualHost or None - :rtype: :class:`~certbot_apache.obj.VirtualHost` or None + :rtype: :class:`~certbot_apache._internal.obj.VirtualHost` or None :raises .errors.PluginError: If no VirtualHost is found """ @@ -1517,7 +1546,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): used for keeping track of VirtualHost directive over time. :param vhost: Virtual host to add the id - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :returns: The unique ID or None :rtype: str or None @@ -1539,7 +1568,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): If ID already exists, returns that instead. :param vhost: Virtual host to add or find the id - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :returns: The unique ID for vhost :rtype: str or None @@ -1577,9 +1606,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~certbot.constants.ENHANCEMENTS` + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` documentation for appropriate parameter. :raises .errors.PluginError: If Enhancement is not supported, or if @@ -1617,7 +1646,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Increase the AutoHSTS max-age value :param vhost: Virtual host object to modify - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :param str id_str: The unique ID string of VirtualHost @@ -1638,7 +1667,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): if header_path: pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)' for match in header_path: - if re.search(pat, self.aug.get(match).lower()): + if re.search(pat, self.parser.aug.get(match).lower()): hsts_dirpath = match if not hsts_dirpath: err_msg = ("Certbot was unable to find the existing HSTS header " @@ -1652,7 +1681,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # Our match statement was for string strict-transport-security, but # we need to update the value instead. The next index is for the value hsts_dirpath = hsts_dirpath.replace("arg[3]", "arg[4]") - self.aug.set(hsts_dirpath, hsts_maxage) + self.parser.aug.set(hsts_dirpath, hsts_maxage) note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost " "in {1}\n".format(nextstep_value, vhost.filep)) logger.debug(note_msg) @@ -1701,13 +1730,13 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :param unused_options: Not currently used :type unused_options: Not Available :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, :class:`~certbot_apache.obj.VirtualHost`) + :rtype: (bool, :class:`~certbot_apache._internal.obj.VirtualHost`) """ min_apache_ver = (2, 3, 3) @@ -1734,7 +1763,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # We'll simply delete the directive, so that we'll have a # consistent OCSP cache path. if stapling_cache_aug_path: - self.aug.remove( + self.parser.aug.remove( re.sub(r"/\w*$", "", stapling_cache_aug_path[0])) self.parser.add_dir_to_ifmodssl(ssl_vhost_aug_path, @@ -1757,14 +1786,14 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :param header_substring: string that uniquely identifies a header. e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. :type str :returns: Success, general_vhost (HTTP vhost) - :rtype: (bool, :class:`~certbot_apache.obj.VirtualHost`) + :rtype: (bool, :class:`~certbot_apache._internal.obj.VirtualHost`) :raises .errors.PluginError: If no viable HTTP host can be created or set with header header_substring. @@ -1792,7 +1821,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): contains the string header_substring. :param ssl_vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :param header_substring: string that uniquely identifies a header. e.g: Strict-Transport-Security, Upgrade-Insecure-Requests. @@ -1811,7 +1840,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # "Existing Header directive for virtualhost" pat = '(?:[ "]|^)(%s)(?:[ "]|$)' % (header_substring.lower()) for match in header_path: - if re.search(pat, self.aug.get(match).lower()): + if re.search(pat, self.parser.aug.get(match).lower()): raise errors.PluginEnhancementAlreadyPresent( "Existing %s header" % (header_substring)) @@ -1829,7 +1858,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): .. note:: This function saves the configuration :param ssl_vhost: Destination of traffic, an ssl enabled vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :param unused_options: Not currently used :type unused_options: Not Available @@ -1914,7 +1943,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): delete certbot's old rewrite rules and set the new one instead. :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :raises errors.PluginEnhancementAlreadyPresent: When the exact certbot redirection WriteRule exists in virtual host. @@ -1938,11 +1967,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): constants.REWRITE_HTTPS_ARGS_WITH_END] for dir_path, args_paths in rewrite_args_dict.items(): - arg_vals = [self.aug.get(x) for x in args_paths] + arg_vals = [self.parser.aug.get(x) for x in args_paths] # Search for past redirection rule, delete it, set the new one if arg_vals in constants.OLD_REWRITE_HTTPS_ARGS: - self.aug.remove(dir_path) + self.parser.aug.remove(dir_path) self._set_https_redirection_rewrite_rule(vhost) self.save() raise errors.PluginEnhancementAlreadyPresent( @@ -1956,7 +1985,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Checks if there exists a RewriteRule directive in vhost :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :returns: True if a RewriteRule directive exists. :rtype: bool @@ -1970,7 +1999,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Checks if a RewriteEngine directive is on :param vhost: vhost to check - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` """ rewrite_engine_path_list = self.parser.find_dir("RewriteEngine", "on", @@ -1987,10 +2016,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Creates an http_vhost specifically to redirect for the ssl_vhost. :param ssl_vhost: ssl vhost - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :returns: tuple of the form - (`success`, :class:`~certbot_apache.obj.VirtualHost`) + (`success`, :class:`~certbot_apache._internal.obj.VirtualHost`) :rtype: tuple """ @@ -1998,7 +2027,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): redirect_filepath = self._write_out_redirect(ssl_vhost, text) - self.aug.load() + self.parser.aug.load() # Make a new vhost data structure and add it to the lists new_vhost = self._create_vhost(parser.get_aug_path(self._escape(redirect_filepath))) self.vhosts.append(new_vhost) @@ -2116,7 +2145,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): of this method where available. :param vhost: vhost to enable - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :raises .errors.NotSupportedError: If filesystem layout is not supported. @@ -2310,7 +2339,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): Enable the AutoHSTS enhancement for defined domains :param _unused_lineage: Certificate lineage object, unused - :type _unused_lineage: certbot.storage.RenewableCert + :type _unused_lineage: certbot._internal.storage.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str @@ -2353,7 +2382,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """Do the initial AutoHSTS deployment to a vhost :param ssl_vhost: The VirtualHost object to deploy the AutoHSTS - :type ssl_vhost: :class:`~certbot_apache.obj.VirtualHost` or None + :type ssl_vhost: :class:`~certbot_apache._internal.obj.VirtualHost` or None :raises errors.PluginEnhancementAlreadyPresent: When already enhanced @@ -2435,7 +2464,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): and changes the HSTS max-age to a high value. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot._internal.storage.RenewableCert """ self._autohsts_fetch_state() if not self._autohsts: @@ -2480,4 +2509,4 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): self._autohsts_save_state() -AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member +AutoHSTSEnhancement.register(ApacheConfigurator) diff --git a/certbot-apache/certbot_apache/constants.py b/certbot-apache/certbot_apache/_internal/constants.py similarity index 84% rename from certbot-apache/certbot_apache/constants.py rename to certbot-apache/certbot_apache/_internal/constants.py index 23a7b7afd..47e3be856 100644 --- a/certbot-apache/certbot_apache/constants.py +++ b/certbot-apache/certbot_apache/_internal/constants.py @@ -1,6 +1,7 @@ """Apache plugin constants.""" import pkg_resources +from certbot.compat import os MOD_SSL_CONF_DEST = "options-ssl-apache.conf" """Name of the mod_ssl config file as saved in `IConfig.config_dir`.""" @@ -9,6 +10,7 @@ MOD_SSL_CONF_DEST = "options-ssl-apache.conf" UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" +# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING! ALL_SSL_OPTIONS_HASHES = [ '2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a', '4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a', @@ -18,11 +20,15 @@ ALL_SSL_OPTIONS_HASHES = [ 'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b', '80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791', 'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082', + '717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20', + '0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137', + '86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c', + '06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7', ] """SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC""" AUGEAS_LENS_DIR = pkg_resources.resource_filename( - "certbot_apache", "augeas_lens") + "certbot_apache", os.path.join("_internal", "augeas_lens")) """Path to the Augeas lens directory""" REWRITE_HTTPS_ARGS = [ diff --git a/certbot-apache/certbot_apache/display_ops.py b/certbot-apache/certbot_apache/_internal/display_ops.py similarity index 98% rename from certbot-apache/certbot_apache/display_ops.py rename to certbot-apache/certbot_apache/_internal/display_ops.py index 2639eabd1..1ae32bb47 100644 --- a/certbot-apache/certbot_apache/display_ops.py +++ b/certbot-apache/certbot_apache/_internal/display_ops.py @@ -3,10 +3,10 @@ import logging import zope.component -import certbot.display.util as display_util from certbot import errors from certbot import interfaces from certbot.compat import os +import certbot.display.util as display_util logger = logging.getLogger(__name__) @@ -77,7 +77,7 @@ def _vhost_menu(domain, vhosts): if free_chars < 2: logger.debug("Display size is too small for " - "certbot_apache.display_ops._vhost_menu()") + "certbot_apache._internal.display_ops._vhost_menu()") # This runs the edge off the screen, but it doesn't cause an "error" filename_size = 1 disp_name_size = 1 diff --git a/certbot-apache/certbot_apache/entrypoint.py b/certbot-apache/certbot_apache/_internal/entrypoint.py similarity index 71% rename from certbot-apache/certbot_apache/entrypoint.py rename to certbot-apache/certbot_apache/_internal/entrypoint.py index df7297d3e..d43094976 100644 --- a/certbot-apache/certbot_apache/entrypoint.py +++ b/certbot-apache/certbot_apache/_internal/entrypoint.py @@ -4,18 +4,18 @@ from distutils.version import LooseVersion # pylint: disable=no-name-in-module,import-error from certbot import util - -from certbot_apache import configurator -from certbot_apache import override_arch -from certbot_apache import override_fedora -from certbot_apache import override_darwin -from certbot_apache import override_debian -from certbot_apache import override_centos -from certbot_apache import override_gentoo -from certbot_apache import override_suse +from certbot_apache._internal import configurator +from certbot_apache._internal import override_arch +from certbot_apache._internal import override_centos +from certbot_apache._internal import override_darwin +from certbot_apache._internal import override_debian +from certbot_apache._internal import override_fedora +from certbot_apache._internal import override_gentoo +from certbot_apache._internal import override_suse OVERRIDE_CLASSES = { "arch": override_arch.ArchConfigurator, + "cloudlinux": override_centos.CentOSConfigurator, "darwin": override_darwin.DarwinConfigurator, "debian": override_debian.DebianConfigurator, "ubuntu": override_debian.DebianConfigurator, @@ -23,7 +23,10 @@ OVERRIDE_CLASSES = { "centos linux": override_centos.CentOSConfigurator, "fedora_old": override_centos.CentOSConfigurator, "fedora": override_fedora.FedoraConfigurator, + "linuxmint": override_debian.DebianConfigurator, "ol": override_centos.CentOSConfigurator, + "oracle": override_centos.CentOSConfigurator, + "redhatenterpriseserver": override_centos.CentOSConfigurator, "red hat enterprise linux server": override_centos.CentOSConfigurator, "rhel": override_centos.CentOSConfigurator, "amazon": override_centos.CentOSConfigurator, @@ -31,6 +34,9 @@ OVERRIDE_CLASSES = { "gentoo base system": override_gentoo.GentooConfigurator, "opensuse": override_suse.OpenSUSEConfigurator, "suse": override_suse.OpenSUSEConfigurator, + "sles": override_suse.OpenSUSEConfigurator, + "scientific": override_centos.CentOSConfigurator, + "scientific linux": override_centos.CentOSConfigurator, } diff --git a/certbot-apache/certbot_apache/http_01.py b/certbot-apache/certbot_apache/_internal/http_01.py similarity index 94% rename from certbot-apache/certbot_apache/http_01.py rename to certbot-apache/certbot_apache/_internal/http_01.py index a6c271aa5..c34abc2b4 100644 --- a/certbot-apache/certbot_apache/http_01.py +++ b/certbot-apache/certbot_apache/_internal/http_01.py @@ -1,20 +1,19 @@ """A class that performs HTTP-01 challenges for Apache""" import logging -from acme.magic_typing import List, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins import common - -from certbot_apache.obj import VirtualHost # pylint: disable=unused-import -from certbot_apache.parser import get_aug_path +from certbot_apache._internal.obj import VirtualHost # pylint: disable=unused-import +from certbot_apache._internal.parser import get_aug_path logger = logging.getLogger(__name__) -class ApacheHttp01(common.TLSSNI01): +class ApacheHttp01(common.ChallengePerformer): """Class that performs HTTP-01 challenges within the Apache configurator.""" CONFIG_TEMPLATE22_PRE = """\ @@ -169,8 +168,7 @@ class ApacheHttp01(common.TLSSNI01): def _set_up_challenges(self): if not os.path.isdir(self.challenge_dir): - os.makedirs(self.challenge_dir) - filesystem.chmod(self.challenge_dir, 0o755) + filesystem.makedirs(self.challenge_dir, 0o755) responses = [] for achall in self.achalls: @@ -196,8 +194,8 @@ class ApacheHttp01(common.TLSSNI01): if vhost not in self.moded_vhosts: logger.debug( - "Adding a temporary challenge validation Include for name: %s " + - "in: %s", vhost.name, vhost.filep) + "Adding a temporary challenge validation Include for name: %s in: %s", + vhost.name, vhost.filep) self.configurator.parser.add_dir_beginning( vhost.path, "Include", self.challenge_conf_pre) self.configurator.parser.add_dir( diff --git a/certbot-apache/certbot_apache/obj.py b/certbot-apache/certbot_apache/_internal/obj.py similarity index 97% rename from certbot-apache/certbot_apache/obj.py rename to certbot-apache/certbot_apache/_internal/obj.py index 22abc85cd..8b3aeb376 100644 --- a/certbot-apache/certbot_apache/obj.py +++ b/certbot-apache/certbot_apache/_internal/obj.py @@ -1,7 +1,7 @@ """Module contains classes used by the Apache Configurator.""" import re -from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot.plugins import common @@ -24,7 +24,7 @@ class Addr(common.Addr): return not self.__eq__(other) def __repr__(self): - return "certbot_apache.obj.Addr(" + repr(self.tup) + ")" + return "certbot_apache._internal.obj.Addr(" + repr(self.tup) + ")" def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ if __eq__ or @@ -98,7 +98,7 @@ class Addr(common.Addr): return self.get_addr_obj(port) -class VirtualHost(object): # pylint: disable=too-few-public-methods +class VirtualHost(object): """Represents an Apache Virtualhost. :ivar str filep: file path of VH @@ -126,7 +126,6 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods def __init__(self, filep, path, addrs, ssl, enabled, name=None, aliases=None, modmacro=False, ancestor=None): - # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.path = path diff --git a/certbot-apache/certbot_apache/options-ssl-apache.conf b/certbot-apache/certbot_apache/_internal/options-ssl-apache.conf similarity index 100% rename from certbot-apache/certbot_apache/options-ssl-apache.conf rename to certbot-apache/certbot_apache/_internal/options-ssl-apache.conf diff --git a/certbot-apache/certbot_apache/override_arch.py b/certbot-apache/certbot_apache/_internal/override_arch.py similarity index 84% rename from certbot-apache/certbot_apache/override_arch.py rename to certbot-apache/certbot_apache/_internal/override_arch.py index c5620e9f9..2765bd238 100644 --- a/certbot-apache/certbot_apache/override_arch.py +++ b/certbot-apache/certbot_apache/_internal/override_arch.py @@ -1,11 +1,11 @@ """ Distribution specific override class for Arch Linux """ import pkg_resources - import zope.interface from certbot import interfaces +from certbot.compat import os +from certbot_apache._internal import configurator -from certbot_apache import configurator @zope.interface.provider(interfaces.IPluginFactory) class ArchConfigurator(configurator.ApacheConfigurator): @@ -27,5 +27,5 @@ class ArchConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/httpd/conf", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) diff --git a/certbot-apache/certbot_apache/override_centos.py b/certbot-apache/certbot_apache/_internal/override_centos.py similarity index 95% rename from certbot-apache/certbot_apache/override_centos.py rename to certbot-apache/certbot_apache/_internal/override_centos.py index a42e1ac9c..b3576e083 100644 --- a/certbot-apache/certbot_apache/override_centos.py +++ b/certbot-apache/certbot_apache/_internal/override_centos.py @@ -4,17 +4,15 @@ import logging import pkg_resources import zope.interface +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import os from certbot.errors import MisconfigurationError - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser - +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser logger = logging.getLogger(__name__) @@ -40,7 +38,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "centos-options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) ) def config_test(self): @@ -86,7 +84,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return CentOSParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) def _deploy_cert(self, *args, **kwargs): # pylint: disable=arguments-differ @@ -155,7 +153,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator): for loadmod_path in loadmod_paths: nodir_path = loadmod_path.split("/directive")[0] # Remove the old LoadModule directive - self.aug.remove(loadmod_path) + self.parser.aug.remove(loadmod_path) # Create a new IfModule !mod_ssl.c if not already found on path ssl_ifmod = self.parser.get_ifmod(nodir_path, "!mod_ssl.c", diff --git a/certbot-apache/certbot_apache/override_darwin.py b/certbot-apache/certbot_apache/_internal/override_darwin.py similarity index 84% rename from certbot-apache/certbot_apache/override_darwin.py rename to certbot-apache/certbot_apache/_internal/override_darwin.py index 4e2a6acac..00faff623 100644 --- a/certbot-apache/certbot_apache/override_darwin.py +++ b/certbot-apache/certbot_apache/_internal/override_darwin.py @@ -1,11 +1,11 @@ """ Distribution specific override class for macOS """ import pkg_resources - import zope.interface from certbot import interfaces +from certbot.compat import os +from certbot_apache._internal import configurator -from certbot_apache import configurator @zope.interface.provider(interfaces.IPluginFactory) class DarwinConfigurator(configurator.ApacheConfigurator): @@ -27,5 +27,5 @@ class DarwinConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/apache2/other", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) diff --git a/certbot-apache/certbot_apache/override_debian.py b/certbot-apache/certbot_apache/_internal/override_debian.py similarity index 85% rename from certbot-apache/certbot_apache/override_debian.py rename to certbot-apache/certbot_apache/_internal/override_debian.py index 5706ce760..77ced6a3f 100644 --- a/certbot-apache/certbot_apache/override_debian.py +++ b/certbot-apache/certbot_apache/_internal/override_debian.py @@ -7,10 +7,10 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import filesystem from certbot.compat import os - -from certbot_apache import apache_util -from certbot_apache import configurator +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator logger = logging.getLogger(__name__) @@ -35,7 +35,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): handle_sites=True, challenge_location="/etc/apache2", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def enable_site(self, vhost): @@ -45,7 +45,7 @@ class DebianConfigurator(configurator.ApacheConfigurator): modules are enabled appropriately. :param vhost: vhost to enable - :type vhost: :class:`~certbot_apache.obj.VirtualHost` + :type vhost: :class:`~certbot_apache._internal.obj.VirtualHost` :raises .errors.NotSupportedError: If filesystem layout is not supported. @@ -65,20 +65,19 @@ class DebianConfigurator(configurator.ApacheConfigurator): try: os.symlink(vhost.filep, enabled_path) except OSError as err: - if os.path.islink(enabled_path) and os.path.realpath( + if os.path.islink(enabled_path) and filesystem.realpath( enabled_path) == vhost.filep: # Already in shape vhost.enabled = True return None - else: - logger.warning( - "Could not symlink %s to %s, got error: %s", enabled_path, - vhost.filep, err.strerror) - errstring = ("Encountered error while trying to enable a " + - "newly created VirtualHost located at {0} by " + - "linking to it from {1}") - raise errors.NotSupportedError(errstring.format(vhost.filep, - enabled_path)) + logger.warning( + "Could not symlink %s to %s, got error: %s", enabled_path, + vhost.filep, err.strerror) + errstring = ("Encountered error while trying to enable a " + + "newly created VirtualHost located at {0} by " + + "linking to it from {1}") + raise errors.NotSupportedError(errstring.format(vhost.filep, + enabled_path)) vhost.enabled = True logger.info("Enabling available site: %s", vhost.filep) self.save_notes += "Enabled site %s\n" % vhost.filep diff --git a/certbot-apache/certbot_apache/override_fedora.py b/certbot-apache/certbot_apache/_internal/override_fedora.py similarity index 91% rename from certbot-apache/certbot_apache/override_fedora.py rename to certbot-apache/certbot_apache/_internal/override_fedora.py index cb0bf06d0..a9607a60f 100644 --- a/certbot-apache/certbot_apache/override_fedora.py +++ b/certbot-apache/certbot_apache/_internal/override_fedora.py @@ -5,10 +5,10 @@ import zope.interface from certbot import errors from certbot import interfaces from certbot import util - -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser +from certbot.compat import os +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser @zope.interface.provider(interfaces.IPluginFactory) @@ -33,7 +33,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): challenge_location="/etc/httpd/conf.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( # TODO: eventually newest version of Fedora will need their own config - "certbot_apache", "centos-options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "centos-options-ssl-apache.conf")) ) def config_test(self): @@ -51,7 +51,7 @@ class FedoraConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return FedoraParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) def _try_restart_fedora(self): diff --git a/certbot-apache/certbot_apache/override_gentoo.py b/certbot-apache/certbot_apache/_internal/override_gentoo.py similarity index 88% rename from certbot-apache/certbot_apache/override_gentoo.py rename to certbot-apache/certbot_apache/_internal/override_gentoo.py index 44b3cac95..38f8aebe9 100644 --- a/certbot-apache/certbot_apache/override_gentoo.py +++ b/certbot-apache/certbot_apache/_internal/override_gentoo.py @@ -1,13 +1,13 @@ """ Distribution specific override class for Gentoo Linux """ import pkg_resources - import zope.interface from certbot import interfaces +from certbot.compat import os +from certbot_apache._internal import apache_util +from certbot_apache._internal import configurator +from certbot_apache._internal import parser -from certbot_apache import apache_util -from certbot_apache import configurator -from certbot_apache import parser @zope.interface.provider(interfaces.IPluginFactory) class GentooConfigurator(configurator.ApacheConfigurator): @@ -30,7 +30,7 @@ class GentooConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/apache2/vhosts.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) def _prepare_options(self): @@ -44,7 +44,7 @@ class GentooConfigurator(configurator.ApacheConfigurator): def get_parser(self): """Initializes the ApacheParser""" return GentooParser( - self.aug, self.option("server_root"), self.option("vhost_root"), + self.option("server_root"), self.option("vhost_root"), self.version, configurator=self) diff --git a/certbot-apache/certbot_apache/override_suse.py b/certbot-apache/certbot_apache/_internal/override_suse.py similarity index 84% rename from certbot-apache/certbot_apache/override_suse.py rename to certbot-apache/certbot_apache/_internal/override_suse.py index 3d0043afe..0c9219e6d 100644 --- a/certbot-apache/certbot_apache/override_suse.py +++ b/certbot-apache/certbot_apache/_internal/override_suse.py @@ -1,11 +1,11 @@ """ Distribution specific override class for OpenSUSE """ import pkg_resources - import zope.interface from certbot import interfaces +from certbot.compat import os +from certbot_apache._internal import configurator -from certbot_apache import configurator @zope.interface.provider(interfaces.IPluginFactory) class OpenSUSEConfigurator(configurator.ApacheConfigurator): @@ -27,5 +27,5 @@ class OpenSUSEConfigurator(configurator.ApacheConfigurator): handle_sites=False, challenge_location="/etc/apache2/vhosts.d", MOD_SSL_CONF_SRC=pkg_resources.resource_filename( - "certbot_apache", "options-ssl-apache.conf") + "certbot_apache", os.path.join("_internal", "options-ssl-apache.conf")) ) diff --git a/certbot-apache/certbot_apache/parser.py b/certbot-apache/certbot_apache/_internal/parser.py similarity index 83% rename from certbot-apache/certbot_apache/parser.py rename to certbot-apache/certbot_apache/_internal/parser.py index 95b0930a0..e4073ee16 100644 --- a/certbot-apache/certbot_apache/parser.py +++ b/certbot-apache/certbot_apache/_internal/parser.py @@ -8,16 +8,17 @@ import sys import six -from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os +from certbot_apache._internal import constants logger = logging.getLogger(__name__) class ApacheParser(object): - # pylint: disable=too-many-public-methods """Class handles the fine details of parsing the Apache Configuration. .. todo:: Make parsing general... remove sites-available etc... @@ -32,7 +33,7 @@ class ApacheParser(object): arg_var_interpreter = re.compile(r"\$\{[^ \}]*}") fnmatch_chars = set(["*", "?", "\\", "[", "]"]) - def __init__(self, aug, root, vhostroot=None, version=(2, 4), + def __init__(self, root, vhostroot=None, version=(2, 4), configurator=None): # Note: Order is important here. @@ -41,11 +42,20 @@ class ApacheParser(object): # issues with aug.load() after adding new files / defines to parse tree self.configurator = configurator + # Initialize augeas + self.aug = None + self.init_augeas() + + if not self.check_aug_version(): + raise errors.NotSupportedError( + "Apache plugin support requires libaugeas0 and augeas-lenses " + "version 1.2.0 or higher, please make sure you have you have " + "those installed.") + self.modules = set() # type: Set[str] self.parser_paths = {} # type: Dict[str, List[str]] self.variables = {} # type: Dict[str, str] - self.aug = aug # Find configuration root and make sure augeas can parse it. self.root = os.path.abspath(root) self.loc = {"root": self._find_config_root()} @@ -77,6 +87,146 @@ class ApacheParser(object): if self.find_dir("Define", exclude=False): raise errors.PluginError("Error parsing runtime variables") + def init_augeas(self): + """ Initialize the actual Augeas instance """ + + try: + import augeas + except ImportError: # pragma: no cover + raise errors.NoInstallationError("Problem in Augeas installation") + + self.aug = augeas.Augeas( + # specify a directory to load our preferred lens from + loadpath=constants.AUGEAS_LENS_DIR, + # Do not save backup (we do it ourselves), do not load + # anything by default + flags=(augeas.Augeas.NONE | + augeas.Augeas.NO_MODL_AUTOLOAD | + augeas.Augeas.ENABLE_SPAN)) + + def check_parsing_errors(self, lens): + """Verify Augeas can parse all of the lens files. + + :param str lens: lens to check for errors + + :raises .errors.PluginError: If there has been an error in parsing with + the specified lens. + + """ + error_files = self.aug.match("/augeas//error") + + for path in error_files: + # Check to see if it was an error resulting from the use of + # the httpd lens + lens_path = self.aug.get(path + "/lens") + # As aug.get may return null + if lens_path and lens in lens_path: + msg = ( + "There has been an error in parsing the file {0} on line {1}: " + "{2}".format( + # Strip off /augeas/files and /error + path[13:len(path) - 6], + self.aug.get(path + "/line"), + self.aug.get(path + "/message"))) + raise errors.PluginError(msg) + + def check_aug_version(self): + """ Checks that we have recent enough version of libaugeas. + If augeas version is recent enough, it will support case insensitive + regexp matching""" + + self.aug.set("/test/path/testing/arg", "aRgUMeNT") + try: + matches = self.aug.match( + "/test//*[self::arg=~regexp('argument', 'i')]") + except RuntimeError: + self.aug.remove("/test/path") + return False + self.aug.remove("/test/path") + return matches + + def unsaved_files(self): + """Lists files that have modified Augeas DOM but the changes have not + been written to the filesystem yet, used by `self.save()` and + ApacheConfigurator to check the file state. + + :raises .errors.PluginError: If there was an error in Augeas, in + an attempt to save the configuration, or an error creating a + checkpoint + + :returns: `set` of unsaved files + """ + save_state = self.aug.get("/augeas/save") + self.aug.set("/augeas/save", "noop") + # Existing Errors + ex_errs = self.aug.match("/augeas//error") + try: + # This is a noop save + self.aug.save() + except (RuntimeError, IOError): + self._log_save_errors(ex_errs) + # Erase Save Notes + self.configurator.save_notes = "" + raise errors.PluginError( + "Error saving files, check logs for more info.") + + # Return the original save method + self.aug.set("/augeas/save", save_state) + + # Retrieve list of modified files + # Note: Noop saves can cause the file to be listed twice, I used a + # set to remove this possibility. This is a known augeas 0.10 error. + save_paths = self.aug.match("/augeas/events/saved") + + save_files = set() + if save_paths: + for path in save_paths: + save_files.add(self.aug.get(path)[6:]) + return save_files + + def ensure_augeas_state(self): + """Makes sure that all Augeas dom changes are written to files to avoid + loss of configuration directives when doing additional augeas parsing, + causing a possible augeas.load() resulting dom reset + """ + + if self.unsaved_files(): + self.configurator.save_notes += "(autosave)" + self.configurator.save() + + def save(self, save_files): + """Saves all changes to the configuration files. + + save() is called from ApacheConfigurator to handle the parser specific + tasks of saving. + + :param list save_files: list of strings of file paths that we need to save. + + """ + self.configurator.save_notes = "" + self.aug.save() + + # Force reload if files were modified + # This is needed to recalculate augeas directive span + if save_files: + for sf in save_files: + self.aug.remove("/files/"+sf) + self.aug.load() + + def _log_save_errors(self, ex_errs): + """Log errors due to bad Augeas save. + + :param list ex_errs: Existing errors before save + + """ + # Check for the root of save problems + new_errs = self.aug.match("/augeas//error") + # logger.error("During Save - %s", mod_conf) + logger.error("Unable to save files: %s. Attempted Save Notes: %s", + ", ".join(err[13:len(err) - 6] for err in new_errs + # Only new errors caused by recent save + if err not in ex_errs), self.configurator.save_notes) + def add_include(self, main_config, inc_path): """Add Include for a new configuration file if one does not exist @@ -134,8 +284,8 @@ class ApacheParser(object): mods.add(mod_name) mods.add(os.path.basename(mod_filename)[:-2] + "c") else: - logger.debug("Could not read LoadModule directive from " + - "Augeas path: %s", match_name[6:]) + logger.debug("Could not read LoadModule directive from Augeas path: %s", + match_name[6:]) self.modules.update(mods) def update_runtime_variables(self): @@ -475,7 +625,7 @@ class ApacheParser(object): # https://httpd.apache.org/docs/2.4/mod/core.html#include for match in matches: dir_ = self.aug.get(match).lower() - if dir_ == "include" or dir_ == "includeoptional": + if dir_ in ("include", "includeoptional"): ordered_matches.extend(self.find_dir( directive, arg, self._get_include_path(self.get_arg(match + "/arg")), @@ -515,8 +665,7 @@ class ApacheParser(object): # e.g. strip now, not later if not value: return None - else: - value = value.strip("'\"") + value = value.strip("'\"") variables = ApacheParser.arg_var_interpreter.findall(value) @@ -658,8 +807,7 @@ class ApacheParser(object): use_new, remove_old = self._check_path_actions(filepath) # Ensure that we have the latest Augeas DOM state on disk before # calling aug.load() which reloads the state from disk - if self.configurator: - self.configurator.ensure_augeas_state() + self.ensure_augeas_state() # Test if augeas included file for Httpd.lens # Note: This works for augeas globs, ie. *.conf if use_new: diff --git a/certbot-apache/certbot_apache/augeas_configurator.py b/certbot-apache/certbot_apache/augeas_configurator.py deleted file mode 100644 index a32c65c41..000000000 --- a/certbot-apache/certbot_apache/augeas_configurator.py +++ /dev/null @@ -1,207 +0,0 @@ -"""Class of Augeas Configurators.""" -import logging - - -from certbot import errors -from certbot.plugins import common - -from certbot_apache import constants - -logger = logging.getLogger(__name__) - - -class AugeasConfigurator(common.Installer): - """Base Augeas Configurator class. - - :ivar config: Configuration. - :type config: :class:`~certbot.interfaces.IConfig` - - :ivar aug: Augeas object - :type aug: :class:`augeas.Augeas` - - :ivar str save_notes: Human-readable configuration change notes - :ivar reverter: saves and reverts checkpoints - :type reverter: :class:`certbot.reverter.Reverter` - - """ - def __init__(self, *args, **kwargs): - super(AugeasConfigurator, self).__init__(*args, **kwargs) - - # Placeholder for augeas - self.aug = None - - self.save_notes = "" - - - def init_augeas(self): - """ Initialize the actual Augeas instance """ - import augeas - self.aug = augeas.Augeas( - # specify a directory to load our preferred lens from - loadpath=constants.AUGEAS_LENS_DIR, - # Do not save backup (we do it ourselves), do not load - # anything by default - flags=(augeas.Augeas.NONE | - augeas.Augeas.NO_MODL_AUTOLOAD | - augeas.Augeas.ENABLE_SPAN)) - # See if any temporary changes need to be recovered - # This needs to occur before VirtualHost objects are setup... - # because this will change the underlying configuration and potential - # vhosts - self.recovery_routine() - - def check_parsing_errors(self, lens): - """Verify Augeas can parse all of the lens files. - - :param str lens: lens to check for errors - - :raises .errors.PluginError: If there has been an error in parsing with - the specified lens. - - """ - error_files = self.aug.match("/augeas//error") - - for path in error_files: - # Check to see if it was an error resulting from the use of - # the httpd lens - lens_path = self.aug.get(path + "/lens") - # As aug.get may return null - if lens_path and lens in lens_path: - msg = ( - "There has been an error in parsing the file {0} on line {1}: " - "{2}".format( - # Strip off /augeas/files and /error - path[13:len(path) - 6], - self.aug.get(path + "/line"), - self.aug.get(path + "/message"))) - raise errors.PluginError(msg) - - def ensure_augeas_state(self): - """Makes sure that all Augeas dom changes are written to files to avoid - loss of configuration directives when doing additional augeas parsing, - causing a possible augeas.load() resulting dom reset - """ - - if self.unsaved_files(): - self.save_notes += "(autosave)" - self.save() - - def unsaved_files(self): - """Lists files that have modified Augeas DOM but the changes have not - been written to the filesystem yet, used by `self.save()` and - ApacheConfigurator to check the file state. - - :raises .errors.PluginError: If there was an error in Augeas, in - an attempt to save the configuration, or an error creating a - checkpoint - - :returns: `set` of unsaved files - """ - save_state = self.aug.get("/augeas/save") - self.aug.set("/augeas/save", "noop") - # Existing Errors - ex_errs = self.aug.match("/augeas//error") - try: - # This is a noop save - self.aug.save() - except (RuntimeError, IOError): - self._log_save_errors(ex_errs) - # Erase Save Notes - self.save_notes = "" - raise errors.PluginError( - "Error saving files, check logs for more info.") - - # Return the original save method - self.aug.set("/augeas/save", save_state) - - # Retrieve list of modified files - # Note: Noop saves can cause the file to be listed twice, I used a - # set to remove this possibility. This is a known augeas 0.10 error. - save_paths = self.aug.match("/augeas/events/saved") - - save_files = set() - if save_paths: - for path in save_paths: - save_files.add(self.aug.get(path)[6:]) - return save_files - - def save(self, title=None, temporary=False): - """Saves all changes to the configuration files. - - This function first checks for save errors, if none are found, - all configuration changes made will be saved. According to the - function parameters. If an exception is raised, a new checkpoint - was not created. - - :param str title: The title of the save. If a title is given, the - configuration will be saved as a new checkpoint and put in a - timestamped directory. - - :param bool temporary: Indicates whether the changes made will - be quickly reversed in the future (ie. challenges) - - """ - save_files = self.unsaved_files() - if save_files: - self.add_to_checkpoint(save_files, - self.save_notes, temporary=temporary) - - self.save_notes = "" - self.aug.save() - - # Force reload if files were modified - # This is needed to recalculate augeas directive span - if save_files: - for sf in save_files: - self.aug.remove("/files/"+sf) - self.aug.load() - if title and not temporary: - self.finalize_checkpoint(title) - - def _log_save_errors(self, ex_errs): - """Log errors due to bad Augeas save. - - :param list ex_errs: Existing errors before save - - """ - # Check for the root of save problems - new_errs = self.aug.match("/augeas//error") - # logger.error("During Save - %s", mod_conf) - logger.error("Unable to save files: %s. Attempted Save Notes: %s", - ", ".join(err[13:len(err) - 6] for err in new_errs - # Only new errors caused by recent save - if err not in ex_errs), self.save_notes) - - # Wrapper functions for Reverter class - def recovery_routine(self): - """Revert all previously modified files. - - Reverts all modified files that have not been saved as a checkpoint - - :raises .errors.PluginError: If unable to recover the configuration - - """ - super(AugeasConfigurator, self).recovery_routine() - # Need to reload configuration after these changes take effect - self.aug.load() - - def revert_challenge_config(self): - """Used to cleanup challenge configurations. - - :raises .errors.PluginError: If unable to revert the challenge config. - - """ - self.revert_temporary_config() - self.aug.load() - - def rollback_checkpoints(self, rollback=1): - """Rollback saved checkpoints. - - :param int rollback: Number of checkpoints to revert - - :raises .errors.PluginError: If there is a problem with the input or - the function is unable to correctly revert the configuration - - """ - super(AugeasConfigurator, self).rollback_checkpoints(rollback) - self.aug.load() diff --git a/certbot-apache/certbot_apache/tests/__init__.py b/certbot-apache/certbot_apache/tests/__init__.py deleted file mode 100644 index 7e7d39fa4..000000000 --- a/certbot-apache/certbot_apache/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Apache Tests""" diff --git a/certbot-apache/docs/Makefile b/certbot-apache/docs/Makefile deleted file mode 100644 index 0e611ecec..000000000 --- a/certbot-apache/docs/Makefile +++ /dev/null @@ -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 ' where 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/certbot-apache.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-apache.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/certbot-apache" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-apache" - @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." diff --git a/certbot-apache/docs/_templates/.gitignore b/certbot-apache/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-apache/docs/api/augeas_configurator.rst b/certbot-apache/docs/api/augeas_configurator.rst deleted file mode 100644 index b47ffbc6b..000000000 --- a/certbot-apache/docs/api/augeas_configurator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.augeas_configurator` ---------------------------------------------- - -.. automodule:: certbot_apache.augeas_configurator - :members: diff --git a/certbot-apache/docs/api/configurator.rst b/certbot-apache/docs/api/configurator.rst deleted file mode 100644 index 8ec266d1a..000000000 --- a/certbot-apache/docs/api/configurator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.configurator` --------------------------------------- - -.. automodule:: certbot_apache.configurator - :members: diff --git a/certbot-apache/docs/api/display_ops.rst b/certbot-apache/docs/api/display_ops.rst deleted file mode 100644 index 26d3ed3dc..000000000 --- a/certbot-apache/docs/api/display_ops.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.display_ops` -------------------------------------- - -.. automodule:: certbot_apache.display_ops - :members: diff --git a/certbot-apache/docs/api/obj.rst b/certbot-apache/docs/api/obj.rst deleted file mode 100644 index 82e58df3f..000000000 --- a/certbot-apache/docs/api/obj.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.obj` ------------------------------ - -.. automodule:: certbot_apache.obj - :members: diff --git a/certbot-apache/docs/api/parser.rst b/certbot-apache/docs/api/parser.rst deleted file mode 100644 index 3427735be..000000000 --- a/certbot-apache/docs/api/parser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.parser` --------------------------------- - -.. automodule:: certbot_apache.parser - :members: diff --git a/certbot-apache/docs/api/tls_sni_01.rst b/certbot-apache/docs/api/tls_sni_01.rst deleted file mode 100644 index 3ecd0a365..000000000 --- a/certbot-apache/docs/api/tls_sni_01.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_apache.tls_sni_01` ------------------------------------- - -.. automodule:: certbot_apache.tls_sni_01 - :members: diff --git a/certbot-apache/docs/conf.py b/certbot-apache/docs/conf.py deleted file mode 100644 index 5375fd2b8..000000000 --- a/certbot-apache/docs/conf.py +++ /dev/null @@ -1,318 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-apache documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:39:26 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 sys -from certbot.compat import os -import shlex - -import mock - - -# http://docs.readthedocs.org/en/latest/faq.html#i-get-import-errors-on-libraries-that-depend-on-c-modules -# c.f. #262 -sys.modules.update( - (mod_name, mock.MagicMock()) for mod_name in ['augeas']) - -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', 'private-members'] - -# 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'certbot-apache' -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 = True - - -# -- 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 -# " v 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 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 = 'certbot-apachedoc' - -# -- 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, 'certbot-apache.tex', u'certbot-apache 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, 'certbot-apache', u'certbot-apache 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, 'certbot-apache', u'certbot-apache Documentation', - author, 'certbot-apache', '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), -} diff --git a/certbot-apache/docs/index.rst b/certbot-apache/docs/index.rst deleted file mode 100644 index bfe4d245c..000000000 --- a/certbot-apache/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. certbot-apache documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:39:26 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-apache's documentation! -============================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -.. automodule:: certbot_apache - :members: - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-apache/docs/make.bat b/certbot-apache/docs/make.bat deleted file mode 100644 index 3a7818940..000000000 --- a/certbot-apache/docs/make.bat +++ /dev/null @@ -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 ^` where ^ 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\certbot-apache.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-apache.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 diff --git a/certbot-apache/local-oldest-requirements.txt b/certbot-apache/local-oldest-requirements.txt index da509406e..3fce6f83b 100644 --- a/certbot-apache/local-oldest-requirements.txt +++ b/certbot-apache/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 --e .[dev] +-e certbot[dev] diff --git a/certbot-apache/readthedocs.org.requirements.txt b/certbot-apache/readthedocs.org.requirements.txt deleted file mode 100644 index fe30ab1dc..000000000 --- a/certbot-apache/readthedocs.org.requirements.txt +++ /dev/null @@ -1,12 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[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 .[docs]" must be used instead - --e acme --e . --e certbot-apache[docs] diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 6e6f0277f..204d01620 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -1,16 +1,16 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.36.0.dev0', + 'certbot>=1.0.0.dev0', 'mock', 'python-augeas', 'setuptools', @@ -18,11 +18,6 @@ install_requires = [ 'zope.interface', ] -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - class PyTest(TestCommand): user_options = [] @@ -62,6 +57,7 @@ setup( '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', @@ -73,12 +69,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'certbot.plugins': [ - 'apache = certbot_apache.entrypoint:ENTRYPOINT', + 'apache = certbot_apache._internal.entrypoint:ENTRYPOINT', ], }, test_suite='certbot_apache', diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt b/certbot-apache/tests/apache-conf-files/NEEDED.txt similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/NEEDED.txt rename to certbot-apache/tests/apache-conf-files/NEEDED.txt diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test b/certbot-apache/tests/apache-conf-files/apache-conf-test similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test rename to certbot-apache/tests/apache-conf-files/apache-conf-test diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py b/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py similarity index 90% rename from certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py rename to certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py index 34f32f2d7..68bd6287d 100755 --- a/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py +++ b/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py @@ -15,7 +15,7 @@ SCRIPT_DIRNAME = os.path.dirname(__file__) def main(args=None): if not args: args = sys.argv[1:] - with acme_server.setup_acme_server('pebble', [], False) as acme_xdist: + with acme_server.ACMEServer('pebble', [], False) as acme_xdist: environ = os.environ.copy() environ['SERVER'] = acme_xdist['directory_url'] command = [os.path.join(SCRIPT_DIRNAME, 'apache-conf-test')] diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf b/certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf rename to certbot-apache/tests/apache-conf-files/failing/missing-double-quote-1724.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093.conf rename to certbot-apache/tests/apache-conf-files/failing/multivhost-1093.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf b/certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/failing/multivhost-1093b.conf rename to certbot-apache/tests/apache-conf-files/failing/multivhost-1093b.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf b/certbot-apache/tests/apache-conf-files/passing/1626-1531.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/1626-1531.conf rename to certbot-apache/tests/apache-conf-files/passing/1626-1531.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules b/certbot-apache/tests/apache-conf-files/passing/README.modules similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/README.modules rename to certbot-apache/tests/apache-conf-files/passing/README.modules diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf b/certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/anarcat-1531.conf rename to certbot-apache/tests/apache-conf-files/passing/anarcat-1531.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf b/certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/comment-continuations-2050.conf rename to certbot-apache/tests/apache-conf-files/passing/comment-continuations-2050.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf b/certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf rename to certbot-apache/tests/apache-conf-files/passing/drupal-errordocument-arg-1724.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf b/certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf rename to certbot-apache/tests/apache-conf-files/passing/drupal-htaccess-1531.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf b/certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf rename to certbot-apache/tests/apache-conf-files/passing/escaped-space-arguments-2735.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf b/certbot-apache/tests/apache-conf-files/passing/example-1755.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-1755.conf rename to certbot-apache/tests/apache-conf-files/passing/example-1755.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf b/certbot-apache/tests/apache-conf-files/passing/example-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/example-ssl.conf rename to certbot-apache/tests/apache-conf-files/passing/example-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf b/certbot-apache/tests/apache-conf-files/passing/example.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/example.conf rename to certbot-apache/tests/apache-conf-files/passing/example.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt rename to certbot-apache/tests/apache-conf-files/passing/finalize-1243.apache2.conf.txt diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf b/certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/finalize-1243.conf rename to certbot-apache/tests/apache-conf-files/passing/finalize-1243.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf b/certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/graphite-quote-1934.conf rename to certbot-apache/tests/apache-conf-files/passing/graphite-quote-1934.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143.conf rename to certbot-apache/tests/apache-conf-files/passing/ipv6-1143.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143b.conf rename to certbot-apache/tests/apache-conf-files/passing/ipv6-1143b.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143c.conf rename to certbot-apache/tests/apache-conf-files/passing/ipv6-1143c.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf b/certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/ipv6-1143d.conf rename to certbot-apache/tests/apache-conf-files/passing/ipv6-1143d.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf b/certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/missing-quote-1724.conf rename to certbot-apache/tests/apache-conf-files/passing/missing-quote-1724.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf b/certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/modmacro-1385.conf rename to certbot-apache/tests/apache-conf-files/passing/modmacro-1385.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf b/certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/owncloud-1264.conf rename to certbot-apache/tests/apache-conf-files/passing/owncloud-1264.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf b/certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf rename to certbot-apache/tests/apache-conf-files/passing/rewrite-quote-1960.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf b/certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/roundcube-1222.conf rename to certbot-apache/tests/apache-conf-files/passing/roundcube-1222.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf b/certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-continuations-2525.conf rename to certbot-apache/tests/apache-conf-files/passing/section-continuations-2525.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf b/certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf rename to certbot-apache/tests/apache-conf-files/passing/section-empty-continuations-2731.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf b/certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/semacode-1598.conf rename to certbot-apache/tests/apache-conf-files/passing/semacode-1598.conf diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess b/certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess rename to certbot-apache/tests/apache-conf-files/passing/sslrequire-wordlist-1827.htaccess diff --git a/certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf b/certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf rename to certbot-apache/tests/apache-conf-files/passing/two-blocks-one-line-1693.conf diff --git a/certbot-apache/certbot_apache/tests/autohsts_test.py b/certbot-apache/tests/autohsts_test.py similarity index 81% rename from certbot-apache/certbot_apache/tests/autohsts_test.py rename to certbot-apache/tests/autohsts_test.py index 2d22df289..c9901ecdb 100644 --- a/certbot-apache/certbot_apache/tests/autohsts_test.py +++ b/certbot-apache/tests/autohsts_test.py @@ -1,14 +1,14 @@ -# pylint: disable=too-many-public-methods,too-many-lines -"""Test for certbot_apache.configurator AutoHSTS functionality""" +# pylint: disable=too-many-lines +"""Test for certbot_apache._internal.configurator AutoHSTS functionality""" import re import unittest + import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import +import six # pylint: disable=unused-import # six is used in mock.patch() from certbot import errors -from certbot_apache import constants -from certbot_apache.tests import util +from certbot_apache._internal import constants +import util class AutoHSTSTest(util.ApacheTest): @@ -39,24 +39,24 @@ class AutoHSTSTest(util.ApacheTest): head.replace("arg[3]", "arg[4]")) return None # pragma: no cover - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_autohsts_enable_headers_mod(self, mock_enable, _restart): self.config.parser.modules.discard("headers_module") self.config.parser.modules.discard("mod_header.c") self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) self.assertTrue(mock_enable.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_autohsts_deploy_already_exists(self, _restart): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) self.assertRaises(errors.PluginEnhancementAlreadyPresent, self.config.enable_autohsts, mock.MagicMock(), ["ocspvhost.com"]) - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.prepare") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.prepare") def test_autohsts_increase(self, mock_prepare, _mock_restart): self.config._prepared = False maxage = "\"max-age={0}\"" @@ -74,8 +74,8 @@ class AutoHSTSTest(util.ApacheTest): inc_val) self.assertTrue(mock_prepare.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator._autohsts_increase") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._autohsts_increase") def test_autohsts_increase_noop(self, mock_increase, _restart): maxage = "\"max-age={0}\"" initial_val = maxage.format(constants.AUTOHSTS_STEPS[0]) @@ -89,8 +89,8 @@ class AutoHSTSTest(util.ApacheTest): self.assertFalse(mock_increase.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) def test_autohsts_increase_no_header(self, _restart): self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"]) # Remove the header @@ -102,8 +102,8 @@ class AutoHSTSTest(util.ApacheTest): self.config.update_autohsts, mock.MagicMock()) - @mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.constants.AUTOHSTS_FREQ", 0) + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_autohsts_increase_and_make_permanent(self, _mock_restart): maxage = "\"max-age={0}\"" max_val = maxage.format(constants.AUTOHSTS_PERMANENT) @@ -141,18 +141,18 @@ class AutoHSTSTest(util.ApacheTest): # Make sure that the execution does not continue when no entries in store self.assertFalse(self.config.storage.put.called) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_autohsts_no_ssl_vhost(self, mock_select): mock_select.return_value = self.vh_truth[0] - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.assertRaises(errors.PluginError, self.config.enable_autohsts, mock.MagicMock(), "invalid.example.com") self.assertTrue( "Certbot was not able to find SSL" in mock_log.call_args[0][0]) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.add_vhost_id") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.add_vhost_id") def test_autohsts_dont_enhance_twice(self, mock_id, _restart): mock_id.return_value = "1234567" self.config.enable_autohsts(mock.MagicMock(), @@ -177,7 +177,7 @@ class AutoHSTSTest(util.ApacheTest): self.config._autohsts_fetch_state() self.config._autohsts["orphan_id"] = {"laststep": 999, "timestamp": 0} self.config._autohsts_save_state() - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.config.deploy_autohsts(mock.MagicMock()) self.assertTrue(mock_log.called) self.assertTrue( diff --git a/certbot-apache/certbot_apache/tests/centos6_test.py b/certbot-apache/tests/centos6_test.py similarity index 97% rename from certbot-apache/certbot_apache/tests/centos6_test.py rename to certbot-apache/tests/centos6_test.py index 0d093aca8..15d086600 100644 --- a/certbot-apache/certbot_apache/tests/centos6_test.py +++ b/certbot-apache/tests/centos6_test.py @@ -1,13 +1,12 @@ -"""Test for certbot_apache.configurator for CentOS 6 overrides""" +"""Test for certbot_apache._internal.configurator for CentOS 6 overrides""" import unittest from certbot.compat import os from certbot.errors import MisconfigurationError - -from certbot_apache import obj -from certbot_apache import override_centos -from certbot_apache import parser -from certbot_apache.tests import util +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos +from certbot_apache._internal import parser +import util def get_vh_truth(temp_dir, config_name): @@ -165,10 +164,6 @@ class CentOS6Tests(util.ApacheTest): "LoadModule", "ssl_module", start=self.vh_truth[1].path, exclude=False) self.assertEqual(len(post_loadmods), 1) - - - - def test_loadmod_non_duplicate(self): # the modules/mod_ssl.so exists in ssl.conf sslmod_args = ["ssl_module", "modules/mod_somethingelse.so"] @@ -197,7 +192,7 @@ class CentOS6Tests(util.ApacheTest): exclude=False) for mod in orig_loadmods: noarg_path = mod.rpartition("/")[0] - self.config.aug.remove(noarg_path) + self.config.parser.aug.remove(noarg_path) self.config.save() self.config.deploy_cert( "random.demo", "example/cert.pem", "example/key.pem", diff --git a/certbot-apache/certbot_apache/tests/centos_test.py b/certbot-apache/tests/centos_test.py similarity index 89% rename from certbot-apache/certbot_apache/tests/centos_test.py rename to certbot-apache/tests/centos_test.py index 5d16c2b55..8959d73b8 100644 --- a/certbot-apache/certbot_apache/tests/centos_test.py +++ b/certbot-apache/tests/centos_test.py @@ -1,14 +1,14 @@ -"""Test for certbot_apache.configurator for Centos overrides""" +"""Test for certbot_apache._internal.configurator for Centos overrides""" import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os - -from certbot_apache import obj -from certbot_apache import override_centos -from certbot_apache.tests import util +from certbot_apache._internal import obj +from certbot_apache._internal import override_centos +import util def get_vh_truth(temp_dir, config_name): @@ -54,7 +54,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test() def test_non_fedora_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: mock_test.side_effect = errors.MisconfigurationError with mock.patch("certbot.util.get_os_info") as mock_info: @@ -63,7 +63,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test) def test_fedora_restart_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] @@ -73,7 +73,7 @@ class FedoraRestartTest(util.ApacheTest): self._run_fedora_test) def test_fedora_restart(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: with mock.patch("certbot.util.run_script") as mock_run: # First call raises error, second doesn't @@ -106,7 +106,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_centos.CentOSParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -155,12 +155,12 @@ class MultipleVhostsTestCentOS(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = os.path.realpath( + self.config.parser.sysconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../sysconfig/httpd")) self.config.parser.variables = {} @@ -176,13 +176,13 @@ class MultipleVhostsTestCentOS(util.ApacheTest): self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() self.assertEqual(mock_run_script.call_count, 3) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, diff --git a/certbot-apache/certbot_apache/tests/complex_parsing_test.py b/certbot-apache/tests/complex_parsing_test.py similarity index 97% rename from certbot-apache/certbot_apache/tests/complex_parsing_test.py rename to certbot-apache/tests/complex_parsing_test.py index 8712eb5bf..8b795b0b6 100644 --- a/certbot-apache/certbot_apache/tests/complex_parsing_test.py +++ b/certbot-apache/tests/complex_parsing_test.py @@ -1,11 +1,10 @@ -"""Tests for certbot_apache.parser.""" +"""Tests for certbot_apache._internal.parser.""" import shutil import unittest from certbot import errors from certbot.compat import os - -from certbot_apache.tests import util +import util class ComplexParserTest(util.ParserTest): @@ -88,7 +87,7 @@ class ComplexParserTest(util.ParserTest): def verify_fnmatch(self, arg, hit=True): """Test if Include was correctly parsed.""" - from certbot_apache import parser + from certbot_apache._internal import parser self.parser.add_dir(parser.get_aug_path(self.parser.loc["default"]), "Include", [arg]) if hit: diff --git a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py b/certbot-apache/tests/configurator_reverter_test.py similarity index 61% rename from certbot-apache/certbot_apache/tests/augeas_configurator_test.py rename to certbot-apache/tests/configurator_reverter_test.py index bfe3b7f16..ad8e73347 100644 --- a/certbot-apache/certbot_apache/tests/augeas_configurator_test.py +++ b/certbot-apache/tests/configurator_reverter_test.py @@ -1,21 +1,19 @@ -"""Test for certbot_apache.augeas_configurator.""" +"""Test for certbot_apache._internal.configurator implementations of reverter""" import shutil import unittest import mock from certbot import errors -from certbot.compat import os - -from certbot_apache.tests import util +import util -class AugeasConfiguratorTest(util.ApacheTest): - """Test for Augeas Configurator base class.""" +class ConfiguratorReverterTest(util.ApacheTest): + """Test for ApacheConfigurator reverter methods""" def setUp(self): # pylint: disable=arguments-differ - super(AugeasConfiguratorTest, self).setUp() + super(ConfiguratorReverterTest, self).setUp() self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir) @@ -28,20 +26,6 @@ class AugeasConfiguratorTest(util.ApacheTest): shutil.rmtree(self.work_dir) shutil.rmtree(self.temp_dir) - def test_bad_parse(self): - # pylint: disable=protected-access - self.config.parser.parse_file(os.path.join( - self.config.parser.root, "conf-available", "bad_conf_file.conf")) - self.assertRaises( - errors.PluginError, self.config.check_parsing_errors, "httpd.aug") - - def test_bad_save(self): - mock_save = mock.Mock() - mock_save.side_effect = IOError - self.config.aug.save = mock_save - - self.assertRaises(errors.PluginError, self.config.save) - def test_bad_save_checkpoint(self): self.config.reverter.add_to_checkpoint = mock.Mock( side_effect=errors.ReverterError) @@ -63,23 +47,9 @@ class AugeasConfiguratorTest(util.ApacheTest): self.assertTrue(mock_finalize.is_called) - def test_recovery_routine(self): - mock_load = mock.Mock() - self.config.aug.load = mock_load - - self.config.recovery_routine() - self.assertEqual(mock_load.call_count, 1) - - def test_recovery_routine_error(self): - self.config.reverter.recovery_routine = mock.Mock( - side_effect=errors.ReverterError) - - self.assertRaises( - errors.PluginError, self.config.recovery_routine) - def test_revert_challenge_config(self): mock_load = mock.Mock() - self.config.aug.load = mock_load + self.config.parser.aug.load = mock_load self.config.revert_challenge_config() self.assertEqual(mock_load.call_count, 1) @@ -93,7 +63,7 @@ class AugeasConfiguratorTest(util.ApacheTest): def test_rollback_checkpoints(self): mock_load = mock.Mock() - self.config.aug.load = mock_load + self.config.parser.aug.load = mock_load self.config.rollback_checkpoints() self.assertEqual(mock_load.call_count, 1) @@ -103,13 +73,11 @@ class AugeasConfiguratorTest(util.ApacheTest): side_effect=errors.ReverterError) self.assertRaises(errors.PluginError, self.config.rollback_checkpoints) - def test_view_config_changes(self): - self.config.view_config_changes() - - def test_view_config_changes_error(self): - self.config.reverter.view_config_changes = mock.Mock( - side_effect=errors.ReverterError) - self.assertRaises(errors.PluginError, self.config.view_config_changes) + def test_recovery_routine_reload(self): + mock_load = mock.Mock() + self.config.parser.aug.load = mock_load + self.config.recovery_routine() + self.assertEqual(mock_load.call_count, 1) if __name__ == "__main__": diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/tests/configurator_test.py similarity index 91% rename from certbot-apache/certbot_apache/tests/configurator_test.py rename to certbot-apache/tests/configurator_test.py index 906232596..9fab5ea5d 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/tests/configurator_test.py @@ -1,5 +1,5 @@ -# pylint: disable=too-many-public-methods,too-many-lines -"""Test for certbot_apache.configurator.""" +# pylint: disable=too-many-lines +"""Test for certbot_apache._internal.configurator.""" import copy import shutil import socket @@ -7,24 +7,21 @@ import tempfile import unittest import mock -# six is used in mock.patch() -import six # pylint: disable=unused-import +import six # pylint: disable=unused-import # six is used in mock.patch() from acme import challenges - from certbot import achallenges from certbot import crypto_util from certbot import errors -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as certbot_util - -from certbot_apache import apache_util -from certbot_apache import constants -from certbot_apache import obj -from certbot_apache import parser -from certbot_apache.tests import util +from certbot_apache._internal import apache_util +from certbot_apache._internal import constants +from certbot_apache._internal import obj +from certbot_apache._internal import parser +import util class MultipleVhostsTest(util.ApacheTest): @@ -45,33 +42,22 @@ class MultipleVhostsTest(util.ApacheTest): def mocked_deploy_cert(*args, **kwargs): """a helper to mock a deployed cert""" - g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" with mock.patch(g_mod): config.real_deploy_cert(*args, **kwargs) self.config.deploy_cert = mocked_deploy_cert return self.config - @mock.patch("certbot_apache.configurator.ApacheConfigurator.init_augeas") - @mock.patch("certbot_apache.configurator.path_surgery") - def test_prepare_no_install(self, mock_surgery, _init_augeas): + @mock.patch("certbot_apache._internal.configurator.path_surgery") + def test_prepare_no_install(self, mock_surgery): silly_path = {"PATH": "/tmp/nothingness2342"} mock_surgery.return_value = False with mock.patch.dict('os.environ', silly_path): self.assertRaises(errors.NoInstallationError, self.config.prepare) self.assertEqual(mock_surgery.call_count, 1) - @mock.patch("certbot_apache.augeas_configurator.AugeasConfigurator.init_augeas") - def test_prepare_no_augeas(self, mock_init_augeas): - """ Test augeas initialization ImportError """ - def side_effect_error(): - """ Side effect error for the test """ - raise ImportError - mock_init_augeas.side_effect = side_effect_error - self.assertRaises( - errors.NoInstallationError, self.config.prepare) - - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") def test_prepare_version(self, mock_exe_exists, _): mock_exe_exists.return_value = True self.config.version = None @@ -81,24 +67,14 @@ class MultipleVhostsTest(util.ApacheTest): self.assertRaises( errors.NotSupportedError, self.config.prepare) - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") - def test_prepare_old_aug(self, mock_exe_exists, _): - mock_exe_exists.return_value = True - self.config.config_test = mock.Mock() - # pylint: disable=protected-access - self.config._check_aug_version = mock.Mock(return_value=False) - self.assertRaises( - errors.NotSupportedError, self.config.prepare) - def test_prepare_locked(self): server_root = self.config.conf("server-root") self.config.config_test = mock.Mock() os.remove(os.path.join(server_root, ".certbot.lock")) certbot_util.lock_and_call(self._test_prepare_locked, server_root) - @mock.patch("certbot_apache.parser.ApacheParser") - @mock.patch("certbot_apache.configurator.util.exe_exists") + @mock.patch("certbot_apache._internal.parser.ApacheParser") + @mock.patch("certbot_apache._internal.configurator.util.exe_exists") def _test_prepare_locked(self, unused_parser, unused_exe_exists): try: self.config.prepare() @@ -110,13 +86,13 @@ class MultipleVhostsTest(util.ApacheTest): self.fail("Exception wasn't raised!") def test_add_parser_arguments(self): # pylint: disable=no-self-use - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.configurator import ApacheConfigurator # Weak test.. ApacheConfigurator.add_parser_arguments(mock.MagicMock()) def test_docs_parser_arguments(self): os.environ["CERTBOT_DOCS"] = "1" - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.configurator import ApacheConfigurator mock_add = mock.MagicMock() ApacheConfigurator.add_parser_arguments(mock_add) parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", @@ -129,13 +105,9 @@ class MultipleVhostsTest(util.ApacheTest): exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] # Special cases exp["vhost-root"] = None - exp["init-script"] = None found = set() for call in mock_add.call_args_list: - # init-script is a special case: deprecated argument - if call[0][0] != "init-script": - self.assertEqual(exp[call[0][0]], call[1]['default']) found.add(call[0][0]) # Make sure that all (and only) the expected values exist @@ -146,13 +118,13 @@ class MultipleVhostsTest(util.ApacheTest): del os.environ["CERTBOT_DOCS"] def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use - from certbot_apache.entrypoint import OVERRIDE_CLASSES + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES for cls in OVERRIDE_CLASSES.values(): cls.add_parser_arguments(mock.MagicMock()) def test_all_configurators_defaults_defined(self): - from certbot_apache.entrypoint import OVERRIDE_CLASSES - from certbot_apache.configurator import ApacheConfigurator + from certbot_apache._internal.entrypoint import OVERRIDE_CLASSES + from certbot_apache._internal.configurator import ApacheConfigurator parameters = set(ApacheConfigurator.OS_DEFAULTS.keys()) for cls in OVERRIDE_CLASSES.values(): self.assertTrue(parameters.issubset(set(cls.OS_DEFAULTS.keys()))) @@ -174,7 +146,7 @@ class MultipleVhostsTest(util.ApacheTest): )) @certbot_util.patch_get_utility() - @mock.patch("certbot_apache.configurator.socket.gethostbyaddr") + @mock.patch("certbot_apache._internal.configurator.socket.gethostbyaddr") def test_get_all_names_addrs(self, mock_gethost, mock_getutility): mock_gethost.side_effect = [("google.com", "", ""), socket.error] mock_utility = mock_getutility() @@ -200,7 +172,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.config._create_vhost("nonexistent"), None) # pylint: disable=protected-access def test_get_aug_internal_path(self): - from certbot_apache.apache_util import get_internal_aug_path + from certbot_apache._internal.apache_util import get_internal_aug_path internal_paths = [ "Virtualhost", "IfModule/VirtualHost", "VirtualHost", "VirtualHost", "Macro/VirtualHost", "IfModule/VirtualHost", "VirtualHost", @@ -245,26 +217,26 @@ class MultipleVhostsTest(util.ApacheTest): # Handle case of non-debian layout get_virtual_hosts with mock.patch( - "certbot_apache.configurator.ApacheConfigurator.conf" + "certbot_apache._internal.configurator.ApacheConfigurator.conf" ) as mock_conf: mock_conf.return_value = False vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 12) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_none_avail(self, mock_select): mock_select.return_value = None self.assertRaises( errors.PluginError, self.config.choose_vhost, "none.com") - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_ssl(self, mock_select): mock_select.return_value = self.vh_truth[1] self.assertEqual( self.vh_truth[1], self.config.choose_vhost("none.com")) - @mock.patch("certbot_apache.display_ops.select_vhost") - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_select_vhost_non_ssl(self, mock_conf, mock_select): mock_select.return_value = self.vh_truth[0] mock_conf.return_value = False @@ -277,8 +249,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(self.vh_truth[0].ssl) self.assertTrue(chosen_vhost.ssl) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._find_best_vhost") - @mock.patch("certbot_apache.parser.ApacheParser.add_dir") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._find_best_vhost") + @mock.patch("certbot_apache._internal.parser.ApacheParser.add_dir") def test_choose_vhost_and_servername_addition(self, mock_add, mock_find): ret_vh = self.vh_truth[8] ret_vh.enabled = False @@ -286,13 +258,13 @@ class MultipleVhostsTest(util.ApacheTest): self.config.choose_vhost("whatever.com") self.assertTrue(mock_add.called) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_with_temp(self, mock_select): mock_select.return_value = self.vh_truth[0] chosen_vhost = self.config.choose_vhost("none.com", create_if_no_ssl=False) self.assertEqual(self.vh_truth[0], chosen_vhost) - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") def test_choose_vhost_select_vhost_conflicting_non_ssl(self, mock_select): mock_select.return_value = self.vh_truth[3] conflicting_vhost = obj.VirtualHost( @@ -675,7 +647,7 @@ class MultipleVhostsTest(util.ApacheTest): # span excludes the closing tag in older versions # of Augeas return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1142] - with mock.patch.object(self.config.aug, 'span') as mock_span: + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: mock_span.return_value = return_value self.test_make_vhost_ssl() @@ -683,7 +655,7 @@ class MultipleVhostsTest(util.ApacheTest): # span includes the closing tag in newer versions # of Augeas return_value = [self.vh_truth[0].filep, 1, 12, 0, 0, 0, 1157] - with mock.patch.object(self.config.aug, 'span') as mock_span: + with mock.patch.object(self.config.parser.aug, 'span') as mock_span: mock_span.return_value = return_value self.test_make_vhost_ssl() @@ -696,8 +668,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_make_vhost_ssl_nonexistent_vhost_path(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1]) self.assertEqual(os.path.dirname(ssl_vhost.filep), - os.path.dirname(os.path.realpath( - self.vh_truth[1].filep))) + os.path.dirname(filesystem.realpath(self.vh_truth[1].filep))) def test_make_vhost_ssl(self): ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0]) @@ -810,8 +781,8 @@ class MultipleVhostsTest(util.ApacheTest): self.config._add_name_vhost_if_necessary(self.vh_truth[0]) self.assertEqual(self.config.add_name_vhost.call_count, 2) - @mock.patch("certbot_apache.configurator.http_01.ApacheHttp01.perform") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.configurator.http_01.ApacheHttp01.perform") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") def test_perform(self, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -827,8 +798,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(mock_restart.call_count, 1) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_cleanup(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -843,8 +814,8 @@ class MultipleVhostsTest(util.ApacheTest): else: self.assertFalse(mock_restart.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.restart") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.restart") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_cleanup_no_errors(self, mock_cfg, mock_restart): mock_cfg.return_value = "" _, achalls = self.get_key_and_achalls() @@ -881,11 +852,11 @@ class MultipleVhostsTest(util.ApacheTest): mock_script.side_effect = errors.SubprocessError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_restart(self, _): self.config.restart() - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_restart_bad_process(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError] @@ -928,8 +899,8 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(self.vh_truth[0].name, res.name) self.assertEqual(self.vh_truth[0].aliases, res.aliases) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._get_http_vhost") - @mock.patch("certbot_apache.display_ops.select_vhost") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._get_http_vhost") + @mock.patch("certbot_apache._internal.display_ops.select_vhost") @mock.patch("certbot.util.exe_exists") def test_enhance_unknown_vhost(self, mock_exe, mock_sel_vhost, mock_get): self.config.parser.modules.add("rewrite_module") @@ -952,7 +923,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.enhance, "certbot.demo", "unknown_enhancement") def test_enhance_no_ssl_vhost(self): - with mock.patch("certbot_apache.configurator.logger.warning") as mock_log: + with mock.patch("certbot_apache._internal.configurator.logger.warning") as mock_log: self.assertRaises(errors.PluginError, self.config.enhance, "certbot.demo", "redirect") # Check that correct logger.warning was printed @@ -1232,7 +1203,7 @@ class MultipleVhostsTest(util.ApacheTest): except errors.PluginEnhancementAlreadyPresent: args_paths = self.config.parser.find_dir( "RewriteRule", None, http_vhost.path, False) - arg_vals = [self.config.aug.get(x) for x in args_paths] + arg_vals = [self.config.parser.aug.get(x) for x in args_paths] self.assertEqual(arg_vals, constants.REWRITE_HTTPS_ARGS) @@ -1257,7 +1228,7 @@ class MultipleVhostsTest(util.ApacheTest): self.config.choose_vhost("red.blue.purple.com") self.config.enhance("red.blue.purple.com", "redirect") - verify_no_redirect = ("certbot_apache.configurator." + verify_no_redirect = ("certbot_apache._internal.configurator." "ApacheConfigurator._verify_no_certbot_redirect") with mock.patch(verify_no_redirect) as mock_verify: self.config.enhance("green.blue.purple.com", "redirect") @@ -1318,13 +1289,13 @@ class MultipleVhostsTest(util.ApacheTest): account_key = self.rsa512jwk achall1 = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( - challenges.TLSSNI01( + challenges.HTTP01( token=b"jIq_Xy1mXGN37tb4L6Xj_es58fW571ZNyXekdZzhh7Q"), "pending"), domain="encryption-example.demo", account_key=account_key) achall2 = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( - challenges.TLSSNI01( + challenges.HTTP01( token=b"uqnaPzxtrndteOqtrXb0Asl5gOJfWAnnx6QJyvcmlDU"), "pending"), domain="certbot.demo", account_key=account_key) @@ -1335,15 +1306,6 @@ class MultipleVhostsTest(util.ApacheTest): return account_key, (achall1, achall2, achall3) - def test_aug_version(self): - mock_match = mock.Mock(return_value=["something"]) - self.config.aug.match = mock_match - # pylint: disable=protected-access - self.assertEqual(self.config._check_aug_version(), - ["something"]) - self.config.aug.match.side_effect = RuntimeError - self.assertFalse(self.config._check_aug_version()) - def test_enable_site_nondebian(self): inc_path = "/path/to/wherever" vhost = self.vh_truth[0] @@ -1366,10 +1328,10 @@ class MultipleVhostsTest(util.ApacheTest): self.config.parser.modules.add("ssl_module") self.config.parser.modules.add("mod_ssl.c") self.config.parser.modules.add("socache_shmcb_module") - tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot")) + tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot")) filesystem.chmod(tmp_path, 0o755) - mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path" - mock_a = "certbot_apache.parser.ApacheParser.add_include" + mock_p = "certbot_apache._internal.configurator.ApacheConfigurator._get_ssl_vhost_path" + mock_a = "certbot_apache._internal.parser.ApacheParser.add_include" with mock.patch(mock_p) as mock_path: mock_path.return_value = os.path.join(tmp_path, "whatever.conf") @@ -1382,7 +1344,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertTrue(mock_add.called) shutil.rmtree(tmp_path) - @mock.patch("certbot_apache.parser.ApacheParser.parsed_in_original") + @mock.patch("certbot_apache._internal.parser.ApacheParser.parsed_in_original") def test_choose_vhost_and_servername_addition_parsed(self, mock_parsed): ret_vh = self.vh_truth[8] ret_vh.enabled = True @@ -1404,7 +1366,7 @@ class MultipleVhostsTest(util.ApacheTest): def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[3]] vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", @@ -1420,10 +1382,10 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(vhs[0] == self.vh_truth[3]) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.make_vhost_ssl") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") def test_choose_vhosts_wildcard_no_ssl(self, mock_makessl): # pylint: disable=protected-access - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[1]] vhs = self.config._choose_vhosts_wildcard("*.certbot.demo", @@ -1431,13 +1393,13 @@ class MultipleVhostsTest(util.ApacheTest): self.assertFalse(mock_makessl.called) self.assertEqual(vhs[0], self.vh_truth[1]) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._vhosts_for_wildcard") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.make_vhost_ssl") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._vhosts_for_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.make_vhost_ssl") def test_choose_vhosts_wildcard_already_ssl(self, mock_makessl, mock_vh_for_w): # pylint: disable=protected-access # Already SSL vhost mock_vh_for_w.return_value = [self.vh_truth[7]] - mock_path = "certbot_apache.display_ops.select_vhost_multiple" + mock_path = "certbot_apache._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [self.vh_truth[7]] vhs = self.config._choose_vhosts_wildcard("whatever", @@ -1458,7 +1420,7 @@ class MultipleVhostsTest(util.ApacheTest): mock_choose_vhosts = mock.MagicMock() mock_choose_vhosts.return_value = [self.vh_truth[7]] self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_apache.configurator.ApacheConfigurator._deploy_cert" + mock_d = "certbot_apache._internal.configurator.ApacheConfigurator._deploy_cert" with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.wildcard.example.org", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @@ -1466,7 +1428,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(len(mock_dep.call_args_list), 1) self.assertEqual(self.vh_truth[7], mock_dep.call_args_list[0][0][0]) - @mock.patch("certbot_apache.display_ops.select_vhost_multiple") + @mock.patch("certbot_apache._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] @@ -1475,7 +1437,7 @@ class MultipleVhostsTest(util.ApacheTest): "*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - @mock.patch("certbot_apache.configurator.ApacheConfigurator._choose_vhosts_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") def test_enhance_wildcard_after_install(self, mock_choose): # pylint: disable=protected-access self.config.parser.modules.add("mod_ssl.c") @@ -1486,7 +1448,7 @@ class MultipleVhostsTest(util.ApacheTest): "Upgrade-Insecure-Requests") self.assertFalse(mock_choose.called) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._choose_vhosts_wildcard") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._choose_vhosts_wildcard") def test_enhance_wildcard_no_install(self, mock_choose): self.vh_truth[3].ssl = True mock_choose.return_value = [self.vh_truth[3]] @@ -1512,7 +1474,7 @@ class MultipleVhostsTest(util.ApacheTest): self.assertEqual(first_id, second_id) def test_realpath_replaces_symlink(self): - orig_match = self.config.aug.match + orig_match = self.config.parser.aug.match mock_vhost = copy.deepcopy(self.vh_truth[0]) mock_vhost.filep = mock_vhost.filep.replace('sites-enabled', u'sites-available') mock_vhost.path = mock_vhost.path.replace('sites-enabled', 'sites-available') @@ -1526,7 +1488,7 @@ class MultipleVhostsTest(util.ApacheTest): return orig_match(aug_expr) self.config.parser.parser_paths = ["/mocked/path"] - self.config.aug.match = mock_match + self.config.parser.aug.match = mock_match vhs = self.config.get_virtual_hosts() self.assertEqual(len(vhs), 2) self.assertTrue(vhs[0] == self.vh_truth[1]) @@ -1552,8 +1514,8 @@ class AugeasVhostsTest(util.ApacheTest): self.work_dir) def test_choosevhost_with_illegal_name(self): - self.config.aug = mock.MagicMock() - self.config.aug.match.side_effect = RuntimeError + self.config.parser.aug = mock.MagicMock() + self.config.parser.aug.match.side_effect = RuntimeError path = "debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf" chosen_vhost = self.config._create_vhost(path) self.assertEqual(None, chosen_vhost) @@ -1563,7 +1525,7 @@ class AugeasVhostsTest(util.ApacheTest): chosen_vhost = self.config._create_vhost(path) self.assertTrue(chosen_vhost is None or chosen_vhost.path == path) - @mock.patch("certbot_apache.configurator.ApacheConfigurator._create_vhost") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator._create_vhost") def test_get_vhost_continue(self, mock_vhost): mock_vhost.return_value = None vhs = self.config.get_virtual_hosts() @@ -1575,18 +1537,18 @@ class AugeasVhostsTest(util.ApacheTest): for name in names: self.assertFalse(name in self.config.choose_vhost(name).aliases) - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_without_matching_wildcard(self, mock_conflicts): mock_conflicts.return_value = False - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" with mock.patch(mock_path, lambda _, vhosts: vhosts[0]): for name in ("a.example.net", "other.example.net"): self.assertTrue(name in self.config.choose_vhost(name).aliases) - @mock.patch("certbot_apache.obj.VirtualHost.conflicts") + @mock.patch("certbot_apache._internal.obj.VirtualHost.conflicts") def test_choose_vhost_wildcard_not_found(self, mock_conflicts): mock_conflicts.return_value = False - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" names = ( "abc.example.net", "not.there.tld", "aa.wildcard.tld" ) @@ -1598,7 +1560,7 @@ class AugeasVhostsTest(util.ApacheTest): self.assertEqual(mock_select.call_count - orig_cc, 1) def test_choose_vhost_wildcard_found(self): - mock_path = "certbot_apache.display_ops.select_vhost" + mock_path = "certbot_apache._internal.display_ops.select_vhost" names = ( "ab.example.net", "a.wildcard.tld", "yetanother.example.net" ) @@ -1652,7 +1614,7 @@ class MultiVhostsTest(util.ApacheTest): self.assertEqual(self.config.is_name_vhost(self.vh_truth[1]), self.config.is_name_vhost(ssl_vhost)) - mock_path = "certbot_apache.configurator.ApacheConfigurator._get_new_vh_path" + mock_path = "certbot_apache._internal.configurator.ApacheConfigurator._get_new_vh_path" with mock.patch(mock_path) as mock_getpath: mock_getpath.return_value = None self.assertRaises(errors.PluginError, self.config.make_vhost_ssl, @@ -1758,7 +1720,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self._assert_current_file() def test_prev_file_updates_to_current(self): - from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES ALL_SSL_OPTIONS_HASHES.insert(0, "test_hash_does_not_match") with mock.patch('certbot.crypto_util.sha256sum') as mock_sha256: mock_sha256.return_value = ALL_SSL_OPTIONS_HASHES[0] @@ -1797,7 +1759,7 @@ class InstallSslOptionsConfTest(util.ApacheTest): self.assertFalse(mock_logger.warning.called) def test_current_file_hash_in_all_hashes(self): - from certbot_apache.constants import ALL_SSL_OPTIONS_HASHES + from certbot_apache._internal.constants import ALL_SSL_OPTIONS_HASHES self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") diff --git a/certbot-apache/certbot_apache/tests/debian_test.py b/certbot-apache/tests/debian_test.py similarity index 92% rename from certbot-apache/certbot_apache/tests/debian_test.py rename to certbot-apache/tests/debian_test.py index f1d5843a5..6e63a9bd3 100644 --- a/certbot-apache/certbot_apache/tests/debian_test.py +++ b/certbot-apache/tests/debian_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_apache.configurator for Debian overrides""" +"""Test for certbot_apache._internal.configurator for Debian overrides""" import shutil import unittest @@ -6,10 +6,9 @@ import mock from certbot import errors from certbot.compat import os - -from certbot_apache import apache_util -from certbot_apache import obj -from certbot_apache.tests import util +from certbot_apache._internal import apache_util +from certbot_apache._internal import obj +import util class MultipleVhostsTestDebian(util.ApacheTest): @@ -32,8 +31,8 @@ class MultipleVhostsTestDebian(util.ApacheTest): def mocked_deploy_cert(*args, **kwargs): """a helper to mock a deployed cert""" - g_mod = "certbot_apache.configurator.ApacheConfigurator.enable_mod" - d_mod = "certbot_apache.override_debian.DebianConfigurator.enable_mod" + g_mod = "certbot_apache._internal.configurator.ApacheConfigurator.enable_mod" + d_mod = "certbot_apache._internal.override_debian.DebianConfigurator.enable_mod" with mock.patch(g_mod): with mock.patch(d_mod): config.real_deploy_cert(*args, **kwargs) @@ -47,7 +46,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): @mock.patch("certbot.util.run_script") @mock.patch("certbot.util.exe_exists") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_enable_mod(self, mock_popen, mock_exe_exists, mock_run_script): mock_popen().communicate.return_value = ("Define: DUMP_RUN_CFG", "") mock_popen().returncode = 0 @@ -79,9 +78,9 @@ class MultipleVhostsTestDebian(util.ApacheTest): def test_enable_site_failure(self): self.config.parser.root = "/tmp/nonexistent" - with mock.patch("os.path.isdir") as mock_dir: + with mock.patch("certbot.compat.os.path.isdir") as mock_dir: mock_dir.return_value = True - with mock.patch("os.path.islink") as mock_link: + with mock.patch("certbot.compat.os.path.islink") as mock_link: mock_link.return_value = False self.assertRaises( errors.NotSupportedError, @@ -196,7 +195,7 @@ class MultipleVhostsTestDebian(util.ApacheTest): def test_enable_site_call_parent(self): with mock.patch( - "certbot_apache.configurator.ApacheConfigurator.enable_site") as e_s: + "certbot_apache._internal.configurator.ApacheConfigurator.enable_site") as e_s: self.config.parser.root = "/tmp/nonexistent" vh = self.vh_truth[0] vh.enabled = False diff --git a/certbot-apache/certbot_apache/tests/display_ops_test.py b/certbot-apache/tests/display_ops_test.py similarity index 86% rename from certbot-apache/certbot_apache/tests/display_ops_test.py rename to certbot-apache/tests/display_ops_test.py index df5cdbac0..50bdc03cf 100644 --- a/certbot-apache/certbot_apache/tests/display_ops_test.py +++ b/certbot-apache/tests/display_ops_test.py @@ -1,22 +1,18 @@ -"""Test certbot_apache.display_ops.""" +"""Test certbot_apache._internal.display_ops.""" import unittest import mock from certbot import errors - from certbot.display import util as display_util - from certbot.tests import util as certbot_util - -from certbot_apache import obj - -from certbot_apache.display_ops import select_vhost_multiple -from certbot_apache.tests import util +from certbot_apache._internal import obj +from certbot_apache._internal.display_ops import select_vhost_multiple +import util class SelectVhostMultiTest(unittest.TestCase): - """Tests for certbot_apache.display_ops.select_vhost_multiple.""" + """Tests for certbot_apache._internal.display_ops.select_vhost_multiple.""" def setUp(self): self.base_dir = "/example_path" @@ -45,7 +41,7 @@ class SelectVhostMultiTest(unittest.TestCase): self.assertFalse(vhs) class SelectVhostTest(unittest.TestCase): - """Tests for certbot_apache.display_ops.select_vhost.""" + """Tests for certbot_apache._internal.display_ops.select_vhost.""" def setUp(self): self.base_dir = "/example_path" @@ -54,7 +50,7 @@ class SelectVhostTest(unittest.TestCase): @classmethod def _call(cls, vhosts): - from certbot_apache.display_ops import select_vhost + from certbot_apache._internal.display_ops import select_vhost return select_vhost("example.com", vhosts) @certbot_util.patch_get_utility() @@ -81,9 +77,9 @@ class SelectVhostTest(unittest.TestCase): def test_no_vhosts(self): self.assertEqual(self._call([]), None) - @mock.patch("certbot_apache.display_ops.display_util") + @mock.patch("certbot_apache._internal.display_ops.display_util") @certbot_util.patch_get_utility() - @mock.patch("certbot_apache.display_ops.logger") + @mock.patch("certbot_apache._internal.display_ops.logger") def test_small_display(self, mock_logger, mock_util, mock_display_util): mock_display_util.WIDTH = 20 mock_util().menu.return_value = (display_util.OK, 0) diff --git a/certbot-apache/certbot_apache/tests/entrypoint_test.py b/certbot-apache/tests/entrypoint_test.py similarity index 90% rename from certbot-apache/certbot_apache/tests/entrypoint_test.py rename to certbot-apache/tests/entrypoint_test.py index 9adcd46dc..04c393bdf 100644 --- a/certbot-apache/certbot_apache/tests/entrypoint_test.py +++ b/certbot-apache/tests/entrypoint_test.py @@ -1,10 +1,10 @@ -"""Test for certbot_apache.entrypoint for override class resolution""" +"""Test for certbot_apache._internal.entrypoint for override class resolution""" import unittest import mock -from certbot_apache import configurator -from certbot_apache import entrypoint +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint class EntryPointTest(unittest.TestCase): diff --git a/certbot-apache/certbot_apache/tests/fedora_test.py b/certbot-apache/tests/fedora_test.py similarity index 89% rename from certbot-apache/certbot_apache/tests/fedora_test.py rename to certbot-apache/tests/fedora_test.py index 67533fe1d..2bfd6babb 100644 --- a/certbot-apache/certbot_apache/tests/fedora_test.py +++ b/certbot-apache/tests/fedora_test.py @@ -1,14 +1,14 @@ -"""Test for certbot_apache.configurator for Fedora 29+ overrides""" +"""Test for certbot_apache._internal.configurator for Fedora 29+ overrides""" import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os - -from certbot_apache import obj -from certbot_apache import override_fedora -from certbot_apache.tests import util +from certbot_apache._internal import obj +from certbot_apache._internal import override_fedora +import util def get_vh_truth(temp_dir, config_name): @@ -57,7 +57,7 @@ class FedoraRestartTest(util.ApacheTest): self.config.config_test() def test_fedora_restart_error(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: # First call raises error, second doesn't mock_test.side_effect = [errors.MisconfigurationError, ''] @@ -67,7 +67,7 @@ class FedoraRestartTest(util.ApacheTest): self._run_fedora_test) def test_fedora_restart(self): - c_test = "certbot_apache.configurator.ApacheConfigurator.config_test" + c_test = "certbot_apache._internal.configurator.ApacheConfigurator.config_test" with mock.patch(c_test) as mock_test: with mock.patch("certbot.util.run_script") as mock_run: # First call raises error, second doesn't @@ -100,7 +100,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): def test_get_parser(self): self.assertIsInstance(self.config.parser, override_fedora.FedoraParser) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): define_val = ( 'Define: TEST1\n' @@ -134,7 +134,7 @@ class MultipleVhostsTestFedora(util.ApacheTest): self.assertTrue("TEST2" in self.config.parser.variables.keys()) self.assertTrue("mod_another.c" in self.config.parser.modules) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_get_version(self, mock_run_script): mock_run_script.return_value = ('', None) self.assertRaises(errors.PluginError, self.config.get_version) @@ -155,12 +155,12 @@ class MultipleVhostsTestFedora(util.ApacheTest): raise Exception("Missed: %s" % vhost) # pragma: no cover self.assertEqual(found, 2) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_get_sysconfig_vars(self, mock_cfg): """Make sure we read the sysconfig OPTIONS variable correctly""" # Return nothing for the process calls mock_cfg.return_value = "" - self.config.parser.sysconfig_filep = os.path.realpath( + self.config.parser.sysconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../sysconfig/httpd")) self.config.parser.variables = {} @@ -176,13 +176,13 @@ class MultipleVhostsTestFedora(util.ApacheTest): self.assertTrue("MOCK_NOSEP" in self.config.parser.variables.keys()) self.assertEqual("NOSEP_VAL", self.config.parser.variables["NOSEP_TWO"]) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() self.assertEqual(mock_run_script.call_count, 3) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_errors(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, diff --git a/certbot-apache/certbot_apache/tests/gentoo_test.py b/certbot-apache/tests/gentoo_test.py similarity index 84% rename from certbot-apache/certbot_apache/tests/gentoo_test.py rename to certbot-apache/tests/gentoo_test.py index dd01e9170..90a163fd3 100644 --- a/certbot-apache/certbot_apache/tests/gentoo_test.py +++ b/certbot-apache/tests/gentoo_test.py @@ -1,14 +1,14 @@ -"""Test for certbot_apache.configurator for Gentoo overrides""" +"""Test for certbot_apache._internal.configurator for Gentoo overrides""" import unittest import mock from certbot import errors +from certbot.compat import filesystem from certbot.compat import os - -from certbot_apache import obj -from certbot_apache import override_gentoo -from certbot_apache.tests import util +from certbot_apache._internal import obj +from certbot_apache._internal import override_gentoo +import util def get_vh_truth(temp_dir, config_name): @@ -51,7 +51,8 @@ class MultipleVhostsTestGentoo(util.ApacheTest): config_root=config_root, vhost_root=vhost_root) - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_runtime_variables"): + # pylint: disable=line-too-long + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_runtime_variables"): self.config = util.get_apache_configurator( self.config_path, self.vhost_path, self.config_dir, self.work_dir, os_info="gentoo") @@ -81,20 +82,20 @@ class MultipleVhostsTestGentoo(util.ApacheTest): """Make sure we read the Gentoo APACHE2_OPTS variable correctly""" defines = ['DEFAULT_VHOST', 'INFO', 'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE'] - self.config.parser.apacheconfig_filep = os.path.realpath( + self.config.parser.apacheconfig_filep = filesystem.realpath( os.path.join(self.config.parser.root, "../conf.d/apache2")) self.config.parser.variables = {} - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"): + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() for define in defines: self.assertTrue(define in self.config.parser.variables.keys()) - @mock.patch("certbot_apache.parser.ApacheParser.parse_from_subprocess") + @mock.patch("certbot_apache._internal.parser.ApacheParser.parse_from_subprocess") def test_no_binary_configdump(self, mock_subprocess): """Make sure we don't call binary dumps other than modules from Apache as this is not supported in Gentoo currently""" - with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"): + with mock.patch("certbot_apache._internal.override_gentoo.GentooParser.update_modules"): self.config.parser.update_runtime_variables() self.config.parser.reset_modules() self.assertFalse(mock_subprocess.called) @@ -103,7 +104,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.config.parser.reset_modules() self.assertTrue(mock_subprocess.called) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_opportunistic_httpd_runtime_parsing(self, mock_get): mod_val = ( 'Loaded Modules:\n' @@ -127,7 +128,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest): self.assertEqual(len(self.config.parser.modules), 4) self.assertTrue("mod_another.c" in self.config.parser.modules) - @mock.patch("certbot_apache.configurator.util.run_script") + @mock.patch("certbot_apache._internal.configurator.util.run_script") def test_alt_restart_works(self, mock_run_script): mock_run_script.side_effect = [None, errors.SubprocessError, None] self.config.restart() diff --git a/certbot-apache/certbot_apache/tests/http_01_test.py b/certbot-apache/tests/http_01_test.py similarity index 92% rename from certbot-apache/certbot_apache/tests/http_01_test.py rename to certbot-apache/tests/http_01_test.py index a6af68037..643a6bdd5 100644 --- a/certbot-apache/certbot_apache/tests/http_01_test.py +++ b/certbot-apache/tests/http_01_test.py @@ -1,24 +1,23 @@ -"""Test for certbot_apache.http_01.""" +"""Test for certbot_apache._internal.http_01.""" import unittest + import mock from acme import challenges from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util - -from certbot_apache.parser import get_aug_path -from certbot_apache.tests import util - +from certbot_apache._internal.parser import get_aug_path +import util NUM_ACHALLS = 3 class ApacheHttp01Test(util.ApacheTest): - """Test for certbot_apache.http_01.ApacheHttp01.""" + """Test for certbot_apache._internal.http_01.ApacheHttp01.""" def setUp(self, *args, **kwargs): # pylint: disable=arguments-differ super(ApacheHttp01Test, self).setUp(*args, **kwargs) @@ -44,13 +43,13 @@ class ApacheHttp01Test(util.ApacheTest): self.config.parser.modules.add("mod_{0}.c".format(mod)) self.config.parser.modules.add(mod + "_module") - from certbot_apache.http_01 import ApacheHttp01 + from certbot_apache._internal.http_01 import ApacheHttp01 self.http = ApacheHttp01(self.config) def test_empty_perform(self): self.assertFalse(self.http.perform()) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_enable_modules_apache_2_2(self, mock_enmod): self.config.version = (2, 2) self.config.parser.modules.remove("authz_host_module") @@ -59,7 +58,7 @@ class ApacheHttp01Test(util.ApacheTest): enmod_calls = self.common_enable_modules_test(mock_enmod) self.assertEqual(enmod_calls[0][0][0], "authz_host") - @mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.enable_mod") def test_enable_modules_apache_2_4(self, mock_enmod): self.config.parser.modules.remove("authz_core_module") self.config.parser.modules.remove("mod_authz_core.c") @@ -180,7 +179,7 @@ class ApacheHttp01Test(util.ApacheTest): self.assertEqual(self.http.perform(), expected_response) self.assertTrue(os.path.isdir(self.http.challenge_dir)) - self._has_min_permissions(self.http.challenge_dir, 0o755) + self.assertTrue(filesystem.has_min_permissions(self.http.challenge_dir, 0o755)) self._test_challenge_conf() for achall in achalls: @@ -218,15 +217,10 @@ class ApacheHttp01Test(util.ApacheTest): name = os.path.join(self.http.challenge_dir, achall.chall.encode("token")) validation = achall.validation(self.account_key) - self._has_min_permissions(name, 0o644) + self.assertTrue(filesystem.has_min_permissions(name, 0o644)) with open(name, 'rb') as f: self.assertEqual(f.read(), validation.encode()) - def _has_min_permissions(self, path, min_mode): - """Tests the given file has at least the permissions in mode.""" - st_mode = os.stat(path).st_mode - self.assertEqual(st_mode, st_mode | min_mode) - if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-apache/certbot_apache/tests/obj_test.py b/certbot-apache/tests/obj_test.py similarity index 89% rename from certbot-apache/certbot_apache/tests/obj_test.py rename to certbot-apache/tests/obj_test.py index 10dba18bc..1761b9c94 100644 --- a/certbot-apache/certbot_apache/tests/obj_test.py +++ b/certbot-apache/tests/obj_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_apache.obj.""" +"""Tests for certbot_apache._internal.obj.""" import unittest @@ -6,8 +6,8 @@ class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from certbot_apache.obj import Addr - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost self.addr1 = Addr.fromstring("127.0.0.1") self.addr2 = Addr.fromstring("127.0.0.1:443") @@ -23,7 +23,8 @@ class VirtualHostTest(unittest.TestCase): "fp", "vhp", set([self.addr2]), False, False, "localhost") def test_repr(self): - self.assertEqual(repr(self.addr2), "certbot_apache.obj.Addr(('127.0.0.1', '443'))") + self.assertEqual(repr(self.addr2), + "certbot_apache._internal.obj.Addr(('127.0.0.1', '443'))") def test_eq(self): self.assertTrue(self.vhost1b == self.vhost1) @@ -36,8 +37,8 @@ class VirtualHostTest(unittest.TestCase): self.assertFalse(self.vhost1 != self.vhost1b) def test_conflicts(self): - from certbot_apache.obj import Addr - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import Addr + from certbot_apache._internal.obj import VirtualHost complex_vh = VirtualHost( "fp", "vhp", @@ -54,7 +55,7 @@ class VirtualHostTest(unittest.TestCase): self.addr_default])) def test_same_server(self): - from certbot_apache.obj import VirtualHost + from certbot_apache._internal.obj import VirtualHost no_name1 = VirtualHost( "fp", "vhp", set([self.addr1]), False, False, None) no_name2 = VirtualHost( @@ -77,7 +78,7 @@ class VirtualHostTest(unittest.TestCase): class AddrTest(unittest.TestCase): """Test obj.Addr.""" def setUp(self): - from certbot_apache.obj import Addr + from certbot_apache._internal.obj import Addr self.addr = Addr.fromstring("*:443") self.addr1 = Addr.fromstring("127.0.0.1") @@ -92,7 +93,7 @@ class AddrTest(unittest.TestCase): self.assertTrue(self.addr2.is_wildcard()) def test_get_sni_addr(self): - from certbot_apache.obj import Addr + from certbot_apache._internal.obj import Addr self.assertEqual( self.addr.get_sni_addr("443"), Addr.fromstring("*:443")) self.assertEqual( diff --git a/certbot-apache/certbot_apache/tests/parser_test.py b/certbot-apache/tests/parser_test.py similarity index 75% rename from certbot-apache/certbot_apache/tests/parser_test.py rename to certbot-apache/tests/parser_test.py index ef4412a58..b334ce52e 100644 --- a/certbot-apache/certbot_apache/tests/parser_test.py +++ b/certbot-apache/tests/parser_test.py @@ -1,14 +1,12 @@ -"""Tests for certbot_apache.parser.""" +"""Tests for certbot_apache._internal.parser.""" import shutil import unittest -import augeas import mock from certbot import errors from certbot.compat import os - -from certbot_apache.tests import util +import util class BasicParserTest(util.ParserTest): @@ -22,6 +20,27 @@ class BasicParserTest(util.ParserTest): shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) + def test_bad_parse(self): + self.parser.parse_file(os.path.join(self.parser.root, + "conf-available", "bad_conf_file.conf")) + self.assertRaises( + errors.PluginError, self.parser.check_parsing_errors, "httpd.aug") + + def test_bad_save(self): + mock_save = mock.Mock() + mock_save.side_effect = IOError + self.parser.aug.save = mock_save + self.assertRaises(errors.PluginError, self.parser.unsaved_files) + + def test_aug_version(self): + mock_match = mock.Mock(return_value=["something"]) + self.parser.aug.match = mock_match + # pylint: disable=protected-access + self.assertEqual(self.parser.check_aug_version(), + ["something"]) + self.parser.aug.match.side_effect = RuntimeError + self.assertFalse(self.parser.check_aug_version()) + def test_find_config_root_no_root(self): # pylint: disable=protected-access os.remove(self.parser.loc["root"]) @@ -93,7 +112,7 @@ class BasicParserTest(util.ParserTest): Path must be valid before attempting to add to augeas """ - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path # This makes sure that find_dir will work self.parser.modules.add("mod_ssl.c") @@ -107,7 +126,7 @@ class BasicParserTest(util.ParserTest): self.assertTrue("IfModule" in matches[0]) def test_add_dir_to_ifmodssl_multiple(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path # This makes sure that find_dir will work self.parser.modules.add("mod_ssl.c") @@ -121,11 +140,11 @@ class BasicParserTest(util.ParserTest): self.assertTrue("IfModule" in matches[0]) def test_get_aug_path(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path self.assertEqual("/files/etc/apache", get_aug_path("/etc/apache")) def test_set_locations(self): - with mock.patch("certbot_apache.parser.os.path") as mock_path: + with mock.patch("certbot_apache._internal.parser.os.path") as mock_path: mock_path.isfile.side_effect = [False, False] @@ -135,18 +154,18 @@ class BasicParserTest(util.ParserTest): self.assertEqual(results["default"], results["listen"]) self.assertEqual(results["default"], results["name"]) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser.get_arg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser.get_arg") def test_parse_modules_bad_syntax(self, mock_arg, mock_find): mock_find.return_value = ["1", "2", "3", "4", "5", "6", "7", "8"] mock_arg.return_value = None - with mock.patch("certbot_apache.parser.logger") as mock_logger: + with mock.patch("certbot_apache._internal.parser.logger") as mock_logger: self.parser.parse_modules() # Make sure that we got None return value and logged the file self.assertTrue(mock_logger.debug.called) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_variables(self, mock_cfg, _): define_val = ( 'ServerRoot: "/etc/apache2"\n' @@ -243,7 +262,7 @@ class BasicParserTest(util.ParserTest): self.parser.modules = set() with mock.patch( - "certbot_apache.parser.ApacheParser.parse_file") as mock_parse: + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() self.assertEqual(self.parser.variables, expected_vars) self.assertEqual(len(self.parser.modules), 58) @@ -251,8 +270,8 @@ class BasicParserTest(util.ParserTest): # Make sure we tried to include them all. self.assertEqual(mock_parse.call_count, 25) - @mock.patch("certbot_apache.parser.ApacheParser.find_dir") - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.find_dir") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_variables_alt_values(self, mock_cfg, _): inc_val = ( 'Included configuration files:\n' @@ -266,7 +285,7 @@ class BasicParserTest(util.ParserTest): self.parser.modules = set() with mock.patch( - "certbot_apache.parser.ApacheParser.parse_file") as mock_parse: + "certbot_apache._internal.parser.ApacheParser.parse_file") as mock_parse: self.parser.update_runtime_variables() # No matching modules should have been found self.assertEqual(len(self.parser.modules), 0) @@ -274,7 +293,7 @@ class BasicParserTest(util.ParserTest): # path derived from root configuration Include statements self.assertEqual(mock_parse.call_count, 1) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_update_runtime_vars_bad_output(self, mock_cfg): mock_cfg.return_value = "Define: TLS=443=24" self.parser.update_runtime_variables() @@ -283,8 +302,8 @@ class BasicParserTest(util.ParserTest): self.assertRaises( errors.PluginError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache.configurator.ApacheConfigurator.option") - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.configurator.ApacheConfigurator.option") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_update_runtime_vars_bad_ctl(self, mock_popen, mock_opt): mock_popen.side_effect = OSError mock_opt.return_value = "nonexistent" @@ -292,7 +311,7 @@ class BasicParserTest(util.ParserTest): errors.MisconfigurationError, self.parser.update_runtime_variables) - @mock.patch("certbot_apache.parser.subprocess.Popen") + @mock.patch("certbot_apache._internal.parser.subprocess.Popen") def test_update_runtime_vars_bad_exit(self, mock_popen): mock_popen().communicate.return_value = ("", "") mock_popen.returncode = -1 @@ -301,7 +320,7 @@ class BasicParserTest(util.ParserTest): self.parser.update_runtime_variables) def test_add_comment(self): - from certbot_apache.parser import get_aug_path + from certbot_apache._internal.parser import get_aug_path self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456") comm = self.parser.find_comments("123456") self.assertEqual(len(comm), 1) @@ -311,53 +330,69 @@ class BasicParserTest(util.ParserTest): class ParserInitTest(util.ApacheTest): def setUp(self): # pylint: disable=arguments-differ super(ParserInitTest, self).setUp() - self.aug = augeas.Augeas( - flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) def tearDown(self): shutil.rmtree(self.temp_dir) shutil.rmtree(self.config_dir) shutil.rmtree(self.work_dir) - @mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg") + @mock.patch("certbot_apache._internal.parser.ApacheParser.init_augeas") + def test_prepare_no_augeas(self, mock_init_augeas): + from certbot_apache._internal.parser import ApacheParser + mock_init_augeas.side_effect = errors.NoInstallationError + self.config.config_test = mock.Mock() + self.assertRaises( + errors.NoInstallationError, ApacheParser, + os.path.relpath(self.config_path), "/dummy/vhostpath", + version=(2, 4, 22), configurator=self.config) + + def test_init_old_aug(self): + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser.check_aug_version") as mock_c: + mock_c.return_value = False + self.assertRaises( + errors.NotSupportedError, + ApacheParser, os.path.relpath(self.config_path), + "/dummy/vhostpath", version=(2, 4, 22), configurator=self.config) + + @mock.patch("certbot_apache._internal.parser.ApacheParser._get_runtime_cfg") def test_unparseable(self, mock_cfg): - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser mock_cfg.return_value = ('Define: TEST') self.assertRaises( errors.PluginError, - ApacheParser, self.aug, os.path.relpath(self.config_path), + ApacheParser, os.path.relpath(self.config_path), "/dummy/vhostpath", version=(2, 2, 22), configurator=self.config) def test_root_normalized(self): - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): path = os.path.join( self.temp_dir, "debian_apache_2_4/////multiple_vhosts/../multiple_vhosts/apache2") - parser = ApacheParser(self.aug, path, - "/dummy/vhostpath", configurator=self.config) + parser = ApacheParser(path, "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) def test_root_absolute(self): - from certbot_apache.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( - self.aug, os.path.relpath(self.config_path), + os.path.relpath(self.config_path), "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) def test_root_no_trailing_slash(self): - from certbot_apache.parser import ApacheParser - with mock.patch("certbot_apache.parser.ApacheParser." + from certbot_apache._internal.parser import ApacheParser + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): parser = ApacheParser( - self.aug, self.config_path + os.path.sep, + self.config_path + os.path.sep, "/dummy/vhostpath", configurator=self.config) self.assertEqual(parser.root, self.config_path) diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README rename to certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/README diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf rename to certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf rename to certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/test.example.com.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf rename to certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf.d/welcome.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf rename to certbot-apache/tests/testdata/centos6_apache/apache/httpd/conf/httpd.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/README diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/autoindex.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/centos.example.com.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/userdir.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.d/welcome.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-base.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-dav.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-lua.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-mpm.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-proxy.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/00-systemd.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf.modules.d/01-cgi.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/httpd.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic b/certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/httpd/conf/magic rename to certbot-apache/tests/testdata/centos7_apache/apache/httpd/conf/magic diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites b/certbot-apache/tests/testdata/centos7_apache/apache/sites similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sites rename to certbot-apache/tests/testdata/centos7_apache/apache/sites diff --git a/certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd b/certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/centos7_apache/apache/sysconfig/httpd rename to certbot-apache/tests/testdata/centos7_apache/apache/sysconfig/httpd diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf b/certbot-apache/tests/testdata/complex_parsing/apache2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/complex_parsing/apache2.conf rename to certbot-apache/tests/testdata/complex_parsing/apache2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf b/certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf rename to certbot-apache/tests/testdata/complex_parsing/conf-enabled/dummy.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf b/certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_fnmatch.conf rename to certbot-apache/tests/testdata/complex_parsing/test_fnmatch.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf b/certbot-apache/tests/testdata/complex_parsing/test_variables.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/complex_parsing/test_variables.conf rename to certbot-apache/tests/testdata/complex_parsing/test_variables.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/apache2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/bad_conf_file.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-available/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/conf-enabled/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/envvars diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/authz_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/dav_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/rewrite.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-available/ssl.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/authz_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/mods-enabled/dav_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/ports.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/another_wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/old-and-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-available/wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/another_wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/old-and-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/apache2/sites-enabled/wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites b/certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites rename to certbot-apache/tests/testdata/debian_apache_2_4/augeas_vhosts/sites diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/apache2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-available/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/conf-enabled/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/envvars diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/mods-available/ssl.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/ports.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/000-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-available/default-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/apache2/sites-enabled/000-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites b/certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/default_vhost/sites rename to certbot-apache/tests/testdata/debian_apache_2_4/default_vhost/sites diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/apache2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/envvars diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/ports.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-available/multi-vhost.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multi_vhosts/apache2/sites-enabled/multi-vhost.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/apache2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/bad_conf_file.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-available/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/other-vhosts-access-log.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/security.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/conf-enabled/serve-cgi-bin.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/envvars diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/authz_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/dav_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/rewrite.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-available/ssl.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/authz_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/mods-enabled/dav_svn.load diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/ports.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/000-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/certbot.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl-port-only.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/default-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttp.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/duplicatehttps.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/encryption-example.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/mod_macro-example.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/ocsp-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-available/wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/000-default.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/certbot.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl-port-only.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/default-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttp.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/duplicatehttps.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/encryption-example.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/mod_macro-example.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/non-symlink.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/ocsp-ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/apache2/sites-enabled/wildcard.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites b/certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites rename to certbot-apache/tests/testdata/debian_apache_2_4/multiple_vhosts/sites diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/httpd.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/magic rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/magic diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_default_settings.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_error_documents.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_languages.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_autoindex.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_info.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_log_config.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_mime.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_status.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mod_userdir.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/00_mpm.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/10_mod_mem_cache.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/40_mod_ssl.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/41_mod_http2.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/45_mod_dav.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/modules.d/46_mod_ldap.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_ssl_vhost.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/00_default_vhost.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/default_vhost.include diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf b/certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf rename to certbot-apache/tests/testdata/gentoo_apache/apache/apache2/vhosts.d/gentoo.example.com.conf diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 b/certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 rename to certbot-apache/tests/testdata/gentoo_apache/apache/conf.d/apache2 diff --git a/certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites b/certbot-apache/tests/testdata/gentoo_apache/apache/sites similarity index 100% rename from certbot-apache/certbot_apache/tests/testdata/gentoo_apache/apache/sites rename to certbot-apache/tests/testdata/gentoo_apache/apache/sites diff --git a/certbot-apache/certbot_apache/tests/util.py b/certbot-apache/tests/util.py similarity index 93% rename from certbot-apache/certbot_apache/tests/util.py rename to certbot-apache/tests/util.py index 2ac51540f..57b20dc9d 100644 --- a/certbot-apache/certbot_apache/tests/util.py +++ b/certbot-apache/tests/util.py @@ -12,13 +12,12 @@ from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import common from certbot.tests import util as test_util - -from certbot_apache import configurator -from certbot_apache import entrypoint -from certbot_apache import obj +from certbot_apache._internal import configurator +from certbot_apache._internal import entrypoint +from certbot_apache._internal import obj -class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods +class ApacheTest(unittest.TestCase): def setUp(self, test_dir="debian_apache_2_4/multiple_vhosts", config_root="debian_apache_2_4/multiple_vhosts/apache2", @@ -28,7 +27,7 @@ class ApacheTest(unittest.TestCase): # pylint: disable=too-few-public-methods self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( test_dir=test_dir, - pkg="certbot_apache.tests") + pkg=__name__) self.config_path = os.path.join(self.temp_dir, config_root) self.vhost_path = os.path.join(self.temp_dir, vhost_root) @@ -72,17 +71,16 @@ class ParserTest(ApacheTest): zope.component.provideUtility(display_util.FileDisplay(sys.stdout, False)) - from certbot_apache.parser import ApacheParser + from certbot_apache._internal.parser import ApacheParser self.aug = augeas.Augeas( flags=augeas.Augeas.NONE | augeas.Augeas.NO_MODL_AUTOLOAD) - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): self.parser = ApacheParser( - self.aug, self.config_path, self.vhost_path, - configurator=self.config) + self.config_path, self.vhost_path, configurator=self.config) -def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals +def get_apache_configurator( config_path, vhost_path, config_dir, work_dir, version=(2, 4, 7), os_info="generic", @@ -106,11 +104,11 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc in_progress_dir=os.path.join(backups, "IN_PROGRESS"), work_dir=work_dir) - with mock.patch("certbot_apache.configurator.util.run_script"): - with mock.patch("certbot_apache.configurator.util." + with mock.patch("certbot_apache._internal.configurator.util.run_script"): + with mock.patch("certbot_apache._internal.configurator.util." "exe_exists") as mock_exe_exists: mock_exe_exists.return_value = True - with mock.patch("certbot_apache.parser.ApacheParser." + with mock.patch("certbot_apache._internal.parser.ApacheParser." "update_runtime_variables"): try: config_class = entrypoint.OVERRIDE_CLASSES[os_info] diff --git a/certbot-auto b/certbot-auto index 756b8e247..24c007e03 100755 --- a/certbot-auto +++ b/certbot-auto @@ -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="0.35.1" +LE_AUTO_VERSION="1.0.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -755,13 +755,33 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +795,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { @@ -1115,73 +1136,83 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1189,26 +1220,26 @@ enum34==1.1.6 ; python_version < '3.4' \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1217,32 +1248,31 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1290,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # @@ -1314,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-ci/certbot_integration_tests/.coveragerc b/certbot-ci/certbot_integration_tests/.coveragerc index 00929eda2..72f7c6adf 100644 --- a/certbot-ci/certbot_integration_tests/.coveragerc +++ b/certbot-ci/certbot_integration_tests/.coveragerc @@ -2,7 +2,8 @@ # Avoid false warnings because certbot packages are not installed in the thread that executes # the coverage: indeed, certbot is launched as a CLI from a subprocess. disable_warnings = module-not-imported,no-data-collected +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/_internal/parser_obj.py [report] # Exclude unit tests in coverage during integration tests. -omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/parser_obj.py +omit = **/*_test.py,**/tests/*,**/dns_common*,**/certbot_nginx/_internal/parser_obj.py diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_cert.pem b/certbot-ci/certbot_integration_tests/assets/cert.pem similarity index 100% rename from certbot-ci/certbot_integration_tests/assets/nginx_cert.pem rename to certbot-ci/certbot_integration_tests/assets/cert.pem diff --git a/certbot-ci/certbot_integration_tests/assets/hook.py b/certbot-ci/certbot_integration_tests/assets/hook.py new file mode 100755 index 000000000..39aa72ac5 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/assets/hook.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +import os +import sys + +hook_script_type = os.path.basename(os.path.dirname(sys.argv[1])) +if hook_script_type == 'deploy' and ('RENEWED_DOMAINS' not in os.environ or 'RENEWED_LINEAGE' not in os.environ): + sys.stderr.write('Environment variables not properly set!\n') + sys.exit(1) + +with open(sys.argv[2], 'a') as file_h: + file_h.write(hook_script_type + '\n') diff --git a/certbot-ci/certbot_integration_tests/assets/nginx_key.pem b/certbot-ci/certbot_integration_tests/assets/key.pem similarity index 100% rename from certbot-ci/certbot_integration_tests/assets/nginx_key.pem rename to certbot-ci/certbot_integration_tests/assets/key.pem diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py index b82c0b5f0..1b5914d1a 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/assertions.py @@ -1,6 +1,18 @@ """This module contains advanced assertions for the certbot integration tests.""" import os -import grp + +try: + import grp + POSIX_MODE = True +except ImportError: + import win32api + import win32security + import ntsecuritycon + POSIX_MODE = False + +EVERYBODY_SID = 'S-1-1-0' +SYSTEM_SID = 'S-1-5-18' +ADMINS_SID = 'S-1-5-32-544' def assert_hook_execution(probe_path, probe_content): @@ -10,9 +22,10 @@ def assert_hook_execution(probe_path, probe_content): :param probe_content: content expected when the hook is executed """ with open(probe_path, 'r') as file: - lines = file.readlines() + data = file.read() - assert '{0}{1}'.format(probe_content, os.linesep) in lines + lines = [line.strip() for line in data.splitlines()] + assert probe_content in lines def assert_saved_renew_hook(config_dir, lineage): @@ -38,16 +51,51 @@ def assert_cert_count_for_lineage(config_dir, lineage, count): assert len(certs) == count -def assert_equals_permissions(file1, file2, mask): +def assert_equals_group_permissions(file1, file2): """ - Assert that permissions on two files are identical in respect to a given umask. + Assert that two files have the same permissions for group owner. :param file1: first file path to compare :param file2: second file path to compare - :param mask: 3-octal representation of a POSIX umask under which the two files mode - should match (eg. 0o074 will test RWX on group and R on world) """ - mode_file1 = os.stat(file1).st_mode & mask - mode_file2 = os.stat(file2).st_mode & mask + # On Windows there is no group, so this assertion does nothing on this platform + if POSIX_MODE: + mode_file1 = os.stat(file1).st_mode & 0o070 + mode_file2 = os.stat(file2).st_mode & 0o070 + + assert mode_file1 == mode_file2 + + +def assert_equals_world_read_permissions(file1, file2): + """ + Assert that two files have the same read permissions for everyone. + :param file1: first file path to compare + :param file2: second file path to compare + """ + if POSIX_MODE: + mode_file1 = os.stat(file1).st_mode & 0o004 + mode_file2 = os.stat(file2).st_mode & 0o004 + else: + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + security1 = win32security.GetFileSecurity(file1, win32security.DACL_SECURITY_INFORMATION) + dacl1 = security1.GetSecurityDescriptorDacl() + + mode_file1 = dacl1.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': everybody, + }) + mode_file1 = mode_file1 & ntsecuritycon.FILE_GENERIC_READ + + security2 = win32security.GetFileSecurity(file2, win32security.DACL_SECURITY_INFORMATION) + dacl2 = security2.GetSecurityDescriptorDacl() + + mode_file2 = dacl2.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': everybody, + }) + mode_file2 = mode_file2 & ntsecuritycon.FILE_GENERIC_READ assert mode_file1 == mode_file2 @@ -57,20 +105,57 @@ def assert_equals_group_owner(file1, file2): Assert that two files have the same group owner. :param file1: first file path to compare :param file2: second file path to compare - :return: """ - group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0] - group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0] + # On Windows there is no group, so this assertion does nothing on this platform + if POSIX_MODE: + group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0] + group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0] - assert group_owner_file1 == group_owner_file2 + assert group_owner_file1 == group_owner_file2 -def assert_world_permissions(file, mode): +def assert_world_no_permissions(file): """ - Assert that a file has the expected world permission. - :param file: file path to check - :param mode: world permissions mode expected + Assert that the given file is not world-readable. + :param file: path of the file to check """ - mode_file_all = os.stat(file).st_mode & 0o007 + if POSIX_MODE: + mode_file_all = os.stat(file).st_mode & 0o007 + assert mode_file_all == 0 + else: + security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID), + }) - assert mode_file_all == mode + assert not mode + + +def assert_world_read_permissions(file): + """ + Assert that the given file is world-readable, but not world-writable or world-executable. + :param file: path of the file to check + """ + if POSIX_MODE: + mode_file_all = os.stat(file).st_mode & 0o007 + assert mode_file_all == 4 + else: + security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID), + }) + + assert not mode & ntsecuritycon.FILE_GENERIC_WRITE + assert not mode & ntsecuritycon.FILE_GENERIC_EXECUTE + assert mode & ntsecuritycon.FILE_GENERIC_READ == ntsecuritycon.FILE_GENERIC_READ + + +def _get_current_user(): + account_name = win32api.GetUserNameEx(win32api.NameSamCompatible) + return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/context.py b/certbot-ci/certbot_integration_tests/certbot_tests/context.py index c4c02a25e..6f8670000 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/context.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/context.py @@ -1,10 +1,11 @@ """Module to handle the context of integration tests.""" +import logging import os import shutil import sys import tempfile -from certbot_integration_tests.utils import misc, certbot_call +from certbot_integration_tests.utils import certbot_call class IntegrationTestsContext(object): @@ -19,7 +20,7 @@ class IntegrationTestsContext(object): self.worker_id = 'primary' acme_xdist = request.config.acme_xdist - self.acme_server =acme_xdist['acme_server'] + self.acme_server = acme_xdist['acme_server'] self.directory_url = acme_xdist['directory_url'] self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id] self.http_01_port = acme_xdist['http_port'][self.worker_id] @@ -30,7 +31,10 @@ class IntegrationTestsContext(object): self.workspace = tempfile.mkdtemp() self.config_dir = os.path.join(self.workspace, 'conf') - self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1] + + probe = tempfile.mkstemp(dir=self.workspace) + os.close(probe[0]) + self.hook_probe = probe[1] self.manual_dns_auth_hook = ( '{0} -c "import os; import requests; import json; ' diff --git a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py index 5428f1a09..94e76cf79 100644 --- a/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py +++ b/certbot-ci/certbot_integration_tests/certbot_tests/test_main.py @@ -2,18 +2,25 @@ from __future__ import print_function import os +from os.path import exists +from os.path import join import re import shutil import subprocess import time -from os.path import join, exists import pytest + from certbot_integration_tests.certbot_tests import context as certbot_context -from certbot_integration_tests.certbot_tests.assertions import ( - assert_hook_execution, assert_saved_renew_hook, assert_cert_count_for_lineage, - assert_world_permissions, assert_equals_group_owner, assert_equals_permissions, -) +from certbot_integration_tests.certbot_tests.assertions import assert_cert_count_for_lineage +from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_owner +from certbot_integration_tests.certbot_tests.assertions import assert_equals_group_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_equals_world_read_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_hook_execution +from certbot_integration_tests.certbot_tests.assertions import assert_saved_renew_hook +from certbot_integration_tests.certbot_tests.assertions import assert_world_no_permissions +from certbot_integration_tests.certbot_tests.assertions import assert_world_read_permissions +from certbot_integration_tests.certbot_tests.assertions import EVERYBODY_SID from certbot_integration_tests.utils import misc @@ -59,11 +66,6 @@ def test_registration_override(context): context.certbot(['unregister']) context.certbot(['register', '--email', 'ex1@domain.org,ex2@domain.org']) - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the two following deprecated uses - context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org']) - context.certbot(['register', '--update-registration', '--email', 'ex1@domain.org,ex2@domain.org']) - context.certbot(['update_account', '--email', 'example@domain.org']) context.certbot(['update_account', '--email', 'ex1@domain.org,ex2@domain.org']) @@ -84,9 +86,9 @@ def test_http_01(context): context.certbot([ '--domains', certname, '--preferred-challenges', 'http-01', 'run', '--cert-name', certname, - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--deploy-hook', misc.echo('deploy', context.hook_probe), ]) assert_hook_execution(context.hook_probe, 'deploy') @@ -104,9 +106,9 @@ def test_manual_http_auth(context): '--cert-name', certname, '--manual-auth-hook', scripts[0], '--manual-cleanup-hook', scripts[1], - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--renew-hook', misc.echo('renew', context.hook_probe), ]) with pytest.raises(AssertionError): @@ -122,9 +124,9 @@ def test_manual_dns_auth(context): 'run', '--cert-name', certname, '--manual-auth-hook', context.manual_dns_auth_hook, '--manual-cleanup-hook', context.manual_dns_cleanup_hook, - '--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe), - '--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('wtf_pre', context.hook_probe), + '--post-hook', misc.echo('wtf_post', context.hook_probe), + '--renew-hook', misc.echo('renew', context.hook_probe), ]) with pytest.raises(AssertionError): @@ -173,21 +175,19 @@ def test_renew_files_permissions(context): certname = context.get_domain('renew') context.certbot(['-d', certname]) + privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem') + privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem') + assert_cert_count_for_lineage(context.config_dir, certname, 1) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0) + assert_world_no_permissions(privkey1) context.certbot(['renew']) assert_cert_count_for_lineage(context.config_dir, certname, 2) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0) - assert_equals_group_owner( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem')) - assert_equals_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + assert_world_no_permissions(privkey2) + assert_equals_group_owner(privkey1, privkey2) + assert_equals_world_read_permissions(privkey1, privkey2) + assert_equals_group_permissions(privkey1, privkey2) def test_renew_with_hook_scripts(context): @@ -211,15 +211,35 @@ def test_renew_files_propagate_permissions(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - os.chmod(join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0o444) + privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem') + privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem') + + if os.name != 'nt': + os.chmod(privkey1, 0o444) + else: + import win32security + import ntsecuritycon + # Get the current DACL of the private key + security = win32security.GetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + # Create a read permission for Everybody group + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_GENERIC_READ, everybody) + # Apply the updated DACL to the private key + security.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION, security) + context.certbot(['renew']) assert_cert_count_for_lineage(context.config_dir, certname, 2) - assert_world_permissions( - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 4) - assert_equals_permissions( - join(context.config_dir, 'archive', certname, 'privkey1.pem'), - join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074) + if os.name != 'nt': + # On Linux, read world permissions + all group permissions will be copied from the previous private key + assert_world_read_permissions(privkey2) + assert_equals_world_read_permissions(privkey1, privkey2) + assert_equals_group_permissions(privkey1, privkey2) + else: + # On Windows, world will never have any permissions, and group permission is irrelevant for this platform + assert_world_no_permissions(privkey2) def test_graceful_renew_it_is_not_time(context): @@ -229,7 +249,7 @@ def test_graceful_renew_it_is_not_time(context): assert_cert_count_for_lineage(context.config_dir, certname, 1) - context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)], force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 1) @@ -250,7 +270,7 @@ def test_graceful_renew_it_is_time(context): with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'w') as file: file.writelines(lines) - context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)], + context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)], force_renew=False) assert_cert_count_for_lineage(context.config_dir, certname, 2) @@ -317,9 +337,9 @@ def test_renew_hook_override(context): context.certbot([ 'certonly', '-d', certname, '--preferred-challenges', 'http-01', - '--pre-hook', 'echo pre >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo post >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('pre', context.hook_probe), + '--post-hook', misc.echo('post', context.hook_probe), + '--deploy-hook', misc.echo('deploy', context.hook_probe), ]) assert_hook_execution(context.hook_probe, 'pre') @@ -330,14 +350,14 @@ def test_renew_hook_override(context): open(context.hook_probe, 'w').close() context.certbot([ 'renew', '--cert-name', certname, - '--pre-hook', 'echo pre-override >> "{0}"'.format(context.hook_probe), - '--post-hook', 'echo post-override >> "{0}"'.format(context.hook_probe), - '--deploy-hook', 'echo deploy-override >> "{0}"'.format(context.hook_probe) + '--pre-hook', misc.echo('pre_override', context.hook_probe), + '--post-hook', misc.echo('post_override', context.hook_probe), + '--deploy-hook', misc.echo('deploy_override', context.hook_probe), ]) - assert_hook_execution(context.hook_probe, 'pre-override') - assert_hook_execution(context.hook_probe, 'post-override') - assert_hook_execution(context.hook_probe, 'deploy-override') + assert_hook_execution(context.hook_probe, 'pre_override') + assert_hook_execution(context.hook_probe, 'post_override') + assert_hook_execution(context.hook_probe, 'deploy_override') with pytest.raises(AssertionError): assert_hook_execution(context.hook_probe, 'pre') with pytest.raises(AssertionError): @@ -349,11 +369,11 @@ def test_renew_hook_override(context): open(context.hook_probe, 'w').close() context.certbot(['renew', '--cert-name', certname]) - assert_hook_execution(context.hook_probe, 'pre-override') - assert_hook_execution(context.hook_probe, 'post-override') - assert_hook_execution(context.hook_probe, 'deploy-override') + assert_hook_execution(context.hook_probe, 'pre_override') + assert_hook_execution(context.hook_probe, 'post_override') + assert_hook_execution(context.hook_probe, 'deploy_override') + - def test_invalid_domain_with_dns_challenge(context): """Test certificate issuance failure with DNS-01 challenge.""" # Manual dns auth hooks from misc are designed to fail if the domain contains 'fail-*'. @@ -512,7 +532,7 @@ def test_revoke_multiple_lineages(context): data = file.read() data = re.sub('archive_dir = .*\n', - 'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1)), + 'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1).replace('\\', '\\\\')), data) with open(join(context.config_dir, 'renewal', '{0}.conf'.format(cert2)), 'w') as file: @@ -555,11 +575,9 @@ def test_ocsp_status_stale(context): def test_ocsp_status_live(context): """Test retrieval of OCSP statuses for live config""" - if context.acme_server == 'pebble': - pytest.skip('Pebble does not support OCSP status requests.') + cert = context.get_domain('ocsp-check') # OSCP 1: Check live certificate OCSP status (VALID) - cert = context.get_domain('ocsp-check') context.certbot(['--domains', cert]) output = context.certbot(['certificates']) @@ -575,3 +593,21 @@ def test_ocsp_status_live(context): assert output.count('INVALID') == 1, 'Expected {0} to be INVALID'.format(cert) assert output.count('REVOKED') == 1, 'Expected {0} to be REVOKED'.format(cert) + + +def test_dry_run_deactivate_authzs(context): + """Test that Certbot deactivates authorizations when performing a dry run""" + + name = context.get_domain('dry-run-authz-deactivation') + args = ['certonly', '--cert-name', name, '-d', name, '--dry-run'] + log_line = 'Recreating order after authz deactivation' + + # First order will not need deactivation + context.certbot(args) + with open(join(context.workspace, 'logs', 'letsencrypt.log'), 'r') as f: + assert log_line not in f.read(), 'First order should not have had any authz reuse' + + # Second order will require deactivation + context.certbot(args) + with open(join(context.workspace, 'logs', 'letsencrypt.log'), 'r') as f: + assert log_line in f.read(), 'Second order should have been recreated due to authz reuse' diff --git a/certbot-ci/certbot_integration_tests/conftest.py b/certbot-ci/certbot_integration_tests/conftest.py index e84a866b9..6eb9ee865 100644 --- a/certbot-ci/certbot_integration_tests/conftest.py +++ b/certbot-ci/certbot_integration_tests/conftest.py @@ -6,9 +6,10 @@ for a directory a specific configuration using built-in pytest hooks. See https://docs.pytest.org/en/latest/reference.html#hook-reference """ +from __future__ import print_function import contextlib -import sys import subprocess +import sys from certbot_integration_tests.utils import acme_server as acme_lib @@ -68,17 +69,18 @@ def _setup_primary_node(config): :param config: Configuration of the pytest primary node """ # Check for runtime compatibility: some tools are required to be available in PATH - try: - subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT) - except (subprocess.CalledProcessError, OSError): - raise ValueError('Error: docker is required in PATH to launch the integration tests, ' - 'but is not installed or not available for current user.') + if 'boulder' in config.option.acme_server: + try: + subprocess.check_output(['docker', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker is required in PATH to launch the integration tests on' + 'boulder, but is not installed or not available for current user.') - try: - subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT) - except (subprocess.CalledProcessError, OSError): - raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, ' - 'but is not installed or not available for current user.') + try: + subprocess.check_output(['docker-compose', '-v'], stderr=subprocess.STDOUT) + except (subprocess.CalledProcessError, OSError): + raise ValueError('Error: docker-compose is required in PATH to launch the integration tests, ' + 'but is not installed or not available for current user.') # Parameter numprocesses is added to option by pytest-xdist workers = ['primary'] if not config.option.numprocesses\ @@ -86,7 +88,7 @@ def _setup_primary_node(config): # By calling setup_acme_server we ensure that all necessary acme server instances will be # fully started. This runtime is reflected by the acme_xdist returned. - acme_server = acme_lib.setup_acme_server(config.option.acme_server, workers) + acme_server = acme_lib.ACMEServer(config.option.acme_server, workers) config.add_cleanup(acme_server.stop) print('ACME xdist config:\n{0}'.format(acme_server.acme_xdist)) acme_server.start() diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/context.py b/certbot-ci/certbot_integration_tests/nginx_tests/context.py index 61facc6af..3a769840c 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/context.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/context.py @@ -2,8 +2,9 @@ import os import subprocess from certbot_integration_tests.certbot_tests import context as certbot_context -from certbot_integration_tests.utils import misc, certbot_call from certbot_integration_tests.nginx_tests import nginx_config as config +from certbot_integration_tests.utils import certbot_call +from certbot_integration_tests.utils import misc class IntegrationTestsContext(certbot_context.IntegrationTestsContext): diff --git a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py index a6305c3cc..18991ae62 100644 --- a/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py +++ b/certbot-ci/certbot_integration_tests/nginx_tests/nginx_config.py @@ -21,9 +21,9 @@ def construct_nginx_config(nginx_root, nginx_webroot, http_port, https_port, oth :rtype: str """ key_path = key_path if key_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_key.pem') + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/key.pem') cert_path = cert_path if cert_path \ - else pkg_resources.resource_filename('certbot_integration_tests', 'assets/nginx_cert.pem') + else pkg_resources.resource_filename('certbot_integration_tests', 'assets/cert.pem') return '''\ # This error log will be written regardless of server scope error_log # definitions, so we have to set this here in the main scope. diff --git a/certbot-ci/certbot_integration_tests/utils/acme_server.py b/certbot-ci/certbot_integration_tests/utils/acme_server.py index f8f4b2c69..fbf97fef1 100755 --- a/certbot-ci/certbot_integration_tests/utils/acme_server.py +++ b/certbot-ci/certbot_integration_tests/utils/acme_server.py @@ -1,186 +1,200 @@ #!/usr/bin/env python """Module to setup an ACME CA server environment able to run multiple tests in parallel""" from __future__ import print_function + +import errno import json +import os +from os.path import join +import shutil +import subprocess +import sys import tempfile import time -import os -import subprocess -import shutil -import sys -from os.path import join import requests -import yaml -from certbot_integration_tests.utils import misc, proxy +from certbot_integration_tests.utils import misc +from certbot_integration_tests.utils import pebble_artifacts +from certbot_integration_tests.utils import proxy from certbot_integration_tests.utils.constants import * class ACMEServer(object): """ - Handler exposing methods to start and stop the ACME server, and get its configuration - (eg. challenges ports). ACMEServer is also a context manager, and so can be used to - ensure ACME server is started/stopped upon context enter/exit. + ACMEServer configures and handles the lifecycle of an ACME CA server and an HTTP reverse proxy + instance, to allow parallel execution of integration tests against the unique http-01 port + expected by the ACME CA server. + Typically all pytest integration tests will be executed in this context. + ACMEServer gives access the acme_xdist parameter, listing the ports and directory url to use + for each pytest node. It exposes also start and stop methods in order to start the stack, and + stop it with proper resources cleanup. + ACMEServer is also a context manager, and so can be used to ensure ACME server is started/stopped + upon context enter/exit. """ - def __init__(self, acme_xdist, start, server_cleanup): - self._proxy_process = None - self._server_cleanup = server_cleanup - self.acme_xdist = acme_xdist - self.start = start + def __init__(self, acme_server, nodes, http_proxy=True, stdout=False): + """ + Create an ACMEServer instance. + :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) + :param list nodes: list of node names that will be setup by pytest xdist + :param bool http_proxy: if False do not start the HTTP proxy + :param bool stdout: if True stream subprocesses stdout to standard stdout + """ + self._construct_acme_xdist(acme_server, nodes) + + self._acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' + self._proxy = http_proxy + self._workspace = tempfile.mkdtemp() + self._processes = [] + self._stdout = sys.stdout if stdout else open(os.devnull, 'w') + + def start(self): + """Start the test stack""" + try: + if self._proxy: + self._prepare_http_proxy() + if self._acme_type == 'pebble': + self._prepare_pebble_server() + if self._acme_type == 'boulder': + self._prepare_boulder_server() + except BaseException as e: + self.stop() + raise e def stop(self): - if self._proxy_process: - self._proxy_process.terminate() - self._proxy_process.wait() - self._server_cleanup() + """Stop the test stack, and clean its resources""" + print('=> Tear down the test infrastructure...') + try: + for process in self._processes: + try: + process.terminate() + except OSError as e: + # Process may be not started yet, so no PID and terminate fails. + # Then the process never started, and the situation is acceptable. + if e.errno != errno.ESRCH: + raise + for process in self._processes: + process.wait() + + if os.path.exists(os.path.join(self._workspace, 'boulder')): + # Boulder docker generates build artifacts owned by root with 0o744 permissions. + # If we started the acme server from a normal user that has access to the Docker + # daemon, this user will not be able to delete these artifacts from the host. + # We need to do it through a docker. + process = self._launch_process(['docker', 'run', '--rm', '-v', + '{0}:/workspace'.format(self._workspace), + 'alpine', 'rm', '-rf', '/workspace/boulder']) + process.wait() + finally: + shutil.rmtree(self._workspace) + if self._stdout != sys.stdout: + self._stdout.close() + print('=> Test infrastructure stopped and cleaned up.') def __enter__(self): - self._proxy_process = self.start() + self.start() return self.acme_xdist def __exit__(self, exc_type, exc_val, exc_tb): self.stop() + def _construct_acme_xdist(self, acme_server, nodes): + """Generate and return the acme_xdist dict""" + acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT} -def setup_acme_server(acme_server, nodes, proxy=True): - """ - This method will setup an ACME CA server and an HTTP reverse proxy instance, to allow parallel - execution of integration tests against the unique http-01 port expected by the ACME CA server. - Typically all pytest integration tests will be executed in this context. - An ACMEServer instance will be returned, giving access to the ports and directory url to use - for each pytest node, and its start and stop methods are appropriately configured to - respectively start the server, and stop it with proper resources cleanup. - :param str acme_server: the type of acme server used (boulder-v1, boulder-v2 or pebble) - :param str[] nodes: list of node names that will be setup by pytest xdist - :param bool proxy: set to False to not start the HTTP proxy - :return: a properly configured ACMEServer instance - :rtype: ACMEServer - """ - acme_type = 'pebble' if acme_server == 'pebble' else 'boulder' - acme_xdist = _construct_acme_xdist(acme_server, nodes) - workspace, server_cleanup = _construct_workspace(acme_type) + # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. + if acme_server == 'pebble': + acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL + else: # boulder + acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \ + if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL - def start(): - proxy_process = _prepare_http_proxy(acme_xdist) if proxy else None - _prepare_acme_server(workspace, acme_type, acme_xdist) + acme_xdist['http_port'] = {node: port for (node, port) + in zip(nodes, range(5200, 5200 + len(nodes)))} + acme_xdist['https_port'] = {node: port for (node, port) + in zip(nodes, range(5100, 5100 + len(nodes)))} + acme_xdist['other_port'] = {node: port for (node, port) + in zip(nodes, range(5300, 5300 + len(nodes)))} - return proxy_process + self.acme_xdist = acme_xdist - return ACMEServer(acme_xdist, start, server_cleanup) + def _prepare_pebble_server(self): + """Configure and launch the Pebble server""" + print('=> Starting pebble instance deployment...') + pebble_path, challtestsrv_path, pebble_config_path = pebble_artifacts.fetch(self._workspace) + # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid + # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. + environ = os.environ.copy() + environ['PEBBLE_VA_NOSLEEP'] = '1' + environ['PEBBLE_WFE_NONCEREJECT'] = '0' + environ['PEBBLE_AUTHZREUSE'] = '100' -def _construct_acme_xdist(acme_server, nodes): - """Generate and return the acme_xdist dict""" - acme_xdist = {'acme_server': acme_server, 'challtestsrv_port': CHALLTESTSRV_PORT} + self._launch_process( + [pebble_path, '-config', pebble_config_path, '-dnsserver', '127.0.0.1:8053'], + env=environ) - # Directory and ACME port are set implicitly in the docker-compose.yml files of Boulder/Pebble. - if acme_server == 'pebble': - acme_xdist['directory_url'] = PEBBLE_DIRECTORY_URL - else: # boulder - acme_xdist['directory_url'] = BOULDER_V2_DIRECTORY_URL \ - if acme_server == 'boulder-v2' else BOULDER_V1_DIRECTORY_URL + self._launch_process( + [challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""', + '-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""']) - acme_xdist['http_port'] = {node: port for (node, port) - in zip(nodes, range(5200, 5200 + len(nodes)))} - acme_xdist['https_port'] = {node: port for (node, port) - in zip(nodes, range(5100, 5100 + len(nodes)))} - acme_xdist['other_port'] = {node: port for (node, port) - in zip(nodes, range(5300, 5300 + len(nodes)))} - - return acme_xdist - - -def _construct_workspace(acme_type): - """Create a temporary workspace for integration tests stack""" - workspace = tempfile.mkdtemp() - - def cleanup(): - """Cleanup function to call that will teardown relevant dockers and their configuration.""" - print('=> Tear down the {0} instance...'.format(acme_type)) - instance_path = join(workspace, acme_type) - try: - if os.path.isfile(join(instance_path, 'docker-compose.yml')): - _launch_command(['docker-compose', 'down'], cwd=instance_path) - except subprocess.CalledProcessError: - pass - print('=> Finished tear down of {0} instance.'.format(acme_type)) - - if acme_type == 'boulder' and os.path.exists(os.path.join(workspace, 'boulder')): - # Boulder docker generates build artifacts owned by root user with 0o744 permissions. - # If we started the acme server from a normal user that has access to the Docker - # daemon, this user will not be able to delete these artifacts from the host. - # We need to do it through a docker. - _launch_command(['docker', 'run', '--rm', '-v', '{0}:/workspace'.format(workspace), - 'alpine', 'rm', '-rf', '/workspace/boulder']) - - shutil.rmtree(workspace) - - return workspace, cleanup - - -def _prepare_acme_server(workspace, acme_type, acme_xdist): - """Configure and launch the ACME server, Boulder or Pebble""" - print('=> Starting {0} instance deployment...'.format(acme_type)) - instance_path = join(workspace, acme_type) - try: - # Load Boulder/Pebble from git, that includes a docker-compose.yml ready for production. - _launch_command(['git', 'clone', 'https://github.com/letsencrypt/{0}'.format(acme_type), - '--single-branch', '--depth=1', instance_path]) - if acme_type == 'boulder': - # Allow Boulder to ignore usual limit rate policies, useful for tests. - os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), - join(instance_path, 'test/rate-limit-policies.yml')) - if acme_type == 'pebble': - # Configure Pebble at full speed (PEBBLE_VA_NOSLEEP=1) and not randomly refusing valid - # nonce (PEBBLE_WFE_NONCEREJECT=0) to have a stable test environment. - with open(os.path.join(instance_path, 'docker-compose.yml'), 'r') as file_handler: - config = yaml.load(file_handler.read()) - - config['services']['pebble'].setdefault('environment', [])\ - .extend(['PEBBLE_VA_NOSLEEP=1', 'PEBBLE_WFE_NONCEREJECT=0']) - with open(os.path.join(instance_path, 'docker-compose.yml'), 'w') as file_handler: - file_handler.write(yaml.dump(config)) - - # Launch the ACME CA server. - _launch_command(['docker-compose', 'up', '--force-recreate', '-d'], cwd=instance_path) + # pebble_ocsp_server is imported here and not at the top of module in order to avoid a useless + # ImportError, in the case where cryptography dependency is too old to support ocsp, but + # Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is the typical + # situation of integration-certbot-oldest tox testenv. + from certbot_integration_tests.utils import pebble_ocsp_server + self._launch_process([sys.executable, pebble_ocsp_server.__file__]) # Wait for the ACME CA server to be up. - print('=> Waiting for {0} instance to respond...'.format(acme_type)) - misc.check_until_timeout(acme_xdist['directory_url']) + print('=> Waiting for pebble instance to respond...') + misc.check_until_timeout(self.acme_xdist['directory_url']) + + print('=> Finished pebble instance deployment.') + + def _prepare_boulder_server(self): + """Configure and launch the Boulder server""" + print('=> Starting boulder instance deployment...') + instance_path = join(self._workspace, 'boulder') + + # Load Boulder from git, that includes a docker-compose.yml ready for production. + process = self._launch_process(['git', 'clone', 'https://github.com/letsencrypt/boulder', + '--single-branch', '--depth=1', instance_path]) + process.wait() + + # Allow Boulder to ignore usual limit rate policies, useful for tests. + os.rename(join(instance_path, 'test/rate-limit-policies-b.yml'), + join(instance_path, 'test/rate-limit-policies.yml')) + + # Launch the Boulder server + self._launch_process(['docker-compose', 'up', '--force-recreate'], cwd=instance_path) + + # Wait for the ACME CA server to be up. + print('=> Waiting for boulder instance to respond...') + misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=240) # Configure challtestsrv to answer any A record request with ip of the docker host. - acme_subnet = '10.77.77' if acme_type == 'boulder' else '10.30.50' - response = requests.post('http://localhost:{0}/set-default-ipv4' - .format(acme_xdist['challtestsrv_port']), - json={'ip': '{0}.1'.format(acme_subnet)}) + response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT), + json={'ip': '10.77.77.1'}) response.raise_for_status() - print('=> Finished {0} instance deployment.'.format(acme_type)) - except BaseException: - print('Error while setting up {0} instance.'.format(acme_type)) - raise + print('=> Finished boulder instance deployment.') + def _prepare_http_proxy(self): + """Configure and launch an HTTP proxy""" + print('=> Configuring the HTTP proxy...') + mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) + for node, port in self.acme_xdist['http_port'].items()} + command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)] + self._launch_process(command) + print('=> Finished configuring the HTTP proxy.') -def _prepare_http_proxy(acme_xdist): - """Configure and launch an HTTP proxy""" - print('=> Configuring the HTTP proxy...') - mapping = {r'.+\.{0}\.wtf'.format(node): 'http://127.0.0.1:{0}'.format(port) - for node, port in acme_xdist['http_port'].items()} - command = [sys.executable, proxy.__file__, str(HTTP_01_PORT), json.dumps(mapping)] - process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) - print('=> Finished configuring the HTTP proxy.') - - return process - - -def _launch_command(command, cwd=os.getcwd()): - """Launch silently an OS command, output will be displayed in case of failure""" - try: - subprocess.check_output(command, stderr=subprocess.STDOUT, cwd=cwd, universal_newlines=True) - except subprocess.CalledProcessError as e: - sys.stderr.write(e.output) - raise + def _launch_process(self, command, cwd=os.getcwd(), env=None): + """Launch silently an subprocess OS command""" + if not env: + env = os.environ + process = subprocess.Popen(command, stdout=self._stdout, stderr=subprocess.STDOUT, cwd=cwd, env=env) + self._processes.append(process) + return process def main(): @@ -191,8 +205,7 @@ def main(): raise ValueError('Invalid server value {0}, should be one of {1}' .format(server_type, possible_values)) - acme_server = setup_acme_server(server_type, [], False) - process = None + acme_server = ACMEServer(server_type, [], http_proxy=False, stdout=True) try: with acme_server as acme_xdist: @@ -200,15 +213,10 @@ def main(): .format(acme_xdist['directory_url'])) print('--> Press CTRL+C to stop the ACME server.') - docker_name = 'pebble_pebble_1' if 'pebble' in server_type else 'boulder_boulder_1' - process = subprocess.Popen(['docker', 'logs', '-f', docker_name]) - while True: time.sleep(3600) except KeyboardInterrupt: - if process: - process.terminate() - process.wait() + pass if __name__ == '__main__': diff --git a/certbot-ci/certbot_integration_tests/utils/certbot_call.py b/certbot-ci/certbot_integration_tests/utils/certbot_call.py index 1bff94e75..2ddaa41c8 100755 --- a/certbot-ci/certbot_integration_tests/utils/certbot_call.py +++ b/certbot-ci/certbot_integration_tests/utils/certbot_call.py @@ -1,12 +1,13 @@ #!/usr/bin/env python """Module to call certbot in test mode""" from __future__ import absolute_import + from distutils.version import LooseVersion +import os import subprocess import sys -import os -from certbot_integration_tests.utils import misc +import certbot_integration_tests from certbot_integration_tests.utils.constants import * @@ -33,18 +34,58 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port, return subprocess.check_output(command, universal_newlines=True, cwd=workspace, env=env) -def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, - config_dir, workspace, force_renew): +def _prepare_environ(workspace): new_environ = os.environ.copy() new_environ['TMPDIR'] = workspace + # So, pytest is nice, and a little too nice for our usage. + # In order to help user to call seamlessly any piece of python code without requiring to + # install it as a full-fledged setuptools distribution for instance, it may inject the path + # to the test files into the PYTHONPATH. This allows the python interpreter to import + # as modules any python file available at this path. + # See https://docs.pytest.org/en/3.2.5/pythonpath.html for the explanation and description. + # However this behavior is not good in integration tests, in particular the nginx oldest ones. + # Indeed during these kind of tests certbot is installed as a transitive dependency to + # certbot-nginx. Here is the trick: this certbot version is not necessarily the same as + # the certbot codebase lying in current working directory. For instance in oldest tests + # certbot==0.36.0 may be installed while the codebase corresponds to certbot==0.37.0.dev0. + # Then during a pytest run, PYTHONPATH contains the path to the Certbot codebase, so invoking + # certbot will import the modules from the codebase (0.37.0.dev0), not from the + # required/installed version (0.36.0). + # This will lead to funny and totally incomprehensible errors. To avoid that, we ensure that + # if PYTHONPATH is set, it does not contain the path to the root of the codebase. + if new_environ.get('PYTHONPATH'): + # certbot_integration_tests.__file__ is: + # '/path/to/certbot/certbot-ci/certbot_integration_tests/__init__.pyc' + # ... and we want '/path/to/certbot' + certbot_root = os.path.dirname(os.path.dirname(os.path.dirname(certbot_integration_tests.__file__))) + python_paths = [path for path in new_environ['PYTHONPATH'].split(':') if path != certbot_root] + new_environ['PYTHONPATH'] = ':'.join(python_paths) + + return new_environ + + +def _compute_additional_args(workspace, environ, force_renew): additional_args = [] - if misc.get_certbot_version() >= LooseVersion('0.30.0'): + output = subprocess.check_output(['certbot', '--version'], + universal_newlines=True, stderr=subprocess.STDOUT, + cwd=workspace, env=environ) + version_str = output.split(' ')[1].strip() # Typical response is: output = 'certbot 0.31.0.dev0' + if LooseVersion(version_str) >= LooseVersion('0.30.0'): additional_args.append('--no-random-sleep-on-renew') if force_renew: additional_args.append('--renew-by-default') + return additional_args + + +def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port, + config_dir, workspace, force_renew): + + new_environ = _prepare_environ(workspace) + additional_args = _compute_additional_args(workspace, new_environ, force_renew) + command = [ 'certbot', '--server', directory_url, diff --git a/certbot-ci/certbot_integration_tests/utils/constants.py b/certbot-ci/certbot_integration_tests/utils/constants.py index 9266e25ea..dfdeda411 100644 --- a/certbot-ci/certbot_integration_tests/utils/constants.py +++ b/certbot-ci/certbot_integration_tests/utils/constants.py @@ -5,3 +5,5 @@ CHALLTESTSRV_PORT = 8055 BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory' BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory' PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir' +PEBBLE_MANAGEMENT_URL = 'https://localhost:15000' +MOCK_OCSP_SERVER_PORT = 4002 diff --git a/certbot-ci/certbot_integration_tests/utils/misc.py b/certbot-ci/certbot_integration_tests/utils/misc.py index 2144b9cee..b08f11e89 100644 --- a/certbot-ci/certbot_integration_tests/utils/misc.py +++ b/certbot-ci/certbot_integration_tests/utils/misc.py @@ -6,34 +6,36 @@ import contextlib import errno import multiprocessing import os +import re import shutil import stat -import subprocess import sys import tempfile import time import warnings -from distutils.version import LooseVersion -import pkg_resources -import requests -from OpenSSL import crypto from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.asymmetric import ec -from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption -from six.moves import socketserver, SimpleHTTPServer - +from cryptography.hazmat.primitives.serialization import Encoding +from cryptography.hazmat.primitives.serialization import NoEncryption +from cryptography.hazmat.primitives.serialization import PrivateFormat +from OpenSSL import crypto +import pkg_resources +import requests +from six.moves import SimpleHTTPServer +from six.moves import socketserver RSA_KEY_TYPE = 'rsa' ECDSA_KEY_TYPE = 'ecdsa' -def check_until_timeout(url): +def check_until_timeout(url, attempts=30): """ Wait and block until given url responds with status 200, or raise an exception - after 150 attempts. + after the specified number of attempts. :param str url: the URL to test - :raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL + :param int attempts: the number of times to try to connect to the URL + :raise ValueError: exception raised if unable to reach the URL """ try: import urllib3 @@ -43,7 +45,7 @@ def check_until_timeout(url): from requests.packages.urllib3.exceptions import InsecureRequestWarning requests.packages.urllib3.disable_warnings(InsecureRequestWarning) - for _ in range(0, 150): + for _ in range(attempts): time.sleep(1) try: if requests.get(url, verify=False).status_code == 200: @@ -51,7 +53,7 @@ def check_until_timeout(url): except requests.exceptions.ConnectionError: pass - raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url)) + raise ValueError('Error, url did not respond after {0} attempts: {1}'.format(attempts, url)) class GracefulTCPServer(socketserver.TCPServer): @@ -62,6 +64,10 @@ class GracefulTCPServer(socketserver.TCPServer): allow_reuse_address = True +def _run_server(port): + GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever() + + @contextlib.contextmanager def create_http_server(port): """ @@ -74,10 +80,7 @@ def create_http_server(port): current_cwd = os.getcwd() webroot = tempfile.mkdtemp() - def run(): - GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever() - - process = multiprocessing.Process(target=run) + process = multiprocessing.Process(target=_run_server, args=(port,)) try: # SimpleHTTPServer is designed to serve files from the current working directory at the @@ -119,15 +122,9 @@ def generate_test_file_hooks(config_dir, hook_probe): :param str config_dir: current certbot config directory :param hook_probe: path to the hook probe to test hook scripts execution """ - if sys.platform == 'win32': - extension = 'bat' - else: - extension = 'sh' + hook_path = pkg_resources.resource_filename('certbot_integration_tests', 'assets/hook.py') - renewal_hooks_dirs = list_renewal_hooks_dirs(config_dir) - renewal_deploy_hook_path = os.path.join(renewal_hooks_dirs[1], 'hook.sh') - - for hook_dir in renewal_hooks_dirs: + for hook_dir in list_renewal_hooks_dirs(config_dir): # We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of # the hierarchy already exists. It is not the case of os.makedirs. Python 3 has an # optional parameter `exists_ok` to not fail on existing dir, but Python 2.7 does not. @@ -137,26 +134,25 @@ def generate_test_file_hooks(config_dir, hook_probe): except OSError as error: if error.errno != errno.EEXIST: raise - hook_path = os.path.join(hook_dir, 'hook.{0}'.format(extension)) - if extension == 'sh': - data = '''\ -#!/bin/bash -xe -if [ "$0" = "{0}" ]; then - if [ -z "$RENEWED_DOMAINS" -o -z "$RENEWED_LINEAGE" ]; then - echo "Environment variables not properly set!" >&2 - exit 1 - fi -fi -echo $(basename $(dirname "$0")) >> "{1}"\ -'''.format(renewal_deploy_hook_path, hook_probe) - else: - # TODO: Write the equivalent bat file for Windows - data = '''\ -''' - with open(hook_path, 'w') as file: - file.write(data) - os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IEXEC) + if os.name != 'nt': + entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.sh') + entrypoint_script = '''\ +#!/usr/bin/env bash +set -e +"{0}" "{1}" "{2}" "{3}" +'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe) + else: + entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.bat') + entrypoint_script = '''\ +@echo off +"{0}" "{1}" "{2}" "{3}" + '''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe) + + with open(entrypoint_script_path, 'w') as file_h: + file_h.write(entrypoint_script) + + os.chmod(entrypoint_script_path, os.stat(entrypoint_script_path).st_mode | stat.S_IEXEC) @contextlib.contextmanager @@ -193,7 +189,7 @@ for _ in range(0, 10): except requests.exceptions.ConnectionError: pass raise ValueError('Error, url did not respond after 10 attempts: {{0}}'.format(url)) -'''.format(http_server_root, http_port)) +'''.format(http_server_root.replace('\\', '\\\\'), http_port)) os.chmod(auth_script_path, 0o755) cleanup_script_path = os.path.join(tempdir, 'cleanup.py') @@ -204,7 +200,7 @@ import os import shutil well_known = os.path.join('{0}', '.well-known') shutil.rmtree(well_known) -'''.format(http_server_root)) +'''.format(http_server_root.replace('\\', '\\\\'))) os.chmod(cleanup_script_path, 0o755) yield ('{0} {1}'.format(sys.executable, auth_script_path), @@ -213,18 +209,6 @@ shutil.rmtree(well_known) shutil.rmtree(tempdir) -def get_certbot_version(): - """ - Find the version of the certbot available in PATH. - :return str: the certbot version - """ - output = subprocess.check_output(['certbot', '--version'], - universal_newlines=True, stderr=subprocess.STDOUT) - # Typical response is: output = 'certbot 0.31.0.dev0' - version_str = output.split(' ')[1].strip() - return LooseVersion(version_str) - - def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE): """ Generate a private key, and a CSR for the given domains using this key. @@ -287,4 +271,32 @@ def load_sample_data_path(workspace): original = pkg_resources.resource_filename('certbot_integration_tests', 'assets/sample-config') copied = os.path.join(workspace, 'sample-config') shutil.copytree(original, copied, symlinks=True) + + if os.name == 'nt': + # Fix the symlinks on Windows since GIT is not creating them upon checkout + for lineage in ['a.encryption-example.com', 'b.encryption-example.com']: + current_live = os.path.join(copied, 'live', lineage) + for name in os.listdir(current_live): + if name != 'README': + current_file = os.path.join(current_live, name) + with open(current_file) as file_h: + src = file_h.read() + os.unlink(current_file) + os.symlink(os.path.join(current_live, src), current_file) + return copied + + +def echo(keyword, path=None): + """ + Generate a platform independent executable command + that echoes the given keyword into the given file. + :param keyword: the keyword to echo (must be a single keyword) + :param path: path to the file were keyword is echoed + :return: the executable command + """ + if not re.match(r'^\w+$', keyword): + raise ValueError('Error, keyword `{0}` is not a single keyword.' + .format(keyword)) + return '{0} -c "from __future__ import print_function; print(\'{1}\')"{2}'.format( + os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '') diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py new file mode 100644 index 000000000..2b1557928 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py @@ -0,0 +1,53 @@ +import json +import os +import stat + +import pkg_resources +import requests + +from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT + +PEBBLE_VERSION = 'v2.2.1' +ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets') + + +def fetch(workspace): + suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe' + + pebble_path = _fetch_asset('pebble', suffix) + challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix) + pebble_config_path = _build_pebble_config(workspace) + + return pebble_path, challtestsrv_path, pebble_config_path + + +def _fetch_asset(asset, suffix): + asset_path = os.path.join(ASSETS_PATH, '{0}_{1}_{2}'.format(asset, PEBBLE_VERSION, suffix)) + if not os.path.exists(asset_path): + asset_url = ('https://github.com/letsencrypt/pebble/releases/download/{0}/{1}_{2}' + .format(PEBBLE_VERSION, asset, suffix)) + response = requests.get(asset_url) + response.raise_for_status() + with open(asset_path, 'wb') as file_h: + file_h.write(response.content) + os.chmod(asset_path, os.stat(asset_path).st_mode | stat.S_IEXEC) + + return asset_path + + +def _build_pebble_config(workspace): + config_path = os.path.join(workspace, 'pebble-config.json') + with open(config_path, 'w') as file_h: + file_h.write(json.dumps({ + 'pebble': { + 'listenAddress': '0.0.0.0:14000', + 'managementListenAddress': '0.0.0.0:15000', + 'certificate': os.path.join(ASSETS_PATH, 'cert.pem'), + 'privateKey': os.path.join(ASSETS_PATH, 'key.pem'), + 'httpPort': 5002, + 'tlsPort': 5001, + 'ocspResponderURL': 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT), + }, + })) + + return config_path diff --git a/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py new file mode 100755 index 000000000..9458560e8 --- /dev/null +++ b/certbot-ci/certbot_integration_tests/utils/pebble_ocsp_server.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python +""" +This runnable module interfaces itself with the Pebble management interface in order +to serve a mock OCSP responder during integration tests against Pebble. +""" +import datetime +import re + +from cryptography import x509 +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.x509 import ocsp +from dateutil import parser +import requests +from six.moves import BaseHTTPServer + +from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT +from certbot_integration_tests.utils.constants import PEBBLE_MANAGEMENT_URL +from certbot_integration_tests.utils.misc import GracefulTCPServer + + +class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler): + def do_POST(self): + request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediate-keys/0', verify=False) + issuer_key = serialization.load_pem_private_key(request.content, None, default_backend()) + + request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediates/0', verify=False) + issuer_cert = x509.load_pem_x509_certificate(request.content, default_backend()) + + try: + content_len = int(self.headers.getheader('content-length', 0)) + except AttributeError: + content_len = int(self.headers.get('Content-Length')) + + ocsp_request = ocsp.load_der_ocsp_request(self.rfile.read(content_len)) + response = requests.get('{0}/cert-status-by-serial/{1}'.format( + PEBBLE_MANAGEMENT_URL, str(hex(ocsp_request.serial_number)).replace('0x', '')), verify=False) + + if not response.ok: + ocsp_response = ocsp.OCSPResponseBuilder.build_unsuccessful(ocsp.OCSPResponseStatus.UNAUTHORIZED) + else: + data = response.json() + + now = datetime.datetime.utcnow() + cert = x509.load_pem_x509_certificate(data['Certificate'].encode(), default_backend()) + if data['Status'] != 'Revoked': + ocsp_status, revocation_time, revocation_reason = ocsp.OCSPCertStatus.GOOD, None, None + else: + ocsp_status, revocation_reason = ocsp.OCSPCertStatus.REVOKED, x509.ReasonFlags.unspecified + revoked_at = re.sub(r'( \+\d{4}).*$', r'\1', data['RevokedAt']) # "... +0000 UTC" => "+0000" + revocation_time = parser.parse(revoked_at) + + ocsp_response = ocsp.OCSPResponseBuilder().add_response( + cert=cert, issuer=issuer_cert, algorithm=hashes.SHA1(), + cert_status=ocsp_status, + this_update=now, next_update=now + datetime.timedelta(hours=1), + revocation_time=revocation_time, revocation_reason=revocation_reason + ).responder_id( + ocsp.OCSPResponderEncoding.NAME, issuer_cert + ).sign(issuer_key, hashes.SHA256()) + + self.send_response(200) + self.end_headers() + self.wfile.write(ocsp_response.public_bytes(serialization.Encoding.DER)) + + +if __name__ == '__main__': + try: + GracefulTCPServer(('', MOCK_OCSP_SERVER_PORT), _ProxyHandler).serve_forever() + except KeyboardInterrupt: + pass diff --git a/certbot-ci/certbot_integration_tests/utils/proxy.py b/certbot-ci/certbot_integration_tests/utils/proxy.py index 69248c771..3a16adebf 100644 --- a/certbot-ci/certbot_integration_tests/utils/proxy.py +++ b/certbot-ci/certbot_integration_tests/utils/proxy.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import json -import sys import re +import sys import requests from six.moves import BaseHTTPServer diff --git a/certbot-ci/setup.py b/certbot-ci/setup.py index 2adf137b8..fb82b6ca5 100644 --- a/certbot-ci/setup.py +++ b/certbot-ci/setup.py @@ -1,21 +1,37 @@ -from setuptools import setup -from setuptools import find_packages +from distutils.version import StrictVersion +import sys +from setuptools import __version__ as setuptools_version +from setuptools import find_packages +from setuptools import setup version = '0.32.0.dev0' install_requires = [ 'coverage', 'cryptography', + 'docker-compose', 'pyopenssl', 'pytest', 'pytest-cov', 'pytest-xdist', + 'python-dateutil', 'pyyaml', 'requests', 'six', ] +# Add pywin32 on Windows platforms to handle low-level system calls. +# This dependency needs to be added using environment markers to avoid its installation on Linux. +# However environment markers are supported only with setuptools >= 36.2. +# So this dependency is not added for old Linux distributions with old setuptools, +# in order to allow these systems to build certbot from sources. +if StrictVersion(setuptools_version) >= StrictVersion('36.2'): + install_requires.append("pywin32>=224 ; sys_platform == 'win32'") +elif 'bdist_wheel' in sys.argv[1:]: + raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' + 'of setuptools. Version 36.2+ of setuptools is required.') + setup( name='certbot-ci', version=version, @@ -37,6 +53,7 @@ setup( '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', ], diff --git a/certbot-compatibility-test/Dockerfile b/certbot-compatibility-test/Dockerfile index 1e9e0d727..a9996f779 100644 --- a/certbot-compatibility-test/Dockerfile +++ b/certbot-compatibility-test/Dockerfile @@ -1,4 +1,4 @@ -FROM debian:jessie +FROM debian:stretch MAINTAINER Brad Warren # no need to mkdir anything: @@ -14,7 +14,7 @@ RUN /opt/certbot/src/letsencrypt-auto-source/letsencrypt-auto --os-packages-only # the above is not likely to change, so by putting it further up the # Dockerfile we make sure we cache as much as possible -COPY setup.py README.rst CHANGELOG.md MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ +COPY certbot/setup.py certbot/README.rst certbot/CHANGELOG.md certbot/MANIFEST.in linter_plugin.py tox.cover.py tox.ini .pylintrc /opt/certbot/src/ # all above files are necessary for setup.py, however, package source # code directory has to be copied separately to a subdirectory... @@ -38,7 +38,7 @@ ENV PATH /opt/certbot/venv/bin:$PATH RUN /opt/certbot/venv/bin/python \ /opt/certbot/src/tools/pip_install_editable.py \ /opt/certbot/src/acme \ - /opt/certbot/src \ + /opt/certbot/src/certbot \ /opt/certbot/src/certbot-apache \ /opt/certbot/src/certbot-nginx \ /opt/certbot/src/certbot-compatibility-test diff --git a/certbot-compatibility-test/MANIFEST.in b/certbot-compatibility-test/MANIFEST.in index 11762538a..a9d4f5ce7 100644 --- a/certbot-compatibility-test/MANIFEST.in +++ b/certbot-compatibility-test/MANIFEST.in @@ -1,6 +1,5 @@ include LICENSE.txt include README.rst -recursive-include docs * include certbot_compatibility_test/configurators/apache/a2enmod.sh include certbot_compatibility_test/configurators/apache/a2dismod.sh include certbot_compatibility_test/configurators/apache/Dockerfile diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py index 82195264b..a9b1ce87e 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/apache/common.py @@ -6,10 +6,10 @@ import subprocess import mock import zope.interface -from certbot import configuration from certbot import errors as le_errors from certbot import util as certbot_util -from certbot_apache import entrypoint +from certbot._internal import configuration +from certbot_apache._internal import entrypoint from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces from certbot_compatibility_test import util @@ -18,7 +18,6 @@ from certbot_compatibility_test.configurators import common as configurators_com @zope.interface.implementer(interfaces.IConfiguratorProxy) class Proxy(configurators_common.Proxy): - # pylint: disable=too-many-instance-attributes """A common base for Apache test configurators""" def __init__(self, args): @@ -28,7 +27,7 @@ class Proxy(configurators_common.Proxy): self.modules = self.server_root = self.test_conf = self.version = None patch = mock.patch( - "certbot_apache.configurator.display_ops.select_vhost") + "certbot_apache._internal.configurator.display_ops.select_vhost") mock_display = patch.start() mock_display.side_effect = le_errors.PluginError( "Unable to determine vhost") diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py index 8f90d37c2..b592d6288 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/common.py @@ -4,16 +4,14 @@ import os import shutil import tempfile -from certbot import constants +from certbot._internal import constants from certbot_compatibility_test import errors from certbot_compatibility_test import util - logger = logging.getLogger(__name__) class Proxy(object): - # pylint: disable=too-many-instance-attributes """A common base for compatibility test configurators""" @classmethod @@ -45,8 +43,7 @@ class Proxy(object): method = getattr(self._configurator, name, None) if callable(method): return method - else: - raise AttributeError() + raise AttributeError() def has_more_configs(self): """Returns true if there are more configs to test""" @@ -84,8 +81,7 @@ class Proxy(object): """Returns the set of domain names that the plugin should find""" if self._all_names: return self._all_names - else: - raise errors.Error("No configuration file loaded") + raise errors.Error("No configuration file loaded") def get_testable_domain_names(self): """Returns the set of domain names that can be tested against""" diff --git a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py index 3207cf88a..3011b9823 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py +++ b/certbot-compatibility-test/certbot_compatibility_test/configurators/nginx/common.py @@ -6,19 +6,17 @@ import subprocess import zope.interface from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module - -from certbot import configuration -from certbot_nginx import configurator -from certbot_nginx import constants +from certbot._internal import configuration from certbot_compatibility_test import errors from certbot_compatibility_test import interfaces from certbot_compatibility_test import util from certbot_compatibility_test.configurators import common as configurators_common +from certbot_nginx._internal import configurator +from certbot_nginx._internal import constants @zope.interface.implementer(interfaces.IConfiguratorProxy) class Proxy(configurators_common.Proxy): - # pylint: disable=too-many-instance-attributes """A common base for Nginx test configurators""" def load_config(self): diff --git a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py index 72204367e..2c3f880e0 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/test_driver.py +++ b/certbot-compatibility-test/certbot_compatibility_test/test_driver.py @@ -5,31 +5,27 @@ import filecmp import logging import os import shutil +import sys import tempfile import time -import sys -from urllib3.util import connection import OpenSSL - -from six.moves import xrange # pylint: disable=import-error,redefined-builtin +from urllib3.util import connection from acme import challenges from acme import crypto_util from acme import messages -from acme.magic_typing import List, Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors as le_errors from certbot.tests import acme_util - from certbot_compatibility_test import errors from certbot_compatibility_test import util from certbot_compatibility_test import validator - from certbot_compatibility_test.configurators.apache import common as a_common from certbot_compatibility_test.configurators.nginx import common as n_common - DESCRIPTION = """ Tests Certbot plugins against different server configurations. It is assumed that Docker is already installed. If no test type is specified, all @@ -60,26 +56,27 @@ def test_authenticator(plugin, config, temp_dir): return False success = True - for i in xrange(len(responses)): - if not responses[i]: + for i, response in enumerate(responses): + achall = achalls[i] + if not response: logger.error( "Plugin failed to complete %s for %s in %s", - type(achalls[i]), achalls[i].domain, config) + type(achall), achall.domain, config) success = False - elif isinstance(responses[i], challenges.HTTP01Response): + elif isinstance(response, challenges.HTTP01Response): # We fake the DNS resolution to ensure that any domain is resolved # to the local HTTP server setup for the compatibility tests with _fake_dns_resolution("127.0.0.1"): - verified = responses[i].simple_verify( - achalls[i].chall, achalls[i].domain, + verified = response.simple_verify( + achall.chall, achall.domain, util.JWK.public_key(), port=plugin.http_port) if verified: logger.info( - "http-01 verification for %s succeeded", achalls[i].domain) + "http-01 verification for %s succeeded", achall.domain) else: logger.error( "**** http-01 verification for %s in %s failed", - achalls[i].domain, config) + achall.domain, config) success = False if success: @@ -92,8 +89,7 @@ def test_authenticator(plugin, config, temp_dir): if _dirs_are_unequal(config, backup): logger.error("Challenge cleanup failed for %s", config) return False - else: - logger.info("Challenge cleanup succeeded") + logger.info("Challenge cleanup succeeded") return success @@ -308,7 +304,7 @@ def get_args(): "-e", "--enhance", action="store_true", help="tests the enhancements " "the plugin supports (implicitly includes installer tests)") - for plugin in PLUGINS.itervalues(): + for plugin in PLUGINS.values(): plugin.add_parser_arguments(parser) args = parser.parse_args() diff --git a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem b/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem deleted file mode 100644 index 8f82146ba..000000000 --- a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQCsREbM+UcfsgDy2w56AVGyxsO0HVsbEZHHoEzv7qksIwFgRYMp -rowwIxD450RQQqjvw9IoXlMVmr1t5szn5KXn9JRO9T5KNCCy3VPx75WBcp6kzd9Q -2HS1OEOtpilNnDkZ+TJfdgFWPUBYj2o4Md1hPmcvagiIJY5U6speka2bjwIDAQAB -AoGANCMZ9pF/mDUsmlP4Rq69hkkoFAxKdZ/UqkF256so4mXZ1cRUFTpxzWPfkCWW -hGAYdzCiG3uo08IYkPmojIqkN1dk5Hcq5eQAmshaPkQHQCHjmPjjcNvgjIXQoGUf -TpDU2hbY4UAlJlj4ZLh+jGP5Zq8/WrNi8RsI3v9Nagfp/FECQQDgi2q8p1gX0TNh -d1aEKmSXkR3bxkyFk6oS+pBrAG3+yX27ZayN6Rx6DOs/FcBsOu7fX3PYBziDeEWe -Lkf1P743AkEAxGYT/LY3puglSz4iJZZzWmRCrVOg41yhfQ+F1BRX43/2vtoU5GyM -2lUn1vQ2e/rfmnAvfJxc90GeZCIHB1ihaQJBALH8UMLxMtbOMJgVbDKfF9U8ZhqK -+KT5A1q/2jG2yXmoZU1hroFeQgBMtTvwFfK0VBwjIUQflSBA+Y4EyW0Q9ckCQGvd -jHitM1+N/H2YwHRYbz5j9mLvnVuCEod3MQ9LpQGj1Eb5y6OxIqL/RgQ+2HW7UXem -yc3sqvp5pZ5lOesE+JECQETPI64gqxlTIs3nErNMpMynUuTWpaElOcIJTT6icLzB -Xix67kKXjROO5D58GEYkM0Yi5k7YdUPoQBW7MoIrSIA= ------END RSA PRIVATE KEY----- diff --git a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem b/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem deleted file mode 100644 index 03f77d903..000000000 --- a/certbot-compatibility-test/certbot_compatibility_test/testdata/rsa1024_key2.pem +++ /dev/null @@ -1,15 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIICXAIBAAKBgQDCzejLjo8wsz0avrylt7HQyF0+vsKritF70EGmc64cV0XfkCTR -o+vMXBXMuUY6Kv3hTXV7klgkNYmL7gXAsFGQ4B9qeMnkYn0GcQdI51u076y/26Fu -37uJg45Q6eApKInJSsyLVMcAT4HUJ6fFnUodFAKR7vTzOQryNW7Et4gA4wIDAQAB -AoGAKiAU40/krwdTg2ETslJS5W8ums7tkeLnAfs69x+02vQUbA/jpmHoL70KCcdW -5GU/mWUCrsIqxUm+gL/sBosaV/TF256qUBt2qQCZTN8MbDaNSYiiMnucOfbWdIqx -Zgls6GUoXQvPic9RUoFSlgfSjo5ezz6el5ihvRMp+wbk24ECQQD3oz4hN029DSZo -Y3+flmBn77gA0BMUvLa6hmt9b3xT5U/ToCLfbmUvpx7zV1g5era2y9qt/o3UtAbW -1zCVETgzAkEAyWHv/+RnSXp8/D4YwTVWyeWi862uNBPkuLGP/0zASdwBfBK3uBls -+VumfSCtp0kt2AXXmScg1fkHdeAVT6AkkQJBAJb2XRnCrRFiwtdAULzo3zx9Vp6o -OfmaUYrEByMgo5pBYLiSFrA+jFDQgH238YCY3mnxPA517+CLHuA5rtQw+yECQCfm -gL/pyFE1tLfhsdPuNpDwL9YqLl7hJis1+zrxQRQhRCYKK16NoxrQ/u7B38ZKaIvp -tGsC5q2elszTJkXNjBECQCVE9QCVx056vHVdPWM8z3GAeV3sJQ01HLLjebTEEz6G -jH54gk+YYPp4kjCvVUykbnB58BY2n88GQt5Jj5eLuMo= ------END RSA PRIVATE KEY----- diff --git a/certbot-compatibility-test/certbot_compatibility_test/util.py b/certbot-compatibility-test/certbot_compatibility_test/util.py index c8de8ddac..3465b7143 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/util.py +++ b/certbot-compatibility-test/certbot_compatibility_test/util.py @@ -8,13 +8,11 @@ import tarfile import josepy as jose -from acme import test_util -from certbot import constants - +from certbot._internal import constants +from certbot.tests import util as test_util from certbot_compatibility_test import errors - -_KEY_BASE = "rsa1024_key.pem" +_KEY_BASE = "rsa2048_key.pem" KEY_PATH = test_util.vector_path(_KEY_BASE) KEY = test_util.load_pyopenssl_private_key(_KEY_BASE) JWK = jose.JWKRSA(key=test_util.load_rsa_private_key(_KEY_BASE)) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator.py b/certbot-compatibility-test/certbot_compatibility_test/validator.py index 3455ce82d..796ebbe9d 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator.py @@ -1,15 +1,14 @@ """Validators to determine the current webserver configuration""" import logging import socket -import requests +import requests import six -from six.moves import xrange # pylint: disable=import-error,redefined-builtin +from six.moves import xrange # pylint: disable=import-error, redefined-builtin from acme import crypto_util from acme import errors as acme_errors - logger = logging.getLogger(__name__) diff --git a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py index c4a668c5e..86edbdb55 100644 --- a/certbot-compatibility-test/certbot_compatibility_test/validator_test.py +++ b/certbot-compatibility-test/certbot_compatibility_test/validator_test.py @@ -1,9 +1,9 @@ """Tests for certbot_compatibility_test.validator.""" import unittest -import requests import mock import OpenSSL +import requests from acme import errors as acme_errors from certbot_compatibility_test import validator diff --git a/certbot-compatibility-test/docs/.gitignore b/certbot-compatibility-test/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-compatibility-test/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-compatibility-test/docs/Makefile b/certbot-compatibility-test/docs/Makefile deleted file mode 100644 index 0c9cf40aa..000000000 --- a/certbot-compatibility-test/docs/Makefile +++ /dev/null @@ -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 ' where 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/certbot-compatibility-test.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-compatibility-test.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/certbot-compatibility-test" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-compatibility-test" - @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." diff --git a/certbot-compatibility-test/docs/_static/.gitignore b/certbot-compatibility-test/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-compatibility-test/docs/_templates/.gitignore b/certbot-compatibility-test/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-compatibility-test/docs/api.rst b/certbot-compatibility-test/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-compatibility-test/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-compatibility-test/docs/api/index.rst b/certbot-compatibility-test/docs/api/index.rst deleted file mode 100644 index fea92d2e5..000000000 --- a/certbot-compatibility-test/docs/api/index.rst +++ /dev/null @@ -1,53 +0,0 @@ -:mod:`certbot_compatibility_test` -------------------------------------- - -.. automodule:: certbot_compatibility_test - :members: - -:mod:`certbot_compatibility_test.errors` -============================================ - -.. automodule:: certbot_compatibility_test.errors - :members: - -:mod:`certbot_compatibility_test.interfaces` -================================================ - -.. automodule:: certbot_compatibility_test.interfaces - :members: - -:mod:`certbot_compatibility_test.test_driver` -================================================= - -.. automodule:: certbot_compatibility_test.test_driver - :members: - -:mod:`certbot_compatibility_test.util` -========================================== - -.. automodule:: certbot_compatibility_test.util - :members: - -:mod:`certbot_compatibility_test.configurators` -=================================================== - -.. automodule:: certbot_compatibility_test.configurators - :members: - -:mod:`certbot_compatibility_test.configurators.apache` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -.. automodule:: certbot_compatibility_test.configurators.apache - :members: - -:mod:`certbot_compatibility_test.configurators.apache.apache24` -------------------------------------------------------------------- - -.. automodule:: certbot_compatibility_test.configurators.apache.apache24 - :members: - -:mod:`certbot_compatibility_test.configurators.apache.common` -------------------------------------------------------------------- - -.. automodule:: certbot_compatibility_test.configurators.apache.common - :members: diff --git a/certbot-compatibility-test/docs/conf.py b/certbot-compatibility-test/docs/conf.py deleted file mode 100644 index f89f4b368..000000000 --- a/certbot-compatibility-test/docs/conf.py +++ /dev/null @@ -1,319 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-compatibility-test documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:40:53 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 sys -import os -import shlex - - -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', - 'repoze.sphinx.autointerface', -] - -autodoc_member_order = 'bysource' -autodoc_default_flags = ['show-inheritance', 'private-members'] - -# 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'certbot-compatibility-test' -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 = True - - -# -- 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 -# " v 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 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 = 'certbot-compatibility-testdoc' - -# -- 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, 'certbot-compatibility-test.tex', - u'certbot-compatibility-test 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, 'certbot-compatibility-test', - u'certbot-compatibility-test 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, 'certbot-compatibility-test', - u'certbot-compatibility-test Documentation', - author, 'certbot-compatibility-test', - '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), - 'certbot-apache': ( - 'https://letsencrypt-apache.readthedocs.org/en/latest/', None), - 'certbot-nginx': ( - 'https://letsencrypt-nginx.readthedocs.org/en/latest/', None), -} diff --git a/certbot-compatibility-test/docs/index.rst b/certbot-compatibility-test/docs/index.rst deleted file mode 100644 index a5e71e844..000000000 --- a/certbot-compatibility-test/docs/index.rst +++ /dev/null @@ -1,27 +0,0 @@ -.. certbot-compatibility-test documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:40:53 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-compatibility-test's documentation! -========================================================== - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-compatibility-test/docs/make.bat b/certbot-compatibility-test/docs/make.bat deleted file mode 100644 index b6c0360f4..000000000 --- a/certbot-compatibility-test/docs/make.bat +++ /dev/null @@ -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 ^` where ^ 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\certbot-compatibility-test.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-compatibility-test.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 diff --git a/certbot-compatibility-test/nginx/roundtrip.py b/certbot-compatibility-test/nginx/roundtrip.py index 85d283c78..afc68647d 100644 --- a/certbot-compatibility-test/nginx/roundtrip.py +++ b/certbot-compatibility-test/nginx/roundtrip.py @@ -3,7 +3,8 @@ import os import sys -from certbot_nginx import nginxparser +from certbot_nginx._internal import nginxparser + def roundtrip(stuff): success = True diff --git a/certbot-compatibility-test/readthedocs.org.requirements.txt b/certbot-compatibility-test/readthedocs.org.requirements.txt deleted file mode 100644 index c2a0c1110..000000000 --- a/certbot-compatibility-test/readthedocs.org.requirements.txt +++ /dev/null @@ -1,13 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[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 .[docs]" must be used instead - --e acme --e . --e certbot-apache --e certbot-compatibility-test[docs] diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index 362043531..f26fb0706 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -1,10 +1,9 @@ import sys -from setuptools import setup from setuptools import find_packages +from setuptools import setup - -version = '0.36.0.dev0' +version = '1.1.0.dev0' install_requires = [ 'certbot', @@ -20,11 +19,6 @@ if sys.version_info < (2, 7, 9): install_requires.append('ndg-httpsclient') install_requires.append('pyasn1') -docs_extras = [ - 'repoze.sphinx.autointerface', - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] setup( name='certbot-compatibility-test', @@ -47,6 +41,7 @@ setup( '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', ], @@ -54,9 +49,6 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'console_scripts': [ 'certbot-compatibility-test = certbot_compatibility_test.test_driver:main', diff --git a/certbot-dns-cloudflare/Dockerfile b/certbot-dns-cloudflare/Dockerfile deleted file mode 100644 index adbf715fa..000000000 --- a/certbot-dns-cloudflare/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-cloudflare - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudflare diff --git a/certbot-dns-cloudflare/MANIFEST.in b/certbot-dns-cloudflare/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-cloudflare/MANIFEST.in +++ b/certbot-dns-cloudflare/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py index 7e53f83ce..b08bc0968 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/__init__.py @@ -22,7 +22,9 @@ Credentials Use of this plugin requires a configuration file containing Cloudflare API credentials, obtained from your Cloudflare -`account page `_. +`account page `_. This plugin +does not currently support Cloudflare's "API Tokens", so please ensure you use +the "Global API Key" for authentication. .. code-block:: ini :name: credentials.ini diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py new file mode 100644 index 000000000..93b0672b5 --- /dev/null +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_cloudflare.dns_cloudflare` plugin.""" diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py similarity index 99% rename from certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py rename to certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py index e3d0d42e0..0bbdf703a 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare.py +++ b/certbot-dns-cloudflare/certbot_dns_cloudflare/_internal/dns_cloudflare.py @@ -10,7 +10,7 @@ from certbot.plugins import dns_common logger = logging.getLogger(__name__) -ACCOUNT_URL = 'https://www.cloudflare.com/a/account/my-account' +ACCOUNT_URL = 'https://dash.cloudflare.com/profile/api-tokens' @zope.interface.implementer(interfaces.IAuthenticator) diff --git a/certbot-dns-cloudflare/docs/api.rst b/certbot-dns-cloudflare/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-cloudflare/docs/api.rst +++ b/certbot-dns-cloudflare/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst b/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst deleted file mode 100644 index 35f525201..000000000 --- a/certbot-dns-cloudflare/docs/api/dns_cloudflare.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_cloudflare.dns_cloudflare` --------------------------------------------- - -.. automodule:: certbot_dns_cloudflare.dns_cloudflare - :members: diff --git a/certbot-dns-cloudflare/docs/conf.py b/certbot-dns-cloudflare/docs/conf.py index aa7809246..488268577 100644 --- a/certbot-dns-cloudflare/docs/conf.py +++ b/certbot-dns-cloudflare/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-cloudflare/local-oldest-requirements.txt b/certbot-dns-cloudflare/local-oldest-requirements.txt index 0bc9ee027..3fce6f83b 100644 --- a/certbot-dns-cloudflare/local-oldest-requirements.txt +++ b/certbot-dns-cloudflare/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-cloudflare/readthedocs.org.requirements.txt b/certbot-dns-cloudflare/readthedocs.org.requirements.txt index b18901111..f1df15227 100644 --- a/certbot-dns-cloudflare/readthedocs.org.requirements.txt +++ b/certbot-dns-cloudflare/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-cloudflare[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-cloudflare[docs]" does not work as +# expected and "pip install -e certbot-dns-cloudflare[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-cloudflare[docs] diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index bd201aca2..b3fd81223 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'cloudflare>=1.5.1', 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-cloudflare', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-cloudflare = certbot_dns_cloudflare.dns_cloudflare:Authenticator', + 'dns-cloudflare = certbot_dns_cloudflare._internal.dns_cloudflare:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_cloudflare', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py similarity index 96% rename from certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py rename to certbot-dns-cloudflare/tests/dns_cloudflare_test.py index 4b9419ca8..b24628b0d 100644 --- a/certbot-dns-cloudflare/certbot_dns_cloudflare/dns_cloudflare_test.py +++ b/certbot-dns-cloudflare/tests/dns_cloudflare_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_cloudflare.dns_cloudflare.""" +"""Tests for certbot_dns_cloudflare._internal.dns_cloudflare.""" import unittest @@ -19,7 +19,7 @@ EMAIL = 'example@example.com' class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_cloudflare.dns_cloudflare import Authenticator + from certbot_dns_cloudflare._internal.dns_cloudflare import Authenticator super(AuthenticatorTest, self).setUp() @@ -58,7 +58,7 @@ class CloudflareClientTest(unittest.TestCase): record_id = 2 def setUp(self): - from certbot_dns_cloudflare.dns_cloudflare import _CloudflareClient + from certbot_dns_cloudflare._internal.dns_cloudflare import _CloudflareClient self.cloudflare_client = _CloudflareClient(EMAIL, API_KEY) diff --git a/certbot-dns-cloudxns/Dockerfile b/certbot-dns-cloudxns/Dockerfile deleted file mode 100644 index 48c88c35c..000000000 --- a/certbot-dns-cloudxns/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-cloudxns - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-cloudxns diff --git a/certbot-dns-cloudxns/MANIFEST.in b/certbot-dns-cloudxns/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-cloudxns/MANIFEST.in +++ b/certbot-dns-cloudxns/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py new file mode 100644 index 000000000..e2177417d --- /dev/null +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_cloudxns.dns_cloudxns` plugin.""" diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py similarity index 100% rename from certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py rename to certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py index 5132137f8..2a0f12ea7 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns.py +++ b/certbot-dns-cloudxns/certbot_dns_cloudxns/_internal/dns_cloudxns.py @@ -1,8 +1,8 @@ """DNS Authenticator for CloudXNS DNS.""" import logging -import zope.interface from lexicon.providers import cloudxns +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-cloudxns/docs/api.rst b/certbot-dns-cloudxns/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-cloudxns/docs/api.rst +++ b/certbot-dns-cloudxns/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst b/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst deleted file mode 100644 index be794d1a0..000000000 --- a/certbot-dns-cloudxns/docs/api/dns_cloudxns.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_cloudxns.dns_cloudxns` ----------------------------------------- - -.. automodule:: certbot_dns_cloudxns.dns_cloudxns - :members: diff --git a/certbot-dns-cloudxns/docs/conf.py b/certbot-dns-cloudxns/docs/conf.py index 9e2f4c0e6..16ccd1d62 100644 --- a/certbot-dns-cloudxns/docs/conf.py +++ b/certbot-dns-cloudxns/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-cloudxns/readthedocs.org.requirements.txt b/certbot-dns-cloudxns/readthedocs.org.requirements.txt index ae2ff8165..a9a4d068b 100644 --- a/certbot-dns-cloudxns/readthedocs.org.requirements.txt +++ b/certbot-dns-cloudxns/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-cloudxns[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-cloudxns[docs]" does not work as +# expected and "pip install -e certbot-dns-cloudxns[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-cloudxns[docs] diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index d8d7aa9b8..288a6d115 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-cloudxns', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-cloudxns = certbot_dns_cloudxns.dns_cloudxns:Authenticator', + 'dns-cloudxns = certbot_dns_cloudxns._internal.dns_cloudxns:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_cloudxns', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py similarity index 82% rename from certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py rename to certbot-dns-cloudxns/tests/dns_cloudxns_test.py index 6bc1e1f79..a1e3cde89 100644 --- a/certbot-dns-cloudxns/certbot_dns_cloudxns/dns_cloudxns_test.py +++ b/certbot-dns-cloudxns/tests/dns_cloudxns_test.py @@ -1,9 +1,10 @@ -"""Tests for certbot_dns_cloudxns.dns_cloudxns.""" +"""Tests for certbot_dns_cloudxns._internal.dns_cloudxns.""" import unittest import mock -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException from certbot.compat import os from certbot.plugins import dns_test_common @@ -24,7 +25,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_cloudxns.dns_cloudxns import Authenticator + from certbot_dns_cloudxns._internal.dns_cloudxns import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"cloudxns_api_key": API_KEY, "cloudxns_secret_key": SECRET}, path) @@ -42,7 +43,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, class CloudXNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexiconClientTest): def setUp(self): - from certbot_dns_cloudxns.dns_cloudxns import _CloudXNSLexiconClient + from certbot_dns_cloudxns._internal.dns_cloudxns import _CloudXNSLexiconClient self.client = _CloudXNSLexiconClient(API_KEY, SECRET, 0) diff --git a/certbot-dns-digitalocean/Dockerfile b/certbot-dns-digitalocean/Dockerfile deleted file mode 100644 index 342e0e876..000000000 --- a/certbot-dns-digitalocean/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-digitalocean - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-digitalocean diff --git a/certbot-dns-digitalocean/MANIFEST.in b/certbot-dns-digitalocean/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-digitalocean/MANIFEST.in +++ b/certbot-dns-digitalocean/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py new file mode 100644 index 000000000..0291a9341 --- /dev/null +++ b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_digitalocean.dns_digitalocean` plugin.""" diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py b/certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py similarity index 100% rename from certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean.py rename to certbot-dns-digitalocean/certbot_dns_digitalocean/_internal/dns_digitalocean.py diff --git a/certbot-dns-digitalocean/docs/api.rst b/certbot-dns-digitalocean/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-digitalocean/docs/api.rst +++ b/certbot-dns-digitalocean/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst b/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst deleted file mode 100644 index 8a787987e..000000000 --- a/certbot-dns-digitalocean/docs/api/dns_digitalocean.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_digitalocean.dns_digitalocean` ------------------------------------------------- - -.. automodule:: certbot_dns_digitalocean.dns_digitalocean - :members: diff --git a/certbot-dns-digitalocean/docs/conf.py b/certbot-dns-digitalocean/docs/conf.py index e223b1535..9c493a220 100644 --- a/certbot-dns-digitalocean/docs/conf.py +++ b/certbot-dns-digitalocean/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-digitalocean/local-oldest-requirements.txt b/certbot-dns-digitalocean/local-oldest-requirements.txt index 0bc9ee027..3fce6f83b 100644 --- a/certbot-dns-digitalocean/local-oldest-requirements.txt +++ b/certbot-dns-digitalocean/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-digitalocean/readthedocs.org.requirements.txt b/certbot-dns-digitalocean/readthedocs.org.requirements.txt index 08d973ab3..d0cc2f74a 100644 --- a/certbot-dns-digitalocean/readthedocs.org.requirements.txt +++ b/certbot-dns-digitalocean/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-digitalocean[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-digitalocean[docs]" does not work as +# expected and "pip install -e certbot-dns-digitalocean[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-digitalocean[docs] diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 92a9b2b14..ba3190567 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'mock', 'python-digitalocean>=1.11', 'setuptools', @@ -21,6 +23,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-digitalocean', version=version, @@ -31,7 +47,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -44,6 +60,7 @@ setup( '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', @@ -60,8 +77,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-digitalocean = certbot_dns_digitalocean.dns_digitalocean:Authenticator', + 'dns-digitalocean = certbot_dns_digitalocean._internal.dns_digitalocean:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_digitalocean', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py similarity index 96% rename from certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py rename to certbot-dns-digitalocean/tests/dns_digitalocean_test.py index 3cb49e9fb..71301a47c 100644 --- a/certbot-dns-digitalocean/certbot_dns_digitalocean/dns_digitalocean_test.py +++ b/certbot-dns-digitalocean/tests/dns_digitalocean_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_digitalocean.dns_digitalocean.""" +"""Tests for certbot_dns_digitalocean._internal.dns_digitalocean.""" import unittest @@ -18,7 +18,7 @@ TOKEN = 'a-token' class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_digitalocean.dns_digitalocean import Authenticator + from certbot_dns_digitalocean._internal.dns_digitalocean import Authenticator super(AuthenticatorTest, self).setUp() @@ -57,7 +57,7 @@ class DigitalOceanClientTest(unittest.TestCase): record_content = "bar" def setUp(self): - from certbot_dns_digitalocean.dns_digitalocean import _DigitalOceanClient + from certbot_dns_digitalocean._internal.dns_digitalocean import _DigitalOceanClient self.digitalocean_client = _DigitalOceanClient(TOKEN) diff --git a/certbot-dns-dnsimple/Dockerfile b/certbot-dns-dnsimple/Dockerfile deleted file mode 100644 index 724675339..000000000 --- a/certbot-dns-dnsimple/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-dnsimple - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsimple diff --git a/certbot-dns-dnsimple/MANIFEST.in b/certbot-dns-dnsimple/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-dnsimple/MANIFEST.in +++ b/certbot-dns-dnsimple/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py new file mode 100644 index 000000000..070927555 --- /dev/null +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_dnsimple.dns_dnsimple` plugin.""" diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py similarity index 100% rename from certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py rename to certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py index ad2a3fa30..8c48d31e7 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple.py +++ b/certbot-dns-dnsimple/certbot_dns_dnsimple/_internal/dns_dnsimple.py @@ -1,8 +1,8 @@ """DNS Authenticator for DNSimple DNS.""" import logging -import zope.interface from lexicon.providers import dnsimple +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-dnsimple/docs/api.rst b/certbot-dns-dnsimple/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-dnsimple/docs/api.rst +++ b/certbot-dns-dnsimple/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst b/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst deleted file mode 100644 index b0544107b..000000000 --- a/certbot-dns-dnsimple/docs/api/dns_dnsimple.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_dnsimple.dns_dnsimple` ----------------------------------------- - -.. automodule:: certbot_dns_dnsimple.dns_dnsimple - :members: diff --git a/certbot-dns-dnsimple/docs/conf.py b/certbot-dns-dnsimple/docs/conf.py index da692fb9e..b5cb24e2f 100644 --- a/certbot-dns-dnsimple/docs/conf.py +++ b/certbot-dns-dnsimple/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-dnsimple/readthedocs.org.requirements.txt b/certbot-dns-dnsimple/readthedocs.org.requirements.txt index fef73916c..04163ff34 100644 --- a/certbot-dns-dnsimple/readthedocs.org.requirements.txt +++ b/certbot-dns-dnsimple/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-dnsimple[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-dnsimple[docs]" does not work as +# expected and "pip install -e certbot-dns-dnsimple[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-dnsimple[docs] diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 709ca8330..5729bd789 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -1,15 +1,17 @@ import os -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'mock', 'setuptools', 'zope.interface', @@ -32,6 +34,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-dnsimple', version=version, @@ -42,7 +58,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -55,6 +71,7 @@ setup( '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', @@ -71,8 +88,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-dnsimple = certbot_dns_dnsimple.dns_dnsimple:Authenticator', + 'dns-dnsimple = certbot_dns_dnsimple._internal.dns_dnsimple:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_dnsimple', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py similarity index 86% rename from certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py rename to certbot-dns-dnsimple/tests/dns_dnsimple_test.py index d84bf71ed..ca5eb4f36 100644 --- a/certbot-dns-dnsimple/certbot_dns_dnsimple/dns_dnsimple_test.py +++ b/certbot-dns-dnsimple/tests/dns_dnsimple_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_dnsimple.dns_dnsimple.""" +"""Tests for certbot_dns_dnsimple._internal.dns_dnsimple.""" import unittest @@ -19,7 +19,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_dnsimple.dns_dnsimple import Authenticator + from certbot_dns_dnsimple._internal.dns_dnsimple import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"dnsimple_token": TOKEN}, path) @@ -39,7 +39,7 @@ class DNSimpleLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseL LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: ...') def setUp(self): - from certbot_dns_dnsimple.dns_dnsimple import _DNSimpleLexiconClient + from certbot_dns_dnsimple._internal.dns_dnsimple import _DNSimpleLexiconClient self.client = _DNSimpleLexiconClient(TOKEN, 0) diff --git a/certbot-dns-dnsmadeeasy/Dockerfile b/certbot-dns-dnsmadeeasy/Dockerfile deleted file mode 100644 index 1480baf4f..000000000 --- a/certbot-dns-dnsmadeeasy/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-dnsmadeeasy - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-dnsmadeeasy diff --git a/certbot-dns-dnsmadeeasy/MANIFEST.in b/certbot-dns-dnsmadeeasy/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-dnsmadeeasy/MANIFEST.in +++ b/certbot-dns-dnsmadeeasy/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py new file mode 100644 index 000000000..37350ce0b --- /dev/null +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_dnsmadeeasy.dns_dnsmadeeasy` plugin.""" diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py similarity index 100% rename from certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py rename to certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py index 4cd8721ce..ed3146dce 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy.py +++ b/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/_internal/dns_dnsmadeeasy.py @@ -1,8 +1,8 @@ """DNS Authenticator for DNS Made Easy DNS.""" import logging -import zope.interface from lexicon.providers import dnsmadeeasy +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-dnsmadeeasy/docs/api.rst b/certbot-dns-dnsmadeeasy/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-dnsmadeeasy/docs/api.rst +++ b/certbot-dns-dnsmadeeasy/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst b/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst deleted file mode 100644 index 81948a77f..000000000 --- a/certbot-dns-dnsmadeeasy/docs/api/dns_dnsmadeeasy.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_dnsmadeeasy.dns_dnsmadeeasy` ------------------------------------- - -.. automodule:: certbot_dns_dnsmadeeasy.dns_dnsmadeeasy - :members: diff --git a/certbot-dns-dnsmadeeasy/docs/conf.py b/certbot-dns-dnsmadeeasy/docs/conf.py index 7d26f9742..60e0163bd 100644 --- a/certbot-dns-dnsmadeeasy/docs/conf.py +++ b/certbot-dns-dnsmadeeasy/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt b/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt index 8f8c6c731..eb205d8f2 100644 --- a/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt +++ b/certbot-dns-dnsmadeeasy/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-dnsmadeeasy[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-dnsmadeeasy[docs]" does not work as +# expected and "pip install -e certbot-dns-dnsmadeeasy[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-dnsmadeeasy[docs] diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 1d55b0fe4..6fc756389 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-dnsmadeeasy', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-dnsmadeeasy = certbot_dns_dnsmadeeasy.dns_dnsmadeeasy:Authenticator', + 'dns-dnsmadeeasy = certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_dnsmadeeasy', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py similarity index 87% rename from certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py rename to certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py index f0901664c..b94cc7d05 100644 --- a/certbot-dns-dnsmadeeasy/certbot_dns_dnsmadeeasy/dns_dnsmadeeasy_test.py +++ b/certbot-dns-dnsmadeeasy/tests/dns_dnsmadeeasy_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_dnsmadeeasy.dns_dnsmadeeasy.""" +"""Tests for certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy.""" import unittest @@ -21,7 +21,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_dnsmadeeasy.dns_dnsmadeeasy import Authenticator + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"dnsmadeeasy_api_key": API_KEY, @@ -44,7 +44,7 @@ class DNSMadeEasyLexiconClientTest(unittest.TestCase, LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_dnsmadeeasy.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient + from certbot_dns_dnsmadeeasy._internal.dns_dnsmadeeasy import _DNSMadeEasyLexiconClient self.client = _DNSMadeEasyLexiconClient(API_KEY, SECRET_KEY, 0) diff --git a/certbot-dns-gehirn/Dockerfile b/certbot-dns-gehirn/Dockerfile deleted file mode 100644 index 7dce0e521..000000000 --- a/certbot-dns-gehirn/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-gehirn - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-gehirn diff --git a/certbot-dns-gehirn/MANIFEST.in b/certbot-dns-gehirn/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-gehirn/MANIFEST.in +++ b/certbot-dns-gehirn/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py new file mode 100644 index 000000000..f8d6485dc --- /dev/null +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_gehirn.dns_gehirn` plugin.""" diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py similarity index 100% rename from certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py rename to certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py index e64e62da9..18090c95a 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn.py +++ b/certbot-dns-gehirn/certbot_dns_gehirn/_internal/dns_gehirn.py @@ -1,8 +1,8 @@ """DNS Authenticator for Gehirn Infrastracture Service DNS.""" import logging -import zope.interface from lexicon.providers import gehirn +import zope.interface from certbot import interfaces from certbot.plugins import dns_common diff --git a/certbot-dns-gehirn/docs/api.rst b/certbot-dns-gehirn/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-gehirn/docs/api.rst +++ b/certbot-dns-gehirn/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-gehirn/docs/api/dns_gehirn.rst b/certbot-dns-gehirn/docs/api/dns_gehirn.rst deleted file mode 100644 index 35a13e9c1..000000000 --- a/certbot-dns-gehirn/docs/api/dns_gehirn.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_gehirn.dns_gehirn` ------------------------------------- - -.. automodule:: certbot_dns_gehirn.dns_gehirn - :members: diff --git a/certbot-dns-gehirn/docs/conf.py b/certbot-dns-gehirn/docs/conf.py index a1b2799fb..67aafa3b4 100644 --- a/certbot-dns-gehirn/docs/conf.py +++ b/certbot-dns-gehirn/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-gehirn/readthedocs.org.requirements.txt b/certbot-dns-gehirn/readthedocs.org.requirements.txt index d9f4f9823..97af343d9 100644 --- a/certbot-dns-gehirn/readthedocs.org.requirements.txt +++ b/certbot-dns-gehirn/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-gehirn[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-gehirn[docs]" does not work as +# expected and "pip install -e certbot-dns-gehirn[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-gehirn[docs] diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index 6dac126d0..7c4da556d 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -1,13 +1,15 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', @@ -19,6 +21,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-gehirn', version=version, @@ -29,7 +45,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -41,6 +57,8 @@ setup( 'Programming Language :: Python :: 3.4', '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', @@ -57,8 +75,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-gehirn = certbot_dns_gehirn.dns_gehirn:Authenticator', + 'dns-gehirn = certbot_dns_gehirn._internal.dns_gehirn:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_gehirn', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py b/certbot-dns-gehirn/tests/dns_gehirn_test.py similarity index 89% rename from certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py rename to certbot-dns-gehirn/tests/dns_gehirn_test.py index 5a591392b..f5b95b6c3 100644 --- a/certbot-dns-gehirn/certbot_dns_gehirn/dns_gehirn_test.py +++ b/certbot-dns-gehirn/tests/dns_gehirn_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_gehirn.dns_gehirn.""" +"""Tests for certbot_dns_gehirn._internal.dns_gehirn.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_gehirn.dns_gehirn import Authenticator + from certbot_dns_gehirn._internal.dns_gehirn import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write( @@ -43,7 +43,7 @@ class GehirnLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_gehirn.dns_gehirn import _GehirnLexiconClient + from certbot_dns_gehirn._internal.dns_gehirn import _GehirnLexiconClient self.client = _GehirnLexiconClient(API_TOKEN, API_SECRET, 0) diff --git a/certbot-dns-google/Dockerfile b/certbot-dns-google/Dockerfile deleted file mode 100644 index 5750b31d9..000000000 --- a/certbot-dns-google/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-google - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-google diff --git a/certbot-dns-google/MANIFEST.in b/certbot-dns-google/MANIFEST.in index c91330e38..a7301ee7f 100644 --- a/certbot-dns-google/MANIFEST.in +++ b/certbot-dns-google/MANIFEST.in @@ -2,3 +2,6 @@ include LICENSE.txt include README.rst recursive-include docs * recursive-include certbot_dns_google/testdata * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-google/certbot_dns_google/_internal/__init__.py b/certbot-dns-google/certbot_dns_google/_internal/__init__.py new file mode 100644 index 000000000..f433213ff --- /dev/null +++ b/certbot-dns-google/certbot_dns_google/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_google.dns_google` plugin.""" diff --git a/certbot-dns-google/certbot_dns_google/dns_google.py b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py similarity index 99% rename from certbot-dns-google/certbot_dns_google/dns_google.py rename to certbot-dns-google/certbot_dns_google/_internal/dns_google.py index b722a38cf..3aa910b52 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google.py +++ b/certbot-dns-google/certbot_dns_google/_internal/dns_google.py @@ -2,11 +2,11 @@ import json import logging -import httplib2 -import zope.interface from googleapiclient import discovery from googleapiclient import errors as googleapiclient_errors +import httplib2 from oauth2client.service_account import ServiceAccountCredentials +import zope.interface from certbot import errors from certbot import interfaces @@ -235,7 +235,7 @@ class _GoogleClient(object): :rtype: `list` of `string` or `None` """ - rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member + rrs_request = self.dns.resourceRecordSets() request = rrs_request.list(managedZone=zone_id, project=self.project_id) # Add dot as the API returns absolute domains record_name += "." diff --git a/certbot-dns-google/docs/api.rst b/certbot-dns-google/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-google/docs/api.rst +++ b/certbot-dns-google/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-google/docs/api/dns_google.rst b/certbot-dns-google/docs/api/dns_google.rst deleted file mode 100644 index 6f5459934..000000000 --- a/certbot-dns-google/docs/api/dns_google.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_google.dns_google` ------------------------------------- - -.. automodule:: certbot_dns_google.dns_google - :members: diff --git a/certbot-dns-google/docs/conf.py b/certbot-dns-google/docs/conf.py index bbb343ee8..8f045cf3f 100644 --- a/certbot-dns-google/docs/conf.py +++ b/certbot-dns-google/docs/conf.py @@ -18,6 +18,7 @@ # import os import sys + sys.path.insert(0, os.path.abspath('_ext')) diff --git a/certbot-dns-google/local-oldest-requirements.txt b/certbot-dns-google/local-oldest-requirements.txt index 0bc9ee027..3fce6f83b 100644 --- a/certbot-dns-google/local-oldest-requirements.txt +++ b/certbot-dns-google/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-google/readthedocs.org.requirements.txt b/certbot-dns-google/readthedocs.org.requirements.txt index 6ea393f86..fe97cee94 100644 --- a/certbot-dns-google/readthedocs.org.requirements.txt +++ b/certbot-dns-google/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-google[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-google[docs]" does not work as +# expected and "pip install -e certbot-dns-google[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-google[docs] diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index 5c31a81f8..a0dc1c386 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -1,19 +1,19 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', - # 1.5 is the first version that supports oauth2client>=2.0 - 'google-api-python-client>=1.5', + 'certbot>=1.0.0.dev0', + 'google-api-python-client>=1.5.5', 'mock', - # for oauth2client.service_account.ServiceAccountCredentials - 'oauth2client>=2.0', + 'oauth2client>=4.0', 'setuptools', 'zope.interface', # already a dependency of google-api-python-client, but added for consistency @@ -25,6 +25,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-google', version=version, @@ -35,7 +49,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -48,6 +62,7 @@ setup( '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', @@ -64,8 +79,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-google = certbot_dns_google.dns_google:Authenticator', + 'dns-google = certbot_dns_google._internal.dns_google:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_google', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-google/certbot_dns_google/dns_google_test.py b/certbot-dns-google/tests/dns_google_test.py similarity index 89% rename from certbot-dns-google/certbot_dns_google/dns_google_test.py rename to certbot-dns-google/tests/dns_google_test.py index 288357bc1..647a75b05 100644 --- a/certbot-dns-google/certbot_dns_google/dns_google_test.py +++ b/certbot-dns-google/tests/dns_google_test.py @@ -1,12 +1,12 @@ -"""Tests for certbot_dns_google.dns_google.""" +"""Tests for certbot_dns_google._internal.dns_google.""" import unittest -import mock from googleapiclient import discovery from googleapiclient.errors import Error from googleapiclient.http import HttpMock from httplib2 import ServerNotFoundError +import mock from certbot import errors from certbot.compat import os @@ -25,7 +25,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_google.dns_google import Authenticator + from certbot_dns_google._internal.dns_google import Authenticator path = os.path.join(self.tempdir, 'file.json') open(path, "wb").close() @@ -68,7 +68,7 @@ class GoogleClientTest(unittest.TestCase): change = "an-id" def _setUp_client_with_mock(self, zone_request_side_effect): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient pwd = os.path.dirname(__file__) rel_path = 'testdata/discovery.json' @@ -96,18 +96,18 @@ class GoogleClientTest(unittest.TestCase): @mock.patch('googleapiclient.discovery.build') @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google._GoogleClient.get_project_id') + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') def test_client_without_credentials(self, get_project_id_mock, credential_mock, unused_discovery_mock): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient _GoogleClient(None) self.assertFalse(credential_mock.called) self.assertTrue(get_project_id_mock.called) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) - @mock.patch('certbot_dns_google.dns_google._GoogleClient.get_project_id') + @mock.patch('certbot_dns_google._internal.dns_google._GoogleClient.get_project_id') def test_add_txt_record(self, get_project_id_mock, credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) credential_mock.assert_called_once_with('/not/a/real/path.json', mock.ANY) @@ -133,7 +133,7 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_and_poll(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -151,12 +151,13 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_delete_old(self, unused_credential_mock): client, changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}]) - mock_get_rrs = "certbot_dns_google.dns_google._GoogleClient.get_existing_txt_rrset" + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = ["sample-txt-contents"] client.add_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @@ -165,7 +166,7 @@ class GoogleClientTest(unittest.TestCase): changes.create.call_args_list[0][1]["body"]["deletions"][0]["rrdatas"]) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_noop(self, unused_credential_mock): client, changes = self._setUp_client_with_mock( @@ -175,7 +176,7 @@ class GoogleClientTest(unittest.TestCase): self.assertFalse(changes.create.called) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_zone_lookup(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock(API_ERROR) @@ -184,7 +185,7 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_zone_not_found(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, @@ -194,7 +195,7 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_add_txt_record_error_during_add(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -204,12 +205,13 @@ class GoogleClientTest(unittest.TestCase): DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) - mock_get_rrs = "certbot_dns_google.dns_google._GoogleClient.get_existing_txt_rrset" + # pylint: disable=line-too-long + mock_get_rrs = "certbot_dns_google._internal.dns_google._GoogleClient.get_existing_txt_rrset" with mock.patch(mock_get_rrs) as mock_rrs: mock_rrs.return_value = ["\"sample-txt-contents\"", "\"example-txt-contents\""] @@ -243,7 +245,7 @@ class GoogleClientTest(unittest.TestCase): project=PROJECT_ID) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_zone_lookup(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock(API_ERROR) @@ -251,7 +253,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_zone_not_found(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock([{'managedZones': []}, @@ -260,7 +262,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_del_txt_record_error_during_delete(self, unused_credential_mock): client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}]) @@ -269,7 +271,7 @@ class GoogleClientTest(unittest.TestCase): client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock( @@ -281,12 +283,11 @@ class GoogleClientTest(unittest.TestCase): self.assertEqual(not_found, None) @mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name') - @mock.patch('certbot_dns_google.dns_google.open', + @mock.patch('certbot_dns_google._internal.dns_google.open', mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True) def test_get_existing_fallback(self, unused_credential_mock): client, unused_changes = self._setUp_client_with_mock( [{'managedZones': [{'id': self.zone}]}]) - # pylint: disable=no-member mock_execute = client.dns.resourceRecordSets.return_value.list.return_value.execute mock_execute.side_effect = API_ERROR @@ -294,7 +295,7 @@ class GoogleClientTest(unittest.TestCase): self.assertFalse(rrset) def test_get_project_id(self): - from certbot_dns_google.dns_google import _GoogleClient + from certbot_dns_google._internal.dns_google import _GoogleClient response = DummyResponse() response.status = 200 diff --git a/certbot-dns-google/certbot_dns_google/testdata/discovery.json b/certbot-dns-google/tests/testdata/discovery.json similarity index 100% rename from certbot-dns-google/certbot_dns_google/testdata/discovery.json rename to certbot-dns-google/tests/testdata/discovery.json diff --git a/certbot-dns-linode/Dockerfile b/certbot-dns-linode/Dockerfile deleted file mode 100644 index 6db8b59fb..000000000 --- a/certbot-dns-linode/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-linode - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-linode diff --git a/certbot-dns-linode/MANIFEST.in b/certbot-dns-linode/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-linode/MANIFEST.in +++ b/certbot-dns-linode/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py b/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py new file mode 100644 index 000000000..9090d92d3 --- /dev/null +++ b/certbot-dns-linode/certbot_dns_linode/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_linode.dns_linode` plugin.""" diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode.py b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py similarity index 100% rename from certbot-dns-linode/certbot_dns_linode/dns_linode.py rename to certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py index 507ad5e53..ea6046849 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode.py +++ b/certbot-dns-linode/certbot_dns_linode/_internal/dns_linode.py @@ -2,9 +2,9 @@ import logging import re -import zope.interface from lexicon.providers import linode from lexicon.providers import linode4 +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-linode/docs/api.rst b/certbot-dns-linode/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-linode/docs/api.rst +++ b/certbot-dns-linode/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-linode/docs/api/dns_linode.rst b/certbot-dns-linode/docs/api/dns_linode.rst deleted file mode 100644 index 6380b3eba..000000000 --- a/certbot-dns-linode/docs/api/dns_linode.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_linode.dns_linode` ------------------------------------------------- - -.. automodule:: certbot_dns_linode.dns_linode - :members: diff --git a/certbot-dns-linode/docs/conf.py b/certbot-dns-linode/docs/conf.py index 1fb721400..f23d65023 100644 --- a/certbot-dns-linode/docs/conf.py +++ b/certbot-dns-linode/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index ff1651cf7..1829f7eb2 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] dns-lexicon==2.2.3 diff --git a/certbot-dns-linode/readthedocs.org.requirements.txt b/certbot-dns-linode/readthedocs.org.requirements.txt index 47449454f..3d28f43bf 100644 --- a/certbot-dns-linode/readthedocs.org.requirements.txt +++ b/certbot-dns-linode/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-linode[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-linode[docs]" does not work as +# expected and "pip install -e certbot-dns-linode[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-linode[docs] diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index b70a6a39c..f772dc26a 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,12 +1,15 @@ -from setuptools import setup -from setuptools import find_packages +import sys -version = '0.36.0.dev0' +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand + +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.3', 'mock', 'setuptools', @@ -18,6 +21,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-linode', version=version, @@ -28,7 +45,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -41,6 +58,7 @@ setup( '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', @@ -57,8 +75,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-linode = certbot_dns_linode.dns_linode:Authenticator', + 'dns-linode = certbot_dns_linode._internal.dns_linode:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_linode', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py b/certbot-dns-linode/tests/dns_linode_test.py similarity index 95% rename from certbot-dns-linode/certbot_dns_linode/dns_linode_test.py rename to certbot-dns-linode/tests/dns_linode_test.py index 153f8b51d..3cf615486 100644 --- a/certbot-dns-linode/certbot_dns_linode/dns_linode_test.py +++ b/certbot-dns-linode/tests/dns_linode_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_linode.dns_linode.""" +"""Tests for certbot_dns_linode._internal.dns_linode.""" import unittest @@ -9,7 +9,7 @@ from certbot.compat import os from certbot.plugins import dns_test_common from certbot.plugins import dns_test_common_lexicon from certbot.tests import util as test_util -from certbot_dns_linode.dns_linode import Authenticator +from certbot_dns_linode._internal.dns_linode import Authenticator TOKEN = 'a-token' TOKEN_V3 = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ64' @@ -121,7 +121,7 @@ class LinodeLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex DOMAIN_NOT_FOUND = Exception('Domain not found') def setUp(self): - from certbot_dns_linode.dns_linode import _LinodeLexiconClient + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient self.client = _LinodeLexiconClient(TOKEN, 3) @@ -133,7 +133,7 @@ class Linode4LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLe DOMAIN_NOT_FOUND = Exception('Domain not found') def setUp(self): - from certbot_dns_linode.dns_linode import _LinodeLexiconClient + from certbot_dns_linode._internal.dns_linode import _LinodeLexiconClient self.client = _LinodeLexiconClient(TOKEN, 4) diff --git a/certbot-dns-luadns/Dockerfile b/certbot-dns-luadns/Dockerfile deleted file mode 100644 index efc9f36d6..000000000 --- a/certbot-dns-luadns/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-luadns - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-luadns diff --git a/certbot-dns-luadns/MANIFEST.in b/certbot-dns-luadns/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-luadns/MANIFEST.in +++ b/certbot-dns-luadns/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py new file mode 100644 index 000000000..8ab0a00e2 --- /dev/null +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_luadns.dns_luadns` plugin.""" diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py similarity index 100% rename from certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py rename to certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py index 7cdd4c8e1..7c18c7131 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns.py +++ b/certbot-dns-luadns/certbot_dns_luadns/_internal/dns_luadns.py @@ -1,8 +1,8 @@ """DNS Authenticator for LuaDNS DNS.""" import logging -import zope.interface from lexicon.providers import luadns +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-luadns/docs/api.rst b/certbot-dns-luadns/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-luadns/docs/api.rst +++ b/certbot-dns-luadns/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-luadns/docs/api/dns_luadns.rst b/certbot-dns-luadns/docs/api/dns_luadns.rst deleted file mode 100644 index 9aecbaf05..000000000 --- a/certbot-dns-luadns/docs/api/dns_luadns.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_luadns.dns_luadns` ----------------------------------- - -.. automodule:: certbot_dns_luadns.dns_luadns - :members: diff --git a/certbot-dns-luadns/docs/conf.py b/certbot-dns-luadns/docs/conf.py index bd81d5a5f..899480f66 100644 --- a/certbot-dns-luadns/docs/conf.py +++ b/certbot-dns-luadns/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-luadns/readthedocs.org.requirements.txt b/certbot-dns-luadns/readthedocs.org.requirements.txt index acb51e4ef..6f467dc7c 100644 --- a/certbot-dns-luadns/readthedocs.org.requirements.txt +++ b/certbot-dns-luadns/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-luadns[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-luadns[docs]" does not work as +# expected and "pip install -e certbot-dns-luadns[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-luadns[docs] diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 5f5322319..18ba8cacc 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-luadns', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-luadns = certbot_dns_luadns.dns_luadns:Authenticator', + 'dns-luadns = certbot_dns_luadns._internal.dns_luadns:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_luadns', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py b/certbot-dns-luadns/tests/dns_luadns_test.py similarity index 87% rename from certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py rename to certbot-dns-luadns/tests/dns_luadns_test.py index 73cef6521..934d3e103 100644 --- a/certbot-dns-luadns/certbot_dns_luadns/dns_luadns_test.py +++ b/certbot-dns-luadns/tests/dns_luadns_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_luadns.dns_luadns.""" +"""Tests for certbot_dns_luadns._internal.dns_luadns.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_luadns.dns_luadns import Authenticator + from certbot_dns_luadns._internal.dns_luadns import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"luadns_email": EMAIL, "luadns_token": TOKEN}, path) @@ -40,7 +40,7 @@ class LuaDNSLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLex LOGIN_ERROR = HTTPError("401 Client Error: Unauthorized for url: ...") def setUp(self): - from certbot_dns_luadns.dns_luadns import _LuaDNSLexiconClient + from certbot_dns_luadns._internal.dns_luadns import _LuaDNSLexiconClient self.client = _LuaDNSLexiconClient(EMAIL, TOKEN, 0) diff --git a/certbot-dns-nsone/Dockerfile b/certbot-dns-nsone/Dockerfile deleted file mode 100644 index de541e850..000000000 --- a/certbot-dns-nsone/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-nsone - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-nsone diff --git a/certbot-dns-nsone/MANIFEST.in b/certbot-dns-nsone/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-nsone/MANIFEST.in +++ b/certbot-dns-nsone/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py new file mode 100644 index 000000000..40a095edf --- /dev/null +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_nsone.dns_nsone` plugin.""" diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py similarity index 100% rename from certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py rename to certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py index b585ddb7a..f5af37389 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone.py +++ b/certbot-dns-nsone/certbot_dns_nsone/_internal/dns_nsone.py @@ -1,8 +1,8 @@ """DNS Authenticator for NS1 DNS.""" import logging -import zope.interface from lexicon.providers import nsone +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-nsone/docs/api.rst b/certbot-dns-nsone/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-nsone/docs/api.rst +++ b/certbot-dns-nsone/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-nsone/docs/api/dns_nsone.rst b/certbot-dns-nsone/docs/api/dns_nsone.rst deleted file mode 100644 index 788ce732a..000000000 --- a/certbot-dns-nsone/docs/api/dns_nsone.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_nsone.dns_nsone` ----------------------------------- - -.. automodule:: certbot_dns_nsone.dns_nsone - :members: diff --git a/certbot-dns-nsone/docs/conf.py b/certbot-dns-nsone/docs/conf.py index cffe2a25c..aec0771a2 100644 --- a/certbot-dns-nsone/docs/conf.py +++ b/certbot-dns-nsone/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-nsone/readthedocs.org.requirements.txt b/certbot-dns-nsone/readthedocs.org.requirements.txt index dbdee4480..bf17eae30 100644 --- a/certbot-dns-nsone/readthedocs.org.requirements.txt +++ b/certbot-dns-nsone/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-nsone[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-nsone[docs]" does not work as +# expected and "pip install -e certbot-dns-nsone[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-nsone[docs] diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 00ed64032..3894f01cd 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-nsone', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-nsone = certbot_dns_nsone.dns_nsone:Authenticator', + 'dns-nsone = certbot_dns_nsone._internal.dns_nsone:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_nsone', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py b/certbot-dns-nsone/tests/dns_nsone_test.py similarity index 88% rename from certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py rename to certbot-dns-nsone/tests/dns_nsone_test.py index b2db2f603..dd6168f08 100644 --- a/certbot-dns-nsone/certbot_dns_nsone/dns_nsone_test.py +++ b/certbot-dns-nsone/tests/dns_nsone_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_nsone.dns_nsone.""" +"""Tests for certbot_dns_nsone._internal.dns_nsone.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_nsone.dns_nsone import Authenticator + from certbot_dns_nsone._internal.dns_nsone import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write({"nsone_api_key": API_KEY}, path) @@ -40,7 +40,7 @@ class NS1LexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_nsone.dns_nsone import _NS1LexiconClient + from certbot_dns_nsone._internal.dns_nsone import _NS1LexiconClient self.client = _NS1LexiconClient(API_KEY, 0) diff --git a/certbot-dns-ovh/Dockerfile b/certbot-dns-ovh/Dockerfile deleted file mode 100644 index 37e488dc4..000000000 --- a/certbot-dns-ovh/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-ovh - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-ovh diff --git a/certbot-dns-ovh/MANIFEST.in b/certbot-dns-ovh/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-ovh/MANIFEST.in +++ b/certbot-dns-ovh/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py new file mode 100644 index 000000000..133694b9e --- /dev/null +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_ovh.dns_ovh` plugin.""" diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py similarity index 100% rename from certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py rename to certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py index 84771b0a8..a495983f2 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh.py +++ b/certbot-dns-ovh/certbot_dns_ovh/_internal/dns_ovh.py @@ -1,8 +1,8 @@ """DNS Authenticator for OVH DNS.""" import logging -import zope.interface from lexicon.providers import ovh +import zope.interface from certbot import errors from certbot import interfaces diff --git a/certbot-dns-ovh/docs/api.rst b/certbot-dns-ovh/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-ovh/docs/api.rst +++ b/certbot-dns-ovh/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-ovh/docs/api/dns_ovh.rst b/certbot-dns-ovh/docs/api/dns_ovh.rst deleted file mode 100644 index 79863d05f..000000000 --- a/certbot-dns-ovh/docs/api/dns_ovh.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_ovh.dns_ovh` ------------------------------- - -.. automodule:: certbot_dns_ovh.dns_ovh - :members: diff --git a/certbot-dns-ovh/docs/conf.py b/certbot-dns-ovh/docs/conf.py index 57194666e..a4985edee 100644 --- a/certbot-dns-ovh/docs/conf.py +++ b/certbot-dns-ovh/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 5472399aa..2e11550d6 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,4 +1,4 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/readthedocs.org.requirements.txt b/certbot-dns-ovh/readthedocs.org.requirements.txt index 0780e12a1..3c21ae0ce 100644 --- a/certbot-dns-ovh/readthedocs.org.requirements.txt +++ b/certbot-dns-ovh/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-ovh[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-ovh[docs]" does not work as +# expected and "pip install -e certbot-dns-ovh[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-ovh[docs] diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index bff394bf9..2fccf17c2 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-ovh', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -42,6 +58,8 @@ setup( 'Programming Language :: Python :: 3.4', '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', @@ -58,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-ovh = certbot_dns_ovh.dns_ovh:Authenticator', + 'dns-ovh = certbot_dns_ovh._internal.dns_ovh:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_ovh', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py b/certbot-dns-ovh/tests/dns_ovh_test.py similarity index 90% rename from certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py rename to certbot-dns-ovh/tests/dns_ovh_test.py index b48a85055..a420239ab 100644 --- a/certbot-dns-ovh/certbot_dns_ovh/dns_ovh_test.py +++ b/certbot-dns-ovh/tests/dns_ovh_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_ovh.dns_ovh.""" +"""Tests for certbot_dns_ovh._internal.dns_ovh.""" import unittest @@ -22,7 +22,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_ovh.dns_ovh import Authenticator + from certbot_dns_ovh._internal.dns_ovh import Authenticator path = os.path.join(self.tempdir, 'file.ini') credentials = { @@ -48,7 +48,7 @@ class OVHLexiconClientTest(unittest.TestCase, dns_test_common_lexicon.BaseLexico LOGIN_ERROR = HTTPError('403 Client Error: Forbidden for url: https://eu.api.ovh.com/1.0/...') def setUp(self): - from certbot_dns_ovh.dns_ovh import _OVHLexiconClient + from certbot_dns_ovh._internal.dns_ovh import _OVHLexiconClient self.client = _OVHLexiconClient( ENDPOINT, APPLICATION_KEY, APPLICATION_SECRET, CONSUMER_KEY, 0 diff --git a/certbot-dns-rfc2136/Dockerfile b/certbot-dns-rfc2136/Dockerfile deleted file mode 100644 index 3ebb6a72e..000000000 --- a/certbot-dns-rfc2136/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-rfc2136 - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-rfc2136 diff --git a/certbot-dns-rfc2136/MANIFEST.in b/certbot-dns-rfc2136/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-rfc2136/MANIFEST.in +++ b/certbot-dns-rfc2136/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py new file mode 100644 index 000000000..44894bb35 --- /dev/null +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_rfc2136.dns_rfc2136` plugin.""" diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py similarity index 96% rename from certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py rename to certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py index 2061374e0..ee71c9681 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136.py +++ b/certbot-dns-rfc2136/certbot_dns_rfc2136/_internal/dns_rfc2136.py @@ -206,7 +206,11 @@ class _RFC2136Client(object): request.flags ^= dns.flags.RD try: - response = dns.query.udp(request, self.server, port=self.port) + try: + response = dns.query.tcp(request, self.server, port=self.port) + except OSError as e: + logger.debug('TCP query failed, fallback to UDP: %s', e) + response = dns.query.udp(request, self.server, port=self.port) rcode = response.rcode() # Authoritative Answer bit should be set diff --git a/certbot-dns-rfc2136/docs/api.rst b/certbot-dns-rfc2136/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-rfc2136/docs/api.rst +++ b/certbot-dns-rfc2136/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst b/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst deleted file mode 100644 index f5e98454a..000000000 --- a/certbot-dns-rfc2136/docs/api/dns_rfc2136.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_rfc2136.dns_rfc2136` --------------------------------------- - -.. automodule:: certbot_dns_rfc2136.dns_rfc2136 - :members: diff --git a/certbot-dns-rfc2136/docs/conf.py b/certbot-dns-rfc2136/docs/conf.py index 8cc5d595f..e4df84594 100644 --- a/certbot-dns-rfc2136/docs/conf.py +++ b/certbot-dns-rfc2136/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-rfc2136/local-oldest-requirements.txt b/certbot-dns-rfc2136/local-oldest-requirements.txt index 0bc9ee027..3fce6f83b 100644 --- a/certbot-dns-rfc2136/local-oldest-requirements.txt +++ b/certbot-dns-rfc2136/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-rfc2136/readthedocs.org.requirements.txt b/certbot-dns-rfc2136/readthedocs.org.requirements.txt index df89018ce..2cf4f70f8 100644 --- a/certbot-dns-rfc2136/readthedocs.org.requirements.txt +++ b/certbot-dns-rfc2136/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-rfc2136[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-rfc2136[docs]" does not work as +# expected and "pip install -e certbot-dns-rfc2136[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-rfc2136[docs] diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index 25c6ae1d1..47167fa2b 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -1,14 +1,16 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dnspython', 'mock', 'setuptools', @@ -20,6 +22,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-rfc2136', version=version, @@ -30,7 +46,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -43,6 +59,7 @@ setup( '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', @@ -59,8 +76,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-rfc2136 = certbot_dns_rfc2136.dns_rfc2136:Authenticator', + 'dns-rfc2136 = certbot_dns_rfc2136._internal.dns_rfc2136:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_rfc2136', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py similarity index 88% rename from certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py rename to certbot-dns-rfc2136/tests/dns_rfc2136_test.py index d800f1ec7..c767dba23 100644 --- a/certbot-dns-rfc2136/certbot_dns_rfc2136/dns_rfc2136_test.py +++ b/certbot-dns-rfc2136/tests/dns_rfc2136_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_rfc2136.dns_rfc2136.""" +"""Tests for certbot_dns_rfc2136._internal.dns_rfc2136.""" import unittest @@ -23,7 +23,7 @@ VALID_CONFIG = {"rfc2136_server": SERVER, "rfc2136_name": NAME, "rfc2136_secret" class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import Authenticator + from certbot_dns_rfc2136._internal.dns_rfc2136 import Authenticator super(AuthenticatorTest, self).setUp() @@ -73,7 +73,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic class RFC2136ClientTest(unittest.TestCase): def setUp(self): - from certbot_dns_rfc2136.dns_rfc2136 import _RFC2136Client + from certbot_dns_rfc2136._internal.dns_rfc2136 import _RFC2136Client self.rfc2136_client = _RFC2136Client(SERVER, PORT, NAME, SECRET, dns.tsig.HMAC_MD5) @@ -162,7 +162,7 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._find_domain, 'foo.bar.'+DOMAIN) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_found(self, query_mock): query_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) query_mock.return_value.rcode.return_value = dns.rcode.NOERROR @@ -173,7 +173,7 @@ class RFC2136ClientTest(unittest.TestCase): query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) self.assertTrue(result) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_not_found(self, query_mock): query_mock.return_value.rcode.return_value = dns.rcode.NXDOMAIN @@ -183,7 +183,7 @@ class RFC2136ClientTest(unittest.TestCase): query_mock.assert_called_with(mock.ANY, SERVER, port=PORT) self.assertFalse(result) - @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") def test_query_soa_wraps_errors(self, query_mock): query_mock.side_effect = Exception @@ -193,6 +193,20 @@ class RFC2136ClientTest(unittest.TestCase): self.rfc2136_client._query_soa, DOMAIN) + @mock.patch("dns.query.udp") + @mock.patch("dns.query.tcp") + def test_query_soa_fallback_to_udp(self, tcp_mock, udp_mock): + tcp_mock.side_effect = OSError + udp_mock.return_value = mock.MagicMock(answer=[mock.MagicMock()], flags=dns.flags.AA) + udp_mock.return_value.rcode.return_value = dns.rcode.NOERROR + + # _query_soa | pylint: disable=protected-access + result = self.rfc2136_client._query_soa(DOMAIN) + + tcp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + udp_mock.assert_called_with(mock.ANY, SERVER, port=PORT) + self.assertTrue(result) + if __name__ == "__main__": unittest.main() # pragma: no cover diff --git a/certbot-dns-route53/Dockerfile b/certbot-dns-route53/Dockerfile deleted file mode 100644 index e1825c11d..000000000 --- a/certbot-dns-route53/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-route53 - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-route53 diff --git a/certbot-dns-route53/LICENSE b/certbot-dns-route53/LICENSE.txt similarity index 100% rename from certbot-dns-route53/LICENSE rename to certbot-dns-route53/LICENSE.txt diff --git a/certbot-dns-route53/MANIFEST.in b/certbot-dns-route53/MANIFEST.in index c48c07e59..fc62028b0 100644 --- a/certbot-dns-route53/MANIFEST.in +++ b/certbot-dns-route53/MANIFEST.in @@ -1,3 +1,6 @@ -include LICENSE +include LICENSE.txt include README recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py b/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py new file mode 100644 index 000000000..ac9ead791 --- /dev/null +++ b/certbot-dns-route53/certbot_dns_route53/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_route53.dns_route53` plugin.""" diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53.py b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py similarity index 94% rename from certbot-dns-route53/certbot_dns_route53/dns_route53.py rename to certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py index e32017b34..637558304 100644 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53.py +++ b/certbot-dns-route53/certbot_dns_route53/_internal/dns_route53.py @@ -4,15 +4,17 @@ import logging import time import boto3 +from botocore.exceptions import ClientError +from botocore.exceptions import NoCredentialsError import zope.interface -from botocore.exceptions import NoCredentialsError, ClientError +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces from certbot.plugins import dns_common -from acme.magic_typing import DefaultDict, List, Dict # pylint: disable=unused-import, no-name-in-module - logger = logging.getLogger(__name__) INSTRUCTIONS = ( @@ -20,6 +22,7 @@ INSTRUCTIONS = ( "https://boto3.readthedocs.io/en/latest/guide/configuration.html#best-practices-for-configuring-credentials " # pylint: disable=line-too-long "and add the necessary permissions for Route53 access.") + @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_common.DNSAuthenticator): diff --git a/certbot-dns-route53/certbot_dns_route53/authenticator.py b/certbot-dns-route53/certbot_dns_route53/authenticator.py index 53215ea1d..2987934a1 100644 --- a/certbot-dns-route53/certbot_dns_route53/authenticator.py +++ b/certbot-dns-route53/certbot_dns_route53/authenticator.py @@ -1,16 +1,17 @@ -"""Shim around `~certbot_dns_route53.dns_route53` for backwards compatibility.""" +"""Shim around `~certbot_dns_route53._internal.dns_route53` for backwards compatibility.""" import warnings import zope.interface from certbot import interfaces -from certbot_dns_route53 import dns_route53 +from certbot_dns_route53._internal import dns_route53 @zope.interface.implementer(interfaces.IAuthenticator) @zope.interface.provider(interfaces.IPluginFactory) class Authenticator(dns_route53.Authenticator): - """Shim around `~certbot_dns_route53.dns_route53.Authenticator` for backwards compatibility.""" + """Shim around `~certbot_dns_route53._internal.dns_route53.Authenticator` + for backwards compatibility.""" hidden = True diff --git a/certbot-dns-route53/docs/api.rst b/certbot-dns-route53/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-route53/docs/api.rst +++ b/certbot-dns-route53/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-route53/docs/api/authenticator.rst b/certbot-dns-route53/docs/api/authenticator.rst deleted file mode 100644 index 2d96a419b..000000000 --- a/certbot-dns-route53/docs/api/authenticator.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_route53.authenticator` ----------------------------------------- - -.. automodule:: certbot_dns_route53.authenticator - :members: diff --git a/certbot-dns-route53/docs/api/dns_route53.rst b/certbot-dns-route53/docs/api/dns_route53.rst deleted file mode 100644 index 7573f2e19..000000000 --- a/certbot-dns-route53/docs/api/dns_route53.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_route53.dns_route53` --------------------------------------- - -.. automodule:: certbot_dns_route53.dns_route53 - :members: diff --git a/certbot-dns-route53/docs/conf.py b/certbot-dns-route53/docs/conf.py index 25a7c6e4d..cb8aae0b6 100644 --- a/certbot-dns-route53/docs/conf.py +++ b/certbot-dns-route53/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-route53/local-oldest-requirements.txt b/certbot-dns-route53/local-oldest-requirements.txt index 0bc9ee027..3fce6f83b 100644 --- a/certbot-dns-route53/local-oldest-requirements.txt +++ b/certbot-dns-route53/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.29.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-route53/readthedocs.org.requirements.txt b/certbot-dns-route53/readthedocs.org.requirements.txt index 660a90d0e..993225eac 100644 --- a/certbot-dns-route53/readthedocs.org.requirements.txt +++ b/certbot-dns-route53/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-route53[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-route53[docs]" does not work as +# expected and "pip install -e certbot-dns-route53[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-route53[docs] diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index b8af58b30..b4dcc58c1 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,19 +1,36 @@ -from setuptools import setup -from setuptools import find_packages +import sys -version = '0.36.0.dev0' +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand + +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ 'acme>=0.29.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'boto3', 'mock', 'setuptools', 'zope.interface', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-route53', version=version, @@ -24,7 +41,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -37,6 +54,7 @@ setup( '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', @@ -50,9 +68,11 @@ setup( keywords=['certbot', 'route53', 'aws'], entry_points={ 'certbot.plugins': [ - 'dns-route53 = certbot_dns_route53.dns_route53:Authenticator', + 'dns-route53 = certbot_dns_route53._internal.dns_route53:Authenticator', 'certbot-route53:auth = certbot_dns_route53.authenticator:Authenticator' ], }, + tests_require=["pytest"], test_suite='certbot_dns_route53', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py b/certbot-dns-route53/tests/dns_route53_test.py similarity index 96% rename from certbot-dns-route53/certbot_dns_route53/dns_route53_test.py rename to certbot-dns-route53/tests/dns_route53_test.py index 36c391690..85ec259b1 100644 --- a/certbot-dns-route53/certbot_dns_route53/dns_route53_test.py +++ b/certbot-dns-route53/tests/dns_route53_test.py @@ -1,9 +1,10 @@ -"""Tests for certbot_dns_route53.dns_route53.Authenticator""" +"""Tests for certbot_dns_route53._internal.dns_route53.Authenticator""" import unittest +from botocore.exceptions import ClientError +from botocore.exceptions import NoCredentialsError import mock -from botocore.exceptions import NoCredentialsError, ClientError from certbot import errors from certbot.compat import os @@ -15,7 +16,7 @@ class AuthenticatorTest(unittest.TestCase, dns_test_common.BaseAuthenticatorTest # pylint: disable=protected-access def setUp(self): - from certbot_dns_route53.dns_route53 import Authenticator + from certbot_dns_route53._internal.dns_route53 import Authenticator super(AuthenticatorTest, self).setUp() @@ -122,7 +123,7 @@ class ClientTest(unittest.TestCase): } def setUp(self): - from certbot_dns_route53.dns_route53 import Authenticator + from certbot_dns_route53._internal.dns_route53 import Authenticator super(ClientTest, self).setUp() diff --git a/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh b/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh deleted file mode 100755 index dbbaa2251..000000000 --- a/certbot-dns-route53/tools/tester.pkoch-macos_sierra.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/bash -# I just wanted a place to dump the incantations I use for testing. -set -e - -brew install openssl libffi - -rm -rf scratch; mkdir scratch - -virtualenv scratch/venv -p /usr/local/bin/python2.7 -scratch/venv/bin/pip install -U pip setuptools - -CPPFLAGS=-I/usr/local/opt/openssl/include LDFLAGS=-L/usr/local/opt/openssl/lib scratch/venv/bin/pip install -e . - -scratch/venv/bin/certbot certonly -n --agree-tos --test-cert --email pkoch@lifeonmars.pt -a certbot-route53:auth -d pkoch.lifeonmars.pt --work-dir scratch --config-dir scratch --logs-dir scratch - -rm -rf scratch diff --git a/certbot-dns-sakuracloud/Dockerfile b/certbot-dns-sakuracloud/Dockerfile deleted file mode 100644 index 9fa9b3c22..000000000 --- a/certbot-dns-sakuracloud/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM certbot/certbot - -COPY . src/certbot-dns-sakuracloud - -RUN pip install --constraint docker_constraints.txt --no-cache-dir --editable src/certbot-dns-sakuracloud diff --git a/certbot-dns-sakuracloud/MANIFEST.in b/certbot-dns-sakuracloud/MANIFEST.in index 18f018c08..5a661cef6 100644 --- a/certbot-dns-sakuracloud/MANIFEST.in +++ b/certbot-dns-sakuracloud/MANIFEST.in @@ -1,3 +1,6 @@ include LICENSE.txt include README.rst recursive-include docs * +recursive-include tests * +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py new file mode 100644 index 000000000..0c8839024 --- /dev/null +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/__init__.py @@ -0,0 +1 @@ +"""Internal implementation of `~certbot_dns_sakuracloud.dns_sakuracloud` plugin.""" diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py similarity index 100% rename from certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py rename to certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py index d6e20894d..25042bfc6 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud.py +++ b/certbot-dns-sakuracloud/certbot_dns_sakuracloud/_internal/dns_sakuracloud.py @@ -1,8 +1,8 @@ """DNS Authenticator for Sakura Cloud DNS.""" import logging -import zope.interface from lexicon.providers import sakuracloud +import zope.interface from certbot import interfaces from certbot.plugins import dns_common diff --git a/certbot-dns-sakuracloud/docs/api.rst b/certbot-dns-sakuracloud/docs/api.rst index 8668ec5d8..ac13c3df2 100644 --- a/certbot-dns-sakuracloud/docs/api.rst +++ b/certbot-dns-sakuracloud/docs/api.rst @@ -2,7 +2,4 @@ API Documentation ================= -.. toctree:: - :glob: - - api/** +Certbot plugins implement the Certbot plugins API, and do not otherwise have an external API. diff --git a/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst b/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst deleted file mode 100644 index 74692e15b..000000000 --- a/certbot-dns-sakuracloud/docs/api/dns_sakuracloud.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_dns_sakuracloud.dns_sakuracloud` ----------------------------------------------- - -.. automodule:: certbot_dns_sakuracloud.dns_sakuracloud - :members: diff --git a/certbot-dns-sakuracloud/docs/conf.py b/certbot-dns-sakuracloud/docs/conf.py index e14fe1d4c..f973779ab 100644 --- a/certbot-dns-sakuracloud/docs/conf.py +++ b/certbot-dns-sakuracloud/docs/conf.py @@ -17,6 +17,7 @@ # documentation root, use os.path.abspath to make it absolute, like shown here. # import os + # import sys # sys.path.insert(0, os.path.abspath('.')) diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index c9999e87a..67d4cc53b 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. acme[dev]==0.31.0 -certbot[dev]==0.34.0 +-e certbot[dev] diff --git a/certbot-dns-sakuracloud/readthedocs.org.requirements.txt b/certbot-dns-sakuracloud/readthedocs.org.requirements.txt index 3f46d95ef..07bc8a289 100644 --- a/certbot-dns-sakuracloud/readthedocs.org.requirements.txt +++ b/certbot-dns-sakuracloud/readthedocs.org.requirements.txt @@ -1,12 +1,12 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# install -e certbot-dns-sakuracloud[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 .[docs]" must be used instead +# in --editable mode (-e), just "pip install certbot-dns-sakuracloud[docs]" does not work as +# expected and "pip install -e certbot-dns-sakuracloud[docs]" must be used instead -e acme --e . +-e certbot -e certbot-dns-sakuracloud[docs] diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index b674b7199..56c209a90 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -1,13 +1,15 @@ -from setuptools import setup +import sys + from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand - -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ 'acme>=0.31.0', - 'certbot>=0.34.0', + 'certbot>=1.0.0.dev0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', @@ -19,6 +21,20 @@ docs_extras = [ 'sphinx_rtd_theme', ] +class PyTest(TestCommand): + user_options = [] + + def initialize_options(self): + TestCommand.initialize_options(self) + self.pytest_args = '' + + def run_tests(self): + import shlex + # import here, cause outside the eggs aren't loaded + import pytest + errno = pytest.main(shlex.split(self.pytest_args)) + sys.exit(errno) + setup( name='certbot-dns-sakuracloud', version=version, @@ -29,7 +45,7 @@ setup( license='Apache License 2.0', python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Development Status :: 3 - Alpha', + 'Development Status :: 5 - Production/Stable', 'Environment :: Plugins', 'Intended Audience :: System Administrators', 'License :: OSI Approved :: Apache Software License', @@ -41,6 +57,8 @@ setup( 'Programming Language :: Python :: 3.4', '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', @@ -57,8 +75,10 @@ setup( }, entry_points={ 'certbot.plugins': [ - 'dns-sakuracloud = certbot_dns_sakuracloud.dns_sakuracloud:Authenticator', + 'dns-sakuracloud = certbot_dns_sakuracloud._internal.dns_sakuracloud:Authenticator', ], }, + tests_require=["pytest"], test_suite='certbot_dns_sakuracloud', + cmdclass={"test": PyTest}, ) diff --git a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py similarity index 88% rename from certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py rename to certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py index 10abc29e2..16890b5a9 100644 --- a/certbot-dns-sakuracloud/certbot_dns_sakuracloud/dns_sakuracloud_test.py +++ b/certbot-dns-sakuracloud/tests/dns_sakuracloud_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot_dns_sakuracloud.dns_sakuracloud.""" +"""Tests for certbot_dns_sakuracloud._internal.dns_sakuracloud.""" import unittest @@ -20,7 +20,7 @@ class AuthenticatorTest(test_util.TempDirTestCase, def setUp(self): super(AuthenticatorTest, self).setUp() - from certbot_dns_sakuracloud.dns_sakuracloud import Authenticator + from certbot_dns_sakuracloud._internal.dns_sakuracloud import Authenticator path = os.path.join(self.tempdir, 'file.ini') dns_test_common.write( @@ -44,7 +44,7 @@ class SakuraCloudLexiconClientTest(unittest.TestCase, LOGIN_ERROR = HTTPError('401 Client Error: Unauthorized for url: {0}.'.format(DOMAIN)) def setUp(self): - from certbot_dns_sakuracloud.dns_sakuracloud import _SakuraCloudLexiconClient + from certbot_dns_sakuracloud._internal.dns_sakuracloud import _SakuraCloudLexiconClient self.client = _SakuraCloudLexiconClient(API_TOKEN, API_SECRET, 0) diff --git a/certbot-nginx/MANIFEST.in b/certbot-nginx/MANIFEST.in index 8707f9443..65b27877e 100644 --- a/certbot-nginx/MANIFEST.in +++ b/certbot-nginx/MANIFEST.in @@ -1,6 +1,6 @@ include LICENSE.txt include README.rst -recursive-include docs * -recursive-include certbot_nginx/tests/testdata * -include certbot_nginx/options-ssl-nginx.conf -include certbot_nginx/options-ssl-nginx-old.conf +recursive-include tests * +recursive-include certbot_nginx/_internal/tls_configs *.conf +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot-nginx/certbot_nginx/_internal/__init__.py b/certbot-nginx/certbot_nginx/_internal/__init__.py new file mode 100644 index 000000000..71d79b0c2 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/__init__.py @@ -0,0 +1 @@ +"""Certbot nginx plugin internal implementation.""" diff --git a/certbot-nginx/certbot_nginx/configurator.py b/certbot-nginx/certbot_nginx/_internal/configurator.py similarity index 91% rename from certbot-nginx/certbot_nginx/configurator.py rename to certbot-nginx/certbot_nginx/_internal/configurator.py index e078ad4cb..70d9d87f8 100644 --- a/certbot-nginx/certbot_nginx/configurator.py +++ b/certbot-nginx/certbot_nginx/_internal/configurator.py @@ -1,4 +1,6 @@ """Nginx Configuration""" +# https://github.com/PyCQA/pylint/issues/73 +from distutils.version import LooseVersion # pylint: disable=no-name-in-module, import-error import logging import re import socket @@ -6,30 +8,27 @@ import subprocess import tempfile import time -import pkg_resources - import OpenSSL +import pkg_resources import zope.interface from acme import challenges from acme import crypto_util as acme_crypto_util -from acme.magic_typing import List, Dict, Set # pylint: disable=unused-import, no-name-in-module - -from certbot import constants as core_constants +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=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 misc from certbot.compat import os from certbot.plugins import common - -from certbot_nginx import constants -from certbot_nginx import display_ops -from certbot_nginx import http_01 -from certbot_nginx import nginxparser -from certbot_nginx import obj # pylint: disable=unused-import -from certbot_nginx import parser +from certbot_nginx._internal import constants +from certbot_nginx._internal import display_ops +from certbot_nginx._internal import http_01 +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj # pylint: disable=unused-import +from certbot_nginx._internal import parser NAME_RANK = 0 START_WILDCARD_RANK = 1 @@ -44,7 +43,6 @@ logger = logging.getLogger(__name__) @zope.interface.implementer(interfaces.IAuthenticator, interfaces.IInstaller) @zope.interface.provider(interfaces.IPluginFactory) class NginxConfigurator(common.Installer): - # pylint: disable=too-many-instance-attributes,too-many-public-methods """Nginx configurator. .. todo:: Add proper support for comments in the config. Currently, @@ -54,7 +52,7 @@ class NginxConfigurator(common.Installer): :type config: :class:`~certbot.interfaces.IConfig` :ivar parser: Handles low level parsing - :type parser: :class:`~certbot_nginx.parser` + :type parser: :class:`~certbot_nginx._internal.parser` :ivar str save_notes: Human-readable config change notes @@ -92,13 +90,14 @@ class NginxConfigurator(common.Installer): :param tup version: version of Nginx as a tuple (1, 4, 7) (used mostly for unittesting) + :param tup openssl_version: version of OpenSSL linked to Nginx as a tuple (1, 4, 7) + (used mostly for unittesting) + """ version = kwargs.pop("version", None) + openssl_version = kwargs.pop("openssl_version", None) super(NginxConfigurator, self).__init__(*args, **kwargs) - # Verify that all directories and files exist with proper permissions - self._verify_setup() - # Files to save self.save_notes = "" @@ -116,6 +115,7 @@ class NginxConfigurator(common.Installer): # These will be set in the prepare function self.parser = None self.version = version + self.openssl_version = openssl_version self._enhance_func = {"redirect": self._enable_redirect, "ensure-http-header": self._set_http_header, "staple-ocsp": self._enable_ocsp_stapling} @@ -125,10 +125,35 @@ class NginxConfigurator(common.Installer): @property def mod_ssl_conf_src(self): """Full absolute path to SSL configuration file source.""" - config_filename = "options-ssl-nginx.conf" - if self.version < (1, 5, 9): - config_filename = "options-ssl-nginx-old.conf" - return pkg_resources.resource_filename("certbot_nginx", config_filename) + + # Why all this complexity? Well, we want to support Mozilla's intermediate + # recommendations. But TLS1.3 is only supported by newer versions of Nginx. + # And as for session tickets, our ideal is to turn them off across the board. + # But! Turning them off at all is only supported with new enough versions of + # Nginx. And older versions of OpenSSL have a bug that leads to browser errors + # given certain configurations. While we'd prefer to have forward secrecy, we'd + # rather fail open than error out. Unfortunately, Nginx can be compiled against + # many versions of OpenSSL. So we have to check both for the two different features, + # leading to four different combinations of options. + # For a complete history, check out https://github.com/certbot/certbot/issues/7322 + + use_tls13 = self.version >= (1, 13, 0) + session_tix_off = self.version >= (1, 5, 9) and self.openssl_version and\ + LooseVersion(self.openssl_version) >= LooseVersion('1.0.2l') + + if use_tls13: + if session_tix_off: + config_filename = "options-ssl-nginx.conf" + else: + config_filename = "options-ssl-nginx-tls13-session-tix-on.conf" + else: + if session_tix_off: + config_filename = "options-ssl-nginx-tls12-only.conf" + else: + config_filename = "options-ssl-nginx-old.conf" + + return pkg_resources.resource_filename( + "certbot_nginx", os.path.join("_internal", "tls_configs", config_filename)) @property def mod_ssl_conf(self): @@ -167,6 +192,9 @@ class NginxConfigurator(common.Installer): if self.version is None: self.version = self.get_version() + if self.openssl_version is None: + self.openssl_version = self._get_openssl_version() + self.install_ssl_options_conf(self.mod_ssl_conf, self.updated_mod_ssl_conf_digest) self.install_ssl_dhparams() @@ -255,7 +283,7 @@ class NginxConfigurator(common.Installer): filtered_vhosts[name] = vhost # Only unique VHost objects - dialog_input = set([vhost for vhost in filtered_vhosts.values()]) + dialog_input = set(filtered_vhosts.values()) # Ask the user which of names to enable, expect list of names back return_vhosts = display_ops.select_vhost_multiple(list(dialog_input)) @@ -294,7 +322,7 @@ class NginxConfigurator(common.Installer): MisconfigurationError. :returns: ssl vhosts associated with name - :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` + :rtype: list of :class:`~certbot_nginx._internal.obj.VirtualHost` """ if util.is_wildcard_domain(target_name): @@ -413,7 +441,7 @@ class NginxConfigurator(common.Installer): :param list matches: list of dicts containing the vhost, the matching name, and the numerical rank :returns: the most matching vhost - :rtype: :class:`~certbot_nginx.obj.VirtualHost` + :rtype: :class:`~certbot_nginx._internal.obj.VirtualHost` """ if not matches: @@ -500,7 +528,7 @@ class NginxConfigurator(common.Installer): MisconfigurationError. :returns: vhosts associated with name - :rtype: list of :class:`~certbot_nginx.obj.VirtualHost` + :rtype: list of :class:`~certbot_nginx._internal.obj.VirtualHost` """ if util.is_wildcard_domain(target_name): @@ -614,7 +642,7 @@ class NginxConfigurator(common.Installer): Make a server SSL by adding new listen and SSL directives. :param vhost: The vhost to add SSL to. - :type vhost: :class:`~certbot_nginx.obj.VirtualHost` + :type vhost: :class:`~certbot_nginx._internal.obj.VirtualHost` """ https_port = self.config.https_port @@ -674,9 +702,9 @@ class NginxConfigurator(common.Installer): :param str domain: domain to enhance :param str enhancement: enhancement type defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: options for the enhancement - See :const:`~certbot.constants.ENHANCEMENTS` + See :const:`~certbot.plugins.enhancements.ENHANCEMENTS` documentation for appropriate parameter. """ @@ -742,9 +770,9 @@ class NginxConfigurator(common.Installer): :param vhost: The server block to break up into two. :param list only_directives: If this exists, only duplicate these directives when splitting the block. - :type vhost: :class:`~certbot_nginx.obj.VirtualHost` + :type vhost: :class:`~certbot_nginx._internal.obj.VirtualHost` :returns: tuple (http_vhost, https_vhost) - :rtype: tuple of type :class:`~certbot_nginx.obj.VirtualHost` + :rtype: tuple of type :class:`~certbot_nginx._internal.obj.VirtualHost` """ http_vhost = self.parser.duplicate_vhost(vhost, only_directives=only_directives) @@ -895,21 +923,27 @@ class NginxConfigurator(common.Installer): except errors.SubprocessError as err: raise errors.MisconfigurationError(str(err)) - def _verify_setup(self): - """Verify the setup to ensure safe operating environment. + def _nginx_version(self): + """Return results of nginx -V - Make sure that files/directories are setup with appropriate permissions - Aim for defensive coding... make sure all input files - have permissions of root. + :returns: version text + :rtype: str + :raises .PluginError: + Unable to run Nginx version command """ - uid = misc.os_geteuid() - util.make_or_verify_dir( - self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid) - util.make_or_verify_dir( - self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid) - util.make_or_verify_dir( - self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid) + try: + proc = subprocess.Popen( + [self.conf('ctl'), "-c", self.nginx_conf, "-V"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + universal_newlines=True) + text = proc.communicate()[1] # nginx prints output to stderr + except (OSError, ValueError) as error: + logger.debug(str(error), exc_info=True) + raise errors.PluginError( + "Unable to run %s -V" % self.conf('ctl')) + return text def get_version(self): """Return version of Nginx Server. @@ -923,17 +957,7 @@ class NginxConfigurator(common.Installer): Unable to find Nginx version or version is unsupported """ - try: - proc = subprocess.Popen( - [self.conf('ctl'), "-c", self.nginx_conf, "-V"], - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - universal_newlines=True) - text = proc.communicate()[1] # nginx prints output to stderr - except (OSError, ValueError) as error: - logger.debug(str(error), exc_info=True) - raise errors.PluginError( - "Unable to run %s -V" % self.conf('ctl')) + text = self._nginx_version() version_regex = re.compile(r"nginx version: ([^/]+)/([0-9\.]*)", re.IGNORECASE) version_matches = version_regex.findall(text) @@ -966,6 +990,28 @@ class NginxConfigurator(common.Installer): return nginx_version + def _get_openssl_version(self): + """Return version of OpenSSL linked to Nginx. + + Version is returned as string. If no version can be found, empty string is returned. + + :returns: openssl_version + :rtype: str + + :raises .PluginError: + Unable to run Nginx version command + """ + text = self._nginx_version() + + matches = re.findall(r"running with OpenSSL ([^ ]+) ", text) + if not matches: + matches = re.findall(r"built with OpenSSL ([^ ]+) ", text) + if not matches: + logger.warning("NGINX configured with OpenSSL alternatives is not officially" + "supported by Certbot.") + return "" + return matches[0] + def more_info(self): """Human-readable string to help understand the module""" return ( @@ -1043,7 +1089,7 @@ class NginxConfigurator(common.Installer): ########################################################################### def get_chall_pref(self, unused_domain): # pylint: disable=no-self-use """Return list of challenge preferences.""" - return [challenges.HTTP01, challenges.TLSSNI01] + return [challenges.HTTP01] # Entry point in main.py for performing challenges def perform(self, achalls): diff --git a/certbot-nginx/certbot_nginx/constants.py b/certbot-nginx/certbot_nginx/_internal/constants.py similarity index 70% rename from certbot-nginx/certbot_nginx/constants.py rename to certbot-nginx/certbot_nginx/_internal/constants.py index cec7acaf5..fbf6ed424 100644 --- a/certbot-nginx/certbot_nginx/constants.py +++ b/certbot-nginx/certbot_nginx/_internal/constants.py @@ -22,11 +22,6 @@ MOD_SSL_CONF_DEST = "options-ssl-nginx.conf" UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt" """Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`.""" -SSL_OPTIONS_HASHES_NEW = [ - '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', -] -"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9""" - ALL_SSL_OPTIONS_HASHES = [ '0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c', '9a7b32c49001fed4cff8ad24353329472a50e86ade1ef9b2b9e43566a619612e', @@ -34,7 +29,19 @@ ALL_SSL_OPTIONS_HASHES = [ '7f95624dd95cf5afc708b9f967ee83a24b8025dc7c8d9df2b556bbc64256b3ff', '394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d', '4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16', -] + SSL_OPTIONS_HASHES_NEW + 'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', + '63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf', + '2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2', + '30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c', + '02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426', + '108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3', + 'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981', + 'ef11e3fb17213e74d3e1816cde0ec37b8b95b4167cf21e7b8ff1eaa9c6f918ee', + 'af85f6193808a44789a1d293e6cffa249cad9a21135940800958b8e3c72dbc69', + 'a2a612fd21b02abaa32d9d11ac63d987d6e3054dbfa356de5800eea0d7ce17f3', + '2d9648302e3588a172c318e46bff88ade46fc7a16d6afc85322776a04800d473', +] """SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC""" def os_constant(key): diff --git a/certbot-nginx/certbot_nginx/display_ops.py b/certbot-nginx/certbot_nginx/_internal/display_ops.py similarity index 99% rename from certbot-nginx/certbot_nginx/display_ops.py rename to certbot-nginx/certbot_nginx/_internal/display_ops.py index 9b973d8d3..bbb47f98a 100644 --- a/certbot-nginx/certbot_nginx/display_ops.py +++ b/certbot-nginx/certbot_nginx/_internal/display_ops.py @@ -4,10 +4,8 @@ import logging import zope.component from certbot import interfaces - import certbot.display.util as display_util - logger = logging.getLogger(__name__) diff --git a/certbot-nginx/certbot_nginx/http_01.py b/certbot-nginx/certbot_nginx/_internal/http_01.py similarity index 93% rename from certbot-nginx/certbot_nginx/http_01.py rename to certbot-nginx/certbot_nginx/_internal/http_01.py index 70147a433..97b111576 100644 --- a/certbot-nginx/certbot_nginx/http_01.py +++ b/certbot-nginx/certbot_nginx/_internal/http_01.py @@ -3,15 +3,12 @@ import logging from acme import challenges -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os from certbot.plugins import common - -from certbot_nginx import obj -from certbot_nginx import nginxparser - +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj logger = logging.getLogger(__name__) @@ -29,10 +26,10 @@ class NginxHttp01(common.ChallengePerformer): :param list indices: Meant to hold indices of challenges in a larger array. NginxHttp01 is capable of solving many challenges at once which causes an indexing issue within NginxConfigurator - who must return all responses in order. Imagine NginxConfigurator - maintaining state about where all of the http-01 Challenges, - TLS-SNI-01 Challenges belong in the response array. This is an - optional utility. + who must return all responses in order. Imagine + NginxConfigurator maintaining state about where all of the + challenges, possibly of different types, belong in the response + array. This is an optional utility. """ @@ -110,7 +107,7 @@ class NginxHttp01(common.ChallengePerformer): def _default_listen_addresses(self): """Finds addresses for a challenge block to listen on. - :returns: list of :class:`certbot_nginx.obj.Addr` to apply + :returns: list of :class:`certbot_nginx._internal.obj.Addr` to apply :rtype: list """ addresses = [] # type: List[obj.Addr] diff --git a/certbot-nginx/certbot_nginx/nginxparser.py b/certbot-nginx/certbot_nginx/_internal/nginxparser.py similarity index 95% rename from certbot-nginx/certbot_nginx/nginxparser.py rename to certbot-nginx/certbot_nginx/_internal/nginxparser.py index bfb75adcc..4fa1362a0 100644 --- a/certbot-nginx/certbot_nginx/nginxparser.py +++ b/certbot-nginx/certbot_nginx/_internal/nginxparser.py @@ -3,10 +3,18 @@ import copy import logging -from pyparsing import ( - Literal, White, Forward, Group, Optional, OneOrMore, QuotedString, Regex, ZeroOrMore, Combine) -from pyparsing import stringEnd +from pyparsing import Combine +from pyparsing import Forward +from pyparsing import Group +from pyparsing import Literal +from pyparsing import OneOrMore +from pyparsing import Optional +from pyparsing import QuotedString +from pyparsing import Regex from pyparsing import restOfLine +from pyparsing import stringEnd +from pyparsing import White +from pyparsing import ZeroOrMore import six logger = logging.getLogger(__name__) @@ -63,7 +71,6 @@ class RawNginxParser(object): return self.parse().asList() class RawNginxDumper(object): - # pylint: disable=too-few-public-methods """A class that dumps nginx configuration from the provided tree.""" def __init__(self, blocks): self.blocks = blocks @@ -179,12 +186,11 @@ class UnspacedList(list): """ if not isinstance(inbound, list): # str or None - return (inbound, inbound) + return inbound, inbound else: if not hasattr(inbound, "spaced"): inbound = UnspacedList(inbound) - return (inbound, inbound.spaced) - + return inbound, inbound.spaced def insert(self, i, x): item, spaced_item = self._coerce(x) diff --git a/certbot-nginx/certbot_nginx/obj.py b/certbot-nginx/certbot_nginx/_internal/obj.py similarity index 97% rename from certbot-nginx/certbot_nginx/obj.py rename to certbot-nginx/certbot_nginx/_internal/obj.py index b14be81ea..1a92c8b37 100644 --- a/certbot-nginx/certbot_nginx/obj.py +++ b/certbot-nginx/certbot_nginx/_internal/obj.py @@ -37,7 +37,6 @@ class Addr(common.Addr): CANONICAL_UNSPECIFIED_ADDRESS = UNSPECIFIED_IPV4_ADDRESSES[0] def __init__(self, host, port, ssl, default, ipv6, ipv6only): - # pylint: disable=too-many-arguments super(Addr, self).__init__((host, port)) self.ssl = ssl self.default = default @@ -122,7 +121,7 @@ class Addr(common.Addr): def __hash__(self): # pylint: disable=useless-super-delegation # Python 3 requires explicit overridden for __hash__ - # See certbot-apache/certbot_apache/obj.py for more information + # See certbot-apache/certbot_apache/_internal/obj.py for more information return super(Addr, self).__hash__() def super_eq(self, other): @@ -145,7 +144,7 @@ class Addr(common.Addr): return False -class VirtualHost(object): # pylint: disable=too-few-public-methods +class VirtualHost(object): """Represents an Nginx Virtualhost. :ivar str filep: file path of VH @@ -162,7 +161,6 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods """ def __init__(self, filep, addrs, ssl, enabled, names, raw, path): - # pylint: disable=too-many-arguments """Initialize a VH.""" self.filep = filep self.addrs = addrs diff --git a/certbot-nginx/certbot_nginx/parser.py b/certbot-nginx/certbot_nginx/_internal/parser.py similarity index 94% rename from certbot-nginx/certbot_nginx/parser.py rename to certbot-nginx/certbot_nginx/_internal/parser.py index d50606fc5..edb77a1c1 100644 --- a/certbot-nginx/certbot_nginx/parser.py +++ b/certbot-nginx/certbot_nginx/_internal/parser.py @@ -4,16 +4,19 @@ import functools import glob import logging import re -import pyparsing +import pyparsing import six +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os - -from certbot_nginx import obj -from certbot_nginx import nginxparser -from acme.magic_typing import Union, Dict, Set, Any, List, Tuple # pylint: disable=unused-import, no-name-in-module +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj logger = logging.getLogger(__name__) @@ -129,7 +132,7 @@ class NginxParser(object): Technically this is a misnomer because Nginx does not have virtual hosts, it has 'server blocks'. - :returns: List of :class:`~certbot_nginx.obj.VirtualHost` + :returns: List of :class:`~certbot_nginx._internal.obj.VirtualHost` objects found in configuration :rtype: list @@ -262,7 +265,7 @@ class NginxParser(object): def has_ssl_on_directive(self, vhost): """Does vhost have ssl on for all ports? - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost in question + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost in question :returns: True if 'ssl on' directive is included :rtype: bool @@ -272,7 +275,7 @@ class NginxParser(object): for directive in server: if not directive: continue - elif _is_ssl_on_directive(directive): + if _is_ssl_on_directive(directive): return True return False @@ -288,7 +291,7 @@ class NginxParser(object): ..todo :: Doesn't match server blocks whose server_name directives are split across multiple conf files. - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost whose information we use to match on :param list directives: The directives to add :param bool insert_at_top: True if the directives need to be inserted at the top @@ -310,7 +313,7 @@ class NginxParser(object): ..todo :: Doesn't match server blocks whose server_name directives are split across multiple conf files. - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost whose information we use to match on :param list directives: The directives to add :param bool insert_at_top: True if the directives need to be inserted at the top @@ -323,7 +326,7 @@ class NginxParser(object): def remove_server_directives(self, vhost, directive_name, match_func=None): """Remove all directives of type directive_name. - :param :class:`~certbot_nginx.obj.VirtualHost` vhost: The vhost + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost: The vhost to remove directives from :param string directive_name: The directive type to remove :param callable match_func: Function of the directive that returns true for directives @@ -359,7 +362,7 @@ class NginxParser(object): only_directives=None): """Duplicate the vhost in the configuration files. - :param :class:`~certbot_nginx.obj.VirtualHost` vhost_template: The vhost + :param :class:`~certbot_nginx._internal.obj.VirtualHost` vhost_template: The vhost whose information we copy :param bool remove_singleton_listen_params: If we should remove parameters from listen directives in the block that can only be used once per address @@ -367,7 +370,7 @@ class NginxParser(object): looks at first level of depth; does not expand includes. :returns: A vhost object for the newly created vhost - :rtype: :class:`~certbot_nginx.obj.VirtualHost` + :rtype: :class:`~certbot_nginx._internal.obj.VirtualHost` """ # TODO: https://github.com/certbot/certbot/issues/5185 # put it in the same file as the template, at the same level @@ -486,7 +489,7 @@ def get_best_match(target_name, names): def _exact_match(target_name, name): - return target_name == name or '.' + target_name == name + return name in (target_name, '.' + target_name) def _wildcard_match(target_name, name, start): @@ -504,7 +507,7 @@ def _wildcard_match(target_name, name, start): # The first part must be a wildcard or blank, e.g. '.eff.org' first = match_parts.pop(0) - if first != '*' and first != '': + if first not in ('*', ''): return False target_name = '.'.join(parts) @@ -582,7 +585,7 @@ def comment_directive(block, location): if isinstance(next_entry, list) and next_entry: if len(next_entry) >= 2 and next_entry[-2] == "#" and COMMENT in next_entry[-1]: return - elif isinstance(next_entry, nginxparser.UnspacedList): + if isinstance(next_entry, nginxparser.UnspacedList): next_entry = next_entry.spaced[0] else: next_entry = next_entry[0] @@ -658,13 +661,12 @@ def _add_directive(block, directive, insert_at_top): for included_directive in included_directives: included_dir_loc = _find_location(block, included_directive[0]) included_dir_name = included_directive[0] - if not _is_whitespace_or_comment(included_directive) \ - and not can_append(included_dir_loc, included_dir_name): + if (not _is_whitespace_or_comment(included_directive) + and not can_append(included_dir_loc, included_dir_name)): if block[included_dir_loc] != included_directive: raise errors.MisconfigurationError(err_fmt.format(included_directive, block[included_dir_loc])) - else: - _comment_out_directive(block, included_dir_loc, directive[1]) + _comment_out_directive(block, included_dir_loc, directive[1]) if can_append(location, directive_name): if insert_at_top: diff --git a/certbot-nginx/certbot_nginx/parser_obj.py b/certbot-nginx/certbot_nginx/_internal/parser_obj.py similarity index 99% rename from certbot-nginx/certbot_nginx/parser_obj.py rename to certbot-nginx/certbot_nginx/_internal/parser_obj.py index 71e8c6088..e03913887 100644 --- a/certbot-nginx/certbot_nginx/parser_obj.py +++ b/certbot-nginx/certbot_nginx/_internal/parser_obj.py @@ -3,12 +3,12 @@ raw lists of tokens from pyparsing. """ import abc import logging + import six +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - logger = logging.getLogger(__name__) COMMENT = " managed by Certbot" COMMENT_BLOCK = ["#", COMMENT] diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf new file mode 100644 index 000000000..731e38919 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-old.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf new file mode 100644 index 000000000..33771a189 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls12-only.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf new file mode 100644 index 000000000..91197d2c8 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx-tls13-session-tix-on.conf @@ -0,0 +1,13 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf new file mode 100644 index 000000000..98b1c4ab9 --- /dev/null +++ b/certbot-nginx/certbot_nginx/_internal/tls_configs/options-ssl-nginx.conf @@ -0,0 +1,14 @@ +# This file contains important security parameters. If you modify this file +# manually, Certbot will be unable to automatically provide future security +# updates. Instead, Certbot will print and log an error message with a path to +# the up-to-date file that you will need to refer to when manually updating +# this file. + +ssl_session_cache shared:le_nginx_SSL:10m; +ssl_session_timeout 1440m; +ssl_session_tickets off; + +ssl_protocols TLSv1.2 TLSv1.3; +ssl_prefer_server_ciphers off; + +ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf deleted file mode 100644 index 292d42984..000000000 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx-old.conf +++ /dev/null @@ -1,13 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:1m; -ssl_session_timeout 1440m; - -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_prefer_server_ciphers on; - -ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; diff --git a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf b/certbot-nginx/certbot_nginx/options-ssl-nginx.conf deleted file mode 100644 index 57a332d2f..000000000 --- a/certbot-nginx/certbot_nginx/options-ssl-nginx.conf +++ /dev/null @@ -1,14 +0,0 @@ -# This file contains important security parameters. If you modify this file -# manually, Certbot will be unable to automatically provide future security -# updates. Instead, Certbot will print and log an error message with a path to -# the up-to-date file that you will need to refer to when manually updating -# this file. - -ssl_session_cache shared:le_nginx_SSL:1m; -ssl_session_timeout 1440m; -ssl_session_tickets off; - -ssl_protocols TLSv1 TLSv1.1 TLSv1.2; -ssl_prefer_server_ciphers on; - -ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS"; diff --git a/certbot-nginx/certbot_nginx/tests/__init__.py b/certbot-nginx/certbot_nginx/tests/__init__.py deleted file mode 100644 index 32ca193d9..000000000 --- a/certbot-nginx/certbot_nginx/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Nginx Tests""" diff --git a/certbot-nginx/certbot_nginx/tests/util.py b/certbot-nginx/certbot_nginx/tests/util.py deleted file mode 100644 index 5476333e0..000000000 --- a/certbot-nginx/certbot_nginx/tests/util.py +++ /dev/null @@ -1,135 +0,0 @@ -"""Common utilities for certbot_nginx.""" -import copy -import shutil -import tempfile -import unittest -import warnings - -import josepy as jose -import mock -import pkg_resources -import zope.component - -from certbot import configuration -from certbot.compat import os -from certbot.plugins import common -from certbot.tests import util as test_util - -from certbot_nginx import configurator -from certbot_nginx import nginxparser - - -class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods - - def setUp(self): - super(NginxTest, self).setUp() - - self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( - "etc_nginx", "certbot_nginx.tests") - self.logs_dir = tempfile.mkdtemp('logs') - - self.config_path = os.path.join(self.temp_dir, "etc_nginx") - - self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( - "rsa512_key.pem")) - - def tearDown(self): - # On Windows we have various files which are not correctly closed at the time of tearDown. - # For know, we log them until a proper file close handling is written. - # Useful for development only, so no warning when we are on a CI process. - def onerror_handler(_, path, excinfo): - """On error handler""" - if not os.environ.get('APPVEYOR'): # pragma: no cover - message = ('Following error occurred when deleting path {0}' - 'during tearDown process: {1}'.format(path, str(excinfo))) - warnings.warn(message) - - shutil.rmtree(self.temp_dir, onerror=onerror_handler) - shutil.rmtree(self.config_dir, onerror=onerror_handler) - shutil.rmtree(self.work_dir, onerror=onerror_handler) - shutil.rmtree(self.logs_dir, onerror=onerror_handler) - - -def get_data_filename(filename): - """Gets the filename of a test data file.""" - return pkg_resources.resource_filename( - "certbot_nginx.tests", os.path.join( - "testdata", "etc_nginx", filename)) - - -def get_nginx_configurator( - config_path, config_dir, work_dir, logs_dir, version=(1, 6, 2)): - """Create an Nginx Configurator with the specified options.""" - - backups = os.path.join(work_dir, "backups") - - with mock.patch("certbot_nginx.configurator.NginxConfigurator." - "config_test"): - with mock.patch("certbot_nginx.configurator.util." - "exe_exists") as mock_exe_exists: - mock_exe_exists.return_value = True - config = configurator.NginxConfigurator( - config=mock.MagicMock( - nginx_server_root=config_path, - le_vhost_ext="-le-ssl.conf", - config_dir=config_dir, - work_dir=work_dir, - logs_dir=logs_dir, - backup_dir=backups, - temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"), - in_progress_dir=os.path.join(backups, "IN_PROGRESS"), - server="https://acme-server.org:443/new", - http01_port=80, - https_port=5001, - ), - name="nginx", - version=version) - config.prepare() - - # Provide general config utility. - nsconfig = configuration.NamespaceConfig(config.config) - zope.component.provideUtility(nsconfig) - - return config - - -def filter_comments(tree): - """Filter comment nodes from parsed configurations.""" - - def traverse(tree): - """Generator dropping comment nodes""" - for entry in tree: - # key, values = entry - spaceless = [e for e in entry if not nginxparser.spacey(e)] - if spaceless: - key = spaceless[0] - values = spaceless[1] if len(spaceless) > 1 else None - else: - key = values = "" - if isinstance(key, list): - new = copy.deepcopy(entry) - new[1] = filter_comments(values) - yield new - else: - if key != '#' and spaceless: - yield spaceless - - return list(traverse(tree)) - - -def contains_at_depth(haystack, needle, n): - """Is the needle in haystack at depth n? - - Return true if the needle is present in one of the sub-iterables in haystack - at depth n. Haystack must be an iterable. - """ - # Specifically use hasattr rather than isinstance(..., collections.Iterable) - # because we want to include lists but reject strings. - if not hasattr(haystack, '__iter__') or hasattr(haystack, 'strip'): - return False - if n == 0: - return needle in haystack - for item in haystack: - if contains_at_depth(item, needle, n - 1): - return True - return False diff --git a/certbot-nginx/docs/.gitignore b/certbot-nginx/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/certbot-nginx/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/certbot-nginx/docs/Makefile b/certbot-nginx/docs/Makefile deleted file mode 100644 index 0bd88a347..000000000 --- a/certbot-nginx/docs/Makefile +++ /dev/null @@ -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 ' where 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/certbot-nginx.qhcp" - @echo "To view the help file:" - @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/certbot-nginx.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/certbot-nginx" - @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/certbot-nginx" - @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." diff --git a/certbot-nginx/docs/_static/.gitignore b/certbot-nginx/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-nginx/docs/_templates/.gitignore b/certbot-nginx/docs/_templates/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/certbot-nginx/docs/api.rst b/certbot-nginx/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/certbot-nginx/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/certbot-nginx/docs/api/nginxparser.rst b/certbot-nginx/docs/api/nginxparser.rst deleted file mode 100644 index 6a3be5247..000000000 --- a/certbot-nginx/docs/api/nginxparser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.nginxparser` ------------------------------------- - -.. automodule:: certbot_nginx.nginxparser - :members: diff --git a/certbot-nginx/docs/api/obj.rst b/certbot-nginx/docs/api/obj.rst deleted file mode 100644 index a2c94037b..000000000 --- a/certbot-nginx/docs/api/obj.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.obj` ----------------------------- - -.. automodule:: certbot_nginx.obj - :members: diff --git a/certbot-nginx/docs/api/parser.rst b/certbot-nginx/docs/api/parser.rst deleted file mode 100644 index 0149f99cb..000000000 --- a/certbot-nginx/docs/api/parser.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.parser` -------------------------------- - -.. automodule:: certbot_nginx.parser - :members: diff --git a/certbot-nginx/docs/api/tls_sni_01.rst b/certbot-nginx/docs/api/tls_sni_01.rst deleted file mode 100644 index 5074f63d9..000000000 --- a/certbot-nginx/docs/api/tls_sni_01.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot_nginx.tls_sni_01` ------------------------------------ - -.. automodule:: certbot_nginx.tls_sni_01 - :members: diff --git a/certbot-nginx/docs/conf.py b/certbot-nginx/docs/conf.py deleted file mode 100644 index d606b6292..000000000 --- a/certbot-nginx/docs/conf.py +++ /dev/null @@ -1,311 +0,0 @@ -# -*- coding: utf-8 -*- -# -# certbot-nginx documentation build configuration file, created by -# sphinx-quickstart on Sun Oct 18 13:39:39 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 sys -from certbot.compat import os -import shlex - - -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', 'private-members'] - -# 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'certbot-nginx' -copyright = u'2014-2015, Let\'s Encrypt Project' -author = u'Let\'s Encrypt 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 = True - - -# -- 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 -# " v 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 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 = 'certbot-nginxdoc' - -# -- 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, 'certbot-nginx.tex', u'certbot-nginx Documentation', - u'Let\'s Encrypt 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, 'certbot-nginx', u'certbot-nginx 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, 'certbot-nginx', u'certbot-nginx Documentation', - author, 'certbot-nginx', '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), -} diff --git a/certbot-nginx/docs/index.rst b/certbot-nginx/docs/index.rst deleted file mode 100644 index 488a7ab9c..000000000 --- a/certbot-nginx/docs/index.rst +++ /dev/null @@ -1,31 +0,0 @@ -.. certbot-nginx documentation master file, created by - sphinx-quickstart on Sun Oct 18 13:39:39 2015. - You can adapt this file completely to your liking, but it should at least - contain the root `toctree` directive. - -Welcome to certbot-nginx's documentation! -============================================= - -Contents: - -.. toctree:: - :maxdepth: 2 - - -.. toctree:: - :maxdepth: 1 - - api - - -.. automodule:: certbot_nginx - :members: - - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` - diff --git a/certbot-nginx/docs/make.bat b/certbot-nginx/docs/make.bat deleted file mode 100644 index b12255d4c..000000000 --- a/certbot-nginx/docs/make.bat +++ /dev/null @@ -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 ^` where ^ 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\certbot-nginx.qhcp - echo.To view the help file: - echo.^> assistant -collectionFile %BUILDDIR%\qthelp\certbot-nginx.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 diff --git a/certbot-nginx/local-oldest-requirements.txt b/certbot-nginx/local-oldest-requirements.txt index 0bc9ee027..37532aabf 100644 --- a/certbot-nginx/local-oldest-requirements.txt +++ b/certbot-nginx/local-oldest-requirements.txt @@ -1,3 +1,3 @@ # Remember to update setup.py to match the package versions below. -acme[dev]==0.29.0 -certbot[dev]==0.34.0 +acme[dev]==1.0.0 +-e certbot[dev] diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 09a1ab8d5..96bf32d3e 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -1,16 +1,16 @@ -from setuptools import setup -from setuptools import find_packages -from setuptools.command.test import test as TestCommand import sys +from setuptools import find_packages +from setuptools import setup +from setuptools.command.test import test as TestCommand -version = '0.36.0.dev0' +version = '1.1.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.29.0', - 'certbot>=0.34.0', + 'acme>=1.0.0', + 'certbot>=1.0.0.dev0', 'mock', 'PyOpenSSL', 'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary? @@ -18,11 +18,6 @@ install_requires = [ 'zope.interface', ] -docs_extras = [ - 'Sphinx>=1.0', # autodoc_member_order = 'bysource', autodoc_default_flags - 'sphinx_rtd_theme', -] - class PyTest(TestCommand): user_options = [] @@ -62,6 +57,7 @@ setup( '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', @@ -73,12 +69,9 @@ setup( packages=find_packages(), include_package_data=True, install_requires=install_requires, - extras_require={ - 'docs': docs_extras, - }, entry_points={ 'certbot.plugins': [ - 'nginx = certbot_nginx.configurator:NginxConfigurator', + 'nginx = certbot_nginx._internal.configurator:NginxConfigurator', ], }, test_suite='certbot_nginx', diff --git a/certbot-nginx/certbot_nginx/tests/configurator_test.py b/certbot-nginx/tests/configurator_test.py similarity index 85% rename from certbot-nginx/certbot_nginx/tests/configurator_test.py rename to certbot-nginx/tests/configurator_test.py index 5e9d61a44..ef5593395 100644 --- a/certbot-nginx/certbot_nginx/tests/configurator_test.py +++ b/certbot-nginx/tests/configurator_test.py @@ -1,23 +1,21 @@ -# pylint: disable=too-many-public-methods -"""Test for certbot_nginx.configurator.""" +"""Test for certbot_nginx._internal.configurator.""" import unittest -import OpenSSL import mock +import OpenSSL + from acme import challenges from acme import messages - from certbot import achallenges from certbot import crypto_util from certbot import errors from certbot.compat import os from certbot.tests import util as certbot_test_util - -from certbot_nginx import obj -from certbot_nginx import parser -from certbot_nginx.configurator import _redirect_block_for_domain -from certbot_nginx.nginxparser import UnspacedList -from certbot_nginx.tests import util +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser +from certbot_nginx._internal.configurator import _redirect_block_for_domain +from certbot_nginx._internal.nginxparser import UnspacedList +import test_util as util class NginxConfiguratorTest(util.NginxTest): @@ -27,10 +25,10 @@ class NginxConfiguratorTest(util.NginxTest): def setUp(self): super(NginxConfiguratorTest, self).setUp() - self.config = util.get_nginx_configurator( + self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) - @mock.patch("certbot_nginx.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") def test_prepare_no_install(self, mock_exe_exists): mock_exe_exists.return_value = False self.assertRaises( @@ -40,8 +38,8 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual((1, 6, 2), self.config.version) self.assertEqual(11, len(self.config.parser.parsed)) - @mock.patch("certbot_nginx.configurator.util.exe_exists") - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_prepare_initializes_version(self, mock_popen, mock_exe_exists): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.6.2", @@ -67,7 +65,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.config_test = mock.Mock() certbot_test_util.lock_and_call(self._test_prepare_locked, server_root) - @mock.patch("certbot_nginx.configurator.util.exe_exists") + @mock.patch("certbot_nginx._internal.configurator.util.exe_exists") def _test_prepare_locked(self, unused_exe_exists): try: self.config.prepare() @@ -78,7 +76,7 @@ class NginxConfiguratorTest(util.NginxTest): else: # pragma: no cover self.fail("Exception wasn't raised!") - @mock.patch("certbot_nginx.configurator.socket.gethostbyaddr") + @mock.patch("certbot_nginx._internal.configurator.socket.gethostbyaddr") def test_get_all_names(self, mock_gethostbyaddr): mock_gethostbyaddr.return_value = ('155.225.50.69.nephoscale.net', [], []) names = self.config.get_all_names() @@ -97,7 +95,7 @@ class NginxConfiguratorTest(util.NginxTest): errors.PluginError, self.config.enhance, 'myhost', 'unknown_enhancement') def test_get_chall_pref(self): - self.assertEqual([challenges.HTTP01, challenges.TLSSNI01], + self.assertEqual([challenges.HTTP01], self.config.get_chall_pref('myhost')) def test_save(self): @@ -218,7 +216,7 @@ class NginxConfiguratorTest(util.NginxTest): "example/chain.pem", None) - @mock.patch('certbot_nginx.parser.NginxParser.update_or_add_server_directives') + @mock.patch('certbot_nginx._internal.parser.NginxParser.update_or_add_server_directives') def test_deploy_cert_raise_on_add_error(self, mock_update_or_add_server_directives): mock_update_or_add_server_directives.side_effect = errors.MisconfigurationError() self.assertRaises( @@ -316,9 +314,9 @@ class NginxConfiguratorTest(util.NginxTest): ]], parsed_migration_conf[0]) - @mock.patch("certbot_nginx.configurator.http_01.NginxHttp01.perform") - @mock.patch("certbot_nginx.configurator.NginxConfigurator.restart") - @mock.patch("certbot_nginx.configurator.NginxConfigurator.revert_challenge_config") + @mock.patch("certbot_nginx._internal.configurator.http_01.NginxHttp01.perform") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.restart") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.revert_challenge_config") def test_perform_and_cleanup(self, mock_revert, mock_restart, mock_http_perform): # Only tests functionality specific to configurator.perform # Note: As more challenges are offered this will have to be expanded @@ -344,7 +342,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual(mock_revert.call_count, 1) self.assertEqual(mock_restart.call_count, 2) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_get_version(self, mock_popen): mock_popen().communicate.return_value = ( "", "\n".join(["nginx version: nginx/1.4.2", @@ -394,21 +392,83 @@ class NginxConfiguratorTest(util.NginxTest): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.PluginError, self.config.get_version) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") + def test_get_openssl_version(self, mock_popen): + # pylint: disable=protected-access + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2g") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2-beta1 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2-beta1") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2 1 Mar 2016 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with OpenSSL 1.0.2g 1 Mar 2016 (running with OpenSSL 1.0.2a 1 Mar 2016) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "1.0.2a") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + built with LibreSSL 2.2.2 + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + + mock_popen().communicate.return_value = ( + "", """ + nginx version: nginx/1.15.5 + built by gcc 5.4.0 20160609 (Ubuntu 5.4.0-6ubuntu1~16.04.9) + TLS SNI support enabled + configure arguments: + """) + self.assertEqual(self.config._get_openssl_version(), "") + + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_nginx_restart(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 0 self.config.restart() - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_nginx_restart_fail(self, mock_popen): mocked = mock_popen() mocked.communicate.return_value = ('', '') mocked.returncode = 1 self.assertRaises(errors.MisconfigurationError, self.config.restart) - @mock.patch("certbot_nginx.configurator.subprocess.Popen") + @mock.patch("certbot_nginx._internal.configurator.subprocess.Popen") def test_no_nginx_start(self, mock_popen): mock_popen.side_effect = OSError("Can't find program") self.assertRaises(errors.MisconfigurationError, self.config.restart) @@ -427,11 +487,6 @@ class NginxConfiguratorTest(util.NginxTest): mock_recovery_routine.side_effect = errors.ReverterError("foo") self.assertRaises(errors.PluginError, self.config.recovery_routine) - @mock.patch("certbot.reverter.Reverter.view_config_changes") - def test_view_config_changes_throws_error_from_reverter(self, mock_view_config_changes): - mock_view_config_changes.side_effect = errors.ReverterError("foo") - self.assertRaises(errors.PluginError, self.config.view_config_changes) - @mock.patch("certbot.reverter.Reverter.rollback_checkpoints") def test_rollback_checkpoints_throws_error_from_reverter(self, mock_rollback_checkpoints): mock_rollback_checkpoints.side_effect = errors.ReverterError("foo") @@ -567,20 +622,20 @@ class NginxConfiguratorTest(util.NginxTest): "ensure-http-header", "Strict-Transport-Security") - @mock.patch('certbot_nginx.obj.VirtualHost.contains_list') + @mock.patch('certbot_nginx._internal.obj.VirtualHost.contains_list') def test_certbot_redirect_exists(self, mock_contains_list): # Test that we add no redirect statement if there is already a # redirect in the block that is managed by certbot # Has a certbot redirect mock_contains_list.return_value = True - with mock.patch("certbot_nginx.configurator.logger") as mock_logger: + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("www.example.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], "Traffic on port %s already redirecting to ssl in %s") def test_redirect_dont_enhance(self): # Test that we don't accidentally add redirect to ssl-only block - with mock.patch("certbot_nginx.configurator.logger") as mock_logger: + with mock.patch("certbot_nginx._internal.configurator.logger") as mock_logger: self.config.enhance("geese.com", "redirect") self.assertEqual(mock_logger.info.call_args[0][0], 'No matching insecure server blocks listening on port %s found.') @@ -771,7 +826,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertTrue(util.contains_at_depth(generated_conf, expected, 2)) @mock.patch('certbot.reverter.logger') - @mock.patch('certbot_nginx.parser.NginxParser.load') + @mock.patch('certbot_nginx._internal.parser.NginxParser.load') def test_parser_reload_after_config_changes(self, mock_parser_load, unused_mock_logger): self.config.recovery_routine() self.config.revert_challenge_config() @@ -780,7 +835,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -796,7 +851,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard_redirect(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -817,7 +872,7 @@ class NginxConfiguratorTest(util.NginxTest): if 'geese.com' in x.names][0] mock_choose_vhosts.return_value = [vhost] self.config._choose_vhosts_wildcard = mock_choose_vhosts - mock_d = "certbot_nginx.configurator.NginxConfigurator._deploy_cert" + mock_d = "certbot_nginx._internal.configurator.NginxConfigurator._deploy_cert" with mock.patch(mock_d) as mock_dep: self.config.deploy_cert("*.com", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") @@ -825,7 +880,7 @@ class NginxConfiguratorTest(util.NginxTest): self.assertEqual(len(mock_dep.call_args_list), 1) self.assertEqual(vhost, mock_dep.call_args_list[0][0][0]) - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_deploy_cert_wildcard_no_vhosts(self, mock_dialog): # pylint: disable=protected-access mock_dialog.return_value = [] @@ -834,7 +889,7 @@ class NginxConfiguratorTest(util.NginxTest): "*.wild.cat", "/tmp/path", "/tmp/path", "/tmp/path", "/tmp/path") - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_ocsp_after_install(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() @@ -843,7 +898,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertFalse(mock_dialog.called) - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_redirect_or_ocsp_no_install(self, mock_dialog): vhost = [x for x in self.config.parser.get_vhosts() if 'summer.com' in x.names][0] @@ -851,7 +906,7 @@ class NginxConfiguratorTest(util.NginxTest): self.config.enhance("*.com", "staple-ocsp", "example/chain.pem") self.assertTrue(mock_dialog.called) - @mock.patch("certbot_nginx.display_ops.select_vhost_multiple") + @mock.patch("certbot_nginx._internal.display_ops.select_vhost_multiple") def test_enhance_wildcard_double_redirect(self, mock_dialog): # pylint: disable=protected-access vhost = [x for x in self.config.parser.get_vhosts() @@ -862,7 +917,7 @@ class NginxConfiguratorTest(util.NginxTest): def test_choose_vhosts_wildcard_no_ssl_filter_port(self): # pylint: disable=protected-access - mock_path = "certbot_nginx.display_ops.select_vhost_multiple" + mock_path = "certbot_nginx._internal.display_ops.select_vhost_multiple" with mock.patch(mock_path) as mock_select_vhs: mock_select_vhs.return_value = [] self.config._choose_vhosts_wildcard("*.com", @@ -878,7 +933,7 @@ class InstallSslOptionsConfTest(util.NginxTest): def setUp(self): super(InstallSslOptionsConfTest, self).setUp() - self.config = util.get_nginx_configurator( + self.config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) def _call(self): @@ -918,20 +973,19 @@ class InstallSslOptionsConfTest(util.NginxTest): return _hash def test_prev_file_updates_to_current(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() def test_prev_file_updates_to_current_old_nginx(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES, SSL_OPTIONS_HASHES_NEW + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES self.config.version = (1, 5, 8) with mock.patch('certbot.crypto_util.sha256sum', new=self._mock_hash_except_ssl_conf_src(ALL_SSL_OPTIONS_HASHES[0])): self._call() self._assert_current_file() - self.assertTrue(self._current_ssl_options_hash() not in SSL_OPTIONS_HASHES_NEW) def test_manually_modified_current_file_does_not_update(self): with open(self.config.mod_ssl_conf, "a") as mod_ssl_conf: @@ -963,27 +1017,63 @@ class InstallSslOptionsConfTest(util.NginxTest): self.assertFalse(mock_logger.warning.called) def test_current_file_hash_in_all_hashes(self): - from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES self.assertTrue(self._current_ssl_options_hash() in ALL_SSL_OPTIONS_HASHES, "Constants.ALL_SSL_OPTIONS_HASHES must be appended" " with the sha256 hash of self.config.mod_ssl_conf when it is updated.") - def test_old_nginx_version_uses_old_config(self): + def test_ssl_config_files_hash_in_all_hashes(self): + """ + It is really critical that all TLS Nginx config files have their SHA256 hash registered in + constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config + file has been manually edited by the user, and will refuse to update it. + This test ensures that all necessary hashes are present. + """ + from certbot_nginx._internal.constants import ALL_SSL_OPTIONS_HASHES + import pkg_resources + all_files = [ + pkg_resources.resource_filename("certbot_nginx", + os.path.join("_internal", "tls_configs", x)) + for x in ("options-ssl-nginx.conf", + "options-ssl-nginx-old.conf", + "options-ssl-nginx-tls12-only.conf") + ] + self.assertTrue(all_files) + for one_file in all_files: + file_hash = crypto_util.sha256sum(one_file) + self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES, + "Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 " + "hash of {0} when it is updated.".format(one_file)) + + def test_nginx_version_uses_correct_config(self): self.config.version = (1, 5, 8) + self.config.openssl_version = "1.0.2g" # shouldn't matter self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx-old.conf") self._call() self._assert_current_file() self.config.version = (1, 5, 9) + self.config.openssl_version = "1.0.2l" + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls12-only.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), "options-ssl-nginx.conf") + self._call() + self._assert_current_file() + self.config.version = (1, 13, 0) + self.config.openssl_version = "1.0.2k" + self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src), + "options-ssl-nginx-tls13-session-tix-on.conf") class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): - """Tests for certbot_nginx.configurator._determine_default_server_root.""" + """Tests for certbot_nginx._internal.configurator._determine_default_server_root.""" def _call(self): - from certbot_nginx.configurator import _determine_default_server_root + from certbot_nginx._internal.configurator import _determine_default_server_root return _determine_default_server_root() @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) @@ -1001,7 +1091,7 @@ class DetermineDefaultServerRootTest(certbot_test_util.ConfigTestCase): self.assertIn("/usr/local/etc/nginx", server_root) self.assertIn("/etc/nginx", server_root) else: - self.assertTrue(server_root == "/etc/nginx" or server_root == "/usr/local/etc/nginx") + self.assertTrue(server_root in ("/etc/nginx", "/usr/local/etc/nginx")) if __name__ == "__main__": diff --git a/certbot-nginx/certbot_nginx/tests/display_ops_test.py b/certbot-nginx/tests/display_ops_test.py similarity index 83% rename from certbot-nginx/certbot_nginx/tests/display_ops_test.py rename to certbot-nginx/tests/display_ops_test.py index e3c6fb66b..377255441 100644 --- a/certbot-nginx/certbot_nginx/tests/display_ops_test.py +++ b/certbot-nginx/tests/display_ops_test.py @@ -1,18 +1,15 @@ -"""Test certbot_apache.display_ops.""" +"""Test certbot_nginx._internal.display_ops.""" import unittest from certbot.display import util as display_util - from certbot.tests import util as certbot_util - -from certbot_nginx import parser - -from certbot_nginx.display_ops import select_vhost_multiple -from certbot_nginx.tests import util +from certbot_nginx._internal import parser +from certbot_nginx._internal.display_ops import select_vhost_multiple +import test_util as util class SelectVhostMultiTest(util.NginxTest): - """Tests for certbot_nginx.display_ops.select_vhost_multiple.""" + """Tests for certbot_nginx._internal.display_ops.select_vhost_multiple.""" def setUp(self): super(SelectVhostMultiTest, self).setUp() diff --git a/certbot-nginx/certbot_nginx/tests/http_01_test.py b/certbot-nginx/tests/http_01_test.py similarity index 83% rename from certbot-nginx/certbot_nginx/tests/http_01_test.py rename to certbot-nginx/tests/http_01_test.py index 41c4b95fc..6418a8841 100644 --- a/certbot-nginx/certbot_nginx/tests/http_01_test.py +++ b/certbot-nginx/tests/http_01_test.py @@ -1,24 +1,24 @@ -"""Tests for certbot_nginx.http_01""" +"""Tests for certbot_nginx._internal.http_01""" import unittest +import josepy as jose import mock import six from acme import challenges - from certbot import achallenges - -from certbot.plugins import common_test from certbot.tests import acme_util +from certbot.tests import util as test_util +from certbot_nginx._internal.obj import Addr +import test_util as util -from certbot_nginx.obj import Addr -from certbot_nginx.tests import util +AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class HttpPerformTest(util.NginxTest): """Test the NginxHttp01 challenge.""" - account_key = common_test.AUTH_KEY + account_key = AUTH_KEY achalls = [ achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.chall_to_challb( @@ -47,17 +47,17 @@ class HttpPerformTest(util.NginxTest): def setUp(self): super(HttpPerformTest, self).setUp() - config = util.get_nginx_configurator( + config = self.get_nginx_configurator( self.config_path, self.config_dir, self.work_dir, self.logs_dir) - from certbot_nginx import http_01 + from certbot_nginx._internal import http_01 self.http01 = http_01.NginxHttp01(config) def test_perform0(self): responses = self.http01.perform() self.assertEqual([], responses) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.save") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.save") def test_perform1(self, mock_save): self.http01.add_chall(self.achalls[0]) response = self.achalls[0].response(self.account_key) @@ -73,11 +73,11 @@ class HttpPerformTest(util.NginxTest): self.http01.add_chall(achall) acme_responses.append(achall.response(self.account_key)) - sni_responses = self.http01.perform() + http_responses = self.http01.perform() - self.assertEqual(len(sni_responses), 4) + self.assertEqual(len(http_responses), 4) for i in six.moves.range(4): - self.assertEqual(sni_responses[i], acme_responses[i]) + self.assertEqual(http_responses[i], acme_responses[i]) def test_mod_config(self): self.http01.add_chall(self.achalls[0]) @@ -103,7 +103,7 @@ class HttpPerformTest(util.NginxTest): # self.assertEqual(vhost.addrs, set(v_addr2_print)) # self.assertEqual(vhost.names, set([response.z_domain.decode('ascii')])) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_no_memoization(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, True) @@ -113,7 +113,7 @@ class HttpPerformTest(util.NginxTest): self.http01._default_listen_addresses() self.assertEqual(ipv6_info.call_count, 2) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_t(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, True) @@ -122,7 +122,7 @@ class HttpPerformTest(util.NginxTest): http_ipv6_addr = Addr.fromstring("[::]:80") self.assertEqual(addrs, [http_addr, http_ipv6_addr]) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_t_f(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (True, False) @@ -131,7 +131,7 @@ class HttpPerformTest(util.NginxTest): http_ipv6_addr = Addr.fromstring("[::]:80 ipv6only=on") self.assertEqual(addrs, [http_addr, http_ipv6_addr]) - @mock.patch("certbot_nginx.configurator.NginxConfigurator.ipv6_info") + @mock.patch("certbot_nginx._internal.configurator.NginxConfigurator.ipv6_info") def test_default_listen_addresses_f_f(self, ipv6_info): # pylint: disable=protected-access ipv6_info.return_value = (False, False) diff --git a/certbot-nginx/certbot_nginx/tests/nginxparser_test.py b/certbot-nginx/tests/nginxparser_test.py similarity index 97% rename from certbot-nginx/certbot_nginx/tests/nginxparser_test.py rename to certbot-nginx/tests/nginxparser_test.py index 7fc4576c3..a5212078f 100644 --- a/certbot-nginx/certbot_nginx/tests/nginxparser_test.py +++ b/certbot-nginx/tests/nginxparser_test.py @@ -1,4 +1,4 @@ -"""Test for certbot_nginx.nginxparser.""" +"""Test for certbot_nginx._internal.nginxparser.""" import copy import operator import tempfile @@ -6,10 +6,13 @@ import unittest from pyparsing import ParseException -from certbot_nginx.nginxparser import ( - RawNginxParser, loads, load, dumps, dump, UnspacedList) -from certbot_nginx.tests import util - +from certbot_nginx._internal.nginxparser import dump +from certbot_nginx._internal.nginxparser import dumps +from certbot_nginx._internal.nginxparser import load +from certbot_nginx._internal.nginxparser import loads +from certbot_nginx._internal.nginxparser import RawNginxParser +from certbot_nginx._internal.nginxparser import UnspacedList +import test_util as util FIRST = operator.itemgetter(0) diff --git a/certbot-nginx/certbot_nginx/tests/obj_test.py b/certbot-nginx/tests/obj_test.py similarity index 92% rename from certbot-nginx/certbot_nginx/tests/obj_test.py rename to certbot-nginx/tests/obj_test.py index 9e5853c4a..db808229f 100644 --- a/certbot-nginx/certbot_nginx/tests/obj_test.py +++ b/certbot-nginx/tests/obj_test.py @@ -1,12 +1,12 @@ -"""Test the helper objects in certbot_nginx.obj.""" -import unittest +"""Test the helper objects in certbot_nginx._internal.obj.""" import itertools +import unittest class AddrTest(unittest.TestCase): """Test the Addr class.""" def setUp(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr self.addr1 = Addr.fromstring("192.168.1.1") self.addr2 = Addr.fromstring("192.168.1.1:* ssl") self.addr3 = Addr.fromstring("192.168.1.1:80") @@ -71,14 +71,14 @@ class AddrTest(unittest.TestCase): self.assertEqual(self.addr6.to_string(include_default=False), "80") def test_eq(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr new_addr1 = Addr.fromstring("192.168.1.1 spdy") self.assertEqual(self.addr1, new_addr1) self.assertNotEqual(self.addr1, self.addr2) self.assertFalse(self.addr1 == 3333) def test_equivalent_any_addresses(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr any_addresses = ("0.0.0.0:80 default_server ssl", "80 default_server ssl", "*:80 default_server ssl", @@ -97,7 +97,7 @@ class AddrTest(unittest.TestCase): Addr.fromstring(any_address)) def test_set_inclusion(self): - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import Addr set_a = set([self.addr1, self.addr2]) addr1b = Addr.fromstring("192.168.1.1") addr2b = Addr.fromstring("192.168.1.1:* ssl") @@ -109,8 +109,8 @@ class AddrTest(unittest.TestCase): class VirtualHostTest(unittest.TestCase): """Test the VirtualHost class.""" def setUp(self): - from certbot_nginx.obj import VirtualHost - from certbot_nginx.obj import Addr + from certbot_nginx._internal.obj import VirtualHost + from certbot_nginx._internal.obj import Addr raw1 = [ ['listen', '69.50.225.155:9000'], [['if', '($scheme', '!=', '"https") '], @@ -159,8 +159,8 @@ class VirtualHostTest(unittest.TestCase): set(['localhost']), raw_has_hsts, []) def test_eq(self): - from certbot_nginx.obj import Addr - from certbot_nginx.obj import VirtualHost + from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.obj import VirtualHost vhost1b = VirtualHost( "filep", set([Addr.fromstring("localhost blah")]), False, False, @@ -183,9 +183,9 @@ class VirtualHostTest(unittest.TestCase): self.assertFalse(self.vhost1.has_header('Bogus-Header')) def test_contains_list(self): - from certbot_nginx.obj import VirtualHost - from certbot_nginx.obj import Addr - from certbot_nginx.configurator import _test_block_from_block + from certbot_nginx._internal.obj import VirtualHost + from certbot_nginx._internal.obj import Addr + from certbot_nginx._internal.configurator import _test_block_from_block test_block = [ ['\n ', 'return', ' ', '301', ' ', 'https://$host$request_uri'], ['\n'] diff --git a/certbot-nginx/certbot_nginx/tests/parser_obj_test.py b/certbot-nginx/tests/parser_obj_test.py similarity index 92% rename from certbot-nginx/certbot_nginx/tests/parser_obj_test.py rename to certbot-nginx/tests/parser_obj_test.py index 2217be54f..132f83771 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_obj_test.py +++ b/certbot-nginx/tests/parser_obj_test.py @@ -1,20 +1,22 @@ """ Tests for functions and classes in parser_obj.py """ import unittest + import mock -from certbot_nginx.parser_obj import parse_raw -from certbot_nginx.parser_obj import COMMENT_BLOCK +from certbot_nginx._internal.parser_obj import COMMENT_BLOCK +from certbot_nginx._internal.parser_obj import parse_raw + class CommentHelpersTest(unittest.TestCase): def test_is_comment(self): - from certbot_nginx.parser_obj import _is_comment + from certbot_nginx._internal.parser_obj import _is_comment self.assertTrue(_is_comment(parse_raw(['#']))) self.assertTrue(_is_comment(parse_raw(['#', ' literally anything else']))) self.assertFalse(_is_comment(parse_raw(['not', 'even', 'a', 'comment']))) def test_is_certbot_comment(self): - from certbot_nginx.parser_obj import _is_certbot_comment + from certbot_nginx._internal.parser_obj import _is_certbot_comment self.assertTrue(_is_certbot_comment( parse_raw(COMMENT_BLOCK))) self.assertFalse(_is_certbot_comment( @@ -25,7 +27,7 @@ class CommentHelpersTest(unittest.TestCase): parse_raw(['not', 'even', 'a', 'comment']))) def test_certbot_comment(self): - from certbot_nginx.parser_obj import _certbot_comment, _is_certbot_comment + from certbot_nginx._internal.parser_obj import _certbot_comment, _is_certbot_comment comment = _certbot_comment(None) self.assertTrue(_is_certbot_comment(comment)) self.assertEqual(comment.dump(), COMMENT_BLOCK) @@ -35,7 +37,7 @@ class CommentHelpersTest(unittest.TestCase): class ParsingHooksTest(unittest.TestCase): def test_is_sentence(self): - from certbot_nginx.parser_obj import Sentence + from certbot_nginx._internal.parser_obj import Sentence self.assertFalse(Sentence.should_parse([])) self.assertTrue(Sentence.should_parse([''])) self.assertTrue(Sentence.should_parse(['word'])) @@ -44,7 +46,7 @@ class ParsingHooksTest(unittest.TestCase): self.assertFalse(Sentence.should_parse(['word', []])) def test_is_block(self): - from certbot_nginx.parser_obj import Block + from certbot_nginx._internal.parser_obj import Block self.assertFalse(Block.should_parse([])) self.assertFalse(Block.should_parse([''])) self.assertFalse(Block.should_parse(['two', 'words'])) @@ -71,7 +73,7 @@ class ParsingHooksTest(unittest.TestCase): fake_parser1.not_called() fake_parser2.called_once() - @mock.patch("certbot_nginx.parser_obj.Parsable.parsing_hooks") + @mock.patch("certbot_nginx._internal.parser_obj.Parsable.parsing_hooks") def test_parse_raw_no_match(self, parsing_hooks): from certbot import errors fake_parser1 = mock.Mock() @@ -91,7 +93,7 @@ class ParsingHooksTest(unittest.TestCase): class SentenceTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Sentence + from certbot_nginx._internal.parser_obj import Sentence self.sentence = Sentence(None) def test_parse_bad_sentence_raises_error(self): @@ -137,7 +139,7 @@ class SentenceTest(unittest.TestCase): class BlockTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Block + from certbot_nginx._internal.parser_obj import Block self.bloc = Block(None) self.name = ['server', 'name'] self.contents = [['thing', '1'], ['thing', '2'], ['another', 'one']] @@ -153,7 +155,7 @@ class BlockTest(unittest.TestCase): def test_iterate_match(self): # can match on contents while expanded - from certbot_nginx.parser_obj import Block, Sentence + from certbot_nginx._internal.parser_obj import Block, Sentence expected = [['thing', '1'], ['thing', '2']] for i, elem in enumerate(self.bloc.iterate(expanded=True, match=lambda x: isinstance(x, Sentence) and 'thing' in x.words)): @@ -192,7 +194,7 @@ class BlockTest(unittest.TestCase): class StatementsTest(unittest.TestCase): def setUp(self): - from certbot_nginx.parser_obj import Statements + from certbot_nginx._internal.parser_obj import Statements self.statements = Statements(None) self.raw = [ ['sentence', 'one'], diff --git a/certbot-nginx/certbot_nginx/tests/parser_test.py b/certbot-nginx/tests/parser_test.py similarity index 92% rename from certbot-nginx/certbot_nginx/tests/parser_test.py rename to certbot-nginx/tests/parser_test.py index 97c542532..2f3b260ca 100644 --- a/certbot-nginx/certbot_nginx/tests/parser_test.py +++ b/certbot-nginx/tests/parser_test.py @@ -1,21 +1,19 @@ -"""Tests for certbot_nginx.parser.""" +"""Tests for certbot_nginx._internal.parser.""" import glob import re import shutil import unittest from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot.compat import os - -from certbot_nginx import nginxparser -from certbot_nginx import obj -from certbot_nginx import parser -from certbot_nginx.tests import util +from certbot_nginx._internal import nginxparser +from certbot_nginx._internal import obj +from certbot_nginx._internal import parser +import test_util as util -class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods +class NginxParserTest(util.NginxTest): """Nginx Parser Test.""" def tearDown(self): @@ -30,8 +28,16 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods self.assertEqual(nparser.root, self.config_path) def test_root_absolute(self): - nparser = parser.NginxParser(os.path.relpath(self.config_path)) - self.assertEqual(nparser.root, self.config_path) + curr_dir = os.getcwd() + try: + # On Windows current directory may be on a different drive than self.tempdir. + # 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.temp_dir) + nparser = parser.NginxParser(os.path.relpath(self.config_path)) + self.assertEqual(nparser.root, self.config_path) + finally: + os.chdir(curr_dir) def test_root_no_trailing_slash(self): nparser = parser.NginxParser(self.config_path + os.path.sep) @@ -43,16 +49,16 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods """ nparser = parser.NginxParser(self.config_path) nparser.load() - self.assertEqual(set([nparser.abs_path(x) for x in - ['foo.conf', 'nginx.conf', 'server.conf', - 'sites-enabled/default', - 'sites-enabled/example.com', - 'sites-enabled/headers.com', - 'sites-enabled/migration.com', - 'sites-enabled/sslon.com', - 'sites-enabled/globalssl.com', - 'sites-enabled/ipv6.com', - 'sites-enabled/ipv6ssl.com']]), + self.assertEqual({nparser.abs_path(x) for x in + ['foo.conf', 'nginx.conf', 'server.conf', + 'sites-enabled/default', + 'sites-enabled/example.com', + 'sites-enabled/headers.com', + 'sites-enabled/migration.com', + 'sites-enabled/sslon.com', + 'sites-enabled/globalssl.com', + 'sites-enabled/ipv6.com', + 'sites-enabled/ipv6ssl.com']}, set(nparser.parsed.keys())) self.assertEqual([['server_name', 'somename', 'alias', 'another.alias']], nparser.parsed[nparser.abs_path('server.conf')]) @@ -245,7 +251,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods [['foo', 'bar'], ['ssl_certificate', '/etc/ssl/cert2.pem']]) nparser.add_server_directives(mock_vhost, [['foo', 'bar']]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -280,7 +286,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods nparser.add_server_directives(mock_vhost, [['\n ', 'include', ' ', nparser.abs_path('comment_in_file.conf')]]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual(nparser.parsed[example_com], [[['server'], [['listen', '69.50.225.155:9000'], ['listen', '127.0.0.1'], @@ -300,7 +306,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods mock_vhost = obj.VirtualHost(filep, None, None, None, target, None, [0]) nparser.update_or_add_server_directives( mock_vhost, [['server_name', 'foobar.com']]) - from certbot_nginx.parser import COMMENT + from certbot_nginx._internal.parser import COMMENT self.assertEqual( nparser.parsed[filep], [[['server'], [['listen', '69.50.225.155:9000'], @@ -359,7 +365,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods ["\n", "a", " ", "b", "\n"], ["c", " ", "d"], ["\n", "e", " ", "f"]]) - from certbot_nginx.parser import comment_directive, COMMENT_BLOCK + from certbot_nginx._internal.parser import comment_directive, COMMENT_BLOCK comment_directive(block, 1) comment_directive(block, 0) self.assertEqual(block.spaced, [ @@ -383,7 +389,7 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods ssl_prefer_server_ciphers on; }""") block = server_block[0][1] - from certbot_nginx.parser import _comment_out_directive + from certbot_nginx._internal.parser import _comment_out_directive _comment_out_directive(block, 4, "blah1") _comment_out_directive(block, 5, "blah2") _comment_out_directive(block, 6, "blah3") diff --git a/certbot-nginx/tests/test_util.py b/certbot-nginx/tests/test_util.py new file mode 100644 index 000000000..8dfd18637 --- /dev/null +++ b/certbot-nginx/tests/test_util.py @@ -0,0 +1,130 @@ +"""Common utilities for certbot_nginx.""" +import copy +import shutil +import tempfile + +import josepy as jose +import mock +import pkg_resources +import zope.component + +from certbot import util +from certbot.compat import os +from certbot.plugins import common +from certbot.tests import util as test_util +from certbot_nginx._internal import configurator +from certbot_nginx._internal import nginxparser + + +class NginxTest(test_util.ConfigTestCase): + + def setUp(self): + super(NginxTest, self).setUp() + + self.configuration = self.config + self.config = None + + self.temp_dir, self.config_dir, self.work_dir = common.dir_setup( + "etc_nginx", __name__) + self.logs_dir = tempfile.mkdtemp('logs') + + self.config_path = os.path.join(self.temp_dir, "etc_nginx") + + self.rsa512jwk = jose.JWKRSA.load(test_util.load_vector( + "rsa512_key.pem")) + + def tearDown(self): + # Cleanup opened resources after a test. This is usually done through atexit handlers in + # Certbot, but during tests, atexit will not run registered functions before tearDown is + # called and instead will run them right before the entire test process exits. + # It is a problem on Windows, that does not accept to clean resources before closing them. + util._release_locks() # pylint: disable=protected-access + + shutil.rmtree(self.temp_dir) + shutil.rmtree(self.config_dir) + shutil.rmtree(self.work_dir) + shutil.rmtree(self.logs_dir) + + def get_nginx_configurator(self, config_path, config_dir, work_dir, logs_dir, + version=(1, 6, 2), openssl_version="1.0.2g"): + """Create an Nginx Configurator with the specified options.""" + + backups = os.path.join(work_dir, "backups") + + self.configuration.nginx_server_root = config_path + self.configuration.le_vhost_ext = "-le-ssl.conf" + self.configuration.config_dir = config_dir + self.configuration.work_dir = work_dir + self.configuration.logs_dir = logs_dir + self.configuration.backup_dir = backups + self.configuration.temp_checkpoint_dir = os.path.join(work_dir, "temp_checkpoints") + self.configuration.in_progress_dir = os.path.join(backups, "IN_PROGRESS") + self.configuration.server = "https://acme-server.org:443/new" + self.configuration.http01_port = 80 + self.configuration.https_port = 5001 + + with mock.patch("certbot_nginx._internal.configurator.NginxConfigurator." + "config_test"): + with mock.patch("certbot_nginx._internal.configurator.util." + "exe_exists") as mock_exe_exists: + mock_exe_exists.return_value = True + config = configurator.NginxConfigurator( + self.configuration, + name="nginx", + version=version, + openssl_version=openssl_version) + config.prepare() + + # Provide general config utility. + zope.component.provideUtility(self.configuration) + + return config + + +def get_data_filename(filename): + """Gets the filename of a test data file.""" + return pkg_resources.resource_filename( + __name__, os.path.join( + "testdata", "etc_nginx", filename)) + + +def filter_comments(tree): + """Filter comment nodes from parsed configurations.""" + + def traverse(tree): + """Generator dropping comment nodes""" + for entry in tree: + # key, values = entry + spaceless = [e for e in entry if not nginxparser.spacey(e)] + if spaceless: + key = spaceless[0] + values = spaceless[1] if len(spaceless) > 1 else None + else: + key = values = "" + if isinstance(key, list): + new = copy.deepcopy(entry) + new[1] = filter_comments(values) + yield new + else: + if key != '#' and spaceless: + yield spaceless + + return list(traverse(tree)) + + +def contains_at_depth(haystack, needle, n): + """Is the needle in haystack at depth n? + + Return true if the needle is present in one of the sub-iterables in haystack + at depth n. Haystack must be an iterable. + """ + # Specifically use hasattr rather than isinstance(..., collections.Iterable) + # because we want to include lists but reject strings. + if not hasattr(haystack, '__iter__') or hasattr(haystack, 'strip'): + return False + if n == 0: + return needle in haystack + for item in haystack: + if contains_at_depth(item, needle, n - 1): + return True + return False diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf b/certbot-nginx/tests/testdata/etc_nginx/broken.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/broken.conf rename to certbot-nginx/tests/testdata/etc_nginx/broken.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf b/certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/comment_in_file.conf rename to certbot-nginx/tests/testdata/etc_nginx/comment_in_file.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf b/certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/edge_cases.conf rename to certbot-nginx/tests/testdata/etc_nginx/edge_cases.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf b/certbot-nginx/tests/testdata/etc_nginx/foo.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/foo.conf rename to certbot-nginx/tests/testdata/etc_nginx/foo.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/mime.types b/certbot-nginx/tests/testdata/etc_nginx/mime.types similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/mime.types rename to certbot-nginx/tests/testdata/etc_nginx/mime.types diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf b/certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/minimalistic_comments.conf rename to certbot-nginx/tests/testdata/etc_nginx/minimalistic_comments.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf b/certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/multiline_quotes.conf rename to certbot-nginx/tests/testdata/etc_nginx/multiline_quotes.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf b/certbot-nginx/tests/testdata/etc_nginx/nginx.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/nginx.conf rename to certbot-nginx/tests/testdata/etc_nginx/nginx.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf b/certbot-nginx/tests/testdata/etc_nginx/server.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/server.conf rename to certbot-nginx/tests/testdata/etc_nginx/server.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/default rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/default diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/example.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/example.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/globalssl.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/headers.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/headers.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/ipv6ssl.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/migration.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/migration.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com b/certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com rename to certbot-nginx/tests/testdata/etc_nginx/sites-enabled/sslon.com diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/fastcgi_params diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-utf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/koi-win diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/mime.types diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi-ui.conf.1.4.1 diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi.rules diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/naxsi_core.rules diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/nginx.conf diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/proxy_params diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/scgi_params diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-available/default diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/sites-enabled/default diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/uwsgi_params diff --git a/certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf b/certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf similarity index 100% rename from certbot-nginx/certbot_nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf rename to certbot-nginx/tests/testdata/etc_nginx/ubuntu_nginx_1_4_6/default_vhost/nginx/win-utf diff --git a/certbot/CHANGELOG.md b/certbot/CHANGELOG.md new file mode 100644 index 000000000..0e8c20a50 --- /dev/null +++ b/certbot/CHANGELOG.md @@ -0,0 +1,1871 @@ +# Certbot change log + +Certbot adheres to [Semantic Versioning](https://semver.org/). + +## 1.1.0 - master + +### Added + +* + +### Changed + +* + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 1.0.0 - 2019-12-03 + +### Added + +* + +### Removed + +* The `docs` extras for the `certbot-apache` and `certbot-nginx` packages + have been removed. + +### Changed + +* certbot-auto has deprecated support for systems using OpenSSL 1.0.1 that are + not running on x86-64. This primarily affects RHEL 6 based systems. +* Certbot's `config_changes` subcommand has been removed +* `certbot.plugins.common.TLSSNI01` has been removed. +* Deprecated attributes related to the TLS-SNI-01 challenge in + `acme.challenges` and `acme.standalone` + have been removed. +* The functions `certbot.client.view_config_changes`, + `certbot.main.config_changes`, + `certbot.plugins.common.Installer.view_config_changes`, + `certbot.reverter.Reverter.view_config_changes`, and + `certbot.util.get_systemd_os_info` have been removed +* Certbot's `register --update-registration` subcommand has been removed +* When possible, default to automatically configuring the webserver so all requests + redirect to secure HTTPS access. This is mostly relevant when running Certbot + in non-interactive mode. Previously, the default was to not redirect all requests. + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 0.40.1 - 2019-11-05 + +### Changed + +* Added back support for Python 3.4 to Certbot components and certbot-auto due + to a bug when requiring Python 2.7 or 3.5+ on RHEL 6 based systems. + +More details about these changes can be found on our GitHub repo. + +## 0.40.0 - 2019-11-05 + +### Added + +* + +### Changed + +* We deprecated support for Python 3.4 in Certbot and its ACME library. Support + for Python 3.4 will be removed in the next major release of Certbot. + certbot-auto users on RHEL 6 based systems will be asked to enable Software + Collections (SCL) repository so Python 3.6 can be installed. certbot-auto can + enable the SCL repo for you on CentOS 6 while users on other RHEL 6 based + systems will be asked to do this manually. +* `--server` may now be combined with `--dry-run`. Certbot will, as before, use the + staging server instead of the live server when `--dry-run` is used. +* `--dry-run` now requests fresh authorizations every time, fixing the issue + where it was prone to falsely reporting success. +* Updated certbot-dns-google to depend on newer versions of + google-api-python-client and oauth2client. +* The OS detection logic again uses distro library for Linux OSes +* certbot.plugins.common.TLSSNI01 has been deprecated and will be removed in a + future release. +* CLI flags --tls-sni-01-port and --tls-sni-01-address have been removed. +* The values tls-sni and tls-sni-01 for the --preferred-challenges flag are no + longer accepted. +* Removed the flags: `--agree-dev-preview`, `--dialog`, and `--apache-init-script` +* acme.standalone.BaseRequestHandlerWithLogging and + acme.standalone.simple_tls_sni_01_server have been deprecated and will be + removed in a future release of the library. +* certbot-dns-rfc2136 now use TCP to query SOA records. + +### Fixed + +* + +More details about these changes can be found on our GitHub repo. + +## 0.39.0 - 2019-10-01 + +### Added + +* Support for Python 3.8 was added to Certbot and all of its components. +* Support for CentOS 8 was added to certbot-auto. + +### Changed + +* Don't send OCSP requests for expired certificates +* Return to using platform.linux_distribution instead of distro.linux_distribution in OS fingerprinting for Python < 3.8 +* Updated the Nginx plugin's TLS configuration to keep support for some versions of IE11. + +### Fixed + +* Fixed OS detection in the Apache plugin on RHEL 6. + +More details about these changes can be found on our GitHub repo. + +## 0.38.0 - 2019-09-03 + +### Added + +* Disable session tickets for Nginx users when appropriate. + +### Changed + +* If Certbot fails to rollback your server configuration, the error message + links to the Let's Encrypt forum. Change the link to the Help category now + that the Server category has been closed. +* Replace platform.linux_distribution with distro.linux_distribution as a step + towards Python 3.8 support in Certbot. + +### Fixed + +* Fixed OS detection in the Apache plugin on Scientific Linux. + +More details about these changes can be found on our GitHub repo. + +## 0.37.2 - 2019-08-21 + +* Stop disabling TLS session tickets in Nginx as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + +## 0.37.1 - 2019-08-08 + +### Fixed + +* Stop disabling TLS session tickets in Apache as it caused TLS failures on + some systems. + +More details about these changes can be found on our GitHub repo. + +## 0.37.0 - 2019-08-07 + +### Added + +* Turn off session tickets for apache plugin by default +* acme: Authz deactivation added to `acme` module. + +### Changed + +* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers, + and ssl_prefer_server_ciphers + +### Fixed + +* Fix certbot-auto failures on RHEL 8. + +More details about these changes can be found on our GitHub repo. + +## 0.36.0 - 2019-07-11 + +### Added + +* Turn off session tickets for nginx plugin by default +* Added missing error types from RFC8555 to acme + +### Changed + +* Support for Ubuntu 14.04 Trusty has been removed. +* Update the 'manage your account' help to be more generic. +* The error message when Certbot's Apache plugin is unable to modify your + Apache configuration has been improved. +* Certbot's config_changes subcommand has been deprecated and will be + removed in a future release. +* `certbot config_changes` no longer accepts a --num parameter. +* The functions `certbot.plugins.common.Installer.view_config_changes` and + `certbot.reverter.Reverter.view_config_changes` have been deprecated and will + be removed in a future release. + +### Fixed + +* Replace some unnecessary platform-specific line separation. + +More details about these changes can be found on our GitHub repo. + +## 0.35.1 - 2019-06-10 + +### Fixed + +* Support for specifying an authoritative base domain in our dns-rfc2136 plugin + has been removed. This feature was added in our last release but had a bug + which caused the plugin to fail so the feature has been removed until it can + be added properly. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* certbot-dns-rfc2136 + +More details about these changes can be found on our GitHub repo. + +## 0.35.0 - 2019-06-05 + +### Added + +* dns_rfc2136 plugin now supports explicitly specifing an authorative + base domain for cases when the automatic method does not work (e.g. + Split horizon DNS) + +### Changed + +* + +### Fixed + +* Renewal parameter `webroot_path` is always saved, avoiding some regressions + when `webroot` authenticator plugin is invoked with no challenge to perform. +* Certbot now accepts OCSP responses when an explicit authorized + responder, different from the issuer, is used to sign OCSP + responses. +* Scripts in Certbot hook directories are no longer executed when their + filenames end in a tilde. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* certbot +* certbot-dns-rfc2136 + +More details about these changes can be found on our GitHub repo. + +## 0.34.2 - 2019-05-07 + +### Fixed + +* certbot-auto no longer writes a check_permissions.py script at the root + of the filesystem. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.34.1 - 2019-05-06 + +### Fixed + +* certbot-auto no longer prints a blank line when there are no permissions + problems. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.34.0 - 2019-05-01 + +### Changed + +* Apache plugin now tries to restart httpd on Fedora using systemctl if a + configuration test error is detected. This has to be done due to the way + Fedora now generates the self signed certificate files upon first + restart. +* Updated Certbot and its plugins to improve the handling of file system permissions + on Windows as a step towards adding proper Windows support to Certbot. +* Updated urllib3 to 1.24.2 in certbot-auto. +* Removed the fallback introduced with 0.32.0 in `acme` to retry a challenge response + with a `keyAuthorization` if sending the response without this field caused a + `malformed` error to be received from the ACME server. +* Linode DNS plugin now supports api keys created from their new panel + at [cloud.linode.com](https://cloud.linode.com) + +### Fixed + +* Fixed Google DNS Challenge issues when private zones exist +* Adding a warning noting that future versions of Certbot will automatically configure the + webserver so that all requests redirect to secure HTTPS access. You can control this + behavior and disable this warning with the --redirect and --no-redirect flags. +* certbot-auto now prints warnings when run as root with insecure file system + permissions. If you see these messages, you should fix the problem by + following the instructions at + https://community.letsencrypt.org/t/certbot-auto-deployment-best-practices/91979/, + however, these warnings can be disabled as necessary with the flag + --no-permissions-check. +* `acme` module uses now a POST-as-GET request to retrieve the registration + from an ACME v2 server +* Convert the tsig algorithm specified in the certbot_dns_rfc2136 configuration file to + all uppercase letters before validating. This makes the value in the config case + insensitive. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-dns-cloudflare +* certbot-dns-cloudxns +* certbot-dns-digitalocean +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-google +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-rfc2136 +* certbot-dns-route53 +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.33.1 - 2019-04-04 + +### Fixed + +* A bug causing certbot-auto to print warnings or crash on some RHEL based + systems has been resolved. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +changes in this release were to certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.33.0 - 2019-04-03 + +### Added + +* Fedora 29+ is now supported by certbot-auto. Since Python 2.x is on a deprecation + path in Fedora, certbot-auto will install and use Python 3.x on Fedora 29+. +* CLI flag `--https-port` has been added for Nginx plugin exclusively, and replaces + `--tls-sni-01-port`. It defines the HTTPS port the Nginx plugin will use while + setting up a new SSL vhost. By default the HTTPS port is 443. + +### Changed + +* Support for TLS-SNI-01 has been removed from all official Certbot plugins. +* Attributes related to the TLS-SNI-01 challenge in `acme.challenges` and `acme.standalone` + modules are deprecated and will be removed soon. +* CLI flags `--tls-sni-01-port` and `--tls-sni-01-address` are now no-op, will + generate a deprecation warning if used, and will be removed soon. +* Options `tls-sni` and `tls-sni-01` in `--preferred-challenges` flag are now no-op, + will generate a deprecation warning if used, and will be removed soon. +* CLI flag `--standalone-supported-challenges` has been removed. + +### Fixed + +* Certbot uses the Python library cryptography for OCSP when cryptography>=2.5 + is installed. We fixed a bug in Certbot causing it to interpret timestamps in + the OCSP response as being in the local timezone rather than UTC. +* Issue causing the default CentOS 6 TLS configuration to ignore some of the + HTTPS VirtualHosts created by Certbot. mod_ssl loading is now moved to main + http.conf for this environment where possible. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.32.0 - 2019-03-06 + +### Added + +* If possible, Certbot uses built-in support for OCSP from recent cryptography + versions instead of the OpenSSL binary: as a consequence Certbot does not need + the OpenSSL binary to be installed anymore if cryptography>=2.5 is installed. + +### Changed + +* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the + warnings described at https://github.com/certbot/josepy/issues/13. +* Apache plugin now respects CERTBOT_DOCS environment variable when adding + command line defaults. +* The running of manual plugin hooks is now always included in Certbot's log + output. +* Tests execution for certbot, certbot-apache and certbot-nginx packages now relies on pytest. +* An ACME CA server may return a "Retry-After" HTTP header on authorization polling, as + specified in the ACME protocol, to indicate when the next polling should occur. Certbot now + reads this header if set and respect its value. +* The `acme` module avoids sending the `keyAuthorization` field in the JWS + payload when responding to a challenge as the field is not included in the + current ACME protocol. To ease the migration path for ACME CA servers, + Certbot and its `acme` module will first try the request without the + `keyAuthorization` field but will temporarily retry the request with the + field included if a `malformed` error is received. This fallback will be + removed in version 0.34.0. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-nginx + +More details about these changes can be found on our GitHub repo. + +## 0.31.0 - 2019-02-07 + +### Added + +* Avoid reprocessing challenges that are already validated + when a certificate is issued. +* Support for initiating (but not solving end-to-end) TLS-ALPN-01 challenges + with the `acme` module. + +### Changed + +* Certbot's official Docker images are now based on Alpine Linux 3.9 rather + than 3.7. The new version comes with OpenSSL 1.1.1. +* Lexicon-based DNS plugins are now fully compatible with Lexicon 3.x (support + on 2.x branch is maintained). +* Apache plugin now attempts to configure all VirtualHosts matching requested + domain name instead of only a single one when answering the HTTP-01 challenge. + +### Fixed + +* Fixed accessing josepy contents through acme.jose when the full acme.jose + path is used. +* Clarify behavior for deleting certs as part of revocation. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-dns-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-sakuracloud + +More details about these changes can be found on our GitHub repo. + +## 0.30.2 - 2019-01-25 + +### Fixed + +* Update the version of setuptools pinned in certbot-auto to 40.6.3 to + solve installation problems on newer OSes. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, this +release only affects certbot-auto. + +More details about these changes can be found on our GitHub repo. + +## 0.30.1 - 2019-01-24 + +### Fixed + +* Always download the pinned version of pip in pipstrap to address breakages +* Rename old,default.conf to old-and-default.conf to address commas in filenames + breaking recent versions of pip. +* Add VIRTUALENV_NO_DOWNLOAD=1 to all calls to virtualenv to address breakages + from venv downloading the latest pip + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* certbot-apache + +More details about these changes can be found on our GitHub repo. + +## 0.30.0 - 2019-01-02 + +### Added + +* Added the `update_account` subcommand for account management commands. + +### Changed + +* Copied account management functionality from the `register` subcommand + to the `update_account` subcommand. +* Marked usage `register --update-registration` for deprecation and + removal in a future release. + +### Fixed + +* Older modules in the josepy library can now be accessed through acme.jose + like it could in previous versions of acme. This is only done to preserve + backwards compatibility and support for doing this with new modules in josepy + will not be added. Users of the acme library should switch to using josepy + directly if they haven't done so already. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme + +More details about these changes can be found on our GitHub repo. + +## 0.29.1 - 2018-12-05 + +### Added + +* + +### Changed + +* + +### Fixed + +* The default work and log directories have been changed back to + /var/lib/letsencrypt and /var/log/letsencrypt respectively. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* certbot + +More details about these changes can be found on our GitHub repo. + +## 0.29.0 - 2018-12-05 + +### Added + +* Noninteractive renewals with `certbot renew` (those not started from a + terminal) now randomly sleep 1-480 seconds before beginning work in + order to spread out load spikes on the server side. +* Added External Account Binding support in cli and acme library. + Command line arguments --eab-kid and --eab-hmac-key added. + +### Changed + +* Private key permissioning changes: Renewal preserves existing group mode + & gid of previous private key material. Private keys for new + lineages (i.e. new certs, not renewed) default to 0o600. + +### Fixed + +* Update code and dependencies to clean up Resource and Deprecation Warnings. +* Only depend on imgconverter extension for Sphinx >= 1.6 + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-dns-cloudflare +* certbot-dns-digitalocean +* certbot-dns-google +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/62?closed=1 + +## 0.28.0 - 2018-11-7 + +### Added + +* `revoke` accepts `--cert-name`, and doesn't accept both `--cert-name` and `--cert-path`. +* Use the ACMEv2 newNonce endpoint when a new nonce is needed, and newNonce is available in the directory. + +### Changed + +* Removed documentation mentions of `#letsencrypt` IRC on Freenode. +* Write README to the base of (config-dir)/live directory +* `--manual` will explicitly warn users that earlier challenges should remain in place when setting up subsequent challenges. +* Warn when using deprecated acme.challenges.TLSSNI01 +* Log warning about TLS-SNI deprecation in Certbot +* Stop preferring TLS-SNI in the Apache, Nginx, and standalone plugins +* OVH DNS plugin now relies on Lexicon>=2.7.14 to support HTTP proxies +* Default time the Linode plugin waits for DNS changes to propogate is now 1200 seconds. + +### Fixed + +* Match Nginx parser update in allowing variable names to start with `${`. +* Fix ranking of vhosts in Nginx so that all port-matching vhosts come first +* Correct OVH integration tests on machines without internet access. +* Stop caching the results of ipv6_info in http01.py +* Test fix for Route53 plugin to prevent boto3 making outgoing connections. +* The grammar used by Augeas parser in Apache plugin was updated to fix various parsing errors. +* The CloudXNS, DNSimple, DNS Made Easy, Gehirn, Linode, LuaDNS, NS1, OVH, and + Sakura Cloud DNS plugins are now compatible with Lexicon 3.0+. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-dns-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-ovh +* certbot-dns-route53 +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/59?closed=1 + +## 0.27.1 - 2018-09-06 + +### Fixed + +* Fixed parameter name in OpenSUSE overrides for default parameters in the + Apache plugin. Certbot on OpenSUSE works again. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* certbot-apache + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/60?closed=1 + +## 0.27.0 - 2018-09-05 + +### Added + +* The Apache plugin now accepts the parameter --apache-ctl which can be + used to configure the path to the Apache control script. + +### Changed + +* When using `acme.client.ClientV2` (or + `acme.client.BackwardsCompatibleClientV2` with an ACME server that supports a + newer version of the ACME protocol), an `acme.errors.ConflictError` will be + raised if you try to create an ACME account with a key that has already been + used. Previously, a JSON parsing error was raised in this scenario when using + the library with Let's Encrypt's ACMEv2 endpoint. + +### Fixed + +* When Apache is not installed, Certbot's Apache plugin no longer prints + messages about being unable to find apachectl to the terminal when the plugin + is not selected. +* If you're using the Apache plugin with the --apache-vhost-root flag set to a + directory containing a disabled virtual host for the domain you're requesting + a certificate for, the virtual host will now be temporarily enabled if + necessary to pass the HTTP challenge. +* The documentation for the Certbot package can now be built using Sphinx 1.6+. +* You can now call `query_registration` without having to first call + `new_account` on `acme.client.ClientV2` objects. +* The requirement of `setuptools>=1.0` has been removed from `certbot-dns-ovh`. +* Names in certbot-dns-sakuracloud's tests have been updated to refer to Sakura + Cloud rather than NS1 whose plugin certbot-dns-sakuracloud was based on. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache +* certbot-dns-ovh +* certbot-dns-sakuracloud + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/57?closed=1 + +## 0.26.1 - 2018-07-17 + +### Fixed + +* Fix a bug that was triggered when users who had previously manually set `--server` to get ACMEv2 certs tried to renew ACMEv1 certs. + +Despite us having broken lockstep, we are continuing to release new versions of all Certbot components during releases for the time being, however, the only package with changes other than its version number was: + +* certbot + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/58?closed=1 + +## 0.26.0 - 2018-07-11 + +### Added + +* A new security enhancement which we're calling AutoHSTS has been added to + Certbot's Apache plugin. This enhancement configures your webserver to send a + HTTP Strict Transport Security header with a low max-age value that is slowly + increased over time. The max-age value is not increased to a large value + until you've successfully managed to renew your certificate. This enhancement + can be requested with the --auto-hsts flag. +* New official DNS plugins have been created for Gehirn Infrastracture Service, + Linode, OVH, and Sakura Cloud. These plugins can be found on our Docker Hub + page at https://hub.docker.com/u/certbot and on PyPI. +* The ability to reuse ACME accounts from Let's Encrypt's ACMEv1 endpoint on + Let's Encrypt's ACMEv2 endpoint has been added. +* Certbot and its components now support Python 3.7. +* Certbot's install subcommand now allows you to interactively choose which + certificate to install from the list of certificates managed by Certbot. +* Certbot now accepts the flag `--no-autorenew` which causes any obtained + certificates to not be automatically renewed when it approaches expiration. +* Support for parsing the TLS-ALPN-01 challenge has been added back to the acme + library. + +### Changed + +* Certbot's default ACME server has been changed to Let's Encrypt's ACMEv2 + endpoint. By default, this server will now be used for both new certificate + lineages and renewals. +* The Nginx plugin is no longer marked labeled as an "Alpha" version. +* The `prepare` method of Certbot's plugins is no longer called before running + "Updater" enhancements that are run on every invocation of `certbot renew`. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with functional changes were: + +* acme +* certbot +* certbot-apache +* certbot-dns-gehirn +* certbot-dns-linode +* certbot-dns-ovh +* certbot-dns-sakuracloud +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/55?closed=1 + +## 0.25.1 - 2018-06-13 + +### Fixed + +* TLS-ALPN-01 support has been removed from our acme library. Using our current + dependencies, we are unable to provide a correct implementation of this + challenge so we decided to remove it from the library until we can provide + proper support. +* Issues causing test failures when running the tests in the acme package with + pytest<3.0 has been resolved. +* certbot-nginx now correctly depends on acme>=0.25.0. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with changes other than their version number were: + +* acme +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/56?closed=1 + +## 0.25.0 - 2018-06-06 + +### Added + +* Support for the ready status type was added to acme. Without this change, + Certbot and acme users will begin encountering errors when using Let's + Encrypt's ACMEv2 API starting on June 19th for the staging environment and + July 5th for production. See + https://community.letsencrypt.org/t/acmev2-order-ready-status/62866 for more + information. +* Certbot now accepts the flag --reuse-key which will cause the same key to be + used in the certificate when the lineage is renewed rather than generating a + new key. +* You can now add multiple email addresses to your ACME account with Certbot by + providing a comma separated list of emails to the --email flag. +* Support for Let's Encrypt's upcoming TLS-ALPN-01 challenge was added to acme. + For more information, see + https://community.letsencrypt.org/t/tls-alpn-validation-method/63814/1. +* acme now supports specifying the source address to bind to when sending + outgoing connections. You still cannot specify this address using Certbot. +* If you run Certbot against Let's Encrypt's ACMEv2 staging server but don't + already have an account registered at that server URL, Certbot will + automatically reuse your staging account from Let's Encrypt's ACMEv1 endpoint + if it exists. +* Interfaces were added to Certbot allowing plugins to be called at additional + points. The `GenericUpdater` interface allows plugins to perform actions + every time `certbot renew` is run, regardless of whether any certificates are + due for renewal, and the `RenewDeployer` interface allows plugins to perform + actions when a certificate is renewed. See `certbot.interfaces` for more + information. + +### Changed + +* When running Certbot with --dry-run and you don't already have a staging + account, the created account does not contain an email address even if one + was provided to avoid expiration emails from Let's Encrypt's staging server. +* certbot-nginx does a better job of automatically detecting the location of + Nginx's configuration files when run on BSD based systems. +* acme now requires and uses pytest when running tests with setuptools with + `python setup.py test`. +* `certbot config_changes` no longer waits for user input before exiting. + +### Fixed + +* Misleading log output that caused users to think that Certbot's standalone + plugin failed to bind to a port when performing a challenge has been + corrected. +* An issue where certbot-nginx would fail to enable HSTS if the server block + already had an `add_header` directive has been resolved. +* certbot-nginx now does a better job detecting the server block to base the + configuration for TLS-SNI challenges on. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with functional changes were: + +* acme +* certbot +* certbot-apache +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/54?closed=1 + +## 0.24.0 - 2018-05-02 + +### Added + +* certbot now has an enhance subcommand which allows you to configure security + enhancements like HTTP to HTTPS redirects, OCSP stapling, and HSTS without + reinstalling a certificate. +* certbot-dns-rfc2136 now allows the user to specify the port to use to reach + the DNS server in its credentials file. +* acme now parses the wildcard field included in authorizations so it can be + used by users of the library. + +### Changed + +* certbot-dns-route53 used to wait for each DNS update to propagate before + sending the next one, but now it sends all updates before waiting which + speeds up issuance for multiple domains dramatically. +* Certbot's official Docker images are now based on Alpine Linux 3.7 rather + than 3.4 because 3.4 has reached its end-of-life. +* We've doubled the time Certbot will spend polling authorizations before + timing out. +* The level of the message logged when Certbot is being used with + non-standard paths warning that crontabs for renewal included in Certbot + packages from OS package managers may not work has been reduced. This stops + the message from being written to stderr every time `certbot renew` runs. + +### Fixed + +* certbot-auto now works with Python 3.6. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with changes other than their version number were: + +* acme +* certbot +* certbot-apache +* certbot-dns-digitalocean (only style improvements to tests) +* certbot-dns-rfc2136 + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/52?closed=1 + +## 0.23.0 - 2018-04-04 + +### Added + +* Support for OpenResty was added to the Nginx plugin. + +### Changed + +* The timestamps in Certbot's logfiles now use the system's local time zone + rather than UTC. +* Certbot's DNS plugins that use Lexicon now rely on Lexicon>=2.2.1 to be able + to create and delete multiple TXT records on a single domain. +* certbot-dns-google's test suite now works without an internet connection. + +### Fixed + +* Removed a small window that if during which an error occurred, Certbot + wouldn't clean up performed challenges. +* The parameters `default` and `ipv6only` are now removed from `listen` + directives when creating a new server block in the Nginx plugin. +* `server_name` directives enclosed in quotation marks in Nginx are now properly + supported. +* Resolved an issue preventing the Apache plugin from starting Apache when it's + not currently running on RHEL and Gentoo based systems. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with changes other than their version number were: + +* certbot +* certbot-apache +* certbot-dns-cloudxns +* certbot-dns-dnsimple +* certbot-dns-dnsmadeeasy +* certbot-dns-google +* certbot-dns-luadns +* certbot-dns-nsone +* certbot-dns-rfc2136 +* certbot-nginx + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/50?closed=1 + +## 0.22.2 - 2018-03-19 + +### Fixed + +* A type error introduced in 0.22.1 that would occur during challenge cleanup + when a Certbot plugin raises an exception while trying to complete the + challenge was fixed. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with changes other than their version number were: + +* certbot + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/53?closed=1 + +## 0.22.1 - 2018-03-19 + +### Changed + +* The ACME server used with Certbot's --dry-run and --staging flags is now + Let's Encrypt's ACMEv2 staging server which allows people to also test ACMEv2 + features with these flags. + +### Fixed + +* The HTTP Content-Type header is now set to the correct value during + certificate revocation with new versions of the ACME protocol. +* When using Certbot with Let's Encrypt's ACMEv2 server, it would add a blank + line to the top of chain.pem and between the certificates in fullchain.pem + for each lineage. These blank lines have been removed. +* Resolved a bug that caused Certbot's --allow-subset-of-names flag not to + work. +* Fixed a regression in acme.client.Client that caused the class to not work + when it was initialized without a ClientNetwork which is done by some of the + other projects using our ACME library. + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +packages with changes other than their version number were: + +* acme +* certbot + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/51?closed=1 + +## 0.22.0 - 2018-03-07 + +### Added + +* Support for obtaining wildcard certificates and a newer version of the ACME + protocol such as the one implemented by Let's Encrypt's upcoming ACMEv2 + endpoint was added to Certbot and its ACME library. Certbot still works with + older ACME versions and will automatically change the version of the protocol + used based on the version the ACME CA implements. +* The Apache and Nginx plugins are now able to automatically install a wildcard + certificate to multiple virtual hosts that you select from your server + configuration. +* The `certbot install` command now accepts the `--cert-name` flag for + selecting a certificate. +* `acme.client.BackwardsCompatibleClientV2` was added to Certbot's ACME library + which automatically handles most of the differences between new and old ACME + versions. `acme.client.ClientV2` is also available for people who only want + to support one version of the protocol or want to handle the differences + between versions themselves. +* certbot-auto now supports the flag --install-only which has the script + install Certbot and its dependencies and exit without invoking Certbot. +* Support for issuing a single certificate for a wildcard and base domain was + added to our Google Cloud DNS plugin. To do this, we now require your API + credentials have additional permissions, however, your credentials will + already have these permissions unless you defined a custom role with fewer + permissions than the standard DNS administrator role provided by Google. + These permissions are also only needed for the case described above so it + will continue to work for existing users. For more information about the + permissions changes, see the documentation in the plugin. + +### Changed + +* We have broken lockstep between our ACME library, Certbot, and its plugins. + This means that the different components do not need to be the same version + to work together like they did previously. This makes packaging easier + because not every piece of Certbot needs to be repackaged to ship a change to + a subset of its components. +* Support for Python 2.6 and Python 3.3 has been removed from ACME, Certbot, + Certbot's plugins, and certbot-auto. If you are using certbot-auto on a RHEL + 6 based system, it will walk you through the process of installing Certbot + with Python 3 and refuse to upgrade to a newer version of Certbot until you + have done so. +* Certbot's components now work with older versions of setuptools to simplify + packaging for EPEL 7. + +### Fixed + +* Issues caused by Certbot's Nginx plugin adding multiple ipv6only directives + has been resolved. +* A problem where Certbot's Apache plugin would add redundant include + directives for the TLS configuration managed by Certbot has been fixed. +* Certbot's webroot plugin now properly deletes any directories it creates. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/48?closed=1 + +## 0.21.1 - 2018-01-25 + +### Fixed + +* When creating an HTTP to HTTPS redirect in Nginx, we now ensure the Host + header of the request is set to an expected value before redirecting users to + the domain found in the header. The previous way Certbot configured Nginx + redirects was a potential security issue which you can read more about at + https://community.letsencrypt.org/t/security-issue-with-redirects-added-by-certbots-nginx-plugin/51493. +* Fixed a problem where Certbot's Apache plugin could fail HTTP-01 challenges + if basic authentication is configured for the domain you request a + certificate for. +* certbot-auto --no-bootstrap now properly tries to use Python 3.4 on RHEL 6 + based systems rather than Python 2.6. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/49?closed=1 + +## 0.21.0 - 2018-01-17 + +### Added + +* Support for the HTTP-01 challenge type was added to our Apache and Nginx + plugins. For those not aware, Let's Encrypt disabled the TLS-SNI-01 challenge + type which was what was previously being used by our Apache and Nginx plugins + last week due to a security issue. For more information about Let's Encrypt's + change, click + [here](https://community.letsencrypt.org/t/2018-01-11-update-regarding-acme-tls-sni-and-shared-hosting-infrastructure/50188). + Our Apache and Nginx plugins will automatically switch to use HTTP-01 so no + changes need to be made to your Certbot configuration, however, you should + make sure your server is accessible on port 80 and isn't behind an external + proxy doing things like redirecting all traffic from HTTP to HTTPS. HTTP to + HTTPS redirects inside Apache and Nginx are fine. +* IPv6 support was added to the Nginx plugin. +* Support for automatically creating server blocks based on the default server + block was added to the Nginx plugin. +* The flags --delete-after-revoke and --no-delete-after-revoke were added + allowing users to control whether the revoke subcommand also deletes the + certificates it is revoking. + +### Changed + +* We deprecated support for Python 2.6 and Python 3.3 in Certbot and its ACME + library. Support for these versions of Python will be removed in the next + major release of Certbot. If you are using certbot-auto on a RHEL 6 based + system, it will guide you through the process of installing Python 3. +* We split our implementation of JOSE (Javascript Object Signing and + Encryption) out of our ACME library and into a separate package named josepy. + This package is available on [PyPI](https://pypi.python.org/pypi/josepy) and + on [GitHub](https://github.com/certbot/josepy). +* We updated the ciphersuites used in Apache to the new [values recommended by + Mozilla](https://wiki.mozilla.org/Security/Server_Side_TLS#Intermediate_compatibility_.28default.29). + The major change here is adding ChaCha20 to the list of supported + ciphersuites. + +### Fixed + +* An issue with our Apache plugin on Gentoo due to differences in their + apache2ctl command have been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/47?closed=1 + +## 0.20.0 - 2017-12-06 + +### Added + +* Certbot's ACME library now recognizes URL fields in challenge objects in + preparation for Let's Encrypt's new ACME endpoint. The value is still + accessible in our ACME library through the name "uri". + +### Changed + +* The Apache plugin now parses some distro specific Apache configuration files + on non-Debian systems allowing it to get a clearer picture on the running + configuration. Internally, these changes were structured so that external + contributors can easily write patches to make the plugin work in new Apache + configurations. +* Certbot better reports network failures by removing information about + connection retries from the error output. +* An unnecessary question when using Certbot's webroot plugin interactively has + been removed. + +### Fixed + +* Certbot's NGINX plugin no longer sometimes incorrectly reports that it was + unable to deploy a HTTP->HTTPS redirect when requesting Certbot to enable a + redirect for multiple domains. +* Problems where the Apache plugin was failing to find directives and + duplicating existing directives on openSUSE have been resolved. +* An issue running the test shipped with Certbot and some our DNS plugins with + older versions of mock have been resolved. +* On some systems, users reported strangely interleaved output depending on + when stdout and stderr were flushed. This problem was resolved by having + Certbot regularly flush these streams. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/44?closed=1 + +## 0.19.0 - 2017-10-04 + +### Added + +* Certbot now has renewal hook directories where executable files can be placed + for Certbot to run with the renew subcommand. Pre-hooks, deploy-hooks, and + post-hooks can be specified in the renewal-hooks/pre, renewal-hooks/deploy, + and renewal-hooks/post directories respectively in Certbot's configuration + directory (which is /etc/letsencrypt by default). Certbot will automatically + create these directories when it is run if they do not already exist. +* After revoking a certificate with the revoke subcommand, Certbot will offer + to delete the lineage associated with the certificate. When Certbot is run + with --non-interactive, it will automatically try to delete the associated + lineage. +* When using Certbot's Google Cloud DNS plugin on Google Compute Engine, you no + longer have to provide a credential file to Certbot if you have configured + sufficient permissions for the instance which Certbot can automatically + obtain using Google's metadata service. + +### Changed + +* When deleting certificates interactively using the delete subcommand, Certbot + will now allow you to select multiple lineages to be deleted at once. +* Certbot's Apache plugin no longer always parses Apache's sites-available on + Debian based systems and instead only parses virtual hosts included in your + Apache configuration. You can provide an additional directory for Certbot to + parse using the command line flag --apache-vhost-root. + +### Fixed + +* The plugins subcommand can now be run without root access. +* certbot-auto now includes a timeout when updating itself so it no longer + hangs indefinitely when it is unable to connect to the external server. +* An issue where Certbot's Apache plugin would sometimes fail to deploy a + certificate on Debian based systems if mod_ssl wasn't already enabled has + been resolved. +* A bug in our Docker image where the certificates subcommand could not report + if certificates maintained by Certbot had been revoked has been fixed. +* Certbot's RFC 2136 DNS plugin (for use with software like BIND) now properly + performs DNS challenges when the domain being verified contains a CNAME + record. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/43?closed=1 + +## 0.18.2 - 2017-09-20 + +### Fixed + +* An issue where Certbot's ACME module would raise an AttributeError trying to + create self-signed certificates when used with pyOpenSSL 17.3.0 has been + resolved. For Certbot users with this version of pyOpenSSL, this caused + Certbot to crash when performing a TLS SNI challenge or when the Nginx plugin + tried to create an SSL server block. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/46?closed=1 + +## 0.18.1 - 2017-09-08 + +### Fixed + +* If certbot-auto was running as an unprivileged user and it upgraded from + 0.17.0 to 0.18.0, it would crash with a permissions error and would need to + be run again to successfully complete the upgrade. This has been fixed and + certbot-auto should upgrade cleanly to 0.18.1. +* Certbot usually uses "certbot-auto" or "letsencrypt-auto" in error messages + and the User-Agent string instead of "certbot" when you are using one of + these wrapper scripts. Proper detection of this was broken with Certbot's new + installation path in /opt in 0.18.0 but this problem has been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/45?closed=1 + +## 0.18.0 - 2017-09-06 + +### Added + +* The Nginx plugin now configures Nginx to use 2048-bit Diffie-Hellman + parameters. Java 6 clients do not support Diffie-Hellman parameters larger + than 1024 bits, so if you need to support these clients you will need to + manually modify your Nginx configuration after using the Nginx installer. + +### Changed + +* certbot-auto now installs Certbot in directories under `/opt/eff.org`. If you + had an existing installation from certbot-auto, a symlink is created to the + new directory. You can configure certbot-auto to use a different path by + setting the environment variable VENV_PATH. +* The Nginx plugin can now be selected in Certbot's interactive output. +* Output verbosity of renewal failures when running with `--quiet` has been + reduced. +* The default revocation reason shown in Certbot help output now is a human + readable string instead of a numerical code. +* Plugin selection is now included in normal terminal output. + +### Fixed + +* A newer version of ConfigArgParse is now installed when using certbot-auto + causing values set to false in a Certbot INI configuration file to be handled + intuitively. Setting a boolean command line flag to false is equivalent to + not including it in the configuration file at all. +* New naming conventions preventing certbot-auto from installing OS + dependencies on Fedora 26 have been resolved. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/42?closed=1 + +## 0.17.0 - 2017-08-02 + +### Added + +* Support in our nginx plugin for modifying SSL server blocks that do + not contain certificate or key directives. +* A `--max-log-backups` flag to allow users to configure or even completely + disable Certbot's built in log rotation. +* A `--user-agent-comment` flag to allow people who build tools around Certbot + to differentiate their user agent string by adding a comment to its default + value. + +### Changed + +* Due to some awesome work by + [cryptography project](https://github.com/pyca/cryptography), compilation can + now be avoided on most systems when using certbot-auto. This eliminates many + problems people have had in the past such as running out of memory, having + invalid headers/libraries, and changes to the OS packages on their system + after compilation breaking Certbot. +* The `--renew-hook` flag has been hidden in favor of `--deploy-hook`. This new + flag works exactly the same way except it is always run when a certificate is + issued rather than just when it is renewed. +* We have started printing deprecation warnings in certbot-auto for + experimentally supported systems with OS packages available. +* A certificate lineage's name is included in error messages during renewal. + +### Fixed + +* Encoding errors that could occur when parsing error messages from the ACME + server containing Unicode have been resolved. +* certbot-auto no longer prints misleading messages about there being a newer + pip version available when installation fails. +* Certbot's ACME library now properly extracts domains from critical SAN + extensions. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.17.0+is%3Aclosed + +## 0.16.0 - 2017-07-05 + +### Added + +* A plugin for performing DNS challenges using dynamic DNS updates as defined + in RFC 2316. This plugin is packaged separately from Certbot and is available + at https://pypi.python.org/pypi/certbot-dns-rfc2136. It supports Python 2.6, + 2.7, and 3.3+. At this time, there isn't a good way to install this plugin + when using certbot-auto, but this should change in the near future. +* Plugins for performing DNS challenges for the providers + [DNS Made Easy](https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy) and + [LuaDNS](https://pypi.python.org/pypi/certbot-dns-luadns). These plugins are + packaged separately from Certbot and support Python 2.7 and 3.3+. Currently, + there isn't a good way to install these plugins when using certbot-auto, + but that should change soon. +* Support for performing TLS-SNI-01 challenges when using the manual plugin. +* Automatic detection of Arch Linux in the Apache plugin providing better + default settings for the plugin. + +### Changed + +* The text of the interactive question about whether a redirect from HTTP to + HTTPS should be added by Certbot has been rewritten to better explain the + choices to the user. +* Simplified HTTP challenge instructions in the manual plugin. + +### Fixed + +* Problems performing a dry run when using the Nginx plugin have been fixed. +* Resolved an issue where certbot-dns-digitalocean's test suite would sometimes + fail when ran using Python 3. +* On some systems, previous versions of certbot-auto would error out with a + message about a missing hash for setuptools. This has been fixed. +* A bug where Certbot would sometimes not print a space at the end of an + interactive prompt has been resolved. +* Nonfatal tracebacks are no longer shown in rare cases where Certbot + encounters an exception trying to close its TCP connection with the ACME + server. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.16.0+is%3Aclosed + +## 0.15.0 - 2017-06-08 + +### Added + +* Plugins for performing DNS challenges for popular providers. Like the Apache + and Nginx plugins, these plugins are packaged separately and not included in + Certbot by default. So far, we have plugins for + [Amazon Route 53](https://pypi.python.org/pypi/certbot-dns-route53), + [Cloudflare](https://pypi.python.org/pypi/certbot-dns-cloudflare), + [DigitalOcean](https://pypi.python.org/pypi/certbot-dns-digitalocean), and + [Google Cloud](https://pypi.python.org/pypi/certbot-dns-google) which all + work on Python 2.6, 2.7, and 3.3+. Additionally, we have plugins for + [CloudXNS](https://pypi.python.org/pypi/certbot-dns-cloudxns), + [DNSimple](https://pypi.python.org/pypi/certbot-dns-dnsimple), + [NS1](https://pypi.python.org/pypi/certbot-dns-nsone) which work on Python + 2.7 and 3.3+ (and not 2.6). Currently, there isn't a good way to install + these plugins when using `certbot-auto`, but that should change soon. +* IPv6 support in the standalone plugin. When performing a challenge, the + standalone plugin automatically handles listening for IPv4/IPv6 traffic based + on the configuration of your system. +* A mechanism for keeping your Apache and Nginx SSL/TLS configuration up to + date. When the Apache or Nginx plugins are used, they place SSL/TLS + configuration options in the root of Certbot's config directory + (`/etc/letsencrypt` by default). Now when a new version of these plugins run + on your system, they will automatically update the file to the newest + version if it is unmodified. If you manually modified the file, Certbot will + display a warning giving you a path to the updated file which you can use as + a reference to manually update your modified copy. +* `--http-01-address` and `--tls-sni-01-address` flags for controlling the + address Certbot listens on when using the standalone plugin. +* The command `certbot certificates` that lists certificates managed by Certbot + now performs additional validity checks to notify you if your files have + become corrupted. + +### Changed + +* Messages custom hooks print to `stdout` are now displayed by Certbot when not + running in `--quiet` mode. +* `jwk` and `alg` fields in JWS objects have been moved into the protected + header causing Certbot to more closely follow the latest version of the ACME + spec. + +### Fixed + +* Permissions on renewal configuration files are now properly preserved when + they are updated. +* A bug causing Certbot to display strange defaults in its help output when + using Python <= 2.7.4 has been fixed. +* Certbot now properly handles mixed case domain names found in custom CSRs. +* A number of poorly worded prompts and error messages. + +### Removed + +* Support for OpenSSL 1.0.0 in `certbot-auto` has been removed as we now pin a + newer version of `cryptography` which dropped support for this version. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.15.0+is%3Aclosed + +## 0.14.2 - 2017-05-25 + +### Fixed + +* Certbot 0.14.0 included a bug where Certbot would create a temporary log file +(usually in /tmp) if the program exited during argument parsing. If a user +provided -h/--help/help, --version, or an invalid command line argument, +Certbot would create this temporary log file. This was especially bothersome to +certbot-auto users as certbot-auto runs `certbot --version` internally to see +if the script needs to upgrade causing it to create at least one of these files +on every run. This problem has been resolved. + +More details about this change can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.2+is%3Aclosed + +## 0.14.1 - 2017-05-16 + +### Fixed + +* Certbot now works with configargparse 0.12.0. +* Issues with the Apache plugin and Augeas 1.7+ have been resolved. +* A problem where the Nginx plugin would fail to install certificates on +systems that had the plugin's SSL/TLS options file from 7+ months ago has been +fixed. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.1+is%3Aclosed + +## 0.14.0 - 2017-05-04 + +### Added + +* Python 3.3+ support for all Certbot packages. `certbot-auto` still currently +only supports Python 2, but the `acme`, `certbot`, `certbot-apache`, and +`certbot-nginx` packages on PyPI now fully support Python 2.6, 2.7, and 3.3+. +* Certbot's Apache plugin now handles multiple virtual hosts per file. +* Lockfiles to prevent multiple versions of Certbot running simultaneously. + +### Changed + +* When converting an HTTP virtual host to HTTPS in Apache, Certbot only copies +the virtual host rather than the entire contents of the file it's contained +in. +* The Nginx plugin now includes SSL/TLS directives in a separate file located +in Certbot's configuration directory rather than copying the contents of the +file into every modified `server` block. + +### Fixed + +* Ensure logging is configured before parts of Certbot attempt to log any +messages. +* Support for the `--quiet` flag in `certbot-auto`. +* Reverted a change made in a previous release to make the `acme` and `certbot` +packages always depend on `argparse`. This dependency is conditional again on +the user's Python version. +* Small bugs in the Nginx plugin such as properly handling empty `server` +blocks and setting `server_names_hash_bucket_size` during challenges. + +As always, a more complete list of changes can be found on GitHub: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.14.0+is%3Aclosed + +## 0.13.0 - 2017-04-06 + +### Added + +* `--debug-challenges` now pauses Certbot after setting up challenges for debugging. +* The Nginx parser can now handle all valid directives in configuration files. +* Nginx ciphersuites have changed to Mozilla Intermediate. +* `certbot-auto --no-bootstrap` provides the option to not install OS dependencies. + +### Fixed + +* `--register-unsafely-without-email` now respects `--quiet`. +* Hyphenated renewal parameters are now saved in renewal config files. +* `--dry-run` no longer persists keys and csrs. +* Certbot no longer hangs when trying to start Nginx in Arch Linux. +* Apache rewrite rules no longer double-encode characters. + +A full list of changes is available on GitHub: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.13.0%20is%3Aclosed%20 + +## 0.12.0 - 2017-03-02 + +### Added + +* Certbot now allows non-camelcase Apache VirtualHost names. +* Certbot now allows more log messages to be silenced. + +### Fixed + +* Fixed a regression around using `--cert-name` when getting new certificates + +More information about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.12.0 + +## 0.11.1 - 2017-02-01 + +### Fixed + +* Resolved a problem where Certbot would crash while parsing command line +arguments in some cases. +* Fixed a typo. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/pulls?q=is%3Apr%20milestone%3A0.11.1%20is%3Aclosed + +## 0.11.0 - 2017-02-01 + +### Added + +* When using the standalone plugin while running Certbot interactively +and a required port is bound by another process, Certbot will give you +the option to retry to grab the port rather than immediately exiting. +* You are now able to deactivate your account with the Let's Encrypt +server using the `unregister` subcommand. +* When revoking a certificate using the `revoke` subcommand, you now +have the option to provide the reason the certificate is being revoked +to Let's Encrypt with `--reason`. + +### Changed + +* Providing `--quiet` to `certbot-auto` now silences package manager output. + +### Removed + +* Removed the optional `dnspython` dependency in our `acme` package. +Now the library does not support client side verification of the DNS +challenge. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.11.0+is%3Aclosed + +## 0.10.2 - 2017-01-25 + +### Added + +* If Certbot receives a request with a `badNonce` error, it now +automatically retries the request. Since nonces from Let's Encrypt expire, +this helps people performing the DNS challenge with the `manual` plugin +who may have to wait an extended period of time for their DNS changes to +propagate. + +### Fixed + +* Certbot now saves the `--preferred-challenges` values for renewal. Previously +these values were discarded causing a different challenge type to be used when +renewing certs in some cases. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.2+is%3Aclosed + +## 0.10.1 - 2017-01-13 + +### Fixed + +* Resolve problems where when asking Certbot to update a certificate at +an existing path to include different domain names, the old names would +continue to be used. +* Fix issues successfully running our unit test suite on some systems. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.1+is%3Aclosed + +## 0.10.0 - 2017-01-11 + +## Added + +* Added the ability to customize and automatically complete DNS and HTTP +domain validation challenges with the manual plugin. The flags +`--manual-auth-hook` and `--manual-cleanup-hook` can now be provided +when using the manual plugin to execute commands provided by the user to +perform and clean up challenges provided by the CA. This is best used in +complicated setups where the DNS challenge must be used or Certbot's +existing plugins cannot be used to perform HTTP challenges. For more +information on how this works, see `certbot --help manual`. +* Added a `--cert-name` flag for specifying the name to use for the +certificate in Certbot's configuration directory. Using this flag in +combination with `-d/--domains`, a user can easily request a new +certificate with different domains and save it with the name provided by +`--cert-name`. Additionally, `--cert-name` can be used to select a +certificate with the `certonly` and `run` subcommands so a full list of +domains in the certificate does not have to be provided. +* Added subcommand `certificates` for listing the certificates managed by +Certbot and their properties. +* Added the `delete` subcommand for removing certificates managed by Certbot +from the configuration directory. +* Certbot now supports requesting internationalized domain names (IDNs). +* Hooks provided to Certbot are now saved to be reused during renewal. +If you run Certbot with `--pre-hook`, `--renew-hook`, or `--post-hook` +flags when obtaining a certificate, the provided commands will +automatically be saved and executed again when renewing the certificate. +A pre-hook and/or post-hook can also be given to the `certbot renew` +command either on the command line or in a [configuration +file](https://certbot.eff.org/docs/using.html#configuration-file) to run +an additional command before/after any certificate is renewed. Hooks +will only be run if a certificate is renewed. +* Support Busybox in certbot-auto. + +### Changed + +* Recategorized `-h/--help` output to improve documentation and +discoverability. + +### Removed + +* Removed the ncurses interface. This change solves problems people +were having on many systems, reduces the number of Certbot +dependencies, and simplifies our code. Certbot's only interface now is +the text interface which was available by providing `-t/--text` to +earlier versions of Certbot. + +### Fixed + +* Many small bug fixes. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.10.0is%3Aclosed + +## 0.9.3 - 2016-10-13 + +### Added + +* The Apache plugin uses information about your OS to help determine the +layout of your Apache configuration directory. We added a patch to +ensure this code behaves the same way when testing on different systems +as the tests were failing in some cases. + +### Changed + +* Certbot adopted more conservative behavior about reporting a needed port as +unavailable when using the standalone plugin. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/27?closed=1 + +## 0.9.2 - 2016-10-12 + +### Added + +* Certbot stopped requiring that all possibly required ports are available when +using the standalone plugin. It now only verifies that the ports are available +when they are necessary. + +### Fixed + +* Certbot now verifies that our optional dependencies version matches what is +required by Certbot. +* Certnot now properly copies the `ssl on;` directives as necessary when +performing domain validation in the Nginx plugin. +* Fixed problem where symlinks were becoming files when they were +packaged, causing errors during testing and OS packaging. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/26?closed=1 + +## 0.9.1 - 2016-10-06 + +### Fixed + +* Fixed a bug that was introduced in version 0.9.0 where the command +line flag -q/--quiet wasn't respected in some cases. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/milestone/25?closed=1 + +## 0.9.0 - 2016-10-05 + +### Added + +* Added an alpha version of the Nginx plugin. This plugin fully automates the +process of obtaining and installing certificates with Nginx. +Additionally, it is able to automatically configure security +enhancements such as an HTTP to HTTPS redirect and OCSP stapling. To use +this plugin, you must have the `certbot-nginx` package installed (which +is installed automatically when using `certbot-auto`) and provide +`--nginx` on the command line. This plugin is still in its early stages +so we recommend you use it with some caution and make sure you have a +backup of your Nginx configuration. +* Added support for the `DNS` challenge in the `acme` library and `DNS` in +Certbot's `manual` plugin. This allows you to create DNS records to +prove to Let's Encrypt you control the requested domain name. To use +this feature, include `--manual --preferred-challenges dns` on the +command line. +* Certbot now helps with enabling Extra Packages for Enterprise Linux (EPEL) on +CentOS 6 when using `certbot-auto`. To use `certbot-auto` on CentOS 6, +the EPEL repository has to be enabled. `certbot-auto` will now prompt +users asking them if they would like the script to enable this for them +automatically. This is done without prompting users when using +`letsencrypt-auto` or if `-n/--non-interactive/--noninteractive` is +included on the command line. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.9.0+is%3Aclosed + +## 0.8.1 - 2016-06-14 + +### Added + +* Certbot now preserves a certificate's common name when using `renew`. +* Certbot now saves webroot values for renewal when they are entered interactively. +* Certbot now gracefully reports that the Apache plugin isn't usable when Augeas is not installed. +* Added experimental support for Mageia has been added to `certbot-auto`. + +### Fixed + +* Fixed problems with an invalid user-agent string on OS X. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.1+ + +## 0.8.0 - 2016-06-02 + +### Added + +* Added the `register` subcommand which can be used to register an account +with the Let's Encrypt CA. +* You can now run `certbot register --update-registration` to +change the e-mail address associated with your registration. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue+milestone%3A0.8.0+ + +## 0.7.0 - 2016-05-27 + +### Added + +* Added `--must-staple` to request certificates from Let's Encrypt +with the OCSP must staple extension. +* Certbot now automatically configures OSCP stapling for Apache. +* Certbot now allows requesting certificates for domains found in the common name +of a custom CSR. + +### Fixed + +* Fixed a number of miscellaneous bugs + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=milestone%3A0.7.0+is%3Aissue + +## 0.6.0 - 2016-05-12 + +### Added + +* Versioned the datetime dependency in setup.py. + +### Changed + +* Renamed the client from `letsencrypt` to `certbot`. + +### Fixed + +* Fixed a small json deserialization error. +* Certbot now preserves domain order in generated CSRs. +* Fixed some minor bugs. + +More details about these changes can be found on our GitHub repo: +https://github.com/certbot/certbot/issues?q=is%3Aissue%20milestone%3A0.6.0%20is%3Aclosed%20 + +## 0.5.0 - 2016-04-05 + +### Added + +* Added the ability to use the webroot plugin interactively. +* Added the flags --pre-hook, --post-hook, and --renew-hook which can be used with +the renew subcommand to register shell commands to run in response to +renewal events. Pre-hook commands will be run before any certs are +renewed, post-hook commands will be run after any certs are renewed, +and renew-hook commands will be run after each cert is renewed. If no +certs are due for renewal, no command is run. +* Added a -q/--quiet flag which silences all output except errors. +* Added an --allow-subset-of-domains flag which can be used with the renew +command to prevent renewal failures for a subset of the requested +domains from causing the client to exit. + +### Changed + +* Certbot now uses renewal configuration files. In /etc/letsencrypt/renewal +by default, these files can be used to control what parameters are +used when renewing a specific certificate. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.5.0+is%3Aissue + +## 0.4.2 - 2016-03-03 + +### Fixed + +* Resolved problems encountered when compiling letsencrypt +against the new OpenSSL release. +* Fixed problems encountered when using `letsencrypt renew` with configuration files +from the private beta. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.2 + +## 0.4.1 - 2016-02-29 + +### Fixed + +* Fixed Apache parsing errors encountered with some configurations. +* Fixed Werkzeug dependency problems encountered on some Red Hat systems. +* Fixed bootstrapping failures when using letsencrypt-auto with --no-self-upgrade. +* Fixed problems with parsing renewal config files from private beta. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is:issue+milestone:0.4.1 + +## 0.4.0 - 2016-02-10 + +### Added + +* Added the verb/subcommand `renew` which can be used to renew your existing +certificates as they approach expiration. Running `letsencrypt renew` +will examine all existing certificate lineages and determine if any are +less than 30 days from expiration. If so, the client will use the +settings provided when you previously obtained the certificate to renew +it. The subcommand finishes by printing a summary of which renewals were +successful, failed, or not yet due. +* Added a `--dry-run` flag to help with testing configuration +without affecting production rate limits. Currently supported by the +`renew` and `certonly` subcommands, providing `--dry-run` on the command +line will obtain certificates from the staging server without saving the +resulting certificates to disk. +* Added major improvements to letsencrypt-auto. This script +has been rewritten to include full support for Python 2.6, the ability +for letsencrypt-auto to update itself, and improvements to the +stability, security, and performance of the script. +* Added support for Apache 2.2 to the Apache plugin. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.4.0 + +## 0.3.0 - 2016-01-27 + +### Added + +* Added a non-interactive mode which can be enabled by including `-n` or +`--non-interactive` on the command line. This can be used to guarantee +the client will not prompt when run automatically using cron/systemd. +* Added preparation for the new letsencrypt-auto script. Over the past +couple months, we've been working on increasing the reliability and +security of letsencrypt-auto. A number of changes landed in this +release to prepare for the new version of this script. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.3.0 + +## 0.2.0 - 2016-01-14 + +### Added + +* Added Apache plugin support for non-Debian based systems. Support has been +added for modern Red Hat based systems such as Fedora 23, Red Hat 7, +and CentOS 7 running Apache 2.4. In theory, this plugin should be +able to be configured to run on any Unix-like OS running Apache 2.4. +* Relaxed PyOpenSSL version requirements. This adds support for systems +with PyOpenSSL versions 0.13 or 0.14. +* Improved error messages from the client. + +### Fixed + +* Resolved issues with the Apache plugin enabling an HTTP to HTTPS +redirect on some systems. + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=is%3Aissue+milestone%3A0.2.0 + +## 0.1.1 - 2015-12-15 + +### Added + +* Added a check that avoids attempting to issue for unqualified domain names like +"localhost". + +### Fixed + +* Fixed a confusing UI path that caused some users to repeatedly renew +their certs while experimenting with the client, in some cases hitting +issuance rate limits. +* Fixed numerous Apache configuration parser problems +* Fixed --webroot permission handling for non-root users + +More details about these changes can be found on our GitHub repo: +https://github.com/letsencrypt/letsencrypt/issues?q=milestone%3A0.1.1 diff --git a/certbot/LICENSE.txt b/certbot/LICENSE.txt new file mode 100644 index 000000000..b905dd120 --- /dev/null +++ b/certbot/LICENSE.txt @@ -0,0 +1,205 @@ +Certbot ACME Client +Copyright (c) Electronic Frontier Foundation and others +Licensed Apache Version 2.0 + +The nginx plugin incorporates code from nginxparser +Copyright (c) 2014 Fatih Erikli +Licensed MIT + + +Text of Apache 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. + + +Text of MIT License +=================== +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MANIFEST.in b/certbot/MANIFEST.in similarity index 70% rename from MANIFEST.in rename to certbot/MANIFEST.in index 7f529c7a7..ef91a3e7c 100644 --- a/MANIFEST.in +++ b/certbot/MANIFEST.in @@ -1,9 +1,10 @@ include README.rst include CHANGELOG.md -include CONTRIBUTING.md include LICENSE.txt -include linter_plugin.py recursive-include docs * recursive-include examples * recursive-include certbot/tests/testdata * +recursive-include tests *.py include certbot/ssl-dhparams.pem +global-exclude __pycache__ +global-exclude *.py[cod] diff --git a/certbot/README.rst b/certbot/README.rst new file mode 100644 index 000000000..2c934ce59 --- /dev/null +++ b/certbot/README.rst @@ -0,0 +1,131 @@ +.. This file contains a series of comments that are used to include sections of this README in other files. Do not modify these comments unless you know what you are doing. tag:intro-begin + +Certbot is part of EFF’s effort to encrypt the entire Internet. Secure communication over the Web relies on HTTPS, which requires the use of a digital certificate that lets browsers verify the identity of web servers (e.g., is that really google.com?). Web servers obtain their certificates from trusted third parties called certificate authorities (CAs). Certbot is an easy-to-use client that fetches a certificate from Let’s Encrypt—an open certificate authority launched by the EFF, Mozilla, and others—and deploys it to a web server. + +Anyone who has gone through the trouble of setting up a secure website knows what a hassle getting and maintaining a certificate is. Certbot and Let’s Encrypt can automate away the pain and let you turn on and manage HTTPS with simple commands. Using Certbot and Let's Encrypt is free, so there’s no need to arrange payment. + +How you use Certbot depends on the configuration of your web server. The best way to get started is to use our `interactive guide `_. It generates instructions based on your configuration settings. In most cases, you’ll need `root or administrator access `_ to your web server to run Certbot. + +Certbot is meant to be run directly on your web server, not on your personal computer. If you’re using a hosted service and don’t have direct access to your web server, you might not be able to use Certbot. Check with your hosting provider for documentation about uploading certificates or using certificates issued by Let’s Encrypt. + +Certbot is a fully-featured, extensible client for the Let's +Encrypt CA (or any other CA that speaks the `ACME +`_ +protocol) that can automate the tasks of obtaining certificates and +configuring webservers to use them. This client runs on Unix-based operating +systems. + +To see the changes made to Certbot between versions please refer to our +`changelog `_. + +Until May 2016, Certbot was named simply ``letsencrypt`` or ``letsencrypt-auto``, +depending on install method. Instructions on the Internet, and some pieces of the +software, may still refer to this older name. + +Contributing +------------ + +If you'd like to contribute to this project please read `Developer Guide +`_. + +This project is governed by `EFF's Public Projects Code of Conduct `_. + +.. _installation: + +How to run the client +--------------------- + +The easiest way to install and run Certbot is by visiting `certbot.eff.org`_, +where you can find the correct instructions for many web server and OS +combinations. For more information, see `Get Certbot +`_. + +.. _certbot.eff.org: https://certbot.eff.org/ + +Understanding the client in more depth +-------------------------------------- + +To understand what the client is doing in detail, it's important to +understand the way it uses plugins. Please see the `explanation of +plugins `_ in +the User Guide. + +Links +===== + +.. Do not modify this comment unless you know what you're doing. tag:links-begin + +Documentation: https://certbot.eff.org/docs + +Software project: https://github.com/certbot/certbot + +Notes for developers: https://certbot.eff.org/docs/contributing.html + +Main Website: https://certbot.eff.org + +Let's Encrypt Website: https://letsencrypt.org + +Community: https://community.letsencrypt.org + +ACME spec: http://ietf-wg-acme.github.io/acme/ + +ACME working area in github: https://github.com/ietf-wg-acme/acme + +|build-status| |coverage| |docs| |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 + +.. |docs| image:: https://readthedocs.org/projects/letsencrypt/badge/ + :target: https://readthedocs.org/projects/letsencrypt/ + :alt: Documentation status + +.. |container| image:: https://quay.io/repository/letsencrypt/letsencrypt/status + :target: https://quay.io/repository/letsencrypt/letsencrypt + :alt: Docker Repository on Quay.io + +.. Do not modify this comment unless you know what you're doing. tag:links-end + +System Requirements +=================== + +See https://certbot.eff.org/docs/install.html#system-requirements. + +.. Do not modify this comment unless you know what you're doing. tag:intro-end + +.. Do not modify this comment unless you know what you're doing. tag:features-begin + +Current Features +===================== + +* Supports multiple web servers: + + - apache/2.x + - nginx/0.8.48+ + - webroot (adds files to webroot directories in order to prove control of + domains and obtain certs) + - standalone (runs its own simple webserver to prove you control a domain) + - other server software via `third party plugins `_ + +* The private key is generated locally on your system. +* Can talk to the Let's Encrypt CA or optionally to other ACME + compliant services. +* Can get domain-validated (DV) certificates. +* Can revoke certificates. +* Adjustable RSA key bit-length (2048 (default), 4096, ...). +* Can optionally install a http -> https redirect, so your site effectively + runs https only (Apache only) +* Fully automated. +* Configuration changes are logged and can be reverted. +* Supports an interactive text UI, or can be driven entirely from the + command line. +* Free and Open Source Software, made with Python. + +.. Do not modify this comment unless you know what you're doing. tag:features-end + +For extensive documentation on using and contributing to Certbot, go to https://certbot.eff.org/docs. If you would like to contribute to the project or run the latest code from git, you should read our `developer guide `_. diff --git a/certbot/__init__.py b/certbot/certbot/__init__.py similarity index 76% rename from certbot/__init__.py rename to certbot/certbot/__init__.py index 32ab75aaa..71c7e4e87 100644 --- a/certbot/__init__.py +++ b/certbot/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.36.0.dev0' +__version__ = '1.1.0.dev0' diff --git a/certbot/certbot/_internal/__init__.py b/certbot/certbot/_internal/__init__.py new file mode 100644 index 000000000..45ec6ac9c --- /dev/null +++ b/certbot/certbot/_internal/__init__.py @@ -0,0 +1,6 @@ +""" +Modules internal to Certbot. + +This package contains modules that are not considered part of Certbot's public +API. They may be changed without updating Certbot's major version. +""" diff --git a/certbot/account.py b/certbot/certbot/_internal/account.py similarity index 94% rename from certbot/account.py rename to certbot/certbot/_internal/account.py index bf5c131db..c4ea6ef35 100644 --- a/certbot/account.py +++ b/certbot/certbot/_internal/account.py @@ -6,27 +6,25 @@ import logging import shutil import socket +from cryptography.hazmat.primitives import serialization import josepy as jose import pyrfc3339 import pytz import six import zope.component -from cryptography.hazmat.primitives import serialization from acme import fields as acme_fields from acme import messages - -from certbot import constants from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc +from certbot._internal import constants from certbot.compat import os logger = logging.getLogger(__name__) -class Account(object): # pylint: disable=too-few-public-methods +class Account(object): """ACME protocol registration. :ivar .RegistrationResource regr: Registration Resource @@ -139,8 +137,7 @@ class AccountFileStorage(interfaces.AccountStorage): """ def __init__(self, config): self.config = config - util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions) def _account_dir_path(self, account_id): return self._account_dir_path_for_server_path(account_id, self.config.server_path) @@ -219,9 +216,8 @@ class AccountFileStorage(interfaces.AccountStorage): else: self._symlink_to_accounts_dir(prev_server_path, server_path) return prev_loaded_account - else: - raise errors.AccountNotFound( - "Account at %s does not exist" % account_dir_path) + raise errors.AccountNotFound( + "Account at %s does not exist" % account_dir_path) try: with open(self._regr_path(account_dir_path)) as regr_file: @@ -233,12 +229,7 @@ class AccountFileStorage(interfaces.AccountStorage): except IOError as error: raise errors.AccountStorageError(error) - acc = Account(regr, key, meta) - if acc.id != account_id: - raise errors.AccountStorageError( - "Account ids mismatch (expected: {0}, found: {1}".format( - account_id, acc.id)) - return acc + return Account(regr, key, meta) def load(self, account_id): return self._load_for_server_path(account_id, self.config.server_path) @@ -322,8 +313,7 @@ class AccountFileStorage(interfaces.AccountStorage): def _save(self, account, acme, regr_only): account_dir_path = self._account_dir_path(account.id) - util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions) try: with open(self._regr_path(account_dir_path), "w") as regr_file: regr = account.regr diff --git a/certbot/auth_handler.py b/certbot/certbot/_internal/auth_handler.py similarity index 91% rename from certbot/auth_handler.py rename to certbot/certbot/_internal/auth_handler.py index 14207db4a..e4ad91247 100644 --- a/certbot/auth_handler.py +++ b/certbot/certbot/_internal/auth_handler.py @@ -1,19 +1,20 @@ """ACME AuthHandler.""" +import datetime import logging import time -import datetime import zope.component from acme import challenges +from acme import errors as acme_errors from acme import messages -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, List -# pylint: enable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors -from certbot import error_handler from certbot import interfaces +from certbot._internal import error_handler logger = logging.getLogger(__name__) @@ -28,7 +29,7 @@ class AuthHandler(object): :ivar acme.client.BackwardsCompatibleClientV2 acme_client: ACME client API. :ivar account: Client's Account - :type account: :class:`certbot.account.Account` + :type account: :class:`certbot._internal.account.Account` :ivar list pref_challs: sorted user specified preferred challenges type strings with the most preferred challenge listed first @@ -97,6 +98,31 @@ class AuthHandler(object): return authzrs_validated + def deactivate_valid_authorizations(self, orderr): + # type: (messages.OrderResource) -> Tuple[List, List] + """ + Deactivate all `valid` authorizations in the order, so that they cannot be re-used + in subsequent orders. + :param messages.OrderResource orderr: must have authorizations filled in + :returns: tuple of list of successfully deactivated authorizations, and + list of unsuccessfully deactivated authorizations. + :rtype: tuple + """ + to_deactivate = [authzr for authzr in orderr.authorizations + if authzr.body.status == messages.STATUS_VALID] + deactivated = [] + failed = [] + + for authzr in to_deactivate: + try: + authzr = self.acme.deactivate_authorization(authzr) + deactivated.append(authzr) + except acme_errors.Error as e: + failed.append(authzr) + logger.debug('Failed to deactivate authorization %s: %s', authzr.uri, e) + + return (deactivated, failed) + def _poll_authorizations(self, authzrs, max_retries, best_effort): """ Poll the ACME CA server, to wait for confirmation that authorizations have their challenges @@ -182,9 +208,6 @@ class AuthHandler(object): achalls.extend(self._challenge_factory(authzr, path)) - if any(isinstance(achall.chall, challenges.TLSSNI01) for achall in achalls): - logger.warning("TLS-SNI-01 is deprecated, and will stop working soon.") - return achalls def _get_chall_pref(self, domain): @@ -262,9 +285,8 @@ def challb_to_achall(challb, account_key, domain): challb=challb, domain=domain, account_key=account_key) elif isinstance(chall, challenges.DNS): return achallenges.DNS(challb=challb, domain=domain) - else: - raise errors.Error( - "Received unsupported challenge of type: {0}".format(chall.typ)) + raise errors.Error( + "Received unsupported challenge of type: {0}".format(chall.typ)) def gen_challenge_path(challbs, preferences, combinations): diff --git a/certbot/cert_manager.py b/certbot/certbot/_internal/cert_manager.py similarity index 96% rename from certbot/cert_manager.py rename to certbot/certbot/_internal/cert_manager.py index ab929b597..1def76a3d 100644 --- a/certbot/cert_manager.py +++ b/certbot/certbot/_internal/cert_manager.py @@ -8,14 +8,12 @@ import pytz import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import crypto_util from certbot import errors from certbot import interfaces -from certbot import ocsp -from certbot import storage from certbot import util -from certbot.compat import misc +from certbot._internal import ocsp +from certbot._internal import storage from certbot.compat import os from certbot.display import util as display_util @@ -34,7 +32,7 @@ def update_live_symlinks(config): .. note:: This assumes that the installation is using a Reverter object. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ for renewal_file in storage.renewal_conf_files(config): @@ -44,7 +42,7 @@ def rename_lineage(config): """Rename the specified lineage to the new name. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ disp = zope.component.getUtility(interfaces.IDisplay) @@ -71,7 +69,7 @@ def certificates(config): """Display information about certs configured with Certbot :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` """ parsed_certs = [] parse_failures = [] @@ -106,7 +104,7 @@ def lineage_for_certname(cli_config, certname): """Find a lineage object with name certname.""" configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755) try: renewal_file = storage.renewal_file_for_certname(cli_config, certname) except errors.CertStorageError: @@ -137,7 +135,7 @@ def find_duplicative_certs(config, domains): undefined. :param config: Configuration. - :type config: :class:`certbot.configuration.NamespaceConfig` + :type config: :class:`certbot._internal.configuration.NamespaceConfig` :param domains: List of domain names :type domains: `list` of `str` @@ -244,8 +242,8 @@ def match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func raise errors.Error("No match found for cert-path {0}!".format(cli_config.cert_path[0])) elif len(matched) > 1: raise errors.OverlappingMatchFound() - else: - return matched + return matched + def human_readable_cert_info(config, cert, skip_filter_checks=False): """ Returns a human readable description of info about a RenewableCert object""" @@ -263,7 +261,7 @@ def human_readable_cert_info(config, cert, skip_filter_checks=False): reasons.append('TEST_CERT') if cert.target_expiry <= now: reasons.append('EXPIRED') - if checker.ocsp_revoked(cert.cert, cert.chain): + elif checker.ocsp_revoked(cert): reasons.append('REVOKED') if reasons: @@ -375,7 +373,7 @@ def _search_lineages(cli_config, func, initial_rv, *args): """ configs_dir = cli_config.renewal_configs_dir # Verify the directory is there - util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid()) + util.make_or_verify_dir(configs_dir, mode=0o755) rv = initial_rv for renewal_file in storage.renewal_conf_files(cli_config): diff --git a/certbot/cli.py b/certbot/certbot/_internal/cli.py similarity index 93% rename from certbot/cli.py rename to certbot/certbot/_internal/cli.py index 794242c24..fb3010a4e 100644 --- a/certbot/cli.py +++ b/certbot/certbot/_internal/cli.py @@ -15,22 +15,21 @@ import zope.interface from zope.interface import interfaces as zope_interfaces from acme import challenges -# 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 acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module import certbot -import certbot.plugins.enhancements as enhancements -import certbot.plugins.selection as plugin_selection -from certbot import constants from certbot import crypto_util from certbot import errors -from certbot import hooks from certbot import interfaces from certbot import util +from certbot._internal import constants +from certbot._internal import hooks +from certbot._internal.plugins import disco as plugins_disco +import certbot._internal.plugins.selection as plugin_selection from certbot.compat import os from certbot.display import util as display_util -from certbot.plugins import disco as plugins_disco +import certbot.plugins.enhancements as enhancements logger = logging.getLogger(__name__) @@ -115,7 +114,7 @@ More detailed help: 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 + -h all print a detailed help page including all topics --version print the version number """ @@ -163,24 +162,6 @@ def report_config_interaction(modified, modifiers): VAR_MODIFIERS.setdefault(var, set()).update(modifiers) -def possible_deprecation_warning(config): - "A deprecation warning for users with the old, not-self-upgrading letsencrypt-auto." - if cli_command != LEAUTO: - return - if config.no_self_upgrade: - # users setting --no-self-upgrade might be hanging on a client version like 0.3.0 - # or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't - # need warnings - return - if "CERTBOT_AUTO" not in os.environ: - logger.warning("You are running with an old copy of letsencrypt-auto" - " that does not receive updates, and is less reliable than more" - " recent versions. The letsencrypt client has also been renamed" - " to Certbot. We recommend upgrading to the latest certbot-auto" - " script, or using native OS packages.") - logger.debug("Deprecation warning circumstances: %s / %s", sys.argv[0], os.environ) - - class _Default(object): """A class to use as a default to detect if a value is set by a user""" @@ -306,10 +287,9 @@ def flag_default(name): def config_help(name, hidden=False): """Extract the help message for an `.IConfig` attribute.""" - # pylint: disable=no-member if hidden: return argparse.SUPPRESS - field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute # pylint: disable=no-value-for-parameter + field = interfaces.IConfig.__getitem__(name) # type: zope.interface.interface.Attribute return field.__doc__ @@ -416,11 +396,6 @@ VERB_HELP = [ "usage": "\n\n certbot install --cert-path /path/to/fullchain.pem " " --key-path /path/to/private-key [options]\n\n" }), - ("config_changes", { - "short": "Show changes that Certbot has made to server configurations", - "opts": "Options for controlling which changes are displayed", - "usage": "\n\n certbot config_changes --num NUM [options]\n\n" - }), ("rollback", { "short": "Roll back server conf changes made during certificate installation", "opts": "Options for rolling back server configuration changes", @@ -428,7 +403,7 @@ VERB_HELP = [ }), ("plugins", { "short": "List plugins that are installed and available on your system", - "opts": 'Options for for the "plugins" subcommand', + "opts": 'Options for the "plugins" subcommand', "usage": "\n\n certbot plugins [options]\n\n" }), ("update_symlinks", { @@ -461,11 +436,10 @@ class HelpfulArgumentParser(object): def __init__(self, args, plugins, detect_defaults=False): - from certbot import main + from certbot._internal import main self.VERBS = { "auth": main.certonly, "certonly": main.certonly, - "config_changes": main.config_changes, "run": main.run, "install": main.install, "plugins": main.plugins_cmd, @@ -642,20 +616,25 @@ class HelpfulArgumentParser(object): raise errors.Error( "Parameters --hsts and --auto-hsts cannot be used simultaneously.") - possible_deprecation_warning(parsed_args) - return parsed_args def set_test_server(self, parsed_args): """We have --staging/--dry-run; perform sanity check and set config.server""" - if parsed_args.server not in (flag_default("server"), constants.STAGING_URI): - conflicts = ["--staging"] if parsed_args.staging else [] - conflicts += ["--dry-run"] if parsed_args.dry_run else [] - raise errors.Error("--server value conflicts with {0}".format( - " and ".join(conflicts))) + # 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 | Conflict error | Use | - parsed_args.server = constants.STAGING_URI + 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"]: @@ -694,7 +673,7 @@ class HelpfulArgumentParser(object): parsed_args.actual_csr = (csr, typ) - csr_domains = set([d.lower() for d in domains]) + csr_domains = {d.lower() for d in domains} config_domains = set(parsed_args.domains) if csr_domains != config_domains: raise errors.ConfigurationError( @@ -867,11 +846,11 @@ class HelpfulArgumentParser(object): chosen_topic = "run" if chosen_topic == "all": # Addition of condition closes #6209 (removal of duplicate route53 option). - return dict([(t, True) if t != 'certbot-route53:auth' else (t, False) - for t in self.help_topics]) + return {t: t != 'certbot-route53:auth' for t in self.help_topics} elif not chosen_topic: - return dict([(t, False) for t in self.help_topics]) - return dict([(t, t == chosen_topic) for t in self.help_topics]) + return {t: False for t in self.help_topics} + return {t: t == chosen_topic for t in self.help_topics} + def _add_all_groups(helpful): helpful.add_group("automation", description="Flags for automating execution & other tweaks") @@ -889,7 +868,7 @@ def _add_all_groups(helpful): helpful.add_group(name, description=docs["opts"]) -def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: disable=too-many-statements +def prepare_and_parse_args(plugins, args, detect_defaults=False): """Returns parsed command line arguments. :param .PluginsRegistry plugins: available plugins @@ -900,7 +879,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis """ - # pylint: disable=too-many-statements helpful = HelpfulArgumentParser(args, plugins, detect_defaults) _add_all_groups(helpful) @@ -999,12 +977,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis "certificates. Updates to the Subscriber Agreement will still " "affect you, and will be effective 14 days after posting an " "update to the web site.") - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete following helpful.add - helpful.add( - "register", "--update-registration", action="store_true", - default=flag_default("update_registration"), dest="update_registration", - help=argparse.SUPPRESS) helpful.add( ["register", "update_account", "unregister", "automation"], "-m", "--email", default=flag_default("email"), @@ -1259,20 +1231,6 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis default=flag_default("autorenew"), dest="autorenew", help="Disable auto renewal of certificates.") - helpful.add_deprecated_argument("--agree-dev-preview", 0) - helpful.add_deprecated_argument("--dialog", 0) - - # Deprecation of tls-sni-01 related cli flags - # TODO: remove theses flags completely in few releases - class _DeprecatedTLSSNIAction(util._ShowWarning): # pylint: disable=protected-access - def __call__(self, parser, namespace, values, option_string=None): - super(_DeprecatedTLSSNIAction, self).__call__(parser, namespace, values, option_string) - namespace.https_port = values - helpful.add( - ["testing", "standalone", "apache", "nginx"], "--tls-sni-01-port", - type=int, action=_DeprecatedTLSSNIAction, help=argparse.SUPPRESS) - helpful.add_deprecated_argument("--tls-sni-01-address", 1) - # Populate the command line parameters for new style enhancements enhancements.populate_cli(helpful.add) @@ -1289,10 +1247,7 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis def _create_subparsers(helpful): - helpful.add("config_changes", "--num", type=int, default=flag_default("num"), - help="How many past revisions you want to be displayed") - - from certbot.client import sample_user_agent # avoid import loops + 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 ' @@ -1421,10 +1376,10 @@ def _plugins_parsing(helpful, plugins): 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", "config_changes"], + 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", "config_changes"], + 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", @@ -1562,18 +1517,10 @@ def parse_preferred_challenges(pref_challs): :raises errors.Error: if pref_challs is invalid """ - aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"} + aliases = {"dns": "dns-01", "http": "http-01"} challs = [c.strip() for c in pref_challs] challs = [aliases.get(c, c) for c in challs] - # Ignore tls-sni-01 from the list, and generates a deprecation warning - # TODO: remove this option completely in few releases - if "tls-sni-01" in challs: - logger.warning('TLS-SNI-01 support is deprecated. This value is being dropped from the ' - 'setting of --preferred-challenges and future versions of Certbot will ' - 'error if it is included.') - challs = [chall for chall in challs if chall != "tls-sni-01"] - unrecognized = ", ".join(name for name in challs if name not in challenges.Challenge.TYPES) if unrecognized: diff --git a/certbot/client.py b/certbot/certbot/_internal/client.py similarity index 94% rename from certbot/client.py rename to certbot/certbot/_internal/client.py index 3c4a894ae..9ce741e38 100644 --- a/certbot/client.py +++ b/certbot/certbot/_internal/client.py @@ -3,38 +3,35 @@ import datetime import logging import platform -import OpenSSL -import josepy as jose -import zope.component from cryptography.hazmat.backends import default_backend -# https://github.com/python/typeshed/blob/master/third_party/ -# 2/cryptography/hazmat/primitives/asymmetric/rsa.pyi +# See https://github.com/pyca/cryptography/issues/4275 from cryptography.hazmat.primitives.asymmetric.rsa import generate_private_key # type: ignore +import josepy as jose +import OpenSSL +import zope.component from acme import client as acme_client from acme import crypto_util as acme_crypto_util from acme import errors as acme_errors from acme import messages -from acme.magic_typing import Optional # pylint: disable=unused-import,no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module import certbot -from certbot import account -from certbot import auth_handler -from certbot import cli -from certbot import constants from certbot import crypto_util -from certbot import eff -from certbot import error_handler from certbot import errors from certbot import interfaces -from certbot import reverter -from certbot import storage from certbot import util -from certbot.compat import misc +from certbot._internal import account +from certbot._internal import auth_handler +from certbot._internal import cli +from certbot._internal import constants +from certbot._internal import eff +from certbot._internal import error_handler +from certbot._internal import storage +from certbot._internal.display import enhancements +from certbot._internal.plugins import selection as plugin_selection from certbot.compat import os -from certbot.display import enhancements from certbot.display import ops as display_ops -from certbot.plugins import selection as plugin_selection logger = logging.getLogger(__name__) @@ -227,11 +224,9 @@ def perform_registration(acme, config, tos_cb): "Please ensure it is a valid email and attempt " "registration again." % config.email) raise errors.Error(msg) - else: - config.email = display_ops.get_email(invalid=True) - return perform_registration(acme, config, tos_cb) - else: - raise + config.email = display_ops.get_email(invalid=True) + return perform_registration(acme, config, tos_cb) + raise class Client(object): @@ -363,10 +358,10 @@ class Client(object): return self.obtain_certificate(successful_domains) else: cert, chain = self.obtain_certificate_from_csr(csr, orderr) - return cert, chain, key, csr def _get_order_and_authorizations(self, csr_pem, best_effort): + # type: (str, bool) -> List[messages.OrderResource] """Request a new order and complete its authorizations. :param str csr_pem: A CSR in PEM format. @@ -382,10 +377,19 @@ class Client(object): except acme_errors.WildcardUnsupportedError: raise errors.Error("The currently selected ACME CA endpoint does" " not support issuing wildcard certificates.") + + # For a dry run, ensure we have an order with fresh authorizations + if orderr and self.config.dry_run: + deactivated, failed = self.auth_handler.deactivate_valid_authorizations(orderr) + if deactivated: + logger.debug("Recreating order after authz deactivations") + orderr = self.acme.new_order(csr_pem) + if failed: + logger.warning("Certbot was unable to obtain fresh authorizations for every domain" + ". The dry run will continue, but results may not be accurate.") + authzr = self.auth_handler.handle_authorizations(orderr, best_effort) return orderr.update(authorizations=authzr) - - # pylint: disable=no-member def obtain_and_enroll_certificate(self, domains, certname): """Obtain and enroll certificate. @@ -398,7 +402,7 @@ class Client(object): :param certname: requested name of lineage :type certname: `str` or `None` - :returns: A new :class:`certbot.storage.RenewableCert` instance + :returns: A new :class:`certbot._internal.storage.RenewableCert` instance referred to the enrolled cert lineage, False if the cert could not be obtained, or None if doing a successful dry run. @@ -459,9 +463,7 @@ class Client(object): """ for path in cert_path, chain_path, fullchain_path: - util.make_or_verify_dir( - os.path.dirname(path), 0o755, misc.os_geteuid(), - self.config.strict_permissions) + util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions) cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path) @@ -627,7 +629,7 @@ class Client(object): reporter.add_message( "An error occurred and we failed to restore your config and " "restart your server. Please post to " - "https://community.letsencrypt.org/c/server-config " + "https://community.letsencrypt.org/c/help " "with details about your configuration and this error you received.", reporter.HIGH_PRIORITY) raise @@ -701,20 +703,6 @@ def rollback(default_installer, checkpoints, config, plugins): installer.rollback_checkpoints(checkpoints) installer.restart() - -def view_config_changes(config, num=None): - """View checkpoints and associated configuration changes. - - .. note:: This assumes that the installation is using a Reverter object. - - :param config: Configuration. - :type config: :class:`certbot.interfaces.IConfig` - - """ - rev = reverter.Reverter(config) - rev.recovery_routine() - rev.view_config_changes(num) - def _open_pem_file(cli_arg_path, pem_path): """Open a pem file. diff --git a/certbot/configuration.py b/certbot/certbot/_internal/configuration.py similarity index 96% rename from certbot/configuration.py rename to certbot/certbot/_internal/configuration.py index cc9cb2d98..f3db207db 100644 --- a/certbot/configuration.py +++ b/certbot/certbot/_internal/configuration.py @@ -1,13 +1,13 @@ """Certbot user-supplied configuration.""" import copy +from six.moves.urllib import parse import zope.interface -from six.moves.urllib import parse # pylint: disable=relative-import -from certbot import constants from certbot import errors from certbot import interfaces from certbot import util +from certbot._internal import constants from certbot.compat import misc from certbot.compat import os @@ -20,7 +20,7 @@ class NamespaceConfig(object): :class:`certbot.interfaces.IConfig`. However, note that the following attributes are dynamically resolved using :attr:`~certbot.interfaces.IConfig.work_dir` and relative - paths defined in :py:mod:`certbot.constants`: + paths defined in :py:mod:`certbot._internal.constants`: - `accounts_dir` - `csr_dir` @@ -30,7 +30,7 @@ class NamespaceConfig(object): And the following paths are dynamically resolved using :attr:`~certbot.interfaces.IConfig.config_dir` and relative - paths defined in :py:mod:`certbot.constants`: + paths defined in :py:mod:`certbot._internal.constants`: - `default_archive_dir` - `live_dir` diff --git a/certbot/constants.py b/certbot/certbot/_internal/constants.py similarity index 91% rename from certbot/constants.py rename to certbot/certbot/_internal/constants.py index 5b268e157..9a2220e0b 100644 --- a/certbot/constants.py +++ b/certbot/certbot/_internal/constants.py @@ -4,7 +4,6 @@ import logging import pkg_resources from acme import challenges - from certbot.compat import misc from certbot.compat import os @@ -32,7 +31,6 @@ CLI_DEFAULTS = dict( certname=None, dry_run=False, register_unsafely_without_email=False, - update_registration=False, email=None, eff_email=None, reinstall=False, @@ -147,18 +145,6 @@ RENEWER_DEFAULTS = dict( ) """Defaults for renewer script.""" - -ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] -"""List of possible :class:`certbot.interfaces.IInstaller` -enhancements. - -List of expected options parameters: -- redirect: None -- ensure-http-header: name of header (i.e. Strict-Transport-Security) -- ocsp-stapling: certificate chain file path - -""" - ARCHIVE_DIR = "archive" """Archive directory, relative to `IConfig.config_dir`.""" @@ -169,9 +155,10 @@ ACCOUNTS_DIR = "accounts" """Directory where all accounts are saved.""" LE_REUSE_SERVERS = { - 'acme-v02.api.letsencrypt.org/directory': 'acme-v01.api.letsencrypt.org/directory', - 'acme-staging-v02.api.letsencrypt.org/directory': - 'acme-staging.api.letsencrypt.org/directory' + os.path.normpath('acme-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-v01.api.letsencrypt.org/directory'), + os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'): + os.path.normpath('acme-staging.api.letsencrypt.org/directory') } """Servers that can reuse accounts from other servers.""" diff --git a/certbot/display/__init__.py b/certbot/certbot/_internal/display/__init__.py similarity index 100% rename from certbot/display/__init__.py rename to certbot/certbot/_internal/display/__init__.py diff --git a/certbot/display/completer.py b/certbot/certbot/_internal/display/completer.py similarity index 96% rename from certbot/display/completer.py rename to certbot/certbot/_internal/display/completer.py index 509a1051a..03719862b 100644 --- a/certbot/display/completer.py +++ b/certbot/certbot/_internal/display/completer.py @@ -1,10 +1,11 @@ """Provides Tab completion when prompting users for a path.""" import glob + # readline module is not available on all systems try: import readline except ImportError: - import certbot.display.dummy_readline as readline # type: ignore + import certbot._internal.display.dummy_readline as readline # type: ignore class Completer(object): diff --git a/certbot/display/dummy_readline.py b/certbot/certbot/_internal/display/dummy_readline.py similarity index 100% rename from certbot/display/dummy_readline.py rename to certbot/certbot/_internal/display/dummy_readline.py diff --git a/certbot/display/enhancements.py b/certbot/certbot/_internal/display/enhancements.py similarity index 94% rename from certbot/display/enhancements.py rename to certbot/certbot/_internal/display/enhancements.py index 0f6b6c57d..ce6470708 100644 --- a/certbot/display/enhancements.py +++ b/certbot/certbot/_internal/display/enhancements.py @@ -7,7 +7,6 @@ from certbot import errors from certbot import interfaces from certbot.display import util as display_util - logger = logging.getLogger(__name__) # Define a helper function to avoid verbose code @@ -18,7 +17,7 @@ def ask(enhancement): """Display the enhancement to the user. :param str enhancement: One of the - :class:`certbot.CONFIG.ENHANCEMENTS` enhancements + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` enhancements :returns: True if feature is desired, False otherwise :rtype: bool @@ -50,7 +49,7 @@ def redirect_by_default(): code, selection = util(interfaces.IDisplay).menu( "Please choose whether or not to redirect HTTP traffic to HTTPS, removing HTTP access.", - choices, default=0, + choices, default=1, cli_flag="--redirect / --no-redirect", force_interactive=True) if code != display_util.OK: diff --git a/certbot/eff.py b/certbot/certbot/_internal/eff.py similarity index 98% rename from certbot/eff.py rename to certbot/certbot/_internal/eff.py index 433cdc8cd..586697dbb 100644 --- a/certbot/eff.py +++ b/certbot/certbot/_internal/eff.py @@ -4,9 +4,8 @@ import logging import requests import zope.component -from certbot import constants from certbot import interfaces - +from certbot._internal import constants logger = logging.getLogger(__name__) diff --git a/certbot/error_handler.py b/certbot/certbot/_internal/error_handler.py similarity index 93% rename from certbot/error_handler.py rename to certbot/certbot/_internal/error_handler.py index 1a570e48e..5ca3cc57e 100644 --- a/certbot/error_handler.py +++ b/certbot/certbot/_internal/error_handler.py @@ -4,10 +4,11 @@ import logging import signal import traceback -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Any, Callable, Dict, List, Union -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.compat import os @@ -92,7 +93,7 @@ class ErrorHandler(object): # SystemExit is ignored to properly handle forks that don't exec if exec_type is SystemExit: return retval - elif exec_type is None: + if exec_type is None: if not self.call_on_regular_exit: return retval elif exec_type is errors.SignalExit: diff --git a/certbot/hooks.py b/certbot/certbot/_internal/hooks.py similarity index 95% rename from certbot/hooks.py rename to certbot/certbot/_internal/hooks.py index 34e06e0a3..25addd915 100644 --- a/certbot/hooks.py +++ b/certbot/certbot/_internal/hooks.py @@ -2,12 +2,14 @@ from __future__ import print_function import logging -from subprocess import Popen, PIPE - -from acme.magic_typing import Set, List # pylint: disable=unused-import, no-name-in-module +from subprocess import PIPE +from subprocess import Popen +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import util +from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import util as plug_util @@ -254,7 +256,7 @@ def execute(cmd_name, shell_cmd): cmd_name, shell_cmd, cmd.returncode) if err: logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err) - return (err, out) + return err, out def list_hooks(dir_path): @@ -267,5 +269,5 @@ def list_hooks(dir_path): """ allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path)) - hooks = [path for path in allpaths if util.is_exe(path) and not path.endswith('~')] + hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')] return sorted(hooks) diff --git a/certbot/lock.py b/certbot/certbot/_internal/lock.py similarity index 92% rename from certbot/lock.py rename to certbot/certbot/_internal/lock.py index fad8a5175..7823eaac3 100644 --- a/certbot/lock.py +++ b/certbot/certbot/_internal/lock.py @@ -1,6 +1,12 @@ """Implements file locks compatible with Linux and Windows for locking files and directories.""" import errno import logging + +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from certbot import errors +from certbot.compat import filesystem +from certbot.compat import os + try: import fcntl # pylint: disable=import-error except ImportError: @@ -9,10 +15,7 @@ except ImportError: else: POSIX_MODE = True -from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module -from certbot import errors -from certbot.compat import os logger = logging.getLogger(__name__) @@ -131,7 +134,7 @@ class _UnixLockMechanism(_BaseLockMechanism): """Acquire the lock.""" while self._fd is None: # Open the file - fd = os.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) + fd = filesystem.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) try: self._try_lock(fd) if self._lock_success(fd): @@ -166,14 +169,18 @@ class _UnixLockMechanism(_BaseLockMechanism): :returns: True if the lock was successfully acquired :rtype: bool """ + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since _lock_success is private and called only on Linux. + from os import stat, fstat # pylint: disable=os-module-forbidden try: - stat1 = os.stat(self._path) + stat1 = stat(self._path) except OSError as err: if err.errno == errno.ENOENT: return False raise - stat2 = os.fstat(fd) + stat2 = fstat(fd) # If our locked file descriptor and the file on disk refer to # the same device and inode, they're the same file. return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino @@ -223,11 +230,15 @@ class _WindowsLockMechanism(_BaseLockMechanism): """Acquire the lock""" open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC - fd = os.open(self._path, open_mode, 0o600) + fd = None try: + # Under Windows, filesystem.open will raise directly an EACCES error + # if the lock file is already locked. + fd = filesystem.open(self._path, open_mode, 0o600) msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) except (IOError, OSError) as err: - os.close(fd) + if fd: + os.close(fd) # Anything except EACCES is unexpected. Raise directly the error in that case. if err.errno != errno.EACCES: raise diff --git a/certbot/log.py b/certbot/certbot/_internal/log.py similarity index 95% rename from certbot/log.py rename to certbot/certbot/_internal/log.py index bf444de07..0a492ba55 100644 --- a/certbot/log.py +++ b/certbot/certbot/_internal/log.py @@ -17,16 +17,15 @@ from __future__ import print_function import functools import logging import logging.handlers +import shutil import sys import tempfile import traceback from acme import messages - -from certbot import constants from certbot import errors from certbot import util -from certbot.compat import misc +from certbot._internal import constants from certbot.compat import os # Logging format @@ -41,7 +40,7 @@ def pre_arg_parse_setup(): """Setup logging before command line arguments are parsed. Terminal logging is setup using - `certbot.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as + `certbot._internal.constants.QUIET_LOGGING_LEVEL` so Certbot is as quiet as possible. File logging is setup so that logging messages are buffered in memory. If Certbot exits before `post_arg_parse_setup` is called, these buffered messages are written to a temporary file. @@ -103,9 +102,9 @@ def post_arg_parse_setup(config): root_logger.addHandler(file_handler) root_logger.removeHandler(memory_handler) - temp_handler = memory_handler.target - memory_handler.setTarget(file_handler) - memory_handler.flush(force=True) + temp_handler = memory_handler.target # pylint: disable=no-member + memory_handler.setTarget(file_handler) # pylint: disable=no-member + memory_handler.flush(force=True) # pylint: disable=unexpected-keyword-arg memory_handler.close() temp_handler.close() @@ -134,8 +133,7 @@ def setup_log_file_handler(config, logfile, fmt): """ # TODO: logs might contain sensitive data such as contents of the # private key! #525 - util.set_up_core_dir( - config.logs_dir, 0o700, misc.os_geteuid(), config.strict_permissions) + util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions) log_file_path = os.path.join(config.logs_dir, logfile) try: handler = logging.handlers.RotatingFileHandler( @@ -240,9 +238,10 @@ class TempHandler(logging.StreamHandler): """ def __init__(self): - stream = tempfile.NamedTemporaryFile('w', delete=False) + self._workdir = tempfile.mkdtemp() + self.path = os.path.join(self._workdir, 'log') + stream = util.safe_open(self.path, mode='w', chmod=0o600) super(TempHandler, self).__init__(stream) - self.path = stream.name self._delete = True def emit(self, record): @@ -266,7 +265,7 @@ class TempHandler(logging.StreamHandler): # stream like stderr to be used self.stream.close() if self._delete: - os.remove(self.path) + shutil.rmtree(self._workdir) self._delete = False super(TempHandler, self).close() finally: diff --git a/certbot/main.py b/certbot/certbot/_internal/main.py similarity index 91% rename from certbot/main.py rename to certbot/certbot/_internal/main.py index d5239fa11..72fcfca71 100644 --- a/certbot/main.py +++ b/certbot/certbot/_internal/main.py @@ -12,31 +12,32 @@ import zope.component from acme import errors as acme_errors from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module - import certbot -from certbot import account -from certbot import cert_manager -from certbot import cli -from certbot import client -from certbot import configuration -from certbot import constants from certbot import crypto_util -from certbot import eff from certbot import errors -from certbot import hooks from certbot import interfaces -from certbot import log -from certbot import renewal -from certbot import reporter -from certbot import storage -from certbot import updater from certbot import util +from certbot._internal import account +from certbot._internal import cert_manager +from certbot._internal import cli +from certbot._internal import client +from certbot._internal import configuration +from certbot._internal import constants +from certbot._internal import eff +from certbot._internal import hooks +from certbot._internal import log +from certbot._internal import renewal +from certbot._internal import reporter +from certbot._internal import storage +from certbot._internal import updater +from certbot._internal.plugins import disco as plugins_disco +from certbot._internal.plugins import selection as plug_sel +from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os -from certbot.display import util as display_util, ops as display_ops -from certbot.plugins import disco as plugins_disco +from certbot.display import ops as display_ops +from certbot.display import util as display_util from certbot.plugins import enhancements -from certbot.plugins import selection as plug_sel USER_CANCELLED = ("User chose to cancel the operation and may " "reinvoke the client.") @@ -120,7 +121,7 @@ def _get_and_save_cert(le_client, config, domains=None, certname=None, lineage=N lineage = le_client.obtain_and_enroll_certificate(domains, certname) if lineage is False: raise errors.Error("Certificate could not be obtained") - elif lineage is not None: + if lineage is not None: hooks.deploy_hook(config, lineage.names(), lineage.live_dir) finally: hooks.post_hook(config) @@ -161,19 +162,18 @@ def _handle_subset_cert_request(config, domains, cert): cli_flag="--expand", force_interactive=True): return "renew", cert - else: - reporter_util = zope.component.getUtility(interfaces.IReporter) - reporter_util.add_message( - "To obtain a new certificate that contains these names without " - "replacing your existing certificate for {0}, you must use the " - "--duplicate option.{br}{br}" - "For example:{br}{br}{1} --duplicate {2}".format( - existing, - sys.argv[0], " ".join(sys.argv[1:]), - br=os.linesep - ), - reporter_util.HIGH_PRIORITY) - raise errors.Error(USER_CANCELLED) + reporter_util = zope.component.getUtility(interfaces.IReporter) + reporter_util.add_message( + "To obtain a new certificate that contains these names without " + "replacing your existing certificate for {0}, you must use the " + "--duplicate option.{br}{br}" + "For example:{br}{br}{1} --duplicate {2}".format( + existing, + sys.argv[0], " ".join(sys.argv[1:]), + br=os.linesep + ), + reporter_util.HIGH_PRIORITY) + raise errors.Error(USER_CANCELLED) def _handle_identical_cert_request(config, lineage): @@ -219,7 +219,7 @@ def _handle_identical_cert_request(config, lineage): # skipping the menu for this case. raise errors.Error( "Operation canceled. You may re-run the client.") - elif response[1] == 0: + if response[1] == 0: return "reinstall", lineage elif response[1] == 1: return "renew", lineage @@ -311,23 +311,20 @@ def _find_lineage_for_domains_and_certname(config, domains, certname): """ if not certname: return _find_lineage_for_domains(config, domains) - else: - lineage = cert_manager.lineage_for_certname(config, certname) - if lineage: - if domains: - if set(cert_manager.domains_for_certname(config, certname)) != set(domains): - _ask_user_to_confirm_new_names(config, domains, certname, - lineage.names()) # raises if no - return "renew", lineage - # unnecessarily specified domains or no domains specified - return _handle_identical_cert_request(config, lineage) - else: - if domains: - return "newcert", None - else: - raise errors.ConfigurationError("No certificate with name {0} found. " - "Use -d to specify domains, or run certbot certificates to see " - "possible certificate names.".format(certname)) + lineage = cert_manager.lineage_for_certname(config, certname) + if lineage: + if domains: + if set(cert_manager.domains_for_certname(config, certname)) != set(domains): + _ask_user_to_confirm_new_names(config, domains, certname, + lineage.names()) # raises if no + return "renew", lineage + # unnecessarily specified domains or no domains specified + return _handle_identical_cert_request(config, lineage) + elif domains: + return "newcert", None + raise errors.ConfigurationError("No certificate with name {0} found. " + "Use -d to specify domains, or run certbot certificates to see " + "possible certificate names.".format(certname)) def _get_added_removed(after, before): """Get lists of items removed from `before` @@ -482,7 +479,7 @@ def _determine_account(config): :returns: Account and optionally ACME client API (biproduct of new registration). - :rtype: tuple of :class:`certbot.account.Account` and :class:`acme.client.Client` + :rtype: tuple of :class:`certbot._internal.account.Account` and :class:`acme.client.Client` :raises errors.Error: If unable to register an account with ACME server @@ -531,7 +528,7 @@ def _determine_account(config): return acc, acme -def _delete_if_appropriate(config): # pylint: disable=too-many-locals,too-many-branches +def _delete_if_appropriate(config): """Does the user want to delete their now-revoked certs? If run in non-interactive mode, deleting happens automatically. @@ -667,14 +664,6 @@ def register(config, unused_plugins): :rtype: None or str """ - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the true case of if block - if config.update_registration: - msg = ("Usage 'certbot register --update-registration' is deprecated.\n" - "Please use 'certbot update_account [options]' instead.\n") - logger.warning(msg) - return update_account(config, unused_plugins) - # Portion of _determine_account logic to see whether accounts already # exist or not. account_storage = account.AccountFileStorage(config) @@ -841,12 +830,12 @@ def _populate_from_certname(config): return config def _check_certificate_and_key(config): - if not os.path.isfile(os.path.realpath(config.cert_path)): + if not os.path.isfile(filesystem.realpath(config.cert_path)): raise errors.ConfigurationError("Error while reading certificate from path " - "{0}".format(config.cert_path)) - if not os.path.isfile(os.path.realpath(config.key_path)): + "{0}".format(config.cert_path)) + if not os.path.isfile(filesystem.realpath(config.key_path)): raise errors.ConfigurationError("Error while reading private key from path " - "{0}".format(config.key_path)) + "{0}".format(config.key_path)) def plugins_cmd(config, plugins): """List server software plugins. @@ -960,24 +949,6 @@ def rollback(config, plugins): """ client.rollback(config.installer, config.checkpoints, config, plugins) - -def config_changes(config, unused_plugins): - """Show changes made to server config during installation - - View checkpoints and associated configuration changes. - - :param config: Configuration object - :type config: interfaces.IConfig - - :param unused_plugins: List of plugins (deprecated) - :type unused_plugins: `list` of `str` - - :returns: `None` - :rtype: None - - """ - client.view_config_changes(config, num=config.num) - def update_symlinks(config, unused_plugins): """Update the certificate file family symlinks @@ -1093,7 +1064,7 @@ def revoke(config, unused_plugins): return None -def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals +def run(config, plugins): """Obtain a certificate and install. :param config: Configuration object @@ -1296,18 +1267,14 @@ def make_or_verify_needed_dirs(config): :rtype: None """ - util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), config.strict_permissions) - util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), config.strict_permissions) + util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) + util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions) hook_dirs = (config.renewal_pre_hooks_dir, config.renewal_deploy_hooks_dir, config.renewal_post_hooks_dir,) for hook_dir in hook_dirs: - util.make_or_verify_dir(hook_dir, - uid=misc.os_geteuid(), - strict=config.strict_permissions) + util.make_or_verify_dir(hook_dir, strict=config.strict_permissions) def set_displayer(config): @@ -1333,12 +1300,13 @@ def set_displayer(config): def main(cli_args=None): - """Command line argument parsing and main script execution. + """Run Certbot. - :returns: result of requested command + :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]`` + :type cli_args: `list` of `str` - :raises errors.Error: OS errors triggered by wrong permissions - :raises errors.Error: error if plugin command is not supported + :returns: value for `sys.exit` about the exit status of Certbot + :rtype: `str` or `int` or `None` """ if not cli_args: @@ -1366,7 +1334,7 @@ def main(cli_args=None): make_or_verify_needed_dirs(config) except errors.Error: # Let plugins_cmd be run as un-privileged user. - if config.func != plugins_cmd: + if config.func != plugins_cmd: # pylint: disable=comparison-with-callable raise set_displayer(config) @@ -1377,10 +1345,3 @@ def main(cli_args=None): util.atexit_register(report.print_messages) return config.func(config, plugins) - - -if __name__ == "__main__": - err_string = main() - if err_string: - logger.warning("Exiting with message %s", err_string) - sys.exit(err_string) # pragma: no cover diff --git a/certbot/notify.py b/certbot/certbot/_internal/notify.py similarity index 100% rename from certbot/notify.py rename to certbot/certbot/_internal/notify.py diff --git a/certbot/ocsp.py b/certbot/certbot/_internal/ocsp.py similarity index 91% rename from certbot/ocsp.py rename to certbot/certbot/_internal/ocsp.py index 0e35f023f..65a6d5c17 100644 --- a/certbot/ocsp.py +++ b/certbot/certbot/_internal/ocsp.py @@ -1,27 +1,37 @@ """Tools for checking certificate revocation.""" +from datetime import datetime +from datetime import timedelta import logging import re -from datetime import datetime, timedelta -from subprocess import Popen, PIPE +from subprocess import PIPE +from subprocess import Popen + +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.exceptions import UnsupportedAlgorithm +from cryptography.hazmat.backends import default_backend +# See https://github.com/pyca/cryptography/issues/4275 +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.hazmat.primitives import serialization +import pytz +import requests + +from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from certbot import crypto_util +from certbot import errors +from certbot import util +from certbot._internal.storage import RenewableCert # pylint: disable=unused-import try: # Only cryptography>=2.5 has ocsp module # and signature_hash_algorithm attribute in OCSPResponse class - from cryptography.x509 import ocsp # pylint: disable=import-error + from cryptography.x509 import ocsp # pylint: disable=import-error, ungrouped-imports getattr(ocsp.OCSPResponse, 'signature_hash_algorithm') except (ImportError, AttributeError): # pragma: no cover ocsp = None # type: ignore -from cryptography import x509 -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -import requests -from acme.magic_typing import Optional, Tuple # pylint: disable=unused-import, no-name-in-module -from certbot import crypto_util -from certbot import errors -from certbot import util + logger = logging.getLogger(__name__) @@ -48,21 +58,29 @@ class RevocationChecker(object): else: self.host_args = lambda host: ["Host", host] - def ocsp_revoked(self, cert_path, chain_path): - # type: (str, str) -> bool + def ocsp_revoked(self, cert): + # type: (RenewableCert) -> bool """Get revoked status for a particular cert version. .. todo:: Make this a non-blocking call - :param str cert_path: Path to certificate - :param str chain_path: Path to intermediate cert - :returns: True if revoked; False if valid or the check failed + :param `.storage.RenewableCert` cert: Certificate object + :returns: True if revoked; False if valid or the check failed or cert is expired. :rtype: bool """ + cert_path, chain_path = cert.cert, cert.chain + if self.broken: return False + # Let's Encrypt doesn't update OCSP for expired certificates, + # so don't check OCSP if the cert is expired. + # https://github.com/certbot/certbot/issues/7152 + now = pytz.UTC.fromutc(datetime.utcnow()) + if cert.target_expiry <= now: + return False + url, host = _determine_ocsp_server(cert_path) if not host or not url: return False @@ -278,5 +296,5 @@ def _translate_ocsp_query(cert_path, ocsp_output, ocsp_errors): return True else: logger.warning("Unable to properly parse OCSP output: %s\nstderr:%s", - ocsp_output, ocsp_errors) + ocsp_output, ocsp_errors) return False diff --git a/certbot/certbot/_internal/plugins/__init__.py b/certbot/certbot/_internal/plugins/__init__.py new file mode 100644 index 000000000..7831eab61 --- /dev/null +++ b/certbot/certbot/_internal/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot plugins.""" diff --git a/certbot/plugins/disco.py b/certbot/certbot/_internal/plugins/disco.py similarity index 99% rename from certbot/plugins/disco.py rename to certbot/certbot/_internal/plugins/disco.py index ec2bff8b7..360597474 100644 --- a/certbot/plugins/disco.py +++ b/certbot/certbot/_internal/plugins/disco.py @@ -5,15 +5,13 @@ import logging import pkg_resources import six - import zope.interface import zope.interface.verify from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module -from certbot import constants from certbot import errors from certbot import interfaces - +from certbot._internal import constants logger = logging.getLogger(__name__) diff --git a/certbot/plugins/manual.py b/certbot/certbot/_internal/plugins/manual.py similarity index 99% rename from certbot/plugins/manual.py rename to certbot/certbot/_internal/plugins/manual.py index 4bb11de3f..be6abaad4 100644 --- a/certbot/plugins/manual.py +++ b/certbot/certbot/_internal/plugins/manual.py @@ -4,12 +4,11 @@ import zope.interface from acme import challenges from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges # pylint: disable=unused-import from certbot import errors -from certbot import hooks from certbot import interfaces from certbot import reverter +from certbot._internal import hooks from certbot.compat import os from certbot.plugins import common diff --git a/certbot/plugins/null.py b/certbot/certbot/_internal/plugins/null.py similarity index 94% rename from certbot/plugins/null.py rename to certbot/certbot/_internal/plugins/null.py index 87c0737a5..bf4615497 100644 --- a/certbot/plugins/null.py +++ b/certbot/certbot/_internal/plugins/null.py @@ -7,7 +7,6 @@ import zope.interface from certbot import interfaces from certbot.plugins import common - logger = logging.getLogger(__name__) @@ -49,9 +48,6 @@ class Installer(common.Plugin): def recovery_routine(self): pass # pragma: no cover - def view_config_changes(self): - pass # pragma: no cover - def config_test(self): pass # pragma: no cover diff --git a/certbot/plugins/selection.py b/certbot/certbot/_internal/plugins/selection.py similarity index 96% rename from certbot/plugins/selection.py rename to certbot/certbot/_internal/plugins/selection.py index 39a6b01fc..6d87e4b07 100644 --- a/certbot/plugins/selection.py +++ b/certbot/certbot/_internal/plugins/selection.py @@ -43,7 +43,7 @@ def get_unprepared_installer(config, plugins): Get an unprepared interfaces.IInstaller object. :param certbot.interfaces.IConfig config: Configuration - :param certbot.plugins.disco.PluginsRegistry plugins: + :param certbot._internal.plugins.disco.PluginsRegistry plugins: All plugins registered as entry points. :returns: Unprepared installer plugin or None @@ -64,16 +64,15 @@ def get_unprepared_installer(config, plugins): inst = list(installers.values())[0] logger.debug("Selecting plugin: %s", inst) return inst.init(config) - else: - raise errors.PluginSelectionError( - "Could not select or initialize the requested installer %s." % req_inst) + raise errors.PluginSelectionError( + "Could not select or initialize the requested installer %s." % req_inst) def pick_plugin(config, default, plugins, question, ifaces): """Pick plugin. :param certbot.interfaces.IConfig: Configuration :param str default: Plugin name supplied by user or ``None``. - :param certbot.plugins.disco.PluginsRegistry plugins: + :param certbot._internal.plugins.disco.PluginsRegistry plugins: All plugins registered as entry points. :param str question: Question to be presented to the user in case multiple candidates are found. @@ -175,7 +174,6 @@ def record_chosen_plugins(config, plugins, auth, inst): def choose_configurator_plugins(config, plugins, verb): - # pylint: disable=too-many-branches """ Figure out which configurator we're going to use, modifies config.authenticator and config.installer strings to reflect that choice if @@ -197,7 +195,7 @@ def choose_configurator_plugins(config, plugins, verb): # Which plugins do we need? if verb == "run": need_inst = need_auth = True - from certbot.cli import cli_command + from certbot._internal.cli import cli_command if req_auth in noninstaller_plugins and not req_inst: msg = ('With the {0} plugin, you probably want to use the "certonly" command, eg:{1}' '{1} {2} certonly --{0}{1}{1}' @@ -210,7 +208,7 @@ def choose_configurator_plugins(config, plugins, verb): need_inst = need_auth = False if verb == "certonly": need_auth = True - if verb == "install" or verb == "enhance": + elif verb in ("install", "enhance"): need_inst = True if config.authenticator: logger.warning("Specifying an authenticator doesn't make sense when " @@ -254,7 +252,7 @@ def set_configurator(previously, now): return now -def cli_plugin_requests(config): # pylint: disable=too-many-branches +def cli_plugin_requests(config): """ Figure out which plugins the user requested with CLI and config options @@ -328,7 +326,7 @@ def diagnose_configurator_problem(cfg_type, requested, plugins): "your existing configuration.\nThe error was: {1!r}" .format(requested, plugins[requested].problem)) elif cfg_type == "installer": - from certbot.cli import cli_command + from certbot._internal.cli import cli_command msg = ('Certbot doesn\'t know how to automatically configure the web ' 'server on this system. However, it can still get a certificate for ' 'you. Please run "{0} certonly" to do so. You\'ll need to ' diff --git a/certbot/plugins/standalone.py b/certbot/certbot/_internal/plugins/standalone.py similarity index 93% rename from certbot/plugins/standalone.py rename to certbot/certbot/_internal/plugins/standalone.py index 9723116c1..80421299e 100644 --- a/certbot/plugins/standalone.py +++ b/certbot/certbot/_internal/plugins/standalone.py @@ -11,13 +11,14 @@ import zope.interface from acme import challenges from acme import standalone as acme_standalone -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import DefaultDict, Dict, Set, Tuple, List, Type, TYPE_CHECKING - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import TYPE_CHECKING # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import from certbot import errors from certbot import interfaces - from certbot.plugins import common logger = logging.getLogger(__name__) @@ -75,7 +76,6 @@ class ServerManager(object): servers.serve_forever() # if port == 0, then random free port on OS is taken - # pylint: disable=no-member # both servers, if they exist, have the same port real_port = servers.getsocknames()[0][1] self._instances[real_port] = servers @@ -195,7 +195,7 @@ def _handle_perform_error(error): "the appropriate permissions (for example, you " "aren't running this program as " "root).".format(error.port)) - elif error.socket_error.errno == socket_errors.EADDRINUSE: + if error.socket_error.errno == socket_errors.EADDRINUSE: display = zope.component.getUtility(interfaces.IDisplay) msg = ( "Could not bind TCP port {0} because it is already in " diff --git a/certbot/plugins/webroot.py b/certbot/certbot/_internal/plugins/webroot.py similarity index 90% rename from certbot/plugins/webroot.py rename to certbot/certbot/_internal/plugins/webroot.py index 1c94b34d3..c7737e0d1 100644 --- a/certbot/plugins/webroot.py +++ b/certbot/certbot/_internal/plugins/webroot.py @@ -10,19 +10,21 @@ import zope.component import zope.interface from acme import challenges # pylint: disable=unused-import -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Dict, Set, DefaultDict, List -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import DefaultDict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module from certbot import achallenges # pylint: disable=unused-import -from certbot import cli from certbot import errors from certbot import interfaces +from certbot._internal import cli +from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util from certbot.plugins import common from certbot.plugins import util +from certbot.util import safe_open logger = logging.getLogger(__name__) @@ -133,8 +135,7 @@ to serve all files under specified web root ({0}).""" raise errors.PluginError( "Every requested domain must have a " "webroot when using the webroot plugin.") - else: # code == display_util.OK - return None if index == 0 else known_webroots[index - 1] + return None if index == 0 else known_webroots[index - 1] # code == display_util.OK def _prompt_for_new_webroot(self, domain, allowraise=False): code, webroot = ops.validated_directory( @@ -144,12 +145,10 @@ to serve all files under specified web root ({0}).""" if code == display_util.CANCEL: if not allowraise: return None - else: - raise errors.PluginError( - "Every requested domain must have a " - "webroot when using the webroot plugin.") - else: # code == display_util.OK - return _validate_webroot(webroot) + raise errors.PluginError( + "Every requested domain must have a " + "webroot when using the webroot plugin.") + return _validate_webroot(webroot) # code == display_util.OK def _create_challenge_dirs(self): path_map = self.conf("map") @@ -168,19 +167,19 @@ to serve all files under specified web root ({0}).""" # run as non-root (GH #1795) old_umask = os.umask(0o022) try: - stat_path = os.stat(path) # 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): try: - # This is coupled with the "umask" call above because + # Set owner as parent directory if possible, apply mode for Linux/Windows. + # For Linux, this is coupled with the "umask" call above because # os.mkdir's "mode" parameter may not always work: # https://docs.python.org/3/library/os.html#os.mkdir - os.mkdir(prefix, 0o0755) + filesystem.mkdir(prefix, 0o755) self._created_dirs.append(prefix) - # Set owner as parent directory if possible try: - os.chown(prefix, stat_path.st_uid, stat_path.st_gid) + filesystem.copy_ownership_and_apply_mode( + path, prefix, 0o755, copy_user=True, copy_group=True) except (OSError, AttributeError) as exception: logger.info("Unable to change owner and uid of webroot directory") logger.debug("Error was: %s", exception) @@ -206,7 +205,7 @@ to serve all files under specified web root ({0}).""" old_umask = os.umask(0o022) try: - with open(validation_path, "wb") as validation_file: + with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file: validation_file.write(validation.encode()) finally: os.umask(old_umask) diff --git a/certbot/renewal.py b/certbot/certbot/_internal/renewal.py similarity index 97% rename from certbot/renewal.py rename to certbot/certbot/_internal/renewal.py index 4b4a5a082..0426b2e2d 100644 --- a/certbot/renewal.py +++ b/certbot/certbot/_internal/renewal.py @@ -14,17 +14,16 @@ import six import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -from certbot import cli from certbot import crypto_util from certbot import errors -from certbot import hooks from certbot import interfaces -from certbot import storage -from certbot import updater from certbot import util +from certbot._internal import cli +from certbot._internal import hooks +from certbot._internal import storage +from certbot._internal import updater +from certbot._internal.plugins import disco as plugins_disco from certbot.compat import os -from certbot.plugins import disco as plugins_disco logger = logging.getLogger(__name__) @@ -372,7 +371,7 @@ def _renew_describe_results(config, renew_successes, renew_failures, disp.notification("\n".join(out), wrap=False) -def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many-branches,too-many-statements +def handle_renewal_request(config): """Examine each lineage; renew if due and report results""" # This is trivially False if config.domains is empty @@ -429,12 +428,12 @@ def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many- # XXX: ensure that each call here replaces the previous one zope.component.provideUtility(lineage_config) renewal_candidate.ensure_deployed() - from certbot import main + from certbot._internal import main plugins = plugins_disco.PluginsRegistry.find_all() if should_renew(lineage_config, renewal_candidate): # Apply random sleep upon first renewal if needed if apply_random_sleep: - sleep_time = random.randint(1, 60 * 8) + sleep_time = random.uniform(1, 60 * 8) logger.info("Non-interactive renewal: random delay of %s seconds", sleep_time) time.sleep(sleep_time) @@ -472,5 +471,4 @@ def handle_renewal_request(config): # pylint: disable=too-many-locals,too-many- if renew_failures or parse_failures: raise errors.Error("{0} renew failure(s), {1} parse failure(s)".format( len(renew_failures), len(parse_failures))) - else: - logger.debug("no renewal failures") + logger.debug("no renewal failures") diff --git a/certbot/reporter.py b/certbot/certbot/_internal/reporter.py similarity index 99% rename from certbot/reporter.py rename to certbot/certbot/_internal/reporter.py index e0063d8e5..947f343d4 100644 --- a/certbot/reporter.py +++ b/certbot/certbot/_internal/reporter.py @@ -12,7 +12,6 @@ import zope.interface from certbot import interfaces from certbot import util - logger = logging.getLogger(__name__) diff --git a/certbot/storage.py b/certbot/certbot/_internal/storage.py similarity index 96% rename from certbot/storage.py rename to certbot/certbot/_internal/storage.py index bfec72c40..964515eee 100644 --- a/certbot/storage.py +++ b/certbot/certbot/_internal/storage.py @@ -12,16 +12,17 @@ import pytz import six import certbot -from certbot import cli -from certbot import constants from certbot import crypto_util -from certbot import error_handler from certbot import errors +from certbot import interfaces from certbot import util -from certbot.compat import os +from certbot._internal import cli +from certbot._internal import constants +from certbot._internal import error_handler +from certbot._internal.plugins import disco as plugins_disco from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins import common as plugins_common -from certbot.plugins import disco as plugins_disco logger = logging.getLogger(__name__) @@ -248,7 +249,7 @@ def _relevant(namespaces, option): :rtype: bool """ - from certbot import renewal + from certbot._internal import renewal return (option in renewal.CONFIG_ITEMS or any(option.startswith(namespace) for namespace in namespaces)) @@ -376,8 +377,7 @@ def delete_files(config, certname): logger.debug("Unable to remove %s", archive_path) -class RenewableCert(object): - # pylint: disable=too-many-instance-attributes,too-many-public-methods +class RenewableCert(interfaces.RenewableCert): """Renewable certificate. Represents a lineage of certificates that is under the management of @@ -424,7 +424,7 @@ class RenewableCert(object): """ self.cli_config = cli_config - self.lineagename = lineagename_for_filename(config_filename) + self._lineagename = lineagename_for_filename(config_filename) # self.configuration should be used to read parameters that # may have been chosen based on default values from the @@ -484,6 +484,15 @@ class RenewableCert(object): """Duck type for self.fullchain""" return self.fullchain + @property + def lineagename(self): + """Name given to the certificate lineage. + + :rtype: str + + """ + return self._lineagename + @property def target_expiry(self): """The current target certificate's expiration datetime @@ -859,21 +868,15 @@ class RenewableCert(object): for _, link in previous_links: os.unlink(link) - def names(self, version=None): + def names(self): """What are the subject names of this certificate? - (If no version is specified, use the current version.) - - :param int version: the desired version number :returns: the subject names :rtype: `list` of `str` :raises .CertStorageError: if could not find cert file. """ - if version is None: - target = self.current_target("cert") - else: - target = self.version("cert", version) + target = self.current_target("cert") if target is None: raise errors.CertStorageError("could not find cert file") with open(target) as f: @@ -952,7 +955,6 @@ class RenewableCert(object): @classmethod def new_lineage(cls, lineagename, cert, privkey, chain, cli_config): - # pylint: disable=too-many-locals """Create a new certificate lineage. Attempts to create a certificate lineage -- enrolled for @@ -984,7 +986,7 @@ class RenewableCert(object): for i in (cli_config.renewal_configs_dir, cli_config.default_archive_dir, cli_config.live_dir): if not os.path.exists(i): - os.makedirs(i, 0o700) + filesystem.makedirs(i, 0o700) logger.debug("Creating directory %s.", i) config_file, config_filename = util.unique_lineage_name( cli_config.renewal_configs_dir, lineagename) @@ -1006,16 +1008,14 @@ class RenewableCert(object): config_file.close() raise errors.CertStorageError( "live directory exists for " + lineagename) - os.mkdir(archive) - os.mkdir(live_dir) + filesystem.mkdir(archive) + filesystem.mkdir(live_dir) logger.debug("Archive directory %s and live " "directory %s created.", archive, live_dir) # Put the data into the appropriate files on disk - target = dict([(kind, os.path.join(live_dir, kind + ".pem")) - for kind in ALL_FOUR]) - archive_target = dict([(kind, os.path.join(archive, kind + "1.pem")) - for kind in ALL_FOUR]) + target = {kind: os.path.join(live_dir, kind + ".pem") for kind in ALL_FOUR} + archive_target = {kind: os.path.join(archive, kind + "1.pem") for kind in ALL_FOUR} for kind in ALL_FOUR: os.symlink(_relpath_from_file(archive_target[kind], target[kind]), target[kind]) with open(target["cert"], "wb") as f: @@ -1080,10 +1080,8 @@ class RenewableCert(object): self.cli_config = cli_config target_version = self.next_free_version() - target = dict( - [(kind, - os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version))) - for kind in ALL_FOUR]) + target = {kind: os.path.join(self.archive_dir, "{0}{1}.pem".format(kind, target_version)) + for kind in ALL_FOUR} old_privkey = os.path.join( self.archive_dir, "privkey{0}.pem".format(prior_version)) @@ -1104,13 +1102,11 @@ class RenewableCert(object): with util.safe_open(target["privkey"], "wb", chmod=BASE_PRIVKEY_MODE) as f: logger.debug("Writing new private key to %s.", target["privkey"]) f.write(new_privkey) - # Preserve gid and (mode & 074) from previous privkey in this lineage. - old_mode = stat.S_IMODE(os.stat(old_privkey).st_mode) & \ - (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | \ - stat.S_IROTH) - mode = BASE_PRIVKEY_MODE | old_mode - os.chown(target["privkey"], -1, os.stat(old_privkey).st_gid) - filesystem.chmod(target["privkey"], mode) + # Preserve gid and (mode & MASK_FOR_PRIVATE_KEY_PERMISSIONS) + # from previous privkey in this lineage. + mode = filesystem.compute_private_key_mode(old_privkey, BASE_PRIVKEY_MODE) + filesystem.copy_ownership_and_apply_mode( + old_privkey, target["privkey"], mode, copy_user=False, copy_group=True) # Save everything else with open(target["cert"], "wb") as f: diff --git a/certbot/updater.py b/certbot/certbot/_internal/updater.py similarity index 98% rename from certbot/updater.py rename to certbot/certbot/_internal/updater.py index 58df6fcb4..961436ca5 100644 --- a/certbot/updater.py +++ b/certbot/certbot/_internal/updater.py @@ -3,8 +3,7 @@ import logging from certbot import errors from certbot import interfaces - -from certbot.plugins import selection as plug_sel +from certbot._internal.plugins import selection as plug_sel import certbot.plugins.enhancements as enhancements logger = logging.getLogger(__name__) diff --git a/certbot/achallenges.py b/certbot/certbot/achallenges.py similarity index 97% rename from certbot/achallenges.py rename to certbot/certbot/achallenges.py index 6535a6b63..70588683d 100644 --- a/certbot/achallenges.py +++ b/certbot/certbot/achallenges.py @@ -23,11 +23,9 @@ import josepy as jose from acme import challenges - logger = logging.getLogger(__name__) -# pylint: disable=too-few-public-methods class AnnotatedChallenge(jose.ImmutableMap): """Client annotated challenge. diff --git a/certbot/compat/__init__.py b/certbot/certbot/compat/__init__.py similarity index 100% rename from certbot/compat/__init__.py rename to certbot/certbot/compat/__init__.py diff --git a/certbot/certbot/compat/_path.py b/certbot/certbot/compat/_path.py new file mode 100644 index 000000000..5c5fe460e --- /dev/null +++ b/certbot/certbot/compat/_path.py @@ -0,0 +1,35 @@ +""" +This compat module wraps os.path to forbid some functions. + +isort:skip_file +""" +# pylint: disable=function-redefined +from __future__ import absolute_import + +# First round of wrapping: we import statically all public attributes exposed by the os.path +# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path +# members are available in certbot.compat.path. +from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden + +# Second round of wrapping: we import dynamically all attributes from the os.path module that have +# not yet been imported by the first round (static star import). +import os.path as std_os_path # pylint: disable=os-module-forbidden +import sys as std_sys + +ourselves = std_sys.modules[__name__] +for attribute in dir(std_os_path): + # Check if the attribute does not already exist in our module. It could be internal attributes + # of the module (__name__, __doc__), or attributes from standard os.path already imported with + # `from os.path import *`. + if not hasattr(ourselves, attribute): + setattr(ourselves, attribute, getattr(std_os_path, attribute)) + +# Clean all remaining importables that are not from the core os.path module. +del ourselves, std_os_path, std_sys + + +# Function os.path.realpath is broken on some versions of Python for Windows. +def realpath(*unused_args, **unused_kwargs): + """Method os.path.realpath() is forbidden""" + raise RuntimeError('Usage of os.path.realpath() is forbidden. ' + 'Use certbot.compat.filesystem.realpath() instead.') diff --git a/certbot/certbot/compat/filesystem.py b/certbot/certbot/compat/filesystem.py new file mode 100644 index 000000000..ba4a155e8 --- /dev/null +++ b/certbot/certbot/compat/filesystem.py @@ -0,0 +1,610 @@ +"""Compat module to handle files security on Windows and Linux""" +from __future__ import absolute_import + +import errno +import os # pylint: disable=os-module-forbidden +import stat + +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module + +try: + # pylint: disable=import-error + import ntsecuritycon + import win32security + import win32con + import win32api + import win32file + import pywintypes + import winerror + # pylint: enable=import-error +except ImportError: + POSIX_MODE = True +else: + POSIX_MODE = False + + +def chmod(file_path, mode): + # type: (str, int) -> None + """ + Apply a POSIX mode on given file_path: + * for Linux, the POSIX mode will be directly applied using chmod, + * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for + Certbot context, and applied to the file using kernel calls. + + The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, + is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the + method _generate_windows_flags(). + + :param str file_path: Path of the file + :param int mode: POSIX mode to apply + """ + if POSIX_MODE: + os.chmod(file_path, mode) + else: + _apply_win_mode(file_path, mode) + + +# One could ask why there is no copy_ownership() function, or even a reimplementation +# of os.chown() that would modify the ownership of file without touching the mode itself. +# This is because on Windows, it would require recalculating the existing DACL against +# the new owner, since the DACL is composed of ACEs that targets a specific user, not dynamically +# the current owner of a file. This action would be necessary to keep consistency between +# the POSIX mode applied to the file and the current owner of this file. +# Since copying and editing arbitrary DACL is very difficult, and since we actually know +# the mode to apply at the time the owner of a file should change, it is easier to just +# change the owner, then reapply the known mode, as copy_ownership_and_apply_mode() does. +def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group): + # type: (str, str, int, bool, bool) -> None + """ + Copy ownership (user and optionally group on Linux) from the source to the + destination, then apply given mode in compatible way for Linux and Windows. + This replaces the os.chown command. + :param str src: Path of the source file + :param str dst: Path of the destination file + :param int mode: Permission mode to apply on the destination file + :param bool copy_user: Copy user if `True` + :param bool copy_group: Copy group if `True` on Linux (has no effect on Windows) + """ + if POSIX_MODE: + stats = os.stat(src) + user_id = stats.st_uid if copy_user else -1 + group_id = stats.st_gid if copy_group else -1 + # On Windows, os.chown does not exist. This is checked through POSIX_MODE value, + # but MyPy/PyLint does not know it and raises an error here on Windows. + # We disable specifically the check to fix the issue. + os.chown(dst, user_id, group_id) + elif copy_user: + # There is no group handling in Windows + _copy_win_ownership(src, dst) + + chmod(dst, mode) + + +def check_mode(file_path, mode): + # type: (str, int) -> bool + """ + Check if the given mode matches the permissions of the given file. + On Linux, will make a direct comparison, on Windows, mode will be compared against + the security model. + :param str file_path: Path of the file + :param int mode: POSIX mode to test + :rtype: bool + :return: True if the POSIX mode matches the file permissions + """ + if POSIX_MODE: + return stat.S_IMODE(os.stat(file_path).st_mode) == mode + + return _check_win_mode(file_path, mode) + + +def check_owner(file_path): + # type: (str) -> bool + """ + Check if given file is owned by current user. + :param str file_path: File path to check + :rtype: bool + :return: True if given file is owned by current user, False otherwise. + """ + if POSIX_MODE: + # On Windows, os.getuid does not exist. This is checked through POSIX_MODE value, + # but MyPy/PyLint does not know it and raises an error here on Windows. + # We disable specifically the check to fix the issue. + return os.stat(file_path).st_uid == os.getuid() # type: ignore + + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # Compare sids + return _get_current_user() == user + + +def check_permissions(file_path, mode): + # type: (str, int) -> bool + """ + Check if given file has the given mode and is owned by current user. + :param str file_path: File path to check + :param int mode: POSIX mode to check + :rtype: bool + :return: True if file has correct mode and owner, False otherwise. + """ + return check_owner(file_path) and check_mode(file_path, mode) + + +def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin + # type: (str, int, int) -> int + """ + Wrapper of original os.open function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int flags: Flags to apply on file while opened + :param int mode: POSIX mode to apply on file when opened, + Python defaults will be applied if ``None`` + :returns: the file descriptor to the opened file + :rtype: int + :raise: OSError(errno.EEXIST) if the file already exists and os.O_CREAT & os.O_EXCL are set, + OSError(errno.EACCES) on Windows if the file already exists and is a directory, and + os.O_CREAT is set. + """ + if POSIX_MODE: + # On Linux, invoke os.open directly. + return os.open(file_path, flags, mode) + + # Windows: handle creation of the file atomically with proper permissions. + if flags & os.O_CREAT: + # If os.O_EXCL is set, we will use the "CREATE_NEW", that will raise an exception if + # file exists, matching the API contract of this bit flag. Otherwise, we use + # "CREATE_ALWAYS" that will always create the file whether it exists or not. + disposition = win32con.CREATE_NEW if flags & os.O_EXCL else win32con.CREATE_ALWAYS + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + # We set second parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long + security.SetSecurityDescriptorOwner(user, 0) + # We set first parameter to 1 (`True`) to say that this security descriptor contains + # a DACL. Otherwise second and third parameters are ignored. + # We set third parameter to 0 (`False`) to say that this security descriptor is + # NOT constructed from a default mechanism, but is explicitly set by the user. + # See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long + security.SetSecurityDescriptorDacl(1, dacl, 0) + + handle = None + try: + handle = win32file.CreateFile(file_path, win32file.GENERIC_READ, + win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE, + attributes, disposition, 0, None) + except pywintypes.error as err: + # Handle native windows errors into python errors to be consistent with the API + # of os.open in the situation of a file already existing or locked. + if err.winerror == winerror.ERROR_FILE_EXISTS: + raise OSError(errno.EEXIST, err.strerror) + if err.winerror == winerror.ERROR_SHARING_VIOLATION: + raise OSError(errno.EACCES, err.strerror) + raise err + finally: + if handle: + handle.Close() + + # At this point, the file that did not exist has been created with proper permissions, + # so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to + # avoid a FileExists exception before calling os.open. + return os.open(file_path, flags ^ os.O_CREAT ^ os.O_EXCL) + + # Windows: general case, we call os.open, let exceptions be thrown, then chmod if all is fine. + handle = os.open(file_path, flags) + chmod(file_path, mode) + return handle + + +def makedirs(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.makedirs function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on leaf directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.makedirs(file_path, mode) + + orig_mkdir_fn = os.mkdir + try: + # As we know that os.mkdir is called internally by os.makedirs, we will swap the function in + # os module for the time of makedirs execution on Windows. + os.mkdir = mkdir # type: ignore + return os.makedirs(file_path, mode) + finally: + os.mkdir = orig_mkdir_fn + + +def mkdir(file_path, mode=0o777): + # type: (str, int) -> None + """ + Rewrite of original os.mkdir function, that will ensure on Windows that given mode + is correctly applied. + :param str file_path: The file path to open + :param int mode: POSIX mode to apply on directory when created, Python defaults + will be applied if ``None`` + """ + if POSIX_MODE: + return os.mkdir(file_path, mode) + + attributes = win32security.SECURITY_ATTRIBUTES() + security = attributes.SECURITY_DESCRIPTOR + user = _get_current_user() + dacl = _generate_dacl(user, mode) + security.SetSecurityDescriptorOwner(user, False) + security.SetSecurityDescriptorDacl(1, dacl, 0) + + try: + win32file.CreateDirectory(file_path, attributes) + except pywintypes.error as err: + # Handle native windows error into python error to be consistent with the API + # of os.mkdir in the situation of a directory already existing. + if err.winerror == winerror.ERROR_ALREADY_EXISTS: + raise OSError(errno.EEXIST, err.strerror, file_path, err.winerror) + raise err + + return None + + +def replace(src, dst): + # type: (str, str) -> None + """ + Rename a file to a destination path and handles situations where the destination exists. + :param str src: The current file path. + :param str dst: The new file path. + """ + if hasattr(os, 'replace'): + # Use replace if possible. On Windows, only Python >= 3.4 is supported + # so we can assume that os.replace() is always available for this platform. + getattr(os, 'replace')(src, dst) + else: + # Otherwise, use os.rename() that behaves like os.replace() on Linux. + os.rename(src, dst) + + +def realpath(file_path): + # type: (str) -> str + """ + Find the real path for the given path. This method resolves symlinks, including + recursive symlinks, and is protected against symlinks that creates an infinite loop. + """ + original_path = file_path + + if POSIX_MODE: + path = os.path.realpath(file_path) + if os.path.islink(path): + # If path returned by realpath is still a link, it means that it failed to + # resolve the symlink because of a loop. + # See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + return path + + inspected_paths = [] # type: List[str] + while os.path.islink(file_path): + link_path = file_path + file_path = os.readlink(file_path) + if not os.path.isabs(file_path): + file_path = os.path.join(os.path.dirname(link_path), file_path) + if file_path in inspected_paths: + raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) + inspected_paths.append(file_path) + + return os.path.abspath(file_path) + + +# On Windows is_executable run from an unprivileged shell may claim that a path is +# executable when it is excutable only if run from a privileged shell. This result +# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights +# without taking into consideration if the target user has currently required the +# elevated privileges or not. However this is not a problem since certbot always +# requires to be run under a privileged shell, so the user will always benefit +# from the highest (privileged one) set of permissions on a given file. +def is_executable(path): + # type: (str) -> bool + """ + Is path an executable file? + :param str path: path to test + :return: True if path is an executable file + :rtype: bool + """ + if POSIX_MODE: + return os.path.isfile(path) and os.access(path, os.X_OK) + + return _win_is_executable(path) + + +def has_world_permissions(path): + # type: (str) -> bool + """ + Check if everybody/world has any right (read/write/execute) on a file given its path + :param str path: path to test + :return: True if everybody/world has any right to the file + :rtype: bool + """ + if POSIX_MODE: + return bool(stat.S_IMODE(os.stat(path).st_mode) & stat.S_IRWXO) + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + return bool(dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': win32security.ConvertStringSidToSid('S-1-1-0'), + })) + + +def compute_private_key_mode(old_key, base_mode): + # type: (str, int) -> int + """ + Calculate the POSIX mode to apply to a private key given the previous private key + :param str old_key: path to the previous private key + :param int base_mode: the minimum modes to apply to a private key + :return: the POSIX mode to apply + :rtype: int + """ + if POSIX_MODE: + # On Linux, we keep read/write/execute permissions + # for group and read permissions for everybody. + old_mode = (stat.S_IMODE(os.stat(old_key).st_mode) & + (stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP | stat.S_IROTH)) + return base_mode | old_mode + + # On Windows, the mode returned by os.stat is not reliable, + # so we do not keep any permission from the previous private key. + return base_mode + + +def has_same_ownership(path1, path2): + # type: (str, str) -> bool + """ + Return True if the ownership of two files given their respective path is the same. + On Windows, ownership is checked against owner only, since files do not have a group owner. + :param str path1: path to the first file + :param str path2: path to the second file + :return: True if both files have the same ownership, False otherwise + :rtype: bool + + """ + if POSIX_MODE: + stats1 = os.stat(path1) + stats2 = os.stat(path2) + return (stats1.st_uid, stats1.st_gid) == (stats2.st_uid, stats2.st_gid) + + security1 = win32security.GetFileSecurity(path1, win32security.OWNER_SECURITY_INFORMATION) + user1 = security1.GetSecurityDescriptorOwner() + + security2 = win32security.GetFileSecurity(path2, win32security.OWNER_SECURITY_INFORMATION) + user2 = security2.GetSecurityDescriptorOwner() + + return user1 == user2 + + +def has_min_permissions(path, min_mode): + # type: (str, int) -> bool + """ + Check if a file given its path has at least the permissions defined by the given minimal mode. + On Windows, group permissions are ignored since files do not have a group owner. + :param str path: path to the file to check + :param int min_mode: the minimal permissions expected + :return: True if the file matches the minimal permissions expectations, False otherwise + :rtype: bool + """ + if POSIX_MODE: + st_mode = os.stat(path).st_mode + return st_mode == st_mode | min_mode + + # Resolve symlinks, to get a consistent result with os.stat on Linux, + # that follows symlinks by default. + path = realpath(path) + + # Get owner sid of the file + security = win32security.GetFileSecurity( + path, win32security.OWNER_SECURITY_INFORMATION | win32security.DACL_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + dacl = security.GetSecurityDescriptorDacl() + min_dacl = _generate_dacl(user, min_mode) + + for index in range(min_dacl.GetAceCount()): + min_ace = min_dacl.GetAce(index) + + # On a given ACE, index 0 is the ACE type, 1 is the permission mask, and 2 is the SID. + # See: http://timgolden.me.uk/pywin32-docs/PyACL__GetAce_meth.html + mask = min_ace[1] + user = min_ace[2] + + effective_mask = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': user, + }) + + if effective_mask != effective_mask | mask: + return False + + return True + + +def _win_is_executable(path): + if not os.path.isfile(path): + return False + + security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + mode = dacl.GetEffectiveRightsFromAcl({ + 'TrusteeForm': win32security.TRUSTEE_IS_SID, + 'TrusteeType': win32security.TRUSTEE_IS_USER, + 'Identifier': _get_current_user(), + }) + + return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE + + +def _apply_win_mode(file_path, mode): + """ + This function converts the given POSIX mode into a Windows ACL list, and applies it to the + file given its path. If the given path is a symbolic link, it will resolved to apply the + mode on the targeted file. + """ + file_path = realpath(file_path) + # Get owner sid of the file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) + user = security.GetSecurityDescriptorOwner() + + # New DACL, that will overwrite existing one (including inherited permissions) + dacl = _generate_dacl(user, mode) + + # Apply the new DACL + security.SetSecurityDescriptorDacl(1, dacl, 0) + win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) + + +def _generate_dacl(user_sid, mode): + analysis = _analyze_mode(mode) + + # Get standard accounts from "well-known" sid + # See the list here: + # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems + system = win32security.ConvertStringSidToSid('S-1-5-18') + admins = win32security.ConvertStringSidToSid('S-1-5-32-544') + everyone = win32security.ConvertStringSidToSid('S-1-1-0') + + # New dacl, without inherited permissions + dacl = win32security.ACL() + + # If user is already system or admins, any ACE defined here would be superseded by + # the full control ACE that will be added after. + if user_sid not in [system, admins]: + # Handle user rights + user_flags = _generate_windows_flags(analysis['user']) + if user_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) + + # Handle everybody rights + everybody_flags = _generate_windows_flags(analysis['all']) + if everybody_flags: + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) + + # Handle administrator rights + full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) + dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) + + return dacl + + +def _analyze_mode(mode): + return { + 'user': { + 'read': mode & stat.S_IRUSR, + 'write': mode & stat.S_IWUSR, + 'execute': mode & stat.S_IXUSR, + }, + 'all': { + 'read': mode & stat.S_IROTH, + 'write': mode & stat.S_IWOTH, + 'execute': mode & stat.S_IXOTH, + }, + } + + +def _copy_win_ownership(src, dst): + security_src = win32security.GetFileSecurity(src, win32security.OWNER_SECURITY_INFORMATION) + user_src = security_src.GetSecurityDescriptorOwner() + + security_dst = win32security.GetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION) + # Second parameter indicates, if `False`, that the owner of the file is not provided by some + # default mechanism, but is explicitly set instead. This is obviously what we are doing here. + security_dst.SetSecurityDescriptorOwner(user_src, False) + + win32security.SetFileSecurity(dst, win32security.OWNER_SECURITY_INFORMATION, security_dst) + + +def _generate_windows_flags(rights_desc): + # Some notes about how each POSIX right is interpreted. + # + # For the rights read and execute, we have a pretty bijective relation between + # POSIX flags and their generic counterparts on Windows, so we use them directly + # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). + # + # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a + # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or + # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. + # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS + # substracted of the rights corresponding to POSIX read and POSIX execute. + # + # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, + # so a "Full Control" on the file. + # + # A complete list of the rights defined on NTFS can be found here: + # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders + flag = 0 + if rights_desc['read']: + flag = flag | ntsecuritycon.FILE_GENERIC_READ + if rights_desc['write']: + flag = flag | (ntsecuritycon.FILE_ALL_ACCESS + ^ ntsecuritycon.FILE_GENERIC_READ + ^ ntsecuritycon.FILE_GENERIC_EXECUTE) + if rights_desc['execute']: + flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE + + return flag + + +def _check_win_mode(file_path, mode): + # Resolve symbolic links + file_path = realpath(file_path) + # Get current dacl file + security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION + | win32security.DACL_SECURITY_INFORMATION) + dacl = security.GetSecurityDescriptorDacl() + + # Get current file owner sid + user = security.GetSecurityDescriptorOwner() + + if not dacl: + # No DACL means full control to everyone + # This is not a deterministic permissions set. + return False + + # Calculate the target dacl + ref_dacl = _generate_dacl(user, mode) + + return _compare_dacls(dacl, ref_dacl) + + +def _compare_dacls(dacl1, dacl2): + """ + This method compare the two given DACLs to check if they are identical. + Identical means here that they contains the same set of ACEs in the same order. + """ + return ([dacl1.GetAce(index) for index in range(dacl1.GetAceCount())] == + [dacl2.GetAce(index) for index in range(dacl2.GetAceCount())]) + + +def _get_current_user(): + """ + Return the pySID corresponding to the current user. + """ + # We craft the account_name ourselves instead of calling for instance win32api.GetUserNameEx, + # because this function returns nonsense values when Certbot is run under NT AUTHORITY\SYSTEM. + # To run Certbot under NT AUTHORITY\SYSTEM, you can open a shell using the instructions here: + # https://blogs.technet.microsoft.com/ben_parker/2010/10/27/how-do-i-run-powershell-execommand-prompt-as-the-localsystem-account-on-windows-7/ + account_name = r"{0}\{1}".format(win32api.GetDomainName(), win32api.GetUserName()) + # LookupAccountName() expects the system name as first parameter. By passing None to it, + # we instruct Windows to first search the matching account in the machine local accounts, + # then into the primary domain accounts, if the machine has joined a domain, then finally + # into the trusted domains accounts. This is the preferred lookup mechanism to use in Windows + # if there is no reason to use a specific lookup mechanism. + # See https://docs.microsoft.com/en-us/windows/desktop/api/winbase/nf-winbase-lookupaccountnamea + return win32security.LookupAccountName(None, account_name)[0] diff --git a/certbot/compat/misc.py b/certbot/certbot/compat/misc.py similarity index 79% rename from certbot/compat/misc.py rename to certbot/certbot/compat/misc.py index c61672364..ffe611edb 100644 --- a/certbot/compat/misc.py +++ b/certbot/certbot/compat/misc.py @@ -5,17 +5,22 @@ particular category. from __future__ import absolute_import import select -import stat import sys +from certbot import errors +from certbot.compat import os + try: from win32com.shell import shell as shellwin32 # pylint: disable=import-error POSIX_MODE = False except ImportError: # pragma: no cover POSIX_MODE = True -from certbot import errors -from certbot.compat import os + + +# For Linux: define OS specific standard binary directories +STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else [] + def raise_for_non_administrative_windows_rights(): # type: () -> None @@ -29,22 +34,6 @@ def raise_for_non_administrative_windows_rights(): raise errors.Error('Error, certbot must be run on a shell with administrative rights.') -def os_geteuid(): - """ - Get current user uid - - :returns: The current user uid. - :rtype: int - - """ - try: - # Linux specific - return os.geteuid() - except AttributeError: - # Windows specific - return 0 - - def readline_with_timeout(timeout, prompt): # type: (float, str) -> str """ @@ -75,16 +64,6 @@ def readline_with_timeout(timeout, prompt): return sys.stdin.readline() -def compare_file_modes(mode1, mode2): - """Return true if the two modes can be considered as equals for this platform""" - if os.name != 'nt': - # Linux specific: standard compare - return oct(stat.S_IMODE(mode1)) == oct(stat.S_IMODE(mode2)) - # Windows specific: most of mode bits are ignored on Windows. Only check user R/W rights. - return (stat.S_IMODE(mode1) & stat.S_IREAD == stat.S_IMODE(mode2) & stat.S_IREAD - and stat.S_IMODE(mode1) & stat.S_IWRITE == stat.S_IMODE(mode2) & stat.S_IWRITE) - - WINDOWS_DEFAULT_FOLDERS = { 'config': 'C:\\Certbot', 'work': 'C:\\Certbot\\lib', diff --git a/certbot/compat/os.py b/certbot/certbot/compat/os.py similarity index 50% rename from certbot/compat/os.py rename to certbot/certbot/compat/os.py index f704055f4..0231dd51a 100644 --- a/certbot/compat/os.py +++ b/certbot/certbot/compat/os.py @@ -2,6 +2,8 @@ This compat modules is a wrapper of the core os module that forbids usage of specific operations (e.g. chown, chmod, getuid) that would be harmful to the Windows file security model of Certbot. This module is intended to replace standard os module throughout certbot projects (except acme). + +isort:skip_file """ # pylint: disable=function-redefined from __future__ import absolute_import @@ -17,6 +19,7 @@ from os import * # type: ignore # pylint: disable=wildcard-import,unused-wildc # and so not in `from os import *`. import os as std_os # pylint: disable=os-module-forbidden import sys as std_sys + ourselves = std_sys.modules[__name__] for attribute in dir(std_os): # Check if the attribute does not already exist in our module. It could be internal attributes @@ -25,7 +28,9 @@ for attribute in dir(std_os): if not hasattr(ourselves, attribute): setattr(ourselves, attribute, getattr(std_os, attribute)) -# Similar to os.path, allow certbot.compat.os.path to behave as a module +# Import our internal path module, then allow certbot.compat.os.path +# to behave as a module (similarly to os.path). +from certbot.compat import _path as path # type: ignore # pylint: disable=wrong-import-position std_sys.modules[__name__ + '.path'] = path # Clean all remaining importables that are not from the core os module. @@ -45,12 +50,51 @@ del ourselves, std_os, std_sys # Basically, it states that appropriate permissions will be set for the owner, nothing for the # group, appropriate permissions for the "Everyone" group, and all permissions to the # "Administrators" group + "System" user, as they can do everything anyway. -def chmod(*unused_args, **unused_kwargs): # pylint: disable=function-redefined +def chmod(*unused_args, **unused_kwargs): """Method os.chmod() is forbidden""" raise RuntimeError('Usage of os.chmod() is forbidden. ' 'Use certbot.compat.filesystem.chmod() instead.') +# Because uid is not a concept on Windows, chown is useless. In fact, it is not even available +# on Python for Windows. So to be consistent on both platforms for Certbot, this method is +# always forbidden. +def chown(*unused_args, **unused_kwargs): + """Method os.chown() is forbidden""" + raise RuntimeError('Usage of os.chown() is forbidden.' + 'Use certbot.compat.filesystem.copy_ownership_and_apply_mode() instead.') + + +# The os.open function on Windows has the same effect as a call to os.chown concerning the file +# modes: these modes lack the correct control over the permissions given to the file. Instead, +# filesystem.open invokes the Windows native API `CreateFile` to ensure that permissions are +# atomically set in case of file creation, or invokes filesystem.chmod to properly set the +# permissions for the other cases. +def open(*unused_args, **unused_kwargs): + """Method os.open() is forbidden""" + raise RuntimeError('Usage of os.open() is forbidden. ' + 'Use certbot.compat.filesystem.open() instead.') + + +# Very similarly to os.open, os.mkdir has the same effects on Windows and creates an unsecured +# folder. So a similar mitigation to security.chmod is provided on this platform. +def mkdir(*unused_args, **unused_kwargs): + """Method os.mkdir() is forbidden""" + raise RuntimeError('Usage of os.mkdir() is forbidden. ' + 'Use certbot.compat.filesystem.mkdir() instead.') + + +# As said above, os.makedirs would call the original os.mkdir function recursively on Windows, +# creating the same flaws for every actual folder created. This method is modified to ensure +# that our modified os.mkdir is called on Windows, by monkey patching temporarily the mkdir method +# on the original os module, executing the modified logic to correctly protect newly created +# folders, then restoring original mkdir method in the os module. +def makedirs(*unused_args, **unused_kwargs): + """Method os.makedirs() is forbidden""" + raise RuntimeError('Usage of os.makedirs() is forbidden. ' + 'Use certbot.compat.filesystem.makedirs() instead.') + + # Because of the blocking strategy on file handlers on Windows, rename does not behave as expected # with POSIX systems: an exception will be raised if dst already exists. def rename(*unused_args, **unused_kwargs): @@ -65,3 +109,30 @@ def replace(*unused_args, **unused_kwargs): """Method os.replace() is forbidden""" raise RuntimeError('Usage of os.replace() is forbidden. ' 'Use certbot.compat.filesystem.replace() instead.') + + +# Results given by os.access are inconsistent or partial on Windows, because this platform is not +# following the POSIX approach. +def access(*unused_args, **unused_kwargs): + """Method os.access() is forbidden""" + raise RuntimeError('Usage of os.access() is forbidden. ' + 'Use certbot.compat.filesystem.check_mode() or ' + 'certbot.compat.filesystem.is_executable() instead.') + + +# On Windows os.stat call result is inconsistent, with a lot of flags that are not set or +# meaningless. We need to use specialized functions from the certbot.compat.filesystem module. +def stat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.stat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') + + +# Method os.fstat has the same problem than os.stat, since it is the same function, +# but accepting a file descriptor instead of a path. +def fstat(*unused_args, **unused_kwargs): + """Method os.stat() is forbidden""" + raise RuntimeError('Usage of os.fstat() is forbidden. ' + 'Use certbot.compat.filesystem functions instead ' + '(eg. has_min_permissions, has_same_ownership).') diff --git a/certbot/crypto_util.py b/certbot/certbot/crypto_util.py similarity index 93% rename from certbot/crypto_util.py rename to certbot/certbot/crypto_util.py index 281a76668..9aae75991 100644 --- a/certbot/crypto_util.py +++ b/certbot/certbot/crypto_util.py @@ -8,12 +8,7 @@ import hashlib import logging import warnings -import pyrfc3339 -import six -import zope.component -from OpenSSL import SSL # type: ignore -from OpenSSL import crypto -# https://github.com/python/typeshed/tree/master/third_party/2/cryptography +# See https://github.com/pyca/cryptography/issues/4275 from cryptography import x509 # type: ignore from cryptography.exceptions import InvalidSignature from cryptography.hazmat.backends import default_backend @@ -21,14 +16,17 @@ from cryptography.hazmat.primitives.asymmetric.ec import ECDSA from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePublicKey from cryptography.hazmat.primitives.asymmetric.padding import PKCS1v15 from cryptography.hazmat.primitives.asymmetric.rsa import RSAPublicKey +from OpenSSL import crypto +from OpenSSL import SSL # type: ignore +import pyrfc3339 +import six +import zope.component from acme import crypto_util as acme_crypto_util from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot import interfaces from certbot import util -from certbot.compat import misc from certbot.compat import os logger = logging.getLogger(__name__) @@ -61,8 +59,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"): config = zope.component.getUtility(interfaces.IConfig) # Save file - util.make_or_verify_dir(key_dir, 0o700, misc.os_geteuid(), - config.strict_permissions) + util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions) key_f, key_path = util.unique_file( os.path.join(key_dir, keyname), 0o600, "wb") with key_f: @@ -92,8 +89,7 @@ def init_save_csr(privkey, names, path): privkey.pem, names, must_staple=config.must_staple) # Save CSR - util.make_or_verify_dir(path, 0o755, misc.os_geteuid(), - config.strict_permissions) + util.make_or_verify_dir(path, 0o755, config.strict_permissions) csr_f, csr_filename = util.unique_file( os.path.join(path, "csr-certbot.pem"), 0o644, "wb") with csr_f: @@ -216,26 +212,28 @@ def verify_renewable_cert(renewable_cert): 2. That fullchain matches cert and chain when concatenated. 3. Check that the private key matches the certificate. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If verification fails. """ verify_renewable_cert_sig(renewable_cert) verify_fullchain(renewable_cert) - verify_cert_matches_priv_key(renewable_cert.cert, renewable_cert.privkey) + verify_cert_matches_priv_key(renewable_cert.cert_path, renewable_cert.key_path) def verify_renewable_cert_sig(renewable_cert): - """Verifies the signature of a `.storage.RenewableCert` object. + """Verifies the signature of a RenewableCert object. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If signature verification fails. """ try: - with open(renewable_cert.chain, 'rb') as chain_file: # type: IO[bytes] + with open(renewable_cert.chain_path, 'rb') as chain_file: # type: IO[bytes] chain = x509.load_pem_x509_certificate(chain_file.read(), default_backend()) - with open(renewable_cert.cert, 'rb') as cert_file: # type: IO[bytes] + with open(renewable_cert.cert_path, 'rb') as cert_file: # type: IO[bytes] cert = x509.load_pem_x509_certificate(cert_file.read(), default_backend()) pk = chain.public_key() with warnings.catch_warnings(): @@ -243,7 +241,7 @@ def verify_renewable_cert_sig(renewable_cert): cert.signature_hash_algorithm) except (IOError, ValueError, InvalidSignature) as e: error_str = "verifying the signature of the cert located at {0} has failed. \ - Details: {1}".format(renewable_cert.cert, e) + Details: {1}".format(renewable_cert.cert_path, e) logger.exception(error_str) raise errors.Error(error_str) @@ -304,16 +302,17 @@ def verify_cert_matches_priv_key(cert_path, key_path): def verify_fullchain(renewable_cert): """ Verifies that fullchain is indeed cert concatenated with chain. - :param `.storage.RenewableCert` renewable_cert: cert to verify + :param renewable_cert: cert to verify + :type renewable_cert: certbot.interfaces.RenewableCert :raises errors.Error: If cert and chain do not combine to fullchain. """ try: - with open(renewable_cert.chain) as chain_file: # type: IO[str] + with open(renewable_cert.chain_path) as chain_file: # type: IO[str] chain = chain_file.read() - with open(renewable_cert.cert) as cert_file: # type: IO[str] + with open(renewable_cert.cert_path) as cert_file: # type: IO[str] cert = cert_file.read() - with open(renewable_cert.fullchain) as fullchain_file: # type: IO[str] + with open(renewable_cert.fullchain_path) as fullchain_file: # type: IO[str] fullchain = fullchain_file.read() if (cert + chain) != fullchain: error_str = "fullchain does not match cert + chain for {0}!" diff --git a/certbot/certbot/display/__init__.py b/certbot/certbot/display/__init__.py new file mode 100644 index 000000000..9d39dce92 --- /dev/null +++ b/certbot/certbot/display/__init__.py @@ -0,0 +1 @@ +"""Certbot display utilities.""" diff --git a/certbot/display/ops.py b/certbot/certbot/display/ops.py similarity index 97% rename from certbot/display/ops.py rename to certbot/certbot/display/ops.py index b5f3655fe..92b09d6a1 100644 --- a/certbot/display/ops.py +++ b/certbot/certbot/display/ops.py @@ -60,11 +60,10 @@ def get_email(invalid=False, optional=True): raise errors.Error( "An e-mail address or " "--register-unsafely-without-email must be provided.") - else: - raise errors.Error("An e-mail address must be provided.") - elif util.safe_email(email): + raise errors.Error("An e-mail address must be provided.") + if util.safe_email(email): return email - elif suggest_unsafe: + if suggest_unsafe: msg += unsafe_suggestion suggest_unsafe = False # add this message at most once @@ -75,7 +74,7 @@ def choose_account(accounts): """Choose an account. :param list accounts: Containing at least one - :class:`~certbot.account.Account` + :class:`~certbot._internal.account.Account` """ # Note this will get more complicated once we start recording authorizations @@ -292,7 +291,7 @@ def _gen_ssl_lab_urls(domains): def _gen_https_names(domains): """Returns a string of the https domains. - Domains are formatted nicely with https:// prepended to each. + Domains are formatted nicely with ``https://`` prepended to each. :param list domains: Each domain is a 'str' diff --git a/certbot/display/util.py b/certbot/certbot/display/util.py similarity index 96% rename from certbot/display/util.py rename to certbot/certbot/display/util.py index 91f3bc33c..ba2dd4ecf 100644 --- a/certbot/display/util.py +++ b/certbot/certbot/display/util.py @@ -5,12 +5,12 @@ import textwrap import zope.interface -from certbot import constants from certbot import errors from certbot import interfaces +from certbot._internal import constants +from certbot._internal.display import completer from certbot.compat import misc from certbot.compat import os -from certbot.display import completer logger = logging.getLogger(__name__) @@ -89,7 +89,6 @@ def input_with_timeout(prompt=None, timeout=36000.0): @zope.interface.implementer(interfaces.IDisplay) class FileDisplay(object): """File-based display.""" - # pylint: disable=too-many-arguments # see https://github.com/certbot/certbot/issues/3915 def __init__(self, outfile, force_interactive): @@ -114,7 +113,7 @@ class FileDisplay(object): message = _wrap_lines(message) self.outfile.write( "{line}{frame}{line}{msg}{line}{frame}{line}".format( - line=os.linesep, frame=SIDE_FRAME, msg=message)) + line='\n', frame=SIDE_FRAME, msg=message)) self.outfile.flush() if pause: if self._can_interact(force_interactive): @@ -178,7 +177,7 @@ class FileDisplay(object): message = _wrap_lines("%s (Enter 'c' to cancel):" % message) + " " ans = input_with_timeout(message) - if ans == "c" or ans == "C": + if ans in ("c", "C"): return CANCEL, "-1" return OK, ans @@ -259,10 +258,9 @@ class FileDisplay(object): selected_tags = self._scrub_checklist_input(indices, tags) if selected_tags: return code, selected_tags - else: - self.outfile.write( - "** Error - Invalid selection **%s" % os.linesep) - self.outfile.flush() + self.outfile.write( + "** Error - Invalid selection **%s" % os.linesep) + self.outfile.flush() else: return code, [] @@ -283,18 +281,17 @@ class FileDisplay(object): # assert_valid_call(prompt, default, cli_flag, force_interactive) if self._can_interact(force_interactive): return False - elif default is None: + if default is None: msg = "Unable to get an answer for the question:\n{0}".format(prompt) if cli_flag: msg += ( "\nYou can provide an answer on the " "command line with the {0} flag.".format(cli_flag)) raise errors.Error(msg) - else: - logger.debug( - "Falling back to default %s for the prompt:\n%s", - default, prompt) - return True + logger.debug( + "Falling back to default %s for the prompt:\n%s", + default, prompt) + return True def _can_interact(self, force_interactive): """Can we safely interact with the user? @@ -309,7 +306,7 @@ class FileDisplay(object): if (self.force_interactive or force_interactive or sys.stdin.isatty() and self.outfile.isatty()): return True - elif not self.skipped_interaction: + if not self.skipped_interaction: logger.warning( "Skipped user interaction because Certbot doesn't appear to " "be running in a terminal. You should probably include " @@ -482,7 +479,7 @@ class NoninteractiveDisplay(object): def menu(self, message, choices, ok_label=None, cancel_label=None, help_label=None, default=None, cli_flag=None, **unused_kwargs): - # pylint: disable=unused-argument,too-many-arguments + # pylint: disable=unused-argument """Avoid displaying a menu. :param str message: title of menu diff --git a/certbot/errors.py b/certbot/certbot/errors.py similarity index 100% rename from certbot/errors.py rename to certbot/certbot/errors.py diff --git a/certbot/interfaces.py b/certbot/certbot/interfaces.py similarity index 94% rename from certbot/interfaces.py rename to certbot/certbot/interfaces.py index 25037a332..e96712d23 100644 --- a/certbot/interfaces.py +++ b/certbot/certbot/interfaces.py @@ -1,10 +1,10 @@ """Certbot client interfaces.""" import abc + import six import zope.interface # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class -# pylint: disable=too-few-public-methods @six.add_metaclass(abc.ABCMeta) @@ -295,10 +295,10 @@ class IInstaller(IPlugin): :param str domain: domain for which to provide enhancement :param str enhancement: An enhancement as defined in - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :param options: Flexible options parameter for enhancement. Check documentation of - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` for expected options for each enhancement. :raises .PluginError: If Enhancement is not supported, or if @@ -310,7 +310,7 @@ class IInstaller(IPlugin): """Returns a `collections.Iterable` of supported enhancements. :returns: supported enhancements which should be a subset of - :const:`~certbot.constants.ENHANCEMENTS` + :const:`~certbot.plugins.enhancements.ENHANCEMENTS` :rtype: :class:`collections.Iterable` of :class:`str` """ @@ -355,13 +355,6 @@ class IInstaller(IPlugin): """ - def view_config_changes(): # type: ignore - """Display all of the LE config changes. - - :raises .PluginError: when config changes cannot be parsed - - """ - def config_test(): # type: ignore """Make sure the configuration is valid. @@ -379,7 +372,6 @@ class IInstaller(IPlugin): class IDisplay(zope.interface.Interface): """Generic display.""" - # pylint: disable=too-many-arguments # see https://github.com/certbot/certbot/issues/3915 def notification(message, pause, wrap=True, force_interactive=False): @@ -541,6 +533,62 @@ class IReporter(zope.interface.Interface): """Prints messages to the user and clears the message queue.""" +@six.add_metaclass(abc.ABCMeta) +class RenewableCert(object): + """Interface to a certificate lineage.""" + + @abc.abstractproperty + def cert_path(self): + """Path to the certificate file. + + :rtype: str + + """ + + @abc.abstractproperty + def key_path(self): + """Path to the private key file. + + :rtype: str + + """ + + @abc.abstractproperty + def chain_path(self): + """Path to the certificate chain file. + + :rtype: str + + """ + + @abc.abstractproperty + def fullchain_path(self): + """Path to the full chain file. + + The full chain is the certificate file plus the chain file. + + :rtype: str + + """ + + @abc.abstractproperty + def lineagename(self): + """Name given to the certificate lineage. + + :rtype: str + + """ + + @abc.abstractmethod + def names(self): + """What are the subject names of this certificate? + + :returns: the subject names + :rtype: `list` of `str` + :raises .CertStorageError: if could not find cert file. + + """ + # Updater interfaces # # When "certbot renew" is run, Certbot will iterate over each lineage and check @@ -579,7 +627,7 @@ class GenericUpdater(object): This method is called once for each lineage. :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert + :type lineage: RenewableCert """ @@ -608,6 +656,6 @@ class RenewDeployer(object): This method is called once for each lineage renewed :param lineage: Certificate lineage object - :type lineage: storage.RenewableCert + :type lineage: RenewableCert """ diff --git a/certbot/certbot/main.py b/certbot/certbot/main.py new file mode 100644 index 000000000..b2fb1dbb7 --- /dev/null +++ b/certbot/certbot/main.py @@ -0,0 +1,15 @@ +"""Certbot main public entry point.""" +from certbot._internal import main as internal_main + + +def main(cli_args=None): + """Run Certbot. + + :param cli_args: command line to Certbot, defaults to ``sys.argv[1:]`` + :type cli_args: `list` of `str` + + :returns: value for `sys.exit` about the exit status of Certbot + :rtype: `str` or `int` or `None` + + """ + return internal_main.main(cli_args) diff --git a/certbot/certbot/plugins/__init__.py b/certbot/certbot/plugins/__init__.py new file mode 100644 index 000000000..7831eab61 --- /dev/null +++ b/certbot/certbot/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot plugins.""" diff --git a/certbot/plugins/common.py b/certbot/certbot/plugins/common.py similarity index 83% rename from certbot/plugins/common.py rename to certbot/certbot/plugins/common.py index 820e86a13..6fa1e76f8 100644 --- a/certbot/plugins/common.py +++ b/certbot/certbot/plugins/common.py @@ -2,25 +2,23 @@ import logging import re import shutil +import sys import tempfile +import warnings -import OpenSSL +from josepy import util as jose_util import pkg_resources import zope.interface -from josepy import util as jose_util - from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import achallenges # pylint: disable=unused-import -from certbot import constants from certbot import crypto_util from certbot import errors from certbot import interfaces from certbot import reverter -from certbot import util -from certbot.compat import os +from certbot._internal import constants from certbot.compat import filesystem +from certbot.compat import os from certbot.plugins.storage import PluginStorage logger = logging.getLogger(__name__) @@ -35,6 +33,7 @@ def dest_namespace(name): """ArgumentParser dest namespace (prefix of all destinations).""" return name.replace("-", "_") + "_" + private_ips_regex = re.compile( r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|" r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)") @@ -190,18 +189,6 @@ class Installer(Plugin): except errors.ReverterError as err: raise errors.PluginError(str(err)) - def view_config_changes(self): - """Show all of the configuration changes that have taken place. - - :raises .errors.PluginError: If there is a problem while processing - the checkpoints directories. - - """ - try: - self.reverter.view_config_changes() - except errors.ReverterError as err: - raise errors.PluginError(str(err)) - @property def ssl_dhparams(self): """Full absolute path to ssl_dhparams file.""" @@ -308,7 +295,7 @@ class Addr(object): # appended to the end append_to_end = True continue - elif len(block) > 1: + if len(block) > 1: # remove leading zeros block = block.lstrip("0") if not append_to_end: @@ -359,63 +346,6 @@ class ChallengePerformer(object): raise NotImplementedError() -class TLSSNI01(ChallengePerformer): - # pylint: disable=abstract-method - """Abstract base for TLS-SNI-01 challenge performers""" - - def __init__(self, configurator): - super(TLSSNI01, self).__init__(configurator) - self.challenge_conf = os.path.join( - configurator.config.config_dir, "le_tls_sni_01_cert_challenge.conf") - # self.completed = 0 - - def get_cert_path(self, achall): - """Returns standardized name for challenge certificate. - - :param .KeyAuthorizationAnnotatedChallenge achall: Annotated - tls-sni-01 challenge. - - :returns: certificate file name - :rtype: str - - """ - return os.path.join(self.configurator.config.work_dir, - achall.chall.encode("token") + ".crt") - - def get_key_path(self, achall): - """Get standardized path to challenge key.""" - return os.path.join(self.configurator.config.work_dir, - achall.chall.encode("token") + '.pem') - - def get_z_domain(self, achall): - """Returns z_domain (SNI) name for the challenge.""" - return achall.response(achall.account_key).z_domain.decode("utf-8") - - def _setup_challenge_cert(self, achall, cert_key=None): - - """Generate and write out challenge certificate.""" - cert_path = self.get_cert_path(achall) - key_path = self.get_key_path(achall) - # Register the path before you write out the file - self.configurator.reverter.register_file_creation(True, key_path) - self.configurator.reverter.register_file_creation(True, cert_path) - - response, (cert, key) = achall.response_and_validation( - cert_key=cert_key) - cert_pem = OpenSSL.crypto.dump_certificate( - OpenSSL.crypto.FILETYPE_PEM, cert) - key_pem = OpenSSL.crypto.dump_privatekey( - OpenSSL.crypto.FILETYPE_PEM, key) - - # Write out challenge cert and key - with open(cert_path, "wb") as cert_chall_fd: - cert_chall_fd.write(cert_pem) - with util.safe_open(key_path, 'wb', chmod=0o400) as key_file: - key_file.write(key_pem) - - return response - - def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes): """Copy a file into an active location (likely the system's config dir) if required. @@ -444,9 +374,9 @@ def install_version_controlled_file(dest_path, digest_path, src_path, all_hashes active_file_digest = crypto_util.sha256sum(dest_path) if active_file_digest == current_hash: # already up to date return - elif active_file_digest in all_hashes: # safe to update + if active_file_digest in all_hashes: # safe to update _install_current_file() - else: # has been manually modified, not safe to update + else: # has been manually modified, not safe to update # did they modify the current version or an old version? if os.path.isfile(digest_path): with open(digest_path, "r") as f: @@ -477,7 +407,7 @@ def dir_setup(test_dir, pkg): # pragma: no cover link, (ex: OS X) such plugins will be confused. This function prevents such a case. """ - return os.path.realpath(tempfile.mkdtemp(prefix)) + return filesystem.realpath(tempfile.mkdtemp(prefix)) temp_dir = expanded_tempdir("temp") config_dir = expanded_tempdir("config") @@ -494,3 +424,34 @@ def dir_setup(test_dir, pkg): # pragma: no cover test_configs, os.path.join(temp_dir, test_dir), symlinks=True) return temp_dir, config_dir, work_dir + + +# This class takes a similar approach to the cryptography project to deprecate attributes +# in public modules. See the _ModuleWithDeprecation class here: +# https://github.com/pyca/cryptography/blob/91105952739442a74582d3e62b3d2111365b0dc7/src/cryptography/utils.py#L129 +class _TLSSNI01DeprecationModule(object): + """ + Internal class delegating to a module, and displaying warnings when + attributes related to TLS-SNI-01 are accessed. + """ + def __init__(self, module): + self.__dict__['_module'] = module + + def __getattr__(self, attr): + if attr == 'TLSSNI01': + warnings.warn('TLSSNI01 is deprecated and will be removed soon.', + DeprecationWarning, stacklevel=2) + return getattr(self._module, attr) + + def __setattr__(self, attr, value): # pragma: no cover + setattr(self._module, attr, value) + + def __delattr__(self, attr): # pragma: no cover + delattr(self._module, attr) + + def __dir__(self): # pragma: no cover + return ['_module'] + dir(self._module) + + +# Patching ourselves to warn about TLS-SNI challenge deprecation and removal. +sys.modules[__name__] = _TLSSNI01DeprecationModule(sys.modules[__name__]) diff --git a/certbot/plugins/dns_common.py b/certbot/certbot/plugins/dns_common.py similarity index 96% rename from certbot/plugins/dns_common.py rename to certbot/certbot/plugins/dns_common.py index 9be2868b0..d31266434 100644 --- a/certbot/plugins/dns_common.py +++ b/certbot/certbot/plugins/dns_common.py @@ -2,16 +2,15 @@ import abc import logging -import stat from time import sleep import configobj import zope.interface from acme import challenges - from certbot import errors from certbot import interfaces +from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util @@ -198,8 +197,7 @@ class DNSAuthenticator(common.Plugin): if code == display_util.OK: return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) + raise errors.PluginError('{0} required to proceed.'.format(label)) @staticmethod def _prompt_for_file(label, validator=None): @@ -232,8 +230,7 @@ class DNSAuthenticator(common.Plugin): if code == display_util.OK: return response - else: - raise errors.PluginError('{0} required to proceed.'.format(label)) + raise errors.PluginError('{0} required to proceed.'.format(label)) class CredentialsConfiguration(object): @@ -303,8 +300,8 @@ def validate_file(filename): if not os.path.exists(filename): raise errors.PluginError('File not found: {0}'.format(filename)) - if not os.path.isfile(filename): - raise errors.PluginError('Path is not a file: {0}'.format(filename)) + if os.path.isdir(filename): + raise errors.PluginError('Path is a directory: {0}'.format(filename)) def validate_file_permissions(filename): @@ -312,8 +309,7 @@ def validate_file_permissions(filename): validate_file(filename) - permissions = stat.S_IMODE(os.stat(filename).st_mode) - if permissions & stat.S_IRWXO: + if filesystem.has_world_permissions(filename): logger.warning('Unsafe permissions on credentials configuration file: %s', filename) diff --git a/certbot/plugins/dns_common_lexicon.py b/certbot/certbot/plugins/dns_common_lexicon.py similarity index 92% rename from certbot/plugins/dns_common_lexicon.py rename to certbot/certbot/plugins/dns_common_lexicon.py index 2c82db030..3e28a291b 100644 --- a/certbot/plugins/dns_common_lexicon.py +++ b/certbot/certbot/plugins/dns_common_lexicon.py @@ -1,9 +1,12 @@ """Common code for DNS Authenticator Plugins built on Lexicon.""" import logging -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException -from acme.magic_typing import Union, Dict, Any # pylint: disable=unused-import,no-name-in-module +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot.plugins import dns_common @@ -97,7 +100,7 @@ class LexiconClient(object): result = self._handle_general_error(e, domain_name) if result: - raise result + raise result # pylint: disable=raising-bad-type raise errors.PluginError('Unable to determine zone identifier for {0} using zone names: {1}' .format(domain, domain_name_guesses)) diff --git a/certbot/plugins/dns_test_common.py b/certbot/certbot/plugins/dns_test_common.py similarity index 89% rename from certbot/plugins/dns_test_common.py rename to certbot/certbot/plugins/dns_test_common.py index 0fc0c9a71..9ef76c2c3 100644 --- a/certbot/plugins/dns_test_common.py +++ b/certbot/certbot/plugins/dns_test_common.py @@ -6,7 +6,6 @@ import mock import six from acme import challenges - from certbot import achallenges from certbot.compat import filesystem from certbot.tests import acme_util @@ -29,18 +28,14 @@ class BaseAuthenticatorTest(object): challb=acme_util.DNS01, domain=DOMAIN, account_key=KEY) def test_more_info(self): - # pylint: disable=no-member - self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) + self.assertTrue(isinstance(self.auth.more_info(), six.string_types)) # pylint: disable=no-member def test_get_chall_pref(self): - # pylint: disable=no-member - self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) + self.assertEqual(self.auth.get_chall_pref(None), [challenges.DNS01]) # pylint: disable=no-member def test_parser_arguments(self): m = mock.MagicMock() - - # pylint: disable=no-member - self.auth.add_parser_arguments(m) + self.auth.add_parser_arguments(m) # pylint: disable=no-member m.assert_any_call('propagation-seconds', type=int, default=mock.ANY, help=mock.ANY) diff --git a/certbot/plugins/dns_test_common_lexicon.py b/certbot/certbot/plugins/dns_test_common_lexicon.py similarity index 98% rename from certbot/plugins/dns_test_common_lexicon.py rename to certbot/certbot/plugins/dns_test_common_lexicon.py index a221cf1bf..c77d6da9e 100644 --- a/certbot/plugins/dns_test_common_lexicon.py +++ b/certbot/certbot/plugins/dns_test_common_lexicon.py @@ -2,7 +2,8 @@ import josepy as jose import mock -from requests.exceptions import HTTPError, RequestException +from requests.exceptions import HTTPError +from requests.exceptions import RequestException from certbot import errors from certbot.plugins import dns_test_common diff --git a/certbot/plugins/enhancements.py b/certbot/certbot/plugins/enhancements.py similarity index 84% rename from certbot/plugins/enhancements.py rename to certbot/certbot/plugins/enhancements.py index 7ca096610..f8d9db7dc 100644 --- a/certbot/plugins/enhancements.py +++ b/certbot/certbot/plugins/enhancements.py @@ -1,10 +1,23 @@ """New interface style Certbot enhancements""" import abc + import six -from certbot import constants +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from certbot._internal import constants -from acme.magic_typing import Dict, List, Any # pylint: disable=unused-import, no-name-in-module +ENHANCEMENTS = ["redirect", "ensure-http-header", "ocsp-stapling"] +"""List of possible :class:`certbot.interfaces.IInstaller` +enhancements. + +List of expected options parameters: +- redirect: None +- ensure-http-header: name of header (i.e. Strict-Transport-Security) +- ocsp-stapling: certificate chain file path + +""" def enabled_enhancements(config): """ @@ -51,7 +64,7 @@ def enable(lineage, domains, installer, config): Run enable method for each requested enhancement that is supported. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str @@ -67,9 +80,9 @@ def enable(lineage, domains, installer, config): def populate_cli(add): """ - Populates the command line flags for certbot.cli.HelpfulParser + Populates the command line flags for certbot._internal.cli.HelpfulParser - :param add: Add function of certbot.cli.HelpfulParser + :param add: Add function of certbot._internal.cli.HelpfulParser :type add: func """ for enh in _INDEX: @@ -112,7 +125,7 @@ class AutoHSTSEnhancement(object): Implementation of this method should increase the max-age value. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert .. note:: prepare() method inherited from `interfaces.IPlugin` might need to be called manually within implementation of this interface method @@ -126,7 +139,7 @@ class AutoHSTSEnhancement(object): Long max-age value should be set in implementation of this method. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert """ @abc.abstractmethod @@ -137,7 +150,7 @@ class AutoHSTSEnhancement(object): over the subsequent runs of Certbot renew. :param lineage: Certificate lineage object - :type lineage: certbot.storage.RenewableCert + :type lineage: certbot.interfaces.RenewableCert :param domains: List of domains in certificate to enhance :type domains: str diff --git a/certbot/plugins/storage.py b/certbot/certbot/plugins/storage.py similarity index 90% rename from certbot/plugins/storage.py rename to certbot/certbot/plugins/storage.py index 51350802c..7956295d2 100644 --- a/certbot/plugins/storage.py +++ b/certbot/certbot/plugins/storage.py @@ -2,13 +2,15 @@ import json import logging -from acme.magic_typing import Any, Dict # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Any # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module from certbot import errors +from certbot.compat import filesystem from certbot.compat import os logger = logging.getLogger(__name__) + class PluginStorage(object): """Class implementing storage functionality for plugins""" @@ -84,9 +86,10 @@ class PluginStorage(object): logger.error(errmsg) raise errors.PluginStorageError(errmsg) try: - with os.fdopen(os.open(self._storagepath, - os.O_WRONLY | os.O_CREAT | os.O_TRUNC, - 0o600), 'w') as fh: + with os.fdopen(filesystem.open( + self._storagepath, + os.O_WRONLY | os.O_CREAT | os.O_TRUNC, + 0o600), 'w') as fh: fh.write(serialized) except IOError as e: errmsg = "Could not write PluginStorage data to file {0} : {1}".format( diff --git a/certbot/plugins/util.py b/certbot/certbot/plugins/util.py similarity index 94% rename from certbot/plugins/util.py rename to certbot/certbot/plugins/util.py index 61f811280..87eb45fe9 100644 --- a/certbot/plugins/util.py +++ b/certbot/certbot/plugins/util.py @@ -3,9 +3,11 @@ import logging from certbot import util from certbot.compat import os +from certbot.compat.misc import STANDARD_BINARY_DIRS logger = logging.getLogger(__name__) + def get_prefixes(path): """Retrieves all possible path prefixes of a path, in descending order of length. For instance, @@ -26,6 +28,7 @@ def get_prefixes(path): break return prefixes + def path_surgery(cmd): """Attempt to perform PATH surgery to find cmd @@ -35,10 +38,9 @@ def path_surgery(cmd): :returns: True if the operation succeeded, False otherwise """ - dirs = ("/usr/sbin", "/usr/local/bin", "/usr/local/sbin") path = os.environ["PATH"] added = [] - for d in dirs: + for d in STANDARD_BINARY_DIRS: if d not in path: path += os.pathsep + d added.append(d) diff --git a/certbot/reverter.py b/certbot/certbot/reverter.py similarity index 88% rename from certbot/reverter.py rename to certbot/certbot/reverter.py index f3088ed7e..47a77c80a 100644 --- a/certbot/reverter.py +++ b/certbot/certbot/reverter.py @@ -8,15 +8,12 @@ import time import traceback import six -import zope.component -from certbot import constants from certbot import errors -from certbot import interfaces from certbot import util -from certbot.compat import misc -from certbot.compat import os +from certbot._internal import constants from certbot.compat import filesystem +from certbot.compat import os logger = logging.getLogger(__name__) @@ -67,8 +64,7 @@ class Reverter(object): self.config = config util.make_or_verify_dir( - config.backup_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + config.backup_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) def revert_temporary_config(self): """Reload users original configuration files after a temporary save. @@ -132,62 +128,6 @@ class Reverter(object): "Unable to load checkpoint during rollback") rollback -= 1 - def view_config_changes(self, for_logging=False, num=None): - """Displays all saved checkpoints. - - All checkpoints are printed by - :meth:`certbot.interfaces.IDisplay.notification`. - - .. todo:: Decide on a policy for error handling, OSError IOError... - - :raises .errors.ReverterError: If invalid directory structure. - - """ - backups = os.listdir(self.config.backup_dir) - backups.sort(reverse=True) - if num: - backups = backups[:num] - if not backups: - logger.info("Certbot has not saved backups of your configuration") - - return None - # Make sure there isn't anything unexpected in the backup folder - # There should only be timestamped (float) directories - try: - for bkup in backups: - float(bkup) - except ValueError: - raise errors.ReverterError( - "Invalid directories in {0}".format(self.config.backup_dir)) - - output = [] - for bkup in backups: - output.append(time.ctime(float(bkup))) - cur_dir = os.path.join(self.config.backup_dir, bkup) - with open(os.path.join(cur_dir, "CHANGES_SINCE")) as changes_fd: - output.append(changes_fd.read()) - - output.append("Affected files:") - with open(os.path.join(cur_dir, "FILEPATHS")) as paths_fd: - filepaths = paths_fd.read().splitlines() - for path in filepaths: - output.append(" {0}".format(path)) - - if os.path.isfile(os.path.join(cur_dir, "NEW_FILES")): - with open(os.path.join(cur_dir, "NEW_FILES")) as new_fd: - output.append("New Configuration Files:") - filepaths = new_fd.read().splitlines() - for path in filepaths: - output.append(" {0}".format(path)) - - output.append(os.linesep) - - if for_logging: - return os.linesep.join(output) - zope.component.getUtility(interfaces.IDisplay).notification( - os.linesep.join(output), force_interactive=True, pause=False) - return None - def add_to_temp_checkpoint(self, save_files, save_notes): """Add files to temporary checkpoint. @@ -222,8 +162,7 @@ class Reverter(object): """ util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) op_fd, existing_filepaths = self._read_and_append( os.path.join(cp_dir, "FILEPATHS")) @@ -442,8 +381,7 @@ class Reverter(object): cp_dir = self.config.in_progress_dir util.make_or_verify_dir( - cp_dir, constants.CONFIG_DIRS_MODE, misc.os_geteuid(), - self.config.strict_permissions) + cp_dir, constants.CONFIG_DIRS_MODE, self.config.strict_permissions) return cp_dir @@ -501,9 +439,9 @@ class Reverter(object): os.remove(path) else: logger.warning( - "File: %s - Could not be found to be deleted %s - " - "Certbot probably shut down unexpectedly", - os.linesep, path) + "File: %s - Could not be found to be deleted\n" + " - Certbot probably shut down unexpectedly", + path) except (IOError, OSError): logger.critical( "Unable to remove filepaths contained within %s", file_list) diff --git a/certbot/ssl-dhparams.pem b/certbot/certbot/ssl-dhparams.pem similarity index 100% rename from certbot/ssl-dhparams.pem rename to certbot/certbot/ssl-dhparams.pem diff --git a/certbot/certbot/tests/__init__.py b/certbot/certbot/tests/__init__.py new file mode 100644 index 000000000..82290ca0b --- /dev/null +++ b/certbot/certbot/tests/__init__.py @@ -0,0 +1 @@ +"""Utilities for running Certbot tests""" diff --git a/certbot/tests/acme_util.py b/certbot/certbot/tests/acme_util.py similarity index 87% rename from certbot/tests/acme_util.py rename to certbot/certbot/tests/acme_util.py index 4854626a1..3d560dcbc 100644 --- a/certbot/tests/acme_util.py +++ b/certbot/certbot/tests/acme_util.py @@ -6,24 +6,19 @@ import six from acme import challenges from acme import messages - -from certbot import auth_handler - +from certbot._internal import auth_handler from certbot.tests import util - JWK = jose.JWK.load(util.load_vector('rsa512_key.pem')) KEY = util.load_rsa_private_key('rsa512_key.pem') # Challenges HTTP01 = challenges.HTTP01( token=b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJ+PCt92wr+oA") -TLSSNI01 = challenges.TLSSNI01( - token=jose.b64decode(b"evaGxfADs6pSRb2LAv9IZf17Dt3juxGJyPCt92wrDoA")) DNS01 = challenges.DNS01(token=b"17817c66b60ce2e4012dfad92657527a") DNS01_2 = challenges.DNS01(token=b"cafecafecafecafecafecafe0feedbac") -CHALLENGES = [HTTP01, TLSSNI01, DNS01] +CHALLENGES = [HTTP01, DNS01] def gen_combos(challbs): @@ -47,21 +42,19 @@ def chall_to_challb(chall, status): # pylint: disable=redefined-outer-name # Pending ChallengeBody objects -TLSSNI01_P = chall_to_challb(TLSSNI01, messages.STATUS_PENDING) HTTP01_P = chall_to_challb(HTTP01, messages.STATUS_PENDING) DNS01_P = chall_to_challb(DNS01, messages.STATUS_PENDING) DNS01_P_2 = chall_to_challb(DNS01_2, messages.STATUS_PENDING) -CHALLENGES_P = [HTTP01_P, TLSSNI01_P, DNS01_P] +CHALLENGES_P = [HTTP01_P, DNS01_P] # AnnotatedChallenge objects HTTP01_A = auth_handler.challb_to_achall(HTTP01_P, JWK, "example.com") -TLSSNI01_A = auth_handler.challb_to_achall(TLSSNI01_P, JWK, "example.net") DNS01_A = auth_handler.challb_to_achall(DNS01_P, JWK, "example.org") DNS01_A_2 = auth_handler.challb_to_achall(DNS01_P_2, JWK, "esimerkki.example.org") -ACHALLENGES = [HTTP01_A, TLSSNI01_A, DNS01_A] +ACHALLENGES = [HTTP01_A, DNS01_A] def gen_authzr(authz_status, domain, challs, statuses, combos=True): diff --git a/certbot/tests/testdata/README b/certbot/certbot/tests/testdata/README similarity index 100% rename from certbot/tests/testdata/README rename to certbot/certbot/tests/testdata/README diff --git a/certbot/tests/testdata/cert-5sans_512.pem b/certbot/certbot/tests/testdata/cert-5sans_512.pem similarity index 100% rename from certbot/tests/testdata/cert-5sans_512.pem rename to certbot/certbot/tests/testdata/cert-5sans_512.pem diff --git a/certbot/tests/testdata/cert-nosans_nistp256.pem b/certbot/certbot/tests/testdata/cert-nosans_nistp256.pem similarity index 100% rename from certbot/tests/testdata/cert-nosans_nistp256.pem rename to certbot/certbot/tests/testdata/cert-nosans_nistp256.pem diff --git a/certbot/tests/testdata/cert-san_512.pem b/certbot/certbot/tests/testdata/cert-san_512.pem similarity index 100% rename from certbot/tests/testdata/cert-san_512.pem rename to certbot/certbot/tests/testdata/cert-san_512.pem diff --git a/certbot/tests/testdata/cert_2048.pem b/certbot/certbot/tests/testdata/cert_2048.pem similarity index 100% rename from certbot/tests/testdata/cert_2048.pem rename to certbot/certbot/tests/testdata/cert_2048.pem diff --git a/certbot/tests/testdata/cert_512.pem b/certbot/certbot/tests/testdata/cert_512.pem similarity index 100% rename from certbot/tests/testdata/cert_512.pem rename to certbot/certbot/tests/testdata/cert_512.pem diff --git a/certbot/tests/testdata/cert_512_bad.pem b/certbot/certbot/tests/testdata/cert_512_bad.pem similarity index 100% rename from certbot/tests/testdata/cert_512_bad.pem rename to certbot/certbot/tests/testdata/cert_512_bad.pem diff --git a/certbot/tests/testdata/cert_fullchain_2048.pem b/certbot/certbot/tests/testdata/cert_fullchain_2048.pem similarity index 100% rename from certbot/tests/testdata/cert_fullchain_2048.pem rename to certbot/certbot/tests/testdata/cert_fullchain_2048.pem diff --git a/certbot/tests/testdata/cli.ini b/certbot/certbot/tests/testdata/cli.ini similarity index 100% rename from certbot/tests/testdata/cli.ini rename to certbot/certbot/tests/testdata/cli.ini diff --git a/certbot/tests/testdata/csr-6sans_512.conf b/certbot/certbot/tests/testdata/csr-6sans_512.conf similarity index 100% rename from certbot/tests/testdata/csr-6sans_512.conf rename to certbot/certbot/tests/testdata/csr-6sans_512.conf diff --git a/certbot/tests/testdata/csr-6sans_512.pem b/certbot/certbot/tests/testdata/csr-6sans_512.pem similarity index 100% rename from certbot/tests/testdata/csr-6sans_512.pem rename to certbot/certbot/tests/testdata/csr-6sans_512.pem diff --git a/certbot/tests/testdata/csr-nonames_512.pem b/certbot/certbot/tests/testdata/csr-nonames_512.pem similarity index 100% rename from certbot/tests/testdata/csr-nonames_512.pem rename to certbot/certbot/tests/testdata/csr-nonames_512.pem diff --git a/certbot/tests/testdata/csr-nosans_512.conf b/certbot/certbot/tests/testdata/csr-nosans_512.conf similarity index 100% rename from certbot/tests/testdata/csr-nosans_512.conf rename to certbot/certbot/tests/testdata/csr-nosans_512.conf diff --git a/certbot/tests/testdata/csr-nosans_512.pem b/certbot/certbot/tests/testdata/csr-nosans_512.pem similarity index 100% rename from certbot/tests/testdata/csr-nosans_512.pem rename to certbot/certbot/tests/testdata/csr-nosans_512.pem diff --git a/certbot/tests/testdata/csr-nosans_nistp256.pem b/certbot/certbot/tests/testdata/csr-nosans_nistp256.pem similarity index 100% rename from certbot/tests/testdata/csr-nosans_nistp256.pem rename to certbot/certbot/tests/testdata/csr-nosans_nistp256.pem diff --git a/certbot/tests/testdata/csr-san_512.pem b/certbot/certbot/tests/testdata/csr-san_512.pem similarity index 100% rename from certbot/tests/testdata/csr-san_512.pem rename to certbot/certbot/tests/testdata/csr-san_512.pem diff --git a/certbot/tests/testdata/csr_512.der b/certbot/certbot/tests/testdata/csr_512.der similarity index 100% rename from certbot/tests/testdata/csr_512.der rename to certbot/certbot/tests/testdata/csr_512.der diff --git a/certbot/tests/testdata/csr_512.pem b/certbot/certbot/tests/testdata/csr_512.pem similarity index 100% rename from certbot/tests/testdata/csr_512.pem rename to certbot/certbot/tests/testdata/csr_512.pem diff --git a/certbot/tests/testdata/nistp256_key.pem b/certbot/certbot/tests/testdata/nistp256_key.pem similarity index 100% rename from certbot/tests/testdata/nistp256_key.pem rename to certbot/certbot/tests/testdata/nistp256_key.pem diff --git a/certbot/tests/testdata/ocsp_certificate.pem b/certbot/certbot/tests/testdata/ocsp_certificate.pem similarity index 100% rename from certbot/tests/testdata/ocsp_certificate.pem rename to certbot/certbot/tests/testdata/ocsp_certificate.pem diff --git a/certbot/tests/testdata/ocsp_issuer_certificate.pem b/certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem similarity index 100% rename from certbot/tests/testdata/ocsp_issuer_certificate.pem rename to certbot/certbot/tests/testdata/ocsp_issuer_certificate.pem diff --git a/certbot/tests/testdata/ocsp_responder_certificate.pem b/certbot/certbot/tests/testdata/ocsp_responder_certificate.pem similarity index 100% rename from certbot/tests/testdata/ocsp_responder_certificate.pem rename to certbot/certbot/tests/testdata/ocsp_responder_certificate.pem diff --git a/certbot/tests/testdata/os-release b/certbot/certbot/tests/testdata/os-release similarity index 100% rename from certbot/tests/testdata/os-release rename to certbot/certbot/tests/testdata/os-release diff --git a/certbot/tests/testdata/rsa2048_key.pem b/certbot/certbot/tests/testdata/rsa2048_key.pem similarity index 100% rename from certbot/tests/testdata/rsa2048_key.pem rename to certbot/certbot/tests/testdata/rsa2048_key.pem diff --git a/certbot/tests/testdata/rsa256_key.pem b/certbot/certbot/tests/testdata/rsa256_key.pem similarity index 100% rename from certbot/tests/testdata/rsa256_key.pem rename to certbot/certbot/tests/testdata/rsa256_key.pem diff --git a/certbot/tests/testdata/rsa512_key.pem b/certbot/certbot/tests/testdata/rsa512_key.pem similarity index 100% rename from certbot/tests/testdata/rsa512_key.pem rename to certbot/certbot/tests/testdata/rsa512_key.pem diff --git a/certbot/tests/testdata/sample-archive/cert1.pem b/certbot/certbot/tests/testdata/sample-archive/cert1.pem similarity index 100% rename from certbot/tests/testdata/sample-archive/cert1.pem rename to certbot/certbot/tests/testdata/sample-archive/cert1.pem diff --git a/certbot/tests/testdata/sample-archive/chain1.pem b/certbot/certbot/tests/testdata/sample-archive/chain1.pem similarity index 100% rename from certbot/tests/testdata/sample-archive/chain1.pem rename to certbot/certbot/tests/testdata/sample-archive/chain1.pem diff --git a/certbot/tests/testdata/sample-archive/fullchain1.pem b/certbot/certbot/tests/testdata/sample-archive/fullchain1.pem similarity index 100% rename from certbot/tests/testdata/sample-archive/fullchain1.pem rename to certbot/certbot/tests/testdata/sample-archive/fullchain1.pem diff --git a/certbot/tests/testdata/sample-archive/privkey1.pem b/certbot/certbot/tests/testdata/sample-archive/privkey1.pem similarity index 100% rename from certbot/tests/testdata/sample-archive/privkey1.pem rename to certbot/certbot/tests/testdata/sample-archive/privkey1.pem diff --git a/certbot/tests/testdata/sample-renewal-ancient.conf b/certbot/certbot/tests/testdata/sample-renewal-ancient.conf similarity index 100% rename from certbot/tests/testdata/sample-renewal-ancient.conf rename to certbot/certbot/tests/testdata/sample-renewal-ancient.conf diff --git a/certbot/tests/testdata/sample-renewal.conf b/certbot/certbot/tests/testdata/sample-renewal.conf similarity index 100% rename from certbot/tests/testdata/sample-renewal.conf rename to certbot/certbot/tests/testdata/sample-renewal.conf diff --git a/certbot/tests/testdata/webrootconftest.ini b/certbot/certbot/tests/testdata/webrootconftest.ini similarity index 100% rename from certbot/tests/testdata/webrootconftest.ini rename to certbot/certbot/tests/testdata/webrootconftest.ini diff --git a/certbot/tests/util.py b/certbot/certbot/tests/util.py similarity index 87% rename from certbot/tests/util.py rename to certbot/certbot/tests/util.py index 1c5f1d8b0..02abe0a31 100644 --- a/certbot/tests/util.py +++ b/certbot/certbot/tests/util.py @@ -4,30 +4,30 @@ """ import logging +from multiprocessing import Event +from multiprocessing import Process import shutil -import stat import sys import tempfile import unittest -from multiprocessing import Process, Event -import OpenSSL +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import serialization import josepy as jose import mock +import OpenSSL import pkg_resources import six from six.moves import reload_module # pylint: disable=import-error -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from certbot import configuration -from certbot import constants from certbot import interfaces -from certbot import lock -from certbot import storage from certbot import util -from certbot.compat import os +from certbot._internal import configuration +from certbot._internal import constants +from certbot._internal import lock +from certbot._internal import storage from certbot.compat import filesystem +from certbot.compat import os from certbot.display import util as display_util @@ -57,8 +57,7 @@ def _guess_loader(filename, loader_pem, loader_der): return loader_pem elif ext.lower() == '.der': return loader_der - else: # pragma: no cover - raise ValueError("Loader could not be recognized based on extension") + raise ValueError("Loader could not be recognized based on extension") # pragma: no cover def load_cert(*names): @@ -95,26 +94,6 @@ def load_pyopenssl_private_key(*names): return OpenSSL.crypto.load_privatekey(loader, load_vector(*names)) -def skip_unless(condition, reason): # pragma: no cover - """Skip tests unless a condition holds. - - This implements the basic functionality of unittest.skipUnless - which is only available on Python 2.7+. - - :param bool condition: If ``False``, the test will be skipped - :param str reason: the reason for skipping the test - - :rtype: callable - :returns: decorator that hides tests unless condition is ``True`` - - """ - if hasattr(unittest, "skipUnless"): - return unittest.skipUnless(condition, reason) - elif condition: - return lambda cls: cls - return lambda cls: None - - def make_lineage(config_dir, testfile): """Creates a lineage defined by testfile. @@ -139,7 +118,7 @@ def make_lineage(config_dir, testfile): for directory in (archive_dir, conf_dir, live_dir,): if not os.path.exists(directory): - os.makedirs(directory) + filesystem.makedirs(directory) sample_archive = vector_path('sample-archive') for kind in os.listdir(sample_archive): @@ -255,8 +234,7 @@ class FreezableMock(object): if self._frozen: if name in self._frozen_set: raise AttributeError('Cannot change frozen attribute ' + name) - else: - return setattr(self._mock, name, value) + return setattr(self._mock, name, value) if name != '_frozen_set': self._frozen_set.add(name) @@ -270,7 +248,7 @@ class FreezableMock(object): def _create_get_utility_mock(): display = FreezableMock() # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: E1120 if name != 'notification': frozen_mock = FreezableMock(frozen=True, func=_assert_valid_call) setattr(display, name, frozen_mock) @@ -295,7 +273,7 @@ def _create_get_utility_mock_with_stdout(stdout): display = FreezableMock() # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: E1120 if name == 'notification': frozen_mock = FreezableMock(frozen=True, func=_write_msg) @@ -339,16 +317,7 @@ class TempDirTestCase(unittest.TestCase): logging.getLogger().handlers = [] util._release_locks() # pylint: disable=protected-access - def handle_rw_files(_, path, __): - """Handle read-only files, that will fail to be removed on Windows.""" - filesystem.chmod(path, stat.S_IWRITE) - try: - os.remove(path) - except (IOError, OSError): - # TODO: remote the try/except once all logic from windows file permissions is merged - if os.name != 'nt': - raise - shutil.rmtree(self.tempdir, onerror=handle_rw_files) + shutil.rmtree(self.tempdir) class ConfigTestCase(TempDirTestCase): @@ -421,15 +390,6 @@ def skip_on_windows(reason): return wrapper -def broken_on_windows(function): - """Decorator to skip temporarily a broken test on Windows.""" - reason = 'Test is broken and ignored on windows but should be fixed.' - return unittest.skipIf( - sys.platform == 'win32' - and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', - reason)(function) - - def temp_join(path): """ Return the given path joined to the tempdir path for the current platform diff --git a/certbot/util.py b/certbot/certbot/util.py similarity index 82% rename from certbot/util.py rename to certbot/certbot/util.py index 66e5d2524..0a47cd87a 100644 --- a/certbot/util.py +++ b/certbot/certbot/util.py @@ -1,10 +1,10 @@ """Utilities for all Certbot.""" +# distutils.version under virtualenv confuses pylint +# For more info, see: https://github.com/PyCQA/pylint/issues/73 import argparse import atexit import collections from collections import OrderedDict -# distutils.version under virtualenv confuses pylint -# For more info, see: https://github.com/PyCQA/pylint/issues/73 import distutils.version # pylint: disable=import-error,no-name-in-module import errno import logging @@ -12,18 +12,25 @@ import platform import re import socket import subprocess +import sys import configargparse import six -from acme.magic_typing import Tuple, Union # pylint: disable=unused-import, no-name-in-module - -from certbot import constants +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot import lock -from certbot.compat import misc +from certbot._internal import constants +from certbot._internal import lock +from certbot.compat import filesystem from certbot.compat import os +if sys.platform.startswith('linux'): + import distro # pylint: disable=import-error + _USE_DISTRO = True +else: + _USE_DISTRO = False + logger = logging.getLogger(__name__) @@ -86,18 +93,6 @@ def run_script(params, log=logger.error): return stdout, stderr -def is_exe(path): - """Is path an executable file? - - :param str path: path to test - - :returns: True iff path is an executable file - :rtype: bool - - """ - return os.path.isfile(path) and os.access(path, os.X_OK) - - def exe_exists(exe): """Determine whether path/name refers to an executable. @@ -109,11 +104,10 @@ def exe_exists(exe): """ path, _ = os.path.split(exe) if path: - return is_exe(exe) - else: - for path in os.environ["PATH"].split(os.pathsep): - if is_exe(os.path.join(path, exe)): - return True + return filesystem.is_executable(exe) + for path in os.environ["PATH"].split(os.pathsep): + if filesystem.is_executable(os.path.join(path, exe)): + return True return False @@ -143,12 +137,11 @@ def _release_locks(): _LOCKS.clear() -def set_up_core_dir(directory, mode, uid, strict): +def set_up_core_dir(directory, mode, strict): """Ensure directory exists with proper permissions and is locked. :param str directory: Path to a directory. :param int mode: Directory mode. - :param int uid: Directory owner. :param bool strict: require directory to be owned by current user :raises .errors.LockError: if the directory cannot be locked @@ -156,19 +149,18 @@ def set_up_core_dir(directory, mode, uid, strict): """ try: - make_or_verify_dir(directory, mode, uid, strict) + make_or_verify_dir(directory, mode, strict) lock_dir_until_exit(directory) except OSError as error: logger.debug("Exception was:", exc_info=True) raise errors.Error(PERM_ERR_FMT.format(error)) -def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False): +def make_or_verify_dir(directory, mode=0o755, strict=False): """Make sure directory exists with proper permissions. :param str directory: Path to a directory. :param int mode: Directory mode. - :param int uid: Directory owner. :param bool strict: require directory to be owned by current user :raises .errors.Error: if a directory already exists, @@ -180,50 +172,31 @@ def make_or_verify_dir(directory, mode=0o755, uid=0, strict=False): """ try: - os.makedirs(directory, mode) + filesystem.makedirs(directory, mode) except OSError as exception: if exception.errno == errno.EEXIST: - if strict and not check_permissions(directory, mode, uid): + if strict and not filesystem.check_permissions(directory, mode): raise errors.Error( - "%s exists, but it should be owned by user %d with" - "permissions %s" % (directory, uid, oct(mode))) + "%s exists, but it should be owned by current user with" + " permissions %s" % (directory, oct(mode))) else: raise -def check_permissions(filepath, mode, uid=0): - """Check file or directory permissions. - - :param str filepath: Path to the tested file (or directory). - :param int mode: Expected file mode. - :param int uid: Expected file owner. - - :returns: True if `mode` and `uid` match, False otherwise. - :rtype: bool - - """ - file_stat = os.stat(filepath) - return misc.compare_file_modes(file_stat.st_mode, mode) and file_stat.st_uid == uid - - -def safe_open(path, mode="w", chmod=None, buffering=None): +def safe_open(path, mode="w", chmod=None): """Safely open a file. :param str path: Path to a file. :param str mode: Same os `mode` for `open`. - :param int chmod: Same as `mode` for `os.open`, uses Python defaults + :param int chmod: Same as `mode` for `filesystem.open`, uses Python defaults if ``None``. - :param int buffering: Same as `bufsize` for `os.fdopen`, uses Python - defaults if ``None``. """ open_args = () # type: Union[Tuple[()], Tuple[int]] if chmod is not None: open_args = (chmod,) fdopen_args = () # type: Union[Tuple[()], Tuple[int]] - if buffering is not None: - fdopen_args = (buffering,) - fd = os.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) + fd = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, *open_args) return os.fdopen(fd, mode, *fdopen_args) @@ -309,77 +282,46 @@ def get_filtered_names(all_names): logger.debug('Not suggesting name "%s"', name, exc_info=True) return filtered_names - -def get_os_info(filepath="/etc/os-release"): +def get_os_info(): """ Get OS name and version - :param str filepath: File path of os-release file :returns: (os_name, os_version) :rtype: `tuple` of `str` """ - if os.path.isfile(filepath): - # Systemd os-release parsing might be viable - os_name, os_version = get_systemd_os_info(filepath=filepath) - if os_name: - return os_name, os_version + return get_python_os_info(pretty=False) - # Fallback to platform module - return get_python_os_info() - - -def get_os_info_ua(filepath="/etc/os-release"): +def get_os_info_ua(): """ Get OS name and version string for User Agent - :param str filepath: File path of os-release file :returns: os_ua :rtype: `str` """ + if _USE_DISTRO: + os_info = distro.name(pretty=True) - if os.path.isfile(filepath): - os_ua = get_var_from_file("PRETTY_NAME", filepath=filepath) - if not os_ua: - os_ua = get_var_from_file("NAME", filepath=filepath) - if os_ua: - return os_ua + if not _USE_DISTRO or not os_info: + return " ".join(get_python_os_info(pretty=True)) + return os_info - # Fallback - return " ".join(get_python_os_info()) - - -def get_systemd_os_info(filepath="/etc/os-release"): - """ - Parse systemd /etc/os-release for distribution information - - :param str filepath: File path of os-release file - :returns: (os_name, os_version) - :rtype: `tuple` of `str` - """ - - os_name = get_var_from_file("ID", filepath=filepath) - os_version = get_var_from_file("VERSION_ID", filepath=filepath) - - return (os_name, os_version) - - -def get_systemd_os_like(filepath="/etc/os-release"): +def get_systemd_os_like(): """ Get a list of strings that indicate the distribution likeness to other distributions. - :param str filepath: File path of os-release file :returns: List of distribution acronyms :rtype: `list` of `str` """ - return get_var_from_file("ID_LIKE", filepath).split(" ") - + if _USE_DISTRO: + return distro.like().split(" ") + return [] def get_var_from_file(varname, filepath="/etc/os-release"): """ - Get single value from systemd /etc/os-release + Get single value from a file formatted like systemd /etc/os-release :param str varname: Name of variable to fetch :param str filepath: File path of os-release file @@ -399,7 +341,6 @@ def get_var_from_file(varname, filepath="/etc/os-release"): return _normalize_string(line.strip()[len(var_string):]) return "" - def _normalize_string(orig): """ Helper function for get_var_from_file() to remove quotes @@ -407,12 +348,13 @@ def _normalize_string(orig): """ return orig.replace('"', '').replace("'", "").strip() - -def get_python_os_info(): +def get_python_os_info(pretty=False): """ Get Operating System type/distribution and major version using python platform module + :param bool pretty: If the returned OS name should be in longer (pretty) form + :returns: (os_name, os_version) :rtype: `tuple` of `str` """ @@ -423,9 +365,9 @@ def get_python_os_info(): ) os_type, os_ver, _ = info os_type = os_type.lower() - if os_type.startswith('linux'): - info = platform.linux_distribution() - # On arch, platform.linux_distribution() is reportedly ('','',''), + if os_type.startswith('linux') and _USE_DISTRO: + info = distro.linux_distribution(pretty) + # On arch, distro.linux_distribution() is reportedly ('','',''), # so handle it defensively if info[0]: os_type = info[0] @@ -493,7 +435,6 @@ def add_deprecated_argument(add_argument, argument_name, nargs): # In version 0.12.0 ACTION_TYPES_THAT_DONT_NEED_A_VALUE was # changed from a set to a tuple. if isinstance(configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE, set): - # pylint: disable=no-member configargparse.ACTION_TYPES_THAT_DONT_NEED_A_VALUE.add( _ShowWarning) else: @@ -594,7 +535,7 @@ def enforce_domain_sanity(domain): for l in labels: if not l: raise errors.ConfigurationError("{0} it contains an empty label.".format(msg)) - elif len(l) > 63: + if len(l) > 63: raise errors.ConfigurationError("{0} label {1} is too long.".format(msg, l)) return domain @@ -628,7 +569,6 @@ def get_strict_version(normalized): """ # strict version ending with "a" and a number designates a pre-release - # pylint: disable=no-member return distutils.version.StrictVersion(normalized.replace(".dev", "a")) diff --git a/certbot/compat/filesystem.py b/certbot/compat/filesystem.py deleted file mode 100644 index 5dc01a622..000000000 --- a/certbot/compat/filesystem.py +++ /dev/null @@ -1,175 +0,0 @@ -"""Compat module to handle files security on Windows and Linux""" -from __future__ import absolute_import - -import os # pylint: disable=os-module-forbidden -import stat - -try: - import ntsecuritycon # pylint: disable=import-error - import win32security # pylint: disable=import-error -except ImportError: - POSIX_MODE = True -else: - POSIX_MODE = False - -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - - -def chmod(file_path, mode): - # type: (str, int) -> None - """ - Apply a POSIX mode on given file_path: - * for Linux, the POSIX mode will be directly applied using chmod, - * for Windows, the POSIX mode will be translated into a Windows DACL that make sense for - Certbot context, and applied to the file using kernel calls. - - The definition of the Windows DACL that correspond to a POSIX mode, in the context of Certbot, - is explained at https://github.com/certbot/certbot/issues/6356 and is implemented by the - method _generate_windows_flags(). - - :param str file_path: Path of the file - :param int mode: POSIX mode to apply - """ - if POSIX_MODE: - os.chmod(file_path, mode) - else: - _apply_win_mode(file_path, mode) - - -def replace(src, dst): - # type: (str, str) -> None - """ - Rename a file to a destination path and handles situations where the destination exists. - :param str src: The current file path. - :param str dst: The new file path. - """ - if hasattr(os, 'replace'): - # Use replace if possible. On Windows, only Python >= 3.4 is supported - # so we can assume that os.replace() is always available for this platform. - getattr(os, 'replace')(src, dst) - else: - # Otherwise, use os.rename() that behaves like os.replace() on Linux. - os.rename(src, dst) - - -def _apply_win_mode(file_path, mode): - """ - This function converts the given POSIX mode into a Windows ACL list, and applies it to the - file given its path. If the given path is a symbolic link, it will resolved to apply the - mode on the targeted file. - """ - original_path = file_path - inspected_paths = [] # type: List[str] - while os.path.islink(file_path): - link_path = file_path - file_path = os.readlink(file_path) - if not os.path.isabs(file_path): - file_path = os.path.join(os.path.dirname(link_path), file_path) - if file_path in inspected_paths: - raise RuntimeError('Error, link {0} is a loop!'.format(original_path)) - inspected_paths.append(file_path) - # Get owner sid of the file - security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION) - user = security.GetSecurityDescriptorOwner() - - # New DACL, that will overwrite existing one (including inherited permissions) - dacl = _generate_dacl(user, mode) - - # Apply the new DACL - security.SetSecurityDescriptorDacl(1, dacl, 0) - win32security.SetFileSecurity(file_path, win32security.DACL_SECURITY_INFORMATION, security) - - -def _generate_dacl(user_sid, mode): - analysis = _analyze_mode(mode) - - # Get standard accounts from "well-known" sid - # See the list here: - # https://support.microsoft.com/en-us/help/243330/well-known-security-identifiers-in-windows-operating-systems - system = win32security.ConvertStringSidToSid('S-1-5-18') - admins = win32security.ConvertStringSidToSid('S-1-5-32-544') - everyone = win32security.ConvertStringSidToSid('S-1-1-0') - - # New dacl, without inherited permissions - dacl = win32security.ACL() - - # If user is already system or admins, any ACE defined here would be superseded by - # the full control ACE that will be added after. - if user_sid not in [system, admins]: - # Handle user rights - user_flags = _generate_windows_flags(analysis['user']) - if user_flags: - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, user_flags, user_sid) - - # Handle everybody rights - everybody_flags = _generate_windows_flags(analysis['all']) - if everybody_flags: - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, everybody_flags, everyone) - - # Handle administrator rights - full_permissions = _generate_windows_flags({'read': True, 'write': True, 'execute': True}) - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, system) - dacl.AddAccessAllowedAce(win32security.ACL_REVISION, full_permissions, admins) - - return dacl - - -def _analyze_mode(mode): - return { - 'user': { - 'read': mode & stat.S_IRUSR, - 'write': mode & stat.S_IWUSR, - 'execute': mode & stat.S_IXUSR, - }, - 'all': { - 'read': mode & stat.S_IROTH, - 'write': mode & stat.S_IWOTH, - 'execute': mode & stat.S_IXOTH, - }, - } - - -def _generate_windows_flags(rights_desc): - # Some notes about how each POSIX right is interpreted. - # - # For the rights read and execute, we have a pretty bijective relation between - # POSIX flags and their generic counterparts on Windows, so we use them directly - # (respectively ntsecuritycon.FILE_GENERIC_READ and ntsecuritycon.FILE_GENERIC_EXECUTE). - # - # But ntsecuritycon.FILE_GENERIC_WRITE does not correspond to what one could expect from a - # write access on Linux: for Windows, FILE_GENERIC_WRITE does not include delete, move or - # rename. This is something that requires ntsecuritycon.FILE_ALL_ACCESS. - # So to reproduce the write right as POSIX, we will apply ntsecuritycon.FILE_ALL_ACCESS - # substracted of the rights corresponding to POSIX read and POSIX execute. - # - # Finally, having read + write + execute gives a ntsecuritycon.FILE_ALL_ACCESS, - # so a "Full Control" on the file. - # - # A complete list of the rights defined on NTFS can be found here: - # https://docs.microsoft.com/en-us/previous-versions/windows/it-pro/windows-server-2003/cc783530(v=ws.10)#permissions-for-files-and-folders - flag = 0 - if rights_desc['read']: - flag = flag | ntsecuritycon.FILE_GENERIC_READ - if rights_desc['write']: - flag = flag | (ntsecuritycon.FILE_ALL_ACCESS - ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - # Despite bit `512` being present in ntsecuritycon.FILE_ALL_ACCESS, it is - # not effectively applied to the file or the directory. - # As _generate_windows_flags is also used to compare two dacls, we remove - # it right now to have flags that contain only the bits effectively applied - # by Windows. - ^ 512) - if rights_desc['execute']: - flag = flag | ntsecuritycon.FILE_GENERIC_EXECUTE - - return flag - - -def _compare_dacls(dacl1, dacl2): - """ - This method compare the two given DACLs to check if they are identical. - Identical means here that they contains the same set of ACEs in the same order. - """ - return ([dacl1.GetAce(index) for index in range(0, dacl1.GetAceCount())] == - [dacl2.GetAce(index) for index in range(0, dacl2.GetAceCount())]) diff --git a/certbot-apache/docs/.gitignore b/certbot/docs/.gitignore similarity index 100% rename from certbot-apache/docs/.gitignore rename to certbot/docs/.gitignore diff --git a/docs/Makefile b/certbot/docs/Makefile similarity index 100% rename from docs/Makefile rename to certbot/docs/Makefile diff --git a/certbot-apache/docs/_static/.gitignore b/certbot/docs/_static/.gitignore similarity index 100% rename from certbot-apache/docs/_static/.gitignore rename to certbot/docs/_static/.gitignore diff --git a/docs/_templates/footer.html b/certbot/docs/_templates/footer.html similarity index 100% rename from docs/_templates/footer.html rename to certbot/docs/_templates/footer.html diff --git a/certbot-apache/docs/api.rst b/certbot/docs/api.rst similarity index 69% rename from certbot-apache/docs/api.rst rename to certbot/docs/api.rst index 8668ec5d8..9c8b2f1fe 100644 --- a/certbot-apache/docs/api.rst +++ b/certbot/docs/api.rst @@ -3,6 +3,6 @@ API Documentation ================= .. toctree:: - :glob: + :maxdepth: 4 - api/** + api/certbot diff --git a/certbot/docs/api/certbot.achallenges.rst b/certbot/docs/api/certbot.achallenges.rst new file mode 100644 index 000000000..3fd2f2a42 --- /dev/null +++ b/certbot/docs/api/certbot.achallenges.rst @@ -0,0 +1,7 @@ +certbot.achallenges module +========================== + +.. automodule:: certbot.achallenges + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.filesystem.rst b/certbot/docs/api/certbot.compat.filesystem.rst new file mode 100644 index 000000000..d4f1e2fe0 --- /dev/null +++ b/certbot/docs/api/certbot.compat.filesystem.rst @@ -0,0 +1,7 @@ +certbot.compat.filesystem module +================================ + +.. automodule:: certbot.compat.filesystem + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.misc.rst b/certbot/docs/api/certbot.compat.misc.rst new file mode 100644 index 000000000..35c2913e7 --- /dev/null +++ b/certbot/docs/api/certbot.compat.misc.rst @@ -0,0 +1,7 @@ +certbot.compat.misc module +========================== + +.. automodule:: certbot.compat.misc + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.os.rst b/certbot/docs/api/certbot.compat.os.rst new file mode 100644 index 000000000..3a4c9fe47 --- /dev/null +++ b/certbot/docs/api/certbot.compat.os.rst @@ -0,0 +1,7 @@ +certbot.compat.os module +======================== + +.. automodule:: certbot.compat.os + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.compat.rst b/certbot/docs/api/certbot.compat.rst new file mode 100644 index 000000000..f6f2b3739 --- /dev/null +++ b/certbot/docs/api/certbot.compat.rst @@ -0,0 +1,17 @@ +certbot.compat package +====================== + +.. automodule:: certbot.compat + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.compat.filesystem + certbot.compat.misc + certbot.compat.os + diff --git a/certbot/docs/api/certbot.crypto_util.rst b/certbot/docs/api/certbot.crypto_util.rst new file mode 100644 index 000000000..34aa665b9 --- /dev/null +++ b/certbot/docs/api/certbot.crypto_util.rst @@ -0,0 +1,7 @@ +certbot.crypto\_util module +=========================== + +.. automodule:: certbot.crypto_util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.display.ops.rst b/certbot/docs/api/certbot.display.ops.rst new file mode 100644 index 000000000..544b0dad3 --- /dev/null +++ b/certbot/docs/api/certbot.display.ops.rst @@ -0,0 +1,7 @@ +certbot.display.ops module +========================== + +.. automodule:: certbot.display.ops + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.display.rst b/certbot/docs/api/certbot.display.rst new file mode 100644 index 000000000..04bc68b07 --- /dev/null +++ b/certbot/docs/api/certbot.display.rst @@ -0,0 +1,16 @@ +certbot.display package +======================= + +.. automodule:: certbot.display + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.display.ops + certbot.display.util + diff --git a/certbot/docs/api/certbot.display.util.rst b/certbot/docs/api/certbot.display.util.rst new file mode 100644 index 000000000..22b59dc98 --- /dev/null +++ b/certbot/docs/api/certbot.display.util.rst @@ -0,0 +1,7 @@ +certbot.display.util module +=========================== + +.. automodule:: certbot.display.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.errors.rst b/certbot/docs/api/certbot.errors.rst new file mode 100644 index 000000000..731b7695d --- /dev/null +++ b/certbot/docs/api/certbot.errors.rst @@ -0,0 +1,7 @@ +certbot.errors module +===================== + +.. automodule:: certbot.errors + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.interfaces.rst b/certbot/docs/api/certbot.interfaces.rst new file mode 100644 index 000000000..2665aaa01 --- /dev/null +++ b/certbot/docs/api/certbot.interfaces.rst @@ -0,0 +1,7 @@ +certbot.interfaces module +========================= + +.. automodule:: certbot.interfaces + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.main.rst b/certbot/docs/api/certbot.main.rst new file mode 100644 index 000000000..ce0539f5c --- /dev/null +++ b/certbot/docs/api/certbot.main.rst @@ -0,0 +1,7 @@ +certbot.main module +=================== + +.. automodule:: certbot.main + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.common.rst b/certbot/docs/api/certbot.plugins.common.rst new file mode 100644 index 000000000..e94b2d12e --- /dev/null +++ b/certbot/docs/api/certbot.plugins.common.rst @@ -0,0 +1,7 @@ +certbot.plugins.common module +============================= + +.. automodule:: certbot.plugins.common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_common.rst b/certbot/docs/api/certbot.plugins.dns_common.rst new file mode 100644 index 000000000..36c7a6428 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_common.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_common module +================================== + +.. automodule:: certbot.plugins.dns_common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst b/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst new file mode 100644 index 000000000..1a961accd --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_common_lexicon.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_common\_lexicon module +=========================================== + +.. automodule:: certbot.plugins.dns_common_lexicon + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_test_common.rst b/certbot/docs/api/certbot.plugins.dns_test_common.rst new file mode 100644 index 000000000..69e672f0a --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_test_common.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_test\_common module +======================================== + +.. automodule:: certbot.plugins.dns_test_common + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst b/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst new file mode 100644 index 000000000..92d516c99 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.dns_test_common_lexicon.rst @@ -0,0 +1,7 @@ +certbot.plugins.dns\_test\_common\_lexicon module +================================================= + +.. automodule:: certbot.plugins.dns_test_common_lexicon + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.enhancements.rst b/certbot/docs/api/certbot.plugins.enhancements.rst new file mode 100644 index 000000000..16db737c7 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.enhancements.rst @@ -0,0 +1,7 @@ +certbot.plugins.enhancements module +=================================== + +.. automodule:: certbot.plugins.enhancements + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.rst b/certbot/docs/api/certbot.plugins.rst new file mode 100644 index 000000000..517a209e6 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.rst @@ -0,0 +1,22 @@ +certbot.plugins package +======================= + +.. automodule:: certbot.plugins + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.plugins.common + certbot.plugins.dns_common + certbot.plugins.dns_common_lexicon + certbot.plugins.dns_test_common + certbot.plugins.dns_test_common_lexicon + certbot.plugins.enhancements + certbot.plugins.storage + certbot.plugins.util + diff --git a/certbot/docs/api/certbot.plugins.storage.rst b/certbot/docs/api/certbot.plugins.storage.rst new file mode 100644 index 000000000..9ed0fe724 --- /dev/null +++ b/certbot/docs/api/certbot.plugins.storage.rst @@ -0,0 +1,7 @@ +certbot.plugins.storage module +============================== + +.. automodule:: certbot.plugins.storage + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.plugins.util.rst b/certbot/docs/api/certbot.plugins.util.rst new file mode 100644 index 000000000..c5453564e --- /dev/null +++ b/certbot/docs/api/certbot.plugins.util.rst @@ -0,0 +1,7 @@ +certbot.plugins.util module +=========================== + +.. automodule:: certbot.plugins.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.reverter.rst b/certbot/docs/api/certbot.reverter.rst new file mode 100644 index 000000000..002b75360 --- /dev/null +++ b/certbot/docs/api/certbot.reverter.rst @@ -0,0 +1,7 @@ +certbot.reverter module +======================= + +.. automodule:: certbot.reverter + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.rst b/certbot/docs/api/certbot.rst new file mode 100644 index 000000000..6f5b4b403 --- /dev/null +++ b/certbot/docs/api/certbot.rst @@ -0,0 +1,31 @@ +certbot package +=============== + +.. automodule:: certbot + :members: + :undoc-members: + :show-inheritance: + +Subpackages +----------- + +.. toctree:: + + certbot.compat + certbot.display + certbot.plugins + certbot.tests + +Submodules +---------- + +.. toctree:: + + certbot.achallenges + certbot.crypto_util + certbot.errors + certbot.interfaces + certbot.main + certbot.reverter + certbot.util + diff --git a/certbot/docs/api/certbot.tests.acme_util.rst b/certbot/docs/api/certbot.tests.acme_util.rst new file mode 100644 index 000000000..908397596 --- /dev/null +++ b/certbot/docs/api/certbot.tests.acme_util.rst @@ -0,0 +1,7 @@ +certbot.tests.acme\_util module +=============================== + +.. automodule:: certbot.tests.acme_util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.tests.rst b/certbot/docs/api/certbot.tests.rst new file mode 100644 index 000000000..336f0eabc --- /dev/null +++ b/certbot/docs/api/certbot.tests.rst @@ -0,0 +1,16 @@ +certbot.tests package +===================== + +.. automodule:: certbot.tests + :members: + :undoc-members: + :show-inheritance: + +Submodules +---------- + +.. toctree:: + + certbot.tests.acme_util + certbot.tests.util + diff --git a/certbot/docs/api/certbot.tests.util.rst b/certbot/docs/api/certbot.tests.util.rst new file mode 100644 index 000000000..3f0335849 --- /dev/null +++ b/certbot/docs/api/certbot.tests.util.rst @@ -0,0 +1,7 @@ +certbot.tests.util module +========================= + +.. automodule:: certbot.tests.util + :members: + :undoc-members: + :show-inheritance: diff --git a/certbot/docs/api/certbot.util.rst b/certbot/docs/api/certbot.util.rst new file mode 100644 index 000000000..11cb33b09 --- /dev/null +++ b/certbot/docs/api/certbot.util.rst @@ -0,0 +1,7 @@ +certbot.util module +=================== + +.. automodule:: certbot.util + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/challenges.rst b/certbot/docs/challenges.rst similarity index 100% rename from docs/challenges.rst rename to certbot/docs/challenges.rst diff --git a/docs/ciphers.rst b/certbot/docs/ciphers.rst similarity index 99% rename from docs/ciphers.rst rename to certbot/docs/ciphers.rst index c3d6abc42..04b24b526 100644 --- a/docs/ciphers.rst +++ b/certbot/docs/ciphers.rst @@ -248,7 +248,7 @@ Dutch National Cyber Security Centre The Dutch National Cyber Security Centre has published guidance on "ICT-beveiligingsrichtlijnen voor Transport Layer Security (TLS)" ("IT Security Guidelines for Transport Layer Security (TLS)"). These are available only in Dutch at -https://www.ncsc.nl/dienstverlening/expertise-advies/kennisdeling/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html +https://web.archive.org/web/20190516085116/https://www.ncsc.nl/actueel/whitepapers/ict-beveiligingsrichtlijnen-voor-transport-layer-security-tls.html I have access to an English-language summary of the recommendations. diff --git a/docs/cli-help.txt b/certbot/docs/cli-help.txt similarity index 97% rename from docs/cli-help.txt rename to certbot/docs/cli-help.txt index 3499acf19..b46206b87 100644 --- a/docs/cli-help.txt +++ b/certbot/docs/cli-help.txt @@ -27,10 +27,10 @@ manage certificates: revoke Revoke a certificate (supply --cert-path or --cert-name) delete Delete a certificate -manage your account with Let's Encrypt: - register Create a Let's Encrypt ACME account - unregister Deactivate a Let's Encrypt ACME account - update_account Update a Let's Encrypt ACME account +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 @@ -113,12 +113,12 @@ 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/0.35.1 - (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, --force-renew, --allow- - subset-of-names, -n, and whether any hooks are set. + "". (default: CertbotACMEClient/1.0.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, + --force-renew, --allow-subset-of-names, -n, and + whether any hooks are set. --user-agent-comment USER_AGENT_COMMENT Add a comment to the default user agent string. May be used when repackaging Certbot or calling it from @@ -392,12 +392,6 @@ unregister: install: Options for modifying how a certificate is deployed -config_changes: - Options for controlling which changes are displayed - - --num NUM How many past revisions you want to be displayed - (default: None) - rollback: Options for rolling back server configuration changes @@ -405,7 +399,7 @@ rollback: (default: 1) plugins: - Options for for the "plugins" subcommand + Options for the "plugins" subcommand --init Initialize plugins. (default: False) --prepare Initialize and prepare plugins. (default: False) diff --git a/docs/conf.py b/certbot/docs/conf.py similarity index 99% rename from docs/conf.py rename to certbot/docs/conf.py index c72d1c1cf..6b7c1c2c0 100644 --- a/docs/conf.py +++ b/certbot/docs/conf.py @@ -19,7 +19,6 @@ import sys import sphinx - here = os.path.abspath(os.path.dirname(__file__)) # read version number (and other metadata) from package init diff --git a/docs/contributing.rst b/certbot/docs/contributing.rst similarity index 88% rename from docs/contributing.rst rename to certbot/docs/contributing.rst index 8685a0dd5..e1289c849 100644 --- a/docs/contributing.rst +++ b/certbot/docs/contributing.rst @@ -36,29 +36,36 @@ run Certbot in Docker. You can find instructions for how to do this :ref:`here install dependencies and set up a virtual environment where you can run Certbot. +Install the OS system dependencies required to run Certbot. + +.. code-block:: shell + + # For APT-based distributions (e.g. Debian, Ubuntu ...) + sudo apt update + sudo apt install python3-dev python3-venv gcc libaugeas0 libssl-dev \ + libffi-dev ca-certificates openssl + # For RPM-based distributions (e.g. Fedora, CentOS ...) + # NB1: old distributions will use yum instead of dnf + # NB2: RHEL-based distributions use python3X-devel instead of python3-devel (e.g. python36-devel) + sudo dnf install python3-devel gcc augeas-libs openssl-devel libffi-devel \ + redhat-rpm-config ca-certificates openssl + +Set up the Python virtual environment that will host your Certbot local instance. + .. code-block:: shell cd certbot - ./certbot-auto --debug --os-packages-only - python tools/venv.py - -If you have Python3 available and want to use it, run the ``venv3.py`` script. - -.. code-block:: shell - python tools/venv3.py .. note:: You may need to repeat this when Certbot's dependencies change or when a new plugin is introduced. You can now run the copy of Certbot from git either by executing -``venv/bin/certbot``, or by activating the virtual environment. You can do the +``venv3/bin/certbot``, or by activating the virtual environment. You can do the latter by running: .. code-block:: shell - source venv/bin/activate - # or source venv3/bin/activate After running this command, ``certbot`` and development tools like ``ipdb``, @@ -114,9 +121,9 @@ Once you are done with your code changes, and the tests in ``foo_test.py`` pass, run all of the unittests for Certbot with ``tox -e py27`` (this uses Python 2.7). -Once all the unittests pass, check for sufficient test coverage using -``tox -e cover``, and then check for code style with ``tox -e lint`` (all files) -or ``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). +Once all the unittests pass, check for sufficient test coverage using ``tox -e +py27-cover``, and then check for code style with ``tox -e lint`` (all files) or +``pylint --rcfile=.pylintrc path/to/file.py`` (single file at a time). Once all of the above is successful, you may run the full test suite using ``tox --skip-missing-interpreters``. We recommend running the commands above @@ -176,7 +183,7 @@ that can be used once the virtual environment is activated: .. code-block:: shell - certbot_tests [ARGS...] + certbot_test [ARGS...] - Execute certbot with the provided arguments and other arguments useful for testing purposes, such as: verbose output, full tracebacks in case Certbot crashes, *etc.* @@ -197,15 +204,20 @@ using an HTTP-01 challenge on a machine with Python 3: Code components and layout ========================== +The following components of the Certbot repository are distributed to users: + acme contains all protocol specific code certbot main client code certbot-apache and certbot-nginx client code to configure specific web servers -certbot.egg-info - configuration for packaging Certbot - +certbot-dns-* + client code to configure DNS providers +certbot-auto and letsencrypt-auto + shell scripts to install Certbot and its dependencies on UNIX systems +windows installer + Installs Certbot on Windows and is built using the files in windows-installer/ Plugin-architecture ------------------- @@ -234,7 +246,7 @@ Authenticators Authenticators are plugins that prove control of a domain name by solving a challenge provided by the ACME server. ACME currently defines several types of -challenges: HTTP, TLS-SNI (deprecated), TLS-ALPR, and DNS, represented by classes in `acme.challenges`. +challenges: HTTP, TLS-ALPN, and DNS, represented by classes in `acme.challenges`. An authenticator plugin should implement support for at least one challenge type. An Authenticator indicates which challenges it supports by implementing @@ -288,6 +300,16 @@ configuration checkpoints and rollback. Writing your own plugin ~~~~~~~~~~~~~~~~~~~~~~~ +.. note:: The Certbot team is not currently accepting any new DNS plugins + because we want to rethink our approach to the challenge and resolve some + issues like `#6464 `_, + `#6503 `_, and `#6504 + `_ first. + + In the meantime, you're welcome to release it as a third-party plugin. See + `certbot-dns-ispconfig `_ + for one example of that. + Certbot client supports dynamic discovery of plugins through the `setuptools entry points`_ using the `certbot.plugins` group. This way you can, for example, create a custom implementation of @@ -316,12 +338,6 @@ plugins. It's technically possible to install third-party plugins into the virtualenv used by `certbot-auto`, but they will be wiped away when `certbot-auto` upgrades. -.. warning:: Please be aware though that as this client is still in a - developer-preview stage, the API may undergo a few changes. If you - believe the plugin will be beneficial to the community, please - consider submitting a pull request to the repo and we will update - it with any necessary API changes. - .. _`setuptools entry points`: http://setuptools.readthedocs.io/en/latest/pkg_resources.html#entry-points @@ -375,6 +391,8 @@ As a developer, when working on Certbot or its plugins, you must use ``certbot.c in every place you would need ``os`` (eg. ``from certbot.compat import os`` instead of ``import os``). Otherwise the tests will fail when your PR is submitted. +.. _type annotations: + Mypy type annotations ===================== @@ -395,7 +413,7 @@ Note that instead of just importing ``typing``, due to packaging issues, in Cert .. code-block:: python - from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module + from acme.magic_typing import Dict Also note that OpenSSL, which we rely on, has type definitions for crypto but not SSL. We use both. Those imports should look like this: @@ -414,7 +432,10 @@ Submitting a pull request Steps: -1. Write your code! +1. Write your code! When doing this, you should add :ref:`mypy type annotations + ` for any functions you add or modify. You can check that + you've done this correctly by running ``tox -e mypy`` on a machine that has + Python 3 installed. 2. Make sure your environment is set up properly and that you're in your virtualenv. You can do this by following the instructions in the :ref:`Getting Started ` section. @@ -428,6 +449,8 @@ Steps: rewriting commits makes changes harder to track between reviews. 6. Did your tests pass on Travis? If they didn't, fix any errors. +.. _ask for help: + Asking for help =============== @@ -454,6 +477,11 @@ Conduct violation, EFF may review discussion channels or direct messages. Updating certbot-auto and letsencrypt-auto ========================================== +.. note:: We are currently only accepting changes to certbot-auto that fix + regressions on platforms where certbot-auto is the recommended installation + method at https://certbot.eff.org/instructions. If you are unsure if a change + you want to make qualifies, don't hesitate to `ask for help`_! + Updating the scripts -------------------- Developers should *not* modify the ``certbot-auto`` and ``letsencrypt-auto`` files @@ -507,7 +535,7 @@ This should generate documentation in the ``docs/_build/html`` directory. .. note:: If you skipped the "Getting Started" instructions above, - run ``pip install -e ".[docs]"`` to install Certbot's docs extras modules. + run ``pip install -e "certbot[docs]"`` to install Certbot's docs extras modules. .. _docker-dev: diff --git a/docs/index.rst b/certbot/docs/index.rst similarity index 100% rename from docs/index.rst rename to certbot/docs/index.rst diff --git a/docs/install.rst b/certbot/docs/install.rst similarity index 80% rename from docs/install.rst rename to certbot/docs/install.rst index 3ae7fa397..d21242367 100644 --- a/docs/install.rst +++ b/certbot/docs/install.rst @@ -41,8 +41,8 @@ client as root, either `letsencrypt-nosudo The Apache plugin currently requires an OS with augeas version 1.0; currently `it supports -`_ -modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. +`_ +modern OSes based on Debian, Ubuntu, Fedora, SUSE, Gentoo and Darwin. Additional integrity verification of certbot-auto script can be done by verifying its digital signature. @@ -70,11 +70,13 @@ The ``certbot-auto`` wrapper script installs Certbot, obtaining some dependencie from your web server OS and putting others in a python virtual environment. You can download and run it as follows:: - user@webserver:~$ wget https://dl.eff.org/certbot-auto - user@webserver:~$ sudo mv certbot-auto /usr/local/bin/certbot-auto - user@webserver:~$ sudo chown root /usr/local/bin/certbot-auto - user@webserver:~$ chmod 0755 /usr/local/bin/certbot-auto - user@webserver:~$ /usr/local/bin/certbot-auto --help + wget https://dl.eff.org/certbot-auto + sudo mv certbot-auto /usr/local/bin/certbot-auto + sudo chown root /usr/local/bin/certbot-auto + sudo chmod 0755 /usr/local/bin/certbot-auto + /usr/local/bin/certbot-auto --help + +To remove certbot-auto, just delete it and the files it places under /opt/eff.org, along with any cronjob or systemd timer you may have created. To check the integrity of the ``certbot-auto`` script, you can use these steps:: @@ -200,23 +202,64 @@ Operating System Packages **Debian** -If you run Debian Stretch or Debian Sid, you can install certbot packages. +If you run Debian Buster or Debian testing/Sid, you can easily install certbot +packages through commands like: .. code-block:: shell sudo apt-get update - sudo apt-get install certbot python-certbot-apache + sudo apt-get install certbot -If you don't want to use the Apache plugin, you can omit the -``python-certbot-apache`` package. Or you can install ``python-certbot-nginx`` instead. - -Packages exist for Debian Jessie via backports. First you'll have to follow the -instructions at http://backports.debian.org/Instructions/ to enable the Jessie backports -repo, if you have not already done so. Then run: +If you run Debian Stretch, we recommend you use the packages in Debian +backports repository. First you'll have to follow the instructions at +https://backports.debian.org/Instructions/ to enable the Stretch backports repo, +if you have not already done so. Then run: .. code-block:: shell - sudo apt-get install certbot python-certbot-apache -t jessie-backports + sudo apt-get install certbot -t stretch-backports + +In all of these cases, there also packages available to help Certbot integrate +with Apache, nginx, or various DNS services. If you are using Apache or nginx, +we strongly recommend that you install the ``python-certbot-apache`` or +``python-certbot-nginx`` package so that Certbot can fully automate HTTPS +configuration for your server. A full list of these packages can be found +through a command like: + +.. code-block:: shell + + apt search 'python-certbot*' + +They can be installed by running the same installation command above but +replacing ``certbot`` with the name of the desired package. + +There are no Certbot packages available for Debian Jessie and Jessie users +should instead use certbot-auto_. + +**Ubuntu** + +If you run Ubuntu Trusty, Xenial, or Bionic, certbot is available through the official PPA, +that can be installed as followed: + +.. code-block:: shell + + sudo apt-get update + sudo apt-get install software-properties-common + sudo add-apt-repository universe + sudo add-apt-repository ppa:certbot/certbot + sudo apt-get update + +Then, certbot can be installed using: + +.. code-block:: shell + + sudo apt-get install certbot + +Optionally to install the Certbot Apache plugin, you can use: + +.. code-block:: shell + + sudo apt-get install python-certbot-apache **Fedora** @@ -287,9 +330,9 @@ Installing from source Installation from source is only supported for developers and the whole process is described in the :doc:`contributing`. -.. warning:: Please do **not** use ``python setup.py install``, ``python pip - install .``, or ``easy_install .``. Please do **not** attempt the +.. warning:: Please do **not** use ``python certbot/setup.py install``, ``python pip + install certbot``, or ``easy_install certbot``. Please do **not** attempt the installation commands as superuser/root and/or without virtual environment, - e.g. ``sudo python setup.py install``, ``sudo pip install``, ``sudo + e.g. ``sudo python certbot/setup.py install``, ``sudo pip install``, ``sudo ./venv/bin/...``. These modes of operation might corrupt your operating system and are **not supported** by the Certbot team! diff --git a/docs/intro.rst b/certbot/docs/intro.rst similarity index 100% rename from docs/intro.rst rename to certbot/docs/intro.rst diff --git a/docs/make.bat b/certbot/docs/make.bat similarity index 100% rename from docs/make.bat rename to certbot/docs/make.bat diff --git a/docs/man/certbot.rst b/certbot/docs/man/certbot.rst similarity index 100% rename from docs/man/certbot.rst rename to certbot/docs/man/certbot.rst diff --git a/certbot/docs/packaging.rst b/certbot/docs/packaging.rst new file mode 100644 index 000000000..7b0b1d41a --- /dev/null +++ b/certbot/docs/packaging.rst @@ -0,0 +1,49 @@ +=============== +Packaging Guide +=============== + +Releases +======== + +We release packages and upload them to PyPI (wheels and source tarballs). + +- https://pypi.python.org/pypi/acme +- https://pypi.python.org/pypi/certbot +- https://pypi.python.org/pypi/certbot-apache +- https://pypi.python.org/pypi/certbot-nginx +- https://pypi.python.org/pypi/certbot-dns-cloudflare +- https://pypi.python.org/pypi/certbot-dns-cloudxns +- https://pypi.python.org/pypi/certbot-dns-digitalocean +- https://pypi.python.org/pypi/certbot-dns-dnsimple +- https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy +- https://pypi.python.org/pypi/certbot-dns-google +- https://pypi.python.org/pypi/certbot-dns-linode +- https://pypi.python.org/pypi/certbot-dns-luadns +- https://pypi.python.org/pypi/certbot-dns-nsone +- https://pypi.python.org/pypi/certbot-dns-ovh +- https://pypi.python.org/pypi/certbot-dns-rfc2136 +- https://pypi.python.org/pypi/certbot-dns-route53 + +The following scripts are used in the process: + +- https://github.com/certbot/certbot/blob/master/tools/release.sh + +We use git tags to identify releases, using `Semantic Versioning`_. For +example: `v0.11.1`. + +.. _`Semantic Versioning`: http://semver.org/ + +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. + +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. + +3. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. + +4. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. + +5. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. diff --git a/docs/resources.rst b/certbot/docs/resources.rst similarity index 100% rename from docs/resources.rst rename to certbot/docs/resources.rst diff --git a/docs/using.rst b/certbot/docs/using.rst similarity index 96% rename from docs/using.rst rename to certbot/docs/using.rst index 8e93bcb41..27ae826bd 100644 --- a/docs/using.rst +++ b/certbot/docs/using.rst @@ -58,8 +58,8 @@ standalone_ Y N | Uses a "standalone" webserver to obtain a certificate. | the only way to obtain wildcard certificates from Let's | Encrypt. manual_ Y N | Helps you obtain a certificate by giving you instructions to http-01_ (80) or - | perform domain validation yourself. Additionally allows you dns-01_ (53) - | to specify scripts to automate the validation task in a + | perform domain validation yourself. Additionally allows you dns-01_ (53) + | to specify scripts to automate the validation task in a | customized way. =========== ==== ==== =============================================================== ============================= @@ -83,7 +83,7 @@ Apache ------ The Apache plugin currently `supports -`_ +`_ modern OSes based on Debian, Fedora, SUSE, Gentoo and Darwin. This automates both obtaining *and* installing certificates on an Apache webserver. To specify this plugin on the command line, simply include @@ -268,32 +268,29 @@ There are also a number of third-party plugins for the client, provided by other developers. Many are beta/experimental, but some are already in widespread use: -=========== ==== ==== =============================================================== -Plugin Auth Inst Notes -=========== ==== ==== =============================================================== -plesk_ Y Y Integration with the Plesk web hosting tool -haproxy_ Y Y Integration with the HAProxy load balancer -s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets -gandi_ Y Y Integration with Gandi LiveDNS API -varnish_ Y N Obtain certificates via a Varnish server -external_ Y N A plugin for convenient scripting (See also ticket 2782_) -icecast_ N Y Deploy certificates to Icecast 2 streaming media servers -pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers -proxmox_ N Y Install certificates in Proxmox Virtualization servers -heroku_ Y Y Integration with Heroku SSL -=========== ==== ==== =============================================================== +================== ==== ==== =============================================================== +Plugin Auth Inst Notes +================== ==== ==== =============================================================== +haproxy_ Y Y Integration with the HAProxy load balancer +s3front_ Y Y Integration with Amazon CloudFront distribution of S3 buckets +gandi_ Y N Obtain certificates via the Gandi LiveDNS API +varnish_ Y N Obtain certificates via a Varnish server +external-auth_ Y Y A plugin for convenient scripting +pritunl_ N Y Install certificates in pritunl distributed OpenVPN servers +proxmox_ N Y Install certificates in Proxmox Virtualization servers +dns-standalone_ Y N Obtain certificates via an integrated DNS server +dns-ispconfig_ Y N DNS Authentication using ISPConfig as DNS server +================== ==== ==== =============================================================== -.. _plesk: https://github.com/plesk/letsencrypt-plesk .. _haproxy: https://github.com/greenhost/certbot-haproxy .. _s3front: https://github.com/dlapiduz/letsencrypt-s3front .. _gandi: https://github.com/obynio/certbot-plugin-gandi -.. _icecast: https://github.com/e00E/lets-encrypt-icecast .. _varnish: http://git.sesse.net/?p=letsencrypt-varnish-plugin -.. _2782: https://github.com/certbot/certbot/issues/2782 .. _pritunl: https://github.com/kharkevich/letsencrypt-pritunl .. _proxmox: https://github.com/kharkevich/letsencrypt-proxmox -.. _external: https://github.com/marcan/letsencrypt-external -.. _heroku: https://github.com/gboudreau/certbot-heroku +.. _external-auth: https://github.com/EnigmaBridge/certbot-external-auth +.. _dns-standalone: https://github.com/siilike/certbot-dns-standalone +.. _dns-ispconfig: https://github.com/m42e/certbot-dns-ispconfig If you're interested, you can also :ref:`write your own plugin `. @@ -682,8 +679,8 @@ Where are my certificates? ========================== All generated keys and issued certificates can be found in -``/etc/letsencrypt/live/$domain``. In the case of creating a SAN certificate -with multiple alternative names, ``$domain`` is the first domain passed in +``/etc/letsencrypt/live/$domain``. In the case of creating a SAN certificate +with multiple alternative names, ``$domain`` is the first domain passed in via -d parameter. Rather than copying, please point your (web) server configuration directly to those files (or create symlinks). During the renewal_, ``/etc/letsencrypt/live`` is updated @@ -919,8 +916,9 @@ Certbot accepts a global configuration file that applies its options to all invo of Certbot. Certificate specific configuration choices should be set in the ``.conf`` files that can be found in ``/etc/letsencrypt/renewal``. -By default no cli.ini file is created, after creating one -it is possible to specify the location of this configuration file with +By default no cli.ini file is created (though it may exist already if you installed Certbot +via a package manager, for instance). +After creating one it is possible to specify the location of this configuration file with ``certbot --config cli.ini`` (or shorter ``-c cli.ini``). An example configuration file is shown below: diff --git a/docs/what.rst b/certbot/docs/what.rst similarity index 100% rename from docs/what.rst rename to certbot/docs/what.rst diff --git a/examples/.gitignore b/certbot/examples/.gitignore similarity index 100% rename from examples/.gitignore rename to certbot/examples/.gitignore diff --git a/examples/cli.ini b/certbot/examples/cli.ini similarity index 100% rename from examples/cli.ini rename to certbot/examples/cli.ini diff --git a/examples/dev-cli.ini b/certbot/examples/dev-cli.ini similarity index 100% rename from examples/dev-cli.ini rename to certbot/examples/dev-cli.ini diff --git a/examples/generate-csr.sh b/certbot/examples/generate-csr.sh similarity index 100% rename from examples/generate-csr.sh rename to certbot/examples/generate-csr.sh diff --git a/examples/openssl.cnf b/certbot/examples/openssl.cnf similarity index 100% rename from examples/openssl.cnf rename to certbot/examples/openssl.cnf diff --git a/examples/plugins/certbot_example_plugins.py b/certbot/examples/plugins/certbot_example_plugins.py similarity index 100% rename from examples/plugins/certbot_example_plugins.py rename to certbot/examples/plugins/certbot_example_plugins.py diff --git a/examples/plugins/setup.py b/certbot/examples/plugins/setup.py similarity index 99% rename from examples/plugins/setup.py rename to certbot/examples/plugins/setup.py index 4538e83b8..ba2b5e4e2 100644 --- a/examples/plugins/setup.py +++ b/certbot/examples/plugins/setup.py @@ -1,6 +1,5 @@ from setuptools import setup - setup( name='certbot-example-plugins', package='certbot_example_plugins.py', diff --git a/local-oldest-requirements.txt b/certbot/local-oldest-requirements.txt similarity index 100% rename from local-oldest-requirements.txt rename to certbot/local-oldest-requirements.txt diff --git a/certbot/plugins/__init__.py b/certbot/plugins/__init__.py deleted file mode 100644 index 7b1aca2b4..000000000 --- a/certbot/plugins/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot client.plugins.""" diff --git a/certbot-nginx/readthedocs.org.requirements.txt b/certbot/readthedocs.org.requirements.txt similarity index 69% rename from certbot-nginx/readthedocs.org.requirements.txt rename to certbot/readthedocs.org.requirements.txt index ca5f33363..f3964e8a7 100644 --- a/certbot-nginx/readthedocs.org.requirements.txt +++ b/certbot/readthedocs.org.requirements.txt @@ -1,12 +1,11 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# 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 .[docs]" must be used instead +# expected and "pip install -e certbot[docs]" must be used instead -e acme --e . --e certbot-nginx[docs] +-e certbot[docs] diff --git a/setup.cfg b/certbot/setup.cfg similarity index 100% rename from setup.cfg rename to certbot/setup.cfg diff --git a/setup.py b/certbot/setup.py similarity index 82% rename from setup.py rename to certbot/setup.py index 622a790bb..d0d62e263 100644 --- a/setup.py +++ b/certbot/setup.py @@ -1,10 +1,12 @@ import codecs +from distutils.version import StrictVersion import os import re import sys -from distutils.version import StrictVersion -from setuptools import find_packages, setup, __version__ as setuptools_version +from setuptools import __version__ as setuptools_version +from setuptools import find_packages +from setuptools import setup from setuptools.command.test import test as TestCommand # Workaround for http://bugs.python.org/issue8876, see @@ -34,13 +36,14 @@ version = meta['version'] # specified here to avoid masking the more specific request requirements in # acme. See https://github.com/pypa/pip/issues/988 for more info. install_requires = [ - 'acme>=0.36.0.dev0', + 'acme>=1.1.0.dev0', # We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but # saying so here causes a runtime error against our temporary fork of 0.9.3 # in which we added 2.6 support (see #2243), so we relax the requirement. 'ConfigArgParse>=0.9.3', 'configobj', 'cryptography>=1.2.3', # load_pem_x509_certificate + 'distro>=1.0.1', # 1.1.0+ is required to avoid the warnings described at # https://github.com/certbot/josepy/issues/13. 'josepy>=1.1.0', @@ -58,28 +61,34 @@ install_requires = [ # However environment markers are supported only with setuptools >= 36.2. # So this dependency is not added for old Linux distributions with old setuptools, # in order to allow these systems to build certbot from sources. +pywin32_req = 'pywin32>=227' # do not forget to edit pywin32 dependency accordingly in windows-installer/construct.py if StrictVersion(setuptools_version) >= StrictVersion('36.2'): - install_requires.append("pywin32 ; sys_platform == 'win32'") + install_requires.append(pywin32_req + " ; sys_platform == 'win32'") elif 'bdist_wheel' in sys.argv[1:]: raise RuntimeError('Error, you are trying to build certbot wheels using an old version ' 'of setuptools. Version 36.2+ of setuptools is required.') +elif os.name == 'nt': + # This branch exists to improve this package's behavior on Windows. Without + # it, if the sdist is installed on Windows with an old version of + # setuptools, pywin32 will not be specified as a dependency. + install_requires.append(pywin32_req) dev_extras = [ - 'astroid==1.6.5', 'coverage', 'ipdb', 'pytest', 'pytest-cov', 'pytest-xdist', - 'pylint==1.9.4', 'tox', 'twine', 'wheel', ] dev3_extras = [ + 'astroid', 'mypy', - 'typing', # for python3.4 + 'pylint', + 'typing', # for python3.4 ] docs_extras = [ @@ -131,6 +140,7 @@ setup( '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', @@ -149,8 +159,6 @@ setup( 'docs': docs_extras, }, - # to test all packages run "python setup.py test -s - # {acme,certbot_apache,certbot_nginx}" test_suite='certbot', tests_require=["pytest"], cmdclass={"test": PyTest}, @@ -160,10 +168,10 @@ setup( 'certbot = certbot.main:main', ], 'certbot.plugins': [ - 'manual = certbot.plugins.manual:Authenticator', - 'null = certbot.plugins.null:Installer', - 'standalone = certbot.plugins.standalone:Authenticator', - 'webroot = certbot.plugins.webroot:Authenticator', + 'manual = certbot._internal.plugins.manual:Authenticator', + 'null = certbot._internal.plugins.null:Installer', + 'standalone = certbot._internal.plugins.standalone:Authenticator', + 'webroot = certbot._internal.plugins.webroot:Authenticator', ], }, ) diff --git a/certbot/tests/__init__.py b/certbot/tests/__init__.py deleted file mode 100644 index 2f4d6e07c..000000000 --- a/certbot/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Certbot Tests""" diff --git a/certbot/tests/account_test.py b/certbot/tests/account_test.py index 24a092cc8..4a6ed3e01 100644 --- a/certbot/tests/account_test.py +++ b/certbot/tests/account_test.py @@ -1,8 +1,6 @@ -"""Tests for certbot.account.""" +"""Tests for certbot._internal.account.""" import datetime import json -import shutil -import stat import unittest import josepy as jose @@ -10,20 +8,20 @@ import mock import pytz from acme import messages - -import certbot.tests.util as test_util from certbot import errors +from certbot.compat import filesystem from certbot.compat import misc from certbot.compat import os +import certbot.tests.util as test_util KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class AccountTest(unittest.TestCase): - """Tests for certbot.account.Account.""" + """Tests for certbot._internal.account.Account.""" def setUp(self): - from certbot.account import Account + from certbot._internal.account import Account self.regr = mock.MagicMock() self.meta = Account.Meta( creation_host="test.certbot.org", @@ -32,9 +30,9 @@ class AccountTest(unittest.TestCase): self.acc = Account(self.regr, KEY, self.meta) self.regr.__repr__ = mock.MagicMock(return_value="i_am_a_regr") - with mock.patch("certbot.account.socket") as mock_socket: + with mock.patch("certbot._internal.account.socket") as mock_socket: mock_socket.getfqdn.return_value = "test.certbot.org" - with mock.patch("certbot.account.datetime") as mock_dt: + with mock.patch("certbot._internal.account.datetime") as mock_dt: mock_dt.datetime.now.return_value = self.meta.creation_dt self.acc_no_meta = Account(self.regr, KEY) @@ -56,18 +54,18 @@ class AccountTest(unittest.TestCase): " 3) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_acme_1(self): - self._test_name1_tls_sni_01_1_common(combos=True) + def test_name1_http_01_1_acme_1(self): + self._test_name1_http_01_1_common(combos=True) - def test_name1_tls_sni_01_1_acme_2(self): + def test_name1_http_01_1_acme_2(self): self.mock_net.acme_version = 2 - self._test_name1_tls_sni_01_1_common(combos=False) + self._test_name1_http_01_1_common(combos=False) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_1(self): + def test_name1_http_01_1_dns_1_acme_1(self): self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) mock_order = mock.MagicMock(authorizations=[authzr]) authzr = self.handler.handle_authorizations(mock_order) - self.assertEqual(self.mock_net.answer_challenge.call_count, 3) + self.assertEqual(self.mock_net.answer_challenge.call_count, 2) self.assertEqual(self.mock_net.poll.call_count, 1) self.assertEqual(self.mock_auth.cleanup.call_count, 1) - # Test if list first element is TLSSNI01, use typ because it is an achall + # Test if list first element is http-01, use typ because it is an achall for achall in self.mock_auth.cleanup.call_args[0][0]: - self.assertTrue(achall.typ in ["tls-sni-01", "http-01", "dns-01"]) + self.assertTrue(achall.typ in ["http-01", "dns-01"]) # Length of authorizations list self.assertEqual(len(authzr), 1) - def test_name1_tls_sni_01_1_http_01_1_dns_1_acme_2(self): + def test_name1_http_01_1_dns_1_acme_2(self): self.mock_net.acme_version = 2 self.mock_net.poll.side_effect = _gen_mock_on_poll() - self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01) self.mock_auth.get_chall_pref.return_value.append(challenges.DNS01) authzr = gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=False) @@ -160,12 +157,12 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_auth.cleanup.call_count, 1) cleaned_up_achalls = self.mock_auth.cleanup.call_args[0][0] self.assertEqual(len(cleaned_up_achalls), 1) - self.assertEqual(cleaned_up_achalls[0].typ, "tls-sni-01") + self.assertEqual(cleaned_up_achalls[0].typ, "http-01") # Length of authorizations list self.assertEqual(len(authzr), 1) - def _test_name3_tls_sni_01_3_common(self, combos): + def _test_name3_http_01_3_common(self, combos): self.mock_net.request_domain_challenges.side_effect = functools.partial( gen_dom_authzr, challs=acme_util.CHALLENGES, combos=combos) @@ -186,12 +183,12 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(len(authzr), 3) - def test_name3_tls_sni_01_3_common_acme_1(self): - self._test_name3_tls_sni_01_3_common(combos=True) + def test_name3_http_01_3_common_acme_1(self): + self._test_name3_http_01_3_common(combos=True) - def test_name3_tls_sni_01_3_common_acme_2(self): + def test_name3_http_01_3_common_acme_2(self): self.mock_net.acme_version = 2 - self._test_name3_tls_sni_01_3_common(combos=False) + self._test_name3_http_01_3_common(combos=False) def test_debug_challenges(self): zope.component.provideUtility( @@ -257,7 +254,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p def _test_preferred_challenges_not_supported_common(self, combos): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES, combos=combos)] mock_order = mock.MagicMock(authorizations=authzrs) - self.handler.pref_challs.append(challenges.HTTP01.typ) + self.handler.pref_challs.append(challenges.DNS01.typ) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) @@ -283,7 +280,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_answer_error(self): self.mock_net.answer_challenge.side_effect = errors.AuthorizationError @@ -295,7 +292,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p errors.AuthorizationError, self.handler.handle_authorizations, mock_order) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_incomplete_authzr_error(self): authzrs = [gen_dom_authzr(domain="0", challs=acme_util.CHALLENGES)] @@ -308,7 +305,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertTrue('Some challenges have failed.' in str(error.exception)) self.assertEqual(self.mock_auth.cleanup.call_count, 1) self.assertEqual( - self.mock_auth.cleanup.call_args[0][0][0].typ, "tls-sni-01") + self.mock_auth.cleanup.call_args[0][0][0].typ, "http-01") def test_best_effort(self): def _conditional_mock_on_poll(authzr): @@ -327,7 +324,7 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p mock_order = mock.MagicMock(authorizations=authzrs) - with mock.patch('certbot.auth_handler._report_failed_authzrs') as mock_report: + with mock.patch('certbot._internal.auth_handler._report_failed_authzrs') as mock_report: valid_authzr = self.handler.handle_authorizations(mock_order, True) # Because best_effort=True, we did not blow up. Instead ... @@ -344,27 +341,58 @@ class HandleAuthorizationsTest(unittest.TestCase): # pylint: disable=too-many-p self.assertTrue('All challenges have failed.' in str(error.exception)) def test_validated_challenge_not_rerun(self): - # With pending challenge, we expect the challenge to be tried, and fail. + # With a pending challenge that is not supported by the plugin, we + # expect an exception to be raised. authzr = acme_util.gen_authzr( messages.STATUS_PENDING, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_PENDING], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.assertRaises( errors.AuthorizationError, self.handler.handle_authorizations, mock_order) - # With validated challenge; we expect the challenge not be tried again, and succeed. + # With a validated challenge that is not supported by the plugin, we + # expect the challenge to not be solved again and + # handle_authorizations() to succeed. authzr = acme_util.gen_authzr( messages.STATUS_VALID, "0", - [acme_util.HTTP01], + [acme_util.DNS01], [messages.STATUS_VALID], False) mock_order = mock.MagicMock(authorizations=[authzr]) self.handler.handle_authorizations(mock_order) - @mock.patch("certbot.auth_handler.logger") - def test_tls_sni_logs(self, logger): - self._test_name1_tls_sni_01_1_common(combos=True) - self.assertTrue("deprecated" in logger.warning.call_args[0][0]) + def test_valid_authzrs_deactivated(self): + """When we deactivate valid authzrs in an orderr, we expect them to become deactivated + and to receive a list of deactivated authzrs in return.""" + def _mock_deactivate(authzr): + if authzr.body.status == messages.STATUS_VALID: + if authzr.body.identifier.value == "is_valid_but_will_fail": + raise acme_errors.Error("Mock deactivation ACME error") + authzb = authzr.body.update(status=messages.STATUS_DEACTIVATED) + authzr = messages.AuthorizationResource(body=authzb) + else: # pragma: no cover + raise errors.Error("Can't deactivate non-valid authz") + return authzr + + to_deactivate = [("is_valid", messages.STATUS_VALID), + ("is_pending", messages.STATUS_PENDING), + ("is_valid_but_will_fail", messages.STATUS_VALID)] + + to_deactivate = [acme_util.gen_authzr(a[1], a[0], [acme_util.HTTP01], + [a[1], False]) for a in to_deactivate] + orderr = mock.MagicMock(authorizations=to_deactivate) + + self.mock_net.deactivate_authorization.side_effect = _mock_deactivate + + authzrs, failed = self.handler.deactivate_valid_authorizations(orderr) + + self.assertEqual(self.mock_net.deactivate_authorization.call_count, 2) + self.assertEqual(len(authzrs), 1) + self.assertEqual(len(failed), 1) + self.assertEqual(authzrs[0].body.identifier.value, "is_valid") + self.assertEqual(authzrs[0].body.status, messages.STATUS_DEACTIVATED) + self.assertEqual(failed[0].body.identifier.value, "is_valid_but_will_fail") + self.assertEqual(failed[0].body.status, messages.STATUS_VALID) def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): @@ -384,10 +412,10 @@ def _gen_mock_on_poll(status=messages.STATUS_VALID, retry=0, wait_value=1): class ChallbToAchallTest(unittest.TestCase): - """Tests for certbot.auth_handler.challb_to_achall.""" + """Tests for certbot._internal.auth_handler.challb_to_achall.""" def _call(self, challb): - from certbot.auth_handler import challb_to_achall + from certbot._internal.auth_handler import challb_to_achall return challb_to_achall(challb, "account_key", "domain") def test_it(self): @@ -400,7 +428,7 @@ class ChallbToAchallTest(unittest.TestCase): class GenChallengePathTest(unittest.TestCase): - """Tests for certbot.auth_handler.gen_challenge_path. + """Tests for certbot._internal.auth_handler.gen_challenge_path. .. todo:: Add more tests for dumb_path... depending on what we want to do. @@ -413,13 +441,13 @@ class GenChallengePathTest(unittest.TestCase): @classmethod def _call(cls, challbs, preferences, combinations): - from certbot.auth_handler import gen_challenge_path + from certbot._internal.auth_handler import gen_challenge_path return gen_challenge_path(challbs, preferences, combinations) def test_common_case(self): - """Given TLSSNI01 and HTTP01 with appropriate combos.""" - challbs = (acme_util.TLSSNI01_P, acme_util.HTTP01_P) - prefs = [challenges.TLSSNI01, challenges.HTTP01] + """Given DNS01 and HTTP01 with appropriate combos.""" + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.DNS01, challenges.HTTP01] combos = ((0,), (1,)) # Smart then trivial dumb path test @@ -430,8 +458,8 @@ class GenChallengePathTest(unittest.TestCase): self.assertTrue(self._call(challbs[::-1], prefs, None)) def test_not_supported(self): - challbs = (acme_util.DNS01_P, acme_util.TLSSNI01_P) - prefs = [challenges.TLSSNI01] + challbs = (acme_util.DNS01_P, acme_util.HTTP01_P) + prefs = [challenges.HTTP01] combos = ((0, 1),) # smart path fails because no challs in perfs satisfies combos @@ -443,7 +471,7 @@ class GenChallengePathTest(unittest.TestCase): class ReportFailedAuthzrsTest(unittest.TestCase): - """Tests for certbot.auth_handler._report_failed_authzrs.""" + """Tests for certbot._internal.auth_handler._report_failed_authzrs.""" # pylint: disable=protected-access def setUp(self): @@ -459,23 +487,23 @@ class ReportFailedAuthzrsTest(unittest.TestCase): http_01 = messages.ChallengeBody(**kwargs) - kwargs["chall"] = acme_util.TLSSNI01 - tls_sni_01 = messages.ChallengeBody(**kwargs) + kwargs["chall"] = acme_util.HTTP01 + http_01 = messages.ChallengeBody(**kwargs) self.authzr1 = mock.MagicMock() self.authzr1.body.identifier.value = 'example.com' - self.authzr1.body.challenges = [http_01, tls_sni_01] + self.authzr1.body.challenges = [http_01, http_01] - kwargs["error"] = messages.Error(typ="dnssec", detail="detail") - tls_sni_01_diff = messages.ChallengeBody(**kwargs) + kwargs["error"] = messages.Error.with_code("dnssec", detail="detail") + http_01_diff = messages.ChallengeBody(**kwargs) self.authzr2 = mock.MagicMock() self.authzr2.body.identifier.value = 'foo.bar' - self.authzr2.body.challenges = [tls_sni_01_diff] + self.authzr2.body.challenges = [http_01_diff] @test_util.patch_get_utility() def test_same_error_and_domain(self, mock_zope): - from certbot import auth_handler + from certbot._internal import auth_handler auth_handler._report_failed_authzrs([self.authzr1], 'key') call_list = mock_zope().add_message.call_args_list @@ -484,7 +512,7 @@ class ReportFailedAuthzrsTest(unittest.TestCase): @test_util.patch_get_utility() def test_different_errors_and_domains(self, mock_zope): - from certbot import auth_handler + from certbot._internal import auth_handler auth_handler._report_failed_authzrs([self.authzr1, self.authzr2], 'key') self.assertTrue(mock_zope().add_message.call_count == 2) diff --git a/certbot/tests/cert_manager_test.py b/certbot/tests/cert_manager_test.py index 08d7282a7..81134f02f 100644 --- a/certbot/tests/cert_manager_test.py +++ b/certbot/tests/cert_manager_test.py @@ -1,5 +1,5 @@ -"""Tests for certbot.cert_manager.""" +"""Tests for certbot._internal.cert_manager.""" # pylint: disable=protected-access import re import shutil @@ -9,13 +9,14 @@ import unittest import configobj import mock -from certbot import configuration from certbot import errors +from certbot._internal import configuration +from certbot._internal.storage import ALL_FOUR +from certbot.compat import filesystem from certbot.compat import os from certbot.display import util as display_util -from certbot.storage import ALL_FOUR -from certbot.tests import storage_test from certbot.tests import util as test_util +import storage_test class BaseCertManagerTest(test_util.ConfigTestCase): @@ -25,7 +26,7 @@ class BaseCertManagerTest(test_util.ConfigTestCase): super(BaseCertManagerTest, self).setUp() self.config.quiet = False - os.makedirs(self.config.renewal_configs_dir) + filesystem.makedirs(self.config.renewal_configs_dir) self.domains = { "example.org": None, @@ -43,14 +44,14 @@ class BaseCertManagerTest(test_util.ConfigTestCase): def _set_up_config(self, domain, custom_archive): # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 - os.makedirs(os.path.join(self.config.live_dir, domain)) + filesystem.makedirs(os.path.join(self.config.live_dir, domain)) config_file = configobj.ConfigObj() if custom_archive is not None: - os.makedirs(custom_archive) + filesystem.makedirs(custom_archive) config_file["archive_dir"] = custom_archive else: - os.makedirs(os.path.join(self.config.default_archive_dir, domain)) + filesystem.makedirs(os.path.join(self.config.default_archive_dir, domain)) for kind in ALL_FOUR: config_file[kind] = os.path.join(self.config.live_dir, domain, @@ -63,13 +64,12 @@ class BaseCertManagerTest(test_util.ConfigTestCase): class UpdateLiveSymlinksTest(BaseCertManagerTest): - """Tests for certbot.cert_manager.update_live_symlinks + """Tests for certbot._internal.cert_manager.update_live_symlinks """ def test_update_live_symlinks(self): """Test update_live_symlinks""" - # pylint: disable=too-many-statements # create files with incorrect symlinks - from certbot import cert_manager + from certbot._internal import cert_manager archive_paths = {} for domain in self.domains: custom_archive = self.domains[domain] @@ -96,23 +96,23 @@ class UpdateLiveSymlinksTest(BaseCertManagerTest): for kind in ALL_FOUR: os.chdir(os.path.dirname(self.config_files[domain][kind])) self.assertEqual( - os.path.realpath(os.readlink(self.config_files[domain][kind])), - os.path.realpath(archive_paths[domain][kind])) + filesystem.realpath(os.readlink(self.config_files[domain][kind])), + filesystem.realpath(archive_paths[domain][kind])) finally: os.chdir(prev_dir) class DeleteTest(storage_test.BaseRenewableCertTest): - """Tests for certbot.cert_manager.delete + """Tests for certbot._internal.cert_manager.delete """ def _call(self): - from certbot import cert_manager + from certbot._internal import cert_manager cert_manager.delete(self.config) @test_util.patch_get_utility() - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.storage.delete_files') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.storage.delete_files') def test_delete_from_config(self, mock_delete_files, mock_lineage_for_certname, unused_get_utility): """Test delete""" @@ -122,8 +122,8 @@ class DeleteTest(storage_test.BaseRenewableCertTest): mock_delete_files.assert_called_once_with(self.config, "example.org") @test_util.patch_get_utility() - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.storage.delete_files') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.storage.delete_files') def test_delete_interactive_single(self, mock_delete_files, mock_lineage_for_certname, mock_util): """Test delete""" @@ -133,8 +133,8 @@ class DeleteTest(storage_test.BaseRenewableCertTest): mock_delete_files.assert_called_once_with(self.config, "example.org") @test_util.patch_get_utility() - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.storage.delete_files') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.storage.delete_files') def test_delete_interactive_multiple(self, mock_delete_files, mock_lineage_for_certname, mock_util): """Test delete""" @@ -147,20 +147,20 @@ class DeleteTest(storage_test.BaseRenewableCertTest): class CertificatesTest(BaseCertManagerTest): - """Tests for certbot.cert_manager.certificates + """Tests for certbot._internal.cert_manager.certificates """ def _certificates(self, *args, **kwargs): - from certbot.cert_manager import certificates + from certbot._internal.cert_manager import certificates return certificates(*args, **kwargs) - @mock.patch('certbot.cert_manager.logger') + @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_get_utility() def test_certificates_parse_fail(self, mock_utility, mock_logger): self._certificates(self.config) self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member self.assertTrue(mock_utility.called) - @mock.patch('certbot.cert_manager.logger') + @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_get_utility() def test_certificates_quiet(self, mock_utility, mock_logger): self.config.quiet = True @@ -169,21 +169,21 @@ class CertificatesTest(BaseCertManagerTest): self.assertTrue(mock_logger.warning.called) #pylint: disable=no-member @mock.patch('certbot.crypto_util.verify_renewable_cert') - @mock.patch('certbot.cert_manager.logger') + @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_get_utility() - @mock.patch("certbot.storage.RenewableCert") - @mock.patch('certbot.cert_manager._report_human_readable') + @mock.patch("certbot._internal.storage.RenewableCert") + @mock.patch('certbot._internal.cert_manager._report_human_readable') def test_certificates_parse_success(self, mock_report, mock_renewable_cert, mock_utility, mock_logger, mock_verifier): mock_verifier.return_value = None mock_report.return_value = "" self._certificates(self.config) - self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member + self.assertFalse(mock_logger.warning.called) self.assertTrue(mock_report.called) self.assertTrue(mock_utility.called) self.assertTrue(mock_renewable_cert.called) - @mock.patch('certbot.cert_manager.logger') + @mock.patch('certbot._internal.cert_manager.logger') @test_util.patch_get_utility() def test_certificates_no_files(self, mock_utility, mock_logger): empty_tempdir = tempfile.mkdtemp() @@ -194,16 +194,16 @@ class CertificatesTest(BaseCertManagerTest): quiet=False )) - os.makedirs(empty_config.renewal_configs_dir) + filesystem.makedirs(empty_config.renewal_configs_dir) self._certificates(empty_config) - self.assertFalse(mock_logger.warning.called) #pylint: disable=no-member + self.assertFalse(mock_logger.warning.called) self.assertTrue(mock_utility.called) shutil.rmtree(empty_tempdir) - @mock.patch('certbot.cert_manager.ocsp.RevocationChecker.ocsp_revoked') - def test_report_human_readable(self, mock_revoked): #pylint: disable=too-many-statements + @mock.patch('certbot._internal.cert_manager.ocsp.RevocationChecker.ocsp_revoked') + def test_report_human_readable(self, mock_revoked): mock_revoked.return_value = None - from certbot import cert_manager + from certbot._internal import cert_manager import datetime import pytz expiry = pytz.UTC.fromutc(datetime.datetime.utcnow()) @@ -240,7 +240,7 @@ class CertificatesTest(BaseCertManagerTest): # pylint: disable=protected-access out = get_report() self.assertTrue('3 days' in out) - self.assertTrue('VALID' in out and 'INVALID' not in out) + self.assertTrue('VALID' in out and 'INVALID' not in out) cert.is_test_cert = True mock_revoked.return_value = True @@ -270,90 +270,82 @@ class CertificatesTest(BaseCertManagerTest): class SearchLineagesTest(BaseCertManagerTest): - """Tests for certbot.cert_manager._search_lineages.""" + """Tests for certbot._internal.cert_manager._search_lineages.""" @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.RenewableCert') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.RenewableCert') def test_cert_storage_error(self, mock_renewable_cert, mock_renewal_conf_files, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_files.return_value = ["badfile"] mock_renewable_cert.side_effect = errors.CertStorageError - from certbot import cert_manager + from certbot._internal import cert_manager # pylint: disable=protected-access - self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, "check"), - "check") + self.assertEqual(cert_manager._search_lineages(self.config, lambda x: x, "check"), "check") self.assertTrue(mock_make_or_verify_dir.called) class LineageForCertnameTest(BaseCertManagerTest): - """Tests for certbot.cert_manager.lineage_for_certname""" + """Tests for certbot._internal.cert_manager.lineage_for_certname""" @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.storage.RenewableCert') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.storage.RenewableCert') def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" mock_match = mock.Mock(lineagename="example.com") mock_renewable_cert.return_value = mock_match - from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - mock_match) + from certbot._internal import cert_manager + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), mock_match) self.assertTrue(mock_make_or_verify_dir.called) @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_match(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "other.com.conf" - from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - None) + from certbot._internal import cert_manager + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), None) self.assertTrue(mock_make_or_verify_dir.called) @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_renewal_file(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + def test_no_renewal_file(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.side_effect = errors.CertStorageError() - from certbot import cert_manager - self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), - None) + from certbot._internal import cert_manager + self.assertEqual(cert_manager.lineage_for_certname(self.config, "example.com"), None) self.assertTrue(mock_make_or_verify_dir.called) class DomainsForCertnameTest(BaseCertManagerTest): - """Tests for certbot.cert_manager.domains_for_certname""" + """Tests for certbot._internal.cert_manager.domains_for_certname""" @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.storage.RenewableCert') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.storage.RenewableCert') def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file, - mock_make_or_verify_dir): + mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" mock_match = mock.Mock(lineagename="example.com") domains = ["example.com", "example.org"] mock_match.names.return_value = domains mock_renewable_cert.return_value = mock_match - from certbot import cert_manager + from certbot._internal import cert_manager self.assertEqual(cert_manager.domains_for_certname(self.config, "example.com"), domains) self.assertTrue(mock_make_or_verify_dir.called) @mock.patch('certbot.util.make_or_verify_dir') - @mock.patch('certbot.storage.renewal_file_for_certname') - def test_no_match(self, mock_renewal_conf_file, - mock_make_or_verify_dir): + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + def test_no_match(self, mock_renewal_conf_file, mock_make_or_verify_dir): mock_renewal_conf_file.return_value = "somefile.conf" - from certbot import cert_manager - self.assertEqual(cert_manager.domains_for_certname(self.config, "other.com"), - None) + from certbot._internal import cert_manager + self.assertEqual(cert_manager.domains_for_certname(self.config, "other.com"), None) self.assertTrue(mock_make_or_verify_dir.called) class RenameLineageTest(BaseCertManagerTest): - """Tests for certbot.cert_manager.rename_lineage""" + """Tests for certbot._internal.cert_manager.rename_lineage""" def setUp(self): super(RenameLineageTest, self).setUp() @@ -361,10 +353,10 @@ class RenameLineageTest(BaseCertManagerTest): self.config.new_certname = "after" def _call(self, *args, **kwargs): - from certbot import cert_manager + from certbot._internal import cert_manager return cert_manager.rename_lineage(*args, **kwargs) - @mock.patch('certbot.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.renewal_conf_files') @test_util.patch_get_utility() def test_no_certname(self, mock_get_utility, mock_renewal_conf_files): self.config.certname = None @@ -395,7 +387,7 @@ class RenameLineageTest(BaseCertManagerTest): self.assertRaises(errors.Error, self._call, self.config) @test_util.patch_get_utility() - @mock.patch('certbot.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') def test_no_existing_certname(self, mock_lineage_for_certname, unused_get_utility): self.config.certname = "one" self.config.new_certname = "two" @@ -404,30 +396,30 @@ class RenameLineageTest(BaseCertManagerTest): self._call, self.config) @test_util.patch_get_utility() - @mock.patch("certbot.storage.RenewableCert._check_symlinks") + @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") def test_rename_cert(self, mock_check, unused_get_utility): mock_check.return_value = True self._call(self.config) - from certbot import cert_manager + from certbot._internal import cert_manager updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname) self.assertTrue(updated_lineage is not None) self.assertEqual(updated_lineage.lineagename, self.config.new_certname) @test_util.patch_get_utility() - @mock.patch("certbot.storage.RenewableCert._check_symlinks") + @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") def test_rename_cert_interactive_certname(self, mock_check, mock_get_utility): mock_check.return_value = True self.config.certname = None util_mock = mock_get_utility() util_mock.menu.return_value = (display_util.OK, 0) self._call(self.config) - from certbot import cert_manager + from certbot._internal import cert_manager updated_lineage = cert_manager.lineage_for_certname(self.config, self.config.new_certname) self.assertTrue(updated_lineage is not None) self.assertEqual(updated_lineage.lineagename, self.config.new_certname) @test_util.patch_get_utility() - @mock.patch("certbot.storage.RenewableCert._check_symlinks") + @mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") def test_rename_cert_bad_new_certname(self, mock_check, unused_get_utility): mock_check.return_value = True @@ -449,7 +441,7 @@ class DuplicativeCertsTest(storage_test.BaseRenewableCertTest): @mock.patch('certbot.util.make_or_verify_dir') def test_find_duplicative_names(self, unused_makedir): - from certbot.cert_manager import find_duplicative_certs + from certbot._internal.cert_manager import find_duplicative_certs test_cert = test_util.load_vector('cert-san_512.pem') with open(self.test_rc.cert, 'wb') as f: f.write(test_cert) @@ -478,7 +470,7 @@ class DuplicativeCertsTest(storage_test.BaseRenewableCertTest): class CertPathToLineageTest(storage_test.BaseRenewableCertTest): - """Tests for certbot.cert_manager.cert_path_to_lineage""" + """Tests for certbot._internal.cert_manager.cert_path_to_lineage""" def setUp(self): super(CertPathToLineageTest, self).setUp() @@ -489,11 +481,11 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): self.config.cert_path = (self.fullchain, '') def _call(self, cli_config): - from certbot.cert_manager import cert_path_to_lineage + from certbot._internal.cert_manager import cert_path_to_lineage return cert_path_to_lineage(cli_config) def _archive_files(self, cli_config, filetype): - from certbot.cert_manager import _archive_files + from certbot._internal.cert_manager import _archive_files return _archive_files(cli_config, filetype) def test_basic_match(self): @@ -505,13 +497,13 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): 'SailorMoon', 'fullchain.pem') self.assertRaises(errors.Error, self._call, bad_test_config) - @mock.patch('certbot.cert_manager._acceptable_matches') + @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_fullchain(self, mock_acceptable_matches): mock_acceptable_matches.return_value = [lambda x: x.fullchain_path] self.config.fullchain_path = self.fullchain self.assertEqual('example.org', self._call(self.config)) - @mock.patch('certbot.cert_manager._acceptable_matches') + @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_cert_path(self, mock_acceptable_matches): mock_acceptable_matches.return_value = [lambda x: x.cert_path] test_cert_path = os.path.join(self.config.config_dir, 'live', 'example.org', @@ -519,7 +511,7 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): self.config.cert_path = (test_cert_path, '') self.assertEqual('example.org', self._call(self.config)) - @mock.patch('certbot.cert_manager._acceptable_matches') + @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_archive_cert(self, mock_acceptable_matches): # Also this and the next test check that the regex of _archive_files is working. self.config.cert_path = (os.path.join(self.config.config_dir, 'archive', 'example.org', @@ -527,7 +519,7 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): mock_acceptable_matches.return_value = [lambda x: self._archive_files(x, 'cert')] self.assertEqual('example.org', self._call(self.config)) - @mock.patch('certbot.cert_manager._acceptable_matches') + @mock.patch('certbot._internal.cert_manager._acceptable_matches') def test_options_archive_fullchain(self, mock_acceptable_matches): self.config.cert_path = (os.path.join(self.config.config_dir, 'archive', 'example.org', 'fullchain11.pem'), '') @@ -537,7 +529,8 @@ class CertPathToLineageTest(storage_test.BaseRenewableCertTest): class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest): - """Tests for certbot.cert_manager.match_and_check_overlaps w/o overlapping archive dirs.""" + """Tests for certbot._internal.cert_manager.match_and_check_overlaps w/o overlapping + archive dirs.""" # A test with real overlapping archive dirs can be found in tests/boulder_integration.sh def setUp(self): super(MatchAndCheckOverlaps, self).setUp() @@ -548,27 +541,27 @@ class MatchAndCheckOverlaps(storage_test.BaseRenewableCertTest): self.config.cert_path = (self.fullchain, '') def _call(self, cli_config, acceptable_matches, match_func, rv_func): - from certbot.cert_manager import match_and_check_overlaps + from certbot._internal.cert_manager import match_and_check_overlaps return match_and_check_overlaps(cli_config, acceptable_matches, match_func, rv_func) def test_basic_match(self): - from certbot.cert_manager import _acceptable_matches + from certbot._internal.cert_manager import _acceptable_matches self.assertEqual(['example.org'], self._call(self.config, _acceptable_matches(), lambda x: self.config.cert_path[0], lambda x: x.lineagename)) - @mock.patch('certbot.cert_manager._search_lineages') + @mock.patch('certbot._internal.cert_manager._search_lineages') def test_no_matches(self, mock_search_lineages): mock_search_lineages.return_value = [] self.assertRaises(errors.Error, self._call, self.config, None, None, None) - @mock.patch('certbot.cert_manager._search_lineages') + @mock.patch('certbot._internal.cert_manager._search_lineages') def test_too_many_matches(self, mock_search_lineages): mock_search_lineages.return_value = ['spider', 'dance'] self.assertRaises(errors.OverlappingMatchFound, self._call, self.config, None, None, None) class GetCertnameTest(unittest.TestCase): - """Tests for certbot.cert_manager.""" + """Tests for certbot._internal.cert_manager.""" def setUp(self): self.get_utility_patch = test_util.patch_get_utility() @@ -579,12 +572,12 @@ class GetCertnameTest(unittest.TestCase): def tearDown(self): self.get_utility_patch.stop() - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @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): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager prompt = "Which certificate would you" self.mock_get_utility().menu.return_value = (display_util.OK, 0) self.assertEqual( @@ -593,12 +586,12 @@ class GetCertnameTest(unittest.TestCase): self.assertTrue( prompt in self.mock_get_utility().menu.call_args[0][0]) - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames_custom_prompt(self, mock_name, mock_files): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager prompt = "custom prompt" self.mock_get_utility().menu.return_value = (display_util.OK, 0) self.assertEqual( @@ -608,24 +601,24 @@ class GetCertnameTest(unittest.TestCase): self.assertEqual(self.mock_get_utility().menu.call_args[0][0], prompt) - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames_user_abort(self, mock_name, mock_files): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager self.mock_get_utility().menu.return_value = (display_util.CANCEL, 0) self.assertRaises( errors.Error, cert_manager.get_certnames, self.config, "erroring_anyway", allow_multiple=False) - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames_allow_multiple(self, mock_name, mock_files): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager prompt = "Which certificate(s) would you" self.mock_get_utility().checklist.return_value = (display_util.OK, ['example.com']) @@ -635,12 +628,12 @@ class GetCertnameTest(unittest.TestCase): self.assertTrue( prompt in self.mock_get_utility().checklist.call_args[0][0]) - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames_allow_multiple_custom_prompt(self, mock_name, mock_files): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager prompt = "custom prompt" self.mock_get_utility().checklist.return_value = (display_util.OK, ['example.com']) @@ -652,12 +645,12 @@ class GetCertnameTest(unittest.TestCase): self.mock_get_utility().checklist.call_args[0][0], prompt) - @mock.patch('certbot.storage.renewal_conf_files') - @mock.patch('certbot.storage.lineagename_for_filename') + @mock.patch('certbot._internal.storage.renewal_conf_files') + @mock.patch('certbot._internal.storage.lineagename_for_filename') def test_get_certnames_allow_multiple_user_abort(self, mock_name, mock_files): mock_files.return_value = ['example.com.conf'] mock_name.return_value = 'example.com' - from certbot import cert_manager + from certbot._internal import cert_manager self.mock_get_utility().checklist.return_value = (display_util.CANCEL, []) self.assertRaises( errors.Error, diff --git a/certbot/tests/cli_test.py b/certbot/tests/cli_test.py index 8259d4040..05da1da4e 100644 --- a/certbot/tests/cli_test.py +++ b/certbot/tests/cli_test.py @@ -1,7 +1,6 @@ -"""Tests for certbot.cli.""" +"""Tests for certbot._internal.cli.""" import argparse import copy -import sys import tempfile import unittest @@ -10,47 +9,57 @@ import six from six.moves import reload_module # pylint: disable=import-error from acme import challenges - -import certbot.tests.util as test_util -from certbot import cli -from certbot import constants from certbot import errors +from certbot._internal import cli +from certbot._internal import constants +from certbot._internal.plugins import disco +from certbot.compat import filesystem from certbot.compat import os -from certbot.plugins import disco +import certbot.tests.util as test_util from certbot.tests.util import TempDirTestCase PLUGINS = disco.PluginsRegistry.find_all() class TestReadFile(TempDirTestCase): - '''Test cli.read_file''' - - + """Test cli.read_file""" def test_read_file(self): - rel_test_path = os.path.relpath(os.path.join(self.tempdir, 'foo')) - self.assertRaises( - argparse.ArgumentTypeError, cli.read_file, rel_test_path) + curr_dir = os.getcwd() + try: + # On Windows current directory may be on a different drive than self.tempdir. + # 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')) + self.assertRaises( + argparse.ArgumentTypeError, cli.read_file, rel_test_path) - test_contents = b'bar\n' - with open(rel_test_path, 'wb') as f: - f.write(test_contents) + test_contents = b'bar\n' + with open(rel_test_path, 'wb') as f: + f.write(test_contents) - path, contents = cli.read_file(rel_test_path) - self.assertEqual(path, os.path.abspath(path)) - self.assertEqual(contents, test_contents) + path, contents = cli.read_file(rel_test_path) + self.assertEqual(path, os.path.abspath(path)) + self.assertEqual(contents, test_contents) + finally: + os.chdir(curr_dir) class FlagDefaultTest(unittest.TestCase): """Tests cli.flag_default""" - def test_linux_directories(self): - if 'fcntl' in sys.modules: + def test_default_directories(self): + if os.name != 'nt': self.assertEqual(cli.flag_default('config_dir'), '/etc/letsencrypt') self.assertEqual(cli.flag_default('work_dir'), '/var/lib/letsencrypt') self.assertEqual(cli.flag_default('logs_dir'), '/var/log/letsencrypt') + else: + self.assertEqual(cli.flag_default('config_dir'), 'C:\\Certbot') + self.assertEqual(cli.flag_default('work_dir'), 'C:\\Certbot\\lib') + self.assertEqual(cli.flag_default('logs_dir'), 'C:\\Certbot\\log') -class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods +class ParseTest(unittest.TestCase): '''Test the cli args entrypoint''' @@ -76,15 +85,15 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods def write_msg(message, *args, **kwargs): # pylint: disable=missing-docstring,unused-argument output.write(message) - with mock.patch('certbot.main.sys.stdout', new=output): + with mock.patch('certbot._internal.main.sys.stdout', new=output): with test_util.patch_get_utility() as mock_get_utility: mock_get_utility().notification.side_effect = write_msg - with mock.patch('certbot.main.sys.stderr'): + with mock.patch('certbot._internal.main.sys.stderr'): self.assertRaises(SystemExit, self._unmocked_parse, args, output) return output.getvalue() - @mock.patch("certbot.cli.flag_default") + @mock.patch("certbot._internal.cli.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 @@ -116,7 +125,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods chain = 'chain' fullchain = 'fullchain' - with mock.patch('certbot.main.install'): + with mock.patch('certbot._internal.main.install'): namespace = self.parse(['install', '--cert-path', cert, '--key-path', 'key', '--chain-path', 'chain', '--fullchain-path', 'fullchain']) @@ -176,7 +185,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertTrue("--delete-after-revoke" in out) self.assertTrue("--no-delete-after-revoke" in out) - out = self._help_output(['-h', 'config_changes']) + out = self._help_output(['-h', 'register']) self.assertTrue("--cert-path" not in out) self.assertTrue("--key-path" not in out) @@ -239,13 +248,6 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(namespace.pref_challs, expected) - # TODO: to be removed once tls-sni deprecation logic is removed - with mock.patch('certbot.cli.logger.warning') as mock_warn: - self.assertEqual(self.parse(['--preferred-challenges', 'http, tls-sni']).pref_challs, - [challenges.HTTP01.typ]) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - short_args = ['--preferred-challenges', 'jumping-over-the-moon'] # argparse.ArgumentError makes argparse print more information # to stderr and call sys.exit() @@ -262,16 +264,6 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertTrue(namespace.must_staple) self.assertTrue(namespace.staple) - def test_no_gui(self): - args = ['renew', '--dialog'] - with mock.patch("certbot.util.logger.warning") as mock_warn: - namespace = self.parse(args) - - self.assertTrue(namespace.noninteractive_mode) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue("is deprecated" in mock_warn.call_args[0][0]) - self.assertEqual("--dialog", mock_warn.call_args[0][1]) - def _check_server_conflict_message(self, parser_args, conflicting_args): try: self.parse(parser_args) @@ -318,21 +310,31 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.parse(short_args + ['renew']), False) account_dir = os.path.join(config_dir, constants.ACCOUNTS_DIR) - os.mkdir(account_dir) - os.mkdir(os.path.join(account_dir, 'fake_account_dir')) + filesystem.mkdir(account_dir) + filesystem.mkdir(os.path.join(account_dir, 'fake_account_dir')) self._assert_dry_run_flag_worked(self.parse(short_args + ['auth']), True) self._assert_dry_run_flag_worked(self.parse(short_args + ['renew']), True) + self._assert_dry_run_flag_worked(self.parse(short_args + ['certonly']), True) + short_args += ['certonly'] - self._assert_dry_run_flag_worked(self.parse(short_args), True) - short_args += '--server example.com'.split() - conflicts = ['--dry-run'] - self._check_server_conflict_message(short_args, '--dry-run') + # `--dry-run --server example.com` should emit example.com + self.assertEqual(self.parse(short_args + ['--server', 'example.com']).server, + 'example.com') - short_args += ['--staging'] - conflicts += ['--staging'] - self._check_server_conflict_message(short_args, conflicts) + # `--dry-run --server STAGING_URI` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', constants.STAGING_URI]).server, + constants.STAGING_URI) + + # `--dry-run --server LIVE` should emit STAGING_URI + self.assertEqual(self.parse(short_args + ['--server', cli.flag_default("server")]).server, + constants.STAGING_URI) + + # `--dry-run --server example.com --staging` should emit an error + conflicts = ['--staging'] + self._check_server_conflict_message(short_args + ['--server', 'example.com', '--staging'], + conflicts) def test_option_was_set(self): key_size_option = 'rsa_key_size' @@ -363,7 +365,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods errors.Error, self.parse, "-n --force-interactive".split()) def test_deploy_hook_conflict(self): - with mock.patch("certbot.cli.sys.stderr"): + with mock.patch("certbot._internal.cli.sys.stderr"): self.assertRaises(SystemExit, self.parse, "--renew-hook foo --deploy-hook bar".split()) @@ -383,7 +385,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertEqual(namespace.renew_hook, value) def test_renew_hook_conflict(self): - with mock.patch("certbot.cli.sys.stderr"): + with mock.patch("certbot._internal.cli.sys.stderr"): self.assertRaises(SystemExit, self.parse, "--deploy-hook foo --renew-hook bar".split()) @@ -403,7 +405,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods self.assertEqual(namespace.renew_hook, value) def test_max_log_backups_error(self): - with mock.patch('certbot.cli.sys.stderr'): + with mock.patch('certbot._internal.cli.sys.stderr'): self.assertRaises( SystemExit, self.parse, "--max-log-backups foo".split()) self.assertRaises( @@ -459,7 +461,7 @@ class ParseTest(unittest.TestCase): # pylint: disable=too-many-public-methods class DefaultTest(unittest.TestCase): - """Tests for certbot.cli._Default.""" + """Tests for certbot._internal.cli._Default.""" def setUp(self): @@ -533,7 +535,7 @@ class SetByCliTest(unittest.TestCase): def _call_set_by_cli(var, args, verb): - with mock.patch('certbot.cli.helpful_parser') as mock_parser: + with mock.patch('certbot._internal.cli.helpful_parser') as mock_parser: with test_util.patch_get_utility(): mock_parser.args = args mock_parser.verb = verb diff --git a/certbot/tests/client_test.py b/certbot/tests/client_test.py index 8d2cd76b9..7232ed84b 100644 --- a/certbot/tests/client_test.py +++ b/certbot/tests/client_test.py @@ -1,29 +1,29 @@ -"""Tests for certbot.client.""" +"""Tests for certbot._internal.client.""" import platform import shutil import tempfile import unittest +from josepy import interfaces import mock -from josepy import interfaces - -import certbot.tests.util as test_util -from certbot import account from certbot import errors -from certbot.compat import os -from certbot.compat import filesystem from certbot import util +from certbot._internal import account +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util KEY = test_util.load_vector("rsa512_key.pem") CSR_SAN = test_util.load_vector("csr-san_512.pem") +# pylint: disable=line-too-long class DetermineUserAgentTest(test_util.ConfigTestCase): - """Tests for certbot.client.determine_user_agent.""" + """Tests for certbot._internal.client.determine_user_agent.""" def _call(self): - from certbot.client import determine_user_agent + from certbot._internal.client import determine_user_agent return determine_user_agent(self.config) @mock.patch.dict(os.environ, {"CERTBOT_DOCS": "1"}) @@ -52,7 +52,7 @@ class DetermineUserAgentTest(test_util.ConfigTestCase): class RegisterTest(test_util.ConfigTestCase): - """Tests for certbot.client.register.""" + """Tests for certbot._internal.client.register.""" def setUp(self): super(RegisterTest, self).setUp() @@ -62,7 +62,7 @@ class RegisterTest(test_util.ConfigTestCase): self.account_storage = account.AccountMemoryStorage() def _call(self): - from certbot.client import register + from certbot._internal.client import register tos_cb = mock.MagicMock() return register(self.config, self.account_storage, tos_cb) @@ -85,11 +85,11 @@ class RegisterTest(test_util.ConfigTestCase): return False def test_no_tos(self): - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client.new_account_and_tos().terms_of_service = "http://tos" mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription") as mock_handle: - with mock.patch("certbot.account.report_new_account"): + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: + with mock.patch("certbot._internal.account.report_new_account"): mock_client().new_account_and_tos.side_effect = errors.Error self.assertRaises(errors.Error, self._call) self.assertFalse(mock_handle.called) @@ -99,36 +99,36 @@ class RegisterTest(test_util.ConfigTestCase): self.assertTrue(mock_handle.called) def test_it(self): - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.account.report_new_account"): - with mock.patch("certbot.eff.handle_subscription"): + with mock.patch("certbot._internal.account.report_new_account"): + with mock.patch("certbot._internal.eff.handle_subscription"): self._call() - @mock.patch("certbot.account.report_new_account") - @mock.patch("certbot.client.display_ops.get_email") + @mock.patch("certbot._internal.account.report_new_account") + @mock.patch("certbot._internal.client.display_ops.get_email") def test_email_retry(self, _rep, mock_get_email): from acme import messages self.config.noninteractive_mode = False msg = "DNS problem: NXDOMAIN looking up MX for example.com" mx_err = messages.Error.with_code('invalidContact', detail=msg) - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription") as mock_handle: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self._call() self.assertEqual(mock_get_email.call_count, 1) self.assertTrue(mock_handle.called) - @mock.patch("certbot.account.report_new_account") + @mock.patch("certbot._internal.account.report_new_account") def test_email_invalid_noninteractive(self, _rep): from acme import messages self.config.noninteractive_mode = True msg = "DNS problem: NXDOMAIN looking up MX for example.com" mx_err = messages.Error.with_code('invalidContact', detail=msg) - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription"): + with mock.patch("certbot._internal.eff.handle_subscription"): mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self.assertRaises(errors.Error, self._call) @@ -136,12 +136,12 @@ class RegisterTest(test_util.ConfigTestCase): self.config.email = None self.assertRaises(errors.Error, self._call) - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.logger") def test_without_email(self, mock_logger): - with mock.patch("certbot.eff.handle_subscription") as mock_handle: - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_clnt: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_clnt: mock_clnt().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.account.report_new_account"): + with mock.patch("certbot._internal.account.report_new_account"): self.config.email = None self.config.register_unsafely_without_email = True self.config.dry_run = False @@ -149,14 +149,14 @@ class RegisterTest(test_util.ConfigTestCase): mock_logger.info.assert_called_once_with(mock.ANY) self.assertTrue(mock_handle.called) - @mock.patch("certbot.account.report_new_account") - @mock.patch("certbot.client.display_ops.get_email") + @mock.patch("certbot._internal.account.report_new_account") + @mock.patch("certbot._internal.client.display_ops.get_email") def test_dry_run_no_staging_account(self, _rep, mock_get_email): """Tests dry-run for no staging account, expect account created with no email""" - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription"): - with mock.patch("certbot.account.report_new_account"): + with mock.patch("certbot._internal.eff.handle_subscription"): + with mock.patch("certbot._internal.account.report_new_account"): self.config.dry_run = True self._call() # check Certbot did not ask the user to provide an email @@ -165,13 +165,13 @@ class RegisterTest(test_util.ConfigTestCase): self.assertFalse(mock_client().new_account_and_tos.call_args[0][0].contact) def test_with_eab_arguments(self): - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().client.directory.__getitem__ = mock.Mock( side_effect=self._new_acct_dir_mock ) mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription"): - target = "certbot.client.messages.ExternalAccountBinding.from_data" + with mock.patch("certbot._internal.eff.handle_subscription"): + target = "certbot._internal.client.messages.ExternalAccountBinding.from_data" with mock.patch(target) as mock_eab_from_data: self.config.eab_kid = "test-kid" self.config.eab_hmac_key = "J2OAqW4MHXsrHVa_PVg0Y-L_R4SYw0_aL1le6mfblbE" @@ -180,10 +180,10 @@ class RegisterTest(test_util.ConfigTestCase): self.assertTrue(mock_eab_from_data.called) def test_without_eab_arguments(self): - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription"): - target = "certbot.client.messages.ExternalAccountBinding.from_data" + with mock.patch("certbot._internal.eff.handle_subscription"): + target = "certbot._internal.client.messages.ExternalAccountBinding.from_data" with mock.patch(target) as mock_eab_from_data: self.config.eab_kid = None self.config.eab_hmac_key = None @@ -192,11 +192,11 @@ class RegisterTest(test_util.ConfigTestCase): self.assertFalse(mock_eab_from_data.called) def test_external_account_required_without_eab_arguments(self): - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().client.net.key.public_key = mock.Mock(side_effect=self._public_key_mock) mock_client().external_account_required.side_effect = self._true_mock - with mock.patch("certbot.eff.handle_subscription"): - with mock.patch("certbot.client.messages.ExternalAccountBinding.from_data"): + with mock.patch("certbot._internal.eff.handle_subscription"): + with mock.patch("certbot._internal.client.messages.ExternalAccountBinding.from_data"): self.config.eab_kid = None self.config.eab_hmac_key = None @@ -205,20 +205,20 @@ class RegisterTest(test_util.ConfigTestCase): def test_unsupported_error(self): from acme import messages msg = "Test" - mx_err = messages.Error(detail=msg, typ="malformed", title="title") - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as mock_client: + mx_err = messages.Error.with_code("malformed", detail=msg, title="title") + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as mock_client: mock_client().client.directory.__getitem__ = mock.Mock( side_effect=self._new_acct_dir_mock ) mock_client().external_account_required.side_effect = self._false_mock - with mock.patch("certbot.eff.handle_subscription") as mock_handle: + with mock.patch("certbot._internal.eff.handle_subscription") as mock_handle: mock_client().new_account_and_tos.side_effect = [mx_err, mock.MagicMock()] self.assertRaises(messages.Error, self._call) self.assertFalse(mock_handle.called) class ClientTestCommon(test_util.ConfigTestCase): - """Common base class for certbot.client.Client tests.""" + """Common base class for certbot._internal.client.Client tests.""" def setUp(self): super(ClientTestCommon, self).setUp() @@ -227,8 +227,8 @@ class ClientTestCommon(test_util.ConfigTestCase): self.account = mock.MagicMock(**{"key.pem": KEY}) - from certbot.client import Client - with mock.patch("certbot.client.acme_client.BackwardsCompatibleClientV2") as acme: + from certbot._internal.client import Client + with mock.patch("certbot._internal.client.acme_client.BackwardsCompatibleClientV2") as acme: self.acme_client = acme self.acme = acme.return_value = mock.MagicMock() self.client = Client( @@ -237,7 +237,7 @@ class ClientTestCommon(test_util.ConfigTestCase): class ClientTest(ClientTestCommon): - """Tests for certbot.client.Client.""" + """Tests for certbot._internal.client.Client.""" def setUp(self): super(ClientTest, self).setUp() @@ -256,6 +256,7 @@ class ClientTest(ClientTestCommon): def _mock_obtain_certificate(self): self.client.auth_handler = mock.MagicMock() self.client.auth_handler.handle_authorizations.return_value = [None] + self.client.auth_handler.deactivate_valid_authorizations.return_value = ([], []) self.acme.finalize_order.return_value = self.eg_order self.acme.new_order.return_value = self.eg_order self.eg_order.update.return_value = self.eg_order @@ -271,8 +272,8 @@ class ClientTest(ClientTestCommon): self.acme.finalize_order.assert_called_once_with( self.eg_order, mock.ANY) - @mock.patch("certbot.client.crypto_util") - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.crypto_util") + @mock.patch("certbot._internal.client.logger") @test_util.patch_get_utility() def test_obtain_certificate_from_csr(self, unused_mock_get_utility, mock_logger, mock_crypto_util): @@ -307,7 +308,7 @@ class ClientTest(ClientTestCommon): test_csr) mock_logger.warning.assert_called_once_with(mock.ANY) - @mock.patch("certbot.client.crypto_util") + @mock.patch("certbot._internal.client.crypto_util") def test_obtain_certificate(self, mock_crypto_util): csr = util.CSR(form="pem", file=None, data=CSR_SAN) mock_crypto_util.init_save_csr.return_value = csr @@ -323,7 +324,7 @@ class ClientTest(ClientTestCommon): mock_crypto_util.cert_and_chain_from_fullchain.assert_called_once_with( self.eg_order.fullchain_pem) - @mock.patch("certbot.client.crypto_util") + @mock.patch("certbot._internal.client.crypto_util") @mock.patch("certbot.compat.os.remove") def test_obtain_certificate_partial_success(self, mock_remove, mock_crypto_util): csr = util.CSR(form="pem", file=mock.sentinel.csr_file, data=CSR_SAN) @@ -341,8 +342,8 @@ class ClientTest(ClientTestCommon): self.assertEqual(mock_remove.call_count, 2) self.assertEqual(mock_crypto_util.cert_and_chain_from_fullchain.call_count, 1) - @mock.patch("certbot.client.crypto_util") - @mock.patch("certbot.client.acme_crypto_util") + @mock.patch("certbot._internal.client.crypto_util") + @mock.patch("certbot._internal.client.acme_crypto_util") def test_obtain_certificate_dry_run(self, mock_acme_crypto, mock_crypto): csr = util.CSR(form="pem", file=None, data=CSR_SAN) mock_acme_crypto.make_csr.return_value = CSR_SAN @@ -360,6 +361,47 @@ class ClientTest(ClientTestCommon): mock_crypto.init_save_csr.assert_not_called() self.assertEqual(mock_crypto.cert_and_chain_from_fullchain.call_count, 1) + @mock.patch("certbot._internal.client.logger") + @mock.patch("certbot._internal.client.crypto_util") + @mock.patch("certbot._internal.client.acme_crypto_util") + def test_obtain_certificate_dry_run_authz_deactivations_failed(self, mock_acme_crypto, + mock_crypto, mock_log): + from acme import messages + csr = util.CSR(form="pem", file=None, data=CSR_SAN) + mock_acme_crypto.make_csr.return_value = CSR_SAN + mock_crypto.make_key.return_value = mock.sentinel.key_pem + key = util.Key(file=None, pem=mock.sentinel.key_pem) + self._set_mock_from_fullchain(mock_crypto.cert_and_chain_from_fullchain) + + self._mock_obtain_certificate() + self.client.config.dry_run = True + + # Two authzs that are already valid and should get deactivated (dry run) + authzrs = self._authzr_from_domains(["example.com", "www.example.com"]) + for authzr in authzrs: + authzr.body.status = messages.STATUS_VALID + + # One deactivation succeeds, one fails + auth_handler = self.client.auth_handler + auth_handler.deactivate_valid_authorizations.return_value = ([authzrs[0]], [authzrs[1]]) + + # Certificate should get issued despite one failed deactivation + self.eg_order.authorizations = authzrs + self.client.auth_handler.handle_authorizations.return_value = authzrs + with test_util.patch_get_utility(): + result = self.client.obtain_certificate(self.eg_domains) + self.assertEqual(result, (mock.sentinel.cert, mock.sentinel.chain, key, csr)) + self._check_obtain_certificate(1) + + # Deactivation success/failure should have been handled properly + self.assertEqual(auth_handler.deactivate_valid_authorizations.call_count, 1, + "Deactivate authorizations should be called") + self.assertEqual(self.acme.new_order.call_count, 2, + "Order should be recreated due to successfully deactivated authorizations") + mock_log.warning.assert_called_with("Certbot was unable to obtain fresh authorizations for" + " every domain. The dry run will continue, but results" + " may not be accurate.") + def _set_mock_from_fullchain(self, mock_from_fullchain): mock_cert = mock.Mock() mock_cert.encode.return_value = mock.sentinel.cert @@ -398,8 +440,8 @@ class ClientTest(ClientTestCommon): (mock.sentinel.cert, mock.sentinel.chain, key, csr)) self._check_obtain_certificate(auth_count) - @mock.patch('certbot.client.Client.obtain_certificate') - @mock.patch('certbot.storage.RenewableCert.new_lineage') + @mock.patch('certbot._internal.client.Client.obtain_certificate') + @mock.patch('certbot._internal.storage.RenewableCert.new_lineage') def test_obtain_and_enroll_certificate(self, mock_storage, mock_obtain_certificate): domains = ["*.example.com", "example.com"] @@ -419,9 +461,8 @@ class ClientTest(ClientTestCommon): names = [call[0][0] for call in mock_storage.call_args_list] self.assertEqual(names, ["example_cert", "example.com", "example.com"]) - @mock.patch("certbot.cli.helpful_parser") + @mock.patch("certbot._internal.cli.helpful_parser") def test_save_certificate(self, mock_parser): - # pylint: disable=too-many-locals certs = ["cert_512.pem", "cert-san_512.pem"] tmp_path = tempfile.mkdtemp() filesystem.chmod(tmp_path, 0o755) # TODO: really?? @@ -521,7 +562,7 @@ class ClientTest(ClientTestCommon): class EnhanceConfigTest(ClientTestCommon): - """Tests for certbot.client.Client.enhance_config.""" + """Tests for certbot._internal.client.Client.enhance_config.""" def setUp(self): super(EnhanceConfigTest, self).setUp() @@ -536,20 +577,20 @@ class EnhanceConfigTest(ClientTestCommon): self.assertRaises( errors.Error, self.client.enhance_config, [self.domain], None) - @mock.patch("certbot.client.enhancements") + @mock.patch("certbot._internal.client.enhancements") def test_unsupported(self, mock_enhancements): self.client.installer = mock.MagicMock() self.client.installer.supported_enhancements.return_value = [] self.config.redirect = None self.config.hsts = True - with mock.patch("certbot.client.logger") as mock_logger: + with mock.patch("certbot._internal.client.logger") as mock_logger: self.client.enhance_config([self.domain], None) self.assertEqual(mock_logger.warning.call_count, 1) self.client.installer.enhance.assert_not_called() mock_enhancements.ask.assert_not_called() - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.logger") def test_already_exists_header(self, mock_log): self.config.hsts = True self._test_with_already_existing() @@ -557,7 +598,7 @@ class EnhanceConfigTest(ClientTestCommon): self.assertEqual(mock_log.warning.call_args[0][1], 'Strict-Transport-Security') - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.logger") def test_already_exists_redirect(self, mock_log): self.config.redirect = True self._test_with_already_existing() @@ -565,14 +606,14 @@ class EnhanceConfigTest(ClientTestCommon): self.assertEqual(mock_log.warning.call_args[0][1], 'redirect') - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.logger") def test_config_set_no_warning_redirect(self, mock_log): self.config.redirect = False self._test_with_already_existing() self.assertFalse(mock_log.warning.called) - @mock.patch("certbot.client.enhancements.ask") - @mock.patch("certbot.client.logger") + @mock.patch("certbot._internal.client.enhancements.ask") + @mock.patch("certbot._internal.client.logger") def test_warn_redirect(self, mock_log, mock_ask): self.config.redirect = None mock_ask.return_value = False @@ -629,7 +670,7 @@ class EnhanceConfigTest(ClientTestCommon): self.client.installer = installer self._test_error_with_rollback() - @mock.patch("certbot.client.enhancements.ask") + @mock.patch("certbot._internal.client.enhancements.ask") def test_ask(self, mock_ask): self.config.redirect = None mock_ask.return_value = True @@ -664,15 +705,15 @@ class EnhanceConfigTest(ClientTestCommon): class RollbackTest(unittest.TestCase): - """Tests for certbot.client.rollback.""" + """Tests for certbot._internal.client.rollback.""" def setUp(self): self.m_install = mock.MagicMock() @classmethod def _call(cls, checkpoints, side_effect): - from certbot.client import rollback - with mock.patch("certbot.client.plugin_selection.pick_installer") as mpi: + from certbot._internal.client import rollback + with mock.patch("certbot._internal.client.plugin_selection.pick_installer") as mpi: mpi.side_effect = side_effect rollback(None, checkpoints, {}, mock.MagicMock()) diff --git a/certbot/tests/compat/filesystem_test.py b/certbot/tests/compat/filesystem_test.py index 3d1363e6a..e721bbd48 100644 --- a/certbot/tests/compat/filesystem_test.py +++ b/certbot/tests/compat/filesystem_test.py @@ -1,18 +1,27 @@ """Tests for certbot.compat.filesystem""" +import contextlib +import errno import unittest +import mock + +from certbot import util +from certbot._internal import lock +from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util +from certbot.tests.util import TempDirTestCase + try: - import win32api # pylint: disable=import-error - import win32security # pylint: disable=import-error - import ntsecuritycon # pylint: disable=import-error + # pylint: disable=import-error + import win32api + import win32security + import ntsecuritycon + # pylint: enable=import-error POSIX_MODE = False except ImportError: POSIX_MODE = True -import certbot.tests.util as test_util -from certbot.compat import os -from certbot.compat import filesystem -from certbot.tests.util import TempDirTestCase EVERYBODY_SID = 'S-1-1-0' @@ -20,7 +29,7 @@ SYSTEM_SID = 'S-1-5-18' ADMINS_SID = 'S-1-5-32-544' -@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') +@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') class WindowsChmodTests(TempDirTestCase): """Unit tests for Windows chmod function in filesystem module""" def setUp(self): @@ -42,18 +51,6 @@ class WindowsChmodTests(TempDirTestCase): self.assertFalse(filesystem._compare_dacls(ref_dacl_probe, cur_dacl_probe)) # pylint: disable=protected-access self.assertTrue(filesystem._compare_dacls(ref_dacl_link, cur_dacl_link)) # pylint: disable=protected-access - def test_symlink_loop_mitigation(self): - link1_path = os.path.join(self.tempdir, 'link1') - link2_path = os.path.join(self.tempdir, 'link2') - link3_path = os.path.join(self.tempdir, 'link3') - os.symlink(link1_path, link2_path) - os.symlink(link2_path, link3_path) - os.symlink(link3_path, link1_path) - - with self.assertRaises(RuntimeError) as error: - filesystem.chmod(link1_path, 0o755) - self.assertTrue('link1 is a loop!' in str(error.exception)) - def test_world_permission(self): everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) @@ -93,8 +90,8 @@ class WindowsChmodTests(TempDirTestCase): self.assertEqual(len(system_aces), 1) self.assertEqual(len(admin_aces), 1) - self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) - self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS ^ 512) + self.assertEqual(system_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) + self.assertEqual(admin_aces[0][1], ntsecuritycon.FILE_ALL_ACCESS) def test_read_flag(self): self._test_flag(4, ntsecuritycon.FILE_GENERIC_READ) @@ -105,12 +102,10 @@ class WindowsChmodTests(TempDirTestCase): def test_write_flag(self): self._test_flag(2, (ntsecuritycon.FILE_ALL_ACCESS ^ ntsecuritycon.FILE_GENERIC_READ - ^ ntsecuritycon.FILE_GENERIC_EXECUTE - ^ 512)) + ^ ntsecuritycon.FILE_GENERIC_EXECUTE)) def test_full_flag(self): - self._test_flag(7, (ntsecuritycon.FILE_ALL_ACCESS - ^ 512)) + self._test_flag(7, ntsecuritycon.FILE_ALL_ACCESS) def _test_flag(self, everyone_mode, windows_flag): # Note that flag is tested against `everyone`, not `user`, because practically these unit @@ -154,9 +149,259 @@ class WindowsChmodTests(TempDirTestCase): self.assertEqual(security_dacl.GetSecurityDescriptorDacl().GetAceCount(), 2) +class ComputePrivateKeyModeTest(TempDirTestCase): + def setUp(self): + super(ComputePrivateKeyModeTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_compute_private_key_mode(self): + filesystem.chmod(self.probe_path, 0o777) + new_mode = filesystem.compute_private_key_mode(self.probe_path, 0o600) + + if POSIX_MODE: + # On Linux RWX permissions for group and R permission for world + # are persisted from the existing moe + self.assertEqual(new_mode, 0o674) + else: + # On Windows no permission is persisted + self.assertEqual(new_mode, 0o600) + + +@unittest.skipIf(POSIX_MODE, reason='Tests specific to Windows security') +class WindowsOpenTest(TempDirTestCase): + def test_new_file_correct_permissions(self): + path = os.path.join(self.tempdir, 'file') + + desc = filesystem.open(path, os.O_CREAT | os.O_EXCL | os.O_RDWR, 0o700) + os.close(desc) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_existing_file_correct_permissions(self): + path = os.path.join(self.tempdir, 'file') + open(path, 'w').close() + + desc = filesystem.open(path, os.O_EXCL | os.O_RDWR, 0o700) + os.close(desc) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_create_file_on_open(self): + # os.O_CREAT | os.O_EXCL + file not exists = OK + self._test_one_creation(1, file_exist=False, flags=(os.O_CREAT | os.O_EXCL)) + + # os.O_CREAT | os.O_EXCL + file exists = EEXIST OS exception + with self.assertRaises(OSError) as raised: + self._test_one_creation(2, file_exist=True, flags=(os.O_CREAT | os.O_EXCL)) + self.assertEqual(raised.exception.errno, errno.EEXIST) + + # os.O_CREAT + file not exists = OK + self._test_one_creation(3, file_exist=False, flags=os.O_CREAT) + + # os.O_CREAT + file exists = OK + self._test_one_creation(4, file_exist=True, flags=os.O_CREAT) + + # os.O_CREAT + file exists (locked) = EACCES OS exception + path = os.path.join(self.tempdir, '5') + open(path, 'w').close() + filelock = lock.LockFile(path) + try: + with self.assertRaises(OSError) as raised: + self._test_one_creation(5, file_exist=True, flags=os.O_CREAT) + self.assertEqual(raised.exception.errno, errno.EACCES) + finally: + filelock.release() + + # os.O_CREAT not set + file not exists = OS exception + with self.assertRaises(OSError): + self._test_one_creation(6, file_exist=False, flags=os.O_RDONLY) + + def _test_one_creation(self, num, file_exist, flags): + one_file = os.path.join(self.tempdir, str(num)) + if file_exist and not os.path.exists(one_file): + with open(one_file, 'w'): + pass + + handler = None + try: + handler = filesystem.open(one_file, flags) + finally: + if handler: + os.close(handler) + + +@unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') +class WindowsMkdirTests(test_util.TempDirTestCase): + """Unit tests for Windows mkdir + makedirs functions in filesystem module""" + def test_mkdir_correct_permissions(self): + path = os.path.join(self.tempdir, 'dir') + + filesystem.mkdir(path, 0o700) + + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + dacl = _get_security_dacl(path).GetSecurityDescriptorDacl() + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_makedirs_correct_permissions(self): + path = os.path.join(self.tempdir, 'dir') + subpath = os.path.join(path, 'subpath') + + filesystem.makedirs(subpath, 0o700) + + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + + dacl = _get_security_dacl(subpath).GetSecurityDescriptorDacl() + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + def test_makedirs_switch_os_mkdir(self): + path = os.path.join(self.tempdir, 'dir') + import os as std_os # pylint: disable=os-module-forbidden + original_mkdir = std_os.mkdir + + filesystem.makedirs(path) + self.assertEqual(original_mkdir, std_os.mkdir) + + try: + filesystem.makedirs(path) # Will fail because path already exists + except OSError: + pass + self.assertEqual(original_mkdir, std_os.mkdir) + + +class OwnershipTest(test_util.TempDirTestCase): + """Tests about copy_ownership_and_apply_mode and has_same_ownership""" + def setUp(self): + super(OwnershipTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') + def test_copy_ownership_windows(self): + system = win32security.ConvertStringSidToSid(SYSTEM_SID) + security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR + security.SetSecurityDescriptorOwner(system, False) + + with mock.patch('win32security.GetFileSecurity') as mock_get: + with mock.patch('win32security.SetFileSecurity') as mock_set: + mock_get.return_value = security + filesystem.copy_ownership_and_apply_mode( + 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=False) + + self.assertEqual(mock_set.call_count, 2) + + first_call = mock_set.call_args_list[0] + security = first_call[0][2] + self.assertEqual(system, security.GetSecurityDescriptorOwner()) + + second_call = mock_set.call_args_list[1] + security = second_call[0][2] + dacl = security.GetSecurityDescriptorDacl() + everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID) + self.assertTrue(dacl.GetAceCount()) + self.assertFalse([dacl.GetAce(index) for index in range(0, dacl.GetAceCount()) + if dacl.GetAce(index)[2] == everybody]) + + @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') + def test_copy_ownership_linux(self): + with mock.patch('os.chown') as mock_chown: + with mock.patch('os.chmod') as mock_chmod: + with mock.patch('os.stat') as mock_stat: + mock_stat.return_value.st_uid = 50 + mock_stat.return_value.st_gid = 51 + filesystem.copy_ownership_and_apply_mode( + 'dummy', self.probe_path, 0o700, copy_user=True, copy_group=True) + + mock_chown.assert_called_once_with(self.probe_path, 50, 51) + mock_chmod.assert_called_once_with(self.probe_path, 0o700) + + def test_has_same_ownership(self): + path1 = os.path.join(self.tempdir, 'test1') + path2 = os.path.join(self.tempdir, 'test2') + + util.safe_open(path1, 'w').close() + util.safe_open(path2, 'w').close() + + self.assertTrue(filesystem.has_same_ownership(path1, path2)) + + +class CheckPermissionsTest(test_util.TempDirTestCase): + """Tests relative to functions that check modes.""" + def setUp(self): + super(CheckPermissionsTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_check_mode(self): + self.assertTrue(filesystem.check_mode(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.check_mode(self.probe_path, 0o744)) + + @unittest.skipIf(POSIX_MODE, reason='Test specific to Windows security') + def test_check_owner_windows(self): + self.assertTrue(filesystem.check_owner(self.probe_path)) + + system = win32security.ConvertStringSidToSid(SYSTEM_SID) + security = win32security.SECURITY_ATTRIBUTES().SECURITY_DESCRIPTOR + security.SetSecurityDescriptorOwner(system, False) + + with mock.patch('win32security.GetFileSecurity') as mock_get: + mock_get.return_value = security + self.assertFalse(filesystem.check_owner(self.probe_path)) + + @unittest.skipUnless(POSIX_MODE, reason='Test specific to Linux security') + def test_check_owner_linux(self): + self.assertTrue(filesystem.check_owner(self.probe_path)) + + import os as std_os # pylint: disable=os-module-forbidden + # See related inline comment in certbot.compat.filesystem.check_owner method + # that explains why MyPy/PyLint check disable is needed here. + uid = std_os.getuid() + + with mock.patch('os.getuid') as mock_uid: + mock_uid.return_value = uid + 1 + self.assertFalse(filesystem.check_owner(self.probe_path)) + + def test_check_permissions(self): + self.assertTrue(filesystem.check_permissions(self.probe_path, 0o744)) + + with mock.patch('certbot.compat.filesystem.check_mode') as mock_mode: + mock_mode.return_value = False + self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + + with mock.patch('certbot.compat.filesystem.check_owner') as mock_owner: + mock_owner.return_value = False + self.assertFalse(filesystem.check_permissions(self.probe_path, 0o744)) + + def test_check_min_permissions(self): + filesystem.chmod(self.probe_path, 0o744) + self.assertTrue(filesystem.has_min_permissions(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + + filesystem.chmod(self.probe_path, 0o741) + self.assertFalse(filesystem.has_min_permissions(self.probe_path, 0o744)) + + def test_is_world_reachable(self): + filesystem.chmod(self.probe_path, 0o744) + self.assertTrue(filesystem.has_world_permissions(self.probe_path)) + + filesystem.chmod(self.probe_path, 0o700) + self.assertFalse(filesystem.has_world_permissions(self.probe_path)) + + class OsReplaceTest(test_util.TempDirTestCase): """Test to ensure consistent behavior of rename method""" - def test_os_replace_to_existing_file(self): """Ensure that replace will effectively rename src into dst for all platforms.""" src = os.path.join(self.tempdir, 'src') @@ -171,6 +416,112 @@ class OsReplaceTest(test_util.TempDirTestCase): self.assertTrue(os.path.exists(dst)) +class RealpathTest(test_util.TempDirTestCase): + """Tests for realpath method""" + def setUp(self): + super(RealpathTest, self).setUp() + self.probe_path = _create_probe(self.tempdir) + + def test_symlink_resolution(self): + # Remove any symlinks already in probe_path + self.probe_path = filesystem.realpath(self.probe_path) + # Absolute resolution + link_path = os.path.join(self.tempdir, 'link_abs') + os.symlink(self.probe_path, link_path) + + self.assertEqual(self.probe_path, filesystem.realpath(self.probe_path)) + self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + + # Relative resolution + curdir = os.getcwd() + link_path = os.path.join(self.tempdir, 'link_rel') + probe_name = os.path.basename(self.probe_path) + try: + os.chdir(os.path.dirname(self.probe_path)) + os.symlink(probe_name, link_path) + + self.assertEqual(self.probe_path, filesystem.realpath(probe_name)) + self.assertEqual(self.probe_path, filesystem.realpath(link_path)) + finally: + os.chdir(curdir) + + def test_symlink_loop_mitigation(self): + link1_path = os.path.join(self.tempdir, 'link1') + link2_path = os.path.join(self.tempdir, 'link2') + link3_path = os.path.join(self.tempdir, 'link3') + os.symlink(link1_path, link2_path) + os.symlink(link2_path, link3_path) + os.symlink(link3_path, link1_path) + + with self.assertRaises(RuntimeError) as error: + filesystem.realpath(link1_path) + self.assertTrue('link1 is a loop!' in str(error.exception)) + + +class IsExecutableTest(test_util.TempDirTestCase): + """Tests for is_executable method""" + def test_not_executable(self): + file_path = os.path.join(self.tempdir, "foo") + + # On Windows a file created within Certbot will always have all permissions to the + # Administrators group set. Since the unit tests are typically executed under elevated + # privileges, it means that current user will always have effective execute rights on the + # hook script, and so the test will fail. To prevent that and represent a file created + # outside Certbot as typically a hook file is, we mock the _generate_dacl function in + # certbot.compat.filesystem to give rights only to the current user. This implies removing + # all ACEs except the first one from the DACL created by original _generate_dacl function. + + from certbot.compat.filesystem import _generate_dacl + + def _execute_mock(user_sid, mode): + dacl = _generate_dacl(user_sid, mode) + for _ in range(1, dacl.GetAceCount()): + dacl.DeleteAce(1) # DeleteAce dynamically updates the internal index mapping. + return dacl + + # create a non-executable file + with mock.patch("certbot.compat.filesystem._generate_dacl", side_effect=_execute_mock): + os.close(filesystem.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) + + self.assertFalse(filesystem.is_executable(file_path)) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_full_path(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = True + self.assertTrue(filesystem.is_executable("/path/to/exe")) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_rel_path(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = True + self.assertTrue(filesystem.is_executable("exe")) + + @mock.patch("certbot.compat.filesystem.os.path.isfile") + @mock.patch("certbot.compat.filesystem.os.access") + def test_not_found(self, mock_access, mock_isfile): + with _fix_windows_runtime(): + mock_access.return_value = True + mock_isfile.return_value = False + self.assertFalse(filesystem.is_executable("exe")) + + +@contextlib.contextmanager +def _fix_windows_runtime(): + if os.name != 'nt': + yield + else: + with mock.patch('win32security.GetFileSecurity') as mock_get: + dacl_mock = mock_get.return_value.GetSecurityDescriptorDacl + mode_mock = dacl_mock.return_value.GetEffectiveRightsFromAcl + mode_mock.return_value = ntsecuritycon.FILE_GENERIC_EXECUTE + yield + + def _get_security_dacl(target): return win32security.GetFileSecurity(target, win32security.DACL_SECURITY_INFORMATION) @@ -188,7 +539,7 @@ def _set_owner(target, security_owner, user): def _create_probe(tempdir): filesystem.chmod(tempdir, 0o744) probe_path = os.path.join(tempdir, 'probe') - open(probe_path, 'w').close() + util.safe_open(probe_path, 'w', chmod=0o744).close() return probe_path diff --git a/certbot/tests/compat/os_test.py b/certbot/tests/compat/os_test.py index 85a3fa9f4..2fe23f700 100644 --- a/certbot/tests/compat/os_test.py +++ b/certbot/tests/compat/os_test.py @@ -7,8 +7,13 @@ from certbot.compat import os class OsTest(unittest.TestCase): """Unit tests for os module.""" def test_forbidden_methods(self): - for method in ['chmod', 'rename', 'replace']: + # Checks for os module + for method in ['chmod', 'chown', 'open', 'mkdir', 'makedirs', 'rename', + 'replace', 'access', 'stat', 'fstat']: self.assertRaises(RuntimeError, getattr(os, method)) + # Checks for os.path module + for method in ['realpath']: + self.assertRaises(RuntimeError, getattr(os.path, method)) if __name__ == "__main__": diff --git a/certbot/tests/configuration_test.py b/certbot/tests/configuration_test.py index aa07a580f..d748b9bfb 100644 --- a/certbot/tests/configuration_test.py +++ b/certbot/tests/configuration_test.py @@ -1,28 +1,28 @@ -"""Tests for certbot.configuration.""" +"""Tests for certbot._internal.configuration.""" import unittest import mock -from certbot import constants from certbot import errors +from certbot._internal import constants from certbot.compat import misc from certbot.compat import os from certbot.tests import util as test_util class NamespaceConfigTest(test_util.ConfigTestCase): - """Tests for certbot.configuration.NamespaceConfig.""" + """Tests for certbot._internal.configuration.NamespaceConfig.""" def setUp(self): super(NamespaceConfigTest, self).setUp() - self.config.foo = 'bar' + self.config.foo = 'bar' # pylint: disable=blacklisted-name self.config.server = 'https://acme-server.org:443/new' self.config.https_port = 1234 self.config.http01_port = 4321 def test_init_same_ports(self): self.config.namespace.https_port = 4321 - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig self.assertRaises(errors.Error, NamespaceConfig, self.config.namespace) def test_proxy_getattr(self): @@ -38,7 +38,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.assertEqual(['user:pass@acme.server:443', 'p', 'a', 't', 'h'], self.config.server_path.split(os.path.sep)) - @mock.patch('certbot.configuration.constants') + @mock.patch('certbot._internal.configuration.constants') def test_dynamic_dirs(self, mock_constants): mock_constants.ACCOUNTS_DIR = 'acc' mock_constants.BACKUP_DIR = 'backups' @@ -70,7 +70,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): os.path.normpath(os.path.join(self.config.work_dir, 't'))) def test_absolute_paths(self): - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig config_base = "foo" work_base = "bar" @@ -103,7 +103,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.assertTrue(os.path.isabs(config.key_dir)) self.assertTrue(os.path.isabs(config.temp_checkpoint_dir)) - @mock.patch('certbot.configuration.constants') + @mock.patch('certbot._internal.configuration.constants') def test_renewal_dynamic_dirs(self, mock_constants): mock_constants.ARCHIVE_DIR = 'a' mock_constants.LIVE_DIR = 'l' @@ -118,7 +118,7 @@ class NamespaceConfigTest(test_util.ConfigTestCase): self.config.config_dir, 'renewal_configs')) def test_renewal_absolute_paths(self): - from certbot.configuration import NamespaceConfig + from certbot._internal.configuration import NamespaceConfig config_base = "foo" work_base = "bar" diff --git a/certbot/tests/crypto_util_test.py b/certbot/tests/crypto_util_test.py index 5c1a07f8d..1d642ae9e 100644 --- a/certbot/tests/crypto_util_test.py +++ b/certbot/tests/crypto_util_test.py @@ -2,15 +2,16 @@ import logging import unittest -import OpenSSL import mock +import OpenSSL import zope.component -import certbot.tests.util as test_util from certbot import errors from certbot import interfaces from certbot import util +from certbot.compat import filesystem from certbot.compat import os +import certbot.tests.util as test_util RSA256_KEY = test_util.load_vector('rsa256_key.pem') RSA256_KEY_PATH = test_util.vector_path('rsa256_key.pem') @@ -29,6 +30,9 @@ class InitSaveKeyTest(test_util.TempDirTestCase): def setUp(self): super(InitSaveKeyTest, self).setUp() + self.workdir = os.path.join(self.tempdir, 'workdir') + filesystem.mkdir(self.workdir, mode=0o700) + logging.disable(logging.CRITICAL) zope.component.provideUtility( mock.Mock(strict_permissions=True), interfaces.IConfig) @@ -46,15 +50,15 @@ class InitSaveKeyTest(test_util.TempDirTestCase): @mock.patch('certbot.crypto_util.make_key') def test_success(self, mock_make): mock_make.return_value = b'key_pem' - key = self._call(1024, self.tempdir) + key = self._call(1024, self.workdir) self.assertEqual(key.pem, b'key_pem') self.assertTrue('key-certbot.pem' in key.file) - self.assertTrue(os.path.exists(os.path.join(self.tempdir, key.file))) + self.assertTrue(os.path.exists(os.path.join(self.workdir, key.file))) @mock.patch('certbot.crypto_util.make_key') def test_key_failure(self, mock_make): mock_make.side_effect = ValueError - self.assertRaises(ValueError, self._call, 431, self.tempdir) + self.assertRaises(ValueError, self._call, 431, self.workdir) class InitSaveCSRTest(test_util.TempDirTestCase): @@ -160,7 +164,7 @@ class ImportCSRFileTest(unittest.TestCase): test_util.load_vector('cert_512.pem')) -class MakeKeyTest(unittest.TestCase): # pylint: disable=too-few-public-methods +class MakeKeyTest(unittest.TestCase): """Tests for certbot.crypto_util.make_key.""" def test_it(self): # pylint: disable=no-self-use @@ -177,15 +181,15 @@ class VerifyCertSetup(unittest.TestCase): super(VerifyCertSetup, self).setUp() self.renewable_cert = mock.MagicMock() - self.renewable_cert.cert = SS_CERT_PATH - self.renewable_cert.chain = SS_CERT_PATH - self.renewable_cert.privkey = RSA2048_KEY_PATH - self.renewable_cert.fullchain = test_util.vector_path('cert_fullchain_2048.pem') + self.renewable_cert.cert_path = SS_CERT_PATH + self.renewable_cert.chain_path = SS_CERT_PATH + self.renewable_cert.key_path = RSA2048_KEY_PATH + self.renewable_cert.fullchain_path = test_util.vector_path('cert_fullchain_2048.pem') self.bad_renewable_cert = mock.MagicMock() - self.bad_renewable_cert.chain = SS_CERT_PATH - self.bad_renewable_cert.cert = SS_CERT_PATH - self.bad_renewable_cert.fullchain = SS_CERT_PATH + self.bad_renewable_cert.chain_path = SS_CERT_PATH + self.bad_renewable_cert.cert_path = SS_CERT_PATH + self.bad_renewable_cert.fullchain_path = SS_CERT_PATH class VerifyRenewableCertTest(VerifyCertSetup): @@ -215,13 +219,13 @@ class VerifyRenewableCertSigTest(VerifyCertSetup): def test_cert_sig_match_ec(self): renewable_cert = mock.MagicMock() - renewable_cert.cert = P256_CERT_PATH - renewable_cert.chain = P256_CERT_PATH - renewable_cert.privkey = P256_KEY + renewable_cert.cert_path = P256_CERT_PATH + renewable_cert.chain_path = P256_CERT_PATH + renewable_cert.key_path = P256_KEY self.assertEqual(None, self._call(renewable_cert)) def test_cert_sig_mismatch(self): - self.bad_renewable_cert.cert = test_util.vector_path('cert_512_bad.pem') + self.bad_renewable_cert.cert_path = test_util.vector_path('cert_512_bad.pem') self.assertRaises(errors.Error, self._call, self.bad_renewable_cert) diff --git a/certbot/tests/display/completer_test.py b/certbot/tests/display/completer_test.py index bbbc50c49..5ddf69266 100644 --- a/certbot/tests/display/completer_test.py +++ b/certbot/tests/display/completer_test.py @@ -1,8 +1,8 @@ -"""Test certbot.display.completer.""" +"""Test certbot._internal.display.completer.""" try: import readline # pylint: disable=import-error except ImportError: - import certbot.display.dummy_readline as readline # type: ignore + import certbot._internal.display.dummy_readline as readline # type: ignore import string import sys import unittest @@ -10,14 +10,14 @@ import unittest import mock from six.moves import reload_module # pylint: disable=import-error -from acme.magic_typing import List # pylint: disable=unused-import,no-name-in-module - +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from certbot.compat import filesystem # pylint: disable=ungrouped-imports from certbot.compat import os # pylint: disable=ungrouped-imports import certbot.tests.util as test_util # pylint: disable=ungrouped-imports class CompleterTest(test_util.TempDirTestCase): - """Test certbot.display.completer.Completer.""" + """Test certbot._internal.display.completer.Completer.""" def setUp(self): super(CompleterTest, self).setUp() @@ -33,13 +33,13 @@ class CompleterTest(test_util.TempDirTestCase): path = os.path.join(self.tempdir, c) self.paths.append(path) if ord(c) % 2: - os.mkdir(path) + filesystem.mkdir(path) else: with open(path, 'w'): pass def test_complete(self): - from certbot.display import completer + from certbot._internal.display import completer my_completer = completer.Completer() num_paths = len(self.paths) @@ -63,7 +63,7 @@ class CompleterTest(test_util.TempDirTestCase): sys.modules['readline'] = original_readline def test_context_manager_with_unmocked_readline(self): - from certbot.display import completer + from certbot._internal.display import completer reload_module(completer) original_completer = readline.get_completer() @@ -75,18 +75,18 @@ class CompleterTest(test_util.TempDirTestCase): self.assertEqual(readline.get_completer(), original_completer) self.assertEqual(readline.get_completer_delims(), original_delims) - @mock.patch('certbot.display.completer.readline', autospec=True) + @mock.patch('certbot._internal.display.completer.readline', autospec=True) def test_context_manager_libedit(self, mock_readline): mock_readline.__doc__ = 'libedit' self._test_context_manager_with_mock_readline(mock_readline) - @mock.patch('certbot.display.completer.readline', autospec=True) + @mock.patch('certbot._internal.display.completer.readline', autospec=True) def test_context_manager_readline(self, mock_readline): mock_readline.__doc__ = 'GNU readline' self._test_context_manager_with_mock_readline(mock_readline) def _test_context_manager_with_mock_readline(self, mock_readline): - from certbot.display import completer + from certbot._internal.display import completer mock_readline.parse_and_bind.side_effect = enable_tab_completion diff --git a/certbot/tests/display/enhancements_test.py b/certbot/tests/display/enhancements_test.py index b8321d940..edace29b1 100644 --- a/certbot/tests/display/enhancements_test.py +++ b/certbot/tests/display/enhancements_test.py @@ -18,10 +18,10 @@ class AskTest(unittest.TestCase): @classmethod def _call(cls, enhancement): - from certbot.display.enhancements import ask + from certbot._internal.display.enhancements import ask return ask(enhancement) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_redirect(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call("redirect")) @@ -34,20 +34,20 @@ class RedirectTest(unittest.TestCase): """Test the redirect_by_default method.""" @classmethod def _call(cls): - from certbot.display.enhancements import redirect_by_default + from certbot._internal.display.enhancements import redirect_by_default return redirect_by_default() - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_secure(self, mock_util): mock_util().menu.return_value = (display_util.OK, 1) self.assertTrue(self._call()) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_cancel(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 1) self.assertFalse(self._call()) - @mock.patch("certbot.display.enhancements.util") + @mock.patch("certbot._internal.display.enhancements.util") def test_easy(self, mock_util): mock_util().menu.return_value = (display_util.OK, 0) self.assertFalse(self._call()) diff --git a/certbot/tests/display/ops_test.py b/certbot/tests/display/ops_test.py index 95334a9d3..5df7bfcf8 100644 --- a/certbot/tests/display/ops_test.py +++ b/certbot/tests/display/ops_test.py @@ -8,13 +8,13 @@ import mock import zope.component from acme import messages - -import certbot.tests.util as test_util -from certbot import account from certbot import errors +from certbot._internal import account +from certbot.compat import filesystem from certbot.compat import os from certbot.display import ops from certbot.display import util as display_util +import certbot.tests.util as test_util KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) @@ -93,7 +93,7 @@ class ChooseAccountTest(test_util.TempDirTestCase): False)) self.account_keys_dir = os.path.join(self.tempdir, "keys") - os.makedirs(self.account_keys_dir, 0o700) + filesystem.makedirs(self.account_keys_dir, 0o700) self.config = mock.MagicMock( accounts_dir=self.tempdir, @@ -353,7 +353,6 @@ class ChooseNamesTest(unittest.TestCase): class SuccessInstallationTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success installation message.""" @classmethod def _call(cls, names): @@ -375,7 +374,6 @@ class SuccessInstallationTest(unittest.TestCase): class SuccessRenewalTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success renewal message.""" @classmethod def _call(cls, names): @@ -396,7 +394,6 @@ class SuccessRenewalTest(unittest.TestCase): self.assertTrue(name in arg) class SuccessRevocationTest(unittest.TestCase): - # pylint: disable=too-few-public-methods """Test the success revocation message.""" @classmethod def _call(cls, path): diff --git a/certbot/tests/display/util_test.py b/certbot/tests/display/util_test.py index 138bfe628..615f33406 100644 --- a/certbot/tests/display/util_test.py +++ b/certbot/tests/display/util_test.py @@ -58,7 +58,6 @@ class FileOutputDisplayTest(unittest.TestCase): functions look to a user, uncomment the test_visual function. """ - # pylint:disable=too-many-public-methods def setUp(self): super(FileOutputDisplayTest, self).setUp() self.mock_stdout = mock.MagicMock() @@ -312,12 +311,12 @@ class FileOutputDisplayTest(unittest.TestCase): def test_methods_take_force_interactive(self): # Every IDisplay method implemented by FileDisplay must take # force_interactive to prevent workflow regressions. - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,no-value-for-parameter + for name in interfaces.IDisplay.names(): if six.PY2: - getargspec = inspect.getargspec # pylint: disable=no-member + getargspec = inspect.getargspec else: - getargspec = inspect.getfullargspec # pylint: disable=no-member - arg_spec = getargspec(getattr(self.displayer, name)) + getargspec = inspect.getfullargspec + arg_spec = getargspec(getattr(self.displayer, name)) # pylint: disable=deprecated-method self.assertTrue("force_interactive" in arg_spec.args) @@ -373,14 +372,14 @@ class NoninteractiveDisplayTest(unittest.TestCase): # NoninteractiveDisplay. # Use pylint code for disable to keep on single line under line length limit - for name in interfaces.IDisplay.names(): # pylint: disable=no-member,E1120 + for name in interfaces.IDisplay.names(): # pylint: disable=E1120 method = getattr(self.displayer, name) # asserts method accepts arbitrary keyword arguments if six.PY2: - result = inspect.getargspec(method).keywords # pylint: disable=no-member + result = inspect.getargspec(method).keywords # pylint:deprecated-method self.assertFalse(result is None) else: - result = inspect.getfullargspec(method).varkw # pylint: disable=no-member + result = inspect.getfullargspec(method).varkw self.assertFalse(result is None) diff --git a/certbot/tests/eff_test.py b/certbot/tests/eff_test.py index cf79ffc70..cdd7908a3 100644 --- a/certbot/tests/eff_test.py +++ b/certbot/tests/eff_test.py @@ -1,15 +1,15 @@ -"""Tests for certbot.eff.""" +"""Tests for certbot._internal.eff.""" import unittest import mock import requests -from certbot import constants +from certbot._internal import constants import certbot.tests.util as test_util class HandleSubscriptionTest(test_util.ConfigTestCase): - """Tests for certbot.eff.handle_subscription.""" + """Tests for certbot._internal.eff.handle_subscription.""" def setUp(self): super(HandleSubscriptionTest, self).setUp() self.email = 'certbot@example.org' @@ -17,11 +17,11 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.config.eff_email = None def _call(self): - from certbot.eff import handle_subscription + from certbot._internal.eff import handle_subscription return handle_subscription(self.config) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_failure(self, mock_subscribe, mock_get_utility): self.config.email = None self.config.eff_email = True @@ -32,7 +32,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): expected_part = "because you didn't provide an e-mail address" self.assertTrue(expected_part in actual) - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_no_subscribe_with_no_prompt(self, mock_subscribe): self.config.eff_email = False with test_util.patch_get_utility() as mock_get_utility: @@ -41,7 +41,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self._assert_no_get_utility_calls(mock_get_utility) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility): self.config.eff_email = True self._call() @@ -53,7 +53,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.assertFalse(mock_get_utility().add_message.called) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility): mock_get_utility().yesno.return_value = True self._call() @@ -66,7 +66,7 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): self.assertEqual(mock_subscribe.call_args[0][0], self.email) @test_util.patch_get_utility() - @mock.patch('certbot.eff.subscribe') + @mock.patch('certbot._internal.eff.subscribe') def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility): mock_get_utility().yesno.return_value = False self._call() @@ -84,18 +84,18 @@ class HandleSubscriptionTest(test_util.ConfigTestCase): class SubscribeTest(unittest.TestCase): - """Tests for certbot.eff.subscribe.""" + """Tests for certbot._internal.eff.subscribe.""" def setUp(self): self.email = 'certbot@example.org' self.json = {'status': True} self.response = mock.Mock(ok=True) self.response.json.return_value = self.json - @mock.patch('certbot.eff.requests.post') + @mock.patch('certbot._internal.eff.requests.post') def _call(self, mock_post): mock_post.return_value = self.response - from certbot.eff import subscribe + from certbot._internal.eff import subscribe subscribe(self.email) self._check_post_call(mock_post) @@ -111,7 +111,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_bad_status(self, mock_get_utility): self.json['status'] = False - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'because your e-mail address appears to be invalid.' self.assertTrue(expected_part in actual) @@ -120,7 +120,7 @@ class SubscribeTest(unittest.TestCase): def test_not_ok(self, mock_get_utility): self.response.ok = False self.response.raise_for_status.side_effect = requests.exceptions.HTTPError - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) unexpected_part = 'because' self.assertFalse(unexpected_part in actual) @@ -128,7 +128,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_response_not_json(self, mock_get_utility): self.response.json.side_effect = ValueError() - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'problem' self.assertTrue(expected_part in actual) @@ -136,7 +136,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_response_json_missing_status_element(self, mock_get_utility): self.json.clear() - self._call() # pylint: disable=no-value-for-parameter + self._call() actual = self._get_reported_message(mock_get_utility) expected_part = 'problem' self.assertTrue(expected_part in actual) @@ -147,7 +147,7 @@ class SubscribeTest(unittest.TestCase): @test_util.patch_get_utility() def test_subscribe(self, mock_get_utility): - self._call() # pylint: disable=no-value-for-parameter + self._call() self.assertFalse(mock_get_utility.called) diff --git a/certbot/tests/error_handler_test.py b/certbot/tests/error_handler_test.py index cc805a9e6..45fec7f39 100644 --- a/certbot/tests/error_handler_test.py +++ b/certbot/tests/error_handler_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.error_handler.""" +"""Tests for certbot._internal.error_handler.""" import contextlib import signal import sys @@ -6,10 +6,9 @@ import unittest import mock -# pylint: disable=unused-import, no-name-in-module -from acme.magic_typing import Callable, Dict, Union -# pylint: enable=unused-import, no-name-in-module - +from acme.magic_typing import Callable # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Union # pylint: disable=unused-import, no-name-in-module from certbot.compat import os @@ -40,10 +39,10 @@ def send_signal(signum): class ErrorHandlerTest(unittest.TestCase): - """Tests for certbot.error_handler.ErrorHandler.""" + """Tests for certbot._internal.error_handler.ErrorHandler.""" def setUp(self): - from certbot import error_handler + from certbot._internal import error_handler self.init_func = mock.MagicMock() self.init_args = set((42,)) @@ -132,10 +131,10 @@ class ErrorHandlerTest(unittest.TestCase): class ExitHandlerTest(ErrorHandlerTest): - """Tests for certbot.error_handler.ExitHandler.""" + """Tests for certbot._internal.error_handler.ExitHandler.""" def setUp(self): - from certbot import error_handler + from certbot._internal import error_handler super(ExitHandlerTest, self).setUp() self.handler = error_handler.ExitHandler(self.init_func, *self.init_args, diff --git a/certbot/tests/errors_test.py b/certbot/tests/errors_test.py index c8a6c4ac5..d6c829322 100644 --- a/certbot/tests/errors_test.py +++ b/certbot/tests/errors_test.py @@ -4,7 +4,6 @@ import unittest import mock from acme import messages - from certbot import achallenges from certbot.tests import acme_util @@ -14,25 +13,27 @@ class FailedChallengesTest(unittest.TestCase): def setUp(self): from certbot.errors import FailedChallenges - self.error = FailedChallenges(set([achallenges.DNS( + self.error = FailedChallenges({achallenges.DNS( domain="example.com", challb=messages.ChallengeBody( chall=acme_util.DNS01, uri=None, - error=messages.Error(typ="tls", detail="detail")))])) + error=messages.Error.with_code("tls", detail="detail")))}) def test_str(self): self.assertTrue(str(self.error).startswith( - "Failed authorization procedure. example.com (dns-01): tls")) + "Failed authorization procedure. example.com (dns-01): " + "urn:ietf:params:acme:error:tls")) def test_unicode(self): from certbot.errors import FailedChallenges arabic_detail = u'\u0639\u062f\u0627\u0644\u0629' - arabic_error = FailedChallenges(set([achallenges.DNS( + arabic_error = FailedChallenges({achallenges.DNS( domain="example.com", challb=messages.ChallengeBody( chall=acme_util.DNS01, uri=None, - error=messages.Error(typ="tls", detail=arabic_detail)))])) + error=messages.Error.with_code("tls", detail=arabic_detail)))}) self.assertTrue(str(arabic_error).startswith( - "Failed authorization procedure. example.com (dns-01): tls")) + "Failed authorization procedure. example.com (dns-01): " + "urn:ietf:params:acme:error:tls")) class StandaloneBindErrorTest(unittest.TestCase): diff --git a/certbot/tests/hook_test.py b/certbot/tests/hook_test.py index ef40d674a..a3bba57d2 100644 --- a/certbot/tests/hook_test.py +++ b/certbot/tests/hook_test.py @@ -1,25 +1,25 @@ -"""Tests for certbot.hooks.""" -import stat +"""Tests for certbot._internal.hooks.""" import unittest import mock -from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors -from certbot.compat import os +from certbot import util from certbot.compat import filesystem -from certbot.tests import util +from certbot.compat import os +from certbot.tests import util as test_util class ValidateHooksTest(unittest.TestCase): - """Tests for certbot.hooks.validate_hooks.""" + """Tests for certbot._internal.hooks.validate_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import validate_hooks + from certbot._internal.hooks import validate_hooks return validate_hooks(*args, **kwargs) - @mock.patch("certbot.hooks.validate_hook") + @mock.patch("certbot._internal.hooks.validate_hook") def test_it(self, mock_validate_hook): config = mock.MagicMock() self._call(config) @@ -30,39 +30,37 @@ class ValidateHooksTest(unittest.TestCase): self.assertEqual("renew", types[-1]) -class ValidateHookTest(util.TempDirTestCase): - """Tests for certbot.hooks.validate_hook.""" +class ValidateHookTest(test_util.TempDirTestCase): + """Tests for certbot._internal.hooks.validate_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import validate_hook + from certbot._internal.hooks import validate_hook return validate_hook(*args, **kwargs) - @util.broken_on_windows - def test_not_executable(self): - file_path = os.path.join(self.tempdir, "foo") - # create a non-executable file - os.close(os.open(file_path, os.O_CREAT | os.O_WRONLY, 0o666)) + def test_hook_not_executable(self): # prevent unnecessary modifications to PATH - with mock.patch("certbot.hooks.plug_util.path_surgery"): - self.assertRaises(errors.HookCommandNotFound, - self._call, file_path, "foo") + with mock.patch("certbot._internal.hooks.plug_util.path_surgery"): + # We just mock out filesystem.is_executable since on Windows, it is difficult + # to get a fully working test around executable permissions. See + # certbot.tests.compat.filesystem::NotExecutableTest for more in-depth tests. + with mock.patch("certbot._internal.hooks.filesystem.is_executable", return_value=False): + self.assertRaises(errors.HookCommandNotFound, self._call, 'dummy', "foo") - @mock.patch("certbot.hooks.util.exe_exists") + @mock.patch("certbot._internal.hooks.util.exe_exists") def test_not_found(self, mock_exe_exists): mock_exe_exists.return_value = False - with mock.patch("certbot.hooks.plug_util.path_surgery") as mock_ps: - self.assertRaises(errors.HookCommandNotFound, - self._call, "foo", "bar") + with mock.patch("certbot._internal.hooks.plug_util.path_surgery") as mock_ps: + self.assertRaises(errors.HookCommandNotFound, self._call, "foo", "bar") self.assertTrue(mock_ps.called) - @mock.patch("certbot.hooks._prog") + @mock.patch("certbot._internal.hooks._prog") def test_unset(self, mock_prog): self._call(None, "foo") self.assertFalse(mock_prog.called) -class HookTest(util.ConfigTestCase): +class HookTest(test_util.ConfigTestCase): """Common base class for hook tests.""" @classmethod @@ -72,31 +70,31 @@ class HookTest(util.ConfigTestCase): @classmethod def _call_with_mock_execute(cls, *args, **kwargs): - """Calls self._call after mocking out certbot.hooks.execute. + """Calls self._call after mocking out certbot._internal.hooks.execute. The mock execute object is returned rather than the return value of self._call. """ - with mock.patch("certbot.hooks.execute") as mock_execute: + with mock.patch("certbot._internal.hooks.execute") as mock_execute: mock_execute.return_value = ("", "") cls._call(*args, **kwargs) return mock_execute class PreHookTest(HookTest): - """Tests for certbot.hooks.pre_hook.""" + """Tests for certbot._internal.hooks.pre_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import pre_hook + from certbot._internal.hooks import pre_hook return pre_hook(*args, **kwargs) def setUp(self): super(PreHookTest, self).setUp() self.config.pre_hook = "foo" - os.makedirs(self.config.renewal_pre_hooks_dir) + filesystem.makedirs(self.config.renewal_pre_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_pre_hooks_dir, "bar") create_hook(self.dir_hook) @@ -109,7 +107,7 @@ class PreHookTest(HookTest): super(PreHookTest, self).tearDown() def _reset_pre_hook_already(self): - from certbot.hooks import executed_pre_hooks + from certbot._internal.hooks import executed_pre_hooks executed_pre_hooks.clear() def test_certonly(self): @@ -130,7 +128,7 @@ class PreHookTest(HookTest): self.config.verb = "renew" os.remove(self.dir_hook) - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) self.assertFalse(mock_execute.called) self.assertFalse(mock_logger.info.called) @@ -156,25 +154,25 @@ class PreHookTest(HookTest): self._test_no_executions_common() def _test_no_executions_common(self): - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute(self.config) self.assertFalse(mock_execute.called) self.assertTrue(mock_logger.info.called) class PostHookTest(HookTest): - """Tests for certbot.hooks.post_hook.""" + """Tests for certbot._internal.hooks.post_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import post_hook + from certbot._internal.hooks import post_hook return post_hook(*args, **kwargs) def setUp(self): super(PostHookTest, self).setUp() self.config.post_hook = "bar" - os.makedirs(self.config.renewal_post_hooks_dir) + filesystem.makedirs(self.config.renewal_post_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_post_hooks_dir, "foo") create_hook(self.dir_hook) @@ -187,7 +185,7 @@ class PostHookTest(HookTest): super(PostHookTest, self).tearDown() def _reset_post_hook_eventually(self): - from certbot.hooks import post_hooks + from certbot._internal.hooks import post_hooks del post_hooks[:] def test_certonly_and_run_with_hook(self): @@ -241,27 +239,27 @@ class PostHookTest(HookTest): self.assertEqual(self._get_eventually(), expected) def _get_eventually(self): - from certbot.hooks import post_hooks + from certbot._internal.hooks import post_hooks return post_hooks class RunSavedPostHooksTest(HookTest): - """Tests for certbot.hooks.run_saved_post_hooks.""" + """Tests for certbot._internal.hooks.run_saved_post_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import run_saved_post_hooks + from certbot._internal.hooks import run_saved_post_hooks return run_saved_post_hooks() def _call_with_mock_execute_and_eventually(self, *args, **kwargs): """Call run_saved_post_hooks but mock out execute and eventually - certbot.hooks.post_hooks is replaced with + certbot._internal.hooks.post_hooks is replaced with self.eventually. The mock execute object is returned rather than the return value of run_saved_post_hooks. """ - eventually_path = "certbot.hooks.post_hooks" + eventually_path = "certbot._internal.hooks.post_hooks" with mock.patch(eventually_path, new=self.eventually): return self._call_with_mock_execute(*args, **kwargs) @@ -292,7 +290,7 @@ class RenewalHookTest(HookTest): # pylint: disable=abstract-method def _call_with_mock_execute(self, *args, **kwargs): - """Calls self._call after mocking out certbot.hooks.execute. + """Calls self._call after mocking out certbot._internal.hooks.execute. The mock execute object is returned rather than the return value of self._call. The mock execute object asserts that environment @@ -313,7 +311,7 @@ class RenewalHookTest(HookTest): self.assertEqual(os.environ["RENEWED_LINEAGE"], lineage) return ("", "") - with mock.patch("certbot.hooks.execute") as mock_execute: + with mock.patch("certbot._internal.hooks.execute") as mock_execute: mock_execute.side_effect = execute_side_effect self._call(*args, **kwargs) return mock_execute @@ -331,14 +329,14 @@ class RenewalHookTest(HookTest): class DeployHookTest(RenewalHookTest): - """Tests for certbot.hooks.deploy_hook.""" + """Tests for certbot._internal.hooks.deploy_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import deploy_hook + from certbot._internal.hooks import deploy_hook return deploy_hook(*args, **kwargs) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_dry_run(self, mock_logger): self.config.deploy_hook = "foo" self.config.dry_run = True @@ -347,7 +345,7 @@ class DeployHookTest(RenewalHookTest): self.assertFalse(mock_execute.called) self.assertTrue(mock_logger.warning.called) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_no_hook(self, mock_logger): self.config.deploy_hook = None mock_execute = self._call_with_mock_execute( @@ -365,18 +363,18 @@ class DeployHookTest(RenewalHookTest): class RenewHookTest(RenewalHookTest): - """Tests for certbot.hooks.renew_hook""" + """Tests for certbot._internal.hooks.renew_hook""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import renew_hook + from certbot._internal.hooks import renew_hook return renew_hook(*args, **kwargs) def setUp(self): super(RenewHookTest, self).setUp() self.config.renew_hook = "foo" - os.makedirs(self.config.renewal_deploy_hooks_dir) + filesystem.makedirs(self.config.renewal_deploy_hooks_dir) self.dir_hook = os.path.join(self.config.renewal_deploy_hooks_dir, "bar") create_hook(self.dir_hook) @@ -387,7 +385,7 @@ class RenewHookTest(RenewalHookTest): self.config, ["example.org"], "/foo/bar") mock_execute.assert_called_once_with("deploy-hook", self.config.renew_hook) - @mock.patch("certbot.hooks.logger") + @mock.patch("certbot._internal.hooks.logger") def test_dry_run(self, mock_logger): self.config.dry_run = True mock_execute = self._call_with_mock_execute( @@ -399,7 +397,7 @@ class RenewHookTest(RenewalHookTest): self.config.renew_hook = None os.remove(self.dir_hook) - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: mock_execute = self._call_with_mock_execute( self.config, ["example.org"], "/foo/bar") self.assertFalse(mock_execute.called) @@ -419,11 +417,11 @@ class RenewHookTest(RenewalHookTest): class ExecuteTest(unittest.TestCase): - """Tests for certbot.hooks.execute.""" + """Tests for certbot._internal.hooks.execute.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import execute + from certbot._internal.hooks import execute return execute(*args, **kwargs) def test_it(self): @@ -435,10 +433,10 @@ class ExecuteTest(unittest.TestCase): def _test_common(self, returncode, stdout, stderr): given_command = "foo" given_name = "foo-hook" - with mock.patch("certbot.hooks.Popen") as mock_popen: + with mock.patch("certbot._internal.hooks.Popen") as mock_popen: mock_popen.return_value.communicate.return_value = (stdout, stderr) mock_popen.return_value.returncode = returncode - with mock.patch("certbot.hooks.logger") as mock_logger: + with mock.patch("certbot._internal.hooks.logger") as mock_logger: self.assertEqual(self._call(given_name, given_command), (stderr, stdout)) executed_command = mock_popen.call_args[1].get( @@ -454,12 +452,12 @@ class ExecuteTest(unittest.TestCase): self.assertTrue(mock_logger.error.called) -class ListHooksTest(util.TempDirTestCase): - """Tests for certbot.hooks.list_hooks.""" +class ListHooksTest(test_util.TempDirTestCase): + """Tests for certbot._internal.hooks.list_hooks.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.hooks import list_hooks + from certbot._internal.hooks import list_hooks return list_hooks(*args, **kwargs) def test_empty(self): @@ -494,8 +492,7 @@ def create_hook(file_path): :param str file_path: path to create the file at """ - open(file_path, "w").close() - filesystem.chmod(file_path, os.stat(file_path).st_mode | stat.S_IXUSR) + util.safe_open(file_path, mode="w", chmod=0o744).close() if __name__ == '__main__': diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index fb3a8fedf..5a48009fd 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -1,13 +1,7 @@ -"""Tests for certbot.lock.""" +"""Tests for certbot._internal.lock.""" import functools import multiprocessing import unittest -try: - import fcntl # pylint: disable=import-error,unused-import -except ImportError: - POSIX_MODE = False -else: - POSIX_MODE = True import mock @@ -15,12 +9,21 @@ from certbot import errors from certbot.compat import os from certbot.tests import util as test_util +try: + import fcntl # pylint: disable=import-error,unused-import +except ImportError: + POSIX_MODE = False +else: + POSIX_MODE = True + + + class LockDirTest(test_util.TempDirTestCase): - """Tests for certbot.lock.lock_dir.""" + """Tests for certbot._internal.lock.lock_dir.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.lock import lock_dir + from certbot._internal.lock import lock_dir return lock_dir(*args, **kwargs) def test_it(self): @@ -31,10 +34,10 @@ class LockDirTest(test_util.TempDirTestCase): class LockFileTest(test_util.TempDirTestCase): - """Tests for certbot.lock.LockFile.""" + """Tests for certbot._internal.lock.LockFile.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.lock import LockFile + from certbot._internal.lock import LockFile return LockFile(*args, **kwargs) def setUp(self): @@ -82,7 +85,10 @@ class LockFileTest(test_util.TempDirTestCase): 'Race conditions on lock are specific to the non-blocking file access approach on Linux.') def test_race(self): should_delete = [True, False] - stat = os.stat + # Normally os module should not be imported in certbot codebase except in certbot.compat + # for the sake of compatibility over Windows and Linux. + # We make an exception here, since test_race is a test function called only on Linux. + from os import stat # pylint: disable=os-module-forbidden def delete_and_stat(path): """Wrap os.stat and maybe delete the file first.""" @@ -90,7 +96,7 @@ class LockFileTest(test_util.TempDirTestCase): os.remove(path) return stat(path) - with mock.patch('certbot.lock.os.stat') as mock_stat: + with mock.patch('certbot._internal.lock.filesystem.os.stat') as mock_stat: mock_stat.side_effect = delete_and_stat self._call(self.lock_path) self.assertFalse(should_delete) @@ -102,9 +108,9 @@ class LockFileTest(test_util.TempDirTestCase): def test_unexpected_lockf_or_locking_err(self): if POSIX_MODE: - mocked_function = 'certbot.lock.fcntl.lockf' + mocked_function = 'certbot._internal.lock.fcntl.lockf' else: - mocked_function = 'certbot.lock.msvcrt.locking' + mocked_function = 'certbot._internal.lock.msvcrt.locking' msg = 'hi there' with mock.patch(mocked_function) as mock_lock: mock_lock.side_effect = IOError(msg) @@ -117,9 +123,9 @@ class LockFileTest(test_util.TempDirTestCase): def test_unexpected_os_err(self): if POSIX_MODE: - mock_function = 'certbot.lock.os.stat' + mock_function = 'certbot._internal.lock.filesystem.os.stat' else: - mock_function = 'certbot.lock.msvcrt.locking' + mock_function = 'certbot._internal.lock.msvcrt.locking' # The only expected errno are ENOENT and EACCES in lock module. msg = 'hi there' with mock.patch(mock_function) as mock_os: diff --git a/certbot/tests/log_test.py b/certbot/tests/log_test.py index d203635a2..3b9adbbf2 100644 --- a/certbot/tests/log_test.py +++ b/certbot/tests/log_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.log.""" +"""Tests for certbot._internal.log.""" import logging import logging.handlers import sys @@ -10,27 +10,26 @@ import six from acme import messages from acme.magic_typing import Optional # pylint: disable=unused-import, no-name-in-module - -from certbot import constants from certbot import errors from certbot import util -from certbot.compat import misc +from certbot._internal import constants +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import util as test_util class PreArgParseSetupTest(unittest.TestCase): - """Tests for certbot.log.pre_arg_parse_setup.""" + """Tests for certbot._internal.log.pre_arg_parse_setup.""" @classmethod def _call(cls, *args, **kwargs): # pylint: disable=unused-argument - from certbot.log import pre_arg_parse_setup + from certbot._internal.log import pre_arg_parse_setup return pre_arg_parse_setup() - @mock.patch('certbot.log.sys') - @mock.patch('certbot.log.pre_arg_parse_except_hook') - @mock.patch('certbot.log.logging.getLogger') - @mock.patch('certbot.log.util.atexit_register') + @mock.patch('certbot._internal.log.sys') + @mock.patch('certbot._internal.log.pre_arg_parse_except_hook') + @mock.patch('certbot._internal.log.logging.getLogger') + @mock.patch('certbot._internal.log.util.atexit_register') def test_it(self, mock_register, mock_get, mock_except_hook, mock_sys): mock_sys.argv = ['--debug'] mock_sys.version_info = sys.version_info @@ -58,11 +57,11 @@ class PreArgParseSetupTest(unittest.TestCase): class PostArgParseSetupTest(test_util.ConfigTestCase): - """Tests for certbot.log.post_arg_parse_setup.""" + """Tests for certbot._internal.log.post_arg_parse_setup.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import post_arg_parse_setup + from certbot._internal.log import post_arg_parse_setup return post_arg_parse_setup(*args, **kwargs) def setUp(self): @@ -73,9 +72,9 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): self.config.verbose_count = constants.CLI_DEFAULTS['verbose_count'] self.devnull = open(os.devnull, 'w') - from certbot.log import ColoredStreamHandler + from certbot._internal.log import ColoredStreamHandler self.stream_handler = ColoredStreamHandler(six.StringIO()) - from certbot.log import MemoryHandler, TempHandler + from certbot._internal.log import MemoryHandler, TempHandler self.temp_handler = TempHandler() self.temp_path = self.temp_handler.path self.memory_handler = MemoryHandler(self.temp_handler) @@ -90,11 +89,11 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): super(PostArgParseSetupTest, self).tearDown() def test_common(self): - with mock.patch('certbot.log.logging.getLogger') as mock_get_logger: + with mock.patch('certbot._internal.log.logging.getLogger') as mock_get_logger: mock_get_logger.return_value = self.root_logger - except_hook_path = 'certbot.log.post_arg_parse_except_hook' + except_hook_path = 'certbot._internal.log.post_arg_parse_except_hook' with mock.patch(except_hook_path) as mock_except_hook: - with mock.patch('certbot.log.sys') as mock_sys: + with mock.patch('certbot._internal.log.sys') as mock_sys: mock_sys.version_info = sys.version_info self._call(self.config) @@ -124,18 +123,18 @@ class PostArgParseSetupTest(test_util.ConfigTestCase): class SetupLogFileHandlerTest(test_util.ConfigTestCase): - """Tests for certbot.log.setup_log_file_handler.""" + """Tests for certbot._internal.log.setup_log_file_handler.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import setup_log_file_handler + from certbot._internal.log import setup_log_file_handler return setup_log_file_handler(*args, **kwargs) def setUp(self): super(SetupLogFileHandlerTest, self).setUp() self.config.max_log_backups = 42 - @mock.patch('certbot.main.logging.handlers.RotatingFileHandler') + @mock.patch('certbot._internal.main.logging.handlers.RotatingFileHandler') def test_failure(self, mock_handler): mock_handler.side_effect = IOError @@ -167,7 +166,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): backup_path = os.path.join(self.config.logs_dir, log_file + '.1') self.assertEqual(os.path.exists(backup_path), should_rollover) - @mock.patch('certbot.log.logging.handlers.RotatingFileHandler') + @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler') def test_max_log_backups_used(self, mock_handler): self._call(self.config, 'test.log', '%(message)s') backup_count = mock_handler.call_args[1]['backupCount'] @@ -175,7 +174,7 @@ class SetupLogFileHandlerTest(test_util.ConfigTestCase): class ColoredStreamHandlerTest(unittest.TestCase): - """Tests for certbot.log.ColoredStreamHandler""" + """Tests for certbot._internal.log.ColoredStreamHandler""" def setUp(self): self.stream = six.StringIO() @@ -183,7 +182,7 @@ class ColoredStreamHandlerTest(unittest.TestCase): self.logger = logging.getLogger() self.logger.setLevel(logging.DEBUG) - from certbot.log import ColoredStreamHandler + from certbot._internal.log import ColoredStreamHandler self.handler = ColoredStreamHandler(self.stream) self.logger.addHandler(self.handler) @@ -207,7 +206,7 @@ class ColoredStreamHandlerTest(unittest.TestCase): class MemoryHandlerTest(unittest.TestCase): - """Tests for certbot.log.MemoryHandler""" + """Tests for certbot._internal.log.MemoryHandler""" def setUp(self): self.logger = logging.getLogger(__name__) self.logger.setLevel(logging.DEBUG) @@ -215,7 +214,7 @@ class MemoryHandlerTest(unittest.TestCase): self.stream = six.StringIO() self.stream_handler = logging.StreamHandler(self.stream) - from certbot.log import MemoryHandler + from certbot._internal.log import MemoryHandler self.handler = MemoryHandler(self.stream_handler) self.logger.addHandler(self.handler) @@ -250,18 +249,17 @@ class MemoryHandlerTest(unittest.TestCase): class TempHandlerTest(unittest.TestCase): - """Tests for certbot.log.TempHandler.""" + """Tests for certbot._internal.log.TempHandler.""" def setUp(self): self.closed = False - from certbot.log import TempHandler + from certbot._internal.log import TempHandler self.handler = TempHandler() def tearDown(self): self.handler.close() def test_permissions(self): - self.assertTrue( - util.check_permissions(self.handler.path, 0o600, misc.os_geteuid())) + self.assertTrue(filesystem.check_permissions(self.handler.path, 0o600)) def test_delete(self): self.handler.close() @@ -275,13 +273,13 @@ class TempHandlerTest(unittest.TestCase): class PreArgParseExceptHookTest(unittest.TestCase): - """Tests for certbot.log.pre_arg_parse_except_hook.""" + """Tests for certbot._internal.log.pre_arg_parse_except_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import pre_arg_parse_except_hook + from certbot._internal.log import pre_arg_parse_except_hook return pre_arg_parse_except_hook(*args, **kwargs) - @mock.patch('certbot.log.post_arg_parse_except_hook') + @mock.patch('certbot._internal.log.post_arg_parse_except_hook') def test_it(self, mock_post_arg_parse_except_hook): memory_handler = mock.MagicMock() args = ('some', 'args',) @@ -295,10 +293,10 @@ class PreArgParseExceptHookTest(unittest.TestCase): class PostArgParseExceptHookTest(unittest.TestCase): - """Tests for certbot.log.post_arg_parse_except_hook.""" + """Tests for certbot._internal.log.post_arg_parse_except_hook.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import post_arg_parse_except_hook + from certbot._internal.log import post_arg_parse_except_hook return post_arg_parse_except_hook(*args, **kwargs) def setUp(self): @@ -354,9 +352,9 @@ class PostArgParseExceptHookTest(unittest.TestCase): raise error_type(self.error_msg) except BaseException: exc_info = sys.exc_info() - with mock.patch('certbot.log.logger') as mock_logger: + with mock.patch('certbot._internal.log.logger') as mock_logger: mock_logger.error.side_effect = write_err - with mock.patch('certbot.log.sys.stderr', mock_err): + with mock.patch('certbot._internal.log.sys.stderr', mock_err): try: self._call( *exc_info, debug=debug, log_path=self.log_path) @@ -388,10 +386,10 @@ class PostArgParseExceptHookTest(unittest.TestCase): class ExitWithLogPathTest(test_util.TempDirTestCase): - """Tests for certbot.log.exit_with_log_path.""" + """Tests for certbot._internal.log.exit_with_log_path.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.log import exit_with_log_path + from certbot._internal.log import exit_with_log_path return exit_with_log_path(*args, **kwargs) def test_log_file(self): diff --git a/certbot/tests/main_test.py b/certbot/tests/main_test.py index bdc42a62f..7b22c81d6 100644 --- a/certbot/tests/main_test.py +++ b/certbot/tests/main_test.py @@ -1,5 +1,5 @@ # coding=utf-8 -"""Tests for certbot.main.""" +"""Tests for certbot._internal.main.""" # pylint: disable=too-many-lines from __future__ import print_function @@ -19,24 +19,23 @@ import six from six.moves import reload_module # pylint: disable=import-error from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - -import certbot.tests.util as test_util -from certbot import account -from certbot import cli -from certbot import configuration -from certbot import constants from certbot import crypto_util from certbot import errors from certbot import interfaces # pylint: disable=unused-import -from certbot import main -from certbot import updater from certbot import util -from certbot.compat import misc +from certbot._internal import account +from certbot._internal import cli +from certbot._internal import configuration +from certbot._internal import constants +from certbot._internal import main +from certbot._internal import updater +from certbot._internal.plugins import disco +from certbot._internal.plugins import manual +from certbot._internal.plugins import null +from certbot.compat import filesystem from certbot.compat import os -from certbot.plugins import disco from certbot.plugins import enhancements -from certbot.plugins import manual -from certbot.plugins import null +import certbot.tests.util as test_util CERT_PATH = test_util.vector_path('cert_512.pem') CERT = test_util.vector_path('cert_512.pem') @@ -48,7 +47,7 @@ SS_CERT_PATH = test_util.vector_path('cert_2048.pem') class TestHandleIdenticalCerts(unittest.TestCase): - """Test for certbot.main._handle_identical_cert_request""" + """Test for certbot._internal.main._handle_identical_cert_request""" def test_handle_identical_cert_request_pending(self): mock_lineage = mock.Mock() mock_lineage.ensure_deployed.return_value = False @@ -58,19 +57,19 @@ class TestHandleIdenticalCerts(unittest.TestCase): class RunTest(test_util.ConfigTestCase): - """Tests for certbot.main.run.""" + """Tests for certbot._internal.main.run.""" def setUp(self): super(RunTest, self).setUp() self.domain = 'example.org' self.patches = [ - mock.patch('certbot.main._get_and_save_cert'), - mock.patch('certbot.main.display_ops.success_installation'), - mock.patch('certbot.main.display_ops.success_renewal'), - mock.patch('certbot.main._init_le_client'), - mock.patch('certbot.main._suggest_donation_if_appropriate'), - mock.patch('certbot.main._report_new_cert'), - mock.patch('certbot.main._find_cert')] + 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'), + mock.patch('certbot._internal.main._init_le_client'), + mock.patch('certbot._internal.main._suggest_donation_if_appropriate'), + 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() @@ -90,7 +89,7 @@ class RunTest(test_util.ConfigTestCase): config = configuration.NamespaceConfig( cli.prepare_and_parse_args(plugins, args)) - from certbot.main import run + from certbot._internal.main import run run(config, plugins) def test_newcert_success(self): @@ -111,7 +110,7 @@ class RunTest(test_util.ConfigTestCase): self._call() self.mock_success_renewal.assert_called_once_with([self.domain]) - @mock.patch('certbot.main.plug_sel.choose_configurator_plugins') + @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins') def test_run_enhancement_not_supported(self, mock_choose): mock_choose.return_value = (null.Installer(self.config, "null"), None) plugins = disco.PluginsRegistry.find_all() @@ -122,7 +121,7 @@ class RunTest(test_util.ConfigTestCase): class CertonlyTest(unittest.TestCase): - """Tests for certbot.main.certonly.""" + """Tests for certbot._internal.main.certonly.""" def setUp(self): self.get_utility_patch = test_util.patch_get_utility() @@ -136,15 +135,15 @@ class CertonlyTest(unittest.TestCase): config = configuration.NamespaceConfig( cli.prepare_and_parse_args(plugins, args)) - with mock.patch('certbot.main._init_le_client') as mock_init: - with mock.patch('certbot.main._suggest_donation_if_appropriate'): + with mock.patch('certbot._internal.main._init_le_client') as mock_init: + with mock.patch('certbot._internal.main._suggest_donation_if_appropriate'): main.certonly(config, plugins) return mock_init() # returns the client - @mock.patch('certbot.main._find_cert') - @mock.patch('certbot.main._get_and_save_cert') - @mock.patch('certbot.main._report_new_cert') + @mock.patch('certbot._internal.main._find_cert') + @mock.patch('certbot._internal.main._get_and_save_cert') + @mock.patch('certbot._internal.main._report_new_cert') def test_no_reinstall_text_pause(self, unused_report, mock_auth, mock_find_cert): mock_notification = self.mock_get_utility().notification @@ -156,10 +155,10 @@ class CertonlyTest(unittest.TestCase): def _assert_no_pause(self, message, pause=True): # pylint: disable=unused-argument self.assertFalse(pause) - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.cert_manager.domains_for_certname') - @mock.patch('certbot.renewal.renew_cert') - @mock.patch('certbot.main._report_new_cert') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.cert_manager.domains_for_certname') + @mock.patch('certbot._internal.renewal.renew_cert') + @mock.patch('certbot._internal.main._report_new_cert') def test_find_lineage_for_domains_and_certname(self, mock_report_cert, mock_renew_cert, mock_domains, mock_lineage): domains = ['example.com', 'test.org'] @@ -185,10 +184,10 @@ class CertonlyTest(unittest.TestCase): self.assertRaises(errors.ConfigurationError, self._call, ('certonly --webroot -d example.com -d test.com --cert-name example.com').split()) - @mock.patch('certbot.cert_manager.domains_for_certname') + @mock.patch('certbot._internal.cert_manager.domains_for_certname') @mock.patch('certbot.display.ops.choose_names') - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main._report_new_cert') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main._report_new_cert') def test_find_lineage_for_domains_new_certname(self, mock_report_cert, mock_lineage, mock_choose_names, mock_domains_for_certname): mock_lineage.return_value = None @@ -206,7 +205,7 @@ class CertonlyTest(unittest.TestCase): self.assertTrue(mock_choose_names.called) class FindDomainsOrCertnameTest(unittest.TestCase): - """Tests for certbot.main._find_domains_or_certname.""" + """Tests for certbot._internal.main._find_domains_or_certname.""" @mock.patch('certbot.display.ops.choose_names') def test_display_ops(self, mock_choose_names): @@ -223,7 +222,7 @@ class FindDomainsOrCertnameTest(unittest.TestCase): # pylint: disable=protected-access self.assertRaises(errors.Error, main._find_domains_or_certname, mock_config, None) - @mock.patch('certbot.cert_manager.domains_for_certname') + @mock.patch('certbot._internal.cert_manager.domains_for_certname') def test_grab_domains(self, mock_domains): mock_config = mock.Mock(domains=None, certname="one.com") mock_domains.return_value = ["one.com", "two.com"] @@ -233,7 +232,7 @@ class FindDomainsOrCertnameTest(unittest.TestCase): class RevokeTest(test_util.TempDirTestCase): - """Tests for certbot.main.revoke.""" + """Tests for certbot._internal.main.revoke.""" def setUp(self): super(RevokeTest, self).setUp() @@ -246,16 +245,16 @@ class RevokeTest(test_util.TempDirTestCase): self.patches = [ mock.patch('acme.client.BackwardsCompatibleClientV2'), - mock.patch('certbot.client.Client'), - mock.patch('certbot.main._determine_account'), - mock.patch('certbot.main.display_ops.success_revocation') + 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() - from certbot.account import Account + from certbot._internal.account import Account self.regr = mock.MagicMock() self.meta = Account.Meta( @@ -280,11 +279,11 @@ class RevokeTest(test_util.TempDirTestCase): config = configuration.NamespaceConfig( cli.prepare_and_parse_args(plugins, args)) - from certbot.main import revoke + from certbot._internal.main import revoke revoke(config, plugins) - @mock.patch('certbot.main._delete_if_appropriate') - @mock.patch('certbot.main.client.acme_client') + @mock.patch('certbot._internal.main._delete_if_appropriate') + @mock.patch('certbot._internal.main.client.acme_client') def test_revoke_with_reason(self, mock_acme_client, mock_delete_if_appropriate): mock_delete_if_appropriate.return_value = False @@ -300,8 +299,8 @@ class RevokeTest(test_util.TempDirTestCase): expected.append(mock.call(mock.ANY, code)) self.assertEqual(expected, mock_revoke.call_args_list) - @mock.patch('certbot.main._delete_if_appropriate') - @mock.patch('certbot.storage.cert_path_for_cert_name') + @mock.patch('certbot._internal.main._delete_if_appropriate') + @mock.patch('certbot._internal.storage.cert_path_for_cert_name') def test_revoke_by_certname(self, mock_cert_path_for_cert_name, mock_delete_if_appropriate): args = 'revoke --cert-name=example.com'.split() @@ -310,7 +309,7 @@ class RevokeTest(test_util.TempDirTestCase): self._call(args) self.mock_success_revoke.assert_called_once_with(self.tmp_cert_path) - @mock.patch('certbot.main._delete_if_appropriate') + @mock.patch('certbot._internal.main._delete_if_appropriate') def test_revocation_success(self, mock_delete_if_appropriate): self._call() mock_delete_if_appropriate.return_value = False @@ -322,8 +321,8 @@ class RevokeTest(test_util.TempDirTestCase): self.assertRaises(acme_errors.ClientError, self._call) self.mock_success_revoke.assert_not_called() - @mock.patch('certbot.main._delete_if_appropriate') - @mock.patch('certbot.cert_manager.delete') + @mock.patch('certbot._internal.main._delete_if_appropriate') + @mock.patch('certbot._internal.cert_manager.delete') @test_util.patch_get_utility() def test_revocation_with_prompt(self, mock_get_utility, mock_delete, mock_delete_if_appropriate): @@ -333,14 +332,14 @@ class RevokeTest(test_util.TempDirTestCase): self.assertFalse(mock_delete.called) class DeleteIfAppropriateTest(test_util.ConfigTestCase): - """Tests for certbot.main._delete_if_appropriate """ + """Tests for certbot._internal.main._delete_if_appropriate """ def _call(self, mock_config): - from certbot.main import _delete_if_appropriate + from certbot._internal.main import _delete_if_appropriate _delete_if_appropriate(mock_config) def _test_delete_opt_out_common(self, mock_get_utility): - with mock.patch('certbot.cert_manager.delete') as mock_delete: + with mock.patch('certbot._internal.cert_manager.delete') as mock_delete: self._call(self.config) mock_delete.assert_not_called() self.assertTrue(mock_get_utility().add_message.called) @@ -356,12 +355,11 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): util_mock.yesno.return_value = False self._test_delete_opt_out_common(mock_get_utility) - # pylint: disable=too-many-arguments - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.cert_manager.delete') - @mock.patch('certbot.cert_manager.match_and_check_overlaps') - @mock.patch('certbot.storage.full_archive_path') - @mock.patch('certbot.cert_manager.cert_path_to_lineage') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.cert_manager.delete') + @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') + @mock.patch('certbot._internal.storage.full_archive_path') + @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage') @test_util.patch_get_utility() def test_overlapping_archive_dirs(self, mock_get_utility, mock_cert_path_to_lineage, mock_archive, @@ -376,12 +374,11 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) mock_delete.assert_not_called() - # pylint: disable=too-many-arguments - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.cert_manager.match_and_check_overlaps') - @mock.patch('certbot.storage.full_archive_path') - @mock.patch('certbot.cert_manager.delete') - @mock.patch('certbot.cert_manager.cert_path_to_lineage') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') + @mock.patch('certbot._internal.storage.full_archive_path') + @mock.patch('certbot._internal.cert_manager.delete') + @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage') @test_util.patch_get_utility() def test_cert_path_only(self, mock_get_utility, mock_cert_path_to_lineage, mock_delete, mock_archive, @@ -395,12 +392,11 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) self.assertEqual(mock_delete.call_count, 1) - # pylint: disable=too-many-arguments - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.cert_manager.match_and_check_overlaps') - @mock.patch('certbot.storage.full_archive_path') - @mock.patch('certbot.cert_manager.cert_path_to_lineage') - @mock.patch('certbot.cert_manager.delete') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') + @mock.patch('certbot._internal.storage.full_archive_path') + @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage') + @mock.patch('certbot._internal.cert_manager.delete') @test_util.patch_get_utility() def test_noninteractive_deletion(self, mock_get_utility, mock_delete, mock_cert_path_to_lineage, mock_full_archive_dir, @@ -416,12 +412,11 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): self._call(config) self.assertEqual(mock_delete.call_count, 1) - # pylint: disable=too-many-arguments - @mock.patch('certbot.storage.renewal_file_for_certname') - @mock.patch('certbot.cert_manager.match_and_check_overlaps') - @mock.patch('certbot.storage.full_archive_path') - @mock.patch('certbot.cert_manager.cert_path_to_lineage') - @mock.patch('certbot.cert_manager.delete') + @mock.patch('certbot._internal.storage.renewal_file_for_certname') + @mock.patch('certbot._internal.cert_manager.match_and_check_overlaps') + @mock.patch('certbot._internal.storage.full_archive_path') + @mock.patch('certbot._internal.cert_manager.cert_path_to_lineage') + @mock.patch('certbot._internal.cert_manager.delete') @test_util.patch_get_utility() def test_opt_in_deletion(self, mock_get_utility, mock_delete, mock_cert_path_to_lineage, mock_full_archive_dir, @@ -440,7 +435,7 @@ class DeleteIfAppropriateTest(test_util.ConfigTestCase): class DetermineAccountTest(test_util.ConfigTestCase): - """Tests for certbot.main._determine_account.""" + """Tests for certbot._internal.main._determine_account.""" def setUp(self): super(DetermineAccountTest, self).setUp() @@ -455,8 +450,8 @@ class DetermineAccountTest(test_util.ConfigTestCase): def _call(self): # pylint: disable=protected-access - from certbot.main import _determine_account - with mock.patch('certbot.main.account.AccountFileStorage') as mock_storage: + from certbot._internal.main import _determine_account + with mock.patch('certbot._internal.main.account.AccountFileStorage') as mock_storage: mock_storage.return_value = self.account_storage return _determine_account(self.config) @@ -473,7 +468,7 @@ class DetermineAccountTest(test_util.ConfigTestCase): self.assertEqual(self.accs[0].id, self.config.account) self.assertTrue(self.config.email is None) - @mock.patch('certbot.client.display_ops.choose_account') + @mock.patch('certbot._internal.client.display_ops.choose_account') def test_multiple_accounts(self, mock_choose_accounts): for acc in self.accs: self.account_storage.save(acc, self.mock_client) @@ -484,11 +479,11 @@ class DetermineAccountTest(test_util.ConfigTestCase): self.assertEqual(self.accs[1].id, self.config.account) self.assertTrue(self.config.email is None) - @mock.patch('certbot.client.display_ops.get_email') + @mock.patch('certbot._internal.client.display_ops.get_email') def test_no_accounts_no_email(self, mock_get_email): mock_get_email.return_value = 'foo@bar.baz' - with mock.patch('certbot.main.client') as client: + with mock.patch('certbot._internal.main.client') as client: client.register.return_value = ( self.accs[0], mock.sentinel.acme) self.assertEqual((self.accs[0], mock.sentinel.acme), self._call()) @@ -500,20 +495,20 @@ class DetermineAccountTest(test_util.ConfigTestCase): def test_no_accounts_email(self): self.config.email = 'other email' - with mock.patch('certbot.main.client') as client: + with mock.patch('certbot._internal.main.client') as client: client.register.return_value = (self.accs[1], mock.sentinel.acme) self._call() self.assertEqual(self.accs[1].id, self.config.account) self.assertEqual('other email', self.config.email) -class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-methods +class MainTest(test_util.ConfigTestCase): """Tests for different commands.""" def setUp(self): super(MainTest, self).setUp() - os.mkdir(self.config.logs_dir) + filesystem.mkdir(self.config.logs_dir) self.standard_args = ['--config-dir', self.config.config_dir, '--work-dir', self.config.work_dir, '--logs-dir', self.config.logs_dir, '--text'] @@ -541,13 +536,13 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met return True return orig_open(fn) - with mock.patch("os.path.isfile") as mock_if: + with mock.patch("certbot.compat.os.path.isfile") as mock_if: mock_if.side_effect = mock_isfile - with mock.patch('certbot.main.client') as client: + with mock.patch('certbot._internal.main.client') as client: ret, stdout, stderr = self._call_no_clientmock(args, stdout) return ret, stdout, stderr, client else: - with mock.patch('certbot.main.client') as client: + with mock.patch('certbot._internal.main.client') as client: ret, stdout, stderr = self._call_no_clientmock(args, stdout) return ret, stdout, stderr, client @@ -556,22 +551,22 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met args = self.standard_args + args toy_stdout = stdout if stdout else six.StringIO() - with mock.patch('certbot.main.sys.stdout', new=toy_stdout): - with mock.patch('certbot.main.sys.stderr') as stderr: + with mock.patch('certbot._internal.main.sys.stdout', new=toy_stdout): + with mock.patch('certbot._internal.main.sys.stderr') as stderr: with mock.patch("certbot.util.atexit"): ret = main.main(args[:]) # NOTE: parser can alter its args! return ret, toy_stdout, stderr def test_no_flags(self): - with mock.patch('certbot.main.run') as mock_run: + with mock.patch('certbot._internal.main.run') as mock_run: self._call([]) self.assertEqual(1, mock_run.call_count) def test_version_string_program_name(self): toy_out = six.StringIO() toy_err = six.StringIO() - with mock.patch('certbot.main.sys.stdout', new=toy_out): - with mock.patch('certbot.main.sys.stderr', new=toy_err): + with mock.patch('certbot._internal.main.sys.stdout', new=toy_out): + with mock.patch('certbot._internal.main.sys.stderr', new=toy_err): try: main.main(["--version"]) except SystemExit: @@ -584,26 +579,26 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met "Ensure that a particular error raises a missing cli flag error containing message" exc = None try: - with mock.patch('certbot.main.sys.stderr'): + with mock.patch('certbot._internal.main.sys.stderr'): main.main(self.standard_args + args[:]) # NOTE: parser can alter its args! except errors.MissingCommandlineFlag as exc_: exc = exc_ self.assertTrue(message in str(exc)) self.assertTrue(exc is not None) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_noninteractive(self, _): args = ['-n', 'certonly'] self._cli_missing_flag(args, "specify a plugin") args.extend(['--standalone', '-d', 'eg.is']) self._cli_missing_flag(args, "register before running") - @mock.patch('certbot.log.post_arg_parse_setup') - @mock.patch('certbot.main._report_new_cert') - @mock.patch('certbot.main.client.acme_client.Client') - @mock.patch('certbot.main._determine_account') - @mock.patch('certbot.main.client.Client.obtain_and_enroll_certificate') - @mock.patch('certbot.main._get_and_save_cert') + @mock.patch('certbot._internal.log.post_arg_parse_setup') + @mock.patch('certbot._internal.main._report_new_cert') + @mock.patch('certbot._internal.main.client.acme_client.Client') + @mock.patch('certbot._internal.main._determine_account') + @mock.patch('certbot._internal.main.client.Client.obtain_and_enroll_certificate') + @mock.patch('certbot._internal.main._get_and_save_cert') def test_user_agent(self, gsc, _obt, det, _client, _, __): # Normally the client is totally mocked out, but here we need more # arguments to automate it... @@ -612,7 +607,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met det.return_value = mock.MagicMock(), None gsc.return_value = mock.MagicMock() - with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net: + with mock.patch('certbot._internal.main.client.acme_client.ClientNetwork') as acme_net: self._call_no_clientmock(args) os_ver = util.get_os_info_ua() ua = acme_net.call_args[1]["user_agent"] @@ -622,30 +617,30 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met if "linux" in plat.lower(): self.assertTrue(util.get_os_info_ua() in ua) - with mock.patch('certbot.main.client.acme_client.ClientNetwork') as acme_net: + with mock.patch('certbot._internal.main.client.acme_client.ClientNetwork') as acme_net: ua = "bandersnatch" args += ["--user-agent", ua] self._call_no_clientmock(args) acme_net.assert_called_once_with(mock.ANY, account=mock.ANY, verify_ssl=True, user_agent=ua) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_installer_selection(self, mock_pick_installer, _rec): self._call(['install', '--domains', 'foo.bar', '--cert-path', 'cert', '--key-path', 'privkey', '--chain-path', 'chain'], mockisfile=True) self.assertEqual(mock_pick_installer.call_count, 1) - @mock.patch('certbot.main._install_cert') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main._install_cert') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_installer_certname(self, _inst, _rec, mock_install): mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'), chain_path=test_util.temp_join('chain'), fullchain_path=test_util.temp_join('chain'), key_path=test_util.temp_join('privkey')) - with mock.patch("certbot.cert_manager.lineage_for_certname") as mock_getlin: + with mock.patch("certbot._internal.cert_manager.lineage_for_certname") as mock_getlin: mock_getlin.return_value = mock_lineage self._call(['install', '--cert-name', 'whatever'], mockisfile=True) call_config = mock_install.call_args[0][0] @@ -653,16 +648,16 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) - @mock.patch('certbot.log.post_arg_parse_setup') - @mock.patch('certbot.main._install_cert') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.log.post_arg_parse_setup') + @mock.patch('certbot._internal.main._install_cert') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_installer_param_override(self, _inst, _rec, mock_install, _): mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'), chain_path=test_util.temp_join('chain'), fullchain_path=test_util.temp_join('chain'), key_path=test_util.temp_join('privkey')) - with mock.patch("certbot.cert_manager.lineage_for_certname") as mock_getlin: + with mock.patch("certbot._internal.cert_manager.lineage_for_certname") as mock_getlin: mock_getlin.return_value = mock_lineage self._call(['install', '--cert-name', 'whatever', '--key-path', test_util.temp_join('overriding_privkey')], mockisfile=True) @@ -681,31 +676,31 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertEqual(call_config.fullchain_path, test_util.temp_join('chain')) self.assertEqual(call_config.key_path, test_util.temp_join('privkey')) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_installer_param_error(self, _inst, _rec): self.assertRaises(errors.ConfigurationError, self._call, ['install', '--cert-name', 'notfound', '--key-path', 'invalid']) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') - @mock.patch('certbot.cert_manager.get_certnames') - @mock.patch('certbot.main._install_cert') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.cert_manager.get_certnames') + @mock.patch('certbot._internal.main._install_cert') def test_installer_select_cert(self, mock_inst, mock_getcert, _inst, _rec): mock_lineage = mock.MagicMock(cert_path=test_util.temp_join('cert'), chain_path=test_util.temp_join('chain'), fullchain_path=test_util.temp_join('chain'), key_path=test_util.temp_join('privkey')) - with mock.patch("certbot.cert_manager.lineage_for_certname") as mock_getlin: + with mock.patch("certbot._internal.cert_manager.lineage_for_certname") as mock_getlin: mock_getlin.return_value = mock_lineage self._call(['install'], mockisfile=True) self.assertTrue(mock_getcert.called) self.assertTrue(mock_inst.called) - @mock.patch('certbot.log.post_arg_parse_setup') - @mock.patch('certbot.main._report_new_cert') + @mock.patch('certbot._internal.log.post_arg_parse_setup') + @mock.patch('certbot._internal.main._report_new_cert') @mock.patch('certbot.util.exe_exists') def test_configurator_selection(self, mock_exe_exists, _, __): mock_exe_exists.return_value = True @@ -715,7 +710,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met # This needed two calls to find_all(), which we're avoiding for now # because of possible side effects: # https://github.com/letsencrypt/letsencrypt/commit/51ed2b681f87b1eb29088dd48718a54f401e4855 - #with mock.patch('certbot.cli.plugins_testable') as plugins: + #with mock.patch('certbot._internal.cli.plugins_testable') as plugins: # plugins.return_value = {"apache": True, "nginx": True} # ret, _, _, _ = self._call(args) # self.assertTrue("Too many flags setting" in ret) @@ -733,18 +728,18 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably") - with mock.patch("certbot.main._init_le_client") as mock_init: - with mock.patch("certbot.main._get_and_save_cert") as mock_gsc: + with mock.patch("certbot._internal.main._init_le_client") as mock_init: + with mock.patch("certbot._internal.main._get_and_save_cert") as mock_gsc: mock_gsc.return_value = mock.MagicMock() self._call(["certonly", "--manual", "-d", "foo.bar"]) unused_config, auth, unused_installer = mock_init.call_args[0] self.assertTrue(isinstance(auth, manual.Authenticator)) - with mock.patch('certbot.main.certonly') as mock_certonly: + with mock.patch('certbot._internal.main.certonly') as mock_certonly: self._call(["auth", "--standalone"]) self.assertEqual(1, mock_certonly.call_count) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_rollback(self, _): _, _, _, client = self._call(['rollback']) self.assertEqual(1, client.rollback.call_count) @@ -753,26 +748,22 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met client.rollback.assert_called_once_with( mock.ANY, 123, mock.ANY, mock.ANY) - def test_config_changes(self): - _, _, _, client = self._call(['config_changes']) - self.assertEqual(1, client.view_config_changes.call_count) - - @mock.patch('certbot.cert_manager.update_live_symlinks') + @mock.patch('certbot._internal.cert_manager.update_live_symlinks') def test_update_symlinks(self, mock_cert_manager): self._call_no_clientmock(['update_symlinks']) self.assertEqual(1, mock_cert_manager.call_count) - @mock.patch('certbot.cert_manager.certificates') + @mock.patch('certbot._internal.cert_manager.certificates') def test_certificates(self, mock_cert_manager): self._call_no_clientmock(['certificates']) self.assertEqual(1, mock_cert_manager.call_count) - @mock.patch('certbot.cert_manager.delete') + @mock.patch('certbot._internal.cert_manager.delete') def test_delete(self, mock_cert_manager): self._call_no_clientmock(['delete']) self.assertEqual(1, mock_cert_manager.call_count) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_plugins(self, _): flags = ['--init', '--prepare', '--authenticators', '--installers'] for args in itertools.chain( @@ -780,8 +771,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met for r in six.moves.range(len(flags)))): self._call(['plugins'] + list(args)) - @mock.patch('certbot.main.plugins_disco') - @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') + @mock.patch('certbot._internal.main.plugins_disco') + @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') def test_plugins_no_args(self, _det, mock_disco): ifaces = [] # type: List[interfaces.IPlugin] plugins = mock_disco.PluginsRegistry.find_all() @@ -795,15 +786,15 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met filtered = plugins.visible().ifaces() self.assertEqual(stdout.getvalue().strip(), str(filtered)) - @mock.patch('certbot.main.plugins_disco') - @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') + @mock.patch('certbot._internal.main.plugins_disco') + @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') def test_plugins_no_args_unprivileged(self, _det, mock_disco): ifaces = [] # type: List[interfaces.IPlugin] plugins = mock_disco.PluginsRegistry.find_all() - def throw_error(directory, mode, uid, strict): + def throw_error(directory, mode, strict): """Raises error.Error.""" - _, _, _, _ = directory, mode, uid, strict + _, _, _ = directory, mode, strict raise errors.Error() stdout = six.StringIO() @@ -817,8 +808,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met filtered = plugins.visible().ifaces() self.assertEqual(stdout.getvalue().strip(), str(filtered)) - @mock.patch('certbot.main.plugins_disco') - @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') + @mock.patch('certbot._internal.main.plugins_disco') + @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') def test_plugins_init(self, _det, mock_disco): ifaces = [] # type: List[interfaces.IPlugin] plugins = mock_disco.PluginsRegistry.find_all() @@ -835,8 +826,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met verified = filtered.verify() self.assertEqual(stdout.getvalue().strip(), str(verified)) - @mock.patch('certbot.main.plugins_disco') - @mock.patch('certbot.main.cli.HelpfulArgumentParser.determine_help_topics') + @mock.patch('certbot._internal.main.plugins_disco') + @mock.patch('certbot._internal.main.cli.HelpfulArgumentParser.determine_help_topics') def test_plugins_prepare(self, _det, mock_disco): ifaces = [] # type: List[interfaces.IPlugin] plugins = mock_disco.PluginsRegistry.find_all() @@ -862,7 +853,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met chain = 'chain' fullchain = 'fullchain' - with mock.patch('certbot.main.certonly') as mock_certonly: + with mock.patch('certbot._internal.main.certonly') as mock_certonly: self._call(['certonly', '--cert-path', cert, '--key-path', 'key', '--chain-path', 'chain', '--fullchain-path', 'fullchain']) @@ -920,9 +911,10 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met 'certonly -d example.org --csr {0}'.format(CSR).split()) def _certonly_new_request_common(self, mock_client, args=None): - with mock.patch('certbot.main._find_lineage_for_domains_and_certname') as mock_renewal: + with mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname') \ + as mock_renewal: mock_renewal.return_value = ("newcert", None) - with mock.patch('certbot.main._init_le_client') as mock_init: + with mock.patch('certbot._internal.main._init_le_client') as mock_init: mock_init.return_value = mock_client if args is None: args = [] @@ -973,7 +965,6 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met args=None, should_renew=True, error_expected=False, quiet_mode=False, expiry_date=datetime.datetime.now(), reuse_key=False): - # pylint: disable=too-many-locals,too-many-arguments,too-many-branches cert_path = test_util.vector_path('cert_512.pem') chain_path = os.path.normpath(os.path.join(self.config.config_dir, 'live/foo.bar/fullchain.pem')) @@ -994,18 +985,19 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met stdout.write(message) try: - with mock.patch('certbot.cert_manager.find_duplicative_certs') as mock_fdc: + with mock.patch('certbot._internal.cert_manager.find_duplicative_certs') as mock_fdc: mock_fdc.return_value = (mock_lineage, None) - with mock.patch('certbot.main._init_le_client') as mock_init: + with mock.patch('certbot._internal.main._init_le_client') as mock_init: mock_init.return_value = mock_client with test_util.patch_get_utility() as mock_get_utility: if not quiet_mode: mock_get_utility().notification.side_effect = write_msg - with mock.patch('certbot.main.renewal.OpenSSL') as mock_ssl: + with mock.patch('certbot._internal.main.renewal.OpenSSL') as mock_ssl: mock_latest = mock.MagicMock() mock_latest.get_issuer.return_value = "Fake fake" mock_ssl.crypto.load_certificate.return_value = mock_latest - with mock.patch('certbot.main.renewal.crypto_util') as mock_crypto_util: + with mock.patch('certbot._internal.main.renewal.crypto_util') \ + as mock_crypto_util: mock_crypto_util.notAfter.return_value = expiry_date if not args: args = ['-d', 'isnot.org', '-a', 'standalone', 'certonly'] @@ -1054,7 +1046,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('fullchain.pem' in cert_msg) self.assertTrue('donate' in get_utility().add_message.call_args[0][0]) - @mock.patch('certbot.log.logging.handlers.RotatingFileHandler.doRollover') + @mock.patch('certbot._internal.log.logging.handlers.RotatingFileHandler.doRollover') @mock.patch('certbot.crypto_util.notAfter') def test_certonly_renewal_triggers(self, _, __): # --dry-run should force renewal @@ -1087,7 +1079,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met args = ["renew", "--dry-run", "--reuse-key"] self._test_renewal_common(True, [], args=args, should_renew=True, reuse_key=True) - @mock.patch('certbot.storage.RenewableCert.save_successor') + @mock.patch('certbot._internal.storage.RenewableCert.save_successor') def test_reuse_key_no_dry_run(self, unused_save_successor): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--reuse-key"] @@ -1113,7 +1105,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._test_renewal_common(True, [], args=args, should_renew=True) self.assertEqual(self.mock_sleep.call_count, 0) - @mock.patch('certbot.renewal.should_renew') + @mock.patch('certbot._internal.renewal.should_renew') def test_renew_skips_recent_certs(self, should_renew): should_renew.return_value = False test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') @@ -1123,7 +1115,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('No renewals were attempted.' in stdout.getvalue()) self.assertTrue('The following certs are not due for renewal yet:' in stdout.getvalue()) - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_quiet_renew(self, _): test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run"] @@ -1147,14 +1139,14 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met test_util.make_lineage(self.config.config_dir, 'sample-renewal.conf') args = ["renew", "--dry-run", "--post-hook=no-such-command", "--disable-hook-validation"] - with mock.patch("certbot.hooks.post_hook"): + with mock.patch("certbot._internal.hooks.post_hook"): self._test_renewal_common(True, [], args=args, should_renew=True, error_expected=False) def test_renew_verb_empty_config(self): rd = os.path.join(self.config.config_dir, 'renewal') if not os.path.exists(rd): - os.makedirs(rd) + filesystem.makedirs(rd) with open(os.path.join(rd, 'empty.conf'), 'w'): pass # leave the file empty args = ["renew", "--dry-run", "-tvv"] @@ -1172,14 +1164,14 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def _make_dummy_renewal_config(self): renewer_configs_dir = os.path.join(self.config.config_dir, 'renewal') - os.makedirs(renewer_configs_dir) + filesystem.makedirs(renewer_configs_dir) with open(os.path.join(renewer_configs_dir, 'test.conf'), 'w') as f: f.write("My contents don't matter") def _test_renew_common(self, renewalparams=None, names=None, assert_oc_called=None, **kwargs): self._make_dummy_renewal_config() - with mock.patch('certbot.storage.RenewableCert') as mock_rc: + with mock.patch('certbot._internal.storage.RenewableCert') as mock_rc: mock_lineage = mock.MagicMock() mock_lineage.fullchain = "somepath/fullchain.pem" if renewalparams is not None: @@ -1187,7 +1179,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met if names is not None: mock_lineage.names.return_value = names mock_rc.return_value = mock_lineage - with mock.patch('certbot.main.renew_cert') as mock_renew_cert: + with mock.patch('certbot._internal.main.renew_cert') as mock_renew_cert: kwargs.setdefault('args', ['renew']) self._test_renewal_common(True, None, should_renew=False, **kwargs) @@ -1222,7 +1214,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self._test_renew_common(renewalparams=renewalparams, error_expected=True, names=names, assert_oc_called=False) - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') def test_renew_with_configurator(self, mock_sel): mock_sel.return_value = (mock.MagicMock(), mock.MagicMock()) renewalparams = {'authenticator': 'webroot'} @@ -1245,19 +1237,19 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met def test_renew_reconstitute_error(self): # pylint: disable=protected-access - with mock.patch('certbot.main.renewal._reconstitute') as mock_reconstitute: + with mock.patch('certbot._internal.main.renewal._reconstitute') as mock_reconstitute: mock_reconstitute.side_effect = Exception self._test_renew_common(assert_oc_called=False, error_expected=True) def test_renew_obtain_cert_error(self): self._make_dummy_renewal_config() - with mock.patch('certbot.storage.RenewableCert') as mock_rc: + with mock.patch('certbot._internal.storage.RenewableCert') as mock_rc: mock_lineage = mock.MagicMock() mock_lineage.fullchain = "somewhere/fullchain.pem" mock_rc.return_value = mock_lineage mock_lineage.configuration = { 'renewalparams': {'authenticator': 'webroot'}} - with mock.patch('certbot.main.renew_cert') as mock_renew_cert: + with mock.patch('certbot._internal.main.renew_cert') as mock_renew_cert: mock_renew_cert.side_effect = Exception self._test_renewal_common(True, None, error_expected=True, args=['renew'], should_renew=False) @@ -1277,8 +1269,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue('No hooks were run.' in stdout.getvalue()) @test_util.patch_get_utility() - @mock.patch('certbot.main._find_lineage_for_domains_and_certname') - @mock.patch('certbot.main._init_le_client') + @mock.patch('certbot._internal.main._find_lineage_for_domains_and_certname') + @mock.patch('certbot._internal.main._init_le_client') def test_certonly_reinstall(self, mock_init, mock_renewal, mock_get_utility): mock_renewal.return_value = ('reinstall', mock.MagicMock()) mock_init.return_value = mock_client = mock.MagicMock() @@ -1300,7 +1292,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.config.config_dir, 'live/example.com/fullchain.pem')) mock_client.save_certificate.return_value = cert_path, None, full_path - with mock.patch('certbot.main._init_le_client') as mock_init: + with mock.patch('certbot._internal.main._init_le_client') as mock_init: mock_init.return_value = mock_client with test_util.patch_get_utility() as mock_get_utility: chain_path = os.path.normpath(os.path.join( @@ -1311,7 +1303,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met CSR, cert_path, chain_path, full_path).split() if extra_args: args += extra_args - with mock.patch('certbot.main.crypto_util'): + with mock.patch('certbot._internal.main.crypto_util'): self._call(args) if '--dry-run' in args: @@ -1336,8 +1328,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue( 'dry run' in mock_get_utility().add_message.call_args[0][0]) - @mock.patch('certbot.main._delete_if_appropriate') - @mock.patch('certbot.main.client.acme_client') + @mock.patch('certbot._internal.main._delete_if_appropriate') + @mock.patch('certbot._internal.main.client.acme_client') def test_revoke_with_key(self, mock_acme_client, mock_delete_if_appropriate): mock_delete_if_appropriate.return_value = False @@ -1360,8 +1352,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met ['--cert-path', CERT, '--key-path', KEY, '--server', server, 'revoke']) - @mock.patch('certbot.main._delete_if_appropriate') - @mock.patch('certbot.main._determine_account') + @mock.patch('certbot._internal.main._delete_if_appropriate') + @mock.patch('certbot._internal.main._determine_account') def test_revoke_without_key(self, mock_determine_account, mock_delete_if_appropriate): mock_delete_if_appropriate.return_value = False @@ -1374,14 +1366,9 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met jose.ComparableX509(cert), mock.ANY) - def test_agree_dev_preview_config(self): - with mock.patch('certbot.main.run') as mocked_run: - self._call(['-c', test_util.vector_path('cli.ini')]) - self.assertTrue(mocked_run.called) - - @mock.patch('certbot.log.post_arg_parse_setup') + @mock.patch('certbot._internal.log.post_arg_parse_setup') def test_register(self, _): - with mock.patch('certbot.main.client') as mocked_client: + with mock.patch('certbot._internal.main.client') as mocked_client: acc = mock.MagicMock() acc.id = "imaginary_account" mocked_client.register.return_value = (acc, "worked") @@ -1389,7 +1376,7 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met # TODO: It would be more correct to explicitly check that # _determine_account() gets called in the above case, # but coverage statistics should also show that it did. - with mock.patch('certbot.main.account') as mocked_account: + with mock.patch('certbot._internal.main.account') as mocked_account: mocked_storage = mock.MagicMock() mocked_account.AccountFileStorage.return_value = mocked_storage mocked_storage.find_all.return_value = ["an account"] @@ -1397,8 +1384,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met self.assertTrue("There is an existing account" in x[0]) def test_update_account_no_existing_accounts(self): - # with mock.patch('certbot.main.client') as mocked_client: - with mock.patch('certbot.main.account') as mocked_account: + # with mock.patch('certbot._internal.main.client') as mocked_client: + with mock.patch('certbot._internal.main.account') as mocked_account: mocked_storage = mock.MagicMock() mocked_account.AccountFileStorage.return_value = mocked_storage mocked_storage.find_all.return_value = [] @@ -1407,42 +1394,15 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met "user@example.org"]) self.assertTrue("Could not find an existing account" in x[0]) - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the following test - def test_update_registration_no_existing_accounts_deprecated(self): - # with mock.patch('certbot.main.client') as mocked_client: - with mock.patch('certbot.main.account') as mocked_account: - mocked_storage = mock.MagicMock() - mocked_account.AccountFileStorage.return_value = mocked_storage - mocked_storage.find_all.return_value = [] - x = self._call_no_clientmock( - ["register", "--update-registration", "--email", - "user@example.org"]) - self.assertTrue("Could not find an existing account" in x[0]) - - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the following test - def test_update_registration_unsafely_deprecated(self): - # This test will become obsolete when register --update-registration - # supports removing an e-mail address from the account - with mock.patch('certbot.main.account') as mocked_account: - mocked_storage = mock.MagicMock() - mocked_account.AccountFileStorage.return_value = mocked_storage - mocked_storage.find_all.return_value = ["an account"] - x = self._call_no_clientmock( - "register --update-registration " - "--register-unsafely-without-email".split()) - self.assertTrue("--register-unsafely-without-email" in x[0]) - - @mock.patch('certbot.main.display_ops.get_email') + @mock.patch('certbot._internal.main.display_ops.get_email') @test_util.patch_get_utility() def test_update_account_with_email(self, mock_utility, mock_email): email = "user@example.com" mock_email.return_value = email - with mock.patch('certbot.eff.handle_subscription') as mock_handle: - with mock.patch('certbot.main._determine_account') as mocked_det: - with mock.patch('certbot.main.account') as mocked_account: - with mock.patch('certbot.main.client') as mocked_client: + with mock.patch('certbot._internal.eff.handle_subscription') as mock_handle: + with mock.patch('certbot._internal.main._determine_account') as mocked_det: + with mock.patch('certbot._internal.main.account') as mocked_account: + with mock.patch('certbot._internal.main.client') as mocked_client: mocked_storage = mock.MagicMock() mocked_account.AccountFileStorage.return_value = mocked_storage mocked_storage.find_all.return_value = ["an account"] @@ -1464,44 +1424,8 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met email in mock_utility().add_message.call_args[0][0]) self.assertTrue(mock_handle.called) - # TODO: When `certbot register --update-registration` is fully deprecated, - # delete the following test - @mock.patch('certbot.main.display_ops.get_email') - @test_util.patch_get_utility() - def test_update_registration_with_email_deprecated(self, mock_utility, mock_email): - email = "user@example.com" - mock_email.return_value = email - with mock.patch('certbot.eff.handle_subscription') as mock_handle: - with mock.patch('certbot.main._determine_account') as mocked_det: - with mock.patch('certbot.main.account') as mocked_account: - with mock.patch('certbot.main.client') as mocked_client: - mocked_storage = mock.MagicMock() - mocked_account.AccountFileStorage.return_value = mocked_storage - mocked_storage.find_all.return_value = ["an account"] - mock_acc = mock.MagicMock() - mock_regr = mock_acc.regr - mocked_det.return_value = (mock_acc, "foo") - cb_client = mock.MagicMock() - mocked_client.Client.return_value = cb_client - x = self._call_no_clientmock( - ["register", "--update-registration"]) - # When registration change succeeds, the return value - # of register() is None - self.assertTrue(x[0] is None) - # and we got supposedly did update the registration from - # the server - reg_arg = cb_client.acme.update_registration.call_args[0][0] - # Test the return value of .update() was used because - # the regr is immutable. - self.assertEqual(reg_arg, mock_regr.update()) - # and we saved the updated registration on disk - self.assertTrue(mocked_storage.save_regr.called) - self.assertTrue( - email in mock_utility().add_message.call_args[0][0]) - self.assertTrue(mock_handle.called) - - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') - @mock.patch('certbot.updater._run_updaters') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.updater._run_updaters') def test_plugin_selection_error(self, mock_run, mock_choose): mock_choose.side_effect = errors.PluginSelectionError self.assertRaises(errors.PluginSelectionError, main.renew_cert, @@ -1517,9 +1441,9 @@ class MainTest(test_util.ConfigTestCase): # pylint: disable=too-many-public-met class UnregisterTest(unittest.TestCase): def setUp(self): self.patchers = { - '_determine_account': mock.patch('certbot.main._determine_account'), - 'account': mock.patch('certbot.main.account'), - 'client': mock.patch('certbot.main.client'), + '_determine_account': mock.patch('certbot._internal.main._determine_account'), + 'account': mock.patch('certbot._internal.main.account'), + 'client': mock.patch('certbot._internal.main.client'), 'get_utility': test_util.patch_get_utility()} self.mocks = dict((k, v.start()) for k, v in self.patchers.items()) @@ -1577,15 +1501,15 @@ class UnregisterTest(unittest.TestCase): class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): - """Tests for certbot.main.make_or_verify_needed_dirs.""" + """Tests for certbot._internal.main.make_or_verify_needed_dirs.""" - @mock.patch("certbot.main.util") + @mock.patch("certbot._internal.main.util") def test_it(self, mock_util): main.make_or_verify_needed_dirs(self.config) for core_dir in (self.config.config_dir, self.config.work_dir,): mock_util.set_up_core_dir.assert_any_call( core_dir, constants.CONFIG_DIRS_MODE, - misc.os_geteuid(), self.config.strict_permissions + self.config.strict_permissions ) hook_dirs = (self.config.renewal_pre_hooks_dir, @@ -1594,12 +1518,11 @@ class MakeOrVerifyNeededDirs(test_util.ConfigTestCase): for hook_dir in hook_dirs: # default mode of 755 is used mock_util.make_or_verify_dir.assert_any_call( - hook_dir, uid=misc.os_geteuid(), - strict=self.config.strict_permissions) + hook_dir, strict=self.config.strict_permissions) class EnhanceTest(test_util.ConfigTestCase): - """Tests for certbot.main.enhance.""" + """Tests for certbot._internal.main.enhance.""" def setUp(self): super(EnhanceTest, self).setUp() @@ -1615,53 +1538,53 @@ class EnhanceTest(test_util.ConfigTestCase): config = configuration.NamespaceConfig( cli.prepare_and_parse_args(plugins, args)) - with mock.patch('certbot.cert_manager.get_certnames') as mock_certs: + with mock.patch('certbot._internal.cert_manager.get_certnames') as mock_certs: mock_certs.return_value = ['example.com'] - with mock.patch('certbot.cert_manager.domains_for_certname') as mock_dom: + with mock.patch('certbot._internal.cert_manager.domains_for_certname') as mock_dom: mock_dom.return_value = ['example.com'] - with mock.patch('certbot.main._init_le_client') as mock_init: + with mock.patch('certbot._internal.main._init_le_client') as mock_init: mock_client = mock.MagicMock() mock_client.config = config mock_init.return_value = mock_client main.enhance(config, plugins) return mock_client # returns the client - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main._find_domains_or_certname') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main._find_domains_or_certname') def test_selection_question(self, mock_find, mock_choose, mock_lineage, _rec): mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent") mock_choose.return_value = ['example.com'] mock_find.return_value = (None, None) - with mock.patch('certbot.main.plug_sel.pick_installer') as mock_pick: + with mock.patch('certbot._internal.main.plug_sel.pick_installer') as mock_pick: self._call(['enhance', '--redirect']) self.assertTrue(mock_pick.called) # Check that the message includes "enhancements" self.assertTrue("enhancements" in mock_pick.call_args[0][3]) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main._find_domains_or_certname') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main._find_domains_or_certname') def test_selection_auth_warning(self, mock_find, mock_choose, mock_lineage, _rec): mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent") mock_choose.return_value = ["example.com"] mock_find.return_value = (None, None) - with mock.patch('certbot.main.plug_sel.pick_installer'): - with mock.patch('certbot.main.plug_sel.logger.warning') as mock_log: + with mock.patch('certbot._internal.main.plug_sel.pick_installer'): + with mock.patch('certbot._internal.main.plug_sel.logger.warning') as mock_log: mock_client = self._call(['enhance', '-a', 'webroot', '--redirect']) self.assertTrue(mock_log.called) self.assertTrue("make sense" in mock_log.call_args[0][0]) self.assertTrue(mock_client.enhance_config.called) - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') def test_enhance_config_call(self, _rec, mock_choose, mock_lineage): mock_lineage.return_value = mock.MagicMock(chain_path="/tmp/nonexistent") mock_choose.return_value = ["example.com"] - with mock.patch('certbot.main.plug_sel.pick_installer'): + with mock.patch('certbot._internal.main.plug_sel.pick_installer'): mock_client = self._call(['enhance', '--redirect', '--hsts']) req_enh = ["redirect", "hsts"] not_req_enh = ["uir"] @@ -1673,24 +1596,24 @@ class EnhanceTest(test_util.ConfigTestCase): self.assertTrue( "example.com" in mock_client.enhance_config.call_args[0][0]) - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') def test_enhance_noninteractive(self, _rec, mock_choose, mock_lineage): mock_lineage.return_value = mock.MagicMock( chain_path="/tmp/nonexistent") mock_choose.return_value = ["example.com"] - with mock.patch('certbot.main.plug_sel.pick_installer'): + with mock.patch('certbot._internal.main.plug_sel.pick_installer'): mock_client = self._call(['enhance', '--redirect', '--hsts', '--non-interactive']) self.assertTrue(mock_client.enhance_config.called) self.assertFalse(mock_choose.called) - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') def test_user_abort_domains(self, _rec, mock_choose): mock_choose.return_value = [] - with mock.patch('certbot.main.plug_sel.pick_installer'): + with mock.patch('certbot._internal.main.plug_sel.pick_installer'): self.assertRaises(errors.Error, self._call, ['enhance', '--redirect', '--hsts']) @@ -1699,9 +1622,9 @@ class EnhanceTest(test_util.ConfigTestCase): self.assertRaises(errors.MisconfigurationError, self._call, ['enhance', '-a', 'null']) - @mock.patch('certbot.main.plug_sel.choose_configurator_plugins') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.choose_configurator_plugins') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') def test_plugin_selection_error(self, _rec, mock_choose, mock_pick): mock_choose.return_value = ["example.com"] mock_pick.return_value = (None, None) @@ -1709,10 +1632,10 @@ class EnhanceTest(test_util.ConfigTestCase): mock_client = self._call(['enhance', '--hsts']) self.assertFalse(mock_client.enhance_config.called) - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.pick_installer') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @test_util.patch_get_utility() def test_enhancement_enable(self, _, _rec, mock_inst, mock_choose, mock_lineage): mock_inst.return_value = self.mockinstaller @@ -1723,10 +1646,10 @@ class EnhanceTest(test_util.ConfigTestCase): self.assertEqual(self.mockinstaller.enable_autohsts.call_args[0][1], ["example.com", "another.tld"]) - @mock.patch('certbot.cert_manager.lineage_for_certname') - @mock.patch('certbot.main.display_ops.choose_values') - @mock.patch('certbot.main.plug_sel.pick_installer') - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.cert_manager.lineage_for_certname') + @mock.patch('certbot._internal.main.display_ops.choose_values') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') @test_util.patch_get_utility() def test_enhancement_enable_not_supported(self, _, _rec, mock_inst, mock_choose, mock_lineage): mock_inst.return_value = null.Installer(self.config, "null") @@ -1743,14 +1666,14 @@ class EnhanceTest(test_util.ConfigTestCase): class InstallTest(test_util.ConfigTestCase): - """Tests for certbot.main.install.""" + """Tests for certbot._internal.main.install.""" def setUp(self): super(InstallTest, self).setUp() self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_install_enhancement_not_supported(self, mock_inst, _rec): mock_inst.return_value = null.Installer(self.config, "null") plugins = disco.PluginsRegistry.find_all() @@ -1760,8 +1683,8 @@ class InstallTest(test_util.ConfigTestCase): main.install, self.config, plugins) - @mock.patch('certbot.main.plug_sel.record_chosen_plugins') - @mock.patch('certbot.main.plug_sel.pick_installer') + @mock.patch('certbot._internal.main.plug_sel.record_chosen_plugins') + @mock.patch('certbot._internal.main.plug_sel.pick_installer') def test_install_enhancement_no_certname(self, mock_inst, _rec): mock_inst.return_value = self.mockinstaller plugins = disco.PluginsRegistry.find_all() diff --git a/certbot/tests/notify_test.py b/certbot/tests/notify_test.py index d2af5b001..d6f7d2239 100644 --- a/certbot/tests/notify_test.py +++ b/certbot/tests/notify_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.notify.""" +"""Tests for certbot._internal.notify.""" import socket import unittest @@ -8,9 +8,9 @@ import mock class NotifyTests(unittest.TestCase): """Tests for the notifier.""" - @mock.patch("certbot.notify.smtplib.LMTP") + @mock.patch("certbot._internal.notify.smtplib.LMTP") def test_smtp_success(self, mock_lmtp): - from certbot.notify import notify + from certbot._internal.notify import notify lmtp_obj = mock.MagicMock() mock_lmtp.return_value = lmtp_obj self.assertTrue(notify("Goose", "auntrhody@example.com", @@ -18,10 +18,10 @@ class NotifyTests(unittest.TestCase): self.assertEqual(lmtp_obj.connect.call_count, 1) self.assertEqual(lmtp_obj.sendmail.call_count, 1) - @mock.patch("certbot.notify.smtplib.LMTP") - @mock.patch("certbot.notify.subprocess.Popen") + @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.notify import notify + from certbot._internal.notify import notify lmtp_obj = mock.MagicMock() mock_lmtp.return_value = lmtp_obj lmtp_obj.sendmail.side_effect = socket.error(17) @@ -32,10 +32,10 @@ class NotifyTests(unittest.TestCase): self.assertEqual(lmtp_obj.sendmail.call_count, 1) self.assertEqual(proc.communicate.call_count, 1) - @mock.patch("certbot.notify.smtplib.LMTP") - @mock.patch("certbot.notify.subprocess.Popen") + @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.notify import notify + from certbot._internal.notify import notify lmtp_obj = mock.MagicMock() mock_lmtp.return_value = lmtp_obj lmtp_obj.sendmail.side_effect = socket.error(17) diff --git a/certbot/tests/ocsp_test.py b/certbot/tests/ocsp_test.py index e8c1b9d03..6e4ab52b8 100644 --- a/certbot/tests/ocsp_test.py +++ b/certbot/tests/ocsp_test.py @@ -1,13 +1,21 @@ """Tests for ocsp.py""" # pylint: disable=protected-access import contextlib +from datetime import datetime +from datetime import timedelta import unittest -from datetime import datetime, timedelta +from cryptography import x509 +from cryptography.exceptions import InvalidSignature +from cryptography.exceptions import UnsupportedAlgorithm from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes # type: ignore -from cryptography.exceptions import UnsupportedAlgorithm, InvalidSignature -from cryptography import x509 +import mock +import pytz + +from certbot import errors +from certbot.tests import util as test_util + try: # Only cryptography>=2.5 has ocsp module # and signature_hash_algorithm attribute in OCSPResponse class @@ -15,10 +23,7 @@ try: getattr(ocsp_lib.OCSPResponse, 'signature_hash_algorithm') except (ImportError, AttributeError): # pragma: no cover ocsp_lib = None # type: ignore -import mock -from certbot import errors -from certbot.tests import util as test_util out = """Missing = in header key=value ocsp: Use -help for summary. @@ -31,8 +36,8 @@ class OCSPTestOpenSSL(unittest.TestCase): """ def setUp(self): - from certbot import ocsp - with mock.patch('certbot.ocsp.Popen') as mock_popen: + from certbot._internal import ocsp + with mock.patch('certbot._internal.ocsp.Popen') as mock_popen: with mock.patch('certbot.util.exe_exists') as mock_exists: mock_communicate = mock.MagicMock() mock_communicate.communicate.return_value = (None, out) @@ -43,8 +48,8 @@ class OCSPTestOpenSSL(unittest.TestCase): def tearDown(self): pass - @mock.patch('certbot.ocsp.logger.info') - @mock.patch('certbot.ocsp.Popen') + @mock.patch('certbot._internal.ocsp.logger.info') + @mock.patch('certbot._internal.ocsp.Popen') @mock.patch('certbot.util.exe_exists') def test_init(self, mock_exists, mock_popen, mock_log): mock_communicate = mock.MagicMock() @@ -52,7 +57,7 @@ class OCSPTestOpenSSL(unittest.TestCase): mock_popen.return_value = mock_communicate mock_exists.return_value = True - from certbot import ocsp + from certbot._internal import ocsp checker = ocsp.RevocationChecker(enforce_openssl_binary_usage=True) self.assertEqual(mock_popen.call_count, 1) self.assertEqual(checker.host_args("x"), ["Host=x"]) @@ -69,37 +74,50 @@ class OCSPTestOpenSSL(unittest.TestCase): self.assertEqual(mock_log.call_count, 1) self.assertEqual(checker.broken, True) - @mock.patch('certbot.ocsp._determine_ocsp_server') + @mock.patch('certbot._internal.ocsp._determine_ocsp_server') @mock.patch('certbot.util.run_script') def test_ocsp_revoked(self, mock_run, mock_determine): + now = pytz.UTC.fromutc(datetime.utcnow()) + cert_obj = mock.MagicMock() + cert_obj.cert = "x" + cert_obj.chain = "y" + cert_obj.target_expiry = now + timedelta(hours=2) + self.checker.broken = True mock_determine.return_value = ("", "") - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.checker.broken = False mock_run.return_value = tuple(openssl_happy[1:]) - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.assertEqual(mock_run.call_count, 0) mock_determine.return_value = ("http://x.co", "x.co") - self.assertEqual(self.checker.ocsp_revoked("blah.pem", "chain.pem"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) mock_run.side_effect = errors.SubprocessError("Unable to load certificate launcher") - self.assertEqual(self.checker.ocsp_revoked("x", "y"), False) + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) self.assertEqual(mock_run.call_count, 2) + # cert expired + cert_obj.target_expiry = now + mock_determine.return_value = ("", "") + count_before = mock_determine.call_count + self.assertEqual(self.checker.ocsp_revoked(cert_obj), False) + self.assertEqual(mock_determine.call_count, count_before) + def test_determine_ocsp_server(self): cert_path = test_util.vector_path('ocsp_certificate.pem') - from certbot import ocsp + from certbot._internal import ocsp result = ocsp._determine_ocsp_server(cert_path) self.assertEqual(('http://ocsp.test4.buypass.com', 'ocsp.test4.buypass.com'), result) - @mock.patch('certbot.ocsp.logger') + @mock.patch('certbot._internal.ocsp.logger') @mock.patch('certbot.util.run_script') def test_translate_ocsp(self, mock_run, mock_log): # pylint: disable=protected-access mock_run.return_value = openssl_confused - from certbot import ocsp + from certbot._internal import ocsp self.assertEqual(ocsp._translate_ocsp_query(*openssl_happy), False) self.assertEqual(ocsp._translate_ocsp_query(*openssl_confused), False) self.assertEqual(mock_log.debug.call_count, 1) @@ -127,22 +145,27 @@ class OSCPTestCryptography(unittest.TestCase): """ def setUp(self): - from certbot import ocsp + from certbot._internal import ocsp self.checker = ocsp.RevocationChecker() self.cert_path = test_util.vector_path('ocsp_certificate.pem') self.chain_path = test_util.vector_path('ocsp_issuer_certificate.pem') + self.cert_obj = mock.MagicMock() + self.cert_obj.cert = self.cert_path + self.cert_obj.chain = self.chain_path + now = pytz.UTC.fromutc(datetime.utcnow()) + self.cert_obj.target_expiry = now + timedelta(hours=2) - @mock.patch('certbot.ocsp._determine_ocsp_server') - @mock.patch('certbot.ocsp._check_ocsp_cryptography') + @mock.patch('certbot._internal.ocsp._determine_ocsp_server') + @mock.patch('certbot._internal.ocsp._check_ocsp_cryptography') def test_ensure_cryptography_toggled(self, mock_revoke, mock_determine): mock_determine.return_value = ('http://example.com', 'example.com') - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) mock_revoke.assert_called_once_with(self.cert_path, self.chain_path, 'http://example.com') def test_revoke(self): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertTrue(revoked) def test_responder_is_issuer(self): @@ -152,7 +175,7 @@ class OSCPTestCryptography(unittest.TestCase): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: mocks['mock_response'].return_value.responder_name = issuer.subject - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) # Here responder and issuer are the same. So only the signature of the OCSP # response is checked (using the issuer/responder public key). self.assertEqual(mocks['mock_check'].call_count, 1) @@ -167,7 +190,7 @@ class OSCPTestCryptography(unittest.TestCase): with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: - self.checker.ocsp_revoked(self.cert_path, self.chain_path) + self.checker.ocsp_revoked(self.cert_obj) # Here responder and issuer are not the same. Two signatures will be checked then, # first to verify the responder cert (using the issuer public key), second to # to verify the OCSP response itself (using the responder public key). @@ -181,17 +204,17 @@ class OSCPTestCryptography(unittest.TestCase): # Server return an invalid HTTP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, http_status_code=400): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response in invalid with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.UNAUTHORIZED): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response is valid, but certificate status is unknown with _ocsp_mock(ocsp_lib.OCSPCertStatus.UNKNOWN, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # The OCSP response says that the certificate is revoked, but certificate @@ -200,32 +223,32 @@ class OSCPTestCryptography(unittest.TestCase): with mock.patch('cryptography.x509.Extensions.get_extension_for_class', side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OCSP response uses an unsupported signature. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=UnsupportedAlgorithm('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # OSCP signature response is invalid. with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=InvalidSignature('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # Assertion error on OCSP response validity with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL, check_signature_side_effect=AssertionError('foo')): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # No responder cert in OCSP response with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL) as mocks: mocks['mock_response'].return_value.certificates = [] - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) # Responder cert is not signed by certificate issuer @@ -234,30 +257,31 @@ class OSCPTestCryptography(unittest.TestCase): cert = mocks['mock_response'].return_value.certificates[0] mocks['mock_response'].return_value.certificates[0] = mock.Mock( issuer='fake', subject=cert.subject) - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) with _ocsp_mock(ocsp_lib.OCSPCertStatus.REVOKED, ocsp_lib.OCSPResponseStatus.SUCCESSFUL): # This mock is necessary to avoid the first call contained in _determine_ocsp_server # of the method cryptography.x509.Extensions.get_extension_for_class. - with mock.patch('certbot.ocsp._determine_ocsp_server') as mock_server: + with mock.patch('certbot._internal.ocsp._determine_ocsp_server') as mock_server: mock_server.return_value = ('https://example.com', 'example.com') with mock.patch('cryptography.x509.Extensions.get_extension_for_class', side_effect=x509.ExtensionNotFound( 'Not found', x509.AuthorityInformationAccessOID.OCSP)): - revoked = self.checker.ocsp_revoked(self.cert_path, self.chain_path) + revoked = self.checker.ocsp_revoked(self.cert_obj) self.assertFalse(revoked) @contextlib.contextmanager def _ocsp_mock(certificate_status, response_status, http_status_code=200, check_signature_side_effect=None): - with mock.patch('certbot.ocsp.ocsp.load_der_ocsp_response') as mock_response: + with mock.patch('certbot._internal.ocsp.ocsp.load_der_ocsp_response') as mock_response: mock_response.return_value = _construct_mock_ocsp_response( certificate_status, response_status) - with mock.patch('certbot.ocsp.requests.post') as mock_post: + with mock.patch('certbot._internal.ocsp.requests.post') as mock_post: mock_post.return_value = mock.Mock(status_code=http_status_code) - with mock.patch('certbot.ocsp.crypto_util.verify_signed_payload') as mock_check: + with mock.patch('certbot._internal.ocsp.crypto_util.verify_signed_payload') \ + as mock_check: if check_signature_side_effect: mock_check.side_effect = check_signature_side_effect yield { diff --git a/certbot/tests/plugins/__init__.py b/certbot/tests/plugins/__init__.py new file mode 100644 index 000000000..3cfcb5008 --- /dev/null +++ b/certbot/tests/plugins/__init__.py @@ -0,0 +1 @@ +"""Certbot Plugins Tests""" diff --git a/certbot/plugins/common_test.py b/certbot/tests/plugins/common_test.py similarity index 75% rename from certbot/plugins/common_test.py rename to certbot/tests/plugins/common_test.py index bce8f833a..915a3ae6c 100644 --- a/certbot/plugins/common_test.py +++ b/certbot/tests/plugins/common_test.py @@ -1,33 +1,25 @@ """Tests for certbot.plugins.common.""" import functools import shutil -import tempfile import unittest -import OpenSSL import josepy as jose import mock from acme import challenges - from certbot import achallenges from certbot import crypto_util from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util AUTH_KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) -ACHALLS = [ - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01(token=b'token1'), "pending"), - domain="encryption-example.demo", account_key=AUTH_KEY), - achallenges.KeyAuthorizationAnnotatedChallenge( - challb=acme_util.chall_to_challb( - challenges.TLSSNI01(token=b'token2'), "pending"), - domain="certbot.demo", account_key=AUTH_KEY), -] +ACHALL = achallenges.KeyAuthorizationAnnotatedChallenge( + challb=acme_util.chall_to_challb(challenges.HTTP01(token=b'token1'), + "pending"), + domain="encryption-example.demo", account_key=AUTH_KEY) class NamespaceFunctionsTest(unittest.TestCase): """Tests for certbot.plugins.common.*_namespace functions.""" @@ -94,12 +86,11 @@ class InstallerTest(test_util.ConfigTestCase): def setUp(self): super(InstallerTest, self).setUp() - os.mkdir(self.config.config_dir) + filesystem.mkdir(self.config.config_dir) from certbot.plugins.common import Installer - with mock.patch("certbot.plugins.common.reverter.Reverter"): - self.installer = Installer(config=self.config, - name="Installer") + self.installer = Installer(config=self.config, + name="Installer") self.reverter = self.installer.reverter def test_add_to_real_checkpoint(self): @@ -121,12 +112,11 @@ class InstallerTest(test_util.ConfigTestCase): temporary=temporary) if temporary: - reverter_func = self.reverter.add_to_temp_checkpoint + reverter_func_name = "add_to_temp_checkpoint" else: - reverter_func = self.reverter.add_to_checkpoint + reverter_func_name = "add_to_checkpoint" - self._test_adapted_method( - installer_func, reverter_func, files, save_notes) + self._test_adapted_method(installer_func, reverter_func_name, files, save_notes) def test_finalize_checkpoint(self): self._test_wrapped_method("finalize_checkpoint", "foo") @@ -140,9 +130,6 @@ class InstallerTest(test_util.ConfigTestCase): def test_rollback_checkpoints(self): self._test_wrapped_method("rollback_checkpoints", 42) - def test_view_config_changes(self): - self._test_wrapped_method("view_config_changes") - def _test_wrapped_method(self, name, *args, **kwargs): """Test a wrapped reverter method. @@ -152,46 +139,45 @@ class InstallerTest(test_util.ConfigTestCase): """ installer_func = getattr(self.installer, name) - reverter_func = getattr(self.reverter, name) - self._test_adapted_method( - installer_func, reverter_func, *args, **kwargs) + self._test_adapted_method(installer_func, name, *args, **kwargs) def _test_adapted_method(self, installer_func, - reverter_func, *passed_args, **passed_kwargs): + reverter_func_name, *passed_args, **passed_kwargs): """Test an adapted reverter method :param callable installer_func: installer method to test - :param mock.MagicMock reverter_func: mocked adapated - reverter method + :param str reverter_func_name: name of the method on the + reverter that should be called :param tuple passed_args: positional arguments passed from installer method to the reverter method :param dict passed_kargs: keyword arguments passed from installer method to the reverter method """ - installer_func(*passed_args, **passed_kwargs) - reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) - reverter_func.side_effect = errors.ReverterError - self.assertRaises( - errors.PluginError, installer_func, *passed_args, **passed_kwargs) + with mock.patch.object(self.reverter, reverter_func_name) as reverter_func: + installer_func(*passed_args, **passed_kwargs) + reverter_func.assert_called_once_with(*passed_args, **passed_kwargs) + reverter_func.side_effect = errors.ReverterError + self.assertRaises( + errors.PluginError, installer_func, *passed_args, **passed_kwargs) def test_install_ssl_dhparams(self): self.installer.install_ssl_dhparams() self.assertTrue(os.path.isfile(self.installer.ssl_dhparams)) def _current_ssl_dhparams_hash(self): - from certbot.constants import SSL_DHPARAMS_SRC + from certbot._internal.constants import SSL_DHPARAMS_SRC return crypto_util.sha256sum(SSL_DHPARAMS_SRC) def test_current_file_hash_in_all_hashes(self): - from certbot.constants import ALL_SSL_DHPARAMS_HASHES + from certbot._internal.constants import ALL_SSL_DHPARAMS_HASHES self.assertTrue(self._current_ssl_dhparams_hash() in ALL_SSL_DHPARAMS_HASHES, "Constants.ALL_SSL_DHPARAMS_HASHES must be appended" " with the sha256 hash of self.config.ssl_dhparams when it is updated.") class AddrTest(unittest.TestCase): - """Tests for certbot.client.plugins.common.Addr.""" + """Tests for certbot._internal.client.plugins.common.Addr.""" def setUp(self): from certbot.plugins.common import Addr @@ -281,7 +267,7 @@ class ChallengePerformerTest(unittest.TestCase): self.performer = ChallengePerformer(configurator) def test_add_chall(self): - self.performer.add_chall(ACHALLS[0], 0) + self.performer.add_chall(ACHALL, 0) self.assertEqual(1, len(self.performer.achalls)) self.assertEqual([0], self.performer.indices) @@ -289,58 +275,6 @@ class ChallengePerformerTest(unittest.TestCase): self.assertRaises(NotImplementedError, self.performer.perform) -class TLSSNI01Test(unittest.TestCase): - """Tests for certbot.plugins.common.TLSSNI01.""" - - def setUp(self): - self.tempdir = tempfile.mkdtemp() - configurator = mock.MagicMock() - configurator.config.config_dir = os.path.join(self.tempdir, "config") - configurator.config.work_dir = os.path.join(self.tempdir, "work") - - from certbot.plugins.common import TLSSNI01 - self.sni = TLSSNI01(configurator=configurator) - - def tearDown(self): - shutil.rmtree(self.tempdir) - - def test_setup_challenge_cert(self): - # This is a helper function that can be used for handling - # open context managers more elegantly. It avoids dealing with - # __enter__ and __exit__ calls. - # http://www.voidspace.org.uk/python/mock/helpers.html#mock.mock_open - mock_open, mock_safe_open = mock.mock_open(), mock.mock_open() - - response = challenges.TLSSNI01Response() - achall = mock.MagicMock() - achall.chall.encode.return_value = "token" - key = test_util.load_pyopenssl_private_key("rsa512_key.pem") - achall.response_and_validation.return_value = ( - response, (test_util.load_cert("cert_512.pem"), key)) - - with mock.patch("certbot.plugins.common.open", - mock_open, create=True): - with mock.patch("certbot.plugins.common.util.safe_open", - mock_safe_open): - # pylint: disable=protected-access - self.assertEqual(response, self.sni._setup_challenge_cert( - achall, "randomS1")) - - # pylint: disable=no-member - mock_open.assert_called_once_with(self.sni.get_cert_path(achall), "wb") - mock_open.return_value.write.assert_called_once_with( - test_util.load_vector("cert_512.pem")) - mock_safe_open.assert_called_once_with( - self.sni.get_key_path(achall), "wb", chmod=0o400) - mock_safe_open.return_value.write.assert_called_once_with( - OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, key)) - - def test_get_z_domain(self): - achall = ACHALLS[0] - self.assertEqual(self.sni.get_z_domain(achall), - achall.response(achall.account_key).z_domain.decode("utf-8")) - - class InstallVersionControlledFileTest(test_util.TempDirTestCase): """Tests for certbot.plugins.common.install_version_controlled_file.""" diff --git a/certbot/plugins/disco_test.py b/certbot/tests/plugins/disco_test.py similarity index 93% rename from certbot/plugins/disco_test.py rename to certbot/tests/plugins/disco_test.py index 720b90b16..6d3c7d97e 100644 --- a/certbot/plugins/disco_test.py +++ b/certbot/tests/plugins/disco_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.disco.""" +"""Tests for certbot._internal.plugins.disco.""" import functools import string import unittest @@ -11,22 +11,21 @@ import zope.interface from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module from certbot import errors from certbot import interfaces - -from certbot.plugins import standalone -from certbot.plugins import webroot +from certbot._internal.plugins import standalone +from certbot._internal.plugins import webroot EP_SA = pkg_resources.EntryPoint( - "sa", "certbot.plugins.standalone", + "sa", "certbot._internal.plugins.standalone", attrs=("Authenticator",), dist=mock.MagicMock(key="certbot")) EP_WR = pkg_resources.EntryPoint( - "wr", "certbot.plugins.webroot", + "wr", "certbot._internal.plugins.webroot", attrs=("Authenticator",), dist=mock.MagicMock(key="certbot")) class PluginEntryPointTest(unittest.TestCase): - """Tests for certbot.plugins.disco.PluginEntryPoint.""" + """Tests for certbot._internal.plugins.disco.PluginEntryPoint.""" def setUp(self): self.ep1 = pkg_resources.EntryPoint( @@ -40,11 +39,11 @@ class PluginEntryPointTest(unittest.TestCase): self.ep3 = pkg_resources.EntryPoint( "ep3", "a.ep3", dist=mock.MagicMock(key="p3")) - from certbot.plugins.disco import PluginEntryPoint + from certbot._internal.plugins.disco import PluginEntryPoint self.plugin_ep = PluginEntryPoint(EP_SA) def test_entry_point_to_plugin_name(self): - from certbot.plugins.disco import PluginEntryPoint + from certbot._internal.plugins.disco import PluginEntryPoint names = { self.ep1: "p1:ep1", @@ -119,7 +118,7 @@ class PluginEntryPointTest(unittest.TestCase): self.plugin_ep._initialized = plugin = mock.MagicMock() exceptions = zope.interface.exceptions - with mock.patch("certbot.plugins." + with mock.patch("certbot._internal.plugins." "disco.zope.interface") as mock_zope: mock_zope.exceptions = exceptions @@ -183,11 +182,11 @@ class PluginEntryPointTest(unittest.TestCase): class PluginsRegistryTest(unittest.TestCase): - """Tests for certbot.plugins.disco.PluginsRegistry.""" + """Tests for certbot._internal.plugins.disco.PluginsRegistry.""" @classmethod def _create_new_registry(cls, plugins): - from certbot.plugins.disco import PluginsRegistry + from certbot._internal.plugins.disco import PluginsRegistry return PluginsRegistry(plugins) def setUp(self): @@ -198,8 +197,8 @@ class PluginsRegistryTest(unittest.TestCase): self.reg = self._create_new_registry(self.plugins) def test_find_all(self): - from certbot.plugins.disco import PluginsRegistry - with mock.patch("certbot.plugins.disco.pkg_resources") as mock_pkg: + from certbot._internal.plugins.disco import PluginsRegistry + with mock.patch("certbot._internal.plugins.disco.pkg_resources") as mock_pkg: mock_pkg.iter_entry_points.side_effect = [iter([EP_SA]), iter([EP_WR])] plugins = PluginsRegistry.find_all() diff --git a/certbot/plugins/dns_common_lexicon_test.py b/certbot/tests/plugins/dns_common_lexicon_test.py similarity index 100% rename from certbot/plugins/dns_common_lexicon_test.py rename to certbot/tests/plugins/dns_common_lexicon_test.py diff --git a/certbot/plugins/dns_common_test.py b/certbot/tests/plugins/dns_common_test.py similarity index 93% rename from certbot/plugins/dns_common_test.py rename to certbot/tests/plugins/dns_common_test.py index 6741ff8e5..eba3c89d6 100644 --- a/certbot/plugins/dns_common_test.py +++ b/certbot/tests/plugins/dns_common_test.py @@ -7,14 +7,15 @@ import unittest import mock from certbot import errors +from certbot import util from certbot.compat import os from certbot.display import util as display_util from certbot.plugins import dns_common from certbot.plugins import dns_test_common -from certbot.tests import util +from certbot.tests import util as test_util -class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): +class DNSAuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthenticatorTest): # pylint: disable=protected-access class _FakeDNSAuthenticator(dns_common.DNSAuthenticator): @@ -50,7 +51,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._cleanup.assert_called_once_with(dns_test_common.DOMAIN, mock.ANY, mock.ANY) - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt(self, mock_get_utility): mock_display = mock_get_utility() mock_display.input.side_effect = ((display_util.OK, "",), @@ -59,14 +60,14 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._configure("other_key", "") self.assertEqual(self.auth.config.fake_other_key, "value") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.input.side_effect = ((display_util.CANCEL, "c",),) self.assertRaises(errors.PluginError, self.auth._configure, "other_key", "") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_file(self, mock_get_utility): path = os.path.join(self.tempdir, 'file.ini') open(path, "wb").close() @@ -80,7 +81,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.auth._configure_file("file_path", "") self.assertEqual(self.auth.config.fake_file_path, path) - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_file_canceled(self, mock_get_utility): mock_display = mock_get_utility() mock_display.directory_select.side_effect = ((display_util.CANCEL, "c",),) @@ -96,7 +97,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.assertEqual(credentials.conf("test"), "value") - @util.patch_get_utility() + @test_util.patch_get_utility() def test_prompt_credentials(self, mock_get_utility): bad_path = os.path.join(self.tempdir, 'bad-file.ini') dns_test_common.write({"fake_other": "other_value"}, bad_path) @@ -116,7 +117,7 @@ class DNSAuthenticatorTest(util.TempDirTestCase, dns_test_common.BaseAuthenticat self.assertEqual(credentials.conf("test"), "value") -class CredentialsConfigurationTest(util.TempDirTestCase): +class CredentialsConfigurationTest(test_util.TempDirTestCase): class _MockLoggingHandler(logging.Handler): messages = None @@ -150,14 +151,14 @@ class CredentialsConfigurationTest(util.TempDirTestCase): dns_common.logger.addHandler(log) path = os.path.join(self.tempdir, 'too-permissive-file.ini') - open(path, "wb").close() + util.safe_open(path, "wb", 0o744).close() dns_common.CredentialsConfiguration(path) self.assertEqual(1, len([_ for _ in log.messages['warning'] if _.startswith("Unsafe")])) -class CredentialsConfigurationRequireTest(util.TempDirTestCase): +class CredentialsConfigurationRequireTest(test_util.TempDirTestCase): def setUp(self): super(CredentialsConfigurationRequireTest, self).setUp() diff --git a/certbot/plugins/enhancements_test.py b/certbot/tests/plugins/enhancements_test.py similarity index 89% rename from certbot/plugins/enhancements_test.py rename to certbot/tests/plugins/enhancements_test.py index 22f6f54e9..05fbc5028 100644 --- a/certbot/plugins/enhancements_test.py +++ b/certbot/tests/plugins/enhancements_test.py @@ -1,10 +1,10 @@ """Tests for new style enhancements""" import unittest + import mock +from certbot._internal.plugins import null from certbot.plugins import enhancements -from certbot.plugins import null - import certbot.tests.util as test_util @@ -37,12 +37,10 @@ class EnhancementTest(test_util.ConfigTestCase): self.assertTrue([i for i in enabled if i["name"] == "somethingelse"]) def test_are_requested(self): - self.assertEqual( - len([i for i in enhancements.enabled_enhancements(self.config)]), 0) + self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 0) self.assertFalse(enhancements.are_requested(self.config)) self.config.auto_hsts = True - self.assertEqual( - len([i for i in enhancements.enabled_enhancements(self.config)]), 1) + self.assertEqual(len(list(enhancements.enabled_enhancements(self.config))), 1) self.assertTrue(enhancements.are_requested(self.config)) def test_are_supported(self): diff --git a/certbot/plugins/manual_test.py b/certbot/tests/plugins/manual_test.py similarity index 95% rename from certbot/plugins/manual_test.py rename to certbot/tests/plugins/manual_test.py index 10dbe73c9..bd11a9538 100644 --- a/certbot/plugins/manual_test.py +++ b/certbot/tests/plugins/manual_test.py @@ -1,20 +1,20 @@ -"""Tests for certbot.plugins.manual""" -import unittest +"""Tests for certbot._internal.plugins.manual""" import sys +import unittest import mock import six from acme import challenges - from certbot import errors +from certbot.compat import filesystem from certbot.compat import os from certbot.tests import acme_util from certbot.tests import util as test_util class AuthenticatorTest(test_util.TempDirTestCase): - """Tests for certbot.plugins.manual.Authenticator.""" + """Tests for certbot._internal.plugins.manual.Authenticator.""" def setUp(self): super(AuthenticatorTest, self).setUp() @@ -23,7 +23,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.dns_achall_2 = acme_util.DNS01_A_2 self.achalls = [self.http_achall, self.dns_achall, self.dns_achall_2] for d in ["config_dir", "work_dir", "in_progress"]: - os.mkdir(os.path.join(self.tempdir, d)) + filesystem.mkdir(os.path.join(self.tempdir, d)) # "backup_dir" and "temp_checkpoint_dir" get created in # certbot.util.make_or_verify_dir() during the Reverter # initialization. @@ -38,7 +38,7 @@ class AuthenticatorTest(test_util.TempDirTestCase): self.tempdir, "temp_checkpoint_dir"), in_progress_dir=os.path.join(self.tempdir, "in_progess")) - from certbot.plugins.manual import Authenticator + from certbot._internal.plugins.manual import Authenticator self.auth = Authenticator(self.config, name='manual') def test_prepare_no_hook_noninteractive(self): diff --git a/certbot/plugins/null_test.py b/certbot/tests/plugins/null_test.py similarity index 73% rename from certbot/plugins/null_test.py rename to certbot/tests/plugins/null_test.py index d5de33fb3..db0213813 100644 --- a/certbot/plugins/null_test.py +++ b/certbot/tests/plugins/null_test.py @@ -1,15 +1,15 @@ -"""Tests for certbot.plugins.null.""" +"""Tests for certbot._internal.plugins.null.""" import unittest -import six import mock +import six class InstallerTest(unittest.TestCase): - """Tests for certbot.plugins.null.Installer.""" + """Tests for certbot._internal.plugins.null.Installer.""" def setUp(self): - from certbot.plugins.null import Installer + from certbot._internal.plugins.null import Installer self.installer = Installer(config=mock.MagicMock(), name="null") def test_it(self): diff --git a/certbot/plugins/selection_test.py b/certbot/tests/plugins/selection_test.py similarity index 83% rename from certbot/plugins/selection_test.py rename to certbot/tests/plugins/selection_test.py index a2a171c1d..ac846af7b 100644 --- a/certbot/plugins/selection_test.py +++ b/certbot/tests/plugins/selection_test.py @@ -6,45 +6,44 @@ import mock import zope.component from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module - from certbot import errors from certbot import interfaces +from certbot._internal.plugins.disco import PluginsRegistry from certbot.compat import os from certbot.display import util as display_util -from certbot.plugins.disco import PluginsRegistry from certbot.tests import util as test_util class ConveniencePickPluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.pick_*.""" + """Tests for certbot._internal.plugins.selection.pick_*.""" def _test(self, fun, ifaces): config = mock.Mock() default = mock.Mock() plugins = mock.Mock() - with mock.patch("certbot.plugins.selection.pick_plugin") as mock_p: + with mock.patch("certbot._internal.plugins.selection.pick_plugin") as mock_p: mock_p.return_value = "foo" self.assertEqual("foo", fun(config, default, plugins, "Question?")) mock_p.assert_called_once_with( config, default, plugins, "Question?", ifaces) def test_authenticator(self): - from certbot.plugins.selection import pick_authenticator + from certbot._internal.plugins.selection import pick_authenticator self._test(pick_authenticator, (interfaces.IAuthenticator,)) def test_installer(self): - from certbot.plugins.selection import pick_installer + from certbot._internal.plugins.selection import pick_installer self._test(pick_installer, (interfaces.IInstaller,)) def test_configurator(self): - from certbot.plugins.selection import pick_configurator + from certbot._internal.plugins.selection import pick_configurator self._test(pick_configurator, (interfaces.IAuthenticator, interfaces.IInstaller)) class PickPluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.pick_plugin.""" + """Tests for certbot._internal.plugins.selection.pick_plugin.""" def setUp(self): self.config = mock.Mock(noninteractive_mode=False) @@ -54,7 +53,7 @@ class PickPluginTest(unittest.TestCase): self.ifaces = [] # type: List[interfaces.IPlugin] def _call(self): - from certbot.plugins.selection import pick_plugin + from certbot._internal.plugins.selection import pick_plugin return pick_plugin(self.config, self.default, self.reg, self.question, self.ifaces) @@ -95,7 +94,7 @@ class PickPluginTest(unittest.TestCase): "bar": plugin_ep, "baz": plugin_ep, } - with mock.patch("certbot.plugins.selection.choose_plugin") as mock_choose: + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = plugin_ep self.assertEqual("foo", self._call()) mock_choose.assert_called_once_with( @@ -107,13 +106,13 @@ class PickPluginTest(unittest.TestCase): "baz": None, } - with mock.patch("certbot.plugins.selection.choose_plugin") as mock_choose: + with mock.patch("certbot._internal.plugins.selection.choose_plugin") as mock_choose: mock_choose.return_value = None self.assertTrue(self._call() is None) class ChoosePluginTest(unittest.TestCase): - """Tests for certbot.plugins.selection.choose_plugin.""" + """Tests for certbot._internal.plugins.selection.choose_plugin.""" def setUp(self): zope.component.provideUtility(display_util.FileDisplay(sys.stdout, @@ -130,17 +129,17 @@ class ChoosePluginTest(unittest.TestCase): ] def _call(self): - from certbot.plugins.selection import choose_plugin + from certbot._internal.plugins.selection import choose_plugin return choose_plugin(self.plugins, "Question?") - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_selection(self, mock_util): mock_util().menu.side_effect = [(display_util.OK, 0), (display_util.OK, 1)] self.assertEqual(self.mock_stand, self._call()) self.assertEqual(mock_util().notification.call_count, 1) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_more_info(self, mock_util): mock_util().menu.side_effect = [ (display_util.OK, 1), @@ -148,12 +147,12 @@ class ChoosePluginTest(unittest.TestCase): self.assertEqual(self.mock_stand, self._call()) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_no_choice(self, mock_util): mock_util().menu.return_value = (display_util.CANCEL, 0) self.assertTrue(self._call() is None) - @test_util.patch_get_utility("certbot.plugins.selection.z_util") + @test_util.patch_get_utility("certbot._internal.plugins.selection.z_util") def test_new_interaction_avoidance(self, mock_util): mock_nginx = mock.Mock( description_with_name="n", misconfigured=False) @@ -174,7 +173,7 @@ class ChoosePluginTest(unittest.TestCase): self.assertTrue("default" in mock_util().menu.call_args[1]) class GetUnpreparedInstallerTest(test_util.ConfigTestCase): - """Tests for certbot.plugins.selection.get_unprepared_installer.""" + """Tests for certbot._internal.plugins.selection.get_unprepared_installer.""" def setUp(self): super(GetUnpreparedInstallerTest, self).setUp() @@ -192,7 +191,7 @@ class GetUnpreparedInstallerTest(test_util.ConfigTestCase): }) def _call(self): - from certbot.plugins.selection import get_unprepared_installer + from certbot._internal.plugins.selection import get_unprepared_installer return get_unprepared_installer(self.config, self.plugins) def test_no_installer_defined(self): diff --git a/certbot/plugins/standalone_test.py b/certbot/tests/plugins/standalone_test.py similarity index 90% rename from certbot/plugins/standalone_test.py rename to certbot/tests/plugins/standalone_test.py index e3e0c1f10..5d9ff5244 100644 --- a/certbot/plugins/standalone_test.py +++ b/certbot/tests/plugins/standalone_test.py @@ -1,31 +1,30 @@ -"""Tests for certbot.plugins.standalone.""" -import socket +"""Tests for certbot._internal.plugins.standalone.""" # https://github.com/python/typeshed/blob/master/stdlib/2and3/socket.pyi +import socket from socket import errno as socket_errors # type: ignore import unittest import josepy as jose import mock -import six - import OpenSSL.crypto # pylint: disable=unused-import +import six from acme import challenges from acme import standalone as acme_standalone # pylint: disable=unused-import -from acme.magic_typing import Dict, Tuple, Set # pylint: disable=unused-import, no-name-in-module - +from acme.magic_typing import Dict # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module +from acme.magic_typing import Tuple # pylint: disable=unused-import, no-name-in-module from certbot import achallenges from certbot import errors - from certbot.tests import acme_util from certbot.tests import util as test_util class ServerManagerTest(unittest.TestCase): - """Tests for certbot.plugins.standalone.ServerManager.""" + """Tests for certbot._internal.plugins.standalone.ServerManager.""" def setUp(self): - from certbot.plugins.standalone import ServerManager + from certbot._internal.plugins.standalone import ServerManager self.certs = {} # type: Dict[bytes, Tuple[OpenSSL.crypto.PKey, OpenSSL.crypto.X509]] self.http_01_resources = {} \ # type: Set[acme_standalone.HTTP01RequestHandler.HTTP01Resource] @@ -38,7 +37,7 @@ class ServerManagerTest(unittest.TestCase): def _test_run_stop(self, challenge_type): server = self.mgr.run(port=0, challenge_type=challenge_type) - port = server.getsocknames()[0][1] # pylint: disable=no-member + port = server.getsocknames()[0][1] self.assertEqual(self.mgr.running(), {port: server}) self.mgr.stop(port=port) self.assertEqual(self.mgr.running(), {}) @@ -48,7 +47,7 @@ class ServerManagerTest(unittest.TestCase): def test_run_idempotent(self): server = self.mgr.run(port=0, challenge_type=challenges.HTTP01) - port = server.getsocknames()[0][1] # pylint: disable=no-member + port = server.getsocknames()[0][1] server2 = self.mgr.run(port=port, challenge_type=challenges.HTTP01) self.assertEqual(self.mgr.running(), {port: server}) self.assertTrue(server is server2) @@ -82,10 +81,10 @@ def get_open_port(): class AuthenticatorTest(unittest.TestCase): - """Tests for certbot.plugins.standalone.Authenticator.""" + """Tests for certbot._internal.plugins.standalone.Authenticator.""" def setUp(self): - from certbot.plugins.standalone import Authenticator + from certbot._internal.plugins.standalone import Authenticator self.config = mock.MagicMock(http01_port=get_open_port()) self.auth = Authenticator(self.config, name="standalone") diff --git a/certbot/plugins/storage_test.py b/certbot/tests/plugins/storage_test.py similarity index 95% rename from certbot/plugins/storage_test.py rename to certbot/tests/plugins/storage_test.py index 2fa2c0345..e9ca2007f 100644 --- a/certbot/plugins/storage_test.py +++ b/certbot/tests/plugins/storage_test.py @@ -1,10 +1,11 @@ """Tests for certbot.plugins.storage.PluginStorage""" import json import unittest + import mock from certbot import errors - +from certbot.compat import filesystem from certbot.compat import os from certbot.plugins import common from certbot.tests import util as test_util @@ -16,7 +17,7 @@ class PluginStorageTest(test_util.ConfigTestCase): def setUp(self): super(PluginStorageTest, self).setUp() self.plugin_cls = common.Installer - os.mkdir(self.config.config_dir) + filesystem.mkdir(self.config.config_dir) with mock.patch("certbot.reverter.util"): self.plugin = self.plugin_cls(config=self.config, name="mockplugin") @@ -30,7 +31,7 @@ class PluginStorageTest(test_util.ConfigTestCase): self.plugin.storage.storagepath = os.path.join(self.config.config_dir, ".pluginstorage.json") with mock.patch("six.moves.builtins.open", mock_open): - with mock.patch('os.path.isfile', return_value=True): + with mock.patch('certbot.compat.os.path.isfile', return_value=True): with mock.patch("certbot.reverter.util"): self.assertRaises(errors.PluginStorageError, self.plugin.storage._load) # pylint: disable=protected-access @@ -72,7 +73,7 @@ class PluginStorageTest(test_util.ConfigTestCase): def test_save_errors_unable_to_write_file(self): mock_open = mock.mock_open() mock_open.side_effect = IOError - with mock.patch("certbot.compat.os.open", mock_open): + with mock.patch("certbot.compat.filesystem.open", mock_open): with mock.patch("certbot.plugins.storage.logger.error") as mock_log: self.plugin.storage._data = {"valid": "data"} # pylint: disable=protected-access self.plugin.storage._initialized = True # pylint: disable=protected-access diff --git a/certbot/plugins/util_test.py b/certbot/tests/plugins/util_test.py similarity index 69% rename from certbot/plugins/util_test.py rename to certbot/tests/plugins/util_test.py index 6ec6f62f4..c41e55222 100644 --- a/certbot/plugins/util_test.py +++ b/certbot/tests/plugins/util_test.py @@ -16,6 +16,7 @@ class GetPrefixTest(unittest.TestCase): self.assertEqual(get_prefixes('/'), [os.path.normpath('/')]) self.assertEqual(get_prefixes('a'), ['a']) + class PathSurgeryTest(unittest.TestCase): """Tests for certbot.plugins.path_surgery.""" @@ -29,13 +30,15 @@ class PathSurgeryTest(unittest.TestCase): self.assertEqual(path_surgery("eg"), True) self.assertEqual(mock_debug.call_count, 0) self.assertEqual(os.environ["PATH"], all_path["PATH"]) - no_path = {"PATH": "/tmp/"} - with mock.patch.dict('os.environ', no_path): - path_surgery("thingy") - self.assertEqual(mock_debug.call_count, 2) - self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) - self.assertTrue("/usr/local/bin" in os.environ["PATH"]) - self.assertTrue("/tmp" in os.environ["PATH"]) + if os.name != 'nt': + # This part is specific to Linux since on Windows no PATH surgery is ever done. + no_path = {"PATH": "/tmp/"} + with mock.patch.dict('os.environ', no_path): + path_surgery("thingy") + self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1) + self.assertTrue("Failed to find" in mock_debug.call_args[0][0]) + self.assertTrue("/usr/local/bin" in os.environ["PATH"]) + self.assertTrue("/tmp" in os.environ["PATH"]) if __name__ == "__main__": diff --git a/certbot/plugins/webroot_test.py b/certbot/tests/plugins/webroot_test.py similarity index 89% rename from certbot/plugins/webroot_test.py rename to certbot/tests/plugins/webroot_test.py index 2d15c46fb..fade12bb1 100644 --- a/certbot/plugins/webroot_test.py +++ b/certbot/tests/plugins/webroot_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.plugins.webroot.""" +"""Tests for certbot._internal.plugins.webroot.""" from __future__ import print_function @@ -14,12 +14,10 @@ import mock import six from acme import challenges - from certbot import achallenges from certbot import errors -from certbot.compat import misc -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os from certbot.display import util as display_util from certbot.tests import acme_util from certbot.tests import util as test_util @@ -28,14 +26,20 @@ KEY = jose.JWKRSA.load(test_util.load_vector("rsa512_key.pem")) class AuthenticatorTest(unittest.TestCase): - """Tests for certbot.plugins.webroot.Authenticator.""" + """Tests for certbot._internal.plugins.webroot.Authenticator.""" achall = achallenges.KeyAuthorizationAnnotatedChallenge( challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) def setUp(self): - from certbot.plugins.webroot import Authenticator - self.path = tempfile.mkdtemp() + from certbot._internal.plugins.webroot import Authenticator + # On Linux directories created by tempfile.mkdtemp inherit their permissions from their + # parent directory. So the actual permissions are inconsistent over various tests env. + # To circumvent this, a dedicated sub-workspace is created under the workspace, using + # filesystem.mkdir to get consistent permissions. + self.workspace = tempfile.mkdtemp() + self.path = os.path.join(self.workspace, 'webroot') + filesystem.mkdir(self.path) self.partial_root_challenge_path = os.path.join( self.path, ".well-known") self.root_challenge_path = os.path.join( @@ -142,13 +146,11 @@ class AuthenticatorTest(unittest.TestCase): self.assertRaises(errors.PluginError, self.auth.perform, []) filesystem.chmod(self.path, 0o700) - @test_util.skip_on_windows('On Windows, there is no chown.') - @mock.patch("certbot.plugins.webroot.os.chown") - def test_failed_chown(self, mock_chown): - mock_chown.side_effect = OSError(errno.EACCES, "msg") + @mock.patch("certbot._internal.plugins.webroot.filesystem.copy_ownership_and_apply_mode") + def test_failed_chown(self, mock_ownership): + mock_ownership.side_effect = OSError(errno.EACCES, "msg") self.auth.perform([self.achall]) # exception caught and logged - @test_util.patch_get_utility() def test_perform_new_webroot_not_in_map(self, mock_get_utility): new_webroot = tempfile.mkdtemp() @@ -170,20 +172,15 @@ class AuthenticatorTest(unittest.TestCase): # Remove exec bit from permission check, so that it # matches the file self.auth.perform([self.achall]) - self.assertTrue(misc.compare_file_modes(os.stat(self.validation_path).st_mode, 0o644)) + self.assertTrue(filesystem.check_mode(self.validation_path, 0o644)) # Check permissions of the directories - for dirpath, dirnames, _ in os.walk(self.path): for directory in dirnames: full_path = os.path.join(dirpath, directory) - self.assertTrue(misc.compare_file_modes(os.stat(full_path).st_mode, 0o755)) + self.assertTrue(filesystem.check_mode(full_path, 0o755)) - parent_gid = os.stat(self.path).st_gid - parent_uid = os.stat(self.path).st_uid - - self.assertEqual(os.stat(self.validation_path).st_gid, parent_gid) - self.assertEqual(os.stat(self.validation_path).st_uid, parent_uid) + self.assertTrue(filesystem.has_same_ownership(self.validation_path, self.path)) def test_perform_cleanup(self): self.auth.prepare() @@ -203,7 +200,7 @@ class AuthenticatorTest(unittest.TestCase): self.assertFalse(os.path.exists(self.partial_root_challenge_path)) def test_perform_cleanup_existing_dirs(self): - os.mkdir(self.partial_root_challenge_path) + filesystem.mkdir(self.partial_root_challenge_path) self.auth.prepare() self.auth.perform([self.achall]) self.auth.cleanup([self.achall]) @@ -219,7 +216,7 @@ class AuthenticatorTest(unittest.TestCase): domain="thing.com", account_key=KEY) bingo_validation_path = "YmluZ28" - os.mkdir(self.partial_root_challenge_path) + filesystem.mkdir(self.partial_root_challenge_path) self.auth.prepare() self.auth.perform([bingo_achall, self.achall]) @@ -235,7 +232,7 @@ class AuthenticatorTest(unittest.TestCase): self.auth.perform([self.achall]) leftover_path = os.path.join(self.root_challenge_path, 'leftover') - os.mkdir(leftover_path) + filesystem.mkdir(leftover_path) self.auth.cleanup([self.achall]) self.assertFalse(os.path.exists(self.validation_path)) @@ -264,7 +261,7 @@ class WebrootActionTest(unittest.TestCase): challb=acme_util.HTTP01_P, domain="thing.com", account_key=KEY) def setUp(self): - from certbot.plugins.webroot import Authenticator + from certbot._internal.plugins.webroot import Authenticator self.path = tempfile.mkdtemp() self.parser = argparse.ArgumentParser() self.parser.add_argument("-d", "--domains", @@ -310,7 +307,7 @@ class WebrootActionTest(unittest.TestCase): self.assertEqual(args.webroot_path, [self.path, other_webroot_path]) def _get_config_after_perform(self, config): - from certbot.plugins.webroot import Authenticator + from certbot._internal.plugins.webroot import Authenticator auth = Authenticator(config, "webroot") auth.perform([self.achall]) return auth.config diff --git a/certbot/tests/renewal_test.py b/certbot/tests/renewal_test.py index e33869e13..e92211ea2 100644 --- a/certbot/tests/renewal_test.py +++ b/certbot/tests/renewal_test.py @@ -1,18 +1,17 @@ -"""Tests for certbot.renewal""" +"""Tests for certbot._internal.renewal""" import unittest + import mock from acme import challenges - -from certbot import configuration from certbot import errors -from certbot import storage - +from certbot._internal import configuration +from certbot._internal import storage import certbot.tests.util as test_util class RenewalTest(test_util.ConfigTestCase): - @mock.patch('certbot.cli.set_by_cli') + @mock.patch('certbot._internal.cli.set_by_cli') def test_ancient_webroot_renewal_conf(self, mock_set_by_cli): mock_set_by_cli.return_value = False rc_path = test_util.make_lineage( @@ -24,15 +23,16 @@ class RenewalTest(test_util.ConfigTestCase): lineage = storage.RenewableCert(rc_path, config) renewalparams = lineage.configuration['renewalparams'] # pylint: disable=protected-access - from certbot import renewal + from certbot._internal import renewal renewal._restore_webroot_config(config, renewalparams) self.assertEqual(config.webroot_path, ['/var/www/']) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_webroot_params_conservation(self, mock_set_by_cli): # For more details about why this test is important, see: - # certbot.plugins.webroot_test::WebrootActionTest::test_webroot_map_partial_without_perform - from certbot import renewal + # certbot._internal.plugins.webroot_test:: + # WebrootActionTest::test_webroot_map_partial_without_perform + from certbot._internal import renewal mock_set_by_cli.return_value = False renewalparams = { @@ -53,39 +53,34 @@ class RenewalTest(test_util.ConfigTestCase): class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): - """Tests for certbot.renewal.restore_required_config_elements.""" + """Tests for certbot._internal.renewal.restore_required_config_elements.""" @classmethod def _call(cls, *args, **kwargs): - from certbot.renewal import restore_required_config_elements + from certbot._internal.renewal import restore_required_config_elements return restore_required_config_elements(*args, **kwargs) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_allow_subset_of_names_success(self, mock_set_by_cli): mock_set_by_cli.return_value = False self._call(self.config, {'allow_subset_of_names': 'True'}) self.assertTrue(self.config.allow_subset_of_names is True) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_allow_subset_of_names_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'allow_subset_of_names': 'maybe'} self.assertRaises( errors.Error, self._call, self.config, renewalparams) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_list(self, mock_set_by_cli): mock_set_by_cli.return_value = False - # TODO: remove tls-sni and related assertions to logger.warning call once - # the deprecation logic has been removed - renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')} - with mock.patch('certbot.renewal.cli.logger.warning') as mock_warn: - self._call(self.config, renewalparams) + renewalparams = {'pref_challs': 'http-01, dns'.split(',')} + self._call(self.config, renewalparams) expected = [challenges.HTTP01.typ, challenges.DNS01.typ] self.assertEqual(self.config.pref_challs, expected) - self.assertEqual(mock_warn.call_count, 1) - self.assertTrue('deprecated' in mock_warn.call_args[0][0]) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_str(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'pref_challs': 'dns'} @@ -93,19 +88,19 @@ class RestoreRequiredConfigElementsTest(test_util.ConfigTestCase): expected = [challenges.DNS01.typ] self.assertEqual(self.config.pref_challs, expected) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_pref_challs_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'pref_challs': 'finding-a-shrubbery'} self.assertRaises(errors.Error, self._call, self.config, renewalparams) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_must_staple_success(self, mock_set_by_cli): mock_set_by_cli.return_value = False self._call(self.config, {'must_staple': 'True'}) self.assertTrue(self.config.must_staple is True) - @mock.patch('certbot.renewal.cli.set_by_cli') + @mock.patch('certbot._internal.renewal.cli.set_by_cli') def test_must_staple_failure(self, mock_set_by_cli): mock_set_by_cli.return_value = False renewalparams = {'must_staple': 'maybe'} diff --git a/certbot/tests/renewupdater_test.py b/certbot/tests/renewupdater_test.py index 5fe188c42..c6f8f3713 100644 --- a/certbot/tests/renewupdater_test.py +++ b/certbot/tests/renewupdater_test.py @@ -1,13 +1,12 @@ """Tests for renewal updater interfaces""" import unittest + import mock from certbot import interfaces -from certbot import main -from certbot import updater - +from certbot._internal import main +from certbot._internal import updater from certbot.plugins import enhancements - import certbot.tests.util as test_util @@ -21,9 +20,9 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.renew_deployer = mock.MagicMock(spec=interfaces.RenewDeployer) self.mockinstaller = mock.MagicMock(spec=enhancements.AutoHSTSEnhancement) - @mock.patch('certbot.main._get_and_save_cert') - @mock.patch('certbot.plugins.selection.choose_configurator_plugins') - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.main._get_and_save_cert') + @mock.patch('certbot._internal.plugins.selection.choose_configurator_plugins') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') @test_util.patch_get_utility() def test_server_updates(self, _, mock_geti, mock_select, mock_getsave): mock_getsave.return_value = mock.MagicMock() @@ -32,7 +31,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): # Generic Updater mock_select.return_value = (mock_generic_updater, None) mock_geti.return_value = mock_generic_updater - with mock.patch('certbot.main._init_le_client'): + with mock.patch('certbot._internal.main._init_le_client'): main.renew_cert(self.config, None, mock.MagicMock()) self.assertTrue(mock_generic_updater.restart.called) @@ -48,7 +47,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): updater.run_renewal_deployer(self.config, lineage, mock_deployer) self.assertTrue(mock_deployer.renew_deploy.called_with(lineage)) - @mock.patch("certbot.updater.logger.debug") + @mock.patch("certbot._internal.updater.logger.debug") def test_updater_skip_dry_run(self, mock_log): self.config.dry_run = True updater.run_generic_updaters(self.config, None, None) @@ -56,7 +55,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.assertEqual(mock_log.call_args[0][0], "Skipping updaters in dry-run mode.") - @mock.patch("certbot.updater.logger.debug") + @mock.patch("certbot._internal.updater.logger.debug") def test_deployer_skip_dry_run(self, mock_log): self.config.dry_run = True updater.run_renewal_deployer(self.config, None, None) @@ -64,7 +63,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.assertEqual(mock_log.call_args[0][0], "Skipping renewal deployer in dry-run mode.") - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates(self, mock_geti): mock_geti.return_value = self.mockinstaller updater.run_generic_updaters(self.config, mock.MagicMock(), None) @@ -76,7 +75,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.mockinstaller) self.assertTrue(self.mockinstaller.deploy_autohsts.called) - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_updates_not_called(self, mock_geti): self.config.disable_renew_updates = True mock_geti.return_value = self.mockinstaller @@ -89,7 +88,7 @@ class RenewUpdaterTest(test_util.ConfigTestCase): self.mockinstaller) self.assertFalse(self.mockinstaller.deploy_autohsts.called) - @mock.patch('certbot.plugins.selection.get_unprepared_installer') + @mock.patch('certbot._internal.plugins.selection.get_unprepared_installer') def test_enhancement_no_updater(self, mock_geti): FAKEINDEX = [ { diff --git a/certbot/tests/reporter_test.py b/certbot/tests/reporter_test.py index b43522364..3d7c80172 100644 --- a/certbot/tests/reporter_test.py +++ b/certbot/tests/reporter_test.py @@ -1,15 +1,15 @@ -"""Tests for certbot.reporter.""" +"""Tests for certbot._internal.reporter.""" import sys import unittest -import mock +import mock import six class ReporterTest(unittest.TestCase): - """Tests for certbot.reporter.Reporter.""" + """Tests for certbot._internal.reporter.Reporter.""" def setUp(self): - from certbot import reporter + from certbot._internal import reporter self.reporter = reporter.Reporter(mock.MagicMock(quiet=False)) self.old_stdout = sys.stdout # type: ignore diff --git a/certbot/tests/reverter_test.py b/certbot/tests/reverter_test.py index 6a805072b..fc9133c5f 100644 --- a/certbot/tests/reverter_test.py +++ b/certbot/tests/reverter_test.py @@ -14,7 +14,6 @@ from certbot.tests import util as test_util class ReverterCheckpointLocalTest(test_util.ConfigTestCase): - # pylint: disable=too-many-instance-attributes, too-many-public-methods """Test the Reverter Class.""" def setUp(self): super(ReverterCheckpointLocalTest, self).setUp() @@ -277,7 +276,6 @@ class ReverterCheckpointLocalTest(test_util.ConfigTestCase): class TestFullCheckpointsReverter(test_util.ConfigTestCase): - # pylint: disable=too-many-instance-attributes """Tests functions having to deal with full checkpoints.""" def setUp(self): super(TestFullCheckpointsReverter, self).setUp() @@ -376,39 +374,6 @@ class TestFullCheckpointsReverter(test_util.ConfigTestCase): self.assertEqual(read_in(self.config2), "directive-dir2") self.assertFalse(os.path.isfile(config3)) - @test_util.patch_get_utility() - def test_view_config_changes(self, mock_output): - """This is not strict as this is subject to change.""" - self._setup_three_checkpoints() - - # Make sure it doesn't throw any errors - self.reverter.view_config_changes() - - # Make sure notification is output - self.assertEqual(mock_output().notification.call_count, 1) - - @mock.patch("certbot.reverter.logger") - def test_view_config_changes_no_backups(self, mock_logger): - self.reverter.view_config_changes() - self.assertTrue(mock_logger.info.call_count > 0) - - def test_view_config_changes_bad_backups_dir(self): - # There shouldn't be any "in progress directories when this is called - # It must just be clean checkpoints - os.makedirs(os.path.join(self.config.backup_dir, "in_progress")) - - self.assertRaises( - errors.ReverterError, self.reverter.view_config_changes) - - def test_view_config_changes_for_logging(self): - self._setup_three_checkpoints() - - config_changes = self.reverter.view_config_changes(for_logging=True) - - self.assertTrue("First Checkpoint" in config_changes) - self.assertTrue("Second Checkpoint" in config_changes) - self.assertTrue("Third Checkpoint" in config_changes) - def _setup_three_checkpoints(self): """Generate some finalized checkpoints.""" # Checkpoint1 - config1 diff --git a/certbot/tests/storage_test.py b/certbot/tests/storage_test.py index 307c053d8..6208974ec 100644 --- a/certbot/tests/storage_test.py +++ b/certbot/tests/storage_test.py @@ -1,4 +1,4 @@ -"""Tests for certbot.storage.""" +"""Tests for certbot._internal.storage.""" # pylint disable=protected-access import datetime import shutil @@ -11,17 +11,15 @@ import pytz import six import certbot -import certbot.tests.util as test_util from certbot import errors -from certbot.compat import misc -from certbot.compat import os +from certbot._internal.storage import ALL_FOUR from certbot.compat import filesystem -from certbot.storage import ALL_FOUR +from certbot.compat import os +import certbot.tests.util as test_util CERT = test_util.load_cert('cert_512.pem') - def unlink_all(rc_object): """Unlink all four items associated with this RenewableCert.""" for kind in ALL_FOUR: @@ -36,17 +34,17 @@ def fill_with_sample_data(rc_object): class RelevantValuesTest(unittest.TestCase): - """Tests for certbot.storage.relevant_values.""" + """Tests for certbot._internal.storage.relevant_values.""" def setUp(self): self.values = {"server": "example.org"} def _call(self, *args, **kwargs): - from certbot.storage import relevant_values + from certbot._internal.storage import relevant_values return relevant_values(*args, **kwargs) - @mock.patch("certbot.cli.option_was_set") - @mock.patch("certbot.plugins.disco.PluginsRegistry.find_all") + @mock.patch("certbot._internal.cli.option_was_set") + @mock.patch("certbot._internal.plugins.disco.PluginsRegistry.find_all") def test_namespace(self, mock_find_all, mock_option_was_set): mock_find_all.return_value = ["certbot-foo:bar"] mock_option_was_set.return_value = True @@ -55,7 +53,7 @@ class RelevantValuesTest(unittest.TestCase): self.assertEqual( self._call(self.values.copy()), self.values) - @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot._internal.cli.option_was_set") def test_option_set(self, mock_option_was_set): mock_option_was_set.return_value = True @@ -67,7 +65,7 @@ class RelevantValuesTest(unittest.TestCase): self.assertEqual(self._call(self.values), expected_relevant_values) - @mock.patch("certbot.cli.option_was_set") + @mock.patch("certbot._internal.cli.option_was_set") def test_option_unset(self, mock_option_was_set): mock_option_was_set.return_value = False @@ -86,16 +84,16 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): """ def setUp(self): - from certbot import storage + from certbot._internal import storage super(BaseRenewableCertTest, self).setUp() # TODO: maybe provide NamespaceConfig.make_dirs? # TODO: main() should create those dirs, c.f. #902 - os.makedirs(os.path.join(self.config.config_dir, "live", "example.org")) + filesystem.makedirs(os.path.join(self.config.config_dir, "live", "example.org")) archive_path = os.path.join(self.config.config_dir, "archive", "example.org") - os.makedirs(archive_path) - os.makedirs(os.path.join(self.config.config_dir, "renewal")) + filesystem.makedirs(archive_path) + filesystem.makedirs(os.path.join(self.config.config_dir, "renewal")) config_file = configobj.ConfigObj() for kind in ALL_FOUR: @@ -119,7 +117,7 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): self.defaults = configobj.ConfigObj() - with mock.patch("certbot.storage.RenewableCert._check_symlinks") as check: + with mock.patch("certbot._internal.storage.RenewableCert._check_symlinks") as check: check.return_value = True self.test_rc = storage.RenewableCert(config_file.filename, self.config) @@ -142,8 +140,7 @@ class BaseRenewableCertTest(test_util.ConfigTestCase): class RenewableCertTests(BaseRenewableCertTest): - # pylint: disable=too-many-public-methods - """Tests for certbot.storage.""" + """Tests for certbot._internal.storage.""" def test_initialization(self): self.assertEqual(self.test_rc.lineagename, "example.org") @@ -157,7 +154,7 @@ class RenewableCertTests(BaseRenewableCertTest): the renewal configuration file doesn't end in ".conf" """ - from certbot import storage + from certbot._internal import storage broken = os.path.join(self.config.config_dir, "broken.conf") with open(broken, "w") as f: f.write("[No closing bracket for you!") @@ -170,7 +167,7 @@ class RenewableCertTests(BaseRenewableCertTest): def test_renewal_incomplete_config(self): """Test that the RenewableCert constructor will complain if the renewal configuration file is missing a required file element.""" - from certbot import storage + from certbot._internal import storage config = configobj.ConfigObj() config["cert"] = "imaginary_cert.pem" # Here the required privkey is missing. @@ -182,29 +179,29 @@ class RenewableCertTests(BaseRenewableCertTest): config.filename, self.config) def test_no_renewal_version(self): - from certbot import storage + from certbot._internal import storage self._write_out_ex_kinds() self.assertTrue("version" not in self.config_file) - with mock.patch("certbot.storage.logger") as mock_logger: + with mock.patch("certbot._internal.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) self.assertFalse(mock_logger.warning.called) def test_renewal_newer_version(self): - from certbot import storage + from certbot._internal import storage self._write_out_ex_kinds() self.config_file["version"] = "99.99.99" self.config_file.write() - with mock.patch("certbot.storage.logger") as mock_logger: + with mock.patch("certbot._internal.storage.logger") as mock_logger: storage.RenewableCert(self.config_file.filename, self.config) self.assertTrue(mock_logger.info.called) self.assertTrue("version" in mock_logger.info.call_args[0][0]) def test_consistent(self): - # pylint: disable=too-many-statements,protected-access + # pylint: disable=protected-access oldcert = self.test_rc.cert self.test_rc.cert = "relative/path" # Absolute path for item requirement @@ -297,7 +294,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual(self.test_rc.latest_common_version(), 17) self.assertEqual(self.test_rc.next_free_version(), 18) - @mock.patch("certbot.storage.logger") + @mock.patch("certbot._internal.storage.logger") def test_ensure_deployed(self, mock_logger): mock_update = self.test_rc.update_all_links_to = mock.Mock() mock_has_pending = self.test_rc.has_pending_deployment = mock.Mock() @@ -359,11 +356,10 @@ class RenewableCertTests(BaseRenewableCertTest): basename = os.path.basename(path) if "fullchain" in basename and basename.startswith("prev"): raise ValueError - else: - real_unlink(path) + real_unlink(path) self._write_out_ex_kinds() - with mock.patch("certbot.storage.os.unlink") as mock_unlink: + with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: mock_unlink.side_effect = unlink_or_raise self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) @@ -375,11 +371,10 @@ class RenewableCertTests(BaseRenewableCertTest): # pylint: disable=missing-docstring if "fullchain" in os.path.basename(path): raise ValueError - else: - real_unlink(path) + real_unlink(path) self._write_out_ex_kinds() - with mock.patch("certbot.storage.os.unlink") as mock_unlink: + with mock.patch("certbot._internal.storage.os.unlink") as mock_unlink: mock_unlink.side_effect = unlink_or_raise self.assertRaises(ValueError, self.test_rc.update_all_links_to, 12) @@ -407,26 +402,12 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual(self.test_rc.names(), ["example.com", "www.example.com"]) - # Trying a non-current version - self._write_out_kind("cert", 15, test_util.load_vector("cert_512.pem")) - - self.assertEqual(self.test_rc.names(12), - ["example.com", "www.example.com"]) - - # Testing common name is listed first - self._write_out_kind( - "cert", 12, test_util.load_vector("cert-5sans_512.pem")) - - self.assertEqual( - self.test_rc.names(12), - ["example.com"] + ["{0}.example.com".format(c) for c in "abcd"]) - # Trying missing cert os.unlink(self.test_rc.cert) self.assertRaises(errors.CertStorageError, self.test_rc.names) - @mock.patch("certbot.storage.cli") - @mock.patch("certbot.storage.datetime") + @mock.patch("certbot._internal.storage.cli") + @mock.patch("certbot._internal.storage.datetime") def test_time_interval_judgments(self, mock_datetime, mock_cli): """Test should_autorenew() on the basis of expiry time windows.""" test_cert = test_util.load_vector("cert_512.pem") @@ -480,12 +461,11 @@ class RenewableCertTests(BaseRenewableCertTest): self.test_rc.configuration["renewalparams"]["autorenew"] = "False" self.assertFalse(self.test_rc.autorenewal_is_enabled()) - @mock.patch("certbot.storage.cli") - @mock.patch("certbot.storage.RenewableCert.ocsp_revoked") + @mock.patch("certbot._internal.storage.cli") + @mock.patch("certbot._internal.storage.RenewableCert.ocsp_revoked") def test_should_autorenew(self, mock_ocsp, mock_cli): """Test should_autorenew on the basis of reasons other than expiry time window.""" - # pylint: disable=too-many-statements mock_cli.set_by_cli.return_value = False # Autorenewal turned off self.test_rc.configuration["renewalparams"] = {"autorenew": "False"} @@ -498,8 +478,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(self.test_rc.should_autorenew()) mock_ocsp.return_value = False - @test_util.broken_on_windows - @mock.patch("certbot.storage.relevant_values") + @mock.patch("certbot._internal.storage.relevant_values") def test_save_successor(self, mock_rv): # Mock relevant_values() to claim that all values are relevant here # (to avoid instantiating parser) @@ -562,8 +541,8 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertFalse(os.path.islink(self.test_rc.version("privkey", 10))) self.assertFalse(os.path.exists(temp_config_file)) - @test_util.broken_on_windows - @mock.patch("certbot.storage.relevant_values") + @test_util.skip_on_windows('Group/everybody permissions are not maintained on Windows.') + @mock.patch("certbot._internal.storage.relevant_values") def test_save_successor_maintains_group_mode(self, mock_rv): # Mock relevant_values() to claim that all values are relevant here # (to avoid instantiating parser) @@ -571,27 +550,22 @@ class RenewableCertTests(BaseRenewableCertTest): for kind in ALL_FOUR: self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) - self.assertTrue(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 1)).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 1), 0o600)) filesystem.chmod(self.test_rc.version("privkey", 1), 0o444) # If no new key, permissions should be the same (we didn't write any keys) self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config) - self.assertTrue(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 2)).st_mode, 0o444)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 2), 0o444)) # If new key, permissions should be kept as 644 self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 3)).st_mode, 0o644)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 3), 0o644)) # If permissions reverted, next renewal will also revert permissions of new key filesystem.chmod(self.test_rc.version("privkey", 3), 0o400) self.test_rc.save_successor(3, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(misc.compare_file_modes( - os.stat(self.test_rc.version("privkey", 4)).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.test_rc.version("privkey", 4), 0o600)) - @test_util.broken_on_windows - @mock.patch("certbot.storage.relevant_values") - @mock.patch("certbot.storage.os.chown") - def test_save_successor_maintains_gid(self, mock_chown, mock_rv): + @mock.patch("certbot._internal.storage.relevant_values") + @mock.patch("certbot._internal.storage.filesystem.copy_ownership_and_apply_mode") + def test_save_successor_maintains_gid(self, mock_ownership, mock_rv): # Mock relevant_values() to claim that all values are relevant here # (to avoid instantiating parser) mock_rv.side_effect = lambda x: x @@ -599,18 +573,18 @@ class RenewableCertTests(BaseRenewableCertTest): self._write_out_kind(kind, 1) self.test_rc.update_all_links_to(1) self.test_rc.save_successor(1, b"newcert", None, b"new chain", self.config) - self.assertFalse(mock_chown.called) + self.assertFalse(mock_ownership.called) self.test_rc.save_successor(2, b"newcert", b"new_privkey", b"new chain", self.config) - self.assertTrue(mock_chown.called) + self.assertTrue(mock_ownership.called) - @mock.patch("certbot.storage.relevant_values") + @mock.patch("certbot._internal.storage.relevant_values") def test_new_lineage(self, mock_rv): """Test for new_lineage() class method.""" # Mock relevant_values to say everything is relevant here (so we # don't have to mock the parser to help it decide!) mock_rv.side_effect = lambda x: x - from certbot import storage + from certbot._internal import storage result = storage.RenewableCert.new_lineage( "the-lineage.com", b"cert", b"privkey", b"chain", self.config) # This consistency check tests most relevant properties about the @@ -623,7 +597,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.config.live_dir, "README"))) self.assertTrue(os.path.exists(os.path.join( self.config.live_dir, "the-lineage.com", "README"))) - self.assertTrue(misc.compare_file_modes(os.stat(result.key_path).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(result.key_path, 0o600)) with open(result.fullchain, "rb") as f: self.assertEqual(f.read(), b"cert" + b"chain") # Let's do it again and make sure it makes a different lineage @@ -634,12 +608,12 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(os.path.exists(os.path.join( self.config.live_dir, "the-lineage.com-0001", "README"))) # Now trigger the detection of already existing files - os.mkdir(os.path.join( + filesystem.mkdir(os.path.join( self.config.live_dir, "the-lineage.com-0002")) self.assertRaises(errors.CertStorageError, storage.RenewableCert.new_lineage, "the-lineage.com", b"cert3", b"privkey3", b"chain3", self.config) - os.mkdir(os.path.join(self.config.default_archive_dir, "other-example.com")) + filesystem.mkdir(os.path.join(self.config.default_archive_dir, "other-example.com")) self.assertRaises(errors.CertStorageError, storage.RenewableCert.new_lineage, "other-example.com", b"cert4", @@ -650,14 +624,14 @@ class RenewableCertTests(BaseRenewableCertTest): # TODO: Conceivably we could test that the renewal parameters actually # got saved - @mock.patch("certbot.storage.relevant_values") + @mock.patch("certbot._internal.storage.relevant_values") def test_new_lineage_nonexistent_dirs(self, mock_rv): """Test that directories can be created if they don't exist.""" # Mock relevant_values to say everything is relevant here (so we # don't have to mock the parser to help it decide!) mock_rv.side_effect = lambda x: x - from certbot import storage + from certbot._internal import storage shutil.rmtree(self.config.renewal_configs_dir) shutil.rmtree(self.config.default_archive_dir) shutil.rmtree(self.config.live_dir) @@ -672,9 +646,9 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertTrue(os.path.exists(os.path.join( self.config.default_archive_dir, "the-lineage.com", "privkey1.pem"))) - @mock.patch("certbot.storage.util.unique_lineage_name") + @mock.patch("certbot._internal.storage.util.unique_lineage_name") def test_invalid_config_filename(self, mock_uln): - from certbot import storage + from certbot._internal import storage mock_uln.return_value = "this_does_not_end_with_dot_conf", "yikes" self.assertRaises(errors.CertStorageError, storage.RenewableCert.new_lineage, "example.com", @@ -704,7 +678,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertFalse(self.test_rc.ocsp_revoked()) def test_add_time_interval(self): - from certbot import storage + from certbot._internal import storage # this month has 30 days, and the next year is a leap year time_1 = pytz.UTC.fromutc(datetime.datetime(2003, 11, 20, 11, 59, 21)) @@ -759,7 +733,7 @@ class RenewableCertTests(BaseRenewableCertTest): self.assertEqual(self.test_rc.is_test_cert, False) def test_missing_cert(self): - from certbot import storage + from certbot._internal import storage self.assertRaises(errors.CertStorageError, storage.RenewableCert, self.config_file.filename, self.config) @@ -784,7 +758,7 @@ class RenewableCertTests(BaseRenewableCertTest): archive_dir = "the_archive" relevant_data = {"useful": "new_value"} - from certbot import storage + from certbot._internal import storage storage.write_renewal_config(temp, temp2, archive_dir, target, relevant_data) with open(temp2, "r") as f: @@ -802,7 +776,7 @@ class RenewableCertTests(BaseRenewableCertTest): stat.S_IMODE(os.lstat(temp2).st_mode)) def test_update_symlinks(self): - from certbot import storage + from certbot._internal import storage archive_dir_path = os.path.join(self.config.config_dir, "archive", "example.org") for kind in ALL_FOUR: live_path = self.config_file[kind] @@ -817,7 +791,7 @@ class RenewableCertTests(BaseRenewableCertTest): update_symlinks=True) class DeleteFilesTest(BaseRenewableCertTest): - """Tests for certbot.storage.delete_files""" + """Tests for certbot._internal.storage.delete_files""" def setUp(self): super(DeleteFilesTest, self).setUp() @@ -835,8 +809,8 @@ class DeleteFilesTest(BaseRenewableCertTest): self.config.config_dir, "archive", "example.org"))) def _call(self): - from certbot import storage - with mock.patch("certbot.storage.logger"): + from certbot._internal import storage + with mock.patch("certbot._internal.storage.logger"): storage.delete_files(self.config, "example.org") def test_delete_all_files(self): @@ -907,7 +881,7 @@ class DeleteFilesTest(BaseRenewableCertTest): self.assertFalse(os.path.exists(archive_dir)) class CertPathForCertNameTest(BaseRenewableCertTest): - """Test for certbot.storage.cert_path_for_cert_name""" + """Test for certbot._internal.storage.cert_path_for_cert_name""" def setUp(self): super(CertPathForCertNameTest, self).setUp() self.config_file.write() @@ -917,7 +891,7 @@ class CertPathForCertNameTest(BaseRenewableCertTest): self.config.cert_path = (self.fullchain, '') def _call(self, cli_config, certname): - from certbot.storage import cert_path_for_cert_name + from certbot._internal.storage import cert_path_for_cert_name return cert_path_for_cert_name(cli_config, certname) def test_simple_cert_name(self): diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index dccd96ab5..ae061de65 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -1,17 +1,17 @@ """Tests for certbot.util.""" import argparse import errno +import sys import unittest import mock import six from six.moves import reload_module # pylint: disable=import-error -import certbot.tests.util as test_util from certbot import errors -from certbot.compat import misc -from certbot.compat import os from certbot.compat import filesystem +from certbot.compat import os +import certbot.tests.util as test_util class RunScriptTest(unittest.TestCase): @@ -53,26 +53,13 @@ class ExeExistsTest(unittest.TestCase): from certbot.util import exe_exists return exe_exists(exe) - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_full_path(self, mock_access, mock_isfile): - mock_access.return_value = True - mock_isfile.return_value = True - self.assertTrue(self._call("/path/to/exe")) + def test_exe_exists(self): + with mock.patch("certbot.util.filesystem.is_executable", return_value=True): + self.assertTrue(self._call("/path/to/exe")) - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_on_path(self, mock_access, mock_isfile): - mock_access.return_value = True - mock_isfile.return_value = True - self.assertTrue(self._call("exe")) - - @mock.patch("certbot.util.os.path.isfile") - @mock.patch("certbot.util.os.access") - def test_not_found(self, mock_access, mock_isfile): - mock_access.return_value = False - mock_isfile.return_value = True - self.assertFalse(self._call("exe")) + def test_exe_not_exists(self): + with mock.patch("certbot.util.filesystem.is_executable", return_value=False): + self.assertFalse(self._call("/path/to/exe")) class LockDirUntilExit(test_util.TempDirTestCase): @@ -92,7 +79,7 @@ class LockDirUntilExit(test_util.TempDirTestCase): @mock.patch('certbot.util.atexit_register') def test_it(self, mock_register, mock_logger): subdir = os.path.join(self.tempdir, 'subdir') - os.mkdir(subdir) + filesystem.mkdir(subdir) self._call(self.tempdir) self._call(subdir) self._call(subdir) @@ -120,15 +107,14 @@ class SetUpCoreDirTest(test_util.TempDirTestCase): @mock.patch('certbot.util.lock_dir_until_exit') def test_success(self, mock_lock): new_dir = os.path.join(self.tempdir, 'new') - self._call(new_dir, 0o700, misc.os_geteuid(), False) + self._call(new_dir, 0o700, False) self.assertTrue(os.path.exists(new_dir)) self.assertEqual(mock_lock.call_count, 1) @mock.patch('certbot.util.make_or_verify_dir') def test_failure(self, mock_make_or_verify): mock_make_or_verify.side_effect = OSError - self.assertRaises(errors.Error, self._call, - self.tempdir, 0o700, misc.os_geteuid(), False) + self.assertRaises(errors.Error, self._call, self.tempdir, 0o700, False) class MakeOrVerifyDirTest(test_util.TempDirTestCase): @@ -143,67 +129,31 @@ class MakeOrVerifyDirTest(test_util.TempDirTestCase): super(MakeOrVerifyDirTest, self).setUp() self.path = os.path.join(self.tempdir, "foo") - os.mkdir(self.path, 0o600) - - self.uid = misc.os_geteuid() + filesystem.mkdir(self.path, 0o600) def _call(self, directory, mode): from certbot.util import make_or_verify_dir - return make_or_verify_dir(directory, mode, self.uid, strict=True) + return make_or_verify_dir(directory, mode, strict=True) def test_creates_dir_when_missing(self): path = os.path.join(self.tempdir, "bar") self._call(path, 0o650) self.assertTrue(os.path.isdir(path)) - self.assertTrue(misc.compare_file_modes(os.stat(path).st_mode, 0o650)) + self.assertTrue(filesystem.check_mode(path, 0o650)) def test_existing_correct_mode_does_not_fail(self): self._call(self.path, 0o600) - self.assertTrue(misc.compare_file_modes(os.stat(self.path).st_mode, 0o600)) + self.assertTrue(filesystem.check_mode(self.path, 0o600)) - @test_util.skip_on_windows('Umask modes are mostly ignored on Windows.') def test_existing_wrong_mode_fails(self): self.assertRaises(errors.Error, self._call, self.path, 0o400) def test_reraises_os_error(self): - with mock.patch.object(os, "makedirs") as makedirs: + with mock.patch.object(filesystem, "makedirs") as makedirs: makedirs.side_effect = OSError() self.assertRaises(OSError, self._call, "bar", 12312312) -class CheckPermissionsTest(test_util.TempDirTestCase): - """Tests for certbot.util.check_permissions. - - Note that it is not possible to test for a wrong file owner, - as this testing script would have to be run as root. - - """ - - def setUp(self): - super(CheckPermissionsTest, self).setUp() - - self.uid = misc.os_geteuid() - - def _call(self, mode): - from certbot.util import check_permissions - return check_permissions(self.tempdir, mode, self.uid) - - def test_ok_mode(self): - filesystem.chmod(self.tempdir, 0o600) - self.assertTrue(self._call(0o600)) - - # TODO: reactivate the test when all logic from windows file permissions is merged. - @test_util.broken_on_windows - def test_wrong_mode(self): - filesystem.chmod(self.tempdir, 0o400) - try: - self.assertFalse(self._call(0o600)) - finally: - # Without proper write permissions, Windows is unable to delete a folder, - # even with admin permissions. Write access must be explicitly set first. - filesystem.chmod(self.tempdir, 0o700) - - class UniqueFileTest(test_util.TempDirTestCase): """Tests for certbot.util.unique_file.""" @@ -226,8 +176,8 @@ class UniqueFileTest(test_util.TempDirTestCase): def test_right_mode(self): fd1, name1 = self._call(0o700) fd2, name2 = self._call(0o600) - self.assertTrue(misc.compare_file_modes(0o700, os.stat(name1).st_mode)) - self.assertTrue(misc.compare_file_modes(0o600, os.stat(name2).st_mode)) + self.assertTrue(filesystem.check_mode(name1, 0o700)) + self.assertTrue(filesystem.check_mode(name2, 0o600)) fd1.close() fd2.close() @@ -288,7 +238,7 @@ class UniqueLineageNameTest(test_util.TempDirTestCase): f.close() def test_failure(self): - with mock.patch("certbot.util.os.open", side_effect=OSError(errno.EIO)): + with mock.patch("certbot.compat.filesystem.open", side_effect=OSError(errno.EIO)): self.assertRaises(OSError, self._call, "wow") @@ -524,71 +474,84 @@ class IsWildcardDomainTest(unittest.TestCase): class OsInfoTest(unittest.TestCase): """Test OS / distribution detection""" - def test_systemd_os_release(self): - from certbot.util import (get_os_info, get_systemd_os_info, - get_os_info_ua) + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_systemd_os_release_like(self, m_distro): + import certbot.util as cbutil + m_distro.like.return_value = "first debian third" + id_likes = cbutil.get_systemd_os_like() + self.assertEqual(len(id_likes), 3) + self.assertTrue("debian" in id_likes) - with mock.patch('os.path.isfile', return_value=True): - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[0], 'systemdos') - self.assertEqual(get_os_info( - test_util.vector_path("os-release"))[1], '42') - self.assertEqual(get_systemd_os_info(os.devnull), ("", "")) - self.assertEqual(get_os_info_ua( - test_util.vector_path("os-release")), "SystemdOS") - with mock.patch('os.path.isfile', return_value=False): - self.assertEqual(get_systemd_os_info(), ("", "")) + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info_ua(self, m_distro): + import certbot.util as cbutil + with mock.patch('platform.system_alias', + return_value=('linux', '42', '42')): + m_distro.name.return_value = "" + m_distro.linux_distribution.return_value = ("something", "1.0", "codename") + cbutil.get_python_os_info(pretty=True) + self.assertEqual(cbutil.get_os_info_ua(), + " ".join(cbutil.get_python_os_info(pretty=True))) - def test_systemd_os_release_like(self): - from certbot.util import get_systemd_os_like + m_distro.name.return_value = "whatever" + self.assertEqual(cbutil.get_os_info_ua(), "whatever") - with mock.patch('os.path.isfile', return_value=True): - id_likes = get_systemd_os_like(test_util.vector_path( - "os-release")) - self.assertEqual(len(id_likes), 3) - self.assertTrue("debian" in id_likes) + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_get_os_info(self, m_distro): + import certbot.util as cbutil + with mock.patch("platform.system") as mock_platform: + m_distro.linux_distribution.return_value = ("name", "version", 'x') + mock_platform.return_value = "linux" + self.assertEqual(cbutil.get_os_info(), ("name", "version")) + + m_distro.linux_distribution.return_value = ("something", "else") + self.assertEqual(cbutil.get_os_info(), ("something", "else")) @mock.patch("certbot.util.subprocess.Popen") def test_non_systemd_os_info(self, popen_mock): - from certbot.util import (get_os_info, get_python_os_info, - get_os_info_ua) - with mock.patch('os.path.isfile', return_value=False): + import certbot.util as cbutil + with mock.patch('certbot.util._USE_DISTRO', False): with mock.patch('platform.system_alias', return_value=('NonSystemD', '42', '42')): - self.assertEqual(get_os_info()[0], 'nonsystemd') - self.assertEqual(get_os_info_ua(), - " ".join(get_python_os_info())) + self.assertEqual(cbutil.get_python_os_info()[0], 'nonsystemd') with mock.patch('platform.system_alias', return_value=('darwin', '', '')): comm_mock = mock.Mock() comm_attrs = {'communicate.return_value': - ('42.42.42', 'error')} + ('42.42.42', 'error')} comm_mock.configure_mock(**comm_attrs) popen_mock.return_value = comm_mock - self.assertEqual(get_os_info()[0], 'darwin') - self.assertEqual(get_os_info()[1], '42.42.42') - - with mock.patch('platform.system_alias', - return_value=('linux', '', '')): - with mock.patch('platform.linux_distribution', - return_value=('', '', '')): - self.assertEqual(get_python_os_info(), ("linux", "")) - - with mock.patch('platform.linux_distribution', - return_value=('testdist', '42', '')): - self.assertEqual(get_python_os_info(), ("testdist", "42")) + self.assertEqual(cbutil.get_python_os_info()[0], 'darwin') + self.assertEqual(cbutil.get_python_os_info()[1], '42.42.42') with mock.patch('platform.system_alias', return_value=('freebsd', '9.3-RC3-p1', '')): - self.assertEqual(get_python_os_info(), ("freebsd", "9")) + self.assertEqual(cbutil.get_python_os_info(), ("freebsd", "9")) with mock.patch('platform.system_alias', return_value=('windows', '', '')): with mock.patch('platform.win32_ver', return_value=('4242', '95', '2', '')): - self.assertEqual(get_python_os_info(), - ("windows", "95")) + self.assertEqual(cbutil.get_python_os_info(), + ("windows", "95")) + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_notfound(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('', '', '') + self.assertEqual(cbutil.get_python_os_info()[0], "linux") + + @mock.patch("certbot.util.distro") + @unittest.skipUnless(sys.platform.startswith("linux"), "requires Linux") + def test_python_os_info_custom(self, m_distro): + import certbot.util as cbutil + m_distro.linux_distribution.return_value = ('testdist', '42', '') + self.assertEqual(cbutil.get_python_os_info(), ("testdist", "42")) class AtexitRegisterTest(unittest.TestCase): diff --git a/docs/.gitignore b/docs/.gitignore deleted file mode 100644 index ba65b13af..000000000 --- a/docs/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/_build/ diff --git a/docs/_static/.gitignore b/docs/_static/.gitignore deleted file mode 100644 index e69de29bb..000000000 diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 8668ec5d8..000000000 --- a/docs/api.rst +++ /dev/null @@ -1,8 +0,0 @@ -================= -API Documentation -================= - -.. toctree:: - :glob: - - api/** diff --git a/docs/api/account.rst b/docs/api/account.rst deleted file mode 100644 index fd90230ea..000000000 --- a/docs/api/account.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.account` --------------------------- - -.. automodule:: certbot.account - :members: diff --git a/docs/api/achallenges.rst b/docs/api/achallenges.rst deleted file mode 100644 index 90dda3f06..000000000 --- a/docs/api/achallenges.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.achallenges` ------------------------------- - -.. automodule:: certbot.achallenges - :members: diff --git a/docs/api/auth_handler.rst b/docs/api/auth_handler.rst deleted file mode 100644 index 8819bb1bd..000000000 --- a/docs/api/auth_handler.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.auth_handler` -------------------------------- - -.. automodule:: certbot.auth_handler - :members: diff --git a/docs/api/cert_manager.rst b/docs/api/cert_manager.rst deleted file mode 100644 index c8c86f8b9..000000000 --- a/docs/api/cert_manager.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.cert_manager` -------------------------------- - -.. automodule:: certbot.cert_manager - :members: diff --git a/docs/api/cli.rst b/docs/api/cli.rst deleted file mode 100644 index 91be86758..000000000 --- a/docs/api/cli.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.cli` ----------------------- - -.. automodule:: certbot.cli - :members: diff --git a/docs/api/client.rst b/docs/api/client.rst deleted file mode 100644 index 00a443cd9..000000000 --- a/docs/api/client.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.client` -------------------------- - -.. automodule:: certbot.client - :members: diff --git a/docs/api/configuration.rst b/docs/api/configuration.rst deleted file mode 100644 index 4e99c73d2..000000000 --- a/docs/api/configuration.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.configuration` --------------------------------- - -.. automodule:: certbot.configuration - :members: diff --git a/docs/api/constants.rst b/docs/api/constants.rst deleted file mode 100644 index 99ecc240a..000000000 --- a/docs/api/constants.rst +++ /dev/null @@ -1,9 +0,0 @@ -:mod:`certbot.constants` ------------------------------------ - -.. automodule:: certbot.constants - :members: - :exclude-members: SSL_DHPARAMS_SRC - -.. autodata:: SSL_DHPARAMS_SRC - :annotation: = '/path/to/certbot/ssl-dhparams.pem' diff --git a/docs/api/crypto_util.rst b/docs/api/crypto_util.rst deleted file mode 100644 index 2f473944c..000000000 --- a/docs/api/crypto_util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.crypto_util` ------------------------------- - -.. automodule:: certbot.crypto_util - :members: diff --git a/docs/api/display.rst b/docs/api/display.rst deleted file mode 100644 index 1a18e6534..000000000 --- a/docs/api/display.rst +++ /dev/null @@ -1,23 +0,0 @@ -:mod:`certbot.display` --------------------------- - -.. automodule:: certbot.display - :members: - -:mod:`certbot.display.util` -=============================== - -.. automodule:: certbot.display.util - :members: - -:mod:`certbot.display.ops` -============================== - -.. automodule:: certbot.display.ops - :members: - -:mod:`certbot.display.enhancements` -======================================= - -.. automodule:: certbot.display.enhancements - :members: diff --git a/docs/api/eff.rst b/docs/api/eff.rst deleted file mode 100644 index 2924b256d..000000000 --- a/docs/api/eff.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.eff` ----------------------- - -.. automodule:: certbot.eff - :members: diff --git a/docs/api/error_handler.rst b/docs/api/error_handler.rst deleted file mode 100644 index f1306177d..000000000 --- a/docs/api/error_handler.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.error_handler` --------------------------------- - -.. automodule:: certbot.error_handler - :members: diff --git a/docs/api/errors.rst b/docs/api/errors.rst deleted file mode 100644 index a9324765b..000000000 --- a/docs/api/errors.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.errors` -------------------------- - -.. automodule:: certbot.errors - :members: diff --git a/docs/api/hooks.rst b/docs/api/hooks.rst deleted file mode 100644 index 140fbf284..000000000 --- a/docs/api/hooks.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.hooks` ------------------------- - -.. automodule:: certbot.hooks - :members: diff --git a/docs/api/index.rst b/docs/api/index.rst deleted file mode 100644 index be94214c9..000000000 --- a/docs/api/index.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot` ------------------- - -.. automodule:: certbot - :members: diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst deleted file mode 100644 index 2988b3b87..000000000 --- a/docs/api/interfaces.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.interfaces` ------------------------------ - -.. automodule:: certbot.interfaces - :members: diff --git a/docs/api/lock.rst b/docs/api/lock.rst deleted file mode 100644 index 6dcbf9589..000000000 --- a/docs/api/lock.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.lock` ------------------------ - -.. automodule:: certbot.lock - :members: diff --git a/docs/api/log.rst b/docs/api/log.rst deleted file mode 100644 index 41311de90..000000000 --- a/docs/api/log.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.log` ----------------------- - -.. automodule:: certbot.log - :members: diff --git a/docs/api/main.rst b/docs/api/main.rst deleted file mode 100644 index a555bab01..000000000 --- a/docs/api/main.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.main` ------------------------ - -.. automodule:: certbot.main - :members: diff --git a/docs/api/notify.rst b/docs/api/notify.rst deleted file mode 100644 index fa042b2d2..000000000 --- a/docs/api/notify.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.notify` -------------------------- - -.. automodule:: certbot.notify - :members: diff --git a/docs/api/ocsp.rst b/docs/api/ocsp.rst deleted file mode 100644 index 7044f4052..000000000 --- a/docs/api/ocsp.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.ocsp` ------------------------ - -.. automodule:: certbot.ocsp - :members: diff --git a/docs/api/plugins/common.rst b/docs/api/plugins/common.rst deleted file mode 100644 index 7cfaf8d70..000000000 --- a/docs/api/plugins/common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.common` ---------------------------------- - -.. automodule:: certbot.plugins.common - :members: diff --git a/docs/api/plugins/disco.rst b/docs/api/plugins/disco.rst deleted file mode 100644 index 1a27f0f69..000000000 --- a/docs/api/plugins/disco.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.disco` --------------------------------- - -.. automodule:: certbot.plugins.disco - :members: diff --git a/docs/api/plugins/dns_common.rst b/docs/api/plugins/dns_common.rst deleted file mode 100644 index ee3945e74..000000000 --- a/docs/api/plugins/dns_common.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common` ---------------------------------- - -.. automodule:: certbot.plugins.dns_common - :members: diff --git a/docs/api/plugins/dns_common_lexicon.rst b/docs/api/plugins/dns_common_lexicon.rst deleted file mode 100644 index a48166828..000000000 --- a/docs/api/plugins/dns_common_lexicon.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.dns_common_lexicon` ------------------------------------------ - -.. automodule:: certbot.plugins.dns_common_lexicon - :members: diff --git a/docs/api/plugins/manual.rst b/docs/api/plugins/manual.rst deleted file mode 100644 index eea443499..000000000 --- a/docs/api/plugins/manual.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.manual` ---------------------------------- - -.. automodule:: certbot.plugins.manual - :members: diff --git a/docs/api/plugins/selection.rst b/docs/api/plugins/selection.rst deleted file mode 100644 index 6211bf9c0..000000000 --- a/docs/api/plugins/selection.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.selection` ------------------------------------- - -.. automodule:: certbot.plugins.selection - :members: diff --git a/docs/api/plugins/standalone.rst b/docs/api/plugins/standalone.rst deleted file mode 100644 index 60aa48b4f..000000000 --- a/docs/api/plugins/standalone.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.standalone` -------------------------------------- - -.. automodule:: certbot.plugins.standalone - :members: diff --git a/docs/api/plugins/util.rst b/docs/api/plugins/util.rst deleted file mode 100644 index 30ab3d49f..000000000 --- a/docs/api/plugins/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.util` -------------------------------- - -.. automodule:: certbot.plugins.util - :members: diff --git a/docs/api/plugins/webroot.rst b/docs/api/plugins/webroot.rst deleted file mode 100644 index e1f4523f7..000000000 --- a/docs/api/plugins/webroot.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.plugins.webroot` ----------------------------------- - -.. automodule:: certbot.plugins.webroot - :members: diff --git a/docs/api/renewal.rst b/docs/api/renewal.rst deleted file mode 100644 index 58557351f..000000000 --- a/docs/api/renewal.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.renewal` --------------------------- - -.. automodule:: certbot.renewal - :members: diff --git a/docs/api/reporter.rst b/docs/api/reporter.rst deleted file mode 100644 index ad71dbb69..000000000 --- a/docs/api/reporter.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.reporter` ---------------------------- - -.. automodule:: certbot.reporter - :members: diff --git a/docs/api/reverter.rst b/docs/api/reverter.rst deleted file mode 100644 index 3e0ac750b..000000000 --- a/docs/api/reverter.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.reverter` ---------------------------- - -.. automodule:: certbot.reverter - :members: diff --git a/docs/api/storage.rst b/docs/api/storage.rst deleted file mode 100644 index 34e3a45c0..000000000 --- a/docs/api/storage.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.storage` --------------------------- - -.. automodule:: certbot.storage - :members: diff --git a/docs/api/util.rst b/docs/api/util.rst deleted file mode 100644 index 7d0e33501..000000000 --- a/docs/api/util.rst +++ /dev/null @@ -1,5 +0,0 @@ -:mod:`certbot.util` --------------------------- - -.. automodule:: certbot.util - :members: diff --git a/docs/packaging.rst b/docs/packaging.rst deleted file mode 100644 index c13a14af3..000000000 --- a/docs/packaging.rst +++ /dev/null @@ -1,126 +0,0 @@ -=============== -Packaging Guide -=============== - -Releases -======== - -We release packages and upload them to PyPI (wheels and source tarballs). - -- https://pypi.python.org/pypi/acme -- https://pypi.python.org/pypi/certbot -- https://pypi.python.org/pypi/certbot-apache -- https://pypi.python.org/pypi/certbot-nginx -- https://pypi.python.org/pypi/certbot-dns-cloudflare -- https://pypi.python.org/pypi/certbot-dns-cloudxns -- https://pypi.python.org/pypi/certbot-dns-digitalocean -- https://pypi.python.org/pypi/certbot-dns-dnsimple -- https://pypi.python.org/pypi/certbot-dns-dnsmadeeasy -- https://pypi.python.org/pypi/certbot-dns-google -- https://pypi.python.org/pypi/certbot-dns-linode -- https://pypi.python.org/pypi/certbot-dns-luadns -- https://pypi.python.org/pypi/certbot-dns-nsone -- https://pypi.python.org/pypi/certbot-dns-ovh -- https://pypi.python.org/pypi/certbot-dns-rfc2136 -- https://pypi.python.org/pypi/certbot-dns-route53 - -The following scripts are used in the process: - -- https://github.com/letsencrypt/letsencrypt/blob/master/tools/release.sh - -We use git tags to identify releases, using `Semantic Versioning`_. For -example: `v0.11.1`. - -.. _`Semantic Versioning`: http://semver.org/ - -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. - -2. If you'd like to include automated renewal in your package ``certbot renew -q`` should be added to crontab or systemd timer. Additionally you should include a random per-machine time offset to avoid having a large number of your clients hit Let's Encrypt's servers simultaneously. - -3. ``jws`` is an internal script for ``acme`` module and it doesn't have to be packaged - it's mostly for debugging: you can use it as ``echo foo | jws sign | jws verify``. - -4. Do get in touch with us. We are happy to make any changes that will make packaging easier. If you need to apply some patches don't do it downstream - make a PR here. - -Already ongoing efforts -======================= - - -Arch ----- - -From our official releases: - -- https://www.archlinux.org/packages/community/any/python-acme -- https://www.archlinux.org/packages/community/any/certbot -- https://www.archlinux.org/packages/community/any/certbot-apache -- https://www.archlinux.org/packages/community/any/certbot-nginx -- https://www.archlinux.org/packages/community/any/certbot-dns-cloudflare -- https://www.archlinux.org/packages/community/any/certbot-dns-cloudxns -- https://www.archlinux.org/packages/community/any/certbot-dns-digitalocean -- https://www.archlinux.org/packages/community/any/certbot-dns-dnsimple -- https://www.archlinux.org/packages/community/any/certbot-dns-dnsmadeeasy -- https://www.archlinux.org/packages/community/any/certbot-dns-google -- https://www.archlinux.org/packages/community/any/certbot-dns-luadns -- https://www.archlinux.org/packages/community/any/certbot-dns-nsone -- https://www.archlinux.org/packages/community/any/certbot-dns-rfc2136 -- https://www.archlinux.org/packages/community/any/certbot-dns-route53 - -From ``master``: https://aur.archlinux.org/packages/certbot-git - -Debian (and its derivatives, including Ubuntu) ----------------------------------------------- - -- https://packages.debian.org/sid/certbot -- https://packages.debian.org/sid/python-certbot -- https://packages.debian.org/sid/python-certbot-apache - -Fedora ------- - -In Fedora 23+. - -- https://apps.fedoraproject.org/packages/python-acme -- https://apps.fedoraproject.org/packages/certbot -- https://apps.fedoraproject.org/packages/python-certbot-apache -- https://apps.fedoraproject.org/packages/python-certbot-dns-cloudflare -- https://apps.fedoraproject.org/packages/python-certbot-dns-cloudxns -- https://apps.fedoraproject.org/packages/python-certbot-dns-digitalocean -- https://apps.fedoraproject.org/packages/python-certbot-dns-dnsimple -- https://apps.fedoraproject.org/packages/python-certbot-dns-dnsmadeeasy -- https://apps.fedoraproject.org/packages/python-certbot-dns-google -- https://apps.fedoraproject.org/packages/python-certbot-dns-luadns -- https://apps.fedoraproject.org/packages/python-certbot-dns-nsone -- https://apps.fedoraproject.org/packages/python-certbot-dns-rfc2136 -- https://apps.fedoraproject.org/packages/python-certbot-dns-route53 -- https://apps.fedoraproject.org/packages/python-certbot-nginx - -FreeBSD -------- - -- https://www.freshports.org/security/py-acme/ -- https://www.freshports.org/security/py-certbot/ - -Gentoo ------- - -Currently, all ``certbot`` related packages are in the testing branch: - -- https://packages.gentoo.org/packages/app-crypt/certbot -- https://packages.gentoo.org/packages/app-crypt/certbot-apache -- https://packages.gentoo.org/packages/app-crypt/certbot-nginx -- https://packages.gentoo.org/packages/app-crypt/acme - -GNU Guix --------- - -- https://www.gnu.org/software/guix/package-list.html#certbot - -OpenBSD -------- - -- http://cvsweb.openbsd.org/cgi-bin/cvsweb/ports/security/letsencrypt/client/ diff --git a/letsencrypt-auto b/letsencrypt-auto index 756b8e247..24c007e03 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -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="0.35.1" +LE_AUTO_VERSION="1.0.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -755,13 +755,33 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +795,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { @@ -1115,73 +1136,83 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1189,26 +1220,26 @@ enum34==1.1.6 ; python_version < '3.4' \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1217,32 +1248,31 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1290,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # @@ -1314,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/Dockerfile.trusty b/letsencrypt-auto-source/Dockerfile.trusty deleted file mode 100644 index 3de88f9af..000000000 --- a/letsencrypt-auto-source/Dockerfile.trusty +++ /dev/null @@ -1,36 +0,0 @@ -# For running tests, build a docker image with a passwordless sudo and a trust -# store we can manipulate. - -FROM ubuntu:trusty - -# Add an unprivileged user: -RUN useradd --create-home --home-dir /home/lea --shell /bin/bash --groups sudo --uid 1000 lea - -# Let that user sudo: -RUN sed -i.bkp -e \ - 's/%sudo\s\+ALL=(ALL\(:ALL\)\?)\s\+ALL/%sudo ALL=NOPASSWD:ALL/g' \ - /etc/sudoers - -# Install pip: -RUN apt-get update && \ - apt-get -q -y install python-pip && \ - apt-get clean -# Use pipstrap to update to a stable and tested version of pip -COPY ./pieces/pipstrap.py /opt -RUN /opt/pipstrap.py -# Pin pytest version for increased stability -RUN pip install pytest==3.2.5 six==1.10.0 - -RUN mkdir -p /home/lea/certbot - -# Install fake testing CA: -COPY ./tests/certs/ca/my-root-ca.crt.pem /usr/local/share/ca-certificates/ -RUN update-ca-certificates - -# Copy code: -COPY . /home/lea/certbot/letsencrypt-auto-source - -USER lea -WORKDIR /home/lea - -CMD ["pytest", "-v", "-s", "certbot/letsencrypt-auto-source/tests"] diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index 0abdfdfdb..aea28117c 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlz+2KgACgkQTRfJlc2X -dfKLvwf/XUnWRyKldk/d8Egx514mpjzV38grCcZTZrY0O/Rd3YMv5KtrxnTnmvxJ -zAkTfNIo7Y1894mZ6XUIF3D7BiPoRqLj6F8tYLV6jbdsPJKC75dQY/6rcttTSJab -Zbqcw+WTXYNZ72AlHw0uTaxNT+S31KvrJ0pNmuj2ezKzZcDfgcxeeqmI1pJYozbQ -+AfqJMFgP9qHtfZVnmRO5UFBW2qPM522E02wWCtkaQSybI9ikRTvJOtilQgsLFJK -vBdD/MgGHm/xQC6lxG6l/SD4pebvNBaIPCiPAo2XC8ML+4tpjuJ5lPoK/aFCvfYQ -wSMHbDbse/Ndw+ssjmriiBreKB37Mg== -=flRo +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAl3mmvMACgkQTRfJlc2X +dfKUbQf/aW8ZWRH36WhTHmZjJmBumSUYclFdDAR4c6Ym+MBTeYT0iQq/dqfqTklB +7jPHTcxWbyMJCjOqtMEDRt+aVF0A91OA1bSRt1MJCm7o8Oa1h4XVVPL2UZYCPNlu +46UEBGDOkd6DlrRvD0X2BrQ4EsktLe1d+EoDbDPebwfip9OYnEYMD7EQB9O3N8eo +aYRkaSJMc2HalI5u0oLEhnZGucNw6K7uvuW0LkwmRWpN8Lc8e9ELZ3FOCE6qD9yh +giAkvZNklwhAxkk9spFkEilvEOPVtKgiSS6jZIL5G1NlAhp8n6+vhatY5Aotw8nO +QrqmPvzBd+2Gy2nrrGuSMC146m0x/g== +=3A0n -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 24e9b295e..2f48751f2 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -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="0.36.0.dev0" +LE_AUTO_VERSION="1.1.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -755,13 +755,33 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -775,6 +795,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { @@ -1115,73 +1136,83 @@ if [ "$1" = "--le-auto-phase2" ]; then # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -1189,26 +1220,26 @@ enum34==1.1.6 ; python_version < '3.4' \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -1217,32 +1248,31 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -1290,18 +1320,46 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 # Contains the requirements for the letsencrypt package. # @@ -1314,18 +1372,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index d3824cf47..705f30e3f 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/letsencrypt-auto.template b/letsencrypt-auto-source/letsencrypt-auto.template index c064580bd..31c5bb134 100755 --- a/letsencrypt-auto-source/letsencrypt-auto.template +++ b/letsencrypt-auto-source/letsencrypt-auto.template @@ -330,13 +330,33 @@ elif [ -f /etc/redhat-release ]; then prev_le_python="$LE_PYTHON" unset LE_PYTHON DeterminePythonVersion "NOCRASH" - # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"` - RPM_DIST_VERSION=0 - if [ "$RPM_DIST_NAME" = "fedora" ]; then - RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"` + + # Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on + # '.' characters (e.g. "8.0" becomes "8"). If the command exits with an + # error, RPM_DIST_VERSION is set to "unknown". + RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown") + + # If RPM_DIST_VERSION is an empty string or it contains any nonnumeric + # characters, the value is unexpected so we set RPM_DIST_VERSION to 0. + if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then + RPM_DIST_VERSION=0 fi + + # Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then. + # RHEL 8 also uses python3 by default. if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + elif [ "$RPM_DIST_NAME" = "centos" -a "$RPM_DIST_VERSION" -ge 8 ]; then + RPM_USE_PYTHON_3=1 + else + RPM_USE_PYTHON_3=0 + fi + + if [ "$RPM_USE_PYTHON_3" = 1 ]; then Bootstrap() { BootstrapMessage "RedHat-based OSes that will use Python3" BootstrapRpmPython3 @@ -350,6 +370,7 @@ elif [ -f /etc/redhat-release ]; then } BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION" fi + LE_PYTHON="$prev_le_python" elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then Bootstrap() { diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 71c041934..d4bdfd49e 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.35.1 \ - --hash=sha256:24821e10b05084a45c5bf29da704115f2637af613866589737cff502294dad2a \ - --hash=sha256:d7e8ecc14e06ed1dc691c6069bc9ce42dce04e8db1684ddfab446fbd71290860 -acme==0.35.1 \ - --hash=sha256:3ec62f638f2b3684bcb3d8476345c7ae37c8f3b28f2999622ff836aec6e73d64 \ - --hash=sha256:a988b8b418cc74075e68b4acf3ff64c026bf52c377b0d01223233660a755c423 -certbot-apache==0.35.1 \ - --hash=sha256:ee4fe10cbd18e0aa7fe36d43ad7792187f41a7298f383610b87049c3a6493bbb \ - --hash=sha256:69962eafe0ec9be8eb2845e3622da6f37ecaeee7e517ea172d71d7b31f01be71 -certbot-nginx==0.35.1 \ - --hash=sha256:22150f13b3c0bd1c3c58b11a64886dad9695796aac42f5809da7ec66de187760 \ - --hash=sha256:85e9a48b4b549f6989304f66cb2fad822c3f8717d361bde0d6a43aabb792d461 +certbot==1.0.0 \ + --hash=sha256:8d074cff89dee002dec1c47cb0da04ea8e0ede8d68838b6d54aa41580d9262df \ + --hash=sha256:86b82d31db19fffffb0d6b218951e2121ef514e3ff659aa042deaf92a33e302a +acme==1.0.0 \ + --hash=sha256:f6972e436e76f7f1e395e81e149f8713ca8462d465b14993bddc53fb18a40644 \ + --hash=sha256:6a08f12f848ce563b50bca421ba9db653df9f82cfefeaf8aba517f046d1386c2 +certbot-apache==1.0.0 \ + --hash=sha256:e591d0cf773ad33ee978f7adb1b69288eac2c8847c643b06e70260e707626f8e \ + --hash=sha256:7335ab5687a0a47d9041d9e13f3a2d67d0e8372da97ab639edb31c14b787cd68 +certbot-nginx==1.0.0 \ + --hash=sha256:ce8a2e51165da7c15bfdc059cd6572d0f368c078f1e1a77633a2773310b2f231 \ + --hash=sha256:63b4ae09d4f1c9ef0a1a2a49c3f651d8a7cb30303ec6f954239e987c5da45dc4 diff --git a/letsencrypt-auto-source/pieces/dependency-requirements.txt b/letsencrypt-auto-source/pieces/dependency-requirements.txt index 48c2afd93..034fae46d 100644 --- a/letsencrypt-auto-source/pieces/dependency-requirements.txt +++ b/letsencrypt-auto-source/pieces/dependency-requirements.txt @@ -2,73 +2,83 @@ # To generate this, do (with docker and package hashin installed): # ``` # letsencrypt-auto-source/rebuild_dependencies.py \ -# letsencrypt-auto-sources/pieces/dependency-requirements.txt +# letsencrypt-auto-source/pieces/dependency-requirements.txt +# ``` +# If you want to update a single dependency, run commands similar to these: +# ``` +# pip install hashin +# hashin -r dependency-requirements.txt cryptography==1.5.2 # ``` ConfigArgParse==0.14.0 \ --hash=sha256:2e2efe2be3f90577aca9415e32cb629aa2ecd92078adbe27b53a03e53ff12e91 -asn1crypto==0.24.0 \ - --hash=sha256:2f1adbb7546ed199e3c90ef23ec95c5cf3585bac7d11fb7eb562a3fe89c64e87 \ - --hash=sha256:9d5c20441baf0cb60a4ac34cc447c6c189024b6b4c6cd7877034f4965c464e49 -certifi==2019.3.9 \ - --hash=sha256:59b7658e26ca9c7339e00f8f4636cdfe59d34fa37b9b04f6f9e9926b3cece1a5 \ - --hash=sha256:b26104d6835d1f5e49452a26eb2ff87fe7090b89dfcaee5ea2212697e1e1d7ae -cffi==1.12.2 \ - --hash=sha256:00b97afa72c233495560a0793cdc86c2571721b4271c0667addc83c417f3d90f \ - --hash=sha256:0ba1b0c90f2124459f6966a10c03794082a2f3985cd699d7d63c4a8dae113e11 \ - --hash=sha256:0bffb69da295a4fc3349f2ec7cbe16b8ba057b0a593a92cbe8396e535244ee9d \ - --hash=sha256:21469a2b1082088d11ccd79dd84157ba42d940064abbfa59cf5f024c19cf4891 \ - --hash=sha256:2e4812f7fa984bf1ab253a40f1f4391b604f7fc424a3e21f7de542a7f8f7aedf \ - --hash=sha256:2eac2cdd07b9049dd4e68449b90d3ef1adc7c759463af5beb53a84f1db62e36c \ - --hash=sha256:2f9089979d7456c74d21303c7851f158833d48fb265876923edcb2d0194104ed \ - --hash=sha256:3dd13feff00bddb0bd2d650cdb7338f815c1789a91a6f68fdc00e5c5ed40329b \ - --hash=sha256:4065c32b52f4b142f417af6f33a5024edc1336aa845b9d5a8d86071f6fcaac5a \ - --hash=sha256:51a4ba1256e9003a3acf508e3b4f4661bebd015b8180cc31849da222426ef585 \ - --hash=sha256:59888faac06403767c0cf8cfb3f4a777b2939b1fbd9f729299b5384f097f05ea \ - --hash=sha256:59c87886640574d8b14910840327f5cd15954e26ed0bbd4e7cef95fa5aef218f \ - --hash=sha256:610fc7d6db6c56a244c2701575f6851461753c60f73f2de89c79bbf1cc807f33 \ - --hash=sha256:70aeadeecb281ea901bf4230c6222af0248c41044d6f57401a614ea59d96d145 \ - --hash=sha256:71e1296d5e66c59cd2c0f2d72dc476d42afe02aeddc833d8e05630a0551dad7a \ - --hash=sha256:8fc7a49b440ea752cfdf1d51a586fd08d395ff7a5d555dc69e84b1939f7ddee3 \ - --hash=sha256:9b5c2afd2d6e3771d516045a6cfa11a8da9a60e3d128746a7fe9ab36dfe7221f \ - --hash=sha256:9c759051ebcb244d9d55ee791259ddd158188d15adee3c152502d3b69005e6bd \ - --hash=sha256:b4d1011fec5ec12aa7cc10c05a2f2f12dfa0adfe958e56ae38dc140614035804 \ - --hash=sha256:b4f1d6332339ecc61275bebd1f7b674098a66fea11a00c84d1c58851e618dc0d \ - --hash=sha256:c030cda3dc8e62b814831faa4eb93dd9a46498af8cd1d5c178c2de856972fd92 \ - --hash=sha256:c2e1f2012e56d61390c0e668c20c4fb0ae667c44d6f6a2eeea5d7148dcd3df9f \ - --hash=sha256:c37c77d6562074452120fc6c02ad86ec928f5710fbc435a181d69334b4de1d84 \ - --hash=sha256:c8149780c60f8fd02752d0429246088c6c04e234b895c4a42e1ea9b4de8d27fb \ - --hash=sha256:cbeeef1dc3c4299bd746b774f019de9e4672f7cc666c777cd5b409f0b746dac7 \ - --hash=sha256:e113878a446c6228669144ae8a56e268c91b7f1fafae927adc4879d9849e0ea7 \ - --hash=sha256:e21162bf941b85c0cda08224dade5def9360f53b09f9f259adb85fc7dd0e7b35 \ - --hash=sha256:fb6934ef4744becbda3143d30c6604718871495a5e36c408431bf33d9c146889 +certifi==2019.9.11 \ + --hash=sha256:e4f3620cfea4f83eedc95b24abd9cd56f3c4b146dd0177e83a21b4eb49e21e50 \ + --hash=sha256:fd7c7c74727ddcf00e9acd26bba8da604ffec95bf1c2144e67aff7a8b50e6cef +cffi==1.13.2 \ + --hash=sha256:0b49274afc941c626b605fb59b59c3485c17dc776dc3cc7cc14aca74cc19cc42 \ + --hash=sha256:0e3ea92942cb1168e38c05c1d56b0527ce31f1a370f6117f1d490b8dcd6b3a04 \ + --hash=sha256:135f69aecbf4517d5b3d6429207b2dff49c876be724ac0c8bf8e1ea99df3d7e5 \ + --hash=sha256:19db0cdd6e516f13329cba4903368bff9bb5a9331d3410b1b448daaadc495e54 \ + --hash=sha256:2781e9ad0e9d47173c0093321bb5435a9dfae0ed6a762aabafa13108f5f7b2ba \ + --hash=sha256:291f7c42e21d72144bb1c1b2e825ec60f46d0a7468f5346841860454c7aa8f57 \ + --hash=sha256:2c5e309ec482556397cb21ede0350c5e82f0eb2621de04b2633588d118da4396 \ + --hash=sha256:2e9c80a8c3344a92cb04661115898a9129c074f7ab82011ef4b612f645939f12 \ + --hash=sha256:32a262e2b90ffcfdd97c7a5e24a6012a43c61f1f5a57789ad80af1d26c6acd97 \ + --hash=sha256:3c9fff570f13480b201e9ab69453108f6d98244a7f495e91b6c654a47486ba43 \ + --hash=sha256:415bdc7ca8c1c634a6d7163d43fb0ea885a07e9618a64bda407e04b04333b7db \ + --hash=sha256:42194f54c11abc8583417a7cf4eaff544ce0de8187abaf5d29029c91b1725ad3 \ + --hash=sha256:4424e42199e86b21fc4db83bd76909a6fc2a2aefb352cb5414833c030f6ed71b \ + --hash=sha256:4a43c91840bda5f55249413037b7a9b79c90b1184ed504883b72c4df70778579 \ + --hash=sha256:599a1e8ff057ac530c9ad1778293c665cb81a791421f46922d80a86473c13346 \ + --hash=sha256:5c4fae4e9cdd18c82ba3a134be256e98dc0596af1e7285a3d2602c97dcfa5159 \ + --hash=sha256:5ecfa867dea6fabe2a58f03ac9186ea64da1386af2159196da51c4904e11d652 \ + --hash=sha256:62f2578358d3a92e4ab2d830cd1c2049c9c0d0e6d3c58322993cc341bdeac22e \ + --hash=sha256:6471a82d5abea994e38d2c2abc77164b4f7fbaaf80261cb98394d5793f11b12a \ + --hash=sha256:6d4f18483d040e18546108eb13b1dfa1000a089bcf8529e30346116ea6240506 \ + --hash=sha256:71a608532ab3bd26223c8d841dde43f3516aa5d2bf37b50ac410bb5e99053e8f \ + --hash=sha256:74a1d8c85fb6ff0b30fbfa8ad0ac23cd601a138f7509dc617ebc65ef305bb98d \ + --hash=sha256:7b93a885bb13073afb0aa73ad82059a4c41f4b7d8eb8368980448b52d4c7dc2c \ + --hash=sha256:7d4751da932caaec419d514eaa4215eaf14b612cff66398dd51129ac22680b20 \ + --hash=sha256:7f627141a26b551bdebbc4855c1157feeef18241b4b8366ed22a5c7d672ef858 \ + --hash=sha256:8169cf44dd8f9071b2b9248c35fc35e8677451c52f795daa2bb4643f32a540bc \ + --hash=sha256:aa00d66c0fab27373ae44ae26a66a9e43ff2a678bf63a9c7c1a9a4d61172827a \ + --hash=sha256:ccb032fda0873254380aa2bfad2582aedc2959186cce61e3a17abc1a55ff89c3 \ + --hash=sha256:d754f39e0d1603b5b24a7f8484b22d2904fa551fe865fd0d4c3332f078d20d4e \ + --hash=sha256:d75c461e20e29afc0aee7172a0950157c704ff0dd51613506bd7d82b718e7410 \ + --hash=sha256:dcd65317dd15bc0451f3e01c80da2216a31916bdcffd6221ca1202d96584aa25 \ + --hash=sha256:e570d3ab32e2c2861c4ebe6ffcad6a8abf9347432a37608fe1fbd157b3f0036b \ + --hash=sha256:fd43a88e045cf992ed09fa724b5315b790525f2676883a6ea64e3263bae6549d chardet==3.0.4 \ --hash=sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae \ --hash=sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691 configobj==5.0.6 \ --hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 -cryptography==2.6.1 \ - --hash=sha256:066f815f1fe46020877c5983a7e747ae140f517f1b09030ec098503575265ce1 \ - --hash=sha256:210210d9df0afba9e000636e97810117dc55b7157c903a55716bb73e3ae07705 \ - --hash=sha256:26c821cbeb683facb966045e2064303029d572a87ee69ca5a1bf54bf55f93ca6 \ - --hash=sha256:2afb83308dc5c5255149ff7d3fb9964f7c9ee3d59b603ec18ccf5b0a8852e2b1 \ - --hash=sha256:2db34e5c45988f36f7a08a7ab2b69638994a8923853dec2d4af121f689c66dc8 \ - --hash=sha256:409c4653e0f719fa78febcb71ac417076ae5e20160aec7270c91d009837b9151 \ - --hash=sha256:45a4f4cf4f4e6a55c8128f8b76b4c057027b27d4c67e3fe157fa02f27e37830d \ - --hash=sha256:48eab46ef38faf1031e58dfcc9c3e71756a1108f4c9c966150b605d4a1a7f659 \ - --hash=sha256:6b9e0ae298ab20d371fc26e2129fd683cfc0cfde4d157c6341722de645146537 \ - --hash=sha256:6c4778afe50f413707f604828c1ad1ff81fadf6c110cb669579dea7e2e98a75e \ - --hash=sha256:8c33fb99025d353c9520141f8bc989c2134a1f76bac6369cea060812f5b5c2bb \ - --hash=sha256:9873a1760a274b620a135054b756f9f218fa61ca030e42df31b409f0fb738b6c \ - --hash=sha256:9b069768c627f3f5623b1cbd3248c5e7e92aec62f4c98827059eed7053138cc9 \ - --hash=sha256:9e4ce27a507e4886efbd3c32d120db5089b906979a4debf1d5939ec01b9dd6c5 \ - --hash=sha256:acb424eaca214cb08735f1a744eceb97d014de6530c1ea23beb86d9c6f13c2ad \ - --hash=sha256:c8181c7d77388fe26ab8418bb088b1a1ef5fde058c6926790c8a0a3d94075a4a \ - --hash=sha256:d4afbb0840f489b60f5a580a41a1b9c3622e08ecb5eec8614d4fb4cd914c4460 \ - --hash=sha256:d9ed28030797c00f4bc43c86bf819266c76a5ea61d006cd4078a93ebf7da6bfd \ - --hash=sha256:e603aa7bb52e4e8ed4119a58a03b60323918467ef209e6ff9db3ac382e5cf2c6 -# Package enum34 needs to be explicitly limited to Python2.x, in order to avoid -# certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. -enum34==1.1.6 ; python_version < '3.4' \ +cryptography==2.8 \ + --hash=sha256:02079a6addc7b5140ba0825f542c0869ff4df9a69c360e339ecead5baefa843c \ + --hash=sha256:1df22371fbf2004c6f64e927668734070a8953362cd8370ddd336774d6743595 \ + --hash=sha256:369d2346db5934345787451504853ad9d342d7f721ae82d098083e1f49a582ad \ + --hash=sha256:3cda1f0ed8747339bbdf71b9f38ca74c7b592f24f65cdb3ab3765e4b02871651 \ + --hash=sha256:44ff04138935882fef7c686878e1c8fd80a723161ad6a98da31e14b7553170c2 \ + --hash=sha256:4b1030728872c59687badcca1e225a9103440e467c17d6d1730ab3d2d64bfeff \ + --hash=sha256:58363dbd966afb4f89b3b11dfb8ff200058fbc3b947507675c19ceb46104b48d \ + --hash=sha256:6ec280fb24d27e3d97aa731e16207d58bd8ae94ef6eab97249a2afe4ba643d42 \ + --hash=sha256:7270a6c29199adc1297776937a05b59720e8a782531f1f122f2eb8467f9aab4d \ + --hash=sha256:73fd30c57fa2d0a1d7a49c561c40c2f79c7d6c374cc7750e9ac7c99176f6428e \ + --hash=sha256:7f09806ed4fbea8f51585231ba742b58cbcfbfe823ea197d8c89a5e433c7e912 \ + --hash=sha256:90df0cc93e1f8d2fba8365fb59a858f51a11a394d64dbf3ef844f783844cc793 \ + --hash=sha256:971221ed40f058f5662a604bd1ae6e4521d84e6cad0b7b170564cc34169c8f13 \ + --hash=sha256:a518c153a2b5ed6b8cc03f7ae79d5ffad7315ad4569b2d5333a13c38d64bd8d7 \ + --hash=sha256:b0de590a8b0979649ebeef8bb9f54394d3a41f66c5584fff4220901739b6b2f0 \ + --hash=sha256:b43f53f29816ba1db8525f006fa6f49292e9b029554b3eb56a189a70f2a40879 \ + --hash=sha256:d31402aad60ed889c7e57934a03477b572a03af7794fa8fb1780f21ea8f6551f \ + --hash=sha256:de96157ec73458a7f14e3d26f17f8128c959084931e8997b9e655a39c8fde9f9 \ + --hash=sha256:df6b4dca2e11865e6cfbfb708e800efb18370f5a46fd601d3755bc7f85b3a8a2 \ + --hash=sha256:ecadccc7ba52193963c0475ac9f6fa28ac01e01349a2ca48509667ef41ffd2cf \ + --hash=sha256:fb81c17e0ebe3358486cd8cc3ad78adbae58af12fc2bf2bc0bb84e8090fa5ce8 +distro==1.4.0 \ + --hash=sha256:362dde65d846d23baee4b5c058c8586f219b5a54be1cf5fc6ff55c4578392f57 \ + --hash=sha256:eedf82a470ebe7d010f1872c17237c79ab04097948800029994fa458e52fb4b4 +enum34==1.1.6 \ --hash=sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850 \ --hash=sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a \ --hash=sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79 \ @@ -76,26 +86,26 @@ enum34==1.1.6 ; python_version < '3.4' \ funcsigs==1.0.2 \ --hash=sha256:330cc27ccbf7f1e992e69fef78261dc7c6569012cf397db8d3de0234e6c937ca \ --hash=sha256:a7bb0f2cf3a3fd1ab2732cb49eba4252c2af4240442415b4abce3b87022a8f50 -future==0.17.1 \ - --hash=sha256:67045236dcfd6816dc439556d009594abf643e5eb48992e36beac09c2ca659b8 +future==0.18.2 \ + --hash=sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d idna==2.8 \ --hash=sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407 \ --hash=sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c -ipaddress==1.0.22 \ - --hash=sha256:64b28eec5e78e7510698f6d4da08800a5c575caa4a286c93d651c5d3ff7b6794 \ - --hash=sha256:b146c751ea45cad6188dd6cf2d9b757f6f4f8d6ffb96a023e6f2e26eea02a72c -josepy==1.1.0 \ - --hash=sha256:1309a25aac3caeff5239729c58ff9b583f7d022ffdb1553406ddfc8e5b52b76e \ - --hash=sha256:fb5c62c77d26e04df29cb5ecd01b9ce69b6fcc9e521eb1ca193b7faa2afa7086 +ipaddress==1.0.23 \ + --hash=sha256:6e0f4a39e66cb5bb9a137b00276a2eff74f93b71dcbdad6f10ff7df9d3557fcc \ + --hash=sha256:b7f8e0369580bb4a24d5ba1d7cc29660a4a6987763faf1d8a8046830e020e7e2 +josepy==1.2.0 \ + --hash=sha256:8ea15573203f28653c00f4ac0142520777b1c59d9eddd8da3f256c6ba3cac916 \ + --hash=sha256:9cec9a839fe9520f0420e4f38e7219525daccce4813296627436fe444cd002d3 mock==1.3.0 \ --hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6 \ --hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb parsedatetime==2.4 \ --hash=sha256:3d817c58fb9570d1eec1dd46fa9448cd644eeed4fb612684b02dfda3a79cb84b \ --hash=sha256:9ee3529454bf35c40a77115f5a596771e59e1aee8c53306f346c461b8e913094 -pbr==5.1.3 \ - --hash=sha256:8257baf496c8522437e8a6cfe0f15e00aedc6c0e0e7c9d55eeeeab31e0853843 \ - --hash=sha256:8c361cc353d988e4f5b998555c88098b9d5964c2e11acf7b0d21925a66bb5824 +pbr==5.4.3 \ + --hash=sha256:2c8e420cd4ed4cec4e7999ee47409e876af575d4c35a45840d59e8b5f3155ab8 \ + --hash=sha256:b32c8ccaac7b1a20c0ce00ce317642e6cf231cf038f9875e0280e28af5bf7ac9 pyOpenSSL==19.0.0 \ --hash=sha256:aeca66338f6de19d1aa46ed634c3b9ae519a64b458f8468aec688e7e3c20f200 \ --hash=sha256:c727930ad54b10fc157015014b666f2d8b41f70c0d03e83ab67624fd3dd5d1e6 @@ -104,32 +114,31 @@ pyRFC3339==1.1 \ --hash=sha256:81b8cbe1519cdb79bed04910dd6fa4e181faf8c88dff1e1b987b5f7ab23a5b1a pycparser==2.19 \ --hash=sha256:a988718abfad80b6b157acce7bf130a30876d27603738ac39f140993246b25b3 -pyparsing==2.3.1 \ - --hash=sha256:66c9268862641abcac4a96ba74506e594c884e3f57690a696d21ad8210ed667a \ - --hash=sha256:f6c5ef0d7480ad048c054c37632c67fca55299990fff127850181659eea33fc3 +pyparsing==2.4.5 \ + --hash=sha256:20f995ecd72f2a1f4bf6b072b63b22e2eb457836601e76d6e5dfcd75436acc1f \ + --hash=sha256:4ca62001be367f01bd3e92ecbb79070272a9d4964dce6a48a82ff0b8bc7e683a python-augeas==0.5.0 \ --hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 -pytz==2018.9 \ - --hash=sha256:32b0891edff07e28efe91284ed9c31e123d84bea3fd98e1f72be2508f43ef8d9 \ - --hash=sha256:d5f05e487007e29e03409f9398d074e158d920d36eb82eaf66fb1136b0c5374c +pytz==2019.3 \ + --hash=sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d \ + --hash=sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be requests==2.21.0 \ --hash=sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e \ --hash=sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b requests-toolbelt==0.9.1 \ --hash=sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f \ --hash=sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0 -six==1.12.0 \ - --hash=sha256:3350809f0555b11f552448330d0b52d5f24c91a322ea4a15ef22629740f3761c \ - --hash=sha256:d16a0141ec1a18405cd4ce8b4613101da75da0e9a7aec5bdd4fa804d0e0eba73 -urllib3==1.24.2 \ - --hash=sha256:4c291ca23bbb55c76518905869ef34bdd5f0e46af7afe6861e8375643ffee1a0 \ - --hash=sha256:9a247273df709c4fedb38c711e44292304f73f39ab01beda9f6b9fc375669ac3 -zope.component==4.5 \ - --hash=sha256:6edfd626c3b593b72895a8cfcf79bff41f4619194ce996a85bce31ac02b94e55 \ - --hash=sha256:984a06ba3def0b02b1117fa4c45b56e772e8c29c0340820fbf367e440a93a3a4 -zope.deferredimport==4.3 \ - --hash=sha256:2ddef5a7ecfff132a2dd796253366ecf9748a446e30f1a0b3a636aec9d9c05c5 \ - --hash=sha256:4aae9cbacb2146cca58e62be0a914f0cec034d3b2d41135ea212ca8a96f4b5ec +six==1.13.0 \ + --hash=sha256:1f1b7d42e254082a9db6279deae68afb421ceba6158efa6131de7b3003ee93fd \ + --hash=sha256:30f610279e8b2578cab6db20741130331735c781b56053c59c4076da27f06b66 +urllib3==1.24.3 \ + --hash=sha256:2393a695cd12afedd0dcb26fe5d50d0cf248e5a66f75dbd89a3d4eb333a61af4 \ + --hash=sha256:a637e5fae88995b256e3409dc4d52c2e2e0ba32c42a6365fee8bbd2238de3cfb +zope.component==4.6 \ + --hash=sha256:ec2afc5bbe611dcace98bb39822c122d44743d635dafc7315b9aef25097db9e6 +zope.deferredimport==4.3.1 \ + --hash=sha256:57b2345e7b5eef47efcd4f634ff16c93e4265de3dcf325afc7315ade48d909e1 \ + --hash=sha256:9a0c211df44aa95f1c4e6d2626f90b400f56989180d3ef96032d708da3d23e0a zope.deprecation==4.4.0 \ --hash=sha256:0d453338f04bacf91bbfba545d8bcdf529aa829e67b705eac8c1a7fdce66e2df \ --hash=sha256:f1480b74995958b24ce37b0ef04d3663d2683e5d6debc96726eff18acf4ea113 @@ -177,15 +186,43 @@ zope.interface==4.6.0 \ --hash=sha256:d788a3999014ddf416f2dc454efa4a5dbeda657c6aba031cf363741273804c6b \ --hash=sha256:eed88ae03e1ef3a75a0e96a55a99d7937ed03e53d0cffc2451c208db445a2966 \ --hash=sha256:f99451f3a579e73b5dd58b1b08d1179791d49084371d9a47baad3b22417f0317 -zope.proxy==4.3.1 \ - --hash=sha256:0cbcfcafaa3b5fde7ba7a7b9a2b5f09af25c9b90087ad65f9e61359fed0ca63b \ - --hash=sha256:3de631dd5054a3a20b9ebff0e375f39c0565f1fb9131200d589a6a8f379214cd \ - --hash=sha256:5429134d04d42262f4dac25f6dea907f6334e9a751ffc62cb1d40226fb52bdeb \ - --hash=sha256:563c2454b2d0f23bca54d2e0e4d781149b7b06cb5df67e253ca3620f37202dd2 \ - --hash=sha256:5bcf773345016b1461bb07f70c635b9386e5eaaa08e37d3939dcdf12d3fdbec5 \ - --hash=sha256:8d84b7aef38c693874e2f2084514522bf73fd720fde0ce2a9352a51315ffa475 \ - --hash=sha256:90de9473c05819b36816b6cb957097f809691836ed3142648bf62da84b4502fe \ - --hash=sha256:dd592a69fe872445542a6e1acbefb8e28cbe6b4007b8f5146da917e49b155cc3 \ - --hash=sha256:e7399ab865399fce322f9cefc6f2f3e4099d087ba581888a9fea1bbe1db42a08 \ - --hash=sha256:e7d1c280d86d72735a420610df592aac72332194e531a8beff43a592c3a1b8eb \ - --hash=sha256:e90243fee902adb0c39eceb3c69995c0f2004bc3fdb482fbf629efc656d124ed +zope.proxy==4.3.3 \ + --hash=sha256:04646ac04ffa9c8e32fb2b5c3cd42995b2548ea14251f3c21ca704afae88e42c \ + --hash=sha256:07b6bceea232559d24358832f1cd2ed344bbf05ca83855a5b9698b5f23c5ed60 \ + --hash=sha256:1ef452cc02e0e2f8e3c917b1a5b936ef3280f2c2ca854ee70ac2164d1655f7e6 \ + --hash=sha256:22bf61857c5977f34d4e391476d40f9a3b8c6ab24fb0cac448d42d8f8b9bf7b2 \ + --hash=sha256:299870e3428cbff1cd9f9b34144e76ecdc1d9e3192a8cf5f1b0258f47a239f58 \ + --hash=sha256:2bfc36bfccbe047671170ea5677efd3d5ab730a55d7e45611d76d495e5b96766 \ + --hash=sha256:32e82d5a640febc688c0789e15ea875bf696a10cf358f049e1ed841f01710a9b \ + --hash=sha256:3b2051bdc4bc3f02fa52483f6381cf40d4d48167645241993f9d7ebbd142ed9b \ + --hash=sha256:3f734bd8a08f5185a64fb6abb8f14dc97ec27a689ca808fb7a83cdd38d745e4f \ + --hash=sha256:3f78dd8de3112df8bbd970f0916ac876dc3fbe63810bd1cf7cc5eec4cbac4f04 \ + --hash=sha256:4eabeb48508953ba1f3590ad0773b8daea9e104eec66d661917e9bbcd7125a67 \ + --hash=sha256:4f05ecc33808187f430f249cb1ccab35c38f570b181f2d380fbe253da94b18d8 \ + --hash=sha256:4f4f4cbf23d3afc1526294a31e7b3eaa0f682cc28ac5366065dc1d6bb18bd7be \ + --hash=sha256:5483d5e70aacd06f0aa3effec9fed597c0b50f45060956eeeb1203c44d4338c3 \ + --hash=sha256:56a5f9b46892b115a75d0a1f2292431ad5988461175826600acc69a24cb3edee \ + --hash=sha256:64bb63af8a06f736927d260efdd4dfc5253d42244f281a8063e4b9eea2ddcbc5 \ + --hash=sha256:653f8cbefcf7c6ac4cece2cdef367c4faa2b7c19795d52bd7cbec11a8739a7c1 \ + --hash=sha256:664211d63306e4bd4eec35bf2b4bd9db61c394037911cf2d1804c43b511a49f1 \ + --hash=sha256:6651e6caed66a8fff0fef1a3e81c0ed2253bf361c0fdc834500488732c5d16e9 \ + --hash=sha256:6c1fba6cdfdf105739d3069cf7b07664f2944d82a8098218ab2300a82d8f40fc \ + --hash=sha256:6e64246e6e9044a4534a69dca1283c6ddab6e757be5e6874f69024329b3aa61f \ + --hash=sha256:838390245c7ec137af4993c0c8052f49d5ec79e422b4451bfa37fee9b9ccaa01 \ + --hash=sha256:856b410a14793069d8ba35f33fff667213ea66f2df25a0024cc72a7493c56d4c \ + --hash=sha256:8b932c364c1d1605a91907a41128ed0ee8a2d326fc0fafb2c55cd46f545f4599 \ + --hash=sha256:9086cf6d20f08dae7f296a78f6c77d1f8d24079d448f023ee0eb329078dd35e1 \ + --hash=sha256:9698533c14afa0548188de4968a7932d1f3f965f3f5ba1474de673596bb875af \ + --hash=sha256:9b12b05dd7c28f5068387c1afee8cb94f9d02501e7ef495a7c5c7e27139b96ad \ + --hash=sha256:a884c7426a5bc6fb7fc71a55ad14e66818e13f05b78b20a6f37175f324b7acb8 \ + --hash=sha256:abe9e7f1a3e76286c5f5baf2bf5162d41dc0310da493b34a2c36555f38d928f7 \ + --hash=sha256:bd6fde63b015a27262be06bd6bbdd895273cc2bdf2d4c7e1c83711d26a8fbace \ + --hash=sha256:bda7c62c954f47b87ed9a89f525eee1b318ec7c2162dfdba76c2ccfa334e0caa \ + --hash=sha256:be8a4908dd3f6e965993c0068b006bdbd0474fbcbd1da4893b49356e73fc1557 \ + --hash=sha256:ced65fc3c7d7205267506d854bb1815bb445899cca9d21d1d4b949070a635546 \ + --hash=sha256:dac4279aa05055d3897ab5e5ee5a7b39db121f91df65a530f8b1ac7f9bd93119 \ + --hash=sha256:e4f1863056e3e4f399c285b67fa816f411a7bfa1c81ef50e186126164e396e59 \ + --hash=sha256:ecd85f68b8cd9ab78a0141e87ea9a53b2f31fd9b1350a1c44da1f7481b5363ef \ + --hash=sha256:ed269b83750413e8fc5c96276372f49ee3fcb7ed61c49fe8e5a67f54459a5a4a \ + --hash=sha256:f19b0b80cba73b204dee68501870b11067711d21d243fb6774256d3ca2e5391f \ + --hash=sha256:ffdafb98db7574f9da84c489a10a5d582079a888cb43c64e9e6b0e3fe1034685 diff --git a/letsencrypt-auto-source/rebuild_dependencies.py b/letsencrypt-auto-source/rebuild_dependencies.py index 7096e226c..a79bdd8aa 100755 --- a/letsencrypt-auto-source/rebuild_dependencies.py +++ b/letsencrypt-auto-source/rebuild_dependencies.py @@ -26,14 +26,14 @@ import argparse # The list of docker distributions to test dependencies against with. DISTRIBUTION_LIST = [ - 'ubuntu:18.04', 'ubuntu:14.04', + 'ubuntu:18.04', 'ubuntu:16.04', 'debian:stretch', 'debian:jessie', 'centos:7', 'centos:6', 'opensuse/leap:15', 'fedora:29', ] -# Theses constraints will be added while gathering dependencies on each distribution. +# These constraints will be added while gathering dependencies on each distribution. # It can be used because a particular version for a package is required for any reason, # or to solve a version conflict between two distributions requirements. AUTHORITATIVE_CONSTRAINTS = { @@ -45,7 +45,13 @@ AUTHORITATIVE_CONSTRAINTS = { # Package enum34 needs to be explicitly limited to Python2.x, in order to avoid # certbot-auto failures on Python 3.6+ which enum34 doesn't support. See #5456. # TODO: hashin seems to overwrite environment markers in dependencies. This needs to be fixed. - 'enum34': '1.1.6 ; python_version < \'3.4\'' + 'enum34': '1.1.6 ; python_version < \'3.4\'', + # Newer versions of the packages below dropped support for python 3.4. Once + # Certbot does as well, we should unpin these dependencies. + 'requests': '2.21.0', + 'ConfigArgParse': '0.14.0', + 'zope.hookable': '4.2.0', + 'zope.interface': '4.6.0', } @@ -59,8 +65,7 @@ CERTBOT_REPO_PATH = dirname(dirname(abspath(__file__))) # without pinned dependencies, and respecting input authoritative requirements # - `certbot plugins` is called to check we have an healthy environment # - finally current set of dependencies is extracted out of the docker using pip freeze -SCRIPT = """\ -#!/bin/sh +SCRIPT = r"""#!/bin/sh set -e cd /tmp/certbot @@ -70,7 +75,7 @@ PYVER=`/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cu /opt/eff.org/certbot/venv/bin/python letsencrypt-auto-source/pieces/create_venv.py /tmp/venv "$PYVER" 1 /tmp/venv/bin/python letsencrypt-auto-source/pieces/pipstrap.py -/tmp/venv/bin/pip install -e acme -e . -e certbot-apache -e certbot-nginx -c /tmp/constraints.txt +/tmp/venv/bin/pip install -e acme -e certbot -e certbot-apache -e certbot-nginx -c /tmp/constraints.txt /tmp/venv/bin/certbot plugins /tmp/venv/bin/pip freeze >> /tmp/workspace/requirements.txt """ diff --git a/letsencrypt-auto-source/version.py b/letsencrypt-auto-source/version.py index c49d96654..d70ffefac 100755 --- a/letsencrypt-auto-source/version.py +++ b/letsencrypt-auto-source/version.py @@ -14,6 +14,7 @@ def certbot_version(build_script_dir): """Return the version number stamped in certbot/__init__.py.""" return re.search('''^__version__ = ['"](.+)['"].*''', file_contents(join(dirname(build_script_dir), + 'certbot', 'certbot', '__init__.py')), re.M).group(1) diff --git a/letshelp-certbot/docs/conf.py b/letshelp-certbot/docs/conf.py index 17d8b3ea9..fcff25d55 100644 --- a/letshelp-certbot/docs/conf.py +++ b/letshelp-certbot/docs/conf.py @@ -12,10 +12,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. -import sys import os import shlex - +import sys here = os.path.abspath(os.path.dirname(__file__)) diff --git a/letshelp-certbot/letshelp_certbot/apache.py b/letshelp-certbot/letshelp_certbot/apache.py index 50f3c5ef6..ebe4e3671 100755 --- a/letshelp-certbot/letshelp_certbot/apache.py +++ b/letshelp-certbot/letshelp_certbot/apache.py @@ -74,7 +74,7 @@ def make_and_verify_selection(server_root, temp_dir): ans = six.moves.input("(Y)es/(N)o: ").lower() if ans.startswith("y"): return - elif ans.startswith("n"): + if ans.startswith("n"): sys.exit("Your files were not submitted") @@ -159,7 +159,7 @@ def safe_config_file(config_file): empty_or_all_comments = False if line.startswith("-----BEGIN"): return False - elif ":" not in line: + 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 diff --git a/letshelp-certbot/letshelp_certbot/apache_test.py b/letshelp-certbot/letshelp_certbot/apache_test.py index a84641bfe..0853046b4 100644 --- a/letshelp-certbot/letshelp_certbot/apache_test.py +++ b/letshelp-certbot/letshelp_certbot/apache_test.py @@ -6,15 +6,14 @@ import subprocess import tarfile import tempfile import unittest -import pkg_resources -import mock # 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( diff --git a/letshelp-certbot/letshelp_certbot/magic_typing.py b/letshelp-certbot/letshelp_certbot/magic_typing.py index 471b8dfa9..5a6358c69 100644 --- a/letshelp-certbot/letshelp_certbot/magic_typing.py +++ b/letshelp-certbot/letshelp_certbot/magic_typing.py @@ -1,6 +1,7 @@ """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): diff --git a/letshelp-certbot/readthedocs.org.requirements.txt b/letshelp-certbot/readthedocs.org.requirements.txt index 7858b312f..b24681caa 100644 --- a/letshelp-certbot/readthedocs.org.requirements.txt +++ b/letshelp-certbot/readthedocs.org.requirements.txt @@ -1,10 +1,10 @@ # readthedocs.org gives no way to change the install command to "pip -# install -e .[docs]" (that would in turn install documentation +# 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 .[docs]" must be used instead +# expected and "pip install -e certbot[docs]" must be used instead -e letshelp-certbot[docs] diff --git a/letshelp-certbot/setup.py b/letshelp-certbot/setup.py index 3e9e31725..af992de16 100644 --- a/letshelp-certbot/setup.py +++ b/letshelp-certbot/setup.py @@ -1,6 +1,5 @@ -from setuptools import setup from setuptools import find_packages - +from setuptools import setup version = '0.7.0.dev0' @@ -36,6 +35,7 @@ setup( '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', diff --git a/linter_plugin.py b/linter_plugin.py index e870fda3a..6be8c2414 100644 --- a/linter_plugin.py +++ b/linter_plugin.py @@ -1,13 +1,14 @@ -"""Certbot PyLint plugin. -http://docs.pylint.org/plugins.html """ -# The built-in ImportChecker of Pylint does a similar job to ForbidStandardOsModule to detect -# deprecated modules. You can check its behavior as a reference to what is coded here. -# See https://github.com/PyCQA/pylint/blob/b20a2984c94e2946669d727dbda78735882bf50a/pylint/checkers/imports.py#L287 +Certbot PyLint plugin. + +The built-in ImportChecker of Pylint does a similar job to ForbidStandardOsModule to detect +deprecated modules. You can check its behavior as a reference to what is coded here. +See https://github.com/PyCQA/pylint/blob/b20a2984c94e2946669d727dbda78735882bf50a/pylint/checkers/imports.py#L287 +See http://docs.pylint.org/plugins.html +""" 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'] diff --git a/pull_request_template.md b/pull_request_template.md index 9ab07e3aa..c806d33e8 100644 --- a/pull_request_template.md +++ b/pull_request_template.md @@ -1,4 +1,4 @@ -Be sure to edit the `master` section of `CHANGELOG.md` to include a description -of the change being made in this PR. +## Pull Request Checklist -You are also welcome to add your name to `AUTHORS.md` if you like. +- [ ] If the change being made is to a [distributed component](https://certbot.eff.org/docs/contributing.html#code-components-and-layout), edit the `master` section of `certbot/CHANGELOG.md` to include a description of the change being made. +- [ ] Include your name in `AUTHORS.md` if you like. diff --git a/pytest.ini b/pytest.ini index 2531e50d2..6c2404056 100644 --- a/pytest.ini +++ b/pytest.ini @@ -13,6 +13,6 @@ filterwarnings = error ignore:decodestring:DeprecationWarning - ignore:(TLSSNI01|TLS-SNI-01):DeprecationWarning ignore:.*collections\.abc:DeprecationWarning ignore:The `color_scheme` argument is deprecated:DeprecationWarning:IPython.* + ignore:.*get_systemd_os_info:DeprecationWarning diff --git a/readthedocs.org.requirements.txt b/readthedocs.org.requirements.txt deleted file mode 100644 index 94a81e788..000000000 --- a/readthedocs.org.requirements.txt +++ /dev/null @@ -1,11 +0,0 @@ -# readthedocs.org gives no way to change the install command to "pip -# install -e .[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 .[docs]" must be used instead - --e acme --e .[docs] diff --git a/tests/letstest/apache2_targets.yaml b/tests/letstest/apache2_targets.yaml index 5ee2632a0..1450a8578 100644 --- a/tests/letstest/apache2_targets.yaml +++ b/tests/letstest/apache2_targets.yaml @@ -16,23 +16,24 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-7b89cc11 - name: ubuntu14.04LTS - type: ubuntu - virt: hvm - user: ubuntu - - ami: ami-9295d0f8 - name: ubuntu14.04LTS_32bit - type: ubuntu - virt: pv - user: ubuntu #----------------------------------------------------------------------------- # Debian + - ami: ami-01db78123b2b99496 + name: debian10 + type: ubuntu + virt: hvm + user: admin - ami: ami-003f19e0e687de1cd name: debian9 type: ubuntu virt: hvm user: admin + - ami: ami-0ed54dd1b25657636 + name: debian9_arm64 + type: ubuntu + virt: hvm + user: admin + machine_type: a1.medium - ami: ami-077bf3962f29d3fa4 name: debian8.1 type: ubuntu diff --git a/tests/letstest/multitester.py b/tests/letstest/multitester.py index 7bc4e034d..9ea9fe76b 100644 --- a/tests/letstest/multitester.py +++ b/tests/letstest/multitester.py @@ -32,17 +32,31 @@ see: from __future__ import print_function from __future__ import with_statement -import sys, os, time, argparse, socket, traceback +import argparse import multiprocessing as mp from multiprocessing import Manager +import os +import socket +import sys +import time +import traceback import urllib2 -import yaml + import boto3 from botocore.exceptions import ClientError +import yaml + import fabric -from fabric.api import run, execute, local, env, sudo, cd, lcd -from fabric.operations import get, put +from fabric.api import cd +from fabric.api import env +from fabric.api import execute +from fabric.api import lcd +from fabric.api import local +from fabric.api import run +from fabric.api import sudo from fabric.context_managers import shell_env +from fabric.operations import get +from fabric.operations import put # Command line parser #------------------------------------------------------------------------------- @@ -84,9 +98,6 @@ parser.add_argument('--killboulder', parser.add_argument('--boulderonly', action='store_true', help="only make a boulder server") -parser.add_argument('--fast', - action='store_true', - help="use larger instance types to run faster (saves about a minute, probably not worth it)") cl_args = parser.parse_args() # Credential Variables @@ -307,11 +318,13 @@ def grab_certbot_log(): def create_client_instance(ec2_client, target, security_group_id, subnet_id): """Create a single client instance for running tests.""" - if target['virt'] == 'hvm': - machine_type = 't2.medium' if cl_args.fast else 't2.micro' + if 'machine_type' in target: + machine_type = target['machine_type'] + elif target['virt'] == 'hvm': + machine_type = 't2.medium' else: # 32 bit systems - machine_type = 'c1.medium' if cl_args.fast else 't1.micro' + machine_type = 'c1.medium' if 'userdata' in target.keys(): userdata = target['userdata'] else: @@ -373,7 +386,7 @@ def cleanup(cl_args, instances, targetlist): # If lengths of instances and targetlist aren't equal, instances failed to # start before running tests so leaving instances running for debugging # isn't very useful. Let's cleanup after ourselves instead. - if len(instances) == len(targetlist) or not cl_args.saveinstances: + if len(instances) != len(targetlist) or not cl_args.saveinstances: print('Terminating EC2 Instances') if cl_args.killboulder: boulder_server.terminate() diff --git a/tests/letstest/scripts/boulder_install.sh b/tests/letstest/scripts/boulder_install.sh index f997268bd..5161de374 100755 --- a/tests/letstest/scripts/boulder_install.sh +++ b/tests/letstest/scripts/boulder_install.sh @@ -1,7 +1,5 @@ #!/bin/bash -x -# >>>> only tested on Ubuntu 14.04LTS <<<< - # Check out special branch until latest docker changes land in Boulder master. git clone -b docker-integration https://github.com/letsencrypt/boulder $BOULDERPATH cd $BOULDERPATH diff --git a/tests/letstest/scripts/test_apache2.sh b/tests/letstest/scripts/test_apache2.sh index 007ab720e..9af39e8bb 100755 --- a/tests/letstest/scripts/test_apache2.sh +++ b/tests/letstest/scripts/test_apache2.sh @@ -50,8 +50,8 @@ fi # instance, Fedora uses Python 3 and Python 2 is not installed. . tests/letstest/scripts/set_python_envvars.sh -"$VENV_SCRIPT" -e acme[dev] -e .[dev,docs] -e certbot-apache -sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-dev-preview --agree-tos \ +"$VENV_SCRIPT" -e acme[dev] -e certbot[dev,docs] -e certbot-apache +sudo "$VENV_PATH/bin/certbot" -v --debug --text --agree-tos \ --renew-by-default --redirect --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL if [ $? -ne 0 ] ; then diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 49606b49c..541f54f6b 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -23,9 +23,10 @@ if command -v python && [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | se INITIAL_VERSION="0.20.0" RUN_RHEL6_TESTS=1 else - # 0.33.x is the oldest version of letsencrypt-auto that works on Fedora 29+. - INITIAL_VERSION="0.33.1" + # 0.37.x is the oldest version of letsencrypt-auto that works on RHEL 8. + INITIAL_VERSION="0.37.1" fi + git checkout -f "v$INITIAL_VERSION" letsencrypt-auto if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail diff --git a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh index eb63b9ca7..c028031c7 100755 --- a/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh +++ b/tests/letstest/scripts/test_letsencrypt_auto_certonly_standalone.sh @@ -32,7 +32,7 @@ mkdir -p "$OLD_VENV_BIN" touch "$OLD_VENV_BIN/letsencrypt" letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \ - --text --agree-dev-preview --agree-tos \ + --text --agree-tos \ --renew-by-default --redirect \ --register-unsafely-without-email \ --domain $PUBLIC_HOSTNAME --server $BOULDER_URL diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index e48e95848..dc024c567 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -1,7 +1,7 @@ #!/bin/sh -xe cd letsencrypt -./certbot-auto --install-only -n --debug +letsencrypt-auto-source/letsencrypt-auto --install-only -n --debug PLUGINS="certbot-apache certbot-nginx" PYTHON_MAJOR_VERSION=$(/opt/eff.org/certbot/venv/bin/python --version 2>&1 | cut -d" " -f 2 | cut -d. -f1) @@ -27,7 +27,7 @@ VERSION=$("$PYTHON_NAME" letsencrypt-auto-source/version.py) tools/pip_install.py pytest # build sdists -for pkg_dir in acme . $PLUGINS; do +for pkg_dir in acme certbot $PLUGINS; do cd $pkg_dir python setup.py clean rm -rf build dist diff --git a/tests/letstest/scripts/test_tests.sh b/tests/letstest/scripts/test_tests.sh index 77ef44270..fb86ce4cd 100755 --- a/tests/letstest/scripts/test_tests.sh +++ b/tests/letstest/scripts/test_tests.sh @@ -7,7 +7,7 @@ REPO_ROOT="letsencrypt" LE_AUTO="$REPO_ROOT/letsencrypt-auto-source/letsencrypt-auto" LE_AUTO="$LE_AUTO --debug --no-self-upgrade --non-interactive" -MODULES="acme certbot certbot_apache certbot_nginx" +MODULES="acme certbot certbot-apache certbot-nginx" PIP_INSTALL="$REPO_ROOT/tools/pip_install.py" VENV_NAME=venv @@ -17,10 +17,13 @@ LE_AUTO_SUDO="" VENV_PATH="$VENV_NAME" $LE_AUTO --no-bootstrap --version . $VENV_NAME/bin/activate "$PIP_INSTALL" pytest -# change to an empty directory to ensure CWD doesn't affect tests -cd $(mktemp -d) +# To run tests that aren't packaged in modules, run pytest +# from the repo root. The directory structure should still +# cause the installed packages to be tested while using +# the tests available in the subdirectories. +cd $REPO_ROOT for module in $MODULES ; do echo testing $module - pytest -v --pyargs $module + pytest -v $module done diff --git a/tests/letstest/targets.yaml b/tests/letstest/targets.yaml index 547c33ffa..188be8e24 100644 --- a/tests/letstest/targets.yaml +++ b/tests/letstest/targets.yaml @@ -16,23 +16,24 @@ targets: type: ubuntu virt: hvm user: ubuntu - - ami: ami-7b89cc11 - name: ubuntu14.04LTS - type: ubuntu - virt: hvm - user: ubuntu - - ami: ami-9295d0f8 - name: ubuntu14.04LTS_32bit - type: ubuntu - virt: pv - user: ubuntu #----------------------------------------------------------------------------- # Debian + - ami: ami-01db78123b2b99496 + name: debian10 + type: ubuntu + virt: hvm + user: admin - ami: ami-003f19e0e687de1cd name: debian9 type: ubuntu virt: hvm user: admin + - ami: ami-0ed54dd1b25657636 + name: debian9_arm64 + type: ubuntu + virt: hvm + user: admin + machine_type: a1.medium - ami: ami-077bf3962f29d3fa4 name: debian8.1 type: ubuntu @@ -44,11 +45,16 @@ targets: # - [ apt-get, install, -y, curl ] #----------------------------------------------------------------------------- # Other Redhat Distros - - ami: ami-a8d369c0 + - ami: ami-0916c408cb02e310b name: RHEL7 type: centos virt: hvm user: ec2-user + - ami: ami-0c322300a1dd5dc79 + name: RHEL8 + type: centos + virt: hvm + user: ec2-user - ami: ami-00bbc6858140f19ed name: fedora30 type: centos diff --git a/tests/lock_test.py b/tests/lock_test.py index aaa8ce2d9..29a77ae17 100644 --- a/tests/lock_test.py +++ b/tests/lock_test.py @@ -15,16 +15,16 @@ import tempfile from cryptography import x509 from cryptography.hazmat.backends import default_backend # TODO: once mypy has cryptography types bundled, type: ignore can be removed. -# See https://github.com/python/typeshed/tree/master/third_party/2/cryptography -from cryptography.hazmat.primitives import serialization, hashes # type: ignore +# See https://github.com/pyca/cryptography/issues/4275 +from cryptography.hazmat.primitives import hashes # type: ignore +from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import rsa -from certbot import lock from certbot import util - +from certbot._internal import lock +from certbot.compat import filesystem from certbot.tests import util as test_util - logger = logging.getLogger(__name__) @@ -92,7 +92,7 @@ def set_up_dirs(): nginx_dir = os.path.join(temp_dir, 'nginx') for directory in (config_dir, logs_dir, work_dir, nginx_dir,): - os.mkdir(directory) + filesystem.mkdir(directory) test_util.make_lineage(config_dir, 'sample-renewal.conf') set_up_nginx_dir(nginx_dir) @@ -173,7 +173,7 @@ def setup_certificate(workspace): key_path = os.path.join(workspace, 'cert.key') with open(key_path, 'wb') as file_handle: - file_handle.write(private_key.private_bytes( + file_handle.write(private_key.private_bytes( # type: ignore encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption() diff --git a/tests/modification-check.py b/tests/modification-check.py index 8abc0fbfe..811f369d4 100755 --- a/tests/modification-check.py +++ b/tests/modification-check.py @@ -3,10 +3,11 @@ from __future__ import print_function import os +import shutil import subprocess import sys import tempfile -import shutil + try: from urllib.request import urlretrieve except ImportError: diff --git a/tools/_release.sh b/tools/_release.sh index e228bae99..89f2a3737 100755 --- a/tools/_release.sh +++ b/tools/_release.sh @@ -30,7 +30,6 @@ SUBPKGS_NOT_IN_AUTO="certbot-dns-cloudflare certbot-dns-cloudxns certbot-dns-dig SUBPKGS_IN_AUTO="certbot $SUBPKGS_IN_AUTO_NO_CERTBOT" SUBPKGS_NO_CERTBOT="$SUBPKGS_IN_AUTO_NO_CERTBOT $SUBPKGS_NOT_IN_AUTO" SUBPKGS="$SUBPKGS_IN_AUTO $SUBPKGS_NOT_IN_AUTO" -subpkgs_modules="$(echo $SUBPKGS | sed s/-/_/g)" # certbot_compatibility_test is not packaged because: # - it is not meant to be used by anyone else than Certbot devs # - it causes problems when running pytest - the latter tries to @@ -66,12 +65,12 @@ fi git checkout "$RELEASE_BRANCH" # Update changelog -sed -i "s/master/$(date +'%Y-%m-%d')/" CHANGELOG.md -git add CHANGELOG.md +sed -i "s/master/$(date +'%Y-%m-%d')/" certbot/CHANGELOG.md +git add certbot/CHANGELOG.md git diff --cached git commit -m "Update changelog for $version release" -for pkg_dir in $SUBPKGS_NO_CERTBOT certbot-compatibility-test . +for pkg_dir in $SUBPKGS certbot-compatibility-test do sed -i 's/\.dev0//' "$pkg_dir/setup.py" git add "$pkg_dir/setup.py" @@ -79,8 +78,8 @@ do if [ -f "$pkg_dir/local-oldest-requirements.txt" ]; then sed -i "s/-e acme\[dev\]/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" sed -i "s/-e acme/acme[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \.\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" - sed -i "s/-e \./certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e certbot\[dev\]/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" + sed -i "s/-e certbot/certbot[dev]==$version/" "$pkg_dir/local-oldest-requirements.txt" git add "$pkg_dir/local-oldest-requirements.txt" fi done @@ -97,7 +96,7 @@ SetVersion() { fi sed -i "s/^version.*/version = '$ver'/" $pkg_dir/setup.py done - init_file="certbot/__init__.py" + init_file="certbot/certbot/__init__.py" if [ $(grep -c '^__version' "$init_file") != 1 ]; then echo "Unexpected count of __version variables in $init_file" exit 1 @@ -113,7 +112,7 @@ SetVersion "$version" # conditionals like the one found in certbot-dns-dnsimple's setup.py file. unset CERTBOT_OLDEST echo "Preparing sdists and wheels" -for pkg_dir in . $SUBPKGS_NO_CERTBOT +for pkg_dir in $SUBPKGS do cd $pkg_dir @@ -133,8 +132,7 @@ done mkdir "dist.$version" -mv dist "dist.$version/certbot" -for pkg_dir in $SUBPKGS_NO_CERTBOT +for pkg_dir in $SUBPKGS do mv $pkg_dir/dist "dist.$version/$pkg_dir/" done @@ -163,7 +161,7 @@ cd ~- # get a snapshot of the CLI help for the docs # We set CERTBOT_DOCS to use dummy values in example user-agent string. -CERTBOT_DOCS=1 certbot --help all > docs/cli-help.txt +CERTBOT_DOCS=1 certbot --help all > certbot/docs/cli-help.txt jws --help > acme/docs/jws-help.txt cd .. @@ -177,12 +175,12 @@ mkdir kgs kgs="kgs/$version" pip freeze | tee $kgs python ../tools/pip_install.py pytest -for module in $subpkgs_modules ; do +cd ~- +for module in $SUBPKGS ; do echo testing $module # use an empty configuration file rather than the one in the repo root - pytest -c <(echo '') --pyargs $module + pytest -c <(echo '') $module done -cd ~- # pin pip hashes of the things we just built for pkg in $SUBPKGS_IN_AUTO ; do @@ -231,7 +229,7 @@ mv letsencrypt-auto-source/letsencrypt-auto.asc letsencrypt-auto-source/certbot- cp -p letsencrypt-auto-source/letsencrypt-auto certbot-auto cp -p letsencrypt-auto-source/letsencrypt-auto letsencrypt-auto -git add certbot-auto letsencrypt-auto letsencrypt-auto-source docs/cli-help.txt +git add certbot-auto letsencrypt-auto letsencrypt-auto-source certbot/docs/cli-help.txt git diff --cached while ! git commit --gpg-sign="$RELEASE_GPG_KEY" -m "Release $version"; do echo "Unable to sign the release commit using git." @@ -251,17 +249,17 @@ echo gpg2 -U $RELEASE_GPG_KEY --detach-sign --armor $name.$rev.tar.xz cd ~- # Add master section to CHANGELOG.md -header=$(head -n 4 CHANGELOG.md) +header=$(head -n 4 certbot/CHANGELOG.md) body=$(sed s/nextversion/$nextversion/ tools/_changelog_top.txt) -footer=$(tail -n +5 CHANGELOG.md) +footer=$(tail -n +5 certbot/CHANGELOG.md) echo "$header $body -$footer" > CHANGELOG.md -git add CHANGELOG.md +$footer" > certbot/CHANGELOG.md +git add certbot/CHANGELOG.md git diff --cached -git commit -m "Add contents to CHANGELOG.md for next version" +git commit -m "Add contents to certbot/CHANGELOG.md for next version" echo "New root: $root" echo "Test commands (in the letstest repo):" diff --git a/tools/_venv_common.py b/tools/_venv_common.py index ac3b5f98d..c61385054 100644 --- a/tools/_venv_common.py +++ b/tools/_venv_common.py @@ -12,17 +12,17 @@ VENV_NAME. from __future__ import print_function -import os -import shutil import glob -import time +import os +import re +import shutil import subprocess import sys -import re +import time REQUIREMENTS = [ '-e acme[dev]', - '-e .[dev,docs]', + '-e certbot[dev,docs]', '-e certbot-apache', '-e certbot-dns-cloudflare', '-e certbot-dns-cloudxns', @@ -177,12 +177,9 @@ def prepare_venv_path(venv_name): return venv_name -def install_packages(venv_name, pip_args=None): +def install_packages(venv_name, pip_args): """Installs packages in the given venv. - If pip_args is given, they are the arguments given to pip, - otherwise, REQUIREMENTS is used. - :param str venv_name: The name or path at where the virtual environment should be created. :param pip_args: Command line arguments that should be given to @@ -190,9 +187,6 @@ def install_packages(venv_name, pip_args=None): :type pip_args: `list` of `str` """ - if not pip_args: - pip_args = REQUIREMENTS - # Using the python executable from venv, we ensure to execute following commands in this venv. py_venv = get_venv_python_path(venv_name) subprocess_with_print([py_venv, os.path.abspath('letsencrypt-auto-source/pieces/pipstrap.py')]) diff --git a/tools/deactivate.py b/tools/deactivate.py index d43b84552..10c9ecd35 100644 --- a/tools/deactivate.py +++ b/tools/deactivate.py @@ -16,8 +16,8 @@ import os import sys from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.asymmetric import rsa from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa import josepy as jose from acme import client as acme_client diff --git a/tools/deps.sh b/tools/deps.sh deleted file mode 100755 index e12f201a5..000000000 --- a/tools/deps.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/sh -# -# Find all Python imports. -# -# ./tools/deps.sh certbot -# ./tools/deps.sh acme -# ./tools/deps.sh certbot-apache -# ... -# -# Manually compare the output with deps in setup.py. - -git grep -h -E '^(import|from.*import)' $1/ | \ - awk '{print $2}' | \ - grep -vE "^$1" | \ - sort -u diff --git a/tools/dev_constraints.txt b/tools/dev_constraints.txt index cc0e54185..d5d78c96a 100644 --- a/tools/dev_constraints.txt +++ b/tools/dev_constraints.txt @@ -4,54 +4,77 @@ # files during tests (eg. letsencrypt-auto-source/pieces/dependency-requirements.txt). alabaster==0.7.10 apipkg==1.4 +appnope==0.1.0 asn1crypto==0.22.0 -astroid==1.6.5 +astroid==2.3.3 attrs==17.3.0 Babel==2.5.1 backports.functools-lru-cache==1.5 backports.shutil-get-terminal-size==1.0.0 +backports.ssl-match-hostname==3.7.0.1 +bcrypt==3.1.6 boto3==1.9.36 botocore==1.12.36 +cached-property==1.5.1 cloudflare==1.5.1 +codecov==2.0.15 configparser==3.7.4 -coverage==4.4.2 +contextlib2==0.6.0.post1 +coverage==4.5.4 decorator==4.1.2 dns-lexicon==3.2.1 dnspython==1.15.0 +docker==3.7.2 +docker-compose==1.25.0 +docker-pycreds==0.4.0 +dockerpty==0.4.1 +docopt==0.6.2 docutils==0.12 execnet==1.5.0 +functools32==3.2.3.post2 future==0.16.0 futures==3.1.1 -google-api-python-client==1.5 +filelock==3.0.12 +google-api-python-client==1.5.5 httplib2==0.10.3 imagesize==0.7.1 +importlib-metadata==0.23 ipdb==0.10.2 ipython==5.5.0 ipython-genutils==0.2.0 -isort==4.2.5 +isort==4.3.21 Jinja2==2.9.6 jmespath==0.9.3 josepy==1.1.0 -lazy-object-proxy==1.3.1 +jsonschema==2.6.0 +lazy-object-proxy==1.4.3 logger==1.4 logilab-common==1.4.1 MarkupSafe==1.0 mccabe==0.6.1 -mypy==0.600 +more-itertools==5.0.0 +mypy==0.710 +mypy-extensions==0.4.3 ndg-httpsclient==0.3.2 -oauth2client==2.0.0 +oauth2client==4.0.0 +packaging==19.2 +paramiko==2.4.2 pathlib2==2.3.0 -pexpect==4.2.1 +pexpect==4.7.0 pickleshare==0.7.4 pkginfo==1.4.2 -pluggy==0.5.2 +pluggy==0.13.0 prompt-toolkit==1.0.15 -ptyprocess==0.5.2 -py==1.4.34 +ptyprocess==0.6.0 +py==1.8.0 pyasn1==0.1.9 pyasn1-modules==0.0.10 Pygments==2.2.0 -pylint==1.9.4 +pylint==2.4.3 +# If pynsist version is upgraded, our NSIS template windows-installer/template.nsi +# must be upgraded if necessary using the new built-in one from pynsist. +pynacl==1.3.0 +pynsist==2.4 pytest==3.2.5 pytest-cov==2.5.1 pytest-forked==0.2 @@ -60,6 +83,7 @@ pytest-sugar==0.9.2 pytest-rerunfailures==4.2 python-dateutil==2.6.1 python-digitalocean==1.11 +pywin32==227 PyYAML==3.13 repoze.sphinx.autointerface==0.8 requests-file==1.4.2 @@ -73,14 +97,18 @@ snowballstemmer==1.2.1 Sphinx==1.7.5 sphinx-rtd-theme==0.2.4 sphinxcontrib-websupport==1.0.1 +texttable==0.9.1 tldextract==2.2.0 -tox==2.9.1 +toml==0.10.0 +tox==3.14.0 tqdm==4.19.4 traitlets==4.3.2 twine==1.11.0 -typed-ast==1.1.0 +typed-ast==1.4.0 typing==3.6.4 -uritemplate==0.6 -virtualenv==15.1.0 +uritemplate==3.0.0 +virtualenv==16.6.2 wcwidth==0.1.7 -wrapt==1.11.1 +websocket-client==0.56.0 +wrapt==1.11.2 +zipp==0.6.0 diff --git a/tools/docker-warning.sh b/tools/docker-warning.sh deleted file mode 100755 index e4f5f40ee..000000000 --- a/tools/docker-warning.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -e -echo "Warning: This Docker image will soon be switching to Alpine Linux." >&2 -echo "You can switch now using the certbot/certbot repo on Docker Hub." >&2 -exec /opt/certbot/venv/bin/certbot $@ diff --git a/tools/extract_changelog.py b/tools/extract_changelog.py new file mode 100755 index 000000000..fb0b849aa --- /dev/null +++ b/tools/extract_changelog.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python +from __future__ import print_function + +import os +import re +import sys + +CERTBOT_ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) + +NEW_SECTION_PATTERN = re.compile(r'^##\s*[\d.]+\s*-\s*[\d-]+$') + + +def main(): + version = sys.argv[1] + + section_pattern = re.compile(r'^##\s*{0}\s*-\s*[\d-]+$' + .format(version.replace('.', '\\.'))) + + with open(os.path.join(CERTBOT_ROOT, 'certbot', 'CHANGELOG.md')) as file_h: + lines = file_h.read().splitlines() + + changelog = [] + + i = 0 + while i < len(lines): + if section_pattern.match(lines[i]): + i = i + 1 + while i < len(lines): + if NEW_SECTION_PATTERN.match(lines[i]): + break + changelog.append(lines[i]) + i = i + 1 + i = i + 1 + + changelog = [entry for entry in changelog if entry] + + print('\n'.join(changelog)) + + +if __name__ == '__main__': + main() diff --git a/tools/install_and_test.py b/tools/install_and_test.py index 6987cf2b1..192708957 100755 --- a/tools/install_and_test.py +++ b/tools/install_and_test.py @@ -8,18 +8,16 @@ from __future__ import print_function import os -import sys -import tempfile -import shutil -import subprocess import re +import subprocess +import sys SKIP_PROJECTS_ON_WINDOWS = ['certbot-apache', 'letshelp-certbot'] -def call_with_print(command, cwd=None): +def call_with_print(command): print(command) - subprocess.check_call(command, shell=True, cwd=cwd or os.getcwd()) + subprocess.check_call(command, shell=True) def main(args): @@ -41,16 +39,8 @@ def main(args): call_with_print(' '.join(current_command)) pkg = re.sub(r'\[\w+\]', '', requirement) - if pkg == '.': - pkg = 'certbot' - - temp_cwd = tempfile.mkdtemp() - shutil.copy2("pytest.ini", temp_cwd) - try: - call_with_print(' '.join([ - sys.executable, '-m', 'pytest', '--pyargs', pkg.replace('-', '_')]), cwd=temp_cwd) - finally: - shutil.rmtree(temp_cwd) + call_with_print(' '.join([ + sys.executable, '-m', 'pytest', pkg])) if __name__ == '__main__': main(sys.argv[1:]) diff --git a/tools/oldest_constraints.txt b/tools/oldest_constraints.txt index e48d6b13c..c5a5c5aa0 100644 --- a/tools/oldest_constraints.txt +++ b/tools/oldest_constraints.txt @@ -16,6 +16,7 @@ pyOpenSSL==0.13.1 pyparsing==1.5.6 pyRFC3339==1.0 python-augeas==0.5.0 +oauth2client==4.0.0 six==1.9.0 # setuptools 0.9.8 is the actual version packaged, but some other dependencies # in this file require setuptools>=1.0 and there are no relevant changes for us @@ -35,11 +36,12 @@ idna==2.0 pbr==1.8.0 pytz==2012rc0 +# Debian Buster constraints +google-api-python-client==1.5.5 + # Our setup.py constraints cloudflare==1.5.1 cryptography==1.2.3 -google-api-python-client==1.5 -oauth2client==2.0 parsedatetime==1.3 pyparsing==1.5.5 python-digitalocean==1.11 @@ -51,6 +53,7 @@ funcsigs==0.4 zope.hookable==4.0.4 # Ubuntu Bionic constraints. +distro==1.0.1 # Lexicon oldest constraint is overridden appropriately on relevant DNS provider plugins # using their local-oldest-requirements.txt dns-lexicon==2.2.1 diff --git a/tools/pip_install.py b/tools/pip_install.py index cf0a7aee5..0a3961384 100755 --- a/tools/pip_install.py +++ b/tools/pip_install.py @@ -8,13 +8,14 @@ # CERTBOT_OLDEST is set, this script must be run with `-e ` and # no other arguments. -from __future__ import print_function, absolute_import +from __future__ import absolute_import +from __future__ import print_function -import subprocess import os -import sys import re import shutil +import subprocess +import sys import tempfile import merge_requirements as merge_module @@ -69,9 +70,9 @@ def merge_requirements(tools_path, requirements, test_constraints, all_constrain fd.write(merged_requirements) -def call_with_print(command, cwd=None): +def call_with_print(command): print(command) - subprocess.check_call(command, shell=True, cwd=cwd or os.getcwd()) + subprocess.check_call(command, shell=True) def pip_install_with_print(args_str): diff --git a/tools/pip_install_editable.py b/tools/pip_install_editable.py index 8eaf3a9fa..3f7c02ba9 100755 --- a/tools/pip_install_editable.py +++ b/tools/pip_install_editable.py @@ -8,6 +8,7 @@ import sys import pip_install + def main(args): new_args = [] for arg in args: diff --git a/tools/readlink.py b/tools/readlink.py index 0199ce184..446c8ebdc 100755 --- a/tools/readlink.py +++ b/tools/readlink.py @@ -11,6 +11,7 @@ from __future__ import print_function import os import sys + def main(link): return os.path.realpath(link) diff --git a/tools/simple_http_server.py b/tools/simple_http_server.py index 233aa6bd3..24c55962d 100755 --- a/tools/simple_http_server.py +++ b/tools/simple_http_server.py @@ -1,6 +1,7 @@ #!/usr/bin/env python """A version of Python's SimpleHTTPServer that flushes its output.""" import sys + try: from http.server import HTTPServer, SimpleHTTPRequestHandler except ImportError: diff --git a/tools/venv.py b/tools/venv.py index 4e205c3a5..f99386eff 100755 --- a/tools/venv.py +++ b/tools/venv.py @@ -5,6 +5,7 @@ import sys import _venv_common + def create_venv(venv_path): """Create a Python 2 virtual environment at venv_path. @@ -25,6 +26,10 @@ def main(pip_args=None): venv_path = _venv_common.prepare_venv_path('venv') create_venv(venv_path) + + if not pip_args: + pip_args = _venv_common.REQUIREMENTS + _venv_common.install_packages(venv_path, pip_args) diff --git a/tools/venv3.py b/tools/venv3.py index dc56a322e..7ead82bd5 100755 --- a/tools/venv3.py +++ b/tools/venv3.py @@ -19,6 +19,10 @@ def create_venv(venv_path): def main(pip_args=None): venv_path = _venv_common.prepare_venv_path('venv3') create_venv(venv_path) + + if not pip_args: + pip_args = _venv_common.REQUIREMENTS + ['-e certbot[dev3]'] + _venv_common.install_packages(venv_path, pip_args) diff --git a/tox.cover.py b/tox.cover.py index 0a94cb73f..0ef5c0d07 100755 --- a/tox.cover.py +++ b/tox.cover.py @@ -1,7 +1,7 @@ #!/usr/bin/env python import argparse -import subprocess import os +import subprocess import sys DEFAULT_PACKAGES = [ @@ -12,11 +12,11 @@ DEFAULT_PACKAGES = [ 'certbot_dns_sakuracloud', 'certbot_nginx', 'letshelp_certbot'] COVER_THRESHOLDS = { - 'certbot': {'linux': 97, 'windows': 96}, + 'certbot': {'linux': 96, 'windows': 96}, 'acme': {'linux': 100, 'windows': 99}, 'certbot_apache': {'linux': 100, 'windows': 100}, 'certbot_dns_cloudflare': {'linux': 98, 'windows': 98}, - 'certbot_dns_cloudxns': {'linux': 99, 'windows': 99}, + 'certbot_dns_cloudxns': {'linux': 98, 'windows': 98}, 'certbot_dns_digitalocean': {'linux': 98, 'windows': 98}, 'certbot_dns_dnsimple': {'linux': 98, 'windows': 98}, 'certbot_dns_dnsmadeeasy': {'linux': 99, 'windows': 99}, @@ -47,8 +47,8 @@ def cover(package): .format(pkg_dir))) return - subprocess.check_call([sys.executable, '-m', 'pytest', '--pyargs', - '--cov', pkg_dir, '--cov-append', '--cov-report=', package]) + subprocess.check_call([sys.executable, '-m', 'pytest', + '--cov', pkg_dir, '--cov-append', '--cov-report=', pkg_dir]) subprocess.check_call([ sys.executable, '-m', 'coverage', 'report', '--fail-under', str(threshold), '--include', '{0}/*'.format(pkg_dir), '--show-missing']) @@ -56,9 +56,9 @@ def cover(package): def main(): description = """ -This script is used by tox.ini (and thus by Travis CI and AppVeyor) in order -to generate separate stats for each package. It should be removed once those -packages are moved to a separate repo. +This script is used by tox.ini (and thus by Travis CI and Azure Pipelines) in +order to generate separate stats for each package. It should be removed once +those packages are moved to a separate repo. Option -e makes sure we fail fast and don't submit to codecov.""" parser = argparse.ArgumentParser(description=description) diff --git a/tox.ini b/tox.ini index b86a840ec..5f1a9a426 100644 --- a/tox.ini +++ b/tox.ini @@ -31,7 +31,7 @@ dns_packages = certbot-dns-sakuracloud all_packages = acme[dev] \ - .[dev] \ + certbot[dev] \ certbot-apache \ {[base]dns_packages} \ certbot-nginx \ @@ -40,7 +40,7 @@ install_packages = python {toxinidir}/tools/pip_install_editable.py {[base]all_packages} source_paths = acme/acme - certbot + certbot/certbot certbot-apache/certbot_apache certbot-compatibility-test/certbot_compatibility_test certbot-dns-cloudflare/certbot_dns_cloudflare @@ -92,7 +92,7 @@ setenv = [testenv:py27-certbot-oldest] commands = - {[base]install_and_test} .[dev] + {[base]install_and_test} certbot[dev] setenv = {[testenv:py27-oldest]setenv} @@ -122,36 +122,37 @@ commands = python tox.cover.py [testenv:lint] -basepython = python2.7 +basepython = python3 # separating into multiple invocations disables cross package # duplicate code checking; if one of the commands fails, others will # continue, but tox return code will reflect previous error commands = {[base]install_packages} + {[base]pip_install} certbot[dev3] python -m pylint --reports=n --rcfile=.pylintrc {[base]source_paths} [testenv:mypy] basepython = python3 commands = {[base]install_packages} - {[base]pip_install} .[dev3] + {[base]pip_install} certbot[dev3] mypy {[base]source_paths} [testenv:apacheconftest] commands = - {[base]pip_install} acme . certbot-apache certbot-compatibility-test - {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test --debian-modules + {[base]pip_install} acme certbot certbot-apache certbot-compatibility-test + {toxinidir}/certbot-apache/tests/apache-conf-files/apache-conf-test --debian-modules passenv = SERVER [testenv:apacheconftest-with-pebble] commands = - {[base]pip_install} acme . certbot-apache certbot-ci certbot-compatibility-test - {toxinidir}/certbot-apache/certbot_apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules + {[base]pip_install} acme certbot certbot-apache certbot-ci certbot-compatibility-test + {toxinidir}/certbot-apache/tests/apache-conf-files/apache-conf-test-pebble.py --debian-modules [testenv:nginxroundtrip] commands = - {[base]pip_install} acme . certbot-apache certbot-nginx + {[base]pip_install} acme certbot certbot-apache certbot-nginx python certbot-compatibility-test/nginx/roundtrip.py certbot-compatibility-test/nginx/nginx-roundtrip-testdata # This is a duplication of the command line in testenv:le_auto to @@ -180,12 +181,11 @@ whitelist_externals = passenv = DOCKER_* -[testenv:le_auto_trusty] -# At the moment, this tests under Python 2.7 only, as only that version is -# readily available on the Trusty Docker image. +[testenv:le_auto_xenial] +# At the moment, this tests under Python 2.7 only. commands = python {toxinidir}/tests/modification-check.py - docker build -f letsencrypt-auto-source/Dockerfile.trusty -t lea letsencrypt-auto-source + docker build -f letsencrypt-auto-source/Dockerfile.xenial -t lea letsencrypt-auto-source docker run --rm -t -i lea whitelist_externals = docker @@ -193,15 +193,6 @@ passenv = DOCKER_* TRAVIS_BRANCH -[testenv:le_auto_xenial] -# At the moment, this tests under Python 2.7 only. -commands = - docker build -f letsencrypt-auto-source/Dockerfile.xenial -t lea letsencrypt-auto-source - docker run --rm -t -i lea -whitelist_externals = - docker -passenv = DOCKER_* - [testenv:le_auto_jessie] # At the moment, this tests under Python 2.7 only, as only that version is # readily available on the Wheezy Docker image. @@ -233,18 +224,27 @@ passenv = DOCKER_* [testenv:integration] commands = - {[base]pip_install} acme . certbot-nginx certbot-ci + {[base]pip_install} acme certbot certbot-nginx certbot-ci pytest certbot-ci/certbot_integration_tests \ --acme-server={env:ACME_SERVER:pebble} \ --cov=acme --cov=certbot --cov=certbot_nginx --cov-report= \ --cov-config=certbot-ci/certbot_integration_tests/.coveragerc - coverage report --include 'certbot/*' --show-missing --fail-under=66 + coverage report --include 'certbot/*' --show-missing --fail-under=65 coverage report --include 'certbot-nginx/*' --show-missing --fail-under=74 passenv = DOCKER_* +[testenv:integration-certbot] +commands = + {[base]pip_install} acme certbot certbot-ci + pytest certbot-ci/certbot_integration_tests/certbot_tests \ + --acme-server={env:ACME_SERVER:pebble} \ + --cov=acme --cov=certbot --cov-report= \ + --cov-config=certbot-ci/certbot_integration_tests/.coveragerc + coverage report --include 'certbot/*' --show-missing --fail-under=62 + [testenv:integration-certbot-oldest] commands = - {[base]pip_install} . + {[base]pip_install} certbot {[base]pip_install} certbot-ci pytest certbot-ci/certbot_integration_tests/certbot_tests \ --acme-server={env:ACME_SERVER:pebble} @@ -275,7 +275,7 @@ setenv = AWS_DEFAULT_REGION=us-east-1 changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py apache2_targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_apache2.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py apache2_targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_apache2.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -284,7 +284,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_leauto_upgrades.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_leauto_upgrades.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -293,7 +293,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_letsencrypt_auto_certonly_standalone.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_letsencrypt_auto_certonly_standalone.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} @@ -302,7 +302,7 @@ setenv = {[testenv:travis-test-farm-tests-base]setenv} changedir = {[testenv:travis-test-farm-tests-base]changedir} commands = {[testenv:travis-test-farm-tests-base]commands} - python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_sdists.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} --fast + python multitester.py targets.yaml travis-test-farm.pem SET_BY_ENV scripts/test_sdists.sh --repo {env:TRAVIS_BUILD_DIR} --branch {env:TRAVIS_BRANCH} deps = {[testenv:travis-test-farm-tests-base]deps} passenv = {[testenv:travis-test-farm-tests-base]passenv} setenv = {[testenv:travis-test-farm-tests-base]setenv} diff --git a/windows-installer/.gitignore b/windows-installer/.gitignore new file mode 100644 index 000000000..a1a48d6b8 --- /dev/null +++ b/windows-installer/.gitignore @@ -0,0 +1,2 @@ +build +build.* diff --git a/windows-installer/certbot.ico b/windows-installer/certbot.ico new file mode 100644 index 000000000..364c32098 Binary files /dev/null and b/windows-installer/certbot.ico differ diff --git a/windows-installer/construct.py b/windows-installer/construct.py new file mode 100644 index 000000000..77ca67e65 --- /dev/null +++ b/windows-installer/construct.py @@ -0,0 +1,192 @@ +#!/usr/bin/env python3 +import contextlib +import ctypes +import os +import shutil +import struct +import subprocess +import sys +import tempfile +import time + +PYTHON_VERSION = (3, 7, 4) +PYTHON_BITNESS = 32 +PYWIN32_VERSION = 227 # do not forget to edit pywin32 dependency accordingly in setup.py +NSIS_VERSION = '3.04' + + +def main(): + build_path, repo_path, venv_path, venv_python = _prepare_environment() + + _copy_assets(build_path, repo_path) + + installer_cfg_path = _generate_pynsist_config(repo_path, build_path) + + _prepare_build_tools(venv_path, venv_python, repo_path) + _compile_wheels(repo_path, build_path, venv_python) + _build_installer(installer_cfg_path, venv_path) + + print('Done') + + +def _build_installer(installer_cfg_path, venv_path): + print('Build the installer') + subprocess.check_call([os.path.join(venv_path, 'Scripts', 'pynsist.exe'), installer_cfg_path]) + + +def _compile_wheels(repo_path, build_path, venv_python): + print('Compile wheels') + + wheels_path = os.path.join(build_path, 'wheels') + os.makedirs(wheels_path) + + certbot_packages = ['acme', 'certbot'] + # Uncomment following line to include all DNS plugins in the installer + # certbot_packages.extend([name for name in os.listdir(repo_path) if name.startswith('certbot-dns-')]) + wheels_project = [os.path.join(repo_path, package) for package in certbot_packages] + + with _prepare_constraints(repo_path) as constraints_file_path: + command = [venv_python, '-m', 'pip', 'wheel', '-w', wheels_path, '--constraint', constraints_file_path] + command.extend(wheels_project) + subprocess.check_call(command) + + +def _prepare_build_tools(venv_path, venv_python, repo_path): + print('Prepare build tools') + subprocess.check_call([sys.executable, '-m', 'venv', venv_path]) + subprocess.check_call([venv_python, os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'pipstrap.py')]) + subprocess.check_call([venv_python, os.path.join(repo_path, 'tools', 'pip_install.py'), 'pynsist']) + subprocess.check_call(['choco', 'upgrade', '-y', 'nsis', '--version', NSIS_VERSION]) + + +@contextlib.contextmanager +def _prepare_constraints(repo_path): + requirements = os.path.join(repo_path, 'letsencrypt-auto-source', 'pieces', 'dependency-requirements.txt') + constraints = subprocess.check_output( + [sys.executable, os.path.join(repo_path, 'tools', 'strip_hashes.py'), requirements], + universal_newlines=True) + workdir = tempfile.mkdtemp() + try: + constraints_file_path = os.path.join(workdir, 'constraints.txt') + with open(constraints_file_path, 'a') as file_h: + file_h.write(constraints) + file_h.write('pywin32=={0}'.format(PYWIN32_VERSION)) + yield constraints_file_path + finally: + shutil.rmtree(workdir) + + +def _copy_assets(build_path, repo_path): + print('Copy assets') + if os.path.exists(build_path): + os.rename(build_path, '{0}.{1}.bak'.format(build_path, int(time.time()))) + os.makedirs(build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'certbot.ico'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'run.bat'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'template.nsi'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-up.ps1'), build_path) + shutil.copy(os.path.join(repo_path, 'windows-installer', 'renew-down.ps1'), build_path) + + +def _generate_pynsist_config(repo_path, build_path): + print('Generate pynsist configuration') + + pywin32_paths_file = os.path.join(build_path, 'pywin32_paths.py') + + # Pywin32 uses non-standard folders to hold its packages. We need to instruct pynsist bootstrap + # explicitly to add them into sys.path. This is done with a custom "pywin32_paths.py" that is + # referred in the pynsist configuration as an "extra_preamble". + # Reference example: https://github.com/takluyver/pynsist/tree/master/examples/pywebview + with open(pywin32_paths_file, 'w') as file_h: + file_h.write('''\ +pkgdir = os.path.join(os.path.dirname(installdir), 'pkgs') + +sys.path.extend([ + os.path.join(pkgdir, 'win32'), + os.path.join(pkgdir, 'win32', 'lib'), +]) + +# Preload pywintypes and pythoncom +pwt = os.path.join(pkgdir, 'pywin32_system32', 'pywintypes{0}{1}.dll') +pcom = os.path.join(pkgdir, 'pywin32_system32', 'pythoncom{0}{1}.dll') +import warnings +with warnings.catch_warnings(): + warnings.simplefilter("ignore") + import imp +imp.load_dynamic('pywintypes', pwt) +imp.load_dynamic('pythoncom', pcom) +'''.format(PYTHON_VERSION[0], PYTHON_VERSION[1])) + + installer_cfg_path = os.path.join(build_path, 'installer.cfg') + + certbot_pkg_path = os.path.join(repo_path, 'certbot') + certbot_version = subprocess.check_output([sys.executable, '-c', 'import certbot; print(certbot.__version__)'], + universal_newlines=True, cwd=certbot_pkg_path).strip() + + with open(installer_cfg_path, 'w') as file_h: + file_h.write('''\ +[Application] +name=Certbot +version={certbot_version} +icon=certbot.ico +publisher=Electronic Frontier Foundation +target=$INSTDIR\\run.bat + +[Build] +directory=nsis +nsi_template=template.nsi +installer_name=certbot-beta-installer-{installer_suffix}.exe + +[Python] +version={python_version} +bitness={python_bitness} + +[Include] +local_wheels=wheels\\*.whl +files=run.bat + renew-up.ps1 + renew-down.ps1 + +[Command certbot] +entry_point=certbot.main:main +extra_preamble=pywin32_paths.py +'''.format(certbot_version=certbot_version, + installer_suffix='win_amd64' if PYTHON_BITNESS == 64 else 'win32', + python_bitness=PYTHON_BITNESS, + python_version='.'.join([str(item) for item in PYTHON_VERSION]))) + + return installer_cfg_path + + +def _prepare_environment(): + print('Prepare environment') + try: + subprocess.check_output(['choco', '--version']) + except subprocess.CalledProcessError: + raise RuntimeError('Error: Chocolatey (https://chocolatey.org/) needs ' + 'to be installed to run this script.') + script_path = os.path.realpath(__file__) + repo_path = os.path.dirname(os.path.dirname(script_path)) + build_path = os.path.join(repo_path, 'windows-installer', 'build') + venv_path = os.path.join(build_path, 'venv-config') + venv_python = os.path.join(venv_path, 'Scripts', 'python.exe') + + return build_path, repo_path, venv_path, venv_python + + +if __name__ == '__main__': + if not os.name == 'nt': + raise RuntimeError('This script must be run under Windows.') + + if ctypes.windll.shell32.IsUserAnAdmin() == 0: + # Administrator privileges are required to properly install NSIS through Chocolatey + raise RuntimeError('This script must be run with administrator privileges.') + + if sys.version_info[:2] != PYTHON_VERSION[:2]: + raise RuntimeError('This script must be run with Python {0}' + .format('.'.join([str(item) for item in PYTHON_VERSION[0:2]]))) + + if struct.calcsize('P') * 8 != PYTHON_BITNESS: + raise RuntimeError('This script must be run with a {0} bit version of Python.' + .format(PYTHON_BITNESS)) + main() diff --git a/windows-installer/renew-down.ps1 b/windows-installer/renew-down.ps1 new file mode 100644 index 000000000..60dc4d9e6 --- /dev/null +++ b/windows-installer/renew-down.ps1 @@ -0,0 +1,6 @@ +$taskName = "Certbot Renew Task" + +$exists = Get-ScheduledTask | Where-Object {$_.TaskName -like $taskName} +if ($exists) { + Unregister-ScheduledTask -TaskName $taskName -Confirm:$false +} diff --git a/windows-installer/renew-up.ps1 b/windows-installer/renew-up.ps1 new file mode 100644 index 000000000..224458748 --- /dev/null +++ b/windows-installer/renew-up.ps1 @@ -0,0 +1,17 @@ +function Get-ScriptDirectory { Split-Path $MyInvocation.ScriptName } +$down = Join-Path (Get-ScriptDirectory) 'renew-down.ps1' +& $down + +$taskName = "Certbot Renew Task" + +$action = New-ScheduledTaskAction -Execute 'Powershell.exe' -Argument '-NoProfile -WindowStyle Hidden -Command "certbot renew"' +$delay = New-TimeSpan -Hours 12 +$triggerAM = New-ScheduledTaskTrigger -Daily -At 12am -RandomDelay $delay +$triggerPM = New-ScheduledTaskTrigger -Daily -At 12pm -RandomDelay $delay +# NB: For now scheduled task is set up under Administrators group account because Certbot Installer installs Certbot for all users. +# If in the future we allow the Installer to install Certbot for one specific user, the scheduled task will need to +# switch to this user, since Certbot will be available only for him. +$adminsSID = New-Object System.Security.Principal.SecurityIdentifier("S-1-5-32-544") +$adminsGroupID = $adminsSID.Translate([System.Security.Principal.NTAccount]).Value +$principal = New-ScheduledTaskPrincipal -GroupId $adminsGroupID -RunLevel Highest +Register-ScheduledTask -Action $action -Trigger $triggerAM,$triggerPM -TaskName $taskName -Description "Execute twice a day the 'certbot renew' command, to renew managed certificates if needed." -Principal $principal diff --git a/windows-installer/run.bat b/windows-installer/run.bat new file mode 100644 index 000000000..efba28800 --- /dev/null +++ b/windows-installer/run.bat @@ -0,0 +1,31 @@ +@echo off + +:: BatchGotAdmin +:------------------------------------- +REM --> Check for permissions + IF "%PROCESSOR_ARCHITECTURE%" EQU "amd64" ( +>nul 2>&1 "%SYSTEMROOT%\SysWOW64\cacls.exe" "%SYSTEMROOT%\SysWOW64\config\system" +) ELSE ( +>nul 2>&1 "%SYSTEMROOT%\system32\cacls.exe" "%SYSTEMROOT%\system32\config\system" +) + +REM --> If error flag set, we do not have admin. +if '%errorlevel%' NEQ '0' ( + echo Requesting administrative privileges... + goto UACPrompt +) else ( goto gotAdmin ) + +:UACPrompt + echo Set UAC = CreateObject^("Shell.Application"^) > "%temp%\getadmin.vbs" + set params= %* + echo UAC.ShellExecute "cmd.exe", "/c ""%~s0"" %params:"=""%", "", "runas", 1 >> "%temp%\getadmin.vbs" + + "%temp%\getadmin.vbs" + del "%temp%\getadmin.vbs" + exit /B + +:gotAdmin + pushd "%CD%" + CD /D "%~dp0" +:-------------------------------------- +cmd.exe /k echo You can run 'certbot' commands here. Type 'certbot --help' for more information. diff --git a/windows-installer/template.nsi b/windows-installer/template.nsi new file mode 100644 index 000000000..50a03865f --- /dev/null +++ b/windows-installer/template.nsi @@ -0,0 +1,260 @@ +; This NSIS template is based on the built-in one in pynsist 2.3. +; Added lines are enclosed within "CERTBOT CUSTOM BEGIN/END" comments. +; If pynsist is upgraded, this template must be updated if necessary using the new built-in one. +; Original file can be found here: https://github.com/takluyver/pynsist/blob/2.4/nsist/pyapp.nsi + +!define PRODUCT_NAME "[[ib.appname]]" +!define PRODUCT_VERSION "[[ib.version]]" +!define PY_VERSION "[[ib.py_version]]" +!define PY_MAJOR_VERSION "[[ib.py_major_version]]" +!define BITNESS "[[ib.py_bitness]]" +!define ARCH_TAG "[[arch_tag]]" +!define INSTALLER_NAME "[[ib.installer_name]]" +!define PRODUCT_ICON "[[icon]]" + +; Marker file to tell the uninstaller that it's a user installation +!define USER_INSTALL_MARKER _user_install_marker + +SetCompressor lzma + +; CERTBOT CUSTOM BEGIN +; Administrator privileges are required to insert a new task in Windows Scheduler. +; Also comment out some options to disable ability to choose AllUsers/CurrentUser install mode. +; As a result, installer run always with admin privileges (because of MULTIUSER_EXECUTIONLEVEL), +; using the AllUsers installation mode by default (because of MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER +; not set), and this default behavior cannot be overridden (because of MULTIUSER_MUI not set). +; See https://nsis.sourceforge.io/Docs/MultiUser/Readme.html +!define MULTIUSER_EXECUTIONLEVEL Admin +;!define MULTIUSER_EXECUTIONLEVEL Highest +;!define MULTIUSER_INSTALLMODE_DEFAULT_CURRENTUSER +;!define MULTIUSER_MUI +;!define MULTIUSER_INSTALLMODE_COMMANDLINE +; CERTBOT CUSTOM END +!define MULTIUSER_INSTALLMODE_INSTDIR "[[ib.appname]]" +[% if ib.py_bitness == 64 %] +!define MULTIUSER_INSTALLMODE_FUNCTION correct_prog_files +[% endif %] +!include MultiUser.nsh + +[% block modernui %] +; Modern UI installer stuff +!include "MUI2.nsh" +!define MUI_ABORTWARNING +!define MUI_ICON "[[icon]]" +!define MUI_UNICON "[[icon]]" + +; UI pages +[% block ui_pages %] +!insertmacro MUI_PAGE_WELCOME +[% if license_file %] +!insertmacro MUI_PAGE_LICENSE [[license_file]] +[% endif %] +; CERTBOT CUSTOM BEGIN +; Disable the installation mode page (AllUsers/CurrentUser) +;!insertmacro MULTIUSER_PAGE_INSTALLMODE +; CERTBOT CUSTOM END +!insertmacro MUI_PAGE_DIRECTORY +!insertmacro MUI_PAGE_INSTFILES +!insertmacro MUI_PAGE_FINISH +[% endblock ui_pages %] +!insertmacro MUI_LANGUAGE "English" +[% endblock modernui %] + +; CERTBOT CUSTOM BEGIN +Name "${PRODUCT_NAME} (beta) ${PRODUCT_VERSION}" +;Name "${PRODUCT_NAME} ${PRODUCT_VERSION}" +; CERTBOT CUSTOM END +OutFile "${INSTALLER_NAME}" +ShowInstDetails show + +Section -SETTINGS + SetOutPath "$INSTDIR" + SetOverwrite ifnewer +SectionEnd + +[% block sections %] + +Section "!${PRODUCT_NAME}" sec_app + SetRegView [[ib.py_bitness]] + SectionIn RO + File ${PRODUCT_ICON} + SetOutPath "$INSTDIR\pkgs" + File /r "pkgs\*.*" + SetOutPath "$INSTDIR" + + ; Marker file for per-user install + StrCmp $MultiUser.InstallMode CurrentUser 0 +3 + FileOpen $0 "$INSTDIR\${USER_INSTALL_MARKER}" w + FileClose $0 + SetFileAttributes "$INSTDIR\${USER_INSTALL_MARKER}" HIDDEN + + [% block install_files %] + ; Install files + [% for destination, group in grouped_files %] + SetOutPath "[[destination]]" + [% for file in group %] + File "[[ file ]]" + [% endfor %] + [% endfor %] + + ; Install directories + [% for dir, destination in ib.install_dirs %] + SetOutPath "[[ pjoin(destination, dir) ]]" + File /r "[[dir]]\*.*" + [% endfor %] + [% endblock install_files %] + + [% block install_shortcuts %] + ; Install shortcuts + ; The output path becomes the working directory for shortcuts + SetOutPath "%HOMEDRIVE%\%HOMEPATH%" + [% if single_shortcut %] + [% for scname, sc in ib.shortcuts.items() %] + CreateShortCut "$SMPROGRAMS\[[scname]].lnk" "[[sc['target'] ]]" \ + '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]" + [% endfor %] + [% else %] + [# Multiple shortcuts: create a directory for them #] + CreateDirectory "$SMPROGRAMS\${PRODUCT_NAME}" + [% for scname, sc in ib.shortcuts.items() %] + CreateShortCut "$SMPROGRAMS\${PRODUCT_NAME}\[[scname]].lnk" "[[sc['target'] ]]" \ + '[[ sc['parameters'] ]]' "$INSTDIR\[[ sc['icon'] ]]" + [% endfor %] + [% endif %] + SetOutPath "$INSTDIR" + [% endblock install_shortcuts %] + + [% block install_commands %] + [% if has_commands %] + DetailPrint "Setting up command-line launchers..." + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_assemble_launchers.py" [[ python ]] "$INSTDIR\bin"' + + StrCmp $MultiUser.InstallMode CurrentUser 0 AddSysPathSystem + ; Add to PATH for current user + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add_user "$INSTDIR\bin"' + GoTo AddedSysPath + AddSysPathSystem: + ; Add to PATH for all users + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" add "$INSTDIR\bin"' + AddedSysPath: + [% endif %] + [% endblock install_commands %] + + ; Byte-compile Python files. + DetailPrint "Byte-compiling Python modules..." + nsExec::ExecToLog '[[ python ]] -m compileall -q "$INSTDIR\pkgs"' + WriteUninstaller $INSTDIR\uninstall.exe + ; Add ourselves to Add/remove programs + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayName" "${PRODUCT_NAME}" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "UninstallString" '"$INSTDIR\uninstall.exe"' + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "InstallLocation" "$INSTDIR" + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayIcon" "$INSTDIR\${PRODUCT_ICON}" + [% if ib.publisher is not none %] + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "Publisher" "[[ib.publisher]]" + [% endif %] + WriteRegStr SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "DisplayVersion" "${PRODUCT_VERSION}" + WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "NoModify" 1 + WriteRegDWORD SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" \ + "NoRepair" 1 + + ; CERTBOT CUSTOM BEGIN + ; Execute ps script to create the certbot renew task + DetailPrint "Setting up certbot renew scheduled task" + nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-up.ps1"' + ; CERTBOT CUSTOM END + + ; Check if we need to reboot + IfRebootFlag 0 noreboot + MessageBox MB_YESNO "A reboot is required to finish the installation. Do you wish to reboot now?" \ + /SD IDNO IDNO noreboot + Reboot + noreboot: +SectionEnd + +Section "Uninstall" + ; CERTBOT CUSTOM BEGIN + ; Execute ps script to remove the certbot renew task + nsExec::ExecToStack 'powershell -inputformat none -ExecutionPolicy RemoteSigned -File "$INSTDIR\renew-down.ps1"' + ; CERTBOT CUSTOM END + + SetRegView [[ib.py_bitness]] + SetShellVarContext all + IfFileExists "$INSTDIR\${USER_INSTALL_MARKER}" 0 +3 + SetShellVarContext current + Delete "$INSTDIR\${USER_INSTALL_MARKER}" + + Delete $INSTDIR\uninstall.exe + Delete "$INSTDIR\${PRODUCT_ICON}" + RMDir /r "$INSTDIR\pkgs" + + ; Remove ourselves from %PATH% + [% block uninstall_commands %] + [% if has_commands %] + nsExec::ExecToLog '[[ python ]] -Es "$INSTDIR\_system_path.py" remove "$INSTDIR\bin"' + [% endif %] + [% endblock uninstall_commands %] + + [% block uninstall_files %] + ; Uninstall files + [% for file, destination in ib.install_files %] + Delete "[[pjoin(destination, file)]]" + [% endfor %] + ; Uninstall directories + [% for dir, destination in ib.install_dirs %] + RMDir /r "[[pjoin(destination, dir)]]" + [% endfor %] + [% endblock uninstall_files %] + + [% block uninstall_shortcuts %] + ; Uninstall shortcuts + [% if single_shortcut %] + [% for scname in ib.shortcuts %] + Delete "$SMPROGRAMS\[[scname]].lnk" + [% endfor %] + [% else %] + RMDir /r "$SMPROGRAMS\${PRODUCT_NAME}" + [% endif %] + [% endblock uninstall_shortcuts %] + RMDir $INSTDIR + DeleteRegKey SHCTX "Software\Microsoft\Windows\CurrentVersion\Uninstall\${PRODUCT_NAME}" +SectionEnd + +[% endblock sections %] + +; Functions + +Function .onMouseOverSection + ; Find which section the mouse is over, and set the corresponding description. + FindWindow $R0 "#32770" "" $HWNDPARENT + GetDlgItem $R0 $R0 1043 ; description item (must be added to the UI) + + [% block mouseover_messages %] + StrCmp $0 ${sec_app} "" +2 + SendMessage $R0 ${WM_SETTEXT} 0 "STR:${PRODUCT_NAME}" + + [% endblock mouseover_messages %] +FunctionEnd + +Function .onInit + !insertmacro MULTIUSER_INIT +FunctionEnd + +Function un.onInit + !insertmacro MULTIUSER_UNINIT +FunctionEnd + +[% if ib.py_bitness == 64 %] +Function correct_prog_files + ; The multiuser machinery doesn't know about the different Program files + ; folder for 64-bit applications. Override the install dir it set. + StrCmp $MultiUser.InstallMode AllUsers 0 +2 + StrCpy $INSTDIR "$PROGRAMFILES64\${MULTIUSER_INSTALLMODE_INSTDIR}" +FunctionEnd +[% endif %] \ No newline at end of file