Compare commits

...

454 Commits

Author SHA1 Message Date
Brad Warren
cc85d309a9 let apacheconftest handle deps 2018-07-05 18:38:09 -07:00
Brad Warren
c1b4f21325 Revert "We don't need to run dpkg -s in before_install."
This reverts commit e5d35099a7.
2018-07-05 18:37:27 -07:00
Brad Warren
29a75eb8a7 Upgrade Python 3.6 tests to 3.7.
Let's continue the approach of testing on the oldest and newest versions of Python 3. We will continue testing on Python 3.6 in the nightly tests.
2018-07-05 17:52:34 -07:00
Brad Warren
309a70c3fe Remove augeas sources.
We only needed it for Ubuntu Precise which is dead and it doesn't work in Ubuntu Xenial.
2018-07-05 17:51:23 -07:00
Brad Warren
e5d35099a7 We don't need to run dpkg -s in before_install. 2018-07-05 17:51:05 -07:00
Brad Warren
9fade9c85c Remove apacheconftest packages.
The apacheconftests handle installing Apache dependencies, so let's remove it from the general case.
2018-07-05 17:48:19 -07:00
Brad Warren
cb076539ec Remove .dev0 from version numbers during releases. (#6116)
This allows us to depend on packages like acme>=0.26.0.dev0 during development
and automatically change it to acme>=0.26.0 during the release. We use `git add
-p` to be safe, but if .dev0 is used at all in our released setup.py files,
we're probably doing something wrong.
2018-07-05 08:26:42 -07:00
Brad Warren
cad95466b0 Allow py37 testing (#6170)
* Reorganize packages in tox to allow for py37 tests

certbot-dns-cloudflare doesn't currently work in Python 3.7 because it transitively depends on pyYAML which doesn't yet support Python 3.7. See https://github.com/yaml/pyyaml/issues/126 for more info.

* add py37 tox environment
2018-07-05 12:11:04 +03:00
Brad Warren
ab9851d97e Upgrade to the latest cryptography version (#6169)
This allows certbot-auto and our development setup to work with Python 3.7.
2018-07-03 06:57:58 -07:00
Brad Warren
552e60a126 Don't use hardcoded port in tests (#6145)
* Don't use port 1234 in standalone tests.

* rename unused variable

* add back failure case

* Add back probe connection error test.

* fix lint

* remove unused import

* fix test file coverage

* prevent future heisenbug
2018-06-29 15:27:58 +03:00
Brad Warren
6e13c2ccc7 Add --disable=locally-enabled to .pylintrc. (#6159) 2018-06-28 15:06:52 -07:00
ohemorange
a816cc8979 Use account reuse symlink logic when loading an account (#6156)
Fixes #6154.

* add symlinking to load flow

* test account reuse on load
2018-06-28 13:34:16 -07:00
Brad Warren
64e06d4201 Use greater than or equal to in requirements. (#6117)
* Use greater than or equal to in requirements.

This changes the existing requirements using strictly greater than to greater
than or equal to so that they're more conventional.

* Use >= for certbot-postfix.

Despite it previously saying 'certbot>0.23.0', certbot-postfix/local-oldest-requirements.txt was pinned to 0.23.0 so let's just use certbot>=0.23.0.
2018-06-28 10:55:21 -07:00
Bahram Aghaei
d00a31622d run Isort on imported packages (#6138) 2018-06-28 10:43:52 -07:00
Brad Warren
742a57722b fix server_root default tests on macOS (#6149) 2018-06-27 17:35:43 -07:00
Brad Warren
ad3c547e1f Update cli-help.txt to use generic values (#6143) 2018-06-27 14:29:21 -07:00
Joona Hoikkala
f169e7374b Interactive certificate selection with install verb (#6097)
If either --cert-name or both --key-path and --cert-path (in which case the user requests installation for a certificate not managed by Certbot) are not provided, prompt the user with managed certificates and let them choose.

Fixes: #5824
2018-06-27 11:35:01 -07:00
Joona Hoikkala
2304f7fcda Remove unnecessary dotfiles (#6151) 2018-06-27 09:32:53 -07:00
Brad Warren
a4760cfe56 Partially revert "Implement TLS-ALPN-01 challenge and standalone TLS-ALPN server (#5894)" (#6144)
This partially reverts commit 15f1405fff.

A basic tls-alpn-01 implementation is left so we can successfully parse the
challenge so it can be used in boulder's tests.
2018-06-26 15:33:41 -07:00
ohemorange
87e1912bf9 Show both possible Nginx default server root values in docs (#6137)
See https://github.com/certbot/website/pull/348#issuecomment-399257703.

```
$ certbot --help all | grep -C 3  nginx-server-root
nginx:
  Nginx Web Server plugin - Alpha

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx)
  --nginx-ctl NGINX_CTL
                        Path to the 'nginx' binary, used for 'configtest' and
 ```

```
$ CERTBOT_DOCS=1 certbot --help all | grep -C 3  nginx-server-root
nginx:
  Nginx Web Server plugin - Alpha

  --nginx-server-root NGINX_SERVER_ROOT
                        Nginx server root directory. (default: /etc/nginx or
                        /usr/local/etc/nginx)
  --nginx-ctl NGINX_CTL
```

* Show both possible Nginx default server root values in docs

* add test

* check that exactly one server root is in the default

* use default magic
2018-06-25 18:09:30 -07:00
r5d
80cd134847 certbot.cli: Remove debug-challenges option for renew subcommand. (#6141)
Addresses issue #5005.
2018-06-25 18:02:07 -07:00
sydneyli
7890de62ec doc(postfix): install instructions (#6136)
fixes #6131

* doc(postfix): install instructions

* address brad's comments
2018-06-21 16:11:02 -07:00
Brad Warren
1e1e7d8e97 Improve UA default in docs (#6120)
* Use less informative UA values in docs.

* set CERTBOT_DOCS during release
2018-06-21 15:40:42 -07:00
ohemorange
2ac0b55208 Reuse ACMEv1 accounts for ACMEv2 in production (#6134)
* Reuse accounts made with ACMEv1 when using an ACMEv2 Let's Encrypt server. This commit turns the feature on for the production server; the bulk of the work was done in 8e4303a.

* add upgrade test for production server
2018-06-21 13:23:09 -07:00
Harlan Lieberman-Berg
6771b8e05b docs: move warning about distro provided renewal (#6133)
Currently, you must read ten paragraphs about writing renewal hooks
before you find that most distributions will automatically renew certs
for you.  This is burying the lede in a major way; moving it up to the
header seems a better choice.
2018-06-21 12:52:08 -07:00
Joona Hoikkala
3877af6619 Gradually increasing HSTS max-age (#5912)
This PR adds the functionality to enhance Apache configuration to include HTTP Strict Transport Security header with a low initial max-age value.

The max-age value will get increased on every (scheduled) run of certbot renew regardless of the certificate actually getting renewed, if the last increase took place longer than ten hours ago. The increase steps are visible in constants.AUTOHSTS_STEPS.

Upon the first actual renewal after reaching the maximum increase step, the max-age value will be made "permanent" and will get value of one year.

To achieve accurate VirtualHost discovery on subsequent runs, a comment with unique id string will be added to each enhanced VirtualHost.

* AutoHSTS code rebased on master

* Fixes to match the changes in master

* Make linter happy with metaclass registration

* Address small review comments

* Use new enhancement interfaces

* New style enhancement changes

* Do not allow --hsts and --auto-hsts simultaneuously

* MyPy annotation fixes and added test

* Change oldest requrements to point to local certbot core version

* Enable new style enhancements for run and install verbs

* Test refactor

* New test class for main.install tests

* Move a test to a correct test class
2018-06-21 07:27:19 -07:00
Brad Warren
a875246a4b Merge pull request #6121 from certbot/squashed-postfix
Postfix plugin
2018-06-15 17:29:34 -07:00
sydneyli
40c50231ed Merge branch 'master' into squashed-postfix 2018-06-15 16:21:15 -07:00
Sydney Li
4ba153949d Fixing up postfix plugin
- Finishing refactor of postconf/postfix command-line utilities
 - Plugin uses starttls_policy plugin to specify per-domain policies

Cleaning up TLS policy code.

Print warning when setting configuration parameter that is overridden by master.

Update client to use new policy API

Cleanup and test fixes

Documentation fix

smaller fixes

Policy is now an enhancement and reverting works

Added a README, and small documentation fixes throughout

Moving testing infra from starttls repo to certbot-postfix

fixing tests and lint

Changes against new policy API

starttls-everywhere => starttls-policy

testing(postfix): Added more varieties of certificates to test against.

Moar fixes against policy API.

Address comments on README and setup.py

Address small comments on postconf and util

Address comments in installer

Python 3 fixes and Postconf tester extends TempDir test class

Mock out postconf calls from tests and test coverage for master overrides

More various fixes. Everything minus testing done

Remove STARTTLS policy enhancement from this branch.

sphinx quickstart

99% test coverage

some cleanup and testfixing

cleanup leftover files

Remove print statement

testfix for python 3.4

Revert dockerfile change

mypy fix

fix(postfix): brad's comments

test(postfix): coverage to 100

test(postfix): mypy

import mypy types

fix(postfix docs): add .rst files and fix build

fix(postfix): tls_only and server_only params behave nicely together

some cleanup

lint

fix more comments

bump version number
2018-06-15 15:46:55 -07:00
Brad Warren
5025b4ea96 Add certbot-postfix to tools
pep8ify

Delint

cover++

test more_info()

Refactor get_config_var

Don't duplicate changes to Postfix config

document instance variables

Always clear save_notes on save

Test deploy_cert and save and add MockPostfix.

Move mock and call to InstallerTest

Add getters and setters

Use postfix getters and setters

protect get_config_var

bump cover to 100%

bump required coverage to 100

s/config_dir/config_utility

Decrease minimum version to Postfix 2.6.

This is the minimum version that allows us to set ciphers to be used with
opportunistic TLS and is the oldest version packaged in any major distro.

Use tls_security_level instead of use_tls.

smtpd_tls_security_level should be used instead according to Postfix documentation.

Test smtpd_tls_security_level conditional

make dunder method an under method

refactor postconf usage

add check_all_output

test check_all_output

Add and test verify_exe_exists

Add PostfixUtilBase

Add ReadOnlyMainMap

Use _get_output instead of _call

Fix split strip typo
2018-06-15 15:46:48 -07:00
sydneyli
adc07ef933 fix(display): alternate spaces and dashes (#6119)
* fix(display): alternate spaces and dashes

* add comment
2018-06-15 15:03:58 -07:00
Brad Warren
3316eac178 Separate integration coverage (#6113)
* check coverage separately

* Add coverage minimums for integration tests.
2018-06-15 09:55:16 -07:00
Brad Warren
8b16a56de8 remove comment about renewer (#6115) 2018-06-15 11:43:48 +03:00
Brad Warren
453eafb11e Used packaged acme in oldest tests. (#6112) 2018-06-15 11:41:38 +03:00
r5d
c4ae376279 Add autorenew option to renew subcommand (#5911)
* Add autorenew option to `renew` subcommand.

* Change default value for 'autorenew' cli option.

* Update certbot.cli.prepare_and_parse_args (autorenew)

Set `default` for --autorenew and --no-autorenew.

* Update certbot.storage.RenewableCert.should_autorenew.

- Remove `interactive` argument in RenewableCert.should_autorenew.
- Update certbot.renewal.should_renew.

* Move autorenew enable/disable check to certbot.storage.

- Remove autorenew enable/disable check in
  `certbot.renewal.handle_renewal_request`.
- Fix RenewableCert.autorenewal_is_enabled; autorenew is stored in
  'renewalparams'.
- Add autorenew enable/disable check in
  `RenewableCert.should_autorenew`.
- Update tests test_time_interval_judgments,
  test_autorenewal_is_enabled, test_should_autorenew tests in
  storage_test.py

* certbot: Update RenewableCert.should_autorenew

Remove block that sets autorenew option in the renewal configuration
file.

* certbot: Update prepare_and_parse_args.

Remove --autorenew option.

* certbot: Update CLI_DEFAULTS.

Set default of `autorenew` to True.

* Remove unused imports in certbot.storage.
2018-06-13 15:24:51 -07:00
Brad Warren
fccfbd14b1 add 0.25.1 changelog (#6111) 2018-06-13 14:20:43 -07:00
Brad Warren
c9ae365f66 0.25.1 update for master (#6110)
* Release 0.25.1

(cherry picked from commit 21b5e4eadb)

* Bump version to 0.26.0
2018-06-13 14:20:15 -07:00
Brad Warren
9f20fa0ef9 Fixes #6085. (#6091)
The value of norecusedirs is the default in newer versions of pytest which is
listed at
https://docs.pytest.org/en/3.0.0/customize.html#confval-norecursedirs.
2018-06-12 17:31:22 -07:00
Brad Warren
95892cd4ab Require acme>=0.25.0 for nginx (#6099) 2018-06-12 17:24:19 -07:00
Roland Bracewell Shoemaker
da028ca9c2 Wrap TLS-ALPN extension with ASN.1 (#6089)
* Wrap TLS-ALPN extension with ASN.1

* Fix test
2018-06-11 11:59:57 -07:00
Brad Warren
5bf1c51de7 Merge pull request #6075 from certbot/candidate-0.25.0
Update certbot-auto and version numbers
2018-06-11 10:41:57 -07:00
Brad Warren
afa7e3fb82 Unrevert #6000 and silence deprecation warnings (#6082)
* Revert "Revert "switch signature verification to use pure cryptography (#6000)" (#6074)"

This reverts commit 3cffe1449c.

* Fixes #6073.

This silences the deprecation warnings from cryptography. I looked into only
silencing the cryptography warning specifically in the function, however,
CryptographyDeprecationWarning doesn't seem to be publicly documented, so we
probably shouldn't depend on it.
2018-06-08 00:45:23 +03:00
Brad Warren
3a8de6d172 Upgrade pinned twine version. (#6078)
For the past couple of releases, twine has errored while trying to upload
packages and this is fixed by upgrading to a newer version of twine. This
commit updates our pinned version installed when using tools/venv.sh to the
latest available version. pkginfo had to be upgraded as well to support the
latest version of twine.
2018-06-07 07:50:36 -07:00
Brad Warren
780a1b3a26 Don't require festival during signing. (#6079)
Festival isn't available via Homebrew and is only needed to read the hash
aloud, so let's not make it a strict requirement that it's installed. You can
simply read the hash from the terminal instead.
2018-06-07 11:43:45 +03:00
Brad Warren
da6320f4d1 Stop testing against Debian 7. (#6077)
Debian Wheezy is no longer supported (see https://wiki.debian.org/LTS) and
Amazon shut down their Debian 7 mirrors so let's stop trying to use Debian 7
during testing.
2018-06-07 11:11:06 +03:00
Brad Warren
eec37f65a8 Update changelog for 0.25.0 (#6076) 2018-06-06 19:01:55 -07:00
Brad Warren
4b11fe1fda Bump version to 0.26.0 2018-06-06 13:50:46 -07:00
Brad Warren
4ae2390c44 Release 0.25.0 2018-06-06 13:50:30 -07:00
Brad Warren
3cffe1449c Revert "switch signature verification to use pure cryptography (#6000)" (#6074)
This reverts commit 366c50e28e.
2018-06-06 07:58:50 -07:00
Brad Warren
868e5b831b Make python setup.py test use pytest for acme (#6072) 2018-06-05 17:59:11 -07:00
ohemorange
d905886f4c Automatically select among default vhosts if we have a port preference in nginx (#5944)
* automatically select among default vhosts if we have a port preference

* ports should be strings in the nginx plugin

* clarify port vs preferred_port behavior by adding allow_port_mismatch flag

* update all instances of default_vhosts to all_default_vhosts

* require port

* port should never be None in _get_default_vhost
2018-06-05 13:40:48 -07:00
sydneyli
09a28c7a27 Allow multiple add_headers directives (#6068)
* fix(nginx-hsts): allow multiple add_headers

* test(nginx): fix nginx tests
2018-06-04 17:44:51 -07:00
ohemorange
8e4303af9f Reuse ACMEv1 accounts for ACMEv2 (#5902)
* Reuse ACMEv1 accounts for ACMEv2

* Correct behavior

* add unit tests

* add _find_all_inner to comply with interface

* acme-staging-v01 --> acme-staging

* only create symlink to previous account if there is one there

* recurse on server path

* update tests and change internal use of load to use server_path

* fail gracefully on corrupted account file by returning [] when rmdir fails

* only reuse accounts in staging for now
2018-06-04 16:04:47 -07:00
Brad Warren
236f9630e0 Remove unneeded sys import (#5873)
* Remove unneeded sys import.

Once upon a time we needed this in some of these setup.py files because we were
using sys in the file, but we aren't anymore so let's remove the import.

* use setuptools instead of distutils
2018-06-04 15:04:56 -07:00
Maciej Dębski
15f1405fff Implement TLS-ALPN-01 challenge and standalone TLS-ALPN server (#5894)
The new challenge is described in https://github.com/rolandshoemaker/acme-tls-alpn.

* TLS-ALPN tests

* Implement TLS-ALPN challenge

* Skip TLS-ALPN tests on old pyopenssl

* make _selection methods private.
2018-06-04 14:54:17 -07:00
Brad Warren
4151737e17 Read in bytes to fix --reuse-key on Python 3 (#6069)
* Read bytes for now for compatibility

* add clarifying comment
2018-06-04 13:13:23 -07:00
Joona Hoikkala
f19ebab441 Make sure the pluginstorage file gets truncated when writing to it (#6062) 2018-06-04 11:08:40 -07:00
schoen
e2d6faa8a9 Add --reuse-key feature (#5901)
* Initial work on new version of --reuse-key

* Test for reuse_key

* Make lint happier

* Also test a non-dry-run reuse_key renewal

* Test --reuse-key in boulder integration test

* Better reuse-key integration testing

* Log fact that key was reused

* Test that the certificates themselves are different

* Change "oldkeypath" to "old_keypath"

* Simply appearance of new-key generation logic

* Reorganize new-key logic

* Move awk logic into TotalAndDistinctLines function

* After refactor, there's now explicit None rather than missing param

* Indicate for MyPy that key can be None

* Actually import the Optional type

* magic_typing is too magical for pylint

* Remove --no-reuse-key option

* Correct pylint test disable
2018-06-01 15:21:02 -07:00
Josh Soref
fb0d2ec3d6 Include missing space (#6061) 2018-06-01 15:09:02 -07:00
ohemorange
d53ef1f7c2 Add mypy info to Certbot docs (#6033)
* Add mypy info to Certbot docs

* break up lines

* link to mypy docs and links to https

* Expand on import wording

* be consistent about mypy styling
2018-05-31 13:57:23 -07:00
Joona Hoikkala
9f6b147d6f Do not call updaters and deployers when run with --dry-run (#6038)
When Certbot is run with --dry-run, skip running GenericUpdater and RenewDeployer interface methods.

This PR also makes the parameter order of updater.run_generic_updaters and updater.run_renewal_deployer consistent.

Fixes #5927

* Do not call updaters and deployers when run with --dry-run

* Use ConfigTestCase instead of mocking config objects manually
2018-05-26 08:31:23 -07:00
Joona Hoikkala
e48c653245 Change GenericUpdater parameter to lineage (#6030)
In order to give more flexibility for plugins using interfaces.GenericUpdater interface, lineage needs to be passed to the updater method instead of individual domains. All of the (present and potential) installers do not work on per domain basis, while the lineage does contain a list of them for installers which do.

This also means that we don't unnecessarily run the updater method multiple times, potentially invoking expensive tooling up to $max_san_amount times.

* Make GenericUpdater use lineage as parameter and get invoked only once per lineage
2018-05-25 11:00:37 -07:00
Jacob Hoffman-Andrews
a03c68fc83 Clean up boulder-fetch a bit. (#6032)
The value for FAKE_DNS is now always the same because Boulder's
docker-compose hardcodes it, so skip some sed.

Set a time limit on how long we'll wait for boulder to come up.
2018-05-24 10:53:21 -07:00
Jeremy Gillula
b1bcccb04b Changing opt-in ask for emails (#6035) 2018-05-23 20:40:34 -07:00
ohemorange
a1f5dc27f2 Add domain to error message when no matching server block found (#6034) 2018-05-23 14:03:30 -07:00
Brad Warren
0b215366b1 turn off cancel notifications (#5918) 2018-05-23 13:57:22 -07:00
Jacob Hoffman-Andrews
4304ff0d62 Bring up just the boulder container. (#6031)
Boulder recently added a "netaccess" container which may conflict.
2018-05-23 11:33:21 -07:00
Kevin Le
deb5b072d9 Log cases when standalone fails to bind a port. (#5985)
* Log cases when standalone fails to bind a port.

* Fix linter + changed log message

* Changed multiline string format

* Fixed indentation in standalone.py
2018-05-23 19:59:49 +03:00
pdamodaran
8440d0814d fixed dependency-requirements.txt (#6023) 2018-05-22 15:35:12 -07:00
Quang Vu
cfd4b8f363 #4242 Support multi emails register (#5994)
This change will allow registering/updating account with multi emails.
Detail is enclosed in #4242

* support multi emails register

* add more test cases

* update test to unregister before register

* update create path to support multi emaill

* refactor payload updating

* fix typo

* move command line doc to another place

* revert the change for updating account registration info, added unit test

* rearrange text for consistency
2018-05-22 15:32:44 -07:00
Brad Warren
c9a206ca89 Get mypy passing with check_untyped_defs everywhere (#6021)
* unchecked_typed_defs everywhere

* fix mypy for lock_test

* add magic_typing

* fix mypy in letshelp

* fix validator errors in compat test

* fix mypy for test_driver.py

* fix mypy in util.py

* delint
2018-05-21 20:23:21 -07:00
ohemorange
0d3a157525 Merge pull request #6020 from certbot/mabayhan-patch
Set correct Nginx server root on FreeBSD and Darwin
2018-05-21 16:53:11 -07:00
schoen
5d1df1cb4c Merge pull request #6022 from certbot/revert-pycon
Revert "Add link to pycon issues (#5959)"
2018-05-21 15:23:58 -07:00
Brad Warren
dec97fc126 Revert "Add link to pycon issues (#5959)"
This reverts commit 68359086ff.
2018-05-18 17:48:30 -07:00
Paul Kehrer
366c50e28e switch signature verification to use pure cryptography (#6000)
* switch signature verification to use pure cryptography

On systems that prevent write/execute pages this prevents a segfault
that is caused by pyopenssl creating a dynamic callback in the
verification helper.

* switch to using a verifier for older cryptography releases

also add ec support, test vectors, and a test
2018-05-18 09:10:41 -07:00
Dmitry Figol
36dfd06503 Prepare certbot module for mypy check untyped defs (#6005)
* Prepare certbot module for mypy check untyped defs

* Fix #5952

* Bump mypy to version 0.600 and fix associated bugs

* Fix pylint bugs after introducing mypy

* Implement Brad's suggestions

* Reenabling pylint and adding nginx mypy back
2018-05-18 06:28:17 -07:00
Brad Warren
250c0d6691 cd before running tests (#6017)
When importing a module, Python first searches the current directory. See
https://docs.python.org/3/tutorial/modules.html#the-module-search-path. This
means that running something like `import certbot` from the root of the Certbot
repo will use the local Certbot files regardless of the version installed on
the system or virtual environment.

Normally this behavior is fine because the local files are what we want to
test, however, during our "oldest" tests, we test against older versions of our
packages to make sure we're keeping compatibility. To make sure our tests use
the correct versions, this commit has our tests cd to an empty temporary
directory before running tests.

We also had to change the package names given to pytest to be the names used in
Python to import the package rather than the name of the files locally to
accommodate this.
2018-05-18 06:05:26 -07:00
Brad Warren
94bf97b812 Add remaining DNS plugins to mypy.ini and sort (#6018) 2018-05-18 12:26:10 +03:00
Erica Portnoy
1239d7a881 check platform with correct python 2018-05-17 20:02:27 -07:00
TyrannosourceExe
9b2862ebb0 3692 --dry-run expiration emails (#6015)
* If --dry-run is used and there exists no staging account, create account with no email

* added unit testing of dry-run to ensure certbot does not ask the user to create an email, and that certbot creates an account with no email
2018-05-17 16:03:01 -07:00
Brad Warren
1be1bd9211 remove PYTHONPATH (#6016) 2018-05-17 09:23:05 -07:00
pdamodaran
20418cdd68 Fixed #5859 (#6011) 2018-05-17 06:52:11 -07:00
Brad Warren
41e1976c17 Fix noisy tests (#6004)
* Fixes #5570.

The issue is calls to atexit aren't mocked out. During the tests there are many
repeated calls registering functions to be called when the process exits so
when the tests finishes, it prints a ton of output from running those
registered functions. This suppresses that by mocking out atexit.

* Mock at a lower level.

This ensures we don't mess with any other mocks in this test class by mocking
at the lowest level we can. Other tests shouldn't be mocking out specific
internals of functions in other modules, so this should work just fine.
2018-05-16 06:24:14 -07:00
Douglas Anger
722dac86d5 Fix crash when email submission endpoint unavailable (#6002)
* Fix crash when email submission endpoint unavailable

Handle KeyError and ValueError so that if the email submission endpoint
goes down, Certbot can still run.

Add tests to eff_test.py:
 - simulate non-JSON response as described in issue #5858
 - simulate JSON response without 'status' element

Non-JSON response throws an uncaught ValueError when attempting to
decode as JSON. A JSON response missing the 'status' element throws an
uncaught KeyError when checking whether status is True or False.

Teach _check_response to handle ValueError and KeyError and report an
issue to the user.

Rewrite if statement as assertion with try-except block to make error
handling consistent within the function. Update test_not_ok to make
mocked raise_for_status function raise a requests.exceptions.HTTPError.

Resolves #5858

* Update PR with requested changes

- Use `if` instead of `assert` to check `status` element of response JSON
- Handle KeyError and ValueError in the same way
- Import requests at the beginning of eff_test.py
- Clear JSON in test case in a more idiomatic way
2018-05-15 12:50:47 -07:00
GmH
751f9843b4 fixed issue #5974 for certbot-dns-route53 (#5984)
* fixed issue #5974 for certbot-dns-route53

* fixed issue #5967 for certbot-dns-digitalocean

* update to use acme.magic_typing and DefaultDict class

* added no-name-in-module identifier, for issue #5974

* added unused-import identifier to disable option, for issue #5974
2018-05-15 11:22:09 -07:00
dschlessman
9bd5b3dda2 Issue 5951/check untyped defs apache (#5989)
* resolved mypy untyped defs in parser.py

* resolved mypy untyped defs in obj.py

* removed unused imports

* resolved mypy untyped defs in http_01.py

* resolved mypy untyped defs in tls_sni_01.py

* resolved mypy untyped defs in configurator.py

* address mypy too-many-arguments error in override_centos.py

* resolved mypy untyped defs in http_01_test.py

* removed unused 'conf' argument that was causing mypy method assignment error

* address mypy error where same variable reassigned to different type

* address pylint and coverage issues

* one character space change for formatting

* fix required acme version for certbot-apache
2018-05-15 10:40:32 -07:00
James Hiebert
307f45f88f Enable checking of type annotation in Nginx plugin (#5997)
* Adds type checking for certbot-nginx

* First pass at type annotation in certbot-nginx

* Ensure linting is disabled for timing imports

* Makes container types specific per PR comments

* Removes unnecessary lint option
2018-05-15 09:36:47 -07:00
signop
802fcc99ee Add requests-toolbelt hashes to requirements. (#6001)
Fixes certbot/certbot#5993
2018-05-15 08:50:09 -07:00
Douglas Anger
2d68c9b81e Display (None) instead of a bullet for empty lists (#5999)
Include a line break before "(None)" to maintain consistency with output
for lists that are not empty.

Previous result as expected for non-empty lists:

    >>> _format_list('+', ['one', 'two', 'three'])
    '\n+ one\n+ two\n+ three'

Previous unexpected result for empty lists:

    >>> _format_list('+', [])
    '\n+ '

New result as expected (unchanged) for non-empty lists:

    >>> _format_list('+', ['one', 'two', 'three'])
    '\n+ one\n+ two\n+ three'

New behavior more explicit for empty lists:

    >>> _format_list('+', [])
    '\n(None)'

Resolves #5886
2018-05-15 08:46:36 -07:00
James Hiebert
42ef252043 Adds a note about Python3 in the Developer Guide (#5998) 2018-05-15 07:58:33 -07:00
Douglas Anger
99d94cc7e8 Make request logs pretty in Python 3 (#5992)
Decode response data as UTF-8 to eliminate ugly bytes repr in Python 3.

Resolves #5932
2018-05-14 14:45:25 -07:00
signop
02b128a128 Add support for specifying source_address to ClientNetwork. (#5990)
For certbot/certbot#3489
2018-05-14 14:43:43 -07:00
speter
372d4a046c docs/using.rst: fix typo (#5962) 2018-05-14 14:40:06 -07:00
Sarah Braden
a724dc659b correct error message text now prompts user to run $certbot certificates (#5988) 2018-05-14 14:21:09 -07:00
Douglas Anger
907ee79715 Check_untyped_defs in mypy with clean output for certbot-dns-nsone (#5987)
* check_untyped_defs in mypy with clean output for certbot-dns-nsone

Resolves #5972
2018-05-14 13:18:49 -07:00
Kevin Le
c1471fe873 Document IPv6/IPv4 binding on standalone. (#5983) 2018-05-14 13:15:02 -07:00
Brad Warren
94829e35be Merge pull request #5982 from certbot/handle-mypy-ini-conflicts
Check unchecked defs in various DNS plugins
2018-05-14 11:43:33 -07:00
Brad Warren
19c6a5e6ee Merge branch 'issue_5968' into handle-mypy-ini-conflicts 2018-05-14 11:12:31 -07:00
Brad Warren
875c8d4c01 Merge branch 'feature/5971' into handle-mypy-ini-conflicts 2018-05-14 11:11:08 -07:00
Brad Warren
8d994ae30d Merge branch 'issue_5970' into handle-mypy-ini-conflicts 2018-05-14 11:10:30 -07:00
Sarah Braden
be03a976d5 fixed issue #5969 for certbot-dns-dnsmadeeasy (#5976) 2018-05-14 10:54:26 -07:00
Kevin Le
74448e9344 Set pause=False to fix view_config_changes (#5977) 2018-05-14 10:45:12 -07:00
Sarah Braden
33583792fa blank line at eof 2018-05-14 13:43:15 -04:00
Sarah Braden
430f9414a9 fix for issue #5968 for certbot-dns-dnsimple 2018-05-14 13:42:12 -04:00
Douglas Anger
5636b55507 Check_untyped_defs in mypy with clean output for certbot-dns-luadns
* check_untyped_defs in mypy with clean output for certbot-dns-luadns

Resolves #5971
2018-05-14 13:40:27 -04:00
Sarah Braden
4bd9f4dac4 fix for issue #5970 regarding certbot-dns-google 2018-05-14 13:30:38 -04:00
Douglas Anger
2d45b0b07a Check_untyped_defs in mypy with clean output for certbot_dns_rfc2136 (#5975)
* check_untyped_defs in mypy with clean output for certbot_dns_rfc2136

Resolves #5973
2018-05-14 10:26:33 -07:00
Sarah Braden
a0775f42ba fixed issue #5969 for certbot-dns-dnsmadeeasy 2018-05-14 12:34:27 -04:00
Brad Warren
cce23c86c7 partially revert #5953 (#5964) 2018-05-14 08:08:24 -07:00
Brad Warren
68359086ff Add link to pycon issues (#5959)
* add link to pycon issues

* add especially
2018-05-13 08:06:19 -07:00
ohemorange
5940ee92ab add ready status type (#5941) 2018-05-11 14:25:02 -07:00
Brad Warren
9568f9d5b0 Add instructions on how to ask for help (#5957)
* Add instructions on how to ask for help

* s/setup/set up
2018-05-11 10:52:45 -07:00
Brad Warren
2f89a10f50 Small dev docs improvements (#5953)
* Clarify UNIX only

* Have people develop natively.

Some systems like Arch Linux and macOS require --debug in order to install
dependencies.

Our bootstrapping script for macOS works, so let's let people who want to
develop natively.

* briefly mention docker as dev option

* remove bad _common.sh info

* update OS dep section

* Remove sudo from certbot-auto usage

When sudo isn't available, Certbot is able to fall back to su. Removing it from
the instructions here allows the command to work when its run in minimal
systems like Docker where sudo may not be installed.

* copy advice about missing interpreters

* Improve integration tests docs

Explain what a boulder is and tell people they probably should just let the
tests run in Travis.

* Don't tell people to run integration tests.

I don't think any paid Certbot devs run integration tests locally and instead
rely on Travis. Let's not make others do it.

* fix spacing

* you wouldn't download a CA

* address review comments
2018-05-11 07:07:02 -07:00
Brad Warren
8851141dcf Revert "disable apacheconftest (#5937)" (#5954)
This reverts commit 83ea820525.
2018-05-11 06:12:10 -07:00
ohemorange
832941279b Specify that every domain name needs to be under a server_name directive (#5949) 2018-05-10 16:46:57 -07:00
Brad Warren
83ea820525 disable apacheconftest (#5937) 2018-05-09 12:11:36 -07:00
ohemorange
3eaf35f1e2 Check_untyped_defs in mypy with clean output for acme (#5874)
* check_untyped_defs in mypy with clean output for acme

* test entire acme module

* Add typing as a dependency because it's only in the stdlib for 3.5+

* Add str_utils, modified for python2.7 compatibility

* make mypy happy in acme

* typing is needed in prod

* we actually only need typing in acme so far

* add tests and more docs for str_utils

* pragma no cover

* add magic_typing

* s/from typing/from magic_typing/g

* move typing to dev_extras

* correctly set up imports

* remove str_utils

* only type: ignore for OpenSSL.SSL, not crypto

* Since we only run mypy with python3 anyway and we're fine importing it when it's not actually there, there's no actual need for typing to be present as a dependency

* comment magic_typing.py

* disable wildcard-import im magic_typing

* disable pylint errors

* add magic_typing_test

* make magic_typing tests work alongside other tests

* make sure temp_typing is set

* add typing as a dev dependency for python3.4

* run mypy with python3.4 on travis to get a little more testing with different environments

* don't stick typing into sys.modules

* reorder imports
2018-05-03 13:10:33 -07:00
Brad Warren
32e85e9a23 correct metaclass usage everywhere (#5919) 2018-05-03 10:59:25 +03:00
Joona Hoikkala
95c0c4a708 Py2 and Py3 compatibility for metaclass interfaces (#5913) 2018-05-02 15:56:37 -07:00
Brad Warren
03b20d972c Merge pull request #5914 from certbot/candidate-0.24.0
Update autos and version number for 0.24.0 release
2018-05-02 12:19:09 -07:00
Brad Warren
7fa3455dc6 Update changelog for 0.24.0 (#5915) 2018-05-02 12:18:29 -07:00
Joona Hoikkala
552bfa5eb7 New interfaces for installers to run tasks on renew verb (#5879)
* ServerTLSUpdater and InstallerSpecificUpdater implementation

* Fixed tests and added disables for linter :/

* Added error logging for misconfigurationerror from plugin check

* Remove redundant parameter from interfaces

* Renaming the interfaces

* Finalize interface renaming and move tests to own file

* Refactored the runners

* Refactor the cli params

* Fix the interface args

* Fixed documentation

* Documentation and naming fixes

* Remove ServerTLSConfigurationUpdater

* Remove unnecessary linter disable

* Rename run_renewal_updaters to run_generic_updaters

* Do not raise exception, but make log message more informative and visible for the user

* Run renewal deployer before installer restart
2018-05-02 10:52:54 +03:00
Brad Warren
0ec0d79c35 Bump version to 0.25.0 2018-05-01 16:59:48 -07:00
Brad Warren
4b870ef940 Release 0.24.0 2018-05-01 16:59:32 -07:00
Jacob Hoffman-Andrews
bf30226c69 Restore parallel waiting to Route53 plugin (#5712)
* Bring back parallel updates to route53.

* Re-add try

* Fix TTL.

* Remove unnecessary wait.

* Add pylint exceptions.

* Add dummy perform.

* review.feedback

* Fix underscore.

* Fix lint.
2018-04-25 15:09:50 -07:00
Brad Warren
f510f4bddf Update good volunteer task to good first issue. (#5891) 2018-04-24 06:38:15 -07:00
Aleksandr Volochnev
9c15fd354f Updated base image to python:2-alpine3.7 (#5889)
Updated base image from python:2-alpine to python:2-alpine3.7. Python:2-alpine internally utilises alpine version 3.4 which end-of-life date is the first of May 2018.
2018-04-20 15:17:05 -07:00
Brad Warren
726f3ce8b3 Remove EOL'd Ubuntu from targets.yaml (#5887)
See https://wiki.ubuntu.com/Releases.

Ubuntu 15.* repositories have been shut down for months now causing our tests
to always fail on these systems. While the tests on Ubuntu 12.04 still work, it
has been unsupported by Canonical for almost a year and I don't think we should
hamstring ourselves trying to continue to support it ourselves.
2018-04-19 17:57:41 -07:00
Erik Rose
f40e04401f Don't install enum34 when using Python 3.4 or later. Fix #5456. (#5846)
The re stdlib module requires attrs that don't exist in the backported 3.4 version.

Technically, we are changing our install behavior beyond what is necessary. Previously, enum34 was used for 3.4 and 3.5 as well, and it happened not to conflict, but I think it's better to use the latest bug-fixed stdlib versions as long as they meet the needs of `cryptography`, which is what depends on enum34. That way, at least the various stdlib modules are guaranteed not to conflict with each other.
2018-04-19 16:35:21 -07:00
Jeremy Gillula
398bd4a2cd Emphasizing the warnings in the READMEs in /etc/letsencrypt/live/exam… (#5871)
* Emphasizing the warnings in the READMEs in /etc/letsencrypt/live/example.com

* Making the warning more of a statement
2018-04-18 16:46:39 -07:00
Joona Hoikkala
a024aaf59d Enhance verb (#5596)
* Add the cli parameters

* Tests and error messages

* Requested fixes

* Only handle SSL vhosts

* Interactive cert-name selection if not defined on CLI

* Address PR comments

* Address review comments

* Added tests and addressed review comments

* Move cert manager tests to correct file

* Add display ops tests

* Use display util constants instead of hardcoded values in tests
2018-04-18 11:08:30 -07:00
Brad Warren
261d063b10 Revert fix-macos-pytest (#5853)
* Revert "Fix pytest on macOS in Travis (#5360)"

This reverts commit 5388842e5b.

* remove oldest passenv
2018-04-18 10:02:31 -07:00
Brad Warren
a9e01ade4c Revert "use older boulder version (#5852)" (#5855)
This reverts commit 6b29d159a2.
2018-04-17 17:17:15 -07:00
Kiel C
5c7fc07ccf Adjust file paths message from Warning to Info. (#5743) 2018-04-17 13:52:39 -07:00
Brad Warren
6dc8b66760 Add _choose_lineagename and update docs (#5650) 2018-04-17 11:27:45 -07:00
mabayhan
b39507c5af Update constants.py
Fixed comma.
2018-04-17 09:09:27 -07:00
ohemorange
590ec375ec Get mypy running in travis for easier review (#5875) 2018-04-13 16:10:58 -07:00
Axel
523cdc578d Add port option for rfc2136 plugin (#5844) 2018-04-13 19:17:08 +03:00
Brad Warren
e0a5b1229f help clarify version number (#5865)
(Hopefully) helps make it clearer that that 22 and 24 corresponds to Apache 2.2 and 2.4.
2018-04-12 19:02:40 -07:00
ohemorange
6253acf335 Get mypy running with clean output (#5864)
Fixes #5849.

* extract mypy flags into mypy.ini file

* Get mypy running with clean output
2018-04-12 18:53:07 -07:00
Brad Warren
a708504d5b remove test metaclass (#5863) 2018-04-12 18:28:00 -07:00
mabayhan
c443db0618 Update constants.py
On FreeBSD or MacOS, "certbot --nginx" fails. The reason is, at these op. systems, nginx directory is different than linux.
2018-04-12 16:33:10 -07:00
ohemorange
2d31598484 Get mypy tox env running in the current setup (#5861)
* get mypy tox env running in the current setup

* use any python3 with mypy

* pin mypy dependencies
2018-04-12 15:47:39 -07:00
Brad Warren
6b29d159a2 use older boulder version (#5852) 2018-04-11 16:14:55 -07:00
sydneyli
88ceaa38d5 Bump certbot's acme depenency to 0.22.1 (#5826) 2018-04-11 14:36:53 -07:00
Brad Warren
e7db97df87 Update CHANGELOG for 0.23.0 (#5822)
* Update CHANGELOG for 0.23.0

* correct date
2018-04-11 11:16:12 -07:00
Joona Hoikkala
4a8e35289c PluginStorage to store variables between invocations. (#5468)
The base class for Installer plugins `certbot.plugins.common.Installer` now provides functionality of `PluginStorage` to all installer plugins. This allows a plugin to save and retrieve variables in between of invocations.

The on disk storage is basically a JSON file at `config_dir`/`.pluginstorage.json`, usually `/etc/letsencrypt/.pluginstorage.json`. The JSON structure is automatically namespaced using the internal plugin name as a namespace key. Because the actual storage is JSON, the supported data types are: dict, list, tuple, str, unicode, int, long, float, boolean and nonetype.

To add a variable from inside the plugin class:
`self.storage.put("my_variable_name", my_var)`

To fetch a variable from inside the plugin class:
`my_var = self.storage.fetch("my_variable_key")`

The storage state isn't written on disk automatically, but needs to be called:
`self.storage.save()`

* Plugin storage implementation

* Added config_dir to existing test mocks

* PluginStorage test cases

* Saner handling of bad config_dir paths

* Storage moved to Installer and not initialized on plugin __init__

* Finetuning and renaming
2018-04-11 08:54:55 -07:00
Brad Warren
58626c3197 Double max_rounds (#5842) 2018-04-09 16:58:58 -07:00
schoen
56fb667e15 Merge pull request #5754 from edaleeta/updating-developer-guide
Add note to developer guide docs about installing docs extras. (#4946)
2018-04-09 16:36:36 -07:00
Brad Warren
0153c04af3 Merge pull request #5829 from certbot/candidate-0.23.0
Update certbot-auto and versions to reflect 0.23.0 release
2018-04-09 12:45:15 -07:00
Peter Linss
db938dcc0e Update messages.py (#5759)
Add wildcard field to AuthorizationResource
2018-04-05 13:39:35 -07:00
Brad Warren
0e30621355 Bump version to 0.24.0 2018-04-04 15:05:08 -07:00
Brad Warren
16b2539f72 Release 0.23.0 2018-04-04 15:04:43 -07:00
Brad Warren
b6afba0d64 Include testdata (#5827) 2018-04-04 14:33:41 -07:00
Brad Warren
b24d9dddc3 Revert ACMEv2 default (#5819)
* Revert "document default is ACMEv2 (#5818)"

This reverts commit 2c502e6f8b.

* Revert "Update default to ACMEv2 server (#5722)"

This reverts commit 4d706ac77e.
2018-04-03 17:55:12 -07:00
Joona Hoikkala
9996730fb1 If restart fails, try alternative restart command if available (#5500)
* Use alternative restart command if available in distro overrides
2018-04-03 14:05:37 -07:00
Brad Warren
2c502e6f8b document default is ACMEv2 (#5818) 2018-04-03 14:04:51 -07:00
ohemorange
bdaccb645b Support quoted server names in Nginx (#5811)
* Support quoted server names in Nginx

* add unit test to check that we strip quotes

* update configurator test
2018-04-03 12:14:23 -07:00
Joona Hoikkala
f5ad08047b Fix comparison to check values (#5815) 2018-04-03 12:04:57 -07:00
Brad Warren
8fd3f6c64c fixes #5380 (#5812) 2018-04-03 11:44:13 -07:00
Joshua Bowman
4d706ac77e Update default to ACMEv2 server (#5722) 2018-03-30 17:16:48 -07:00
sydneyli
8231b1a19c Pin Lexicon version to 2.2.1 (#5803) 2018-03-29 17:09:21 -07:00
ohemorange
5ff7f2211e Explicitly add six as a dependency in letsencrypt-auto-source dockerfiles (#5808)
* update documentation

* explicitly add six as a dependency in letsencrypt-auto-source dockerfiles

* pin six version
2018-03-29 15:34:38 -07:00
Brad Warren
7630550ac4 Revert "Update oldest tests to test against 0.22.0 versions (#5800)" (#5809)
This reverts commit 336950c0b9.
2018-03-29 14:15:59 -07:00
Brad Warren
336950c0b9 Update oldest tests to test against 0.22.0 versions (#5800) 2018-03-28 08:37:00 -07:00
ohemorange
a779e06d47 Add integration tests for nginx plugin (#5441)
* Add a rewrite directive for the .well-known location so we don't hit existing rewrites

* add comment

* Add (nonexistent) document root so we don't use the default value

* Add integration tests for nginx plugin

* add a sleep 5 to test on travis

* put sleep 5 in the right spot

* test return status of grep respecting -e and note that we're actually not posix compliant

* redelete newline
2018-03-27 17:33:48 -07:00
ohemorange
669312d248 We don't try to add location blocks through a mechanism that checks REPEATABLE_DIRECTIVES, and it wouldn't work as an accurate check even if we did, so just remove it (#5787) 2018-03-27 15:25:34 -07:00
ohemorange
4d082e22e6 Remove ipv6only=on from duplicated vhosts (#5793)
* rename delete_default to remove_singleton_listen_params

* update docstring

* add documentation to obj.py

* add test for remove duplicate ipv6only

* Remove ipv6only=on from duplicated vhosts

* add test to make sure ipv6only=on is not erroneously removed
2018-03-27 15:11:39 -07:00
sydneyli
af2cce4ca8 fix(auth_handler): cleanup is always called (#5779)
* fix(auth_handler): cleanup is always called

* test(auth_handler): tests for various error cases
2018-03-26 17:09:02 -07:00
ohemorange
804fd4b78a factor out location_directive_for_achall (#5794) 2018-03-26 16:28:30 -07:00
Andrew Starr-Bochicchio
8cdb213a61 Google DNS: Mock API discovery to run tests without internet connection. (#5791)
* Google DNS: Mock API discovery to run tests without internet connection.

* Allow test to pass when run from main cerbot package.
2018-03-26 16:12:55 -07:00
ohemorange
e9707ebc26 Allow 'default' along with 'default_server' in Nginx (#5788)
* test default detection

* Allow 'default' along with 'default_server' in Nginx

* Test that default gets written out as default_server in canonical string

* remove superfulous parens
2018-03-26 14:56:31 -07:00
ohemorange
8d0d42a739 Refactor _add_directive into separate functions (#5786)
* Refactor _add_directive to separate functions

* UnspacedList isn't idempotent

* refactor parser in add_server_directives and update_or_add_server_directives

* update parser tests

* remove replace=False and add to update_or_add for replace=True in configurator

* remove replace=False and add to update_or_add for replace=True in http01

* update documentation
2018-03-23 16:30:13 -07:00
Alokin Software Pvt Ltd
693cb1d162 Support Openresty in the NGINX plugin (#5467)
* fixes #4919 openresty_support

* making the regex more general

* reformatting warning to pass lint

* Fix string formatting in logging function

* Fix LE_AUTO_VERSION
2018-03-22 17:50:05 -07:00
Delan Azabani
8e9a4447ff make pip_install.sh compatible with POSIX sh(1) again (#5622) 2018-03-22 12:24:53 -07:00
sydneyli
bca0aa48c2 logging: log timestamps as local timezone instead of UTC (#5607)
* logging: log timestamps as local timezone instead of UTC

* test(logging): expect localtime instead of gmtime

* linter fix in logging
2018-03-21 15:41:33 -07:00
Brad Warren
afb6260c34 update changelog for 0.22.1 and 0.22.2 (#5770) 2018-03-21 11:21:35 -07:00
Brad Warren
3f291e51c6 Update certbot auto to reflect 0.22 point releases (#5768)
* Release 0.22.1

(cherry picked from commit 05c75e34e2)

* Bump version to 0.23.0

(cherry picked from commit 6fd3a57791)

* Release 0.22.2

(cherry picked from commit ea445ed11e)

* Bump version to 0.23.0

(cherry picked from commit cbe87d451c66931a084f4e513d899aae085a37d3)
2018-03-21 11:21:09 -07:00
Sebastiaan Lokhorst
fe8e0c98c5 Update docs for Apache plugin (#5776)
The supported OSs are now listed in another file. The table also contradicted the text below.
2018-03-21 11:18:39 -07:00
Harlan Lieberman-Berg
cbd827382e Documentation on cron renewal (#5460) 2018-03-21 08:17:06 -07:00
Edelita Valdez
f01aa1295f Add quotes to command for docs extras. 2018-03-20 23:40:44 -07:00
noci2012
c0dc31fd88 Allow _acme-challenge as a zone (#5707)
* Allow _acme-challenge as a zone

Like described here:
https://github.com/lukas2511/dehydrated/wiki/example-dns-01-nsupdate-script

Not using this patch may be an issue if the parent zone has been (where a wildcard certificate has been requested.) signed by DNSSEC.

Please consider this also for inclusion before dns-01 will be allowed for wildcards.

* Update dns_rfc2136.py

forgot one domain_name reference

* Update dns_rfc2136.py

moved domain up & added assignment.

* Update dns_rfc2136_test.py

tests adjusted to new calls.

* Update dns_rfc2136_test.py

Forgot on DOMAIN...

* Update dns_rfc2136_test.py

* Update dns_rfc2136.py

pydoc updates.

* Update dns_rfc2136.py
2018-03-20 13:29:24 -07:00
Brad Warren
41ce108881 Fix cleanup_challenges call (#5761)
* fixes cleanup_challenges

* add test to prevent regressions
2018-03-19 16:51:01 -07:00
Gopal Adhikari
41ed6367b4 Fix typo: damain -> domain (#5756)
Fix typo: damain -> domain in certbot/util.py:607
2018-03-19 11:08:45 -07:00
Edelita Valdez
a26a78e84e Add note to developer guide docs about installing docs extras. (#4946) 2018-03-17 19:24:14 -07:00
sydneyli
3077b51500 Merge pull request #5749 from certbot/fix-docker-link
Fix Docker link
2018-03-16 18:15:05 -07:00
Brad Warren
d4834da0f4 fix docker link 2018-03-16 17:48:46 -07:00
Brad Warren
ba6bdb5099 Fix acme.client.Client.__init__ (#5747)
* fixes #5738

* add test to prevent regressions
2018-03-16 17:45:46 -07:00
sydneyli
79d90d6745 feat(nginx plugin): add HSTS enhancement (#5463)
* feat(nginx plugin): add HSTS enhancement

* chore(nginx): factor out block-splitting code from redirect & hsts enhancements!

* chore(nginx): merge fixes

* address comments

* fix linter: remove a space

* fix(config): remove SSL directives in HTTP block after block split, and remove_directive removes 'Managed by certbot' comment

* chore(nginx-hsts): Move added SSL directives to a constant on Configurator class

* fix(nginx-hsts): rebase on wildcard cert changes
2018-03-16 15:27:39 -07:00
ohemorange
5ecb68f2ed Update instances of acme-staging url to acme-staging-v02 (#5734)
* update instances of acme-staging url to acme-staging-v02

* keep example client as v1

* keep deactivate script as v1
2018-03-16 15:24:55 -07:00
Brad Warren
b3e73bd2ab removes blank line from chain.pem (#5730) 2018-03-14 17:38:37 -07:00
Spencer Eick
065e923bc9 Improve "cannot find cert of key directive" error (#5525) (#5679)
- Fix code to log separate error messages when either SSLCertificateFile or SSLCertificateKeyFile -
 directives are not found.
- Update the section in install.rst where the relevant error is referenced.
- Edit a docstring where 'cert' previously referred to certificate.
- Edit test_deploy_cert_invalid_vhost in the test suite to cover changes.

Fixes #5525.
2018-03-14 12:59:13 -07:00
cclauss
e405aaa4c1 Fix print() and xrange() for Python 3 (#5590) 2018-03-14 09:37:29 -07:00
Brad Warren
9ea14d2e2b Add docs about --server (#5713)
* Add docs about --server

* address review comments

* mention server in Docker docs

* correct server URL

* Use prod ACMEv2 example
2018-03-14 08:48:40 -07:00
Brad Warren
1d0e3b1bfa Add documentation about DNS plugins and Docker (#5710)
* make binding port optional

* Add DNS docker docs

* add basic DNS plugin docs

* Add link to DNS plugin docs from Docker docs

* Shrink table size
2018-03-13 07:08:01 -07:00
Brad Warren
d310ad18c7 Put API link at the bottom of DNS plugin docs (#5699)
* Put link to API at the bottom for future docs.

* Put API link at the bottom of existing docs.
2018-03-12 17:10:23 -07:00
Brad Warren
53c6b9a08f Merge pull request #5682 from certbot/candidate-0.22.0
Release 0.22.0
2018-03-12 13:06:30 -07:00
Brad Warren
64d647774e Update the changelog to reflect 0.22.0 (#5691) 2018-03-12 10:57:46 -07:00
Brad Warren
f13fdccf04 document resps param (#5695) 2018-03-12 10:51:45 -07:00
Brad Warren
2e6d65d9ec Add readthedocs requirements files (#5696)
* Add readthedocs requirements files.

* Only install docs extras for plugin.
2018-03-08 17:24:30 -08:00
Brad Warren
cc24b4e40a Fix --allow-subset-of-names (#5690)
* Remove aauthzr instance variable

* If domain begins with fail, fail the challenge.

* test --allow-subset-of-names

* Fix renewal and add extra check

* test after hook checks
2018-03-08 11:12:33 -08:00
Brad Warren
cc18da926e Quiet pylint (#5689) 2018-03-08 11:09:31 -08:00
sydneyli
f4bac423fb fix(acme): client._revoke sends default content_type (#5687) 2018-03-07 15:09:47 -08:00
Brad Warren
7a495f2656 Bump version to 0.23.0 2018-03-07 10:26:08 -08:00
Brad Warren
77fdb4d7d6 Release 0.22.0 2018-03-07 10:25:42 -08:00
Brad Warren
e0ae356aa3 Upgrade pipstrap to 1.5.1 (#5681)
* upgrade pipstrap to 1.5.1

* build leauto
2018-03-07 09:10:47 -08:00
Brad Warren
6357e051f4 Fallback without dns.resourceRecordSets.list permission (#5678)
* Add rrset list fallback

* List dns.resourceRecordSets.list as required

* Handle list failures differently for add and del

* Quote record content

* disable not-callable for iter_entry_points

* List update permission
2018-03-06 15:32:22 -08:00
Brad Warren
d62c56f9c9 Remove the assumption the domain is unique in the manual plugin (#5670)
* use entire achall as key

* Add manual cleanup hook

* use manual cleanup hook
2018-03-06 07:21:01 -08:00
Brad Warren
cee9ac586e Don't report coverage on Apache during integration tests (#5669)
* ignore Apache coverage

* drop min coverage to 67
2018-03-06 07:20:34 -08:00
Brad Warren
a643877f88 Merge pull request #5672 from certbot/route53_acmev2v2
Version 2 of ACMEv2 support for Route53 plugin
2018-03-06 07:19:46 -08:00
Brad Warren
7bc45121a1 Remove the need for route53:ListResourceRecordSets
* add test_change_txt_record_delete
2018-03-05 18:58:32 -08:00
Joona Hoikkala
fe682e779b ACMEv2 support for Route53 plugin 2018-03-05 18:58:27 -08:00
Joona Hoikkala
441625c610 Allow Google DNS plugin to write multiple TXT record values (#5652)
* Allow Google DNS plugin to write multiple TXT record values in same resourcerecord

* Atomic updates

* Split rrsets request
2018-03-05 12:49:02 -08:00
Brad Warren
cc344bfd1e Break lockstep between our packages (#5655)
Fixes #5490.

There's a lot of possibilities discussed in #5490, but I'll try and explain what I actually did here as succinctly as I can. Unfortunately, there's a fair bit to explain. My goal was to break lockstep and give us tests to ensure the minimum specified versions are correct without taking the time now to refactor our whole test setup.

To handle specifying each package's minimum acme/certbot version, I added a requirements file to each package. This won't actually be included in the shipped package (because it's not in the MANIFEST).

After creating these files and modifying tools/pip_install.sh to use them, I created a separate tox env for most packages (I kept the DNS plugins together for convenience). The reason this is necessary is because we currently use a single environment for each plugin, but if we used this approach for these tests we'd hit issues due to different installed plugins requiring different versions of acme/certbot. There's a lot more discussion about this in #5490 if you're interested in this piece. I unfortunately wasted a lot of time trying to remove the boilerplate this approach causes in tox.ini, but to do this I think we need negations described at complex factor conditions which hasn't made it into a tox release yet.

The biggest missing piece here is how to make sure the oldest versions that are currently pinned to master get updated. Currently, they'll stay pinned that way without manual intervention and won't be properly testing the oldest version. I think we should solve this during the larger test/repo refactoring after the release because the tests are using the correct values now and I don't see a simple way around the problem.

Once this lands, I'm planning on updating the test-everything tests to do integration tests with the "oldest" versions here.

* break lockstep between packages

* Use per package requirements files

* add local oldest requirements files

* update tox.ini

* work with dev0 versions

* Install requirements in separate step.

* don't error when we don't have requirements

* install latest packages in editable mode

* Update .travis.yml

* Add reminder comments

* move dev to requirements

* request acme[dev]

* Update pip_install documentation
2018-03-05 09:50:19 -08:00
Brad Warren
e1878593d5 Ensure fullchain_pem in the order is unicode/str (#5654)
* Decode fullchain_pem in ACMEv1

* Convert back to bytes in Certbot

* document bytes are returned
2018-03-05 07:27:44 -08:00
Brad Warren
31805c5a5f Merge pull request #5628 from certbot/dns-docker
Add DNS Dockerfiles
2018-03-02 11:36:16 -08:00
Brad Warren
57bdc590df Add DNS Dockerfiles 2018-02-26 16:27:38 -08:00
Brad Warren
66953435c9 Merge remote-tracking branch 'starttls-everywhere/certbotify' into postfix 2017-08-29 10:44:07 -07:00
Brad Warren
dde0bf0821 Remove non-plugin files 2017-08-29 10:42:31 -07:00
Brad Warren
142bc33545 Remove dead code 2017-08-29 10:38:53 -07:00
Brad Warren
a339de80f4 Add save() 2017-08-28 17:17:29 -07:00
Brad Warren
d663f7981a Add _write_config_changes 2017-08-28 15:24:51 -07:00
Brad Warren
72637b2cf6 Add util.check_call 2017-08-28 15:20:22 -07:00
Brad Warren
d0ea5958f9 Protect _set_config_var 2017-08-28 14:54:53 -07:00
Brad Warren
4805fb4b88 Add deploy_cert 2017-08-28 14:52:05 -07:00
Brad Warren
11b820c0e4 add set_config_var 2017-08-28 14:41:07 -07:00
Brad Warren
b21b66c0c0 Test restart command. 2017-08-25 14:29:50 -07:00
Brad Warren
218e15c9d4 Make restart() more robust. 2017-08-25 14:25:22 -07:00
Brad Warren
a6c08a2e25 Update prepare docstring. 2017-08-25 14:03:29 -07:00
Brad Warren
68dc678eed Call config_test in prepare 2017-08-25 14:02:57 -07:00
Brad Warren
5f3be9b1cf test config_test failure 2017-08-25 13:58:45 -07:00
Brad Warren
2ae187b1d6 Update config_test method 2017-08-25 13:55:21 -07:00
Brad Warren
b4b5c44750 Check postfix executable is found. 2017-08-25 13:38:07 -07:00
Brad Warren
a29a99fb6f Add --postfix-ctl flag. 2017-08-25 13:34:03 -07:00
Brad Warren
0f4c5c2305 Use common installer base 2017-08-25 11:41:30 -07:00
Brad Warren
60c6cc5f2a Write enhance() 2017-08-24 15:26:52 -07:00
Brad Warren
00e28592b6 Add supported_enhancements 2017-08-24 15:18:40 -07:00
Brad Warren
b92df1b71c Remove unused find_postfix_cf 2017-08-24 15:15:41 -07:00
Brad Warren
baf0d3343a Remove postfix version notes 2017-08-24 15:13:02 -07:00
Brad Warren
cc3896d5d4 Add test_lock_error 2017-08-24 15:12:11 -07:00
Brad Warren
b94e268f83 Remove unused certs_only_config 2017-08-24 15:05:39 -07:00
Brad Warren
b9177948d3 Remove _write_config 2017-08-24 15:05:24 -07:00
Brad Warren
3b2e9e49be Remove unneeded instance variables 2017-08-24 15:04:36 -07:00
Brad Warren
0efc02d6ee Lock the Postfix config dir 2017-08-24 15:03:11 -07:00
Brad Warren
967a1830e6 Rewrite get_all_names 2017-08-24 14:59:25 -07:00
Brad Warren
90ffe2aac0 Remove legacy get_all_certs_and_keys() method 2017-08-24 09:04:57 -07:00
Brad Warren
b342f40c2b remove old comments 2017-08-23 15:16:36 -07:00
Brad Warren
83e37acc8b group IPlugin methods 2017-08-23 15:16:07 -07:00
Brad Warren
d1f3a2deef move _get_version 2017-08-23 15:13:06 -07:00
Brad Warren
c9813a44d7 protect get_version() 2017-08-10 16:42:48 -07:00
Brad Warren
b98f541b91 clean up prepare() 2017-08-10 16:41:59 -07:00
Brad Warren
50a1f6340f Add _check_version. 2017-08-10 16:38:27 -07:00
Brad Warren
8c4ff5cb63 Use context manager to read conf file. 2017-08-10 16:36:08 -07:00
Brad Warren
290f5b8ce7 add test_set_config_dir 2017-08-10 16:35:21 -07:00
Brad Warren
48c5731a6b Write out temp config instead of mocking. 2017-08-10 16:29:08 -07:00
Brad Warren
749f758adb use a temporary directory 2017-08-10 16:25:30 -07:00
Brad Warren
2e8a8dfed5 add _set_config_dir 2017-08-10 16:17:46 -07:00
Brad Warren
25d1f6ec75 Test all branches of test_get_config_var 2017-08-10 16:11:09 -07:00
Brad Warren
4c4b63437f Test building of get_config_var command 2017-08-10 15:28:17 -07:00
Brad Warren
02c7eca6da Rename test classes and methods. 2017-08-10 15:08:41 -07:00
Brad Warren
b72dfc0c08 Add get_config_var 2017-08-10 15:07:32 -07:00
Brad Warren
7334fc3066 Rename postfix_dir to config_dir 2017-08-10 14:29:56 -07:00
Brad Warren
4e5740615c Use util.check_output in Postfix installer 2017-08-10 14:25:08 -07:00
Brad Warren
5a1d031f07 Rename util to certbot_util 2017-08-10 14:19:44 -07:00
Brad Warren
4715b2b12c Further document check_output 2017-08-10 14:18:27 -07:00
Brad Warren
5beaae3b65 Add check_output function and tests. 2017-08-10 14:17:13 -07:00
Brad Warren
dfd1cceb9b Test prepare() failure due to missing postconf 2017-08-10 12:26:47 -07:00
Brad Warren
192f0f60da test add_parser_arguments 2017-08-10 12:19:59 -07:00
Brad Warren
b395b72d1b Don't hardcode postconf path. 2017-08-10 11:51:01 -07:00
Brad Warren
a2dbf2fe4c Fix spacing 2017-08-10 11:20:21 -07:00
Brad Warren
86fe5ad362 Move calls to postconf to prepare(). 2017-08-10 11:16:46 -07:00
Brad Warren
89ae874f89 (temporarily) remove ca-certificates logic 2017-08-10 11:03:47 -07:00
Brad Warren
2a217189a6 (temporarily) remove policy_file 2017-08-10 10:58:27 -07:00
Brad Warren
481fb8413b Fix Postfix Installer __init__() 2017-08-10 08:50:08 -07:00
Brad Warren
d97a15861b Add --postfix-config-dir argument 2017-08-09 16:06:06 -07:00
Brad Warren
a15fe57225 remove policy config param 2017-08-09 15:57:19 -07:00
Brad Warren
6c5a8423b8 Remove unused logger from tests 2017-08-04 10:28:52 -07:00
Brad Warren
a66500ea38 Remove unused version argument. 2017-08-04 10:25:09 -07:00
Brad Warren
49cdfcec06 add six dependency 2017-08-04 10:20:36 -07:00
Brad Warren
b50a71ff4e Remove fopen argument in favor of mock.
This simplifies the actual production code and is a more standard approach in
Python.
2017-08-04 10:19:46 -07:00
Brad Warren
b37be61807 Import installer module directly in tests. 2017-08-04 10:12:54 -07:00
Brad Warren
4a3fd19c93 Move parse_line to the end of installer.py 2017-08-04 10:00:04 -07:00
Brad Warren
66ba0b5276 Remove invalid permissions exception.
Once things like locks are added, this error shouldn't be possible as it will
have occurred earlier.
2017-08-04 09:57:44 -07:00
Brad Warren
61c2209110 Use Certbot error types in the Postfix Installer 2017-08-04 09:56:39 -07:00
Brad Warren
694746409f s/ExistingConfigError/MisconfigurationError 2017-08-04 09:42:26 -07:00
Brad Warren
1c258c0a2c Add basic docstrings to Installer 2017-08-04 09:31:10 -07:00
Brad Warren
6c4b3c08a7 Clean up installer imports 2017-08-04 09:30:11 -07:00
Brad Warren
c2a8ce59ae Remove code to run the installer as on its own. 2017-08-04 09:28:22 -07:00
Brad Warren
5bf4ad1f52 Rename PostfixConfigGenerator to simply Installer 2017-08-04 09:25:12 -07:00
Brad Warren
ae08dc6bea Fix Postfix installer tests 2017-08-04 09:24:04 -07:00
Brad Warren
f89051cc2a Completely implement the Certbot plugin interfaces 2017-08-04 09:18:51 -07:00
Brad Warren
74b22a596e Ignore egg-info dirs 2017-08-04 09:03:30 -07:00
Brad Warren
e2d95b3719 Create packaging around PostfixConfigGenerator. 2017-08-04 09:02:56 -07:00
Aaron Zauner
dca274085d Merge pull request #38 from ekohl/patch-1
Correct markdown link syntax
2017-05-13 07:59:15 +02:00
Ewoud Kohl van Wijngaarden
619e273ae5 Correct markdown link syntax 2017-05-10 15:44:55 +02:00
Peter Eckersley
baa563f359 Merge pull request #30 from EFForg/azet/readme-fixup
README fixup
2016-05-12 19:12:50 -07:00
Aaron Zauner
64f2ddfa80 Merge pull request #31 from EFForg/dmwilcox/start-le-api
Dmwilcox/start le api
2016-05-03 13:45:42 +07:00
Daniel Wilcox
a5f23b5314 Configure logger to be a touch louder... than silent 2016-04-28 17:10:21 -07:00
Daniel Wilcox
af38c30c9c Fix path to postfix config variable. 2016-04-28 17:02:29 -07:00
Daniel Wilcox
887871833d Fix typo in changing quotes. 2016-04-28 16:44:25 -07:00
Daniel Wilcox
5d07b70269 Change over to using logging module from print statements. 2016-04-28 16:40:06 -07:00
Daniel Wilcox
c43602c908 Add simple config_test implementation. 2016-04-28 16:30:08 -07:00
Daniel Wilcox
4d24eb83a8 Move version fetching into get_version and implement more_info method. 2016-04-28 16:11:37 -07:00
Daniel Wilcox
7edceec8ac Add test case and fix to properly handle configs with no smtpd_tls_* vars. 2016-04-28 15:27:11 -07:00
Daniel Wilcox
c6baa82ee4 Implement basic get_all_certs_keys, tests pass. 2016-04-28 15:14:06 -07:00
Daniel Wilcox
e75bafa439 Add basic test for get_all_certs_keys IInstaller interface method. 2016-04-28 12:27:49 -07:00
Daniel Wilcox
cc83e9ba52 Wrap some lines, new style exceptions, return check for restart. 2016-04-28 12:26:56 -07:00
Peter Eckersley
5d355044c4 Merge pull request #28 from EFForg/azet/disable-v2-v3-fixup
set _all_ client&server options to exclude v2 and v3 #24
2016-04-25 23:18:05 +10:00
Aaron Zauner
1f95ac9640 README fixup pt. 1 2016-04-20 12:43:51 +07:00
Daniel Wilcox
5928fae89e Merge branch 'master' of github.com:dmwilcox/starttls-everywhere into hackathon 2016-03-29 15:04:23 -07:00
Daniel Wilcox
0bf2537a55 Add initial gitignore. 2016-03-29 14:32:53 -07:00
Daniel Wilcox
fd1cef3fa0 Implement get_all_names. 2016-03-29 14:31:27 -07:00
Daniel Wilcox
fee9c86233 Add failing test for get_all_names. 2016-03-29 14:20:33 -07:00
Daniel Wilcox
5cc317408c Move attributes into init and allow for injecting file contents for testing. 2016-03-29 14:19:13 -07:00
Aaron Zauner
2900d5122c set _all_ client&server options to exclude v2 and v3 #24 2016-03-29 19:31:00 +02:00
Peter Eckersley
c7a8d1cb7a Merge pull request #27 from EFForg/azet/disable-v2-v3
disable SSLv2,3 client-side too #24
2016-03-29 10:12:48 -07:00
Aaron Zauner
0ba508ee2d disable SSLv2,3 client-side too #24 2016-03-29 19:02:00 +02:00
Aaron Zauner
1d37e94e17 disable SSLv3 and v3 by default #24 2016-03-29 18:43:08 +02:00
Peter Eckersley
843e156a51 Merge pull request #26 from EFForg/log-summary
Improve log summary code
2016-03-29 09:23:43 -07:00
Peter Eckersley
ce88098ba6 Merge remote-tracking branch 'origin/master' 2016-03-29 09:21:15 -07:00
Aaron Zauner
1cde7f9b54 added doc. on postfix version dependent features 2016-03-29 16:22:19 +02:00
Aaron Zauner
e42a222c5d preliminary Postfix version check 2016-03-29 15:44:51 +02:00
Peter Eckersley
87022782fb Catch stray missing line 2016-03-09 10:59:10 -08:00
Peter Eckersley
a58c652443 ConfigParser is gone! 2016-03-09 10:54:15 -08:00
Peter Eckersley
0ce1684ba6 Changes to MTAConfigGenerator needed to be moved by hand 2016-03-09 10:50:15 -08:00
Peter Eckersley
af1e94be5a Merge branch 'log-summary'
Including adding dmwilcox's mandatory policy argument to jsha's argparse code
2016-03-09 10:05:43 -08:00
Peter Eckersley
e88eac65da [documentation] Add links to LEPC interface sources 2016-03-02 10:47:59 -08:00
Peter Eckersley
874c59012a Update README 2016-03-02 10:47:52 -08:00
Peter Eckersley
b2977ad6a9 Documentation details 2016-03-01 19:06:02 -08:00
Peter Eckersley
a435036a1e Document MVP objectives 2016-03-01 19:03:47 -08:00
Peter Eckersley
3a8e1d7a70 Further status update 2016-03-01 16:40:03 -08:00
Peter Eckersley
1210c04f14 tweak README 2016-03-01 16:38:55 -08:00
Peter Eckersley
6e2b6a0817 Update README 2016-03-01 16:37:56 -08:00
Peter Eckersley
9a122626b9 Merge pull request #23 from EFForg/delimiters
Fix postfix TLS policy map delimeters
2016-03-01 16:23:10 -08:00
Peter Eckersley
d0629e62ea Merge remote-tracking branch 'origin/master' into delimiters 2016-02-24 19:08:37 -08:00
Peter Eckersley
b8ce13f96a Merge pull request #21 from EFForg/master
Restructured project directory, unified scripts, moved to subdirs
2016-02-24 19:06:06 -08:00
Peter Eckersley
6db2858825 Correct policy map delimitation 2016-02-24 18:19:47 -08:00
Aaron Zauner
4d14423a21 re-structured project folder..
* Removed `ConfigParser.py` (ACK by Daniel).
* Removed MTAConfigGenerator-stub and renamed to `PostfixConfigGenerator.py`
* Moved all text/csv processing scripts to `tools/`.
* Moved all configuration files into dedicated `examples/` directory.
* unified all shebangs to `#!/usr/bin/env python` (default system python).
* Moved domain CSV and text-files to `share/`.
2016-02-24 23:35:52 +01:00
Peter Eckersley
959e943de8 Merge remote-tracking branch 'github/master' 2016-02-18 18:58:45 -08:00
Peter Eckersley
9e42f6ed08 Clarify project status 2016-02-18 18:58:34 -08:00
dmwilcox
bd20b50879 Merge pull request #19 from EFForg/hackathon
We now install certs!
2016-02-17 12:34:23 -08:00
Peter Eckersley
28bb0eb6ac Obtain acceptable_mxs the right way 2016-02-17 12:20:26 -08:00
Peter Eckersley
074fef773b Make up a domain 2016-02-17 12:14:24 -08:00
Peter Eckersley
3aeb62cf7e bugfixes, cleanups 2016-02-17 12:13:28 -08:00
Peter Eckersley
8f5b8558d2 Actually deploy a cert?
- Also add missing selves to interface methods
2016-02-17 12:09:25 -08:00
Peter Eckersley
47a5b7e3ba Start implementing cert installation 2016-02-17 11:56:41 -08:00
Peter Eckersley
cdc8b94823 Merge remote-tracking branch 'dmwilcox/master' into hackathon 2016-02-17 11:38:06 -08:00
dmwilcox
fedf970284 Fix bad import to be import *as*... as it should be. 2016-02-17 11:37:28 -08:00
Peter Eckersley
965027ce52 Start metamorphising to use LE's IInstaller interface 2016-02-17 11:36:38 -08:00
dmwilcox
1cde095dc2 Merge pull request #18 from EFForg/hackathon
hackathon work
2016-02-17 11:22:18 -08:00
Peter Eckersley
39a01190d5 Use 4 space indents
For greater compatibility with LE codebases
2016-02-17 11:19:02 -08:00
Peter Eckersley
9539f21390 Merge pull request #16 from dmwilcox/master
New Config container -- drop-in replacement
2016-02-17 10:34:14 -08:00
dmwilcox
9abef4c0bd Log MX records that will not be configured. 2016-02-17 10:20:56 -08:00
dmwilcox
146fce3878 Add comment about magic hat trick with class properties. 2016-02-17 09:51:37 -08:00
dmwilcox
904dc11b03 Add docstrings for Config objects update/merge methods. 2016-02-17 09:45:37 -08:00
dmwilcox
7c6c3efb0f Confirmed Postfix log parsing is working again. 2016-01-21 01:46:30 -08:00
dmwilcox
c87b5d6a78 Hook the MTA config generation into the new config container. 2016-01-21 00:56:29 -08:00
pypoet
9a71b18b85 Fix updates and merges, add testing to make sure they stay fixed. 2015-10-23 18:26:26 -07:00
pypoet
6da5de6b19 Beginnings of generic config composibility in place. 2015-10-16 00:57:42 -04:00
pypoet
147f58bdbc Rounds out missing features and is now on par with ConfigParser.py.
Still missing logging, composibility and a couple of attributes.
2015-10-14 02:49:34 -04:00
pypoet
fe17c873c0 Initial re-vamp of the Config object to centralize validation and
lay the basis for making compositions of configs and overrides.
Lots of TODOs, be warned.
2015-10-13 17:09:03 -04:00
Jacob Hoffman-Andrews
613a8f5e88 Improve cronjob operation to only process diffs. 2014-10-20 16:15:13 -04:00
Jacob Hoffman-Andrews
f04e8259a9 Protocols separated by colons, not commas. 2014-10-20 09:29:22 -04:00
Jacob Hoffman-Andrews
828c00b758 Find deferred lines in log summary.
Also add a cron mode that only emits output if there's something wrong.
2014-10-17 15:28:49 -04:00
Jacob Hoffman-Andrews
1d47acddfd Remove extraneous print of config. 2014-10-17 15:28:44 -04:00
Jacob Hoffman-Andrews
726afb8b95 Install CAfile on config generation. 2014-10-17 15:28:32 -04:00
Jacob Hoffman-Andrews
a1d016d031 Add motivating examples to README 2014-10-13 15:57:59 -04:00
jsha
91f6be6b3b Merge pull request #6 from jsha/move-collection
Move collection
2014-10-08 16:36:19 -04:00
Peter Eckersley
97cce82e5a Merge pull request #7 from jsha/min-tls-version
Treat min-tls-version as a minimum.
2014-10-08 11:32:54 -07:00
Jacob Hoffman-Andrews
622fc72dc1 Treat min-tls-version as a minimum.
Fixes #5.
2014-09-10 17:36:46 -04:00
Jacob Hoffman-Andrews
42c63cb6dd More informative message for RDNS fail. 2014-09-08 16:25:40 -04:00
Jacob Hoffman-Andrews
31e320d0a7 Collect certs in a subdir. 2014-08-13 15:59:50 -04:00
Jacob Hoffman-Andrews
7f9dadd681 Merge branch 'master' of github.com:EFForg/starttls-everywhere 2014-08-13 10:50:18 -04:00
Jacob Hoffman-Andrews
ad40618897 Respond to pde's comments 2014-08-13 10:49:50 -04:00
jsha
e0edc1b7ec Add link to mailing list. 2014-08-12 12:18:32 -04:00
Peter Eckersley
78a55c3823 Question about postfix logparsing output 2014-08-08 13:16:40 -07:00
Peter Eckersley
9cafcf1caf Comment regexp 2014-08-08 13:15:15 -07:00
Peter Eckersley
8c6d28ce95 Comment with some sample postfix log lines
(both those we support already, and those we may want to in the future...)
2014-08-08 13:01:33 -07:00
Peter Eckersley
30ba7e9305 Deduplicate stray comment line 2014-08-08 13:01:04 -07:00
Peter Eckersley
a6700e3172 Merge branch 'misc' of ssh://github.com/EFForg/starttls-everywhere into misc 2014-08-08 11:58:26 -07:00
Peter Eckersley
0bd8134e5f Comments
(and code review in comment form)
2014-08-08 11:57:01 -07:00
Peter Eckersley
ff5810d78f Don't accept files on the command line that don't do anything 2014-08-08 11:36:04 -07:00
Peter Eckersley
21ff3acf93 Set/list comprehensions are a bit more readable than lambdas 2014-08-08 11:26:59 -07:00
Jacob Hoffman-Andrews
cebc6f9a20 ProcessGoogleSTARTTLSDomains -> latest CSV format.
Also output in a more useful format, require >= 99% encrypted output in the CSV,
hande .{...} domains, and manually add gmail.com.
2014-08-08 13:28:01 -04:00
Jacob Hoffman-Andrews
749c4e39e0 Update meta-config with latest domains. 2014-08-07 17:34:40 -04:00
Jacob Hoffman-Andrews
6a1aa8e6b6 Update to latest config format. 2014-08-07 17:34:21 -04:00
Jacob Hoffman-Andrews
bdd4d01dc7 Update and sort golden-domains.txt, commit google-starttls-domains.csv 2014-08-07 16:57:51 -04:00
Jacob Hoffman-Andrews
6a40e1964b Notice if there are no "Trusted" entries. 2014-08-07 15:05:10 -04:00
Jacob Hoffman-Andrews
82ef8b185a Merge branch 'master' of github.com:EFForg/starttls-everywhere into misc
Conflicts:
	MTAConfigGenerator.py
	starttls-everywhere.json
2014-08-06 18:26:31 -04:00
Jacob Hoffman-Andrews
dd4f9d35ae Improve checker and starttls-everywhere.json.
Now we alphabetize keys on output for more useful diffs.
2014-08-06 18:23:13 -04:00
Jacob Hoffman-Andrews
127d49e837 Manually add a couple known-good domains.
These were skipped because in the Google data they are represented as,
e.g. 'gmail.{..}'.
2014-08-06 18:22:32 -04:00
Jacob Hoffman-Andrews
a85fad98c0 Restore default config 2014-08-06 18:22:08 -04:00
Jacob Hoffman-Andrews
1c3c69aaad Allow parameters to MTAConfigGenerator. 2014-08-06 17:08:49 -04:00
Jacob Hoffman-Andrews
3de8c2a651 Do a better job finding address domain from mx doman. 2014-08-06 17:08:30 -04:00
Peter Eckersley
803c39e585 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
2014-06-19 09:11:15 -07:00
Jacob Hoffman-Andrews
51980e212f First pass at logs analysis 2014-06-18 17:50:41 -04:00
Peter Eckersley
3df343495e Various fixups 2014-06-18 10:58:53 -07:00
Peter Eckersley
45aeb5b003 Merge remote-tracking branch 'github/master'
Conflicts:
	ConfigParser.py
	starttls-everywhere.json
2014-06-18 10:56:48 -07:00
Jacob Hoffman-Andrews
67ee3b0488 Config format change - don't use * as it's misleading. 2014-06-18 12:32:17 -04:00
Peter Eckersley
2eba47a716 Verify more of the policy language 2014-06-16 14:23:51 -07:00
Jacob Hoffman-Andrews
51f90ffafb Write policies based on address domain, not stripped mx-domain 2014-06-16 18:26:56 +00:00
Peter Eckersley
8d6b6c358a Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere
Conflicts:
	MTAConfigGenerator.py
2014-06-16 02:47:15 -07:00
Peter Eckersley
9da4f93ae5 Changes for live demo 2014-06-16 02:46:46 -07:00
Jacob Hoffman-Andrews
3cf61a54b7 Add alternatives section 2014-06-13 13:57:19 -04:00
Jacob Hoffman-Andrews
43d457aa77 Typo cleanup in MTAConfigGenerator 2014-06-12 13:18:20 -04:00
Jacob Hoffman-Andrews
499f6c2fad Add comment to ProcessgoogleSTARTTLSDomains.py 2014-06-12 11:53:02 -04:00
Jacob Hoffman-Andrews
9cd71642fb Fix italicization boundaries 2014-06-12 11:50:39 -04:00
Jacob Hoffman-Andrews
7a7329fa19 Merge remote-tracking branch 'pde/master' 2014-06-12 11:49:09 -04:00
Jacob Hoffman-Andrews
9ce047980a Add .gitignore 2014-06-12 11:40:17 -04:00
Peter Eckersley
02abaf57bd Remove stray conf entry from README 2014-06-12 05:24:51 -07:00
Peter Eckersley
3d7b53daf1 Tune verbosity; reload postfix conf if we can 2014-06-12 05:24:05 -07:00
Peter Eckersley
e99abfacfd Include the demo example with the real stuff, temporarily 2014-06-12 05:22:30 -07:00
Peter Eckersley
a2ee328bc0 Paramaterise "/etc/postfix" 2014-06-11 10:32:52 -07:00
Peter Eckersley
34cba3accf Now successfully parsing the larger policy set 2014-06-11 09:51:56 -07:00
Peter Eckersley
3712a45399 Further (and different, and better) standardisation 2014-06-11 09:48:43 -07:00
Peter Eckersley
182e9b29e4 Trying to standardize JSON terms 2014-06-11 09:42:17 -07:00
Peter Eckersley
2540f1f1e8 Writing to the domain-wise policy file actually works now. 2014-06-11 09:31:41 -07:00
Peter Eckersley
eea1b0d8c5 Switch naming conventions so that modules are importable :)
It turns out that python won't import modules with hyphens in their names.
It seems that CamelCase is most consistent with our Class naming.  However
feel free to do something different instead :)
2014-06-11 09:18:56 -07:00
Peter Eckersley
6e1bcfdb2a WIP implementing domain-wise TLS policies 2014-06-11 09:17:50 -07:00
Peter Eckersley
d7e4d93190 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 09:13:01 -07:00
Jacob Hoffman-Andrews
0c4e332811 Set up test CA and valid signed cert by that CA.
Also require valid cert for host 'valid'.
2014-06-11 11:45:28 -04:00
Peter Eckersley
46ce09d36d MTA config wrangling seems to work 2014-06-11 05:01:46 -07:00
Peter Eckersley
269f15f9ee Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-11 00:38:40 -07:00
Jacob Hoffman-Andrews
21e841fd13 Move synced folders into a common one.
Also, create certificates.
2014-06-10 15:08:52 -04:00
Peter Eckersley
a03db04ff4 WIP implementing deletion of existing cf lines 2014-06-10 08:08:40 -07:00
Peter Eckersley
d0bcc13059 Break ground on an postfix config wrangling engine 2014-06-10 08:08:17 -07:00
Jacob Hoffman-Andrews
17425e7337 Merge branch 'master' of github.com:jsha/starttls-everywhere
Conflicts:
	README.md
2014-06-09 14:56:34 -07:00
jsha
7bdb63376c Merge pull request #2 from pde/master
Initial checkin of config parser, some changes to the config format.
2014-06-09 14:53:23 -07:00
Jacob Hoffman-Andrews
bdbc46fc84 Add candidate starttls-everywhere config json 2014-06-09 13:10:43 -07:00
Jacob Hoffman-Andrews
0d43d2988a Update check-starttls.py to generate starttls everywhere config. 2014-06-09 13:08:01 -07:00
Jacob Hoffman-Andrews
79924108c7 Reorder JSON file to emphasize MX policies over address-domain -> MX domain mapping. 2014-06-09 10:12:09 -07:00
Peter Eckersley
c033905b16 Now validating most of the config json 2014-06-08 06:22:32 -07:00
Peter Eckersley
839c523048 Fix typos 2014-06-08 06:22:22 -07:00
Peter Eckersley
7c81f23a07 Merge branch 'master' of ssh://github.com/jsha/starttls-everywhere 2014-06-07 06:50:21 -07:00
Jacob Hoffman-Andrews
e534a43d1a Make sender actually attempt TLS on outbound connections. 2014-06-06 16:07:38 -07:00
Peter Eckersley
fcd1a98201 Break ground on a config parser 2014-06-06 15:54:33 -07:00
Peter Eckersley
dc606eac7d Some tweaks to the config format 2014-06-06 15:54:22 -07:00
Jacob Hoffman-Andrews
372c96d9fd Update Postfix configuration and mail-send-loop 2014-06-06 14:06:08 -07:00
Jacob Hoffman-Andrews
5a9f90dc30 Merge branch 'master' of github.com:jsha/starttls-everywhere 2014-06-06 14:05:03 -07:00
Jacob Hoffman-Andrews
ce0a6a1814 Add example config.json 2014-06-06 14:04:38 -07:00
jsha
fa5acdf674 Simplify SPKI hash usage 2014-06-06 13:44:03 -07:00
jsha
31db3b7034 Merge pull request #1 from pde/master
Add specific postfix configs
2014-06-06 12:59:41 -07:00
Peter Eckersley
30938260d4 Split postfix configs across the VMs, and start making them do things 2014-06-05 19:45:21 -07:00
Peter Eckersley
7bd06a4d35 Work in progress 2014-06-05 17:04:28 -07:00
Peter Eckersley
ace9d2383d Start work on forcing TLS to valid 2014-06-05 16:51:38 -07:00
Jacob Hoffman-Andrews
ed0c024209 Improved provisioning and certificate checking. 2014-06-05 16:01:07 -07:00
Jacob Hoffman-Andrews
714cb17dcb Clarify example usage of pinsets by including a CA 2014-06-05 14:26:45 -07:00
Jacob Hoffman-Andrews
6fb51d5422 Example shouldn't include hashes from Chrome source. 2014-06-05 14:11:08 -07:00
jsha
4b5b9f164f nexthop-domains -> address-domains 2014-06-05 11:51:19 -07:00
jsha
3d9d5607bd Formatting issue #2 in design doc 2014-06-05 11:34:12 -07:00
jsha
aa417eec15 Formatting issue in design doc 2014-06-05 11:10:07 -07:00
jsha
844ec79f01 Add design doc 2014-06-05 11:05:08 -07:00
Jacob Hoffman-Andrews
f0b9ef2716 Add a Vagrantfile and the list of golden domains. 2014-06-05 10:43:01 -07:00
Jacob Hoffman-Andrews
8857302347 check-starttls and process-google-starttls-domains.py 2014-06-04 14:41:18 -07:00
254 changed files with 9154 additions and 1535 deletions

1
.gitignore vendored
View File

@@ -38,6 +38,7 @@ tests/letstest/venv/
# pytest cache
.cache
.mypy_cache/
# docker files
.docker

View File

@@ -41,7 +41,7 @@ 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,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
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

View File

@@ -29,16 +29,21 @@ matrix:
addons:
- python: "2.7"
env: TOXENV=lint
- python: "3.4"
env: TOXENV=mypy
- python: "3.5"
env: TOXENV=mypy
- python: "2.7"
env: TOXENV=py27-oldest
env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest'
sudo: required
services: docker
- python: "3.4"
env: TOXENV=py34
sudo: required
services: docker
- python: "3.6"
env: TOXENV=py36
- python: "3.7"
dist: xenial
env: TOXENV=py37
sudo: required
services: docker
- sudo: required
@@ -73,8 +78,6 @@ sudo: false
addons:
apt:
sources:
- augeas
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
- python-dev
- python-virtualenv
@@ -86,10 +89,6 @@ addons:
# For certbot-nginx integration testing
- nginx-light
- openssl
# for apacheconftest
- apache2
- libapache2-mod-wsgi
- libapache2-mod-macro
install: "travis_retry $(command -v pip || command -v pip3) install tox coveralls"
script:
@@ -103,6 +102,7 @@ notifications:
irc:
channels:
- secure: "SGWZl3ownKx9xKVV2VnGt7DqkTmutJ89oJV9tjKhSs84kLijU6EYdPnllqISpfHMTxXflNZuxtGo0wTDYHXBuZL47w1O32W6nzuXdra5zC+i4sYQwYULUsyfOv9gJX8zWAULiK0Z3r0oho45U+FR5ZN6TPCidi8/eGU+EEPwaAw="
on_cancel: never
on_success: never
on_failure: always
use_notice: true

View File

@@ -2,6 +2,282 @@
Certbot adheres to [Semantic Versioning](http://semver.org/).
## 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

View File

@@ -1,4 +1,4 @@
FROM python:2-alpine
FROM python:2-alpine3.7
ENTRYPOINT [ "certbot" ]
EXPOSE 80 443

View File

@@ -1,5 +1,6 @@
include LICENSE.txt
include README.rst
include pytest.ini
recursive-include docs *
recursive-include examples *
recursive-include acme/testdata *

View File

@@ -9,6 +9,7 @@ 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
@@ -139,16 +140,16 @@ class KeyAuthorizationChallengeResponse(ChallengeResponse):
return True
@six.add_metaclass(abc.ABCMeta)
class KeyAuthorizationChallenge(_TokenChallenge):
# pylint: disable=abstract-class-little-used,too-many-ancestors
"""Challenge based on Key Authorization.
:param response_cls: Subclass of `KeyAuthorizationChallengeResponse`
that will be used to generate `response`.
:param str typ: type of the challenge
"""
__metaclass__ = abc.ABCMeta
typ = NotImplemented
response_cls = NotImplemented
thumbprint_hash_function = (
KeyAuthorizationChallengeResponse.thumbprint_hash_function)
@@ -477,7 +478,7 @@ class TLSSNI01Response(KeyAuthorizationChallengeResponse):
try:
cert = self.probe_cert(domain=domain, **kwargs)
except errors.Error as error:
logger.debug(error, exc_info=True)
logger.debug(str(error), exc_info=True)
return False
return self.verify_cert(cert)
@@ -506,6 +507,21 @@ class TLSSNI01(KeyAuthorizationChallenge):
return self.response(account_key).gen_cert(key=kwargs.get('cert_key'))
@Challenge.register # pylint: disable=too-many-ancestors
class TLSALPN01(KeyAuthorizationChallenge):
"""ACME tls-alpn-01 challenge.
This class simply allows parsing the TLS-ALPN-01 challenge returned from
the CA. Full TLS-ALPN-01 support is not currently provided.
"""
typ = "tls-alpn-01"
def validation(self, account_key, **kwargs):
"""Generate validation for the challenge."""
raise NotImplementedError()
@Challenge.register # pylint: disable=too-many-ancestors
class DNS(_TokenChallenge):
"""ACME "dns" challenge."""

View File

@@ -393,6 +393,38 @@ class TLSSNI01Test(unittest.TestCase):
mock_gen_cert.assert_called_once_with(key=mock.sentinel.cert_key)
class TLSALPN01Test(unittest.TestCase):
def setUp(self):
from acme.challenges import TLSALPN01
self.msg = TLSALPN01(
token=jose.b64decode('a82d5ff8ef740d12881f6d3c2277ab2e'))
self.jmsg = {
'type': 'tls-alpn-01',
'token': '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 TLSALPN01
self.assertEqual(self.msg, TLSALPN01.from_json(self.jmsg))
def test_from_json_hashable(self):
from acme.challenges import TLSALPN01
hash(TLSALPN01.from_json(self.jmsg))
def test_from_json_invalid_token_length(self):
from acme.challenges import TLSALPN01
self.jmsg['token'] = jose.encode_b64jose(b'abcd')
self.assertRaises(
jose.DeserializationError, TLSALPN01.from_json, self.jmsg)
def test_validation(self):
self.assertRaises(NotImplementedError, self.msg.validation, KEY)
class DNSTest(unittest.TestCase):
def setUp(self):

View File

@@ -9,17 +9,20 @@ import time
import six
from six.moves import http_client # pylint: disable=import-error
import josepy as jose
import OpenSSL
import re
from requests_toolbelt.adapters.source import SourceAddressAdapter
import requests
from requests.adapters import HTTPAdapter
import sys
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
logger = logging.getLogger(__name__)
@@ -227,8 +230,7 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
response = self._post(url,
messages.Revocation(
certificate=cert,
reason=rsn),
content_type=None)
reason=rsn))
if response.status_code != http_client.OK:
raise errors.ClientError(
'Successful revocation must return HTTP OK status')
@@ -260,11 +262,12 @@ class Client(ClientBase):
"""
# pylint: disable=too-many-arguments
self.key = key
self.net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl) if net is None else net
if net is None:
net = ClientNetwork(key, alg=alg, verify_ssl=verify_ssl)
if isinstance(directory, six.string_types):
directory = messages.Directory.from_json(
self.net.get(directory).json())
net.get(directory).json())
super(Client, self).__init__(directory=directory,
net=net, acme_version=1)
@@ -415,7 +418,7 @@ class Client(ClientBase):
"""
# pylint: disable=too-many-locals
assert max_attempts > 0
attempts = collections.defaultdict(int)
attempts = collections.defaultdict(int) # type: Dict[messages.AuthorizationResource, int]
exhausted = set()
# priority queue with datetime.datetime (based on Retry-After) as key,
@@ -529,7 +532,7 @@ class Client(ClientBase):
:rtype: `list` of `OpenSSL.crypto.X509` wrapped in `.ComparableX509`
"""
chain = []
chain = [] # type: List[jose.ComparableX509]
uri = certr.cert_chain_uri
while uri is not None and len(chain) < max_length:
response, cert = self._get_cert(uri)
@@ -809,8 +812,8 @@ class BackwardsCompatibleClientV2(object):
'certificate, please rerun the command for a new one.')
cert = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped)
chain = crypto_util.dump_pyopenssl_chain(chain)
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped).decode()
chain = crypto_util.dump_pyopenssl_chain(chain).decode()
return orderr.update(fullchain_pem=(cert + chain))
else:
@@ -856,18 +859,28 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
:param bool verify_ssl: Whether to verify certificates on SSL connections.
:param str user_agent: String to send as User-Agent header.
:param float timeout: Timeout for requests.
:param source_address: Optional source address to bind to when making requests.
:type source_address: str or tuple(str, int)
"""
def __init__(self, key, account=None, alg=jose.RS256, verify_ssl=True,
user_agent='acme-python', timeout=DEFAULT_NETWORK_TIMEOUT):
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
self.verify_ssl = verify_ssl
self._nonces = set()
self._nonces = set() # type: Set[Text]
self.user_agent = user_agent
self.session = requests.Session()
self._default_timeout = timeout
adapter = HTTPAdapter()
if source_address is not None:
adapter = SourceAddressAdapter(source_address)
self.session.mount("http://", adapter)
self.session.mount("https://", adapter)
def __del__(self):
# Try to close the session, but don't show exceptions to the
@@ -1017,7 +1030,7 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
if response.headers.get("Content-Type") == DER_CONTENT_TYPE:
debug_content = base64.b64encode(response.content)
else:
debug_content = response.content
debug_content = response.content.decode("utf-8")
logger.debug('Received response:\nHTTP %d\n%s\n\n%s',
response.status_code,
"\n".join(["{0}: {1}".format(k, v)

View File

@@ -17,6 +17,7 @@ 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
CERT_DER = test_util.load_vector('cert.der')
@@ -61,7 +62,8 @@ class ClientTestBase(unittest.TestCase):
self.contact = ('mailto:cert-admin@example.com', 'tel:+12025551212')
reg = messages.Registration(
contact=self.contact, key=KEY.public_key())
self.new_reg = messages.NewRegistration(**dict(reg))
the_arg = dict(reg) # type: Dict
self.new_reg = messages.NewRegistration(**the_arg) # pylint: disable=star-args
self.regr = messages.RegistrationResource(
body=reg, uri='https://www.letsencrypt-demo.org/acme/reg/1')
@@ -99,10 +101,10 @@ class BackwardsCompatibleClientV2Test(ClientTestBase):
self.chain = [wrapped, wrapped]
self.cert_pem = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped)
OpenSSL.crypto.FILETYPE_PEM, messages_test.CERT.wrapped).decode()
single_chain = OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, loaded)
OpenSSL.crypto.FILETYPE_PEM, loaded).decode()
self.chain_pem = single_chain + single_chain
self.fullchain_pem = self.cert_pem + self.chain_pem
@@ -299,6 +301,16 @@ class ClientTest(ClientTestBase):
directory=uri, key=KEY, alg=jose.RS256, net=self.net)
self.net.get.assert_called_once_with(uri)
@mock.patch('acme.client.ClientNetwork')
def test_init_without_net(self, mock_net):
mock_net.return_value = mock.sentinel.net
alg = jose.RS256
from acme.client import Client
self.client = Client(
directory=self.directory, key=KEY, alg=alg)
mock_net.called_once_with(KEY, alg=alg, verify_ssl=True)
self.assertEqual(self.client.net, mock.sentinel.net)
def test_register(self):
# "Instance of 'Field' has no to_json/update member" bug:
# pylint: disable=no-member
@@ -635,8 +647,7 @@ class ClientTest(ClientTestBase):
def test_revoke(self):
self.client.revoke(self.certr.body, self.rsn)
self.net.post.assert_called_once_with(
self.directory[messages.Revocation], mock.ANY, content_type=None,
acme_version=1)
self.directory[messages.Revocation], mock.ANY, acme_version=1)
def test_revocation_payload(self):
obj = messages.Revocation(certificate=self.certr.body, reason=self.rsn)
@@ -776,8 +787,7 @@ class ClientV2Test(ClientTestBase):
def test_revoke(self):
self.client.revoke(messages_test.CERT, self.rsn)
self.net.post.assert_called_once_with(
self.directory["revokeCert"], mock.ANY, content_type=None,
acme_version=2)
self.directory["revokeCert"], mock.ANY, acme_version=2)
class MockJSONDeSerializable(jose.JSONDeSerializable):
@@ -1119,6 +1129,31 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
self.assertRaises(requests.exceptions.RequestException,
self.net.post, 'uri', obj=self.obj)
class ClientNetworkSourceAddressBindingTest(unittest.TestCase):
"""Tests that if ClientNetwork has a source IP set manually, the underlying library has
used the provided source address."""
def setUp(self):
self.source_address = "8.8.8.8"
def test_source_address_set(self):
from acme.client import ClientNetwork
net = ClientNetwork(key=None, alg=None, source_address=self.source_address)
for adapter in net.session.adapters.values():
self.assertTrue(self.source_address in adapter.source_address)
def test_behavior_assumption(self):
"""This is a test that guardrails the HTTPAdapter behavior so that if the default for
a Session() changes, the assumptions here aren't violated silently."""
from acme.client import ClientNetwork
# Source address not specified, so the default adapter type should be bound -- this
# test should fail if the default adapter type is changed by requests
net = ClientNetwork(key=None, alg=None)
session = requests.Session()
for scheme in session.adapters.keys():
client_network_adapter = net.session.adapters.get(scheme)
default_adapter = session.adapters.get(scheme)
self.assertEqual(client_network_adapter.__class__, default_adapter.__class__)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -6,11 +6,14 @@ import os
import re
import socket
import OpenSSL
from OpenSSL import crypto
from OpenSSL import SSL # type: ignore # https://github.com/python/typeshed/issues/2052
import josepy as jose
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
logger = logging.getLogger(__name__)
@@ -25,7 +28,7 @@ logger = logging.getLogger(__name__)
# https://www.openssl.org/docs/ssl/SSLv23_method.html). _serve_sni
# should be changed to use "set_options" to disable SSLv2 and SSLv3,
# in case it's used for things other than probing/serving!
_DEFAULT_TLSSNI01_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD # type: ignore
_DEFAULT_TLSSNI01_SSL_METHOD = SSL.SSLv23_METHOD # type: ignore
class SSLSocket(object): # pylint: disable=too-few-public-methods
@@ -64,9 +67,9 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
logger.debug("Server name (%s) not recognized, dropping SSL",
server_name)
return
new_context = OpenSSL.SSL.Context(self.method)
new_context.set_options(OpenSSL.SSL.OP_NO_SSLv2)
new_context.set_options(OpenSSL.SSL.OP_NO_SSLv3)
new_context = SSL.Context(self.method)
new_context.set_options(SSL.OP_NO_SSLv2)
new_context.set_options(SSL.OP_NO_SSLv3)
new_context.use_privatekey(key)
new_context.use_certificate(cert)
connection.set_context(new_context)
@@ -89,18 +92,18 @@ class SSLSocket(object): # pylint: disable=too-few-public-methods
def accept(self): # pylint: disable=missing-docstring
sock, addr = self.sock.accept()
context = OpenSSL.SSL.Context(self.method)
context.set_options(OpenSSL.SSL.OP_NO_SSLv2)
context.set_options(OpenSSL.SSL.OP_NO_SSLv3)
context = SSL.Context(self.method)
context.set_options(SSL.OP_NO_SSLv2)
context.set_options(SSL.OP_NO_SSLv3)
context.set_tlsext_servername_callback(self._pick_certificate_cb)
ssl_sock = self.FakeConnection(OpenSSL.SSL.Connection(context, sock))
ssl_sock = self.FakeConnection(SSL.Connection(context, sock))
ssl_sock.set_accept_state()
logger.debug("Performing handshake with %s", addr)
try:
ssl_sock.do_handshake()
except OpenSSL.SSL.Error as error:
except SSL.Error as error:
# _pick_certificate_cb might have returned without
# creating SSL context (wrong server name)
raise socket.error(error)
@@ -128,30 +131,39 @@ def probe_sni(name, host, port=443, timeout=300,
:rtype: OpenSSL.crypto.X509
"""
context = OpenSSL.SSL.Context(method)
context = SSL.Context(method)
context.set_timeout(timeout)
socket_kwargs = {'source_address': source_address}
host_protocol_agnostic = None if host == '::' or host == '0' else host
host_protocol_agnostic = host
if host == '::' or host == '0':
# https://github.com/python/typeshed/pull/2136
# while PR is not merged, we need to ignore
host_protocol_agnostic = None
try:
# pylint: disable=star-args
logger.debug("Attempting to connect to %s:%d%s.", host_protocol_agnostic, port,
" from {0}:{1}".format(source_address[0], source_address[1]) if \
socket_kwargs else "")
sock = socket.create_connection((host_protocol_agnostic, port), **socket_kwargs)
logger.debug(
"Attempting to connect to %s:%d%s.", host_protocol_agnostic, port,
" from {0}:{1}".format(
source_address[0],
source_address[1]
) if socket_kwargs else ""
)
socket_tuple = (host_protocol_agnostic, port) # type: Tuple[Optional[str], int]
sock = socket.create_connection(socket_tuple, **socket_kwargs) # type: ignore
except socket.error as error:
raise errors.Error(error)
with contextlib.closing(sock) as client:
client_ssl = OpenSSL.SSL.Connection(context, client)
client_ssl = SSL.Connection(context, client)
client_ssl.set_connect_state()
client_ssl.set_tlsext_host_name(name) # pyOpenSSL>=0.13
try:
client_ssl.do_handshake()
client_ssl.shutdown()
except OpenSSL.SSL.Error as error:
except SSL.Error as error:
raise errors.Error(error)
return client_ssl.get_peer_certificate()
@@ -164,18 +176,18 @@ def make_csr(private_key_pem, domains, must_staple=False):
OCSP Must Staple: https://tools.ietf.org/html/rfc7633).
:returns: buffer PEM-encoded Certificate Signing Request.
"""
private_key = OpenSSL.crypto.load_privatekey(
OpenSSL.crypto.FILETYPE_PEM, private_key_pem)
csr = OpenSSL.crypto.X509Req()
private_key = crypto.load_privatekey(
crypto.FILETYPE_PEM, private_key_pem)
csr = crypto.X509Req()
extensions = [
OpenSSL.crypto.X509Extension(
crypto.X509Extension(
b'subjectAltName',
critical=False,
value=', '.join('DNS:' + d for d in domains).encode('ascii')
),
]
if must_staple:
extensions.append(OpenSSL.crypto.X509Extension(
extensions.append(crypto.X509Extension(
b"1.3.6.1.5.5.7.1.24",
critical=False,
value=b"DER:30:03:02:01:05"))
@@ -183,8 +195,8 @@ def make_csr(private_key_pem, domains, must_staple=False):
csr.set_pubkey(private_key)
csr.set_version(2)
csr.sign(private_key, 'sha256')
return OpenSSL.crypto.dump_certificate_request(
OpenSSL.crypto.FILETYPE_PEM, csr)
return crypto.dump_certificate_request(
crypto.FILETYPE_PEM, csr)
def _pyopenssl_cert_or_req_all_names(loaded_cert_or_req):
common_name = loaded_cert_or_req.get_subject().CN
@@ -221,11 +233,12 @@ def _pyopenssl_cert_or_req_san(cert_or_req):
parts_separator = ", "
prefix = "DNS" + part_separator
if isinstance(cert_or_req, OpenSSL.crypto.X509):
func = OpenSSL.crypto.dump_certificate
if isinstance(cert_or_req, crypto.X509):
# pylint: disable=line-too-long
func = crypto.dump_certificate # type: Union[Callable[[int, crypto.X509Req], bytes], Callable[[int, crypto.X509], bytes]]
else:
func = OpenSSL.crypto.dump_certificate_request
text = func(OpenSSL.crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
func = crypto.dump_certificate_request
text = func(crypto.FILETYPE_TEXT, cert_or_req).decode("utf-8")
# WARNING: this function does not support multiple SANs extensions.
# Multiple X509v3 extensions of the same type is disallowed by RFC 5280.
match = re.search(r"X509v3 Subject Alternative Name:(?: critical)?\s*(.*)", text)
@@ -252,12 +265,12 @@ def gen_ss_cert(key, domains, not_before=None,
"""
assert domains, "Must provide one or more hostnames for the cert."
cert = OpenSSL.crypto.X509()
cert = crypto.X509()
cert.set_serial_number(int(binascii.hexlify(os.urandom(16)), 16))
cert.set_version(2)
extensions = [
OpenSSL.crypto.X509Extension(
crypto.X509Extension(
b"basicConstraints", True, b"CA:TRUE, pathlen:0"),
]
@@ -266,7 +279,7 @@ def gen_ss_cert(key, domains, not_before=None,
cert.set_issuer(cert.get_subject())
if force_san or len(domains) > 1:
extensions.append(OpenSSL.crypto.X509Extension(
extensions.append(crypto.X509Extension(
b"subjectAltName",
critical=False,
value=b", ".join(b"DNS:" + d.encode() for d in domains)
@@ -281,12 +294,15 @@ def gen_ss_cert(key, domains, not_before=None,
cert.sign(key, "sha256")
return cert
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
def dump_pyopenssl_chain(chain, filetype=crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.
:param list chain: List of `OpenSSL.crypto.X509` (or wrapped in
:class:`josepy.util.ComparableX509`).
:returns: certificate chain bundle
:rtype: bytes
"""
# XXX: returns empty string when no chain is available, which
# shuts up RenewableCert, but might not be the best solution...
@@ -295,7 +311,7 @@ def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
if isinstance(cert, jose.ComparableX509):
# pylint: disable=protected-access
cert = cert.wrapped
return OpenSSL.crypto.dump_certificate(filetype, cert)
return crypto.dump_certificate(filetype, cert)
# assumes that OpenSSL.crypto.dump_certificate includes ending
# newline character

View File

@@ -13,6 +13,7 @@ import OpenSSL
from acme import errors
from acme import test_util
from acme.magic_typing import List # pylint: disable=unused-import, no-name-in-module
class SSLSocketAndProbeSNITest(unittest.TestCase):
@@ -41,28 +42,38 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
self.server_thread = threading.Thread(
# pylint: disable=no-member
target=self.server.handle_request)
self.server_thread.start()
time.sleep(1) # TODO: avoid race conditions in other way
def tearDown(self):
self.server_thread.join()
if self.server_thread.is_alive():
# The thread may have already terminated.
self.server_thread.join() # pragma: no cover
def _probe(self, name):
from acme.crypto_util import probe_sni
return jose.ComparableX509(probe_sni(
name, host='127.0.0.1', port=self.port))
def _start_server(self):
self.server_thread.start()
time.sleep(1) # TODO: avoid race conditions in other way
def test_probe_ok(self):
self._start_server()
self.assertEqual(self.cert, self._probe(b'foo'))
def test_probe_not_recognized_name(self):
self._start_server()
self.assertRaises(errors.Error, self._probe, b'bar')
# TODO: py33/py34 tox hangs forever on do_handshake in second probe
#def probe_connection_error(self):
# self._probe(b'foo')
# #time.sleep(1) # TODO: avoid race conditions in other way
# self.assertRaises(errors.Error, self._probe, b'bar')
def test_probe_connection_error(self):
# pylint has a hard time with six
self.server.server_close() # pylint: disable=no-member
original_timeout = socket.getdefaulttimeout()
try:
socket.setdefaulttimeout(1)
self.assertRaises(errors.Error, self._probe, b'bar')
finally:
socket.setdefaulttimeout(original_timeout)
class PyOpenSSLCertOrReqAllNamesTest(unittest.TestCase):
@@ -165,7 +176,7 @@ class RandomSnTest(unittest.TestCase):
def setUp(self):
self.cert_count = 5
self.serial_num = []
self.serial_num = [] # type: List[int]
self.key = OpenSSL.crypto.PKey()
self.key.generate_key(OpenSSL.crypto.TYPE_RSA, 2048)

16
acme/acme/magic_typing.py Normal file
View File

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

View File

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

View File

@@ -145,6 +145,7 @@ STATUS_PROCESSING = Status('processing')
STATUS_VALID = Status('valid')
STATUS_INVALID = Status('invalid')
STATUS_REVOKED = Status('revoked')
STATUS_READY = Status('ready')
class IdentifierType(_Constant):
@@ -284,7 +285,7 @@ class Registration(ResourceBody):
if phone is not None:
details.append(cls.phone_prefix + phone)
if email is not None:
details.append(cls.email_prefix + email)
details.extend([cls.email_prefix + mail for mail in email.split(',')])
kwargs['contact'] = tuple(details)
return cls(**kwargs)
@@ -435,6 +436,7 @@ class Authorization(ResourceBody):
# be absent'... then acme-spec gives example with 'expires'
# present... That's confusing!
expires = fields.RFC3339Field('expires', omitempty=True)
wildcard = jose.Field('wildcard', omitempty=True)
@challenges.decoder
def challenges(value): # pylint: disable=missing-docstring,no-self-argument

View File

@@ -6,6 +6,7 @@ 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
CERT = test_util.load_comparable_cert('cert.der')
@@ -85,7 +86,7 @@ class ConstantTest(unittest.TestCase):
from acme.messages import _Constant
class MockConstant(_Constant): # pylint: disable=missing-docstring
POSSIBLE_NAMES = {}
POSSIBLE_NAMES = {} # type: Dict
self.MockConstant = MockConstant # pylint: disable=invalid-name
self.const_a = MockConstant('a')

View File

@@ -16,6 +16,7 @@ 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
logger = logging.getLogger(__name__)
@@ -66,8 +67,8 @@ class BaseDualNetworkedServers(object):
def __init__(self, ServerClass, server_address, *remaining_args, **kwargs):
port = server_address[1]
self.threads = []
self.servers = []
self.threads = [] # type: List[threading.Thread]
self.servers = [] # type: List[ACMEServerMixin]
# Must try True first.
# Ubuntu, for example, will fail to bind to IPv4 if we've already bound
@@ -82,9 +83,22 @@ class BaseDualNetworkedServers(object):
new_address = (server_address[0],) + (port,) + server_address[2:]
new_args = (new_address,) + remaining_args
server = ServerClass(*new_args, **kwargs) # pylint: disable=star-args
except socket.error:
logger.debug("Failed to bind to %s:%s using %s", new_address[0],
logger.debug(
"Successfully bound to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
except socket.error:
if self.servers:
# Already bound using IPv6.
logger.debug(
"Certbot wasn't able to bind to %s:%s using %s, this " +
"is often expected due to the dual stack nature of " +
"IPv6 socket implementations.",
new_address[0], new_address[1],
"IPv6" if ip_version else "IPv4")
else:
logger.debug(
"Failed to bind to %s:%s using %s", new_address[0],
new_address[1], "IPv6" if ip_version else "IPv4")
else:
self.servers.append(server)
# If two servers are set up and port 0 was passed in, ensure we always
@@ -189,7 +203,7 @@ class HTTP01RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def __init__(self, *args, **kwargs):
self.simple_http_resources = kwargs.pop("simple_http_resources", set())
socketserver.BaseRequestHandler.__init__(self, *args, **kwargs)
BaseHTTPServer.BaseHTTPRequestHandler.__init__(self, *args, **kwargs)
def log_message(self, format, *args): # pylint: disable=redefined-builtin
"""Log arbitrary message."""
@@ -262,7 +276,7 @@ def simple_tls_sni_01_server(cli_args, forever=True):
certs = {}
_, hosts, _ = next(os.walk('.'))
_, 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()

View File

@@ -4,10 +4,10 @@ import shutil
import socket
import threading
import tempfile
import time
import unittest
from six.moves import http_client # pylint: disable=import-error
from six.moves import queue # pylint: disable=import-error
from six.moves import socketserver # type: ignore # pylint: disable=import-error
import josepy as jose
@@ -16,8 +16,8 @@ import requests
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
class TLSServerTest(unittest.TestCase):
@@ -72,7 +72,7 @@ class HTTP01ServerTest(unittest.TestCase):
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))
self.resources = set()
self.resources = set() # type: Set
from acme.standalone import HTTP01Server
self.server = HTTP01Server(('', 0), resources=self.resources)
@@ -201,7 +201,7 @@ class HTTP01DualNetworkedServersTest(unittest.TestCase):
def setUp(self):
self.account_key = jose.JWK.load(
test_util.load_vector('rsa1024_key.pem'))
self.resources = set()
self.resources = set() # type: Set
from acme.standalone import HTTP01DualNetworkedServers
self.servers = HTTP01DualNetworkedServers(('', 0), resources=self.resources)
@@ -260,10 +260,9 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
os.path.join(localhost_dir, 'key.pem'))
from acme.standalone import simple_tls_sni_01_server
self.port = 1234
self.thread = threading.Thread(
target=simple_tls_sni_01_server, kwargs={
'cli_args': ('xxx', '--port', str(self.port)),
'cli_args': ('filename',),
'forever': False,
},
)
@@ -275,25 +274,20 @@ class TestSimpleTLSSNI01Server(unittest.TestCase):
self.thread.join()
shutil.rmtree(self.test_cwd)
def test_it(self):
max_attempts = 5
for attempt in range(max_attempts):
try:
cert = crypto_util.probe_sni(
b'localhost', b'0.0.0.0', self.port)
except errors.Error:
self.assertTrue(attempt + 1 < max_attempts, "Timeout!")
time.sleep(1) # wait until thread starts
else:
self.assertEqual(jose.ComparableX509(cert),
test_util.load_comparable_cert(
'rsa2048_cert.pem'))
break
@mock.patch('acme.standalone.logger')
def test_it(self, mock_logger):
# Use a Queue because mock objects aren't thread safe.
q = queue.Queue() # type: queue.Queue[int]
# Add port number to the queue.
mock_logger.info.side_effect = lambda *args: q.put(args[-1])
self.thread.start()
if attempt == 0:
# the first attempt is always meant to fail, so we can test
# the socket failure code-path for probe_sni, as well
self.thread.start()
# After the timeout, an exception is raised if the queue is empty.
port = q.get(timeout=5)
cert = crypto_util.probe_sni(b'localhost', b'0.0.0.0', port)
self.assertEqual(jose.ComparableX509(cert),
test_util.load_comparable_cert(
'rsa2048_cert.pem'))
if __name__ == "__main__":

View File

@@ -10,7 +10,7 @@ import unittest
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import josepy as jose
import OpenSSL
from OpenSSL import crypto
def vector_path(*names):
@@ -39,8 +39,8 @@ def _guess_loader(filename, loader_pem, loader_der):
def load_cert(*names):
"""Load certificate."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_certificate(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_certificate(loader, load_vector(*names))
def load_comparable_cert(*names):
@@ -51,8 +51,8 @@ def load_comparable_cert(*names):
def load_csr(*names):
"""Load certificate request."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_certificate_request(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_certificate_request(loader, load_vector(*names))
def load_comparable_csr(*names):
@@ -71,8 +71,8 @@ def load_rsa_private_key(*names):
def load_pyopenssl_private_key(*names):
"""Load pyOpenSSL private key."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
names[-1], crypto.FILETYPE_PEM, crypto.FILETYPE_ASN1)
return crypto.load_privatekey(loader, load_vector(*names))
def skip_unless(condition, reason): # pragma: no cover

2
acme/pytest.ini Normal file
View File

@@ -0,0 +1,2 @@
[pytest]
norecursedirs = .* build dist CVS _darcs {arch} *.egg

View File

@@ -1,10 +1,9 @@
import sys
from setuptools import setup
from setuptools import find_packages
from setuptools.command.test import test as TestCommand
import sys
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [
@@ -19,6 +18,7 @@ install_requires = [
'pyrfc3339',
'pytz',
'requests[security]>=2.4.1', # security extras added in 2.4.1
'requests-toolbelt>=0.3.0',
'setuptools',
'six>=1.9.0', # needed for python_2_unicode_compatible
]
@@ -34,6 +34,19 @@ 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='acme',
@@ -66,5 +79,7 @@ setup(
'dev': dev_extras,
'docs': docs_extras,
},
tests_require=["pytest"],
test_suite='acme',
cmdclass={"test": PyTest},
)

View File

@@ -1,4 +1,5 @@
""" Utility functions for certbot-apache plugin """
import binascii
import os
from certbot import util
@@ -98,3 +99,8 @@ def parse_define_file(filepath, varname):
var_parts = v[2:].partition("=")
return_vars[var_parts[0]] = var_parts[2]
return return_vars
def unique_id():
""" Returns an unique id to be used as a VirtualHost identifier"""
return binascii.hexlify(os.urandom(16)).decode("utf-8")

View File

@@ -13,13 +13,16 @@ import zope.component
import zope.interface
from acme import challenges
from acme.magic_typing import Any, DefaultDict, Dict, List, Set, 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.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
@@ -130,10 +133,10 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
default=cls.OS_DEFAULTS["challenge_location"],
help="Directory path for challenge configuration.")
add("handle-modules", default=cls.OS_DEFAULTS["handle_mods"],
help="Let installer handle enabling required modules for you." +
help="Let installer handle enabling required modules for you. " +
"(Only Ubuntu/Debian currently)")
add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"],
help="Let installer handle enabling sites for you." +
help="Let installer handle enabling sites for you. " +
"(Only Ubuntu/Debian currently)")
util.add_deprecated_argument(add, argument_name="ctl", nargs=1)
util.add_deprecated_argument(
@@ -150,14 +153,16 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
super(ApacheConfigurator, self).__init__(*args, **kwargs)
# Add name_server association dict
self.assoc = dict()
self.assoc = dict() # type: Dict[str, obj.VirtualHost]
# Outstanding challenges
self._chall_out = set()
self._chall_out = set() # type: Set[KeyAuthorizationAnnotatedChallenge]
# List of vhosts configured per wildcard domain on this run.
# used by deploy_cert() and enhance()
self._wildcard_vhosts = dict()
self._wildcard_vhosts = dict() # type: Dict[str, List[obj.VirtualHost]]
# Maps enhancements to vhosts we've enabled the enhancement for
self._enhanced_vhosts = defaultdict(set)
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]]]
# These will be set in the prepare function
self.parser = None
@@ -285,8 +290,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
chain_path=None, fullchain_path=None):
"""Deploys certificate to specified virtual host.
Currently tries to find the last directives to deploy the cert in
the VHost associated with the given domain. If it can't find the
Currently tries to find the last directives to deploy the certificate
in the VHost associated with the given domain. If it can't find the
directives, it searches the "included" confs. The function verifies
that it has located the three directives and finally modifies them
to point to the correct destination. After the certificate is
@@ -323,7 +328,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Returned objects are guaranteed to be ssl vhosts
return self._choose_vhosts_wildcard(domain, create_if_no_ssl)
else:
return [self.choose_vhost(domain)]
return [self.choose_vhost(domain, create_if_no_ssl)]
def _vhosts_for_wildcard(self, domain):
"""
@@ -424,14 +429,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
path["chain_path"] = self.parser.find_dir(
"SSLCertificateChainFile", None, vhost.path)
if not path["cert_path"] or not path["cert_key"]:
# Throw some can't find all of the directives error"
# Handle errors when certificate/key directives cannot be found
if not path["cert_path"]:
logger.warning(
"Cannot find a cert or key directive in %s. "
"Cannot find an SSLCertificateFile directive in %s. "
"VirtualHost was not modified", vhost.path)
# Presumably break here so that the virtualhost is not modified
raise errors.PluginError(
"Unable to find cert and/or key directives")
"Unable to find an SSLCertificateFile directive")
elif not path["cert_key"]:
logger.warning(
"Cannot find an SSLCertificateKeyFile directive for "
"certificate in %s. VirtualHost was not modified", vhost.path)
raise errors.PluginError(
"Unable to find an SSLCertificateKeyFile directive for "
"certificate")
logger.info("Deploying Certificate to VirtualHost %s", vhost.filep)
@@ -469,20 +480,21 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if chain_path is not None:
self.save_notes += "\tSSLCertificateChainFile %s\n" % chain_path
def choose_vhost(self, target_name, temp=False):
def choose_vhost(self, target_name, create_if_no_ssl=True):
"""Chooses a virtual host based on the given domain name.
If there is no clear virtual host to be selected, the user is prompted
with all available choices.
The returned vhost is guaranteed to have TLS enabled unless temp is
True. If temp is True, there is no such guarantee and the result is
not cached.
The returned vhost is guaranteed to have TLS enabled unless
create_if_no_ssl is set to False, in which case there is no such guarantee
and the result is not cached.
:param str target_name: domain name
:param bool temp: whether the vhost is only used temporarily
:param bool create_if_no_ssl: If found VirtualHost doesn't have a HTTPS
counterpart, should one get created
:returns: ssl vhost associated with name
:returns: vhost associated with name
:rtype: :class:`~certbot_apache.obj.VirtualHost`
:raises .errors.PluginError: If no vhost is available or chosen
@@ -495,7 +507,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# Try to find a reasonable vhost
vhost = self._find_best_vhost(target_name)
if vhost is not None:
if temp:
if not create_if_no_ssl:
return vhost
if not vhost.ssl:
vhost = self.make_vhost_ssl(vhost)
@@ -504,7 +516,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
self.assoc[target_name] = vhost
return vhost
return self._choose_vhost_from_list(target_name, temp)
# Negate create_if_no_ssl value to indicate if we want a SSL vhost
# to get created if a non-ssl vhost is selected.
return self._choose_vhost_from_list(target_name, temp=not create_if_no_ssl)
def _choose_vhost_from_list(self, target_name, temp=False):
# Select a vhost from a list
@@ -650,7 +664,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:rtype: set
"""
all_names = set()
all_names = set() # type: Set[str]
vhost_macro = []
@@ -791,8 +805,8 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
# Search base config, and all included paths for VirtualHosts
file_paths = {}
internal_paths = defaultdict(set)
file_paths = {} # type: Dict[str, str]
internal_paths = defaultdict(set) # type: DefaultDict[str, Set[str]]
vhs = []
# Make a list of parser paths because the parser_paths
# dictionary may be modified during the loop.
@@ -1230,7 +1244,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if not self.parser.parsed_in_current(ssl_fp):
self.parser.parse_file(ssl_fp)
except IOError:
logger.fatal("Error writing/reading to file in make_vhost_ssl")
logger.critical("Error writing/reading to file in make_vhost_ssl", exc_info=True)
raise errors.PluginError("Unable to write/read in make_vhost_ssl")
if sift:
@@ -1318,7 +1332,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
try:
span_val = self.aug.span(vhost.path)
except ValueError:
logger.fatal("Error while reading the VirtualHost %s from "
logger.critical("Error while reading the VirtualHost %s from "
"file %s", vhost.name, vhost.filep, exc_info=True)
raise errors.PluginError("Unable to read VirtualHost from file")
span_filep = span_val[0]
@@ -1461,6 +1475,67 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if need_to_save:
self.save()
def find_vhost_by_id(self, id_str):
"""
Searches through VirtualHosts and tries to match the id in a comment
:param str id_str: Id string for matching
:returns: The matched VirtualHost or None
:rtype: :class:`~certbot_apache.obj.VirtualHost` or None
:raises .errors.PluginError: If no VirtualHost is found
"""
for vh in self.vhosts:
if self._find_vhost_id(vh) == id_str:
return vh
msg = "No VirtualHost with ID {} was found.".format(id_str)
logger.warning(msg)
raise errors.PluginError(msg)
def _find_vhost_id(self, vhost):
"""Tries to find the unique ID from the VirtualHost comments. This is
used for keeping track of VirtualHost directive over time.
:param vhost: Virtual host to add the id
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:returns: The unique ID or None
:rtype: str or None
"""
# Strip the {} off from the format string
search_comment = constants.MANAGED_COMMENT_ID.format("")
id_comment = self.parser.find_comments(search_comment, vhost.path)
if id_comment:
# Use the first value, multiple ones shouldn't exist
comment = self.parser.get_arg(id_comment[0])
return comment.split(" ")[-1]
return None
def add_vhost_id(self, vhost):
"""Adds an unique ID to the VirtualHost as a comment for mapping back
to it on later invocations, as the config file order might have changed.
If ID already exists, returns that instead.
:param vhost: Virtual host to add or find the id
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:returns: The unique ID for vhost
:rtype: str or None
"""
vh_id = self._find_vhost_id(vhost)
if vh_id:
return vh_id
id_string = apache_util.unique_id()
comment = constants.MANAGED_COMMENT_ID.format(id_string)
self.parser.add_comment(vhost.path, comment)
return id_string
def _escape(self, fp):
fp = fp.replace(",", "\\,")
fp = fp.replace("[", "\\[")
@@ -1499,7 +1574,20 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
raise errors.PluginError(
"Unsupported enhancement: {0}".format(enhancement))
vhosts = self.choose_vhosts(domain, create_if_no_ssl=False)
matched_vhosts = self.choose_vhosts(domain, create_if_no_ssl=False)
# We should be handling only SSL vhosts for enhancements
vhosts = [vhost for vhost in matched_vhosts if vhost.ssl]
if not vhosts:
msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a "
"domain {0} for enabling enhancement \"{1}\". The requested "
"enhancement was not configured.")
msg_enhancement = enhancement
if options:
msg_enhancement += ": " + options
msg = msg_tmpl.format(domain, msg_enhancement)
logger.warning(msg)
raise errors.PluginError(msg)
try:
for vhost in vhosts:
func(vhost, options)
@@ -1507,6 +1595,78 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
logger.warning("Failed %s for %s", enhancement, domain)
raise
def _autohsts_increase(self, vhost, id_str, nextstep):
"""Increase the AutoHSTS max-age value
:param vhost: Virtual host object to modify
:type vhost: :class:`~certbot_apache.obj.VirtualHost`
:param str id_str: The unique ID string of VirtualHost
:param int nextstep: Next AutoHSTS max-age value index
"""
nextstep_value = constants.AUTOHSTS_STEPS[nextstep]
self._autohsts_write(vhost, nextstep_value)
self._autohsts[id_str] = {"laststep": nextstep, "timestamp": time.time()}
def _autohsts_write(self, vhost, nextstep_value):
"""
Write the new HSTS max-age value to the VirtualHost file
"""
hsts_dirpath = None
header_path = self.parser.find_dir("Header", None, vhost.path)
if header_path:
pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)'
for match in header_path:
if re.search(pat, self.aug.get(match).lower()):
hsts_dirpath = match
if not hsts_dirpath:
err_msg = ("Certbot was unable to find the existing HSTS header "
"from the VirtualHost at path {0}.").format(vhost.filep)
raise errors.PluginError(err_msg)
# Prepare the HSTS header value
hsts_maxage = "\"max-age={0}\"".format(nextstep_value)
# Update the header
# 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)
note_msg = ("Increasing HSTS max-age value to {0} for VirtualHost "
"in {1}\n".format(nextstep_value, vhost.filep))
logger.debug(note_msg)
self.save_notes += note_msg
self.save(note_msg)
def _autohsts_fetch_state(self):
"""
Populates the AutoHSTS state from the pluginstorage
"""
try:
self._autohsts = self.storage.fetch("autohsts")
except KeyError:
self._autohsts = dict()
def _autohsts_save_state(self):
"""
Saves the state of AutoHSTS object to pluginstorage
"""
self.storage.put("autohsts", self._autohsts)
self.storage.save()
def _autohsts_vhost_in_lineage(self, vhost, lineage):
"""
Searches AutoHSTS managed VirtualHosts that belong to the lineage.
Matches the private key path.
"""
return bool(
self.parser.find_dir("SSLCertificateKeyFile",
lineage.key_path, vhost.path))
def _enable_ocsp_stapling(self, ssl_vhost, unused_options):
"""Enables OCSP Stapling
@@ -1748,7 +1908,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
# There can be other RewriteRule directive lines in vhost config.
# rewrite_args_dict keys are directive ids and the corresponding value
# for each is a list of arguments to that directive.
rewrite_args_dict = defaultdict(list)
rewrite_args_dict = defaultdict(list) # type: DefaultDict[str, List[str]]
pat = r'(.*directive\[\d+\]).*'
for match in rewrite_path:
m = re.match(pat, match)
@@ -1842,7 +2002,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
if ssl_vhost.aliases:
serveralias = "ServerAlias " + " ".join(ssl_vhost.aliases)
rewrite_rule_args = []
rewrite_rule_args = [] # type: List[str]
if self.get_version() >= (2, 3, 9):
rewrite_rule_args = constants.REWRITE_HTTPS_ARGS_WITH_END
else:
@@ -1994,10 +2154,27 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:raises .errors.MisconfigurationError: If reload fails
"""
error = ""
try:
util.run_script(self.constant("restart_cmd"))
except errors.SubprocessError as err:
raise errors.MisconfigurationError(str(err))
logger.info("Unable to restart apache using %s",
self.constant("restart_cmd"))
alt_restart = self.constant("restart_cmd_alt")
if alt_restart:
logger.debug("Trying alternative restart command: %s",
alt_restart)
# There is an alternative restart command available
# This usually is "restart" verb while original is "graceful"
try:
util.run_script(self.constant(
"restart_cmd_alt"))
return
except errors.SubprocessError as secerr:
error = str(secerr)
else:
error = str(err)
raise errors.MisconfigurationError(error)
def config_test(self): # pylint: disable=no-self-use
"""Check the configuration of Apache for errors.
@@ -2118,4 +2295,176 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
return common.install_version_controlled_file(options_ssl, options_ssl_digest,
self.constant("MOD_SSL_CONF_SRC"), constants.ALL_SSL_OPTIONS_HASHES)
def enable_autohsts(self, _unused_lineage, domains):
"""
Enable the AutoHSTS enhancement for defined domains
:param _unused_lineage: Certificate lineage object, unused
:type _unused_lineage: certbot.storage.RenewableCert
:param domains: List of domains in certificate to enhance
:type domains: str
"""
self._autohsts_fetch_state()
_enhanced_vhosts = []
for d in domains:
matched_vhosts = self.choose_vhosts(d, create_if_no_ssl=False)
# We should be handling only SSL vhosts for AutoHSTS
vhosts = [vhost for vhost in matched_vhosts if vhost.ssl]
if not vhosts:
msg_tmpl = ("Certbot was not able to find SSL VirtualHost for a "
"domain {0} for enabling AutoHSTS enhancement.")
msg = msg_tmpl.format(d)
logger.warning(msg)
raise errors.PluginError(msg)
for vh in vhosts:
try:
self._enable_autohsts_domain(vh)
_enhanced_vhosts.append(vh)
except errors.PluginEnhancementAlreadyPresent:
if vh in _enhanced_vhosts:
continue
msg = ("VirtualHost for domain {0} in file {1} has a " +
"String-Transport-Security header present, exiting.")
raise errors.PluginEnhancementAlreadyPresent(
msg.format(d, vh.filep))
if _enhanced_vhosts:
note_msg = "Enabling AutoHSTS"
self.save(note_msg)
logger.info(note_msg)
self.restart()
# Save the current state to pluginstorage
self._autohsts_save_state()
def _enable_autohsts_domain(self, ssl_vhost):
"""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
:raises errors.PluginEnhancementAlreadyPresent: When already enhanced
"""
# This raises the exception
self._verify_no_matching_http_header(ssl_vhost,
"Strict-Transport-Security")
if "headers_module" not in self.parser.modules:
self.enable_mod("headers")
# Prepare the HSTS header value
hsts_header = constants.HEADER_ARGS["Strict-Transport-Security"][:-1]
initial_maxage = constants.AUTOHSTS_STEPS[0]
hsts_header.append("\"max-age={0}\"".format(initial_maxage))
# Add ID to the VirtualHost for mapping back to it later
uniq_id = self.add_vhost_id(ssl_vhost)
self.save_notes += "Adding unique ID {0} to VirtualHost in {1}\n".format(
uniq_id, ssl_vhost.filep)
# Add the actual HSTS header
self.parser.add_dir(ssl_vhost.path, "Header", hsts_header)
note_msg = ("Adding gradually increasing HSTS header with initial value "
"of {0} to VirtualHost in {1}\n".format(
initial_maxage, ssl_vhost.filep))
self.save_notes += note_msg
# Save the current state to pluginstorage
self._autohsts[uniq_id] = {"laststep": 0, "timestamp": time.time()}
def update_autohsts(self, _unused_domain):
"""
Increase the AutoHSTS values of VirtualHosts that the user has enabled
this enhancement for.
:param _unused_domain: Not currently used
:type _unused_domain: Not Available
"""
self._autohsts_fetch_state()
if not self._autohsts:
# No AutoHSTS enabled for any domain
return
curtime = time.time()
save_and_restart = False
for id_str, config in list(self._autohsts.items()):
if config["timestamp"] + constants.AUTOHSTS_FREQ > curtime:
# Skip if last increase was < AUTOHSTS_FREQ ago
continue
nextstep = config["laststep"] + 1
if nextstep < len(constants.AUTOHSTS_STEPS):
# Have not reached the max value yet
try:
vhost = self.find_vhost_by_id(id_str)
except errors.PluginError:
msg = ("Could not find VirtualHost with ID {0}, disabling "
"AutoHSTS for this VirtualHost").format(id_str)
logger.warning(msg)
# Remove the orphaned AutoHSTS entry from pluginstorage
self._autohsts.pop(id_str)
continue
self._autohsts_increase(vhost, id_str, nextstep)
msg = ("Increasing HSTS max-age value for VirtualHost with id "
"{0}").format(id_str)
self.save_notes += msg
save_and_restart = True
if save_and_restart:
self.save("Increased HSTS max-age values")
self.restart()
self._autohsts_save_state()
def deploy_autohsts(self, lineage):
"""
Checks if autohsts vhost has reached maximum auto-increased value
and changes the HSTS max-age to a high value.
:param lineage: Certificate lineage object
:type lineage: certbot.storage.RenewableCert
"""
self._autohsts_fetch_state()
if not self._autohsts:
# No autohsts enabled for any vhost
return
vhosts = []
affected_ids = []
# Copy, as we are removing from the dict inside the loop
for id_str, config in list(self._autohsts.items()):
if config["laststep"]+1 >= len(constants.AUTOHSTS_STEPS):
# max value reached, try to make permanent
try:
vhost = self.find_vhost_by_id(id_str)
except errors.PluginError:
msg = ("VirtualHost with id {} was not found, unable to "
"make HSTS max-age permanent.").format(id_str)
logger.warning(msg)
self._autohsts.pop(id_str)
continue
if self._autohsts_vhost_in_lineage(vhost, lineage):
vhosts.append(vhost)
affected_ids.append(id_str)
save_and_restart = False
for vhost in vhosts:
self._autohsts_write(vhost, constants.AUTOHSTS_PERMANENT)
msg = ("Strict-Transport-Security max-age value for "
"VirtualHost in {0} was made permanent.").format(vhost.filep)
logger.debug(msg)
self.save_notes += msg+"\n"
save_and_restart = True
if save_and_restart:
self.save("Made HSTS max-age permanent")
self.restart()
for id_str in affected_ids:
self._autohsts.pop(id_str)
# Update AutoHSTS storage (We potentially removed vhosts from managed)
self._autohsts_save_state()
AutoHSTSEnhancement.register(ApacheConfigurator) # pylint: disable=no-member

View File

@@ -48,3 +48,16 @@ UIR_ARGS = ["always", "set", "Content-Security-Policy",
HEADER_ARGS = {"Strict-Transport-Security": HSTS_ARGS,
"Upgrade-Insecure-Requests": UIR_ARGS}
AUTOHSTS_STEPS = [60, 300, 900, 3600, 21600, 43200, 86400]
"""AutoHSTS increase steps: 1min, 5min, 15min, 1h, 6h, 12h, 24h"""
AUTOHSTS_PERMANENT = 31536000
"""Value for the last max-age of HSTS"""
AUTOHSTS_FREQ = 172800
"""Minimum time since last increase to perform a new one: 48h"""
MANAGED_COMMENT = "DO NOT REMOVE - Managed by Certbot"
MANAGED_COMMENT_ID = MANAGED_COMMENT+", VirtualHost id: {0}"
"""Managed by Certbot comments and the VirtualHost identification template"""

View File

@@ -2,9 +2,10 @@
import logging
import os
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from certbot import errors
from certbot.plugins import common
from certbot_apache.obj import VirtualHost # pylint: disable=unused-import
logger = logging.getLogger(__name__)
@@ -51,7 +52,7 @@ class ApacheHttp01(common.TLSSNI01):
self.challenge_dir = os.path.join(
self.configurator.config.work_dir,
"http_challenges")
self.moded_vhosts = set()
self.moded_vhosts = set() # type: Set[VirtualHost]
def perform(self):
"""Perform all HTTP-01 challenges."""

View File

@@ -1,6 +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 certbot.plugins import common
@@ -140,7 +141,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
def get_names(self):
"""Return a set of all names."""
all_names = set()
all_names = set() # type: Set[str]
all_names.update(self.aliases)
# Strip out any scheme:// and <port> field from servername
if self.name is not None:
@@ -251,7 +252,7 @@ class VirtualHost(object): # pylint: disable=too-few-public-methods
# already_found acts to keep everything very conservative.
# Don't allow multiple ip:ports in same set.
already_found = set()
already_found = set() # type: Set[str]
for addr in vhost.addrs:
for local_addr in self.addrs:

View File

@@ -21,6 +21,7 @@ class CentOSConfigurator(configurator.ApacheConfigurator):
version_cmd=['apachectl', '-v'],
apache_cmd="apachectl",
restart_cmd=['apachectl', 'graceful'],
restart_cmd_alt=['apachectl', 'restart'],
conftest_cmd=['apachectl', 'configtest'],
enmod=None,
dismod=None,
@@ -46,10 +47,10 @@ class CentOSParser(parser.ApacheParser):
self.sysconfig_filep = "/etc/sysconfig/httpd"
super(CentOSParser, self).__init__(*args, **kwargs)
def update_runtime_variables(self, *args, **kwargs):
def update_runtime_variables(self):
""" Override for update_runtime_variables for custom parsing """
# Opportunistic, works if SELinux not enforced
super(CentOSParser, self).update_runtime_variables(*args, **kwargs)
super(CentOSParser, self).update_runtime_variables()
self.parse_sysconfig_var()
def parse_sysconfig_var(self):

View File

@@ -21,6 +21,7 @@ class GentooConfigurator(configurator.ApacheConfigurator):
version_cmd=['/usr/sbin/apache2', '-v'],
apache_cmd="apache2ctl",
restart_cmd=['apache2ctl', 'graceful'],
restart_cmd_alt=['apache2ctl', 'restart'],
conftest_cmd=['apache2ctl', 'configtest'],
enmod=None,
dismod=None,

View File

@@ -9,12 +9,14 @@ import sys
import six
from acme.magic_typing import Dict, List, Set # pylint: disable=unused-import, no-name-in-module
from certbot import errors
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...
@@ -38,9 +40,9 @@ class ApacheParser(object):
# issues with aug.load() after adding new files / defines to parse tree
self.configurator = configurator
self.modules = set()
self.parser_paths = {}
self.variables = {}
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.
@@ -119,7 +121,7 @@ class ApacheParser(object):
the iteration issue. Else... parse and enable mods at same time.
"""
mods = set()
mods = set() # type: Set[str]
matches = self.find_dir("LoadModule")
iterator = iter(matches)
# Make sure prev_size != cur_size for do: while: iteration
@@ -349,6 +351,37 @@ class ApacheParser(object):
else:
self.aug.set(first_dir + "/arg", args)
def add_comment(self, aug_conf_path, comment):
"""Adds the comment to the augeas path
:param str aug_conf_path: Augeas configuration path to add directive
:param str comment: Comment content
"""
self.aug.set(aug_conf_path + "/#comment[last() + 1]", comment)
def find_comments(self, arg, start=None):
"""Finds a comment with specified content from the provided DOM path
:param str arg: Comment content to search
:param str start: Beginning Augeas path to begin looking
:returns: List of augeas paths containing the comment content
:rtype: list
"""
if not start:
start = get_aug_path(self.root)
comments = self.aug.match("%s//*[label() = '#comment']" % start)
results = []
for comment in comments:
c_content = self.aug.get(comment)
if c_content and arg in c_content:
results.append(comment)
return results
def find_dir(self, directive, arg=None, start=None, exclude=True):
"""Finds directive in the configuration.
@@ -408,7 +441,7 @@ class ApacheParser(object):
else:
arg_suffix = "/*[self::arg=~regexp('%s')]" % case_i(arg)
ordered_matches = []
ordered_matches = [] # type: List[str]
# TODO: Wildcards should be included in alphabetical order
# https://httpd.apache.org/docs/2.4/mod/core.html#include

View File

@@ -46,6 +46,7 @@ function Cleanup() {
# if our environment asks us to enable modules, do our best!
if [ "$1" = --debian-modules ] ; then
sudo apt-get install -y apache2
sudo apt-get install -y libapache2-mod-wsgi
sudo apt-get install -y libapache2-mod-macro

View File

@@ -0,0 +1,181 @@
# pylint: disable=too-many-public-methods,too-many-lines
"""Test for certbot_apache.configurator AutoHSTS functionality"""
import re
import unittest
import mock
# six is used in mock.patch()
import six # pylint: disable=unused-import
from certbot import errors
from certbot_apache import constants
from certbot_apache.tests import util
class AutoHSTSTest(util.ApacheTest):
"""Tests for AutoHSTS feature"""
# pylint: disable=protected-access
def setUp(self): # pylint: disable=arguments-differ
super(AutoHSTSTest, self).setUp()
self.config = util.get_apache_configurator(
self.config_path, self.vhost_path, self.config_dir, self.work_dir)
self.config.parser.modules.add("headers_module")
self.config.parser.modules.add("mod_headers.c")
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
def get_autohsts_value(self, vh_path):
""" Get value from Strict-Transport-Security header """
header_path = self.config.parser.find_dir("Header", None, vh_path)
if header_path:
pat = '(?:[ "]|^)(strict-transport-security)(?:[ "]|$)'
for head in header_path:
if re.search(pat, self.config.parser.aug.get(head).lower()):
return self.config.parser.aug.get(head.replace("arg[3]",
"arg[4]"))
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.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")
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")
def test_autohsts_increase(self, _mock_restart):
maxage = "\"max-age={0}\""
initial_val = maxage.format(constants.AUTOHSTS_STEPS[0])
inc_val = maxage.format(constants.AUTOHSTS_STEPS[1])
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
# Increase
self.config.update_autohsts(mock.MagicMock())
# Verify increased value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
inc_val)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.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])
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Verify initial value
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
initial_val)
self.config.update_autohsts(mock.MagicMock())
# Freq not patched, so value shouldn't increase
self.assertFalse(mock_increase.called)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
def test_autohsts_increase_no_header(self, _restart):
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
# Remove the header
dir_locs = self.config.parser.find_dir("Header", None,
self.vh_truth[7].path)
dir_loc = "/".join(dir_locs[0].split("/")[:-1])
self.config.parser.aug.remove(dir_loc)
self.assertRaises(errors.PluginError,
self.config.update_autohsts,
mock.MagicMock())
@mock.patch("certbot_apache.constants.AUTOHSTS_FREQ", 0)
@mock.patch("certbot_apache.configurator.ApacheConfigurator.restart")
def test_autohsts_increase_and_make_permanent(self, _mock_restart):
maxage = "\"max-age={0}\""
max_val = maxage.format(constants.AUTOHSTS_PERMANENT)
mock_lineage = mock.MagicMock()
mock_lineage.key_path = "/etc/apache2/ssl/key-certbot_15.pem"
self.config.enable_autohsts(mock.MagicMock(), ["ocspvhost.com"])
for i in range(len(constants.AUTOHSTS_STEPS)-1):
# Ensure that value is not made permanent prematurely
self.config.deploy_autohsts(mock_lineage)
self.assertNotEquals(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
self.config.update_autohsts(mock.MagicMock())
# Value should match pre-permanent increment step
cur_val = maxage.format(constants.AUTOHSTS_STEPS[i+1])
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
cur_val)
# Make permanent
self.config.deploy_autohsts(mock_lineage)
self.assertEquals(self.get_autohsts_value(self.vh_truth[7].path),
max_val)
def test_autohsts_update_noop(self):
with mock.patch("time.time") as mock_time:
# Time mock is used to make sure that the execution does not
# continue when no autohsts entries exist in pluginstorage
self.config.update_autohsts(mock.MagicMock())
self.assertFalse(mock_time.called)
def test_autohsts_make_permanent_noop(self):
self.config.storage.put = mock.MagicMock()
self.config.deploy_autohsts(mock.MagicMock())
# 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")
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:
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")
def test_autohsts_dont_enhance_twice(self, mock_id, _restart):
mock_id.return_value = "1234567"
self.config.enable_autohsts(mock.MagicMock(),
["ocspvhost.com", "ocspvhost.com"])
self.assertEquals(mock_id.call_count, 1)
def test_autohsts_remove_orphaned(self):
# pylint: disable=protected-access
self.config._autohsts_fetch_state()
self.config._autohsts["orphan_id"] = {"laststep": 0, "timestamp": 0}
self.config._autohsts_save_state()
self.config.update_autohsts(mock.MagicMock())
self.assertFalse("orphan_id" in self.config._autohsts)
# Make sure it's removed from the pluginstorage file as well
self.config._autohsts = None
self.config._autohsts_fetch_state()
self.assertFalse(self.config._autohsts)
def test_autohsts_make_permanent_vhost_not_found(self):
# pylint: disable=protected-access
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:
self.config.deploy_autohsts(mock.MagicMock())
self.assertTrue(mock_log.called)
self.assertTrue(
"VirtualHost with id orphan_id was not" in mock_log.call_args[0][0])
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -4,6 +4,8 @@ import unittest
import mock
from certbot import errors
from certbot_apache import obj
from certbot_apache import override_centos
from certbot_apache.tests import util
@@ -121,5 +123,17 @@ 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")
def test_alt_restart_works(self, mock_run_script):
mock_run_script.side_effect = [None, errors.SubprocessError, None]
self.config.restart()
self.assertEquals(mock_run_script.call_count, 3)
@mock.patch("certbot_apache.configurator.util.run_script")
def test_alt_restart_errors(self, mock_run_script):
mock_run_script.side_effect = [None,
errors.SubprocessError,
errors.SubprocessError]
self.assertRaises(errors.MisconfigurationError, self.config.restart)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -246,7 +246,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache.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", temp=True)
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")
@@ -353,14 +353,11 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.parser.find_dir = mock_find_dir
mock_add.reset_mock()
self.config._add_dummy_ssl_directives(self.vh_truth[0]) # pylint: disable=protected-access
tried_to_add = []
for a in mock_add.call_args_list:
tried_to_add.append(a[0][1] == "Include" and
a[0][2] == self.config.mod_ssl_conf)
# Include shouldn't be added, as patched find_dir "finds" existing one
self.assertFalse(any(tried_to_add))
if a[0][1] == "Include" and a[0][2] == self.config.mod_ssl_conf:
self.fail("Include shouldn't be added, as patched find_dir 'finds' existing one") \
# pragma: no cover
def test_deploy_cert(self):
self.config.parser.modules.add("ssl_module")
@@ -441,13 +438,37 @@ class MultipleVhostsTest(util.ApacheTest):
self.vh_truth[1].path))
def test_deploy_cert_invalid_vhost(self):
"""For test cases where the `ApacheConfigurator` class' `_deploy_cert`
method is called with an invalid vhost parameter. Currently this tests
that a PluginError is appropriately raised when important directives
are missing in an SSL module."""
self.config.parser.modules.add("ssl_module")
mock_find = mock.MagicMock()
mock_find.return_value = []
self.config.parser.find_dir = mock_find
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
def side_effect(*args):
"""Mocks case where an SSLCertificateFile directive can be found
but an SSLCertificateKeyFile directive is missing."""
if "SSLCertificateFile" in args:
return ["example/cert.pem"]
else:
return []
mock_find_dir = mock.MagicMock(return_value=[])
mock_find_dir.side_effect = side_effect
self.config.parser.find_dir = mock_find_dir
# Get the default 443 vhost
self.config.assoc["random.demo"] = self.vh_truth[1]
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
# Remove side_effect to mock case where both SSLCertificateFile
# and SSLCertificateKeyFile directives are missing
self.config.parser.find_dir.side_effect = None
self.assertRaises(
errors.PluginError, self.config.deploy_cert, "random.demo",
"example/cert.pem", "example/key.pem", "example/cert_chain.pem")
@@ -912,6 +933,22 @@ class MultipleVhostsTest(util.ApacheTest):
errors.PluginError,
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:
self.assertRaises(errors.PluginError, self.config.enhance,
"certbot.demo", "redirect")
# Check that correct logger.warning was printed
self.assertTrue("not able to find" in mock_log.call_args[0][0])
self.assertTrue("\"redirect\"" in mock_log.call_args[0][0])
mock_log.reset_mock()
self.assertRaises(errors.PluginError, self.config.enhance,
"certbot.demo", "ensure-http-header", "Test")
# Check that correct logger.warning was printed
self.assertTrue("not able to find" in mock_log.call_args[0][0])
self.assertTrue("Test" in mock_log.call_args[0][0])
@mock.patch("certbot.util.exe_exists")
def test_ocsp_stapling(self, mock_exe):
self.config.parser.update_runtime_variables = mock.Mock()
@@ -921,6 +958,7 @@ class MultipleVhostsTest(util.ApacheTest):
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "staple-ocsp")
# Get the ssl vhost for certbot.demo
@@ -947,6 +985,7 @@ class MultipleVhostsTest(util.ApacheTest):
mock_exe.return_value = True
# Checking the case with already enabled ocsp stapling configuration
self.config.choose_vhost("ocspvhost.com")
self.config.enhance("ocspvhost.com", "staple-ocsp")
# Get the ssl vhost for letsencrypt.demo
@@ -971,6 +1010,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
self.config.get_version = mock.Mock(return_value=(2, 2, 0))
self.config.choose_vhost("certbot.demo")
self.assertRaises(errors.PluginError,
self.config.enhance, "certbot.demo", "staple-ocsp")
@@ -996,6 +1036,7 @@ class MultipleVhostsTest(util.ApacheTest):
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "ensure-http-header",
"Strict-Transport-Security")
@@ -1015,7 +1056,8 @@ class MultipleVhostsTest(util.ApacheTest):
# skip the enable mod
self.config.parser.modules.add("headers_module")
# This will create an ssl vhost for certbot.demo
# This will create an ssl vhost for encryption-example.demo
self.config.choose_vhost("encryption-example.demo")
self.config.enhance("encryption-example.demo", "ensure-http-header",
"Strict-Transport-Security")
@@ -1034,6 +1076,7 @@ class MultipleVhostsTest(util.ApacheTest):
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "ensure-http-header",
"Upgrade-Insecure-Requests")
@@ -1055,7 +1098,8 @@ class MultipleVhostsTest(util.ApacheTest):
# skip the enable mod
self.config.parser.modules.add("headers_module")
# This will create an ssl vhost for certbot.demo
# This will create an ssl vhost for encryption-example.demo
self.config.choose_vhost("encryption-example.demo")
self.config.enhance("encryption-example.demo", "ensure-http-header",
"Upgrade-Insecure-Requests")
@@ -1073,6 +1117,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.get_version = mock.Mock(return_value=(2, 2))
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "redirect")
# These are not immediately available in find_dir even with save() and
@@ -1123,6 +1168,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.save()
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "redirect")
# These are not immediately available in find_dir even with save() and
@@ -1189,6 +1235,9 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.parser.modules.add("rewrite_module")
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
# Creates ssl vhost for the domain
self.config.choose_vhost("red.blue.purple.com")
self.config.enhance("red.blue.purple.com", "redirect")
verify_no_redirect = ("certbot_apache.configurator."
"ApacheConfigurator._verify_no_certbot_redirect")
@@ -1200,7 +1249,7 @@ class MultipleVhostsTest(util.ApacheTest):
# Skip the enable mod
self.config.parser.modules.add("rewrite_module")
self.config.get_version = mock.Mock(return_value=(2, 3, 9))
self.config.choose_vhost("red.blue.purple.com")
self.config.enhance("red.blue.purple.com", "redirect")
# Clear state about enabling redirect on this run
# pylint: disable=protected-access
@@ -1422,6 +1471,7 @@ class MultipleVhostsTest(util.ApacheTest):
# pylint: disable=protected-access
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
self.vh_truth[3].ssl = True
self.config._wildcard_vhosts["*.certbot.demo"] = [self.vh_truth[3]]
self.config.enhance("*.certbot.demo", "ensure-http-header",
"Upgrade-Insecure-Requests")
@@ -1429,6 +1479,7 @@ class MultipleVhostsTest(util.ApacheTest):
@mock.patch("certbot_apache.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]]
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("headers_module")
@@ -1436,6 +1487,21 @@ class MultipleVhostsTest(util.ApacheTest):
"Upgrade-Insecure-Requests")
self.assertTrue(mock_choose.called)
def test_add_vhost_id(self):
for vh in [self.vh_truth[0], self.vh_truth[1], self.vh_truth[2]]:
vh_id = self.config.add_vhost_id(vh)
self.assertEqual(vh, self.config.find_vhost_by_id(vh_id))
def test_find_vhost_by_id_404(self):
self.assertRaises(errors.PluginError,
self.config.find_vhost_by_id,
"nonexistent")
def test_add_vhost_id_already_exists(self):
first_id = self.config.add_vhost_id(self.vh_truth[0])
second_id = self.config.add_vhost_id(self.vh_truth[0])
self.assertEqual(first_id, second_id)
class AugeasVhostsTest(util.ApacheTest):
"""Test vhosts with illegal names dependent on augeas version."""

View File

@@ -161,6 +161,8 @@ class MultipleVhostsTestDebian(util.ApacheTest):
self.config.parser.modules.add("mod_ssl.c")
self.config.get_version = mock.Mock(return_value=(2, 4, 7))
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "staple-ocsp")
self.assertTrue("socache_shmcb_module" in self.config.parser.modules)
@@ -172,6 +174,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
mock_exe.return_value = True
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "ensure-http-header",
"Strict-Transport-Security")
self.assertTrue("headers_module" in self.config.parser.modules)
@@ -183,6 +186,7 @@ class MultipleVhostsTestDebian(util.ApacheTest):
mock_exe.return_value = True
self.config.get_version = mock.Mock(return_value=(2, 2))
# This will create an ssl vhost for certbot.demo
self.config.choose_vhost("certbot.demo")
self.config.enhance("certbot.demo", "redirect")
self.assertTrue("rewrite_module" in self.config.parser.modules)

View File

@@ -4,6 +4,8 @@ import unittest
import mock
from certbot import errors
from certbot_apache import override_gentoo
from certbot_apache import obj
from certbot_apache.tests import util
@@ -123,5 +125,11 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
self.assertEquals(len(self.config.parser.modules), 4)
self.assertTrue("mod_another.c" in self.config.parser.modules)
@mock.patch("certbot_apache.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.assertEquals(mock_run_script.call_count, 3)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -4,47 +4,26 @@ import os
import unittest
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.tests import acme_util
from certbot_apache.tests import util
NUM_ACHALLS = 3
class ApacheHttp01TestMeta(type):
"""Generates parmeterized tests for testing perform."""
def __new__(mcs, name, bases, class_dict):
def _gen_test(num_achalls, minor_version):
def _test(self):
achalls = self.achalls[:num_achalls]
vhosts = self.vhosts[:num_achalls]
self.config.version = (2, minor_version)
self.common_perform_test(achalls, vhosts)
return _test
for i in range(1, NUM_ACHALLS + 1):
for j in (2, 4):
test_name = "test_perform_{0}_{1}".format(i, j)
class_dict[test_name] = _gen_test(i, j)
return type.__new__(mcs, name, bases, class_dict)
class ApacheHttp01Test(util.ApacheTest):
"""Test for certbot_apache.http_01.ApacheHttp01."""
__metaclass__ = ApacheHttp01TestMeta
def setUp(self, *args, **kwargs):
super(ApacheHttp01Test, self).setUp(*args, **kwargs)
self.account_key = self.rsa512jwk
self.achalls = []
self.achalls = [] # type: List[achallenges.KeyAuthorizationAnnotatedChallenge]
vh_truth = util.get_vh_truth(
self.temp_dir, "debian_apache_2_4/multiple_vhosts")
# Takes the vhosts for encryption-example.demo, certbot.demo, and
@@ -71,7 +50,7 @@ class ApacheHttp01Test(util.ApacheTest):
self.assertFalse(self.http.perform())
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_22(self, mock_enmod):
def test_enable_modules_apache_2_2(self, mock_enmod):
self.config.version = (2, 2)
self.config.parser.modules.remove("authz_host_module")
self.config.parser.modules.remove("mod_authz_host.c")
@@ -80,7 +59,7 @@ class ApacheHttp01Test(util.ApacheTest):
self.assertEqual(enmod_calls[0][0][0], "authz_host")
@mock.patch("certbot_apache.configurator.ApacheConfigurator.enable_mod")
def test_enable_modules_24(self, mock_enmod):
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")
@@ -137,6 +116,31 @@ class ApacheHttp01Test(util.ApacheTest):
self.config.config.http01_port = 12345
self.assertRaises(errors.PluginError, self.http.perform)
def test_perform_1_achall_apache_2_2(self):
self.combinations_perform_test(num_achalls=1, minor_version=2)
def test_perform_1_achall_apache_2_4(self):
self.combinations_perform_test(num_achalls=1, minor_version=4)
def test_perform_2_achall_apache_2_2(self):
self.combinations_perform_test(num_achalls=2, minor_version=2)
def test_perform_2_achall_apache_2_4(self):
self.combinations_perform_test(num_achalls=2, minor_version=4)
def test_perform_3_achall_apache_2_2(self):
self.combinations_perform_test(num_achalls=3, minor_version=2)
def test_perform_3_achall_apache_2_4(self):
self.combinations_perform_test(num_achalls=3, minor_version=4)
def combinations_perform_test(self, num_achalls, minor_version):
"""Test perform with the given achall count and Apache version."""
achalls = self.achalls[:num_achalls]
vhosts = self.vhosts[:num_achalls]
self.config.version = (2, minor_version)
self.common_perform_test(achalls, vhosts)
def common_perform_test(self, achalls, vhosts):
"""Tests perform with the given achalls."""
challenge_dir = self.http.challenge_dir

View File

@@ -299,6 +299,13 @@ class BasicParserTest(util.ParserTest):
errors.MisconfigurationError,
self.parser.update_runtime_variables)
def test_add_comment(self):
from certbot_apache.parser import get_aug_path
self.parser.add_comment(get_aug_path(self.parser.loc["name"]), "123456")
comm = self.parser.find_comments("123456")
self.assertEquals(len(comm), 1)
self.assertTrue(self.parser.loc["name"] in comm[0])
class ParserInitTest(util.ApacheTest):
def setUp(self): # pylint: disable=arguments-differ

View File

@@ -87,7 +87,6 @@ class ParserTest(ApacheTest):
def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-locals
config_path, vhost_path,
config_dir, work_dir, version=(2, 4, 7),
conf=None,
os_info="generic",
conf_vhost_path=None):
"""Create an Apache Configurator with the specified options.
@@ -133,10 +132,6 @@ def get_apache_configurator( # pylint: disable=too-many-arguments, too-many-loc
config_class = configurator.ApacheConfigurator
config = config_class(config=mock_le_config, name="apache",
version=version)
# This allows testing scripts to set it a bit more
# quickly
if conf is not None:
config.conf = conf # pragma: no cover
config.prepare()
return config

View File

@@ -3,6 +3,7 @@
import os
import logging
from acme.magic_typing import Set # pylint: disable=unused-import, no-name-in-module
from certbot.plugins import common
from certbot.errors import PluginError, MissingCommandlineFlag
@@ -93,7 +94,7 @@ class ApacheTlsSni01(common.TLSSNI01):
:rtype: set
"""
addrs = set()
addrs = set() # type: Set[obj.Addr]
config_text = "<IfModule mod_ssl.c>\n"
for achall in self.achalls:
@@ -123,7 +124,8 @@ class ApacheTlsSni01(common.TLSSNI01):
self.configurator.config.tls_sni_01_port)))
try:
vhost = self.configurator.choose_vhost(achall.domain, temp=True)
vhost = self.configurator.choose_vhost(achall.domain,
create_if_no_ssl=False)
except (PluginError, MissingCommandlineFlag):
# We couldn't find the virtualhost for this domain, possibly
# because it's a new vhost that's not configured yet

View File

@@ -0,0 +1,2 @@
acme[dev]==0.25.0
-e .[dev]

View File

@@ -1,15 +1,14 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'acme>=0.25.0',
'certbot>=0.26.0.dev0',
'mock',
'python-augeas',
'setuptools',

View File

@@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then
fi
VENV_BIN="$VENV_PATH/bin"
BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt"
LE_AUTO_VERSION="0.21.1"
LE_AUTO_VERSION="0.25.1"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -47,6 +47,7 @@ Help for certbot itself cannot be provided until it is installed.
--no-bootstrap do not install OS dependencies
--no-self-upgrade do not download updates
--os-packages-only install OS dependencies and exit
--install-only install certbot, upgrade if needed, and exit
-v, --verbose provide more output
-q, --quiet provide only update/error output;
implies --non-interactive
@@ -60,6 +61,8 @@ for arg in "$@" ; do
DEBUG=1;;
--os-packages-only)
OS_PACKAGES_ONLY=1;;
--install-only)
INSTALL_ONLY=1;;
--no-self-upgrade)
# Do not upgrade this script (also prevents client upgrades, because each
# copy of the script pins a hash of the python client)
@@ -246,7 +249,7 @@ DeprecationBootstrap() {
fi
}
MIN_PYTHON_VERSION="2.6"
MIN_PYTHON_VERSION="2.7"
MIN_PYVER=$(echo "$MIN_PYTHON_VERSION" | sed 's/\.//')
# Sets LE_PYTHON to Python version string and PYVER to the first two
# digits of the python version
@@ -1052,9 +1055,11 @@ cffi==1.10.0 \
--hash=sha256:5576644b859197da7bbd8f8c7c2fb5dcc6cd505cadb42992d5f104c013f8a214 \
--hash=sha256:b3b02911eb1f6ada203b0763ba924234629b51586f72a21faacc638269f4ced5
ConfigArgParse==0.12.0 \
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339
--hash=sha256:28cd7d67669651f2a4518367838c49539457504584a139709b2b8f6c208ef339 \
--no-binary ConfigArgParse
configobj==5.0.6 \
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902
--hash=sha256:a2f5650770e1c87fb335af19a9b7eb73fc05ccf22144eb68db7d00cd2bcb0902 \
--no-binary configobj
cryptography==2.0.2 \
--hash=sha256:187ae17358436d2c760f28c2aeb02fefa3f37647a9c5b6f7f7c3e83cd1c5a972 \
--hash=sha256:19e43a13bbf52028dd1e810c803f2ad8880d0692d772f98d42e1eaf34bdee3d6 \
@@ -1086,7 +1091,7 @@ cryptography==2.0.2 \
--hash=sha256:01e6e60654df64cca53733cda39446d67100c819c181d403afb120e0d2a71e1b \
--hash=sha256:d46f4e5d455cb5563685c52ef212696f0a6cc1ea627603218eabbd8a095291d8 \
--hash=sha256:3780b2663ee7ebb37cb83263326e3cd7f8b2ea439c448539d4b87de12c8d06ab
enum34==1.1.2 \
enum34==1.1.2 ; python_version < '3.4' \
--hash=sha256:2475d7fcddf5951e92ff546972758802de5260bf409319a9f1934e6bbc8b1dc7 \
--hash=sha256:35907defb0f992b75ab7788f65fedc1cf20ffa22688e0e6f6f12afc06b3ea501
funcsigs==1.0.2 \
@@ -1109,7 +1114,8 @@ mock==1.3.0 \
--hash=sha256:3f573a18be94de886d1191f27c168427ef693e8dcfcecf95b170577b2eb69cbb \
--hash=sha256:1e247dbecc6ce057299eb7ee019ad68314bb93152e81d9a6110d35f4d5eca0f6
ordereddict==1.1 \
--hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f
--hash=sha256:1c35b4ac206cef2d24816c89f89cf289dd3d38cf7c449bb3fab7bf6d43f01b1f \
--no-binary ordereddict
packaging==16.8 \
--hash=sha256:99276dc6e3a7851f32027a68f1095cd3f77c148091b092ea867a351811cfe388 \
--hash=sha256:5d50835fdf0a7edf0b55e311b7c887786504efea1177abd7e69329a8e5ea619e
@@ -1135,7 +1141,8 @@ pyRFC3339==1.0 \
--hash=sha256:eea31835c56e2096af4363a5745a784878a61d043e247d3a6d6a0a32a9741f56 \
--hash=sha256:8dfbc6c458b8daba1c0f3620a8c78008b323a268b27b7359e92a4ae41325f535
python-augeas==0.5.0 \
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2
--hash=sha256:67d59d66cdba8d624e0389b87b2a83a176f21f16a87553b50f5703b23f29bac2 \
--no-binary python-augeas
pytz==2015.7 \
--hash=sha256:3abe6a6d3fc2fbbe4c60144211f45da2edbe3182a6f6511af6bbba0598b1f992 \
--hash=sha256:939ef9c1e1224d980405689a97ffcf7828c56d1517b31d73464356c1f2b7769e \
@@ -1163,9 +1170,11 @@ unittest2==1.1.0 \
--hash=sha256:13f77d0875db6d9b435e1d4f41e74ad4cc2eb6e1d5c824996092b3430f088bb8 \
--hash=sha256:22882a0e418c284e1f718a822b3b022944d53d2d908e1690b319a9d3eb2c0579
zope.component==4.2.2 \
--hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a
--hash=sha256:282c112b55dd8e3c869a3571f86767c150ab1284a9ace2bdec226c592acaf81a \
--no-binary zope.component
zope.event==4.1.0 \
--hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786
--hash=sha256:dc7a59a2fd91730d3793131a5d261b29e93ec4e2a97f1bc487ce8defee2fe786 \
--no-binary zope.event
zope.interface==4.1.3 \
--hash=sha256:f07b631f7a601cd8cbd3332d54f43142c7088a83299f859356f08d1d4d4259b3 \
--hash=sha256:de5cca083b9439d8002fb76bbe6b4998c5a5a721fab25b84298967f002df4c94 \
@@ -1184,6 +1193,9 @@ zope.interface==4.1.3 \
--hash=sha256:928138365245a0e8869a5999fbcc2a45475a0a6ed52a494d60dbdc540335fedd \
--hash=sha256:0d841ba1bb840eea0e6489dc5ecafa6125554971f53b5acb87764441e61bceba \
--hash=sha256:b09c8c1d47b3531c400e0195697f1414a63221de6ef478598a4f1460f7d9a392
requests-toolbelt==0.8.0 \
--hash=sha256:42c9c170abc2cacb78b8ab23ac957945c7716249206f90874651971a4acff237 \
--hash=sha256:f6a531936c6fa4c6cfce1b9c10d5c4f498d16528d2a54a22ca00011205a187b5
# Contains the requirements for the letsencrypt package.
#
@@ -1196,24 +1208,24 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.21.1 \
--hash=sha256:08f026078807fbcfd7bfab44c4d827ee287738fefcc86fbe1493ce752d2fdccb \
--hash=sha256:e6c8e9b0b5e38834330831d5a91e1c08accdb9b4923855d14d524e7327e6c4ea
acme==0.21.1 \
--hash=sha256:4b2b5ef80c755dfa30eb5c67ab4b4e66e7f205ad922b43170502c5f8d8ef1242 \
--hash=sha256:296e8abf4f5a69af1a892416faceea90e15f39e2920bf87beeaad1d6ce70a60b
certbot-apache==0.21.1 \
--hash=sha256:faa4af1033564a0e676d16940775593fb849527b494a15f6a816ad0ed4fa273c \
--hash=sha256:0bce4419d4fdabbdda2223cff8db6794c5717632fb9511b00498ec00982a3fa5
certbot-nginx==0.21.1 \
--hash=sha256:3fad3b4722544558ce03132f853e18da5e516013086aaa40f1036aa6667c70a9 \
--hash=sha256:55a32afe0950ff49d3118f93035463a46c85c2f399d261123f5fe973afdd4f64
certbot==0.25.1 \
--hash=sha256:01689015364685fef3f1e1fb7832ba84eb3b0aa85bc5a71c96661f6d4c59981f \
--hash=sha256:5c23e5186133bb1afd805be5e0cd2fb7b95862a8b0459c9ecad4ae60f933e54e
acme==0.25.1 \
--hash=sha256:26e641a01536705fe5f12d856703b8ef06e5a07981a7b6379d2771dcdb69a742 \
--hash=sha256:47b5f3f73d69b7b1d13f918aa2cd75a8093069a68becf4af38e428e4613b2734
certbot-apache==0.25.1 \
--hash=sha256:a28b7c152cc11474bef5b5e7967aaea42b2c0aaf86fd82ee4082713d33cee5a9 \
--hash=sha256:ed012465617073a0f1057fe854dc8d1eb6d2dd7ede1fb2eee765129fed2a095a
certbot-nginx==0.25.1 \
--hash=sha256:83f82c3ba08c0b1d4bf449ac24018e8e7dd34a6248d35466f2de7da1cd312e15 \
--hash=sha256:68f98b41c54e0bf4218ef293079597176617bee3837ae3aa6528ce2ff0bf4f9c
UNLIKELY_EOF
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/pipstrap.py"
#!/usr/bin/env python
"""A small script that can act as a trust root for installing pip 8
"""A small script that can act as a trust root for installing pip >=8
Embed this in your project, and your VCS checkout is all you have to trust. In
a post-peep era, this lets you claw your way to a hash-checking version of pip,
@@ -1237,6 +1249,7 @@ anything goes wrong, it will exit with a non-zero status code.
from __future__ import print_function
from distutils.version import StrictVersion
from hashlib import sha256
from os import environ
from os.path import join
from pipes import quote
from shutil import rmtree
@@ -1270,14 +1283,14 @@ except ImportError:
from urllib.parse import urlparse # 3.4
__version__ = 1, 3, 0
__version__ = 1, 5, 1
PIP_VERSION = '9.0.1'
DEFAULT_INDEX_BASE = 'https://pypi.python.org'
# wheel has a conditional dependency on argparse:
maybe_argparse = (
[('https://pypi.python.org/packages/18/dd/'
'e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
[('18/dd/e617cfc3f6210ae183374cd9f6a26b20514bbb5a792af97949c5aacddf0f/'
'argparse-1.4.0.tar.gz',
'62b089a55be1d8949cd2bc7e0df0bddb9e028faefc8c32038cc84862aefdd6e4')]
if version_info < (2, 7, 0) else [])
@@ -1285,18 +1298,14 @@ maybe_argparse = (
PACKAGES = maybe_argparse + [
# Pip has no dependencies, as it vendors everything:
('https://pypi.python.org/packages/11/b6/'
'abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'
.format(PIP_VERSION),
('11/b6/abcb525026a4be042b486df43905d6893fb04f05aac21c32c638e939e447/'
'pip-{0}.tar.gz'.format(PIP_VERSION),
'09f243e1a7b461f654c26a725fa373211bb7ff17a9300058b205c61658ca940d'),
# This version of setuptools has only optional dependencies:
('https://pypi.python.org/packages/69/65/'
'4c544cde88d4d876cdf5cbc5f3f15d02646477756d89547e9a7ecd6afa76/'
'setuptools-20.2.2.tar.gz',
'24fcfc15364a9fe09a220f37d2dcedc849795e3de3e4b393ee988e66a9cbd85a'),
('https://pypi.python.org/packages/c9/1d/'
'bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
('59/88/2f3990916931a5de6fa9706d6d75eb32ee8b78627bb2abaab7ed9e6d0622/'
'setuptools-29.0.1.tar.gz',
'b539118819a4857378398891fa5366e090690e46b3e41421a1e07d6e9fd8feb0'),
('c9/1d/bd19e691fd4cfe908c76c429fe6e4436c9e83583c4414b54f6c85471954a/'
'wheel-0.29.0.tar.gz',
'1ebb8ad7e26b448e9caa4773d2357849bf80ff9e313964bcaf79cbf0201a1648')
]
@@ -1317,12 +1326,13 @@ def hashed_download(url, temp, digest):
# >=2.7.9 verifies HTTPS certs itself, and, in any case, the cert
# authenticity has only privacy (not arbitrary code execution)
# implications, since we're checking hashes.
def opener():
def opener(using_https=True):
opener = build_opener(HTTPSHandler())
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
if using_https:
# Strip out HTTPHandler to prevent MITM spoof:
for handler in opener.handlers:
if isinstance(handler, HTTPHandler):
opener.handlers.remove(handler)
return opener
def read_chunks(response, chunk_size):
@@ -1332,8 +1342,9 @@ def hashed_download(url, temp, digest):
break
yield chunk
response = opener().open(url)
path = join(temp, urlparse(url).path.split('/')[-1])
parsed_url = urlparse(url)
response = opener(using_https=parsed_url.scheme == 'https').open(url)
path = join(temp, parsed_url.path.split('/')[-1])
actual_hash = sha256()
with open(path, 'wb') as file:
for chunk in read_chunks(response, 4096):
@@ -1346,6 +1357,24 @@ def hashed_download(url, temp, digest):
return path
def get_index_base():
"""Return the URL to the dir containing the "packages" folder.
Try to wring something out of PIP_INDEX_URL, if set. Hack "/simple" off the
end if it's there; that is likely to give us the right dir.
"""
env_var = environ.get('PIP_INDEX_URL', '').rstrip('/')
if env_var:
SIMPLE = '/simple'
if env_var.endswith(SIMPLE):
return env_var[:-len(SIMPLE)]
else:
return env_var
else:
return DEFAULT_INDEX_BASE
def main():
pip_version = StrictVersion(check_output(['pip', '--version'])
.decode('utf-8').split()[1])
@@ -1353,11 +1382,13 @@ def main():
if pip_version >= min_pip_version:
return 0
has_pip_cache = pip_version >= StrictVersion('6.0')
index_base = get_index_base()
temp = mkdtemp(prefix='pipstrap-')
try:
downloads = [hashed_download(url, temp, digest)
for url, digest in PACKAGES]
downloads = [hashed_download(index_base + '/packages/' + path,
temp,
digest)
for path, digest in PACKAGES]
check_output('pip install --no-index --no-deps -U ' +
# Disable cache since we're not using it and it otherwise
# sometimes throws permission warnings:
@@ -1428,6 +1459,12 @@ UNLIKELY_EOF
say "Installation succeeded."
fi
if [ "$INSTALL_ONLY" = 1 ]; then
say "Certbot is installed."
exit 0
fi
"$VENV_BIN/letsencrypt" "$@"
else

View File

@@ -10,9 +10,12 @@ import sys
import OpenSSL
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
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 certbot import achallenges
from certbot import errors as le_errors
from certbot.tests import acme_util
@@ -50,9 +53,8 @@ def test_authenticator(plugin, config, temp_dir):
try:
responses = plugin.perform(achalls)
except le_errors.Error as error:
logger.error("Performing challenges on %s caused an error:", config)
logger.exception(error)
except le_errors.Error:
logger.error("Performing challenges on %s caused an error:", config, exc_info=True)
return False
success = True
@@ -80,9 +82,8 @@ def test_authenticator(plugin, config, temp_dir):
if success:
try:
plugin.cleanup(achalls)
except le_errors.Error as error:
logger.error("Challenge cleanup for %s caused an error:", config)
logger.exception(error)
except le_errors.Error:
logger.error("Challenge cleanup for %s caused an error:", config, exc_info=True)
success = False
if _dirs_are_unequal(config, backup):
@@ -145,9 +146,8 @@ def test_deploy_cert(plugin, temp_dir, domains):
try:
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
plugin.save() # Needed by the Apache plugin
except le_errors.Error as error:
logger.error("**** Plugin failed to deploy certificate for %s:", domain)
logger.exception(error)
except le_errors.Error:
logger.error("**** Plugin failed to deploy certificate for %s:", domain, exc_info=True)
return False
if not _save_and_restart(plugin, "deployed"):
@@ -177,7 +177,7 @@ def test_enhancements(plugin, domains):
"enhancements")
return False
domains_and_info = [(domain, []) for domain in domains]
domains_and_info = [(domain, []) for domain in domains] # type: List[Tuple[str, List[bool]]]
for domain, info in domains_and_info:
try:
@@ -190,10 +190,9 @@ def test_enhancements(plugin, domains):
# Don't immediately fail because a redirect may already be enabled
logger.warning("*** Plugin failed to enable redirect for %s:", domain)
logger.warning("%s", error)
except le_errors.Error as error:
except le_errors.Error:
logger.error("*** An error occurred while enabling redirect for %s:",
domain)
logger.exception(error)
domain, exc_info=True)
if not _save_and_restart(plugin, "enhanced"):
return False
@@ -220,9 +219,8 @@ def _save_and_restart(plugin, title=None):
plugin.save(title)
plugin.restart()
return True
except le_errors.Error as error:
logger.error("*** Plugin failed to save and restart server:")
logger.exception(error)
except le_errors.Error:
logger.error("*** Plugin failed to save and restart server:", exc_info=True)
return False
@@ -230,9 +228,8 @@ def test_rollback(plugin, config, backup):
"""Tests the rollback checkpoints function"""
try:
plugin.rollback_checkpoints(1337)
except le_errors.Error as error:
logger.error("*** Plugin raised an exception during rollback:")
logger.exception(error)
except le_errors.Error:
logger.error("*** Plugin raised an exception during rollback:", exc_info=True)
return False
if _dirs_are_unequal(config, backup):
@@ -261,21 +258,21 @@ def _dirs_are_unequal(dir1, dir2):
logger.error("The following files and directories are only "
"present in one directory")
if dircmp.left_only:
logger.error(dircmp.left_only)
logger.error(str(dircmp.left_only))
else:
logger.error(dircmp.right_only)
logger.error(str(dircmp.right_only))
return True
elif dircmp.common_funny or dircmp.funny_files:
logger.error("The following files and directories could not be "
"compared:")
if dircmp.common_funny:
logger.error(dircmp.common_funny)
logger.error(str(dircmp.common_funny))
else:
logger.error(dircmp.funny_files)
logger.error(str(dircmp.funny_files))
return True
elif dircmp.diff_files:
logger.error("The following files differ:")
logger.error(dircmp.diff_files)
logger.error(str(dircmp.diff_files))
return True
for subdir in dircmp.subdirs.itervalues():
@@ -352,9 +349,8 @@ def main():
success = test_authenticator(plugin, config, temp_dir)
if success and args.install:
success = test_installer(args, plugin, config, temp_dir)
except errors.Error as error:
logger.error("Tests on %s raised:", config)
logger.exception(error)
except errors.Error:
logger.error("Tests on %s raised:", config, exc_info=True)
success = False
if success:

View File

@@ -26,12 +26,12 @@ def create_le_config(parent_dir):
config = copy.deepcopy(constants.CLI_DEFAULTS)
le_dir = os.path.join(parent_dir, "certbot")
config["config_dir"] = os.path.join(le_dir, "config")
config["work_dir"] = os.path.join(le_dir, "work")
config["logs_dir"] = os.path.join(le_dir, "logs_dir")
os.makedirs(config["config_dir"])
os.mkdir(config["work_dir"])
os.mkdir(config["logs_dir"])
os.mkdir(le_dir)
for dir_name in ("config", "logs", "work"):
full_path = os.path.join(le_dir, dir_name)
os.mkdir(full_path)
full_name = dir_name + "_dir"
config[full_name] = full_path
config["domains"] = None

View File

@@ -5,6 +5,7 @@ import requests
import zope.interface
import six
from six.moves import xrange # pylint: disable=import-error,redefined-builtin
from acme import crypto_util
from acme import errors as acme_errors
@@ -32,7 +33,7 @@ class Validator(object):
try:
presented_cert = crypto_util.probe_sni(name, host, port)
except acme_errors.Error as error:
logger.exception(error)
logger.exception(str(error))
return False
return presented_cert.digest("sha256") == cert.digest("sha256")
@@ -85,8 +86,7 @@ class Validator(object):
return False
try:
_, max_age_value = max_age[0]
max_age_value = int(max_age_value)
max_age_value = int(max_age[0][1])
except ValueError:
logger.error("Server responded with invalid HSTS header field")
return False

View File

@@ -8,7 +8,7 @@ from certbot_nginx import nginxparser
def roundtrip(stuff):
success = True
for t in stuff:
print t
print(t)
if not os.path.isfile(t):
continue
with open(t, "r") as f:

View File

@@ -4,7 +4,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
install_requires = [
'certbot',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-cloudflare
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudflare

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudflare's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_cloudflare
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_cloudflare
:members:
Indices and tables
==================

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-cloudflare[docs]

View File

@@ -1,15 +1,14 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'acme>=0.21.1',
'certbot>=0.21.1',
'cloudflare>=1.5.1',
'mock',
'setuptools',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-cloudxns
RUN pip install --no-cache-dir --editable src/certbot-dns-cloudxns

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-cloudxns's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_cloudxns
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_cloudxns
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-cloudxns[docs]

View File

@@ -1,16 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'dns-lexicon',
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-digitalocean
RUN pip install --no-cache-dir --editable src/certbot-dns-digitalocean

View File

@@ -50,7 +50,8 @@ class AuthenticatorTest(test_util.TempDirTestCase, dns_test_common.BaseAuthentic
class DigitalOceanClientTest(unittest.TestCase):
id = 1
id_num = 1
record_prefix = "_acme-challenge"
record_name = record_prefix + "." + DOMAIN
record_content = "bar"
@@ -70,7 +71,7 @@ class DigitalOceanClientTest(unittest.TestCase):
domain_mock = mock.MagicMock()
domain_mock.name = DOMAIN
domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id}}
domain_mock.create_new_domain_record.return_value = {'domain_record': {'id': self.id_num}}
self.manager.get_all_domains.return_value = [wrong_domain_mock, domain_mock]

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-digitalocean's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_digitalocean
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_digitalocean
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-digitalocean[docs]

View File

@@ -1,15 +1,14 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'acme>=0.21.1',
'certbot>=0.21.1',
'mock',
'python-digitalocean>=1.11',
'setuptools',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-dnsimple
RUN pip install --no-cache-dir --editable src/certbot-dns-dnsimple

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsimple's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_dnsimple
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_dnsimple
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-dnsimple[docs]

View File

@@ -1,16 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'dns-lexicon',
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-dnsmadeeasy
RUN pip install --no-cache-dir --editable src/certbot-dns-dnsmadeeasy

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-dnsmadeeasy's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_dnsmadeeasy
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_dnsmadeeasy
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-dnsmadeeasy[docs]

View File

@@ -1,16 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'dns-lexicon',
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-google
RUN pip install --no-cache-dir --editable src/certbot-dns-google

View File

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

View File

@@ -29,6 +29,8 @@ for an account with the following permissions:
* ``dns.managedZones.list``
* ``dns.resourceRecordSets.create``
* ``dns.resourceRecordSets.delete``
* ``dns.resourceRecordSets.list``
* ``dns.resourceRecordSets.update``
Google provides instructions for `creating a service account <https://developers
.google.com/identity/protocols/OAuth2ServiceAccount#creatinganaccount>`_ and

View File

@@ -81,7 +81,7 @@ class _GoogleClient(object):
Encapsulates all communication with the Google Cloud DNS API.
"""
def __init__(self, account_json=None):
def __init__(self, account_json=None, dns_api=None):
scopes = ['https://www.googleapis.com/auth/ndev.clouddns.readwrite']
if account_json is not None:
@@ -92,7 +92,12 @@ class _GoogleClient(object):
credentials = None
self.project_id = self.get_project_id()
self.dns = discovery.build('dns', 'v1', credentials=credentials, cache_discovery=False)
if not dns_api:
self.dns = discovery.build('dns', 'v1',
credentials=credentials,
cache_discovery=False)
else:
self.dns = dns_api
def add_txt_record(self, domain, record_name, record_content, record_ttl):
"""
@@ -107,6 +112,17 @@ class _GoogleClient(object):
zone_id = self._find_managed_zone_id(domain)
record_contents = self.get_existing_txt_rrset(zone_id, record_name)
if record_contents is None:
record_contents = []
add_records = record_contents[:]
if "\""+record_content+"\"" in record_contents:
# The process was interrupted previously and validation token exists
return
add_records.append(record_content)
data = {
"kind": "dns#change",
"additions": [
@@ -114,12 +130,24 @@ class _GoogleClient(object):
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": record_name + ".",
"rrdatas": [record_content, ],
"rrdatas": add_records,
"ttl": record_ttl,
},
],
}
if record_contents:
# We need to remove old records in the same request
data["deletions"] = [
{
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": record_name + ".",
"rrdatas": record_contents,
"ttl": record_ttl,
},
]
changes = self.dns.changes() # changes | pylint: disable=no-member
try:
@@ -154,6 +182,10 @@ class _GoogleClient(object):
logger.warn('Error finding zone. Skipping cleanup.')
return
record_contents = self.get_existing_txt_rrset(zone_id, record_name)
if record_contents is None:
record_contents = ["\"" + record_content + "\""]
data = {
"kind": "dns#change",
"deletions": [
@@ -161,12 +193,26 @@ class _GoogleClient(object):
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": record_name + ".",
"rrdatas": [record_content, ],
"rrdatas": record_contents,
"ttl": record_ttl,
},
],
}
# Remove the record being deleted from the list
readd_contents = [r for r in record_contents if r != "\"" + record_content + "\""]
if readd_contents:
# We need to remove old records in the same request
data["additions"] = [
{
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": record_name + ".",
"rrdatas": readd_contents,
"ttl": record_ttl,
},
]
changes = self.dns.changes() # changes | pylint: disable=no-member
try:
@@ -175,6 +221,37 @@ class _GoogleClient(object):
except googleapiclient_errors.Error as e:
logger.warn('Encountered error deleting TXT record: %s', e)
def get_existing_txt_rrset(self, zone_id, record_name):
"""
Get existing TXT records from the RRset for the record name.
If an error occurs while requesting the record set, it is suppressed
and None is returned.
:param str zone_id: The ID of the managed zone.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:returns: List of TXT record values or None
:rtype: `list` of `string` or `None`
"""
rrs_request = self.dns.resourceRecordSets() # pylint: disable=no-member
request = rrs_request.list(managedZone=zone_id, project=self.project_id)
# Add dot as the API returns absolute domains
record_name += "."
try:
response = request.execute()
except googleapiclient_errors.Error:
logger.info("Unable to list existing records. If you're "
"requesting a wildcard certificate, this might not work.")
logger.debug("Error was:", exc_info=True)
else:
if response:
for rr in response["rrsets"]:
if rr["name"] == record_name and rr["type"] == "TXT":
return rr["rrdatas"]
return None
def _find_managed_zone_id(self, domain):
"""
Find the managed zone for a given domain.

View File

@@ -4,7 +4,9 @@ import os
import unittest
import mock
from googleapiclient import discovery
from googleapiclient.errors import Error
from googleapiclient.http import HttpMock
from httplib2 import ServerNotFoundError
from certbot import errors
@@ -68,16 +70,27 @@ class GoogleClientTest(unittest.TestCase):
def _setUp_client_with_mock(self, zone_request_side_effect):
from certbot_dns_google.dns_google import _GoogleClient
client = _GoogleClient(ACCOUNT_JSON_PATH)
pwd = os.path.dirname(__file__)
rel_path = 'testdata/discovery.json'
discovery_file = os.path.join(pwd, rel_path)
http_mock = HttpMock(discovery_file, {'status': '200'})
dns_api = discovery.build('dns', 'v1', http=http_mock)
client = _GoogleClient(ACCOUNT_JSON_PATH, dns_api)
# Setup
mock_mz = mock.MagicMock()
mock_mz.list.return_value.execute.side_effect = zone_request_side_effect
mock_rrs = mock.MagicMock()
rrsets = {"rrsets": [{"name": "_acme-challenge.example.org.", "type": "TXT",
"rrdatas": ["\"example-txt-contents\""]}]}
mock_rrs.list.return_value.execute.return_value = rrsets
mock_changes = mock.MagicMock()
client.dns.managedZones = mock.MagicMock(return_value=mock_mz)
client.dns.changes = mock.MagicMock(return_value=mock_changes)
client.dns.resourceRecordSets = mock.MagicMock(return_value=mock_rrs)
return client, mock_changes
@@ -137,6 +150,30 @@ class GoogleClientTest(unittest.TestCase):
managedZone=self.zone,
project=PROJECT_ID)
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
@mock.patch('certbot_dns_google.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"
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)
self.assertTrue(changes.create.called)
self.assertTrue("sample-txt-contents" in
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.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(
[{'managedZones': [{'id': self.zone}]}])
client.add_txt_record(DOMAIN, "_acme-challenge.example.org",
"example-txt-contents", self.record_ttl)
self.assertFalse(changes.create.called)
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
@mock.patch('certbot_dns_google.dns_google.open',
mock.mock_open(read_data='{"project_id": "' + PROJECT_ID + '"}'), create=True)
@@ -172,7 +209,12 @@ class GoogleClientTest(unittest.TestCase):
def test_del_txt_record(self, unused_credential_mock):
client, changes = self._setUp_client_with_mock([{'managedZones': [{'id': self.zone}]}])
client.del_txt_record(DOMAIN, self.record_name, self.record_content, self.record_ttl)
mock_get_rrs = "certbot_dns_google.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\""]
client.del_txt_record(DOMAIN, "_acme-challenge.example.org",
"example-txt-contents", self.record_ttl)
expected_body = {
"kind": "dns#change",
@@ -180,8 +222,17 @@ class GoogleClientTest(unittest.TestCase):
{
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": self.record_name + ".",
"rrdatas": [self.record_content, ],
"name": "_acme-challenge.example.org.",
"rrdatas": ["\"sample-txt-contents\"", "\"example-txt-contents\""],
"ttl": self.record_ttl,
},
],
"additions": [
{
"kind": "dns#resourceRecordSet",
"type": "TXT",
"name": "_acme-challenge.example.org.",
"rrdatas": ["\"sample-txt-contents\"", ],
"ttl": self.record_ttl,
},
],
@@ -217,6 +268,31 @@ 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.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(
[{'managedZones': [{'id': self.zone}]}])
# Record name mocked in setUp
found = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org")
self.assertEquals(found, ["\"example-txt-contents\""])
not_found = client.get_existing_txt_rrset(self.zone, "nonexistent.tld")
self.assertEquals(not_found, None)
@mock.patch('oauth2client.service_account.ServiceAccountCredentials.from_json_keyfile_name')
@mock.patch('certbot_dns_google.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
rrset = client.get_existing_txt_rrset(self.zone, "_acme-challenge.example.org")
self.assertFalse(rrset)
def test_get_project_id(self):
from certbot_dns_google.dns_google import _GoogleClient

File diff suppressed because it is too large Load Diff

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-google's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_google
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_google
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-google[docs]

View File

@@ -1,15 +1,14 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'acme>=0.21.1',
'certbot>=0.21.1',
# 1.5 is the first version that supports oauth2client>=2.0
'google-api-python-client>=1.5',
'mock',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-luadns
RUN pip install --no-cache-dir --editable src/certbot-dns-luadns

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-luadns's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_luadns
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_luadns
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-luadns[docs]

View File

@@ -1,16 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'dns-lexicon',
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-nsone
RUN pip install --no-cache-dir --editable src/certbot-dns-nsone

View File

@@ -10,14 +10,14 @@ Welcome to certbot-dns-nsone's documentation!
:maxdepth: 2
:caption: Contents:
.. automodule:: certbot_dns_nsone
:members:
.. toctree::
:maxdepth: 1
api
.. automodule:: certbot_dns_nsone
:members:
Indices and tables

View File

@@ -0,0 +1,2 @@
acme[dev]==0.21.1
certbot[dev]==0.21.1

View File

@@ -0,0 +1,12 @@
# 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-dns-nsone[docs]

View File

@@ -1,16 +1,15 @@
import sys
from setuptools import setup
from setuptools import find_packages
version = '0.22.0.dev0'
version = '0.26.0.dev0'
# Please update tox.ini when modifying dependency version requirements
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme=={0}'.format(version),
'certbot=={0}'.format(version),
'dns-lexicon',
'acme>=0.21.1',
'certbot>=0.21.1',
'dns-lexicon>=2.2.1', # Support for >1 TXT record per name
'mock',
'setuptools',
'zope.interface',

View File

@@ -0,0 +1,5 @@
FROM certbot/certbot
COPY . src/certbot-dns-rfc2136
RUN pip install --no-cache-dir --editable src/certbot-dns-rfc2136

View File

@@ -21,8 +21,9 @@ Credentials
-----------
Use of this plugin requires a configuration file containing the target DNS
server that supports RFC 2136 Dynamic Updates, the name of the TSIG key, the
TSIG key secret itself and the algorithm used if it's different to HMAC-MD5.
server and optional port that supports RFC 2136 Dynamic Updates, the name
of the TSIG key, the TSIG key secret itself and the algorithm used if it's
different to HMAC-MD5.
.. code-block:: ini
:name: credentials.ini
@@ -30,6 +31,8 @@ TSIG key secret itself and the algorithm used if it's different to HMAC-MD5.
# Target DNS server
dns_rfc2136_server = 192.0.2.1
# Target DNS port
dns_rfc2136_port = 53
# TSIG key name
dns_rfc2136_name = keyname.
# TSIG key secret

View File

@@ -36,6 +36,8 @@ class Authenticator(dns_common.DNSAuthenticator):
'HMAC-SHA512': dns.tsig.HMAC_SHA512
}
PORT = 53
description = 'Obtain certificates using a DNS TXT record (if you are using BIND for DNS).'
ttl = 120
@@ -70,14 +72,15 @@ class Authenticator(dns_common.DNSAuthenticator):
self._validate_algorithm
)
def _perform(self, domain, validation_name, validation):
self._get_rfc2136_client().add_txt_record(domain, validation_name, validation, self.ttl)
def _perform(self, _domain, validation_name, validation):
self._get_rfc2136_client().add_txt_record(validation_name, validation, self.ttl)
def _cleanup(self, domain, validation_name, validation):
self._get_rfc2136_client().del_txt_record(domain, validation_name, validation)
def _cleanup(self, _domain, validation_name, validation):
self._get_rfc2136_client().del_txt_record(validation_name, validation)
def _get_rfc2136_client(self):
return _RFC2136Client(self.credentials.conf('server'),
int(self.credentials.conf('port') or self.PORT),
self.credentials.conf('name'),
self.credentials.conf('secret'),
self.ALGORITHMS.get(self.credentials.conf('algorithm'),
@@ -88,25 +91,25 @@ class _RFC2136Client(object):
"""
Encapsulates all communication with the target DNS server.
"""
def __init__(self, server, key_name, key_secret, key_algorithm):
def __init__(self, server, port, key_name, key_secret, key_algorithm):
self.server = server
self.port = port
self.keyring = dns.tsigkeyring.from_text({
key_name: key_secret
})
self.algorithm = key_algorithm
def add_txt_record(self, domain_name, record_name, record_content, record_ttl):
def add_txt_record(self, record_name, record_content, record_ttl):
"""
Add a TXT record using the supplied information.
:param str domain: The domain to use to find the closest SOA.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
"""
domain = self._find_domain(domain_name)
domain = self._find_domain(record_name)
n = dns.name.from_text(record_name)
o = dns.name.from_text(domain)
@@ -119,7 +122,7 @@ class _RFC2136Client(object):
update.add(rel, record_ttl, dns.rdatatype.TXT, record_content)
try:
response = dns.query.tcp(update, self.server)
response = dns.query.tcp(update, self.server, port=self.port)
except Exception as e:
raise errors.PluginError('Encountered error adding TXT record: {0}'
.format(e))
@@ -131,18 +134,17 @@ class _RFC2136Client(object):
raise errors.PluginError('Received response from server: {0}'
.format(dns.rcode.to_text(rcode)))
def del_txt_record(self, domain_name, record_name, record_content):
def del_txt_record(self, record_name, record_content):
"""
Delete a TXT record using the supplied information.
:param str domain: The domain to use to find the closest SOA.
:param str record_name: The record name (typically beginning with '_acme-challenge.').
:param str record_content: The record content (typically the challenge validation).
:param int record_ttl: The record TTL (number of seconds that the record may be cached).
:raises certbot.errors.PluginError: if an error occurs communicating with the DNS server
"""
domain = self._find_domain(domain_name)
domain = self._find_domain(record_name)
n = dns.name.from_text(record_name)
o = dns.name.from_text(domain)
@@ -155,7 +157,7 @@ class _RFC2136Client(object):
update.delete(rel, dns.rdatatype.TXT, record_content)
try:
response = dns.query.tcp(update, self.server)
response = dns.query.tcp(update, self.server, port=self.port)
except Exception as e:
raise errors.PluginError('Encountered error deleting TXT record: {0}'
.format(e))
@@ -167,17 +169,17 @@ class _RFC2136Client(object):
raise errors.PluginError('Received response from server: {0}'
.format(dns.rcode.to_text(rcode)))
def _find_domain(self, domain_name):
def _find_domain(self, record_name):
"""
Find the closest domain with an SOA record for a given domain name.
:param str domain_name: The domain name for which to find the closest SOA record.
:param str record_name: The record name for which to find the closest SOA record.
:returns: The domain, if found.
:rtype: str
:raises certbot.errors.PluginError: if no SOA record can be found.
"""
domain_name_guesses = dns_common.base_domain_name_guesses(domain_name)
domain_name_guesses = dns_common.base_domain_name_guesses(record_name)
# Loop through until we find an authoritative SOA record
for guess in domain_name_guesses:
@@ -185,7 +187,7 @@ class _RFC2136Client(object):
return guess
raise errors.PluginError('Unable to determine base domain for {0} using names: {1}.'
.format(domain_name, domain_name_guesses))
.format(record_name, domain_name_guesses))
def _query_soa(self, domain_name):
"""
@@ -204,7 +206,7 @@ class _RFC2136Client(object):
request.flags ^= dns.flags.RD
try:
response = dns.query.udp(request, self.server)
response = dns.query.udp(request, self.server, port=self.port)
rcode = response.rcode()
# Authoritative Answer bit should be set

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