Compare commits

...

82 Commits

Author SHA1 Message Date
Adrien Ferrand
9e0f58271e Disable notifications 2019-08-29 14:13:16 +00:00
Adrien Ferrand
b1f3c6f859 Set trusty distribution 2019-08-29 15:04:52 +02:00
Adrien Ferrand
d0c6be244f Simplify Travis config. Fix docker-dev 2019-08-29 15:02:10 +02:00
Adrien Ferrand
3c572b84ab Avoid installing APT packages on OSX ... 2019-08-29 01:05:15 +02:00
Adrien Ferrand
d4f47e920b Fix cleanup 2019-08-28 21:51:22 +02:00
Adrien Ferrand
e1bae626f1 Fixing 2019-08-28 21:28:29 +02:00
Adrien Ferrand
df40057bcc Reconfigure apt install, totally remove sudo that is not used by travis anymore 2019-08-28 19:54:51 +02:00
Adrien Ferrand
95f9ed247a Update packages 2019-08-28 17:47:07 +02:00
Adrien Ferrand
3a411c092f Real path 2019-08-28 17:46:04 +02:00
Adrien Ferrand
3163801123 Merge branch 'master' into apache-integration-tests
# Conflicts:
#	certbot-ci/certbot_integration_tests/utils/certbot_call.py
2019-08-28 17:43:10 +02:00
Adrien Ferrand
a68de55696 Update coverage 2019-08-28 17:27:13 +02:00
Adrien Ferrand
4bc473a2db Finish isolation and toxenv 2019-08-28 17:16:58 +02:00
Adrien Ferrand
23ec9afdb4 Working implementation 2019-08-28 16:05:34 +02:00
Adrien Ferrand
f401750b43 Copy symlinks 2019-08-28 14:13:12 +02:00
Adrien Ferrand
3ef88a68ed Fix various paths 2019-08-28 13:03:54 +02:00
Adrien Ferrand
e6b2689dcb Fix escape characters 2019-08-28 12:56:06 +02:00
Adrien Ferrand
26aae00c36 Setup apache2.conf 2019-08-28 12:53:10 +02:00
Adrien Ferrand
0eeae8196d Continue logic 2019-08-28 12:46:18 +02:00
Adrien Ferrand
11d9107c95 Add logic 2019-08-28 12:23:24 +02:00
Adrien Ferrand
3becf7be16 Create base config 2019-08-28 12:13:25 +02:00
ohemorange
0fe28a6459 Replace platform.linux_distribution with distro.linux_distribution (#7337)
Smallest possible fix for #7106 

* Replace platform.linux_dependencies with distro.linux_dependencies

* run build.py

* Add minimum version of 1.0.1

* Pin back requests package

* Update changelog
2019-08-27 18:31:35 -07:00
Adrien Ferrand
aaeb4582e2 Fix PYTHONPATH in integration tests (#7357)
This PR supersedes #7353.

It fixes the execution of nginx oldest tests when these tests are executed on top of the modifications made in #7337. This execution failure revealed the fact that in some cases, the wrong version of certbot logic was used during integration tests (namely the logic lying in the codebase of the branch built, instead of the logic from the version of certbot declared by certbot-nginx for instance).

I let you appreciate my inline comment for the explanation and the workaround.

Thanks a lot to @bmw who found this python/pytest madness.

You can see the oldest tests succeeding with the logic of #7337 + this PR here: https://travis-ci.com/certbot/certbot/builds/124816254

* Remove certbot root from PYTHONPATH during integration tests

* Add a biiiiig comment.
2019-08-27 16:25:31 -07:00
schoen
fdb0a14812 Merge pull request #7336 from certbot/update-debian-instructions
Update Debian instructions in docs
2019-08-23 13:39:55 -07:00
Adrien Ferrand
0324d1740e Ensure relpath is executed on paths in the same drive (#7335)
On Windows you can have several drives (`C:`, `D:`, ...), that is the roughly (really roughly) equivalent of mount points, since each drive is usually associated to a specific physical partition.

So you can have paths like `C:\one\path`, `D:\another\path`.

In parallel, `os.path.relpath(path, start='.')` calculates the relative path between the given `path` and a `start` path (current directory if not provided). In recent versions of Python, `os.path.relpath` will fail if `path` and `start` are not on the same drive, because a relative path between two paths like `C:\one\path`, `D:\another\path` is not possible.

In saw unit tests failing because of this in two locations. This occurs when the certbot codebase that is tested is on a given drive (like `D:`) while the default temporary directory used by `tempfile` is on another drive (most of the time located in `C:` drive).

This PR fixes that.
2019-08-23 12:53:30 -07:00
Brad Warren
ce325db4e4 address review comments 2019-08-23 12:43:05 -07:00
Brad Warren
74e6736c79 use latest RHEL 7 AMI (#7349) 2019-08-22 09:28:57 -07:00
ohemorange
2ed7608ed3 Merge pull request #7347 from certbot/candidate-0.37.2
Release 0.37.2
2019-08-21 16:21:23 -07:00
ohemorange
eb02acfc4b Merge branch 'master' into candidate-0.37.2 2019-08-21 16:03:51 -07:00
Erica Portnoy
4f19d516d6 Bump version to 0.38.0 2019-08-21 15:23:15 -07:00
Erica Portnoy
3dd918b024 Add contents to CHANGELOG.md for next version 2019-08-21 15:23:15 -07:00
Erica Portnoy
8320018978 Release 0.37.2 2019-08-21 15:23:14 -07:00
Erica Portnoy
c17f2ff6b0 Update changelog for 0.37.2 release 2019-08-21 14:48:40 -07:00
Brad Warren
46a2ef8ba1 Stop turning session tickets off in Nginx (#7344) (#7345)
Related to #7322.

* Stop turning session tickets off in Nginx

* update changelog

(cherry picked from commit 17c1d016c1)
2019-08-21 14:44:09 -07:00
ohemorange
17c1d016c1 Stop turning session tickets off in Nginx (#7344)
Related to #7322.

* Stop turning session tickets off in Nginx

* update changelog
2019-08-21 14:29:10 -07:00
Brad Warren
70ed791709 Update Debian instructions in docs. 2019-08-16 11:42:34 -07:00
Adrien Ferrand
d39f63feca Use travis_retry for farm tests (#7327)
* Use travis_retry in travis builds to retry the farm tests

* travis_retry is a bash function, so it can be called only from current bash

* Update .travis.yml

* Update .travis.yml
2019-08-16 14:55:45 +02:00
Adrien Ferrand
6882f006ac [Windows] Fix closing files descriptors during unit tests (#7326)
* Fix file descriptor cleanup during tests on Windows

* Fix lint

* Remove useless tearDown

* Clean pylint
2019-08-16 11:08:42 +02:00
Adrien Ferrand
9a047a6996 Clean travis config (#7328)
This PR removes some useless capabilities in .travis.yml that are associated to the jobs. This concerns mainly sudo and docker.
2019-08-15 16:41:51 -07:00
Matthias Bilger
a8bd839223 Added DNS plugin for ISPConfig to list (#7332) 2019-08-15 14:43:14 -07:00
tyborr
a1aef4c15c Fix Certbot's Apache plugin doesn't work on Scientific Linux (#7294)
This PR adds OVERRIDE_CLASS in certbot-apache/entrypoint.py for Scientific Linux. Fixes #7248.

* add OVERRIDE_CLASS for Scientific Linux os name

* add entry for Scientific Linux using "scientific" as key

* Update changelog
2019-08-12 12:59:29 -07:00
ohemorange
cb7598b007 Merge pull request #7320 from certbot/merge-cand-0.37.1
Merge cand 0.37.1
2019-08-08 18:01:27 -07:00
Brad Warren
55cf49cebe Merge pull request #7318 from certbot/candidate-0.37.1
Candidate 0.37.1
2019-08-08 17:56:57 -07:00
ohemorange
933f60a3c1 Merge branch 'master' into candidate-0.37.1 2019-08-08 17:48:22 -07:00
Erica Portnoy
44eb048098 Bump version to 0.38.0 2019-08-08 17:01:39 -07:00
Erica Portnoy
794ce57356 Add contents to CHANGELOG.md for next version 2019-08-08 17:01:38 -07:00
Erica Portnoy
48d9715bd5 Release 0.37.1 2019-08-08 17:01:32 -07:00
Erica Portnoy
c5e1be4fd7 Update changelog for 0.37.1 release 2019-08-08 16:39:43 -07:00
Brad Warren
e21401004b Revert disabling TLS session tickets in Apache (#7315) (#7316)
See https://community.letsencrypt.org/t/ssl-error-after-cert-renew/99430.

The first commit of this PR is a simple, clean revert of #7191. Subsequent commits add back pieces of that PR we want to keep.

I also reverted #7299 which landed in a separate PR, but needs to be reverted to keep including the TLS config files in the certbot-apache package when it is built.

I tested this on Ubuntu 18.04 by installing a cert to Apache using Certbot master and then running certbot renew with this branch. I watched the Apache plugin update the configuration file to remove SSLSessionTickets off.

* Revert "Disable TLS session tickets for Apache 2.4.11+ (#7191)"

This reverts commit 9174c631d9.

* Keep hashes with TLS session tickets disabled.

* dont delete changelog entries

* add changelog entry

* Revert "Clean the useless entries in MANIFEST.in (#7299)"

This reverts commit f4d17d9a6b.

(cherry picked from commit 120137eb8d)
2019-08-08 16:36:45 -07:00
Brad Warren
120137eb8d Revert disabling TLS session tickets in Apache (#7315)
See https://community.letsencrypt.org/t/ssl-error-after-cert-renew/99430.

The first commit of this PR is a simple, clean revert of #7191. Subsequent commits add back pieces of that PR we want to keep.

I also reverted #7299 which landed in a separate PR, but needs to be reverted to keep including the TLS config files in the certbot-apache package when it is built.

I tested this on Ubuntu 18.04 by installing a cert to Apache using Certbot master and then running certbot renew with this branch. I watched the Apache plugin update the configuration file to remove SSLSessionTickets off.

* Revert "Disable TLS session tickets for Apache 2.4.11+ (#7191)"

This reverts commit 9174c631d9.

* Keep hashes with TLS session tickets disabled.

* dont delete changelog entries

* add changelog entry

* Revert "Clean the useless entries in MANIFEST.in (#7299)"

This reverts commit f4d17d9a6b.
2019-08-08 16:23:37 -07:00
Matt Nordhoff
2911eda3bd Update link to the Server forum category (#7309)
Let's Encrypt closed it in favor of the Help category.

https://community.letsencrypt.org/t/closing-the-server-category/93016
2019-08-08 11:44:21 -07:00
ohemorange
f1ea37dd71 Merge pull request #7311 from certbot/candidate-0.37.0
Update from 0.37.0 release
2019-08-07 17:44:16 -07:00
Brad Warren
3d3cbc0d16 Don't run tox -e cover. (#7312) 2019-08-08 00:07:37 +02:00
Brad Warren
d978440cb5 Bump version to 0.38.0 2019-08-07 10:35:13 -07:00
Brad Warren
0c04ce3c32 Add contents to CHANGELOG.md for next version 2019-08-07 10:35:13 -07:00
Brad Warren
987ce2c6b2 Release 0.37.0 2019-08-07 10:35:11 -07:00
Brad Warren
dded9290b7 Update changelog for 0.37.0 release 2019-08-07 10:26:34 -07:00
Brad Warren
745ef6e869 Merge pull request #7302 from certbot/rhel8_fix-with-tests
This PR builds off of #7240 to fix #7241.

The code in certbot-auto is unchanged which I +1. Someone else should give it a 2nd review.

For the code in the tests, you can see all tests passing (including test_tests.sh) at  https://travis-ci.com/certbot/certbot/builds/122198270.

I created #7301 to track removing the temporary code in test_leauto_upgrades.sh as suggested at #7282 (comment).

One noteworthy thing here is I did not add the RHEL 8 AMI to the Apache tests due to #7273. This problem is not related to support in certbot-auto though, is an edge case, and I do not personally believe it should block this PR.
2019-08-06 17:02:57 -07:00
Brad Warren
e2844bd0ad Add RHEL8 to test farm targets
* Add RHEL 8 to targets

* Use latest certbot-auto to bootstrap.

* Workaround leauto failures.
2019-08-06 16:39:35 -07:00
Adrien Ferrand
b67fda8832 Fix integration tests on Windows (#7271)
* Fix account_tests

* Fix hook executable test

* Remove the temporary decorator @broken_on_windows

* Fix util_test

* No broken unit test on Windows anymore

* More elegant mock

* Fix context manager

* Fix lint

* Fix mypy

* Adapt coverage

* Corrections

* Fix lint

* Adapt coverage

* Update certbot/tests/compat/filesystem_test.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Update util_test.py

* Fix pylint

* Forbid os.access

* Update os_test.py

* Update os.py

* Fix lint

* Update filesystem.py

* Update filesystem.py

* Update filesystem.py

* Update os.py

* Start fixing tests

* Platform independent hooks

* Fix probe fd close

* Add broken_on_windows for integration tests

* Fix a lot of tests

* Use a python hook script, to prepare cross-platform

* New approach to be compliant with Linux and Windows on hook scripts

* New tests fixed

* Test for permissions on Windows

* Permissions comparison for Windows

* No broken tests in certbot core anymore

* Change mode

* Specific config for appveyor

* Use forked pebble for now

* Various fixes

* Assert file permissions for world on private keys

* Clean code

* Fix several things

* Add integration target

* Optimize integration env

* Re-enable all AppVeyor envs

* Use again official pebble

* Update pebble_artifacts.py

* Set PYTEST_ADDOPTS silently

* Update appveyor.yml

* Pin pywin32 for tests, give a minimal requirement for certbot.

* Remove injection of nginx in PATH

* Clean debug code

* Various cleanup, ensure to remove workspace after tests

* Update tox target

* Improve assertions. Control the keyword echoed in hooks

* Fix for virtualenv on Python 3.7.4 for Windows

* Update certbot-ci/certbot_integration_tests/certbot_tests/assertions.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Add conditionally pywin in certbot-ci like in certbot
2019-08-07 00:02:16 +02:00
Michael Watters
d6e6d64848 Update certbot-auto script to work with RHEL 8
/usr/bin/python no longer exists in RHEL 8.  This patch updates
the certbot-auto script to use python3 on nodes running RHEL 8.

Also fixed a bug in the RPM_DIST_VERSION logic which would cause
letsencrypt-auto to fail on servers running CentOS/RHEL 6.
2019-08-06 09:16:14 -04:00
Adrien Ferrand
f4d17d9a6b Clean the useless entries in MANIFEST.in (#7299)
Since #7191, TLS configuration files for Apache have been moved to a dedicated folder tls_configs. Then the entries in MANIFEST.in removed by this PR do not correspond to an existing path, and so are not useful anymore.
2019-08-05 15:57:20 -07:00
Adrien Ferrand
8bcb04af4a Move Nginx TLS configuration files into a specific folder (#7300)
Following discussions in #7298.

This PR moves the three Nginx TLS configuration files into a specific folder, tls_configs, update the MANIFEST to include this folder and its content into the certbot-nginx package, and update tests accordingly.

* Move tls configuration files in a specific folder

* Move new file
2019-08-05 15:45:08 -07:00
ohemorange
14e10f40e5 Follow Mozilla recs for Nginx ssl_protocols, ssl_ciphers, and ssl_prefer_server_ciphers (#7274)
* Follow Mozilla recs for Nginx ssl_protocols, ssl_ciphers, and ssl_prefer_server_ciphers

* Add tests and fix if statement

* Update CHANGELOG.md

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Test that the hashes of all of the current configuration files are in ALL_SSL_OPTIONS_HASHES

* Remove conditioning on OpenSSL version, since Nginx behaves cleanly if its linked OpenSSL doesn't support TLS1.3
2019-08-02 12:25:40 -07:00
Adrien Ferrand
1c7105a940 Create a mock OCSP server for Pebble integration tests (#7281)
* Implement a logic, miss the private key of pebble

* Complete process

* Fix nginx cert path

* Check conditionnally docker

* Update gitignore, fix apacheconftest

* Full object

* Carriage return

* Work in progress

* Move to official v2.1.0 of pebble

* Fix name

* Update acme_server.py

* Link things together with new version of pebble

* Plug the logic to tests

* Update config

* Reinitiate config

* Add OCSP config to pebble

* Working.

* Simplify logic

* Clean code

* Use forked pebble for now

# Conflicts:
#	certbot-ci/certbot_integration_tests/utils/pebble_artifacts.py

* Move full logic of mock at the acme server config

* Continue work

* Finish fixing the date parsing

* Update module name

* Use again official pebble

* Activate mock OCSP server

* Clean code

* Update pebble_artifacts.py

* Remove OCSP stale test

* Add executable permissions

* Clean code

* Update setup.py

* Simplify code

* On-demand import of pebble_ocsp_server

* Revert "Remove OCSP stale test"

This reverts commit 2e4c985b427120cc15526bbcfd15806d02a6f3fc.

# Conflicts:
#	certbot-ci/certbot_integration_tests/utils/misc.py

* Fix for virtualenv on Python 3.7.4 for Windows

* Update acme_server.py
2019-08-02 11:46:12 -07:00
Adrien Ferrand
36b4c312c6 Upgrade virtualenv in dev/tests environments (#7287)
AppVeyor recently upgrade the Python 3.7.x installed in their VM to 3.7.4. However, virtualenv 16.6.1 is broken on that specific version of Python for Windows.

This PR upgrade virtualenv installed for a dev/test environment from 16.6.1 to 16.6.2 in order to fix this issue, and repair the CI jobs execute by AppVeyor on PRs.
2019-08-02 09:47:36 -07:00
Adrien Ferrand
56f609d4f5 Fix unit tests on Windows (#7270)
Fixes #6850

This PR makes the last corrections needed to run all unit tests on Windows:

add a function to check if a hook is executable in a cross-platform compatible way
handle correctly the PATH surgery for Windows during hook execution
handle correctly an account compatibility over both ACMEv1 and ACMEv2
remove (finally!) the @broken_on_windows decorator.

* Fix account_tests

* Fix hook executable test

* Remove the temporary decorator @broken_on_windows

* Fix util_test

* No broken unit test on Windows anymore

* More elegant mock

* Fix context manager

* Adapt coverage

* Corrections

* Adapt coverage

* Forbid os.access
2019-08-01 10:39:46 -07:00
Mikel Kew
2d3f3a042a Update dns-cloudflare docs regarding API Tokens (#7285)
A quick update to the docs to explicitly mention that the Cloudflare Global API Key must me used instead of an API Token.
2019-07-31 10:31:05 +02:00
Brad Warren
bfd4955bad Bump timeout waiting for ACME server to 4 minutes. (#7284)
* Bump timeout to 4 minutes.

* address review comments
2019-07-30 21:28:18 +02:00
Adrien Ferrand
9174c631d9 Disable TLS session tickets for Apache 2.4.11+ (#7191)
* Implement the logic

* Update tests

* Fix lint and changelog

* Update configurator.py

* Move the TLS configs in a dedicated folder. Fix the formalism of their naming and location.

* Improve existing test to check all TLS config have their hash registered in Certbot

* Corrections after review

* Improve a test

* Remove commented useless lines in TLS configs

* Add a nice warning. Because I am nice.

* Fix lint

* Add a test
2019-07-29 22:54:51 +03:00
Adrien Ferrand
81e0b92b43 Refer to ubuntu in install.rst (#6986)
Fixes #5758
2019-07-29 10:27:09 -07:00
Brad Warren
d3da19919f Remove duplicate, failing oldest tests. (#7272)
Nightly tests failed last night at https://travis-ci.com/certbot/certbot/builds/120816454.

The cause was the oldest the version of Ubuntu used in the tests suddenly changed from Trusty to Xenial. You can see Xenial being used in the failing test at  https://travis-ci.com/certbot/certbot/jobs/219873088#L9 and Trusty being used at the last passing test at https://travis-ci.com/certbot/certbot/jobs/218936290#L9. The change in the default doesn't seem to be documented (yet) at https://docs.travis-ci.com/user/reference/overview/.

I started to pin Trusty in these tests, however, I noticed that we are running these same unit tests at e6bf3fe7f8/.travis.yml (L58). These other tests are still succeeding because it appears that including `sudo: required` causes Travis to still default to Trusty.

Deleting these duplicated tests fixes our Travis failures and speeds things up ever so slightly.

* Remove duplicate, failing oldest tests.

* pin trusty
2019-07-26 13:37:16 -07:00
Adrien Ferrand
e6bf3fe7f8 [Windows] Security model for files permissions - STEP 3f (#7233)
* Correct file permissions on TempHandler

* Forbid os.chown and os.geteuid, as theses functions can be harmful to the security model on Windows.

* Implement copy_ownership

* Apply copy_ownership

* Correct webroot tests (and activate another broken test !)

* Correct lint and mypy

* Ensure to apply mode in makedirs

* Apply strict permissions on directories created with tempfile.mkdtemp(), like on Unix.

* Ensure streamHandler has 0600 on Windows

* Reactivate a test on windows

* Pin oldest requirements to current internal libraries (acme and certbot)

* Add dynamically pywin32 in dependencies: always except for certbot-oldest to avoid to break the relevant tests.

* Administrative privileges are always required.

* Correct security implementation (not the logic yet)

* First correction. Allow to manipulate finely file permissions during their generation

* Align to master + fix lint + resolve correctly symbolic links

* Add a test for windows about default paths

* Strenghthen the detection of Linux/Windows to check the standard files layout.

* Fix lint and mypy

* Reflect non usage of cache discovery from dns google plugin to its tests, solving Windows tests on the way

* Apply suggestions from code review

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Add more details in a comment

* Retrigger build.

* Add documentation.

* Fix a test

* Correct RW clear down

* Update util.py

* Remove unused code

* Fix code style

* Adapt certbot coverage threshold on Linux due to Windows specific LOC addition.

* Various optimizations around file owner and file mode

* Fix last error

* Fix copy_ownership_and_apply_mode

* Fix lint

* Correct mypy

* Extract out first part from windows-file-permissions

* Ignore new_compat in coverage for now

* Create test package for compat

* Add unit tests for security module.

* Add pywin32

* Adapt linux coverages to the windows-specific LOCs added

* Clean imports

* Correct import

* Trigger CI

* Reactivate a test

* Create the certbot.compat package. Move logic in certbot.compat.misc

* Clean comment

* Add doc

* Fix lint

* Correct mypy

* Add executable permissions

* Add the delegate certbot.compat.os module, add check coding style to enforce usage of certbot.compat.os instead of standard os

* Load certbot.compat.os instead of os

* Move existing compat test

* Update local oldest requirements

* Import sys

* Fix some mocks

* Update account_test.py

* Update os.py

* Update os.py

* Update local oldest requirements

* Implement the new linter_plugin

* Fix remaining linting errors

* Fix local oldest for nginx

* Remove custom check in favor of pylint plugin

* Remove check coding style

* Update linter_plugin.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Add several comments

* Update the setup.py

* Add documentation

* Update acme dependencies

* Update certbot/compat/os.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot/compat/os.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update certbot/compat/os.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update linter_plugin.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update linter_plugin.py

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Corrections

* Handle os.path. Simplify checker.

* Add a comment to a reference implementation

* Update changelog

* Fix module registering

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update docs/contributing.rst

Co-Authored-By: adferrand <adferrand@users.noreply.github.com>

* Update config and changelog

* Correction

* Correct os

* Fix merge

* Disable pylint checks

* Normalize imports

* Simplify security

* Corrections

* Reorganize module

* Clean code

* Clean code

* Remove coverage

* No cover

* Implement security.chmod

* Disable a test for now

* Disable hard error for now

* Add a first test. Remove unused import

* Recalibrate coverage

* Modifications for misc

* Correct function call

* Add some types

* Remove newline

* Use os_rename

* Implement security.open

* Revert to windows-files-permissions approach

* Fix lint

* Implement security.mkdir and security.makedirs

* Fix lint

* Clean lint

* Clean lint

* Revert "Clean lint"

This reverts commit 83bf81960ac6bf3f76c286ca065a5ac850c6870b.

* Correct mock

* Conditionally add pywin32 on setuptools versions that support environment markers.

* Fix separator

* Fix separator

* Rename security into filesystem

* Change module security to filesystem

* Move rename into filesystem

* Rename security into filesystem

* Rename security into filesystem

* Rerun CI

* Fix import

* Fix pylint

* Implement copy_ownership_and_apply_mode

* Fix pylint

* Update certbot/compat/os.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Remove default values

* Rewrite a comment.

* Relaunch CI

* Pass as keyword arguments

* Update certbot/compat/filesystem.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Update certbot/compat/filesystem.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Update certbot/compat/filesystem.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Make the private key permissions transfer platform specific

* Update certbot/compat/filesystem.py

Co-Authored-By: Brad Warren <bmw@users.noreply.github.com>

* Rename variable

* Fix comment0

* Add unit test for copy_ownership_and_apply_mode

* Adapt coverage

* Implement new methods.

* Remove the old method

* Reimplement make_or_verify_dir

* Finish migration

* Start to fix tests

* Fix ownership when creating a file with filesystem.open

* Fix security on TempHandler

* Fix validation path permissions

* Fix owner on mkdir

* Use a proper workdir for crypto tests

* Fix pylint

* Adapt coverage

* Update storage_test.py

* Update util_test.py

* Clean code

* Update certbot/compat/filesystem.py

Co-Authored-By: ohemorange <ebportnoy@gmail.com>

* Add comment

* Update certbot/compat/filesystem.py

Co-Authored-By: ohemorange <ebportnoy@gmail.com>

* Check permissions

* Change test mode

* Add unit test for filesystem.check_* functions

* Update filesystem_test.py

* Better logic for TempHandler

* Adapt coverage
2019-07-26 00:25:36 +02:00
alexzorin
40da709792 docs: s/certbot_tests/certbot_test/ (#7267) 2019-07-25 10:23:28 +02:00
Brad Warren
bf9c681c4f fix backwards logic (#7265) 2019-07-25 10:20:52 +02:00
alexzorin
391f301dd8 acme: Implement authz deactivation (#7254)
Resolves #4945. First PR in order to address #5116.

* acme: Implement authz deactivation

Resolves #4945

* update AUTHORS and CHANGELOG

* typos in mypy annotations

* formatting: missing newline

* improve test_deactivate_authorization

* improve deactivate_authorization

* test: s/STATUS_INVALID/STATUS_DEACTIVATED/

* simplify dict to keyword argument

* acme: add UpdateAuthorization

* acme: use UpdateAuthorization in deactivate_authz

and add mypy annotation

This allows deactivate_authorization to succeed for both ACME v1
and v2 servers.
2019-07-24 18:04:59 -07:00
Brad Warren
06a0dae67f Fix test_symlink_resolution on macOS. (#7263)
This fixes the test failures which can be seen at
https://travis-ci.com/certbot/certbot/builds/120123338.

The problem here is the path returned by tempfile.mkdtemp() contains a symlink.
For instance, one run of the function produced
'/var/folders/3b/zg8fdh5j71x92yyzc1tyllfw0000gp/T/tmp3k9ytfj1' which is a
symlink to
'/private/var/folders/3b/zg8fdh5j71x92yyzc1tyllfw0000gp/T/tmp3k9ytfj1'.

Removing this symlink before testing filesystem.realpath solves the problem.

You can see the macOS tests passing with this change at https://travis-ci.com/certbot/certbot/builds/120250667.
2019-07-23 11:01:29 -07:00
Adrien Ferrand
a35470292e Remove Dockerfiles (#7257) 2019-07-22 13:43:58 +03:00
Brad Warren
47f64c7280 Remove list of packaging efforts. (#7258)
I think this list maybe had value when distros were first starting to package Certbot, but now I don't think it does. What function does this list serve? The instruction generator at https://certbot.eff.org/instructions does a much better job telling users how to use these packages. On the packaging side, I think anyone capable of packaging Certbot at the various distros would be able to search their repositories to see if a Certbot package is available.

Since this list is hard to maintain as links semi-regularly break and keeping it up to date with all distros and all Certbot components is a fair bit of work, let's just remove it.

This PR was motivated by the Travis failures at https://travis-ci.com/certbot/website/builds/119588518 due to GNU Guix changing the layout of their site.
2019-07-19 10:44:17 -07:00
Brad Warren
f7c736da6f Update pexpect to fix Python 3.7 dev venvs. (#7259) 2019-07-18 15:44:01 -07:00
Adrien Ferrand
71ff47daad Implement a consistent realpath function in certbot.compat.filesystem (#7242)
Fixes #7115 

This PR creates a `realpath` method in `filesystem`, whose goal is to replace any call to `os.path.realpath` in Certbot. The reason is that `os.path.realpath` is broken on some versions of Python for Windows. See https://bugs.python.org/issue9949. The function created here works consistently across Linux and Windows.

As for the other forbidden functions in `os` module, our `certbot.compat.os` will raise an exception if its `path.realpath` function is invoked, and using the `os` module from Python is forbidden from the pylint check implemented in our CI.

Every call to `os.path.realpath` is corrected in `certbot` and `certbot-apache` modules.

* Forbid os.path.realpath

* Finish implementation

* Use filesystem.realpath

* Control symlink loops also for Linux

* Add a test for forbidden method

* Import a new object from os.path module

* Use same approach of wrapping than certbot.compat.os

* Correct errors

* Fix dependencies

* Make path module internal
2019-07-18 14:31:39 -07:00
J0WI
41a17f913e Use Buster as base image (#7251) 2019-07-17 13:05:02 -07:00
Po-Chuan Hsieh
750d6a9686 Unify license filename (LICENSE.txt) (#7239)
* Unify license filename (LICENSE.txt)
2019-07-12 22:53:43 +03:00
136 changed files with 1859 additions and 1131 deletions

View File

@@ -6,13 +6,13 @@ coverage:
flags: linux
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.6
target: 97.5
threshold: 0.1
base: auto
windows:
flags: windows
# Fixed target instead of auto set by #7173, can
# be removed when flags in Codecov are added back.
target: 97.0
target: 97.6
threshold: 0.1
base: auto

View File

@@ -5,9 +5,21 @@ cache:
- $HOME/.cache/pip
before_script:
# Install required apt packages
- |
if [[ "$TRAVIS_OS_NAME" != "osx" ]]; then
./certbot-auto --non-interactive --os-packages-only
sudo -E apt-get -yq --no-install-suggests --no-install-recommends install nginx-light
sudo -E /etc/init.d/nginx stop
sudo -E apt-get -yq --no-install-suggests --no-install-recommends install apache2
sudo -E /etc/init.d/apache2 stop
sudo -E chmod 777 -R /var/lib/apache2/module
fi
- 'if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then ulimit -n 1024 ; fi'
# On Travis, the fastest parallelization for integration tests has proved to be 4.
- 'if [[ "$TOXENV" == *"integration"* ]]; then export PYTEST_ADDOPTS="--numprocesses 4"; fi'
# Use Travis retry feature for farm tests since they are flaky
- 'if [[ "$TOXENV" == "travis-test-farm"* ]]; then export TRAVIS_RETRY=travis_retry; fi'
- export TOX_TESTENV_PASSENV=TRAVIS
# Only build pushes to the master branch, PRs, and branches beginning with
@@ -37,8 +49,6 @@ matrix:
# Main test suite
- python: "2.7"
env: ACME_SERVER=pebble TOXENV=integration
sudo: required
services: docker
<<: *not-on-master
# This job is always executed, including on master
@@ -55,46 +65,31 @@ matrix:
env: TOXENV=mypy
<<: *not-on-master
- python: "2.7"
# Ubuntu Trusty or older must be used because the oldest version of
# cryptography we support cannot be compiled against the version of
# OpenSSL in Xenial or newer.
dist: trusty
env: TOXENV='py27-{acme,apache,certbot,dns,nginx}-oldest'
sudo: required
services: docker
<<: *not-on-master
- python: "3.4"
env: TOXENV=py34
sudo: required
services: docker
<<: *not-on-master
- python: "3.7"
dist: xenial
env: TOXENV=py37
sudo: required
services: docker
<<: *not-on-master
- sudo: required
env: TOXENV=apache_compat
services: docker
before_install:
addons:
- env: TOXENV=apache_compat
<<: *not-on-master
- sudo: required
env: TOXENV=le_auto_xenial
services: docker
- env: TOXENV=le_auto_xenial
<<: *not-on-master
- python: "2.7"
env: TOXENV=apacheconftest-with-pebble
sudo: required
services: docker
<<: *not-on-master
- python: "2.7"
env: TOXENV=nginxroundtrip
<<: *not-on-master
# Extended test suite on cron jobs and pushes to tested branches other than master
- sudo: required
env: TOXENV=nginx_compat
services: docker
before_install:
addons:
- env: TOXENV=nginx_compat
<<: *extended-test-suite
- python: "2.7"
env:
@@ -119,44 +114,29 @@ matrix:
- secure: "f+j/Lj9s1lcuKo5sEFrlRd1kIAMnIJI4z0MTI7QF8jl9Fkmbx7KECGzw31TNgzrOSzxSapHbcueFYvNCLKST+kE/8ogMZBbwqXfEDuKpyF6BY3uYoJn+wPVE5pIb8Hhe08xPte8TTDSMIyHI3EyTfcAKrIreauoArePvh/cRvSw="
<<: *extended-test-suite
- python: "3.7"
dist: xenial
env: TOXENV=py37 CERTBOT_NO_PIN=1
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "2.7"
env: TOXENV=py27-certbot-oldest
<<: *extended-test-suite
- python: "2.7"
env: TOXENV=py27-nginx-oldest
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration-certbot-oldest
sudo: required
services: docker
dist: trusty # See py27-{acme,apache,certbot,dns,nginx}-oldest tests
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration-certbot-oldest
sudo: required
services: docker
dist: trusty # See py27-{acme,apache,certbot,dns,nginx}-oldest tests
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v1 TOXENV=integration-nginx-oldest
sudo: required
services: docker
dist: trusty # See py27-{acme,apache,certbot,dns,nginx}-oldest tests
<<: *extended-test-suite
- python: "2.7"
env: ACME_SERVER=boulder-v2 TOXENV=integration-nginx-oldest
sudo: required
services: docker
dist: trusty # See py27-{acme,apache,certbot,dns,nginx}-oldest tests
<<: *extended-test-suite
- python: "3.4"
env: TOXENV=py34
@@ -168,66 +148,37 @@ matrix:
env: TOXENV=py36
<<: *extended-test-suite
- python: "3.7"
dist: xenial
env: TOXENV=py37
<<: *extended-test-suite
- python: "3.4"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.4"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.5"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.5"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.6"
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.6"
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.7"
dist: xenial
env: ACME_SERVER=boulder-v1 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- python: "3.7"
dist: xenial
env: ACME_SERVER=boulder-v2 TOXENV=integration
sudo: required
services: docker
<<: *extended-test-suite
- sudo: required
env: TOXENV=le_auto_jessie
services: docker
- env: TOXENV=le_auto_jessie
<<: *extended-test-suite
- sudo: required
env: TOXENV=le_auto_centos6
services: docker
- env: TOXENV=le_auto_centos6
<<: *extended-test-suite
- sudo: required
env: TOXENV=docker_dev
services: docker
addons:
apt:
packages: # don't install nginx and apache
- libaugeas0
- env: TOXENV=docker_dev
<<: *extended-test-suite
- language: generic
env: TOXENV=py27
@@ -254,41 +205,29 @@ matrix:
- python3
<<: *extended-test-suite
# container-based infrastructure
sudo: false
addons:
apt:
packages: # Keep in sync with letsencrypt-auto-source/pieces/bootstrappers/deb_common.sh and Boulder.
- python-dev
- gcc
- libaugeas0
- libssl-dev
- libffi-dev
- ca-certificates
# For certbot-nginx integration testing
- nginx-light
- openssl
# tools/pip_install.py is used to pin packages to a known working version
# except in tests where the environment variable CERTBOT_NO_PIN is set.
# virtualenv is listed here explicitly to make sure it is upgraded when
# CERTBOT_NO_PIN is set to work around failures we've seen when using an older
# version of virtualenv.
install: "tools/pip_install.py -U codecov tox virtualenv"
script: tox
install: 'tools/pip_install.py -U codecov tox virtualenv'
# Most of the time TRAVIS_RETRY is an empty string, and has no effect on the
# script command. It is set only to `travis_retry` during farm tests, in
# order to trigger the Travis retry feature, and compensate the inherent
# flakiness of these specific tests.
script: '$TRAVIS_RETRY tox'
after_success: '[ "$TOXENV" == "py27-cover" ] && codecov -F linux'
notifications:
email: false
irc:
channels:
# This is set to a secure variable to prevent forks from sending
# notifications. This value was created by installing
# https://github.com/travis-ci/travis.rb and running
# `travis encrypt "chat.freenode.net#certbot-devel"`.
- secure: "EWW66E2+KVPZyIPR8ViENZwfcup4Gx3/dlimmAZE0WuLwxDCshBBOd3O8Rf6pBokEoZlXM5eDT6XdyJj8n0DLslgjO62pExdunXpbcMwdY7l1ELxX2/UbnDTE6UnPYa09qVBHNG7156Z6yE0x2lH4M9Ykvp0G0cubjPQHylAwo0="
on_cancel: never
on_success: never
on_failure: always
#notifications:
# email: false
# irc:
# channels:
# # This is set to a secure variable to prevent forks from sending
# # notifications. This value was created by installing
# # https://github.com/travis-ci/travis.rb and running
# # `travis encrypt "chat.freenode.net#certbot-devel"`.
# - secure: "EWW66E2+KVPZyIPR8ViENZwfcup4Gx3/dlimmAZE0WuLwxDCshBBOd3O8Rf6pBokEoZlXM5eDT6XdyJj8n0DLslgjO62pExdunXpbcMwdY7l1ELxX2/UbnDTE6UnPYa09qVBHNG7156Z6yE0x2lH4M9Ykvp0G0cubjPQHylAwo0="
# on_cancel: never
# on_success: never
# on_failure: always

View File

@@ -15,6 +15,7 @@ Authors
* [Alex Gaynor](https://github.com/alex)
* [Alex Halderman](https://github.com/jhalderm)
* [Alex Jordan](https://github.com/strugee)
* [Alex Zorin](https://github.com/alexzorin)
* [Amjad Mashaal](https://github.com/TheNavigat)
* [Andrew Murray](https://github.com/radarhere)
* [Anselm Levskaya](https://github.com/levskaya)
@@ -161,6 +162,7 @@ Authors
* [Michael Schumacher](https://github.com/schumaml)
* [Michael Strache](https://github.com/Jarodiv)
* [Michael Sverdlin](https://github.com/sveder)
* [Michael Watters](https://github.com/blackknight36)
* [Michal Moravec](https://github.com/https://github.com/Majkl578)
* [Michal Papis](https://github.com/mpapis)
* [Minn Soe](https://github.com/MinnSoe)

View File

@@ -2,7 +2,7 @@
Certbot adheres to [Semantic Versioning](https://semver.org/).
## 0.37.0 - master
## 0.38.0 - master
### Added
@@ -10,11 +10,49 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
### Changed
*
* If Certbot fails to rollback your server configuration, the error message
links to the Let's Encrypt forum. Change the link to the Help category now
that the Server category has been closed.
* Replace platform.linux_distribution with distro.linux_distribution as a step
towards Python 3.8 support in Certbot.
### Fixed
*
* Fixed OS detection in the Apache plugin on Scientific Linux.
More details about these changes can be found on our GitHub repo.
## 0.37.2 - 2019-08-21
* Stop disabling TLS session tickets in Nginx as it caused TLS failures on
some systems.
More details about these changes can be found on our GitHub repo.
## 0.37.1 - 2019-08-08
### Fixed
* Stop disabling TLS session tickets in Apache as it caused TLS failures on
some systems.
More details about these changes can be found on our GitHub repo.
## 0.37.0 - 2019-08-07
### Added
* Turn off session tickets for apache plugin by default
* acme: Authz deactivation added to `acme` module.
### Changed
* Follow updated Mozilla recommendations for Nginx ssl_protocols, ssl_ciphers,
and ssl_prefer_server_ciphers
### Fixed
* Fix certbot-auto failures on RHEL 8.
More details about these changes can be found on our GitHub repo.

View File

@@ -1,35 +0,0 @@
FROM python:2-alpine3.9
ENTRYPOINT [ "certbot" ]
EXPOSE 80 443
VOLUME /etc/letsencrypt /var/lib/letsencrypt
WORKDIR /opt/certbot
COPY CHANGELOG.md README.rst setup.py src/
# Generate constraints file to pin dependency versions
COPY letsencrypt-auto-source/pieces/dependency-requirements.txt .
COPY tools /opt/certbot/tools
RUN sh -c 'cat dependency-requirements.txt | /opt/certbot/tools/strip_hashes.py > unhashed_requirements.txt'
RUN sh -c 'cat tools/dev_constraints.txt unhashed_requirements.txt | /opt/certbot/tools/merge_requirements.py > docker_constraints.txt'
COPY acme src/acme
COPY certbot src/certbot
RUN apk add --no-cache --virtual .certbot-deps \
libffi \
libssl1.1 \
openssl \
ca-certificates \
binutils
RUN apk add --no-cache --virtual .build-deps \
gcc \
linux-headers \
openssl-dev \
musl-dev \
libffi-dev \
&& pip install -r /opt/certbot/dependency-requirements.txt \
&& pip install --no-cache-dir --no-deps \
--editable /opt/certbot/src/acme \
--editable /opt/certbot/src \
&& apk del .build-deps

View File

@@ -1,5 +1,5 @@
# This Dockerfile builds an image for development.
FROM ubuntu:xenial
FROM debian:buster
# Note: this only exposes the port to other docker containers.
EXPOSE 80 443

View File

@@ -123,6 +123,21 @@ class ClientBase(object): # pylint: disable=too-many-instance-attributes
"""
return self.update_registration(regr, update={'status': 'deactivated'})
def deactivate_authorization(self, authzr):
# type: (messages.AuthorizationResource) -> messages.AuthorizationResource
"""Deactivate authorization.
:param messages.AuthorizationResource authzr: The Authorization resource
to be deactivated.
:returns: The Authorization resource that was deactivated.
:rtype: `.AuthorizationResource`
"""
body = messages.UpdateAuthorization(status='deactivated')
response = self._post(authzr.uri, body)
return self._authzr_from_response(response)
def _authzr_from_response(self, response, identifier=None, uri=None):
authzr = messages.AuthorizationResource(
body=messages.Authorization.from_json(response.json()),

View File

@@ -637,6 +637,14 @@ class ClientTest(ClientTestBase):
errors.PollError, self.client.poll_and_request_issuance,
csr, authzrs, mintime=mintime, max_attempts=2)
def test_deactivate_authorization(self):
authzb = self.authzr.body.update(status=messages.STATUS_DEACTIVATED)
self.response.json.return_value = authzb.to_json()
authzr = self.client.deactivate_authorization(self.authzr)
self.assertEqual(authzb, authzr.body)
self.assertEqual(self.client.net.post.call_count, 1)
self.assertTrue(self.authzr.uri in self.net.post.call_args_list[0][0])
def test_check_cert(self):
self.response.headers['Location'] = self.certr.uri
self.response.content = CERT_DER

View File

@@ -168,6 +168,7 @@ STATUS_VALID = Status('valid')
STATUS_INVALID = Status('invalid')
STATUS_REVOKED = Status('revoked')
STATUS_READY = Status('ready')
STATUS_DEACTIVATED = Status('deactivated')
class IdentifierType(_Constant):
@@ -471,7 +472,7 @@ class Authorization(ResourceBody):
:ivar datetime.datetime expires:
"""
identifier = jose.Field('identifier', decoder=Identifier.from_json)
identifier = jose.Field('identifier', decoder=Identifier.from_json, omitempty=True)
challenges = jose.Field('challenges', omitempty=True)
combinations = jose.Field('combinations', omitempty=True)
@@ -501,6 +502,12 @@ class NewAuthorization(Authorization):
resource = fields.Resource(resource_type)
class UpdateAuthorization(Authorization):
"""Update authorization."""
resource_type = 'authz'
resource = fields.Resource(resource_type)
class AuthorizationResource(ResourceWithURI):
"""Authorization Resource.

View File

@@ -3,7 +3,7 @@ from setuptools import find_packages
from setuptools.command.test import test as TestCommand
import sys
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View File

@@ -4,6 +4,7 @@ environment:
matrix:
- TOXENV: py35
- TOXENV: py37-cover
- TOXENV: integration-certbot
branches:
only:
@@ -24,14 +25,16 @@ init:
install:
# Use Python 3.7 by default
- "SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%"
- SET PATH=C:\\Python37;C:\\Python37\\Scripts;%PATH%
# Using 4 processes is proven to be the most efficient integration tests config for AppVeyor
- IF %TOXENV%==integration-certbot SET PYTEST_ADDOPTS=--numprocesses=4
# Check env
- "python --version"
- python --version
# Upgrade pip to avoid warnings
- "python -m pip install --upgrade pip"
- python -m pip install --upgrade pip
# Ready to install tox and coverage
# tools/pip_install.py is used to pin packages to a known working version.
- "python tools\\pip_install.py tox codecov"
- python tools\\pip_install.py tox codecov
build: off

View File

@@ -23,6 +23,7 @@ from certbot import interfaces
from certbot import util
from certbot.achallenges import KeyAuthorizationAnnotatedChallenge # pylint: disable=unused-import
from certbot.compat import filesystem
from certbot.compat import os
from certbot.plugins import common
from certbot.plugins.util import path_surgery
@@ -895,7 +896,7 @@ class ApacheConfigurator(common.Installer):
if not new_vhost:
continue
internal_path = apache_util.get_internal_aug_path(new_vhost.path)
realpath = os.path.realpath(new_vhost.filep)
realpath = filesystem.realpath(new_vhost.filep)
if realpath not in file_paths:
file_paths[realpath] = new_vhost.filep
internal_paths[realpath].add(internal_path)
@@ -1221,11 +1222,11 @@ class ApacheConfigurator(common.Installer):
"""
if self.conf("vhost-root") and os.path.exists(self.conf("vhost-root")):
fp = os.path.join(os.path.realpath(self.option("vhost_root")),
fp = os.path.join(filesystem.realpath(self.option("vhost_root")),
os.path.basename(non_ssl_vh_fp))
else:
# Use non-ssl filepath
fp = os.path.realpath(non_ssl_vh_fp)
fp = filesystem.realpath(non_ssl_vh_fp)
if fp.endswith(".conf"):
return fp[:-(len(".conf"))] + self.option("le_vhost_ext")

View File

@@ -9,6 +9,7 @@ MOD_SSL_CONF_DEST = "options-ssl-apache.conf"
UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-apache-conf-digest.txt"
"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`."""
# NEVER REMOVE A SINGLE HASH FROM THIS LIST UNLESS YOU KNOW EXACTLY WHAT YOU ARE DOING!
ALL_SSL_OPTIONS_HASHES = [
'2086bca02db48daf93468332543c60ac6acdb6f0b58c7bfdf578a5d47092f82a',
'4844d36c9a0f587172d9fa10f4f1c9518e3bcfa1947379f155e16a70a728c21a',
@@ -18,6 +19,10 @@ ALL_SSL_OPTIONS_HASHES = [
'cfdd7c18d2025836ea3307399f509cfb1ebf2612c87dd600a65da2a8e2f2797b',
'80720bd171ccdc2e6b917ded340defae66919e4624962396b992b7218a561791',
'c0c022ea6b8a51ecc8f1003d0a04af6c3f2bc1c3ce506b3c2dfc1f11ef931082',
'717b0a89f5e4c39b09a42813ac6e747cfbdeb93439499e73f4f70a1fe1473f20',
'0fcdc81280cd179a07ec4d29d3595068b9326b455c488de4b09f585d5dafc137',
'86cc09ad5415cd6d5f09a947fe2501a9344328b1e8a8b458107ea903e80baa6c',
'06675349e457eae856120cdebb564efe546f0b87399f2264baeb41e442c724c7',
]
"""SHA256 hashes of the contents of previous versions of all versions of MOD_SSL_CONF_SRC"""

View File

@@ -31,6 +31,8 @@ OVERRIDE_CLASSES = {
"gentoo base system": override_gentoo.GentooConfigurator,
"opensuse": override_suse.OpenSUSEConfigurator,
"suse": override_suse.OpenSUSEConfigurator,
"scientific": override_centos.CentOSConfigurator,
"scientific linux": override_centos.CentOSConfigurator,
}

View File

@@ -7,6 +7,7 @@ import zope.interface
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import filesystem
from certbot.compat import os
from certbot_apache import apache_util
@@ -65,7 +66,7 @@ class DebianConfigurator(configurator.ApacheConfigurator):
try:
os.symlink(vhost.filep, enabled_path)
except OSError as err:
if os.path.islink(enabled_path) and os.path.realpath(
if os.path.islink(enabled_path) and filesystem.realpath(
enabled_path) == vhost.filep:
# Already in shape
vhost.enabled = True

View File

@@ -4,6 +4,7 @@ import unittest
import mock
from certbot import errors
from certbot.compat import filesystem
from certbot.compat import os
from certbot_apache import obj
@@ -160,7 +161,7 @@ class MultipleVhostsTestCentOS(util.ApacheTest):
"""Make sure we read the sysconfig OPTIONS variable correctly"""
# Return nothing for the process calls
mock_cfg.return_value = ""
self.config.parser.sysconfig_filep = os.path.realpath(
self.config.parser.sysconfig_filep = filesystem.realpath(
os.path.join(self.config.parser.root, "../sysconfig/httpd"))
self.config.parser.variables = {}

View File

@@ -675,8 +675,7 @@ class MultipleVhostsTest(util.ApacheTest):
def test_make_vhost_ssl_nonexistent_vhost_path(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[1])
self.assertEqual(os.path.dirname(ssl_vhost.filep),
os.path.dirname(os.path.realpath(
self.vh_truth[1].filep)))
os.path.dirname(filesystem.realpath(self.vh_truth[1].filep)))
def test_make_vhost_ssl(self):
ssl_vhost = self.config.make_vhost_ssl(self.vh_truth[0])
@@ -1336,7 +1335,7 @@ class MultipleVhostsTest(util.ApacheTest):
self.config.parser.modules.add("ssl_module")
self.config.parser.modules.add("mod_ssl.c")
self.config.parser.modules.add("socache_shmcb_module")
tmp_path = os.path.realpath(tempfile.mkdtemp("vhostroot"))
tmp_path = filesystem.realpath(tempfile.mkdtemp("vhostroot"))
filesystem.chmod(tmp_path, 0o755)
mock_p = "certbot_apache.configurator.ApacheConfigurator._get_ssl_vhost_path"
mock_a = "certbot_apache.parser.ApacheParser.add_include"

View File

@@ -79,9 +79,9 @@ class MultipleVhostsTestDebian(util.ApacheTest):
def test_enable_site_failure(self):
self.config.parser.root = "/tmp/nonexistent"
with mock.patch("os.path.isdir") as mock_dir:
with mock.patch("certbot.compat.os.path.isdir") as mock_dir:
mock_dir.return_value = True
with mock.patch("os.path.islink") as mock_link:
with mock.patch("certbot.compat.os.path.islink") as mock_link:
mock_link.return_value = False
self.assertRaises(
errors.NotSupportedError,

View File

@@ -4,6 +4,7 @@ import unittest
import mock
from certbot import errors
from certbot.compat import filesystem
from certbot.compat import os
from certbot_apache import obj
@@ -160,7 +161,7 @@ class MultipleVhostsTestFedora(util.ApacheTest):
"""Make sure we read the sysconfig OPTIONS variable correctly"""
# Return nothing for the process calls
mock_cfg.return_value = ""
self.config.parser.sysconfig_filep = os.path.realpath(
self.config.parser.sysconfig_filep = filesystem.realpath(
os.path.join(self.config.parser.root, "../sysconfig/httpd"))
self.config.parser.variables = {}

View File

@@ -4,6 +4,7 @@ import unittest
import mock
from certbot import errors
from certbot.compat import filesystem
from certbot.compat import os
from certbot_apache import obj
@@ -81,7 +82,7 @@ class MultipleVhostsTestGentoo(util.ApacheTest):
"""Make sure we read the Gentoo APACHE2_OPTS variable correctly"""
defines = ['DEFAULT_VHOST', 'INFO',
'SSL', 'SSL_DEFAULT_VHOST', 'LANGUAGE']
self.config.parser.apacheconfig_filep = os.path.realpath(
self.config.parser.apacheconfig_filep = filesystem.realpath(
os.path.join(self.config.parser.root, "../conf.d/apache2"))
self.config.parser.variables = {}
with mock.patch("certbot_apache.override_gentoo.GentooParser.update_modules"):

View File

@@ -1,3 +1,3 @@
# Remember to update setup.py to match the package versions below.
acme[dev]==0.29.0
certbot[dev]==0.36.0
certbot[dev]==0.37.0

View File

@@ -4,13 +4,13 @@ from setuptools.command.test import test as TestCommand
import sys
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.
install_requires = [
'acme>=0.29.0',
'certbot>=0.36.0',
'certbot>=0.37.0',
'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.36.0"
LE_AUTO_VERSION="0.37.2"
BASENAME=$(basename $0)
USAGE="Usage: $BASENAME [OPTIONS]
A self-updating wrapper script for the Certbot ACME client. When run, updates
@@ -755,13 +755,31 @@ elif [ -f /etc/redhat-release ]; then
prev_le_python="$LE_PYTHON"
unset LE_PYTHON
DeterminePythonVersion "NOCRASH"
# Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then.
RPM_DIST_NAME=`(. /etc/os-release 2> /dev/null && echo $ID) || echo "unknown"`
RPM_DIST_VERSION=0
if [ "$RPM_DIST_NAME" = "fedora" ]; then
RPM_DIST_VERSION=`(. /etc/os-release 2> /dev/null && echo $VERSION_ID) || echo "0"`
# Set RPM_DIST_VERSION to VERSION_ID from /etc/os-release after splitting on
# '.' characters (e.g. "8.0" becomes "8"). If the command exits with an
# error, RPM_DIST_VERSION is set to "unknown".
RPM_DIST_VERSION=$( (. /etc/os-release 2> /dev/null && echo "$VERSION_ID") | cut -d '.' -f1 || echo "unknown")
# If RPM_DIST_VERSION is an empty string or it contains any nonnumeric
# characters, the value is unexpected so we set RPM_DIST_VERSION to 0.
if [ -z "$RPM_DIST_VERSION" ] || [ -n "$(echo "$RPM_DIST_VERSION" | tr -d '[0-9]')" ]; then
RPM_DIST_VERSION=0
fi
# Starting to Fedora 29, python2 is on a deprecation path. Let's move to python3 then.
# RHEL 8 also uses python3 by default.
if [ "$RPM_DIST_NAME" = "fedora" -a "$RPM_DIST_VERSION" -ge 29 -o "$PYVER" -eq 26 ]; then
RPM_USE_PYTHON_3=1
elif [ "$RPM_DIST_NAME" = "rhel" -a "$RPM_DIST_VERSION" -ge 8 ]; then
RPM_USE_PYTHON_3=1
else
RPM_USE_PYTHON_3=0
fi
if [ "$RPM_USE_PYTHON_3" = 1 ]; then
Bootstrap() {
BootstrapMessage "RedHat-based OSes that will use Python3"
BootstrapRpmPython3
@@ -775,6 +793,7 @@ elif [ -f /etc/redhat-release ]; then
}
BOOTSTRAP_VERSION="BootstrapRpmCommon $BOOTSTRAP_RPM_COMMON_VERSION"
fi
LE_PYTHON="$prev_le_python"
elif [ -f /etc/os-release ] && `grep -q openSUSE /etc/os-release` ; then
Bootstrap() {
@@ -1314,18 +1333,18 @@ letsencrypt==0.7.0 \
--hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \
--hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9
certbot==0.36.0 \
--hash=sha256:486cee6c4861762fe4a94b4f44f7d227034d026d1a8d7ba2911ef4e86a737613 \
--hash=sha256:bf6745b823644cdca8461150455aeb67d417f87f80b9ec774c716e9587ef20a2
acme==0.36.0 \
--hash=sha256:5570c8e87383fbc733224fd0f7d164313b67dd9c21deafe9ddc8e769441f0c86 \
--hash=sha256:0461ee3c882d865e98e624561843dc135fa1a1412b15603d7ebfbb392de6a668
certbot-apache==0.36.0 \
--hash=sha256:2537f7fb67a38b6d1ed5ee79f6a799090ca609695ac3799bb840b2fb677ac98d \
--hash=sha256:458d20a3e9e8a88563d3deb0bbe38752bd2b80100f0e5854e4069390c1b4e5cd
certbot-nginx==0.36.0 \
--hash=sha256:4303b54adf2030671c54bb3964c1f43aec0f677045e0cdb6d4fb931268d08310 \
--hash=sha256:4c34e6114dd8204b6667f101579dd9ab2b38fef0dd5a15702585edcb2aefb322
certbot==0.37.2 \
--hash=sha256:8f6f0097fb2aac64f13e5d6974781ac85a051d84a6cb3f4d79c6b75c5ea451b8 \
--hash=sha256:e454368aa8d62559c673091b511319c130c8e0ea1c4dfa314ed7bdc91dd96ef5
acme==0.37.2 \
--hash=sha256:5666ba927a9e7bf3f9ed5a268bd5acf627b5838fb409e8401f05d2aaaee188ba \
--hash=sha256:88798fae3bc692397db79c66930bd02fcaba8a6b1fba9a62f111dda42cc47f5c
certbot-apache==0.37.2 \
--hash=sha256:e3ae7057f727506ab3796095ed66ca083f4e295d06f209ab96d2a3f37dea51b9 \
--hash=sha256:4cb44d1a7c56176a84446a11412c561479ed0fed19848632e61f104dbf6a3031
certbot-nginx==0.37.2 \
--hash=sha256:a92dffdf3daca97db5d7ae2287e505110c3fa01c035b9356abb2ef9fa32e8695 \
--hash=sha256:404f7b5b7611f0dce8773739170f306e94a59b69528cb74337e7f354936ac061
UNLIKELY_EOF
# -------------------------------------------------------------------------

View File

@@ -0,0 +1,171 @@
import shutil
import subprocess
import os
import pkg_resources
import getpass
def construct_apache_config_dir(apache_root, http_port, https_port, key_path=None,
cert_path=None, wtf_prefix='le'):
config_path = os.path.join(apache_root, 'config')
shutil.copytree('/etc/apache2', config_path, symlinks=True)
webroot_path = os.path.join(apache_root, 'www')
os.mkdir(webroot_path)
with open(os.path.join(webroot_path, 'index.html'), 'w') as file_h:
file_h.write('Hello World!')
main_config_path = os.path.join(config_path, 'apache2.conf')
with open(main_config_path, 'w') as file_h:
file_h.write('''\
ServerRoot "{config}"
DefaultRuntimeDir ${{APACHE_RUN_DIR}}
PidFile ${{APACHE_PID_FILE}}
Timeout 300
KeepAlive On
MaxKeepAliveRequests 100
KeepAliveTimeout 5
User ${{APACHE_RUN_USER}}
Group ${{APACHE_RUN_GROUP}}
HostnameLookups Off
ErrorLog ${{APACHE_LOG_DIR}}/error.log
LogLevel warn
IncludeOptional mods-enabled/*.load
IncludeOptional mods-enabled/*.conf
Include ports.conf
<Directory />
Options FollowSymLinks
AllowOverride None
Require all denied
</Directory>
<Directory /usr/share>
AllowOverride None
Require all granted
</Directory>
<Directory {webroot}/>
Options Indexes FollowSymLinks
AllowOverride None
Require all granted
</Directory>
AccessFileName .htaccess
<FilesMatch "^\.ht">
Require all denied
</FilesMatch>
LogFormat "%v:%p %h %l %u %t \\"%r\\" %>s %O \\"%{{Referer}}i\\" \\"%{{User-Agent}}i\\"" vhost_combined
LogFormat "%h %l %u %t \\"%r\\" %>s %O \\"%{{Referer}}i\\" \\"%{{User-Agent}}i\\"" combined
LogFormat "%h %l %u %t \\"%r\\" %>s %O" common
LogFormat "%{{Referer}}i -> %U" referer
LogFormat "%{{User-agent}}i" agent
IncludeOptional conf-enabled/*.conf
IncludeOptional sites-enabled/*.conf
'''.format(config=config_path, webroot=webroot_path))
with open(os.path.join(config_path, 'ports.conf'), 'w') as file_h:
file_h.write('''\
Listen {http}
<IfModule ssl_module>
Listen {https}
</IfModule>
<IfModule mod_gnutls.c>
Listen {https}
</IfModule>
'''.format(http=http_port, https=https_port))
new_environ = os.environ.copy()
new_environ['APACHE_CONFDIR'] = config_path
run_path = os.path.join(apache_root, 'run')
lock_path = os.path.join(apache_root, 'lock')
logs_path = os.path.join(apache_root, 'logs')
os.mkdir(run_path)
os.mkdir(lock_path)
os.mkdir(logs_path)
user = getpass.getuser()
user = user if user != 'root' else 'www-data'
group = user
pid_file = os.path.join(run_path, 'apache.pid')
with open(os.path.join(config_path, 'envvars'), 'w') as file_h:
file_h.write('''\
unset HOME
export APACHE_RUN_USER={user}
export APACHE_RUN_GROUP={group}
export APACHE_PID_FILE={pid_file}
export APACHE_RUN_DIR={run_path}
export APACHE_LOCK_DIR={lock_path}
export APACHE_LOG_DIR={logs_path}
export LANG=C
'''.format(user=user, group=group, pid_file=pid_file,
run_path=run_path, lock_path=lock_path, logs_path=logs_path))
new_environ['APACHE_RUN_USER'] = user
new_environ['APACHE_RUN_GROUP'] = group
new_environ['APACHE_PID_FILE'] = pid_file
new_environ['APACHE_RUN_DIR'] = run_path
new_environ['APACHE_LOCK_DIR'] = lock_path
new_environ['APACHE_LOG_DIR'] = logs_path
le_host = 'apache.{0}.wtf'.format(wtf_prefix)
with open(os.path.join(config_path, 'sites-available', '000-default.conf'), 'w') as file_h:
file_h.write('''\
<VirtualHost *:{http}>
ServerAdmin webmaster@localhost
ServerName {le_host}
DocumentRoot {webroot}
ErrorLog ${{APACHE_LOG_DIR}}/error.log
CustomLog ${{APACHE_LOG_DIR}}/access.log combined
</VirtualHost>
'''.format(http=http_port, le_host=le_host, webroot=webroot_path))
key_path = key_path if key_path \
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/key.pem')
cert_path = cert_path if cert_path \
else pkg_resources.resource_filename('certbot_integration_tests', 'assets/cert.pem')
with open(os.path.join(config_path, 'sites-available', 'default-ssl.conf'), 'w') as file_h:
file_h.write('''\
<IfModule mod_ssl.c>
<VirtualHost _default_:{https}>
ServerAdmin webmaster@localhost
ServerName {le_host}
DocumentRoot {webroot}
ErrorLog ${{APACHE_LOG_DIR}}/error.log
CustomLog ${{APACHE_LOG_DIR}}/access.log combined
SSLEngine on
SSLCertificateFile {cert_path}
SSLCertificateKeyFile {key_path}
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory /usr/lib/cgi-bin>
SSLOptions +StdEnvVars
</Directory>
</VirtualHost>
</IfModule>
'''.format(https=https_port, le_host=le_host, webroot=webroot_path,
cert_path=cert_path, key_path=key_path))
return new_environ, config_path, pid_file
def test():
env = construct_apache_config_dir('/tmp/test1', 5001, 5002)
subprocess.call(['apache2ctl', '-DFOREGROUND'], env=env)

View File

@@ -0,0 +1,51 @@
import errno
import os
import signal
import subprocess
from certbot_integration_tests.certbot_tests import context as certbot_context
from certbot_integration_tests.apache_tests import apache_config
from certbot_integration_tests.utils import certbot_call
class IntegrationTestsContext(certbot_context.IntegrationTestsContext):
def __init__(self, request):
super(IntegrationTestsContext, self).__init__(request)
subprocess.check_output(['chmod', '+x', self.workspace])
self.apache_root = os.path.join(self.workspace, 'apache')
os.mkdir(self.apache_root)
self.env, self.config_dir, self.pid_file = apache_config.construct_apache_config_dir(
self.apache_root, self.http_01_port, self.tls_alpn_01_port,
wtf_prefix=self.worker_id)
def cleanup(self):
self._stop_apache()
super(IntegrationTestsContext, self).cleanup()
def certbot_test_apache(self, args):
command = ['--authenticator', 'apache', '--installer', 'apache',
'--apache-server-root', self.config_dir,
'--apache-challenge-location', self.apache_root]
command.extend(args)
return certbot_call.certbot_test(
command, self.directory_url, self.http_01_port, self.tls_alpn_01_port,
self.config_dir, self.workspace, env=self.env, force_renew=True)
def _stop_apache(self):
try:
with open(self.pid_file) as file_h:
pid = int(file_h.read().strip())
except BaseException:
pid = None
if pid:
try:
os.kill(pid, signal.SIGTERM)
except OSError as err:
# Ignore "No such process" error, Apache may already be stopped.
if err.errno != errno.ESRCH:
raise

View File

@@ -0,0 +1,18 @@
import pytest
from certbot_integration_tests.apache_tests import context as apache_context
@pytest.fixture()
def context(request):
# Fixture request is a built-in pytest fixture describing current test request.
integration_test_context = apache_context.IntegrationTestsContext(request)
try:
yield integration_test_context
finally:
integration_test_context.cleanup()
def test_it(context):
command = ['-d', 'apache.{0}.wtf'.format(context.worker_id)]
context.certbot_test_apache(command)

View File

@@ -0,0 +1,11 @@
#!/usr/bin/env python
import sys
import os
hook_script_type = os.path.basename(os.path.dirname(sys.argv[1]))
if hook_script_type == 'deploy' and ('RENEWED_DOMAINS' not in os.environ or 'RENEWED_LINEAGE' not in os.environ):
sys.stderr.write('Environment variables not properly set!\n')
sys.exit(1)
with open(sys.argv[2], 'a') as file_h:
file_h.write(hook_script_type + '\n')

View File

@@ -1,6 +1,17 @@
"""This module contains advanced assertions for the certbot integration tests."""
import os
import grp
try:
import grp
POSIX_MODE = True
except ImportError:
import win32api
import win32security
import ntsecuritycon
POSIX_MODE = False
EVERYBODY_SID = 'S-1-1-0'
SYSTEM_SID = 'S-1-5-18'
ADMINS_SID = 'S-1-5-32-544'
def assert_hook_execution(probe_path, probe_content):
@@ -10,9 +21,10 @@ def assert_hook_execution(probe_path, probe_content):
:param probe_content: content expected when the hook is executed
"""
with open(probe_path, 'r') as file:
lines = file.readlines()
data = file.read()
assert '{0}{1}'.format(probe_content, os.linesep) in lines
lines = [line.strip() for line in data.splitlines()]
assert probe_content in lines
def assert_saved_renew_hook(config_dir, lineage):
@@ -38,16 +50,51 @@ def assert_cert_count_for_lineage(config_dir, lineage, count):
assert len(certs) == count
def assert_equals_permissions(file1, file2, mask):
def assert_equals_group_permissions(file1, file2):
"""
Assert that permissions on two files are identical in respect to a given umask.
Assert that two files have the same permissions for group owner.
:param file1: first file path to compare
:param file2: second file path to compare
:param mask: 3-octal representation of a POSIX umask under which the two files mode
should match (eg. 0o074 will test RWX on group and R on world)
"""
mode_file1 = os.stat(file1).st_mode & mask
mode_file2 = os.stat(file2).st_mode & mask
# On Windows there is no group, so this assertion does nothing on this platform
if POSIX_MODE:
mode_file1 = os.stat(file1).st_mode & 0o070
mode_file2 = os.stat(file2).st_mode & 0o070
assert mode_file1 == mode_file2
def assert_equals_world_read_permissions(file1, file2):
"""
Assert that two files have the same read permissions for everyone.
:param file1: first file path to compare
:param file2: second file path to compare
"""
if POSIX_MODE:
mode_file1 = os.stat(file1).st_mode & 0o004
mode_file2 = os.stat(file2).st_mode & 0o004
else:
everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)
security1 = win32security.GetFileSecurity(file1, win32security.DACL_SECURITY_INFORMATION)
dacl1 = security1.GetSecurityDescriptorDacl()
mode_file1 = dacl1.GetEffectiveRightsFromAcl({
'TrusteeForm': win32security.TRUSTEE_IS_SID,
'TrusteeType': win32security.TRUSTEE_IS_USER,
'Identifier': everybody,
})
mode_file1 = mode_file1 & ntsecuritycon.FILE_GENERIC_READ
security2 = win32security.GetFileSecurity(file2, win32security.DACL_SECURITY_INFORMATION)
dacl2 = security2.GetSecurityDescriptorDacl()
mode_file2 = dacl2.GetEffectiveRightsFromAcl({
'TrusteeForm': win32security.TRUSTEE_IS_SID,
'TrusteeType': win32security.TRUSTEE_IS_USER,
'Identifier': everybody,
})
mode_file2 = mode_file2 & ntsecuritycon.FILE_GENERIC_READ
assert mode_file1 == mode_file2
@@ -57,20 +104,57 @@ def assert_equals_group_owner(file1, file2):
Assert that two files have the same group owner.
:param file1: first file path to compare
:param file2: second file path to compare
:return:
"""
group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0]
group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0]
# On Windows there is no group, so this assertion does nothing on this platform
if POSIX_MODE:
group_owner_file1 = grp.getgrgid(os.stat(file1).st_gid)[0]
group_owner_file2 = grp.getgrgid(os.stat(file2).st_gid)[0]
assert group_owner_file1 == group_owner_file2
assert group_owner_file1 == group_owner_file2
def assert_world_permissions(file, mode):
def assert_world_no_permissions(file):
"""
Assert that a file has the expected world permission.
:param file: file path to check
:param mode: world permissions mode expected
Assert that the given file is not world-readable.
:param file: path of the file to check
"""
mode_file_all = os.stat(file).st_mode & 0o007
if POSIX_MODE:
mode_file_all = os.stat(file).st_mode & 0o007
assert mode_file_all == 0
else:
security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION)
dacl = security.GetSecurityDescriptorDacl()
mode = dacl.GetEffectiveRightsFromAcl({
'TrusteeForm': win32security.TRUSTEE_IS_SID,
'TrusteeType': win32security.TRUSTEE_IS_USER,
'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID),
})
assert mode_file_all == mode
assert not mode
def assert_world_read_permissions(file):
"""
Assert that the given file is world-readable, but not world-writable or world-executable.
:param file: path of the file to check
"""
if POSIX_MODE:
mode_file_all = os.stat(file).st_mode & 0o007
assert mode_file_all == 4
else:
security = win32security.GetFileSecurity(file, win32security.DACL_SECURITY_INFORMATION)
dacl = security.GetSecurityDescriptorDacl()
mode = dacl.GetEffectiveRightsFromAcl({
'TrusteeForm': win32security.TRUSTEE_IS_SID,
'TrusteeType': win32security.TRUSTEE_IS_USER,
'Identifier': win32security.ConvertStringSidToSid(EVERYBODY_SID),
})
assert not mode & ntsecuritycon.FILE_GENERIC_WRITE
assert not mode & ntsecuritycon.FILE_GENERIC_EXECUTE
assert mode & ntsecuritycon.FILE_GENERIC_READ == ntsecuritycon.FILE_GENERIC_READ
def _get_current_user():
account_name = win32api.GetUserNameEx(win32api.NameSamCompatible)
return win32security.LookupAccountName(None, account_name)[0]

View File

@@ -1,10 +1,11 @@
"""Module to handle the context of integration tests."""
import logging
import os
import shutil
import sys
import tempfile
from certbot_integration_tests.utils import misc, certbot_call
from certbot_integration_tests.utils import certbot_call
class IntegrationTestsContext(object):
@@ -19,7 +20,7 @@ class IntegrationTestsContext(object):
self.worker_id = 'primary'
acme_xdist = request.config.acme_xdist
self.acme_server =acme_xdist['acme_server']
self.acme_server = acme_xdist['acme_server']
self.directory_url = acme_xdist['directory_url']
self.tls_alpn_01_port = acme_xdist['https_port'][self.worker_id]
self.http_01_port = acme_xdist['http_port'][self.worker_id]
@@ -30,7 +31,10 @@ class IntegrationTestsContext(object):
self.workspace = tempfile.mkdtemp()
self.config_dir = os.path.join(self.workspace, 'conf')
self.hook_probe = tempfile.mkstemp(dir=self.workspace)[1]
probe = tempfile.mkstemp(dir=self.workspace)
os.close(probe[0])
self.hook_probe = probe[1]
self.manual_dns_auth_hook = (
'{0} -c "import os; import requests; import json; '

View File

@@ -11,8 +11,11 @@ from os.path import join, exists
import pytest
from certbot_integration_tests.certbot_tests import context as certbot_context
from certbot_integration_tests.certbot_tests.assertions import (
assert_hook_execution, assert_saved_renew_hook, assert_cert_count_for_lineage,
assert_world_permissions, assert_equals_group_owner, assert_equals_permissions,
assert_hook_execution, assert_saved_renew_hook,
assert_cert_count_for_lineage,
assert_world_no_permissions, assert_world_read_permissions,
assert_equals_group_owner, assert_equals_group_permissions, assert_equals_world_read_permissions,
EVERYBODY_SID
)
from certbot_integration_tests.utils import misc
@@ -84,9 +87,9 @@ def test_http_01(context):
context.certbot([
'--domains', certname, '--preferred-challenges', 'http-01', 'run',
'--cert-name', certname,
'--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe),
'--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe),
'--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)
'--pre-hook', misc.echo('wtf_pre', context.hook_probe),
'--post-hook', misc.echo('wtf_post', context.hook_probe),
'--deploy-hook', misc.echo('deploy', context.hook_probe),
])
assert_hook_execution(context.hook_probe, 'deploy')
@@ -104,9 +107,9 @@ def test_manual_http_auth(context):
'--cert-name', certname,
'--manual-auth-hook', scripts[0],
'--manual-cleanup-hook', scripts[1],
'--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe),
'--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe),
'--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe)
'--pre-hook', misc.echo('wtf_pre', context.hook_probe),
'--post-hook', misc.echo('wtf_post', context.hook_probe),
'--renew-hook', misc.echo('renew', context.hook_probe),
])
with pytest.raises(AssertionError):
@@ -122,9 +125,9 @@ def test_manual_dns_auth(context):
'run', '--cert-name', certname,
'--manual-auth-hook', context.manual_dns_auth_hook,
'--manual-cleanup-hook', context.manual_dns_cleanup_hook,
'--pre-hook', 'echo wtf.pre >> "{0}"'.format(context.hook_probe),
'--post-hook', 'echo wtf.post >> "{0}"'.format(context.hook_probe),
'--renew-hook', 'echo renew >> "{0}"'.format(context.hook_probe)
'--pre-hook', misc.echo('wtf_pre', context.hook_probe),
'--post-hook', misc.echo('wtf_post', context.hook_probe),
'--renew-hook', misc.echo('renew', context.hook_probe),
])
with pytest.raises(AssertionError):
@@ -173,21 +176,19 @@ def test_renew_files_permissions(context):
certname = context.get_domain('renew')
context.certbot(['-d', certname])
privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem')
privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
assert_cert_count_for_lineage(context.config_dir, certname, 1)
assert_world_permissions(
join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0)
assert_world_no_permissions(privkey1)
context.certbot(['renew'])
assert_cert_count_for_lineage(context.config_dir, certname, 2)
assert_world_permissions(
join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0)
assert_equals_group_owner(
join(context.config_dir, 'archive', certname, 'privkey1.pem'),
join(context.config_dir, 'archive', certname, 'privkey2.pem'))
assert_equals_permissions(
join(context.config_dir, 'archive', certname, 'privkey1.pem'),
join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074)
assert_world_no_permissions(privkey2)
assert_equals_group_owner(privkey1, privkey2)
assert_equals_world_read_permissions(privkey1, privkey2)
assert_equals_group_permissions(privkey1, privkey2)
def test_renew_with_hook_scripts(context):
@@ -211,15 +212,35 @@ def test_renew_files_propagate_permissions(context):
assert_cert_count_for_lineage(context.config_dir, certname, 1)
os.chmod(join(context.config_dir, 'archive', certname, 'privkey1.pem'), 0o444)
privkey1 = join(context.config_dir, 'archive', certname, 'privkey1.pem')
privkey2 = join(context.config_dir, 'archive', certname, 'privkey2.pem')
if os.name != 'nt':
os.chmod(privkey1, 0o444)
else:
import win32security
import ntsecuritycon
# Get the current DACL of the private key
security = win32security.GetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION)
dacl = security.GetSecurityDescriptorDacl()
# Create a read permission for Everybody group
everybody = win32security.ConvertStringSidToSid(EVERYBODY_SID)
dacl.AddAccessAllowedAce(win32security.ACL_REVISION, ntsecuritycon.FILE_GENERIC_READ, everybody)
# Apply the updated DACL to the private key
security.SetSecurityDescriptorDacl(1, dacl, 0)
win32security.SetFileSecurity(privkey1, win32security.DACL_SECURITY_INFORMATION, security)
context.certbot(['renew'])
assert_cert_count_for_lineage(context.config_dir, certname, 2)
assert_world_permissions(
join(context.config_dir, 'archive', certname, 'privkey2.pem'), 4)
assert_equals_permissions(
join(context.config_dir, 'archive', certname, 'privkey1.pem'),
join(context.config_dir, 'archive', certname, 'privkey2.pem'), 0o074)
if os.name != 'nt':
# On Linux, read world permissions + all group permissions will be copied from the previous private key
assert_world_read_permissions(privkey2)
assert_equals_world_read_permissions(privkey1, privkey2)
assert_equals_group_permissions(privkey1, privkey2)
else:
# On Windows, world will never have any permissions, and group permission is irrelevant for this platform
assert_world_no_permissions(privkey2)
def test_graceful_renew_it_is_not_time(context):
@@ -229,7 +250,7 @@ def test_graceful_renew_it_is_not_time(context):
assert_cert_count_for_lineage(context.config_dir, certname, 1)
context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)],
context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)],
force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 1)
@@ -250,7 +271,7 @@ def test_graceful_renew_it_is_time(context):
with open(join(context.config_dir, 'renewal', '{0}.conf'.format(certname)), 'w') as file:
file.writelines(lines)
context.certbot(['renew', '--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)],
context.certbot(['renew', '--deploy-hook', misc.echo('deploy', context.hook_probe)],
force_renew=False)
assert_cert_count_for_lineage(context.config_dir, certname, 2)
@@ -317,9 +338,9 @@ def test_renew_hook_override(context):
context.certbot([
'certonly', '-d', certname,
'--preferred-challenges', 'http-01',
'--pre-hook', 'echo pre >> "{0}"'.format(context.hook_probe),
'--post-hook', 'echo post >> "{0}"'.format(context.hook_probe),
'--deploy-hook', 'echo deploy >> "{0}"'.format(context.hook_probe)
'--pre-hook', misc.echo('pre', context.hook_probe),
'--post-hook', misc.echo('post', context.hook_probe),
'--deploy-hook', misc.echo('deploy', context.hook_probe),
])
assert_hook_execution(context.hook_probe, 'pre')
@@ -330,14 +351,14 @@ def test_renew_hook_override(context):
open(context.hook_probe, 'w').close()
context.certbot([
'renew', '--cert-name', certname,
'--pre-hook', 'echo pre-override >> "{0}"'.format(context.hook_probe),
'--post-hook', 'echo post-override >> "{0}"'.format(context.hook_probe),
'--deploy-hook', 'echo deploy-override >> "{0}"'.format(context.hook_probe)
'--pre-hook', misc.echo('pre_override', context.hook_probe),
'--post-hook', misc.echo('post_override', context.hook_probe),
'--deploy-hook', misc.echo('deploy_override', context.hook_probe),
])
assert_hook_execution(context.hook_probe, 'pre-override')
assert_hook_execution(context.hook_probe, 'post-override')
assert_hook_execution(context.hook_probe, 'deploy-override')
assert_hook_execution(context.hook_probe, 'pre_override')
assert_hook_execution(context.hook_probe, 'post_override')
assert_hook_execution(context.hook_probe, 'deploy_override')
with pytest.raises(AssertionError):
assert_hook_execution(context.hook_probe, 'pre')
with pytest.raises(AssertionError):
@@ -349,11 +370,11 @@ def test_renew_hook_override(context):
open(context.hook_probe, 'w').close()
context.certbot(['renew', '--cert-name', certname])
assert_hook_execution(context.hook_probe, 'pre-override')
assert_hook_execution(context.hook_probe, 'post-override')
assert_hook_execution(context.hook_probe, 'deploy-override')
assert_hook_execution(context.hook_probe, 'pre_override')
assert_hook_execution(context.hook_probe, 'post_override')
assert_hook_execution(context.hook_probe, 'deploy_override')
def test_invalid_domain_with_dns_challenge(context):
"""Test certificate issuance failure with DNS-01 challenge."""
# Manual dns auth hooks from misc are designed to fail if the domain contains 'fail-*'.
@@ -512,7 +533,7 @@ def test_revoke_multiple_lineages(context):
data = file.read()
data = re.sub('archive_dir = .*\n',
'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1)),
'archive_dir = {0}\n'.format(join(context.config_dir, 'archive', cert1).replace('\\', '\\\\')),
data)
with open(join(context.config_dir, 'renewal', '{0}.conf'.format(cert2)), 'w') as file:
@@ -555,11 +576,9 @@ def test_ocsp_status_stale(context):
def test_ocsp_status_live(context):
"""Test retrieval of OCSP statuses for live config"""
if context.acme_server == 'pebble':
pytest.skip('Pebble does not support OCSP status requests.')
cert = context.get_domain('ocsp-check')
# OSCP 1: Check live certificate OCSP status (VALID)
cert = context.get_domain('ocsp-check')
context.certbot(['--domains', cert])
output = context.certbot(['certificates'])

View File

@@ -134,6 +134,13 @@ class ACMEServer(object):
[challtestsrv_path, '-management', ':{0}'.format(CHALLTESTSRV_PORT), '-defaultIPv6', '""',
'-defaultIPv4', '127.0.0.1', '-http01', '""', '-tlsalpn01', '""', '-https01', '""'])
# pebble_ocsp_server is imported here and not at the top of module in order to avoid a useless
# ImportError, in the case where cryptography dependency is too old to support ocsp, but
# Boulder is used instead of Pebble, so pebble_ocsp_server is not used. This is the typical
# situation of integration-certbot-oldest tox testenv.
from certbot_integration_tests.utils import pebble_ocsp_server
self._launch_process([sys.executable, pebble_ocsp_server.__file__])
# Wait for the ACME CA server to be up.
print('=> Waiting for pebble instance to respond...')
misc.check_until_timeout(self.acme_xdist['directory_url'])
@@ -159,7 +166,7 @@ class ACMEServer(object):
# Wait for the ACME CA server to be up.
print('=> Waiting for boulder instance to respond...')
misc.check_until_timeout(self.acme_xdist['directory_url'])
misc.check_until_timeout(self.acme_xdist['directory_url'], attempts=240)
# Configure challtestsrv to answer any A record request with ip of the docker host.
response = requests.post('http://localhost:{0}/set-default-ipv4'.format(CHALLTESTSRV_PORT),

View File

@@ -6,12 +6,12 @@ import subprocess
import sys
import os
from certbot_integration_tests.utils import misc
import certbot_integration_tests
from certbot_integration_tests.utils.constants import *
def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
config_dir, workspace, force_renew=True):
config_dir, workspace, env=None, force_renew=True):
"""
Invoke the certbot executable available in PATH in a test context for the given args.
The test context consists in running certbot in debug mode, with various flags suitable
@@ -23,28 +23,70 @@ def certbot_test(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
:param int tls_alpn_01_port: port for the TLS-ALPN-01 challenges
:param str config_dir: certbot configuration directory to use
:param str workspace: certbot current directory to use
:param obj env: the environment variables to use (default: None, then current env will be used)
:param bool force_renew: set False to not force renew existing certificates (default: True)
:return: stdout as string
:rtype: str
"""
new_environ = env if env else os.environ.copy()
command, env = _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
config_dir, workspace, force_renew)
config_dir, workspace, force_renew, new_environ)
return subprocess.check_output(command, universal_newlines=True, cwd=workspace, env=env)
def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
config_dir, workspace, force_renew):
new_environ = os.environ.copy()
def _prepare_environ(workspace, new_environ):
new_environ = new_environ.copy()
new_environ['TMPDIR'] = workspace
# So, pytest is nice, and a little too nice for our usage.
# In order to help user to call seamlessly any piece of python code without requiring to
# install it as a full-fledged setuptools distribution for instance, it may inject the path
# to the test files into the PYTHONPATH. This allows the python interpreter to import
# as modules any python file available at this path.
# See https://docs.pytest.org/en/3.2.5/pythonpath.html for the explanation and description.
# However this behavior is not good in integration tests, in particular the nginx oldest ones.
# Indeed during these kind of tests certbot is installed as a transitive dependency to
# certbot-nginx. Here is the trick: this certbot version is not necessarily the same as
# the certbot codebase lying in current working directory. For instance in oldest tests
# certbot==0.36.0 may be installed while the codebase corresponds to certbot==0.37.0.dev0.
# Then during a pytest run, PYTHONPATH contains the path to the Certbot codebase, so invoking
# certbot will import the modules from the codebase (0.37.0.dev0), not from the
# required/installed version (0.36.0).
# This will lead to funny and totally incomprehensible errors. To avoid that, we ensure that
# if PYTHONPATH is set, it does not contain the path to the root of the codebase.
if new_environ.get('PYTHONPATH'):
# certbot_integration_tests.__file__ is:
# '/path/to/certbot/certbot-ci/certbot_integration_tests/__init__.pyc'
# ... and we want '/path/to/certbot'
certbot_root = os.path.dirname(os.path.dirname(os.path.dirname(certbot_integration_tests.__file__)))
python_paths = [path for path in new_environ['PYTHONPATH'].split(':') if path != certbot_root]
new_environ['PYTHONPATH'] = ':'.join(python_paths)
return new_environ
def _compute_additional_args(workspace, environ, force_renew):
additional_args = []
if misc.get_certbot_version() >= LooseVersion('0.30.0'):
output = subprocess.check_output(['certbot', '--version'],
universal_newlines=True, stderr=subprocess.STDOUT,
cwd=workspace, env=environ)
version_str = output.split(' ')[1].strip() # Typical response is: output = 'certbot 0.31.0.dev0'
if LooseVersion(version_str) >= LooseVersion('0.30.0'):
additional_args.append('--no-random-sleep-on-renew')
if force_renew:
additional_args.append('--renew-by-default')
return additional_args
def _prepare_args_env(certbot_args, directory_url, http_01_port, tls_alpn_01_port,
config_dir, workspace, force_renew, new_environ):
new_environ = _prepare_environ(workspace, new_environ)
additional_args = _compute_additional_args(workspace, new_environ, force_renew)
command = [
'certbot',
'--server', directory_url,

View File

@@ -5,3 +5,5 @@ CHALLTESTSRV_PORT = 8055
BOULDER_V1_DIRECTORY_URL = 'http://localhost:4000/directory'
BOULDER_V2_DIRECTORY_URL = 'http://localhost:4001/directory'
PEBBLE_DIRECTORY_URL = 'https://localhost:14000/dir'
PEBBLE_MANAGEMENT_URL = 'https://localhost:15000'
MOCK_OCSP_SERVER_PORT = 4002

View File

@@ -3,9 +3,11 @@ Misc module contains stateless functions that could be used during pytest execut
or outside during setup/teardown of the integration tests environment.
"""
import contextlib
import logging
import errno
import multiprocessing
import os
import re
import shutil
import stat
import subprocess
@@ -23,17 +25,17 @@ from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
from six.moves import socketserver, SimpleHTTPServer
RSA_KEY_TYPE = 'rsa'
ECDSA_KEY_TYPE = 'ecdsa'
def check_until_timeout(url):
def check_until_timeout(url, attempts=30):
"""
Wait and block until given url responds with status 200, or raise an exception
after 150 attempts.
after the specified number of attempts.
:param str url: the URL to test
:raise ValueError: exception raised after 150 unsuccessful attempts to reach the URL
:param int attempts: the number of times to try to connect to the URL
:raise ValueError: exception raised if unable to reach the URL
"""
try:
import urllib3
@@ -43,7 +45,7 @@ def check_until_timeout(url):
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
for _ in range(0, 150):
for _ in range(attempts):
time.sleep(1)
try:
if requests.get(url, verify=False).status_code == 200:
@@ -51,7 +53,7 @@ def check_until_timeout(url):
except requests.exceptions.ConnectionError:
pass
raise ValueError('Error, url did not respond after 150 attempts: {0}'.format(url))
raise ValueError('Error, url did not respond after {0} attempts: {1}'.format(attempts, url))
class GracefulTCPServer(socketserver.TCPServer):
@@ -62,6 +64,10 @@ class GracefulTCPServer(socketserver.TCPServer):
allow_reuse_address = True
def _run_server(port):
GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever()
@contextlib.contextmanager
def create_http_server(port):
"""
@@ -74,10 +80,7 @@ def create_http_server(port):
current_cwd = os.getcwd()
webroot = tempfile.mkdtemp()
def run():
GracefulTCPServer(('', port), SimpleHTTPServer.SimpleHTTPRequestHandler).serve_forever()
process = multiprocessing.Process(target=run)
process = multiprocessing.Process(target=_run_server, args=(port,))
try:
# SimpleHTTPServer is designed to serve files from the current working directory at the
@@ -119,15 +122,9 @@ def generate_test_file_hooks(config_dir, hook_probe):
:param str config_dir: current certbot config directory
:param hook_probe: path to the hook probe to test hook scripts execution
"""
if sys.platform == 'win32':
extension = 'bat'
else:
extension = 'sh'
hook_path = pkg_resources.resource_filename('certbot_integration_tests', 'assets/hook.py')
renewal_hooks_dirs = list_renewal_hooks_dirs(config_dir)
renewal_deploy_hook_path = os.path.join(renewal_hooks_dirs[1], 'hook.sh')
for hook_dir in renewal_hooks_dirs:
for hook_dir in list_renewal_hooks_dirs(config_dir):
# We want an equivalent of bash `chmod -p $HOOK_DIR, that does not fail if one folder of
# the hierarchy already exists. It is not the case of os.makedirs. Python 3 has an
# optional parameter `exists_ok` to not fail on existing dir, but Python 2.7 does not.
@@ -137,26 +134,25 @@ def generate_test_file_hooks(config_dir, hook_probe):
except OSError as error:
if error.errno != errno.EEXIST:
raise
hook_path = os.path.join(hook_dir, 'hook.{0}'.format(extension))
if extension == 'sh':
data = '''\
#!/bin/bash -xe
if [ "$0" = "{0}" ]; then
if [ -z "$RENEWED_DOMAINS" -o -z "$RENEWED_LINEAGE" ]; then
echo "Environment variables not properly set!" >&2
exit 1
fi
fi
echo $(basename $(dirname "$0")) >> "{1}"\
'''.format(renewal_deploy_hook_path, hook_probe)
else:
# TODO: Write the equivalent bat file for Windows
data = '''\
'''
with open(hook_path, 'w') as file:
file.write(data)
os.chmod(hook_path, os.stat(hook_path).st_mode | stat.S_IEXEC)
if os.name != 'nt':
entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.sh')
entrypoint_script = '''\
#!/usr/bin/env bash
set -e
"{0}" "{1}" "{2}" "{3}"
'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe)
else:
entrypoint_script_path = os.path.join(hook_dir, 'entrypoint.bat')
entrypoint_script = '''\
@echo off
"{0}" "{1}" "{2}" "{3}"
'''.format(sys.executable, hook_path, entrypoint_script_path, hook_probe)
with open(entrypoint_script_path, 'w') as file_h:
file_h.write(entrypoint_script)
os.chmod(entrypoint_script_path, os.stat(entrypoint_script_path).st_mode | stat.S_IEXEC)
@contextlib.contextmanager
@@ -193,7 +189,7 @@ for _ in range(0, 10):
except requests.exceptions.ConnectionError:
pass
raise ValueError('Error, url did not respond after 10 attempts: {{0}}'.format(url))
'''.format(http_server_root, http_port))
'''.format(http_server_root.replace('\\', '\\\\'), http_port))
os.chmod(auth_script_path, 0o755)
cleanup_script_path = os.path.join(tempdir, 'cleanup.py')
@@ -204,7 +200,7 @@ import os
import shutil
well_known = os.path.join('{0}', '.well-known')
shutil.rmtree(well_known)
'''.format(http_server_root))
'''.format(http_server_root.replace('\\', '\\\\')))
os.chmod(cleanup_script_path, 0o755)
yield ('{0} {1}'.format(sys.executable, auth_script_path),
@@ -213,18 +209,6 @@ shutil.rmtree(well_known)
shutil.rmtree(tempdir)
def get_certbot_version():
"""
Find the version of the certbot available in PATH.
:return str: the certbot version
"""
output = subprocess.check_output(['certbot', '--version'],
universal_newlines=True, stderr=subprocess.STDOUT)
# Typical response is: output = 'certbot 0.31.0.dev0'
version_str = output.split(' ')[1].strip()
return LooseVersion(version_str)
def generate_csr(domains, key_path, csr_path, key_type=RSA_KEY_TYPE):
"""
Generate a private key, and a CSR for the given domains using this key.
@@ -287,4 +271,32 @@ def load_sample_data_path(workspace):
original = pkg_resources.resource_filename('certbot_integration_tests', 'assets/sample-config')
copied = os.path.join(workspace, 'sample-config')
shutil.copytree(original, copied, symlinks=True)
if os.name == 'nt':
# Fix the symlinks on Windows since GIT is not creating them upon checkout
for lineage in ['a.encryption-example.com', 'b.encryption-example.com']:
current_live = os.path.join(copied, 'live', lineage)
for name in os.listdir(current_live):
if name != 'README':
current_file = os.path.join(current_live, name)
with open(current_file) as file_h:
src = file_h.read()
os.unlink(current_file)
os.symlink(os.path.join(current_live, src), current_file)
return copied
def echo(keyword, path=None):
"""
Generate a platform independent executable command
that echoes the given keyword into the given file.
:param keyword: the keyword to echo (must be a single keyword)
:param path: path to the file were keyword is echoed
:return: the executable command
"""
if not re.match(r'^\w+$', keyword):
raise ValueError('Error, keyword `{0}` is not a single keyword.'
.format(keyword))
return '{0} -c "from __future__ import print_function; print(\'{1}\')"{2}'.format(
os.path.basename(sys.executable), keyword, ' >> "{0}"'.format(path) if path else '')

View File

@@ -1,19 +1,18 @@
import json
import platform
import os
import stat
import pkg_resources
import requests
PEBBLE_VERSION = 'v2.1.0'
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT
PEBBLE_VERSION = 'v2.2.1'
ASSETS_PATH = pkg_resources.resource_filename('certbot_integration_tests', 'assets')
def fetch(workspace):
suffix = '{0}-{1}{2}'.format(platform.system().lower(),
platform.machine().lower().replace('x86_64', 'amd64'),
'.exe' if platform.system() == 'Windows' else '')
suffix = 'linux-amd64' if os.name != 'nt' else 'windows-amd64.exe'
pebble_path = _fetch_asset('pebble', suffix)
challtestsrv_path = _fetch_asset('pebble-challtestsrv', suffix)
@@ -42,10 +41,12 @@ def _build_pebble_config(workspace):
file_h.write(json.dumps({
'pebble': {
'listenAddress': '0.0.0.0:14000',
'managementListenAddress': '0.0.0.0:15000',
'certificate': os.path.join(ASSETS_PATH, 'cert.pem'),
'privateKey': os.path.join(ASSETS_PATH, 'key.pem'),
'httpPort': 5002,
'tlsPort': 5001,
'ocspResponderURL': 'http://127.0.0.1:{0}'.format(MOCK_OCSP_SERVER_PORT),
},
}))

View File

@@ -0,0 +1,71 @@
#!/usr/bin/env python
"""
This runnable module interfaces itself with the Pebble management interface in order
to serve a mock OCSP responder during integration tests against Pebble.
"""
import datetime
import re
import requests
from dateutil import parser
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization, hashes
from cryptography import x509
from cryptography.x509 import ocsp
from six.moves import BaseHTTPServer
from certbot_integration_tests.utils.misc import GracefulTCPServer
from certbot_integration_tests.utils.constants import MOCK_OCSP_SERVER_PORT, PEBBLE_MANAGEMENT_URL
class _ProxyHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def do_POST(self):
request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediate-keys/0', verify=False)
issuer_key = serialization.load_pem_private_key(request.content, None, default_backend())
request = requests.get(PEBBLE_MANAGEMENT_URL + '/intermediates/0', verify=False)
issuer_cert = x509.load_pem_x509_certificate(request.content, default_backend())
try:
content_len = int(self.headers.getheader('content-length', 0))
except AttributeError:
content_len = int(self.headers.get('Content-Length'))
ocsp_request = ocsp.load_der_ocsp_request(self.rfile.read(content_len))
response = requests.get('{0}/cert-status-by-serial/{1}'.format(
PEBBLE_MANAGEMENT_URL, str(hex(ocsp_request.serial_number)).replace('0x', '')), verify=False)
if not response.ok:
ocsp_response = ocsp.OCSPResponseBuilder.build_unsuccessful(ocsp.OCSPResponseStatus.UNAUTHORIZED)
else:
data = response.json()
now = datetime.datetime.utcnow()
cert = x509.load_pem_x509_certificate(data['Certificate'].encode(), default_backend())
if data['Status'] != 'Revoked':
ocsp_status, revocation_time, revocation_reason = ocsp.OCSPCertStatus.GOOD, None, None
else:
ocsp_status, revocation_reason = ocsp.OCSPCertStatus.REVOKED, x509.ReasonFlags.unspecified
revoked_at = re.sub(r'( \+\d{4}).*$', r'\1', data['RevokedAt']) # "... +0000 UTC" => "+0000"
revocation_time = parser.parse(revoked_at)
ocsp_response = ocsp.OCSPResponseBuilder().add_response(
cert=cert, issuer=issuer_cert, algorithm=hashes.SHA1(),
cert_status=ocsp_status,
this_update=now, next_update=now + datetime.timedelta(hours=1),
revocation_time=revocation_time, revocation_reason=revocation_reason
).responder_id(
ocsp.OCSPResponderEncoding.NAME, issuer_cert
).sign(issuer_key, hashes.SHA256())
self.send_response(200)
self.end_headers()
self.wfile.write(ocsp_response.public_bytes(serialization.Encoding.DER))
if __name__ == '__main__':
try:
GracefulTCPServer(('', MOCK_OCSP_SERVER_PORT), _ProxyHandler).serve_forever()
except KeyboardInterrupt:
pass

View File

@@ -1,5 +1,7 @@
from setuptools import setup
from setuptools import find_packages
import sys
from distutils.version import StrictVersion
from setuptools import setup, find_packages, __version__ as setuptools_version
version = '0.32.0.dev0'
@@ -11,11 +13,23 @@ install_requires = [
'pytest',
'pytest-cov',
'pytest-xdist',
'python-dateutil',
'pyyaml',
'requests',
'six',
]
# Add pywin32 on Windows platforms to handle low-level system calls.
# This dependency needs to be added using environment markers to avoid its installation on Linux.
# However environment markers are supported only with setuptools >= 36.2.
# So this dependency is not added for old Linux distributions with old setuptools,
# in order to allow these systems to build certbot from sources.
if StrictVersion(setuptools_version) >= StrictVersion('36.2'):
install_requires.append("pywin32>=224 ; sys_platform == 'win32'")
elif 'bdist_wheel' in sys.argv[1:]:
raise RuntimeError('Error, you are trying to build certbot wheels using an old version '
'of setuptools. Version 36.2+ of setuptools is required.')
setup(
name='certbot-ci',
version=version,

View File

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

View File

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

View File

@@ -22,7 +22,9 @@ Credentials
Use of this plugin requires a configuration file containing Cloudflare API
credentials, obtained from your Cloudflare
`account page <https://www.cloudflare.com/a/account/my-account>`_.
`account page <https://www.cloudflare.com/a/account/my-account>`_. This plugin
does not currently support Cloudflare's "API Tokens", so please ensure you use
the "Global API Key" for authentication.
.. code-block:: ini
:name: credentials.ini

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -3,7 +3,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -1,7 +1,7 @@
from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -1,3 +1,3 @@
include LICENSE
include LICENSE.txt
include README
recursive-include docs *

View File

@@ -1,7 +1,7 @@
from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Remember to update local-oldest-requirements.txt when changing the minimum
# acme/certbot version.

View File

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

View File

@@ -2,7 +2,7 @@ from setuptools import setup
from setuptools import find_packages
version = '0.37.0.dev0'
version = '0.38.0.dev0'
# Please update tox.ini when modifying dependency version requirements
install_requires = [

View File

@@ -2,5 +2,4 @@ include LICENSE.txt
include README.rst
recursive-include docs *
recursive-include certbot_nginx/tests/testdata *
include certbot_nginx/options-ssl-nginx.conf
include certbot_nginx/options-ssl-nginx-old.conf
recursive-include certbot_nginx/tls_configs *.conf

View File

@@ -20,7 +20,6 @@ from certbot import crypto_util
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import misc
from certbot.compat import os
from certbot.plugins import common
@@ -128,7 +127,10 @@ class NginxConfigurator(common.Installer):
config_filename = "options-ssl-nginx.conf"
if self.version < (1, 5, 9):
config_filename = "options-ssl-nginx-old.conf"
return pkg_resources.resource_filename("certbot_nginx", config_filename)
elif self.version < (1, 13, 0):
config_filename = "options-ssl-nginx-tls12-only.conf"
return pkg_resources.resource_filename(
"certbot_nginx", os.path.join("tls_configs", config_filename))
@property
def mod_ssl_conf(self):
@@ -903,13 +905,9 @@ class NginxConfigurator(common.Installer):
have permissions of root.
"""
uid = misc.os_geteuid()
util.make_or_verify_dir(
self.config.work_dir, core_constants.CONFIG_DIRS_MODE, uid)
util.make_or_verify_dir(
self.config.backup_dir, core_constants.CONFIG_DIRS_MODE, uid)
util.make_or_verify_dir(
self.config.config_dir, core_constants.CONFIG_DIRS_MODE, uid)
util.make_or_verify_dir(self.config.work_dir, core_constants.CONFIG_DIRS_MODE)
util.make_or_verify_dir(self.config.backup_dir, core_constants.CONFIG_DIRS_MODE)
util.make_or_verify_dir(self.config.config_dir, core_constants.CONFIG_DIRS_MODE)
def get_version(self):
"""Return version of Nginx Server.

View File

@@ -23,10 +23,19 @@ UPDATED_MOD_SSL_CONF_DIGEST = ".updated-options-ssl-nginx-conf-digest.txt"
"""Name of the hash of the updated or informed mod_ssl_conf as saved in `IConfig.config_dir`."""
SSL_OPTIONS_HASHES_NEW = [
'108c4555058a087496a3893aea5d9e1cee0f20a3085d44a52dc1a66522299ac3',
'd5e021706ecdccc7090111b0ae9a29ef61523e927f020e410caf0a1fd7063981',
]
"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.13.0"""
SSL_OPTIONS_HASHES_MEDIUM = [
'63e2bddebb174a05c9d8a7cf2adf72f7af04349ba59a1a925fe447f73b2f1abf',
'2901debc7ecbc10917edd9084c05464c9c5930b463677571eaf8c94bffd11ae2',
'30baca73ed9a5b0e9a69ea40e30482241d8b1a7343aa79b49dc5d7db0bf53b6c',
'02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426',
]
"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9"""
"""SHA256 hashes of the contents of versions of MOD_SSL_CONF_SRC for nginx >= 1.5.9
and nginx < 1.13.0"""
ALL_SSL_OPTIONS_HASHES = [
'0f81093a1465e3d4eaa8b0c14e77b2a2e93568b0fc1351c2b87893a95f0de87c',
@@ -36,7 +45,8 @@ ALL_SSL_OPTIONS_HASHES = [
'394732f2bbe3e5e637c3fb5c6e980a1f1b90b01e2e8d6b7cff41dde16e2a756d',
'4b16fec2bcbcd8a2f3296d886f17f9953ffdcc0af54582452ca1e52f5f776f16',
'c052ffff0ad683f43bffe105f7c606b339536163490930e2632a335c8d191cc4',
] + SSL_OPTIONS_HASHES_NEW
'02329eb19930af73c54b3632b3165d84571383b8c8c73361df940cb3894dd426',
] + SSL_OPTIONS_HASHES_MEDIUM + SSL_OPTIONS_HASHES_NEW
"""SHA256 hashes of the contents of all versions of MOD_SSL_CONF_SRC"""
def os_constant(key):

View File

@@ -1,13 +0,0 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";

View File

@@ -1,14 +0,0 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_session_tickets off;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
ssl_ciphers "ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS";

View File

@@ -963,13 +963,40 @@ class InstallSslOptionsConfTest(util.NginxTest):
"Constants.ALL_SSL_OPTIONS_HASHES must be appended"
" with the sha256 hash of self.config.mod_ssl_conf when it is updated.")
def test_old_nginx_version_uses_old_config(self):
def test_ssl_config_files_hash_in_all_hashes(self):
"""
It is really critical that all TLS Nginx config files have their SHA256 hash registered in
constants.ALL_SSL_OPTIONS_HASHES. Otherwise Certbot will mistakenly assume that the config
file has been manually edited by the user, and will refuse to update it.
This test ensures that all necessary hashes are present.
"""
from certbot_nginx.constants import ALL_SSL_OPTIONS_HASHES
import pkg_resources
all_files = [
pkg_resources.resource_filename("certbot_nginx", os.path.join("tls_configs", x))
for x in ("options-ssl-nginx.conf",
"options-ssl-nginx-old.conf",
"options-ssl-nginx-tls12-only.conf")
]
self.assertTrue(all_files)
for one_file in all_files:
file_hash = crypto_util.sha256sum(one_file)
self.assertTrue(file_hash in ALL_SSL_OPTIONS_HASHES,
"Constants.ALL_SSL_OPTIONS_HASHES must be appended with the sha256 "
"hash of {0} when it is updated.".format(one_file))
def test_nginx_version_uses_correct_config(self):
self.config.version = (1, 5, 8)
self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
"options-ssl-nginx-old.conf")
self._call()
self._assert_current_file()
self.config.version = (1, 5, 9)
self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
"options-ssl-nginx-tls12-only.conf")
self._call()
self._assert_current_file()
self.config.version = (1, 13, 0)
self.assertEqual(os.path.basename(self.config.mod_ssl_conf_src),
"options-ssl-nginx.conf")

View File

@@ -30,8 +30,16 @@ class NginxParserTest(util.NginxTest): #pylint: disable=too-many-public-methods
self.assertEqual(nparser.root, self.config_path)
def test_root_absolute(self):
nparser = parser.NginxParser(os.path.relpath(self.config_path))
self.assertEqual(nparser.root, self.config_path)
curr_dir = os.getcwd()
try:
# On Windows current directory may be on a different drive than self.tempdir.
# However a relative path between two different drives is invalid. So we move to
# self.tempdir to ensure that we stay on the same drive.
os.chdir(self.temp_dir)
nparser = parser.NginxParser(os.path.relpath(self.config_path))
self.assertEqual(nparser.root, self.config_path)
finally:
os.chdir(curr_dir)
def test_root_no_trailing_slash(self):
nparser = parser.NginxParser(self.config_path + os.path.sep)

View File

@@ -3,7 +3,6 @@ import copy
import shutil
import tempfile
import unittest
import warnings
import josepy as jose
import mock
@@ -11,6 +10,7 @@ import pkg_resources
import zope.component
from certbot import configuration
from certbot import util
from certbot.compat import os
from certbot.plugins import common
from certbot.tests import util as test_util
@@ -34,20 +34,16 @@ class NginxTest(unittest.TestCase): # pylint: disable=too-few-public-methods
"rsa512_key.pem"))
def tearDown(self):
# On Windows we have various files which are not correctly closed at the time of tearDown.
# For know, we log them until a proper file close handling is written.
# Useful for development only, so no warning when we are on a CI process.
def onerror_handler(_, path, excinfo):
"""On error handler"""
if not os.environ.get('APPVEYOR'): # pragma: no cover
message = ('Following error occurred when deleting path {0}'
'during tearDown process: {1}'.format(path, str(excinfo)))
warnings.warn(message)
# Cleanup opened resources after a test. This is usually done through atexit handlers in
# Certbot, but during tests, atexit will not run registered functions before tearDown is
# called and instead will run them right before the entire test process exits.
# It is a problem on Windows, that does not accept to clean resources before closing them.
util._release_locks() # pylint: disable=protected-access
shutil.rmtree(self.temp_dir, onerror=onerror_handler)
shutil.rmtree(self.config_dir, onerror=onerror_handler)
shutil.rmtree(self.work_dir, onerror=onerror_handler)
shutil.rmtree(self.logs_dir, onerror=onerror_handler)
shutil.rmtree(self.temp_dir)
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
shutil.rmtree(self.logs_dir)
def get_data_filename(filename):

View File

@@ -0,0 +1,13 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

View File

@@ -0,0 +1,13 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.2;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

View File

@@ -0,0 +1,13 @@
# This file contains important security parameters. If you modify this file
# manually, Certbot will be unable to automatically provide future security
# updates. Instead, Certbot will print and log an error message with a path to
# the up-to-date file that you will need to refer to when manually updating
# this file.
ssl_session_cache shared:le_nginx_SSL:10m;
ssl_session_timeout 1440m;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_prefer_server_ciphers off;
ssl_ciphers "ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384";

View File

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

View File

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

View File

@@ -20,7 +20,6 @@ from certbot import constants
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import misc
from certbot.compat import os
logger = logging.getLogger(__name__)
@@ -139,8 +138,7 @@ class AccountFileStorage(interfaces.AccountStorage):
"""
def __init__(self, config):
self.config = config
util.make_or_verify_dir(config.accounts_dir, 0o700, misc.os_geteuid(),
self.config.strict_permissions)
util.make_or_verify_dir(config.accounts_dir, 0o700, self.config.strict_permissions)
def _account_dir_path(self, account_id):
return self._account_dir_path_for_server_path(account_id, self.config.server_path)
@@ -322,8 +320,7 @@ class AccountFileStorage(interfaces.AccountStorage):
def _save(self, account, acme, regr_only):
account_dir_path = self._account_dir_path(account.id)
util.make_or_verify_dir(account_dir_path, 0o700, misc.os_geteuid(),
self.config.strict_permissions)
util.make_or_verify_dir(account_dir_path, 0o700, self.config.strict_permissions)
try:
with open(self._regr_path(account_dir_path), "w") as regr_file:
regr = account.regr

View File

@@ -15,7 +15,6 @@ from certbot import interfaces
from certbot import ocsp
from certbot import storage
from certbot import util
from certbot.compat import misc
from certbot.compat import os
from certbot.display import util as display_util
@@ -106,7 +105,7 @@ def lineage_for_certname(cli_config, certname):
"""Find a lineage object with name certname."""
configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid())
util.make_or_verify_dir(configs_dir, mode=0o755)
try:
renewal_file = storage.renewal_file_for_certname(cli_config, certname)
except errors.CertStorageError:
@@ -375,7 +374,7 @@ def _search_lineages(cli_config, func, initial_rv, *args):
"""
configs_dir = cli_config.renewal_configs_dir
# Verify the directory is there
util.make_or_verify_dir(configs_dir, mode=0o755, uid=misc.os_geteuid())
util.make_or_verify_dir(configs_dir, mode=0o755)
rv = initial_rv
for renewal_file in storage.renewal_conf_files(cli_config):

View File

@@ -30,7 +30,6 @@ from certbot import interfaces
from certbot import reverter
from certbot import storage
from certbot import util
from certbot.compat import misc
from certbot.compat import os
from certbot.display import enhancements
from certbot.display import ops as display_ops
@@ -459,9 +458,7 @@ class Client(object):
"""
for path in cert_path, chain_path, fullchain_path:
util.make_or_verify_dir(
os.path.dirname(path), 0o755, misc.os_geteuid(),
self.config.strict_permissions)
util.make_or_verify_dir(os.path.dirname(path), 0o755, self.config.strict_permissions)
cert_file, abs_cert_path = _open_pem_file('cert_path', cert_path)
@@ -627,7 +624,7 @@ class Client(object):
reporter.add_message(
"An error occurred and we failed to restore your config and "
"restart your server. Please post to "
"https://community.letsencrypt.org/c/server-config "
"https://community.letsencrypt.org/c/help "
"with details about your configuration and this error you received.",
reporter.HIGH_PRIORITY)
raise

31
certbot/compat/_path.py Normal file
View File

@@ -0,0 +1,31 @@
"""This compat module wraps os.path to forbid some functions."""
# pylint: disable=function-redefined
from __future__ import absolute_import
# First round of wrapping: we import statically all public attributes exposed by the os.path
# module. This allows in particular to have pylint, mypy, IDEs be aware that most of os.path
# members are available in certbot.compat.path.
from os.path import * # type: ignore # pylint: disable=wildcard-import,unused-wildcard-import,redefined-builtin,os-module-forbidden
# Second round of wrapping: we import dynamically all attributes from the os.path module that have
# not yet been imported by the first round (static star import).
import os.path as std_os_path # pylint: disable=os-module-forbidden
import sys as std_sys
ourselves = std_sys.modules[__name__]
for attribute in dir(std_os_path):
# Check if the attribute does not already exist in our module. It could be internal attributes
# of the module (__name__, __doc__), or attributes from standard os.path already imported with
# `from os.path import *`.
if not hasattr(ourselves, attribute):
setattr(ourselves, attribute, getattr(std_os_path, attribute))
# Clean all remaining importables that are not from the core os.path module.
del ourselves, std_os_path, std_sys
# Function os.path.realpath is broken on some versions of Python for Windows.
def realpath(*unused_args, **unused_kwargs):
"""Method os.path.realpath() is forbidden"""
raise RuntimeError('Usage of os.path.realpath() is forbidden. '
'Use certbot.compat.filesystem.realpath() instead.')

View File

@@ -77,6 +77,54 @@ def copy_ownership_and_apply_mode(src, dst, mode, copy_user, copy_group):
chmod(dst, mode)
def check_mode(file_path, mode):
# type: (str, int) -> bool
"""
Check if the given mode matches the permissions of the given file.
On Linux, will make a direct comparison, on Windows, mode will be compared against
the security model.
:param str file_path: Path of the file
:param int mode: POSIX mode to test
:rtype: bool
:return: True if the POSIX mode matches the file permissions
"""
if POSIX_MODE:
return stat.S_IMODE(os.stat(file_path).st_mode) == mode
return _check_win_mode(file_path, mode)
def check_owner(file_path):
# type: (str) -> bool
"""
Check if given file is owned by current user.
:param str file_path: File path to check
:rtype: bool
:return: True if given file is owned by current user, False otherwise.
"""
if POSIX_MODE:
return os.stat(file_path).st_uid == os.getuid()
# Get owner sid of the file
security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)
user = security.GetSecurityDescriptorOwner()
# Compare sids
return _get_current_user() == user
def check_permissions(file_path, mode):
# type: (str, int) -> bool
"""
Check if given file has the given mode and is owned by current user.
:param str file_path: File path to check
:param int mode: POSIX mode to check
:rtype: bool
:return: True if file has correct mode and owner, False otherwise.
"""
return check_owner(file_path) and check_mode(file_path, mode)
def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin
# type: (str, int, int) -> int
"""
@@ -107,6 +155,10 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin
security = attributes.SECURITY_DESCRIPTOR
user = _get_current_user()
dacl = _generate_dacl(user, mode)
# We set second parameter to 0 (`False`) to say that this security descriptor is
# NOT constructed from a default mechanism, but is explicitly set by the user.
# See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptorowner # pylint: disable=line-too-long
security.SetSecurityDescriptorOwner(user, 0)
# We set first parameter to 1 (`True`) to say that this security descriptor contains
# a DACL. Otherwise second and third parameters are ignored.
# We set third parameter to 0 (`False`) to say that this security descriptor is
@@ -114,11 +166,11 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin
# See https://docs.microsoft.com/en-us/windows/desktop/api/securitybaseapi/nf-securitybaseapi-setsecuritydescriptordacl # pylint: disable=line-too-long
security.SetSecurityDescriptorDacl(1, dacl, 0)
handle = None
try:
handle = win32file.CreateFile(file_path, win32file.GENERIC_READ,
win32file.FILE_SHARE_READ & win32file.FILE_SHARE_WRITE,
attributes, disposition, 0, None)
handle.Close()
except pywintypes.error as err:
# Handle native windows errors into python errors to be consistent with the API
# of os.open in the situation of a file already existing or locked.
@@ -127,6 +179,9 @@ def open(file_path, flags, mode=0o777): # pylint: disable=redefined-builtin
if err.winerror == winerror.ERROR_SHARING_VIOLATION:
raise OSError(errno.EACCES, err.strerror)
raise err
finally:
if handle:
handle.Close()
# At this point, the file that did not exist has been created with proper permissions,
# so os.O_CREAT and os.O_EXCL are not needed anymore. We remove them from the flags to
@@ -177,6 +232,7 @@ def mkdir(file_path, mode=0o777):
security = attributes.SECURITY_DESCRIPTOR
user = _get_current_user()
dacl = _generate_dacl(user, mode)
security.SetSecurityDescriptorOwner(user, False)
security.SetSecurityDescriptorDacl(1, dacl, 0)
try:
@@ -207,13 +263,22 @@ def replace(src, dst):
os.rename(src, dst)
def _apply_win_mode(file_path, mode):
def realpath(file_path):
"""
This function converts the given POSIX mode into a Windows ACL list, and applies it to the
file given its path. If the given path is a symbolic link, it will resolved to apply the
mode on the targeted file.
Find the real path for the given path. This method resolves symlinks, including
recursive symlinks, and is protected against symlinks that creates an infinite loop.
"""
original_path = file_path
if POSIX_MODE:
path = os.path.realpath(file_path)
if os.path.islink(path):
# If path returned by realpath is still a link, it means that it failed to
# resolve the symlink because of a loop.
# See realpath code: https://github.com/python/cpython/blob/master/Lib/posixpath.py
raise RuntimeError('Error, link {0} is a loop!'.format(original_path))
return path
inspected_paths = [] # type: List[str]
while os.path.islink(file_path):
link_path = file_path
@@ -223,6 +288,53 @@ def _apply_win_mode(file_path, mode):
if file_path in inspected_paths:
raise RuntimeError('Error, link {0} is a loop!'.format(original_path))
inspected_paths.append(file_path)
return os.path.abspath(file_path)
# On Windows is_executable run from an unprivileged shell may claim that a path is
# executable when it is excutable only if run from a privileged shell. This result
# is due to the fact that GetEffectiveRightsFromAcl calculate effective rights
# without taking into consideration if the target user has currently required the
# elevated privileges or not. However this is not a problem since certbot always
# requires to be run under a privileged shell, so the user will always benefit
# from the highest (privileged one) set of permissions on a given file.
def is_executable(path):
"""
Is path an executable file?
:param str path: path to test
:returns: True if path is an executable file
:rtype: bool
"""
if POSIX_MODE:
return os.path.isfile(path) and os.access(path, os.X_OK)
return _win_is_executable(path)
def _win_is_executable(path):
if not os.path.isfile(path):
return False
security = win32security.GetFileSecurity(path, win32security.DACL_SECURITY_INFORMATION)
dacl = security.GetSecurityDescriptorDacl()
mode = dacl.GetEffectiveRightsFromAcl({
'TrusteeForm': win32security.TRUSTEE_IS_SID,
'TrusteeType': win32security.TRUSTEE_IS_USER,
'Identifier': _get_current_user(),
})
return mode & ntsecuritycon.FILE_GENERIC_EXECUTE == ntsecuritycon.FILE_GENERIC_EXECUTE
def _apply_win_mode(file_path, mode):
"""
This function converts the given POSIX mode into a Windows ACL list, and applies it to the
file given its path. If the given path is a symbolic link, it will resolved to apply the
mode on the targeted file.
"""
file_path = realpath(file_path)
# Get owner sid of the file
security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION)
user = security.GetSecurityDescriptorOwner()
@@ -333,6 +445,28 @@ def _generate_windows_flags(rights_desc):
return flag
def _check_win_mode(file_path, mode):
# Resolve symbolic links
file_path = realpath(file_path)
# Get current dacl file
security = win32security.GetFileSecurity(file_path, win32security.OWNER_SECURITY_INFORMATION
| win32security.DACL_SECURITY_INFORMATION)
dacl = security.GetSecurityDescriptorDacl()
# Get current file owner sid
user = security.GetSecurityDescriptorOwner()
if not dacl:
# No DACL means full control to everyone
# This is not a deterministic permissions set.
return False
# Calculate the target dacl
ref_dacl = _generate_dacl(user, mode)
return _compare_dacls(dacl, ref_dacl)
def _compare_dacls(dacl1, dacl2):
"""
This method compare the two given DACLs to check if they are identical.

View File

@@ -30,6 +30,10 @@ else:
MASK_FOR_PRIVATE_KEY_PERMISSIONS = 0
# For Linux: define OS specific standard binary directories
STANDARD_BINARY_DIRS = ["/usr/sbin", "/usr/local/bin", "/usr/local/sbin"] if POSIX_MODE else []
def raise_for_non_administrative_windows_rights():
# type: () -> None
"""
@@ -42,22 +46,6 @@ def raise_for_non_administrative_windows_rights():
raise errors.Error('Error, certbot must be run on a shell with administrative rights.')
def os_geteuid():
"""
Get current user uid
:returns: The current user uid.
:rtype: int
"""
try:
# Linux specific
return os.geteuid()
except AttributeError:
# Windows specific
return 0
def readline_with_timeout(timeout, prompt):
# type: (float, str) -> str
"""
@@ -88,16 +76,6 @@ def readline_with_timeout(timeout, prompt):
return sys.stdin.readline()
def compare_file_modes(mode1, mode2):
"""Return true if the two modes can be considered as equals for this platform"""
if os.name != 'nt':
# Linux specific: standard compare
return oct(stat.S_IMODE(mode1)) == oct(stat.S_IMODE(mode2))
# Windows specific: most of mode bits are ignored on Windows. Only check user R/W rights.
return (stat.S_IMODE(mode1) & stat.S_IREAD == stat.S_IMODE(mode2) & stat.S_IREAD
and stat.S_IMODE(mode1) & stat.S_IWRITE == stat.S_IMODE(mode2) & stat.S_IWRITE)
WINDOWS_DEFAULT_FOLDERS = {
'config': 'C:\\Certbot',
'work': 'C:\\Certbot\\lib',

View File

@@ -26,7 +26,9 @@ for attribute in dir(std_os):
if not hasattr(ourselves, attribute):
setattr(ourselves, attribute, getattr(std_os, attribute))
# Similar to os.path, allow certbot.compat.os.path to behave as a module
# Import our internal path module, then allow certbot.compat.os.path
# to behave as a module (similarly to os.path).
from certbot.compat import _path as path # type: ignore # pylint: disable=wrong-import-position
std_sys.modules[__name__ + '.path'] = path
# Clean all remaining importables that are not from the core os module.
@@ -105,3 +107,12 @@ def replace(*unused_args, **unused_kwargs):
"""Method os.replace() is forbidden"""
raise RuntimeError('Usage of os.replace() is forbidden. '
'Use certbot.compat.filesystem.replace() instead.')
# Results given by os.access are inconsistent or partial on Windows, because this platform is not
# following the POSIX approach.
def access(*unused_args, **unused_kwargs):
"""Method os.access() is forbidden"""
raise RuntimeError('Usage of os.access() is forbidden. '
'Use certbot.compat.filesystem.check_mode() or '
'certbot.compat.filesystem.is_executable() instead.')

View File

@@ -169,9 +169,10 @@ ACCOUNTS_DIR = "accounts"
"""Directory where all accounts are saved."""
LE_REUSE_SERVERS = {
'acme-v02.api.letsencrypt.org/directory': 'acme-v01.api.letsencrypt.org/directory',
'acme-staging-v02.api.letsencrypt.org/directory':
'acme-staging.api.letsencrypt.org/directory'
os.path.normpath('acme-v02.api.letsencrypt.org/directory'):
os.path.normpath('acme-v01.api.letsencrypt.org/directory'),
os.path.normpath('acme-staging-v02.api.letsencrypt.org/directory'):
os.path.normpath('acme-staging.api.letsencrypt.org/directory')
}
"""Servers that can reuse accounts from other servers."""

View File

@@ -28,7 +28,6 @@ from acme.magic_typing import IO # pylint: disable=unused-import, no-name-in-mo
from certbot import errors
from certbot import interfaces
from certbot import util
from certbot.compat import misc
from certbot.compat import os
logger = logging.getLogger(__name__)
@@ -61,8 +60,7 @@ def init_save_key(key_size, key_dir, keyname="key-certbot.pem"):
config = zope.component.getUtility(interfaces.IConfig)
# Save file
util.make_or_verify_dir(key_dir, 0o700, misc.os_geteuid(),
config.strict_permissions)
util.make_or_verify_dir(key_dir, 0o700, config.strict_permissions)
key_f, key_path = util.unique_file(
os.path.join(key_dir, keyname), 0o600, "wb")
with key_f:
@@ -92,8 +90,7 @@ def init_save_csr(privkey, names, path):
privkey.pem, names, must_staple=config.must_staple)
# Save CSR
util.make_or_verify_dir(path, 0o755, misc.os_geteuid(),
config.strict_permissions)
util.make_or_verify_dir(path, 0o755, config.strict_permissions)
csr_f, csr_filename = util.unique_file(
os.path.join(path, "csr-certbot.pem"), 0o644, "wb")
with csr_f:

View File

@@ -8,6 +8,7 @@ from acme.magic_typing import Set, List # pylint: disable=unused-import, no-nam
from certbot import errors
from certbot import util
from certbot.compat import filesystem
from certbot.compat import os
from certbot.plugins import util as plug_util
@@ -254,7 +255,7 @@ def execute(cmd_name, shell_cmd):
cmd_name, shell_cmd, cmd.returncode)
if err:
logger.error('Error output from %s command %s:\n%s', cmd_name, base_cmd, err)
return (err, out)
return err, out
def list_hooks(dir_path):
@@ -267,5 +268,5 @@ def list_hooks(dir_path):
"""
allpaths = (os.path.join(dir_path, f) for f in os.listdir(dir_path))
hooks = [path for path in allpaths if util.is_exe(path) and not path.endswith('~')]
hooks = [path for path in allpaths if filesystem.is_executable(path) and not path.endswith('~')]
return sorted(hooks)

View File

@@ -17,6 +17,7 @@ from __future__ import print_function
import functools
import logging
import logging.handlers
import shutil
import sys
import tempfile
import traceback
@@ -26,7 +27,6 @@ from acme import messages
from certbot import constants
from certbot import errors
from certbot import util
from certbot.compat import misc
from certbot.compat import os
# Logging format
@@ -134,8 +134,7 @@ def setup_log_file_handler(config, logfile, fmt):
"""
# TODO: logs might contain sensitive data such as contents of the
# private key! #525
util.set_up_core_dir(
config.logs_dir, 0o700, misc.os_geteuid(), config.strict_permissions)
util.set_up_core_dir(config.logs_dir, 0o700, config.strict_permissions)
log_file_path = os.path.join(config.logs_dir, logfile)
try:
handler = logging.handlers.RotatingFileHandler(
@@ -240,9 +239,10 @@ class TempHandler(logging.StreamHandler):
"""
def __init__(self):
stream = tempfile.NamedTemporaryFile('w', delete=False)
self._workdir = tempfile.mkdtemp()
self.path = os.path.join(self._workdir, 'log')
stream = util.safe_open(self.path, mode='w', chmod=0o600)
super(TempHandler, self).__init__(stream)
self.path = stream.name
self._delete = True
def emit(self, record):
@@ -266,7 +266,7 @@ class TempHandler(logging.StreamHandler):
# stream like stderr to be used
self.stream.close()
if self._delete:
os.remove(self.path)
shutil.rmtree(self._workdir)
self._delete = False
super(TempHandler, self).close()
finally:

View File

@@ -31,6 +31,7 @@ from certbot import reporter
from certbot import storage
from certbot import updater
from certbot import util
from certbot.compat import filesystem
from certbot.compat import misc
from certbot.compat import os
from certbot.display import util as display_util, ops as display_ops
@@ -841,12 +842,12 @@ def _populate_from_certname(config):
return config
def _check_certificate_and_key(config):
if not os.path.isfile(os.path.realpath(config.cert_path)):
if not os.path.isfile(filesystem.realpath(config.cert_path)):
raise errors.ConfigurationError("Error while reading certificate from path "
"{0}".format(config.cert_path))
if not os.path.isfile(os.path.realpath(config.key_path)):
"{0}".format(config.cert_path))
if not os.path.isfile(filesystem.realpath(config.key_path)):
raise errors.ConfigurationError("Error while reading private key from path "
"{0}".format(config.key_path))
"{0}".format(config.key_path))
def plugins_cmd(config, plugins):
"""List server software plugins.
@@ -1298,18 +1299,14 @@ def make_or_verify_needed_dirs(config):
:rtype: None
"""
util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
misc.os_geteuid(), config.strict_permissions)
util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,
misc.os_geteuid(), config.strict_permissions)
util.set_up_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions)
util.set_up_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE, config.strict_permissions)
hook_dirs = (config.renewal_pre_hooks_dir,
config.renewal_deploy_hooks_dir,
config.renewal_post_hooks_dir,)
for hook_dir in hook_dirs:
util.make_or_verify_dir(hook_dir,
uid=misc.os_geteuid(),
strict=config.strict_permissions)
util.make_or_verify_dir(hook_dir, strict=config.strict_permissions)
def set_displayer(config):

View File

@@ -486,7 +486,7 @@ def dir_setup(test_dir, pkg): # pragma: no cover
link, (ex: OS X) such plugins will be confused. This function prevents
such a case.
"""
return os.path.realpath(tempfile.mkdtemp(prefix))
return filesystem.realpath(tempfile.mkdtemp(prefix))
temp_dir = expanded_tempdir("temp")
config_dir = expanded_tempdir("config")

View File

@@ -31,7 +31,7 @@ class PluginStorageTest(test_util.ConfigTestCase):
self.plugin.storage.storagepath = os.path.join(self.config.config_dir,
".pluginstorage.json")
with mock.patch("six.moves.builtins.open", mock_open):
with mock.patch('os.path.isfile', return_value=True):
with mock.patch('certbot.compat.os.path.isfile', return_value=True):
with mock.patch("certbot.reverter.util"):
self.assertRaises(errors.PluginStorageError,
self.plugin.storage._load) # pylint: disable=protected-access

View File

@@ -3,9 +3,11 @@ import logging
from certbot import util
from certbot.compat import os
from certbot.compat.misc import STANDARD_BINARY_DIRS
logger = logging.getLogger(__name__)
def get_prefixes(path):
"""Retrieves all possible path prefixes of a path, in descending order
of length. For instance,
@@ -26,6 +28,7 @@ def get_prefixes(path):
break
return prefixes
def path_surgery(cmd):
"""Attempt to perform PATH surgery to find cmd
@@ -35,10 +38,9 @@ def path_surgery(cmd):
:returns: True if the operation succeeded, False otherwise
"""
dirs = ("/usr/sbin", "/usr/local/bin", "/usr/local/sbin")
path = os.environ["PATH"]
added = []
for d in dirs:
for d in STANDARD_BINARY_DIRS:
if d not in path:
path += os.pathsep + d
added.append(d)

View File

@@ -16,6 +16,7 @@ class GetPrefixTest(unittest.TestCase):
self.assertEqual(get_prefixes('/'), [os.path.normpath('/')])
self.assertEqual(get_prefixes('a'), ['a'])
class PathSurgeryTest(unittest.TestCase):
"""Tests for certbot.plugins.path_surgery."""
@@ -29,13 +30,15 @@ class PathSurgeryTest(unittest.TestCase):
self.assertEqual(path_surgery("eg"), True)
self.assertEqual(mock_debug.call_count, 0)
self.assertEqual(os.environ["PATH"], all_path["PATH"])
no_path = {"PATH": "/tmp/"}
with mock.patch.dict('os.environ', no_path):
path_surgery("thingy")
self.assertEqual(mock_debug.call_count, 2)
self.assertTrue("Failed to find" in mock_debug.call_args[0][0])
self.assertTrue("/usr/local/bin" in os.environ["PATH"])
self.assertTrue("/tmp" in os.environ["PATH"])
if os.name != 'nt':
# This part is specific to Linux since on Windows no PATH surgery is ever done.
no_path = {"PATH": "/tmp/"}
with mock.patch.dict('os.environ', no_path):
path_surgery("thingy")
self.assertEqual(mock_debug.call_count, 2 if os.name != 'nt' else 1)
self.assertTrue("Failed to find" in mock_debug.call_args[0][0])
self.assertTrue("/usr/local/bin" in os.environ["PATH"])
self.assertTrue("/tmp" in os.environ["PATH"])
if __name__ == "__main__":

View File

@@ -24,6 +24,7 @@ from certbot.display import ops
from certbot.display import util as display_util
from certbot.plugins import common
from certbot.plugins import util
from certbot.util import safe_open
logger = logging.getLogger(__name__)
@@ -207,7 +208,7 @@ to serve all files under specified web root ({0})."""
old_umask = os.umask(0o022)
try:
with open(validation_path, "wb") as validation_file:
with safe_open(validation_path, mode="wb", chmod=0o644) as validation_file:
validation_file.write(validation.encode())
finally:
os.umask(old_umask)

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