Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement webhook validation #279

Merged
merged 13 commits into from
Mar 27, 2023
Merged

Implement webhook validation #279

merged 13 commits into from
Mar 27, 2023

Conversation

mickmister
Copy link
Contributor

Summary

Viewing the second commit in isolation will make reviewing this PR much more straightforward, since I moved around some code in the first commit.

Description copied from #278:

Zoom has changed how their webhook setup works, and requires some logic on our end to validate the webhook is setup properly. This involves:

The webhook must be validated before Zoom's interface will allow the admin to save the webhook configuration in Zoom's app setup UI. This does not affect existing setups of the plugin, but it will affect any new setup, and any instance of an admin wanting to update the webhook secret or change the events associated with the webhook configuration.

Setting up the webhook for validation requires us to store a new Secret Token from Zoom's webhook configuration UI. If this value is not set, we simply skip the "signature verification" step when processing webhook events. We technically don't need to do this step, because Zoom has no way of verifying this is occurring on subsequent webhook events, and because we are already using our own secret for the webhook. Either way, it's best to comply with what Zoom is asking for, and it adds another layer of security provided by Zoom.

Ticket Link

Fixes #278

@mickmister mickmister added 2: Dev Review Requires review by a core committer 3: QA Review Requires review by a QA tester labels Aug 11, 2022
if !strings.Contains(r.Header.Get("Content-Type"), "application/json") {
res := fmt.Sprintf("Expected Content-Type 'application/json' for webhook request, received '%s'.", r.Header.Get("Content-Type"))
p.API.LogWarn(res)
http.Error(w, res, http.StatusBadRequest)

Check warning

Code scanning / CodeQL

Reflected cross-site scripting

Cross-site scripting vulnerability due to [user-provided value](1).
@codecov
Copy link

codecov bot commented Aug 15, 2022

Codecov Report

Patch coverage: 58.38% and project coverage change: +23.51 🎉

Comparison is base (cb15d74) 0.00% compared to head (47712f6) 23.51%.

Additional details and impacted files
@@             Coverage Diff             @@
##           master     #279       +/-   ##
===========================================
+ Coverage    0.00%   23.51%   +23.51%     
===========================================
  Files           8        9        +1     
  Lines        1143     1212       +69     
===========================================
+ Hits            0      285      +285     
+ Misses       1143      874      -269     
- Partials        0       53       +53     
Impacted Files Coverage Δ
server/configuration.go 25.24% <ø> (+25.24%) ⬆️
server/http.go 20.00% <0.00%> (+20.00%) ⬆️
server/webhook.go 59.11% <59.11%> (ø)

... and 4 files with indirect coverage changes

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

☔ View full report in Codecov by Sentry.
📢 Do you have feedback about the report comment? Let us know in this issue.

@DHaussermann DHaussermann mentioned this pull request Aug 15, 2022
@DHaussermann
Copy link

This seems functional I can see the hook working when It's deployed. However the validation functionality seems to now be removed from the Zoom so I can't test the API change.

Also it's a bit odd that we're keeping the webhook secret in the query string. We would need to be very clear with the documentation about this but it is working 👍

@DHaussermann
Copy link

cc @mickmister ☝️

@mickmister
Copy link
Contributor Author

@DHaussermann I kept the webhook secret in the query string because it is something we have control over and is battle tested in our system. We can treat it as the primary secret check, while adhering to Zoom's new requirements when they take effect. This also was the simplest route for backwards compatibility.

@mickmister
Copy link
Contributor Author

@DHaussermann Can you please test this for backwards compatibility? Thank you!

@catalintomai
Copy link

cc: @DHaussermann , when you have the bandwidth (or have Malik looking into it).

@DHaussermann
Copy link

/update-branch

@hanzei hanzei requested review from trilopin and srkgupta and removed request for catalintomai, trilopin and srkgupta February 21, 2023 09:58
Copy link
Contributor

@trilopin trilopin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's some work here :)

I left some comments, the only ones I consider blocking are the double-deserialization ones.

On the other hand, I had some problems doing manual QA here but it's probably my fault. Also manually tested 🎉 .

"key": "ZoomWebhookSecret",
"display_name": "Zoom Webhook Secret:",
"type": "text",
"help_text": "`Secret Token` taken from Zoom's webhook configuration page",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a followup jira task/PR to update the documentation with this step?

EncryptionKey string
WebhookSecret string
ZoomWebhookSecret string
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ZoomWebhookSecret string
// ZoomWebhookSecret is the secret taken from Zoom's webhook configuration page
ZoomWebhookSecret string

2/5 I know that this is a bit repetitive but it helps a lot to disambiguate when seeing at WebhookSecret vs ZoomWebhookSecret as conf keys.

I'd add the docstring at least in both secret fields.


func (p *Plugin) handleWebhook(w http.ResponseWriter, r *http.Request) {
if !p.verifyWebhookSecret(r) {
p.API.LogWarn("Could not verify webhook secret")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0/5 if it's easy I'd return a different log/string-error for secrets that are empty or exactly "WEBHOOKSECRET" (people copying exactly the doc).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We actually enforce the value to be defined on plugin startup, so no need to have that condition handled here

server/webhook.go Show resolved Hide resolved
p.API.LogError("Error unmarshaling webhook", "err", err.Error())
http.Error(w, err.Error(), http.StatusBadRequest)
return
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thoughts on merging the read block and unmarshal block?

	if err := json.NewDecoder(r.Body).Decode(&webhook); err != nil {
        // log
        http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is written the way that it is because we would essentially be "discarding" the HTTP body that would need to be re-read if json.NewDecoder was used.

server/webhook.go Show resolved Hide resolved
server/webhook.go Show resolved Hide resolved
server/webhook.go Show resolved Hide resolved
Copy link
Contributor

@trilopin trilopin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@mickmister mickmister requested a review from trilopin March 8, 2023 20:37
server/http.go Outdated
Comment on lines 440 to 444
func (p *Plugin) deauthorizeUser(w http.ResponseWriter, r *http.Request) {
if !p.verifyWebhookSecret(r) {
if !p.verifyMattermostWebhookSecret(r) {
http.Error(w, "Not authorized", http.StatusUnauthorized)
return
}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After renaming this called method and durfaced this change, it seems odd to me that we are using a webhook secret for a deauthorizeUser operation. I'm not sure if this route gets called

Copy link
Contributor Author

@mickmister mickmister Mar 10, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a feature of the plugin that uses the webhooks secret:

## Deauthorization
This plugin allows users to be deauthorized directly from Zoom, in order to comply with Zoom’s commitment to security and the protection of user data. If you **would like to publish on Zoom Marketplace**, you can set up a deauthorization URL.
1. Select **Information**.
2. Near the end of the page, is a section called **Deauthorization Notification**.
3. Enter a valid **Endpoint URL** \(`https://SITEURL/plugins/zoom/deauthorization?secret=WEBHOOKSECRET`\).
* `SITEURL` should be your Mattermost server URL.
* `WEBHOOKSECRET` is generated during [Mattermost Setup](../mattermost-setup.md).

We aren't checking the verification of Zoom's signature in this case. I don't think this is worth holding up this PR though.

Copy link
Contributor

@trilopin trilopin Mar 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree on doing that in a separate effort, pls create a Jira ticket for that and add a comment with the jira link close to the related code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created ticket here #291

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! Could you please add a comment to deauthorizeUser with the link to the ticket?

@mickmister mickmister removed the 2: Dev Review Requires review by a core committer label Mar 22, 2023
@DHaussermann
Copy link

@mickmister I do not see a change in behavior when running this build.
Webhook end point validation is still failing in the Zoom Setup wizard.

image

image

I double checked the URL and secret. I'm a bit confused....

  1. I assume the path where webhook is exposed is not changing?
  2. So what path is the Zoom wizard checking and is there a way I can check it manually with the plugin running? If I could confirm the change with CURL or Postman it will help isolate what's happening here.
  3. Probably not important but I get an older version stamp when I build this ☝️

@mickmister
Copy link
Contributor Author

@DHaussermann I tested this out again and it's working for me. Can we meet to discuss this?

Copy link

@DHaussermann DHaussermann left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested and passed

  • Webhooks are working
  • Webhook validation wizard is working
  • Dev provided doc changes for new secret value
  • Tested on Zoom upgrade and Fresh install
  • Tested on new Zoom app and editing features of an existing Zoom app

LGTM!

@DHaussermann DHaussermann added 4: Reviews Complete All reviewers have approved the pull request and removed 3: QA Review Requires review by a QA tester labels Mar 27, 2023
@mickmister mickmister merged commit 6331608 into master Mar 27, 2023
@mickmister mickmister deleted the webhook-validation branch March 27, 2023 21:49
@mickmister mickmister mentioned this pull request Mar 27, 2023
mickmister added a commit that referenced this pull request Mar 28, 2023
* move webhook handlers to its own file

* implement webhook validation

* lint

* return useful error message

* document usage of zoom's webhook secret

* PR feedback

* rename methods for readability

* add todo comment

---------

Co-authored-by: Mattermost Build <[email protected]>
mickmister added a commit that referenced this pull request Apr 3, 2023
* bump version to 1.6.2

* update github actions

* .nvmrc

* golangci

* Implement webhook validation (#279)

* move webhook handlers to its own file

* implement webhook validation

* lint

* return useful error message

* document usage of zoom's webhook secret

* PR feedback

* rename methods for readability

* add todo comment

---------

Co-authored-by: Mattermost Build <[email protected]>

* fix merge issues

* go mod tidy

* [MM-51751] Fix regex for tags trigger in CI/CD (#294)

---------

Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Ben Schumacher <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4: Reviews Complete All reviewers have approved the pull request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Implement webhook validation
6 participants