Skip to content
Open
Show file tree
Hide file tree
Changes from 17 commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d45f8fb
wip: add run-securityadmin job
labrenbe Aug 14, 2025
c489b44
configure tls on run-securityadmin
labrenbe Aug 15, 2025
ffa0585
add tls secret class to crd
labrenbe Aug 19, 2025
aaa560f
Merge remote-tracking branch 'origin/main' into feat/run-securityadmi…
labrenbe Aug 19, 2025
4d7496c
add tls volume to sts
labrenbe Aug 20, 2025
7f805b8
add tls config to opensearch.yml
labrenbe Aug 21, 2025
c6db525
disable security demo install
labrenbe Aug 22, 2025
1c3e672
Merge remote-tracking branch 'origin/main' into feat/run-securityadmi…
labrenbe Oct 24, 2025
bbbdbb6
wip amend integration tests
labrenbe Oct 27, 2025
1aeb4db
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Oct 27, 2025
ab42f5a
fix smoke test
labrenbe Oct 28, 2025
97681a3
restore properties file
labrenbe Oct 28, 2025
815d243
mount tls volume in default directory of official image
labrenbe Oct 28, 2025
95986a5
use tls feature in all integration tests
labrenbe Oct 28, 2025
910a58d
use OPENSEARCH_PATH_CONF for tls config
labrenbe Oct 28, 2025
9252b06
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Oct 28, 2025
beb2aff
fix incorrectly resolved merge conflict
labrenbe Oct 28, 2025
383e358
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Nov 4, 2025
25745d1
wip: adress feedback on pr
labrenbe Nov 6, 2025
bcb5542
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Nov 6, 2025
8257b95
address feedback on PR
labrenbe Nov 13, 2025
bd86ebc
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Nov 13, 2025
36120a8
add changelog entry
labrenbe Nov 13, 2025
0294f91
rename CRD fields based on decision
labrenbe Nov 17, 2025
cb97dee
Merge remote-tracking branch 'origin/main' into feat/tls-support
labrenbe Nov 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion deploy/helm/opensearch-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,24 @@ spec:
generates in the [operator documentation](https://docs.stackable.tech/home/nightly/opensearch/).
properties:
clusterConfig:
default: {}
default:
tls:
secretClass: null
description: Configuration that applies to all roles and role groups
properties:
tls:
properties:
secretClass:
description: |-
Affects client connections and internal transport connections.
This setting controls:
- If TLS encryption is used at all
- Which cert the servers should use to authenticate themselves against the client
maxLength: 223
minLength: 1
nullable: true
type: string
type: object
vectorAggregatorConfigMapName:
description: |-
Name of the Vector aggregator [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery).
Expand All @@ -42,6 +57,8 @@ spec:
minLength: 1
nullable: true
type: string
required:
- tls
type: object
clusterOperation:
default:
Expand Down Expand Up @@ -303,6 +320,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:
Expand Down Expand Up @@ -651,6 +676,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:
Expand Down
16 changes: 14 additions & 2 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use validate::validate;
use crate::{
crd::{
NodeRoles,
v1alpha1::{self},
v1alpha1::{self, OpenSearchClusterConfig},
},
framework::{
ClusterName, ControllerName, HasName, HasUid, ListenerClassName, NameIsValidLabelValue,
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -164,17 +165,20 @@ pub struct ValidatedCluster {
pub name: ClusterName,
pub namespace: NamespaceName,
pub uid: Uid,
pub cluster_config: OpenSearchClusterConfig,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If OpenSearchClusterConfig is extended in the future, then it will probably contain unvalidated entries. If the whole structure is added now to ValidatedCluster, then it will have to be removed again and all the TLS related code has to be refactored.

Additionally, OpenSearchClusterConfig contains vector_aggregator_config_map_name which is already contained in role_group_configs.logging.vector_container.vector_aggregator_config_map_name.

I would directly add the OpenSearchTls structure here.

pub role_config: GenericRoleConfig,
pub role_group_configs: BTreeMap<RoleGroupName, OpenSearchRoleGroupConfig>,
}

impl ValidatedCluster {
#[allow(clippy::too_many_arguments)]
pub fn new(
image: ResolvedProductImage,
product_version: ProductVersion,
name: ClusterName,
namespace: NamespaceName,
uid: impl Into<Uid>,
cluster_config: OpenSearchClusterConfig,
role_config: GenericRoleConfig,
role_group_configs: BTreeMap<RoleGroupName, OpenSearchRoleGroupConfig>,
) -> Self {
Expand All @@ -191,6 +195,7 @@ impl ValidatedCluster {
name,
namespace,
uid,
cluster_config,
role_config,
role_group_configs,
}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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(),
[
(
Expand Down Expand Up @@ -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,
},
Expand Down
9 changes: 8 additions & 1 deletion rust/operator-binary/src/controller/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ mod tests {
kvp::LabelValue,
product_logging::spec::AutomaticContainerLogConfig,
role_utils::GenericRoleConfig,
shared::time::Duration,
};
use uuid::uuid;

Expand All @@ -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,
Expand Down Expand Up @@ -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(),
[
(
Expand Down Expand Up @@ -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,
},
Expand Down
122 changes: 106 additions & 16 deletions rust/operator-binary/src/controller/build/node_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,41 @@ 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,
Expand All @@ -81,8 +116,31 @@ 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<String, Value> {
let mut config = self.static_opensearch_config();

if self.cluster.cluster_config.tls.secret_class.is_some() {
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
Expand Down Expand Up @@ -112,25 +170,53 @@ 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<String, Value> {
let mut config = serde_json::Map::new();

// TLS config for HTTP port
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/tls.crt".to_string()),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the value of $OPENSEARCH_PATH_CONF is correct in my opinion but if it is not set, the value of $OPENSEARCH_HOME/config is the correct one.

Currently, this exception is thrown in the integration tests:

Exception in thread "main" java.lang.IllegalArgumentException: Could not resolve placeholder 'OPENSEARCH_PATH_CONF'

The resolution of the actual path is already done in

// 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"));
, because there it is used for VolumeMounts which cannot depend on environment variables.

But if the location is decided in the volume mount then it is wrong to depend on an environment variable here. Therefore, it would be better to move the mentioned code to this module. The code depends anyway only on the environment variables in this module. Then the resolved path can be used here.

);
config.insert(
CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMKEY_FILEPATH.to_owned(),
json!("${OPENSEARCH_PATH_CONF}/tls/tls.key".to_string()),
);
config.insert(
CONFIG_OPTION_PLUGINS_SECURITY_SSL_HTTP_PEMTRUSTEDCAS_FILEPATH.to_owned(),
json!("${OPENSEARCH_PATH_CONF}/tls/ca.crt".to_string()),
);
// TLS config for TRANSPORT port
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/tls.crt".to_string()),
);
config.insert(
CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMKEY_FILEPATH.to_owned(),
json!("${OPENSEARCH_PATH_CONF}/tls/tls.key".to_string()),
);
config.insert(
CONFIG_OPTION_PLUGINS_SECURITY_SSL_TRANSPORT_PEMTRUSTEDCAS_FILEPATH.to_owned(),
json!("${OPENSEARCH_PATH_CONF}/tls/ca.crt".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)
Expand Down Expand Up @@ -277,13 +363,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,
Expand Down Expand Up @@ -328,6 +415,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,
},
Expand Down Expand Up @@ -364,6 +453,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"),
Expand Down Expand Up @@ -395,7 +485,7 @@ mod tests {
"test: \"value\""
)
.to_owned(),
node_config.static_opensearch_config_file_content()
node_config.opensearch_config_file_content()
);
}

Expand Down
Loading