Opensearch-dashboards OpenID authentication produces error 401 Unauthorized

Versions (relevant - OpenSearch/Dashboard/Server OS/Browser):
OpenSearch and OpenSearch Dashboards version 2.12 (I’ve subsequently upgraded OpenSearch to 2.13, but no change in issue)

RHEL 8.9 servers

This is my dev environment. I have 2 hot, 2 warm, 2 cold storage nodes. 3 coordinating and 3 master nodes. Single OpenSearch Dashboards server

Client is Windows 10, and I have tried with Firefox 124.0.2 (64-bit), Edge 123.0.2420.81 (Official build) (64-bit), and Chrome 123.0.6312.106 (Official Build) (64-bit)

Describe the issue:
We have been successfully authenticating users to ElasticSearch with OpenDistro for several years, and I piloted OpenSearch 2.2.1 last year and was using external authentication with the same configuration. We are in the process of moving to OpenSearch. OpenSearch / OpenDashboards was set up and working with local user accounts.

I added the configuration to use PingID as the OAUTH IdP, and all logon attempts yield 401 errors. I have tried to follow the OpenID troubleshooting instructions and increase logging on every OpenSearch server in the environment:

logger.securityjwt.name = com.amazon.dlic.auth.http.jwt
logger.securityjwt.level = trace

However there are no additional details logged. I’ve tried setting the global logging to trace, but this generates an unusable amount of logging data (and I’m still not finding my logon ID in an attempt to grep across all of the logs). I upgraded the OpenSearch environment from 2.12 to 2.13 to see if that would sort the lack of trace logging, but that didn’t pan out.

Configuration:
OpenSearch Dashboard ./config/opensearch_dashboards.yml

# I normally have an array, but limited the hosts to a single server in an attempt to simply troubleshooting
opensearch.hosts: ["https://SingleOpensearchServer.example.net:9200"]
## PingID config
## Comment out lines below to disable
opensearch_security.auth.type: "openid"
opensearch_security.openid.connect_url: "https://pingid-dev.example.com/.well-known/openid-configuration"
opensearch_security.openid.client_id: "REDACTED-BIG-STRING"
opensearch_security.openid.client_secret: "REDACTED-BIG-STRING"
opensearch_security.openid.scope: "openid"
opensearch_security.openid.header: "Authorization"
opensearch_security.openid.base_redirect_url: "https://opensearchdashboard.example.net:5601/auth/openid/login"

OpenSearch ./config/opensearch-security/config.yml

_meta:
  type: "config"
  config_version: 2

config:
  dynamic:
    http:
      anonymous_auth_enabled: false
      xff:
        enabled: false
        internalProxies: '192\.168\.0\.10|192\.168\.0\.11' # regex pattern
    authc:
      openid_auth_domain:
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: "openid"
          challenge: false
          config:
            openid_connect_idp:
              enable_ssl: true
              verify_hostnames: false
            openid_connect_url: https://pingid-dev.example.com/.well-known/openid-configuration
        authentication_backend:
          type: noop
      kerberos_auth_domain:
        http_enabled: false
        transport_enabled: false
        order: 6
        http_authenticator:
          type: kerberos
          challenge: true
          config:
            krb_debug: false
            strip_realm_from_principal: true
        authentication_backend:
          type: noop
      basic_internal_auth_domain:
        description: "Authenticate via HTTP Basic against internal users database"
        http_enabled: true
        transport_enabled: true
        order: 4
        http_authenticator:
          type: basic
          challenge: true
        authentication_backend:
          type: intern
      proxy_auth_domain:
        description: "Authenticate via proxy"
        http_enabled: false
        transport_enabled: false
        order: 3
        http_authenticator:
          type: proxy
          challenge: false
          config:
            user_header: "x-proxy-user"
            roles_header: "x-proxy-roles"
        authentication_backend:
          type: noop
      jwt_auth_domain:
        description: "Authenticate via Json Web Token"
        http_enabled: false
        transport_enabled: false
        order: 0
        http_authenticator:
          type: jwt
          challenge: false
          config:
            signing_key: "base64 encoded HMAC key or public RSA/ECDSA pem key"
            jwt_header: "Authorization"
            jwt_url_parameter: null
            jwt_clock_skew_tolerance_seconds: 30
            roles_key: null
            subject_key: null
        authentication_backend:
          type: noop
      clientcert_auth_domain:
        description: "Authenticate via SSL client certificates"
        http_enabled: false
        transport_enabled: false
        order: 2
        http_authenticator:
          type: clientcert
          config:
            username_attribute: cn #optional, if omitted DN becomes username
          challenge: false
        authentication_backend:
          type: noop
      ldap:
        description: "Authenticate via LDAP or Active Directory"
        http_enabled: false
        transport_enabled: false
        order: 5
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          # LDAP authentication backend (authenticate users against a LDAP or Active Directory)
          type: ldap
          config:
            enable_ssl: false
            enable_start_tls: false
            enable_ssl_client_auth: false
            verify_hostnames: true
            hosts:
            - localhost:8389
            bind_dn: null
            password: null
            userbase: 'ou=people,dc=example,dc=com'
            usersearch: '(sAMAccountName={0})'
            username_attribute: null
    authz:
      roles_from_myldap:
        description: "Authorize via LDAP or Active Directory"
        http_enabled: false
        transport_enabled: false
        authorization_backend:
          type: ldap
          config:
            enable_ssl: false
            enable_start_tls: false
            enable_ssl_client_auth: false
            verify_hostnames: true
            hosts:
            - localhost:8389
            bind_dn: null
            password: null
            rolebase: 'ou=groups,dc=example,dc=com'
            rolesearch: '(member={0})'
            userroleattribute: null
            userrolename: disabled
            rolename: cn
            resolve_nested_roles: true
            userbase: 'ou=people,dc=example,dc=com'
            usersearch: '(uid={0})'
      roles_from_another_ldap:
        description: "Authorize via another Active Directory"
        http_enabled: false
        transport_enabled: false
        authorization_backend:
          type: ldap

OpenSearch ./config//opensearch.yml

# plugins.security.ssl.transport.truststore_filepath needed to be set for OpenID auth to work
plugins.security.ssl.transport.truststore_filepath: /opt/elk/opensearch_config/certs/cacerts
plugins.security.ssl.transport.truststore_type: JKS
plugins.security.ssl.transport.truststore_password: REDACTED

Relevant Logs or Screenshots:
There are, unfortunately, few relevant logs even with my attempt to enable trace logging on authentication. The only OpenSearch log entries are from o.o.j.s.JobScheduler, o.o.s.a.BackendRegistry, and o.o.i.i.ManagedIndexRunner.

opensearch-dashboards.log

{"type":"response","@timestamp":"2024-04-10T16:20:09Z","tags":[],"pid":2850232,"method":"get","statusCode":401,"req":{"url":"/auth/openid/login/auth/openid/login?code=REDACTED&state=REDACTED","method":"get","headers":{"host":"dashboard.example.net:5601","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0","accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8","accept-language":"en-US,en;q=0.5","accept-encoding":"gzip, deflate, br","referer":"https://pingid-dev.example.com/","dnt":"1","sec-gpc":"1","connection":"keep-alive","upgrade-insecure-requests":"1","sec-fetch-dest":"document","sec-fetch-mode":"navigate","sec-fetch-site":"cross-site","sec-fetch-user":"?1"},"remoteAddress":"10.108.240.186","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0","referer":"https://pingid-dev.example.com/"},"res":{"statusCode":401,"responseTime":4,"contentLength":9},"message":"GET /auth/openid/login/auth/openid/login?code=REDACTED&state=REDACTED 401 4ms - 9.0B"}
{"type":"response","@timestamp":"2024-04-10T16:20:09Z","tags":[],"pid":2850232,"method":"get","statusCode":401,"req":{"url":"/favicon.ico","method":"get","headers":{"host":"dashboard.example.net:5601","user-agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0","accept":"image/avif,image/webp,*/*","accept-language":"en-US,en;q=0.5","accept-encoding":"gzip, deflate, br","dnt":"1","sec-gpc":"1","connection":"keep-alive","referer":"https://dashboard.example.net:5601/auth/openid/login/auth/openid/login?code=REDACTED&state=REDACTED","sec-fetch-dest":"image","sec-fetch-mode":"no-cors","sec-fetch-site":"cross-site"},"remoteAddress":"10.108.240.186","userAgent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:124.0) Gecko/20100101 Firefox/124.0","referer":"https://dashboard.example.net:5601/auth/openid/login/auth/openid/login?code=REDACTED&state=REDACTED"},"res":{"statusCode":401,"responseTime":2,"contentLength":9},"message":"GET /favicon.ico 401 2ms - 9.0B"}

All of the role mappings that we use with OpenDistro are in place, and these worked with our earlier OpenSearch pilot. My ID is mapped to all_access among other custom roles. In case the IdP is providing the userPrincipalName style ID (uid@example.com), I have added version of my ID to the role mapping as well.

When we had piloted OpenSearch, increasing the logging level for IdP authentication worked & I had been able to troubleshoot/resolve issues on my own. Without the increased logging, I am at a loss as to how to proceed.

Hi @lisa5,

Could you run the below and share the output:
curl --insecure -u <admin_username>:<admin_password> -XGET https://<OS_node>:9200/_plugins/_security/api/securityconfig

Could you also share a sample of JWT generated and sent from your Idp to OpenSearch?

Note: please blank any sensitive information when sharing.

Thanks,
mj

Hi! Thanks for the reply. Below is the output from the curl command. I’ve added the subject_key parameter since I created my original post thinking maybe OpenSearch didn’t know what to use as the logon name, but I am still getting 401 errors.

Unfortunately, I don’t know how to get the response my IdP is sending OpenSearch – there’s nothing showing up in the log files, and any communication is encrypted so network traces don’t help. The IdP is managed by a different department. This is a sample decoded token from a custom-coded application where I can just log out all of the responses (decoded the response with https://jwt.io/)

Access Token:

Curl output:

lisa:~ # curl --insecure -u $OSUSER:$OSPASS -XGET https://$(hostname):9200/_plugins/_security/api/securityconfig
{
    "config": {
        "dynamic": {
            "filtered_alias_mode": "warn",
            "disable_rest_auth": false,
            "disable_intertransport_auth": false,
            "respect_request_indices_options": false,
            "kibana": {
                "multitenancy_enabled": true,
                "private_tenant_enabled": true,
                "default_tenant": "",
                "server_username": "kibanaserver",
                "index": ".kibana"
            },
            "http": {
                "anonymous_auth_enabled": false,
                "xff": {
                    "enabled": false,
                    "internalProxies": "192\\.168\\.0\\.10|192\\.168\\.0\\.11",
                    "remoteIpHeader": "X-Forwarded-For"
                }
            },
            "authc": {
                "openid_auth_domain": {
                    "http_enabled": true,
                    "order": 1,
                    "http_authenticator": {
                        "challenge": false,
                        "type": "openid",
                        "config": {
                            "openid_connect_idp": {
                                "enable_ssl": true,
                                "subject_key": "username",
                                "verify_hostnames": false
                            },
                            "openid_connect_url": "https://pingid.example.com/.well-known/openid-configuration"
                        }
                    },
                    "authentication_backend": {
                        "type": "noop",
                        "config": {}
                    }
                },
                "jwt_auth_domain": {
                    "http_enabled": false,
                    "order": 0,
                    "http_authenticator": {
                        "challenge": false,
                        "type": "jwt",
                        "config": {
                            "signing_key": "base64 encoded HMAC key or public RSA/ECDSA pem key",
                            "jwt_header": "Authorization",
                            "jwt_clock_skew_tolerance_seconds": 30
                        }
                    },
                    "authentication_backend": {
                        "type": "noop",
                        "config": {}
                    },
                    "description": "Authenticate via Json Web Token"
                },
                "ldap": {
                    "http_enabled": false,
                    "order": 5,
                    "http_authenticator": {
                        "challenge": false,
                        "type": "basic",
                        "config": {}
                    },
                    "authentication_backend": {
                        "type": "ldap",
                        "config": {
                            "enable_ssl": false,
                            "enable_start_tls": false,
                            "enable_ssl_client_auth": false,
                            "verify_hostnames": true,
                            "hosts": [
                                "localhost:8389"
                            ],
                            "userbase": "ou=people,dc=example,dc=com",
                            "usersearch": "(sAMAccountName={0})"
                        }
                    },
                    "description": "Authenticate via LDAP or Active Directory"
                },
                "basic_internal_auth_domain": {
                    "http_enabled": true,
                    "order": 4,
                    "http_authenticator": {
                        "challenge": true,
                        "type": "basic",
                        "config": {}
                    },
                    "authentication_backend": {
                        "type": "intern",
                        "config": {}
                    },
                    "description": "Authenticate via HTTP Basic against internal users database"
                },
                "proxy_auth_domain": {
                    "http_enabled": false,
                    "order": 3,
                    "http_authenticator": {
                        "challenge": false,
                        "type": "proxy",
                        "config": {
                            "user_header": "x-proxy-user",
                            "roles_header": "x-proxy-roles"
                        }
                    },
                    "authentication_backend": {
                        "type": "noop",
                        "config": {}
                    },
                    "description": "Authenticate via proxy"
                },
                "clientcert_auth_domain": {
                    "http_enabled": false,
                    "order": 2,
                    "http_authenticator": {
                        "challenge": false,
                        "type": "clientcert",
                        "config": {
                            "username_attribute": "cn"
                        }
                    },
                    "authentication_backend": {
                        "type": "noop",
                        "config": {}
                    },
                    "description": "Authenticate via SSL client certificates"
                },
                "kerberos_auth_domain": {
                    "http_enabled": false,
                    "order": 6,
                    "http_authenticator": {
                        "challenge": true,
                        "type": "kerberos",
                        "config": {
                            "krb_debug": false,
                            "strip_realm_from_principal": true
                        }
                    },
                    "authentication_backend": {
                        "type": "noop",
                        "config": {}
                    }
                }
            },
            "authz": {
                "roles_from_another_ldap": {
                    "http_enabled": false,
                    "authorization_backend": {
                        "type": "ldap",
                        "config": {}
                    },
                    "description": "Authorize via another Active Directory"
                },
                "roles_from_myldap": {
                    "http_enabled": false,
                    "authorization_backend": {
                        "type": "ldap",
                        "config": {
                            "enable_ssl": false,
                            "enable_start_tls": false,
                            "enable_ssl_client_auth": false,
                            "verify_hostnames": true,
                            "hosts": [
                                "localhost:8389"
                            ],
                            "rolebase": "ou=groups,dc=example,dc=com",
                            "rolesearch": "(member={0})",
                            "userrolename": "disabled",
                            "rolename": "cn",
                            "resolve_nested_roles": true,
                            "userbase": "ou=people,dc=example,dc=com",
                            "usersearch": "(uid={0})"
                        }
                    },
                    "description": "Authorize via LDAP or Active Directory"
                }
            },
            "auth_failure_listeners": {},
            "do_not_fail_on_forbidden": false,
            "multi_rolespan_enabled": true,
            "hosts_resolver_mode": "ip-only",
            "do_not_fail_on_forbidden_empty": false,
            "on_behalf_of": {
                "enabled": false
            }
        }
    }
}

Hi @lisa5,

It looks like the roles_key is missing from your openid_auth_domain.http_authenticator.config, this should “map” to the key in the JSON payload that stores the user’s roles - that would then be mapped to an OpenSearch role (let me know if you need more details).

More details can be found here as well: OpenID Connect - OpenSearch Documentation

As you have "enable_ssl": true please check the below for full set-up:

Best,
mj

Have you checked the “OpenID Connect troubleshooting” :

Best,
mj

I think this is what I don’t understand about the configuration – in our OpenDistro setup and the pilot of OpenSearch we did last year, we didn’t have any role mappings in the OpenID config. Our local role mappings have user IDs that don’t exist as local accounts.

I go to the OpenSearch server and get redirected to PingID. I log in. PingID redirects me back to OpenSearch saying “yup, that’s lisa5”. Then the OpenSearch server says “oh, hey, lisa5 is mapped to this local all_access role that can do all sorts of things … she’s good to go!” It worked. With the 2.12 and 2.13 iterations, that no longer appears to be the case.

What I wanted to do is have the IdP handle the authentication and OpenSearch handle the authorization. Is that no longer possible? The IdP has to handle both authentication and authorization?

FWIW, I’ve followed the “Troubleshooting OpenID Connect” article as far as I can – the ‘set log level to debug’ doesn’t actually produce any additional logging output. I’ve added the lines at the end of the log4j2.properties file on every server, performed a rolling restart. Even shut down all of the applications, rebooted everything, and brought OpenSearch back online.

lisa:~ # tail -4 /opt/elk/opensearch/config/log4j2.properties
#################################################
logger.securityjwt.name = com.amazon.dlic.auth.http.jwt
logger.securityjwt.level = trace
#################################################

Hi @lisa5

It is indeed possible to map a username to a role too, how do you map “lisa5” to all_access in this example?

i.e:

all_access:
  reserved: true
  hidden: false
  backend_roles:
  - "admin"
  hosts: []
  users: 
  - "lisa5"
  and_backend_roles: []
  description: "Maps admin to all_access"

Could you please run the below and share the output:

curl --insecure -u <admin_username>:<admin_password> -XGET https://<OS_node>:9200/_plugins/_security/api/rolesmapping?pretty

thanks,
mj

I’m glad to hear it is still possible just not working for me at the time! I mapped both the username that I see in other claim tokens (lisa5) and the userPrincipalName version (lisa@example.com except not literally example.com) to the all_access role through the GUI. The output requested is below. Thank you again for your time assisting me with this vexing issue!

lisa:~ # curl --insecure  -XGET -u $OSUSER:$OSPASS https://$(hostname):9200/_plugins/_security/api/rolesmapping?pretty
{
  "resolve_role" : {
    "hosts" : [ ],
    "users" : [
      "elk_resolve"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "own_index" : {
    "hosts" : [ ],
    "users" : [
      "*"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ],
    "description" : "Allow full access to an index named like the username"
  },
  "kibana_user" : {
    "hosts" : [ ],
    "users" : [
      "kibanaserver"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [
      "kibanauser"
    ],
    "and_backend_roles" : [ ]
  },
  "ops_role" : {
    "hosts" : [ ],
    "users" : [
      "cpe_ops",
      "nw_ops"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "all_access" : {
    "hosts" : [ ],
    "users" : [
      "lisa5",
      "admin",
      "lisa5@example.com"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [
      "admin"
    ],
    "and_backend_roles" : [ ]
  },
  "cisco_ise_syslog_readwrite" : {
    "hosts" : [ ],
    "users" : [
      "testdave"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "metricbeat_role" : {
    "hosts" : [ ],
    "users" : [
      "testram"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "logstash_custom" : {
    "hosts" : [ ],
    "users" : [
      "testls"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "readall" : {
    "hosts" : [ ],
    "users" : [ ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [
      "readall"
    ],
    "and_backend_roles" : [ ]
  },
  "sfty_role" : {
    "hosts" : [ ],
    "users" : [
      "sfty_test",
      "testadmin1",
      "testadmin2",
      "testadmin3"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "manage_snapshots" : {
    "hosts" : [ ],
    "users" : [ ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [
      "snapshotrestore"
    ],
    "and_backend_roles" : [ ]
  },
  "nss_firewall_readwrite" : {
    "hosts" : [ ],
    "users" : [
      "nsstest",
      "ljrnsstest",
      "ljrnsstest2"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "test_hawk_role" : {
    "hosts" : [ ],
    "users" : [
      "Tester_1"
    ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  },
  "logstash" : {
    "hosts" : [ ],
    "users" : [ ],
    "reserved" : false,
    "hidden" : false,
    "backend_roles" : [
      "logstash"
    ],
    "and_backend_roles" : [ ]
  },
  "kibana_server" : {
    "hosts" : [ ],
    "users" : [
      "kibanaserver"
    ],
    "reserved" : true,
    "hidden" : false,
    "backend_roles" : [ ],
    "and_backend_roles" : [ ]
  }
}

Hi @lisa5,

Could you please match your opensearch_security.openid.scope: ( in opensearch_dashboards.yml) with your token (openid, email, profile):
image

best,
mj

Hi!

I wanted to thank you again for your time and suggestions. I finally sorted out the issue. The redirect URL is a base (which, yes, is obvious from the name). I had the actual redirect URI instead of the base. I used Fiddler to capture the network traffic and inspected the URLs being called. After the PingID auth, I was redirected to https://opensearchdashboard.example.net:5601/auth/openid/login/auth/openid/login?code=X0790xPIY… having two /auth/openid/login components seemed off.

I changed:
opensearch_security.openid.base_redirect_url: "https://opensearchdashboard.example.net:5601/auth/openid/login"

To:
opensearch_security.openid.base_redirect_url: "https://opensearchdashboard.example.net:5601"

Restarted the web service, and voila! I am able to log in. Figured I would post that here in case anyone else suffers a similar misunderstanding.

It’s still odd that setting the trace logging doesn’t seem to do anything … but I’m not bored enough to read random auth tokens! :grinning:

1 Like

Hi @lisa5,

You are very welcome! Glad to read you got there eventually.

Thanks for sharing your solution!

Best,
mj