drop old ssl support + compatrequests

Koji now uses ssl via python-requests

 - use_old_ssl option is removed
 - koji.ssl library removed
 - compatrequests dropped

Related: https://pagure.io/koji/issue/467
This commit is contained in:
Tomas Kopecek 2017-09-25 15:10:27 +02:00
parent 15bc1155bb
commit 8f22069fda
16 changed files with 26 additions and 756 deletions

View file

@ -7,7 +7,6 @@ omit =
tests/*
hub/*
util/*
koji/ssl/*
koji/daemon.py
koji/tasks.py

View file

@ -5632,7 +5632,6 @@ def get_options():
'offline_retry_interval': 120,
'keepalive' : True,
'timeout' : None,
'use_old_ssl' : False,
'no_ssl_verify' : False,
'use_fast_upload': True,
'use_createrepo_c': False,
@ -5661,7 +5660,7 @@ def get_options():
elif name in ['offline_retry', 'use_createrepo_c', 'createrepo_skip_stat',
'createrepo_update', 'keepalive', 'use_fast_upload',
'support_rpm_source_layout', 'krb_rdns', 'krb_canon_host',
'build_arch_can_fail', 'use_old_ssl', 'no_ssl_verify']:
'build_arch_can_fail', 'no_ssl_verify']:
defaults[name] = config.getboolean('kojid', name)
elif name in ['plugin', 'plugins']:
defaults['plugin'] = value.split()

View file

@ -33,8 +33,7 @@
Name: koji
Version: 1.15.0
Release: %{release}%{?dist}
License: LGPLv2 and GPLv2+
# koji.ssl libs (from plague) are GPLv2+
License: LGPLv2
Summary: Build system tools
Group: Applications/System
URL: https://pagure.io/koji

View file

@ -8,7 +8,7 @@ ifeq ($(PYTHON), python3)
else
PYFILES = $(wildcard *.py)
PYSCRIPTS = context.py
SUBDIRS = ssl
SUBDIRS =
endif
PYVER := $(shell $(PYTHON) -c 'import sys; print("%.3s" % (sys.version))')
PYSYSDIR := $(shell $(PYTHON) -c 'import sys; print(sys.prefix)')

View file

@ -62,10 +62,7 @@ import os.path
import pwd
import random
import re
try:
import requests
except ImportError: #pragma: no cover
requests = None
import requests
try:
from requests_kerberos import HTTPKerberosAuth
except ImportError: #pragma: no cover
@ -1638,7 +1635,6 @@ def read_config(profile_name, user_config=None):
'anon_retry' : None,
'offline_retry' : None,
'offline_retry_interval' : None,
'use_old_ssl' : False,
'keepalive' : True,
'timeout' : None,
'use_fast_upload': False,
@ -1708,7 +1704,7 @@ def read_config(profile_name, user_config=None):
#not have a default value set in the option parser.
if name in result:
if name in ('anon_retry', 'offline_retry', 'keepalive',
'use_fast_upload', 'krb_rdns', 'use_old_ssl',
'use_fast_upload', 'krb_rdns', 'debug',
'debug', 'debug_xmlrpc', 'krb_canon_host'):
result[name] = config.getboolean(profile_name, name)
elif name in ('max_retries', 'retry_interval',
@ -1916,9 +1912,6 @@ pathinfo = PathInfo()
def is_requests_cert_error(e):
"""Determine if a requests error is due to a bad cert"""
if requests is None: #pragma: no cover
# We are not using requests, so this is not a requests cert error
return False
if not isinstance(e, requests.exceptions.SSLError):
return False
@ -1982,22 +1975,21 @@ def is_conn_error(e):
return False
if isinstance(e, six.moves.http_client.BadStatusLine):
return True
if requests is not None:
try:
if isinstance(e, requests.exceptions.ConnectionError):
# we see errors like this in keep alive timeout races
# ConnectionError(ProtocolError('Connection aborted.', BadStatusLine("''",)),)
e2 = getattr(e, 'args', [None])[0]
if isinstance(e2, requests.packages.urllib3.exceptions.ProtocolError):
e3 = getattr(e2, 'args', [None, None])[1]
if isinstance(e3, six.moves.http_client.BadStatusLine):
return True
if isinstance(e2, socket.error):
# same check as unwrapped socket error
if getattr(e, 'errno', None) in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
return True
except (TypeError, AttributeError):
pass
try:
if isinstance(e, requests.exceptions.ConnectionError):
# we see errors like this in keep alive timeout races
# ConnectionError(ProtocolError('Connection aborted.', BadStatusLine("''",)),)
e2 = getattr(e, 'args', [None])[0]
if isinstance(e2, requests.packages.urllib3.exceptions.ProtocolError):
e3 = getattr(e2, 'args', [None, None])[1]
if isinstance(e3, six.moves.http_client.BadStatusLine):
return True
if isinstance(e2, socket.error):
# same check as unwrapped socket error
if getattr(e, 'errno', None) in (errno.ECONNRESET, errno.ECONNABORTED, errno.EPIPE):
return True
except (TypeError, AttributeError):
pass
# otherwise
return False
@ -2035,7 +2027,6 @@ def grab_session_options(options):
'upload_blocksize',
'krb_rdns',
'krb_canon_host',
'use_old_ssl',
'no_ssl_verify',
'serverca',
)
@ -2070,24 +2061,13 @@ class ClientSession(object):
self.rsession = None
self.new_session()
self.opts.setdefault('timeout', 60 * 60 * 12)
self._old_ssl_warning = False
def new_session(self):
self.logger.debug("Opening new requests session")
if self.rsession:
self.rsession.close()
if self.opts.get('use_old_ssl', False) or requests is None:
if not self._old_ssl_warning:
# only warn once per instance
self.logger.warn('The use_old_ssl option is deprecated')
self._old_ssl_warning = True
if not six.PY2:
raise GenericError('use_old_ssl is only supported on python2')
import koji.compatrequests
self.rsession = koji.compatrequests.Session()
else:
self.rsession = requests.Session()
self.rsession = requests.Session()
def setSession(self, sinfo):
"""Set the session info
@ -2230,7 +2210,6 @@ class ClientSession(object):
raise PythonImportError(
"Please install python-requests-kerberos to use GSSAPI."
)
# force https
old_baseurl = self.baseurl
uri = six.moves.urllib.parse.urlsplit(self.baseurl)

View file

@ -1,150 +0,0 @@
"""
koji.compatrequests
~~~~~~~~~~~~~~~~~~~
This module contains a *very limited* partial implemention of the requests
module that is based on the older codepaths in koji. It only provides
the bits that koji needs.
"""
import httplib
import urlparse
import urllib
import sys
import ssl.SSLCommon
from warnings import warn
try:
from ssl import ssl as pyssl
except ImportError: # pragma: no cover
pass
class Session(object):
def __init__(self):
self.connection = None
warn('koji: compatrequests is deprecated', DeprecationWarning)
def post(self, url, data=None, headers=None, stream=None, verify=None,
cert=None, timeout=None):
uri = urlparse.urlsplit(url)
if uri[3]:
handler = "%s?%s" % (uri[2], uri[3])
else:
handler = uri[2]
cnx = self.get_connection(uri, cert, verify, timeout)
#cnx.set_debuglevel(1)
cnx.putrequest('POST', handler)
if headers:
for k in headers:
cnx.putheader(k, headers[k])
cnx.endheaders()
if data is not None:
cnx.send(data)
response = cnx.getresponse()
return Response(self, response)
def get_connection(self, uri, cert, verify, timeout):
scheme = uri[0]
host, port = urllib.splitport(uri[1])
key = (scheme, host, cert, verify, timeout)
#if self.connection and self.opts.get('keepalive'):
if self.connection: # XXX honor keepalive
if key == self.connection[0]:
cnx = self.connection[1]
if getattr(cnx, 'sock', None):
return cnx
# Otherwise we make a new one
default_port = 80
certs = {}
if isinstance(verify, basestring):
certs['peer_ca_cert'] = verify
if cert:
certs['key_and_cert'] = cert
ctx = ssl.SSLCommon.CreateSSLContext(certs)
cnxOpts = {'ssl_context' : ctx}
cnxClass = ssl.SSLCommon.PlgHTTPSConnection
default_port = 443
elif scheme == 'https':
cnxOpts = {}
if verify:
if sys.version_info[:3] >= (2, 7, 9):
try:
proto = pyssl.PROTOCOL_TLS
except AttributeError:
proto = pyssl.PROTOCOL_SSLv23
ctx = pyssl.SSLContext(proto)
ctx.load_verify_locations(cafile=verify)
ctx.verify_mode = pyssl.CERT_REQUIRED
cnxOpts['context'] = ctx
else:
cnxOpts['cert_file'] = verify
elif verify is None:
# not specified, leave as default
pass
elif sys.version_info[:3] >= (2, 7, 9):
# no verify
ctx = pyssl._create_unverified_context()
cnxOpts['context'] = ctx
cnxClass = httplib.HTTPSConnection
default_port = 443
elif scheme == 'http':
cnxOpts = {}
cnxClass = httplib.HTTPConnection
else:
raise IOError("unsupported protocol: %s" % scheme)
timeout_compat = False
if timeout:
if sys.version_info[:3] < (2, 6, 0) and 'ssl_context' not in cnxOpts:
timeout_compat = True
else:
cnxOpts['timeout'] = timeout
# no need to close connection
port = (port and int(port) or default_port)
cnx = cnxClass(host, port, **cnxOpts)
self.connection = (key, cnx)
if timeout_compat:
# in python < 2.6 httplib does not support the timeout option
# but socket supports it since 2.3
cnx.connect()
cnx.sock.settimeout(timeout)
return cnx
def close(self):
if self.connection:
self.connection[1].close()
self.connection = None
class Response(object):
def __init__(self, session, response):
self.session = session
self.response = response
def raise_for_status(self):
if self.response.status >= 400:
raise httplib.HTTPException("HTTP %s: %s" % (self.response.status,
self.response.reason))
def iter_content(self, blocksize=1):
# should we check this in Session.post()?
# should we even check this here?
if self.response.status != 200:
if (self.response.getheader("content-length", 0)):
self.response.read()
# XXX wrong exception
raise Exception("Server status: %s" % self.response.status)
while True:
chunk = self.response.read(blocksize)
if not chunk:
break
yield chunk
def close(self):
self.response.close()

View file

@ -1,21 +0,0 @@
FILES = $(wildcard *.py)
PYTHON=python
_default:
@echo "nothing to make. try make install"
clean:
rm -f *.o *.so *.pyc *~
install:
@if [ "$(DESTDIR)" = "" ]; then \
echo " "; \
echo "ERROR: A destdir is required"; \
exit 1; \
fi
mkdir -p $(DESTDIR)
install -p -m 644 $(FILES) $(DESTDIR)
$(PYTHON) -c "import compileall; compileall.compile_dir('$(DESTDIR)', 1, '$(PYDIR)', 1)"

View file

@ -1,80 +0,0 @@
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Library General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2005 Dan Williams <dcbw@redhat.com> and Red Hat, Inc.
import os, sys
from OpenSSL import SSL
import SSLConnection
import httplib
import socket
def our_verify(connection, x509, errNum, errDepth, preverifyOK):
# print("Verify: errNum = %s, errDepth = %s, preverifyOK = %s" % (errNum, errDepth, preverifyOK))
# preverifyOK should tell us whether or not the client's certificate
# correctly authenticates against the CA chain
return preverifyOK
def CreateSSLContext(certs):
key_and_cert = certs['key_and_cert']
peer_ca_cert = certs['peer_ca_cert']
for f in key_and_cert, peer_ca_cert:
if f and not os.access(f, os.R_OK):
raise Exception("%s does not exist or is not readable" % f)
ctx = SSL.Context(SSL.SSLv23_METHOD) # Use best possible TLS Method
ctx.use_certificate_file(key_and_cert)
ctx.use_privatekey_file(key_and_cert)
ctx.load_verify_locations(peer_ca_cert)
verify = SSL.VERIFY_PEER | SSL.VERIFY_FAIL_IF_NO_PEER_CERT
ctx.set_verify(verify, our_verify)
ctx.set_verify_depth(10)
ctx.set_options(SSL.OP_NO_SSLv3 | SSL.OP_NO_SSLv2) # disable SSLv2 and SSLv3
return ctx
class PlgHTTPSConnection(httplib.HTTPConnection):
"This class allows communication via SSL."
response_class = httplib.HTTPResponse
def __init__(self, host, port=None, ssl_context=None, strict=None, timeout=None):
httplib.HTTPConnection.__init__(self, host, port, strict)
self.ssl_ctx = ssl_context
self._timeout = timeout
def connect(self):
for res in socket.getaddrinfo(self.host, self.port, 0, socket.SOCK_STREAM):
af, socktype, proto, canonname, sa = res
try:
sock = socket.socket(af, socktype, proto)
con = SSL.Connection(self.ssl_ctx, sock)
self.sock = SSLConnection.SSLConnection(con)
if sys.version_info[:3] >= (2, 3, 0):
self.sock.settimeout(self._timeout)
self.sock.connect(sa)
if self.debuglevel > 0:
print("connect: (%s, %s) [ssl]" % (self.host, self.port))
except socket.error:
if self.debuglevel > 0:
print('connect fail:', (self.host, self.port))
if self.sock:
self.sock.close()
self.sock = None
continue
break
else:
raise socket.error("failed to connect")

View file

@ -1,162 +0,0 @@
# Higher-level SSL objects used by rpclib
#
# Copyright (c) 2002 Red Hat, Inc.
#
# Author: Mihai Ibanescu <misa@redhat.com>
# Modifications by Dan Williams <dcbw@redhat.com>
from OpenSSL import SSL
import time, socket, select
class SSLConnection:
"""
This whole class exists just to filter out a parameter
passed in to the shutdown() method in SimpleXMLRPC.doPOST()
"""
DEFAULT_TIMEOUT = 600
def __init__(self, conn):
"""
Connection is not yet a new-style class,
so I'm making a proxy instead of subclassing.
"""
self.__dict__["conn"] = conn
self.__dict__["close_refcount"] = 1
self.__dict__["closed"] = False
self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
def __del__(self):
self.__dict__["conn"].close()
def __getattr__(self, name):
return getattr(self.__dict__["conn"], name)
def __setattr__(self, name, value):
setattr(self.__dict__["conn"], name, value)
def settimeout(self, timeout):
if timeout == None:
self.__dict__["timeout"] = self.DEFAULT_TIMEOUT
else:
self.__dict__["timeout"] = timeout
self.__dict__["conn"].settimeout(timeout)
def shutdown(self, how=1):
"""
SimpleXMLRpcServer.doPOST calls shutdown(1),
and Connection.shutdown() doesn't take
an argument. So we just discard the argument.
"""
self.__dict__["conn"].shutdown()
def accept(self):
"""
This is the other part of the shutdown() workaround.
Since servers create new sockets, we have to infect
them with our magic. :)
"""
c, a = self.__dict__["conn"].accept()
return (SSLConnection(c), a)
def makefile(self, mode='r', bufsize=-1):
"""
We need to use socket._fileobject Because SSL.Connection
doesn't have a 'dup'. Not exactly sure WHY this is, but
this is backed up by comments in socket.py and SSL/connection.c
Since httplib.HTTPSResponse/HTTPConnection depend on the
socket being duplicated when they close it, we refcount the
socket object and don't actually close until its count is 0.
"""
self.__dict__["close_refcount"] = self.__dict__["close_refcount"] + 1
return PlgFileObject(self, mode, bufsize)
def close(self):
if self.__dict__["closed"]:
return
self.__dict__["close_refcount"] = self.__dict__["close_refcount"] - 1
if self.__dict__["close_refcount"] == 0:
self.shutdown()
self.__dict__["conn"].close()
self.__dict__["closed"] = True
def sendall(self, data, flags=0):
"""
- Use select() to simulate a socket timeout without setting the socket
to non-blocking mode.
- Don't use pyOpenSSL's sendall() either, since it just loops on WantRead
or WantWrite, consuming 100% CPU, and never times out.
"""
timeout = self.__dict__["timeout"]
con = self.__dict__["conn"]
(read, write, excpt) = select.select([], [con], [], timeout)
if not con in write:
raise socket.timeout((110, "Operation timed out."))
starttime = time.time()
origlen = len(data)
sent = -1
while len(data):
curtime = time.time()
if curtime - starttime > timeout:
raise socket.timeout((110, "Operation timed out."))
try:
sent = con.send(data, flags)
except SSL.SysCallError as e:
if e[0] == 32: # Broken Pipe
self.close()
sent = 0
else:
raise socket.error(e)
except (SSL.WantWriteError, SSL.WantReadError):
time.sleep(0.2)
continue
data = data[sent:]
return origlen - len(data)
def recv(self, bufsize, flags=0):
"""
Use select() to simulate a socket timeout without setting the socket
to non-blocking mode
"""
timeout = self.__dict__["timeout"]
con = self.__dict__["conn"]
(read, write, excpt) = select.select([con], [], [], timeout)
if not con in read:
raise socket.timeout((110, "Operation timed out."))
starttime = time.time()
while True:
curtime = time.time()
if curtime - starttime > timeout:
raise socket.timeout((110, "Operation timed out."))
try:
return con.recv(bufsize, flags)
except SSL.ZeroReturnError:
return None
except SSL.WantReadError:
time.sleep(0.2)
except SSL.SysCallError as e:
if e.args == (-1, 'Unexpected EOF'):
break
raise
return None
class PlgFileObject(socket._fileobject):
def close(self):
"""
socket._fileobject doesn't actually _close_ the socket,
which we want it to do, so we have to override.
"""
try:
if self._sock:
self.flush()
self._sock.close()
finally:
self._sock = None

View file

@ -1,8 +0,0 @@
# identify this as the ssl module
# our own ssl submodule masks python's in the main lib, so we import this here
try:
import ssl # python's ssl module
except ImportError: # pragma: no cover
# ssl module added in 2.6
pass

View file

@ -34,30 +34,17 @@ class TestClientSession(unittest.TestCase):
@mock.patch('requests.Session')
def test_new_session(self, rsession):
opts = {'use_old_ssl': False}
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
koji.ClientSession('http://koji.example.com/kojihub')
# init should have called new_session for us
rsession.assert_called_once()
@mock.patch('requests.Session')
def test_new_session_old(self, rsession):
if six.PY3:
return
opts = {'use_old_ssl': True}
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
# init should have called new_session for us
rsession.assert_not_called()
@mock.patch('requests.Session')
def test_new_session_close(self, rsession):
if six.PY3:
return
opts = {'use_old_ssl': True}
ksession = koji.ClientSession('http://koji.example.com/kojihub', opts)
ksession = koji.ClientSession('http://koji.example.com/kojihub')
my_rsession = mock.MagicMock()
ksession.rsession = my_rsession
@ -69,7 +56,7 @@ class TestClientSession(unittest.TestCase):
class TestFastUpload(unittest.TestCase):
def setUp(self):
self.ksession = koji.ClientSession('http://koji.example.com/kojihub', {})
self.ksession = koji.ClientSession('http://koji.example.com/kojihub')
self.do_fake_login()
# mocks
self.ksession._callMethod = mock.MagicMock()

View file

@ -20,7 +20,6 @@ NORMAL_VAL = {'user': 'jdoe',
'upload_blocksize': 1024,
'krb_rdns': 'fakerdns',
'krb_canon_host': 'fakehost',
'use_old_ssl': False,
'no_ssl_verify': True,
'serverca': '/fake/serverca.cert',
}
@ -43,7 +42,6 @@ NONE_VAL = {'user': None,
'upload_blocksize': None,
'krb_rdns': None,
'krb_canon_host': None,
'use_old_ssl': None,
'no_ssl_verify': None,
'serverca': None,
}

View file

@ -1,267 +0,0 @@
import httplib
import mock
import unittest
import urlparse
import koji.compatrequests
class TestResponse(unittest.TestCase):
def setUp(self):
session = mock.MagicMock()
response = mock.MagicMock()
self.response = koji.compatrequests.Response(session, response)
def tearDown(self):
del self.response
def test_read(self):
self.response.response.status = 200
data = [
"Here's some data",
"Here's some mooore data",
"And look!",
"Here's a nice block of lorem text",
"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do "
"eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut "
"enim ad minim veniam, quis nostrud exercitation ullamco laboris "
"nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor "
"in reprehenderit in voluptate velit esse cillum dolore eu fugiat "
"nulla pariatur. Excepteur sint occaecat cupidatat non proident, "
"sunt in culpa qui officia deserunt mollit anim id est laborum.",
"", #eof
]
self.response.response.read.side_effect = data
result = list(self.response.iter_content(blocksize=10240))
self.assertEqual(result, data[:-1])
rcalls = [mock.call(10240) for s in data]
self.response.response.read.assert_has_calls(rcalls)
self.response.close()
self.response.response.close.assert_called_once()
def test_error(self):
self.response.response.status = 404
self.response.response.getheader.return_value = 0
with self.assertRaises(Exception):
list(self.response.iter_content(8192))
self.response.response.read.assert_not_called()
self.response.response.status = 404
self.response.response.getheader.return_value = 42
with self.assertRaises(Exception):
list(self.response.iter_content(8192))
self.response.response.read.assert_called_once()
self.response.response.status = 404
self.response.response.reason = 'Not Found'
self.response.response.getheader.return_value = 42
with self.assertRaises(httplib.HTTPException):
self.response.raise_for_status()
class TestSessionPost(unittest.TestCase):
def test_simple(self):
session = koji.compatrequests.Session()
url = 'https://www.fakedomain.org/KOJIHUB'
cnx = mock.MagicMock()
session.get_connection = mock.MagicMock()
session.get_connection.return_value = cnx
response = mock.MagicMock()
cnx.getresponse.return_value = response
ret = session.post(url, data="data", headers={"foo": "bar"})
cnx.putrequest.assert_called_once_with('POST', '/KOJIHUB')
cnx.putheader.assert_called_once_with('foo', 'bar')
cnx.send.assert_called_once_with("data")
self.assertEqual(ret.response, response)
def test_less_simple(self):
session = koji.compatrequests.Session()
url = 'https://www.fakedomain.org/KOJIHUB?a=1&b=2'
cnx = mock.MagicMock()
session.get_connection = mock.MagicMock()
session.get_connection.return_value = cnx
response = mock.MagicMock()
cnx.getresponse.return_value = response
ret = session.post(url, data="data", headers={"foo": "bar"},
cert="cert", verify="verify", stream=True, timeout=1701)
cnx.putrequest.assert_called_once_with('POST', '/KOJIHUB?a=1&b=2')
cnx.putheader.assert_called_once_with('foo', 'bar')
cnx.send.assert_called_once_with("data")
self.assertEqual(ret.response, response)
class TestSessionConnection(unittest.TestCase):
@mock.patch('httplib.HTTPConnection')
def test_http(self, HTTPConnection):
# no cert, no verify, no timeout
session = koji.compatrequests.Session()
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
cnx = session.get_connection(uri, None, None, None)
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80)
key = ('http', 'www.fakedomain234234.org', None, None, None)
self.assertEqual(session.connection, (key, cnx))
# and close it
session.close()
self.assertEqual(session.connection, None)
cnx.close.assert_called_with()
# double close should not error
session.close()
def test_cached(self):
session = koji.compatrequests.Session()
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
key = ('http', 'www.fakedomain234234.org', None, None, None)
cnx = mock.MagicMock()
session.connection = (key, cnx)
ret = session.get_connection(uri, None, None, None)
self.assertEqual(ret, cnx)
def test_badproto(self):
session = koji.compatrequests.Session()
url = 'nosuchproto://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
with self.assertRaises(IOError):
ret = session.get_connection(uri, None, None, None)
@mock.patch('httplib.HTTPConnection')
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
def test_timeout(self, HTTPConnection):
# no cert, no verify
session = koji.compatrequests.Session()
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
timeout = 1701
cnx = session.get_connection(uri, None, None, 1701)
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80, timeout=1701)
key = ('http', 'www.fakedomain234234.org', None, None, 1701)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('httplib.HTTPConnection')
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
def test_timeout_compat(self, HTTPConnection):
# no cert, no verify
session = koji.compatrequests.Session()
url = 'http://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
timeout = 1701
cnx = session.get_connection(uri, None, None, 1701)
HTTPConnection.assert_called_once_with('www.fakedomain234234.org', 80)
key = ('http', 'www.fakedomain234234.org', None, None, 1701)
self.assertEqual(session.connection, (key, cnx))
cnx.connect.assert_called_once()
cnx.sock.settimeout.assert_called_with(1701)
@mock.patch('httplib.HTTPSConnection')
def test_https(self, HTTPSConnection):
# no cert, no verify, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
cnx = session.get_connection(uri, None, None, None)
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443)
key = ('https', 'www.fakedomain234234.org', None, None, None)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('koji.ssl.SSLCommon.CreateSSLContext')
@mock.patch('koji.ssl.SSLCommon.PlgHTTPSConnection')
def test_cert(self, PlgHTTPSConnection, CreateSSLContext):
# no verify, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
cert = '/path/to/cert/file'
context = mock.MagicMock()
CreateSSLContext.return_value = context
cnx = session.get_connection(uri, cert, None, None)
PlgHTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, ssl_context=context)
key = ('https', 'www.fakedomain234234.org', cert, None, None)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('ssl._create_unverified_context')
@mock.patch('httplib.HTTPSConnection')
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
def test_unverified(self, HTTPSConnection, create_unverified_context):
# no cert, verify=False, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
context = mock.MagicMock()
create_unverified_context.return_value = context
cnx = session.get_connection(uri, None, False, None)
create_unverified_context.assert_called_once()
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, context=context)
key = ('https', 'www.fakedomain234234.org', None, False, None)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('httplib.HTTPSConnection')
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
def test_unverified_compat(self, HTTPSConnection):
# no cert, verify=False, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
cnx = session.get_connection(uri, None, False, None)
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443)
key = ('https', 'www.fakedomain234234.org', None, False, None)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('ssl._create_unverified_context')
@mock.patch('ssl.SSLContext')
@mock.patch('httplib.HTTPSConnection')
@mock.patch('sys.version_info', new=(2, 7, 12, 'final', 0))
def test_verify(self, HTTPSConnection, SSLContext, create_unverified_context):
# no cert, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
context = mock.MagicMock()
SSLContext.return_value = context
verify = '/path/to/verify/cert'
cnx = session.get_connection(uri, None, verify, None)
create_unverified_context.assert_not_called()
SSLContext.assert_called_once()
context.load_verify_locations.called_once_with(cafile=verify)
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, context=context)
key = ('https', 'www.fakedomain234234.org', None, verify, None)
self.assertEqual(session.connection, (key, cnx))
@mock.patch('ssl._create_unverified_context')
@mock.patch('ssl.SSLContext')
@mock.patch('httplib.HTTPSConnection')
@mock.patch('sys.version_info', new=(2, 4, 3, 'final', 0))
def test_verify_compat(self, HTTPSConnection, SSLContext, create_unverified_context):
# no cert, no timeout
session = koji.compatrequests.Session()
url = 'https://www.fakedomain234234.org/KOJIHUB?a=1&b=2'
uri = urlparse.urlsplit(url)
verify = '/path/to/verify/cert'
cnx = session.get_connection(uri, None, verify, None)
create_unverified_context.assert_not_called()
SSLContext.assert_not_called()
HTTPSConnection.assert_called_once_with('www.fakedomain234234.org', 443, cert_file=verify)
key = ('https', 'www.fakedomain234234.org', None, verify, None)
self.assertEqual(session.connection, (key, cnx))

View file

@ -154,7 +154,6 @@ def get_options():
['unprotected_keys', None, 'string'],
['grace_period', None, 'string'],
['trashcan_tag', None, 'string'],
['use_old_ssl', None, 'boolean'],
['no_ssl_verify', None, 'boolean'],
['timeout', None, 'integer'],
]

View file

@ -744,7 +744,6 @@ def get_options():
'max_retries': 120,
'offline_retry': True,
'offline_retry_interval': 120,
'use_old_ssl': False,
'no_ssl_verify': False,
'max_delete_processes': 4,
'max_repo_tasks' : 4,
@ -767,7 +766,7 @@ def get_options():
str_opts = ('topdir', 'server', 'user', 'password', 'logfile', 'principal', 'keytab', 'krbservice',
'cert', 'ca', 'serverca', 'debuginfo_tags', 'source_tags') # FIXME: remove ca here
bool_opts = ('with_src','verbose','debug','ignore_stray_repos', 'offline_retry',
'krb_rdns', 'krb_canon_host', 'use_old_ssl', 'no_ssl_verify')
'krb_rdns', 'krb_canon_host', 'no_ssl_verify')
for name in config.options(section):
if name in int_opts:
defaults[name] = config.getint(section, name)

View file

@ -127,7 +127,6 @@ def get_options():
'server': None,
'user': None,
'password': None,
'use_old_ssl': False,
'no_ssl_verify': False,
'retry_interval': 60,
'max_retries': 120,
@ -147,7 +146,7 @@ def get_options():
except ValueError:
quit("value for %s option must be a valid integer" % name)
elif name in ['offline_retry', 'krb_rdns', 'krb_canon_host',
'use_old_ssl', 'no_ssl_verify']:
'no_ssl_verify']:
defaults[name] = config.getboolean('kojivmd', name)
elif name in ['plugin', 'plugins']:
defaults['plugin'] = value.split()