generated from leanix/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CID-2777: Secure webhook listener endpoint - Initial implementation
- Loading branch information
1 parent
63070e8
commit e403039
Showing
8 changed files
with
116 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/main/kotlin/net/leanix/githubagent/controllers/advice/GlobalExceptionHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
package net.leanix.githubagent.controllers.advice | ||
|
||
import net.leanix.githubagent.exceptions.InvalidEventSignatureException | ||
import net.leanix.githubagent.exceptions.WebhookSecretNotSetException | ||
import org.slf4j.Logger | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.http.HttpStatus | ||
import org.springframework.http.ProblemDetail | ||
import org.springframework.web.bind.annotation.ControllerAdvice | ||
import org.springframework.web.bind.annotation.ExceptionHandler | ||
|
||
@ControllerAdvice | ||
class GlobalExceptionHandler { | ||
|
||
val exceptionLogger: Logger = LoggerFactory.getLogger(GlobalExceptionHandler::class.java) | ||
|
||
@ExceptionHandler(InvalidEventSignatureException::class) | ||
fun handleInvalidEventSignatureException(exception: InvalidEventSignatureException): ProblemDetail { | ||
val problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.UNAUTHORIZED, "Invalid event signature") | ||
problemDetail.title = exception.message | ||
exceptionLogger.warn(exception.message) | ||
return problemDetail | ||
} | ||
|
||
@ExceptionHandler(WebhookSecretNotSetException::class) | ||
fun handleWebhookSecretNotSetException(exception: WebhookSecretNotSetException): ProblemDetail { | ||
val problemDetail = ProblemDetail.forStatusAndDetail(HttpStatus.BAD_REQUEST, "Webhook secret not set") | ||
problemDetail.title = exception.message | ||
return problemDetail | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
50 changes: 50 additions & 0 deletions
50
src/main/kotlin/net/leanix/githubagent/services/GitHubWebhookService.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package net.leanix.githubagent.services | ||
|
||
import net.leanix.githubagent.config.GitHubEnterpriseProperties | ||
import net.leanix.githubagent.exceptions.InvalidEventSignatureException | ||
import net.leanix.githubagent.exceptions.WebhookSecretNotSetException | ||
import net.leanix.githubagent.shared.SUPPORTED_EVENT_TYPES | ||
import net.leanix.githubagent.shared.hmacSHA256 | ||
import net.leanix.githubagent.shared.timingSafeEqual | ||
import org.slf4j.LoggerFactory | ||
import org.springframework.stereotype.Service | ||
|
||
@Service | ||
class GitHubWebhookService( | ||
private val webhookService: WebhookService, | ||
private val gitHubEnterpriseProperties: GitHubEnterpriseProperties | ||
) { | ||
|
||
private val logger = LoggerFactory.getLogger(GitHubWebhookService::class.java) | ||
private var isWebhookProcessingEnabled = true | ||
|
||
fun processWebhookEvent(eventType: String, host: String, signature256: String?, payload: String) { | ||
if (!isWebhookProcessingEnabled) { | ||
throw WebhookSecretNotSetException() | ||
} | ||
if (!gitHubEnterpriseProperties.baseUrl.contains(host)) { | ||
logger.error("Received a webhook event from an unknown host: $host") | ||
return | ||
} | ||
if (gitHubEnterpriseProperties.webhookSecret == "" && !signature256.isNullOrEmpty()) { | ||
logger.error( | ||
"Event signature is present but webhook secret is not set, " + | ||
"please restart the agent with a valid secret" | ||
) | ||
isWebhookProcessingEnabled = false | ||
throw WebhookSecretNotSetException() | ||
} | ||
if (gitHubEnterpriseProperties.webhookSecret != "" && !signature256.isNullOrEmpty()) { | ||
val hashedSecret = hmacSHA256(gitHubEnterpriseProperties.webhookSecret, payload) | ||
val isEqual = timingSafeEqual(signature256.removePrefix("sha256="), hashedSecret) | ||
if (!isEqual) throw InvalidEventSignatureException() | ||
} else { | ||
logger.warn("Webhook secret is not set, Skipping signature verification") | ||
} | ||
if (SUPPORTED_EVENT_TYPES.contains(eventType.uppercase())) { | ||
webhookService.consumeWebhookEvent(eventType, payload) | ||
} else { | ||
logger.warn("Received an unsupported event of type: $eventType") | ||
} | ||
} | ||
} |
24 changes: 24 additions & 0 deletions
24
src/main/kotlin/net/leanix/githubagent/shared/GitHubWebHookEventHelper.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package net.leanix.githubagent.shared | ||
|
||
import javax.crypto.Mac | ||
import javax.crypto.spec.SecretKeySpec | ||
|
||
fun hmacSHA256(secret: String, data: String): String { | ||
val secretKey = SecretKeySpec(secret.toByteArray(), "HmacSHA256") | ||
val mac = Mac.getInstance("HmacSHA256") | ||
mac.init(secretKey) | ||
val hmacData = mac.doFinal(data.toByteArray()) | ||
return hmacData.joinToString("") { "%02x".format(it) } | ||
} | ||
|
||
fun timingSafeEqual(a: String, b: String): Boolean { | ||
val aBytes = a.toByteArray() | ||
val bBytes = b.toByteArray() | ||
if (aBytes.size != bBytes.size) return false | ||
|
||
var diff = 0 | ||
for (i in aBytes.indices) { | ||
diff = diff or (aBytes[i].toInt() xor bBytes[i].toInt()) | ||
} | ||
return diff == 0 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters