Closed dbanshee closed 6 months ago
Hi @dbanshee Thanks for reaching out! Can you share a recorded video of the issue you are facing to re-pro?
@opensearch-project/admin Could you please help transfer this to Dashboards security team to look into?
[Triage] @dbanshee Thank you for filing this issue and providing an example configuration. @RyanL1997 Can you confirm if you can reproduce the issue from the description?
For the case without the iframe, I wasn't able to create the issue. I will go thru provided configuration again, but what I have found that:
set-cookie: security_authentication=; Max-Age=0; Expires=Thu, 01 Jan 1970 00:00:00 GMT; HttpOnly; Path=/
The expiration on this doesn't seem correct.
Hi again. I'll provide another example. Hope it helps.
Video showing infinite loop on browser
Dashboard link generated by Open-dashboards (added manually jwtToken
url_param
http://l:5601/app/dashboards?jwtToken=
eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2MTZhMDFiYS0zNjQ3LTRiYTQtYjBlMy1jYzdhNDI0ZTkzYzgiLCJ0ZW5hbnRfaWQiOiJ0ZWRpYWxfc3dvcmtkZXYwMiIsIkdyb3VwIjpbIkFkbWluaXN0cmF0b3JzIiwiZXZlcnlvbmUiXSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LWFwcGxpY2F0aW9ucyIsInZpZXctY29uc2VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwiZGVsZXRlLWFjY291bnQiLCJtYW5hZ2UtY29uc2VudCIsInZpZXctcHJvZmlsZSJdfX0sImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sImlzcyI6InZveWFnZXItZ3ciLCJ0eXAiOiJCZWFyZXIiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJvbmFtYXlhIiwic2lkIjoiODJjZTFlNzgtMzAwOC00ZTAzLWIyYjQtNDZiODJkN2ZkYjc0IiwiYWNyIjoiMCIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJFbnJpY2htZW50IiwiUGxhdGZvcm0gQWRtaW4iLCJQbGF0Zm9ybSBXRiBEZXNpZ25lciIsIk1pZ3JhdGlvbiIsIkxvd0NvZGUgV0YgRGVzaWduZXIiLCJQaWNrbGlzdCIsIlB1Ymxpc2hpbmciLCJJbmdlc3QiLCJvZmZsaW5lX2FjY2VzcyIsIk1vbml0b3JpbmcgTWFuYWdlbWVudCIsInVtYV9hdXRob3JpemF0aW9uIiwib3BlbnNlYXJjaF9zZWFyY2hfYWxsX3JvbGUiLCJJbmdlc3QgTWFuYWdlbWVudCIsIkxvd0NvZGUgV0YgVmlld2VyIiwiTGl2ZSIsIlNjaGVkdWxpbmciLCJUZW5hbnQgQWRtaW4iLCJTaXRlIEFkbWluIiwiRXhjaGFuZ2UiLCJEZXZlbCIsIlNoYXJlZCBTaXRlIEFkbWluIiwiVGVkaWFsIE9wcyIsIkRlbGl2ZXJ5IiwiQ29sbGFib3JhdGlvbnMiLCJkZWZhdWx0LXJvbGVzLXRlZGlhbCJdfSwiYXpwIjoicGxhdGZvcm0tdWkiLCJhdXRoX3RpbWUiOjE2OTg3NDIyNDEsInNjb3BlIjoib3BlbmlkIG9wZW5zZWFyY2hfc2NvcGUgdGVuYW50X2lkIGVtYWlsIHByb2ZpbGUiLCJleHAiOjE2OTg3NDQ4MzIsInNlc3Npb25fc3RhdGUiOiI4MmNlMWU3OC0zMDA4LTRlMDMtYjJiNC00NmI4MmQ3ZmRiNzQiLCJpYXQiOjE2OTg3NDQyMzIsImp0aSI6IjA4MzA3YTlmLTViN2UtNGEwZC1hYzYzLTViM2E2NWE1NmY2ZSIsImVtYWlsIjoib25hbWF5YUB0ZWRpYWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJvcGVuc2VhcmNoX3JvbGVzIjpbIkVucmljaG1lbnQiLCJQbGF0Zm9ybSBBZG1pbiIsIlBsYXRmb3JtIFdGIERlc2lnbmVyIiwiTWlncmF0aW9uIiwiTG93Q29kZSBXRiBEZXNpZ25lciIsIlBpY2tsaXN0IiwiUHVibGlzaGluZyIsIkluZ2VzdCIsIm9mZmxpbmVfYWNjZXNzIiwiTW9uaXRvcmluZyBNYW5hZ2VtZW50IiwidW1hX2F1dGhvcml6YXRpb24iLCJvcGVuc2VhcmNoX3NlYXJjaF9hbGxfcm9sZSIsIkluZ2VzdCBNYW5hZ2VtZW50IiwiTG93Q29kZSBXRiBWaWV3ZXIiLCJMaXZlIiwiU2NoZWR1bGluZyIsIlRlbmFudCBBZG1pbiIsIlNpdGUgQWRtaW4iLCJFeGNoYW5nZSIsIkRldmVsIiwiU2hhcmVkIFNpdGUgQWRtaW4iLCJUZWRpYWwgT3BzIiwiRGVsaXZlcnkiLCJDb2xsYWJvcmF0aW9ucyIsImRlZmF1bHQtcm9sZXMtdGVkaWFsIl0sImdpdmVuX25hbWUiOiJvc2NhciIsIm5vbmNlIjoiNjc3MDJmNWUtNWU3Ni00MGZmLTg5MzktZThhZTlkZWZhZGZiIiwiYXVkIjpbImFjY291bnQiXSwibmFtZSI6Im9zY2FyIGFtYXlhIiwiZmFtaWx5X25hbWUiOiJhbWF5YSIsInVzZXIiOnsiaWQiOjAsIm5hbWUiOiJvbmFtYXlhIiwiZGVzY3JpcHRpb24iOm51bGwsImVtYWlsIjoib25hbWF5YUB0ZWRpYWwuY29tIiwidGVuYW50IjoidGVkaWFsX3N3b3JrZGV2MDIiLCJzaXRlIjoiIiwicm9sZXMiOlsiQWRtaW5pc3RyYXRvcnMiLCJldmVyeW9uZSIsIkVucmljaG1lbnQiLCJQbGF0Zm9ybSBBZG1pbiIsIlBsYXRmb3JtIFdGIERlc2lnbmVyIiwiTWlncmF0aW9uIiwiTG93Q29kZSBXRiBEZXNpZ25lciIsIlBpY2tsaXN0IiwiUHVibGlzaGluZyIsIkluZ2VzdCIsIm9mZmxpbmVfYWNjZXNzIiwiTW9uaXRvcmluZyBNYW5hZ2VtZW50IiwidW1hX2F1dGhvcml6YXRpb24iLCJvcGVuc2VhcmNoX3NlYXJjaF9hbGxfcm9sZSIsIkluZ2VzdCBNYW5hZ2VtZW50IiwiTG93Q29kZSBXRiBWaWV3ZXIiLCJMaXZlIiwiU2NoZWR1bGluZyIsIlRlbmFudCBBZG1pbiIsIlNpdGUgQWRtaW4iLCJFeGNoYW5nZSIsIkRldmVsIiwiU2hhcmVkIFNpdGUgQWRtaW4iLCJUZWRpYWwgT3BzIiwiRGVsaXZlcnkiLCJDb2xsYWJvcmF0aW9ucyIsImRlZmF1bHQtcm9sZXMtdGVkaWFsIl19LCJyb2xlcyI6IkFkbWluaXN0cmF0b3JzLGV2ZXJ5b25lLEVucmljaG1lbnQsUGxhdGZvcm0gQWRtaW4sUGxhdGZvcm0gV0YgRGVzaWduZXIsTWlncmF0aW9uLExvd0NvZGUgV0YgRGVzaWduZXIsUGlja2xpc3QsUHVibGlzaGluZyxJbmdlc3Qsb2ZmbGluZV9hY2Nlc3MsTW9uaXRvcmluZyBNYW5hZ2VtZW50LHVtYV9hdXRob3JpemF0aW9uLG9wZW5zZWFyY2hfc2VhcmNoX2FsbF9yb2xlLEluZ2VzdCBNYW5hZ2VtZW50LExvd0NvZGUgV0YgVmlld2VyLExpdmUsU2NoZWR1bGluZyxUZW5hbnQgQWRtaW4sU2l0ZSBBZG1pbixFeGNoYW5nZSxEZXZlbCxTaGFyZWQgU2l0ZSBBZG1pbixUZWRpYWwgT3BzLERlbGl2ZXJ5LENvbGxhYm9yYXRpb25zLGRlZmF1bHQtcm9sZXMtdGVkaWFsIiwidGVuYW50IjoidGVkaWFsX3N3b3JrZGV2MDIiLCJzaXRlIjoiIn0._oHVPy9PTEJbpoZjzQNSqqyK0rTJjVG8sj0VHSBAAtiebFD6zDqp3x_P4WHLlg8okF-flBX5uRwHW9J7U6CJYg&security_tenant=global#/view/88784cf0-9f9d-11ec-9b44-4d7f75aed853
Full opensearch config file:
---
cluster.name: docker-cluster
# Bind to all interfaces because we don't know what IP address Docker will assign to us.
network.host: 0.0.0.0
# # minimum_master_nodes need to be explicitly set when bound on a public IP
# # set to 1 to allow single node clusters
# discovery.zen.minimum_master_nodes: 1
# Setting network.host to a non-loopback address enables the annoying bootstrap checks. "Single-node" mode disables them again.
# discovery.type: single-node
######## Start OpenSearch Security Demo Configuration ########
# WARNING: revise all the lines below before you go into production
plugins.security.ssl.transport.pemcert_filepath: esnode.pem
plugins.security.ssl.transport.pemkey_filepath: esnode-key.pem
plugins.security.ssl.transport.pemtrustedcas_filepath: root-ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemcert_filepath: esnode.pem
plugins.security.ssl.http.pemkey_filepath: esnode-key.pem
plugins.security.ssl.http.pemtrustedcas_filepath: root-ca.pem
plugins.security.allow_unsafe_democertificates: true
plugins.security.allow_default_init_securityindex: true
plugins.security.authcz.admin_dn:
- CN=kirk,OU=client,O=client,L=test, C=de
plugins.security.audit.type: internal_opensearch
plugins.security.enable_snapshot_restore_privilege: true
plugins.security.check_snapshot_restore_write_privileges: true
plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
plugins.security.system_indices.enabled: true
plugins.security.system_indices.indices: [".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".opendistro-alerting-config", ".opendistro-alerting-alert*", ".opendistro-anomaly-results*", ".opendistro-anomaly-detector*", ".opendistro-anomaly-checkpoints", ".opendistro-anomaly-detection-state", ".opendistro-reports-*", ".opensearch-notifications-*", ".opensearch-notebooks", ".opensearch-observability", ".ql-datasources", ".opendistro-asynchronous-search-response*", ".replication-metadata-store", ".opensearch-knn-models"]
node.max_local_storage_nodes: 3
######## End OpenSearch Security Demo Configuration ########
Full opensearch security config file:
---
_meta:
type: "config"
config_version: 2
config:
dynamic:
# Set filtered_alias_mode to 'disallow' to forbid more than 2 filtered aliases per index
# Set filtered_alias_mode to 'warn' to allow more than 2 filtered aliases per index but warns about it (default)
# Set filtered_alias_mode to 'nowarn' to allow more than 2 filtered aliases per index silently
#filtered_alias_mode: warn
#do_not_fail_on_forbidden: false
#kibana:
# Kibana multitenancy
#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' # regex pattern
#internalProxies: '.*' # trust all internal proxies, regex pattern
#remoteIpHeader: 'x-forwarded-for'
###### see https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html for regex help
###### more information about XFF https://en.wikipedia.org/wiki/X-Forwarded-For
###### and here https://tools.ietf.org/html/rfc7239
###### and https://tomcat.apache.org/tomcat-8.0-doc/config/valve.html#Remote_IP_Valve
authc:
kerberos_auth_domain:
http_enabled: false
transport_enabled: false
order: 6
http_authenticator:
type: kerberos
challenge: true
config:
# If true a lot of kerberos/security related debugging output will be logged to standard out
krb_debug: false
# If true then the realm will be stripped from the user name
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: true
transport_enabled: true
order: 0
http_authenticator:
type: jwt
challenge: false
config:
# Shared Secret from AuthService
signing_key: "<my_secret>"
jwt_header: "Authorization"
jwt_url_parameter: "jwtToken"
jwt_clock_skew_tolerance_seconds: 30
roles_key: opensearch_roles
subject_key: preferred_username
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 ldaps
enable_ssl: false
# enable start tls, enable_ssl should be false
enable_start_tls: false
# send client certificate
enable_ssl_client_auth: false
# verify ldap hostname
verify_hostnames: true
hosts:
- localhost:8389
bind_dn: null
password: null
userbase: 'ou=people,dc=example,dc=com'
# Filter to search for users (currently in the whole subtree beneath userbase)
# {0} is substituted with the username
usersearch: '(sAMAccountName={0})'
# Use this attribute from the user as username (if not set then DN is used)
username_attribute: null
authz:
roles_from_myldap:
description: "Authorize via LDAP or Active Directory"
http_enabled: false
transport_enabled: false
authorization_backend:
# LDAP authorization backend (gather roles from a LDAP or Active Directory, you have to configure the above LDAP authentication backend settings too)
type: ldap
config:
# enable ldaps
enable_ssl: false
# enable start tls, enable_ssl should be false
enable_start_tls: false
# send client certificate
enable_ssl_client_auth: false
# verify ldap hostname
verify_hostnames: true
hosts:
- localhost:8389
bind_dn: null
password: null
rolebase: 'ou=groups,dc=example,dc=com'
# Filter to search for roles (currently in the whole subtree beneath rolebase)
# {0} is substituted with the DN of the user
# {1} is substituted with the username
# {2} is substituted with an attribute value from user's directory entry, of the authenticated user. Use userroleattribute to specify the name of the attribute
rolesearch: '(member={0})'
# Specify the name of the attribute which value should be substituted with {2} above
userroleattribute: null
# Roles as an attribute of the user entry
userrolename: disabled
#userrolename: memberOf
# The attribute in a role entry containing the name of that role, Default is "name".
# Can also be "dn" to use the full DN as rolename.
rolename: cn
# Resolve nested roles transitive (roles which are members of other roles and so on ...)
resolve_nested_roles: true
userbase: 'ou=people,dc=example,dc=com'
# Filter to search for users (currently in the whole subtree beneath userbase)
# {0} is substituted with the username
usersearch: '(uid={0})'
# Skip users matching a user name, a wildcard or a regex pattern
#skip_users:
# - 'cn=Michael Jackson,ou*people,o=TEST'
# - '/\S*/'
roles_from_another_ldap:
description: "Authorize via another Active Directory"
http_enabled: false
transport_enabled: false
authorization_backend:
type: ldap
#config goes here ...
Full opensearch-dashboards config file:
opensearch.hosts: [https://localhost:9200]
opensearch.ssl.verificationMode: none
opensearch.username: kibanaserver
opensearch.password: kibanaserver
opensearch.requestHeadersWhitelist: [authorization, securitytenant]
opensearch_security.multitenancy.enabled: true
opensearch_security.multitenancy.tenants.preferred: [Private, Global]
opensearch_security.readonly_mode.roles: [kibana_read_only]
# Use this setting if you are running opensearch-dashboards without https
opensearch_security.cookie.secure: false
server.host: '0.0.0.0'
opensearch_security.auth.type: "jwt"
opensearch_security.jwt.url_param: jwtToken
opensearch_security.jwt.enabled: true
Full tcpdump when access the permalink on chrome (captured on 5601 port) capture_file
Hey @RyanL1997 can you follow-up on this again? Thanks.
Hi @dbanshee thanks for the details. After some investigation, I still couldn't reproduce the loop on my local, however, I do have trouble to access to permlink of dashboard with jwt param added:
{"statusCode":401,"error":"Unauthorized","message":"Unauthorized"}
I'm actively looking into it and I will update my setup in the next comment.
Thanks @RyanL1997 let me know if you need anything.
Hi @dbanshee, were you able to address this issue or did you work with @RyanL1997 to? If not, we will need to revisit this.
Hello @scrawfor99.
I still have the problem. All I know is what is in this thread.
[Triage] Thanks for following up @dbanshee. Going to mark this bug as triaged so that someone takes a look.
@dbanshee I was able to replicate your issue and see now that its due to the JWT being too large. If its possible to reduce the size of the token, that could help get you immediately beyond this issue, but a longer term fix could be to extend https://github.com/opensearch-project/security-dashboards-plugin/pull/1352 to the JWT backend as well.
I made a POC on a branch here which resolves the issue, but need to add tests to verify the behavior.
thank you @cwperks This workaround is very useful. It would be great if the limit was higher in future releases, but maybe we can clean up our token in the meantime.
Closing this ticket as resolved. Cookie splitting was extended to JWT authentication.
Thanks for all @cwperks
Describe the bug
Dashboards permalink and iframe losses url param JWT on internal API calls.
After configure Opensearch and OS-Dashboards to use url param JWT and check by curl that works sucessfully, I tried to open dashboards permalink and iframe generated by opensearch.
The browser enters on infinite loop (if the token JWT has expired, directly returns Unathorized as expected).
No browser debug or opensearch dashboards appears to be relevant but capturing traffic on 5601 port I can check how the url_param jwt are lossed on internal API calls (restapiinfo, configuration accoung api, …) returning HTTP 401 Unathorized
I replicated with curl the same calls and adding url_param with the same token and the request works.
To Reproduce Steps to reproduce the behavior:
Expected behavior Expected to show de dashboards successfully, but browser enters on infinite loop.
OpenSearch Version 2.10.0 Also tested on 2.8.0
Dashboards Version 2.10.0 Also tested on 2.8.0
Plugins
Please list all plugins currently enabled
Host/Environment (please complete the following information):
Additional context Configuration:
Opensearch Dashboards Config:
Manual curl to test internal APIS (adding jwtToken url param) works successfuly:
Accesing permalink with url_param and capturing network traffic. Internal calls loss url params and fails to Authenticate: