Skip to content

Commit d580373

Browse files
committed
fix: resolve client hanging and EQL operation mapping issues
- Send error response to client on bind errors instead of just returning, preventing indefinite client hangs - Map EqlTermVariant to correct QueryOp for encryption: - JsonPath/JsonAccessor use SteVecSelector for SteVec queries - Tokenized uses Default for LIKE/ILIKE match indexes - Add reusable test:integration:setup:tls task for standalone test runs - Update EQL version to 2.2.1 in example config
1 parent 81c73de commit d580373

4 files changed

Lines changed: 73 additions & 15 deletions

File tree

mise.local.example.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ CS_CLIENT_KEY = "client-key"
1515
CS_CLIENT_ID = "client-id"
1616

1717
# The release of EQL that the proxy tests will use and releases will be built with
18-
CS_EQL_VERSION = "eql-2.1.8"
18+
CS_EQL_VERSION = "eql-2.2.1"
1919

2020
# TLS variables are required for providing TLS to Proxy's clients.
2121
# CS_TLS__TYPE can be either "Path" or "Pem" (case-sensitive).

mise.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ description = "Run psql (interactively) against the proxy; assumes the proxy is
108108
alias = "psql"
109109
run = """
110110
set -eu
111+
echo psql "postgresql://${CS_DATABASE__USERNAME}:${CS_DATABASE__PASSWORD_ESCAPED_FOR_TESTS}@${CS_PROXY__HOST}:6432/${CS_DATABASE__NAME}"
111112
docker exec -it postgres${CONTAINER_SUFFIX:-} psql "postgresql://${CS_DATABASE__USERNAME}:${CS_DATABASE__PASSWORD_ESCAPED_FOR_TESTS}@${CS_PROXY__HOST}:6432/${CS_DATABASE__NAME}"
112113
"""
113114

@@ -174,7 +175,39 @@ run = """
174175
cargo nextest run --no-fail-fast --nocapture -p cipherstash-proxy-integration
175176
"""
176177

178+
[tasks."test:integration:setup:tls"]
179+
description = "Setup for TLS integration tests: preflight, postgres, proxy"
180+
run = """
181+
set -e
182+
183+
echo
184+
echo '###############################################'
185+
echo '# Preflight'
186+
echo '###############################################'
187+
echo
188+
189+
mise run test:integration:preflight
190+
191+
echo
192+
echo '###############################################'
193+
echo '# Setup'
194+
echo '###############################################'
195+
echo
196+
197+
mise --env tls run postgres:setup
198+
199+
echo
200+
echo '###############################################'
201+
echo '# Start Proxy'
202+
echo '###############################################'
203+
echo
204+
205+
mise --env tls run proxy:up proxy-tls --extra-args "--detach --wait"
206+
mise --env tls run test:wait_for_postgres_to_quack --port 6432 --max-retries 20 --tls
207+
"""
208+
177209
[tasks."test:integration:without_multitenant"]
210+
depends = ["test:integration:setup:tls"]
178211
description = "Runs integration tests excluding multitenant"
179212
run = """
180213
cargo nextest run --no-fail-fast --nocapture -E 'package(cipherstash-proxy-integration) and not test(multitenant)'
@@ -494,6 +527,7 @@ echo docker compose up --build {{arg(name="service",default="postgres postgres-t
494527
description = "Run psql (interactively) against the Postgres instance; assumes Postgres is already up"
495528
run = """
496529
set -eu
530+
echo "postgresql://${CS_DATABASE__USERNAME}:${CS_DATABASE__PASSWORD_ESCAPED_FOR_TESTS}@${CS_DATABASE__HOST}:${CS_DATABASE__PORT}/${CS_DATABASE__NAME}"
497531
docker exec -it postgres${CONTAINER_SUFFIX:-} psql "postgresql://${CS_DATABASE__USERNAME}:${CS_DATABASE__PASSWORD_ESCAPED_FOR_TESTS}@${CS_DATABASE__HOST}:${CS_DATABASE__PORT}/${CS_DATABASE__NAME}"
498532
"""
499533

packages/cipherstash-proxy/src/postgresql/frontend.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,8 @@ where
255255
msg = "Bind Error",
256256
err = err.to_string()
257257
);
258-
return Err(err);
258+
self.send_error_response(err)?;
259+
return Ok(());
259260
}
260261
},
261262
}

packages/cipherstash-proxy/src/proxy/zerokms/zerokms.rs

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,13 @@ use crate::{
77
proxy::EncryptionService,
88
};
99
use cipherstash_client::{
10-
encryption::Plaintext,
10+
encryption::{Plaintext, QueryOp},
1111
eql::{
1212
decrypt_eql, encrypt_eql, EqlCiphertext, EqlDecryptOpts, EqlEncryptOpts, EqlOperation,
1313
PreparedPlaintext,
1414
},
1515
};
16+
use eql_mapper::EqlTermVariant;
1617
use metrics::counter;
1718
use moka::future::Cache;
1819
use std::borrow::Cow;
@@ -152,18 +153,36 @@ impl EncryptionService for ZeroKms {
152153

153154
for (idx, (plaintext_opt, col_opt)) in plaintexts.iter().zip(columns.iter()).enumerate() {
154155
if let (Some(plaintext), Some(col)) = (plaintext_opt, col_opt) {
155-
let eql_op = if col.is_encryptable() {
156-
EqlOperation::Store
157-
} else {
158-
// For search-only, we need to get the index type from the column config
159-
// and use Query operation
160-
if let Some(index) = col.config.indexes.first() {
161-
EqlOperation::Query(
162-
&index.index_type,
163-
cipherstash_client::encryption::QueryOp::Default,
164-
)
165-
} else {
166-
EqlOperation::Store
156+
// Determine the EQL operation based on the term variant
157+
let eql_op = match col.eql_term {
158+
// Full and Partial terms store encrypted data with all indexes
159+
EqlTermVariant::Full | EqlTermVariant::Partial => EqlOperation::Store,
160+
161+
// JsonPath generates a selector term for SteVec queries
162+
EqlTermVariant::JsonPath => {
163+
if let Some(index) = col.config.indexes.first() {
164+
EqlOperation::Query(&index.index_type, QueryOp::SteVecSelector)
165+
} else {
166+
EqlOperation::Store
167+
}
168+
}
169+
170+
// JsonAccessor generates a selector term for SteVec field access (-> operator)
171+
EqlTermVariant::JsonAccessor => {
172+
if let Some(index) = col.config.indexes.first() {
173+
EqlOperation::Query(&index.index_type, QueryOp::SteVecSelector)
174+
} else {
175+
EqlOperation::Store
176+
}
177+
}
178+
179+
// Tokenized generates match index terms for LIKE/ILIKE
180+
EqlTermVariant::Tokenized => {
181+
if let Some(index) = col.config.indexes.first() {
182+
EqlOperation::Query(&index.index_type, QueryOp::Default)
183+
} else {
184+
EqlOperation::Store
185+
}
167186
}
168187
};
169188

@@ -186,9 +205,11 @@ impl EncryptionService for ZeroKms {
186205
// Use default opts since cipher is already initialized with the correct keyset
187206
let opts = EqlEncryptOpts::default();
188207

208+
debug!(target: ENCRYPT, msg="Calling encrypt_eql", count = prepared_plaintexts.len());
189209
let encrypted = encrypt_eql(cipher, prepared_plaintexts, &opts)
190210
.await
191211
.map_err(EncryptError::from)?;
212+
debug!(target: ENCRYPT, msg="encrypt_eql completed", count = encrypted.len());
192213

193214
// Reconstruct the result vector with None values in the right places
194215
let mut result: Vec<Option<EqlCiphertext>> = vec![None; plaintexts.len()];
@@ -237,9 +258,11 @@ impl EncryptionService for ZeroKms {
237258
// Use default opts since cipher is already initialized with the correct keyset
238259
let opts = EqlDecryptOpts::default();
239260

261+
debug!(target: ENCRYPT, msg="Calling decrypt_eql", count = ciphertexts_to_decrypt.len());
240262
let decrypted = decrypt_eql(cipher, ciphertexts_to_decrypt, &opts)
241263
.await
242264
.map_err(EncryptError::from)?;
265+
debug!(target: ENCRYPT, msg="decrypt_eql completed", count = decrypted.len());
243266

244267
// Reconstruct the result vector with None values in the right places
245268
let mut result: Vec<Option<Plaintext>> = vec![None; ciphertexts.len()];

0 commit comments

Comments
 (0)