Skip to content

Commit c96ffa6

Browse files
Resolve hosts when checking against host deny list (#496) (#498)
* Resolve hosts when checking against host deny list Signed-off-by: Mohammad Qureshi <[email protected]> * Stub isHostInDenyList() for notification core unit tests Signed-off-by: Mohammad Qureshi <[email protected]> * Stub the correct isHostInDenylist function Signed-off-by: Mohammad Qureshi <[email protected]> * Use Before annotation instead for mocking setup Signed-off-by: Mohammad Qureshi <[email protected]> * Test switching one of the ChimeDestinationTests to use mockk instead of EasyMock Signed-off-by: Mohammad Qureshi <[email protected]> * Switch to BeforeEach for setup and remove unneeded unit test Signed-off-by: Mohammad Qureshi <[email protected]> * Change CustomWebhookDestinationTest and SlackDestinationTests to use BeforeEach as well for consistency Signed-off-by: Mohammad Qureshi <[email protected]> (cherry picked from commit 82a926e) Co-authored-by: Mohammad Qureshi <[email protected]>
1 parent fec51f9 commit c96ffa6

File tree

6 files changed

+66
-21
lines changed

6 files changed

+66
-21
lines changed

notifications/core-spi/build.gradle

+12-3
Original file line numberDiff line numberDiff line change
@@ -51,15 +51,24 @@ dependencies {
5151
implementation "org.apache.httpcomponents:httpcore:${versions.httpcore}"
5252
implementation "org.apache.httpcomponents:httpclient:${versions.httpclient}"
5353
implementation "com.github.seancfoley:ipaddress:5.3.3"
54-
implementation("commons-validator:commons-validator:1.7")
54+
implementation "commons-validator:commons-validator:1.7"
5555

5656
testImplementation(
57-
'org.junit.jupiter:junit-jupiter-api:5.6.2',
57+
"io.mockk:mockk:1.11.0",
58+
"io.mockk:mockk-common:1.11.0",
59+
"io.mockk:mockk-dsl:1.11.0",
60+
"io.mockk:mockk-dsl-jvm:1.11.0",
61+
"io.mockk:mockk-agent-api:1.11.0",
62+
"io.mockk:mockk-agent-common:1.11.0",
63+
"io.mockk:mockk-agent-jvm:1.11.0",
64+
"org.junit.jupiter:junit-jupiter-api:5.6.2",
5865
"org.junit.jupiter:junit-jupiter-params:5.6.2",
59-
'org.mockito:mockito-junit-jupiter:3.10.0',
66+
"org.mockito:mockito-junit-jupiter:3.10.0",
6067
)
6168
testRuntimeOnly('org.junit.jupiter:junit-jupiter-engine:5.6.2')
6269
testImplementation "org.jetbrains.kotlin:kotlin-test:${kotlin_version}"
70+
testImplementation "org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}" // required by mockk
71+
testImplementation "net.bytebuddy:byte-buddy-agent:1.12.7"
6372
testImplementation "org.opensearch.test:framework:${opensearch_version}"
6473
}
6574

notifications/core-spi/src/main/kotlin/org/opensearch/notifications/spi/utils/ValidationHelpers.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import org.apache.http.client.methods.HttpPost
1212
import org.apache.http.client.methods.HttpPut
1313
import org.opensearch.common.Strings
1414
import org.opensearch.notifications.spi.utils.ValidationHelpers.FQDN_REGEX
15+
import java.net.InetAddress
1516
import java.net.URL
1617

1718
private object ValidationHelpers {
@@ -51,7 +52,7 @@ fun isValidUrl(urlString: String): Boolean {
5152
fun isHostInDenylist(urlString: String, hostDenyList: List<String>): Boolean {
5253
val url = URL(urlString)
5354
if (url.host != null) {
54-
val ipStr = IPAddressString(url.host)
55+
val ipStr = IPAddressString(InetAddress.getByName(url.host).hostAddress)
5556
for (network in hostDenyList) {
5657
val netStr = IPAddressString(network)
5758
if (netStr.contains(ipStr)) {

notifications/core-spi/src/test/kotlin/org/opensearch/notifications/spi/utils/ValidationHelpersTests.kt

+14-7
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55

66
package org.opensearch.notifications.spi.utils
77

8+
import io.mockk.every
9+
import io.mockk.mockkStatic
810
import org.junit.jupiter.api.Assertions.assertEquals
911
import org.junit.jupiter.api.Test
1012
import org.junit.jupiter.api.assertThrows
13+
import java.net.InetAddress
1114
import java.net.MalformedURLException
1215

1316
internal class ValidationHelpersTests {
@@ -20,7 +23,7 @@ internal class ValidationHelpersTests {
2023
private val WEBHOOK_URL = "https://test-webhook.com:1234/subdirectory?param1=value1&param2=&param3=value3"
2124
private val CHIME_URL = "https://domain.com/sample_chime_url#1234567890"
2225

23-
private val hostDentyList = listOf(
26+
private val hostDenyList = listOf(
2427
"127.0.0.0/8",
2528
"10.0.0.0/8",
2629
"172.16.0.0/12",
@@ -41,16 +44,20 @@ internal class ValidationHelpersTests {
4144
"9.9.9.9"
4245
)
4346
for (ip in ips) {
44-
assertEquals(true, isHostInDenylist("https://$ip", hostDentyList))
47+
assertEquals(true, isHostInDenylist("https://$ip", hostDenyList))
4548
}
4649
}
4750

4851
@Test
49-
fun `test url in denylist`() {
50-
val urls = listOf("https://www.amazon.com", "https://mytest.com", "https://mytest.com")
51-
for (url in urls) {
52-
assertEquals(false, isHostInDenylist(url, hostDentyList))
53-
}
52+
fun `test hostname gets resolved to ip for denylist`() {
53+
val invalidHost = "invalid.com"
54+
mockkStatic(InetAddress::class)
55+
every { InetAddress.getByName(invalidHost).hostAddress } returns "10.0.0.1" // 10.0.0.0/8
56+
assertEquals(true, isHostInDenylist("https://$invalidHost", hostDenyList))
57+
58+
val validHost = "valid.com"
59+
every { InetAddress.getByName(validHost).hostAddress } returns "174.12.0.0"
60+
assertEquals(false, isHostInDenylist("https://$validHost", hostDenyList))
5461
}
5562

5663
@Test

notifications/core/src/test/kotlin/org/opensearch/notifications/core/destinations/ChimeDestinationTests.kt

+18-10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
package org.opensearch.notifications.core.destinations
77

8+
import io.mockk.every
9+
import io.mockk.mockk
10+
import io.mockk.mockkStatic
811
import org.apache.http.client.methods.CloseableHttpResponse
912
import org.apache.http.client.methods.HttpPost
1013
import org.apache.http.entity.StringEntity
@@ -13,6 +16,7 @@ import org.apache.http.message.BasicStatusLine
1316
import org.easymock.EasyMock
1417
import org.junit.jupiter.api.Assertions
1518
import org.junit.jupiter.api.Assertions.assertEquals
19+
import org.junit.jupiter.api.BeforeEach
1620
import org.junit.jupiter.api.Test
1721
import org.junit.jupiter.api.assertThrows
1822
import org.junit.jupiter.params.ParameterizedTest
@@ -43,23 +47,27 @@ internal class ChimeDestinationTests {
4347
)
4448
}
4549

50+
@BeforeEach
51+
fun setup() {
52+
// Stubbing isHostInDenylist() so it doesn't attempt to resolve hosts that don't exist in the unit tests
53+
mockkStatic("org.opensearch.notifications.spi.utils.ValidationHelpersKt")
54+
every { org.opensearch.notifications.spi.utils.isHostInDenylist(any(), any()) } returns false
55+
}
56+
4657
@Test
4758
fun `test chime message null entity response`() {
48-
val mockHttpClient: CloseableHttpClient = EasyMock.createMock(CloseableHttpClient::class.java)
59+
val mockHttpClient = mockk<CloseableHttpClient>()
4960

5061
// The DestinationHttpClient replaces a null entity with "{}".
5162
val expectedWebhookResponse = DestinationMessageResponse(RestStatus.OK.status, "{}")
5263
// TODO replace EasyMock in all UTs with mockk which fits Kotlin better
53-
val httpResponse: CloseableHttpResponse = EasyMock.createMock(CloseableHttpResponse::class.java)
54-
EasyMock.expect(mockHttpClient.execute(EasyMock.anyObject(HttpPost::class.java))).andReturn(httpResponse)
64+
val httpResponse = mockk<CloseableHttpResponse>()
65+
every { mockHttpClient.execute(any<HttpPost>()) } returns httpResponse
5566

56-
val mockStatusLine: BasicStatusLine = EasyMock.createMock(BasicStatusLine::class.java)
57-
EasyMock.expect(httpResponse.statusLine).andReturn(mockStatusLine)
58-
EasyMock.expect(httpResponse.entity).andReturn(null).anyTimes()
59-
EasyMock.expect(mockStatusLine.statusCode).andReturn(RestStatus.OK.status)
60-
EasyMock.replay(mockHttpClient)
61-
EasyMock.replay(httpResponse)
62-
EasyMock.replay(mockStatusLine)
67+
val mockStatusLine = mockk<BasicStatusLine>()
68+
every { httpResponse.statusLine } returns mockStatusLine
69+
every { httpResponse.entity } returns null
70+
every { mockStatusLine.statusCode } returns RestStatus.OK.status
6371

6472
val httpClient = DestinationHttpClient(mockHttpClient)
6573
val webhookDestinationTransport = WebhookDestinationTransport(httpClient)

notifications/core/src/test/kotlin/org/opensearch/notifications/core/destinations/CustomWebhookDestinationTests.kt

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package org.opensearch.notifications.core.destinations
77

8+
import io.mockk.every
9+
import io.mockk.mockkStatic
810
import org.apache.http.client.methods.CloseableHttpResponse
911
import org.apache.http.client.methods.HttpPatch
1012
import org.apache.http.client.methods.HttpPost
@@ -16,6 +18,7 @@ import org.apache.http.message.BasicStatusLine
1618
import org.easymock.EasyMock
1719
import org.junit.jupiter.api.Assertions
1820
import org.junit.jupiter.api.Assertions.assertEquals
21+
import org.junit.jupiter.api.BeforeEach
1922
import org.junit.jupiter.api.Test
2023
import org.junit.jupiter.api.assertThrows
2124
import org.junit.jupiter.params.ParameterizedTest
@@ -53,6 +56,13 @@ internal class CustomWebhookDestinationTests {
5356
)
5457
}
5558

59+
@BeforeEach
60+
fun setup() {
61+
// Stubbing isHostInDenylist() so it doesn't attempt to resolve hosts that don't exist in the unit tests
62+
mockkStatic("org.opensearch.notifications.spi.utils.ValidationHelpersKt")
63+
every { org.opensearch.notifications.spi.utils.isHostInDenylist(any(), any()) } returns false
64+
}
65+
5666
@ParameterizedTest(name = "method {0} should return corresponding type of Http request object {1}")
5767
@MethodSource("methodToHttpRequestType")
5868
fun `test custom webhook message null entity response`(method: String, expectedHttpClass: Class<HttpUriRequest>) {

notifications/core/src/test/kotlin/org/opensearch/notifications/core/destinations/SlackDestinationTests.kt

+10
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
package org.opensearch.notifications.core.destinations
77

8+
import io.mockk.every
9+
import io.mockk.mockkStatic
810
import org.apache.http.client.methods.CloseableHttpResponse
911
import org.apache.http.client.methods.HttpPost
1012
import org.apache.http.entity.StringEntity
@@ -13,6 +15,7 @@ import org.apache.http.message.BasicStatusLine
1315
import org.easymock.EasyMock
1416
import org.junit.jupiter.api.Assertions
1517
import org.junit.jupiter.api.Assertions.assertEquals
18+
import org.junit.jupiter.api.BeforeEach
1619
import org.junit.jupiter.api.Test
1720
import org.junit.jupiter.api.assertThrows
1821
import org.junit.jupiter.params.ParameterizedTest
@@ -44,6 +47,13 @@ internal class SlackDestinationTests {
4447
)
4548
}
4649

50+
@BeforeEach
51+
fun setup() {
52+
// Stubbing isHostInDenylist() so it doesn't attempt to resolve hosts that don't exist in the unit tests
53+
mockkStatic("org.opensearch.notifications.spi.utils.ValidationHelpersKt")
54+
every { org.opensearch.notifications.spi.utils.isHostInDenylist(any(), any()) } returns false
55+
}
56+
4757
@Test
4858
fun `test Slack message null entity response`() {
4959
val mockHttpClient: CloseableHttpClient = EasyMock.createMock(CloseableHttpClient::class.java)

0 commit comments

Comments
 (0)