Skip to content

Commit 9d021c0

Browse files
feat: Batch OPA column mask calls (#827)
* add batchColumnMasks OPA endpoint, add schema definition, adjust operator to use new batched endpoint * add/adjust tests, add crd field to disable column masking * incorporate decision feedback * adjust tests * make `opa` within `authorization` a required enum variant * pre-commit fixes * refactor code, add documenation * add changelog entries * cleanup test and unnecessary function * fix docs * remove version 451 test handling * Update rust/operator-binary/src/crd/mod.rs Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * Update tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * Update tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego Co-authored-by: Siegfried Weber <mail@siegfriedweber.net> * remove unneeded line wrap * fix docs * update config overrides list * fix rego test * disable columnmasking in example * pre-commit fixes --------- Co-authored-by: Siegfried Weber <mail@siegfriedweber.net>
1 parent dbe3abf commit 9d021c0

File tree

16 files changed

+357
-35
lines changed

16 files changed

+357
-35
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,19 @@ All notable changes to this project will be documented in this file.
99
- Support objectOverrides using `.spec.objectOverrides`.
1010
See [objectOverrides concepts page](https://docs.stackable.tech/home/nightly/concepts/overrides/#object-overrides) for details ([#831]).
1111
- Enable the [restart-controller](https://docs.stackable.tech/home/nightly/commons-operator/restarter/), so that the Pods are automatically restarted on config changes ([#833]).
12+
- Add `enabledColumnMasking` field to `opa` configuration in `authorization` ([#827]).
13+
- Support batched column masks in Rego rules ([#827]).
1214

1315
### Changed
1416

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

1722
[#831]: https://github.com/stackabletech/trino-operator/pull/831
1823
[#833]: https://github.com/stackabletech/trino-operator/pull/833
24+
[#827]: https://github.com/stackabletech/trino-operator/pull/827
1925

2026
## [25.11.0] - 2025-11-07
2127

deploy/helm/trino-operator/crds/crds.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,20 +70,26 @@ spec:
7070
Authorization options for Trino.
7171
Learn more in the [Trino authorization usage guide](https://docs.stackable.tech/home/nightly/trino/usage-guide/security#authorization).
7272
nullable: true
73+
oneOf:
74+
- required:
75+
- opa
7376
properties:
7477
opa:
7578
description: |-
7679
Configure the OPA stacklet [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery)
7780
and the name of the Rego package containing your authorization rules.
7881
Consult the [OPA authorization documentation](https://docs.stackable.tech/home/nightly/concepts/opa)
7982
to learn how to deploy Rego authorization rules with OPA.
80-
nullable: true
8183
properties:
8284
configMapName:
8385
description: |-
8486
The [discovery ConfigMap](https://docs.stackable.tech/home/nightly/concepts/service_discovery)
8587
for the OPA stacklet that should be used for authorization requests.
8688
type: string
89+
enableColumnMasking:
90+
default: true
91+
description: Whether to set the OPA batched column masking URI for Trino queries; defaults to true
92+
type: boolean
8793
package:
8894
description: The name of the Rego package containing the Rego rules for the product.
8995
nullable: true
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
= Security
2+
3+
== Authorization
4+
5+
=== OPA
6+
7+
==== Column masking
8+
9+
===== CRD configuration
10+
11+
[source,yaml]
12+
----
13+
apiVersion: trino.stackable.tech/v1alpha1
14+
kind: TrinoCluster
15+
spec:
16+
clusterConfig:
17+
authorization:
18+
opa:
19+
enableColumnMasking: true # default
20+
----
21+
22+
===== Result
23+
24+
In the `access-control.properties` file, the following value is set when `enableColumnMasking` is set to `true`:
25+
26+
[source]
27+
----
28+
opa.policy.batch-column-masking-uri=<opa-url>/v1/data/<package>/batchColumnMasks # <1> <2>
29+
----
30+
31+
<1> `<opa-url>` is read from the OPA discovery ConfigMap
32+
<2> `<package>` is read from `spec.clusterConfig.authorization.opa.package` if set, otherwise defaults to the TrinoCluster name
33+
34+
===== Considerations
35+
36+
The default setting for `enableColumnMasking` assumes a `batchColumnMasks` rule is defined in the Rego rules for the TrinoCluster.
37+
If no such rule is defined, Trino queries that utilize the column masking endpoint will fail.

docs/modules/trino/pages/usage-guide/configuration.adoc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ For a role or role group, at the same level of `config`, you can specify `config
1313
* `access-control.properties`
1414
* `config.properties`
1515
* `node.properties`
16-
* `password-authenticator.properties`
1716
* `security.properties`
1817
* `exchange-manager.properties`
1918
* `spooling-manager.properties`

docs/modules/trino/pages/usage-guide/overrides.adoc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ Confiuration overrides are applied like so:
2727

2828
Configuration overrides can be applied to:
2929

30+
* `access-control.properties`
3031
* `config.properties`
3132
* `node.properties`
33+
* `security.properties`
34+
* `exchange-manager.properties`
35+
* `spooling-manager.properties`
3236

3337
=== Configuration overrides in the TrinoCatalog
3438

docs/modules/trino/partials/nav.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,5 +33,6 @@
3333
** xref:trino:reference/crds.adoc[]
3434
*** {crd-docs}/trino.stackable.tech/trinocluster/v1alpha1/[TrinoCluster {external-link-icon}^]
3535
*** {crd-docs}/trino.stackable.tech/trinocatalog/v1alpha1/[TrinoCatalog {external-link-icon}^]
36+
** xref:trino:reference/security.adoc[]
3637
** xref:trino:reference/commandline-parameters.adoc[]
3738
** xref:trino:reference/environment-variables.adoc[]

examples/simple-trino-cluster-authentication-opa-authorization-s3.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ spec:
1111
- authenticationClass: simple-trino-users
1212
authorization:
1313
opa:
14+
enableColumnMasking: false
1415
configMapName: simple-opa
1516
package: trino
1617
catalogLabelSelector:

rust/operator-binary/src/authorization/opa.rs

Lines changed: 33 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
use std::collections::BTreeMap;
22

33
use stackable_operator::{
4-
client::Client,
5-
commons::opa::{OpaApiVersion, OpaConfig},
6-
k8s_openapi::api::core::v1::ConfigMap,
4+
client::Client, commons::opa::OpaApiVersion, k8s_openapi::api::core::v1::ConfigMap,
75
kube::ResourceExt,
86
};
97

10-
use crate::crd::v1alpha1::TrinoCluster;
8+
use crate::crd::v1alpha1;
119

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

@@ -23,10 +21,10 @@ pub struct TrinoOpaConfig {
2321
/// `http://localhost:8081/v1/data/trino/rowFilters` - if not set,
2422
/// no row filtering will be applied
2523
pub(crate) row_filters_connection_string: Option<String>,
26-
/// URI for fetching column masks, e.g.
27-
/// `http://localhost:8081/v1/data/trino/columnMask` - if not set,
24+
/// URI for fetching columns masks in batches, e.g.
25+
/// `http://localhost:8081/v1/data/trino/batchColumnMasks` - if not set,
2826
/// no masking will be applied
29-
pub(crate) column_masking_connection_string: Option<String>,
27+
pub(crate) batched_column_masking_connection_string: Option<String>,
3028
/// Whether to allow permission management (GRANT, DENY, ...) and
3129
/// role management operations - OPA will not be queried for any
3230
/// such operations, they will be bulk allowed or denied depending
@@ -41,13 +39,15 @@ pub struct TrinoOpaConfig {
4139
impl TrinoOpaConfig {
4240
pub async fn from_opa_config(
4341
client: &Client,
44-
trino: &TrinoCluster,
45-
opa_config: &OpaConfig,
42+
trino: &v1alpha1::TrinoCluster,
43+
opa_config: &v1alpha1::TrinoAuthorizationOpaConfig,
4644
) -> Result<Self, stackable_operator::commons::opa::Error> {
4745
let non_batched_connection_string = opa_config
46+
.opa
4847
.full_document_url_from_config_map(client, trino, Some("allow"), OpaApiVersion::V1)
4948
.await?;
5049
let batched_connection_string = opa_config
50+
.opa
5151
.full_document_url_from_config_map(
5252
client,
5353
trino,
@@ -57,6 +57,7 @@ impl TrinoOpaConfig {
5757
)
5858
.await?;
5959
let row_filters_connection_string = opa_config
60+
.opa
6061
.full_document_url_from_config_map(
6162
client,
6263
trino,
@@ -65,19 +66,27 @@ impl TrinoOpaConfig {
6566
OpaApiVersion::V1,
6667
)
6768
.await?;
68-
let column_masking_connection_string = opa_config
69-
.full_document_url_from_config_map(
70-
client,
71-
trino,
72-
// Sticking to https://github.com/trinodb/trino/blob/455/plugin/trino-opa/src/test/java/io/trino/plugin/opa/TestOpaAccessControlDataFilteringSystem.java#L47
73-
Some("columnMask"),
74-
OpaApiVersion::V1,
69+
70+
let batched_column_masking_connection_string = if opa_config.enable_column_masking {
71+
Some(
72+
opa_config
73+
.opa
74+
.full_document_url_from_config_map(
75+
client,
76+
trino,
77+
// Sticking to https://github.com/trinodb/trino/blob/455/plugin/trino-opa/src/test/java/io/trino/plugin/opa/TestOpaAccessControlDataFilteringSystem.java#L48
78+
Some("batchColumnMasks"),
79+
OpaApiVersion::V1,
80+
)
81+
.await?,
7582
)
76-
.await?;
83+
} else {
84+
None
85+
};
7786

7887
let tls_secret_class = client
7988
.get::<ConfigMap>(
80-
&opa_config.config_map_name,
89+
&opa_config.opa.config_map_name,
8190
trino.namespace().as_deref().unwrap_or("default"),
8291
)
8392
.await
@@ -89,7 +98,7 @@ impl TrinoOpaConfig {
8998
non_batched_connection_string,
9099
batched_connection_string,
91100
row_filters_connection_string: Some(row_filters_connection_string),
92-
column_masking_connection_string: Some(column_masking_connection_string),
101+
batched_column_masking_connection_string,
93102
allow_permission_management_operations: true,
94103
tls_secret_class,
95104
})
@@ -113,10 +122,12 @@ impl TrinoOpaConfig {
113122
Some(row_filters_connection_string.clone()),
114123
);
115124
}
116-
if let Some(column_masking_connection_string) = &self.column_masking_connection_string {
125+
if let Some(batched_column_masking_connection_string) =
126+
&self.batched_column_masking_connection_string
127+
{
117128
config.insert(
118-
"opa.policy.column-masking-uri".to_string(),
119-
Some(column_masking_connection_string.clone()),
129+
"opa.policy.batch-column-masking-uri".to_string(),
130+
Some(batched_column_masking_connection_string.clone()),
120131
);
121132
}
122133
if self.allow_permission_management_operations {

rust/operator-binary/src/controller.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,8 +2070,8 @@ mod tests {
20702070
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/rowFilters"
20712071
.to_string(),
20722072
),
2073-
column_masking_connection_string: Some(
2074-
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/columnMask"
2073+
batched_column_masking_connection_string: Some(
2074+
"http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks"
20752075
.to_string(),
20762076
),
20772077
allow_permission_management_operations: true,
@@ -2157,6 +2157,7 @@ mod tests {
21572157
hello-from-role-group: "true" # only defined here at group level
21582158
foo.bar: "true" # overrides role value
21592159
opa.policy.batched-uri: "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batch-new" # override value from config
2160+
opa.policy.batch-column-masking-uri: "http://simple-opa.default.svc.cluster.local:8081/v1/data/my-product/batchColumnMasks-new" # override value from config
21602161
replicas: 1
21612162
workers:
21622163
roleGroups:
@@ -2173,7 +2174,7 @@ mod tests {
21732174
assert!(access_control_config.contains("foo.bar=true"));
21742175
assert!(access_control_config.contains("opa.allow-permission-management-operations=false"));
21752176
assert!(access_control_config.contains(r#"opa.policy.batched-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/batch-new"#));
2176-
assert!(access_control_config.contains(r#"opa.policy.column-masking-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/columnMask"#));
2177+
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"#));
21772178
assert!(access_control_config.contains(r#"opa.policy.row-filters-uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/rowFilters"#));
21782179
assert!(access_control_config.contains(r#"opa.policy.uri=http\://simple-opa.default.svc.cluster.local\:8081/v1/data/my-product/allow"#));
21792180
}

rust/operator-binary/src/crd/mod.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -317,10 +317,24 @@ pub mod versioned {
317317

318318
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
319319
#[serde(rename_all = "camelCase")]
320-
pub struct TrinoAuthorization {
320+
pub enum TrinoAuthorization {
321+
Opa {
322+
// no doc - it's in the struct.
323+
#[serde(default, flatten)]
324+
config: TrinoAuthorizationOpaConfig,
325+
},
326+
}
327+
328+
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
329+
#[serde(rename_all = "camelCase")]
330+
pub struct TrinoAuthorizationOpaConfig {
321331
// no doc - it's in the struct.
322-
#[serde(default, skip_serializing_if = "Option::is_none")]
323-
pub opa: Option<OpaConfig>,
332+
#[serde(flatten)]
333+
pub opa: OpaConfig,
334+
335+
/// Whether to set the OPA batched column masking URI for Trino queries; defaults to true
336+
#[serde(default = "TrinoAuthorizationOpaConfig::enabled_column_masking_default")]
337+
pub enable_column_masking: bool,
324338
}
325339

326340
#[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)]
@@ -370,6 +384,12 @@ pub mod versioned {
370384
}
371385
}
372386

387+
impl v1alpha1::TrinoAuthorizationOpaConfig {
388+
pub fn enabled_column_masking_default() -> bool {
389+
true
390+
}
391+
}
392+
373393
impl Default for v1alpha1::TrinoCoordinatorRoleConfig {
374394
fn default() -> Self {
375395
v1alpha1::TrinoCoordinatorRoleConfig {
@@ -882,12 +902,14 @@ impl v1alpha1::TrinoCluster {
882902
!spec.cluster_config.authentication.is_empty()
883903
}
884904

885-
pub fn get_opa_config(&self) -> Option<&OpaConfig> {
905+
pub fn get_opa_config(&self) -> Option<&v1alpha1::TrinoAuthorizationOpaConfig> {
886906
self.spec
887907
.cluster_config
888908
.authorization
889909
.as_ref()
890-
.and_then(|a| a.opa.as_ref())
910+
.map(|a| match a {
911+
v1alpha1::TrinoAuthorization::Opa { config } => config,
912+
})
891913
}
892914

893915
/// Return user provided server TLS settings

0 commit comments

Comments
 (0)