Skip to content

Commit dbd287b

Browse files
Tighten OpenSecret startup configuration error handling (#1029)
* Tighten OpenSecret startup configuration errors * Keep bootstrap parser diagnostics value-free
1 parent 1918b86 commit dbd287b

4 files changed

Lines changed: 121 additions & 68 deletions

File tree

‎services/opensecret/entrypoint.sh‎

Lines changed: 34 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -190,9 +190,8 @@ vsock_request() {
190190
response=$(python3 /app/vsock_helper.py "$cid" "$port" "$request")
191191

192192
# Check if the response contains an error
193-
if echo "$response" | jq -e 'has("error")' > /dev/null; then
194-
error_message=$(echo "$response" | jq -r '.error')
195-
log "VSOCK request failed: $error_message"
193+
if echo "$response" | jq -e 'has("error")' > /dev/null 2>&1; then
194+
log "VSOCK request failed"
196195
return 1
197196
fi
198197

@@ -287,51 +286,44 @@ get_tinfoil_proxy_api_key_secret() {
287286
}
288287

289288
# Get AWS credentials
290-
log "Fetching AWS credentials"
291289
aws_creds=$(get_aws_credentials)
292290
if [ -z "$aws_creds" ]; then
293291
log "Error: Failed to get AWS credentials"
294292
exit 1
295293
fi
296294

297295
# Add error checking for jq parsing
298-
if ! access_key_id=$(echo "$aws_creds" | jq -r '.response_value.AccessKeyId'); then
296+
if ! access_key_id=$(echo "$aws_creds" | jq -r '.response_value.AccessKeyId' 2>/dev/null); then
299297
log "Error: Failed to parse AccessKeyId from AWS credentials"
300-
log "AWS credentials response: $aws_creds"
301298
exit 1
302299
fi
303-
if ! secret_access_key=$(echo "$aws_creds" | jq -r '.response_value.SecretAccessKey'); then
300+
if ! secret_access_key=$(echo "$aws_creds" | jq -r '.response_value.SecretAccessKey' 2>/dev/null); then
304301
log "Error: Failed to parse SecretAccessKey from AWS credentials"
305302
exit 1
306303
fi
307-
if ! session_token=$(echo "$aws_creds" | jq -r '.response_value.Token'); then
304+
if ! session_token=$(echo "$aws_creds" | jq -r '.response_value.Token' 2>/dev/null); then
308305
log "Error: Failed to parse Token from AWS credentials"
309306
exit 1
310307
fi
311-
if ! region=$(echo "$aws_creds" | jq -r '.response_value.Region'); then
308+
if ! region=$(echo "$aws_creds" | jq -r '.response_value.Region' 2>/dev/null); then
312309
log "Error: Failed to parse Region from AWS credentials"
313310
exit 1
314311
fi
315312

316-
log "AWS credentials retrieved and parsed successfully"
317-
318313
# Get encrypted database URL from Secrets Manager
319-
log "Fetching encrypted database URL"
320314
secret_response=$(get_database_url_secret)
321-
log "Retrieved raw secret response"
322315

323316
# Extract the database_url value from the JSON structure
324-
encrypted_db_url=$(echo "$secret_response" | jq -r '.response_value | fromjson | .database_url')
317+
if ! encrypted_db_url=$(echo "$secret_response" | jq -r '.response_value | fromjson | .database_url' 2>/dev/null); then
318+
log "Error: Failed to get encrypted database URL"
319+
exit 1
320+
fi
325321
if [ -z "$encrypted_db_url" ]; then
326322
log "Error: Failed to get encrypted database URL"
327-
log "Secret response: $secret_response"
328323
exit 1
329324
fi
330325

331-
log "Encrypted database URL retrieved successfully"
332-
333326
# Decrypt the database URL using kmstool_enclave_cli
334-
log "Decrypting database URL"
335327
decryption_output=$(kmstool_enclave_cli decrypt \
336328
--region "$region" \
337329
--proxy-port 8000 \
@@ -340,20 +332,18 @@ decryption_output=$(kmstool_enclave_cli decrypt \
340332
--aws-session-token "$session_token" \
341333
--ciphertext "$encrypted_db_url" 2>&1)
342334

343-
log "Got decryption output, parsing URL"
344-
345335
decrypted_db_url=$(echo "$decryption_output" | sed -n 's/PLAINTEXT: //p')
346336

347337
if [ -z "$decrypted_db_url" ]; then
348338
log "Error: Failed to decrypt database URL"
349-
log "Decryption output: $decryption_output"
350339
exit 1
351340
fi
352341

353-
log "Database URL decrypted successfully"
354-
355342
# Decode the base64 decrypted URL
356-
decoded_db_url=$(echo "$decrypted_db_url" | base64 -d)
343+
if ! decoded_db_url=$(echo "$decrypted_db_url" | base64 -d 2>/dev/null); then
344+
log "Error: Failed to decode base64 database URL"
345+
exit 1
346+
fi
357347

358348
if [ -z "$decoded_db_url" ]; then
359349
log "Error: Failed to decode base64 database URL"
@@ -368,7 +358,6 @@ if [ -z "$DB_HOSTNAME" ]; then
368358
fi
369359

370360
echo "127.0.0.1 $DB_HOSTNAME" >> /etc/hosts
371-
log "Added $DB_HOSTNAME to /etc/hosts"
372361

373362
# Add OpenAI API hostname to /etc/hosts
374363
echo "127.0.0.1 api.openai.com" >> /etc/hosts
@@ -459,7 +448,6 @@ log "Created /app/libnsm.so"
459448
log "Network configuration:"
460449
ip addr show
461450
ip route
462-
cat /etc/hosts
463451

464452
# Start the traffic forwarder for the database in the background
465453
log "Starting database traffic forwarder"
@@ -866,27 +854,25 @@ fi
866854
# Start the continuum-proxy if we're in AWS Nitro mode
867855
if [ "$APP_MODE" != "local" ]; then
868856
# Get Continuum Proxy API key from Secrets Manager
869-
log "Fetching Continuum Proxy API key"
870857
continuum_proxy_api_key_response=$(get_continuum_proxy_api_key_secret)
871-
log "Retrieved raw Continuum Proxy API key response"
872858

873859
# Check if the response is an error
874-
if echo "$continuum_proxy_api_key_response" | jq -e '.response_type == "error"' > /dev/null; then
875-
error_message=$(echo "$continuum_proxy_api_key_response" | jq -r '.response_value')
876-
log "Error: Failed to get Continuum Proxy API key. Error message: $error_message"
860+
if echo "$continuum_proxy_api_key_response" | jq -e '.response_type == "error"' > /dev/null 2>&1; then
861+
log "Error: Failed to get Continuum Proxy API key"
877862
exit 1
878863
fi
879864

880865
# Extract the encrypted API key value from the JSON structure
881-
continuum_proxy_api_key_encrypted=$(echo "$continuum_proxy_api_key_response" | jq -r '.response_value | fromjson | .api_key')
866+
if ! continuum_proxy_api_key_encrypted=$(echo "$continuum_proxy_api_key_response" | jq -r '.response_value | fromjson | .api_key' 2>/dev/null); then
867+
log "Error: Failed to extract Continuum Proxy API key from the response"
868+
exit 1
869+
fi
882870
if [ -z "$continuum_proxy_api_key_encrypted" ]; then
883871
log "Error: Failed to extract Continuum Proxy API key from the response"
884-
log "Secret response: $continuum_proxy_api_key_response"
885872
exit 1
886873
fi
887874

888875
# Decrypt the API key using kmstool_enclave_cli
889-
log "Decrypting Continuum Proxy API key"
890876
decryption_output=$(kmstool_enclave_cli decrypt \
891877
--region "$region" \
892878
--proxy-port 8000 \
@@ -899,20 +885,20 @@ if [ "$APP_MODE" != "local" ]; then
899885

900886
if [ -z "$decrypted_api_key" ]; then
901887
log "Error: Failed to decrypt Continuum Proxy API key"
902-
log "Decryption output: $decryption_output"
903888
exit 1
904889
fi
905890

906891
# Base64 decode the decrypted API key
907-
continuum_proxy_api_key=$(echo "$decrypted_api_key" | base64 -d)
892+
if ! continuum_proxy_api_key=$(echo "$decrypted_api_key" | base64 -d 2>/dev/null); then
893+
log "Error: Failed to base64 decode Continuum Proxy API key"
894+
exit 1
895+
fi
908896

909897
if [ -z "$continuum_proxy_api_key" ]; then
910898
log "Error: Failed to base64 decode Continuum Proxy API key"
911899
exit 1
912900
fi
913901

914-
log "Continuum Proxy API key retrieved, decrypted, and decoded successfully"
915-
916902
log "Starting continuum-proxy on port 8092 with user-scoped salts and per-request fallback"
917903
run_forever continuum_proxy /app/continuum-proxy --port 8092 --apiKey "$continuum_proxy_api_key" &
918904

@@ -923,27 +909,25 @@ if [ "$APP_MODE" != "local" ]; then
923909
export OPENAI_API_BASE="http://127.0.0.1:8092"
924910

925911
# Get Tinfoil Proxy API key from Secrets Manager
926-
log "Fetching Tinfoil Proxy API key"
927912
tinfoil_proxy_api_key_response=$(get_tinfoil_proxy_api_key_secret)
928-
log "Retrieved raw Tinfoil Proxy API key response"
929913

930914
# Check if the response is an error
931-
if echo "$tinfoil_proxy_api_key_response" | jq -e '.response_type == "error"' > /dev/null; then
932-
error_message=$(echo "$tinfoil_proxy_api_key_response" | jq -r '.response_value')
933-
log "Error: Failed to get Tinfoil Proxy API key. Error message: $error_message"
915+
if echo "$tinfoil_proxy_api_key_response" | jq -e '.response_type == "error"' > /dev/null 2>&1; then
916+
log "Error: Failed to get Tinfoil Proxy API key"
934917
exit 1
935918
fi
936919

937920
# Extract the encrypted API key value from the JSON structure
938-
tinfoil_proxy_api_key_encrypted=$(echo "$tinfoil_proxy_api_key_response" | jq -r '.response_value | fromjson | .api_key')
921+
if ! tinfoil_proxy_api_key_encrypted=$(echo "$tinfoil_proxy_api_key_response" | jq -r '.response_value | fromjson | .api_key' 2>/dev/null); then
922+
log "Error: Failed to extract Tinfoil Proxy API key from the response"
923+
exit 1
924+
fi
939925
if [ -z "$tinfoil_proxy_api_key_encrypted" ]; then
940926
log "Error: Failed to extract Tinfoil Proxy API key from the response"
941-
log "Secret response: $tinfoil_proxy_api_key_response"
942927
exit 1
943928
fi
944929

945930
# Decrypt the API key using kmstool_enclave_cli
946-
log "Decrypting Tinfoil Proxy API key"
947931
decryption_output=$(kmstool_enclave_cli decrypt \
948932
--region "$region" \
949933
--proxy-port 8000 \
@@ -956,19 +940,20 @@ if [ "$APP_MODE" != "local" ]; then
956940

957941
if [ -z "$decrypted_api_key" ]; then
958942
log "Error: Failed to decrypt Tinfoil Proxy API key"
959-
log "Decryption output: $decryption_output"
960943
exit 1
961944
fi
962945

963946
# Base64 decode the decrypted API key
964-
tinfoil_proxy_api_key=$(echo "$decrypted_api_key" | base64 -d)
947+
if ! tinfoil_proxy_api_key=$(echo "$decrypted_api_key" | base64 -d 2>/dev/null); then
948+
log "Error: Failed to base64 decode Tinfoil Proxy API key"
949+
exit 1
950+
fi
965951

966952
if [ -z "$tinfoil_proxy_api_key" ]; then
967953
log "Error: Failed to base64 decode Tinfoil Proxy API key"
968954
exit 1
969955
fi
970956

971-
log "Tinfoil Proxy API key retrieved, decrypted, and decoded successfully"
972957
else
973958
# For local mode, use the default OpenAI API base or the one set in the environment
974959
export OPENAI_API_BASE=${OPENAI_API_BASE:-"https://api.openai.com"}

‎services/opensecret/nitro-toolkit/vsock_helper.py‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,9 @@ def vsock_request(cid, port, request, max_retries=5, retry_delay=10, initial_del
5151
decoded_response = response.decode()
5252
print(f"Decoded response length: {len(decoded_response)}", file=sys.stderr)
5353
return decoded_response
54-
except UnicodeDecodeError as e:
55-
print(f"Error decoding response: {e}", file=sys.stderr)
56-
print(f"Raw response: {response}", file=sys.stderr)
57-
raise
54+
except UnicodeDecodeError:
55+
print("Invalid VSOCK response encoding", file=sys.stderr)
56+
return json.dumps({"error": "Invalid VSOCK response encoding"})
5857
else:
5958
print("No response received, retrying...", file=sys.stderr)
6059
continue

‎services/opensecret/src/encrypt.rs‎

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -363,9 +363,7 @@ pub fn decrypt_with_kms(
363363
})?;
364364

365365
if !output.status.success() {
366-
let stderr = String::from_utf8_lossy(&output.stderr);
367-
tracing::error!("kmstool_enclave_cli decryption failed: {}", stderr);
368-
return Err(EncryptError::KmsError(stderr.to_string()));
366+
return Err(EncryptError::KmsError("kmstool decrypt failed".to_string()));
369367
}
370368

371369
let output_str =
@@ -512,9 +510,7 @@ pub fn create_new_encryption_key(
512510
})?;
513511

514512
if !output.status.success() {
515-
let stderr = String::from_utf8_lossy(&output.stderr);
516-
tracing::error!("kmstool_enclave_cli failed: {}", stderr);
517-
return Err(EncryptError::KmsError(stderr.to_string()));
513+
return Err(EncryptError::KmsError("kmstool genkey failed".to_string()));
518514
}
519515

520516
let output_str =
@@ -590,12 +586,9 @@ pub async fn generate_random_bytes_from_enclave(
590586
})?;
591587

592588
if !output.status.success() {
593-
let stderr = String::from_utf8_lossy(&output.stderr);
594-
tracing::error!(
595-
"kmstool_enclave_cli random byte generation failed: {}",
596-
stderr
597-
);
598-
return Err(EncryptError::KmsError(stderr.to_string()));
589+
return Err(EncryptError::KmsError(
590+
"kmstool genrandom failed".to_string(),
591+
));
599592
}
600593

601594
let output_str =

‎services/opensecret/src/main.rs‎

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3049,6 +3049,79 @@ async fn get_secret(key_name: &str) -> Result<String, Error> {
30493049
}
30503050
}
30513051

3052+
fn decode_decrypted_url(
3053+
bytes: Vec<u8>,
3054+
allowed_schemes: &[&str],
3055+
error_message: &'static str,
3056+
) -> Result<String, Error> {
3057+
let invalid_url = || Error::EncryptionError(error_message.to_string());
3058+
let value = String::from_utf8(bytes).map_err(|_| invalid_url())?;
3059+
let parsed = url::Url::parse(&value).map_err(|_| invalid_url())?;
3060+
if parsed.host().is_none() || !allowed_schemes.contains(&parsed.scheme()) {
3061+
return Err(invalid_url());
3062+
}
3063+
Ok(value)
3064+
}
3065+
3066+
#[cfg(test)]
3067+
mod decrypted_url_tests {
3068+
use super::*;
3069+
3070+
#[test]
3071+
fn accepts_supported_configuration_urls() {
3072+
for database in [
3073+
"postgres://user:password@db.example.test/app",
3074+
"postgresql://user:password@db.example.test/app",
3075+
] {
3076+
assert_eq!(
3077+
decode_decrypted_url(
3078+
database.as_bytes().to_vec(),
3079+
&["postgres", "postgresql"],
3080+
"Invalid database URL",
3081+
)
3082+
.unwrap(),
3083+
database
3084+
);
3085+
}
3086+
3087+
let queue = "https://sqs.example.test/queue";
3088+
assert_eq!(
3089+
decode_decrypted_url(
3090+
queue.as_bytes().to_vec(),
3091+
&["http", "https"],
3092+
"Invalid SQS queue URL",
3093+
)
3094+
.unwrap(),
3095+
queue
3096+
);
3097+
}
3098+
3099+
#[test]
3100+
fn rejects_invalid_configuration_urls_without_echoing_them() {
3101+
for (schemes, message, malformed_url) in [
3102+
(
3103+
&["postgres", "postgresql"][..],
3104+
"Invalid database URL",
3105+
&b"postgres://fixture-secret .example.test/app"[..],
3106+
),
3107+
(
3108+
&["http", "https"][..],
3109+
"Invalid SQS queue URL",
3110+
&b"https://fixture-secret .example.test/queue"[..],
3111+
),
3112+
] {
3113+
for bytes in [b"fixture-secret-\xff".to_vec(), malformed_url.to_vec()] {
3114+
let error = decode_decrypted_url(bytes, schemes, message).unwrap_err();
3115+
assert_eq!(error.to_string(), format!("Encryption error: {message}"));
3116+
assert_eq!(
3117+
format!("{error:?}"),
3118+
format!("EncryptionError({message:?})")
3119+
);
3120+
}
3121+
}
3122+
}
3123+
}
3124+
30523125
async fn get_or_create_enclave_key(
30533126
app_mode: &AppMode,
30543127
aws_credential_manager: Arc<tokio::sync::RwLock<Option<AwsCredentialManager>>>,
@@ -3968,7 +4041,6 @@ async fn main() -> Result<(), Error> {
39684041
.await
39694042
.expect("should have just waited for credentials");
39704043

3971-
tracing::info!("Retrieved and decrypting database URL from Secrets Manager");
39724044
let url_vec = decrypt_with_kms(
39734045
&creds.region,
39744046
&creds.access_key_id,
@@ -3981,7 +4053,7 @@ async fn main() -> Result<(), Error> {
39814053
Error::EncryptionError(e.to_string())
39824054
})?;
39834055

3984-
String::from_utf8(url_vec).expect("should parse url")
4056+
decode_decrypted_url(url_vec, &["postgres", "postgresql"], "Invalid database URL")?
39854057
}
39864058
Err(e) => {
39874059
tracing::error!(
@@ -4123,7 +4195,11 @@ async fn main() -> Result<(), Error> {
41234195
Error::EncryptionError(e.to_string())
41244196
})?;
41254197

4126-
Some(String::from_utf8(url_vec).expect("should parse url"))
4198+
Some(decode_decrypted_url(
4199+
url_vec,
4200+
&["http", "https"],
4201+
"Invalid SQS queue URL",
4202+
)?)
41274203
} else {
41284204
// URL not found in database - this is optional so we'll return None
41294205
None

0 commit comments

Comments
 (0)