diff --git a/CHANGELOG.md b/CHANGELOG.md index 52a6d74..13f9bb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,9 @@ All notable changes to this project will be documented in this file. ### Added - Add the role group as a node attribute ([#63]). +- Allow the configuration of TLS for the HTTP and TRANSPORT ports with the operator ([#55]). +[#55]: https://github.com/stackabletech/opensearch-operator/pull/55 [#63]: https://github.com/stackabletech/opensearch-operator/pull/63 ## [25.11.0] - 2025-11-07 diff --git a/deploy/helm/opensearch-operator/crds/crds.yaml b/deploy/helm/opensearch-operator/crds/crds.yaml index 50448f6..e29d34d 100644 --- a/deploy/helm/opensearch-operator/crds/crds.yaml +++ b/deploy/helm/opensearch-operator/crds/crds.yaml @@ -29,9 +29,42 @@ spec: generates in the [operator documentation](https://docs.stackable.tech/home/nightly/opensearch/). properties: clusterConfig: - default: {} + default: + tls: + internalSecretClass: tls + serverSecretClass: tls description: Configuration that applies to all roles and role groups properties: + tls: + default: + internalSecretClass: tls + serverSecretClass: tls + description: TLS configuration options for the server (REST API) and internal communication (transport). + properties: + internalSecretClass: + default: tls + description: |- + Only affects internal communication (transport). Used for mutual verification between OpenSearch nodes. + This setting controls: + - Which cert the servers should use to authenticate themselves against other servers + - Which ca.crt to use when validating the other server + maxLength: 253 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + serverSecretClass: + default: tls + description: |- + Only affects client connections to the REST API. + This setting controls: + - If TLS encryption is used at all + - Which cert the servers should use to authenticate themselves against the client + maxLength: 253 + minLength: 1 + nullable: true + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?(\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*$ + type: string + type: object vectorAggregatorConfigMapName: description: |- Name of the Vector aggregator [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery). @@ -305,6 +338,14 @@ spec: type: string nullable: true type: array + requestedSecretLifetime: + description: |- + Request secret (currently only autoTls certificates) lifetime from the secret operator, e.g. `7d`, or `30d`. + This can be shortened by the `maxCertificateLifetime` setting on the SecretClass issuing the TLS certificate. + + Defaults to 1d. + nullable: true + type: string resources: default: cpu: @@ -654,6 +695,14 @@ spec: type: string nullable: true type: array + requestedSecretLifetime: + description: |- + Request secret (currently only autoTls certificates) lifetime from the secret operator, e.g. `7d`, or `30d`. + This can be shortened by the `maxCertificateLifetime` setting on the SecretClass issuing the TLS certificate. + + Defaults to 1d. + nullable: true + type: string resources: default: cpu: diff --git a/docs/modules/opensearch/examples/getting_started/opensearch.yaml b/docs/modules/opensearch/examples/getting_started/opensearch.yaml index e98b45a..6def257 100644 --- a/docs/modules/opensearch/examples/getting_started/opensearch.yaml +++ b/docs/modules/opensearch/examples/getting_started/opensearch.yaml @@ -14,14 +14,6 @@ spec: opensearch.yml: plugins.security.allow_default_init_securityindex: "true" plugins.security.restapi.roles_enabled: all_access - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt podOverrides: spec: containers: @@ -30,25 +22,8 @@ spec: - name: security-config mountPath: /stackable/opensearch/config/opensearch-security readOnly: true - - name: tls - mountPath: /stackable/opensearch/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - secrets.stackable.tech/scope: node,pod,service=simple-opensearch,service=simple-opensearch-nodes-default,service=simple-opensearch-nodes-default-headless - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" diff --git a/docs/modules/opensearch/pages/usage-guide/security.adoc b/docs/modules/opensearch/pages/usage-guide/security.adoc new file mode 100644 index 0000000..88b6db8 --- /dev/null +++ b/docs/modules/opensearch/pages/usage-guide/security.adoc @@ -0,0 +1,40 @@ += Security +:description: Configure TLS encryption for OpenSearch with the Stackable Operator. + +== TLS + +The internal and client communication at the REST API can be encrypted with TLS. +This requires the xref:secret-operator:index.adoc[Secret Operator] to be running in the Kubernetes cluster providing certificates. +The used certificates can be changed in a cluster-wide config and are configured using xref:secret-operator:secretclass.adoc[SecretClasses]. +TLS encryption on the REST API may be disabled, while it is always enabled for the internal communication between nodes using the `transport` port. + +[source,yaml] +---- +--- +apiVersion: opensearch.stackable.tech/v1alpha1 +kind: OpenSearchCluster +metadata: + name: opensearch +spec: + image: + productVersion: 3.1.0 + clusterConfig: + tls: + serverSecretClass: tls # <1> + internalSecretClass: opensearch-internal-tls # <2> + nodes: + config: + requestedSecretLifetime: 7d # <3> + roleGroups: + default: + replicas: 3 +---- +<1> The `spec.clusterConfig.tls.serverSecretClass` refers to the client-to-server encryption at the REST API. +Defaults to the `tls` SecretClass and can be disabled by setting `serverSecretClass` to `null`. +<2> The `spec.clusterConfig.tls.internalSecretClass` refers to the internal encryption between OpenSearch nodes using mTLS (transport). +Defaults to the `tls` SecretClass and can't be disabled. +<3> The lifetime for autoTls certificates generated by the secret operator. +Only a lifetime up to the `maxCertificateLifetime` setting in the SecretClass is applied. + +Important: The operator sets the configuration `plugins.security.nodes_dn` to `["CN=generated certificate for pod"]` which provides weak authentication between nodes. +If you want to increase security and use certificates which identify the OpenSearch nodes specifically, you must also adapt the `plugins.security.nodes_dn` setting via configOverrides. diff --git a/docs/modules/opensearch/partials/nav.adoc b/docs/modules/opensearch/partials/nav.adoc index 1994a6e..c279825 100644 --- a/docs/modules/opensearch/partials/nav.adoc +++ b/docs/modules/opensearch/partials/nav.adoc @@ -10,6 +10,7 @@ ** xref:opensearch:usage-guide/logging.adoc[] ** xref:opensearch:usage-guide/opensearch-dashboards.adoc[] ** xref:opensearch:usage-guide/scaling.adoc[] +** xref:opensearch:usage-guide/security.adoc[] ** xref:opensearch:usage-guide/operations/index.adoc[] *** xref:opensearch:usage-guide/operations/cluster-operations.adoc[] *** xref:opensearch:usage-guide/operations/pod-placement.adoc[] diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 3a6d210..5822917 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -28,10 +28,7 @@ use update_status::update_status; use validate::validate; use crate::{ - crd::{ - NodeRoles, - v1alpha1::{self}, - }, + crd::{NodeRoles, v1alpha1}, framework::{ HasName, HasUid, NameIsValidLabelValue, product_logging::framework::{ValidatedContainerLogConfigChoice, VectorContainerLogConfig}, @@ -137,6 +134,7 @@ pub struct ValidatedOpenSearchConfig { pub listener_class: ListenerClassName, pub logging: ValidatedLogging, pub node_roles: NodeRoles, + pub requested_secret_lifetime: Duration, pub resources: OpenSearchNodeResources, pub termination_grace_period_seconds: i64, } @@ -172,9 +170,11 @@ pub struct ValidatedCluster { pub uid: Uid, pub role_config: GenericRoleConfig, pub role_group_configs: BTreeMap, + pub tls_config: v1alpha1::OpenSearchTls, } impl ValidatedCluster { + #[allow(clippy::too_many_arguments)] pub fn new( image: ResolvedProductImage, product_version: ProductVersion, @@ -183,6 +183,7 @@ impl ValidatedCluster { uid: impl Into, role_config: GenericRoleConfig, role_group_configs: BTreeMap, + tls_config: v1alpha1::OpenSearchTls, ) -> Self { let uid = uid.into(); ValidatedCluster { @@ -199,6 +200,7 @@ impl ValidatedCluster { uid, role_config, role_group_configs, + tls_config, } } @@ -378,6 +380,7 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use uuid::uuid; @@ -503,6 +506,7 @@ mod tests { ), ] .into(), + v1alpha1::OpenSearchTls::default(), ) } @@ -522,6 +526,8 @@ mod tests { vector_container: None, }, node_roles: NodeRoles(node_roles.to_vec()), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: OpenSearchNodeResources::default(), termination_grace_period_seconds: 120, }, diff --git a/rust/operator-binary/src/controller/build.rs b/rust/operator-binary/src/controller/build.rs index 08a1afb..bc14fb0 100644 --- a/rust/operator-binary/src/controller/build.rs +++ b/rust/operator-binary/src/controller/build.rs @@ -68,6 +68,7 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use uuid::uuid; @@ -197,6 +198,7 @@ mod tests { ), ] .into(), + v1alpha1::OpenSearchTls::default(), ) } @@ -216,6 +218,8 @@ mod tests { vector_container: None, }, node_roles: NodeRoles(node_roles.to_vec()), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: OpenSearchNodeResources::default(), termination_grace_period_seconds: 120, }, diff --git a/rust/operator-binary/src/controller/build/node_config.rs b/rust/operator-binary/src/controller/build/node_config.rs index 6a1aa4b..60dd738 100644 --- a/rust/operator-binary/src/controller/build/node_config.rs +++ b/rust/operator-binary/src/controller/build/node_config.rs @@ -62,12 +62,49 @@ pub const CONFIG_OPTION_PLUGINS_SECURITY_NODES_DN: &str = "plugins.security.node pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED: &str = "plugins.security.ssl.http.enabled"; +/// Path to the cert PEM file used for TLS on the HTTP PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMCERT_FILEPATH: &str = + "plugins.security.ssl.http.pemcert_filepath"; + +/// Path to the key PEM file used for TLS on the HTTP PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMKEY_FILEPATH: &str = + "plugins.security.ssl.http.pemkey_filepath"; + +/// Path to the trusted CAs PEM file used for TLS on the HTTP PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH: &str = + "plugins.security.ssl.http.pemtrustedcas_filepath"; + +/// Whether to enable TLS on internal node-to-node communication using the transport port. +/// type: boolean +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_ENABLED: &str = + "plugins.security.ssl.transport.enabled"; + +/// Path to the cert PEM file used for TLS on the transport PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH: &str = + "plugins.security.ssl.transport.pemcert_filepath"; + +/// Path to the key PEM file used for TLS on the transport PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH: &str = + "plugins.security.ssl.transport.pemkey_filepath"; + +/// Path to the trusted CAs PEM file used for TLS on the transport PORT. +/// type: string +pub const CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH: &str = + "plugins.security.ssl.transport.pemtrustedcas_filepath"; + +const DEFAULT_OPENSEARCH_HOME: &str = "/stackable/opensearch"; + /// Configuration of an OpenSearch node based on the cluster and role-group configuration pub struct NodeConfig { cluster: ValidatedCluster, role_group_name: RoleGroupName, role_group_config: OpenSearchRoleGroupConfig, - discovery_service_name: ServiceName, + pub discovery_service_name: ServiceName, } // Most functions are public because their configuration values could also be used in environment @@ -88,8 +125,29 @@ impl NodeConfig { } /// Creates the main OpenSearch configuration file in YAML format - pub fn static_opensearch_config_file_content(&self) -> String { - Self::to_yaml(self.static_opensearch_config()) + pub fn opensearch_config_file_content(&self) -> String { + Self::to_yaml(self.opensearch_config()) + } + + pub fn opensearch_config(&self) -> serde_json::Map { + let mut config = self.static_opensearch_config(); + + config.append(&mut self.tls_config()); + + for (setting, value) in self + .role_group_config + .config_overrides + .get(CONFIGURATION_FILE_OPENSEARCH_YML) + .into_iter() + .flatten() + { + config.insert(setting.to_owned(), json!(value)); + } + + // Ensure a deterministic result + config.sort_keys(); + + config } /// Creates the main OpenSearch configuration file as JSON map @@ -123,25 +181,62 @@ impl NodeConfig { json!(self.role_group_name), ); - for (setting, value) in self - .role_group_config - .config_overrides - .get(CONFIGURATION_FILE_OPENSEARCH_YML) - .into_iter() - .flatten() - { - config.insert(setting.to_owned(), json!(value)); - } + config + } - // Ensure a deterministic result - config.sort_keys(); + pub fn tls_config(&self) -> serde_json::Map { + let mut config = serde_json::Map::new(); + let opensearch_path_conf = self.opensearch_path_conf(); + + // TLS config for TRANSPORT port which is always enabled. + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_ENABLED.to_owned(), + json!(true), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/internal/tls.crt")), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/internal/tls.key")), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/internal/ca.crt")), + ); + + // TLS config for HTTP port (REST API) (optional). + if self.cluster.tls_config.server_secret_class.is_some() { + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED.to_owned(), + json!(true), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMCERT_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/server/tls.crt")), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMKEY_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/server/tls.key")), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH.to_owned(), + json!(format!("{opensearch_path_conf}/tls/server/ca.crt")), + ); + } else { + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED.to_owned(), + json!(false), + ); + } config } /// Returns `true` if TLS is enabled on the HTTP port pub fn tls_on_http_port_enabled(&self) -> bool { - self.static_opensearch_config() + self.opensearch_config() .get(CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED) .and_then(Self::value_as_bool) == Some(true) @@ -272,6 +367,23 @@ impl NodeConfig { String::new() } } + + /// Return content of the `OPENSEARCH_HOME` environment variable from envOverrides or default to `DEFAULT_OPENSEARCH_HOME` + pub fn opensearch_home(&self) -> String { + self.environment_variables() + .get(&EnvVarName::from_str_unsafe("OPENSEARCH_HOME")) + .and_then(|env_var| env_var.value.clone()) + .unwrap_or(DEFAULT_OPENSEARCH_HOME.to_owned()) + } + + /// Return content of the `OPENSEARCH_PATH_CONF` environment variable from envOverrides or default to `OPENSEARCH_HOME/config` + pub fn opensearch_path_conf(&self) -> String { + let opensearch_home = self.opensearch_home(); + self.environment_variables() + .get(&EnvVarName::from_str_unsafe("OPENSEARCH_PATH_CONF")) + .and_then(|env_var| env_var.value.clone()) + .unwrap_or(format!("{opensearch_home}/config")) + } } #[cfg(test)] @@ -288,13 +400,14 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use uuid::uuid; use super::*; use crate::{ controller::{ValidatedLogging, ValidatedOpenSearchConfig}, - crd::NodeRoles, + crd::{NodeRoles, v1alpha1}, framework::{ product_logging::framework::ValidatedContainerLogConfigChoice, role_utils::GenericProductSpecificCommonConfig, @@ -344,6 +457,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -386,6 +501,7 @@ mod tests { role_group_config.clone(), )] .into(), + v1alpha1::OpenSearchTls::default(), ); NodeConfig::new( @@ -410,10 +526,18 @@ mod tests { "network.host: \"0.0.0.0\"\n", "node.attr.role-group: \"data\"\n", "plugins.security.nodes_dn: [\"CN=generated certificate for pod\"]\n", - "test: \"value\"" + "plugins.security.ssl.http.enabled: true\n", + "plugins.security.ssl.http.pemcert_filepath: \"/stackable/opensearch/config/tls/server/tls.crt\"\n", + "plugins.security.ssl.http.pemkey_filepath: \"/stackable/opensearch/config/tls/server/tls.key\"\n", + "plugins.security.ssl.http.pemtrustedcas_filepath: \"/stackable/opensearch/config/tls/server/ca.crt\"\n", + "plugins.security.ssl.transport.enabled: true\n", + "plugins.security.ssl.transport.pemcert_filepath: \"/stackable/opensearch/config/tls/internal/tls.crt\"\n", + "plugins.security.ssl.transport.pemkey_filepath: \"/stackable/opensearch/config/tls/internal/tls.key\"\n", + "plugins.security.ssl.transport.pemtrustedcas_filepath: \"/stackable/opensearch/config/tls/internal/ca.crt\"\n", + "test: \"value\"", ) .to_owned(), - node_config.static_opensearch_config_file_content() + node_config.opensearch_config_file_content() ); } @@ -431,7 +555,7 @@ mod tests { ..TestConfig::default() }); - assert!(!node_config_tls_undefined.tls_on_http_port_enabled()); + assert!(node_config_tls_undefined.tls_on_http_port_enabled()); assert!(node_config_tls_enabled.tls_on_http_port_enabled()); assert!(!node_config_tls_disabled.tls_on_http_port_enabled()); } diff --git a/rust/operator-binary/src/controller/build/role_builder.rs b/rust/operator-binary/src/controller/build/role_builder.rs index f556909..a78d62a 100644 --- a/rust/operator-binary/src/controller/build/role_builder.rs +++ b/rust/operator-binary/src/controller/build/role_builder.rs @@ -230,6 +230,7 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use uuid::uuid; @@ -282,6 +283,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -311,6 +314,7 @@ mod tests { role_group_config.clone(), )] .into(), + v1alpha1::OpenSearchTls::default(), ); RoleBuilder::new(cluster, context_names) diff --git a/rust/operator-binary/src/controller/build/role_group_builder.rs b/rust/operator-binary/src/controller/build/role_group_builder.rs index 8d8880f..e52480e 100644 --- a/rust/operator-binary/src/controller/build/role_group_builder.rs +++ b/rust/operator-binary/src/controller/build/role_group_builder.rs @@ -3,7 +3,10 @@ use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ - builder::meta::ObjectMetaBuilder, + builder::{ + meta::ObjectMetaBuilder, + pod::volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, + }, crd::listener::{self}, k8s_openapi::{ DeepMerge, @@ -23,6 +26,7 @@ use stackable_operator::{ VECTOR_CONFIG_FILE, calculate_log_volume_size_limit, create_vector_shutdown_file_command, remove_vector_shutdown_file_command, }, + shared::time::Duration, utils::COMMON_BASH_TRAP_FUNCTIONS, }; @@ -45,7 +49,7 @@ use crate::{ builder::{ meta::ownerreference_from_resource, pod::{ - container::{EnvVarName, new_container_builder}, + container::new_container_builder, volume::{ListenerReference, listener_operator_volume_source_builder_build_pvc}, }, }, @@ -55,7 +59,10 @@ use crate::{ }, role_group_utils::ResourceNames, types::{ - kubernetes::{PersistentVolumeClaimName, ServiceAccountName, ServiceName, VolumeName}, + kubernetes::{ + PersistentVolumeClaimName, SecretClassName, ServiceAccountName, ServiceName, + VolumeName, + }, operator::RoleGroupName, }, }, @@ -74,11 +81,12 @@ constant!(DATA_VOLUME_NAME: VolumeName = "data"); constant!(LISTENER_VOLUME_NAME: PersistentVolumeClaimName = "listener"); const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; +constant!(TLS_SERVER_VOLUME_NAME: VolumeName = "tls-server"); +constant!(TLS_INTERNAL_VOLUME_NAME: VolumeName = "tls-internal"); + constant!(LOG_VOLUME_NAME: VolumeName = "log"); const LOG_VOLUME_DIR: &str = "/stackable/log"; -const DEFAULT_OPENSEARCH_HOME: &str = "/stackable/opensearch"; - /// Builder for role-group resources pub struct RoleGroupBuilder<'a> { service_account_name: ServiceAccountName, @@ -130,7 +138,7 @@ impl<'a> RoleGroupBuilder<'a> { data.insert( CONFIGURATION_FILE_OPENSEARCH_YML.to_owned(), - self.node_config.static_opensearch_config_file_content(), + self.node_config.opensearch_config_file_content(), ); if let ValidatedContainerLogConfigChoice::Automatic(log_config) = @@ -216,6 +224,8 @@ impl<'a> RoleGroupBuilder<'a> { /// Builds the [`PodTemplateSpec`] for the role-group [`StatefulSet`] fn build_pod_template(&self) -> PodTemplateSpec { let mut node_role_labels = Labels::new(); + let service_scopes = vec![self.node_config.discovery_service_name.clone()]; + for node_role in self.role_group_config.config.node_roles.iter() { node_role_labels.insert(Self::build_node_role_label(node_role)); } @@ -260,7 +270,7 @@ impl<'a> RoleGroupBuilder<'a> { self.resource_names.role_group_config_map() }; - let volumes = vec![ + let mut volumes = vec![ Volume { name: CONFIG_VOLUME_NAME.to_string(), config_map: Some(ConfigMapVolumeSource { @@ -289,8 +299,27 @@ impl<'a> RoleGroupBuilder<'a> { }), ..Volume::default() }, + self.build_tls_volume( + &TLS_INTERNAL_VOLUME_NAME, + &self.cluster.tls_config.internal_secret_class, + vec![], + SecretFormat::TlsPem, + &self.role_group_config.config.requested_secret_lifetime, + &LISTENER_VOLUME_NAME, + ), ]; + if let Some(tls_http_secret_class_name) = &self.cluster.tls_config.server_secret_class { + volumes.push(self.build_tls_volume( + &TLS_SERVER_VOLUME_NAME, + tls_http_secret_class_name, + service_scopes, + SecretFormat::TlsPem, + &self.role_group_config.config.requested_secret_lifetime, + &LISTENER_VOLUME_NAME, + )) + }; + // The PodBuilder is not used because it re-validates the values which are already // validated. For instance, it would be necessary to convert the // termination_grace_period_seconds into a Duration, the PodBuilder parses the Duration, @@ -396,21 +425,10 @@ impl<'a> RoleGroupBuilder<'a> { ..Probe::default() }; - let env_vars = self.node_config.environment_variables(); - - // Use `OPENSEARCH_HOME` from envOverrides or default to `DEFAULT_OPENSEARCH_HOME`. - let opensearch_home = env_vars - .get(&EnvVarName::from_str_unsafe("OPENSEARCH_HOME")) - .and_then(|env_var| env_var.value.clone()) - .unwrap_or(DEFAULT_OPENSEARCH_HOME.to_owned()); - // Use `OPENSEARCH_PATH_CONF` from envOverrides or default to `OPENSEARCH_HOME/config`, - // i.e. depend on `OPENSEARCH_HOME`. - let opensearch_path_conf = env_vars - .get(&EnvVarName::from_str_unsafe("OPENSEARCH_PATH_CONF")) - .and_then(|env_var| env_var.value.clone()) - .unwrap_or(format!("{opensearch_home}/config")); - - let volume_mounts = [ + let opensearch_home = self.node_config.opensearch_home(); + let opensearch_path_conf = self.node_config.opensearch_path_conf(); + + let mut volume_mounts = vec![ VolumeMount { mount_path: format!("{opensearch_path_conf}/{CONFIGURATION_FILE_OPENSEARCH_YML}"), name: CONFIG_VOLUME_NAME.to_string(), @@ -442,8 +460,21 @@ impl<'a> RoleGroupBuilder<'a> { name: LOG_VOLUME_NAME.to_string(), ..VolumeMount::default() }, + VolumeMount { + mount_path: format!("{opensearch_path_conf}/tls/internal"), + name: TLS_INTERNAL_VOLUME_NAME.to_string(), + ..VolumeMount::default() + }, ]; + if self.cluster.tls_config.server_secret_class.is_some() { + volume_mounts.push(VolumeMount { + mount_path: format!("{opensearch_path_conf}/tls/server"), + name: TLS_SERVER_VOLUME_NAME.to_string(), + ..VolumeMount::default() + }) + } + new_container_builder(&v1alpha1::Container::OpenSearch.to_container_name()) .image_from_product_image(&self.cluster.image) .command(vec![ @@ -471,7 +502,7 @@ impl<'a> RoleGroupBuilder<'a> { create_vector_shutdown_file_command = create_vector_shutdown_file_command(STACKABLE_LOG_DIR), )]) - .add_env_vars(env_vars.into()) + .add_env_vars(self.node_config.environment_variables().into()) .add_volume_mounts(volume_mounts) .expect("The mount paths are statically defined and there should be no duplicates.") .add_container_ports(vec![ @@ -631,6 +662,35 @@ impl<'a> RoleGroupBuilder<'a> { &self.role_group_name, ) } + + fn build_tls_volume( + &self, + volume_name: &VolumeName, + tls_secret_class_name: &SecretClassName, + service_scopes: Vec, + secret_format: SecretFormat, + requested_secret_lifetime: &Duration, + listener_scope: &PersistentVolumeClaimName, + ) -> Volume { + let mut secret_volume_source_builder = + SecretOperatorVolumeSourceBuilder::new(tls_secret_class_name); + + for scope in service_scopes { + secret_volume_source_builder.with_service_scope(scope); + } + + VolumeBuilder::new(volume_name.to_string()) + .ephemeral( + secret_volume_source_builder + .with_listener_volume_scope(listener_scope) + .with_pod_scope() + .with_format(secret_format) + .with_auto_tls_cert_lifetime(*requested_secret_lifetime) + .build() + .expect("volume should be built without parse errors"), + ) + .build() + } } #[cfg(test)] @@ -651,6 +711,7 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use strum::IntoEnumIterator; use uuid::uuid; @@ -734,6 +795,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -756,6 +819,7 @@ mod tests { role_group_config.clone(), )] .into(), + v1alpha1::OpenSearchTls::default(), ) } @@ -1030,6 +1094,14 @@ mod tests { { "mountPath": "/stackable/log", "name": "log" + }, + { + "mountPath": "/stackable/opensearch/config/tls/internal", + "name": "tls-internal" + }, + { + "mountPath": "/stackable/opensearch/config/tls/server", + "name": "tls-server", } ] }, @@ -1150,13 +1222,65 @@ mod tests { "name": "my-opensearch-cluster-nodes-default" }, "name": "log-config" - }, - { + }, + { "emptyDir": { "sizeLimit": "30Mi" }, "name": "log" - } + }, + { + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "annotations": { + "secrets.stackable.tech/backend.autotls.cert.lifetime": "1d", + "secrets.stackable.tech/class": "tls", + "secrets.stackable.tech/format": "tls-pem", + "secrets.stackable.tech/scope": "listener-volume=listener,pod" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "1" + } + }, + "storageClassName": "secrets.stackable.tech" + } + } + }, + "name": "tls-internal" + }, + { + "ephemeral": { + "volumeClaimTemplate": { + "metadata": { + "annotations": { + "secrets.stackable.tech/backend.autotls.cert.lifetime": "1d", + "secrets.stackable.tech/class": "tls", + "secrets.stackable.tech/format": "tls-pem", + "secrets.stackable.tech/scope": "service=my-opensearch-cluster,listener-volume=listener,pod" + } + }, + "spec": { + "accessModes": [ + "ReadWriteOnce" + ], + "resources": { + "requests": { + "storage": "1" + } + }, + "storageClassName": "secrets.stackable.tech" + } + } + }, + "name": "tls-server" + } ] } }, @@ -1248,7 +1372,7 @@ mod tests { "annotations": { "prometheus.io/path": "/_prometheus/metrics", "prometheus.io/port": "9200", - "prometheus.io/scheme": "http", + "prometheus.io/scheme": "https", "prometheus.io/scrape": "true" }, "labels": { diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index 0f6f10c..2433e15 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -153,6 +153,7 @@ pub fn validate( uid, cluster.spec.nodes.role_config.clone(), role_group_configs, + cluster.spec.cluster_config.tls.clone(), )) } @@ -196,6 +197,7 @@ fn validate_role_group_config( listener_class: merged_role_group.config.config.listener_class, logging, node_roles: merged_role_group.config.config.node_roles, + requested_secret_lifetime: merged_role_group.config.config.requested_secret_lifetime, resources: merged_role_group.config.config.resources, termination_grace_period_seconds, }; @@ -285,10 +287,7 @@ mod tests { use crate::{ built_info, controller::{ContextNames, ValidatedCluster, ValidatedLogging, ValidatedOpenSearchConfig}, - crd::{ - NodeRoles, - v1alpha1::{self}, - }, + crd::{NodeRoles, v1alpha1}, framework::{ builder::pod::container::{EnvVarName, EnvVarSet}, product_logging::framework::{ @@ -296,7 +295,7 @@ mod tests { }, role_utils::{GenericProductSpecificCommonConfig, RoleGroupConfig}, types::{ - kubernetes::{ConfigMapName, ListenerClassName, NamespaceName}, + kubernetes::{ConfigMapName, ListenerClassName, NamespaceName, SecretClassName}, operator::{ ClusterName, ControllerName, OperatorName, ProductName, ProductVersion, RoleGroupName, @@ -425,6 +424,8 @@ mod tests { ] .into() ), + requested_secret_lifetime: Duration::from_str("1d") + .expect("should be a valid duration"), resources: Resources { memory: MemoryLimits { limit: Some(Quantity("2Gi".to_owned())), @@ -510,6 +511,10 @@ mod tests { } )] .into(), + v1alpha1::OpenSearchTls { + server_secret_class: Some(SecretClassName::from_str_unsafe("tls")), + internal_secret_class: SecretClassName::from_str_unsafe("tls") + }, )), result.ok() ); @@ -687,6 +692,7 @@ mod tests { image: serde_json::from_str(r#"{"productVersion": "3.1.0"}"#) .expect("should be a valid ProductImage structure"), cluster_config: v1alpha1::OpenSearchClusterConfig { + tls: v1alpha1::OpenSearchTls::default(), vector_aggregator_config_map_name: Some(ConfigMapName::from_str_unsafe( "vector-aggregator", )), diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 768ea11..89bc7de 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -32,13 +32,14 @@ use crate::{ NameIsValidLabelValue, role_utils::GenericProductSpecificCommonConfig, types::{ - kubernetes::{ConfigMapName, ContainerName, ListenerClassName}, + kubernetes::{ConfigMapName, ContainerName, ListenerClassName, SecretClassName}, operator::{ClusterName, ProductName, RoleName}, }, }, }; constant!(DEFAULT_LISTENER_CLASS: ListenerClassName = "cluster-internal"); +constant!(TLS_DEFAULT_SECRET_CLASS: SecretClassName = "tls"); #[versioned( version(name = "v1alpha1"), @@ -84,6 +85,10 @@ pub mod versioned { #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OpenSearchClusterConfig { + /// TLS configuration options for the server (REST API) and internal communication (transport). + #[serde(default)] + pub tls: OpenSearchTls, + /// Name of the Vector aggregator [discovery ConfigMap](DOCS_BASE_URL_PLACEHOLDER/concepts/service_discovery). /// It must contain the key `ADDRESS` with the address of the Vector aggregator. /// Follow the [logging tutorial](DOCS_BASE_URL_PLACEHOLDER/tutorials/logging-vector-aggregator) @@ -92,6 +97,27 @@ pub mod versioned { pub vector_aggregator_config_map_name: Option, } + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct OpenSearchTls { + /// Only affects client connections to the REST API. + /// This setting controls: + /// - If TLS encryption is used at all + /// - Which cert the servers should use to authenticate themselves against the client + #[serde( + default = "server_secret_class_default", + skip_serializing_if = "Option::is_none" + )] + pub server_secret_class: Option, + + /// Only affects internal communication (transport). Used for mutual verification between OpenSearch nodes. + /// This setting controls: + /// - Which cert the servers should use to authenticate themselves against other servers + /// - Which ca.crt to use when validating the other server + #[serde(default = "internal_secret_class_default")] + pub internal_secret_class: SecretClassName, + } + // The possible node roles are by default the built-in roles and the search role, see // https://github.com/opensearch-project/OpenSearch/blob/3.0.0/server/src/main/java/org/opensearch/cluster/node/DiscoveryNode.java#L609-L614. // @@ -172,6 +198,13 @@ pub mod versioned { /// documentation](DOCS_BASE_URL_PLACEHOLDER/opensearch/usage-guide/node-roles) for details. pub node_roles: NodeRoles, + /// Request secret (currently only autoTls certificates) lifetime from the secret operator, e.g. `7d`, or `30d`. + /// This can be shortened by the `maxCertificateLifetime` setting on the SecretClass issuing the TLS certificate. + /// + /// Defaults to 1d. + #[fragment_attrs(serde(default))] + pub requested_secret_lifetime: Duration, + #[fragment_attrs(serde(default))] pub resources: Resources, } @@ -274,6 +307,9 @@ impl v1alpha1::OpenSearchConfig { v1alpha1::NodeRole::Data, v1alpha1::NodeRole::RemoteClusterClient, ])), + requested_secret_lifetime: Some( + Duration::from_str("1d").expect("should be a valid duration"), + ), resources: ResourcesFragment { memory: MemoryLimitsFragment { // An idle node already requires 2 Gi. @@ -303,6 +339,23 @@ impl v1alpha1::OpenSearchConfig { } } +impl Default for v1alpha1::OpenSearchTls { + fn default() -> Self { + v1alpha1::OpenSearchTls { + server_secret_class: server_secret_class_default(), + internal_secret_class: internal_secret_class_default(), + } + } +} + +fn server_secret_class_default() -> Option { + Some(TLS_DEFAULT_SECRET_CLASS.to_owned()) +} + +fn internal_secret_class_default() -> SecretClassName { + TLS_DEFAULT_SECRET_CLASS.to_owned() +} + #[derive(Clone, Debug, Default, Deserialize, JsonSchema, PartialEq, Serialize)] pub struct NodeRoles(pub Vec); diff --git a/rust/operator-binary/src/framework/types/kubernetes.rs b/rust/operator-binary/src/framework/types/kubernetes.rs index b172806..f7b5282 100644 --- a/rust/operator-binary/src/framework/types/kubernetes.rs +++ b/rust/operator-binary/src/framework/types/kubernetes.rs @@ -80,6 +80,14 @@ attributed_string_type! { is_rfc_1123_dns_subdomain_name } +attributed_string_type! { + SecretClassName, + "The name of a SecretClass", + "tls", + // The secret class name is used in an annotation on the tls volume. + is_rfc_1123_dns_subdomain_name +} + attributed_string_type! { SecretKey, "The key for a Secret", @@ -145,8 +153,8 @@ attributed_string_type! { mod tests { use super::{ ClusterRoleName, ConfigMapKey, ConfigMapName, ContainerName, ListenerClassName, - ListenerName, NamespaceName, PersistentVolumeClaimName, RoleBindingName, SecretKey, - SecretName, ServiceAccountName, ServiceName, StatefulSetName, Uid, VolumeName, + ListenerName, NamespaceName, PersistentVolumeClaimName, RoleBindingName, SecretClassName, + SecretKey, SecretName, ServiceAccountName, ServiceName, StatefulSetName, Uid, VolumeName, }; #[test] @@ -160,6 +168,7 @@ mod tests { NamespaceName::test_example(); PersistentVolumeClaimName::test_example(); RoleBindingName::test_example(); + SecretClassName::test_example(); SecretKey::test_example(); SecretName::test_example(); ServiceAccountName::test_example(); diff --git a/tests/templates/kuttl/backup-restore/21-install-opensearch-1.yaml.j2 b/tests/templates/kuttl/backup-restore/21-install-opensearch-1.yaml.j2 index fa2e305..0535e0c 100644 --- a/tests/templates/kuttl/backup-restore/21-install-opensearch-1.yaml.j2 +++ b/tests/templates/kuttl/backup-restore/21-install-opensearch-1.yaml.j2 @@ -35,14 +35,7 @@ spec: plugins.security.allow_default_init_securityindex: "true" plugins.security.authcz.admin_dn: CN=opensearch-1-admin-certificate plugins.security.restapi.roles_enabled: all_access - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt + plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/concatenated/ca.crt s3.client.default.endpoint: https://minio:9000/ s3.client.default.protocol: https s3.client.default.region: unused # but required @@ -117,7 +110,7 @@ spec: /stackable/opensearch/config/tls-admin/tls.crt > \ /stackable/opensearch/config/tls-concatenated/ca.crt volumeMounts: - - name: tls + - name: tls-server mountPath: /stackable/opensearch/config/tls readOnly: true - name: admin-certificate @@ -137,7 +130,7 @@ spec: subPath: java/cacerts readOnly: true - name: tls-concatenated - mountPath: /stackable/opensearch/config/tls + mountPath: /stackable/opensearch/config/tls/concatenated readOnly: true - name: keystore mountPath: /stackable/opensearch/config/opensearch.keystore @@ -166,20 +159,6 @@ spec: - name: system-trust-store emptyDir: sizeLimit: 10Mi - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch-1,service=opensearch-1-nodes-default,service=opensearch-1-nodes-default-headless,listener-volume=listener - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" - name: tls-concatenated emptyDir: sizeLimit: 1Mi diff --git a/tests/templates/kuttl/backup-restore/22-create-testuser.yaml b/tests/templates/kuttl/backup-restore/22-create-testuser.yaml index 9689f15..31795f1 100644 --- a/tests/templates/kuttl/backup-restore/22-create-testuser.yaml +++ b/tests/templates/kuttl/backup-restore/22-create-testuser.yaml @@ -72,7 +72,7 @@ data: client = OpenSearch( http_auth=('admin', 'AJVFsGJBbpT6mChn'), hosts=[{ - 'host': f'opensearch-1-nodes-default.{namespace}.svc.cluster.local', + 'host': f'opensearch-1.{namespace}.svc.cluster.local', 'port': 9200 }], http_compress=True, diff --git a/tests/templates/kuttl/backup-restore/23-create-data.yaml b/tests/templates/kuttl/backup-restore/23-create-data.yaml index 1c27372..db13738 100644 --- a/tests/templates/kuttl/backup-restore/23-create-data.yaml +++ b/tests/templates/kuttl/backup-restore/23-create-data.yaml @@ -72,7 +72,7 @@ data: client = OpenSearch( http_auth=('testuser', 'L9hUHtLVVEsrcLzZ'), hosts=[{ - 'host': f'opensearch-1-nodes-default.{namespace}.svc.cluster.local', + 'host': f'opensearch-1.{namespace}.svc.cluster.local', 'port': 9200 }], http_compress=True, diff --git a/tests/templates/kuttl/backup-restore/30-create-snapshot.yaml b/tests/templates/kuttl/backup-restore/30-create-snapshot.yaml index c1dd4a7..031bbd7 100644 --- a/tests/templates/kuttl/backup-restore/30-create-snapshot.yaml +++ b/tests/templates/kuttl/backup-restore/30-create-snapshot.yaml @@ -70,7 +70,7 @@ data: client = OpenSearch( hosts=[{ - 'host': f'opensearch-1-nodes-default.{namespace}.svc.cluster.local', + 'host': f'opensearch-1.{namespace}.svc.cluster.local', 'port': 9200 }], http_auth=('admin', 'AJVFsGJBbpT6mChn'), diff --git a/tests/templates/kuttl/backup-restore/31-backup-security-indices.yaml.j2 b/tests/templates/kuttl/backup-restore/31-backup-security-indices.yaml.j2 index 109431a..645d701 100644 --- a/tests/templates/kuttl/backup-restore/31-backup-security-indices.yaml.j2 +++ b/tests/templates/kuttl/backup-restore/31-backup-security-indices.yaml.j2 @@ -116,7 +116,7 @@ data: -cacert config/tls/ca.crt \ -cert config/tls-client/tls.crt \ -key config/tls-client/tls.key \ - --hostname opensearch-1-nodes-default.$NAMESPACE.svc.cluster.local \ + --hostname opensearch-1.$NAMESPACE.svc.cluster.local \ -backup /tmp/backup upload-security-indices-backup.sh: | #!/usr/bin/env sh diff --git a/tests/templates/kuttl/backup-restore/51-install-opensearch-2.yaml.j2 b/tests/templates/kuttl/backup-restore/51-install-opensearch-2.yaml.j2 index 996df72..a2b4ee9 100644 --- a/tests/templates/kuttl/backup-restore/51-install-opensearch-2.yaml.j2 +++ b/tests/templates/kuttl/backup-restore/51-install-opensearch-2.yaml.j2 @@ -35,14 +35,7 @@ spec: plugins.security.allow_default_init_securityindex: "true" plugins.security.authcz.admin_dn: CN=opensearch-2-admin-certificate plugins.security.restapi.roles_enabled: all_access - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt + plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/concatenated/ca.crt s3.client.default.endpoint: https://minio:9000/ s3.client.default.protocol: https s3.client.default.region: unused # but required @@ -117,7 +110,7 @@ spec: /stackable/opensearch/config/tls-admin/tls.crt > \ /stackable/opensearch/config/tls-concatenated/ca.crt volumeMounts: - - name: tls + - name: tls-server mountPath: /stackable/opensearch/config/tls readOnly: true - name: admin-certificate @@ -137,7 +130,7 @@ spec: subPath: java/cacerts readOnly: true - name: tls-concatenated - mountPath: /stackable/opensearch/config/tls + mountPath: /stackable/opensearch/config/tls/concatenated readOnly: true - name: keystore mountPath: /stackable/opensearch/config/opensearch.keystore @@ -166,20 +159,6 @@ spec: - name: system-trust-store emptyDir: sizeLimit: 10Mi - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch-2,service=opensearch-2-nodes-default,service=opensearch-2-nodes-default-headless,listener-volume=listener - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" - name: tls-concatenated emptyDir: sizeLimit: 1Mi diff --git a/tests/templates/kuttl/backup-restore/60-restore-security-indices.yaml.j2 b/tests/templates/kuttl/backup-restore/60-restore-security-indices.yaml.j2 index 403763b..2ccaca8 100644 --- a/tests/templates/kuttl/backup-restore/60-restore-security-indices.yaml.j2 +++ b/tests/templates/kuttl/backup-restore/60-restore-security-indices.yaml.j2 @@ -121,5 +121,5 @@ data: -cacert config/tls/ca.crt \ -cert config/tls-client/tls.crt \ -key config/tls-client/tls.key \ - --hostname opensearch-2-nodes-default.$NAMESPACE.svc.cluster.local \ + --hostname opensearch-2.$NAMESPACE.svc.cluster.local \ --configdir /tmp/backup diff --git a/tests/templates/kuttl/backup-restore/61-restore-snapshot.yaml b/tests/templates/kuttl/backup-restore/61-restore-snapshot.yaml index e9f5d56..67483eb 100644 --- a/tests/templates/kuttl/backup-restore/61-restore-snapshot.yaml +++ b/tests/templates/kuttl/backup-restore/61-restore-snapshot.yaml @@ -70,7 +70,7 @@ data: client = OpenSearch( hosts=[{ - 'host': f'opensearch-2-nodes-default.{namespace}.svc.cluster.local', + 'host': f'opensearch-2.{namespace}.svc.cluster.local', 'port': 9200 }], http_auth=('admin', 'AJVFsGJBbpT6mChn'), diff --git a/tests/templates/kuttl/backup-restore/70-test-opensearch-2.yaml b/tests/templates/kuttl/backup-restore/70-test-opensearch-2.yaml index f0e90eb..63157b0 100644 --- a/tests/templates/kuttl/backup-restore/70-test-opensearch-2.yaml +++ b/tests/templates/kuttl/backup-restore/70-test-opensearch-2.yaml @@ -72,7 +72,7 @@ data: client = OpenSearch( http_auth=('testuser', 'L9hUHtLVVEsrcLzZ'), hosts=[{ - 'host': f'opensearch-2-nodes-default.{namespace}.svc.cluster.local', + 'host': f'opensearch-2.{namespace}.svc.cluster.local', 'port': 9200 }], http_compress=True, diff --git a/tests/templates/kuttl/external-access/opensearch.yaml.j2 b/tests/templates/kuttl/external-access/opensearch.yaml.j2 index 05df6b6..514fef6 100644 --- a/tests/templates/kuttl/external-access/opensearch.yaml.j2 +++ b/tests/templates/kuttl/external-access/opensearch.yaml.j2 @@ -25,45 +25,18 @@ spec: - cluster_manager listenerClass: test-external-stable-$NAMESPACE replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-cluster-manager-headless data1: config: nodeRoles: - data listenerClass: test-external-unstable-$NAMESPACE replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch-nodes-data1-headless data2: config: nodeRoles: - data listenerClass: test-cluster-internal-$NAMESPACE replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch-nodes-data2-headless envOverrides: # Only required for the official image # The official image (built with https://github.com/opensearch-project/opensearch-build) @@ -81,14 +54,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt podOverrides: spec: containers: @@ -97,27 +62,11 @@ spec: - name: security-config mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security readOnly: true - - name: tls - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret diff --git a/tests/templates/kuttl/ldap/20_opensearch-security-config.yaml.j2 b/tests/templates/kuttl/ldap/20_opensearch-security-config.yaml.j2 index 6ee2c86..2ee2485 100644 --- a/tests/templates/kuttl/ldap/20_opensearch-security-config.yaml.j2 +++ b/tests/templates/kuttl/ldap/20_opensearch-security-config.yaml.j2 @@ -58,7 +58,7 @@ stringData: enable_ssl: true hosts: - openldap.$NAMESPACE.svc.cluster.local:1636 - pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt + pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/ca.crt userbase: ou=users,dc=stackable,dc=tech username_attribute: uid usersearch: (cn={0}) @@ -73,7 +73,7 @@ stringData: enable_ssl: true hosts: - openldap.$NAMESPACE.svc.cluster.local:1636 - pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt + pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/ca.crt userbase: ou=users,dc=stackable,dc=tech username_attribute: uid usersearch: (cn={0}) diff --git a/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 b/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 index a98249b..8312285 100644 --- a/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 @@ -48,14 +48,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt podOverrides: spec: containers: @@ -64,25 +56,8 @@ spec: - name: security-config mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security readOnly: true - - name: tls - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-default-headless - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" diff --git a/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 b/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 index 164f957..b2a0805 100644 --- a/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 @@ -50,15 +50,6 @@ spec: ROOT: level: INFO replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-automatic-headless custom: config: logging: @@ -68,15 +59,6 @@ spec: custom: configMap: custom-log-config replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-custom-headless configOverrides: opensearch.yml: # Disable memory mapping in this test; If memory mapping were activated, the kernel setting @@ -88,14 +70,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt podOverrides: spec: containers: @@ -104,26 +78,10 @@ spec: - name: security-config mountPath: /stackable/opensearch/config/opensearch-security readOnly: true - - name: tls - mountPath: /stackable/opensearch/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret diff --git a/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 b/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 index 1ccd35e..e5d8b16 100644 --- a/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 @@ -38,14 +38,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: /stackable/opensearch/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: /stackable/opensearch/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: /stackable/opensearch/config/tls/ca.crt podOverrides: spec: containers: @@ -54,28 +46,11 @@ spec: - name: security-config mountPath: /stackable/opensearch/config/opensearch-security readOnly: true - - name: tls - mountPath: /stackable/opensearch/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-default-headless - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret diff --git a/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 index f0e99a0..106afd9 100644 --- a/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 @@ -10,8 +10,12 @@ spec: {% endif %} productVersion: "{{ test_scenario['values']['opensearch'].split(',')[0] }}" pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: +{% if test_scenario['values']['server-use-tls'] == 'false' %} + tls: + serverSecretClass: null +{% endif %} +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -23,15 +27,6 @@ spec: config: listenerClass: external-unstable replicas: 1 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-default-headless envOverrides: # Only required for the official image # The official image (built with https://github.com/opensearch-project/opensearch-build) @@ -49,14 +44,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt podOverrides: spec: containers: @@ -65,27 +52,11 @@ spec: - name: security-config mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security readOnly: true - - name: tls - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret diff --git a/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 index 14dc539..fbd2371 100644 --- a/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 +++ b/tests/templates/kuttl/opensearch-dashboards/20-install-opensearch-dashboards.yaml.j2 @@ -7,7 +7,11 @@ commands: --repo https://opensearch-project.github.io/helm-charts --version "{{ test_scenario['values']['opensearch'].split(',')[0] }}" --values 20_opensearch-dashboards-values.yaml +{% if test_scenario['values']['server-use-tls'] == 'true' %} --set opensearchHosts=https://opensearch.$NAMESPACE.svc.cluster.local:9200 +{% else %} + --set opensearchHosts=http://opensearch.$NAMESPACE.svc.cluster.local:9200 +{% endif %} --namespace $NAMESPACE --wait timeout: 600 diff --git a/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 b/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 index 3ef67a3..30b5b56 100644 --- a/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 +++ b/tests/templates/kuttl/opensearch-dashboards/20_opensearch-dashboards-values.yaml.j2 @@ -8,6 +8,12 @@ serviceAccount: # Use the ServiceAccount of OpenSearch because its permissions are already configured to work on # OpenShift. name: opensearch-serviceaccount +extraEnvs: + - name: OPENSEARCH_PASSWORD + valueFrom: + secretKeyRef: + name: opensearch-credentials + key: kibanaserver config: opensearch_dashboards.yml: server: @@ -17,19 +23,14 @@ config: key: /stackable/opensearch-dashboards/config/tls/tls.key opensearch: username: kibanaserver +{% if test_scenario['values']['server-use-tls'] == 'true' %} ssl: verificationMode: full - certificateAuthorities: - - /stackable/opensearch-dashboards/config/tls/ca.crt + certificateAuthorities: [/stackable/opensearch-dashboards/config/tls/ca.crt] +{% endif %} opensearch_security: cookie: secure: true -extraEnvs: - - name: OPENSEARCH_PASSWORD - valueFrom: - secretKeyRef: - name: opensearch-credentials - key: kibanaserver extraVolumes: - name: tls ephemeral: @@ -45,12 +46,13 @@ extraVolumes: resources: requests: storage: "1" +# The Helm chart sets the volume "config" and adds a volume mount at +# /usr/share/opensearch-dashboards/config. But in the Stackable image, the configuration directory +# is located in /stackable/opensearch-dashboards/config. extraVolumeMounts: +# The same is true for the TLS volume. - mountPath: /stackable/opensearch-dashboards/config/tls name: tls - # The Helm chart sets the volume "config" and adds a volume mount at - # /usr/share/opensearch-dashboards/config. But in the Stackable image, the configuration directory - # is located in /stackable/opensearch-dashboards/config. - mountPath: /stackable/opensearch-dashboards/config/opensearch_dashboards.yml name: config subPath: opensearch_dashboards.yml diff --git a/tests/templates/kuttl/smoke/10-assert.yaml.j2 b/tests/templates/kuttl/smoke/10-assert.yaml.j2 index 6979ac6..667d31a 100644 --- a/tests/templates/kuttl/smoke/10-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-assert.yaml.j2 @@ -169,12 +169,15 @@ spec: name: listener - mountPath: /stackable/log name: log + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/internal + name: tls-internal +{% if test_scenario['values']['server-use-tls'] == 'true' %} + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/server + name: tls-server +{% endif %} - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security name: security-config readOnly: true - - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - name: tls - readOnly: true {% if lookup('env', 'VECTOR_AGGREGATOR') %} - args: - |- @@ -255,16 +258,12 @@ spec: - emptyDir: sizeLimit: 30Mi name: log - - name: security-config - secret: - defaultMode: 0o660 - secretName: opensearch-security-config - ephemeral: volumeClaimTemplate: metadata: annotations: secrets.stackable.tech/class: tls - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-cluster-manager-headless + secrets.stackable.tech/scope: listener-volume=listener,pod spec: accessModes: - ReadWriteOnce @@ -273,7 +272,30 @@ spec: storage: "1" storageClassName: secrets.stackable.tech volumeMode: Filesystem - name: tls + name: tls-internal +{% if test_scenario['values']['server-use-tls'] == 'true' %} + - ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/backend.autotls.cert.lifetime: 1d + secrets.stackable.tech/class: tls + secrets.stackable.tech/format: tls-pem + secrets.stackable.tech/scope: service=opensearch,listener-volume=listener,pod + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem + name: tls-server +{% endif %} + - name: security-config + secret: + defaultMode: 0o660 + secretName: opensearch-security-config volumeClaimTemplates: - apiVersion: v1 kind: PersistentVolumeClaim @@ -480,12 +502,15 @@ spec: name: listener - mountPath: /stackable/log name: log + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/internal + name: tls-internal +{% if test_scenario['values']['server-use-tls'] == 'true' %} + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/server + name: tls-server +{% endif %} - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security name: security-config readOnly: true - - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - name: tls - readOnly: true {% if lookup('env', 'VECTOR_AGGREGATOR') %} - args: - |- @@ -566,16 +591,12 @@ spec: - emptyDir: sizeLimit: 30Mi name: log - - name: security-config - secret: - defaultMode: 0o660 - secretName: opensearch-security-config - ephemeral: volumeClaimTemplate: metadata: annotations: secrets.stackable.tech/class: tls - secrets.stackable.tech/scope: node,pod,service=opensearch-nodes-data-headless + secrets.stackable.tech/scope: listener-volume=listener,pod spec: accessModes: - ReadWriteOnce @@ -584,7 +605,30 @@ spec: storage: "1" storageClassName: secrets.stackable.tech volumeMode: Filesystem - name: tls + name: tls-internal +{% if test_scenario['values']['server-use-tls'] == 'true' %} + - ephemeral: + volumeClaimTemplate: + metadata: + annotations: + secrets.stackable.tech/backend.autotls.cert.lifetime: 1d + secrets.stackable.tech/class: tls + secrets.stackable.tech/format: tls-pem + secrets.stackable.tech/scope: service=opensearch,listener-volume=listener,pod + spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: "1" + storageClassName: secrets.stackable.tech + volumeMode: Filesystem + name: tls-server +{% endif %} + - name: security-config + secret: + defaultMode: 0o660 + secretName: opensearch-security-config volumeClaimTemplates: - apiVersion: v1 kind: PersistentVolumeClaim @@ -654,14 +698,18 @@ data: node.store.allow_mmap: "false" plugins.security.allow_default_init_securityindex: "true" plugins.security.nodes_dn: ["CN=generated certificate for pod"] - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt" - plugins.security.ssl.http.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key" - plugins.security.ssl.http.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt" - plugins.security.ssl.transport.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key" - plugins.security.ssl.transport.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt" +{% if test_scenario['values']['server-use-tls'] == 'true' %} + plugins.security.ssl.http.enabled: true + plugins.security.ssl.http.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/tls.crt" + plugins.security.ssl.http.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/tls.key" + plugins.security.ssl.http.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/ca.crt" +{% else %} + plugins.security.ssl.http.enabled: false +{% endif %} + plugins.security.ssl.transport.enabled: true + plugins.security.ssl.transport.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/tls.crt" + plugins.security.ssl.transport.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/tls.key" + plugins.security.ssl.transport.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/ca.crt" --- apiVersion: v1 kind: ConfigMap @@ -690,14 +738,18 @@ data: node.store.allow_mmap: "false" plugins.security.allow_default_init_securityindex: "true" plugins.security.nodes_dn: ["CN=generated certificate for pod"] - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt" - plugins.security.ssl.http.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key" - plugins.security.ssl.http.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt" - plugins.security.ssl.transport.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key" - plugins.security.ssl.transport.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt" +{% if test_scenario['values']['server-use-tls'] == 'true' %} + plugins.security.ssl.http.enabled: true + plugins.security.ssl.http.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/tls.crt" + plugins.security.ssl.http.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/tls.key" + plugins.security.ssl.http.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/server/ca.crt" +{% else %} + plugins.security.ssl.http.enabled: false +{% endif %} + plugins.security.ssl.transport.enabled: true + plugins.security.ssl.transport.pemcert_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/tls.crt" + plugins.security.ssl.transport.pemkey_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/tls.key" + plugins.security.ssl.transport.pemtrustedcas_filepath: "{{ test_scenario['values']['opensearch_home'] }}/config/tls/internal/ca.crt" --- apiVersion: v1 kind: Service diff --git a/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 b/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 index 47c203c..734bc07 100644 --- a/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 @@ -10,8 +10,12 @@ spec: {% endif %} productVersion: "{{ test_scenario['values']['opensearch'].split(',')[0] }}" pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: +{% if test_scenario['values']['server-use-tls'] == 'false' %} + tls: + serverSecretClass: null +{% endif %} +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -29,15 +33,6 @@ spec: capacity: 100Mi listenerClass: external-unstable replicas: 3 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-cluster-manager-headless data: config: nodeRoles: @@ -50,15 +45,6 @@ spec: capacity: 2Gi listenerClass: cluster-internal replicas: 2 - podOverrides: - spec: - volumes: - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch-nodes-data-headless envOverrides: # Only required for the official image # The official image (built with https://github.com/opensearch-project/opensearch-build) @@ -76,14 +62,6 @@ spec: # not be created even if enough disk space would be available. cluster.routing.allocation.disk.threshold_enabled: "false" plugins.security.allow_default_init_securityindex: "true" - plugins.security.ssl.transport.enabled: "true" - plugins.security.ssl.transport.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.transport.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.transport.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt - plugins.security.ssl.http.enabled: "true" - plugins.security.ssl.http.pemcert_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.crt - plugins.security.ssl.http.pemkey_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/tls.key - plugins.security.ssl.http.pemtrustedcas_filepath: {{ test_scenario['values']['opensearch_home'] }}/config/tls/ca.crt podOverrides: spec: containers: @@ -92,27 +70,11 @@ spec: - name: security-config mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security readOnly: true - - name: tls - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls - readOnly: true volumes: - name: security-config secret: secretName: opensearch-security-config defaultMode: 0o660 - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret diff --git a/tests/templates/kuttl/smoke/20-test-opensearch.yaml b/tests/templates/kuttl/smoke/20-test-opensearch.yaml.j2 similarity index 95% rename from tests/templates/kuttl/smoke/20-test-opensearch.yaml rename to tests/templates/kuttl/smoke/20-test-opensearch.yaml.j2 index fcc3fcc..b122d8f 100644 --- a/tests/templates/kuttl/smoke/20-test-opensearch.yaml +++ b/tests/templates/kuttl/smoke/20-test-opensearch.yaml.j2 @@ -22,6 +22,8 @@ spec: # required for pip install - name: HOME value: /stackable + - name: HTTP_USE_TLS + value: "{{ test_scenario['values']['server-use-tls'] }}" - name: NAMESPACE valueFrom: fieldRef: @@ -78,6 +80,7 @@ data: from opensearchpy import OpenSearch namespace = os.environ['NAMESPACE'] + http_use_tls = os.environ['HTTP_USE_TLS'] == 'true' host = f'opensearch.{namespace}.svc.cluster.local' port = 9200 @@ -89,7 +92,7 @@ data: hosts = [{'host': host, 'port': port}], http_compress = True, # enables gzip compression for request bodies http_auth = auth, - use_ssl = True, + use_ssl = http_use_tls, verify_certs = True, ca_certs = ca_certs_path ) diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 74d268e..d2ffb73 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -9,6 +9,10 @@ dimensions: - name: opensearch_home values: - /stackable/opensearch + - name: server-use-tls + values: + - "true" + - "false" # The release must sometimes be known in podOverrides or Helm values - name: release values: @@ -18,6 +22,7 @@ tests: dimensions: - opensearch - opensearch_home + - server-use-tls - name: external-access dimensions: - opensearch @@ -38,6 +43,7 @@ tests: dimensions: - opensearch - opensearch_home + - server-use-tls - release # requires the repository-s3 plugin - name: backup-restore