mesosphere / marathon-lb

Marathon-lb is a service discovery & load balancing tool for DC/OS
Apache License 2.0
449 stars 300 forks source link

Not working correctly with marathon 1.7 with auth enabled #632

Closed eLvErDe closed 5 years ago

eLvErDe commented 5 years ago

Because your patch enabling pycurl to follow redirect fails with 401, pycurl does not send back credentials when redirecting...

I tried to use another approach: if status == 302, read response headers, extract URL and re-do the query. I'm not sure if it's working, it looks so but I'm not sure yet.

--- /usr/share/marathon-lb/utils.py 2018-02-20 15:56:32.000000000 +0000
+++ /tmp/utils.py   2019-04-03 12:35:53.975850101 +0000
@@ -149,6 +149,7 @@
     def __init__(self, url, auth, verify):
         self.url = url
         self.received_buffer = BytesIO()
+        self.received_headers = BytesIO()

         headers = ['Cache-Control: no-cache', 'Accept: text/event-stream']

@@ -157,6 +158,8 @@
         self.curl.setopt(pycurl.ENCODING, 'gzip')
         self.curl.setopt(pycurl.CONNECTTIMEOUT, 10)
         self.curl.setopt(pycurl.WRITEDATA, self.received_buffer)
+        self.curl.setopt(pycurl.HEADERFUNCTION, self.received_headers.write)
+
         if auth and type(auth) is DCOSAuth:
             auth.refresh_auth_header()
             headers.append('Authorization: %s' % auth.auth_header)
@@ -184,10 +187,28 @@
         self.received_buffer.seek(0)
         return result

+    def _get_received_headers(self):
+        result = self.received_headers.getvalue()
+        self.received_headers.truncate(0)
+        self.received_headers.seek(0)
+        return result
+
     def _check_status_code(self):
         if self.status_code == 0:
             self.status_code = self.curl.getinfo(pycurl.HTTP_CODE)
-        if self.status_code != 0 and self.status_code != 200:
+
+        # Got a redirect from Marathon 1.7.x
+        # Do not use pycurl.FOLLOWLOCATION because it won't re-send authorization
+        # and thus, ends up with 401 error
+        if self.status_code == 302:
+            for header_line in self._get_received_headers().split(b'\r\n'):
+                if header_line.startswith(b'Location:'):
+                    new_url = header_line.split(b':', maxsplit=1)[1].strip().decode()
+                    self.url = new_url
+                    self.curlmulti.perform()
+                    self.status_code = self.curl.getinfo(pycurl.HTTP_CODE)
+
+        elif self.status_code not in [0, 200]:
             raise Exception(str(self.status_code) + ' ' + self.url)

     def _perform_on_curl(self):
jkoelker commented 5 years ago

@eLvErDe I believe we just need to set the CURLOPT_UNRESTRICTED_AUTH option which is the libcurl opt for the command line argument --location-trusted. Could you try the patch in PR #633 and see if that fixes the issue as well?