From ce169d3a4a84d5e4b7a8a3585d0346617bfa413e Mon Sep 17 00:00:00 2001 From: Anton Schubert Date: Wed, 18 Nov 2020 00:36:14 +0100 Subject: [PATCH] implement HTTP auth, fix authconfig parsing --- auth/http.go | 52 +++++++++++++++++++++++++++++++++++++------- auth/static.go | 12 ++++++---- config.toml.example | 18 ++++++++++----- config/config.go | 53 +++++++++++++++++---------------------------- main.go | 7 +++++- stream/streamid.go | 11 ++++++++++ 6 files changed, 101 insertions(+), 52 deletions(-) diff --git a/auth/http.go b/auth/http.go index e4b6aea..61cc4ce 100644 --- a/auth/http.go +++ b/auth/http.go @@ -1,23 +1,59 @@ package auth -import "github.com/voc/srtrelay/stream" +import ( + "log" + "net/http" + "net/url" + "time" + + "github.com/voc/srtrelay/stream" +) type httpAuth struct { - url string + config HTTPAuthConfig + client *http.Client } -type HttpAuthConfig struct { - URL string +type HTTPAuthConfig struct { + URL string + Application string + Timeout time.Duration // Timeout for Auth request + PasswordParam string // POST Parameter containing stream passphrase } -func NewHttpAuth(config HttpAuthConfig) *httpAuth { +// NewHttpAuth creates an Authenticator with a HTTP backend +func NewHTTPAuth(config HTTPAuthConfig) *httpAuth { return &httpAuth{ - url: config.URL, + config: config, + client: &http.Client{ + Timeout: config.Timeout, + }, } } // Implement Authenticator + +// Authenticate sends form-data in a POST-request to the configured url. +// If the response code is 2xx the publish/play is allowed, otherwise it is denied. +// This should be compatible with nginx-rtmps on_play/on_publish directives. +// https://github.com/arut/nginx-rtmp-module/wiki/Directives#on_play func (h *httpAuth) Authenticate(streamid stream.StreamID) bool { - // TODO: implement post request - return false + response, err := h.client.PostForm(h.config.URL, url.Values{ + "call": {streamid.Mode().String()}, + "app": {h.config.Application}, + "name": {streamid.Name()}, + h.config.PasswordParam: {streamid.Password()}, + }) + + if err != nil { + log.Println("http-auth:", err) + return false + } + defer response.Body.Close() + + if response.StatusCode < 200 || response.StatusCode >= 300 { + return false + } + + return true } diff --git a/auth/static.go b/auth/static.go index 1d99d9b..5b67cb8 100644 --- a/auth/static.go +++ b/auth/static.go @@ -4,7 +4,7 @@ import ( "github.com/voc/srtrelay/stream" ) -type staticAuth struct { +type StaticAuth struct { allow []string } @@ -12,14 +12,18 @@ type StaticAuthConfig struct { Allow []string } -func NewStaticAuth(config StaticAuthConfig) *staticAuth { - return &staticAuth{ +// NewStaticAuth creates an Authenticator with a static config backend +func NewStaticAuth(config StaticAuthConfig) *StaticAuth { + return &StaticAuth{ allow: config.Allow, } } // Implement Authenticator -func (auth *staticAuth) Authenticate(streamid stream.StreamID) bool { + +// Authenticate tries to match the stream id against the locally +// configured matches in the allowlist. +func (auth *StaticAuth) Authenticate(streamid stream.StreamID) bool { for _, allowed := range auth.allow { if streamid.Match(allowed) { return true diff --git a/config.toml.example b/config.toml.example index 0c214c2..9f4a07f 100644 --- a/config.toml.example +++ b/config.toml.example @@ -28,10 +28,16 @@ #allow = ["*", "publish/foo/bar", "play/*"] [auth.http] -# Streams are authenticated using HTTP callbacks -# nginx-rtmp on_publish/on_subscribe compatible http auth -#url = "http://localhost/auth" +# Streams are authenticated using HTTP POST calls against this URL +# Should be compatible to nginx-rtmp on_publish/on_subscribe directives +#url = "http://localhost:8080/publish" -# static -# the full url with be // -#app = "stream" +# auth timeout duration +#timeout = "1s" + +# Value of the 'app' form-field to send in the POST request +# Needed for compatibility with the RTMP-application field +#application = "stream" + +# Key of the form-field to send the stream password in +#passwordParam = "auth" \ No newline at end of file diff --git a/config/config.go b/config/config.go index c854b5e..03c01d6 100644 --- a/config/config.go +++ b/config/config.go @@ -1,11 +1,11 @@ package config import ( - "errors" "fmt" "io/ioutil" "log" "os" + "time" "github.com/pelletier/go-toml" "github.com/voc/srtrelay/auth" @@ -23,42 +23,22 @@ type AppConfig struct { Buffersize uint } -type AuthType int - -const ( - AuthTypeStatic AuthType = iota - AuthTypeHttp -) - -func (a *AuthType) UnmarshalTOML(src interface{}) error { - log.Println("got", src) - switch v := src.(type) { - case string: - if v == "static" { - *a = AuthTypeStatic - } else if v == "http" { - *a = AuthTypeHttp - } else { - return fmt.Errorf("Unknown type '%s'", v) - } - return nil - default: - } - return errors.New("Unknown type") -} - type AuthConfig struct { - Type AuthType + Type string Static auth.StaticAuthConfig - Http auth.HttpAuthConfig + HTTP auth.HTTPAuthConfig } -func GetAuthenticator(conf AuthConfig) auth.Authenticator { - if conf.Type == AuthTypeHttp { - return auth.NewHttpAuth(conf.Http) +// GetAuthenticator creates a new authenticator according to AuthConfig +func GetAuthenticator(conf AuthConfig) (auth.Authenticator, error) { + switch conf.Type { + case "static": + return auth.NewStaticAuth(conf.Static), nil + case "http": + return auth.NewHTTPAuth(conf.HTTP), nil + default: + return nil, fmt.Errorf("Unknown auth type '%v'", conf.Type) } - - return auth.NewStaticAuth(conf.Static) } // Parse tries to find and parse config from paths in order @@ -73,10 +53,17 @@ func Parse(paths []string) (*Config, error) { Buffersize: 384000, }, Auth: AuthConfig{ - Type: AuthTypeStatic, + Type: "static", Static: auth.StaticAuthConfig{ + // Allow everything by default Allow: []string{"*"}, }, + HTTP: auth.HTTPAuthConfig{ + URL: "http://localhost:8080/publish", + Timeout: time.Second, + Application: "stream", + PasswordParam: "auth", + }, }, } diff --git a/main.go b/main.go index 3f0c608..c9192c2 100644 --- a/main.go +++ b/main.go @@ -65,12 +65,17 @@ func main() { `relay buffer size in bytes, determines maximum delay of a client`) flag.Parse() + auth, err := config.GetAuthenticator(conf.Auth) + if err != nil { + log.Println(err) + } + serverConfig := server.Config{ Server: server.ServerConfig{ Address: conf.App.Address, Port: uint16(conf.App.Port), Latency: conf.App.Latency, - Auth: config.GetAuthenticator(conf.Auth), + Auth: auth, }, Relay: relay.RelayConfig{ Buffersize: conf.App.Buffersize, // 1s @ 3Mbits/ diff --git a/stream/streamid.go b/stream/streamid.go index 0be0bdc..5f4eb75 100644 --- a/stream/streamid.go +++ b/stream/streamid.go @@ -21,6 +21,17 @@ const ( ModePublish ) +func (m Mode) String() string { + switch m { + case ModePlay: + return "play" + case ModePublish: + return "publish" + default: + return "unknown" + } +} + // StreamID represents a connection tuple type StreamID struct { str string