wazuh / wazuh-indexer

Wazuh indexer, the Wazuh search engine
https://opensearch.org/docs/latest/opensearch/index/
Apache License 2.0
11 stars 17 forks source link

The certificates from the Single sign-on authentication throw an `Illegal base64 character` #332

Closed Enaraque closed 2 months ago

Enaraque commented 2 months ago
Wazuh version Install type Action performed Platform
4.9.0-alpha3 AIO Single sing-on auth CentOS 8
Related issue
https://github.com/wazuh/wazuh/issues/24855

Description

While testing the single sign-on with Okta and Azure when I have to modificate the /etc/wazuh-indexer/opensearch-security/config.yml file and include the certificate generated from Okta or Azure in the exchange_key: field, the {‘statusCode’:500, ‘error’: ‘Internal Server Error’, ‘message’: ‘Internal Error’} error appear when trying to access to the dashboard.

Checking the logs of /var/log/wazuh-indexer/wazuh-cluster.log shows that there is the following error:

[2024-07-24T12:40:18,734][ERROR][c.a.d.a.h.s.HTTPSamlAuthenticator] [node-1] Error creating HTTPSamlAuthenticator. SAML authentication will not work.
[2024-07-24T12:40:18,737][ERROR][o.o.s.s.DynamicConfigModelV7] [node-1] Failed to initialize auth domain saml_auth_domain=AuthcDomain [http_enabled=true, order=1, http_authenticator=HttpAuthenticator [challenge=true, type=saml, config={idp={metadata_url=https://dev-xxxx. okta.com/app/xxxxxxx/sso/saml/metadata, entity_id=http://www. okta.com/xxxxxx}, sp={entity_id=wazuh-saml}, kibana_url=https://xxxxxxx, roles_key=Roles, exchange_key=xxxxxx}], authentication_backend=AuthcBackend [type=noop, config={}], description=null] due to OpenSearchException[java.lang.reflect. InvocationTargetException]; nested: InvocationTargetException; nested: RuntimeException[java.lang.IllegalArgumentException: Illegal base64 character 2b]; nested: IllegalArgumentException[Illegal base64 character 2b];

We have attempted to create a new certificate and encountered the same issue as above.

If we leave the exchange_key option empty o with a dummy string (like exchange_key: "hello" instead of a correct certificate) in the /etc/wazuh-indexer/opensearch-security/config.yml file and re-enter the dashboard, we can log in successfully, but we receive a permissions error once inside the dashboard (due to the wrong certificate, i guess).

f-galland commented 2 months ago

@Enaraque, I was able to reproduce the issue by following the steps in our documentation. I will do some troubleshooting and get back to you as I make progress.

f-galland commented 2 months ago

Full error log below:

[2024-07-29T14:27:53,444][ERROR][c.a.d.a.h.s.HTTPSamlAuthenticator] [node-1] Error creating HTTPSamlAuthenticator. SAML authentication will not work
java.lang.IllegalArgumentException: Illegal base64 character 2b
        at java.base/java.util.Base64$Decoder.decode0(Base64.java:852) ~[?:?]
        at java.base/java.util.Base64$Decoder.decode(Base64.java:570) ~[?:?]
        at java.base/java.util.Base64$Decoder.decode(Base64.java:593) ~[?:?]
        at com.amazon.dlic.auth.http.saml.AuthTokenProcessorHandler.createJwkFromSettings(AuthTokenProcessorHandler.java:245) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.configuration.ConfigurationRepository.reloadConfiguration(ConfigurationRepository.java:390) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.action.configupdate.TransportConfigUpdateAction.nodeOperation(TransportConfigUpdateAction.java:128) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.action.configupdate.TransportConfigUpdateAction.nodeOperation(TransportConfigUpdateAction.java:52) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.action.support.nodes.TransportNodesAction.nodeOperation(TransportNodesAction.java:200) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.action.support.nodes.TransportNodesAction$NodeTransportHandler.messageReceived(TransportNodesAction.java:328) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.action.support.nodes.TransportNodesAction$NodeTransportHandler.messageReceived(TransportNodesAction.java:324) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.security.ssl.transport.SecuritySSLRequestHandler.messageReceivedDecorate(SecuritySSLRequestHandler.java:206) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.transport.SecurityRequestHandler.messageReceivedDecorate(SecurityRequestHandler.java:211) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.ssl.transport.SecuritySSLRequestHandler.messageReceived(SecuritySSLRequestHandler.java:105) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.security.OpenSearchSecurityPlugin$6$1.messageReceived(OpenSearchSecurityPlugin.java:828) [opensearch-security-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.indexmanagement.rollup.interceptor.RollupInterceptor$interceptHandler$1.messageReceived(RollupInterceptor.kt:114) [opensearch-index-management-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.performanceanalyzer.transport.PerformanceAnalyzerTransportRequestHandler.messageReceived(PerformanceAnalyzerTransportRequestHandler.java:43) [opensearch-performance-analyzer-2.13.0.0.jar:2.13.0.0]
        at org.opensearch.transport.RequestHandlerRegistry.processMessageReceived(RequestHandlerRegistry.java:106) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.transport.TransportService$7.doRun(TransportService.java:1059) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:913) [opensearch-2.13.0.jar:2.13.0]
        at org.opensearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:52) [opensearch-2.13.0.jar:2.13.0]
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144) [?:?]
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642) [?:?]
        at java.base/java.lang.Thread.run(Thread.java:1583) [?:?]
Caused by: java.lang.reflect.InvocationTargetException
        at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:74) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
        at org.opensearch.security.support.ReflectionHelper.instantiateAAA(ReflectionHelper.java:62) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        ... 28 more
Caused by: java.lang.RuntimeException: java.lang.IllegalArgumentException: Illegal base64 character 2b
        at com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator.<init>(HTTPSamlAuthenticator.java:154) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
        at org.opensearch.security.support.ReflectionHelper.instantiateAAA(ReflectionHelper.java:62) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        ... 28 more
Caused by: java.lang.IllegalArgumentException: Illegal base64 character 2b
        at java.base/java.util.Base64$Decoder.decode0(Base64.java:852) ~[?:?]
        at java.base/java.util.Base64$Decoder.decode(Base64.java:570) ~[?:?]
        at java.base/java.util.Base64$Decoder.decode(Base64.java:593) ~[?:?]
        at com.amazon.dlic.auth.http.saml.AuthTokenProcessorHandler.createJwkFromSettings(AuthTokenProcessorHandler.java:245) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        at com.amazon.dlic.auth.http.saml.AuthTokenProcessorHandler.<init>(AuthTokenProcessorHandler.java:113) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        at com.amazon.dlic.auth.http.saml.HTTPSamlAuthenticator.<init>(HTTPSamlAuthenticator.java:148) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        at java.base/jdk.internal.reflect.DirectConstructorHandleAccessor.newInstance(DirectConstructorHandleAccessor.java:62) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstanceWithCaller(Constructor.java:502) ~[?:?]
        at java.base/java.lang.reflect.Constructor.newInstance(Constructor.java:486) ~[?:?]
        at org.opensearch.security.support.ReflectionHelper.instantiateAAA(ReflectionHelper.java:62) ~[opensearch-security-2.13.0.0.jar:2.13.0.0]
        ... 28 more
f-galland commented 2 months ago

The exchange_key line in my config.yml:

            exchange_key: 'MIIDqDCCApCgAwIBAgIGAZD+2TSaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0xNjk2NzIwNDEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTAeFw0yNDA3MjkxNDE1MDZaFw0zNDA3MjkxNDE2MDVaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5jaXNjbzENMAsGA1UECgwET2t0YTEUMBIGA1UECwwLU1NPUHJvdmlkZXIxFTATBgNVBAMMDGRldi0xNjk2NzIwNDEcMBoGCSqGSIb3DQEJARYNaW5mb0Bva3RhLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANBGKQJoWAE2hFLJ71a0pKIaeR/QrgkkhMmLktKUQ31BpTAMvxO/ZT14GtPHdFic5b8Wsf7hsgDo99mqKAsfTOCNr3yz1OOpjReqyLXG1X6Vnw5w+Isktgd5b1MQv4wQDGT+YArUwGMCBpaQKPkfG1T8dVxExE4sIMAW4oyCM8VmfYTmEaR3LX5Qv3O2/iUUfwtANHkp3vtdoVebCIevDFli+Ih9oJOkQwolI/hVH1UiA5ajN8zu26rlPqEcq2eHXknxpPe8h/otGsrTMF2sdnyZok/4pllfmZ2KeWK7QxhI25QWN2tTtdsnMhfFGxJ5+aiGuWs4MzG0ty+/TJRKvuUCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAV9Y62RTP2xctJ2S6rSIMStukFgx9q0ULEk5gXvnTVwBSQIY1d0u7ss0v3rKu1u3HTUBucvj7GI1O3ZDHasitvT355d7YQTeCpgpeJuXSAbe5juWVsp70gSPJmEGs87WvdvpjT8otSpxXIxeDkhZGj6nrtt2tj75qS1SRn+nrNSPdODP/nV38c+cw5RR6CXR5kHkDjT/EVYQt0aM5uVdD5f4YyhN4ir4lsi+Vjfk5KBL+8G3CVZda9BY0ljfzn2DzH7uOXJR4r6zLuWd4AwLF8a87CEkY/FdBmFEWDI6erJ12aG2STbSMh1aoJNFvIVqd2T/ahhklGBaBD1LGbpHEOA=='
f-galland commented 2 months ago

I've tried using the above key with all possible scalar types without success. It might not be possible to work this bug around.

This issue seems to be caused by the use of the following line of the opensearch security plugin:

f-galland commented 2 months ago

I was able to reproduce the issue while setting this up in opensearch 2.13. I'm bringing up a 2.15 environment to test whether this has been fixed. We may need to report this as an issue to opensearch.

f-galland commented 2 months ago

By @gdiazlo suggestion, I tried using a 64 characters long random string as an exchange_key, making sure it didn't include any non-alphabetical characters. Doing so I could login to the dashboard using my okta account.

I'm leaving a copy of my config.yml for reference:

config.yml ```yml --- # This is the main OpenSearch Security configuration file where authentication # and authorization is defined. # # You need to configure at least one authentication domain in the authc of this file. # An authentication domain is responsible for extracting the user credentials from # the request and for validating them against an authentication backend like Active Directory for example. # # If more than one authentication domain is configured the first one which succeeds wins. # If all authentication domains fail then the request is unauthenticated. # In this case an exception is thrown and/or the HTTP status is set to 401. # # After authentication authorization (authz) will be applied. There can be zero or more authorizers which collect # the roles from a given backend for the authenticated user. # # Both, authc and auth can be enabled/disabled separately for REST and TRANSPORT layer. Default is true for both. # http_enabled: true # transport_enabled: true # # For HTTP it is possible to allow anonymous authentication. If that is the case then the HTTP authenticators try to # find user credentials in the HTTP request. If credentials are found then the user gets regularly authenticated. # If none can be found the user will be authenticated as an "anonymous" user. This user has always the username "anonymous" # and one role named "anonymous_backendrole". # If you enable anonymous authentication all HTTP authenticators will not challenge. # # # Note: If you define more than one HTTP authenticators make sure to put non-challenging authenticators like "proxy" or "clientcert" # first and the challenging one last. # Because it's not possible to challenge a client with two different authentication methods (for example # Kerberos and Basic) only one can have the challenge flag set to true. You can cope with this situation # by using pre-authentication, e.g. sending a HTTP Basic authentication header in the request. # # Default value of the challenge flag is true. # # # HTTP # basic (challenging) # proxy (not challenging, needs xff) # kerberos (challenging) # clientcert (not challenging, needs https) # jwt (not challenging) # host (not challenging) #DEPRECATED, will be removed in a future version. # host based authentication is configurable in roles_mapping # Authc # internal # noop # ldap # Authz # ldap # noop _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: 0 http_authenticator: type: basic challenge: false authentication_backend: type: intern saml_auth_domain: http_enabled: true transport_enabled: false order: 1 http_authenticator: type: saml challenge: true config: idp: metadata_url: 'https://orgname.okta.com/app/abcdef1234567890/sso/saml/metadata' entity_id: 'http://www.okta.com/abcdef1234567890' sp: entity_id: wazuh-saml kibana_url: https://192.168.58.29 roles_key: Roles exchange_key: 'MIIDqDCCApCgAwIBAgIGAZD2TSaMA0GCSqGSIb3DQEBCwUAMIGUMQswCQYDVQQGa' authentication_backend: type: noop 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 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 ... # auth_failure_listeners: # ip_rate_limiting: # type: ip # allowed_tries: 10 # time_window_seconds: 3600 # block_expiry_seconds: 600 # max_blocked_clients: 100000 # max_tracked_clients: 100000 # internal_authentication_backend_limiting: # type: username # authentication_backend: intern # allowed_tries: 10 # time_window_seconds: 3600 # block_expiry_seconds: 600 # max_blocked_clients: 100000 # max_tracked_clients: 100000 ```
gdiazlo commented 2 months ago

This has been solved and will be addressed in the documentation.