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 plugin #104

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

sudowork
Copy link
Contributor

@sudowork sudowork commented Mar 31, 2021

This plugin provides functionality to trigger a webhook after a shot has completed.

Provides the following settings:
webhook_domain
webhook_endpoint
webhook_secret_key
webhook_trigger_after

Sends an HTTP webhook to:

POST https://$webhook_domain$webhook_endpoint
Authorization: $clock!$hmac_signature

$body

The HMAC signature is used so your server can verify the authenticity of a
message. The signature is created from:
HMAC($webhook_secret_key, "$path\n$clock\n$body")

@Mimoja
Copy link
Member

Mimoja commented Mar 31, 2021

Maybe it should be intergrated into the web_api plugin (or the other way round?) and the callback paths can be set from the outside that way?

@jeffsf
Copy link
Contributor

jeffsf commented Apr 1, 2021

As a general comment, spurred by this code, not targeting this code specifically, and remembering that I'm just a customer, not even a committer here, I am hoping to get some clarity from @decentjohn around a policy for third-party plugins as to how they are hosted and delivered.

On a more specific note, I agree with @Mimoja that unification of various, related services is a good idea. I think the existing plugin would benefit from a going over around security. One open port is, for some, already one port too many.

@sudowork
Copy link
Contributor Author

sudowork commented Apr 1, 2021

Yes good question. I wasn’t really sure what the contribution policy was for third party plugins. Happy to take a look at the web_api plugin and see if it makes sense to merge in there, though it may actually be spiritually closer to the visualizer upload.

Also I don’t really mind if this never gets merged in. It’s probably somewhat bespoke.

@decentjohn
Copy link
Contributor

I'm ok with any open port that defaults to localhost. To change it to an external IP, the user has to do something, and thus they take on that liability. That's a fairly common scheme I've seen. So.. bind() needs to be set to 127.0.0.1 as a default for anything that takes communication from the outside.

array set ::plugins::webhook::settings {
webhook_domain "example.com"
webhook_endpoint "/decent/webhook"
webhook_secret_key "SecretKeyForSigning"
Copy link
Contributor

Choose a reason for hiding this comment

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

You can't have a default password on a device that is sold in California unless it is device specific.

ref: https://leginfo.legislature.ca.gov/faces/billTextClient.xhtml?bill_id=201720180SB327

Edit: This may not be an incoming password, but a signing secret.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this is just a signing secret for an outbound request. The signature is then attached as a header to the outbound request.

Copy link
Contributor

Choose a reason for hiding this comment

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

why not make the password blank, and required to be created by the user before the plugin will work?

msg "triggering webhook"
borg toast [translate "Triggering Webhook"]

http::register https 443 [list ::tls::socket -servername $settings(webhook_domain)]
Copy link
Contributor

Choose a reason for hiding this comment

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

The docs might want to mention how to install a server certificate

Getting into how to generate one can probably be skipped, with "search for 'self-signed certificate'" or something appropriate.

If this is just sending POSTs, why is it opening a socket?

Copy link
Contributor Author

@sudowork sudowork Apr 1, 2021

Choose a reason for hiding this comment

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

This may be my misunderstanding, but I don’t believe this is binding anything/ opening a socket for listening. Based on my read of the docs, it’s used to establish a TLS transport for making the HTTPS request to the server: https://www.tcl-lang.org/man/tcl/TclCmd/http.htm#M49
https://wiki.tcl-lang.org/page/HTTPS

Copy link
Contributor

Choose a reason for hiding this comment

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

yes that is correct, there is no listen there, that code is registering essentially an 'extension' to a socket. This is how https GET is implemented on tcl.

http::register https 443 [list ::tls::socket -servername $settings(webhook_domain)]

# Craft HTTP Body
set espresso_data [format_espresso_for_history]
Copy link
Contributor

Choose a reason for hiding this comment

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

This can take over a second to generate on a v1.3 stock tablet with a long shot. I've seen scale timeouts at one second with just the core app and things like the flow-cal profile. Though you've got this on the idle queue and after a delay, it can cause the BLE stack to back up and potentially lose packets.

This would have it running potentially three times; core app, visualiser upload, and web hook

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good call. For my particular use case, I don’t actually require the history, so I’m fine with actually removing it unless someone finds it useful.

^ clock ^ HMAC signature
```

Where `1617092331` represents the clock at the time of the shot, and `f728a8092b6882749efa61a6609414d14c6f25286bdb1fd76e0dda4bab0f0cdf` is the calculated HMAC signature. Note that they are delimited by the character `!`.
Copy link
Contributor

Choose a reason for hiding this comment

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

...clock at the time the payload generation began...

Why something predictable here? If this is just confirming that the sender and the receiver share the same secret, why not just a random number?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Clock is used as a way to have a time-based component to the signature to mitigate replay attacks (really probably overkill for this). On the server receiving the request, it can verify that the timestamp is within some interval of the server time.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK, reasoning makes sense to me now

proc post_shot_data {} {
variable settings

msg "triggering webhook"
Copy link
Contributor

Choose a reason for hiding this comment

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

On msg, please set severity codes. We're working on getting the logs down to the point where they can be meaningfully run by everyone, all the time, especially to help out the support team.

See https://github.com/decentespresso/de1app/blob/main/de1plus/logging.tcl#L28

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Will do

after [expr 1000 * $::plugins::webhook::settings(webhook_trigger_after)] ::plugins::webhook::post_shot_data
}

proc msg { msg } {
Copy link
Contributor

Choose a reason for hiding this comment

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

borg toast "Webhook succeeded!"
}

proc async_dispatch {old new} {
Copy link
Contributor

@jeffsf jeffsf Apr 1, 2021

Choose a reason for hiding this comment

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

The {old new} arg list was from the previous system that only triggered on major state changes.

As you're not doing anything with the states (yet), {event_dict} is what the event will be called with.


proc main {} {
::de1::event::listener::after_flow_complete_add \
[lambda {event_dict} {
Copy link
Contributor

Choose a reason for hiding this comment

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

See note above at Line 113

If your callback accepts {event_dict} the wrapper for old-style callbacks is not needed.

}

proc main {} {
::de1::event::listener::after_flow_complete_add \
Copy link
Contributor

Choose a reason for hiding this comment

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

package require wibble ??

Definitely should not be in the outer body or file, as it causes loading of the library even if the plugin is disabled.

There's some complexity here, looking back at #73

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Is this comment meant for another PR?

Copy link
Contributor

Choose a reason for hiding this comment

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

I had thought you were opening a server, which I think people do with the wibble library. Though installed with AndroWish, it isn't part of the stock distro. ::http is shown as part of the standard distro, so you don't need wibble.

@jeffsf
Copy link
Contributor

jeffsf commented Apr 1, 2021

Please don't take the comments as a lack of appreciation for the work!

I'm picky. If I didn't think the concept was worth pursuing, I wouldn't have taken the time.

@decentjohn
Copy link
Contributor

Definitely, if you're doing a API, use ::http not wibble. Simpler is better.

Provides the following settings:
webhook_domain
webhook_endpoint
webhook_secret_key
webhook_trigger_after

Sends an HTTP webhook to:
```
POST https://$webhook_domain$webhook_endpoint
Authorization: $clock!$hmac_signature

$body
```

The HMAC signature is used so your server can verify the authenticity of a
message. The signature is created from:
`HMAC($webhook_secret_key, "$path\n$clock\n$body")`
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants