Skip to content

Commit 6ad760d

Browse files
make it generic
1 parent b4a3f4d commit 6ad760d

6 files changed

Lines changed: 44 additions & 60 deletions

File tree

.github/CONTRIBUTING.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,23 @@
1-
# Contributing to otoroshi-plugin-yousign-webhook-validator
1+
# Contributing to otoroshi-plugin-webhook-validator
22

3-
These guidelines apply to all projects living in the the `cloud-apim/otoroshi-plugin-yousign-webhook-validator` repository.
3+
These guidelines apply to all projects living in the the `cloud-apim/otoroshi-plugin-webhook-validator` repository.
44

55
These guidelines are meant to be a living document that should be changed and adapted as needed.
66
We encourage changes that make it easier to achieve our goals in an efficient way.
77

88
## Codebase
99

10-
* [src](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator/src): contains the otoroshi-plugin-yousign-webhook-validator sources and tests
10+
* [src](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator/src): contains the otoroshi-plugin-webhook-validator sources and tests
1111

1212
## Workflow
1313

1414
The steps below describe how to get a patch into a main development branch (e.g. `maon`).
1515
The steps are exactly the same for everyone involved in the project (be it core team, or first time contributor).
1616
We follow the standard GitHub [fork & pull](https://help.github.com/articles/using-pull-requests/#fork--pull) approach to pull requests. Just fork the official repo, develop in a branch, and submit a PR!
1717

18-
1. To avoid duplicated effort, it might be good to check the [issue tracker](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator/issues) and [existing pull requests](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator/pulls) for existing work.
19-
- If there is no ticket yet, feel free to [create one](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator/issues/new) to discuss the problem and the approach you want to take to solve it.
20-
1. [Fork the project](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator#fork-destination-box) on GitHub. You'll need to create a feature-branch for your work on your fork, as this way you'll be able to submit a pull request against the mainline otoroshi-plugin-yousign-webhook-validator.
18+
1. To avoid duplicated effort, it might be good to check the [issue tracker](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator/issues) and [existing pull requests](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator/pulls) for existing work.
19+
- If there is no ticket yet, feel free to [create one](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator/issues/new) to discuss the problem and the approach you want to take to solve it.
20+
1. [Fork the project](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator#fork-destination-box) on GitHub. You'll need to create a feature-branch for your work on your fork, as this way you'll be able to submit a pull request against the mainline otoroshi-plugin-webhook-validator.
2121
1. Create a branch on your fork and work on the feature. For example: `git checkout -b wip-awesome-new-feature`
2222
- Please make sure to follow the general quality guidelines (specified below) when developing your patch.
2323
- Please write additional tests covering your feature and adjust existing ones if needed before submitting your pull request.
@@ -32,7 +32,7 @@ We follow the standard GitHub [fork & pull](https://help.github.com/articles/usi
3232

3333
The TL;DR; of the above very precise workflow version is:
3434

35-
1. Fork otoroshi-plugin-yousign-webhook-validator
35+
1. Fork otoroshi-plugin-webhook-validator
3636
2. Hack and test on your feature (on a branch)
3737
3. Document it
3838
4. Submit a PR

.github/workflows/release.yaml

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: release-otoroshi-plugin-yousign-webhook-validator
1+
name: release-otoroshi-plugin-webhook-validator
22

33
on:
44
workflow_dispatch:
@@ -26,15 +26,15 @@ jobs:
2626
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
2727
run: |
2828
sbt ';compile;package;assembly'
29-
rm ./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar
30-
mv ./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator-assembly_2.12-dev.jar ./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar
29+
rm ./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar
30+
mv ./target/scala-2.12/otoroshi-plugin-webhook-validator-assembly_2.12-dev.jar ./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar
3131
- name: Generate SHA-256
3232
run: |
33-
shasum -a 256 ./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar | cut -d ' ' -f 1 > ./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar.sha256
33+
shasum -a 256 ./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar | cut -d ' ' -f 1 > ./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar.sha256
3434
- name: Release binary and SHA-256 checksum to GitHub
3535
uses: softprops/action-gh-release@v1
3636
with:
3737
tag_name: ${{ inputs.version }}
3838
files: |
39-
./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar
40-
./target/scala-2.12/otoroshi-plugin-yousign-webhook-validator_2.12-${{ inputs.version }}.jar.sha256
39+
./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar
40+
./target/scala-2.12/otoroshi-plugin-webhook-validator_2.12-${{ inputs.version }}.jar.sha256

SECURITY.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# Security Policy
22

3-
We strongly advise you to update your otoroshi-plugin-yousign-webhook-validator usage as soon as a new version is released.
3+
We strongly advise you to update your otoroshi-plugin-webhook-validator usage as soon as a new version is released.
44

55
## Supported Versions
66

77
Each version is supported until the next one is released
88

99
## Reporting a Vulnerability
1010

11-
We want to keep otoroshi-plugin-yousign-webhook-validator safe for everyone. If you've discovered a security vulnerability in otoroshi-plugin-yousign-webhook-validator, we appreciate your help in disclosing it to us in a responsible manner by using the dedicated [github form](https://github.com/cloud-apim/otoroshi-plugin-yousign-webhook-validator/security) or by sending an email to [security@cloud-apim.com](mailto:security@cloud-apim.com) containing your name, an email address, a description of the vulnerability, the impacted versions of otoroshi-plugin-yousign-webhook-validator. You can encrypt the email content using [this pgp key](https://mathieuancelin.keybase.pub/pgp_key.asc)
11+
We want to keep otoroshi-plugin-webhook-validator safe for everyone. If you've discovered a security vulnerability in otoroshi-plugin-webhook-validator, we appreciate your help in disclosing it to us in a responsible manner by using the dedicated [github form](https://github.com/cloud-apim/otoroshi-plugin-webhook-validator/security) or by sending an email to [security@cloud-apim.com](mailto:security@cloud-apim.com) containing your name, an email address, a description of the vulnerability, the impacted versions of otoroshi-plugin-webhook-validator. You can encrypt the email content using [this pgp key](https://mathieuancelin.keybase.pub/pgp_key.asc)

build.sbt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@ lazy val excludesSlf4j = Seq(
1111

1212
lazy val root = (project in file("."))
1313
.settings(
14-
name := "otoroshi-plugin-yousign-webhook-validator",
14+
name := "otoroshi-plugin-webhook-validator",
1515
assembly / test := {},
16-
assembly / assemblyJarName := "otoroshi-plugin-yousign-webhook-validator-assembly_2.12-dev.jar",
16+
assembly / assemblyJarName := "otoroshi-plugin-webhook-validator-assembly_2.12-dev.jar",
1717
libraryDependencies ++= Seq(
1818
"fr.maif" %% "otoroshi" % "17.13.0" % "provided",
1919
munit % Test

readme.md

Lines changed: 8 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
# Cloud APIM – YouSign Webhook Validator – Otoroshi plugin
1+
# Cloud APIM – Webhook Validator – Otoroshi plugin
22

3-
An [Otoroshi](https://github.com/MAIF/otoroshi) plugin that validates [YouSign](https://developers.yousign.com/docs/use-webhooks-in-your-app) webhook payloads before they reach your backend.
3+
An [Otoroshi](https://github.com/MAIF/otoroshi) plugin that validates webhook payloads before they reach your backend using body payload signature validation.
44

55
## How it works
66

7-
The plugin is provider-agnostic: the signature header, HMAC algorithm and prefix are all configurable. Out of the box it is pre-configured for YouSign, whose webhooks include an `X-Yousign-Signature-256` header containing an HMAC-SHA256 hash of the raw request body prefixed with `sha256=`.
7+
The plugin is provider-agnostic: the signature header, HMAC algorithm and prefix are all configurable.
88

99
The plugin:
1010

@@ -50,10 +50,10 @@ $ curl -X POST 'http://otoroshi-api.oto.tools:8080/api/routes' \
5050

5151
## Plugin configuration
5252

53-
| Field | Type | Required | Default | Description |
54-
|--------------------|----------|----------|----------------------------|--------------------------------------------------------------------------------------|
55-
| `secret` | `string` | yes || The HMAC secret shared with the webhook provider (e.g. YouSign webhook secret). |
56-
| `signature_header` | `string` | no | `X-Yousign-Signature-256` | Name of the HTTP header that carries the signature. |
53+
| Field | Type | Required | Default | Description |
54+
|--------------------|----------|----------|----------------------------|-------------------------------------------------------------------------------------|
55+
| `secret` | `string` | yes || The HMAC secret shared with the webhook provider. |
56+
| `signature_header` | `string` | no | `X-Yousign-Signature-256` | Name of the HTTP header that carries the signature. |
5757
| `algorithm` | `string` | no | `HmacSHA256` | Java HMAC algorithm name. Supported values: `HmacSHA256`, `HmacSHA512`, `HmacSHA384`, `HmacSHA1`. |
5858
| `prefix` | `string` | no | derived from `algorithm` | String prepended to the hex hash before comparison (e.g. `sha256=`). Defaults are derived automatically from the chosen algorithm. |
5959

@@ -84,30 +84,17 @@ $ curl -X POST 'http://otoroshi-api.oto.tools:8080/api/routes' \
8484
| `401 Unauthorized` | `{ "error": "invalid signature" }` | The computed HMAC does not match the header value. |
8585
| `401 Unauthorized` | `{ "error": "webhook secret not configured" }` | The plugin `secret` field is empty. |
8686

87-
## YouSign webhook headers
88-
89-
The following headers are sent by YouSign on every webhook call:
90-
91-
| Header | Description |
92-
|--------|-------------|
93-
| `X-Yousign-Signature-256` | `sha256=<hmac-sha256 hex>` – used by this plugin for payload authentication |
94-
| `X-Yousign-Retry` | Retry attempt counter (0 for the first delivery) |
95-
| `X-Yousign-Issued-At` | Timestamp of webhook transmission |
96-
| `Content-Type` | Always `application/json` |
97-
| `User-Agent` | Always `Yousign Webhook Bot` |
9887

9988
## Security notes
10089

10190
- The plugin uses **constant-time byte comparison** (`MessageDigest.isEqual`) to prevent timing-based side-channel attacks.
102-
- YouSign only delivers webhooks over **HTTPS**; make sure your Otoroshi route is exposed on a TLS-enabled domain.
103-
- YouSign webhooks originate from the following CIDRs: `5.39.7.128/28`, `52.143.162.31`, `51.103.81.166`. You can add an Otoroshi IP allowlist plugin alongside this one for defence-in-depth.
10491

10592
## Build
10693

10794
```shell
10895
sbt assembly
10996
```
11097

111-
The resulting jar is placed in `target/scala-2.12/otoroshi-plugin-yousign-webhook-validator-assembly_2.12-dev.jar`.
98+
The resulting jar is placed in `target/scala-2.12/otoroshi-plugin-webhook-validator-assembly_2.12-dev.jar`.
11299

113100
Copy it to your Otoroshi `plugins/` directory (or reference it via the classpath loader) and restart Otoroshi.

src/main/scala/com/cloud/apim/otoroshi/plugins/yousign/validator.scala renamed to src/main/scala/com/cloud/apim/otoroshi/plugins/webhook/validator.scala

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package otoroshi_plugins.com.cloud.apim.otoroshi.plugins.yousign
1+
package otoroshi_plugins.com.cloud.apim.otoroshi.plugins.webhook
22

33
import akka.stream.Materializer
44
import akka.stream.scaladsl.Source
@@ -17,31 +17,31 @@ import javax.crypto.spec.SecretKeySpec
1717
import scala.concurrent.{ExecutionContext, Future}
1818
import scala.util.{Failure, Success, Try}
1919

20-
case class YouSignWebhookValidatorConfig(
20+
case class WebhookValidatorConfig(
2121
secret: String = "",
2222
signatureHeader: String = "X-Yousign-Signature-256",
2323
algorithm: String = "HmacSHA256",
2424
prefix: String = "sha256=",
2525
) extends NgPluginConfig {
26-
def json: JsValue = YouSignWebhookValidatorConfig.format.writes(this)
26+
def json: JsValue = WebhookValidatorConfig.format.writes(this)
2727
}
2828

29-
object YouSignWebhookValidatorConfig {
30-
val default: YouSignWebhookValidatorConfig = YouSignWebhookValidatorConfig()
31-
val format: Format[YouSignWebhookValidatorConfig] = new Format[YouSignWebhookValidatorConfig] {
32-
override def writes(o: YouSignWebhookValidatorConfig): JsValue = Json.obj(
29+
object WebhookValidatorConfig {
30+
val default: WebhookValidatorConfig = WebhookValidatorConfig()
31+
val format: Format[WebhookValidatorConfig] = new Format[WebhookValidatorConfig] {
32+
override def writes(o: WebhookValidatorConfig): JsValue = Json.obj(
3333
"secret" -> o.secret,
3434
"signature_header" -> o.signatureHeader,
3535
"algorithm" -> o.algorithm,
3636
"prefix" -> o.prefix,
3737
)
38-
override def reads(json: JsValue): JsResult[YouSignWebhookValidatorConfig] = Try {
38+
override def reads(json: JsValue): JsResult[WebhookValidatorConfig] = Try {
3939
val algo = json.select("algorithm").asOpt[String].getOrElse("HmacSHA256")
40-
YouSignWebhookValidatorConfig(
40+
WebhookValidatorConfig(
4141
secret = json.select("secret").asOpt[String].getOrElse(""),
4242
signatureHeader = json.select("signature_header").asOpt[String].getOrElse("X-Yousign-Signature-256"),
4343
algorithm = algo,
44-
prefix = json.select("prefix").asOpt[String].getOrElse(YouSignWebhookValidatorConfig.defaultPrefix(algo)),
44+
prefix = json.select("prefix").asOpt[String].getOrElse(WebhookValidatorConfig.defaultPrefix(algo)),
4545
)
4646
} match {
4747
case Failure(e) => JsError(e.getMessage)
@@ -78,21 +78,21 @@ object YouSignWebhookValidatorConfig {
7878
))
7979
}
8080

81-
class YouSignWebhookValidator extends NgRequestTransformer {
81+
class WebhookPayloadValidator extends NgRequestTransformer {
8282

83-
private val logger = Logger("otoroshi-cloud-apim-yousign-webhook-validator")
83+
private val logger = Logger("otoroshi-cloud-apim-webhook-payload-validator")
8484

8585
override def steps: Seq[NgStep] = Seq(NgStep.TransformRequest)
8686
override def categories: Seq[NgPluginCategory] = Seq(NgPluginCategory.AccessControl, NgPluginCategory.Custom("Cloud APIM"))
8787
override def visibility: NgPluginVisibility = NgPluginVisibility.NgUserLand
8888
override def multiInstance: Boolean = true
8989
override def core: Boolean = false
90-
override def name: String = "Cloud APIM - YouSign Webhook Validator"
91-
override def description: Option[String] = Some("This plugin validates webhook payloads by verifying an HMAC signature. The header name, algorithm and prefix are all configurable (defaults to YouSign's X-Yousign-Signature-256 / HmacSHA256 / sha256=).")
92-
override def defaultConfigObject: Option[NgPluginConfig] = Some(YouSignWebhookValidatorConfig.default)
90+
override def name: String = "Cloud APIM - Webhook Payload Validator"
91+
override def description: Option[String] = Some("This plugin validates webhook payloads by verifying an HMAC signature. The header name, algorithm and prefix are all configurable.")
92+
override def defaultConfigObject: Option[NgPluginConfig] = Some(WebhookValidatorConfig.default)
9393
override def noJsForm: Boolean = false
94-
override def configFlow: Seq[String] = YouSignWebhookValidatorConfig.configFlow
95-
override def configSchema: Option[JsObject] = YouSignWebhookValidatorConfig.configSchema
94+
override def configFlow: Seq[String] = WebhookValidatorConfig.configFlow
95+
override def configSchema: Option[JsObject] = WebhookValidatorConfig.configSchema
9696

9797
override def isTransformRequestAsync: Boolean = true
9898
override def usesCallbacks: Boolean = false
@@ -101,7 +101,7 @@ class YouSignWebhookValidator extends NgRequestTransformer {
101101
override def transformsError: Boolean = false
102102

103103
override def start(env: Env): Future[Unit] = {
104-
env.logger.info("[Cloud APIM] the 'YouSign Webhook Validator' plugin is available !")
104+
env.logger.info("[Cloud APIM] the 'Webhook Validator' plugin is available !")
105105
().vfuture
106106
}
107107

@@ -113,8 +113,7 @@ class YouSignWebhookValidator extends NgRequestTransformer {
113113
}
114114

115115
override def transformRequest(ctx: NgTransformerRequestContext)(implicit env: Env, ec: ExecutionContext, mat: Materializer): Future[Either[Result, NgPluginHttpRequest]] = {
116-
val config = ctx.cachedConfig(internalName)(YouSignWebhookValidatorConfig.format).getOrElse(YouSignWebhookValidatorConfig.default)
117-
116+
val config = ctx.cachedConfig(internalName)(WebhookValidatorConfig.format).getOrElse(WebhookValidatorConfig.default)
118117
if (config.secret.isEmpty) {
119118
logger.warn("[Webhook Validator] no secret configured, rejecting request")
120119
Left(Results.Unauthorized(Json.obj("error" -> "webhook secret not configured"))).vfuture
@@ -123,7 +122,6 @@ class YouSignWebhookValidator extends NgRequestTransformer {
123122
case None =>
124123
if (logger.isDebugEnabled) logger.debug(s"[Webhook Validator] missing ${config.signatureHeader} header")
125124
Left(Results.Unauthorized(Json.obj("error" -> s"missing ${config.signatureHeader} header"))).vfuture
126-
127125
case Some(receivedSignature) =>
128126
ctx.otoroshiRequest.body.runFold(ByteString.empty)(_ ++ _).map { bodyBytes =>
129127
val computedHash = computeHmac(config.algorithm, config.secret, bodyBytes)
@@ -133,7 +131,6 @@ class YouSignWebhookValidator extends NgRequestTransformer {
133131
logger.debug(s"[Webhook Validator] expected : $expectedSignature")
134132
logger.debug(s"[Webhook Validator] received : $receivedSignature")
135133
}
136-
137134
// Constant-time comparison to prevent timing-attack side channels
138135
val expected = expectedSignature.getBytes("UTF-8")
139136
val received = receivedSignature.getBytes("UTF-8")

0 commit comments

Comments
 (0)