<html>
  <head>
    <meta content="text/html; charset=windows-1252"
      http-equiv="Content-Type">
  </head>
  <body bgcolor="#FFFFFF" text="#000000">
    <div class="moz-cite-prefix">On 08/24/2014 01:55 AM, Yukinori Sagara
      wrote:<br>
      <br>
      Can you please submit this patch to Gerrit.  Taking it off the
      mailing list without a signed contributors agreement is
      problematic.<br>
      <br>
      <br>
    </div>
    <blockquote
cite="mid:CAJfqOF8zyEj9ShF5geqYa3NRqFB5oHC4a8owr_ZE9Ar5eNdO9A@mail.gmail.com"
      type="cite">
      <div dir="ltr">
        <p class="">Hi.</p>
        <p class="">
        </p>
        <p class=""><br>
        </p>
        <p class="">I am trying Heat instance HA, using RDO Icehouse.<br>
        </p>
        <p class="">After instance boot, instance push own stats to heat
          alarm with cfn-push-stats command.<br>
        </p>
        <p class="">But cfn-push-stats always failed with error '403
          SignatureDoesNotMatch', this message is</p>
        <p class="">output to /var/log/cfn-push-stats.log.</p>
        <p class=""><br>
        </p>
        <p class="">I debugged client and server side code. (i.e.
          cfn-push-stats, boto, heat, keystone,</p>
        <p class="">keystoneclient) And I found curious code mismatch
          between boto and keystoneclient about </p>
        <p class="">signature calculation.</p>
        <p class=""><br>
        </p>
        <p class="">Here is a result of debugging, and code examination.</p>
        <p class=""><br>
        </p>
        <p class="">* Client side</p>
        <p class="">cfn-push-stats uses heat-cfntools library, and
          heat-cfntools do 'POST' request with boto.<br>
        </p>
        <p class="">boto perfomes signature calculation. [1]</p>
        <p class="">for signature calculation, firstly it construct
          'CanonicalRequest', some strings are joined.<br>
        </p>
        <p class="">And create a digest hash of the CanonicalRequest for
          signature calculation.</p>
        <p class="">CanonicalRequest contains CanonicalQueryString,
          which is transfomed URL query strings.<br>
        </p>
        <p class=""><br>
        </p>
        <p class="">CanonicalRequest =<br>
        </p>
        <p class="">  HTTPRequestMethod + '\n' +</p>
        <p class="">
            CanonicalURI + '\n' +</p>
        <p class="">  CanonicalQueryString + '\n' +</p>
        <p class="">  CanonicalHeaders + '\n' +</p>
        <p class="">  SignedHeaders + '\n' +</p>
        <p class="">  HexEncode(Hash(RequestPayload))</p>
        <p class=""><br>
        </p>
        <p class="">
        </p>
        <p class="">**AWS original tool (aws-cfn-bootstrap-1.4) and boto
          uses empty string as</p>
        <p class="">CanonicalQueryString, when request is POST.**</p>
        <p class=""><br>
        </p>
        <p class="">AWS original tool's code is following.</p>
        <p class=""><br>
        </p>
        <p class="">
        </p>
        <p class="">cfnbootstrap/aws_client.py<br>
        </p>
        <p class=""><span class="">110 </span>class V4Signer(Signer):<br>
        </p>
        <p class=""><br>
        </p>
        <p class=""><span class="">144 </span>       
          (canonical_headers, signed_headers) =
          self._canonicalize_headers(new_headers)</p>
        <p class=""><span class="">145 </span>        canonical_request
          += canonical_headers + '\n' + signed_headers + '\n'</p>
        <p class="">
        </p>
        <p class=""><span class="">146 </span>        canonical_request
          +=
          hashlib.sha256(self._construct_query(params).encode('utf-8')
          if verb == 'POST' else '').hexdigest()</p>
        <p class=""><br>
        </p>
        <p class="">
          boto's code is following.</p>
        <p class=""><br>
        </p>
        <p class="">boto/auth.py<br>
        </p>
        <p class="">283 class HmacAuthV4Handler(AuthHandler, HmacKeys):<br>
        </p>
        <p class=""><br>
        </p>
        <p class="">393     def canonical_request(self, http_request):</p>
        <p class="">394         cr = [http_request.method.upper()]</p>
        <p class="">395        
          cr.append(self.canonical_uri(http_request))</p>
        <p class="">396        
          cr.append(self.canonical_query_string(http_request))</p>
        <p class="">
          397         headers_to_sign =
          self.headers_to_sign(http_request)</p>
        <p class="">398        
          cr.append(self.canonical_headers(headers_to_sign) + '\n')</p>
        <p class="">399        
          cr.append(self.signed_headers(headers_to_sign))</p>
        <p class="">400         cr.append(self.payload(http_request))</p>
        <p class="">401         return '\n'.join(cr)</p>
        <p class=""><br>
        </p>
        <p class="">337     def canonical_query_string(self,
          http_request):</p>
        <p class="">
          338         # POST requests pass parameters in through the</p>
        <p class="">339         # http_request.body field.</p>
        <p class="">340         if http_request.method == 'POST':</p>
        <p class="">341             return ""</p>
        <p class="">342         l = []</p>
        <p class="">343         for param in
          sorted(http_request.params):</p>
        <p class="">344             value =
          boto.utils.get_utf8_value(http_request.params[param])</p>
        <p class="">345             l.append('%s=%s' %
          (urllib.quote(param, safe='-_.~'),</p>
        <p class="">346                                
          urllib.quote(value, safe='-_.~')))</p>
        <p class="">
        </p>
        <p class="">347         return '&'.join(l)</p>
        <p class=""><br>
        </p>
        <p class="">* Server side<br>
        </p>
        <p class="">heat-api-cfn queries to keystone in order to check
          request authorization.<br>
        </p>
        <p class="">
          keystone uses keystoneclient to check EC2 format request
          signature.</p>
        <p class=""><br>
        </p>
        <p class="">In here, **keystoneclient uses (non-empty) query
          string as CanonicalQueryString, even</p>
        <p class="">though request is POST.**</p>
        <p class="">And create a digest hash of the CanonicalRequest for
          signature calculation.</p>
        <p class=""><br>
        </p>
        <p class="">keystoneclient's code is following.</p>
        <p class=""><br>
        </p>
        <p class="">keystoneclient/contrib/ec2/utils.py<br>
        </p>
        <p class=""> 28 class Ec2Signer(object):<br>
        </p>
        <p class=""><br>
        </p>
        <p class="">154     def _calc_signature_4(self, params, verb,
          server_string, path, headers,</p>
        <p class="">155                           body_hash):</p>
        <p class="">156         """Generate AWS signature version 4
          string."""</p>
        <p class=""><br>
        </p>
        <p class="">235         # Create canonical request:</p>
        <p class="">236         # <a moz-do-not-send="true"
            href="http://docs.aws.amazon.com/general/latest/gr/">http://docs.aws.amazon.com/general/latest/gr/</a></p>
        <p class="">237         # sigv4-create-canonical-request.html</p>
        <p class="">238         # Get parameters and headers in expected
          string format</p>
        <p class="">239         cr = "\n".join((verb.upper(), path,</p>
        <p class="">
          240                         self._canonical_qs(params),</p>
        <p class="">241                         canonical_header_str(),</p>
        <p class="">242                        
          auth_param('SignedHeaders'),</p>
        <p class="">
        </p>
        <p class="">243                         body_hash))</p>
        <p class=""><br>
        </p>
        <p class="">125     @staticmethod</p>
        <p class="">126     def _canonical_qs(params):</p>
        <p class="">127         """Construct a sorted, correctly encoded
          query string as required for</p>
        <p class="">128         _calc_signature_2 and _calc_signature_4.</p>
        <p class="">129         """</p>
        <p class="">130         keys = list(params)</p>
        <p class="">131         keys.sort()</p>
        <p class="">132         pairs = []</p>
        <p class="">133         for key in keys:</p>
        <p class="">134             val =
          Ec2Signer._get_utf8_value(params[key])</p>
        <p class="">135             val = urllib.parse.quote(val,
          safe='-_~')</p>
        <p class="">136             pairs.append(urllib.parse.quote(key,
          safe='') + '=' + val)</p>
        <p class="">137         qs = '&'.join(pairs)</p>
        <p class="">138         return qs</p>
        <p class=""><br>
        </p>
        <p class="">So it should be different from boto(client side) to
          keystoneclient(server side),</p>
        <p class="">
          cfn-push-stats always fails with error log '403
          SignatureDoesNotMatch' in such reason.<br>
        </p>
        <p class=""><br>
        </p>
        <p class="">I wrote a patch to resolve how to treat
          CanonicalQueryString mismatch,</p>
        <p class="">
          My patch honored AWS original tool and boto, so if request is
          POST,</p>
        <p class="">'CanonicalQueryString' is regarded as a empty
          string.</p>
        <p class=""><br>
        </p>
        <p class="">With my patch, Heat instance HA works fine.</p>
        <p class="">
        </p>
        <p class=""><br>
        </p>
        <p class="">This bug affects Heat and Keystone, but patch is
          only needed in python-keystoneclient.</p>
        <p class="">So I will report to python-keystoneclient launchpad
          and submit a patch to Gerrit.</p>
        <p class="">Please confirm it.</p>
        <p class="">----<br>
        </p>
        <p class="">My environment is RDO Icehouse/CentOS6.5, and
          package versions is following.<br>
        </p>
        <p class=""><br>
        </p>
        <p class="">* Client side</p>
        <p class="">cloud-init-0.7.4-2.el6.noarch<br>
        </p>
        <p class="">heat-cfntools-1.2.6-2.el6.noarch</p>
        <p class="">python-boto-2.27.0-1.el6.noarch</p>
        <p class=""><br>
        </p>
        <p class="">* Server side</p>
        <p class="">python-keystoneclient-0.9.0-1.el6.noarch<br>
        </p>
        <p class="">
          python-keystone-2014.1.1-1.el6.noarch</p>
        <p class="">openstack-keystone-2014.1.1-1.el6.noarch</p>
        <p class="">----<br>
        </p>
        <p class="">References<br>
        </p>
        <p class="">[1] <a moz-do-not-send="true"
href="http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html">http://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html</a><br>
        </p>
        <p class=""><br>
        </p>
        <p class="">
        </p>
        <p class="">Thanks,</p>
        <p class="MsoNormal" style="margin:0in 0in
          0.0001pt;font-size:11pt;font-family:Calibri,sans-serif"><span
            style="font-family:arial;font-size:small">Yukinori Sagara</span><br>
        </p>
        <div><br>
        </div>
      </div>
      <br>
      <fieldset class="mimeAttachmentHeader"></fieldset>
      <br>
      <pre wrap="">_______________________________________________
OpenStack-dev mailing list
<a class="moz-txt-link-abbreviated" href="mailto:OpenStack-dev@lists.openstack.org">OpenStack-dev@lists.openstack.org</a>
<a class="moz-txt-link-freetext" href="http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev">http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev</a>
</pre>
    </blockquote>
    <br>
  </body>
</html>