# OpenSearch .NET Client Certificate Authentication

**URL:** <https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128>\
**Category:** Security\
**Created:** [February 28, 2024, 2:04am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128 "2024-02-28T02:04:33Z")\
**Posts on this page:** 8\
**Page:** 1

<div class="post-metadata">

**Author:** ![palakornlegend](https://avatars.discourse-cdn.com/v4/letter/p/848f3c/32.png) [@palakornlegend](https://forum.opensearch.org/u/palakornlegend)\
**Post date:** [February 28, 2024, 2:04am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/1 "2024-02-28T02:04:33Z")

</div>

**Versions** (relevant - OpenSearch/Dashboard/Server OS/Browser):  
OpenSearch 2.11.1

Hi, OpenSearch community  
I’ve finished configuration to use my own certificate and able to access the localhost:9200 via HTTPS

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/a/aeacaabe055633103505c2089f5b3b9067b2dd4f.png)

The problem is when I’m trying to create the index and send the document via C# .NET OpenSearch.Client library to [https://localhost:9200](https://localhost:9200) I’ve got the 401 code return back this is my error message : Could not authenticate with the specified node. Try verifying your credentials or check your Shield configuration… Call: Status code 401 from: POST /wether-forecast-demo-3/\_doc

and here is my opensearch.yml

```auto
---
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.pemkey_password: Test@1234
plugins.security.ssl.transport.pemcert_filepath: certificate.pem
plugins.security.ssl.transport.pemkey_filepath: privatekey.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.pemkey_password: Test@1234
plugins.security.ssl.http.pemcert_filepath: certificaotate.pem
plugins.security.ssl.http.pemkey_filepath: privatekey.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-config", ".plugins-ml-connector", ".plugins-ml-model-group", ".plugins-ml-model", ".plugins-ml-task", ".plugins-ml-conversation-meta", ".plugins-ml-conversation-interactions", ".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", ".geospatial-ip2geo-data*"]
node.max_local_storage_nodes: 3
######## End OpenSearch Security Demo Configuration ########

```

I would appreciate If do you guys have any suggestion or C# Client example code to perform create index.

Thanks in advanced.

---

<div class="post-metadata">

**Author:** ![Gsmitt](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.opensearch.org/gsmitt/32/1718_2.png) [@Gsmitt](https://forum.opensearch.org/u/Gsmitt)\
**Post date:** [February 28, 2024, 5:46am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/2 "2024-02-28T05:46:12Z")

</div>

hey @palakornlegend

Sis you setup Opensearch-dashboards with certificates also?

---

<div class="post-metadata">

**Author:** ![palakornlegend](https://avatars.discourse-cdn.com/v4/letter/p/848f3c/32.png) [@palakornlegend](https://forum.opensearch.org/u/palakornlegend)\
**Post date:** [February 28, 2024, 5:58am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/3 "2024-02-28T05:58:36Z")

</div>

Hi Gsmitt @Gsmitt  
Thanks for your reply.  
No, I’m just only finished setting only for opensearch node(Opensearch.yml).  
So do I have to setup opensearch-dashboards also?

Thanks

---

<div class="post-metadata">

**Author:** ![Mantas](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@Mantas](https://forum.opensearch.org/u/Mantas)\
**Post date:** [February 28, 2024, 2:40pm UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/4 "2024-02-28T14:40:41Z")

</div>

Hi @palakornlegend ,

How are you authenticating to your node (internal user/IDP/cert)? Could you please share your config.yml file and any config you have for your OpenSearch.Client?

thanks,  
mj

---

<div class="post-metadata">

**Author:** ![Gsmitt](https://sea1.discourse-cdn.com/flex019/user_avatar/forum.opensearch.org/gsmitt/32/1718_2.png) [@Gsmitt](https://forum.opensearch.org/u/Gsmitt)\
**Post date:** [February 28, 2024, 10:20pm UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/5 "2024-02-28T22:20:32Z")

</div>

> [@palakornlegend](#):
>
> So do I have to setup opensearch-dashboards also?

No its not necessary. LIke @Mantas suggested we would need more info.

---

<div class="post-metadata">

**Author:** ![palakornlegend](https://avatars.discourse-cdn.com/v4/letter/p/848f3c/32.png) [@palakornlegend](https://forum.opensearch.org/u/palakornlegend)\
**Post date:** [February 29, 2024, 1:56am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/6 "2024-02-29T01:56:26Z")

</div>

@Gsmitt @Mantas  
I’ve done the configuration follow by the OpenSearch document and here are my configs

OpenSearch-Dashboards.yml  
opensearch.hosts: [[https://localhost:9200](https://localhost:9200)]  
server.ssl.keyPassphrase: Test@1234  
server.ssl.enabled: true  
opensearch.ssl.verificationMode: none  
server.ssl.certificate: /usr/share/opensearch-dashboards/config/certificate.pem  
server.ssl.key: /usr/share/opensearch-dashboards/config/privatekey.pem  
opensearch.ssl.certificateAuthorities: [“/usr/share/opensearch-dashboards/config/root-ca.pem”]  
opensearch.username: admin   
opensearch.password: admin  
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: true  
server.host: ‘0.0.0.0’

and here is the config.yml inside opensearch-security folder in fact this file is auto generated by OpenSearch while I’m starting compose the docker and I didn’t deal with this config

## OpenSearch-Security/Config.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 [Pattern (Java Platform SE 7 )](https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html) for regex help  
###### more information about XFF [X-Forwarded-For - Wikipedia](https://en.wikipedia.org/wiki/X-Forwarded-For)  
###### and here [RFC 7239 - Forwarded HTTP Extension](https://tools.ietf.org/html/rfc7239)  
###### and [Apache Tomcat 8 Configuration Reference (8.0.53) - The Valve Component](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: 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

and here is my instance in .Net to initialize logger to create index to OpenSearch

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/f/fdf5eaa7eb91cc1769a129fc53882e8470b45636.png)

This is error message when I’m start calling to [https://localhost:9200](https://localhost:9200) with .NET

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/2/2f6c577184858ec57433fea2fbe273fe747356a0.png)

I’ve also try to call the API by Postman with the same certificate as my .NET application and this is the result.

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/3/3e58a512ec7fd3f76396b772b7d393ec24b08349.png)

If you need more information or any config please letting me know  
Thanks for your help.

---

<div class="post-metadata">

**Author:** ![palakornlegend](https://avatars.discourse-cdn.com/v4/letter/p/848f3c/32.png) [@palakornlegend](https://forum.opensearch.org/u/palakornlegend)\
**Post date:** [February 29, 2024, 10:32am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/7 "2024-02-29T10:32:25Z")

</div>

@Gsmitt @Mantas  
Thanks for your suggestions.  
Now I’m finally fixed the issue my OpenSearch node was setup like I show you the configuration.  
All I’ve done for fixed the issue is to set the BasicAuthentication for my logger instance and set the Authentication in Postman.  
My .NET Application Logger Instance setting.

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/d/d2386188a92ea3466d742c312a5c0dc5a69acaa4.png)

My Postman

 ![image](https://us1.discourse-cdn.com/flex019/uploads/mauve_hedgehog/original/2X/0/07f4e135e4e773763c9dc42073569cc6a0495bfc.png)

This is my POC and these all met my expectation and in my thought this is the basic authentication or demo security for testing purpose so do you guys have any example or suggestion if I need to implement the OpenSearch on production environment.

Thanks for your help.

---

<div class="post-metadata">

**Author:** ![Mantas](https://avatars.discourse-cdn.com/v4/letter/m/7bcc69/32.png) [@Mantas](https://forum.opensearch.org/u/Mantas)\
**Post date:** [February 29, 2024, 10:38am UTC](https://forum.opensearch.org/t/opensearch-net-client-certificate-authentication/18128/8 "2024-02-29T10:38:50Z")

</div>

@palakornlegend, I am glad to hear it is fixed now, and thank you for documenting your solution here, it will benefit the community.

Best,  
mj
