Merge "Add workaround for OSError raised by Popen.communicate()"
This commit is contained in:
@@ -21,6 +21,7 @@ If set_subprocess() is not called, this module will pick Python's subprocess
|
|||||||
or eventlet.green.subprocess based on if os module is patched by eventlet.
|
or eventlet.green.subprocess based on if os module is patched by eventlet.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import errno
|
||||||
import hashlib
|
import hashlib
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
@@ -57,6 +58,46 @@ def set_subprocess(_subprocess=None):
|
|||||||
subprocess = _subprocess
|
subprocess = _subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def _check_files_accessible(files):
|
||||||
|
err = None
|
||||||
|
try:
|
||||||
|
for try_file in files:
|
||||||
|
with open(try_file, 'r'):
|
||||||
|
pass
|
||||||
|
except IOError as e:
|
||||||
|
# Catching IOError means there is an issue with
|
||||||
|
# the given file.
|
||||||
|
err = ('Hit OSError in _process_communicate_handle_oserror()\n'
|
||||||
|
'Likely due to %s: %s') % (try_file, e.strerror)
|
||||||
|
|
||||||
|
return err
|
||||||
|
|
||||||
|
|
||||||
|
def _process_communicate_handle_oserror(process, text, files):
|
||||||
|
"""Wrapper around process.communicate that checks for OSError."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
output, err = process.communicate(text)
|
||||||
|
except OSError as e:
|
||||||
|
if e.errno != errno.EPIPE:
|
||||||
|
raise
|
||||||
|
# OSError with EPIPE only occurs with Python 2.6.x/old 2.7.x
|
||||||
|
# http://bugs.python.org/issue10963
|
||||||
|
|
||||||
|
# The quick exit is typically caused by the openssl command not being
|
||||||
|
# able to read an input file, so check ourselves if can't read a file.
|
||||||
|
err = _check_files_accessible(files)
|
||||||
|
if process.stderr:
|
||||||
|
err += process.stderr.read()
|
||||||
|
|
||||||
|
output = ""
|
||||||
|
retcode = -1
|
||||||
|
else:
|
||||||
|
retcode = process.poll()
|
||||||
|
|
||||||
|
return output, err, retcode
|
||||||
|
|
||||||
|
|
||||||
def cms_verify(formatted, signing_cert_file_name, ca_file_name):
|
def cms_verify(formatted, signing_cert_file_name, ca_file_name):
|
||||||
"""Verifies the signature of the contents IAW CMS syntax.
|
"""Verifies the signature of the contents IAW CMS syntax.
|
||||||
|
|
||||||
@@ -73,8 +114,8 @@ def cms_verify(formatted, signing_cert_file_name, ca_file_name):
|
|||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
output, err = process.communicate(formatted)
|
output, err, retcode = _process_communicate_handle_oserror(
|
||||||
retcode = process.poll()
|
process, formatted, (signing_cert_file_name, ca_file_name))
|
||||||
|
|
||||||
# Do not log errors, as some happen in the positive thread
|
# Do not log errors, as some happen in the positive thread
|
||||||
# instead, catch them in the calling code and log them there.
|
# instead, catch them in the calling code and log them there.
|
||||||
@@ -184,8 +225,10 @@ def cms_sign_text(text, signing_cert_file_name, signing_key_file_name):
|
|||||||
stdin=subprocess.PIPE,
|
stdin=subprocess.PIPE,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE)
|
stderr=subprocess.PIPE)
|
||||||
output, err = process.communicate(text)
|
|
||||||
retcode = process.poll()
|
output, err, retcode = _process_communicate_handle_oserror(
|
||||||
|
process, text, (signing_cert_file_name, signing_key_file_name))
|
||||||
|
|
||||||
if retcode or "Error" in err:
|
if retcode or "Error" in err:
|
||||||
LOG.error('Signing error: %s' % err)
|
LOG.error('Signing error: %s' % err)
|
||||||
raise subprocess.CalledProcessError(retcode, "openssl")
|
raise subprocess.CalledProcessError(retcode, "openssl")
|
||||||
|
@@ -28,7 +28,7 @@ TESTDIR = os.path.dirname(os.path.abspath(__file__))
|
|||||||
ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..'))
|
ROOTDIR = os.path.normpath(os.path.join(TESTDIR, '..', '..'))
|
||||||
CERTDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'certs')
|
CERTDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'certs')
|
||||||
CMSDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'cms')
|
CMSDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'cms')
|
||||||
|
KEYDIR = os.path.join(ROOTDIR, 'examples', 'pki', 'private')
|
||||||
|
|
||||||
# @TODO(mordred) This should become a testresources resource attached to the
|
# @TODO(mordred) This should become a testresources resource attached to the
|
||||||
# class
|
# class
|
||||||
@@ -51,9 +51,17 @@ with open(os.path.join(CMSDIR, 'revocation_list.json')) as f:
|
|||||||
REVOCATION_LIST = jsonutils.loads(f.read())
|
REVOCATION_LIST = jsonutils.loads(f.read())
|
||||||
with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f:
|
with open(os.path.join(CMSDIR, 'revocation_list.pem')) as f:
|
||||||
SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()})
|
SIGNED_REVOCATION_LIST = jsonutils.dumps({'signed': f.read()})
|
||||||
with open(os.path.join(CERTDIR, 'signing_cert.pem')) as f:
|
|
||||||
|
SIGNING_CERT_FILE = os.path.join(CERTDIR, 'signing_cert.pem')
|
||||||
|
with open(SIGNING_CERT_FILE) as f:
|
||||||
SIGNING_CERT = f.read()
|
SIGNING_CERT = f.read()
|
||||||
with open(os.path.join(CERTDIR, 'cacert.pem')) as f:
|
|
||||||
|
SIGNING_KEY_FILE = os.path.join(KEYDIR, 'signing_key.pem')
|
||||||
|
with open(SIGNING_KEY_FILE) as f:
|
||||||
|
SIGNING_KEY = f.read()
|
||||||
|
|
||||||
|
SIGNING_CA_FILE = os.path.join(CERTDIR, 'cacert.pem')
|
||||||
|
with open(SIGNING_CA_FILE) as f:
|
||||||
SIGNING_CA = f.read()
|
SIGNING_CA = f.read()
|
||||||
|
|
||||||
UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d"
|
UUID_TOKEN_DEFAULT = "ec6c0710ec2f471498484c1b53ab4f9d"
|
||||||
|
@@ -12,15 +12,100 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
from keystoneclient.common import cms
|
from keystoneclient.common import cms
|
||||||
from keystoneclient import exceptions
|
from keystoneclient import exceptions
|
||||||
|
from keystoneclient.tests import client_fixtures
|
||||||
from keystoneclient.tests import utils
|
from keystoneclient.tests import utils
|
||||||
|
|
||||||
|
|
||||||
class CMSTest(utils.TestCase):
|
class CMSTest(utils.TestCase):
|
||||||
|
|
||||||
|
"""Unit tests for the keystoneclient.common.cms module."""
|
||||||
|
|
||||||
def test_cms_verify(self):
|
def test_cms_verify(self):
|
||||||
self.assertRaises(exceptions.CertificateConfigError,
|
self.assertRaises(exceptions.CertificateConfigError,
|
||||||
cms.cms_verify,
|
cms.cms_verify,
|
||||||
'data',
|
'data',
|
||||||
'no_exist_cert_file',
|
'no_exist_cert_file',
|
||||||
'no_exist_ca_file')
|
'no_exist_ca_file')
|
||||||
|
|
||||||
|
def test_token_to_cms_to_token(self):
|
||||||
|
with open(os.path.join(client_fixtures.CMSDIR,
|
||||||
|
'auth_token_scoped.pem')) as f:
|
||||||
|
AUTH_TOKEN_SCOPED_CMS = f.read()
|
||||||
|
|
||||||
|
self.assertEqual(cms.token_to_cms(client_fixtures.SIGNED_TOKEN_SCOPED),
|
||||||
|
AUTH_TOKEN_SCOPED_CMS)
|
||||||
|
|
||||||
|
tok = cms.cms_to_token(cms.token_to_cms(
|
||||||
|
client_fixtures.SIGNED_TOKEN_SCOPED))
|
||||||
|
self.assertEqual(tok, client_fixtures.SIGNED_TOKEN_SCOPED)
|
||||||
|
|
||||||
|
def test_ans1_token(self):
|
||||||
|
self.assertTrue(cms.is_ans1_token(client_fixtures.SIGNED_TOKEN_SCOPED))
|
||||||
|
self.assertFalse(cms.is_ans1_token('FOOBAR'))
|
||||||
|
|
||||||
|
def test_cms_sign_token_no_files(self):
|
||||||
|
self.assertRaises(subprocess.CalledProcessError,
|
||||||
|
cms.cms_sign_token,
|
||||||
|
client_fixtures.SIGNED_TOKEN_SCOPED,
|
||||||
|
'/no/such/file', '/no/such/key')
|
||||||
|
|
||||||
|
def test_cms_sign_token_success(self):
|
||||||
|
self.assertTrue(
|
||||||
|
cms.cms_sign_token(client_fixtures.SIGNED_TOKEN_SCOPED,
|
||||||
|
client_fixtures.SIGNING_CERT_FILE,
|
||||||
|
client_fixtures.SIGNING_KEY_FILE))
|
||||||
|
|
||||||
|
def test_cms_verify_token_no_files(self):
|
||||||
|
self.assertRaises(exceptions.CertificateConfigError,
|
||||||
|
cms.cms_verify,
|
||||||
|
client_fixtures.SIGNED_TOKEN_SCOPED,
|
||||||
|
'/no/such/file', '/no/such/key')
|
||||||
|
|
||||||
|
def test_cms_verify_token_no_oserror(self):
|
||||||
|
import errno
|
||||||
|
|
||||||
|
def raise_OSError(*args):
|
||||||
|
e = OSError()
|
||||||
|
e.errno = errno.EPIPE
|
||||||
|
raise e
|
||||||
|
|
||||||
|
with mock.patch('subprocess.Popen.communicate', new=raise_OSError):
|
||||||
|
try:
|
||||||
|
cms.cms_verify("x", '/no/such/file', '/no/such/key')
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
self.assertIn('/no/such/file', e.output)
|
||||||
|
self.assertIn('Hit OSError ', e.output)
|
||||||
|
else:
|
||||||
|
self.fail('Expected subprocess.CalledProcessError')
|
||||||
|
|
||||||
|
def test_cms_verify_token_scoped(self):
|
||||||
|
cms_content = cms.token_to_cms(client_fixtures.SIGNED_TOKEN_SCOPED)
|
||||||
|
self.assertTrue(cms.cms_verify(cms_content,
|
||||||
|
client_fixtures.SIGNING_CERT_FILE,
|
||||||
|
client_fixtures.SIGNING_CA_FILE))
|
||||||
|
|
||||||
|
def test_cms_verify_token_scoped_expired(self):
|
||||||
|
cms_content = cms.token_to_cms(
|
||||||
|
client_fixtures.SIGNED_TOKEN_SCOPED_EXPIRED)
|
||||||
|
self.assertTrue(cms.cms_verify(cms_content,
|
||||||
|
client_fixtures.SIGNING_CERT_FILE,
|
||||||
|
client_fixtures.SIGNING_CA_FILE))
|
||||||
|
|
||||||
|
def test_cms_verify_token_unscoped(self):
|
||||||
|
cms_content = cms.token_to_cms(client_fixtures.SIGNED_TOKEN_UNSCOPED)
|
||||||
|
self.assertTrue(cms.cms_verify(cms_content,
|
||||||
|
client_fixtures.SIGNING_CERT_FILE,
|
||||||
|
client_fixtures.SIGNING_CA_FILE))
|
||||||
|
|
||||||
|
def test_cms_verify_token_v3_scoped(self):
|
||||||
|
cms_content = cms.token_to_cms(client_fixtures.SIGNED_v3_TOKEN_SCOPED)
|
||||||
|
self.assertTrue(cms.cms_verify(cms_content,
|
||||||
|
client_fixtures.SIGNING_CERT_FILE,
|
||||||
|
client_fixtures.SIGNING_CA_FILE))
|
||||||
|
Reference in New Issue
Block a user