-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ca_certs from environment or certifi #117
Conversation
Codecov Report
@@ Coverage Diff @@
## master #117 +/- ##
==========================================
+ Coverage 73.13% 73.45% +0.31%
==========================================
Files 6 8 +2
Lines 2479 2535 +56
==========================================
+ Hits 1813 1862 +49
- Misses 666 673 +7
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like environ over certifi better than #52
python3/httplib2/certs.py
Outdated
import os.path | ||
|
||
try: | ||
from certifi import where as get_cacerts |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ca_certs_locater
is not mentioned here. Sorry, just found that it has same py2/3 difference in approved #52
Should work same in py2/3.
tests/test_cacerts_from_env.py
Outdated
import httplib2 | ||
|
||
try: | ||
import certifi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Test requirements don't specify certifi
explicitly, but it may be installed as dependency of another packet in future. So seemingly unrelated change will affect how CA is tested.
Solution: separate certifi
import and usage. That allows to mock presence of certifi in test. Otherwise to test with/out certifi you'd have to run subtest in another Python process or patch/reload sys.modules[httplib2.certs]
or something even less reliable.
# httplib2/certs.py
import os
# import os.path - this is not needed really, previous line does the job, sorry it also slipped in original review
ca_certs_locater = None
try:
import ca_certs_locater
except ImportError:
pass
certifi = None
try:
import certifi
except ImportError:
pass
BUILTIN_CA_CERTS = os.path.join(...)
def where():
env = os.environ.get('HTTPLIB2_CA_CERTS')
if env is not None:
return env
if ca_certs_locater:
return ca_certs_locater.get()
if certifi:
return certifi.where()
return BUILTIN_CA_CERTS
python2/httplib2/certs.py
Outdated
pass | ||
|
||
|
||
def certifi_where(): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am using a method definition here which I try to override with the import later as this is easier to use with mock.
Thanks. ca_certs_loader is not used in py3. |
You're right. Now the docs reflect the correct order of return value of where() and in python3 docs |
Suppose you have application using httplib2 and And yes it's broken right now and not part of environ variable support. IMHO still has to be done so why not now. |
@httplib2/maintainers review ping and see below Important API design to decide: what to do with empty string Right now it means "validate against empty CA list", so all TLS will fail. Arguably such behavior could be invoked via value like @dirkboye please have your say here too |
Vote here for |
Vote here for |
Vote here for |
should I put in the import of ca_certs_locator in py3 and add a test for it? |
Yes, please. |
python2/httplib2/certs.py
Outdated
|
||
def where(): | ||
env = os.environ.get("HTTPLIB2_CA_CERTS") | ||
if env: # if not None and != "" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if env:
to be changed based on design vote
python3/httplib2/certs.py
Outdated
|
||
|
||
def certifi_where(): | ||
return None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe this should raise NotImplementedError
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If certifi
is available, this function will be replaced by import below. If not available, this function will not be called.
@dirkboye so maybe foo=None ; import mod ; foo = mod.where
is more readable version afterall.
As a side note, this PR would also solve #114 |
tests/test_cacerts_from_env.py
Outdated
|
||
|
||
def test_certs_file_from_builtin(): | ||
httplib2.certs.certifi_available = False |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This assignment affects other tests, so should be returned to previous value.
Something like this will reliably return global state.
with mock.patch("httplib2.certs.certifi_available", True):
assert where()...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. I used @mock.patch for the available flags
Should I use them for the where methods as well? Then I'll need to define them as None before try except block so that they are defined and I can patch them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it's not about any particular flag. Rule is: tests must either pass or fail only because main code is valid or not.
Tests must behave same way ...
- when certifi/custom_ca_loader is installed and not, unless test suite installs package in controllable manner
- when some environment variable is set or not, unless test runner controls environment
- when tests order were shuffled - this excludes modification of any global variables
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please have patience mate, I'm sorry this is not ideal one step review like change in these places then merged. You're doing important work.
I've voted for:
because of the two options, the alternative is not good. If the variable is set in the environment, in any form, it should mean something and not be ignored. If the impact of the empty string is undefined, then that should result in an error. The correct way for A third option for var set with the empty string is that there are no certs. I think this makes most semantic sense in terms of the var itself, but if it makes no sense in terms of the behavior of the system, then having an error on empty string is the reasonable next choice. Otherwise: The code, while feeling a bit cumbersome, makes sense and seems to do the desired job. |
In shell, "unset" environment variables is the right thing to do. So +1 to error in Python.
|
It's |
Or something like |
I addressed the issues regarding wrong filename in environment variable by Also decorator mocks are now used everywhere |
@cdent @stinky2nine ping |
@dirkboye do you use httplib2 with this patch somewhere? I want to release it, hopefully with more confidence from field usage. |
Yes I am using it together with google/containerregistry. So my workaround right now is to use IP or a host remapping But this PR here works very well, I can set HTTPLIB2_CA_CERTS and google/containerregistry pushes to our docker-registry :) |
@temoto when is pypi release from master planned? |
This PR is similar to #52 with following differences:
environment variable "HTTPLIB2_CA_CERTS" also overrides certifi.where()
I added some unit tests.