jeremyschulman / netbox-plugin-auth-saml2

Netbox plugin for SSO using SAML2
120 stars 21 forks source link

Redirect Loop on successful authentication #9

Closed bonzi closed 3 years ago

bonzi commented 4 years ago

When logging in to NetBox with this SAML2 plugin, it causes an infinite loop of authentication requests to my IDP (Keycloak), See the attached image and console output for an example of the redirection loop

image

Keycloak reports that my client has successfully authenticated and has sent the correct assertion to NetBox.

(venv) root@MemeMachine:/opt/netbox/netbox# ./manage.py runserver -v 3 0.0.0.0:8001
Watching for file changes with StatReloader
Performing system checks...

System check identified no issues (0 silenced).
July 23, 2020 - 18:56:20
Django version 3.0.8, using settings 'netbox.settings'
Starting development server at http://0.0.0.0:8001/
Quit the server with CONTROL-C.
[23/Jul/2020 18:56:32] "GET /api/plugins/sso/login/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:34] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:34] "GET /admin HTTP/1.0" 301 0
[23/Jul/2020 18:56:34] "GET /admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:34] "GET /api/plugins/sso/login/?next=/admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:35] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:35] "GET /admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:35] "GET /api/plugins/sso/login/?next=/admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:36] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:36] "GET /admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:36] "GET /api/plugins/sso/login/?next=/admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:37] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:37] "GET /admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:37] "GET /api/plugins/sso/login/?next=/admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:38] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:38] "GET /admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:38] "GET /api/plugins/sso/login/?next=/admin/ HTTP/1.0" 302 0
[23/Jul/2020 18:56:39] "POST /api/plugins/sso/acs/ HTTP/1.0" 302 0
Forbidden (Permission denied): /api/plugins/sso/acs/
Traceback (most recent call last):
  File "/opt/netbox-2.8.7/venv/lib/python3.7/site-packages/django/core/handlers/exception.py", line 34, in inner
    response = get_response(request)
  File "/opt/netbox-2.8.7/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 115, in _get_response
    response = self.process_exception_by_middleware(e, request)
  File "/opt/netbox-2.8.7/venv/lib/python3.7/site-packages/django/core/handlers/base.py", line 113, in _get_response
    response = wrapped_callback(request, *callback_args, **callback_kwargs)
  File "/opt/netbox-2.8.7/venv/lib/python3.7/site-packages/django/views/decorators/csrf.py", line 54, in wrapped_view
    return view_func(*args, **kwargs)
  File "/opt/netbox-2.8.7/venv/lib/python3.7/site-packages/django3_auth_saml2/views.py", line 80, in sso_acs
    raise PermissionDenied(errmsg)
django.core.exceptions.PermissionDenied: SAML2: missing response
[23/Jul/2020 18:56:39] "GET /api/plugins/sso/acs/ HTTP/1.0" 403 9454

My config looks like this:

...
PLUGINS = ['django3_saml2_nbplugin']

PLUGINS_CONFIG = {
       'django3_saml2_nbplugin': {

        # Use the Netbox default remote backend
#        'AUTHENTICATION_BACKEND': 'django.contrib.auth.backends.RemoteUserBackend',
    'AUTHENTICATION_BACKEND': 'django3_saml2_nbplugin.backends.SAML2AttrUserBackend',
#   'AUTHENTICATION_BACKEND': 'django3_saml2_nbplugin.backends.SAML2AttrEmailUserBackend', Custom backend, seems to work but this redirection loop is preventing me from making sure it does
#        'AUTHENTICATION_BACKEND': REMOTE_AUTH_BACKEND,

    'DEFAULT_NEXT_URL': '/',

        # Custom URL to validate incoming SAML requests against
        'ASSERTION_URL': 'http://netboxd.testing',

        # Populates the Issuer element in authn reques e.g defined as "Audience URI (SP Entity ID)" in SSO
        'ENTITY_ID': 'http://netboxd.testing',

        # Metadata is required, choose either remote url or local file path
        'METADATA_AUTO_CONF_URL': "https://sso.keycloak.demo/auth/realms/master/protocol/saml/descriptor",

        # NameID Format
#        'NAME_ID_FORMAT': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',

    }
}
...
log1cb0mb commented 4 years ago

@LordBonzi can you share your nginx config for NetBox?

bonzi commented 4 years ago
server {
    listen ip:80;

    # CHANGE THIS TO YOUR SERVER'S NAME
    server_name netboxd.testing;

    client_max_body_size 25m;

    location /static/ {
        alias /opt/netbox/netbox/static/;
    }

    location / {
        proxy_pass http://127.0.0.1:8001;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-Proto $scheme;
    proxy_set_header Host $host; 

    }

    location /login/ {
        proxy_pass http://127.0.0.1:8001/api/plugins/sso/login/;
    }    

    location /sso/ {
        proxy_pass http://127.0.0.1:8001/api/plugins/sso/;  # Must have a trailing slash to strip the original path
    }
}
(venv) root@MemeMachine:/opt/netbox/netbox# 
log1cb0mb commented 4 years ago

And what URL did you configure as Destination/Redirection URL on keycloak side for the app?

bonzi commented 4 years ago

screencapture-sso-as207960-net-auth-admin-master-console-1595533690336

log1cb0mb commented 4 years ago

Those are correct as well, not sure what else could be causing this. May be try not using DEFAULT_NEXT_URL even though it should get overwritten and not sure if that would cause this but worth a try.

bonzi commented 4 years ago

Even with the DEFAULT_NEXT_URL commented out, It still loops.

jeremyschulman commented 4 years ago

Hi @LordBonzi - apologize for the delay responding Hi @log1cb0mb - thanks for jumping in to assist!

@LordBonzi - I could not tell from your screenshots if you had configured your KeyCloak system to provide the specific SAML attributes the code is looking for in that backend.
https://github.com/jeremyschulman/netbox-plugin-auth-saml2/blob/master/django3_saml2_nbplugin/backends.py#L32

Specifically:

image

The KeyCloak system needs to provide the first_name and last_name values as attributes in the response payload. Could you please confirm that the system is configured accordingly?

Another approach is to change your plugin config to use the other backend: https://github.com/jeremyschulman/netbox-plugin-auth-saml2/blob/master/django3_saml2_nbplugin/backends.py#L12

In which case only the email-address attribute is used.

Hope this helps!

bonzi commented 4 years ago

Hi @jeremyschulman,

Here is a list of all attributes sent to the SP.

image

SAML2DottedEmailUserBackend does not work for our use case as we do not use first_name.last_name as our email schema. However, our usernames do match the beginning of our email. e.g. bonzi@netboxd.testing, jeremy@netbox.testing, etc

Here is a full dump of the SAML attributes returned to NetBox:

...
    <saml:AttributeStatement>
      <saml:Attribute FriendlyName="nb-sso email" Name="email" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">bonzi@netboxd.testing</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="email" Name="urn:oid:1.2.840.113549.1.9.1" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">bonzi@netboxd.testing</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="nb-sso last-name" Name="last_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Foster</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="nb-saml2 first_name" Name="first_name" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Alfie</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="surname" Name="urn:oid:2.5.4.4" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Foster</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute FriendlyName="givenName" Name="urn:oid:2.5.4.42" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:basic">
        <saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">Alfie</saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
...

My SSO server returns Email, First name and Lastname, both in standard SAML terms (using urn:oid's) and the ways specified by you.

This can create accounts, as witnessed when disabling SSO and entering the admin panel manually. See below image. image

the alfie.foster account was generated by the SAML2AttrUserBackend backend, yet is stuck in the loop. Therefore proving my SSO server send the correct attributes.

I hope this helps.

log1cb0mb commented 4 years ago

I am starting to think that it might be related to the fact that configuration.py probably has two different backends. You are defining plugin one directly in PLUGIN_CONFIG and then what about NetBox system level parameter i.e REMOTE_AUTH_BACKEND = 'backend_name'. I am hoping that either only one is defined (considering NetBox accepts that if only plugin config is calling backend) or you have two different backends?

How about you define:

REMOTE_AUTH_BACKEND = 'django3_saml2_nbplugin.backends.SAML2AttrUserBackend'

and then in PLUGIN_CONFIG below just call that variable:

'AUTHENTICATION_BACKEND': REMOTE_AUTH_BACKEND

I suspect even though plugin is creating user profile as per plugin_config but NetBox app itself is looking to verify user against: REMOTE_AUTH_BACKEND

bonzi commented 4 years ago

OK, apparently that worked like a charm! Now my only gripe is the lack of an email user as username backend as then I'd be able to log in to my existing accounts

I'd most certainly used that exact conf before, so something else must be at play but I won't try to fix what's not technically broken.

Thanks for your help, I shall open another issue/pr to add an email user as username backend.

log1cb0mb commented 4 years ago

Perfect! And email as username? Isnt that already there, the first backend available in this plugin backend called: SAML2DottedEmailUserBackend

Also, FYI the one that you are currently using has a small caveat which i explained here: https://github.com/jeremyschulman/netbox-plugin-auth-saml2/issues/8#issuecomment-663051804

bonzi commented 4 years ago

SAML2DottedEmailUserBackend would not work for our use case as it extrapolates a person's name based off of the email

For example, an email of bonzi@netboxd.testing lacks a . in the middle of the name for it to split to get their name. What would be needed is taking the Firstname/Lastname generation from the SAML2AttrUserBackend backend, but keeping the extracting the username from the email, (e.g. bonzi from bonzi@netboxd.testing) as this matches our normal username/email schema.

jeremyschulman commented 3 years ago

closing issue as I believe it is resolved. we can re-open if not.