opensearch-project / opensearch-py

Python Client for OpenSearch
https://opensearch.org/docs/latest/clients/python/
Apache License 2.0
338 stars 170 forks source link

[BUG] SSL connection problem with Cient Certificates Auth #484

Closed EdEngineering closed 1 year ago

EdEngineering commented 1 year ago

What is the bug?

When trying to connect to OpenSearch Instace with client certificate and key i get this error:

opensearchpy.exceptions.SSLError: ConnectionError([SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:2635)) caused by: SSLError([SSL: SSLV3_ALERT_BAD_CERTIFICATE] sslv3 alert bad certificate (_ssl.c:2635))

OpenSearch logs shows this, i believe to be relevant

io.netty.handler.codec.DecoderException: javax.net.ssl.SSLHandshakeException: Empty client certificate chain

How can one reproduce the bug?

opensearch.yml

cluster.name: os-cluster
network.host: 0.0.0.0

bootstrap.memory_lock: "true" # along with the memlock settings below, disables swapping

plugins.security.allow_unsafe_democertificates: true
plugins.security.ssl.http.enabled: true
plugins.security.ssl.http.pemtrustedcas_filepath: certificates/ca/ca.pem
plugins.security.ssl.http.clientauth_mode: REQUIRE
plugins.security.ssl.transport.enabled: true
plugins.security.ssl.transport.pemtrustedcas_filepath: certificates/ca/ca.pem
plugins.security.ssl.transport.enforce_hostname_verification: false

plugins.security.authcz.admin_dn:
  - CN=ADMIN,O=ORG,L=XXXXXXXXX,ST=XXXXXXXXX,C=XX
plugins.security.nodes_dn:
  - CN=node1,O=ORG,L=XXXXXXXXX,ST=XXXXXXXXX,C=XX

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"]
cluster.routing.allocation.disk.threshold_enabled: false

config.yml

_meta:
  type: "config"
  config_version: 2

config:
  dynamic:
    http:
      anonymous_auth_enabled: false
    authc:
      internal_auth:
        order: 0
        description: "HTTP basic authentication using the internal user database"
        http_enabled: true
        transport_enabled: true
        http_authenticator:
          type: basic
          challenge: false
        authentication_backend:
          type: internal
      clientcert_auth_domain:
        description: "Authenticate via SSL client certificates"
        http_enabled: true
        transport_enabled: true
        order: 1
        http_authenticator:
          type: clientcert
          config:
            username_attribute: cn #optional, if omitted DN becomes username
          challenge: false
        authentication_backend:
          type: noop

What is the expected behavior?

It's expected to authenticate correctly with opensearch and make the connection.

What is your host/environment?

NAME="Ubuntu" VERSION="20.04.6 LTS (Focal Fossa)"

Do you have any additional context?

I've try connecting just with curl client, and javascript client and it works fine, so its not related to the certificates.

dblock commented 1 year ago

Are you using the python client? What's your code? What does curl http... return?

EdEngineering commented 1 year ago

Sorry i did not specify that part, so yes i'm using the python client with this code (replaced sensitive values):

from opensearchpy import OpenSearch
import os
# connect to an instance of OpenSearch

host = os.getenv('HOST', default='MY_HOST')
port = int(os.getenv('PORT', 9200))
auth = (
    os.getenv('USERNAME_OPENSEARCH', 'myusername'), 
    os.getenv('PASSWORD', 'mypassword')
)
print("host: ", host, "port: ", port, "auth: ", auth)
# ca_certs_path = '/path/to/ca.pem' # Provide a CA bundle if you use intermediate CAs with your root CA.

# Optional client certificates if you don't want to use HTTP basic authentication.
client_cert_path = '/path/to/cert.pem'
client_key_path = 'path/to/cert.key'

# Create the client with SSL/TLS enabled, but hostname verification disabled.
client = OpenSearch(
    hosts = [{'host': host, 'port': port}],
    http_compress = True, # enables gzip compression for request bodies
    http_auth = auth,
    client_cert = client_cert_path,
    client_key = client_key_path,
    use_ssl = True,
    verify_certs = False,
    ssl_assert_hostname = False,
    ssl_show_warn = False,
    ca_certs = ca_certs_path
)

For the curl command test i did was, it did run well:

curl -u $user:$password -E certificates/$user/$user.pem --key certificates/$user/$user.key -X GET "https://HOST:9200/"  -k

# Response i got, whats expected

{
  "name" : "node1",
  "cluster_name" : "os-cluster",
  "cluster_uuid" : "GA5VxR3ETqWVO1LiTa50ww",
  "version" : {
    "distribution" : "opensearch",
    "number" : "2.8.0",
    "build_type" : "tar",
    "build_hash" : "db90a415ff2fd428b4f7b3f800a51dc229287cb4",
    "build_date" : "2023-06-03T06:24:25.112415503Z",
    "build_snapshot" : false,
    "lucene_version" : "9.6.0",
    "minimum_wire_compatibility_version" : "7.10.0",
    "minimum_index_compatibility_version" : "7.0.0"
  },
  "tagline" : "The OpenSearch Project: https://opensearch.org/"
}
EdEngineering commented 1 year ago

Found a solution, Sorry for the trouble and thanks:

In your case, since you're using Client Certificate Authentication, you need to use the RequestsHttpConnection class because it supports sending client certificates. The Urllib3HttpConnection class does not support this feature.

So i just imported the class and added a line to the Client

from opensearchpy import OpenSearch, RequestsHttpConnection
client = OpenSearch(
    ...
    ...
    connection_class = RequestsHttpConnection,
    )
dblock commented 1 year ago

@EdEngineering is that something we (you?) could add to the user guide? The https://github.com/opensearch-project/opensearch-py/blob/main/guides/ssl.md doc is severely lacking

EdEngineering commented 1 year ago

Sure i can add that to the docs, I'll be making a pull request later.