From c358d848b9a6fb332dd1d2fe5423315a32465230 Mon Sep 17 00:00:00 2001 From: Logic Date: Thu, 30 Jul 2026 16:13:00 +0800 Subject: [PATCH 1/2] maintenance: limit SMS client request logging --- .../service/impl/AlibabaSmsClientImpl.java | 9 +- .../alert/service/impl/AwsSmsClientImpl.java | 14 +-- .../alert/service/impl/UniSmsClientImpl.java | 12 +- .../service/impl/SmsClientLoggingTest.java | 103 ++++++++++++++++++ 4 files changed, 119 insertions(+), 19 deletions(-) create mode 100644 hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java index 34b6ebe1d03..47157d03a98 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java @@ -37,7 +37,6 @@ import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; -import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -154,14 +153,14 @@ private void sendSms(String phoneNumber, String templateParam) { httpPost.setHeader("x-acs-content-sha256", CryptoUtils.sha256Hex("")); - log.info("Sending Alibaba SMS request to {}", url + ", params: " + templateParam + "headers: " + Arrays.toString(httpPost.getAllHeaders())); + log.debug("Sending SMS request via Alibaba Cloud"); // Send request and handle response try (CloseableHttpResponse response = httpClient.execute(httpPost)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.info("SMS response status: {}, body: {}", statusCode, responseBody); + log.debug("Alibaba Cloud SMS response status: {}", statusCode); if (statusCode != 200) { throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); @@ -174,10 +173,10 @@ private void sendSms(String phoneNumber, String templateParam) { throw new SendMessageException(code + ":" + message); } - log.info("Successfully sent SMS to phone: {}", phoneNumber); + log.info("Successfully sent SMS via Alibaba Cloud"); } } catch (Exception e) { - LogUtil.warn(logger, "Failed to send SMS: {0}", e.getMessage()); + LogUtil.warn(logger, "Failed to send SMS via Alibaba Cloud"); throw new SendMessageException(e.getMessage()); } } diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java index 6e9dd601c6c..376dac55ce0 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java @@ -40,7 +40,6 @@ import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; @@ -116,10 +115,10 @@ private void send(String phoneNumber, String message) { URI requestUri = new URI(endpoint); HttpPost httpPost = createHttpPost(requestUri, amzDate, payloadInString); - log.info("Sending AWS SMS request to {}", requestUri + "," + "headers: " + Arrays.toString(httpPost.getAllHeaders())); - executeRequest(httpClient, httpPost, phoneNumber); + log.debug("Sending SMS request via AWS"); + executeRequest(httpClient, httpPost); } catch (Exception e) { - log.warn("Failed to send SMS: {}", e.getMessage()); + log.warn("Failed to send SMS via AWS"); throw new SendMessageException(e.getMessage()); } } @@ -149,11 +148,11 @@ private HttpPost createHttpPost(URI requestUri, String amzDate, String payloadIn return httpPost; } - private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber) throws Exception { + private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost) throws Exception { try (CloseableHttpResponse response = httpClient.execute(httpPost)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.info("SMS response status: {}, body: {}", statusCode, responseBody); + log.debug("AWS SMS response status: {}", statusCode); if (statusCode != 200) { throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); @@ -170,7 +169,7 @@ private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, S } String messageId = responseNode.asText(); - log.info("Successfully sent SMS to phone: {}, messageId: {}", phoneNumber, messageId); + log.info("Successfully sent SMS via AWS, messageId: {}", messageId); } } @@ -286,4 +285,3 @@ private byte[] getSignatureKey(String key, String dateStamp, String regionName, } } - diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java index d4a414e12cc..2ad8e29451c 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java @@ -102,13 +102,13 @@ public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, String payload = JsonUtil.toJson(params); httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8)); - log.info("Sending SMS request to UniSMS, payload: {}, url: {}", payload, url); + log.debug("Sending SMS request via UniSMS"); try (CloseableHttpResponse response = httpClient.execute(httpPost)) { - handleResponse(response, receiver.getPhone()); + handleResponse(response); } } catch (Exception e) { - log.error("Failed to send SMS via UniSMS: {}", e.getMessage()); + log.error("Failed to send SMS via UniSMS"); throw new SendMessageException(e.getMessage()); } } @@ -145,11 +145,11 @@ private String generateNonce() { return UUID.randomUUID().toString().replace("-", "").substring(0, 16); } - private void handleResponse(CloseableHttpResponse response, String phone) throws IOException { + private void handleResponse(CloseableHttpResponse response) throws IOException { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.info("UniSMS response status: {}, body: {}", statusCode, responseBody); + log.debug("UniSMS response status: {}", statusCode); if (statusCode != 200) { throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); @@ -162,7 +162,7 @@ private void handleResponse(CloseableHttpResponse response, String phone) throws throw new SendMessageException(code + ":" + message); } - log.info("Successfully sent SMS to phone: {}", phone); + log.info("Successfully sent SMS via UniSMS"); } @Override diff --git a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java new file mode 100644 index 00000000000..a3f4170f403 --- /dev/null +++ b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.alert.service.impl; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +import java.util.Map; +import org.apache.hertzbeat.common.entity.alerter.GroupAlert; +import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver; +import org.apache.hertzbeat.common.entity.dto.sms.AlibabaSmsProperties; +import org.apache.hertzbeat.common.entity.dto.sms.AwsSmsProperties; +import org.apache.hertzbeat.common.entity.dto.sms.UniSmsProperties; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.springframework.boot.test.system.CapturedOutput; +import org.springframework.boot.test.system.OutputCaptureExtension; + +/** + * Verifies that SMS clients do not write request credentials or message data to logs. + */ +@ExtendWith(OutputCaptureExtension.class) +class SmsClientLoggingTest { + + private static final String ACCESS_KEY = "access-key-log-sentinel"; + private static final String PHONE = "15555550123"; + private static final String ALERT_CONTENT = "alert-content-log-sentinel"; + + @Test + void requestCredentialsAndMessageDataShouldNotBeLogged(CapturedOutput output) throws Exception { + NoticeReceiver receiver = new NoticeReceiver(); + receiver.setPhone(PHONE); + GroupAlert alert = new GroupAlert(); + alert.setGroupKey("instance"); + alert.setCommonLabels(Map.of()); + alert.setCommonAnnotations(Map.of("summary", ALERT_CONTENT)); + + AwsSmsProperties awsProperties = new AwsSmsProperties(); + awsProperties.setAccessKeyId(ACCESS_KEY); + awsProperties.setAccessKeySecret("aws-secret"); + awsProperties.setRegion("us-east-1"); + withSuccessfulResponse("{\"MessageId\":\"message-id\"}", + () -> new AwsSmsClientImpl(awsProperties).sendMessage(receiver, null, alert)); + + AlibabaSmsProperties alibabaProperties = + new AlibabaSmsProperties(ACCESS_KEY, "alibaba-secret", "sign", "template"); + withSuccessfulResponse("{\"Code\":\"OK\"}", + () -> new AlibabaSmsClientImpl(alibabaProperties).sendMessage(receiver, null, alert)); + + UniSmsProperties uniProperties = + new UniSmsProperties(ACCESS_KEY, "unisms-secret", "sign", "template", "hmac"); + withSuccessfulResponse("{\"code\":\"0\"}", + () -> new UniSmsClientImpl(uniProperties).sendMessage(receiver, null, alert)); + + String logs = output.getAll(); + assertFalse(logs.contains(ACCESS_KEY)); + assertFalse(logs.contains(PHONE)); + assertFalse(logs.contains(ALERT_CONTENT)); + assertFalse(logs.contains("Authorization")); + assertFalse(logs.contains("Signature=")); + } + + private void withSuccessfulResponse(String responseBody, Runnable operation) throws Exception { + CloseableHttpClient httpClient = mock(CloseableHttpClient.class); + CloseableHttpResponse response = mock(CloseableHttpResponse.class); + StatusLine statusLine = mock(StatusLine.class); + when(statusLine.getStatusCode()).thenReturn(200); + when(response.getStatusLine()).thenReturn(statusLine); + when(response.getEntity()).thenReturn(new StringEntity(responseBody, ContentType.APPLICATION_JSON)); + when(httpClient.execute(any(HttpPost.class))).thenReturn(response); + + try (MockedStatic httpClients = mockStatic(HttpClients.class)) { + httpClients.when(HttpClients::createDefault).thenReturn(httpClient); + operation.run(); + } + } +} From 2df2be3402ab97c8d528c36f26305440e5d3228a Mon Sep 17 00:00:00 2001 From: Logic Date: Thu, 30 Jul 2026 23:13:39 +0800 Subject: [PATCH 2/2] maintenance: bound SMS failure details --- .../service/impl/AlibabaSmsClientImpl.java | 23 +- .../alert/service/impl/AwsSmsClientImpl.java | 17 +- .../service/impl/SmsFailureMessages.java | 55 +++++ .../service/impl/SmsLocalSmsClientImpl.java | 34 +-- .../service/impl/TencentSmsClientImpl.java | 47 ++-- .../service/impl/TwilioSmsClientImpl.java | 37 ++- .../alert/service/impl/UniSmsClientImpl.java | 15 +- .../service/impl/SmsClientLoggingTest.java | 211 +++++++++++++++++- 8 files changed, 360 insertions(+), 79 deletions(-) create mode 100644 hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsFailureMessages.java diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java index 47157d03a98..1876d539401 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AlibabaSmsClientImpl.java @@ -26,14 +26,11 @@ import org.apache.hertzbeat.common.entity.alerter.NoticeTemplate; import org.apache.hertzbeat.common.support.exception.SendMessageException; import org.apache.hertzbeat.common.util.JsonUtil; -import org.apache.hertzbeat.common.util.LogUtil; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; import org.apache.http.util.EntityUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; @@ -64,7 +61,6 @@ public class AlibabaSmsClientImpl implements SmsClient { private final String accessKeySecret; private final String signName; private final String templateCode; - private static final Logger logger = LoggerFactory.getLogger(AlibabaSmsClientImpl.class); public AlibabaSmsClientImpl(AlibabaSmsProperties config) { if (config != null) { @@ -163,21 +159,27 @@ private void sendSms(String phoneNumber, String templateParam) { log.debug("Alibaba Cloud SMS response status: {}", statusCode); if (statusCode != 200) { - throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); + throw SmsFailureMessages.httpStatus("Alibaba Cloud SMS", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); + if (jsonResponse == null || jsonResponse.get("Code") == null) { + throw SmsFailureMessages.invalidResponse("Alibaba Cloud SMS"); + } String code = jsonResponse.get("Code").asText(); if (!"OK".equals(code)) { - String message = jsonResponse.get("Message").asText(); - throw new SendMessageException(code + ":" + message); + throw SmsFailureMessages.providerCode("Alibaba Cloud SMS", code); } log.info("Successfully sent SMS via Alibaba Cloud"); } + } catch (SendMessageException e) { + log.warn("Failed to send SMS via Alibaba Cloud"); + throw e; } catch (Exception e) { - LogUtil.warn(logger, "Failed to send SMS via Alibaba Cloud"); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to send SMS via Alibaba Cloud, failure type: {}", + e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("Alibaba Cloud SMS"); } } @@ -195,7 +197,8 @@ private String calculateAuthorization(String canonicalQueryString, String timest // Step 4: Build authorization header return ALGORITHM + " Credential=" + accessKeyId + ",SignedHeaders=host;x-acs-action;x-acs-content-sha256;x-acs-date;" + "x-acs-signature-nonce;x-acs-version,Signature=" + signature; } catch (Exception e) { - LogUtil.warn(logger, "Failed to calculate authorization {0}", e.getMessage()); + log.warn("Failed to calculate Alibaba Cloud authorization, failure type: {}", + e.getClass().getSimpleName()); throw new RuntimeException("Failed to calculate authorization", e); } } diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java index 376dac55ce0..e019d8ff8c2 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/AwsSmsClientImpl.java @@ -117,9 +117,12 @@ private void send(String phoneNumber, String message) { HttpPost httpPost = createHttpPost(requestUri, amzDate, payloadInString); log.debug("Sending SMS request via AWS"); executeRequest(httpClient, httpPost); - } catch (Exception e) { + } catch (SendMessageException e) { log.warn("Failed to send SMS via AWS"); - throw new SendMessageException(e.getMessage()); + throw e; + } catch (Exception e) { + log.warn("Failed to send SMS via AWS, failure type: {}", e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("AWS SMS"); } } @@ -155,21 +158,20 @@ private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost) t log.debug("AWS SMS response status: {}", statusCode); if (statusCode != 200) { - throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); + throw SmsFailureMessages.httpStatus("AWS SMS", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); if (jsonResponse == null) { - throw new SendMessageException(statusCode + ":" + responseBody); + throw SmsFailureMessages.invalidResponse("AWS SMS"); } JsonNode responseNode = jsonResponse.get("MessageId"); if (responseNode == null) { - throw new SendMessageException(statusCode + ":" + responseBody); + throw SmsFailureMessages.invalidResponse("AWS SMS"); } - String messageId = responseNode.asText(); - log.info("Successfully sent SMS via AWS, messageId: {}", messageId); + log.info("Successfully sent SMS via AWS"); } } @@ -284,4 +286,3 @@ private byte[] getSignatureKey(String key, String dateStamp, String regionName, } } - diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsFailureMessages.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsFailureMessages.java new file mode 100644 index 00000000000..3d09dddb832 --- /dev/null +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsFailureMessages.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.hertzbeat.alert.service.impl; + +import java.util.regex.Pattern; +import org.apache.hertzbeat.common.support.exception.SendMessageException; + +/** + * Builds bounded SMS failures without copying provider-controlled response + * bodies, request URLs, or transport exception messages. + */ +final class SmsFailureMessages { + + private static final Pattern SAFE_PROVIDER_CODE = Pattern.compile("[-A-Za-z0-9_.]{1,64}"); + private static final String UNKNOWN_PROVIDER_CODE = "UNKNOWN_PROVIDER_ERROR"; + + private SmsFailureMessages() { + } + + static SendMessageException requestFailed(String providerLabel) { + return new SendMessageException(providerLabel + " request failed"); + } + + static SendMessageException httpStatus(String providerLabel, int statusCode) { + return new SendMessageException( + providerLabel + " request failed with HTTP status " + statusCode); + } + + static SendMessageException providerCode(String providerLabel, String code) { + String safeCode = code != null && SAFE_PROVIDER_CODE.matcher(code).matches() + ? code + : UNKNOWN_PROVIDER_CODE; + return new SendMessageException( + providerLabel + " request failed (code: " + safeCode + ")"); + } + + static SendMessageException invalidResponse(String providerLabel) { + return new SendMessageException(providerLabel + " provider returned an invalid response"); + } +} diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsLocalSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsLocalSmsClientImpl.java index bcb16f6abb9..030b65dbc77 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsLocalSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/SmsLocalSmsClientImpl.java @@ -61,7 +61,7 @@ public SmsLocalSmsClientImpl(SmslocalSmsProperties smslocalSmsProperties) { @Override public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, GroupAlert alert) { if (Objects.isNull(receiver) || Objects.isNull(alert)) { - log.warn("receiver and alert can not be null! receiver: {}, alert:{}", receiver, alert); + log.warn("SMSLocal receiver and alert cannot be null"); return; } @@ -79,36 +79,42 @@ public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, httpPost.setHeader("Token", config.getApiKey()); httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8)); - log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload); + log.debug("Sending SMS request via SMSLocal"); // send http request and handle response try (CloseableHttpResponse response = httpClient.execute(httpPost)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.debug("SMS response status: {}, body: {}", statusCode, responseBody); + log.debug("SMSLocal response status: {}", statusCode); if (statusCode != 200) { - throw new SendMessageException("HTTP request failed with status code: " + statusCode); + throw SmsFailureMessages.httpStatus("SMSLocal", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); + if (jsonResponse == null || !jsonResponse.isArray() || jsonResponse.isEmpty()) { + throw SmsFailureMessages.invalidResponse("SMSLocal"); + } JsonNode jsonNode = jsonResponse.get(0); - if (Objects.isNull(jsonNode)) { - log.warn("jsonResponse parse errorCode failed: {}", jsonResponse); - return; + JsonNode errorCodeNode = jsonNode.get("errorCode"); + if (errorCodeNode == null) { + throw SmsFailureMessages.invalidResponse("SMSLocal"); } - String errorCode = jsonNode.get("errorCode").asText(); + String errorCode = errorCodeNode.asText(); if (!SUCCESS_CODE.equals(errorCode)) { - String msgid = jsonNode.get("id").asText(); - throw new SendMessageException(errorCode + ":" + msgid); + throw SmsFailureMessages.providerCode("SMSLocal", errorCode); } - log.info("Successfully sent SMS to phone: {}", receiver.getPhone()); + log.info("Successfully sent SMS via SMSLocal"); } + } catch (SendMessageException e) { + log.warn("Failed to send SMS via SMSLocal"); + throw e; } catch (Exception e) { - log.error("Failed to send SMS: {}", e.getMessage()); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to send SMS via SMSLocal, failure type: {}", + e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("SMSLocal"); } } @@ -121,7 +127,7 @@ public String getType() { @Override public boolean checkConfig() { if (Objects.isNull(config) || Objects.isNull(config.getApiKey()) || config.getApiKey().isBlank()) { - log.warn("smslocal properties can not be null: {}", config); + log.warn("SMSLocal properties cannot be null or blank"); return false; } return true; diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TencentSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TencentSmsClientImpl.java index 90ceefe5f85..270b6dddf8c 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TencentSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TencentSmsClientImpl.java @@ -134,41 +134,56 @@ public void sendSms(String appId, String signName, String templateId, httpPost.setHeader("Authorization", authorization); httpPost.setEntity(new StringEntity(payload, StandardCharsets.UTF_8)); - log.debug("Sending SMS request to {}, payload: {}", httpPost.getURI(), payload); + log.debug("Sending SMS request via Tencent Cloud"); // send http request and handle response try (CloseableHttpResponse response = httpClient.execute(httpPost)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.debug("SMS response status: {}, body: {}", statusCode, responseBody); + log.debug("Tencent Cloud SMS response status: {}", statusCode); if (statusCode != 200) { - throw new SendMessageException("HTTP request failed with status code: " + statusCode); + throw SmsFailureMessages.httpStatus("Tencent Cloud SMS", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); + if (jsonResponse == null) { + throw SmsFailureMessages.invalidResponse("Tencent Cloud SMS"); + } JsonNode responseNode = jsonResponse.get("Response"); + if (responseNode == null) { + throw SmsFailureMessages.invalidResponse("Tencent Cloud SMS"); + } JsonNode error = responseNode.get("Error"); if (error != null) { - String code = error.get("Code").asText(); - String message = error.get("Message").asText(); - throw new SendMessageException(code + ":" + message); + JsonNode codeNode = error.get("Code"); + if (codeNode == null) { + throw SmsFailureMessages.invalidResponse("Tencent Cloud SMS"); + } + throw SmsFailureMessages.providerCode("Tencent Cloud SMS", codeNode.asText()); } JsonNode sendStatusSet = responseNode.get("SendStatusSet"); - if (sendStatusSet != null && sendStatusSet.isArray() && sendStatusSet.size() > 0) { - JsonNode firstStatus = sendStatusSet.get(0); - String code = firstStatus.get("Code").asText(); - String message = firstStatus.get("Message").asText(); - if (!RESPONSE_OK.equals(code)) { - throw new SendMessageException(code + ":" + message); - } + if (sendStatusSet == null || !sendStatusSet.isArray() || sendStatusSet.isEmpty()) { + throw SmsFailureMessages.invalidResponse("Tencent Cloud SMS"); + } + JsonNode codeNode = sendStatusSet.get(0).get("Code"); + if (codeNode == null) { + throw SmsFailureMessages.invalidResponse("Tencent Cloud SMS"); + } + String code = codeNode.asText(); + if (!RESPONSE_OK.equals(code)) { + throw SmsFailureMessages.providerCode("Tencent Cloud SMS", code); } - log.info("Successfully sent SMS to phones: {}", String.join(",", phones)); + log.info("Successfully sent SMS via Tencent Cloud"); } + } catch (SendMessageException e) { + log.warn("Failed to send SMS via Tencent Cloud"); + throw e; } catch (Exception e) { - log.warn("Failed to send SMS: {}", e.getMessage()); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to send SMS via Tencent Cloud, failure type: {}", + e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("Tencent Cloud SMS"); } } diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TwilioSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TwilioSmsClientImpl.java index 55e1fd355f9..5d305e38340 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TwilioSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/TwilioSmsClientImpl.java @@ -97,11 +97,15 @@ private void send(String phoneNumber, String message) { URI requestUri = new URI(endpoint); HttpPost httpPost = createHttpPost(requestUri, phoneNumber, message); - log.info("Sending Twilio SMS request to {}", requestUri); - executeRequest(httpClient, httpPost, phoneNumber); + log.debug("Sending SMS request via Twilio"); + executeRequest(httpClient, httpPost); + } catch (SendMessageException e) { + log.warn("Failed to send SMS via Twilio"); + throw e; } catch (Exception e) { - log.warn("Failed to send SMS: {}", e.getMessage()); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to send SMS via Twilio, failure type: {}", + e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("Twilio SMS"); } } @@ -121,41 +125,36 @@ private HttpPost createHttpPost(URI requestUri, String toNumber, String message) httpPost.setEntity(new UrlEncodedFormEntity(parameters)); return httpPost; } catch (Exception e) { - log.error("Failed to create HTTP request: {}", e.getMessage()); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to create Twilio SMS request, failure type: {}", + e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("Twilio SMS"); } } - private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost, String phoneNumber) - throws Exception { + private void executeRequest(CloseableHttpClient httpClient, HttpPost httpPost) throws Exception { try (CloseableHttpResponse response = httpClient.execute(httpPost)) { int statusCode = response.getStatusLine().getStatusCode(); String responseBody = EntityUtils.toString(response.getEntity()); - log.info("SMS response status: {}, body: {}", statusCode, responseBody); + log.debug("Twilio SMS response status: {}", statusCode); if (statusCode < 200 || statusCode >= 300) { - if (responseBody.contains("21608")) { - throw new SendMessageException( - "The Twilio trial account can only send SMS to verified phone numbers"); - } else { - throw new SendMessageException( - "HTTP request failed with status code: " + statusCode + ", response: " + responseBody); + throw SmsFailureMessages.providerCode("Twilio SMS", "21608"); } + throw SmsFailureMessages.httpStatus("Twilio SMS", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); if (jsonResponse == null) { - throw new SendMessageException(statusCode + ":" + responseBody); + throw SmsFailureMessages.invalidResponse("Twilio SMS"); } JsonNode sidNode = jsonResponse.get("sid"); if (sidNode == null) { - throw new SendMessageException(statusCode + ":" + responseBody); + throw SmsFailureMessages.invalidResponse("Twilio SMS"); } - String sid = sidNode.asText(); - log.info("Successfully sent SMS to phone: {}, sid: {}", phoneNumber, sid); + log.info("Successfully sent SMS via Twilio"); } } diff --git a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java index 2ad8e29451c..241229ea28d 100644 --- a/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java +++ b/hertzbeat-alerter/src/main/java/org/apache/hertzbeat/alert/service/impl/UniSmsClientImpl.java @@ -107,9 +107,12 @@ public void sendMessage(NoticeReceiver receiver, NoticeTemplate noticeTemplate, try (CloseableHttpResponse response = httpClient.execute(httpPost)) { handleResponse(response); } + } catch (SendMessageException e) { + log.warn("Failed to send SMS via UniSMS"); + throw e; } catch (Exception e) { - log.error("Failed to send SMS via UniSMS"); - throw new SendMessageException(e.getMessage()); + log.warn("Failed to send SMS via UniSMS, failure type: {}", e.getClass().getSimpleName()); + throw SmsFailureMessages.requestFailed("UniSMS"); } } @@ -152,14 +155,16 @@ private void handleResponse(CloseableHttpResponse response) throws IOException { log.debug("UniSMS response status: {}", statusCode); if (statusCode != 200) { - throw new SendMessageException("HTTP request failed with status code: " + statusCode + ", response: " + responseBody); + throw SmsFailureMessages.httpStatus("UniSMS", statusCode); } JsonNode jsonResponse = JsonUtil.fromJson(responseBody); + if (jsonResponse == null || jsonResponse.get("code") == null) { + throw SmsFailureMessages.invalidResponse("UniSMS"); + } String code = jsonResponse.get("code").asText(); if (!SUCCESS_CODE.equals(code)) { - String message = jsonResponse.get("message").asText(); - throw new SendMessageException(code + ":" + message); + throw SmsFailureMessages.providerCode("UniSMS", code); } log.info("Successfully sent SMS via UniSMS"); diff --git a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java index a3f4170f403..fc365ff305f 100644 --- a/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java +++ b/hertzbeat-alerter/src/test/java/org/apache/hertzbeat/alert/service/impl/SmsClientLoggingTest.java @@ -17,18 +17,25 @@ package org.apache.hertzbeat.alert.service.impl; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.when; +import java.io.IOException; import java.util.Map; import org.apache.hertzbeat.common.entity.alerter.GroupAlert; import org.apache.hertzbeat.common.entity.alerter.NoticeReceiver; import org.apache.hertzbeat.common.entity.dto.sms.AlibabaSmsProperties; import org.apache.hertzbeat.common.entity.dto.sms.AwsSmsProperties; +import org.apache.hertzbeat.common.entity.dto.sms.SmslocalSmsProperties; +import org.apache.hertzbeat.common.entity.dto.sms.TencentSmsProperties; +import org.apache.hertzbeat.common.entity.dto.sms.TwilioSmsProperties; import org.apache.hertzbeat.common.entity.dto.sms.UniSmsProperties; +import org.apache.hertzbeat.common.support.exception.SendMessageException; import org.apache.http.StatusLine; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpPost; @@ -51,15 +58,13 @@ class SmsClientLoggingTest { private static final String ACCESS_KEY = "access-key-log-sentinel"; private static final String PHONE = "15555550123"; private static final String ALERT_CONTENT = "alert-content-log-sentinel"; + private static final String PROVIDER_BODY = "provider-body-log-sentinel"; + private static final String SIGNED_URL = "https://provider.invalid/send?Signature=signed-url-log-sentinel"; @Test void requestCredentialsAndMessageDataShouldNotBeLogged(CapturedOutput output) throws Exception { - NoticeReceiver receiver = new NoticeReceiver(); - receiver.setPhone(PHONE); - GroupAlert alert = new GroupAlert(); - alert.setGroupKey("instance"); - alert.setCommonLabels(Map.of()); - alert.setCommonAnnotations(Map.of("summary", ALERT_CONTENT)); + NoticeReceiver receiver = receiver(); + GroupAlert alert = alert(); AwsSmsProperties awsProperties = new AwsSmsProperties(); awsProperties.setAccessKeyId(ACCESS_KEY); @@ -78,6 +83,15 @@ void requestCredentialsAndMessageDataShouldNotBeLogged(CapturedOutput output) th withSuccessfulResponse("{\"code\":\"0\"}", () -> new UniSmsClientImpl(uniProperties).sendMessage(receiver, null, alert)); + withSuccessfulResponse("{\"sid\":\"message-id\"}", + () -> new TwilioSmsClientImpl(twilioProperties()).sendMessage(receiver, null, alert)); + + withSuccessfulResponse("{\"Response\":{\"SendStatusSet\":[{\"Code\":\"Ok\"}]}}", + () -> new TencentSmsClientImpl(tencentProperties()).sendMessage(receiver, null, alert)); + + withSuccessfulResponse("[{\"errorCode\":\"200\",\"id\":\"message-id\"}]", + () -> new SmsLocalSmsClientImpl(smslocalProperties()).sendMessage(receiver, null, alert)); + String logs = output.getAll(); assertFalse(logs.contains(ACCESS_KEY)); assertFalse(logs.contains(PHONE)); @@ -86,18 +100,201 @@ void requestCredentialsAndMessageDataShouldNotBeLogged(CapturedOutput output) th assertFalse(logs.contains("Signature=")); } + @Test + void failedResponsesExposeOnlyProviderAndHttpStatus(CapturedOutput output) throws Exception { + String body = "{\"message\":\"" + PROVIDER_BODY + "\",\"phone\":\"" + PHONE + "\"}"; + + SendMessageException awsFailure = withResponse(503, body, + () -> new AwsSmsClientImpl(awsProperties()).sendMessage(receiver(), null, alert())); + SendMessageException alibabaFailure = withResponse(502, body, + () -> new AlibabaSmsClientImpl(alibabaProperties()).sendMessage(receiver(), null, alert())); + SendMessageException uniFailure = withResponse(429, body, + () -> new UniSmsClientImpl(uniProperties()).sendMessage(receiver(), null, alert())); + SendMessageException twilioFailure = withResponse(429, body, + () -> new TwilioSmsClientImpl(twilioProperties()).sendMessage(receiver(), null, alert())); + SendMessageException tencentFailure = withResponse(429, body, + () -> new TencentSmsClientImpl(tencentProperties()).sendMessage(receiver(), null, alert())); + SendMessageException smslocalFailure = withResponse(429, body, + () -> new SmsLocalSmsClientImpl(smslocalProperties()).sendMessage(receiver(), null, alert())); + + assertEquals("AWS SMS request failed with HTTP status 503", awsFailure.getMessage()); + assertEquals("Alibaba Cloud SMS request failed with HTTP status 502", alibabaFailure.getMessage()); + assertEquals("UniSMS request failed with HTTP status 429", uniFailure.getMessage()); + assertEquals("Twilio SMS request failed with HTTP status 429", twilioFailure.getMessage()); + assertEquals("Tencent Cloud SMS request failed with HTTP status 429", tencentFailure.getMessage()); + assertEquals("SMSLocal request failed with HTTP status 429", smslocalFailure.getMessage()); + assertNoSensitiveSentinels(output.getAll() + + awsFailure.getMessage() + + alibabaFailure.getMessage() + + uniFailure.getMessage() + + twilioFailure.getMessage() + + tencentFailure.getMessage() + + smslocalFailure.getMessage()); + } + + @Test + void providerErrorsDoNotExposeProviderMessages(CapturedOutput output) throws Exception { + SendMessageException alibabaFailure = withResponse( + 200, + "{\"Code\":\"THROTTLED\",\"Message\":\"" + PROVIDER_BODY + "\"}", + () -> new AlibabaSmsClientImpl(alibabaProperties()).sendMessage(receiver(), null, alert())); + SendMessageException uniFailure = withResponse( + 200, + "{\"code\":\"RATE_LIMITED\",\"message\":\"" + PROVIDER_BODY + "\"}", + () -> new UniSmsClientImpl(uniProperties()).sendMessage(receiver(), null, alert())); + SendMessageException awsFailure = withResponse( + 200, + "{\"message\":\"" + PROVIDER_BODY + "\"}", + () -> new AwsSmsClientImpl(awsProperties()).sendMessage(receiver(), null, alert())); + SendMessageException twilioFailure = withResponse( + 400, + "{\"code\":21608,\"message\":\"" + PROVIDER_BODY + "\"}", + () -> new TwilioSmsClientImpl(twilioProperties()).sendMessage(receiver(), null, alert())); + SendMessageException tencentFailure = withResponse( + 200, + "{\"Response\":{\"Error\":{\"Code\":\"THROTTLED\",\"Message\":\"" + + PROVIDER_BODY + "\"}}}", + () -> new TencentSmsClientImpl(tencentProperties()).sendMessage(receiver(), null, alert())); + SendMessageException smslocalFailure = withResponse( + 200, + "[{\"errorCode\":\"RATE_LIMITED\",\"id\":\"" + PROVIDER_BODY + "\"}]", + () -> new SmsLocalSmsClientImpl(smslocalProperties()).sendMessage(receiver(), null, alert())); + + assertEquals("Alibaba Cloud SMS request failed (code: THROTTLED)", alibabaFailure.getMessage()); + assertEquals("UniSMS request failed (code: RATE_LIMITED)", uniFailure.getMessage()); + assertEquals("AWS SMS provider returned an invalid response", awsFailure.getMessage()); + assertEquals("Twilio SMS request failed (code: 21608)", twilioFailure.getMessage()); + assertEquals("Tencent Cloud SMS request failed (code: THROTTLED)", tencentFailure.getMessage()); + assertEquals("SMSLocal request failed (code: RATE_LIMITED)", smslocalFailure.getMessage()); + assertNoSensitiveSentinels(output.getAll() + + alibabaFailure.getMessage() + + uniFailure.getMessage() + + awsFailure.getMessage() + + twilioFailure.getMessage() + + tencentFailure.getMessage() + + smslocalFailure.getMessage()); + } + + @Test + void networkExceptionsDoNotExposeSignedUrls(CapturedOutput output) throws Exception { + SendMessageException awsFailure = withNetworkFailure( + () -> new AwsSmsClientImpl(awsProperties()).sendMessage(receiver(), null, alert())); + SendMessageException alibabaFailure = withNetworkFailure( + () -> new AlibabaSmsClientImpl(alibabaProperties()).sendMessage(receiver(), null, alert())); + SendMessageException uniFailure = withNetworkFailure( + () -> new UniSmsClientImpl(uniProperties()).sendMessage(receiver(), null, alert())); + SendMessageException twilioFailure = withNetworkFailure( + () -> new TwilioSmsClientImpl(twilioProperties()).sendMessage(receiver(), null, alert())); + SendMessageException tencentFailure = withNetworkFailure( + () -> new TencentSmsClientImpl(tencentProperties()).sendMessage(receiver(), null, alert())); + SendMessageException smslocalFailure = withNetworkFailure( + () -> new SmsLocalSmsClientImpl(smslocalProperties()).sendMessage(receiver(), null, alert())); + + assertEquals("AWS SMS request failed", awsFailure.getMessage()); + assertEquals("Alibaba Cloud SMS request failed", alibabaFailure.getMessage()); + assertEquals("UniSMS request failed", uniFailure.getMessage()); + assertEquals("Twilio SMS request failed", twilioFailure.getMessage()); + assertEquals("Tencent Cloud SMS request failed", tencentFailure.getMessage()); + assertEquals("SMSLocal request failed", smslocalFailure.getMessage()); + assertNoSensitiveSentinels(output.getAll() + + awsFailure.getMessage() + + alibabaFailure.getMessage() + + uniFailure.getMessage() + + twilioFailure.getMessage() + + tencentFailure.getMessage() + + smslocalFailure.getMessage()); + } + private void withSuccessfulResponse(String responseBody, Runnable operation) throws Exception { + withResponse(200, responseBody, operation, false); + } + + private SendMessageException withResponse(int statusCode, String responseBody, Runnable operation) + throws Exception { + return withResponse(statusCode, responseBody, operation, true); + } + + private SendMessageException withResponse( + int statusCode, + String responseBody, + Runnable operation, + boolean expectsFailure) throws Exception { CloseableHttpClient httpClient = mock(CloseableHttpClient.class); CloseableHttpResponse response = mock(CloseableHttpResponse.class); StatusLine statusLine = mock(StatusLine.class); - when(statusLine.getStatusCode()).thenReturn(200); + when(statusLine.getStatusCode()).thenReturn(statusCode); when(response.getStatusLine()).thenReturn(statusLine); when(response.getEntity()).thenReturn(new StringEntity(responseBody, ContentType.APPLICATION_JSON)); when(httpClient.execute(any(HttpPost.class))).thenReturn(response); try (MockedStatic httpClients = mockStatic(HttpClients.class)) { httpClients.when(HttpClients::createDefault).thenReturn(httpClient); + if (expectsFailure) { + return assertThrows(SendMessageException.class, operation::run); + } operation.run(); + return null; + } + } + + private SendMessageException withNetworkFailure(Runnable operation) throws Exception { + CloseableHttpClient httpClient = mock(CloseableHttpClient.class); + when(httpClient.execute(any(HttpPost.class))) + .thenThrow(new IOException(SIGNED_URL + "&phone=" + PHONE + "&body=" + PROVIDER_BODY)); + try (MockedStatic httpClients = mockStatic(HttpClients.class)) { + httpClients.when(HttpClients::createDefault).thenReturn(httpClient); + return assertThrows(SendMessageException.class, operation::run); } } + + private NoticeReceiver receiver() { + NoticeReceiver receiver = new NoticeReceiver(); + receiver.setPhone(PHONE); + return receiver; + } + + private GroupAlert alert() { + GroupAlert alert = new GroupAlert(); + alert.setGroupKey("instance"); + alert.setCommonLabels(Map.of()); + alert.setCommonAnnotations(Map.of("summary", ALERT_CONTENT, "description", ALERT_CONTENT)); + return alert; + } + + private AwsSmsProperties awsProperties() { + AwsSmsProperties properties = new AwsSmsProperties(); + properties.setAccessKeyId(ACCESS_KEY); + properties.setAccessKeySecret("aws-secret"); + properties.setRegion("us-east-1"); + return properties; + } + + private AlibabaSmsProperties alibabaProperties() { + return new AlibabaSmsProperties(ACCESS_KEY, "alibaba-secret", "sign", "template"); + } + + private UniSmsProperties uniProperties() { + return new UniSmsProperties(ACCESS_KEY, "unisms-secret", "sign", "template", "hmac"); + } + + private TwilioSmsProperties twilioProperties() { + return new TwilioSmsProperties(ACCESS_KEY, "twilio-secret", "twilio-phone"); + } + + private TencentSmsProperties tencentProperties() { + return new TencentSmsProperties(ACCESS_KEY, "tencent-secret", "app-id", "sign", "template"); + } + + private SmslocalSmsProperties smslocalProperties() { + return new SmslocalSmsProperties(ACCESS_KEY); + } + + private void assertNoSensitiveSentinels(String text) { + assertFalse(text.contains(ACCESS_KEY)); + assertFalse(text.contains(PHONE)); + assertFalse(text.contains(ALERT_CONTENT)); + assertFalse(text.contains(PROVIDER_BODY)); + assertFalse(text.contains(SIGNED_URL)); + assertFalse(text.contains("signed-url-log-sentinel")); + } }