Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ All notable changes to this project will be documented in this file.
### Fixed

- Also listen on the loopback interface so that k8s port-forwards work ([#870]).
- Don't ignore the configured `.spec.clusterConfig.authorization.opa.package`, but pass it into the NiFi config instead ([#881]).

[#870]: https://github.com/stackabletech/nifi-operator/pull/870
[#881]: https://github.com/stackabletech/nifi-operator/pull/881

## [25.11.0] - 2025-11-07

Expand Down
8 changes: 4 additions & 4 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -452,7 +452,7 @@ pub async fn reconcile_nifi(
}

let authorization_config = NifiAuthorizationConfig::from(
&nifi.spec.cluster_config.authorization,
nifi.spec.cluster_config.authorization.as_ref(),
client,
nifi.metadata
.namespace
Expand Down Expand Up @@ -716,7 +716,7 @@ async fn build_node_rolegroup_config_map(
nifi: &v1alpha1::NifiCluster,
resolved_product_image: &ResolvedProductImage,
authentication_config: &NifiAuthenticationConfig,
authorization_config: &NifiAuthorizationConfig,
authorization_config: &NifiAuthorizationConfig<'_>,
role: &Role<NifiConfigFragment, NifiNodeRoleConfig, JavaCommonConfig>,
rolegroup: &RoleGroupRef<v1alpha1::NifiCluster>,
rolegroup_config: &HashMap<PropertyNameKind, BTreeMap<String, String>>,
Expand All @@ -731,7 +731,7 @@ async fn build_node_rolegroup_config_map(
.context(InvalidNifiAuthenticationConfigSnafu)?;

let authorizers_xml = authorization_config
.get_authorizers_config(authentication_config)
.get_authorizers_config(nifi, authentication_config)
.context(InvalidNifiAuthorizationConfigSnafu)?;

let jvm_sec_props: BTreeMap<String, Option<String>> = rolegroup_config
Expand Down Expand Up @@ -845,7 +845,7 @@ async fn build_node_rolegroup_statefulset(
rolegroup_config: &HashMap<PropertyNameKind, BTreeMap<String, String>>,
merged_config: &NifiConfig,
authentication_config: &NifiAuthenticationConfig,
authorization_config: &NifiAuthorizationConfig,
authorization_config: &NifiAuthorizationConfig<'_>,
rolling_update_supported: bool,
replicas: Option<i32>,
service_account_name: &str,
Expand Down
79 changes: 41 additions & 38 deletions rust/operator-binary/src/security/authorization.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ use indoc::{formatdoc, indoc};
use snafu::{OptionExt, ResultExt, Snafu};
use stackable_operator::{
client::Client,
commons::opa::OpaConfig,
crd::authentication::ldap,
k8s_openapi::api::core::v1::{ConfigMap, ConfigMapKeySelector, EnvVar, EnvVarSource},
kube::ResourceExt,
};

use super::authentication::NifiAuthenticationConfig;
use crate::crd::NifiAuthorization;
use crate::crd::{NifiAuthorization, v1alpha1};

pub const OPA_TLS_VOLUME_NAME: &str = "opa-tls";
pub const OPA_TLS_MOUNT_PATH: &str = "/stackable/opa_tls";
Expand All @@ -27,58 +29,51 @@ pub enum Error {
},
}

pub enum NifiAuthorizationConfig {
pub enum NifiAuthorizationConfig<'a> {
Opa {
configmap_name: String,
config: &'a OpaConfig,
cache_entry_time_to_live_secs: u64,
cache_max_entries: u32,
secret_class: Option<String>,
},
Default,
}

impl NifiAuthorizationConfig {
impl<'a> NifiAuthorizationConfig<'a> {
pub async fn from(
nifi_authorization: &Option<NifiAuthorization>,
nifi_authorization: Option<&'a NifiAuthorization>,
client: &Client,
namespace: &str,
) -> Result<Self, Error> {
let config = match nifi_authorization {
Some(authorization_config) => match authorization_config.opa.clone() {
Some(opa_config) => {
let configmap_name = opa_config.opa.config_map_name.clone();

// Resolve the secret class from the ConfigMap
let secret_class = client
.get::<ConfigMap>(&configmap_name, namespace)
.await
.with_context(|_| FetchOpaConfigMapSnafu {
configmap_name: configmap_name.clone(),
namespace: namespace.to_string(),
})?
.data
.and_then(|mut data| data.remove("OPA_SECRET_CLASS"));

NifiAuthorizationConfig::Opa {
configmap_name,
cache_entry_time_to_live_secs: opa_config
.cache
.entry_time_to_live
.as_secs(),
cache_max_entries: opa_config.cache.max_entries,
secret_class,
}
}
None => NifiAuthorizationConfig::Default,
},
None => NifiAuthorizationConfig::Default,
let Some(NifiAuthorization {
opa: Some(opa_config),
}) = nifi_authorization
else {
return Ok(NifiAuthorizationConfig::Default);
};

Ok(config)
// Resolve the secret class from the ConfigMap
let secret_class = client
.get::<ConfigMap>(&opa_config.opa.config_map_name, namespace)
.await
.with_context(|_| FetchOpaConfigMapSnafu {
configmap_name: &opa_config.opa.config_map_name,
namespace,
})?
.data
.and_then(|mut data| data.remove("OPA_SECRET_CLASS"));

Ok(NifiAuthorizationConfig::Opa {
config: &opa_config.opa,
cache_entry_time_to_live_secs: opa_config.cache.entry_time_to_live.as_secs(),
cache_max_entries: opa_config.cache.max_entries,
secret_class,
})
}

pub fn get_authorizers_config(
&self,
nifi_cluster: &v1alpha1::NifiCluster,
authentication_config: &NifiAuthenticationConfig,
) -> Result<String, Error> {
let mut authorizers_xml = indoc! {r#"
Expand All @@ -91,16 +86,19 @@ impl NifiAuthorizationConfig {
NifiAuthorizationConfig::Opa {
cache_entry_time_to_live_secs,
cache_max_entries,
config: OpaConfig { package, .. },
..
} => {
// According to [`OpaConfig::document_url`] we default the stacklet name
let package = package.clone().unwrap_or_else(|| nifi_cluster.name_any());
authorizers_xml.push_str(&formatdoc! {r#"
<authorizer>
<identifier>authorizer</identifier>
<class>org.nifiopa.nifiopa.OpaAuthorizer</class>
<property name="CACHE_TIME_SECS">{cache_entry_time_to_live_secs}</property>
<property name="CACHE_MAX_ENTRY_COUNT">{cache_max_entries}</property>
<property name="OPA_URI">${{env:OPA_BASE_URL}}</property>
<property name="OPA_RULE_HEAD">nifi/allow</property>
<property name="OPA_RULE_HEAD">{package}/allow</property>
</authorizer>
"#});
}
Expand Down Expand Up @@ -172,13 +170,18 @@ impl NifiAuthorizationConfig {

pub fn get_env_vars(&self) -> Vec<EnvVar> {
match self {
NifiAuthorizationConfig::Opa { configmap_name, .. } => {
NifiAuthorizationConfig::Opa {
config: OpaConfig {
config_map_name, ..
},
..
} => {
vec![EnvVar {
name: "OPA_BASE_URL".to_owned(),
value_from: Some(EnvVarSource {
config_map_key_ref: Some(ConfigMapKeySelector {
key: "OPA".to_owned(),
name: configmap_name.to_owned(),
name: config_map_name.to_owned(),
..Default::default()
}),
..Default::default()
Expand Down
4 changes: 2 additions & 2 deletions tests/templates/kuttl/oidc-opa/25-opa-rego.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ metadata:
labels:
opa.stackable.tech/bundle: "true"
data:
nifi.rego: |
package nifi
my_nifi_package.rego: |
package my_nifi_package

nifi_node_proxy := "CN=generated certificate for pod"
nifi_reporting_task_user := "admin"
Expand Down
2 changes: 1 addition & 1 deletion tests/templates/kuttl/oidc-opa/30_nifi.yaml.j2
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ spec:
authorization:
opa:
configMapName: opa
package: nifi
package: my_nifi_package
cache:
entryTimeToLive: 5s
maxEntries: 10
Expand Down