Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9b5eba6
add batchColumnMasks OPA endpoint, add schema definition, adjust oper…
xeniape Dec 16, 2025
76aeaf5
add/adjust tests, add crd field to disable column masking
xeniape Jan 6, 2026
e2e563d
Merge branch 'main' into feat/batch-opa-column-mask-calls
xeniape Jan 6, 2026
a607d95
incorporate decision feedback
xeniape Jan 7, 2026
de1b3af
Merge branch 'main' into feat/batch-opa-column-mask-calls
xeniape Jan 7, 2026
b1addca
adjust tests
xeniape Jan 7, 2026
df98f6c
make `opa` within `authorization` a required enum variant
xeniape Jan 8, 2026
36319f9
Merge branch 'main' into feat/batch-opa-column-mask-calls
xeniape Jan 12, 2026
1320aef
pre-commit fixes
xeniape Jan 12, 2026
2d4c4ad
refactor code, add documenation
xeniape Jan 12, 2026
06b2878
add changelog entries
xeniape Jan 12, 2026
34767d9
Merge branch 'main' into feat/batch-opa-column-mask-calls
xeniape Jan 14, 2026
44e6bc5
cleanup test and unnecessary function
xeniape Jan 21, 2026
185e651
fix docs
xeniape Jan 21, 2026
8afff5d
Merge branch 'main' into feat/batch-opa-column-mask-calls
xeniape Jan 21, 2026
e43bf44
remove version 451 test handling
xeniape Jan 21, 2026
e5440e7
Update rust/operator-binary/src/crd/mod.rs
xeniape Jan 21, 2026
b13c4ed
Update tests/templates/kuttl/opa-authorization/trino_rules/trino/veri…
xeniape Jan 21, 2026
0e7d758
Update tests/templates/kuttl/opa-authorization/trino_rules/trino/veri…
xeniape Jan 21, 2026
d635bdd
remove unneeded line wrap
xeniape Jan 21, 2026
681bc60
fix docs
xeniape Jan 21, 2026
8505a4e
update config overrides list
xeniape Jan 21, 2026
3c12023
fix rego test
xeniape Jan 21, 2026
92a4f17
disable columnmasking in example
xeniape Jan 21, 2026
d35c6e7
pre-commit fixes
xeniape Jan 21, 2026
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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,19 @@ All notable changes to this project will be documented in this file.
- Support objectOverrides using `.spec.objectOverrides`.
See [objectOverrides concepts page](https://docs.stackable.tech/home/nightly/concepts/overrides/#object-overrides) for details ([#831]).
- Enable the [restart-controller](https://docs.stackable.tech/home/nightly/commons-operator/restarter/), so that the Pods are automatically restarted on config changes ([#833]).
- Add `enabledColumnMasking` field to `opa` configuration in `authorization` ([#827]).
- Support batched column masks in Rego rules ([#827]).

### Changed

- Pin k8s-openapi to `0.26.0` ([#831]).
- BREAKING: The field `opa` in `authorization` is now a mandatory enum variant instead of being optional ([#827]).
- BREAKING: The operator no longer sets `opa.policy.column-masking-uri` in `access-control.properties` but
`opa.policy.batch-column-masking-uri` instead, allowing Trino to fetch multiple column masks in a single request ([#827]).

[#831]: https://github.com/stackabletech/trino-operator/pull/831
[#833]: https://github.com/stackabletech/trino-operator/pull/833
[#827]: https://github.com/stackabletech/trino-operator/pull/827

## [25.11.0] - 2025-11-07

Expand Down
8 changes: 7 additions & 1 deletion deploy/helm/trino-operator/crds/crds.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,20 +70,26 @@ spec:
Authorization options for Trino.
Learn more in the [Trino authorization usage guide](https://docs.stackable.tech/home/nightly/trino/usage-guide/security#authorization).
nullable: true
oneOf:
- required:
- opa
properties:
opa:
description: |-
Configure the OPA stacklet [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery)
and the name of the Rego package containing your authorization rules.
Consult the [OPA authorization documentation](https://docs.stackable.tech/home/nightly/concepts/opa)
to learn how to deploy Rego authorization rules with OPA.
nullable: true
properties:
configMapName:
description: |-
The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery)
for the OPA stacklet that should be used for authorization requests.
type: string
enableColumnMasking:
default: true
description: Whether to set the OPA batched column masking URI for Trino queries; defaults to true
type: boolean
package:
description: The name of the Rego package containing the Rego rules for the product.
nullable: true
Expand Down
37 changes: 37 additions & 0 deletions docs/modules/trino/pages/reference/security.adoc
Copy link
Member

Choose a reason for hiding this comment

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

Why haven't you added this information to usage-guide/security.adoc? I would not have expected a second security page.

Copy link
Member Author

Choose a reason for hiding this comment

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

Because the content of that documentation is a reference and not a guide https://diataxis.fr/reference/

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
= Security

== Authorization

=== OPA

==== Column masking

===== CRD configuration

[source,yaml]
----
apiVersion: trino.stackable.tech/v1alpha1
kind: TrinoCluster
spec:
clusterConfig:
authorization:
opa:
enableColumnMasking: true # default
----

===== Result

In the `access-control.properties` file, the following value is set when `enableColumnMasking` is set to `true`:

[source]
----
opa.policy.batch-column-masking-uri=<opa-url>/v1/data/<package>/batchColumnMasks # <1> <2>
----

<1> `<opa-url>` is read from the OPA discovery ConfigMap
<2> `<package>` is read from `spec.clusterConfig.authorization.opa.package` if set, otherwise defaults to the TrinoCluster name

===== Considerations

The default setting for `enableColumnMasking` assumes a `batchColumnMasks` rule is defined in the Rego rules for the TrinoCluster.
If no such rule is defined, Trino queries that utilize the column masking endpoint will fail.
1 change: 0 additions & 1 deletion docs/modules/trino/pages/usage-guide/configuration.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ For a role or role group, at the same level of `config`, you can specify `config
* `access-control.properties`
* `config.properties`
* `node.properties`
* `password-authenticator.properties`
* `security.properties`
* `exchange-manager.properties`
* `spooling-manager.properties`
Expand Down
4 changes: 4 additions & 0 deletions docs/modules/trino/pages/usage-guide/overrides.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,12 @@ Confiuration overrides are applied like so:

Configuration overrides can be applied to:

* `access-control.properties`
* `config.properties`
* `node.properties`
* `security.properties`
* `exchange-manager.properties`
* `spooling-manager.properties`

=== Configuration overrides in the TrinoCatalog

Expand Down
1 change: 1 addition & 0 deletions docs/modules/trino/partials/nav.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -33,5 +33,6 @@
** xref:trino:reference/crds.adoc[]
*** {crd-docs}/trino.stackable.tech/trinocluster/v1alpha1/[TrinoCluster {external-link-icon}^]
*** {crd-docs}/trino.stackable.tech/trinocatalog/v1alpha1/[TrinoCatalog {external-link-icon}^]
** xref:trino:reference/security.adoc[]
** xref:trino:reference/commandline-parameters.adoc[]
** xref:trino:reference/environment-variables.adoc[]
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ spec:
- authenticationClass: simple-trino-users
authorization:
opa:
enableColumnMasking: false
configMapName: simple-opa
package: trino
catalogLabelSelector:
Expand Down
55 changes: 33 additions & 22 deletions rust/operator-binary/src/authorization/opa.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
use std::collections::BTreeMap;

use stackable_operator::{
client::Client,
commons::opa::{OpaApiVersion, OpaConfig},
k8s_openapi::api::core::v1::ConfigMap,
client::Client, commons::opa::OpaApiVersion, k8s_openapi::api::core::v1::ConfigMap,
kube::ResourceExt,
};

use crate::crd::v1alpha1::TrinoCluster;
use crate::crd::v1alpha1;

pub const OPA_TLS_VOLUME_NAME: &str = "opa-tls";

Expand All @@ -23,10 +21,10 @@ pub struct TrinoOpaConfig {
/// `http://localhost:8081/v1/data/trino/rowFilters` - if not set,
/// no row filtering will be applied
pub(crate) row_filters_connection_string: Option<String>,
/// URI for fetching column masks, e.g.
/// `http://localhost:8081/v1/data/trino/columnMask` - if not set,
/// URI for fetching columns masks in batches, e.g.
/// `http://localhost:8081/v1/data/trino/batchColumnMasks` - if not set,
/// no masking will be applied
pub(crate) column_masking_connection_string: Option<String>,
pub(crate) batched_column_masking_connection_string: Option<String>,
/// Whether to allow permission management (GRANT, DENY, ...) and
/// role management operations - OPA will not be queried for any
/// such operations, they will be bulk allowed or denied depending
Expand All @@ -41,13 +39,15 @@ pub struct TrinoOpaConfig {
impl TrinoOpaConfig {
pub async fn from_opa_config(
client: &Client,
trino: &TrinoCluster,
opa_config: &OpaConfig,
trino: &v1alpha1::TrinoCluster,
opa_config: &v1alpha1::TrinoAuthorizationOpaConfig,
) -> Result<Self, stackable_operator::commons::opa::Error> {
let non_batched_connection_string = opa_config
.opa
.full_document_url_from_config_map(client, trino, Some("allow"), OpaApiVersion::V1)
.await?;
let batched_connection_string = opa_config
.opa
.full_document_url_from_config_map(
client,
trino,
Expand All @@ -57,6 +57,7 @@ impl TrinoOpaConfig {
)
.await?;
let row_filters_connection_string = opa_config
.opa
.full_document_url_from_config_map(
client,
trino,
Expand All @@ -65,19 +66,27 @@ impl TrinoOpaConfig {
OpaApiVersion::V1,
)
.await?;
let column_masking_connection_string = opa_config
.full_document_url_from_config_map(
client,
trino,
// Sticking to https://github.com/trinodb/trino/blob/455/plugin/trino-opa/src/test/java/io/trino/plugin/opa/TestOpaAccessControlDataFilteringSystem.java#L47
Some("columnMask"),
OpaApiVersion::V1,

let batched_column_masking_connection_string = if opa_config.enable_column_masking {
Some(
opa_config
.opa
.full_document_url_from_config_map(
client,
trino,
// Sticking to https://github.com/trinodb/trino/blob/455/plugin/trino-opa/src/test/java/io/trino/plugin/opa/TestOpaAccessControlDataFilteringSystem.java#L48
Some("batchColumnMasks"),
OpaApiVersion::V1,
)
.await?,
)
.await?;
} else {
None
};

let tls_secret_class = client
.get::<ConfigMap>(
&opa_config.config_map_name,
&opa_config.opa.config_map_name,
trino.namespace().as_deref().unwrap_or("default"),
)
.await
Expand All @@ -89,7 +98,7 @@ impl TrinoOpaConfig {
non_batched_connection_string,
batched_connection_string,
row_filters_connection_string: Some(row_filters_connection_string),
column_masking_connection_string: Some(column_masking_connection_string),
batched_column_masking_connection_string,
allow_permission_management_operations: true,
tls_secret_class,
})
Expand All @@ -113,10 +122,12 @@ impl TrinoOpaConfig {
Some(row_filters_connection_string.clone()),
);
}
if let Some(column_masking_connection_string) = &self.column_masking_connection_string {
if let Some(batched_column_masking_connection_string) =
&self.batched_column_masking_connection_string
{
config.insert(
"opa.policy.column-masking-uri".to_string(),
Some(column_masking_connection_string.clone()),
"opa.policy.batch-column-masking-uri".to_string(),
Some(batched_column_masking_connection_string.clone()),
);
}
if self.allow_permission_management_operations {
Expand Down
7 changes: 4 additions & 3 deletions rust/operator-binary/src/controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2070,8 +2070,8 @@ mod tests {
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/rowFilters"
.to_string(),
),
column_masking_connection_string: Some(
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/columnMask"
batched_column_masking_connection_string: Some(
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks"
.to_string(),
),
allow_permission_management_operations: true,
Expand Down Expand Up @@ -2157,6 +2157,7 @@ mod tests {
hello-from-role-group: "true" # only defined here at group level
foo.bar: "true" # overrides role value
opa.policy.batched-uri: "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batch-new" # override value from config
opa.policy.batch-column-masking-uri: "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks-new" # override value from config
replicas: 1
workers:
roleGroups:
Expand All @@ -2173,7 +2174,7 @@ mod tests {
assert!(access_control_config.contains("foo.bar=true"));
assert!(access_control_config.contains("opa.allow-permission-management-operations=false"));
assert!(access_control_config.contains(r#"opa.policy.batched-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/batch-new"#));
assert!(access_control_config.contains(r#"opa.policy.column-masking-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/columnMask"#));
assert!(access_control_config.contains(r#"opa.policy.batch-column-masking-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/batchColumnMasks-new"#));
assert!(access_control_config.contains(r#"opa.policy.row-filters-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/rowFilters"#));
assert!(access_control_config.contains(r#"opa.policy.uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/allow"#));
}
Expand Down
32 changes: 27 additions & 5 deletions rust/operator-binary/src/crd/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,24 @@ pub mod versioned {

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TrinoAuthorization {
pub enum TrinoAuthorization {
Opa {
// no doc - it's in the struct.
#[serde(default, flatten)]
config: TrinoAuthorizationOpaConfig,
},
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct TrinoAuthorizationOpaConfig {
// no doc - it's in the struct.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub opa: Option<OpaConfig>,
#[serde(flatten)]
pub opa: OpaConfig,

/// Whether to set the OPA batched column masking URI for Trino queries; defaults to true
#[serde(default = "TrinoAuthorizationOpaConfig::enabled_column_masking_default")]
pub enable_column_masking: bool,
}

#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
Expand Down Expand Up @@ -370,6 +384,12 @@ pub mod versioned {
}
}

impl v1alpha1::TrinoAuthorizationOpaConfig {
pub fn enabled_column_masking_default() -> bool {
true
}
}

impl Default for v1alpha1::TrinoCoordinatorRoleConfig {
fn default() -> Self {
v1alpha1::TrinoCoordinatorRoleConfig {
Expand Down Expand Up @@ -882,12 +902,14 @@ impl v1alpha1::TrinoCluster {
!spec.cluster_config.authentication.is_empty()
}

pub fn get_opa_config(&self) -> Option<&OpaConfig> {
pub fn get_opa_config(&self) -> Option<&v1alpha1::TrinoAuthorizationOpaConfig> {
self.spec
.cluster_config
.authorization
.as_ref()
.and_then(|a| a.opa.as_ref())
.map(|a| match a {
v1alpha1::TrinoAuthorization::Opa { config } => config,
})
}

/// Return user provided server TLS settings
Expand Down
7 changes: 4 additions & 3 deletions rust/operator-binary/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,9 +238,10 @@ fn references_config_map(
};

match &trino.spec.cluster_config.authorization {
Some(trino_authorization) => match &trino_authorization.opa {
Some(opa_config) => opa_config.config_map_name == config_map.name_any(),
None => false,
Some(trino_authorization) => match &trino_authorization {
v1alpha1::TrinoAuthorization::Opa { config } => {
config.opa.config_map_name == config_map.name_any()
}
},
None => false,
}
Expand Down
Loading