ansible / awx

AWX provides a web-based user interface, REST API, and task engine built on top of Ansible. It is one of the upstream projects for Red Hat Ansible Automation Platform.
Other
14.06k stars 3.42k forks source link

SAML Authentication broken on 1.0.4.33 #1418

Closed Exordian closed 6 years ago

Exordian commented 6 years ago
ISSUE TYPE
COMPONENT NAME
SUMMARY

SAML Authentication (which worked in 1.0.2.103) is now broken when the IdP response gets sent to AWX

ENVIRONMENT
STEPS TO REPRODUCE

Fresh Install AWX 1.0.4.33 Configure SAML Authentication using an ADFS IdP Login with SAML

EXPECTED RESULTS

I'm logged in

ACTUAL RESULTS

AWX crashes, see stacktrace below

ADDITIONAL INFORMATION

Even if not authenticated, the User got created

Tested with settings.py

AUTHENTICATION_BACKENDS = (
    'awx.sso.backends.SAMLAuth',
    'django.contrib.auth.backends.ModelBackend',
)

and

AUTHENTICATION_BACKENDS = (
    'awx.sso.backends.SAMLAuth',
)

Stacktrace:

2018/03/01 21:33:27 [warn] 31#0: *24 upstream server temporarily disabled while reading response header from upstream, client: 172.18.0.4, server: _, request: "GET /sso/complete/ HTTP/1.1", upstream: "uwsgi://127.0.0.1:8050", host: "awx.example.com", referrer: "https://logon.example.com/adfs/ls?SAMLRequest=<RESPONSE>&RelayState=IDP"
    new_obj = func(obj, *arg_vals)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/templatetags/rest_framework.py", line 244, in items
    return value.items()
AttributeError: 'NoneType' object has no attribute 'items'
[pid: 42|app: 0|req: 2/8] 172.18.0.4 () {56 vars in 1674 bytes} [Thu Mar  1 21:33:26 2018] GET /sso/complete/ => generated 0 bytes in 271 msecs (HTTP/1.1 500) 0 headers in 0 bytes (0 switches on core 0)
2018-03-01 21:33:27,330 ERROR    django.request Internal Server Error: /sso/complete/
Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner
    return func(*args, **kwargs)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/lib/python2.7/site-packages/awx/sso/views.py", line 42, in dispatch
    auth.login(self.request, self.request.user)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/contrib/auth/__init__.py", line 149, in login
    'You have multiple authentication backends configured and '
ValueError: You have multiple authentication backends configured and therefore must provide the `backend` argument or set the `backend` attribute on the user.
2018-03-01 21:33:27,330 ERROR    django.request Internal Server Error: /sso/complete/
Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner
    response = get_response(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response
    response = self._get_response(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner
    return func(*args, **kwargs)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view
    return self.dispatch(request, *args, **kwargs)
  File "/usr/lib/python2.7/site-packages/awx/sso/views.py", line 42, in dispatch
    auth.login(self.request, self.request.user)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/contrib/auth/__init__.py", line 149, in login
    'You have multiple authentication backends configured and '
ValueError: You have multiple authentication backends configured and therefore must provide the `backend` argument or set the `backend` attribute on the user.
Traceback (most recent call last):
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/wsgi.py", line 157, in __call__
    response = self.get_response(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 124, in get_response
    response = self._middleware_chain(request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 43, in inner
    response = response_for_exception(request, exc)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 93, in response_for_exception
    response = handle_uncaught_exception(request, get_resolver(get_urlconf()), sys.exc_info())
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 143, in handle_uncaught_exception
    return callback(request, **param_dict)
  File "/usr/lib/python2.7/site-packages/awx/main/views.py", line 88, in handle_500
    return handle_error(request, 500, **kwargs)
  File "/usr/lib/python2.7/site-packages/awx/main/views.py", line 56, in handle_error
    return render(request, 'error.html', kwargs, status=status)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/shortcuts.py", line 30, in render
    content = loader.render_to_string(template_name, context, request, using=using)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/loader.py", line 68, in render_to_string
    return template.render(context, request)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/backends/django.py", line 66, in render
    return self.template.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 207, in render
    return self._render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/loader_tags.py", line 177, in render
    return compiled_parent._render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/loader_tags.py", line 177, in render
    return compiled_parent._render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 199, in _render
    return self.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/loader_tags.py", line 72, in render
    result = block.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/loader_tags.py", line 72, in render
    result = block.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/defaulttags.py", line 40, in render
    output = self.nodelist.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 990, in render
    bit = node.render_annotated(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 957, in render_annotated
    return self.render(context)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/defaulttags.py", line 166, in render
    values = self.sequence.resolve(context, True)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/template/base.py", line 736, in resolve
    new_obj = func(obj, *arg_vals)
  File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/rest_framework/templatetags/rest_framework.py", line 244, in items
    return value.items()
AttributeError: 'NoneType' object has no attribute 'items'
anlutro commented 6 years ago

I'm seeing the same in 1.0.4.38, but using Github instead of SAML, but I'm not seeing the "You have multiple authentication backends configured" message in my logs.

ewjoachim commented 6 years ago

Seing the same in 1.4.0.64 with github and I do have the You have multiple authentication backends configured logs

ghost commented 6 years ago

I am also getting the "multiple authentication backends configured "log. But I do see more than one in my settings/all. I am not sure how to remove the TACACSPlus, as I never configured that.

"AUTHENTICATION_BACKENDS": [ "awx.sso.backends.TACACSPlusBackend", "awx.sso.backends.SAMLAuth", "django.contrib.auth.backends.ModelBackend" ],

ghost commented 6 years ago

I can get rid of the TACACS backend by adding this to my settings.py

AUTHENTICATION_BACKENDS = ( 'awx.sso.backends.SAMLAuth', 'django.contrib.auth.backends.ModelBackend', )

However with only those two backends, I am still getting the error:

2018-03-09 19:08:35,981 ERROR django.request Internal Server Error: /sso/complete/ Traceback (most recent call last): File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/exception.py", line 41, in inner response = get_response(request) File "/usr/lib/python2.7/site-packages/awx/wsgi.py", line 65, in _legacy_get_response return super(AWXWSGIHandler, self)._legacy_get_response(request) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 249, in _legacy_get_response response = self._get_response(request) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 187, in _get_response response = self.process_exception_by_middleware(e, request) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/core/handlers/base.py", line 185, in _get_response response = wrapped_callback(request, *callback_args, callback_kwargs) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/utils/decorators.py", line 185, in inner return func(*args, *kwargs) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/views/generic/base.py", line 68, in view return self.dispatch(request, args, kwargs) File "/usr/lib/python2.7/site-packages/awx/sso/views.py", line 42, in dispatch auth.login(self.request, self.request.user) File "/var/lib/awx/venv/awx/lib/python2.7/site-packages/django/contrib/auth/init.py", line 150, in login 'You have multiple authentication backends configured and ' ValueError: You have multiple authentication backends configured and therefore must provide the backend argument or set the backend attribute on the user.

Version "1.0.4.64" running on kubernetes. With SSL termination at loadbalancer

ewjoachim commented 6 years ago

The traceback points to:

  File "/usr/lib/python2.7/site-packages/awx/sso/views.py", line 42, in dispatch
    auth.login(self.request, self.request.user)

My initial guess would be to extract the backend from the informations on the request and add it as backend= in the call ..?

Are you already on it, would it be useful if someone gave a look ?

Could it be related to 1c2621cd60d0e9c986c57236a6e9711d3ee2099f ?

ghost commented 6 years ago

Thank you. It looks like that commit is what adds the troublesome line in views.py. I thought I had seen other users that are able to use SSO. I would be interested in having someone take a look if possible.

ghost commented 6 years ago

Hey Thanks again for the tip. I changed that line to

auth.login(self.request, self.request.user, backend='awx.sso.backends.SAMLAuth')

and it's logging me into Okta. It's just taking me back to the login page so I think I have another problem... But that solved this issue.

ewjoachim commented 6 years ago

Yes but it only works if your backend was SAML. My backend is GitHub, so setting it to SAML won't do :)

piotron commented 6 years ago

I'm seeing the same in 1.0.4.130, but using GoogleOAuth instead of SAML, when changed defaults.py to use only one backend it returns to login screen, then I need to pass any(including totally wrong) credentials and I'm logged in via google account.

aperigault commented 6 years ago

Hi, Same bug in 1.0.5.20. We located the issue between 1.0.4.14 and 1.0.4.29 (docker version). It seems that commit 1c2621c is the root cause.

BenJuan26 commented 6 years ago

Seeing this error with 1.0.5.22. The logs are basically identical to the OP.

calston commented 6 years ago

@jangsutsr

BenJuan26 commented 6 years ago

@hitmenow I tried editing awx/sso/views.py the same way you did and I was also redirected to the login page with no error, neither on the screen nor in the logs. So either we're doing the same things wrong or the one-liner isn't an actual fix for the issue.

Tiduster commented 6 years ago

@BenJuan26 : @piotron explained earlier the next step :

it returns to login screen, then I need to pass any(including totally wrong) credentials and I'm logged in via google account.

It really works this way !

So when we "fix" the auth.login function to remove the "multiple authentication backends" error, we are correctly logged (it seems) but wrongly redirected to the login page :-) .

BenJuan26 commented 6 years ago

@Tiduster Thanks, I missed that comment. I'm curious whether the wrongful login page redirection existed prior to the multiple backends error, or was introduced in 1c2621c with the rest of the changes.

jangsutsr commented 6 years ago

Nice catch on this bug 👍 According to django source, user.backend is supposed to be populated, otherwise the exception block beneath it would try iterate over settings.AUTHENTICATION_BACKENDS, which is most likely longer than 1, and throw multiple backend error.

@rooftopcellist I believe this was originally covered during implementation, meaning user.backend was populated at that time, but some later changes could have erased that effect. Personally I think the solution should be adding some logic before this line to make sure self.request.user.backend is populated with the correct auth backend.

xordoquy commented 6 years ago

user.backend is populated by calling authenticate (Django documentation) before login. Does any other auth backend redirects to the sso_complete ? If not, it should be fixed by adding the backend argument while calling login.

rooftopcellist commented 6 years ago

With the addition to auth.login() mentioned earlier, I am able to successfully log in, but am also seeing an automatic redirect to the login page. The incorrect redirect to the login page can be fixed by adding the following line here response.set_cookie('userLoggedIn', 'true')

Resolved downstream.

Tiduster commented 6 years ago

Thanks @rooftopcellist : we will try it on our system !

sujiar37 commented 6 years ago

@rooftopcellist @ryanpetrello @jangsutsr #1588 these work around are not working for azure ad auth through SSO. just checking, are there any fix available yet ?

rooftopcellist commented 6 years ago

@sujiar37 try removing this redundant auth.login call, it is clearing the user.backend, which is the core of the problem.

Fix coming shortly.

rooftopcellist commented 6 years ago

@sujiar37 @Tiduster @xordoquy @ewjoachim this is fixed with #1800. Drop a comment here if you are still experiencing this issue with this commit.

ewjoachim commented 6 years ago

(Tested, it works now !)

aperigault commented 6 years ago

Problem solved ! Thanks

rooftopcellist commented 6 years ago

Thanks for the feedback!