HTTPSamlAuthenticator rejects login

Versions (relevant - OpenSearch/Dashboard/Server OS/Browser):
OpenSearch 2.15.0
Dashboard 2.15.0
Docker image

Describe the issue:
I’ve somewhat successfully configured SAML connector to corporate Azure Entra ID (former Azure Active Directory). But whilst trying to log in from Dashboard, I always end up with plain:

{"statusCode":500,"error":"Internal Server Error","message":"Internal Error"}

After I authenticate with Microsoft and being redirected back to Dashboard. Seems like, Opensearch receives correct response from MS and is able to generate JWT token.

This token seems to be passed back to Dashboards but then when it is being used for the first time whilst calling

const user = await this.securityClient.authenticateWithHeader(request, 'authorization', credentials.authorization);

in /plugins/securityDashboards/server/auth/types/saml/routes.js.

Security audit log does not say anything useful, just plan LOGIN FAILED or something similarly generic. Only thing remotely useful I was able to find was in Opensearch’s logs when I enabled debug for entire project:

[2024-07-16T15:50:22,381][DEBUG][c.o.s.a.SamlResponse     ] [opensearch-node1] SAMLResponse has NameID --> my@email.com
[2024-07-16T15:50:22,381][DEBUG][c.o.s.a.SamlResponse     ] [opensearch-node1] SAMLResponse has NameID Format --> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
[2024-07-16T15:50:22,381][DEBUG][c.o.s.a.SamlResponse     ] [opensearch-node1] SAMLResponse has NameID Format --> urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress
[2024-07-16T15:50:22,388][DEBUG][c.o.s.a.SamlResponse     ] [opensearch-node1] SAMLResponse has attributes: {httplink://schemas.microsoft.com/identity/claims/tenantid=[{id}], httplink://schemas.microsoft.com/identity/claims/objectidentifier=[{id}], httplink://schemas.microsoft.com/identity/claims/displayname=[Some username], httplink://schemas.microsoft.com/ws/2008/06/identity/claims/groups=[99999999-9999-9999-9999-999999999999, 99999999-9999-9999-9999-999999999998, 99999999-9999-9999-9999-999999999997, 99999999-9999-9999-9999-999999999996, 99999999-9999-9999-9999-999999999995, 99999999-9999-9999-9999-999999999994, 99999999-9999-9999-9999-999999999993, 99999999-9999-9999-9999-999999999992], httplink://schemas.microsoft.com/identity/claims/identityprovider=[httpslink://sts.windows.net/{id}/], httplink://schemas.microsoft.com/claims/authnmethodsreferences=[httplink://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/password, httplink://schemas.microsoft.com/claims/multipleauthn], httplink://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname=[Somefirstname], httplink://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname=[Somesurname], httplink://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress=[my@email.com], httplink://schemas.xmlsoap.org/ws/2005/05/identity/claims/name=[my@email.com]}
[2024-07-16T15:50:22,396][DEBUG][c.a.d.a.h.s.Token        ] [opensearch-node1] Created JWT: <redacted>.<redacted>.<redacted>
{"alg":"HS512"}
{"sub":"my@email.com","nbf":1721145022,"roles":["99999999-9999-9999-9999-999999999999","99999999-9999-9999-9999-999999999998","99999999-9999-9999-9999-999999999997","99999999-9999-9999-9999-999999999996","99999999-9999-9999-9999-999999999995","99999999-9999-9999-9999-999999999994","99999999-9999-9999-9999-999999999993","99999999-9999-9999-9999-999999999992"],"saml_ni":"my@email.com","saml_nif":"email","exp":1721148622,"saml_si":"_53c1fbac-a71c-47d4-b300-8f9ac0041d00"}
[2024-07-16T15:50:22,402][DEBUG][o.o.t.TransportService   ] [opensearch-node1] Action: indices:data/write/bulk[s][p]
[2024-07-16T15:50:22,407][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Check authdomain for rest internal/0 or 2 in total
[2024-07-16T15:50:22,407][WARN ][o.o.s.h.HTTPBasicAuthenticator] [opensearch-node1] No 'Basic Authorization' header, send 401 and 'WWW-Authenticate Basic'
[2024-07-16T15:50:22,407][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Check authdomain for rest internal/1 or 2 in total
[2024-07-16T15:50:22,416][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Can not authenticate my@email.com due to exception
com.google.common.util.concurrent.UncheckedExecutionException: OpenSearchSecurityException[empty passwords not supported]
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2087) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache.get(LocalCache.java:4019) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache$LocalManualCache.get(LocalCache.java:4933) ~[guava-32.1.3-jre.jar:?]
        at org.opensearch.security.auth.BackendRegistry.authcz(BackendRegistry.java:579) [opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.auth.BackendRegistry.authenticate(BackendRegistry.java:331) [opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.filter.SecurityRestFilter.checkAndAuthenticateRequest(SecurityRestFilter.java:309) [opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.ssl.http.netty.Netty4HttpRequestHeaderVerifier.channelRead0(Netty4HttpRequestHeaderVerifier.java:91) [opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.ssl.http.netty.Netty4HttpRequestHeaderVerifier.channelRead0(Netty4HttpRequestHeaderVerifier.java:38) [opensearch-security-2.15.0.0.jar:2.15.0.0]
        at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:346) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:318) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:289) [netty-handler-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:442) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1475) [netty-handler-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1338) [netty-handler-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1387) [netty-handler-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:530) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:469) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:290) [netty-codec-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:444) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:412) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1407) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:440) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:420) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:918) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeysPlain(NioEventLoop.java:689) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:652) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) [netty-transport-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:994) [netty-common-4.1.110.Final.jar:4.1.110.Final]
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) [netty-common-4.1.110.Final.jar:4.1.110.Final]
        at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]
Caused by: org.opensearch.OpenSearchSecurityException: empty passwords not supported
        at org.opensearch.security.auth.internal.InternalAuthenticationBackend.authenticate(InternalAuthenticationBackend.java:124) ~[opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.auth.BackendRegistry$5.call(BackendRegistry.java:589) ~[opensearch-security-2.15.0.0.jar:2.15.0.0]
        at org.opensearch.security.auth.BackendRegistry$5.call(BackendRegistry.java:579) ~[opensearch-security-2.15.0.0.jar:2.15.0.0]
        at com.google.common.cache.LocalCache$LocalManualCache$1.load(LocalCache.java:4938) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache$LoadingValueReference.loadFuture(LocalCache.java:3576) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache$Segment.loadSync(LocalCache.java:2318) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache$Segment.lockedGetOrLoad(LocalCache.java:2191) ~[guava-32.1.3-jre.jar:?]
        at com.google.common.cache.LocalCache$Segment.get(LocalCache.java:2081) ~[guava-32.1.3-jre.jar:?]
        ... 45 more
[2024-07-16T15:50:22,423][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Cannot authenticate rest user my@email.com (or add roles) with authdomain internal/1 of [AuthDomain [backend=org.opensearch.security.auth.internal.InternalAuthenticationBackend@3fcc58f8, httpAuthenticator=org.opensearch.security.http.HTTPBasicAuthenticator@2e9e0c37, order=0, challenge=false], AuthDomain [backend=org.opensearch.security.auth.internal.InternalAuthenticationBackend@3fcc58f8, httpAuthenticator=com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator@1564b5a6, order=1, challenge=true]], try next
[2024-07-16T15:50:22,423][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] User still not authenticated after checking 2 auth domains
[2024-07-16T15:50:22,423][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Rerequest with class com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator
[2024-07-16T15:50:22,425][DEBUG][c.o.s.a.AuthnRequest     ] [opensearch-node1] AuthNRequest --> <samlp:AuthnRequest xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="ONELOGIN_d999999-9999-9999-9999-b74df739882d" Version="2.0" IssueInstant="2024-07-16T15:50:22Z" Destination="https://login.microsoftonline.com/{id}/saml2" ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="https://my.server.com/_opendistro/_security/saml/acs"><saml:Issuer>https://my.server.com</saml:Issuer><samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified" AllowCreate="true" /></samlp:AuthnRequest>
[2024-07-16T15:50:22,426][DEBUG][o.o.s.a.BackendRegistry  ] [opensearch-node1] Rerequest class com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator failed
[2024-07-16T15:50:22,427][WARN ][o.o.s.a.BackendRegistry  ] [opensearch-node1] Authentication finally failed for my@email.com from 10.0.0.1:39224

Configuration:
opensearch-security/config.yml:

_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
doc/config/valve.html#Remote_IP_Valve
    authc:
      basic_internal_auth_domain:
        description: "Authenticate via HTTP Basic against internal users database"
        http_enabled: true
        transport_enabled: true
        order: 0
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          type: intern
      saml_auth_domain:
        order: 1
        description: "SAML provider"
        http_enabled: true
        transport_enabled: false
        http_authenticator:
          type: saml
          challenge: true
          config:
            idp:
              entity_id: {url}
              metadata_url: {url}
              verify_hostnames: true
              enable.ssl: true
            sp:
              entity_id: {url}
            authentication_backend:
              type: noop
            kibana_url: {url}
            subject_key: "httplink://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
            roles_key: "httplink://schemas.microsoft.com/ws/2008/06/identity/claims/groups"
            exchange_key: '64 characters long string'
            jwt_clock_skew_tolerance_seconds: 7200 # from testing earlier on - does not make any difference at all

Any ideas what I might be missing? There’s no nginx or any sort of proxy anywhere and all communication is running via HTTPS configured on both opensearch transport/rest and dashboard as well.

Hi @svecp ,

You don’t need to use subject_key for Azure. Please try to remove this parameter in config.yml .

Could you please share your opensearch-dashboards.yml file?

Also, please enable debug mode for SAML so you can see the token from the Azure in the logs:

I did not have this configured for a long time but just to be sure - I removed it just now but the result is still the same - OpenSearchSecurityException[empty passwords not supported] on attempt to use issued JWT.

In regards to SAML output, it is in original post (lines 5 through 7). I just redacted usernames, role ids and identifiers but that’s pretty much what I get. I also had to redact URLs. But replaced http for httplink:// and https for httpslink://

Otherwise I was blocked posting it in the first place.

docker@dockerhost01:~/docker-services/opensearch# cat custom-config/opensearch_dashboards.yml
---
# Copyright OpenSearch Contributors
# SPDX-License-Identifier: Apache-2.0

# Description:
# Default configuration for OpenSearch Dashboards

# OpenSearch Dashboards is served by a back end server. This setting specifies the port to use.
# server.port: 5601

# Specifies the address to which the OpenSearch Dashboards server will bind. IP addresses and host names are both valid values.
# The default is 'localhost', which usually means remote machines will not be able to connect.
# To allow connections from remote users, set this parameter to a non-loopback address.
server.host: "0.0.0.0"

# Enables you to specify a path to mount OpenSearch Dashboards at if you are running behind a proxy.
# Use the `server.rewriteBasePath` setting to tell OpenSearch Dashboards if it should remove the basePath
# from requests it receives, and to prevent a deprecation warning at startup.
# This setting cannot end in a slash.
# server.basePath: ""

# Specifies whether OpenSearch Dashboards should rewrite requests that are prefixed with
# `server.basePath` or require that they are rewritten by your reverse proxy.
# server.rewriteBasePath: false

# The maximum payload size in bytes for incoming server requests.
# server.maxPayloadBytes: 1048576

# The OpenSearch Dashboards server's name.  This is used for display purposes.
# server.name: "your-hostname"

# The URLs of the OpenSearch instances to use for all your queries.
#opensearch.hosts: ["https://opensearch-node1:9200", "https://opensearch-node2:9200"]
opensearch.hosts: ["https://opensearch-node1:9200"]

# OpenSearch Dashboards uses an index in OpenSearch to store saved searches, visualizations and
# dashboards. OpenSearch Dashboards creates a new index if the index doesn't already exist.
# opensearchDashboards.index: ".opensearch_dashboards"

# The default application to load.
# opensearchDashboards.defaultAppId: "home"

# Setting for an optimized healthcheck that only uses the local OpenSearch node to do Dashboards healthcheck.
# This settings should be used for large clusters or for clusters with ingest heavy nodes.
# It allows Dashboards to only healthcheck using the local OpenSearch node rather than fan out requests across all nodes.
#
# It requires the user to create an OpenSearch node attribute with the same name as the value used in the setting
# This node attribute should assign all nodes of the same cluster an integer value that increments with each new cluster that is spun up
# e.g. in opensearch.yml file you would set the value to a setting using node.attr.cluster_id:
# Should only be enabled if there is a corresponding node attribute created in your OpenSearch config that matches the value here
# opensearch.optimizedHealthcheckId: "cluster_id"

# If your OpenSearch is protected with basic authentication, these settings provide
# the username and password that the OpenSearch Dashboards server uses to perform maintenance on the OpenSearch Dashboards
# index at startup. Your OpenSearch Dashboards users still need to authenticate with OpenSearch, which
# is proxied through the OpenSearch Dashboards server.
# opensearch.username: "opensearch_dashboards_system"
# opensearch.password: "pass"

# Enables SSL and paths to the PEM-format SSL certificate and SSL key files, respectively.
# These settings enable SSL for outgoing requests from the OpenSearch Dashboards server to the browser.
server.ssl.enabled: true
server.ssl.certificate: /etc/pki/certs/opensearch-dashboards.crt
server.ssl.key: /etc/pki/private/opensearch-dashboards.key

# Optional settings that provide the paths to the PEM-format SSL certificate and key files.
# These files are used to verify the identity of OpenSearch Dashboards to OpenSearch and are required when
# xpack.security.http.ssl.client_authentication in OpenSearch is set to required.
# opensearch.ssl.certificate: /path/to/your/client.crt
# opensearch.ssl.key: /path/to/your/client.key

# Optional setting that enables you to specify a path to the PEM file for the certificate
# authority for your OpenSearch instance.
# opensearch.ssl.certificateAuthorities: [ "/path/to/your/CA.pem" ]

# To disregard the validity of SSL certificates, change this setting's value to 'none'.
# opensearch.ssl.verificationMode: full

# Time in milliseconds to wait for OpenSearch to respond to pings. Defaults to the value of
# the opensearch.requestTimeout setting.
# opensearch.pingTimeout: 1500

# Time in milliseconds to wait for responses from the back end or OpenSearch. This value
# must be a positive integer.
# opensearch.requestTimeout: 30000

# List of OpenSearch Dashboards client-side headers to send to OpenSearch. To send *no* client-side
# headers, set this value to [] (an empty list).
# opensearch.requestHeadersWhitelist: [ authorization ]

# Header names and values that are sent to OpenSearch. Any custom headers cannot be overwritten
# by client-side headers, regardless of the opensearch.requestHeadersWhitelist configuration.
# opensearch.customHeaders: {}

# Time in milliseconds for OpenSearch to wait for responses from shards. Set to 0 to disable.
# opensearch.shardTimeout: 30000

# Logs queries sent to OpenSearch. Requires logging.verbose set to true.
# opensearch.logQueries: false

# Specifies the path where OpenSearch Dashboards creates the process ID file.
# pid.file: /var/run/opensearchDashboards.pid

# Enables you to specify a file where OpenSearch Dashboards stores log output.
# logging.dest: stdout

# Set the value of this setting to true to suppress all logging output.
# logging.silent: false

# Set the value of this setting to true to suppress all logging output other than error messages.
# logging.quiet: false

# Set the value of this setting to true to log all events, including system usage information
# and all requests.
# logging.verbose: false

# Set the interval in milliseconds to sample system and process performance
# metrics. Minimum is 100ms. Defaults to 5000.
# ops.interval: 5000

# Specifies locale to be used for all localizable strings, dates and number formats.
# Supported languages are the following: English - en , by default , Chinese - zh-CN .
# i18n.locale: "en"

# Set the allowlist to check input graphite Url. Allowlist is the default check list.
# vis_type_timeline.graphiteAllowedUrls: ['https://www.hostedgraphite.com/UID/ACCESS_KEY/graphite']

# Set the blocklist to check input graphite Url. Blocklist is an IP list.
# Below is an example for reference
# vis_type_timeline.graphiteBlockedIPs: [
#  //Loopback
#  '127.0.0.0/8',
#  '::1/128',
#  //Link-local Address for IPv6
#  'fe80::/10',
#  //Private IP address for IPv4
#  '10.0.0.0/8',
#  '172.16.0.0/12',
#  '192.168.0.0/16',
#  //Unique local address (ULA)
#  'fc00::/7',
#  //Reserved IP address
#  '0.0.0.0/8',
#  '100.64.0.0/10',
#  '192.0.0.0/24',
#  '192.0.2.0/24',
#  '198.18.0.0/15',
#  '192.88.99.0/24',
#  '198.51.100.0/24',
#  '203.0.113.0/24',
#  '224.0.0.0/4',
#  '240.0.0.0/4',
#  '255.255.255.255/32',
#  '::/128',
#  '2001:db8::/32',
#  'ff00::/8',
# ]
# vis_type_timeline.graphiteBlockedIPs: []

# opensearchDashboards.branding:
#   logo:
#     defaultUrl: ""
#     darkModeUrl: ""
#   mark:
#     defaultUrl: ""
#     darkModeUrl: ""
#   loadingLogo:
#     defaultUrl: ""
#     darkModeUrl: ""
#   faviconUrl: ""
#   applicationTitle: ""

# Set the value of this setting to true to capture region blocked warnings and errors
# for your map rendering services.
# map.showRegionBlockedWarning: false%

opensearch_security.auth.type: ["basicauth","saml"]
opensearch_security.auth.multiple_auth_enabled: true

# Set the value of this setting to false to suppress search usage telemetry
# for reducing the load of OpenSearch cluster.
# data.search.usageTelemetry.enabled: false

# 2.4 renames 'wizard.enabled: false' to 'vis_builder.enabled: false'
# Set the value of this setting to false to disable VisBuilder
# functionality in Visualization.
# vis_builder.enabled: false

# 2.4 New Experimental Feature
# Set the value of this setting to true to enable the experimental multiple data source
# support feature. Use with caution.
# data_source.enabled: false
# Set the value of these settings to customize crypto materials to encryption saved credentials
# in data sources.
# data_source.encryption.wrappingKeyName: 'changeme'
# data_source.encryption.wrappingKeyNamespace: 'changeme'
# data_source.encryption.wrappingKey: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]

# 2.6 New ML Commons Dashboards Feature
# Set the value of this setting to true to enable the ml commons dashboards
# ml_commons_dashboards.enabled: false

# 2.12 New experimental Assistant Dashboards Feature
# Set the value of this setting to true to enable the assistant dashboards
# assistant.chat.enabled: false

# 2.13 New Query Assistant Feature
# Set the value of this setting to false to disable the query assistant
# observability.query_assist.enabled: false

# 2.14 Enable Ui Metric Collectors in Usage Collector
# Set the value of this setting to true to enable UI Metric collections
# usageCollection.uiMetric.enabled: false

opensearch.ssl.verificationMode: none
opensearch.username: kibanaserver
opensearch.password: kibanaserver # (= seriously, got so desperate I even tried the default one with fresh instance)
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.xsrf.allowlist: [
  "/_plugins/_security/saml/acs/idpinitiated",
  "/_plugins/_security/saml/acs",
  "/_plugins/_security/saml/logout",
  "/_opendistro/_security/saml/acs/idpinitiated",
  "/_opendistro/_security/saml/acs",
  "/_opendistro/_security/saml/logout"
]
#server.xsrf.allowlist: ["/_plugins/_security/saml/acs/idpinitiated", "/_plugins/_security/saml/acs", "/_plugins/_security/saml/logout", "/_plugins/_security/saml/acs"]
#server.xsrf.allowlist: ["/_opendistro/_security/saml/acs", "/_opendistro/_security/saml/logout"]

  #xpack.security.authc.providers:
  #  basic.basic1:
  #    order: 0
  #      #    icon: "logoElasticsearch"
  #    hint: "Local login"
  #  saml.saml1:
  #    order: 1
  #    realm: "saml1"
  #    description: "Login with SSO"
  #    hint: "SSO"
  #      #    icon: "logoElasticsearch"

After going through source code, I was able to find out that instead of NoOp authentication backend but intern authentication was used, despite NoOp being configured in the config.yml. Well, turned out that my nesting was a bit off and instead of configuring it in:

config.dynamic.authc.saml_auth_domain

I had it in

config.dynamic.authc.saml_auth_domain.http_authenticator.config

Not sure how/when it had happened but “de-nesting” seems to have fixed the problem.