Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Access Denied / Missing SAML response #61

Open
tacerus opened this issue May 30, 2022 · 4 comments
Open

Access Denied / Missing SAML response #61

tacerus opened this issue May 30, 2022 · 4 comments

Comments

@tacerus
Copy link

tacerus commented May 30, 2022

Hi!

I think the issue is more related with https://github.com/jeremyschulman/django3-auth-saml2, but since I'm only using this Netbox plugin with it I hope it fits here.

I configured the plugin with Keycloak, and followed the advice and applied settings as per the screenshots from the nice fellow over in #9.

The authentication itself works - Netbox login forwards to Keycloak, and after logging in there, it forwards back to Netbox - however, Netbox returns "Access Denied - You do not have permission to access this page.":

Screenshot

image

After enabling debug mode in Netbox, I notice the following log output: gunicorn[30354]: django.core.exceptions.PermissionDenied: ('SAML2: missing response').

Upon studying your code, it seems this is thrown even before the individual mapped attributes are parsed, and with the comment in https://github.com/jeremyschulman/django3-auth-saml2/blob/48a5084c24556340236350bf8008b71ec5105061/django3_auth_saml2/views.py#L68, it seems to me as if the POST response data passed to the plugin is simply empty - however, I am able to find POST response data in my browser:

Screenshot

image

This is the SAML representation logged by Keycloak during the process:

Representation
{
  "id": "x",
  "clientId": "NetBox",
  "surrogateAuthRequired": false,
  "enabled": true,
  "alwaysDisplayInConsole": true,
  "clientAuthenticatorType": "client-secret",
  "redirectUris": [
    "https://netbox.intranet.squirrelcube.com/sso/acs/"
  ],
  "webOrigins": [],
  "notBefore": 0,
  "bearerOnly": false,
  "consentRequired": false,
  "standardFlowEnabled": true,
  "implicitFlowEnabled": false,
  "directAccessGrantsEnabled": false,
  "serviceAccountsEnabled": false,
  "publicClient": false,
  "frontchannelLogout": true,
  "protocol": "saml",
  "attributes": {
    "saml_assertion_consumer_url_redirect": "https://netbox.intranet.squirrelcube.com/sso/acs/",
    "saml.force.post.binding": "true",
    "saml.multivalued.roles": "false",
    "frontchannel.logout.session.required": "false",
    "oauth2.device.authorization.grant.enabled": "false",
    "backchannel.logout.revoke.offline.tokens": "false",
    "saml.server.signature.keyinfo.ext": "false",
    "use.refresh.tokens": "true",
    "saml.signing.certificate": "x",
    "oidc.ciba.grant.enabled": "false",
    "backchannel.logout.session.required": "false",
    "client_credentials.use_refresh_token": "false",
    "saml.signature.algorithm": "RSA_SHA256",
    "require.pushed.authorization.requests": "false",
    "saml.client.signature": "false",
    "saml.signing.private.key": "x",
    "saml.allow.ecp.flow": "false",
    "id.token.as.detached.signature": "false",
    "saml.assertion.signature": "true",
    "client.secret.creation.time": "1653834855",
    "saml.encrypt": "false",
    "saml_assertion_consumer_url_post": "https://netbox.intranet.squirrelcube.com/sso/acs/",
    "saml.server.signature": "true",
    "exclude.session.state.from.auth.response": "false",
    "saml.artifact.binding.identifier": "x",
    "saml.artifact.binding": "false",
    "saml_force_name_id_format": "true",
    "acr.loa.map": "{}",
    "tls.client.certificate.bound.access.tokens": "false",
    "saml.authnstatement": "true",
    "display.on.consent.screen": "false",
    "saml_name_id_format": "username",
    "token.response.type.bearer.lower-case": "false",
    "saml_signature_canonicalization_method": "http://www.w3.org/2001/10/xml-exc-c14n#",
    "saml.onetimeuse.condition": "false"
  },
  "authenticationFlowBindingOverrides": {},
  "fullScopeAllowed": true,
  "nodeReRegistrationTimeout": -1,
  "protocolMappers": [
    {
      "id": "x",
      "name": "username",
      "protocol": "saml",
      "protocolMapper": "saml-user-property-mapper",
      "consentRequired": false,
      "config": {
        "attribute.nameformat": "URI Reference",
        "user.attribute": "username",
        "attribute.name": "username"
      }
    },
    {
      "id": "x",
      "name": "lastName",
      "protocol": "saml",
      "protocolMapper": "saml-user-property-mapper",
      "consentRequired": false,
      "config": {
        "attribute.nameformat": "URI Reference",
        "user.attribute": "lastName",
        "attribute.name": "last_name"
      }
    },
    {
      "id": "x",
      "name": "groups",
      "protocol": "saml",
      "protocolMapper": "saml-group-membership-mapper",
      "consentRequired": false,
      "config": {
        "single": "true",
        "attribute.nameformat": "URI Reference",
        "full.path": "false",
        "attribute.name": "member"
      }
    },
    {
      "id": "x",
      "name": "email",
      "protocol": "saml",
      "protocolMapper": "saml-user-property-mapper",
      "consentRequired": false,
      "config": {
        "attribute.nameformat": "URI Reference",
        "user.attribute": "email",
        "attribute.name": "email"
      }
    },
    {
      "id": "x",
      "name": "firstName",
      "protocol": "saml",
      "protocolMapper": "saml-user-property-mapper",
      "consentRequired": false,
      "config": {
        "attribute.nameformat": "URI Reference",
        "user.attribute": "firstName",
        "attribute.name": "first_name"
      }
    }
  ],
  "defaultClientScopes": [
    "role_list"
  ],
  "optionalClientScopes": [],
  "access": {
    "view": true,
    "configure": true,
    "manage": true
  }
}

And this is the configuration I added in Netbox:

Configuration
PLUGINS_CONFIG = {
            'django3_saml2_nbplugin': {
                'AUTHENTICATION_BACKEND': 'django3_saml2_nbplugin.backends.SAML2AttrUserBackend',
                'DEFAULT_NEXT_URL': '/',
                'ASSERTION_URL': 'https://netbox.intranet.squirrelcube.com',
                'ENTITY_ID': 'NetBox',
                'METADATA_LOCAL_FILE_PATH': '/opt/netbox-data/saml.xml',
                'ALWAYS_UPDATE_USER': True,
                'FLAGS_BY_GROUP': {
                  'is_staff': 'syscid_netbox_staff',
                  'is_superuser': 'syscid_netbox_admins'
                }
            }
}

REMOTE_AUTH_ENABLED = True
REMOTE_AUTH_BACKEND = 'django3_saml2_nbplugin.backends.SAML2CustomAttrUserBackend'
REMOTE_AUTH_HEADER = 'HTTP_REMOTE_USER'
REMOTE_AUTH_AUTO_CREATE_USER = True
REMOTE_AUTH_DEFAULT_GROUPS = []
REMOTE_AUTH_DEFAULT_PERMISSIONS = {}

In an attempt to troubleshoot it, I expanded the exception to raise PermissionDenied(errmsg, dir(req), req.GET, req.POST, req.META) - it was not very helpful for me, but including it for completeness:

Long exception

gunicorn[30354]: django.core.exceptions.PermissionDenied: ('SAML2: missing response', ['COOKIES', 'FILES', 'GET', 'META', 'POST', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_cors_enabled', '_current_scheme_host', '_encoding', '_files', '_get_full_path', '_get_post', '_get_raw_host', '_get_scheme', '_initialize_handlers', '_load_post_and_files', '_mark_post_parse_error', '_messages', '_post', '_read_started', '_set_content_type_params', '_set_post', '_stream', '_upload_handlers', 'accepted_types', 'accepts', 'body', 'build_absolute_uri', 'close', 'content_params', 'content_type', 'encoding', 'environ', 'get_full_path', 'get_full_path_info', 'get_host', 'get_port', 'get_signed_cookie', 'headers', 'id', 'is_secure', 'method', 'parse_file_upload', 'path', 'path_info', 'prometheus_after_middleware_event', 'prometheus_before_middleware_event', 'read', 'readline', 'readlines', 'resolver_match', 'scheme', 'session', 'upload_handlers', 'user'], <QueryDict: {}>, <QueryDict: {}>, {'wsgi.errors': <gunicorn.http.wsgi.WSGIErrorsWrapper object at 0x7fcbd10cb220>, 'wsgi.version': (1, 0), 'wsgi.multithread': True, 'wsgi.multiprocess': True, 'wsgi.run_once': False, 'wsgi.file_wrapper': <class 'gunicorn.http.wsgi.FileWrapper'>, 'wsgi.input_terminated': True, 'SERVER_SOFTWARE': 'gunicorn/20.1.0', 'wsgi.input': <gunicorn.http.body.Body object at 0x7fcbd10cb8b0>, 'gunicorn.socket': <socket.socket fd=11, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 8001), raddr=('127.0.0.1', 49538)>, 'REQUEST_METHOD': 'GET', 'QUERY_STRING': '', 'RAW_URI': '/api/plugins/sso/acs/', 'SERVER_PROTOCOL': 'HTTP/1.0', 'HTTP_HOST': '127.0.0.1:8001', 'HTTP_CONNECTION': 'close', 'HTTP_USER_AGENT': 'Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8', 'HTTP_ACCEPT_LANGUAGE': 'en-US,en;q=0.7,de-AT;q=0.3', 'HTTP_ACCEPT_ENCODING': 'gzip, deflate, br', 'HTTP_DNT': '1', 'HTTP_COOKIE': 'csrftoken=x; mellon-intranet=x', 'HTTP_UPGRADE_INSECURE_REQUESTS': '1', 'HTTP_SEC_FETCH_DEST': 'document', 'HTTP_SEC_FETCH_MODE': 'navigate', 'HTTP_SEC_FETCH_SITE': 'cross-site', 'HTTP_SEC_GPC': '1', 'HTTP_X_FORWARDED_PROTO': 'https', 'HTTP_X_FORWARDED_PORT': '443', 'HTTP_X_FORWARDED_FOR': '185.xx.xx.xx', 'HTTP_X_FORWARDED_HOST': 'netbox.intranet.squirrelcube.com', 'HTTP_X_FORWARDED_SERVER': 'netbox.intranet.squirrelcube.com', 'wsgi.url_scheme': 'https', 'REMOTE_ADDR': '127.0.0.1', 'REMOTE_PORT': '49538', 'SERVER_NAME': '127.0.0.1', 'SERVER_PORT': '8001', 'PATH_INFO': '/api/plugins/sso/acs/', 'SCRIPT_NAME': '', 'CSRF_COOKIE': 'x', 'CSRF_COOKIE_NEEDS_UPDATE': False})

Would appreciate any ideas as to what else I could check!

Thanks a lot for any input!

Best,
Georg

@jclbc
Copy link

jclbc commented Mar 2, 2023

hello,
did you tiry with:
'AUTHENTICATION_BACKEND': REMOTE_AUTH_BACKEND,
because you have 2 differents backends in your config, SAML2AttrUserBackend and SAML2CustomAttrUserBackend.
regards,

@tacerus
Copy link
Author

tacerus commented Mar 2, 2023

Thanks a lot for the suggestion. I will try it.

@tacerus
Copy link
Author

tacerus commented Mar 2, 2023

Unfortunately, it does not seem to be valid syntax:

Mar 02 16:15:31 orpheus gunicorn[22363]:     'AUTHENTICATION_BACKEND': REMOTE_AUTH_BACKEND,
Mar 02 16:15:31 orpheus gunicorn[22363]: NameError: name 'REMOTE_AUTH_BACKEND' is not defined

@jclbc
Copy link

jclbc commented Mar 3, 2023

try to move the PLUGINS_CONFIG section after REMOTE_AUTH_ variables

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants