Add ssl_certs_file arg and config for custom ca bundles
This commit is contained in:
parent
9986a4cebf
commit
fbbe9af343
10 changed files with 85 additions and 3 deletions
|
@ -77,6 +77,7 @@ format. An example config file with all available options::
|
||||||
offline = true
|
offline = true
|
||||||
proxy = https://user:pass@localhost:8080
|
proxy = https://user:pass@localhost:8080
|
||||||
no_ssl_verify = false
|
no_ssl_verify = false
|
||||||
|
ssl_certs_file =
|
||||||
timeout = 30
|
timeout = 30
|
||||||
hostname = machinename
|
hostname = machinename
|
||||||
[projectmap]
|
[projectmap]
|
||||||
|
|
|
@ -13,6 +13,7 @@ include_only_with_project_file = false
|
||||||
offline = false
|
offline = false
|
||||||
proxy = https://user:pass@localhost:8080
|
proxy = https://user:pass@localhost:8080
|
||||||
no_ssl_verify = false
|
no_ssl_verify = false
|
||||||
|
ssl_certs_file =
|
||||||
timeout = abc
|
timeout = abc
|
||||||
api_url = https://localhost:0/api/v1/heartbeats
|
api_url = https://localhost:0/api/v1/heartbeats
|
||||||
hostname = fromcfgfile
|
hostname = fromcfgfile
|
||||||
|
|
18
tests/samples/configs/ssl_custom_certfile.cfg
Normal file
18
tests/samples/configs/ssl_custom_certfile.cfg
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
[settings]
|
||||||
|
verbose = true
|
||||||
|
api_key = d491a956-c8f2-44a9-98a7-987814bd71ba
|
||||||
|
log_file = /tmp/waka
|
||||||
|
hide_filenames = true
|
||||||
|
exclude =
|
||||||
|
^COMMIT_EDITMSG$
|
||||||
|
^TAG_EDITMSG$
|
||||||
|
^/var/
|
||||||
|
^/etc/
|
||||||
|
include =
|
||||||
|
.*
|
||||||
|
offline = false
|
||||||
|
proxy = https://user:pass@localhost:8080
|
||||||
|
ssl_certs_file = /fake/ca/certs/bundle.pem
|
||||||
|
timeout = abc
|
||||||
|
api_url = https://localhost:0/api/v1/heartbeats
|
||||||
|
hostname = fromcfgfile
|
|
@ -13,6 +13,7 @@ include =
|
||||||
offline = false
|
offline = false
|
||||||
proxy = https://user:pass@localhost:8080
|
proxy = https://user:pass@localhost:8080
|
||||||
no_ssl_verify = true
|
no_ssl_verify = true
|
||||||
|
ssl_certs_file =
|
||||||
timeout = abc
|
timeout = abc
|
||||||
api_url = https://localhost:0/api/v1/heartbeats
|
api_url = https://localhost:0/api/v1/heartbeats
|
||||||
hostname = fromcfgfile
|
hostname = fromcfgfile
|
|
@ -1,7 +1,8 @@
|
||||||
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
|
||||||
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
|
[--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
|
||||||
[--entity-type ENTITY_TYPE] [--category CATEGORY]
|
[--entity-type ENTITY_TYPE] [--category CATEGORY]
|
||||||
[--proxy PROXY] [--no-ssl-verify] [--project PROJECT]
|
[--proxy PROXY] [--no-ssl-verify]
|
||||||
|
[--ssl-certs-file SSL_CERTS_FILE] [--project PROJECT]
|
||||||
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
[--alternate-project ALTERNATE_PROJECT] [--language LANGUAGE]
|
||||||
[--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
|
[--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
|
||||||
[--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
|
[--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
|
||||||
|
|
|
@ -29,6 +29,9 @@ optional arguments:
|
||||||
socks5://user:pass@host:port or domain\user:pass
|
socks5://user:pass@host:port or domain\user:pass
|
||||||
--no-ssl-verify Disables SSL certificate verification for HTTPS
|
--no-ssl-verify Disables SSL certificate verification for HTTPS
|
||||||
requests. By default, SSL certificates are verified.
|
requests. By default, SSL certificates are verified.
|
||||||
|
--ssl-certs-file SSL_CERTS_FILE
|
||||||
|
Override the bundled Python Requests CA certs file. By
|
||||||
|
default, uses certifi for ca certs.
|
||||||
--project PROJECT Optional project name.
|
--project PROJECT Optional project name.
|
||||||
--alternate-project ALTERNATE_PROJECT
|
--alternate-project ALTERNATE_PROJECT
|
||||||
Optional alternate project name. Auto-discovered
|
Optional alternate project name. Auto-discovered
|
||||||
|
|
|
@ -471,6 +471,33 @@ class ArgumentsTestCase(TestCase):
|
||||||
|
|
||||||
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies=ANY, stream=False, timeout=60, verify=False)
|
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies=ANY, stream=False, timeout=60, verify=False)
|
||||||
|
|
||||||
|
@log_capture()
|
||||||
|
def test_custom_ssl_certs_file_argument(self, logs):
|
||||||
|
logging.disable(logging.NOTSET)
|
||||||
|
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse()
|
||||||
|
|
||||||
|
with TemporaryDirectory() as tempdir:
|
||||||
|
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||||
|
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
|
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
|
|
||||||
|
certfile = '/fake/certfile.pem'
|
||||||
|
config = 'tests/samples/configs/good_config.cfg'
|
||||||
|
args = ['--file', entity, '--config', config, '--ssl-certs-file', certfile]
|
||||||
|
retval = execute(args)
|
||||||
|
self.assertEquals(retval, SUCCESS)
|
||||||
|
self.assertNothingPrinted()
|
||||||
|
self.assertNothingLogged(logs)
|
||||||
|
|
||||||
|
self.patched['wakatime.session_cache.SessionCache.get'].assert_called_once_with()
|
||||||
|
self.patched['wakatime.session_cache.SessionCache.delete'].assert_not_called()
|
||||||
|
self.patched['wakatime.session_cache.SessionCache.save'].assert_called_once_with(ANY)
|
||||||
|
|
||||||
|
self.patched['wakatime.offlinequeue.Queue.push'].assert_not_called()
|
||||||
|
self.patched['wakatime.offlinequeue.Queue.pop'].assert_called_once_with()
|
||||||
|
|
||||||
|
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].assert_called_once_with(ANY, cert=None, proxies=ANY, stream=False, timeout=60, verify=certfile)
|
||||||
|
|
||||||
@log_capture()
|
@log_capture()
|
||||||
def test_write_argument(self, logs):
|
def test_write_argument(self, logs):
|
||||||
logging.disable(logging.NOTSET)
|
logging.disable(logging.NOTSET)
|
||||||
|
|
|
@ -663,7 +663,7 @@ class ConfigsTestCase(TestCase):
|
||||||
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
|
|
||||||
config = 'tests/samples/configs/has_ssl_verify_disabled.cfg'
|
config = 'tests/samples/configs/ssl_verify_disabled.cfg'
|
||||||
args = ['--file', entity, '--config', config, '--timeout', '15', '--log-file', '~/.wakatime.log']
|
args = ['--file', entity, '--config', config, '--timeout', '15', '--log-file', '~/.wakatime.log']
|
||||||
retval = execute(args)
|
retval = execute(args)
|
||||||
self.assertEquals(retval, SUCCESS)
|
self.assertEquals(retval, SUCCESS)
|
||||||
|
@ -674,3 +674,23 @@ class ConfigsTestCase(TestCase):
|
||||||
self.assertHeartbeatNotSavedOffline()
|
self.assertHeartbeatNotSavedOffline()
|
||||||
self.assertOfflineHeartbeatsSynced()
|
self.assertOfflineHeartbeatsSynced()
|
||||||
self.assertSessionCacheSaved()
|
self.assertSessionCacheSaved()
|
||||||
|
|
||||||
|
def test_ssl_custom_ca_certs_file(self):
|
||||||
|
self.patched['wakatime.packages.requests.adapters.HTTPAdapter.send'].return_value = CustomResponse()
|
||||||
|
|
||||||
|
with TemporaryDirectory() as tempdir:
|
||||||
|
entity = 'tests/samples/codefiles/emptyfile.txt'
|
||||||
|
shutil.copy(entity, os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
|
entity = os.path.realpath(os.path.join(tempdir, 'emptyfile.txt'))
|
||||||
|
|
||||||
|
config = 'tests/samples/configs/ssl_custom_certfile.cfg'
|
||||||
|
args = ['--file', entity, '--config', config, '--timeout', '15', '--log-file', '~/.wakatime.log']
|
||||||
|
retval = execute(args)
|
||||||
|
self.assertEquals(retval, SUCCESS)
|
||||||
|
self.assertNothingPrinted()
|
||||||
|
|
||||||
|
self.assertHeartbeatSent(proxies=ANY, timeout=15, verify='/fake/ca/certs/bundle.pem')
|
||||||
|
|
||||||
|
self.assertHeartbeatNotSavedOffline()
|
||||||
|
self.assertOfflineHeartbeatsSynced()
|
||||||
|
self.assertSessionCacheSaved()
|
||||||
|
|
|
@ -99,12 +99,16 @@ def send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=False):
|
||||||
should_try_ntlm = '\\' in args.proxy
|
should_try_ntlm = '\\' in args.proxy
|
||||||
proxies['https'] = args.proxy
|
proxies['https'] = args.proxy
|
||||||
|
|
||||||
|
ssl_verify = not args.nosslverify
|
||||||
|
if args.ssl_certs_file and ssl_verify:
|
||||||
|
ssl_verify = args.ssl_certs_file
|
||||||
|
|
||||||
# send request to api
|
# send request to api
|
||||||
response, code = None, None
|
response, code = None, None
|
||||||
try:
|
try:
|
||||||
response = session.post(api_url, data=request_body, headers=headers,
|
response = session.post(api_url, data=request_body, headers=headers,
|
||||||
proxies=proxies, timeout=timeout,
|
proxies=proxies, timeout=timeout,
|
||||||
verify=not args.nosslverify)
|
verify=ssl_verify)
|
||||||
except RequestException:
|
except RequestException:
|
||||||
if should_try_ntlm:
|
if should_try_ntlm:
|
||||||
return send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=True)
|
return send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=True)
|
||||||
|
|
|
@ -103,6 +103,10 @@ def parse_arguments():
|
||||||
help='Disables SSL certificate verification for HTTPS '+
|
help='Disables SSL certificate verification for HTTPS '+
|
||||||
'requests. By default, SSL certificates are ' +
|
'requests. By default, SSL certificates are ' +
|
||||||
'verified.')
|
'verified.')
|
||||||
|
parser.add_argument('--ssl-certs-file', dest='ssl_certs_file',
|
||||||
|
action=StoreWithoutQuotes,
|
||||||
|
help='Override the bundled Python Requests CA certs ' +
|
||||||
|
'file. By default, uses certifi for ca certs.')
|
||||||
parser.add_argument('--project', dest='project', action=StoreWithoutQuotes,
|
parser.add_argument('--project', dest='project', action=StoreWithoutQuotes,
|
||||||
help='Optional project name.')
|
help='Optional project name.')
|
||||||
parser.add_argument('--alternate-project', dest='alternate_project',
|
parser.add_argument('--alternate-project', dest='alternate_project',
|
||||||
|
@ -307,6 +311,8 @@ def parse_arguments():
|
||||||
'domain\\user:pass.')
|
'domain\\user:pass.')
|
||||||
if configs.has_option('settings', 'no_ssl_verify'):
|
if configs.has_option('settings', 'no_ssl_verify'):
|
||||||
args.nosslverify = configs.getboolean('settings', 'no_ssl_verify')
|
args.nosslverify = configs.getboolean('settings', 'no_ssl_verify')
|
||||||
|
if configs.has_option('settings', 'ssl_certs_file'):
|
||||||
|
args.ssl_certs_file = configs.get('settings', 'ssl_certs_file')
|
||||||
if not args.verbose and configs.has_option('settings', 'verbose'):
|
if not args.verbose and configs.has_option('settings', 'verbose'):
|
||||||
args.verbose = configs.getboolean('settings', 'verbose')
|
args.verbose = configs.getboolean('settings', 'verbose')
|
||||||
if not args.verbose and configs.has_option('settings', 'debug'):
|
if not args.verbose and configs.has_option('settings', 'debug'):
|
||||||
|
|
Loading…
Reference in a new issue