21 changed files with 634 additions and 760 deletions
@ -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 @@ |
|||||||
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