diff --git a/deploy/helm/opensearch-operator/crds/crds.yaml b/deploy/helm/opensearch-operator/crds/crds.yaml index 12f3da0..d483c41 100644 --- a/deploy/helm/opensearch-operator/crds/crds.yaml +++ b/deploy/helm/opensearch-operator/crds/crds.yaml @@ -29,9 +29,40 @@ spec: generates in the [operator documentation](https://docs.stackable.tech/home/nightly/opensearch/). properties: clusterConfig: - default: {} + default: + tls: + restSecretClass: tls + transportSecretClass: tls description: Configuration that applies to all roles and role groups properties: + tls: + default: + restSecretClass: tls + transportSecretClass: tls + description: TLS configuration options for the REST API and internal communication (transport). + properties: + restSecretClass: + 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 + type: string + transportSecretClass: + 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 + type: string + type: object vectorAggregatorConfigMapName: description: |- Name of the Vector aggregator [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery). @@ -303,6 +334,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: @@ -651,6 +690,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/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index 832e488..3c3a88d 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -30,7 +30,7 @@ use validate::validate; use crate::{ crd::{ NodeRoles, - v1alpha1::{self}, + v1alpha1::{self, OpenSearchClusterConfig}, }, framework::{ ClusterName, ControllerName, HasName, HasUid, ListenerClassName, NameIsValidLabelValue, @@ -131,6 +131,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, } @@ -164,17 +165,20 @@ pub struct ValidatedCluster { pub name: ClusterName, pub namespace: NamespaceName, pub uid: Uid, + pub cluster_config: OpenSearchClusterConfig, pub role_config: GenericRoleConfig, pub role_group_configs: BTreeMap, } impl ValidatedCluster { + #[allow(clippy::too_many_arguments)] pub fn new( image: ResolvedProductImage, product_version: ProductVersion, name: ClusterName, namespace: NamespaceName, uid: impl Into, + cluster_config: OpenSearchClusterConfig, role_config: GenericRoleConfig, role_group_configs: BTreeMap, ) -> Self { @@ -191,6 +195,7 @@ impl ValidatedCluster { name, namespace, uid, + cluster_config, role_config, role_group_configs, } @@ -372,13 +377,17 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use uuid::uuid; use super::{Context, OpenSearchRoleGroupConfig, ValidatedCluster, ValidatedLogging}; use crate::{ controller::{OpenSearchNodeResources, ValidatedOpenSearchConfig}, - crd::{NodeRoles, v1alpha1}, + crd::{ + NodeRoles, + v1alpha1::{self, OpenSearchClusterConfig}, + }, framework::{ ClusterName, ListenerClassName, NamespaceName, OperatorName, ProductVersion, RoleGroupName, builder::pod::container::EnvVarSet, @@ -460,6 +469,7 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch"), NamespaceName::from_str_unsafe("default"), uuid!("e6ac237d-a6d4-43a1-8135-f36506110912"), + OpenSearchClusterConfig::default(), GenericRoleConfig::default(), [ ( @@ -513,6 +523,8 @@ mod tests { vector_container: None, }, node_roles: NodeRoles(node_roles.to_vec()), + requested_secret_lifetime: Duration::from_str("15d") + .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 43c3bda..bc1da16 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; @@ -77,7 +78,10 @@ mod tests { ContextNames, OpenSearchNodeResources, OpenSearchRoleGroupConfig, ValidatedCluster, ValidatedContainerLogConfigChoice, ValidatedLogging, ValidatedOpenSearchConfig, }, - crd::{NodeRoles, v1alpha1}, + crd::{ + NodeRoles, + v1alpha1::{self, OpenSearchClusterConfig}, + }, framework::{ ClusterName, ControllerName, ListenerClassName, NamespaceName, OperatorName, ProductName, ProductVersion, RoleGroupName, builder::pod::container::EnvVarSet, @@ -168,6 +172,7 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch"), NamespaceName::from_str_unsafe("default"), uuid!("e6ac237d-a6d4-43a1-8135-f36506110912"), + OpenSearchClusterConfig::default(), GenericRoleConfig::default(), [ ( @@ -210,6 +215,8 @@ mod tests { vector_container: None, }, node_roles: NodeRoles(node_roles.to_vec()), + requested_secret_lifetime: Duration::from_str("15d") + .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 db795f7..83d67d9 100644 --- a/rust/operator-binary/src/controller/build/node_config.rs +++ b/rust/operator-binary/src/controller/build/node_config.rs @@ -58,11 +58,46 @@ 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"; + /// Configuration of an OpenSearch node based on the cluster and role-group configuration pub struct NodeConfig { cluster: ValidatedCluster, 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 @@ -81,8 +116,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 @@ -112,25 +168,61 @@ impl NodeConfig { json!(["CN=generated certificate for pod".to_owned()]), ); - 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(); + + // TLS config for TRANSPORT port which is always enabled. + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_ENABLED.to_owned(), + json!("true".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMCERT_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/transport/tls.crt".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/transport/tls.key".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/transport/ca.crt".to_string()), + ); + + // TLS config for HTTP port which is optional. + if self.cluster.cluster_config.tls.rest_secret_class.is_some() { + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED.to_owned(), + json!("true".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMCERT_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/rest/tls.crt".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMKEY_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/rest/tls.key".to_string()), + ); + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH.to_owned(), + json!("${OPENSEARCH_PATH_CONF}/tls/rest/ca.crt".to_string()), + ); + } else { + config.insert( + CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_ENABLED.to_owned(), + json!("false".to_string()), + ); + } 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) @@ -277,13 +369,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::OpenSearchClusterConfig}, framework::{ ClusterName, ListenerClassName, NamespaceName, ProductVersion, RoleGroupName, product_logging::framework::ValidatedContainerLogConfigChoice, @@ -328,6 +421,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("15d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -364,6 +459,7 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch-cluster"), NamespaceName::from_str_unsafe("default"), uuid!("0b1e30e6-326e-4c1a-868d-ad6598b49e8b"), + OpenSearchClusterConfig::default(), GenericRoleConfig::default(), [( RoleGroupName::from_str_unsafe("default"), @@ -395,7 +491,7 @@ mod tests { "test: \"value\"" ) .to_owned(), - node_config.static_opensearch_config_file_content() + node_config.opensearch_config_file_content() ); } diff --git a/rust/operator-binary/src/controller/build/role_builder.rs b/rust/operator-binary/src/controller/build/role_builder.rs index 454d7d5..3199817 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; @@ -239,7 +240,10 @@ mod tests { ContextNames, OpenSearchRoleGroupConfig, ValidatedCluster, ValidatedContainerLogConfigChoice, ValidatedLogging, ValidatedOpenSearchConfig, }, - crd::{NodeRoles, v1alpha1}, + crd::{ + NodeRoles, + v1alpha1::{self, OpenSearchClusterConfig}, + }, framework::{ ClusterName, ControllerName, ListenerClassName, NamespaceName, OperatorName, ProductName, ProductVersion, RoleGroupName, builder::pod::container::EnvVarSet, @@ -276,6 +280,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("15d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -299,6 +305,7 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch-cluster"), NamespaceName::from_str_unsafe("default"), uuid!("0b1e30e6-326e-4c1a-868d-ad6598b49e8b"), + OpenSearchClusterConfig::default(), GenericRoleConfig::default(), [( RoleGroupName::from_str_unsafe("default"), 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 431690f..eea6de4 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,7 @@ use std::{collections::BTreeMap, str::FromStr}; use stackable_operator::{ - builder::meta::ObjectMetaBuilder, + builder::{meta::ObjectMetaBuilder, pod::volume::SecretFormat}, crd::listener::{self}, k8s_openapi::{ DeepMerge, @@ -49,6 +49,7 @@ use crate::{ container::{EnvVarName, new_container_builder}, volume::{ListenerReference, listener_operator_volume_source_builder_build_pvc}, }, + volume::build_tls_volume, }, kvp::label::{recommended_labels, role_group_selector, role_selector}, product_logging::framework::{ @@ -71,6 +72,9 @@ constant!(DATA_VOLUME_NAME: VolumeName = "data"); constant!(LISTENER_VOLUME_NAME: PersistentVolumeClaimName = "listener"); const LISTENER_VOLUME_DIR: &str = "/stackable/listener"; +constant!(TLS_REST_VOLUME_NAME: VolumeName = "tls-rest"); +constant!(TLS_TRANSPORT_VOLUME_NAME: VolumeName = "tls-transport"); + constant!(LOG_VOLUME_NAME: VolumeName = "log"); const LOG_VOLUME_DIR: &str = "/stackable/log"; @@ -126,7 +130,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) = @@ -212,6 +216,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)); } @@ -256,7 +262,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 { @@ -285,8 +291,28 @@ impl<'a> RoleGroupBuilder<'a> { }), ..Volume::default() }, + build_tls_volume( + &TLS_TRANSPORT_VOLUME_NAME.to_string(), + &self.cluster.cluster_config.tls.transport_secret_class, + service_scopes.clone(), + SecretFormat::TlsPem, + &self.role_group_config.config.requested_secret_lifetime, + Some(&LISTENER_VOLUME_NAME.to_string()), + ), ]; + if let Some(tls_rest_secret_class_name) = &self.cluster.cluster_config.tls.rest_secret_class + { + volumes.push(build_tls_volume( + &TLS_REST_VOLUME_NAME.to_string(), + tls_rest_secret_class_name, + service_scopes, + SecretFormat::TlsPem, + &self.role_group_config.config.requested_secret_lifetime, + Some(&LISTENER_VOLUME_NAME.to_string()), + )) + }; + // 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, @@ -406,7 +432,7 @@ impl<'a> RoleGroupBuilder<'a> { .and_then(|env_var| env_var.value.clone()) .unwrap_or(format!("{opensearch_home}/config")); - let volume_mounts = [ + let mut volume_mounts = vec![ VolumeMount { mount_path: format!("{opensearch_path_conf}/{CONFIGURATION_FILE_OPENSEARCH_YML}"), name: CONFIG_VOLUME_NAME.to_string(), @@ -438,8 +464,21 @@ impl<'a> RoleGroupBuilder<'a> { name: LOG_VOLUME_NAME.to_string(), ..VolumeMount::default() }, + VolumeMount { + mount_path: format!("{opensearch_path_conf}/tls/transport"), + name: TLS_TRANSPORT_VOLUME_NAME.to_string(), + ..VolumeMount::default() + }, ]; + if self.cluster.cluster_config.tls.rest_secret_class.is_some() { + volume_mounts.push(VolumeMount { + mount_path: format!("{opensearch_path_conf}/tls/rest"), + name: TLS_REST_VOLUME_NAME.to_string(), + ..VolumeMount::default() + }) + } + new_container_builder(&v1alpha1::Container::OpenSearch.to_container_name()) .image_from_product_image(&self.cluster.image) .command(vec![ @@ -647,6 +686,7 @@ mod tests { kvp::LabelValue, product_logging::spec::AutomaticContainerLogConfig, role_utils::GenericRoleConfig, + shared::time::Duration, }; use strum::IntoEnumIterator; use uuid::uuid; @@ -660,7 +700,10 @@ mod tests { ContextNames, OpenSearchRoleGroupConfig, ValidatedCluster, ValidatedContainerLogConfigChoice, ValidatedLogging, ValidatedOpenSearchConfig, }, - crd::{NodeRoles, v1alpha1}, + crd::{ + NodeRoles, + v1alpha1::{self, OpenSearchClusterConfig}, + }, framework::{ ClusterName, ConfigMapName, ControllerName, ListenerClassName, NamespaceName, OperatorName, ProductName, ProductVersion, RoleGroupName, ServiceAccountName, @@ -722,6 +765,8 @@ mod tests { v1alpha1::NodeRole::Ingest, v1alpha1::NodeRole::RemoteClusterClient, ]), + requested_secret_lifetime: Duration::from_str("15d") + .expect("should be a valid duration"), resources: Resources::default(), termination_grace_period_seconds: 30, }, @@ -738,6 +783,7 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch-cluster"), NamespaceName::from_str_unsafe("default"), uuid!("0b1e30e6-326e-4c1a-868d-ad6598b49e8b"), + OpenSearchClusterConfig::default(), GenericRoleConfig::default(), [( RoleGroupName::from_str_unsafe("default"), diff --git a/rust/operator-binary/src/controller/validate.rs b/rust/operator-binary/src/controller/validate.rs index e3dd839..7fd1eb1 100644 --- a/rust/operator-binary/src/controller/validate.rs +++ b/rust/operator-binary/src/controller/validate.rs @@ -138,6 +138,7 @@ pub fn validate( cluster_name, namespace, uid, + cluster.spec.cluster_config.clone(), cluster.spec.nodes.role_config.clone(), role_group_configs, )) @@ -183,6 +184,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, }; @@ -273,11 +275,11 @@ mod tests { controller::{ContextNames, ValidatedCluster, ValidatedLogging, ValidatedOpenSearchConfig}, crd::{ NodeRoles, - v1alpha1::{self}, + v1alpha1::{self, OpenSearchClusterConfig, OpenSearchTls}, }, framework::{ ClusterName, ConfigMapName, ControllerName, ListenerClassName, NamespaceName, - OperatorName, ProductName, ProductVersion, RoleGroupName, + OperatorName, ProductName, ProductVersion, RoleGroupName, SecretClassName, builder::pod::container::{EnvVarName, EnvVarSet}, product_logging::framework::{ ValidatedContainerLogConfigChoice, VectorContainerLogConfig, @@ -304,6 +306,15 @@ mod tests { ClusterName::from_str_unsafe("my-opensearch"), NamespaceName::from_str_unsafe("default"), uuid!("e6ac237d-a6d4-43a1-8135-f36506110912"), + OpenSearchClusterConfig { + tls: OpenSearchTls { + rest_secret_class: Some(SecretClassName::from_str_unsafe("tls")), + transport_secret_class: SecretClassName::from_str_unsafe("tls") + }, + vector_aggregator_config_map_name: Some(ConfigMapName::from_str_unsafe( + "vector-aggregator" + )) + }, GenericRoleConfig::default(), [( RoleGroupName::from_str_unsafe("default"), @@ -400,6 +411,8 @@ mod tests { ] .into() ), + requested_secret_lifetime: Duration::from_str("15d") + .expect("should be a valid duration"), resources: Resources { memory: MemoryLimits { limit: Some(Quantity("2Gi".to_owned())), @@ -662,6 +675,10 @@ mod tests { image: serde_json::from_str(r#"{"productVersion": "3.1.0"}"#) .expect("should be a valid ProductImage structure"), cluster_config: v1alpha1::OpenSearchClusterConfig { + tls: OpenSearchTls { + rest_secret_class: Some(SecretClassName::from_str_unsafe("tls")), + transport_secret_class: SecretClassName::from_str_unsafe("tls"), + }, 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 4803e70..49a6d0c 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -30,11 +30,12 @@ use crate::{ constant, framework::{ ClusterName, ConfigMapName, ContainerName, ListenerClassName, NameIsValidLabelValue, - ProductName, RoleName, role_utils::GenericProductSpecificCommonConfig, + ProductName, RoleName, SecretClassName, role_utils::GenericProductSpecificCommonConfig, }, }; constant!(DEFAULT_LISTENER_CLASS: ListenerClassName = "cluster-internal"); +constant!(TLS_DEFAULT_SECRET_CLASS: SecretClassName = "tls"); #[versioned( version(name = "v1alpha1"), @@ -80,6 +81,9 @@ pub mod versioned { #[derive(Clone, Debug, Default, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct OpenSearchClusterConfig { + /// TLS configuration options for the 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) @@ -88,6 +92,26 @@ 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 = "rest_secret_class_default", + skip_serializing_if = "Option::is_none" + )] + pub rest_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 = "transport_secret_class_default")] + pub transport_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. // @@ -168,6 +192,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, } @@ -270,6 +301,9 @@ impl v1alpha1::OpenSearchConfig { v1alpha1::NodeRole::Data, v1alpha1::NodeRole::RemoteClusterClient, ])), + requested_secret_lifetime: Some( + Duration::from_str("15d").expect("should be a valid duration"), + ), resources: ResourcesFragment { memory: MemoryLimitsFragment { // An idle node already requires 2 Gi. @@ -299,6 +333,23 @@ impl v1alpha1::OpenSearchConfig { } } +impl Default for v1alpha1::OpenSearchTls { + fn default() -> Self { + v1alpha1::OpenSearchTls { + rest_secret_class: rest_secret_class_default(), + transport_secret_class: transport_secret_class_default(), + } + } +} + +fn rest_secret_class_default() -> Option { + Some(TLS_DEFAULT_SECRET_CLASS.to_owned()) +} + +fn transport_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.rs b/rust/operator-binary/src/framework.rs index 3442b2e..f4f3cbf 100644 --- a/rust/operator-binary/src/framework.rs +++ b/rust/operator-binary/src/framework.rs @@ -512,7 +512,14 @@ attributed_string_type! { is_rfc_1123_label_name, is_valid_label_value } - +attributed_string_type! { + SecretClassName, + "The TLS SecretClass name", + "tls", + // The secret class name is used in an annotation on the tls volume. + (max_length = RFC_1123_SUBDOMAIN_MAX_LENGTH), + is_rfc_1123_dns_subdomain_name +} #[cfg(test)] mod tests { use std::str::FromStr; diff --git a/rust/operator-binary/src/framework/builder.rs b/rust/operator-binary/src/framework/builder.rs index 40caba1..078acfa 100644 --- a/rust/operator-binary/src/framework/builder.rs +++ b/rust/operator-binary/src/framework/builder.rs @@ -1,3 +1,4 @@ pub mod meta; pub mod pdb; pub mod pod; +pub mod volume; diff --git a/rust/operator-binary/src/framework/builder/volume.rs b/rust/operator-binary/src/framework/builder/volume.rs new file mode 100644 index 0000000..944b900 --- /dev/null +++ b/rust/operator-binary/src/framework/builder/volume.rs @@ -0,0 +1,37 @@ +use stackable_operator::{ + builder::pod::volume::{SecretFormat, SecretOperatorVolumeSourceBuilder, VolumeBuilder}, + k8s_openapi::api::core::v1::Volume, + shared::time::Duration, +}; + +use crate::framework::{SecretClassName, ServiceName}; + +pub fn build_tls_volume( + volume_name: &String, + tls_secret_class_name: &SecretClassName, + service_scopes: Vec, + secret_format: SecretFormat, + requested_secret_lifetime: &Duration, + listener_scope: Option<&str>, +) -> 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); + } + if let Some(listener_scope) = listener_scope { + secret_volume_source_builder.with_listener_volume_scope(listener_scope); + } + + VolumeBuilder::new(volume_name) + .ephemeral( + secret_volume_source_builder + .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() +} diff --git a/tests/templates/kuttl/external-access/opensearch.yaml.j2 b/tests/templates/kuttl/external-access/opensearch.yaml.j2 index 2b7da52..9e232bb 100644 --- a/tests/templates/kuttl/external-access/opensearch.yaml.j2 +++ b/tests/templates/kuttl/external-access/opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -27,45 +29,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) @@ -83,14 +58,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: @@ -99,27 +66,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/21-install-opensearch.yaml.j2 b/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 index ffd74f8..0de3d69 100644 --- a/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/ldap/21-install-opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -50,14 +52,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: @@ -66,25 +60,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 f17b427..e0ad9c9 100644 --- a/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/logging/20-install-opensearch.yaml.j2 @@ -27,6 +27,8 @@ spec: {% endif %} pullPolicy: IfNotPresent clusterConfig: + tls: + secretClass: tls vectorAggregatorConfigMapName: opensearch-vector-aggregator-discovery nodes: roleGroups: @@ -52,15 +54,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: @@ -70,15 +63,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 @@ -90,14 +74,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: @@ -106,26 +82,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 ad50b21..0de56c2 100644 --- a/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/metrics/20-install-opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -40,14 +42,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: @@ -56,28 +50,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 0d620e1..c92794c 100644 --- a/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/opensearch-dashboards/10-install-opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -25,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) @@ -51,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: @@ -67,26 +52,10 @@ 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 - - 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/10-assert.yaml.j2 b/tests/templates/kuttl/smoke/10-assert.yaml.j2 index 43b8aa5..88e63a8 100644 --- a/tests/templates/kuttl/smoke/10-assert.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-assert.yaml.j2 @@ -169,11 +169,10 @@ spec: name: listener - mountPath: /stackable/log name: log - - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security - name: security-config - readOnly: true - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls name: tls + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security + name: security-config readOnly: true {% if lookup('env', 'VECTOR_AGGREGATOR') %} - args: @@ -256,16 +255,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: service=opensearch,service=opensearch-nodes-cluster-manager-headless,listener-volume=listener,pod spec: accessModes: - ReadWriteOnce @@ -275,6 +270,10 @@ spec: storageClassName: secrets.stackable.tech volumeMode: Filesystem name: tls + - name: security-config + secret: + defaultMode: 0o660 + secretName: opensearch-security-config volumeClaimTemplates: - apiVersion: v1 kind: PersistentVolumeClaim @@ -481,11 +480,10 @@ spec: name: listener - mountPath: /stackable/log name: log - - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security - name: security-config - readOnly: true - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/tls name: tls + - mountPath: {{ test_scenario['values']['opensearch_home'] }}/config/opensearch-security + name: security-config readOnly: true {% if lookup('env', 'VECTOR_AGGREGATOR') %} - args: @@ -568,16 +566,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: service=opensearch,service=opensearch-nodes-data-headless,listener-volume=listener,pod spec: accessModes: - ReadWriteOnce @@ -587,6 +581,10 @@ spec: storageClassName: secrets.stackable.tech volumeMode: Filesystem name: tls + - name: security-config + secret: + defaultMode: 0o660 + secretName: opensearch-security-config volumeClaimTemplates: - apiVersion: v1 kind: PersistentVolumeClaim @@ -656,13 +654,13 @@ data: 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.http.pemcert_filepath: "${OPENSEARCH_HOME}/config/tls/tls.crt" + plugins.security.ssl.http.pemkey_filepath: "${OPENSEARCH_HOME}/config/tls/tls.key" + plugins.security.ssl.http.pemtrustedcas_filepath: "${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" + plugins.security.ssl.transport.pemcert_filepath: "${OPENSEARCH_HOME}/config/tls/tls.crt" + plugins.security.ssl.transport.pemkey_filepath: "${OPENSEARCH_HOME}/config/tls/tls.key" + plugins.security.ssl.transport.pemtrustedcas_filepath: "${OPENSEARCH_HOME}/config/tls/ca.crt" --- apiVersion: v1 kind: ConfigMap @@ -691,13 +689,13 @@ data: 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.http.pemcert_filepath: "${OPENSEARCH_HOME}/config/tls/tls.crt" + plugins.security.ssl.http.pemkey_filepath: "${OPENSEARCH_HOME}/config/tls/tls.key" + plugins.security.ssl.http.pemtrustedcas_filepath: "${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" + plugins.security.ssl.transport.pemcert_filepath: "${OPENSEARCH_HOME}/config/tls/tls.crt" + plugins.security.ssl.transport.pemkey_filepath: "${OPENSEARCH_HOME}/config/tls/tls.key" + plugins.security.ssl.transport.pemtrustedcas_filepath: "${OPENSEARCH_HOME}/config/tls/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 c717b98..f141954 100644 --- a/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-install-opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -31,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: @@ -52,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) @@ -78,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: @@ -94,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/snapshot-s3/20-install-opensearch.yaml.j2 b/tests/templates/kuttl/snapshot-s3/20-install-opensearch.yaml.j2 index fd33951..5284693 100644 --- a/tests/templates/kuttl/snapshot-s3/20-install-opensearch.yaml.j2 +++ b/tests/templates/kuttl/snapshot-s3/20-install-opensearch.yaml.j2 @@ -12,8 +12,10 @@ spec: productVersion: "{{ test_scenario['values']['opensearch'] }}" {% endif %} pullPolicy: IfNotPresent -{% if lookup('env', 'VECTOR_AGGREGATOR') %} clusterConfig: + tls: + secretClass: tls +{% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} nodes: @@ -35,14 +37,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 s3.client.default.endpoint: https://minio:9000/ s3.client.default.protocol: https s3.client.default.region: unused # but required @@ -100,9 +94,6 @@ spec: mountPath: /etc/pki/java/cacerts subPath: java/cacerts readOnly: true - - name: tls - mountPath: /stackable/opensearch/config/tls - readOnly: true - name: keystore mountPath: /stackable/opensearch/config/opensearch.keystore subPath: opensearch.keystore @@ -126,20 +117,6 @@ spec: - name: system-trust-store emptyDir: sizeLimit: 10Mi - - name: tls - ephemeral: - volumeClaimTemplate: - metadata: - annotations: - secrets.stackable.tech/scope: node,pod,service=opensearch,service=opensearch-nodes-default-headless - secrets.stackable.tech/class: tls - spec: - storageClassName: secrets.stackable.tech - accessModes: - - ReadWriteOnce - resources: - requests: - storage: "1" --- apiVersion: v1 kind: Secret