Bug 1578347

Summary: [OSP12] Heat in DEBUG logs private keys when a template creates a keypair
Product: Red Hat OpenStack Reporter: Matthew Booth <mbooth>
Component: openstack-keystoneAssignee: John Dennis <jdennis>
Status: CLOSED WONTFIX QA Contact: nlevinki <nlevinki>
Severity: medium Docs Contact:
Priority: high    
Version: 12.0 (Pike)CC: apevec, berrange, dasmith, dciabrin, eglynn, jhakimra, jruzicka, kchamart, lhh, lyarwood, mbooth, mburns, mkrcmari, nkinder, nova-maint, ojanas, pablo.iranzo, sbaker, sbauza, sgordon, shardy, slinaber, srevivo, vromanso, vstinner
Target Milestone: z4Keywords: Triaged, ZStream
Target Release: 12.0 (Pike)   
Hardware: Unspecified   
OS: Unspecified   
Whiteboard:
Fixed In Version: Doc Type: Bug Fix
Doc Text:
If Nova or Heat is configured to log at the DEBUG log level, private keys were logged as clear text when a keypair was created. oslo.utils now hides private keys in logs.
Story Points: ---
Clone Of: 1575945
: 1612132 (view as bug list) Environment:
Last Closed: 2018-12-13 16:27:27 UTC Type: Bug
Regression: --- Mount Type: ---
Documentation: --- CRM:
Verified Versions: Category: ---
oVirt Team: --- RHEL 7.3 requirements from Atomic Host:
Cloudforms Team: --- Target Upstream Version:
Embargoed:
Bug Depends On: 1575945, 1612132    
Bug Blocks: 1578343, 1578346    

Comment 1 Matthew Booth 2018-05-15 11:23:29 UTC
Submitted a backport for upstream Ocata: https://review.openstack.org/568557

Comment 2 Matthew Booth 2018-05-15 11:25:31 UTC
Should have been upstream Pike: https://review.openstack.org/568558

Comment 3 Victor Stinner 2018-06-05 13:11:15 UTC
I requested Release oslo.utils 3.28.3 for Pike:
https://review.openstack.org/#/c/572383/

Comment 4 Victor Stinner 2018-06-06 17:00:41 UTC
python-oslo-utils-3.28.3-1.el7ost package is now available for tests.

Comment 10 Victor Stinner 2018-07-03 13:37:10 UTC
> "private_key": "-----BEGIN RSA PRIVATE ..."

Oh, I confirm that the fix doesn't work.

"heat-engine.log": so the log comes from Heat, and Heat is supposed to use the oslo_utils module installed on the system.

I checked the package and it includes the patch. I looked again at the bug upstream, and there is a single fix, the one that I backported.

(1) Can you please test manually to call oslo_utils.strutils.mask_password()?

$ python -c 'from oslo_utils import strutils; print(strutils.mask_password({"keypair": {"private_key": "secret"}}))'

{'keypair': {'private_key': '***'}}


(2) Can you please also check if my patch is correctly applied on the system?

$ grep '_SANITIZE_KEYS =' -A3 $(.tox/py3/bin/python -c 'from oslo_utils import strutils; print(strutils.__file__.replace(".pyc", ".py"))')

_SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password',
                  'auth_token', 'new_pass', 'auth_password', 'secret_uuid',
                  'secret', 'sys_pswd', 'token', 'configdrive',
                  'CHAPPASSWORD', 'encrypted_key', 'private_key']

The fix is the addition of the last item to this _SANITIZE_KEYS list: 'private_key'.

Comment 11 Marian Krcmarik 2018-07-12 12:05:10 UTC
(In reply to Victor Stinner from comment #10)
> > "private_key": "-----BEGIN RSA PRIVATE ..."
> 
> Oh, I confirm that the fix doesn't work.
> 
> "heat-engine.log": so the log comes from Heat, and Heat is supposed to use
> the oslo_utils module installed on the system.
> 
> I checked the package and it includes the patch. I looked again at the bug
> upstream, and there is a single fix, the one that I backported.
> 
> (1) Can you please test manually to call oslo_utils.strutils.mask_password()?
> 
> $ python -c 'from oslo_utils import strutils;
> print(strutils.mask_password({"keypair": {"private_key": "secret"}}))'
> 
> {'keypair': {'private_key': '***'}}
That's successful - The password is astrerixed as expected when I try the command you provided.
> 
> 
> (2) Can you please also check if my patch is correctly applied on the system?
> 
> $ grep '_SANITIZE_KEYS =' -A3 $(.tox/py3/bin/python -c 'from oslo_utils
> import strutils; print(strutils.__file__.replace(".pyc", ".py"))')
> 
> _SANITIZE_KEYS = ['adminPass', 'admin_pass', 'password', 'admin_password',
>                   'auth_token', 'new_pass', 'auth_password', 'secret_uuid',
>                   'secret', 'sys_pswd', 'token', 'configdrive',
>                   'CHAPPASSWORD', 'encrypted_key', 'private_key']
> 
> The fix is the addition of the last item to this _SANITIZE_KEYS list:
> 'private_key'.
That's okay too, the patch seems to be applied

I ain't a coder but I think that mask_password() from strutils is not being used at all when the message is being logged. The log message comes from _http_log_response() from keystoneauth1/session which looks like following on OSP12:

def _http_log_response(self, response=None, json=None,
                           status_code=None, headers=None, text=None,
                           logger=_logger):
        if not logger.isEnabledFor(logging.DEBUG):
            return

        if response is not None:
            if not status_code:
                status_code = response.status_code
            if not headers:
                headers = response.headers
            if not text:
                # NOTE(samueldmq): If the response does not provide enough info
                # about the content type to decide whether it is useful and
                # safe to log it or not, just do not log the body. Trying to
                # read the response body anyways may result on reading a long
                # stream of bytes and getting an unexpected MemoryError. See
                # bug 1616105 for further details.
                content_type = response.headers.get('content-type', None)

                # NOTE(lamt): Per [1], the Content-Type header can be of the
                # form Content-Type := type "/" subtype *[";" parameter]
                # [1] https://www.w3.org/Protocols/rfc1341/4_Content-Type.html
                for log_type in _LOG_CONTENT_TYPES:
                    if content_type is not None and content_type.startswith(
                            log_type):
                        text = self._remove_service_catalog(response.text)
                        break
                else:
                    text = ('Omitted, Content-Type is set to %s. Only '
                            '%s responses have their bodies logged.')
                    text = text % (content_type, ', '.join(_LOG_CONTENT_TYPES))
        if json:
            text = self._json.encode(json)

        string_parts = ['RESP:']

        if status_code:
            string_parts.append('[%s]' % status_code)
        if headers:
            for header in headers.items():
                string_parts.append('%s: %s' % self._process_header(header))
        if text:
            string_parts.append('\nRESP BODY: %s\n' % text)

        logger.debug(' '.join(string_parts))


I cannot see mask_password() being used anyweher, I would expect (probably wrongly) to see sth like:
string_parts.append('\nRESP BODY: %s\n' % strutils.mask_password(text))
at the line before the last line.

Comment 12 Victor Stinner 2018-07-12 14:06:54 UTC
> I ain't a coder but I think that mask_password() from strutils is not being used at all when the message is being logged. (...)

Oh, you're right. It seems like the upstream has been closed too early, I commented it to explain that keystoneauth still should be fixed:
https://bugs.launchpad.net/oslo.utils/+bug/1770683

I reassigned this issue to the openstack-keystone component, since keystoneauth should be patched to use mask_password().

Comment 13 Victor Stinner 2018-07-12 14:10:06 UTC
I reset the issue status to NEW.

Comment 16 Damien Ciabrini 2018-08-03 14:34:46 UTC
Since that bug requires a fix in both python-oslo-utils and openstack-keystone, I have just clone it [1] to track the python-oslo-utils fix in a dedicated bz.

[1] https://bugzilla.redhat.com/show_bug.cgi?id=1612132