mirror of
https://github.com/livekit/livekit.git
synced 2026-08-29 05:29:32 +00:00
Add TURN permission handler. (#4505)
* Add TURN permission handler. - Turn off permissions to private/link local/multicast and internal IPs - Add a list of CIDRs that can be used for more things to deny permission to. * unused * add config for allowing private IPs, used in testing * add a TTL to user name and use it to auth * allow list for restricted peer CIDRs
This commit is contained in:
@@ -277,6 +277,21 @@ keys:
|
||||
# # optional (set only if not using external TLS termination)
|
||||
# # cert_file: /path/to/cert.pem
|
||||
# # key_file: /path/to/key.pem
|
||||
# # TTL of the TURN credentials in seconds - defaults to 300
|
||||
# ttl_seconds: 300
|
||||
# # list of restricted peer CIDRs (loopback, link-local (unicast, multicast), multicast, private, unspecified) to allow access to.
|
||||
# # By default (i. e. empty list), all restricted peer CIDRs are denied access.
|
||||
# # When not empty, only the specified CIDRs are allowed access.
|
||||
# # Note that this check is applied to restricted peer CIDRs only.
|
||||
# allow_restricted_peer_cidrs:
|
||||
# - 10.0.0.0/8
|
||||
# - 192.168.0.0/16
|
||||
# # list of peer CIDRs to deny access to.
|
||||
# # This applies to all peer CIDRs, including restricted ones.
|
||||
# # Deny list takes precedence over allow list.
|
||||
# deny_peer_cidrs:
|
||||
# - 10.0.0.0/8
|
||||
# - 192.168.0.0/16
|
||||
|
||||
# ingress server
|
||||
# ingress:
|
||||
|
||||
@@ -227,6 +227,17 @@ type TURNConfig struct {
|
||||
RelayPortRangeEnd uint16 `yaml:"relay_range_end,omitempty"`
|
||||
ExternalTLS bool `yaml:"external_tls,omitempty"`
|
||||
BindAddresses []string `yaml:"bind_addresses,omitempty"`
|
||||
// TTL of the TURN credentials in seconds - defaults to 300
|
||||
TTLSeconds int `yaml:"ttl_seconds,omitempty"`
|
||||
// list of restricted peer CIDRs (loopback, link-local (unicast, multicast), multicast, private, unspecified) to allow access to.
|
||||
// By default (i. e. empty list), all restricted peer CIDRs are denied access.
|
||||
// When not empty, only the specified CIDRs are allowed access.
|
||||
// Note that this check is applied to restricted peer CIDRs only.
|
||||
AllowRestrictedPeerCIDRs []string `yaml:"allow_restricted_peer_cidrs,omitempty"`
|
||||
// list of peer CIDRs to deny access to
|
||||
// This applies to all peer CIDRs, including restricted ones.
|
||||
// Deny list takes precedence over allow list.
|
||||
DenyPeerCIDRs []string `yaml:"deny_peer_cidrs,omitempty"`
|
||||
}
|
||||
|
||||
type NodeSelectorConfig struct {
|
||||
@@ -421,6 +432,7 @@ var DefaultConfig = Config{
|
||||
TURN: TURNConfig{
|
||||
Enabled: false,
|
||||
BindAddresses: []string{"0.0.0.0"},
|
||||
TTLSeconds: 300,
|
||||
},
|
||||
NodeSelector: NodeSelectorConfig{
|
||||
Kind: "any",
|
||||
|
||||
@@ -1036,7 +1036,7 @@ func (r *RoomManager) iceServersForParticipant(apiKey string, participant types.
|
||||
urls = append(urls, fmt.Sprintf("turns:%s:443?transport=tcp", r.config.TURN.Domain))
|
||||
}
|
||||
if len(urls) > 0 {
|
||||
username := r.turnAuthHandler.CreateUsername(apiKey, participant.ID())
|
||||
username := r.turnAuthHandler.CreateUsername(apiKey, participant.ID(), r.config.TURN.TTLSeconds)
|
||||
password, err := r.turnAuthHandler.CreatePassword(apiKey, participant.ID())
|
||||
if err != nil {
|
||||
participant.GetLogger().Warnw("could not create turn password", err)
|
||||
|
||||
+65
-8
@@ -21,6 +21,7 @@ import (
|
||||
"net"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/jxskiss/base62"
|
||||
"github.com/pion/turn/v4"
|
||||
@@ -92,6 +93,41 @@ func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone
|
||||
relayAddrGen = telemetry.NewRelayAddressGenerator(relayAddrGen)
|
||||
}
|
||||
|
||||
permissionHandler := func(_clientAddr net.Addr, peerIP net.IP) bool {
|
||||
// restricted peer IP is denied by default, unless allowed by the allow list,
|
||||
if peerIP.IsLoopback() ||
|
||||
peerIP.IsLinkLocalUnicast() ||
|
||||
peerIP.IsLinkLocalMulticast() ||
|
||||
peerIP.IsMulticast() ||
|
||||
peerIP.IsPrivate() ||
|
||||
peerIP.IsUnspecified() {
|
||||
allowed := false
|
||||
for _, cidr := range turnConf.AllowRestrictedPeerCIDRs {
|
||||
if _, ipnet, err := net.ParseCIDR(cidr); err == nil {
|
||||
if ipnet.Contains(peerIP) {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if !allowed {
|
||||
return false
|
||||
}
|
||||
|
||||
// if allowed, check deny list for overrides
|
||||
}
|
||||
|
||||
for _, cidr := range turnConf.DenyPeerCIDRs {
|
||||
if _, ipnet, err := net.ParseCIDR(cidr); err == nil {
|
||||
if ipnet.Contains(peerIP) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
if turnConf.TLSPort > 0 {
|
||||
var listener net.Listener
|
||||
var listenerErr error
|
||||
@@ -121,6 +157,7 @@ func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone
|
||||
listenerConfig := turn.ListenerConfig{
|
||||
Listener: listener,
|
||||
RelayAddressGenerator: relayAddrGen,
|
||||
PermissionHandler: permissionHandler,
|
||||
}
|
||||
serverConfig.ListenerConfigs = append(serverConfig.ListenerConfigs, listenerConfig)
|
||||
|
||||
@@ -140,6 +177,7 @@ func NewTurnServer(conf *config.Config, authHandler turn.AuthHandler, standalone
|
||||
packetConfig := turn.PacketConnConfig{
|
||||
PacketConn: udpListener,
|
||||
RelayAddressGenerator: relayAddrGen,
|
||||
PermissionHandler: permissionHandler,
|
||||
}
|
||||
serverConfig.PacketConnConfigs = append(serverConfig.PacketConnConfigs, packetConfig)
|
||||
logValues = append(logValues, "turn.portUDP", turnConf.UDPPort)
|
||||
@@ -164,21 +202,30 @@ func NewTURNAuthHandler(keyProvider auth.KeyProvider) *TURNAuthHandler {
|
||||
}
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) CreateUsername(apiKey string, pID livekit.ParticipantID) string {
|
||||
return base62.EncodeToString([]byte(fmt.Sprintf("%s|%s", apiKey, pID)))
|
||||
func (h *TURNAuthHandler) CreateUsername(apiKey string, pID livekit.ParticipantID, ttlSeconds int) string {
|
||||
expiry := time.Now().Add(time.Duration(ttlSeconds) * time.Second).Unix()
|
||||
return base62.EncodeToString(fmt.Appendf(nil, "%s|%s|%d", apiKey, pID, expiry))
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) ParseUsername(username string) (apiKey string, pID livekit.ParticipantID, err error) {
|
||||
func (h *TURNAuthHandler) ParseUsername(username string) (apiKey string, pID livekit.ParticipantID, expiry time.Time, err error) {
|
||||
decoded, err := base62.DecodeString(username)
|
||||
if err != nil {
|
||||
return "", "", err
|
||||
return "", "", time.Time{}, err
|
||||
}
|
||||
parts := strings.Split(string(decoded), "|")
|
||||
if len(parts) != 2 {
|
||||
return "", "", errors.New("invalid username")
|
||||
if len(parts) != 2 && len(parts) != 3 {
|
||||
return "", "", time.Time{}, errors.New("invalid username")
|
||||
}
|
||||
expiry = time.Time{}
|
||||
if len(parts) == 3 {
|
||||
if unixTime, err := strconv.ParseInt(parts[2], 10, 64); err != nil {
|
||||
return "", "", time.Time{}, err
|
||||
} else {
|
||||
expiry = time.Unix(unixTime, 0)
|
||||
}
|
||||
}
|
||||
|
||||
return parts[0], livekit.ParticipantID(parts[1]), nil
|
||||
return parts[0], livekit.ParticipantID(parts[1]), expiry, nil
|
||||
}
|
||||
|
||||
func (h *TURNAuthHandler) CreatePassword(apiKey string, pID livekit.ParticipantID) (string, error) {
|
||||
@@ -197,9 +244,19 @@ func (h *TURNAuthHandler) HandleAuth(username, realm string, srcAddr net.Addr) (
|
||||
return nil, false
|
||||
}
|
||||
parts := strings.Split(string(decoded), "|")
|
||||
if len(parts) != 2 {
|
||||
if len(parts) != 2 && len(parts) != 3 {
|
||||
return nil, false
|
||||
}
|
||||
if len(parts) == 3 {
|
||||
if unixTime, err := strconv.ParseInt(parts[2], 10, 64); err != nil {
|
||||
return nil, false
|
||||
} else {
|
||||
expiry := time.Unix(unixTime, 0)
|
||||
if time.Now().After(expiry) {
|
||||
return nil, false
|
||||
}
|
||||
}
|
||||
}
|
||||
password, err := h.CreatePassword(parts[0], livekit.ParticipantID(parts[1]))
|
||||
if err != nil {
|
||||
logger.Warnw("could not create TURN password", err, "username", username)
|
||||
|
||||
@@ -691,8 +691,11 @@ func (c *RTCClient) handleSignalResponse(res *livekit.SignalResponse) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *RTCClient) WaitUntilConnected() error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
func (c *RTCClient) WaitUntilConnected(timeout time.Duration) error {
|
||||
if timeout == 0 {
|
||||
timeout = 20 * time.Second
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
for {
|
||||
select {
|
||||
|
||||
@@ -132,14 +132,30 @@ func waitUntilConnected(t *testing.T, clients ...*testclient.RTCClient) {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := range clients {
|
||||
c := clients[i]
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
err := c.WaitUntilConnected()
|
||||
wg.Go(func() {
|
||||
err := c.WaitUntilConnected(5 * time.Second)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}()
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
t.FailNow()
|
||||
}
|
||||
}
|
||||
|
||||
func ensureNotConnected(t *testing.T, clients ...*testclient.RTCClient) {
|
||||
logger.Infow("checking if clients connect")
|
||||
wg := sync.WaitGroup{}
|
||||
for i := range clients {
|
||||
c := clients[i]
|
||||
wg.Go(func() {
|
||||
err := c.WaitUntilConnected(5 * time.Second)
|
||||
if err == nil {
|
||||
t.Error(fmt.Errorf("expected client to not connect: %s", c.ID()))
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
if t.Failed() {
|
||||
|
||||
+59
-23
@@ -1321,31 +1321,67 @@ func TestTurnRelay(t *testing.T) {
|
||||
return
|
||||
}
|
||||
|
||||
s := createSingleNodeServer(func(c *config.Config) {
|
||||
c.TURN.Enabled = true
|
||||
c.TURN.UDPPort = 3478
|
||||
})
|
||||
go func() {
|
||||
if err := s.Start(); err != nil {
|
||||
logger.Errorw("server returned error", err)
|
||||
}
|
||||
}()
|
||||
defer s.Stop(true)
|
||||
testCases := []struct {
|
||||
name string
|
||||
allowRestrictedPeerCIDRs []string
|
||||
denyPeerCIDRs []string
|
||||
expectedToConnect bool
|
||||
}{
|
||||
{
|
||||
"allow",
|
||||
[]string{"10.0.0.0/8", "192.168.0.0/16"},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"not-allowed",
|
||||
nil,
|
||||
nil,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"denied-overrides-allowed",
|
||||
[]string{"10.0.0.0/8", "192.168.0.0/16"},
|
||||
[]string{"10.0.0.0/8", "192.168.0.0/16"},
|
||||
false,
|
||||
},
|
||||
}
|
||||
|
||||
waitForServerToStart(s)
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
s := createSingleNodeServer(func(c *config.Config) {
|
||||
c.TURN.Enabled = true
|
||||
c.TURN.UDPPort = 3478
|
||||
c.TURN.AllowRestrictedPeerCIDRs = tc.allowRestrictedPeerCIDRs
|
||||
c.TURN.DenyPeerCIDRs = tc.denyPeerCIDRs
|
||||
})
|
||||
go func() {
|
||||
if err := s.Start(); err != nil {
|
||||
logger.Errorw("server returned error", err)
|
||||
}
|
||||
}()
|
||||
defer s.Stop(true)
|
||||
|
||||
c1 := createRTCClient("relay_c1", defaultServerPort, testRTCServicePathv0, &testclient.Options{
|
||||
AutoSubscribe: true,
|
||||
ForceRelay: true,
|
||||
})
|
||||
defer c1.Stop()
|
||||
waitForServerToStart(s)
|
||||
|
||||
waitUntilConnected(t, c1)
|
||||
c1 := createRTCClient("relay_c1", defaultServerPort, testRTCServicePathv0, &testclient.Options{
|
||||
AutoSubscribe: true,
|
||||
ForceRelay: true,
|
||||
})
|
||||
defer c1.Stop()
|
||||
|
||||
testutils.WithTimeout(t, func() string {
|
||||
if !c1.IsLocalCandidateRelaySelected() {
|
||||
return "expected local candidate to be relay"
|
||||
}
|
||||
return ""
|
||||
})
|
||||
if tc.expectedToConnect {
|
||||
waitUntilConnected(t, c1)
|
||||
|
||||
testutils.WithTimeout(t, func() string {
|
||||
if !c1.IsLocalCandidateRelaySelected() {
|
||||
return "expected local candidate to be relay"
|
||||
}
|
||||
return ""
|
||||
})
|
||||
} else {
|
||||
ensureNotConnected(t, c1)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user