From fa7307e874566ceaf4c083dc82508587de19ed55 Mon Sep 17 00:00:00 2001 From: Ignatious Johnson Christopher Date: Wed, 19 Oct 2016 12:32:17 -0700 Subject: [PATCH] Making certfile/keyfile optional, so that vnc_api can rely on CA or CA/CERT. Change-Id: Iffb9bf9d8cf23fe3943335565bf2adaf878c5df8 Partial-Bug: 1630513 (cherry picked from commit d7407a1fbb0876f0a84a0864824b3eb3c6ef591d) Issue: Password is displayed in the log files of the config daemon, during uncaught exceptions. Fix: cgitb sets sys.excepthook to format uncaught exceptions. Deriving the cgitb Hook and modifying the handle method to mask password along with formatting. Change-Id: I5b4251f2ebe0205465b15430a9ef38ef04b3a634 Closes-Bug: 1626317 (cherry picked from commit 6dc670c851d31b12ffa0f07f418b74705e3b5902) Certificates needs to be chanined and bundled in the order (certfile, keyfile and cacert). 1. Chaining in the certificate in correct order 2. Making certfile/keyfile optional Closes-Bug: 1639426 Closes-Bug: 1630513 Getting certs as argument to the VncApi class and creating unique certbundle for request to different api-servers. Closes-Bug: 1644713 Closes-Bug: 1644707 Change-Id: Ib5e66bfdd27795bd090c3b3b49207241cbc5f0ae (cherry picked from commit df192ce6f9623c628dee975754027f827dbc28d9) (cherry picked from commit d49aec87815d0b881aaec405832c5ac581e29c3d) (cherry picked from commit 18a920da6f4ce95a66565a5e61ed9b5d6af39d4f) Conflicts: src/api-lib/vnc_api.py Adding the missing import, due to cherry-pick from a branch which has import os earlier to commit. Change-Id: Ibbdf7173ffd30d64526a7ecb525c109ff37098a3 Closes-Bug: 1644707 (cherry picked from commit 6223e65dd1ecda43ab6b686a924eaa5d2ff9c035) --- src/api-lib/vnc_api.py | 67 ++++++---- src/config/api-server/db_manage.py | 8 +- src/config/api-server/tests/test_askip.py | 5 +- .../api-server/tests/test_crud_basic.py | 5 +- src/config/api-server/tests/test_ip_alloc.py | 5 +- .../api-server/tests/test_logical_router.py | 4 +- src/config/api-server/tests/test_perms.py | 5 +- src/config/api-server/tests/test_perms2.py | 5 +- src/config/api-server/tests/test_rbac.py | 5 +- .../api-server/tests/test_subnet_ip_count.py | 4 +- src/config/api-server/vnc_auth_keystone.py | 9 +- src/config/api-server/vnc_cfg_api_server.py | 5 +- src/config/common/SConscript | 1 + src/config/common/tests/test_common.py | 10 +- src/config/common/tests/test_discovery.py | 11 +- src/config/common/utils.py | 69 ++--------- src/config/common/vnc_cgitb.py | 116 ++++++++++++++++++ .../device_manager/device_manager.py | 4 +- src/config/schema-transformer/to_bgp.py | 4 +- .../svc-monitor/svc_monitor/svc_monitor.py | 4 +- .../vnc_openstack/vnc_openstack/__init__.py | 10 +- src/discovery/disc_server.py | 4 +- src/discovery/disc_server_zk.py | 4 +- 23 files changed, 222 insertions(+), 142 deletions(-) create mode 100644 src/config/common/vnc_cgitb.py diff --git a/src/api-lib/vnc_api.py b/src/api-lib/vnc_api.py index 01793568e2c..2ec5c09cd08 100644 --- a/src/api-lib/vnc_api.py +++ b/src/api-lib/vnc_api.py @@ -1,6 +1,7 @@ # # Copyright (c) 2013 Juniper Networks, Inc. All rights reserved. # +import os import logging import requests from requests.exceptions import ConnectionError @@ -109,8 +110,8 @@ class VncApi(object): # ssl termination on port 8082(default contrail-api port) _DEFAULT_API_SERVER_CONNECT="http" _DEFAULT_API_SERVER_SSL_CONNECT="https" - _DEFAULT_KS_CERT_BUNDLE="/tmp/keystonecertbundle.pem" - _DEFAULT_API_CERT_BUNDLE="/tmp/apiservercertbundle.pem" + _DEFAULT_KS_CERT_BUNDLE="keystonecertbundle.pem" + _DEFAULT_API_CERT_BUNDLE="apiservercertbundle.pem" # Connection to api-server through Quantum _DEFAULT_WEB_PORT = 8082 @@ -130,7 +131,9 @@ def __init__(self, username=None, password=None, tenant_name=None, auth_token=None, auth_host=None, auth_port=None, auth_protocol = None, auth_url=None, auth_type=None, wait_for_connect=False, api_server_use_ssl=False, - domain_name=None, auth_token_url=None): + domain_name=None, auth_token_url=None, apicertfile=None, + apikeyfile=None, apicafile=None, kscertfile=None, + kskeyfile=None, kscafile=None,): # TODO allow for username/password to be present in creds file self._obj_serializer = self._obj_serializer_diff @@ -164,6 +167,12 @@ def __init__(self, username=None, password=None, tenant_name=None, if use_ssl: self._api_connect_protocol = VncApi._DEFAULT_API_SERVER_SSL_CONNECT + if not api_server_host: + self._web_host = _read_cfg(cfg_parser, 'global', 'WEB_SERVER', + self._DEFAULT_WEB_SERVER) + else: + self._web_host = api_server_host + # keystone self._authn_type = auth_type or \ _read_cfg(cfg_parser, 'auth', 'AUTHN_TYPE', @@ -204,16 +213,24 @@ def __init__(self, username=None, password=None, tenant_name=None, ConfigParser.NoOptionError, ConfigParser.NoSectionError): self._apiinsecure = False - apicertfile=_read_cfg(cfg_parser,'global','certfile','') - apikeyfile=_read_cfg(cfg_parser,'global','keyfile','') - apicafile=_read_cfg(cfg_parser,'global','cafile','') + apicertfile = (apicertfile or + _read_cfg(cfg_parser,'global','certfile','')) + apikeyfile = (apikeyfile or + _read_cfg(cfg_parser,'global','keyfile','')) + apicafile = (apicafile or + _read_cfg(cfg_parser,'global','cafile','')) self._use_api_certs=False - if apicertfile and apikeyfile \ - and apicafile and api_server_use_ssl: + if apicafile and api_server_use_ssl: + certs=[apicafile] + if apikeyfile and apicertfile: certs=[apicertfile, apikeyfile, apicafile] - self._apicertbundle=utils.getCertKeyCaBundle(VncApi._DEFAULT_API_CERT_BUNDLE,certs) - self._use_api_certs=True + apicertbundle = os.path.join( + '/tmp', self._web_host.replace('.', '_'), + VncApi._DEFAULT_API_CERT_BUNDLE) + self._apicertbundle=utils.getCertKeyCaBundle(apicertbundle, + certs) + self._use_api_certs=True # keystone SSL support try: @@ -222,16 +239,24 @@ def __init__(self, username=None, password=None, tenant_name=None, ConfigParser.NoOptionError, ConfigParser.NoSectionError): self._ksinsecure = False - kscertfile=_read_cfg(cfg_parser,'auth','certfile','') - kskeyfile=_read_cfg(cfg_parser,'auth','keyfile','') - kscafile=_read_cfg(cfg_parser,'auth','cafile','') + kscertfile = (kscertfile or + _read_cfg(cfg_parser,'auth','certfile','')) + kskeyfile = (kskeyfile or + _read_cfg(cfg_parser,'auth','keyfile','')) + kscafile = (kscafile or + _read_cfg(cfg_parser,'auth','cafile','')) self._use_ks_certs=False - if kscertfile and kskeyfile and kscafile \ - and self._authn_protocol == 'https': - certs=[kscertfile, kskeyfile, kscafile] - self._kscertbundle=utils.getCertKeyCaBundle(VncApi._DEFAULT_KS_CERT_BUNDLE,certs) - self._use_ks_certs=True + if kscafile and self._authn_protocol == 'https': + certs=[kscafile] + if kskeyfile and kscertfile: + certs=[kscertfile, kskeyfile, kscafile] + kscertbundle = os.path.join( + '/tmp', self._web_host.replace('.', '_'), + VncApi._DEFAULT_KS_CERT_BUNDLE) + self._kscertbundle=utils.getCertKeyCaBundle(kscertbundle, + certs) + self._use_ks_certs=True if 'v2' in self._authn_url: self._authn_body = \ @@ -261,12 +286,6 @@ def __init__(self, username=None, password=None, tenant_name=None, '}' self._user_info = user_info - if not api_server_host: - self._web_host = _read_cfg(cfg_parser, 'global', 'WEB_SERVER', - self._DEFAULT_WEB_SERVER) - else: - self._web_host = api_server_host - if not api_server_port: self._web_port = _read_cfg(cfg_parser, 'global', 'WEB_PORT', self._DEFAULT_WEB_PORT) diff --git a/src/config/api-server/db_manage.py b/src/config/api-server/db_manage.py index 648d206abd8..b02a021917a 100644 --- a/src/config/api-server/db_manage.py +++ b/src/config/api-server/db_manage.py @@ -8,11 +8,11 @@ from netaddr import IPAddress, IPNetwork import argparse from cStringIO import StringIO -import cgitb import kazoo.client import kazoo.exceptions import cfgm_common +from cfgm_common import vnc_cgitb from cfgm_common.utils import cgitb_hook from cfgm_common.ifmap.client import client from cfgm_common.ifmap.request import NewSessionRequest @@ -1452,7 +1452,7 @@ def heal_security_groups_id(self): # end class DatabaseCleaner def db_check(args_str): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') db_checker = DatabaseChecker(args_str) # Mode and node count check across all nodes @@ -1469,7 +1469,7 @@ def db_check(args_str): # end db_check def db_clean(args_str): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') db_cleaner = DatabaseCleaner(args_str) db_cleaner.clean_obj_missing_mandatory_fields() @@ -1484,7 +1484,7 @@ def db_clean(args_str): # end db_clean def db_heal(args_str): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') db_healer = DatabaseHealer(args_str) db_healer.heal_fq_name_index() diff --git a/src/config/api-server/tests/test_askip.py b/src/config/api-server/tests/test_askip.py index 00259d5b2f7..6200b8c6168 100644 --- a/src/config/api-server/tests/test_askip.py +++ b/src/config/api-server/tests/test_askip.py @@ -10,9 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') - import testtools from testtools.matchers import Equals, MismatchError, Not, Contains from testtools import content, content_type, ExpectedException @@ -29,6 +26,8 @@ import vnc_api.gen.vnc_api_test_gen from vnc_api.gen.resource_test import * import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') from test_utils import * diff --git a/src/config/api-server/tests/test_crud_basic.py b/src/config/api-server/tests/test_crud_basic.py index 3f524c03f51..6752a3c5bb1 100644 --- a/src/config/api-server/tests/test_crud_basic.py +++ b/src/config/api-server/tests/test_crud_basic.py @@ -13,9 +13,6 @@ import netaddr import tempfile -import cgitb -cgitb.enable(format='text') - import fixtures import testtools from testtools.matchers import Equals, MismatchError, Not, Contains, LessThan @@ -41,6 +38,8 @@ import cfgm_common from cfgm_common import vnc_plugin_base from cfgm_common import imid +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') from test_utils import * diff --git a/src/config/api-server/tests/test_ip_alloc.py b/src/config/api-server/tests/test_ip_alloc.py index 0015301770a..3d54c51c28e 100644 --- a/src/config/api-server/tests/test_ip_alloc.py +++ b/src/config/api-server/tests/test_ip_alloc.py @@ -10,9 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') - import testtools from testtools.matchers import Equals, MismatchError, Not, Contains from testtools import content, content_type, ExpectedException @@ -29,6 +26,8 @@ import vnc_api.gen.vnc_api_test_gen from vnc_api.gen.resource_test import * import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') from test_utils import * diff --git a/src/config/api-server/tests/test_logical_router.py b/src/config/api-server/tests/test_logical_router.py index 7706b39d312..87b0c152f16 100644 --- a/src/config/api-server/tests/test_logical_router.py +++ b/src/config/api-server/tests/test_logical_router.py @@ -10,8 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') import testtools from testtools.matchers import Equals, MismatchError, Not, Contains @@ -32,6 +30,8 @@ from netaddr import IPNetwork, IPAddress import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') from test_utils import * diff --git a/src/config/api-server/tests/test_perms.py b/src/config/api-server/tests/test_perms.py index 619660663e2..1663bf2377d 100644 --- a/src/config/api-server/tests/test_perms.py +++ b/src/config/api-server/tests/test_perms.py @@ -11,9 +11,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') - import fixtures import testtools from testtools.matchers import Equals, MismatchError, Not, Contains @@ -32,6 +29,8 @@ from vnc_api.vnc_api import * import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') import test_utils diff --git a/src/config/api-server/tests/test_perms2.py b/src/config/api-server/tests/test_perms2.py index 0c3e012dede..b5ce2044407 100644 --- a/src/config/api-server/tests/test_perms2.py +++ b/src/config/api-server/tests/test_perms2.py @@ -10,9 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') - import fixtures import testtools from testtools.matchers import Equals, MismatchError, Not, Contains @@ -34,6 +31,8 @@ from cfgm_common import rest, utils from cfgm_common.rbaclib import * import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') import test_utils diff --git a/src/config/api-server/tests/test_rbac.py b/src/config/api-server/tests/test_rbac.py index 4a6a000f663..9a5989fdb63 100644 --- a/src/config/api-server/tests/test_rbac.py +++ b/src/config/api-server/tests/test_rbac.py @@ -10,9 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') - import fixtures import testtools from testtools.matchers import Equals, MismatchError, Not, Contains @@ -32,6 +29,8 @@ from keystonemiddleware import auth_token from cfgm_common import rest, utils import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') import test_utils diff --git a/src/config/api-server/tests/test_subnet_ip_count.py b/src/config/api-server/tests/test_subnet_ip_count.py index 4bb1632aeb3..229495a02c0 100644 --- a/src/config/api-server/tests/test_subnet_ip_count.py +++ b/src/config/api-server/tests/test_subnet_ip_count.py @@ -10,8 +10,6 @@ import logging import coverage -import cgitb -cgitb.enable(format='text') import testtools from testtools.matchers import Equals, MismatchError, Not, Contains @@ -29,6 +27,8 @@ import vnc_api.gen.vnc_api_test_gen from vnc_api.gen.resource_test import * import cfgm_common +from cfgm_common import vnc_cgitb +vnc_cgitb.enable(format='text') sys.path.append('../common/tests') from test_utils import * diff --git a/src/config/api-server/vnc_auth_keystone.py b/src/config/api-server/vnc_auth_keystone.py index 4278e499731..b1c7766d31d 100644 --- a/src/config/api-server/vnc_auth_keystone.py +++ b/src/config/api-server/vnc_auth_keystone.py @@ -139,10 +139,11 @@ class AuthServiceKeystone(object): def __init__(self, server_mgr, args): _kscertbundle='' - if args.certfile and args.keyfile and args.cafile \ - and args.auth_protocol == 'https': - certs=[args.certfile, args.keyfile, args.cafile] - _kscertbundle=cfgmutils.getCertKeyCaBundle(_DEFAULT_KS_CERT_BUNDLE,certs) + if args.auth_protocol == 'https' and args.cafile: + certs=[args.cafile] + if args.keyfile and args.certfile: + certs=[args.certfile, args.keyfile, args.cafile] + _kscertbundle=cfgmutils.getCertKeyCaBundle(_DEFAULT_KS_CERT_BUNDLE,certs) identity_uri = '%s://%s:%s' % (args.auth_protocol, args.auth_host, args.auth_port) self._conf_info = { 'auth_host': args.auth_host, diff --git a/src/config/api-server/vnc_cfg_api_server.py b/src/config/api-server/vnc_cfg_api_server.py index f19d7234f46..3bbaea11cc8 100644 --- a/src/config/api-server/vnc_cfg_api_server.py +++ b/src/config/api-server/vnc_cfg_api_server.py @@ -34,6 +34,8 @@ from lxml import etree # import GreenletProfiler +from cfgm_common import vnc_cgitb + logger = logging.getLogger(__name__) """ @@ -3575,8 +3577,7 @@ def main(args_str=None): # end main def server_main(args_str=None): - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() #server_main diff --git a/src/config/common/SConscript b/src/config/common/SConscript index b961928724a..30ce74652e5 100644 --- a/src/config/common/SConscript +++ b/src/config/common/SConscript @@ -40,6 +40,7 @@ local_sources = [ 'dependency_tracker.py', 'vnc_api_stats.py', 'ssl_adapter.py', + 'vnc_cgitb.py', ] local_sources_rules = [] for file in local_sources: diff --git a/src/config/common/tests/test_common.py b/src/config/common/tests/test_common.py index 0c337ebdc6c..ad46f007529 100644 --- a/src/config/common/tests/test_common.py +++ b/src/config/common/tests/test_common.py @@ -24,6 +24,7 @@ import cfgm_common.zkclient from cfgm_common.uve.vnc_api.ttypes import VncApiConfigLog from cfgm_common import imid +from cfgm_common import vnc_cgitb from cfgm_common.utils import cgitb_hook from test_utils import * @@ -148,8 +149,7 @@ def launch_disc_server(test_id, listen_ip, listen_port, http_server_port, conf_s args_str = args_str + "--log_local " args_str = args_str + "--log_file discovery_server_%s.log " % test_id - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') with tempfile.NamedTemporaryFile() as conf, tempfile.NamedTemporaryFile() as logconf: cfg_parser = generate_conf_file_contents(conf_sections) @@ -219,8 +219,7 @@ def launch_api_server(test_id, listen_ip, listen_port, http_server_port, args_str = args_str + "--log_local " args_str = args_str + "--log_file api_server_%s.log " %(test_id) - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') with tempfile.NamedTemporaryFile() as conf, tempfile.NamedTemporaryFile() as logconf: cfg_parser = generate_conf_file_contents(conf_sections) @@ -412,8 +411,7 @@ def __init__(self, *args, **kwargs): self.addOnException(self._add_detailed_traceback) def _add_detailed_traceback(self, exc_info): - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') from cStringIO import StringIO tmp_file = StringIO() diff --git a/src/config/common/tests/test_discovery.py b/src/config/common/tests/test_discovery.py index 5d2db3f8595..bcbcff9f21e 100644 --- a/src/config/common/tests/test_discovery.py +++ b/src/config/common/tests/test_discovery.py @@ -27,6 +27,9 @@ import gevent.wsgi import uuid +from cfgm_common import vnc_cgitb + + def lineno(): """Returns the current line number in our program.""" return inspect.currentframe().f_back.f_lineno @@ -103,8 +106,7 @@ def launch_disc_server(listen_ip, listen_port, http_server_port, conf_sections): args_str = args_str + "--log_local " args_str = args_str + "--log_file discovery_server_sandesh.log " - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') with tempfile.NamedTemporaryFile() as conf, tempfile.NamedTemporaryFile() as logconf: cfg_parser = generate_conf_file_contents(conf_sections) @@ -163,12 +165,11 @@ def __init__(self, *args, **kwargs): } def _add_detailed_traceback(self, exc_info): - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') from cStringIO import StringIO tmp_file = StringIO() - cgitb.Hook(format="text", file=tmp_file).handle(exc_info) + vnc_cgitb.Hook(format="text", file=tmp_file).handle(exc_info) tb_str = tmp_file.getvalue() tmp_file.close() self.addDetail('detailed-traceback', content.text_content(tb_str)) diff --git a/src/config/common/utils.py b/src/config/common/utils.py index 9fb45c1fe16..78ee5daaa49 100644 --- a/src/config/common/utils.py +++ b/src/config/common/utils.py @@ -22,76 +22,18 @@ import os -import re +import errno import urllib from collections import OrderedDict import sys -import cgitb import cStringIO import logging - -# Masking of password from openstack/common/log.py -_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] - -# NOTE(ldbragst): Let's build a list of regex objects using the list of -# _SANITIZE_KEYS we already have. This way, we only have to add the new key -# to the list of _SANITIZE_KEYS and we can generate regular expressions -# for XML and JSON automatically. -_SANITIZE_PATTERNS = [] -_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', - r'(<%(key)s>).*?()', - r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', - r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])'] - -for key in _SANITIZE_KEYS: - for pattern in _FORMAT_PATTERNS: - reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) - _SANITIZE_PATTERNS.append(reg_ex) - - -def mask_password(message, secret="***"): - """Replace password with 'secret' in message. - :param message: The string which includes security information. - :param secret: value with which to replace passwords. - :returns: The unicode value of message with the password fields masked. - - For example: - - >>> mask_password("'adminPass' : 'aaaaa'") - "'adminPass' : '***'" - >>> mask_password("'admin_pass' : 'aaaaa'") - "'admin_pass' : '***'" - >>> mask_password('"password" : "aaaaa"') - '"password" : "***"' - >>> mask_password("'original_password' : 'aaaaa'") - "'original_password' : '***'" - >>> mask_password("u'original_password' : u'aaaaa'") - "u'original_password' : u'***'" - """ - if not any(key in message for key in _SANITIZE_KEYS): - return message - - secret = r'\g<1>' + secret + r'\g<2>' - for pattern in _SANITIZE_PATTERNS: - message = re.sub(pattern, secret, message) - return message -# end mask_password +from cfgm_common import vnc_cgitb def cgitb_hook(info=None, **kwargs): - buf = sys.stdout - if 'file' in kwargs: - buf = kwargs['file'] - - local_buf = cStringIO.StringIO() - kwargs['file'] = local_buf - cgitb.Hook(**kwargs).handle(info or sys.exc_info()) - - doc = local_buf.getvalue() - local_buf.close() - buf.write(mask_password(doc)) - buf.flush() + vnc_cgitb.Hook(**kwargs).handle(info or sys.exc_info()) # end cgitb_hook @@ -207,6 +149,11 @@ def getCertKeyCaBundle(bundle, certs): if not bundle_is_stale: return bundle + try: + os.makedirs(os.path.dirname(bundle)) + except OSError as e: + if e.errno != errno.EEXIST: + raise with open(bundle, 'w') as ofile: for cert in certs: with open(cert) as ifile: diff --git a/src/config/common/vnc_cgitb.py b/src/config/common/vnc_cgitb.py new file mode 100644 index 00000000000..d14e469792a --- /dev/null +++ b/src/config/common/vnc_cgitb.py @@ -0,0 +1,116 @@ +"""Traceback formatting with masked password for vnc config daemons. + +To enable this module, do: + + import vnc_cgitb; vnc_cgitb.enable() + +at the top of your script. The optional arguments to enable() are: + + display - if true, tracebacks are displayed in the web browser + logdir - if set, tracebacks are written to files in this directory + context - number of lines of source code to show for each stack frame + format - 'text' or 'html' controls the output format + +By default, tracebacks are displayed but not saved, the context is 5 lines +and the output format is 'html' (for backwards compatibility with the +original use of this module) + +If you have caught an exception and want vnc_cgitb to display it +after masking passwords, call vn_cgitb.handler(). The optional argument to +handler() is a 3-item tuple (etype, evalue, etb) just like the value of +sys.exc_info(). The default handler displays output as text. + +Alternatively, if you have caught an exception and want to collect the +formatted/masked traceback as string and use it , +>>> string_buf = StringIO() +>>> vnc_cgitb.Hook(file=string_buf, format="text").handle(sys.exc_info()) +>>> formatted_tb = string_buf.get_value() + +""" +import re +import sys +import cgitb +import cStringIO + + +# Masking of password from openstack/common/log.py +_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password'] + +# NOTE(ldbragst): Let's build a list of regex objects using the list of +# _SANITIZE_KEYS we already have. This way, we only have to add the new key +# to the list of _SANITIZE_KEYS and we can generate regular expressions +# for XML and JSON automatically. +_SANITIZE_PATTERNS = [] +_FORMAT_PATTERNS = [r'(%(key)s\s*[=]\s*[\"\']).*?([\"\'])', + r'(<%(key)s>).*?()', + r'([\"\']%(key)s[\"\']\s*:\s*[\"\']).*?([\"\'])', + r'([\'"].*?%(key)s[\'"]\s*:\s*u?[\'"]).*?([\'"])'] + +for key in _SANITIZE_KEYS: + for pattern in _FORMAT_PATTERNS: + reg_ex = re.compile(pattern % {'key': key}, re.DOTALL) + _SANITIZE_PATTERNS.append(reg_ex) + + +def mask_password(message, secret="***"): + """Replace password with 'secret' in message. + :param message: The string which includes security information. + :param secret: value with which to replace passwords. + :returns: The unicode value of message with the password fields masked. + + For example: + + >>> mask_password("'adminPass' : 'aaaaa'") + "'adminPass' : '***'" + >>> mask_password("'admin_pass' : 'aaaaa'") + "'admin_pass' : '***'" + >>> mask_password('"password" : "aaaaa"') + '"password" : "***"' + >>> mask_password("'original_password' : 'aaaaa'") + "'original_password' : '***'" + >>> mask_password("u'original_password' : u'aaaaa'") + "u'original_password' : u'***'" + """ + if not any(key in message for key in _SANITIZE_KEYS): + return message + + secret = r'\g<1>' + secret + r'\g<2>' + for pattern in _SANITIZE_PATTERNS: + message = re.sub(pattern, secret, message) + return message + + +class Hook(cgitb.Hook): + """A hook to replace sys.excepthook that masks password from tracebacks. + Derived from the standard cgitb Hook class. + """ + + def handle(self, info=None): + # Format the traceback and store it in StringIO buffer + local_buf = cStringIO.StringIO() + kwargs = {'display': self.display, + 'logdir': self.logdir, + 'context': self.context, + 'file': local_buf, + 'format': self.format, + } + cgitb.Hook(**kwargs).handle(info) + # Retrive the formatted traceback from StringIO buffer, + # mask the passwords and write to the IO + doc = local_buf.getvalue() + local_buf.close() + self.file.write(mask_password(doc)) + self.file.flush() + + +handler = Hook(format='text').handle + + +def enable(display=1, logdir=None, context=5, format="html"): + """Install an exception handler that formats tracebacks as HTML. + + The optional argument 'display' can be set to 0 to suppress sending the + traceback to the browser, and 'logdir' can be set to a directory to cause + tracebacks to be written to files there.""" + sys.excepthook = Hook(display=display, logdir=logdir, + context=context, format=format) diff --git a/src/config/device-manager/device_manager/device_manager.py b/src/config/device-manager/device_manager/device_manager.py index cc709813b07..a817fb12d0d 100644 --- a/src/config/device-manager/device_manager/device_manager.py +++ b/src/config/device-manager/device_manager/device_manager.py @@ -11,7 +11,6 @@ from gevent import monkey monkey.patch_all() from cfgm_common.vnc_kombu import VncKombuClient -import cgitb import sys import argparse import requests @@ -42,6 +41,7 @@ GlobalVRouterConfigDM, FloatingIpDM, InstanceIpDM, DMCassandraDB, PortTupleDM from physical_router_config import PushConfigState from cfgm_common.dependency_tracker import DependencyTracker +from cfgm_common import vnc_cgitb from cfgm_common.utils import cgitb_hook @@ -606,7 +606,7 @@ def run_device_manager(args): def server_main(): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() # end server_main diff --git a/src/config/schema-transformer/to_bgp.py b/src/config/schema-transformer/to_bgp.py index 4fd44463fcf..050609e0775 100644 --- a/src/config/schema-transformer/to_bgp.py +++ b/src/config/schema-transformer/to_bgp.py @@ -18,13 +18,13 @@ sys.setdefaultencoding('UTF8') import requests import ConfigParser -import cgitb import argparse import socket from cfgm_common import vnc_cpu_info +from cfgm_common import vnc_cgitb from cfgm_common.exceptions import * from config_db import * @@ -862,7 +862,7 @@ def main(args_str=None): def server_main(): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() # end server_main diff --git a/src/config/svc-monitor/svc_monitor/svc_monitor.py b/src/config/svc-monitor/svc_monitor/svc_monitor.py index 84444a6b901..d670fc560f1 100644 --- a/src/config/svc-monitor/svc_monitor/svc_monitor.py +++ b/src/config/svc-monitor/svc_monitor/svc_monitor.py @@ -16,7 +16,6 @@ from cfgm_common.zkclient import ZookeeperClient import requests import ConfigParser -import cgitb import cStringIO import argparse @@ -28,6 +27,7 @@ from cfgm_common.imid import * from cfgm_common import importutils from cfgm_common import svc_info +from cfgm_common import vnc_cgitb from cfgm_common.utils import cgitb_hook from config_db import * @@ -887,7 +887,7 @@ def main(args_str=None): def server_main(): - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() # end server_main diff --git a/src/config/vnc_openstack/vnc_openstack/__init__.py b/src/config/vnc_openstack/vnc_openstack/__init__.py index 89cda163795..787dc2ea918 100644 --- a/src/config/vnc_openstack/vnc_openstack/__init__.py +++ b/src/config/vnc_openstack/vnc_openstack/__init__.py @@ -82,10 +82,12 @@ def fill_keystone_opts(obj, conf_sections): obj._kscertbundle='' obj._use_certs=False - if obj._certfile and obj._keyfile and obj._cafile: - certs=[obj._certfile,obj._keyfile,obj._cafile] - obj._kscertbundle=cfgmutils.getCertKeyCaBundle(_DEFAULT_KS_CERT_BUNDLE,certs) - obj._use_certs=True + if obj._certfile: + certs = [obj._certfile] + if obj._keyfile and obj._cafile: + certs=[obj._certfile,obj._keyfile,obj._cafile] + obj._kscertbundle=cfgmutils.getCertKeyCaBundle(_DEFAULT_KS_CERT_BUNDLE,certs) + obj._use_certs=True try: obj._auth_url = conf_sections.get('KEYSTONE', 'auth_url') diff --git a/src/discovery/disc_server.py b/src/discovery/disc_server.py index 15b666b52c0..1f31cd0b035 100644 --- a/src/discovery/disc_server.py +++ b/src/discovery/disc_server.py @@ -52,6 +52,7 @@ from gevent.lock import BoundedSemaphore from cfgm_common.rest import LinkObject +from cfgm_common import vnc_cgitb import disc_auth_keystone @@ -1505,8 +1506,7 @@ def main(args_str=None): # end main def server_main(): - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() # end server_main diff --git a/src/discovery/disc_server_zk.py b/src/discovery/disc_server_zk.py index e71f9f47ef5..5b2445e6056 100644 --- a/src/discovery/disc_server_zk.py +++ b/src/discovery/disc_server_zk.py @@ -45,6 +45,7 @@ from gevent.lock import BoundedSemaphore from cfgm_common.rest import LinkObject +from cfgm_common import vnc_cgitb def obj_to_json(obj): @@ -1134,8 +1135,7 @@ def main(args_str=None): # end main def server_main(): - import cgitb - cgitb.enable(format='text') + vnc_cgitb.enable(format='text') main() # end server_main