diff --git a/deploy/helm/trino-operator/crds/crds.yaml b/deploy/helm/trino-operator/crds/crds.yaml index 9f66ef66..fb8004ee 100644 --- a/deploy/helm/trino-operator/crds/crds.yaml +++ b/deploy/helm/trino-operator/crds/crds.yaml @@ -70,6 +70,9 @@ 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: |- @@ -77,13 +80,15 @@ spec: 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 + type: boolean package: description: The name of the Rego package containing the Rego rules for the product. nullable: true diff --git a/rust/operator-binary/src/authorization/opa.rs b/rust/operator-binary/src/authorization/opa.rs index 1bd9cd41..216643d6 100644 --- a/rust/operator-binary/src/authorization/opa.rs +++ b/rust/operator-binary/src/authorization/opa.rs @@ -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"; @@ -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, - /// URI for fetching column masks, e.g. - /// `http://localhost:8081/v1/data/trino/columnMask` - if not set, - /// no masking will be applied - pub(crate) column_masking_connection_string: Option, + /// URI for fetching columns masks in batches, e.g. + /// `http://localhost:8081/v1/data/trino/batchColumnMasks` - if not set, + /// column-masking-uri will be used for getting column masks in parallel + pub(crate) batched_column_masking_connection_string: Option, /// 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 @@ -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 { 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, @@ -57,6 +57,7 @@ impl TrinoOpaConfig { ) .await?; let row_filters_connection_string = opa_config + .opa .full_document_url_from_config_map( client, trino, @@ -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 trino.column_masking_enabled() { + 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::( - &opa_config.config_map_name, + &opa_config.opa.config_map_name, trino.namespace().as_deref().unwrap_or("default"), ) .await @@ -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, }) @@ -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 { diff --git a/rust/operator-binary/src/controller.rs b/rust/operator-binary/src/controller.rs index d8ddea6a..cb2b3aaa 100644 --- a/rust/operator-binary/src/controller.rs +++ b/rust/operator-binary/src/controller.rs @@ -2064,8 +2064,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, @@ -2167,7 +2167,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"#)); 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"#)); } diff --git a/rust/operator-binary/src/crd/mod.rs b/rust/operator-binary/src/crd/mod.rs index 61e7522a..6f2012d2 100644 --- a/rust/operator-binary/src/crd/mod.rs +++ b/rust/operator-binary/src/crd/mod.rs @@ -312,10 +312,23 @@ 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, + #[serde(flatten)] + pub opa: OpaConfig, + + #[serde(default = "TrinoAuthorization::enabled_column_masking_default")] + pub enable_column_masking: bool, } #[derive(Clone, Debug, Deserialize, Eq, JsonSchema, PartialEq, Serialize)] @@ -365,6 +378,12 @@ pub mod versioned { } } +impl v1alpha1::TrinoAuthorization { + pub fn enabled_column_masking_default() -> bool { + true + } +} + impl Default for v1alpha1::TrinoCoordinatorRoleConfig { fn default() -> Self { v1alpha1::TrinoCoordinatorRoleConfig { @@ -877,12 +896,21 @@ impl v1alpha1::TrinoCluster { !spec.cluster_config.authentication.is_empty() } - pub fn get_opa_config(&self) -> Option<&OpaConfig> { + pub fn column_masking_enabled(&self) -> bool { + match self.get_opa_config() { + Some(a) => a.enable_column_masking, + None => v1alpha1::TrinoAuthorization::enabled_column_masking_default(), + } + } + + 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 diff --git a/rust/operator-binary/src/main.rs b/rust/operator-binary/src/main.rs index 0c49d09e..4f1536fa 100644 --- a/rust/operator-binary/src/main.rs +++ b/rust/operator-binary/src/main.rs @@ -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, } diff --git a/tests/templates/kuttl/opa-authorization/20-install-trino.yaml.j2 b/tests/templates/kuttl/opa-authorization/20-install-trino.yaml.j2 index be67c0a8..ce42a72a 100644 --- a/tests/templates/kuttl/opa-authorization/20-install-trino.yaml.j2 +++ b/tests/templates/kuttl/opa-authorization/20-install-trino.yaml.j2 @@ -1,110 +1,129 @@ --- -apiVersion: trino.stackable.tech/v1alpha1 -kind: TrinoCluster -metadata: - name: trino -spec: - image: +apiVersion: kuttl.dev/v1beta1 +kind: TestStep +commands: + - script: | + kubectl apply -n $NAMESPACE -f - < 0 %} - custom: "{{ test_scenario['values']['trino'].split(',')[1] }}" - productVersion: "{{ test_scenario['values']['trino'].split(',')[0] }}" + custom: "{{ test_scenario['values']['trino'].split(',')[1] }}" + productVersion: "{{ test_scenario['values']['trino'].split(',')[0] }}" {% else %} - productVersion: "{{ test_scenario['values']['trino'] }}" + productVersion: "{{ test_scenario['values']['trino'] }}" +{% endif %} + pullPolicy: IfNotPresent + clusterConfig: + catalogLabelSelector: + matchLabels: + trino: trino + authentication: + - authenticationClass: trino-users-auth + authorization: + opa: + configMapName: opa + package: trino +{% if test_scenario['values']['trino'] == "451" %} + enableColumnMasking: false {% endif %} - pullPolicy: IfNotPresent - clusterConfig: - catalogLabelSelector: - matchLabels: - trino: trino - authentication: - - authenticationClass: trino-users-auth - authorization: - opa: - configMapName: opa - package: trino {% if lookup('env', 'VECTOR_AGGREGATOR') %} - vectorAggregatorConfigMapName: vector-aggregator-discovery + vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} - coordinators: - config: - logging: - enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - roleGroups: - default: - replicas: 1 - config: {} - workers: - config: - gracefulShutdownTimeout: 10s # Let the test run faster - logging: - enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} - roleGroups: - default: - replicas: 1 - config: {} ---- -apiVersion: authentication.stackable.tech/v1alpha1 -kind: AuthenticationClass -metadata: - name: trino-users-auth -spec: - provider: - static: - userCredentialsSecret: + coordinators: + config: + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} +{% if test_scenario['values']['trino'] == "451" %} + configOverrides: + access-control.properties: + opa.policy.column-masking-uri: "https://opa-server.$NAMESPACE.svc.cluster.local:8443/v1/data/trino/columnMask" +{% endif %} + roleGroups: + default: + replicas: 1 + config: {} + workers: + config: + gracefulShutdownTimeout: 10s # Let the test run faster + logging: + enableVectorAgent: {{ lookup('env', 'VECTOR_AGGREGATOR') | length > 0 }} +{% if test_scenario['values']['trino'] == "451" %} + configOverrides: + access-control.properties: + opa.policy.column-masking-uri: "https://opa-server.$NAMESPACE.svc.cluster.local:8443/v1/data/trino/columnMask" +{% endif %} + roleGroups: + default: + replicas: 1 + config: {} + --- + apiVersion: authentication.stackable.tech/v1alpha1 + kind: AuthenticationClass + metadata: + name: trino-users-auth + spec: + provider: + static: + userCredentialsSecret: + name: trino-users + --- + apiVersion: v1 + kind: Secret + metadata: name: trino-users ---- -apiVersion: v1 -kind: Secret -metadata: - name: trino-users -type: kubernetes.io/opaque -stringData: - admin: admin - banned-user: banned-user - group-user: group-user - lakehouse: lakehouse - iceberg: iceberg ---- -apiVersion: trino.stackable.tech/v1alpha1 -kind: TrinoCatalog -metadata: - name: lakehouse - labels: - trino: trino -spec: - connector: - tpch: {} ---- -apiVersion: trino.stackable.tech/v1alpha1 -kind: TrinoCatalog -metadata: - name: tpch - labels: - trino: trino -spec: - connector: - tpch: {} ---- -apiVersion: trino.stackable.tech/v1alpha1 -kind: TrinoCatalog -metadata: - name: tpcds - labels: - trino: trino -spec: - connector: - tpcds: {} ---- -apiVersion: trino.stackable.tech/v1alpha1 -kind: TrinoCatalog -metadata: - name: iceberg - labels: - trino: trino -spec: - connector: - iceberg: - metastore: - configMap: hive # It's fine to reuse the existing HMS for tests. Not recommended for production though, there a dedicated HMS should be used. - s3: - reference: minio + type: kubernetes.io/opaque + stringData: + admin: admin + banned-user: banned-user + group-user: group-user + lakehouse: lakehouse + iceberg: iceberg + --- + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCatalog + metadata: + name: lakehouse + labels: + trino: trino + spec: + connector: + tpch: {} + --- + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCatalog + metadata: + name: tpch + labels: + trino: trino + spec: + connector: + tpch: {} + --- + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCatalog + metadata: + name: tpcds + labels: + trino: trino + spec: + connector: + tpcds: {} + --- + apiVersion: trino.stackable.tech/v1alpha1 + kind: TrinoCatalog + metadata: + name: iceberg + labels: + trino: trino + spec: + connector: + iceberg: + metastore: + configMap: hive # It's fine to reuse the existing HMS for tests. Not recommended for production though, there a dedicated HMS should be used. + s3: + reference: minio diff --git a/tests/templates/kuttl/opa-authorization/trino_rules/schema/input.json b/tests/templates/kuttl/opa-authorization/trino_rules/schema/input.json index f0149767..27f35f73 100644 --- a/tests/templates/kuttl/opa-authorization/trino_rules/schema/input.json +++ b/tests/templates/kuttl/opa-authorization/trino_rules/schema/input.json @@ -517,6 +517,18 @@ }, "GetColumnMask": { + "type": "object", + "oneOf": [ + { + "$ref": "#/$defs/SingleColumnMask" + }, + { + "$ref": "#/$defs/BatchColumnMasks" + } + ] + }, + + "SingleColumnMask": { "type": "object", "properties": { "operation": { @@ -562,6 +574,55 @@ "required": ["operation", "resource"] }, + "BatchColumnMasks": { + "type": "object", + "properties": { + "operation": { + "const": "GetColumnMask" + }, + "filterResources": { + "type": "array", + "items": { + "type": "object", + "properties": { + "column": { + "type": "object", + "properties": { + "catalogName": { + "type": "string" + }, + "schemaName": { + "type": "string" + }, + "tableName": { + "type": "string" + }, + "columnName": { + "type": "string" + }, + "columnType": { + "type": "string" + } + }, + "additionalProperties": false, + "required": [ + "catalogName", + "schemaName", + "tableName", + "columnName", + "columnType" + ] + } + }, + "additionalProperties": false, + "required": ["column"] + } + } + }, + "additionalProperties": false, + "required": ["operation", "filterResources"] + }, + "GetRowFilters": { "type": "object", "properties": { diff --git a/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego b/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego index 9e001fc1..f5a83a42 100644 --- a/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego +++ b/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification.rego @@ -5,6 +5,7 @@ # - allow # - batch # - columnMask +# - batchColumnMasks # - rowFilters # These rules use the rules and functions in requested_permission.rego # and actual_permissions.rego to calculate the result. @@ -302,6 +303,88 @@ columnMask := column_mask if { column_mask := {"expression": column.mask} } +# METADATA +# description: | +# Entry point for fetching column masks in batch, configured in the +# Trino property `opa.policy.batch-column-masking-uri`. +# +# The input has the following form: +# +# { +# "action": { +# "operation": "GetColumnMasks", +# "filterResources": [{ +# "column": { +# "catalogName": "catalog", +# "schemaName": "schema", +# "tableName": "table", +# "columnName": "column", +# }}, +# {"column": ...}, +# ... +# ], +# }, +# "context": { +# "identity": { +# "groups": ["group1", ...], +# "user": "username", +# }, +# "softwareStack": {"trinoVersion": "455"}, +# } +# } +# +# The batchColumnMask rule queries the column constraints in the +# Trino policies for each of the resources in the "filterResources" +# list of the request and returns a list of viewExpressions, containing +# the column mask if any set and optionally the identity for the mask +# evaluation, and the index of the corresponding resource in the +# "filterResources" list of the request. +# A column mask is an SQL expression, +# e.g. "'XXX-XX-' + substring(credit_card, -4)". +# entrypoint: true +batchColumnMasks contains column_mask if { + input.action.operation == "GetColumnMask" + some index, resource in input.action.filterResources + + column := column_constraints( + resource.column.catalogName, + resource.column.schemaName, + resource.column.tableName, + resource.column.columnName, + ) + + is_string(column.mask) + is_string(column.mask_environment.user) + + column_mask := { + "index": index, + "viewExpression": { + "expression": column.mask, + "identity": column.mask_environment.user, + }, + } +} + +batchColumnMasks contains column_mask if { + input.action.operation == "GetColumnMask" + some index, resource in input.action.filterResources + + column := column_constraints( + resource.column.catalogName, + resource.column.schemaName, + resource.column.tableName, + resource.column.columnName, + ) + + is_string(column.mask) + is_null(column.mask_environment.user) + + column_mask := { + "index": index, + "viewExpression": {"expression": column.mask}, + } +} + # METADATA # description: | # Entry point for fetching row filters, configured in the Trino diff --git a/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification_test.rego b/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification_test.rego index 007c5868..bb505bb1 100644 --- a/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification_test.rego +++ b/tests/templates/kuttl/opa-authorization/trino_rules/trino/verification_test.rego @@ -436,6 +436,89 @@ test_column_mask_with_no_matching_rule if { with data.trino_policies.policies as policies } +test_batch_column_mask_with_expression_and_optional_identity if { + request := { + "action": { + "operation": "GetColumnMask", + "filterResources": [ + {"column": { + "catalogName": "testcatalog", + "schemaName": "testschema", + "tableName": "testtable", + "columnName": "testcolumn", + }}, + {"column": { + "catalogName": "testcatalog", + "schemaName": "testschema", + "tableName": "testtable", + "columnName": "testcolumn2", + }}, + ], + }, + "context": testcontext, + } + policies := {"tables": [{ + "privileges": ["SELECT"], + "columns": [ + { + "name": "testcolumn", + "mask": "testmask", + "mask_environment": {"user": "testmaskenvironmentuser"}, + }, + { + "name": "testcolumn2", + "mask": "testmask2", + }, + ], + }]} + + column_masks := trino.batchColumnMasks with input as request + with data.trino_policies.policies as policies + + column_masks == { + { + "index": 0, + "viewExpression": { + "expression": "testmask", + "identity": "testmaskenvironmentuser", + }, + }, + { + "index": 1, + "viewExpression": {"expression": "testmask2"}, + }, + } +} + +test_batch_column_mask_with_no_matching_rule if { + request := { + "action": { + "operation": "GetColumnMask", + "filterResources": [ + {"column": { + "catalogName": "testcatalog", + "schemaName": "testschema", + "tableName": "testtable", + "columnName": "testcolumn", + }}, + {"column": { + "catalogName": "testcatalog", + "schemaName": "testschema", + "tableName": "testtable", + "columnName": "testcolumn2", + }}, + ], + }, + "context": testcontext, + } + policies := {} + + column_masks := trino.batchColumnMasks with input as request + with data.trino_policies.policies as policies + + count(column_masks) == 0 +} + test_row_filters_with_expression_and_identity if { request := { "action": { diff --git a/tests/templates/kuttl/smoke/09-install-opa.yaml.j2 b/tests/templates/kuttl/smoke/09-install-opa.yaml.j2 index dca1bebb..6f712b8d 100644 --- a/tests/templates/kuttl/smoke/09-install-opa.yaml.j2 +++ b/tests/templates/kuttl/smoke/09-install-opa.yaml.j2 @@ -57,3 +57,5 @@ data: is_bob() if { input.context.identity.user == "bob" } + + batchColumnMasks = [] diff --git a/tests/templates/kuttl/smoke/10-install-trino.yaml.j2 b/tests/templates/kuttl/smoke/10-install-trino.yaml.j2 index f73c7136..c3474adc 100644 --- a/tests/templates/kuttl/smoke/10-install-trino.yaml.j2 +++ b/tests/templates/kuttl/smoke/10-install-trino.yaml.j2 @@ -22,6 +22,9 @@ spec: opa: configMapName: opa package: trino +{% if test_scenario['values']['trino'] == "451" %} + enableColumnMasking: false +{% endif %} {% if lookup('env', 'VECTOR_AGGREGATOR') %} vectorAggregatorConfigMapName: vector-aggregator-discovery {% endif %} @@ -32,6 +35,11 @@ spec: envOverrides: COMMON_VAR: role-value # overridden by role group below ROLE_VAR: role-value # only defined here at role level +{% if test_scenario['values']['trino'] == "451" %} + configOverrides: + access-control.properties: + opa.policy.column-masking-uri: "http://opa-server:8081/v1/data/trino/columnMask" +{% endif %} roleGroups: default: replicas: 1 @@ -47,6 +55,11 @@ spec: envOverrides: COMMON_VAR: role-value # overridden by role group below ROLE_VAR: role-value # only defined here at role level +{% if test_scenario['values']['trino'] == "451" %} + configOverrides: + access-control.properties: + opa.policy.column-masking-uri: "http://opa-server:8081/v1/data/trino/columnMask" +{% endif %} roleGroups: default: replicas: 1 diff --git a/tests/templates/kuttl/smoke_aws/09-install-opa.yaml.j2 b/tests/templates/kuttl/smoke_aws/09-install-opa.yaml.j2 index dca1bebb..6f712b8d 100644 --- a/tests/templates/kuttl/smoke_aws/09-install-opa.yaml.j2 +++ b/tests/templates/kuttl/smoke_aws/09-install-opa.yaml.j2 @@ -57,3 +57,5 @@ data: is_bob() if { input.context.identity.user == "bob" } + + batchColumnMasks = []