diff --git a/cmd/livepeer/starter/flags.go b/cmd/livepeer/starter/flags.go index 05f015e572..34e0a36f94 100644 --- a/cmd/livepeer/starter/flags.go +++ b/cmd/livepeer/starter/flags.go @@ -66,7 +66,7 @@ func NewLivepeerConfig(fs *flag.FlagSet) LivepeerConfig { // Live AI: cfg.MediaMTXApiPassword = fs.String("mediaMTXApiPassword", "", "HTTP basic auth password for MediaMTX API requests") cfg.LiveAITrickleHostForRunner = fs.String("liveAITrickleHostForRunner", "", "Trickle Host used by AI Runner; It's used to overwrite the publicly available Trickle Host") - cfg.LiveAIAuthApiKey = fs.String("liveAIAuthApiKey", "", "API key to use for Live AI authentication requests") + cfg.LiveAIAuthSecret = fs.String("liveAIAuthSecret", "", "HMAC secret for Live AI auth signing") cfg.LiveAIHeartbeatURL = fs.String("liveAIHeartbeatURL", "", "Base URL for Live AI heartbeat requests") cfg.LiveAIHeartbeatHeaders = fs.String("liveAIHeartbeatHeaders", "", "Map of headers to use for Live AI heartbeat requests. e.g. 'header:val,header2:val2'") cfg.LiveAIHeartbeatInterval = fs.Duration("liveAIHeartbeatInterval", *cfg.LiveAIHeartbeatInterval, "Interval to send Live AI heartbeat requests") @@ -173,4 +173,4 @@ func UpdateNilsForUnsetFlags(cfg LivepeerConfig) LivepeerConfig { } return res -} +} \ No newline at end of file diff --git a/cmd/livepeer/starter/starter.go b/cmd/livepeer/starter/starter.go index f1eaca003b..9e2dad670a 100755 --- a/cmd/livepeer/starter/starter.go +++ b/cmd/livepeer/starter/starter.go @@ -176,7 +176,7 @@ type LivepeerConfig struct { KafkaPassword *string KafkaGatewayTopic *string MediaMTXApiPassword *string - LiveAIAuthApiKey *string + LiveAIAuthSecret *string LiveAIHeartbeatURL *string LiveAIHeartbeatHeaders *string LiveAIHeartbeatInterval *time.Duration @@ -1676,8 +1676,8 @@ func StartLivepeer(ctx context.Context, cfg LivepeerConfig) { if cfg.MediaMTXApiPassword != nil { n.MediaMTXApiPassword = *cfg.MediaMTXApiPassword } - if cfg.LiveAIAuthApiKey != nil { - n.LiveAIAuthApiKey = *cfg.LiveAIAuthApiKey + if cfg.LiveAIAuthSecret != nil { + n.LiveAIAuthSecret = *cfg.LiveAIAuthSecret } if cfg.LiveAIHeartbeatURL != nil { n.LiveAIHeartbeatURL = *cfg.LiveAIHeartbeatURL diff --git a/core/livepeernode.go b/core/livepeernode.go index 07c5841ff1..19213d5013 100644 --- a/core/livepeernode.go +++ b/core/livepeernode.go @@ -159,7 +159,7 @@ type LivepeerNode struct { MediaMTXApiPassword string LiveAITrickleHostForRunner string - LiveAIAuthApiKey string + LiveAIAuthSecret string LiveAIHeartbeatURL string LiveAIHeartbeatHeaders map[string]string LiveAIHeartbeatInterval time.Duration diff --git a/server/ai_mediaserver.go b/server/ai_mediaserver.go index 27a911f7b2..f3ffaa3492 100644 --- a/server/ai_mediaserver.go +++ b/server/ai_mediaserver.go @@ -505,8 +505,15 @@ func (ls *LivepeerServer) StartLiveVideo() http.Handler { mediaMTXClient := media.NewMediaMTXClient(remoteHost, ls.mediaMTXApiPassword, sourceID, sourceType) whepURL := generateWhepUrl(streamName, requestID) - if LiveAIAuthWebhookURL != nil { - authResp, err := authenticateAIStream(LiveAIAuthWebhookURL, ls.liveAIAuthApiKey, AIAuthRequest{ + authURL := LiveAIAuthWebhookURL + override := qp.Get("webhookUrl") + if override != "" { + if parsed, err := url.Parse(override); err == nil && parsed.Scheme != "" && parsed.Host != "" { + authURL = parsed + } + } + if authURL != nil { + authResp, err := authenticateAIStream(authURL, ls.liveAIAuthSecret, AIAuthRequest{ Stream: streamName, Type: sourceTypeStr, QueryParams: queryParams, @@ -977,8 +984,15 @@ func (ls *LivepeerServer) CreateWhip(server *media.WHIPServer) http.Handler { ctx = clog.AddVal(ctx, "source_type", sourceTypeStr) - if LiveAIAuthWebhookURL != nil { - authResp, err := authenticateAIStream(LiveAIAuthWebhookURL, ls.liveAIAuthApiKey, AIAuthRequest{ + authURL := LiveAIAuthWebhookURL + override := r.URL.Query().Get("webhookUrl") + if override != "" { + if parsed, err := url.Parse(override); err == nil && parsed.Scheme != "" && parsed.Host != "" { + authURL = parsed + } + } + if authURL != nil { + authResp, err := authenticateAIStream(authURL, ls.liveAIAuthSecret, AIAuthRequest{ Stream: streamName, Type: sourceTypeStr, QueryParams: queryParams, diff --git a/server/auth.go b/server/auth.go index 457df00afb..50a3de2927 100644 --- a/server/auth.go +++ b/server/auth.go @@ -10,6 +10,10 @@ import ( "net/url" "time" + "crypto/hmac" + "crypto/sha256" + "encoding/hex" + "github.com/golang/glog" "github.com/livepeer/go-livepeer/monitor" ) @@ -133,7 +137,7 @@ type AIAuthResponse struct { paramsMap map[string]interface{} // unmarshaled params } -func authenticateAIStream(authURL *url.URL, apiKey string, req AIAuthRequest) (*AIAuthResponse, error) { +func authenticateAIStream(authURL *url.URL, secret string, req AIAuthRequest) (*AIAuthResponse, error) { req.StreamKey = req.Stream if authURL == nil { return nil, fmt.Errorf("No auth URL configured") @@ -151,8 +155,14 @@ func authenticateAIStream(authURL *url.URL, apiKey string, req AIAuthRequest) (* } request.Header.Set("Content-Type", "application/json") - request.Header.Set("x-api-key", apiKey) - request.Header.Set("Authorization", apiKey) + + ts := time.Now().UTC().Format(time.RFC3339Nano) + m := hmac.New(sha256.New, []byte(secret)) + m.Write([]byte(ts)) + m.Write(jsonValue) + sig := hex.EncodeToString(m.Sum(nil)) + request.Header.Set("x-timestamp", ts) + request.Header.Set("x-signature", sig) resp, err := http.DefaultClient.Do(request) if err != nil { diff --git a/server/mediaserver.go b/server/mediaserver.go index 8e9d75d9fb..21c38997b4 100644 --- a/server/mediaserver.go +++ b/server/mediaserver.go @@ -127,7 +127,7 @@ type LivepeerServer struct { serverLock *sync.RWMutex mediaMTXApiPassword string - liveAIAuthApiKey string + liveAIAuthSecret string livePaymentInterval time.Duration outSegmentTimeout time.Duration } @@ -196,7 +196,7 @@ func NewLivepeerServer(ctx context.Context, rtmpAddr string, lpNode *core.Livepe recordingsAuthResponses: cache.New(time.Hour, 2*time.Hour), AISessionManager: NewAISessionManager(lpNode, AISessionManagerTTL), mediaMTXApiPassword: lpNode.MediaMTXApiPassword, - liveAIAuthApiKey: lpNode.LiveAIAuthApiKey, + liveAIAuthSecret: lpNode.LiveAIAuthSecret, livePaymentInterval: lpNode.LivePaymentInterval, outSegmentTimeout: lpNode.LiveOutSegmentTimeout, }