21 changed files with 634 additions and 760 deletions
@ -0,0 +1,183 @@
@@ -0,0 +1,183 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"bytes" |
||||
"crypto/sha256" |
||||
"encoding/base64" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net" |
||||
"net/http" |
||||
"strings" |
||||
|
||||
"github.com/bluenviron/gortsplib/v3/pkg/auth" |
||||
"github.com/bluenviron/gortsplib/v3/pkg/base" |
||||
"github.com/bluenviron/gortsplib/v3/pkg/headers" |
||||
"github.com/bluenviron/gortsplib/v3/pkg/url" |
||||
"github.com/google/uuid" |
||||
|
||||
"github.com/aler9/mediamtx/internal/conf" |
||||
) |
||||
|
||||
func sha256Base64(in string) string { |
||||
h := sha256.New() |
||||
h.Write([]byte(in)) |
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)) |
||||
} |
||||
|
||||
func checkCredential(right string, guess string) bool { |
||||
if strings.HasPrefix(right, "sha256:") { |
||||
return right[len("sha256:"):] == sha256Base64(guess) |
||||
} |
||||
|
||||
return right == guess |
||||
} |
||||
|
||||
type authProtocol string |
||||
|
||||
const ( |
||||
authProtocolRTSP authProtocol = "rtsp" |
||||
authProtocolRTMP authProtocol = "rtmp" |
||||
authProtocolHLS authProtocol = "hls" |
||||
authProtocolWebRTC authProtocol = "webrtc" |
||||
) |
||||
|
||||
func externalAuth( |
||||
ur string, |
||||
ip string, |
||||
user string, |
||||
password string, |
||||
path string, |
||||
protocol authProtocol, |
||||
id *uuid.UUID, |
||||
publish bool, |
||||
query string, |
||||
) error { |
||||
enc, _ := json.Marshal(struct { |
||||
IP string `json:"ip"` |
||||
User string `json:"user"` |
||||
Password string `json:"password"` |
||||
Path string `json:"path"` |
||||
Protocol string `json:"protocol"` |
||||
ID *uuid.UUID `json:"id"` |
||||
Action string `json:"action"` |
||||
Query string `json:"query"` |
||||
}{ |
||||
IP: ip, |
||||
User: user, |
||||
Password: password, |
||||
Path: path, |
||||
Protocol: string(protocol), |
||||
Action: func() string { |
||||
if publish { |
||||
return "publish" |
||||
} |
||||
return "read" |
||||
}(), |
||||
Query: query, |
||||
}) |
||||
res, err := http.Post(ur, "application/json", bytes.NewReader(enc)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer res.Body.Close() |
||||
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 { |
||||
if resBody, err := io.ReadAll(res.Body); err == nil && len(resBody) != 0 { |
||||
return fmt.Errorf("external authentication replied with code %d: %s", res.StatusCode, string(resBody)) |
||||
} |
||||
|
||||
return fmt.Errorf("external authentication replied with code %d", res.StatusCode) |
||||
} |
||||
|
||||
return nil |
||||
} |
||||
|
||||
type authCredentials struct { |
||||
query string |
||||
ip net.IP |
||||
user string |
||||
pass string |
||||
proto authProtocol |
||||
id *uuid.UUID |
||||
rtspRequest *base.Request |
||||
rtspBaseURL *url.URL |
||||
rtspNonce string |
||||
} |
||||
|
||||
func authenticate( |
||||
externalAuthenticationURL string, |
||||
rtspAuthMethods conf.AuthMethods, |
||||
pathName string, |
||||
pathConf *conf.PathConf, |
||||
publish bool, |
||||
credentials authCredentials, |
||||
) error { |
||||
var rtspAuth headers.Authorization |
||||
if credentials.rtspRequest != nil { |
||||
err := rtspAuth.Unmarshal(credentials.rtspRequest.Header["Authorization"]) |
||||
if err == nil && rtspAuth.Method == headers.AuthBasic { |
||||
credentials.user = rtspAuth.BasicUser |
||||
credentials.pass = rtspAuth.BasicPass |
||||
} |
||||
} |
||||
|
||||
if externalAuthenticationURL != "" { |
||||
err := externalAuth( |
||||
externalAuthenticationURL, |
||||
credentials.ip.String(), |
||||
credentials.user, |
||||
credentials.pass, |
||||
pathName, |
||||
credentials.proto, |
||||
credentials.id, |
||||
publish, |
||||
credentials.query, |
||||
) |
||||
if err != nil { |
||||
return fmt.Errorf("external authentication failed: %s", err) |
||||
} |
||||
} |
||||
|
||||
var pathIPs conf.IPsOrCIDRs |
||||
var pathUser string |
||||
var pathPass string |
||||
|
||||
if publish { |
||||
pathIPs = pathConf.PublishIPs |
||||
pathUser = string(pathConf.PublishUser) |
||||
pathPass = string(pathConf.PublishPass) |
||||
} else { |
||||
pathIPs = pathConf.ReadIPs |
||||
pathUser = string(pathConf.ReadUser) |
||||
pathPass = string(pathConf.ReadPass) |
||||
} |
||||
|
||||
if pathIPs != nil { |
||||
if !ipEqualOrInRange(credentials.ip, pathIPs) { |
||||
return fmt.Errorf("IP '%s' not allowed", credentials.ip) |
||||
} |
||||
} |
||||
|
||||
if pathUser != "" { |
||||
if credentials.rtspRequest != nil && rtspAuth.Method == headers.AuthDigest { |
||||
err := auth.Validate( |
||||
credentials.rtspRequest, |
||||
pathUser, |
||||
pathPass, |
||||
credentials.rtspBaseURL, |
||||
rtspAuthMethods, |
||||
"IPCAM", |
||||
credentials.rtspNonce) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
} else if !checkCredential(pathUser, credentials.user) || |
||||
!checkCredential(pathPass, credentials.pass) { |
||||
return fmt.Errorf("invalid credentials") |
||||
} |
||||
} |
||||
|
||||
return nil |
||||
} |
@ -1,71 +0,0 @@
@@ -1,71 +0,0 @@
|
||||
package core |
||||
|
||||
import ( |
||||
"bytes" |
||||
"encoding/json" |
||||
"fmt" |
||||
"io" |
||||
"net/http" |
||||
|
||||
"github.com/google/uuid" |
||||
) |
||||
|
||||
type externalAuthProto string |
||||
|
||||
const ( |
||||
externalAuthProtoRTSP externalAuthProto = "rtsp" |
||||
externalAuthProtoRTMP externalAuthProto = "rtmp" |
||||
externalAuthProtoHLS externalAuthProto = "hls" |
||||
externalAuthProtoWebRTC externalAuthProto = "webrtc" |
||||
) |
||||
|
||||
func externalAuth( |
||||
ur string, |
||||
ip string, |
||||
user string, |
||||
password string, |
||||
path string, |
||||
protocol externalAuthProto, |
||||
id *uuid.UUID, |
||||
publish bool, |
||||
query string, |
||||
) error { |
||||
enc, _ := json.Marshal(struct { |
||||
IP string `json:"ip"` |
||||
User string `json:"user"` |
||||
Password string `json:"password"` |
||||
Path string `json:"path"` |
||||
Protocol string `json:"protocol"` |
||||
ID *uuid.UUID `json:"id"` |
||||
Action string `json:"action"` |
||||
Query string `json:"query"` |
||||
}{ |
||||
IP: ip, |
||||
User: user, |
||||
Password: password, |
||||
Path: path, |
||||
Protocol: string(protocol), |
||||
Action: func() string { |
||||
if publish { |
||||
return "publish" |
||||
} |
||||
return "read" |
||||
}(), |
||||
Query: query, |
||||
}) |
||||
res, err := http.Post(ur, "application/json", bytes.NewReader(enc)) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
defer res.Body.Close() |
||||
|
||||
if res.StatusCode < 200 || res.StatusCode > 299 { |
||||
if resBody, err := io.ReadAll(res.Body); err == nil { |
||||
return fmt.Errorf("external authentication replied with code %d: %s", res.StatusCode, resBody) |
||||
} |
||||
|
||||
return fmt.Errorf("external authentication replied with code %d", res.StatusCode) |
||||
} |
||||
|
||||
return nil |
||||
} |
Loading…
Reference in new issue