Deploy OpenSearch with Helm

Use Helm to deploy an OpenSearch cluster on Kubernetes

What should you get after following the guide?

If you follow along with the instructions I have written here, you should have a set of 4 Kubernetes pods running 3 instances of the OpenSearch server software and 1 instance of the OpenSearch Dashboards software. OpenSearch should be configured for production usage with the security plugin enabled to provide authentication and TLS connections between cluster members.

I make no warranty or guarantee about the safety, security, or success of anyone who follows these instructions. Consult with an OpenSearch, security, or other expert on your particular use case.

What you need to follow these instructions?

You will need access to a Kubernetes cluster with Helm and cert-manager installed. I used k3s to run a single-node Kubernetes cluster on a virtual machine with 8 vCPUs, 24GB of RAM, and 80GB of storage. You could probably run this deployment on a smaller machine with fewer resources, but you will not be able to run very large or intensive workloads.

I used the built-in kubectl command to apply the secrets and cert-manager configuration to Kubernetes.

For installing Helm, I recommend following the official Helm installation guide for the platform on which you are running Helm.

For installing cert-manager, I recommend following the official cert-manager installation instructions.

Create SSL/TLS certificates using cert-manager and Kubernetes

If you have access to generating signed SSL/TLS certificates and know how to use them, you should use signed SSL/TLS certificates and skip this step. The cert-manager website has documentation on setting up an ACME issuer using Let's Encrypt to do this.

Creating simple self-signed certificates for OpenSearch is straightforward with cert-manager. The following example Kubernetes configuration file will provision:

  1. a self-signed Issuer
  2. a root Certificate Authority
  3. a Certificate Authority issuer
  4. two Certificate resources for OpenSearch nodes and the admin client to use

Self-signed certificates are suitable for demonstration purposes and for when you only need to encrypt traffic in transit without outside certificate validation. For this deployment, the OpenSearch cluster uses the CA certificate to validate the self-signed certificates were issued using the CA certificate created through cert-manager. I was fine with that for my needs because if someone can successfully intercept the node traffic and impersonate nodes using a self-signed certificate issued using the CA certificate then more serious problems have already occurred for me to address.

---
# Create a self-signed certificate issuer for issuing a self-signed root certificate
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: selfsigned-issuer
spec:
  selfSigned: {}
---
# Generate a self-signed certificate to function as the root signing certificate
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: ca-cert
spec:
  secretName: ca-cert # cert-manager stores the signing certificate as a Kubernetes secret
  duration: 87600h # Last for 10 years before expiring
  renewBefore: 360h
  isCA: true
  privateKey:
    size: 2048
    algorithm: RSA
    encoding: PKCS1
  commonName: cacert
  dnsNames:
    - caroot
  issuerRef:
    name: selfsigned-issuer # User the self-signed issuer we declared first
---
# Create a CA cert-manager issuer using the self-signed signing certificate
apiVersion: cert-manager.io/v1
kind: Issuer
metadata:
  name: ca-issuer
spec:
  ca:
    secretName: ca-cert
---
# Generate self-signed node certificates for OpenSearch using the CA issuer
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: opensearch-cluster # Change to match your cluster name if needed
spec:
  privateKey:
    encoding: PKCS8 # You must use PKCS8 format for compatibility with Java, in which OpenSearch is written
  secretName: opensearch-cluster-tls # cert-manager stores the certificates as Kubernetes secrets. Change the name if needed
  usages:
    - digital signature
    - key encipherment
    - server auth
    - client auth
  literalSubject: "CN=opensearch-cluster-master" # You can change this to what you want, but this must match your opensearch.yml config
  dnsNames: # The dnsNames should include the DNS records your pods will use in Kuberentes to resolve pod IP addresses
    - opensearch-cluster-master-0
    - opensearch-cluster-master-1
    - opensearch-cluster-master-2
    - opensearch-cluster-master
  issuerRef: # Use the CA issuer declared earlier
    name: ca-issuer
    kind: Issuer
---
# Generate a separate certificate to use to authenticate the OpenSearch security admin script with OpenSearch
apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: opensearch-cluster-admin
spec:
  privateKey:
    encoding: PKCS8 # You must use PKCS8 format for compatibility with Java, in which OpenSearch is written
  secretName: opensearch-cluster-admin-cert # cert-manager stores the certificate as a Kubernetes secret
  usages:
    - digital signature
    - key encipherment
    - server auth
    - client auth
  literalSubject: "CN=opensearch-cluster-admin" # You can change this value, but you must put the value in your opensearch.yml config later
  dnsNames:
    - opensearch-cluster-admin # You can change this value, but you must put the value in your opensearch.yml config later
  issuerRef:
    name: ca-issuer
    kind: Issuer

To create the certificates through cert-manager, copy the configuration to a file in your local environment, and apply the configuration using kubectl apply -f or whatever you use to manage your Kubernetes cluster. If Kuberentes successfully applies the configuration you should have secrets available with the SSL/TLS certificate files, keys, and root CA certificate.

Here are the Helm charts, config files, and values you need to deploy OpenSearch using Helm to your Kubernetes cluster

OpenSearch Security Config Files

action_groups.yml secret declaration

The actions_groups.yml file defines Action Groups in OpenSearch. This deployment does not use Action Groups, and the secret defined below results in an empty configuration file.

If you are not using Action Groups you can skip this section.

If you want to use Action Groups, you can create a secret by extending the example below and applying the configuration using kubectl apply -f.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-action-groups-config
type: Opaque
stringData:
  action_groups.yml: |-
    _meta:
      type: "actiongroups"
      config_version: 2

allowlist.yml secret declaration

This deployment relies on the default Helm configuration for the allowlist.yml file. The Helm chart for OpenSearch does not expose a way to define your own allowlist.yml file. If you want to customize this file you will have to expose the file to the pod by some other way.

audit.yml secret declaration

This deployment relies on the default Helm configuration for the audit.yml file. The Helm chart for OpenSearch does not expose a way to define your own audit.yml file. If you want to customize this file you will have to expose the file to the pod by some other way.

config.yml secret declaration

The configuration in this secret disables anonymous HTTP authentication for OpenSearch and only allows authentication using HTTP Basic authentication against the internal users database. The internal users database is the users defined in the internal_users.yml file.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-security-config
type: Opaque
stringData:
  config.yml: |-
    _meta:
      type: "config"
      config_version: 2
    config:
      dynamic:
        http:
          anonymous_auth_enabled: false # Require HTTP authentication
          xff: # explicitly disabled X-Forwarded-For for proxied requests
            enabled: false
      authc:
        basic_internal_auth_domain:
          description: "Authenticate via HTTP Basic against internal users database"
          http_enabled: true
          transport_enabled: true
          order: 1
          http_authenticator:
            type: basic
            challenge: true
          authentication_backend:
            type: internal

Copy the configuration below to a file and run kubectl apply -f <the-name-of-the-file>. Replace <the-name-of-the-file> with the actual name you gave the file. Kubernetes should create a secret named opensearch-security-config.

internal_users.yml secret declaration

The internal_users.yml file declares the internal user database for OpenSearch to use when authenticating HTTP requests against the internal database is enabled. The following configuration would create an admin user, a user for the OpenSearch Dashboard server, and a non-admin example user. The value of the hash key in each user declaration should be replaced with a hash from a password generated using the OpenSearch Security hash script. If you do not have a pod running OpenSearch yet with the OpenSearch Security hash script, you can use something else to produce a bcrypt hash of your password.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-internal-users-config
type: Opaque
stringData:
  internal_users.yml: |-
    _meta:
      type: "internalusers"
      config_version: 2
    admin:
      hash: "some-password-hashed-using-the-opensearch-security-hash-script"
      reserved: true
      backend_roles: # backend roles are defined in roles.yml
      - "admin"
      description: "admin user"
    kibanaserver: # Called Kibana for legacy compatibility reasons
      hash: "some-password-hashed-using-the-opensearch-security-hash-script"
      reserved: true
      backend_roles:
      - "kibana_server"
      description: "OpenSearch Dashboards user"
    someotheruser:
      hash: "some-password-hashed-using-the-opensearch-security-hash-script"
      reserved: false
      backend_roles:
      - "somebackendrole"

Copy the configuration below to a file and run kubectl apply -f <the-name-of-the-file>. Replace <the-name-of-the-file> with the actual name you gave the file.

Use htpasswd to generate a bcrypt hash of your password

You should not use websites that offer to generate bcrypt hashes for you for passwords or any other sensitive information. You cannot be certain the websites are not logging entries somewhere for later misuse.

You can use the htpasswd CLI library from the Apache web server project to generate a bcrypt hash of a password.

To install the htpasswd tool on RedHat, CentOS, Oracle Linux, Rocky Linux, AlmaLinux, and dnf/yum Linux distros install the httpd-tools package, such as by running dnf install httpd-tools or yum install httpd-tools. You may need to use sudo or login as root if your normal user does not have privileges for installing packages.

To install the htpasswd tool on Ubuntu, Debian, and apt/deb Linux distros install the apachd2-utils package, such as by running apt-get install apache2-utils. You may need to use sudo or login as root if your normal user does not have privileges for installing packages.

After you have installed htpasswd, you can generate a hashed password by running the following shell script:

read -s -p "Password to hash:" password && printf "\nHash: $(htpasswd -bnBC 10 "" $password | tr -d ':\n')\n"

This simple shell script executes two commands. The first command - read -s -p "Password to hash:" password - prompts for text input without showing what you type in. The input is stored in a variable called password. The second command - printf "\nHash: $(htpasswd -bnBC 10 "" $password | tr -d ':\n')\n" - actually comprises a command for printing output and another nested command for generating the bcrypt hash using the htpasswd tool. When the shell evaluates the second command, the shell first executes the nested htpasswd -bnBC 10 "" $password | tr -d ':\n' command first using the input stored in the password variable in the read command. The output from the nested command is then passed up to the printf command, which embeds the output from htpasswd in between the `"\nHash:" and the trailing "\n" new line character. If everything works well, the output should look something like:

~$ read -s -p "Password to hash:" password && printf "\nHash: $(htpasswd -bnBC 10 "" $password | tr -d ':\n')\n"
Password to hash:
Hash: $2y$10$PPfWs1vc51LfRMddY4ZTo.8tL33.M21lz6p6jQwjFhLT5khai8fAO
~$

The output after the word Hash: is the bcrypt hash of the input you provided when running the script. You should be able to use the hash (like $2y$10$PPfWs1vc51LfRMddY4ZTo.8tL33.M21lz6p6jQwjFhLT5khai8fAO from the example) as the value of the hash key in your internal_users.yml configuration.

Do not use $2y$10$PPfWs1vc51LfRMddY4ZTo.8tL33.M21lz6p6jQwjFhLT5khai8fAO from the example as your hash. Generate your own hash that you do not share with anyone.

nodes_dn.yml secret declaration

This deployment relies on the default Helm configuration for the nodes_dn.yml file. The Helm chart for OpenSearch does not expose a way to define your own nodes_dn.yml file. If you want to customize this file you will have to expose the file to the pod by some other way.

roles_mapping.yml secret declaration

OpenSearch Security uses the roles_mapping.yml file to associate specific permissions to specific users. The configuration below is based on the default OpenSearch Security configuration. This configuration defines a roles mapping for the admin user and default users for OpenSearch Dashboards and Logstash data ingestion.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-internal-users-config
type: Opaque
stringData:
  roles_mapping.yml: |-
    _meta:
      type: "rolesmapping"
      config_version: 2
    all_access:
      reserved: true
      hidden: false
      backend_roles:
      - "admin"
      hosts: []
      users: []
      and_backend_roles: []
      description: "Maps admin to all_access"
    manage_snapshots:
      reserved: true
      hidden: false
      backend_roles:
      - "snapshotrestore"
      hosts: []
      users: []
      and_backend_roles: []
    logstash:
      reserved: false
      hidden: false
      backend_roles:
      - "logstash"
      hosts: []
      users: []
      and_backend_roles: []
    own_index:
      reserved: false
      hidden: false
      backend_roles: []
      hosts: []
      users:
      - "*"
      and_backend_roles: []
      description: "Allow full access to an index named like the username"
    kibana_user:
      reserved: false
      hidden: false
      backend_roles:
      - "kibanauser"
      hosts: []
      users: []
      and_backend_roles: []
      description: "Maps kibanauser to kibana_user"
    readall:
      reserved: true
      hidden: false
      backend_roles:
      - "readall"
      hosts: []
      users: []
      and_backend_roles: []
    kibana_server:
      reserved: true
      hidden: false
      backend_roles: []
      hosts: []
      users:
      - "kibanaserver"
      and_backend_roles: []

Copy the configuration below to a file and run kubectl apply -f <the-name-of-the-file>. Replace <the-name-of-the-file> with the actual name you gave the file.

The roles.yml secret declaration

The roles.yml file declares all of the available OpenSearch Security roles using OpenSearch Security's permissions primitives. The roles I have included below are taken from the roles that OpenSearch Security generates when you initialize the demo configuration. The roles declarations do not do anything on their own. You must map the roles in the roles_mapping.yml file to individual users in order for the roles to have an effect on what users can and cannot access.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-internal-users-config
type: Opaque
stringData:
  roles.yml: |-
    _meta:
      type: "roles"
      config_version: 2

    # Restrict users so they can only view visualization and dashboard on OpenSearchDashboards
    kibana_read_only:
      reserved: true

    # The security REST API access role is used to assign specific users access to change the security settings through the REST API.
    security_rest_api_access:
      reserved: true

    security_rest_api_full_access:
      reserved: true
      cluster_permissions:
        - "restapi:admin/actiongroups"
        - "restapi:admin/allowlist"
        - "restapi:admin/config/update"
        - "restapi:admin/internalusers"
        - "restapi:admin/nodesdn"
        - "restapi:admin/roles"
        - "restapi:admin/rolesmapping"
        - "restapi:admin/ssl/certs/info"
        - "restapi:admin/ssl/certs/reload"
        - "restapi:admin/tenants"

    # Allows users to view monitors, destinations and alerts
    alerting_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/alerting/alerts/get"
        - "cluster:admin/opendistro/alerting/destination/get"
        - "cluster:admin/opendistro/alerting/monitor/get"
        - "cluster:admin/opendistro/alerting/monitor/search"
        - "cluster:admin/opensearch/alerting/comments/search"
        - "cluster:admin/opensearch/alerting/findings/get"
        - "cluster:admin/opensearch/alerting/remote/indexes/get"
        - "cluster:admin/opensearch/alerting/workflow/get"
        - "cluster:admin/opensearch/alerting/workflow_alerts/get"

    # Allows users to view and acknowledge alerts
    alerting_ack_alerts:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/alerting/alerts/*"
        - "cluster:admin/opendistro/alerting/chained_alerts/*"
        - "cluster:admin/opendistro/alerting/workflow_alerts/*"
        - "cluster:admin/opensearch/alerting/comments/*"

    # Allows users to use all alerting functionality
    alerting_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/alerting/*"
        - "cluster:admin/opensearch/alerting/*"
        - "cluster:admin/opensearch/notifications/feature/publish"
        - "cluster_monitor"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/aliases/get"
            - "indices:admin/mappings/get"
            - "indices_monitor"

    # Allow users to read Anomaly Detection detectors and results
    anomaly_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/ad/detector/info"
        - "cluster:admin/opendistro/ad/detector/search"
        - "cluster:admin/opendistro/ad/detector/validate"
        - "cluster:admin/opendistro/ad/detectors/get"
        - "cluster:admin/opendistro/ad/result/search"
        - "cluster:admin/opendistro/ad/result/topAnomalies"
        - "cluster:admin/opendistro/ad/tasks/search"

    # Allows users to use all Anomaly Detection functionality
    anomaly_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/ingest/pipeline/delete"
        - "cluster:admin/ingest/pipeline/put"
        - "cluster:admin/opendistro/ad/*"
        - "cluster_monitor"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/aliases/get"
            - "indices:admin/mappings/fields/get"
            - "indices:admin/mappings/fields/get*"
            - "indices:admin/mappings/get"
            - "indices:admin/resolve/index"
            - "indices:admin/setting/put"
            - "indices:data/read/field_caps*"
            - "indices:data/read/search"
            - "indices_monitor"

    # Allow users to execute read only k-NN actions
    knn_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/knn_get_model_action"
        - "cluster:admin/knn_search_model_action"
        - "cluster:admin/knn_stats_action"

    # Allow users to use all k-NN functionality
    knn_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/knn_delete_model_action"
        - "cluster:admin/knn_get_model_action"
        - "cluster:admin/knn_remove_model_from_cache_action"
        - "cluster:admin/knn_search_model_action"
        - "cluster:admin/knn_stats_action"
        - "cluster:admin/knn_training_job_route_decision_info_action"
        - "cluster:admin/knn_training_job_router_action"
        - "cluster:admin/knn_training_model_action"
        - "cluster:admin/knn_update_model_graveyard_action"
        - "cluster:admin/knn_warmup_action"

    # Allow users to execute read only ip2geo datasource action
    ip2geo_datasource_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/geospatial/datasource/get"

    # Allow users to use all ip2geo datasource action
    ip2geo_datasource_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/geospatial/datasource/*"

    # Allows users to read Notebooks
    notebooks_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/notebooks/get"
        - "cluster:admin/opendistro/notebooks/list"

    # Allows users to all Notebooks functionality
    notebooks_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/notebooks/create"
        - "cluster:admin/opendistro/notebooks/delete"
        - "cluster:admin/opendistro/notebooks/get"
        - "cluster:admin/opendistro/notebooks/list"
        - "cluster:admin/opendistro/notebooks/update"

    # Allows users to read observability objects
    observability_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/observability/get"

    # Allows users to all Observability functionality
    observability_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/observability/create"
        - "cluster:admin/opensearch/observability/delete"
        - "cluster:admin/opensearch/observability/get"
        - "cluster:admin/opensearch/observability/update"

    # Allows users to all PPL functionality
    ppl_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/ppl"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/mappings/get"
            - "indices:data/read/search*"
            - "indices:monitor/settings/get"

    # Allows users to read and download Reports
    reports_instances_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/reports/instance/get"
        - "cluster:admin/opendistro/reports/instance/list"
        - "cluster:admin/opendistro/reports/menu/download"

    # Allows users to read and download Reports and Report-definitions
    reports_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/reports/definition/get"
        - "cluster:admin/opendistro/reports/definition/list"
        - "cluster:admin/opendistro/reports/instance/get"
        - "cluster:admin/opendistro/reports/instance/list"
        - "cluster:admin/opendistro/reports/menu/download"

    # Allows users to all Reports functionality
    reports_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/reports/definition/create"
        - "cluster:admin/opendistro/reports/definition/delete"
        - "cluster:admin/opendistro/reports/definition/get"
        - "cluster:admin/opendistro/reports/definition/list"
        - "cluster:admin/opendistro/reports/definition/on_demand"
        - "cluster:admin/opendistro/reports/definition/update"
        - "cluster:admin/opendistro/reports/instance/get"
        - "cluster:admin/opendistro/reports/instance/list"
        - "cluster:admin/opendistro/reports/menu/download"

    # Allows users to use all asynchronous-search functionality
    asynchronous_search_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/asynchronous_search/*"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:data/read/search*"

    # Allows users to read stored asynchronous-search results
    asynchronous_search_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/asynchronous_search/get"

    # Allows user to use all index_management actions - ism policies, rollups, transforms
    index_management_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opendistro/ism/*"
        - "cluster:admin/opendistro/rollup/*"
        - "cluster:admin/opendistro/transform/*"
        - "cluster:admin/opensearch/controlcenter/lron/*"
        - "cluster:admin/opensearch/notifications/channels/get"
        - "cluster:admin/opensearch/notifications/feature/publish"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/opensearch/ism/*"
            - "indices:internal/plugins/replication/index/stop"

    # Allows users to use all cross cluster replication functionality at leader cluster
    cross_cluster_replication_leader_full_access:
      reserved: true
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/plugins/replication/index/setup/validate"
            - "indices:data/read/plugins/replication/changes"
            - "indices:data/read/plugins/replication/file_chunk"

    # Allows users to use all cross cluster replication functionality at follower cluster
    cross_cluster_replication_follower_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/plugins/replication/autofollow/update"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/plugins/replication/index/pause"
            - "indices:admin/plugins/replication/index/resume"
            - "indices:admin/plugins/replication/index/setup/validate"
            - "indices:admin/plugins/replication/index/start"
            - "indices:admin/plugins/replication/index/status_check"
            - "indices:admin/plugins/replication/index/stop"
            - "indices:admin/plugins/replication/index/update"
            - "indices:data/write/plugins/replication/changes"

    # Allows users to use all cross cluster search functionality at remote cluster
    cross_cluster_search_remote_full_access:
      reserved: true
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/shards/search_shards"
            - "indices:data/read/search"

    # Allow users to operate query assistant
    query_assistant_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/ml/config/get"
        - "cluster:admin/opensearch/ml/execute"
        - "cluster:admin/opensearch/ml/predict"
        - "cluster:admin/opensearch/ppl"

    # Allow users to read ML stats/models/tasks
    ml_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/ml/config/get"
        - "cluster:admin/opensearch/ml/connectors/get"
        - "cluster:admin/opensearch/ml/connectors/search"
        - "cluster:admin/opensearch/ml/controllers/get"
        - "cluster:admin/opensearch/ml/memory/conversation/get"
        - "cluster:admin/opensearch/ml/memory/conversation/interaction/search"
        - "cluster:admin/opensearch/ml/memory/conversation/list"
        - "cluster:admin/opensearch/ml/memory/conversation/search"
        - "cluster:admin/opensearch/ml/memory/interaction/get"
        - "cluster:admin/opensearch/ml/memory/interaction/list"
        - "cluster:admin/opensearch/ml/memory/trace/get"
        - "cluster:admin/opensearch/ml/model_groups/get"
        - "cluster:admin/opensearch/ml/model_groups/search"
        - "cluster:admin/opensearch/ml/models/get"
        - "cluster:admin/opensearch/ml/models/search"
        - "cluster:admin/opensearch/ml/profile/nodes"
        - "cluster:admin/opensearch/ml/stats/nodes"
        - "cluster:admin/opensearch/ml/tasks/get"
        - "cluster:admin/opensearch/ml/tasks/search"
        - "cluster:admin/opensearch/ml/tools/get"
        - "cluster:admin/opensearch/ml/tools/list"

    # Allows users to use all ML functionality
    ml_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/ml/*"
        - "cluster_monitor"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices_monitor"

    # Allows users to use all Notifications functionality
    notifications_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/notifications/*"

    # Allows users to read Notifications config/channels
    notifications_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/notifications/channels/get"
        - "cluster:admin/opensearch/notifications/configs/get"
        - "cluster:admin/opensearch/notifications/features"

    # Allows users to use all snapshot management functionality
    snapshot_management_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/notifications/feature/publish"
        - "cluster:admin/opensearch/snapshot_management/*"
        - "cluster:admin/repository/*"
        - "cluster:admin/snapshot/*"

    # Allows users to see snapshots, repositories, and snapshot management policies
    snapshot_management_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/snapshot_management/policy/explain"
        - "cluster:admin/opensearch/snapshot_management/policy/get"
        - "cluster:admin/opensearch/snapshot_management/policy/search"
        - "cluster:admin/repository/get"
        - "cluster:admin/snapshot/get"

    # Allows user to use point in time functionality
    point_in_time_full_access:
      reserved: true
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "manage_point_in_time"

    # Allows users to see security analytics detectors and others
    security_analytics_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/securityanalytics/alerts/get"
        - "cluster:admin/opensearch/securityanalytics/correlationAlerts/get"
        - "cluster:admin/opensearch/securityanalytics/correlations/findings"
        - "cluster:admin/opensearch/securityanalytics/correlations/list"
        - "cluster:admin/opensearch/securityanalytics/detector/get"
        - "cluster:admin/opensearch/securityanalytics/detector/search"
        - "cluster:admin/opensearch/securityanalytics/findings/get"
        - "cluster:admin/opensearch/securityanalytics/logtype/search"
        - "cluster:admin/opensearch/securityanalytics/mapping/get"
        - "cluster:admin/opensearch/securityanalytics/mapping/view/get"
        - "cluster:admin/opensearch/securityanalytics/rule/get"
        - "cluster:admin/opensearch/securityanalytics/rule/search"
        - "cluster:admin/opensearch/securityanalytics/threatintel/alerts/get"
        - "cluster:admin/opensearch/securityanalytics/threatintel/iocs/findings/get"
        - "cluster:admin/opensearch/securityanalytics/threatintel/iocs/list"
        - "cluster:admin/opensearch/securityanalytics/threatintel/monitors/search"
        - "cluster:admin/opensearch/securityanalytics/threatintel/sources/get"
        - "cluster:admin/opensearch/securityanalytics/threatintel/sources/search"

    # Allows users to use all security analytics functionality
    security_analytics_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/securityanalytics/alerts/*"
        - "cluster:admin/opensearch/securityanalytics/connections/*"
        - "cluster:admin/opensearch/securityanalytics/correlationAlerts/*"
        - "cluster:admin/opensearch/securityanalytics/correlations/*"
        - "cluster:admin/opensearch/securityanalytics/detector/*"
        - "cluster:admin/opensearch/securityanalytics/findings/*"
        - "cluster:admin/opensearch/securityanalytics/logtype/*"
        - "cluster:admin/opensearch/securityanalytics/mapping/*"
        - "cluster:admin/opensearch/securityanalytics/rule/*"
        - "cluster:admin/opensearch/securityanalytics/threatintel/*"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/mapping/put"
            - "indices:admin/mappings/get"

    # Allows users to view and acknowledge alerts
    security_analytics_ack_alerts:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/securityanalytics/alerts/*"
        - "cluster:admin/opensearch/securityanalytics/correlationAlerts/*"
        - "cluster:admin/opensearch/securityanalytics/threatintel/alerts/*"

    # Allows users to use all Flow Framework functionality
    flow_framework_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/flow_framework/*"
        - "cluster_monitor"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/aliases/get"
            - "indices:admin/mappings/get"
            - "indices_monitor"

    # Allow users to read flow framework's workflows and their state
    flow_framework_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/flow_framework/workflow/get"
        - "cluster:admin/opensearch/flow_framework/workflow/search"
        - "cluster:admin/opensearch/flow_framework/workflow_state/get"
        - "cluster:admin/opensearch/flow_framework/workflow_state/search"
        - "cluster:admin/opensearch/flow_framework/workflow_step/get"

    # Allows users to use all query insights APIs
    query_insights_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/insights/*"
      index_permissions:
        - index_patterns:
            - "top_queries-*"
          allowed_actions:
            - "indices_all"

    # Allow users to execute read only LTR actions
    ltr_read_access:
      reserved: true
      cluster_permissions:
        - cluster:admin/ltr/caches/stats
        - cluster:admin/ltr/featurestore/list
        - cluster:admin/ltr/stats

    # Allow users to execute all LTR actions
    ltr_full_access:
      reserved: true
      cluster_permissions:
        - cluster:admin/ltr/*

    # Allow users to use all Search Relevance functionalities
    search_relevance_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/search_relevance/*"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/mappings/get"
            - "indices:data/read/search*"

    # Allow users to read Search Relevance resources
    search_relevance_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/opensearch/search_relevance/experiment/get"
        - "cluster:admin/opensearch/search_relevance/judgment/get"
        - "cluster:admin/opensearch/search_relevance/queryset/get"
        - "cluster:admin/opensearch/search_relevance/search_configuration/get"

    # Allow users to read Forecast resources
    forecast_read_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/plugin/forecast/forecaster/info"
        - "cluster:admin/plugin/forecast/forecaster/stats"
        - "cluster:admin/plugin/forecast/forecaster/suggest"
        - "cluster:admin/plugin/forecast/forecaster/validate"
        - "cluster:admin/plugin/forecast/forecasters/get"
        - "cluster:admin/plugin/forecast/forecasters/info"
        - "cluster:admin/plugin/forecast/forecasters/search"
        - "cluster:admin/plugin/forecast/result/topForecasts"
        - "cluster:admin/plugin/forecast/tasks/search"
      index_permissions:
        - index_patterns:
            - "opensearch-forecast-result*"
          allowed_actions:
            - "indices:admin/mappings/fields/get*"
            - "indices:admin/resolve/index"
            - "indices:data/read*"

    # Allows users to use all Forecasting functionality
    forecast_full_access:
      reserved: true
      cluster_permissions:
        - "cluster:admin/plugin/forecast/*"
        - "cluster:admin/settings/update"
        - "cluster_monitor"
      index_permissions:
        - index_patterns:
            - "*"
          allowed_actions:
            - "indices:admin/aliases/get"
            - "indices:admin/mapping/get"
            - "indices:admin/mapping/put"
            - "indices:admin/mappings/fields/get*"
            - "indices:admin/mappings/get"
            - "indices:admin/resolve/index"
            - "indices:data/read*"
            - "indices:data/read/field_caps*"
            - "indices:data/read/search"
            - "indices:data/write*"
            - "indices_monitor"

Copy the configuration below to a file and run kubectl apply -f <the-name-of-the-file>. Replace <the-name-of-the-file> with the actual name you gave the file.

The tenants.yml secret declaration

The tenants.yml file declares the tenants OpenSearch Security accepts. This configuration is helpful for isolating OpenSearch data from different groups of users, but defining tenants is not necessary unless you need to completely isolate data from groups of users. OpenSearch Dashboards enables multi-tenancy by default.

apiVersion: v1
kind: Secret
metadata:
  name: opensearch-tenants-config
type: Opaque
stringData:
  tenants.yml: |-
    _meta:
      type: "tenants"
      config_version: 2
    admin_tenant:
      reserved: false
      description: "Tenant for admin user"

Copy the configuration below to a file and run kubectl apply -f <the-name-of-the-file>. Replace <the-name-of-the-file> with the actual name you gave the file.

The Chart.yaml file

The Chart.yaml file defines a new Helm chart that deploys both OpenSearch and OpenSearch Dashboards through a single Helm run.


apiVersion: v2
name: OpenSearch
type: application
description: Deploy OpenSearch and OpenSearch Dashboards
version: 1.0.0 # The version of your Helm chart
dependencies:

- name: opensearch
  version: 3.2.1 # Give the upstream Helm chart version you want to use
  repository: https://opensearch-project.github.io/helm-charts/
- name: opensearch-dashboards
  version: 3.2.1 # Give the upstream Helm chart version you want to use
  repository: https://opensearch-project.github.io/helm-charts/

Create a folder and copy Chart.yaml into the folder. You will use this configuration later when running Helm.

The values.yaml file

The values.yaml file provides the configuration Helm will use to generate the Kubernetes configuration from the templates contained in the Helm charts. The following example values.yaml file has configuration for both OpenSearch and OpenSearch Dashboards. There are additional configuration options for the Helm charts. You can find the options listed on the repo for the OpenSearch Helm charts.

opensearch:
  replicas: 3 # Create three OpenSearch pods in a StatefulSet
  config:
    opensearch.yml: |-
      plugins.security.restapi.roles_enabled: ["all_access", "security_rest_api_access"]
      plugins.security.ssl.transport.pemcert_filepath: ssl/tls.crt # Path to the node certificate. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.transport.pemkey_filepath: ssl/tls.key # Path to the node key. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.transport.pemtrustedcas_filepath: ssl/ca.crt # Path to the node CA certificate. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.http.pemcert_filepath: ssl/tls.crt # Path to the node certificate. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.http.pemkey_filepath: ssl/tls.key # Path to the node key. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.http.pemtrustedcas_filepath: ssl/ca.crt # Path to the node CA certificate. This should match the secretMount defined later in the values.yaml file
      plugins.security.ssl.http.enabled: true # Enable HTTPS on REST API routes
      plugins.security.allow_default_init_securityindex: false # Do not initialize default security settings if OpenSearch fails to load security settings on launch
      plugins.security.audit.type: internal_opensearch
      plugins.security.enable_snapshort_restore_privilege: true
      plugins.security.check_snapshot_restore_write_privileges: true
      plugins.security.system_indices.enabled: true
      plugins.security.authcz.admin_dn: - 'CN=opensearch-cluster-admin' # Must match the name you set when making certificates
      plugins.security.nodes_dn: - 'CN=opensearch-cluster-master\* # Must match the name you set when making certificates
  extraEnvs:
    - name: OPENSEARCH_INITIAL_ADMIN_PASSWORD
      value: "your-admin-password-here" # Replace with a strong password
    - name: "DISABLE_INSTALL_DEMO_CONFIG"
      value: "true" # Stop OpenSearch Security from setting up demo certificates and users
  resources: # Adjust resource requests to meet your needs
    requests:
      cpu: "1000m" # "1000m" is default upstream Helm chart config
      memory: "2048Mi" # "100Mi" is the default upstream Helm chart config
  securityConfig: # Give the Helm chart the path to opensearch-security config folder and the secrets to load for each config type
    enabled: true
    path: "/usr/share/opensearch/config/opensearch-security"
    actionGroupsSecret: opensearch-action-groups-config
    configSecret: opensearch-security-config
    internalUsersSecret: opensearch-internal-users-config
    rolesSecret: opensearch-roles-config
    rolesMappingSecret: opensearch-roles-mapping-config
    tenantsSecret: opensearch-tenants-config
  secretMounts: # Mount the SSL/TLS certificates for the OpenSearch cluster traffic and for the admin client into the pod
    - name: opensearch-ssl-certs
      secretName: opensearch-cluster-tls
      path: /usr/share/opensearch/config/ssl/
    - name: opensearch-cluster-admin-cert
      secretName: opensearch-cluster-admin-cert
      path: /usr/share/openserach/config/admin-ssl/
opensearch-dashboards:
  opensearchHosts: "https://opensearch-cluster-master:9200" # This is the default hostname of the cluster pods
  replicaCount: 1
  config:
    opensearch_dashboards.yml: |-
      server.host: 0.0.0.0
      opensearch.hosts: ["https://opensearch-cluster-master:9200"] # This should be a domain name that resolves to OpenSearch pods in Kubernetes
      opensearch.ssl.certificateAuthorities: [ "certs/ca.crt" ]
      opensearch.username: "your-dashboard-server-username"
      opensearch.password: "your-dashboard-username-password" # Enable verbose logging with the next two lines without a starting hash # logging.dest: stdout # logging.verbose: true
  resources:
    requests:
      cpu: "200m"
      memory: "4096Mi" # OpenSearch Dashboards ran out of memory, and I had memory kills for OpenSearch Dashboards with less memory than this
    limits:
      cpu: "200m"
      memory: "4096Mi" # OpenSearch Dashboards ran out of memory, and I had memory kills for OpenSearch Dashboards with less memory than this
  secretMount: # Mount the OpenSearch cluster SSL/TLS certificates for TLS between OpenSearch Dashboards and OpenSearch
    - name: opensearch-ssl-certs
      secretName: opensearch-cluster-tls
      path: /usr/share/opensearch-dashboards/certs

Copy the configuration to the new directory you created with a name like values.yaml.

Deploying OpenSearch and OpenSearch Dashboards using Helm and all of the previous configuration

Build your chart's dependencies

Before you can install OpenSearch with Helm, you have to download the dependent Helm charts this deployment requires. If you copied the configuration to a local folder, you can run the following Helm command to download the dependencies Helm requires:

helm dependency build ./<your-folder-name>`

Helm will download the Helm charts for OpenSearch and OpenSearch Dashboards from the OpenSeach repos defined in the Chart.yaml file.

Deploy OpenSearch and OpenSearch Dashboards using Helm

At this point, if you have applied all the Kubernetes configuration for certificates and secrets, have downloaded the dependencies, and have copied the values.yaml file configuration, you should be ready to run Helm to deploy OpenSearch and OpenSearch Dashboards. Use the helm install command like in the following example:

helm install -f ./<your-directory-name>/values.yaml opensearch-cluster ./<your-directory-name>

When you run the install command, Helm will combine the values defined in your values.yaml file with the templates in the Helm charts to produce the configuration Kubernetes uses to deploy OpenSearch and OpenSearch Dashboards. You should be able to see the pods in your Kubernetes cluster (such as by running kubectl get pods).

Post-deployment steps

Run the securityadmin.sh script to apply the OpenSearch Security config

OpenSearch Security provides a script for applying the OpenSearch Security configuration to your cluster. You can run the script on the pod from where you run kubectl. The script requires several arguments to run:

1.-cacert: The path to the OpenSearch cluster root CA certificate 2.-cert: The path to the OpenSearch admin client SSL/TLS certificate 3.-key: The path to the OpenSearch admin client SSL/TLS certificate key 4.-h: The hostname address to use to connect to the OpenSearch cluster 5.-cn: The name of the OpenSearch cluster 6.-cd: The path to the OpenSearch Security configuration directory

Here is an example command running the securityadmin.sh script with the values and configuration from the examples from earlier in this post:

kubectl exec -it opensearch-cluster-master-0 -- ./plugins/opensearch-security/tools/securityadmin.sh \
    -cacert ./config/admin-ssl/ca.crt \
    -cert ./config/admin-ssl/tls.crt \
    -key ./config/admin-ssl/tls.key \
    -h opensearch-cluster-master-0 \
    -cn opensearch-cluster \
    -cd ./config/opensearch-security/

This command will use kubectl to execute the securityadmin.sh script on the pod named opensearch-cluster-master-0 at the path ./plugins/opensearch-security/tools/securityadmin.sh.