-
Notifications
You must be signed in to change notification settings - Fork 171
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
Webhook URL #752
Webhook URL #752
Conversation
@j0sh If this looks ok, then I will (at least try) add tests for it. |
82e9170
to
efa0681
Compare
added tests |
efa0681
to
9f6aaa2
Compare
cmd/livepeer/livepeer.go
Outdated
@@ -102,6 +102,7 @@ func main() { | |||
faceValue := flag.Float64("faceValue", 0, "The faceValue to expect in PM tickets, denominated in ETH (e.g. 0.3)") | |||
winProb := flag.Float64("winProb", 0, "The win probability to expect in PM tickets, as a percent float between 0 and 100 (e.g. 5.3)") | |||
maxSessions := flag.Int("maxSessions", 10, "Orchestrator only. Maximum number of concurrent transcoding sessions") | |||
webhookURL := flag.String("webhookUrl", "", "Authentication webhook URL") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe the description could be "RTMP authentication webhook URL". Should we also call it something like authUrl
so we don't have to rename things if/when we add more hooks later on?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
cmd/livepeer/livepeer.go
Outdated
@@ -379,6 +380,19 @@ func main() { | |||
// TODO provide an option to disable this? | |||
*rtmpAddr = defaultAddr(*rtmpAddr, "127.0.0.1", RtmpPort) | |||
*httpAddr = defaultAddr(*httpAddr, "127.0.0.1", RpcPort) | |||
if *webhookURL != "" { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps factor this out into a getWebhookURL
helper? Eg, server.WebhookURL = getWebhookURL(*webhookURL)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
server/mediaserver.go
Outdated
} | ||
s.connectionLock.RUnlock() | ||
|
||
if !authenticateStream(string(mid)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's authenticate first thing, before acquiring the lock.
I'm thinking we should pass in the full URL rather than the ManifestID. That way the auth endpoint can inspect the entire host + path, and enforce their own rules about path construction.
The thought was to have the webhook URL return the manifest ID. We should also structure the response to leave room for further customization, eg transcoding options for the stream.
GET https://host/webhook/endpoint
Request Body: { "url" : <url> }
Response Body: { "manifestID" : <manifestID> }
We could also do a POST here since a GET with a body is a bit unusual.
func createRTMPStreamIDHandler(s *LivepeerServer) func(url *url.URL) (strmID string) {
return func(url *url.URL) (strmID string) {
// Create a ManifestID
// If an auth webhook is defined, get the mid from the webhook
// Else if manifestID is passed in, use that one
// Else create one
var mid string
if WebhookURL != "" {
mid, err = authenticateStream(url)
if err != nil {
return ""
}
} else {
mid = parseManifestID(...)
}
```
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's authenticate first thing, before acquiring the lock.
Previous check will allow us to quit early in case of same Manifest ids, so why not?
The thought was to have the webhook URL return the manifest ID
I really don't remember discussing such thing. Can you point me to such discussion?
We should also structure the response to leave room for further customization,
This is starting to be something other from what we're discussed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Previous check will allow us to quit early in case of same Manifest ids
If the manifest ID is defined by the webhook then the webhook needs to come before. Also avoids acquiring the lock which also helps.
I really don't remember discussing such thing. Can you point me to such discussion?
Yeah, we had a lot of discussions around this and some details may have been lost. This was mentioned here https://hackmd.io/n0Lq1l6_SJCe0VnVHf4G-A#Webhook-Flow which was outlined as part of the discussion here :
https://docs.google.com/a/livepeer.org/document/d/1ThTyFeTjTfnWWj-HeNCMnkglD7Vf-9YvA71fB9dKVQs/edit?disco=AAAACet6oNM
This is starting to be something other from what we're discussed.
How so? The end result is the same, we aren't changing the scope of the work here. But tweaking the webhook format makes the construct much more versatile.
Once we merge this, it effectively becomes part of the public API, and it's harder to make breaking changes (even internally), so it helps a lot to leave a little room for expansion.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
missed this one
was outlined as part of the discussion here :
https://docs.google.com/a/livepeer.org/document/d/1ThTyFeTjTfnWWj-HeNCMnkglD7Vf-9YvA71fB9dKVQs/edit?disco=AAAACet6oNM
Here is no mention of webhook defining manifest ids.
How so? The end result is the same, we aren't changing the scope of the work here.
Webhook's server should be more complicated now, plus our logic gets more complicated - what to do if webhook returned manifest id that we already have? Or if it is invalid manifest id?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here is no mention of webhook defining manifest ids.
There seems to have been quite a bit lost "in translation" when migrating from the old doc to the new one.
Alternative Implementation - Webhook:
Start up node with a-streamAuthWebhook http://host/streamauth
RTMP stream comes in
Node calls the streamAuthWebhook with the RTMP URL, and other parameters?
Node receives manifest ID in response (disconnects incoming rtmp stream if webhook response indicates an invalid stream)
Stream is available for playback via the manifest ID
The behavior spec in the old doc also details how it would be desirable to have the input URL to be decoupled from the output URL to prevent input hijacking or guessing playback URLs given an input. Having the webhook return the manifest ID enables that.
Do you think that is not useful?
Webhook's server should be more complicated now
Reading a JSON request and replying with JSON is standard, as far as APIs go.
plus our logic gets more complicated
Our logic stays the same. The only thing that changes is the webhook gets called first to set the manifest id, if necessary.
A webhook is a large feature as far as API surface area goes, and we want to extract as much utility from it as possible. Right now its utility is limited.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've added POST API, with only one addition - if webhook returns 200 with empty body - it is enough to pass authentication. This will simplify creation of simple webhooks.
57128e5
to
a14173f
Compare
cmd/livepeer/livepeer.go
Outdated
return "", true | ||
} | ||
glog.Infof("Using webhook url %s", u) | ||
return "", false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return u
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed
cmd/livepeer/livepeer.go
Outdated
@@ -499,6 +505,23 @@ func main() { | |||
} | |||
} | |||
|
|||
func getAuthWebhookURL(u string) (string, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps returning the error here rather than a bool would be more informative
cmd/livepeer/livepeer.go
Outdated
|
||
var err bool | ||
if server.AuthWebhookURL, err = getAuthWebhookURL(*authWebhookURL); err { | ||
return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The logging + exit can be done once here by returning an error:
if server.AuthWebhookURL, err = getAuthWebookURL(*authWebhookURL); err != nil {
glog.Fatal("Error setting auth webhook URL ", err)
}
I've been trying to use glog.Fatal
around livepeer.go
to log and exit.
Then maybe some test cases can be added in the new script checking cli args - https://github.com/livepeer/go-livepeer/blob/master/test_args.sh
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
server/mediaserver.go
Outdated
if AuthWebhookURL == "" { | ||
return core.ManifestID(mid), true | ||
} | ||
payload := "{\"manifestID\":\"" + mid + "\",\"url\":\"" + url + "\"}" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you can use backticks to avoid escaping, like `{"url":"`+url.String()+`"}`
. Using fmt.Sprintf
might also help readability around concatenation.
Don't think passing in the manifestID is necessary since any query strings can be extracted from the URL itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Originally it was done to pass randomly generated manifest id, but you're right, there is no point in that. Fixed.
server/mediaserver.go
Outdated
|
||
func authenticateStream(url, mid string) (core.ManifestID, bool) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Returning an error rather than a bool would help upstream logging and avoid the need for logs within this function
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
server/mediaserver.go
Outdated
@@ -154,19 +162,59 @@ func createRTMPStreamIDHandler(s *LivepeerServer) func(url *url.URL) (strmID str | |||
mid = core.RandomManifestID() | |||
} | |||
|
|||
var auth bool | |||
if mid, auth = authenticateStream(url.String(), string(mid)); !auth { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the flow clearer if we called authenticateStream
first, before the manifestID checks. This also avoids having to pass in the manifest ID itself, which makes authenticateStream
easier to understand (single responsibility). Also, returning an error rather than bool is more helpful.
mid, err := authenticateStream(url)
if err != nil {
glog.Error("Could not authenticate stream: ", err)
return ""
}
if mid == "" {
mid = parseManifestID(url.Query().Get("manifestID"))
}
if mid == "" {
mid = core.RandomManifestID()
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
server/mediaserver.go
Outdated
// Ensure there's no concurrent StreamID with the same name | ||
s.connectionLock.RLock() | ||
defer s.connectionLock.RUnlock() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can probably leave the defer
here since subsequent operations are not expensive. Less error prone
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
server/mediaserver_test.go
Outdated
mux.HandleFunc("/auth/invalidjson", func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte("{manifestID\":}")) | ||
}) | ||
go srv.ListenAndServe() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spinning up a live HTTP server for testing can be unpredictable / problematic so it's best to avoid it if possible. It's not always possible, but here I think we can avoid it here we follow the format that's used in https://github.com/livepeer/go-livepeer/blob/90c10193860999497cef5f1031fec4890bb9f19e/server/handlers_test.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
cmd/livepeer/livepeer.go
Outdated
@@ -106,6 +106,9 @@ func main() { | |||
gsBucket := flag.String("gsbucket", "", "Google storage bucket") | |||
gsKey := flag.String("gskey", "", "Google Storage private key file name (in json format)") | |||
|
|||
// API | |||
authWebhookURL := flag.String("authWebhookUrl", "", "Authentication webhook URL") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can the message mention that it's a "RTMP authentication webhook URL" ? That makes it clearer what it's used for.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
cmd/livepeer/livepeer.go
Outdated
} | ||
if p.Scheme != "http" && p.Scheme != "https" { | ||
glog.Error("Webhook URL should be HTTP or HTTP") | ||
return "", true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return "", fmt.Errorf("Webhook URL should be HTTP or HTTPS")
then we can avoid the logging.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
a14173f
to
f319978
Compare
f319978
to
0bb05b2
Compare
@j0sh Looks like I've addressed all the comments |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tested with a local auth server and works nicely!! Just one style nit, otherwise LGTM 🚢
mid := parseManifestID(url.Query().Get("manifestID")) | ||
var mid core.ManifestID | ||
var err error | ||
if mid, err = authenticateStream(url.String()); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a tiny style nit, but we don't need to predeclare the vars here (and would save one line overall):
mid, err := authenticateStream(url.String())
if err != nil {
What does this pull request do? Explain your changes. (required)
Webhook URL option - allows authenticate incoming streams through call to webhook
Specific updates (required)
Adds
webhookUrl
option to Livepeer node - this URL get called upon receiving RTMP connection and manifest id being passed to it. If this request returns non 200 response, then RTMP connection being denied.How did you test each of these updates (required)
Does this pull request close any open issues?
Fixes #703
Checklist:
./test.sh
pass