From dfafda7665ab32661afbac353296f4caffa8af29 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Thu, 29 Jul 2021 15:13:06 -0700 Subject: [PATCH 1/6] Add SNS data model Signed-off-by: Joshua Li --- .../commons/notifications/model/ConfigType.kt | 5 + .../commons/notifications/model/SNS.kt | 112 ++++++++++++++++++ .../model/config/ConfigDataProperties.kt | 3 + 3 files changed, 120 insertions(+) create mode 100644 src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt index 0dbe70a6..d5400a75 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/ConfigType.kt @@ -57,6 +57,11 @@ enum class ConfigType(val tag: String) { return tag } }, + SNS("sns") { + override fun toString(): String { + return tag + } + }, SMTP_ACCOUNT("smtp_account") { override fun toString(): String { return tag diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt new file mode 100644 index 00000000..b40ba647 --- /dev/null +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -0,0 +1,112 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ +package org.opensearch.commons.notifications.model + +import org.opensearch.common.io.stream.StreamInput +import org.opensearch.common.io.stream.StreamOutput +import org.opensearch.common.io.stream.Writeable +import org.opensearch.common.xcontent.ToXContent +import org.opensearch.common.xcontent.XContentBuilder +import org.opensearch.common.xcontent.XContentParser +import org.opensearch.common.xcontent.XContentParserUtils +import java.io.IOException +import java.util.regex.Pattern + +/** + * SNS notification data model + */ +data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { + + init { + require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } + if (roleARN != null) { + require(IAM_ARN_REGEX.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } + } + } + + override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { + return builder.startObject(SNS_TYPE) + .field(TOPIC_ARN_FIELD, topicARN) + .field(ROLE_ARN_FIELD, roleARN) // TODO: optional? + .endObject() + } + + /** + * Constructor used in transport action communication. + * @param input StreamInput stream to deserialize data from. + */ + constructor(input: StreamInput) : this( + topicARN = input.readString(), + roleARN = input.readString() + ) + + @Throws(IOException::class) + override fun writeTo(out: StreamOutput) { + out.writeString(topicARN) + out.writeOptionalString(roleARN) + } + + companion object { + + private val SNS_ARN_REGEX = + Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") + private val IAM_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") + + const val TOPIC_ARN_FIELD = "topic_arn" + const val ROLE_ARN_FIELD = "role_arn" + const val SNS_TYPE = "sns" + + /** + * reader to create instance of class from writable. + */ + val reader = Writeable.Reader { SNS(it) } + + /** + * Parser to parse xContent + */ + val xParser = XParser { parse(it) } + + @JvmStatic + @Throws(IOException::class) + fun parse(xcp: XContentParser): SNS { + lateinit var topicARN: String + var roleARN: String? = null + + XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) + while (xcp.nextToken() != XContentParser.Token.END_OBJECT) { + val fieldName = xcp.currentName() + xcp.nextToken() + when (fieldName) { + TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() + ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() + else -> { + throw IllegalStateException("Unexpected field: $fieldName, while parsing SNS destination") + } + } + } + // if (DestinationType.snsUseIamRole) { + // requireNotNull(roleARN) { "SNS Action role_arn is null" } + // } + return SNS(requireNotNull(topicARN) { "SNS Action topic_arn is null" }, roleARN) + } + + @JvmStatic + @Throws(IOException::class) + fun readFrom(sin: StreamInput): SNS? { + return if (sin.readBoolean()) { + SNS( + topicARN = sin.readString(), + roleARN = sin.readOptionalString() + ) + } else null + } + } +} diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt index 1500879c..34bd2edc 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/config/ConfigDataProperties.kt @@ -33,6 +33,7 @@ import org.opensearch.commons.notifications.model.Chime import org.opensearch.commons.notifications.model.ConfigType import org.opensearch.commons.notifications.model.Email import org.opensearch.commons.notifications.model.EmailGroup +import org.opensearch.commons.notifications.model.SNS import org.opensearch.commons.notifications.model.Slack import org.opensearch.commons.notifications.model.SmtpAccount import org.opensearch.commons.notifications.model.Webhook @@ -53,6 +54,7 @@ internal object ConfigDataProperties { Pair(ConfigType.CHIME, ConfigProperty(Chime.reader, Chime.xParser)), Pair(ConfigType.WEBHOOK, ConfigProperty(Webhook.reader, Webhook.xParser)), Pair(ConfigType.EMAIL, ConfigProperty(Email.reader, Email.xParser)), + Pair(ConfigType.SNS, ConfigProperty(SNS.reader, SNS.xParser)), Pair(ConfigType.EMAIL_GROUP, ConfigProperty(EmailGroup.reader, EmailGroup.xParser)), Pair(ConfigType.SMTP_ACCOUNT, ConfigProperty(SmtpAccount.reader, SmtpAccount.xParser)) ) @@ -78,6 +80,7 @@ internal object ConfigDataProperties { ConfigType.EMAIL_GROUP -> configData is EmailGroup ConfigType.SMTP_ACCOUNT -> configData is SmtpAccount ConfigType.CHIME -> configData is Chime + ConfigType.SNS -> configData is SNS ConfigType.NONE -> true } } From 44a2cb2d97f60c223c537ccde61f87d55d5376d6 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Thu, 29 Jul 2021 16:38:53 -0700 Subject: [PATCH 2/6] Fix parsing of SNS object Signed-off-by: Joshua Li --- .../opensearch/commons/notifications/model/EventStatus.kt | 1 + .../kotlin/org/opensearch/commons/notifications/model/SNS.kt | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt index 1233a997..bf72836d 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/EventStatus.kt @@ -64,6 +64,7 @@ data class EventStatus( ConfigType.WEBHOOK -> requireNotNull(deliveryStatus) ConfigType.SLACK -> requireNotNull(deliveryStatus) ConfigType.EMAIL -> require(emailRecipientStatus.isNotEmpty()) + ConfigType.SNS -> requireNotNull(deliveryStatus) ConfigType.NONE -> log.info("Some config field not recognized") else -> { log.info("non-allowed config type for Status") diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt index b40ba647..c13e1a1b 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -17,6 +17,7 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.utils.fieldIfNotNull import java.io.IOException import java.util.regex.Pattern @@ -33,9 +34,9 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { } override fun toXContent(builder: XContentBuilder, params: ToXContent.Params): XContentBuilder { - return builder.startObject(SNS_TYPE) + return builder.startObject() .field(TOPIC_ARN_FIELD, topicARN) - .field(ROLE_ARN_FIELD, roleARN) // TODO: optional? + .fieldIfNotNull(ROLE_ARN_FIELD, roleARN) .endObject() } From 2130509ab060eec3021e488ad90938ef7f1cce35 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Fri, 6 Aug 2021 15:18:24 -0700 Subject: [PATCH 3/6] Add unit tests for SNS Signed-off-by: Joshua Li --- .../commons/notifications/model/SNS.kt | 15 +-- .../commons/notifications/model/SNSTests.kt | 104 ++++++++++++++++++ 2 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt index c13e1a1b..0c32a6e9 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -18,6 +18,7 @@ import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils import org.opensearch.commons.utils.fieldIfNotNull +import org.opensearch.commons.utils.logger import java.io.IOException import java.util.regex.Pattern @@ -46,7 +47,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { */ constructor(input: StreamInput) : this( topicARN = input.readString(), - roleARN = input.readString() + roleARN = input.readOptionalString() ) @Throws(IOException::class) @@ -56,6 +57,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { } companion object { + private val log by logger(SNS::class.java) private val SNS_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") @@ -78,7 +80,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { @JvmStatic @Throws(IOException::class) fun parse(xcp: XContentParser): SNS { - lateinit var topicARN: String + var topicARN: String? = null var roleARN: String? = null XContentParserUtils.ensureExpectedToken(XContentParser.Token.START_OBJECT, xcp.currentToken(), xcp) @@ -89,14 +91,13 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { TOPIC_ARN_FIELD -> topicARN = xcp.textOrNull() ROLE_ARN_FIELD -> roleARN = xcp.textOrNull() else -> { - throw IllegalStateException("Unexpected field: $fieldName, while parsing SNS destination") + xcp.skipChildren() + log.info("Unexpected field: $fieldName, while parsing SNS destination") } } } - // if (DestinationType.snsUseIamRole) { - // requireNotNull(roleARN) { "SNS Action role_arn is null" } - // } - return SNS(requireNotNull(topicARN) { "SNS Action topic_arn is null" }, roleARN) + topicARN ?: throw IllegalArgumentException("$TOPIC_ARN_FIELD field absent") + return SNS(topicARN, roleARN) } @JvmStatic diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt new file mode 100644 index 00000000..8aff7167 --- /dev/null +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt @@ -0,0 +1,104 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + * + * The OpenSearch Contributors require contributions made to + * this file be licensed under the Apache-2.0 license or a + * compatible open source license. + * + * Modifications Copyright OpenSearch Contributors. See + * GitHub history for details. + */ + +package org.opensearch.commons.notifications.model + +import com.fasterxml.jackson.core.JsonParseException +import org.junit.jupiter.api.Assertions +import org.junit.jupiter.api.Assertions.assertThrows +import org.junit.jupiter.api.Test +import org.opensearch.commons.utils.createObjectFromJsonString +import org.opensearch.commons.utils.getJsonString +import org.opensearch.commons.utils.recreateObject + +internal class SNSTests { + + @Test + fun `SNS should throw exception if empty topic`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("", null) + } + val jsonString = "{\"topic_arn\":\"\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception if invalid topic ARN`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("arn:aws:es:us-east-1:012345678989:test", null) + } + val jsonString = "{\"topic_arn\":\"arn:aws:es:us-east-1:012345678989:test\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception if invalid role ARN`() { + assertThrows(IllegalArgumentException::class.java) { + SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") + } + val jsonString = "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS serialize and deserialize transport object should be equal`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val recreatedObject = recreateObject(sampleSNS) { SNS(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS serialize and deserialize using json object should be equal`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = getJsonString(sampleSNS) + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS should deserialize json object using parser`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } + + @Test + fun `SNS should throw exception when invalid json object is passed`() { + val jsonString = "sample message" + assertThrows(JsonParseException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should throw exception when arn is replace with arn2 in json object`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam::012345678912:role/iam-test") + val jsonString = "{\"topic_arn2\":\"${sampleSNS.topicARN}\",\"role_arn\":\"${sampleSNS.roleARN}\"}" + assertThrows(IllegalArgumentException::class.java) { + createObjectFromJsonString(jsonString) { SNS.parse(it) } + } + } + + @Test + fun `SNS should safely ignore extra field in json object`() { + val sampleSNS = SNS("arn:aws:sns:us-east-1:012345678912:topic-test", null) + val jsonString = "{\"topic_arn\":\"${sampleSNS.topicARN}\", \"another\":\"field\"}" + val recreatedObject = createObjectFromJsonString(jsonString) { SNS.parse(it) } + Assertions.assertEquals(sampleSNS, recreatedObject) + } +} From 21701a1f1fbea885f825edc96abe6962401e1706 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Fri, 6 Aug 2021 15:33:36 -0700 Subject: [PATCH 4/6] Fix formatting Signed-off-by: Joshua Li --- .../org/opensearch/commons/notifications/model/SNSTests.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt index 8aff7167..46d7cfc8 100644 --- a/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt +++ b/src/test/kotlin/org/opensearch/commons/notifications/model/SNSTests.kt @@ -48,7 +48,8 @@ internal class SNSTests { assertThrows(IllegalArgumentException::class.java) { SNS("arn:aws:sns:us-east-1:012345678912:topic-test", "arn:aws:iam:us-east-1:0123456789:role-test") } - val jsonString = "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" + val jsonString = + "{\"topic_arn\":\"arn:aws:sns:us-east-1:012345678912:topic-test\",\"role_arn\":\"arn:aws:iam:us-east-1:0123456789:role-test\"}" assertThrows(IllegalArgumentException::class.java) { createObjectFromJsonString(jsonString) { SNS.parse(it) } } From bafe9aa674bc356cf129c81802a953976798c376 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 9 Aug 2021 09:49:22 -0700 Subject: [PATCH 5/6] Move constants out of SNS model Signed-off-by: Joshua Li --- .../commons/notifications/NotificationConstants.kt | 2 ++ .../org/opensearch/commons/notifications/model/SNS.kt | 6 ++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt index 344cf0b8..04f3affe 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/NotificationConstants.kt @@ -42,6 +42,8 @@ object NotificationConstants { const val TAGS_TAG = "tags" const val URL_TAG = "url" const val HEADER_PARAMS_TAG = "header_params" + const val TOPIC_ARN_FIELD = "topic_arn" + const val ROLE_ARN_FIELD = "role_arn" const val HOST_TAG = "host" const val PORT_TAG = "port" const val METHOD_TAG = "method" diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt index 0c32a6e9..3506cced 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -17,6 +17,8 @@ import org.opensearch.common.xcontent.ToXContent import org.opensearch.common.xcontent.XContentBuilder import org.opensearch.common.xcontent.XContentParser import org.opensearch.common.xcontent.XContentParserUtils +import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD +import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger import java.io.IOException @@ -63,10 +65,6 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") private val IAM_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") - const val TOPIC_ARN_FIELD = "topic_arn" - const val ROLE_ARN_FIELD = "role_arn" - const val SNS_TYPE = "sns" - /** * reader to create instance of class from writable. */ From be06c67b170a814e64f63fbcb8d2305158519447 Mon Sep 17 00:00:00 2001 From: Joshua Li Date: Mon, 9 Aug 2021 14:51:13 -0700 Subject: [PATCH 6/6] Move validating role ARN function to utils Signed-off-by: Joshua Li --- .../org/opensearch/commons/notifications/model/SNS.kt | 4 ++-- .../org/opensearch/commons/utils/ValidationHelpers.kt | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt index 3506cced..12cdba4f 100644 --- a/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt +++ b/src/main/kotlin/org/opensearch/commons/notifications/model/SNS.kt @@ -21,6 +21,7 @@ import org.opensearch.commons.notifications.NotificationConstants.ROLE_ARN_FIELD import org.opensearch.commons.notifications.NotificationConstants.TOPIC_ARN_FIELD import org.opensearch.commons.utils.fieldIfNotNull import org.opensearch.commons.utils.logger +import org.opensearch.commons.utils.validateIAMRoleArn import java.io.IOException import java.util.regex.Pattern @@ -32,7 +33,7 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { init { require(SNS_ARN_REGEX.matcher(topicARN).find()) { "Invalid AWS SNS topic ARN: $topicARN" } if (roleARN != null) { - require(IAM_ARN_REGEX.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } + validateIAMRoleArn(roleARN) } } @@ -63,7 +64,6 @@ data class SNS(val topicARN: String, val roleARN: String?) : BaseConfigData { private val SNS_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:sns:([a-zA-Z0-9-]+):([0-9]{12}):([a-zA-Z0-9-_]+)$") - private val IAM_ARN_REGEX = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") /** * reader to create instance of class from writable. diff --git a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt index 0124179f..fc117923 100644 --- a/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt +++ b/src/main/kotlin/org/opensearch/commons/utils/ValidationHelpers.kt @@ -28,6 +28,7 @@ package org.opensearch.commons.utils import java.net.URL +import java.util.regex.Pattern // Valid ID characters = (All Base64 chars + "_-") to support UUID format and Base64 encoded IDs private val VALID_ID_CHARS: Set = (('a'..'z') + ('A'..'Z') + ('0'..'9') + '+' + '/' + '_' + '-').toSet() @@ -69,3 +70,8 @@ fun isValidEmail(email: String): Boolean { fun isValidId(idString: String): Boolean { return idString.isNotBlank() && idString.all { VALID_ID_CHARS.contains(it) } } + +fun validateIAMRoleArn(roleARN: String) { + val roleArnRegex = Pattern.compile("^arn:aws(-[^:]+)?:iam::([0-9]{12}):([a-zA-Z_0-9+=,.@\\-_/]+)$") + require(roleArnRegex.matcher(roleARN).find()) { "Invalid AWS role ARN: $roleARN " } +}