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
 | 
			
		||||
    proxy = https://user:pass@localhost:8080
 | 
			
		||||
    no_ssl_verify = false
 | 
			
		||||
    ssl_certs_file =
 | 
			
		||||
    timeout = 30
 | 
			
		||||
    hostname = machinename
 | 
			
		||||
    [projectmap]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,6 +13,7 @@ include_only_with_project_file = false
 | 
			
		|||
offline = false
 | 
			
		||||
proxy = https://user:pass@localhost:8080
 | 
			
		||||
no_ssl_verify = false
 | 
			
		||||
ssl_certs_file =
 | 
			
		||||
timeout = abc
 | 
			
		||||
api_url = https://localhost:0/api/v1/heartbeats
 | 
			
		||||
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
 | 
			
		||||
proxy = https://user:pass@localhost:8080
 | 
			
		||||
no_ssl_verify = true
 | 
			
		||||
ssl_certs_file =
 | 
			
		||||
timeout = abc
 | 
			
		||||
api_url = https://localhost:0/api/v1/heartbeats
 | 
			
		||||
hostname = fromcfgfile
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,8 @@
 | 
			
		|||
usage: wakatime [-h] [--entity FILE] [--key KEY] [--write] [--plugin PLUGIN]
 | 
			
		||||
                [--time time] [--lineno LINENO] [--cursorpos CURSORPOS]
 | 
			
		||||
                [--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]
 | 
			
		||||
                [--local-file FILE] [--hostname HOSTNAME] [--disable-offline]
 | 
			
		||||
                [--hide-file-names] [--hide-project-names] [--exclude EXCLUDE]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -29,6 +29,9 @@ optional arguments:
 | 
			
		|||
                        socks5://user:pass@host:port or domain\user:pass
 | 
			
		||||
  --no-ssl-verify       Disables SSL certificate verification for HTTPS
 | 
			
		||||
                        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.
 | 
			
		||||
  --alternate-project ALTERNATE_PROJECT
 | 
			
		||||
                        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)
 | 
			
		||||
 | 
			
		||||
    @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()
 | 
			
		||||
    def test_write_argument(self, logs):
 | 
			
		||||
        logging.disable(logging.NOTSET)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -663,7 +663,7 @@ class ConfigsTestCase(TestCase):
 | 
			
		|||
            shutil.copy(entity, 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']
 | 
			
		||||
            retval = execute(args)
 | 
			
		||||
            self.assertEquals(retval, SUCCESS)
 | 
			
		||||
| 
						 | 
				
			
			@ -674,3 +674,23 @@ class ConfigsTestCase(TestCase):
 | 
			
		|||
            self.assertHeartbeatNotSavedOffline()
 | 
			
		||||
            self.assertOfflineHeartbeatsSynced()
 | 
			
		||||
            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
 | 
			
		||||
            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
 | 
			
		||||
    response, code = None, None
 | 
			
		||||
    try:
 | 
			
		||||
        response = session.post(api_url, data=request_body, headers=headers,
 | 
			
		||||
                                proxies=proxies, timeout=timeout,
 | 
			
		||||
                                verify=not args.nosslverify)
 | 
			
		||||
                                verify=ssl_verify)
 | 
			
		||||
    except RequestException:
 | 
			
		||||
        if should_try_ntlm:
 | 
			
		||||
            return send_heartbeats(heartbeats, args, configs, use_ntlm_proxy=True)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -103,6 +103,10 @@ def parse_arguments():
 | 
			
		|||
                        help='Disables SSL certificate verification for HTTPS '+
 | 
			
		||||
                             'requests. By default, SSL certificates are ' +
 | 
			
		||||
                             '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,
 | 
			
		||||
                        help='Optional project name.')
 | 
			
		||||
    parser.add_argument('--alternate-project', dest='alternate_project',
 | 
			
		||||
| 
						 | 
				
			
			@ -307,6 +311,8 @@ def parse_arguments():
 | 
			
		|||
                         'domain\\user:pass.')
 | 
			
		||||
    if configs.has_option('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'):
 | 
			
		||||
        args.verbose = configs.getboolean('settings', 'verbose')
 | 
			
		||||
    if not args.verbose and configs.has_option('settings', 'debug'):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue