diff --git a/CHANGELOG.md b/CHANGELOG.md index 01de4f75..42dd2e22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file. - Helm: Allow Pod `priorityClassName` to be configured ([#762]). - Add support for OPA `1.8.0` ([#765]). - Add `prometheus.io/path|port|scheme` annotations to metrics service ([#767]). +- Add support for TLS ([#774]) ### Changed @@ -41,6 +42,7 @@ All notable changes to this project will be documented in this file. [#767]: https://github.com/stackabletech/opa-operator/pull/767 [#771]: https://github.com/stackabletech/opa-operator/pull/771 [#772]: https://github.com/stackabletech/opa-operator/pull/772 +[#774]: https://github.com/stackabletech/opa-operator/pull/774 ## [25.7.0] - 2025-07-23 diff --git a/deploy/helm/opa-operator/crds/crds.yaml b/deploy/helm/opa-operator/crds/crds.yaml index 13deb8b5..7e2b9c38 100644 --- a/deploy/helm/opa-operator/crds/crds.yaml +++ b/deploy/helm/opa-operator/crds/crds.yaml @@ -27,6 +27,7 @@ spec: clusterConfig: default: listenerClass: cluster-internal + tls: null userInfo: null description: Global OPA cluster configuration that applies to all roles and role groups. properties: @@ -49,6 +50,19 @@ spec: - external-unstable - external-stable type: string + tls: + description: |- + TLS encryption settings for the OPA server. + When configured, OPA will use HTTPS (port 8443) instead of HTTP (port 8081). + Clients must connect using HTTPS and trust the certificates provided by the configured SecretClass. + nullable: true + properties: + serverSecretClass: + description: Name of the SecretClass which will provide TLS certificates for the OPA server. + type: string + required: + - serverSecretClass + type: object userInfo: description: |- Configures how to fetch additional metadata about users (such as group memberships) diff --git a/docs/modules/opa/pages/usage-guide/tls.adoc b/docs/modules/opa/pages/usage-guide/tls.adoc new file mode 100644 index 00000000..14dcaa88 --- /dev/null +++ b/docs/modules/opa/pages/usage-guide/tls.adoc @@ -0,0 +1,71 @@ += Enabling TLS Encryption +:description: Learn how to enable TLS encryption for your OPA cluster to secure client connections. + +TLS encryption for securing client connections to the OPA server can be configured in the `OpaCluster` resource. When enabled, OPA serves requests over HTTPS instead of HTTP. + +== Overview + +TLS encryption in OPA is disabled by default. To enable it, you need to: + +1. Create a `SecretClass` that provides TLS certificates +2. Reference the `SecretClass` in your `OpaCluster` custom resource + +The operator integrates with the xref:secret-operator:index.adoc[Secret Operator] to automatically provision and mount TLS certificates into the OPA pods. + +== Configuration + +=== Creating a SecretClass + +First, create a `SecretClass` that will provide TLS certificates. Here's an example using xref:secret-operator:secretclass.adoc#backend-autotls[autoTls]: + +[source,yaml] +---- +apiVersion: secrets.stackable.tech/v1alpha1 +kind: SecretClass +metadata: + name: opa-tls +spec: + backend: + autoTls: + ca: + autoGenerate: true + secret: + name: opa-tls-ca + namespace: default +---- + +This SecretClass uses the autoTls backend, which automatically generates a Certificate Authority (CA) and signs certificates for your OPA cluster. + +Similarly, you can also use xref:secret-operator:secretclass.adoc#backend[other backends] supported by Secret Operator. + +=== Enabling TLS in OpaCluster + +Once you have a SecretClass, enable TLS in your OpaCluster by setting the `.spec.clusterConfig.tls.serverSecretClass` field: + +[source,yaml] +---- +kind: OpaCluster +name: opa-with-tls +spec: + clusterConfig: + tls: + serverSecretClass: opa-tls # <1> +---- +<1> Reference the SecretClass created above + +== Discovery ConfigMap + +The operator automatically creates a discovery ConfigMap, with the same name as the OPA cluster, that contains the connection URL for your cluster. When TLS is enabled, this ConfigMap will contain an HTTPS URL and the SecretClass name: + +[source,yaml] +---- +apiVersion: v1 +kind: ConfigMap +metadata: + name: opa-with-tls +data: + OPA: "https://opa-with-tls.default.svc.cluster.local:8443/" + OPA_SECRET_CLASS: "opa-tls" +---- + +Applications can use this ConfigMap to discover and connect to the OPA cluster automatically. diff --git a/docs/modules/opa/partials/nav.adoc b/docs/modules/opa/partials/nav.adoc index f354d583..2f7dd58c 100644 --- a/docs/modules/opa/partials/nav.adoc +++ b/docs/modules/opa/partials/nav.adoc @@ -10,6 +10,7 @@ ** xref:opa:usage-guide/monitoring.adoc[] ** xref:opa:usage-guide/OpenTelemetry.adoc[] ** xref:opa:usage-guide/configuration-environment-overrides.adoc[] +** xref:opa:usage-guide/tls.adoc[] ** xref:opa:usage-guide/operations/index.adoc[] *** xref:opa:usage-guide/operations/cluster-operations.adoc[] // *** xref:hdfs:usage-guide/operations/pod-placement.adoc[] Missing diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index c092e924..b5a6e934 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -23,7 +23,7 @@ use stackable_operator::{ container::{ContainerBuilder, FieldPathEnvVar}, resources::ResourceRequirementsBuilder, security::PodSecurityContextBuilder, - volume::VolumeBuilder, + volume::{SecretOperatorVolumeSourceBuilder, VolumeBuilder}, }, }, cluster_resources::{ClusterResourceApplyStrategy, ClusterResources}, @@ -104,6 +104,8 @@ const USER_INFO_FETCHER_CREDENTIALS_VOLUME_NAME: &str = "credentials"; const USER_INFO_FETCHER_CREDENTIALS_DIR: &str = "/stackable/credentials"; const USER_INFO_FETCHER_KERBEROS_VOLUME_NAME: &str = "kerberos"; const USER_INFO_FETCHER_KERBEROS_DIR: &str = "/stackable/kerberos"; +const TLS_VOLUME_NAME: &str = "tls"; +const TLS_STORE_DIR: &str = "/stackable/tls"; const DOCKER_IMAGE_BASE_NAME: &str = "opa"; @@ -329,6 +331,11 @@ pub enum Error { #[snafu(display("failed to build service"))] BuildService { source: service::Error }, + + #[snafu(display("failed to build TLS volume"))] + TlsVolumeBuild { + source: builder::pod::volume::SecretOperatorVolumeSourceBuilderError, + }, } type Result = std::result::Result; @@ -835,29 +842,50 @@ fn build_server_rolegroup_daemonset( .args(vec![build_opa_start_command( merged_config, &opa_container_name, + opa.spec.cluster_config.tls_enabled(), )]) .add_env_vars(env) .add_env_var( "CONTAINERDEBUG_LOG_DIRECTORY", format!("{STACKABLE_LOG_DIR}/containerdebug"), - ) - .add_container_port(APP_PORT_NAME, APP_PORT.into()) - // If we also add a container port "metrics" pointing to the same port number, we get a - // - // .spec.template.spec.containers[name="opa"].ports: duplicate entries for key [containerPort=8081,protocol="TCP"] - // - // So we don't do that + ); + + // Add appropriate container port based on TLS configuration + // If we also add a container port "metrics" pointing to the same port number, we get a + // + // .spec.template.spec.containers[name="opa"].ports: duplicate entries for key [containerPort=8081,protocol="TCP"] + // + // So we don't do that + if opa.spec.cluster_config.tls_enabled() { + cb_opa.add_container_port(service::APP_TLS_PORT_NAME, service::APP_TLS_PORT.into()); + cb_opa + .add_volume_mount(TLS_VOLUME_NAME, TLS_STORE_DIR) + .context(AddVolumeMountSnafu)?; + } else { + cb_opa.add_container_port(APP_PORT_NAME, APP_PORT.into()); + } + + cb_opa .add_volume_mount(CONFIG_VOLUME_NAME, CONFIG_DIR) .context(AddVolumeMountSnafu)? .add_volume_mount(LOG_VOLUME_NAME, STACKABLE_LOG_DIR) .context(AddVolumeMountSnafu)? - .resources(merged_config.resources.to_owned().into()) + .resources(merged_config.resources.to_owned().into()); + + let (probe_port_name, probe_scheme) = if opa.spec.cluster_config.tls_enabled() { + (service::APP_TLS_PORT_NAME, Some("HTTPS".to_string())) + } else { + (APP_PORT_NAME, Some("HTTP".to_string())) + }; + + cb_opa .readiness_probe(Probe { initial_delay_seconds: Some(5), period_seconds: Some(10), failure_threshold: Some(5), http_get: Some(HTTPGetAction { - port: IntOrString::String(APP_PORT_NAME.to_string()), + port: IntOrString::String(probe_port_name.to_string()), + scheme: probe_scheme.clone(), ..HTTPGetAction::default() }), ..Probe::default() @@ -866,7 +894,8 @@ fn build_server_rolegroup_daemonset( initial_delay_seconds: Some(30), period_seconds: Some(10), http_get: Some(HTTPGetAction { - port: IntOrString::String(APP_PORT_NAME.to_string()), + port: IntOrString::String(probe_port_name.to_string()), + scheme: probe_scheme, ..HTTPGetAction::default() }), ..Probe::default() @@ -918,6 +947,22 @@ fn build_server_rolegroup_daemonset( .service_account_name(service_account.name_any()) .security_context(PodSecurityContextBuilder::new().fs_group(1000).build()); + if let Some(tls) = &opa.spec.cluster_config.tls { + pb.add_volume( + VolumeBuilder::new(TLS_VOLUME_NAME) + .ephemeral( + SecretOperatorVolumeSourceBuilder::new(&tls.server_secret_class) + .with_service_scope(opa.server_role_service_name()) + .with_service_scope(rolegroup_ref.rolegroup_headless_service_name()) + .with_service_scope(rolegroup_ref.rolegroup_metrics_service_name()) + .build() + .context(TlsVolumeBuildSnafu)?, + ) + .build(), + ) + .context(AddVolumeSnafu)?; + } + if let Some(user_info) = &opa.spec.cluster_config.user_info { let mut cb_user_info_fetcher = ContainerBuilder::new("user-info-fetcher").context(IllegalContainerNameSnafu)?; @@ -1146,7 +1191,11 @@ fn build_config_file(merged_config: &v1alpha1::OpaConfig) -> String { serde_json::to_string_pretty(&json!(config)).unwrap() } -fn build_opa_start_command(merged_config: &v1alpha1::OpaConfig, container_name: &str) -> String { +fn build_opa_start_command( + merged_config: &v1alpha1::OpaConfig, + container_name: &str, + tls_enabled: bool, +) -> String { let mut file_log_level = DEFAULT_FILE_LOG_LEVEL; let mut console_log_level = DEFAULT_CONSOLE_LOG_LEVEL; let mut server_log_level = DEFAULT_SERVER_LOG_LEVEL; @@ -1187,6 +1236,17 @@ fn build_opa_start_command(merged_config: &v1alpha1::OpaConfig, container_name: } } + let (bind_port, tls_flags) = if tls_enabled { + ( + service::APP_TLS_PORT, + format!( + "--tls-cert-file {TLS_STORE_DIR}/tls.crt --tls-private-key-file {TLS_STORE_DIR}/tls.key" + ), + ) + } else { + (APP_PORT, String::new()) + }; + // Redirects matter! // We need to watch out, that the following "$!" call returns the PID of the main (opa-bundle-builder) process, // and not some utility (e.g. multilog or tee) process. @@ -1202,7 +1262,7 @@ fn build_opa_start_command(merged_config: &v1alpha1::OpaConfig, container_name: {remove_vector_shutdown_file_command} prepare_signal_handlers containerdebug --output={STACKABLE_LOG_DIR}/containerdebug-state.json --loop & - opa run -s -a 0.0.0.0:{APP_PORT} -c {CONFIG_DIR}/{CONFIG_FILE} -l {opa_log_level} --shutdown-grace-period {shutdown_grace_period_s} --disable-telemetry {logging_redirects} & + opa run -s -a 0.0.0.0:{bind_port} -c {CONFIG_DIR}/{CONFIG_FILE} -l {opa_log_level} --shutdown-grace-period {shutdown_grace_period_s} --disable-telemetry {tls_flags} {logging_redirects} & wait_for_termination $! {create_vector_shutdown_file_command} ", diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 0cd9951f..8b782a20 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -115,6 +115,18 @@ pub mod versioned { /// from an external directory service. #[serde(default)] pub user_info: Option, + /// TLS encryption settings for the OPA server. + /// When configured, OPA will use HTTPS (port 8443) instead of HTTP (port 8081). + /// Clients must connect using HTTPS and trust the certificates provided by the configured SecretClass. + #[serde(default)] + pub tls: Option, + } + + #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] + #[serde(rename_all = "camelCase")] + pub struct OpaTls { + /// Name of the SecretClass which will provide TLS certificates for the OPA server. + pub server_secret_class: String, } // TODO: Temporary solution until listener-operator is finished @@ -239,6 +251,13 @@ impl v1alpha1::CurrentlySupportedListenerClasses { } } +impl v1alpha1::OpaClusterConfig { + /// Returns whether TLS encryption is enabled for the OPA server. + pub fn tls_enabled(&self) -> bool { + self.tls.is_some() + } +} + impl v1alpha1::OpaConfig { fn default_config() -> v1alpha1::OpaConfigFragment { v1alpha1::OpaConfigFragment { diff --git a/rust/operator-binary/src/discovery.rs b/rust/operator-binary/src/discovery.rs index f9c1023b..3870c03c 100644 --- a/rust/operator-binary/src/discovery.rs +++ b/rust/operator-binary/src/discovery.rs @@ -8,7 +8,10 @@ use stackable_operator::{ utils::cluster_info::KubernetesClusterInfo, }; -use crate::{controller::build_recommended_labels, service::APP_PORT}; +use crate::{ + controller::build_recommended_labels, + service::{APP_PORT, APP_TLS_PORT}, +}; #[derive(Snafu, Debug)] pub enum Error { @@ -63,8 +66,14 @@ fn build_discovery_configmap( svc: &Service, cluster_info: &KubernetesClusterInfo, ) -> Result { + let (scheme, port) = if opa.spec.cluster_config.tls_enabled() { + ("https", APP_TLS_PORT) + } else { + ("http", APP_PORT) + }; + let url = format!( - "http://{name}.{namespace}.svc.{cluster_domain}:{port}/", + "{scheme}://{name}.{namespace}.svc.{cluster_domain}:{port}/", name = svc.metadata.name.as_deref().context(NoNameSnafu)?, namespace = svc .metadata @@ -72,7 +81,6 @@ fn build_discovery_configmap( .as_deref() .context(NoNamespaceSnafu)?, cluster_domain = cluster_info.cluster_domain, - port = APP_PORT ); let metadata = ObjectMetaBuilder::new() @@ -91,9 +99,13 @@ fn build_discovery_configmap( .context(ObjectMetaSnafu)? .build(); - ConfigMapBuilder::new() - .metadata(metadata) - .add_data("OPA", url) - .build() - .context(BuildConfigMapSnafu) + let mut cm_builder = ConfigMapBuilder::new(); + + cm_builder.metadata(metadata).add_data("OPA", url); + + if let Some(tls) = opa.spec.cluster_config.tls.as_ref() { + cm_builder.add_data("OPA_SECRET_CLASS", &tls.server_secret_class); + } + + cm_builder.build().context(BuildConfigMapSnafu) } diff --git a/rust/operator-binary/src/service.rs b/rust/operator-binary/src/service.rs index 6fb58f09..72cdd6ef 100644 --- a/rust/operator-binary/src/service.rs +++ b/rust/operator-binary/src/service.rs @@ -11,7 +11,9 @@ use stackable_operator::{ use crate::controller::build_recommended_labels; pub const APP_PORT: u16 = 8081; +pub const APP_TLS_PORT: u16 = 8443; pub const APP_PORT_NAME: &str = "http"; +pub const APP_TLS_PORT_NAME: &str = "https"; pub const METRICS_PORT_NAME: &str = "metrics"; #[derive(Snafu, Debug)] @@ -57,7 +59,7 @@ pub(crate) fn build_server_role_service( let service_spec = ServiceSpec { type_: Some(opa.spec.cluster_config.listener_class.k8s_service_type()), - ports: Some(data_service_ports()), + ports: Some(data_service_ports(opa.spec.cluster_config.tls_enabled())), selector: Some(service_selector_labels.into()), internal_traffic_policy: Some("Local".to_string()), ..ServiceSpec::default() @@ -102,7 +104,7 @@ pub(crate) fn build_rolegroup_headless_service( // options there are non-existent (mTLS still opens plain port) or suck (Kerberos). type_: Some("ClusterIP".to_string()), cluster_ip: Some("None".to_string()), - ports: Some(data_service_ports()), + ports: Some(data_service_ports(opa.spec.cluster_config.tls_enabled())), selector: Some(role_group_selector_labels(opa, rolegroup)?.into()), publish_not_ready_addresses: Some(true), ..ServiceSpec::default() @@ -135,13 +137,17 @@ pub(crate) fn build_rolegroup_metrics_service( )) .context(ObjectMetaSnafu)? .with_labels(prometheus_labels()) - .with_annotations(prometheus_annotations()) + .with_annotations(prometheus_annotations( + opa.spec.cluster_config.tls_enabled(), + )) .build(); let service_spec = ServiceSpec { type_: Some("ClusterIP".to_string()), cluster_ip: Some("None".to_string()), - ports: Some(vec![metrics_service_port()]), + ports: Some(vec![metrics_service_port( + opa.spec.cluster_config.tls_enabled(), + )]), selector: Some(role_group_selector_labels(opa, rolegroup)?.into()), ..ServiceSpec::default() }; @@ -162,21 +168,28 @@ fn role_group_selector_labels( .context(BuildLabelSnafu) } -fn data_service_ports() -> Vec { - // Currently only HTTP is exposed +fn data_service_ports(tls_enabled: bool) -> Vec { + let (port_name, port) = if tls_enabled { + (APP_TLS_PORT_NAME, APP_TLS_PORT) + } else { + (APP_PORT_NAME, APP_PORT) + }; + vec![ServicePort { - name: Some(APP_PORT_NAME.to_string()), - port: APP_PORT.into(), + name: Some(port_name.to_string()), + port: port.into(), protocol: Some("TCP".to_string()), ..ServicePort::default() }] } -fn metrics_service_port() -> ServicePort { +fn metrics_service_port(tls_enabled: bool) -> ServicePort { + let port = if tls_enabled { APP_TLS_PORT } else { APP_PORT }; + ServicePort { name: Some(METRICS_PORT_NAME.to_string()), - // The metrics are served on the same port as the HTTP traffic - port: APP_PORT.into(), + // The metrics are served on the same port as the HTTP/HTTPS traffic + port: port.into(), protocol: Some("TCP".to_string()), ..ServicePort::default() } @@ -192,11 +205,17 @@ fn prometheus_labels() -> Labels { /// These annotations can be used in a ServiceMonitor. /// /// see also -fn prometheus_annotations() -> Annotations { +fn prometheus_annotations(tls_enabled: bool) -> Annotations { + let (port, scheme) = if tls_enabled { + (APP_TLS_PORT, "https") + } else { + (APP_PORT, "http") + }; + Annotations::try_from([ ("prometheus.io/path".to_owned(), "/metrics".to_owned()), - ("prometheus.io/port".to_owned(), APP_PORT.to_string()), - ("prometheus.io/scheme".to_owned(), "http".to_owned()), + ("prometheus.io/port".to_owned(), port.to_string()), + ("prometheus.io/scheme".to_owned(), scheme.to_owned()), ("prometheus.io/scrape".to_owned(), "true".to_owned()), ]) .expect("should be valid annotations") diff --git a/tests/templates/kuttl/aas-user-info/test-regorule.py b/tests/templates/kuttl/aas-user-info/test-regorule.py index e95d6070..b05dba1d 100755 --- a/tests/templates/kuttl/aas-user-info/test-regorule.py +++ b/tests/templates/kuttl/aas-user-info/test-regorule.py @@ -26,9 +26,9 @@ def assertions( # todo: split out customAttribute assertions print(f"Testing for {username} with customAttributes {expected_attributes}") custom_attributes = result[opa_attribute]["customAttributes"] - assert ( - custom_attributes == expected_attributes - ), f"got {custom_attributes}, expected: {expected_attributes}" + assert custom_attributes == expected_attributes, ( + f"got {custom_attributes}, expected: {expected_attributes}" + ) if __name__ == "__main__": @@ -40,9 +40,9 @@ def assertions( def make_request(payload): response = requests.post(args["url"], data=json.dumps(payload), params=params) expected_status_code = 200 - assert ( - response.status_code == expected_status_code - ), f"got {response.status_code}, expected: {expected_status_code}" + assert response.status_code == expected_status_code, ( + f"got {response.status_code}, expected: {expected_status_code}" + ) return response.json() for subject_id in ["alice", "bob"]: diff --git a/tests/templates/kuttl/keycloak-user-info/test-regorule.py b/tests/templates/kuttl/keycloak-user-info/test-regorule.py index d8f3e20b..cac2b7fa 100755 --- a/tests/templates/kuttl/keycloak-user-info/test-regorule.py +++ b/tests/templates/kuttl/keycloak-user-info/test-regorule.py @@ -32,9 +32,9 @@ def assertions( # todo: split out customAttribute assertions print(f"Testing for {username} with customAttributes {expected_attributes}") custom_attributes = result[opa_attribute]["customAttributes"] - assert ( - custom_attributes == expected_attributes - ), f"got {custom_attributes}, expected: {expected_attributes}" + assert custom_attributes == expected_attributes, ( + f"got {custom_attributes}, expected: {expected_attributes}" + ) if __name__ == "__main__": @@ -46,9 +46,9 @@ def assertions( def make_request(payload): response = requests.post(args["url"], data=json.dumps(payload), params=params) expected_status_code = 200 - assert ( - response.status_code == expected_status_code - ), f"got {response.status_code}, expected: {expected_status_code}" + assert response.status_code == expected_status_code, ( + f"got {response.status_code}, expected: {expected_status_code}" + ) return response.json() for username, groups in users_and_groups.items(): diff --git a/tests/templates/kuttl/smoke/09-install-secretclass.yaml.j2 b/tests/templates/kuttl/smoke/09-install-secretclass.yaml.j2 new file mode 100644 index 00000000..6e23dddb --- /dev/null +++ b/tests/templates/kuttl/smoke/09-install-secretclass.yaml.j2 @@ -0,0 +1,22 @@ +{% if test_scenario['values']['use-tls'] == "true" %} +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - << EOF + --- + apiVersion: secrets.stackable.tech/v1alpha1 + kind: SecretClass + metadata: + name: opa-tls-$NAMESPACE + spec: + backend: + autoTls: + ca: + autoGenerate: true + secret: + name: opa-tls-ca + namespace: $NAMESPACE + EOF +{% endif %} diff --git a/tests/templates/kuttl/smoke/10-install-opa.yaml.j2 b/tests/templates/kuttl/smoke/10-install-opa.yaml.j2 index 25d5aa57..6a77339e 100644 --- a/tests/templates/kuttl/smoke/10-install-opa.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-install-opa.yaml.j2 @@ -1,46 +1,57 @@ --- -apiVersion: v1 -kind: ConfigMap -metadata: - name: test - labels: - opa.stackable.tech/bundle: "true" -data: - test.rego: | - package test +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - < 0 %} - custom: "{{ test_scenario['values']['opa'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['opa'].split(',')[0] }}" + custom: "{{ test_scenario['values']['opa'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['opa'].split(',')[0] }}" {% else %} - productVersion: "{{ test_scenario['values']['opa'] }}" + productVersion: "{{ test_scenario['values']['opa'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: +{% if test_scenario['values']['use-tls'] == "true" %} + tls: + serverSecretClass: opa-tls-$NAMESPACE {% endif %} - pullPolicy: IfNotPresent {% if lookup('env', 'VECTOR_AGGREGATOR') %} - clusterConfig: - vectorAggregatorConfigMapName: vector-aggregator-discovery + vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} - servers: - config: - logging: - enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - envOverrides: - SERVER_ROLE_LEVEL_ENV_VAR: "SERVER_ROLE_LEVEL_ENV_VAR" - roleGroups: - default: - envOverrides: - SERVER_ROLE_GROUP_LEVEL_ENV_VAR: "SERVER_ROLE_GROUP_LEVEL_ENV_VAR" + servers: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} + envOverrides: + SERVER_ROLE_LEVEL_ENV_VAR: "SERVER_ROLE_LEVEL_ENV_VAR" + roleGroups: + default: + envOverrides: + SERVER_ROLE_GROUP_LEVEL_ENV_VAR: "SERVER_ROLE_GROUP_LEVEL_ENV_VAR" + EOF diff --git a/tests/templates/kuttl/smoke/20-install-test-opa.yaml b/tests/templates/kuttl/smoke/20-install-test-opa.yaml deleted file mode 100644 index 05d33126..00000000 --- a/tests/templates/kuttl/smoke/20-install-test-opa.yaml +++ /dev/null @@ -1,29 +0,0 @@ ---- -apiVersion: apps/v1 -kind: StatefulSet -metadata: - name: test-opa - labels: - app: test-opa -spec: - replicas: 1 - selector: - matchLabels: - app: test-opa - template: - metadata: - labels: - app: test-opa - spec: - containers: - - name: test-opa - image: oci.stackable.tech/sdp/testing-tools:0.2.0-stackable0.0.0-dev - stdin: true - tty: true - resources: - requests: - memory: "128Mi" - cpu: "512m" - limits: - memory: "128Mi" - cpu: "1" diff --git a/tests/templates/kuttl/smoke/20-install-test-opa.yaml.j2 b/tests/templates/kuttl/smoke/20-install-test-opa.yaml.j2 new file mode 100644 index 00000000..0af56858 --- /dev/null +++ b/tests/templates/kuttl/smoke/20-install-test-opa.yaml.j2 @@ -0,0 +1,56 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - <:/v1/data//()+ - # In our case http://:8081/v1/data/test + # We need to query: https://:/v1/data//()+ + # In our case https://:8443/v1/data/test # --> {'result': {'hello': True}} - # or http://:8081/v1/data/test/hello + # or https://:8443/v1/data/test/hello # --> {'hello': True} + # For HTTP: http://:8081/v1/data/test - # url = 'http://test-opa-svc:8081/v1/data/test' - response = requests.post(args["url"], json={"input": {}}).json() + # Determine verification setting based on whether TLS is used + if args["url"].startswith("http://"): + verify = False + protocol = "HTTP" + else: + verify = "/tls/ca.crt" + protocol = "HTTPS" + + response = requests.post(args["url"], json={"input": {}}, verify=verify).json() if ( "result" in response and "hello" in response["result"] and response["result"]["hello"] ): - print("Regorule test successful!") + print(f"Regorule test ({protocol}) successful!") exit(0) else: print( - "Error: received " + f"Error ({protocol}): received " + str(response) + " - expected: {'result': {'hello': True}}" ) diff --git a/tests/templates/kuttl/smoke/31-assert.yaml b/tests/templates/kuttl/smoke/31-assert.yaml deleted file mode 100644 index 0084e371..00000000 --- a/tests/templates/kuttl/smoke/31-assert.yaml +++ /dev/null @@ -1,7 +0,0 @@ ---- -apiVersion: kuttl.dev/v1beta1 -kind: TestAssert -metadata: - name: test-metrics -commands: - - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-metrics.py diff --git a/tests/templates/kuttl/smoke/31-assert.yaml.j2 b/tests/templates/kuttl/smoke/31-assert.yaml.j2 new file mode 100644 index 00000000..dd2d28cc --- /dev/null +++ b/tests/templates/kuttl/smoke/31-assert.yaml.j2 @@ -0,0 +1,11 @@ +--- +apiVersion: kuttl.dev/v1beta1 +kind: TestAssert +metadata: + name: test-metrics +commands: +{% if test_scenario['values']['use-tls'] == "true" %} + - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-metrics.py -u "https://test-opa-server-default-metrics.$NAMESPACE.svc.cluster.local:8443/metrics" +{% else %} + - script: kubectl exec -n $NAMESPACE test-opa-0 -- python /tmp/30_test-metrics.py -u "http://test-opa-server-default-metrics.$NAMESPACE.svc.cluster.local:8081/metrics" +{% endif %} diff --git a/tests/test-definition.yaml b/tests/test-definition.yaml index 775385a7..b43f9a39 100644 --- a/tests/test-definition.yaml +++ b/tests/test-definition.yaml @@ -17,11 +17,16 @@ dimensions: - name: openshift values: - "false" + - name: use-tls + values: + - "true" + - "false" tests: - name: smoke dimensions: - opa - openshift + - use-tls - name: resources dimensions: - opa-latest