Skip to content

Commit 8c662a6

Browse files
authored
Adding max http response string length as a setting, and capping http response string based on that setting (#861)
Signed-off-by: Dennis Toepker <toepkerd@amazon.com>
1 parent 39bd3bf commit 8c662a6

File tree

3 files changed

+69
-1
lines changed

3 files changed

+69
-1
lines changed

notifications/core/src/main/kotlin/org/opensearch/notifications/core/client/DestinationHttpClient.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ class DestinationHttpClient {
148148
@Throws(IOException::class)
149149
fun getResponseString(response: CloseableHttpResponse): String {
150150
val entity: HttpEntity = response.entity ?: return "{}"
151-
val responseString = EntityUtils.toString(entity)
151+
val responseString = EntityUtils.toString(entity, PluginSettings.maxHttpResponseSize / 2) // Java char is 2 bytes
152152
// DeliveryStatus need statusText must not be empty, convert empty response to {}
153153
return if (responseString.isNullOrEmpty()) "{}" else responseString
154154
}

notifications/core/src/main/kotlin/org/opensearch/notifications/core/setting/PluginSettings.kt

+38
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.opensearch.common.settings.Setting.Property.Final
1616
import org.opensearch.common.settings.Setting.Property.NodeScope
1717
import org.opensearch.common.settings.Settings
1818
import org.opensearch.core.common.settings.SecureString
19+
import org.opensearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CONTENT_LENGTH
1920
import org.opensearch.notifications.core.NotificationCorePlugin.Companion.LOG_PREFIX
2021
import org.opensearch.notifications.core.NotificationCorePlugin.Companion.PLUGIN_NAME
2122
import org.opensearch.notifications.core.utils.OpenForTesting
@@ -81,6 +82,11 @@ internal object PluginSettings {
8182
*/
8283
private const val SOCKET_TIMEOUT_MILLISECONDS_KEY = "$HTTP_CONNECTION_KEY_PREFIX.socket_timeout"
8384

85+
/**
86+
* Setting for maximum string length of HTTP response, allows protection from DoS
87+
*/
88+
private const val MAX_HTTP_RESPONSE_SIZE_KEY = "$KEY_PREFIX.max_http_response_size"
89+
8490
/**
8591
* Legacy setting for list of host deny list in Alerting
8692
*/
@@ -146,6 +152,11 @@ internal object PluginSettings {
146152
*/
147153
private const val DEFAULT_SOCKET_TIMEOUT_MILLISECONDS = 50000
148154

155+
/**
156+
* Default maximum HTTP response string length
157+
*/
158+
private val DEFAULT_MAX_HTTP_RESPONSE_SIZE = SETTING_HTTP_MAX_CONTENT_LENGTH.getDefault(Settings.EMPTY).getBytes().toInt()
159+
149160
/**
150161
* Default email header length. minimum value from 100 reference emails
151162
*/
@@ -223,6 +234,12 @@ internal object PluginSettings {
223234
@Volatile
224235
var socketTimeout: Int
225236

237+
/**
238+
* Maximum HTTP response string length
239+
*/
240+
@Volatile
241+
var maxHttpResponseSize: Int
242+
226243
/**
227244
* Tooltip support
228245
*/
@@ -273,6 +290,7 @@ internal object PluginSettings {
273290
connectionTimeout = (settings?.get(CONNECTION_TIMEOUT_MILLISECONDS_KEY)?.toInt())
274291
?: DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS
275292
socketTimeout = (settings?.get(SOCKET_TIMEOUT_MILLISECONDS_KEY)?.toInt()) ?: DEFAULT_SOCKET_TIMEOUT_MILLISECONDS
293+
maxHttpResponseSize = (settings?.get(MAX_HTTP_RESPONSE_SIZE_KEY)?.toInt()) ?: DEFAULT_MAX_HTTP_RESPONSE_SIZE
276294
allowedConfigTypes = settings?.getAsList(ALLOWED_CONFIG_TYPE_KEY, null) ?: DEFAULT_ALLOWED_CONFIG_TYPES
277295
tooltipSupport = settings?.getAsBoolean(TOOLTIP_SUPPORT_KEY, true) ?: DEFAULT_TOOLTIP_SUPPORT
278296
hostDenyList = settings?.getAsList(HOST_DENY_LIST_KEY, null) ?: DEFAULT_HOST_DENY_LIST
@@ -286,6 +304,7 @@ internal object PluginSettings {
286304
MAX_CONNECTIONS_PER_ROUTE_KEY to maxConnectionsPerRoute.toString(DECIMAL_RADIX),
287305
CONNECTION_TIMEOUT_MILLISECONDS_KEY to connectionTimeout.toString(DECIMAL_RADIX),
288306
SOCKET_TIMEOUT_MILLISECONDS_KEY to socketTimeout.toString(DECIMAL_RADIX),
307+
MAX_HTTP_RESPONSE_SIZE_KEY to maxHttpResponseSize.toString(DECIMAL_RADIX),
289308
TOOLTIP_SUPPORT_KEY to tooltipSupport.toString()
290309
)
291310
}
@@ -333,6 +352,13 @@ internal object PluginSettings {
333352
Dynamic
334353
)
335354

355+
val MAX_HTTP_RESPONSE_SIZE: Setting<Int> = Setting.intSetting(
356+
MAX_HTTP_RESPONSE_SIZE_KEY,
357+
defaultSettings[MAX_HTTP_RESPONSE_SIZE_KEY]!!.toInt(),
358+
NodeScope,
359+
Dynamic
360+
)
361+
336362
val ALLOWED_CONFIG_TYPES: Setting<List<String>> = Setting.listSetting(
337363
ALLOWED_CONFIG_TYPE_KEY,
338364
DEFAULT_ALLOWED_CONFIG_TYPES,
@@ -420,6 +446,7 @@ internal object PluginSettings {
420446
MAX_CONNECTIONS_PER_ROUTE,
421447
CONNECTION_TIMEOUT_MILLISECONDS,
422448
SOCKET_TIMEOUT_MILLISECONDS,
449+
MAX_HTTP_RESPONSE_SIZE,
423450
ALLOWED_CONFIG_TYPES,
424451
TOOLTIP_SUPPORT,
425452
HOST_DENY_LIST,
@@ -440,6 +467,7 @@ internal object PluginSettings {
440467
maxConnectionsPerRoute = MAX_CONNECTIONS_PER_ROUTE.get(clusterService.settings)
441468
connectionTimeout = CONNECTION_TIMEOUT_MILLISECONDS.get(clusterService.settings)
442469
socketTimeout = SOCKET_TIMEOUT_MILLISECONDS.get(clusterService.settings)
470+
maxHttpResponseSize = MAX_HTTP_RESPONSE_SIZE.get(clusterService.settings)
443471
tooltipSupport = TOOLTIP_SUPPORT.get(clusterService.settings)
444472
hostDenyList = HOST_DENY_LIST.get(clusterService.settings)
445473
destinationSettings = loadDestinationSettings(clusterService.settings)
@@ -482,6 +510,11 @@ internal object PluginSettings {
482510
log.debug("$LOG_PREFIX:$SOCKET_TIMEOUT_MILLISECONDS_KEY -autoUpdatedTo-> $clusterSocketTimeout")
483511
socketTimeout = clusterSocketTimeout
484512
}
513+
val clusterMaxHttpResponseSize = clusterService.clusterSettings.get(MAX_HTTP_RESPONSE_SIZE)
514+
if (clusterMaxHttpResponseSize != null) {
515+
log.debug("$LOG_PREFIX:$MAX_HTTP_RESPONSE_SIZE_KEY -autoUpdatedTo-> $clusterMaxHttpResponseSize")
516+
socketTimeout = clusterSocketTimeout
517+
}
485518
val clusterAllowedConfigTypes = clusterService.clusterSettings.get(ALLOWED_CONFIG_TYPES)
486519
if (clusterAllowedConfigTypes != null) {
487520
log.debug("$LOG_PREFIX:$ALLOWED_CONFIG_TYPE_KEY -autoUpdatedTo-> $clusterAllowedConfigTypes")
@@ -542,6 +575,10 @@ internal object PluginSettings {
542575
socketTimeout = it
543576
log.info("$LOG_PREFIX:$SOCKET_TIMEOUT_MILLISECONDS_KEY -updatedTo-> $it")
544577
}
578+
clusterService.clusterSettings.addSettingsUpdateConsumer(MAX_HTTP_RESPONSE_SIZE) {
579+
maxHttpResponseSize = it
580+
log.info("$LOG_PREFIX:$MAX_HTTP_RESPONSE_SIZE_KEY -updatedTo-> $it")
581+
}
545582
clusterService.clusterSettings.addSettingsUpdateConsumer(TOOLTIP_SUPPORT) {
546583
tooltipSupport = it
547584
log.info("$LOG_PREFIX:$TOOLTIP_SUPPORT_KEY -updatedTo-> $it")
@@ -605,6 +642,7 @@ internal object PluginSettings {
605642
maxConnectionsPerRoute = DEFAULT_MAX_CONNECTIONS_PER_ROUTE
606643
connectionTimeout = DEFAULT_CONNECTION_TIMEOUT_MILLISECONDS
607644
socketTimeout = DEFAULT_SOCKET_TIMEOUT_MILLISECONDS
645+
maxHttpResponseSize = DEFAULT_MAX_HTTP_RESPONSE_SIZE
608646
allowedConfigTypes = DEFAULT_ALLOWED_CONFIG_TYPES
609647
tooltipSupport = DEFAULT_TOOLTIP_SUPPORT
610648
hostDenyList = DEFAULT_HOST_DENY_LIST

notifications/core/src/test/kotlin/org/opensearch/notifications/core/settings/PluginSettingsTests.kt

+30
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import org.opensearch.cluster.ClusterName
1616
import org.opensearch.cluster.service.ClusterService
1717
import org.opensearch.common.settings.ClusterSettings
1818
import org.opensearch.common.settings.Settings
19+
import org.opensearch.http.HttpTransportSettings.SETTING_HTTP_MAX_CONTENT_LENGTH
1920
import org.opensearch.notifications.core.NotificationCorePlugin
2021
import org.opensearch.notifications.core.setting.PluginSettings
2122

@@ -32,6 +33,7 @@ internal class PluginSettingsTests {
3233
private val httpMaxConnectionPerRouteKey = "$httpKeyPrefix.max_connection_per_route"
3334
private val httpConnectionTimeoutKey = "$httpKeyPrefix.connection_timeout"
3435
private val httpSocketTimeoutKey = "$httpKeyPrefix.socket_timeout"
36+
private val maxHttpResponseSizeKey = "$keyPrefix.max_http_response_size"
3537
private val legacyAlertingHostDenyListKey = "opendistro.destination.host.deny_list"
3638
private val alertingHostDenyListKey = "plugins.destination.host.deny_list"
3739
private val httpHostDenyListKey = "$httpKeyPrefix.host_deny_list"
@@ -48,6 +50,7 @@ internal class PluginSettingsTests {
4850
.put(httpMaxConnectionPerRouteKey, 20)
4951
.put(httpConnectionTimeoutKey, 5000)
5052
.put(httpSocketTimeoutKey, 50000)
53+
.put(maxHttpResponseSizeKey, SETTING_HTTP_MAX_CONTENT_LENGTH.getDefault(Settings.EMPTY).getBytes().toInt())
5154
.putList(httpHostDenyListKey, emptyList<String>())
5255
.putList(
5356
allowedConfigTypeKey,
@@ -91,6 +94,7 @@ internal class PluginSettingsTests {
9194
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
9295
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
9396
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
97+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
9498
PluginSettings.ALLOWED_CONFIG_TYPES,
9599
PluginSettings.TOOLTIP_SUPPORT,
96100
PluginSettings.HOST_DENY_LIST
@@ -118,6 +122,10 @@ internal class PluginSettingsTests {
118122
defaultSettings[httpSocketTimeoutKey],
119123
PluginSettings.socketTimeout.toString()
120124
)
125+
Assertions.assertEquals(
126+
defaultSettings[maxHttpResponseSizeKey],
127+
PluginSettings.maxHttpResponseSize.toString()
128+
)
121129
Assertions.assertEquals(
122130
defaultSettings[allowedConfigTypeKey],
123131
PluginSettings.allowedConfigTypes.toString()
@@ -145,6 +153,7 @@ internal class PluginSettingsTests {
145153
.put(httpMaxConnectionPerRouteKey, 100)
146154
.put(httpConnectionTimeoutKey, 100)
147155
.put(httpSocketTimeoutKey, 100)
156+
.put(maxHttpResponseSizeKey, 20000000)
148157
.putList(httpHostDenyListKey, listOf("sample"))
149158
.putList(allowedConfigTypeKey, listOf("slack"))
150159
.put(tooltipSupportKey, false)
@@ -163,6 +172,7 @@ internal class PluginSettingsTests {
163172
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
164173
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
165174
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
175+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
166176
PluginSettings.ALLOWED_CONFIG_TYPES,
167177
PluginSettings.TOOLTIP_SUPPORT,
168178
PluginSettings.HOST_DENY_LIST,
@@ -191,6 +201,14 @@ internal class PluginSettingsTests {
191201
100,
192202
clusterService.clusterSettings.get(PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS)
193203
)
204+
Assertions.assertEquals(
205+
100,
206+
clusterService.clusterSettings.get(PluginSettings.SOCKET_TIMEOUT_MILLISECONDS)
207+
)
208+
Assertions.assertEquals(
209+
20000000,
210+
clusterService.clusterSettings.get(PluginSettings.MAX_HTTP_RESPONSE_SIZE)
211+
)
194212
Assertions.assertEquals(
195213
listOf("sample"),
196214
clusterService.clusterSettings.get(PluginSettings.HOST_DENY_LIST)
@@ -224,6 +242,7 @@ internal class PluginSettingsTests {
224242
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
225243
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
226244
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
245+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
227246
PluginSettings.ALLOWED_CONFIG_TYPES,
228247
PluginSettings.TOOLTIP_SUPPORT,
229248
PluginSettings.HOST_DENY_LIST,
@@ -252,6 +271,14 @@ internal class PluginSettingsTests {
252271
defaultSettings[httpConnectionTimeoutKey],
253272
clusterService.clusterSettings.get(PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS).toString()
254273
)
274+
Assertions.assertEquals(
275+
defaultSettings[httpSocketTimeoutKey],
276+
clusterService.clusterSettings.get(PluginSettings.SOCKET_TIMEOUT_MILLISECONDS).toString()
277+
)
278+
Assertions.assertEquals(
279+
defaultSettings[maxHttpResponseSizeKey],
280+
clusterService.clusterSettings.get(PluginSettings.MAX_HTTP_RESPONSE_SIZE).toString()
281+
)
255282
Assertions.assertEquals(
256283
defaultSettings[httpHostDenyListKey],
257284
clusterService.clusterSettings.get(PluginSettings.HOST_DENY_LIST).toString()
@@ -290,6 +317,7 @@ internal class PluginSettingsTests {
290317
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
291318
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
292319
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
320+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
293321
PluginSettings.ALLOWED_CONFIG_TYPES,
294322
PluginSettings.TOOLTIP_SUPPORT,
295323
PluginSettings.LEGACY_ALERTING_HOST_DENY_LIST,
@@ -325,6 +353,7 @@ internal class PluginSettingsTests {
325353
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
326354
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
327355
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
356+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
328357
PluginSettings.ALLOWED_CONFIG_TYPES,
329358
PluginSettings.TOOLTIP_SUPPORT,
330359
PluginSettings.LEGACY_ALERTING_HOST_DENY_LIST,
@@ -359,6 +388,7 @@ internal class PluginSettingsTests {
359388
PluginSettings.MAX_CONNECTIONS_PER_ROUTE,
360389
PluginSettings.CONNECTION_TIMEOUT_MILLISECONDS,
361390
PluginSettings.SOCKET_TIMEOUT_MILLISECONDS,
391+
PluginSettings.MAX_HTTP_RESPONSE_SIZE,
362392
PluginSettings.ALLOWED_CONFIG_TYPES,
363393
PluginSettings.TOOLTIP_SUPPORT,
364394
PluginSettings.LEGACY_ALERTING_HOST_DENY_LIST,

0 commit comments

Comments
 (0)