feat: enforce tenant login IP whitelists
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/tinode/chat/server/auth"
|
||||
@@ -15,6 +16,18 @@ import (
|
||||
"github.com/tinode/chat/server/store/types"
|
||||
)
|
||||
|
||||
const tenantLoginIPWhitelistCacheTTL = 30 * time.Second
|
||||
|
||||
type tenantLoginIPWhitelistCacheEntry struct {
|
||||
config tenantLoginIPWhitelistConfig
|
||||
expiresAt time.Time
|
||||
}
|
||||
|
||||
var tenantLoginIPWhitelistCache = struct {
|
||||
sync.RWMutex
|
||||
items map[types.TenantID]tenantLoginIPWhitelistCacheEntry
|
||||
}{items: make(map[types.TenantID]tenantLoginIPWhitelistCacheEntry)}
|
||||
|
||||
func recordSessionLoginSuccess(s *Session, rec *auth.Rec, scheme, suppliedAccount, requestID string, at time.Time) {
|
||||
if s == nil || rec == nil || rec.Uid.IsZero() || !s.tenantID.IsValid() {
|
||||
return
|
||||
@@ -102,6 +115,81 @@ func checkUserLoginIPWhitelist(tenantID types.TenantID, uid types.Uid, remoteAdd
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
|
||||
func checkTenantLoginIPWhitelist(tenantID types.TenantID, platform, remoteAddr string) error {
|
||||
config, err := loadTenantLoginIPWhitelist(tenantID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config == nil {
|
||||
return nil
|
||||
}
|
||||
values := config.AppList
|
||||
if tenantLoginIPChannel(platform) == "web" {
|
||||
values = config.WebList
|
||||
}
|
||||
if len(values) == 0 {
|
||||
return nil
|
||||
}
|
||||
if whitelistContainsIP(values, sessionAuditIP(strings.Split(remoteAddr, ",")[0])) {
|
||||
return nil
|
||||
}
|
||||
return types.ErrPermissionDenied
|
||||
}
|
||||
|
||||
func loadTenantLoginIPWhitelist(tenantID types.TenantID) (*tenantLoginIPWhitelistConfig, error) {
|
||||
if loginIPWhitelistProvider == nil {
|
||||
return nil, nil
|
||||
}
|
||||
now := time.Now()
|
||||
tenantLoginIPWhitelistCache.RLock()
|
||||
cached, ok := tenantLoginIPWhitelistCache.items[tenantID]
|
||||
tenantLoginIPWhitelistCache.RUnlock()
|
||||
if ok && now.Before(cached.expiresAt) {
|
||||
result := cached.config
|
||||
return &result, nil
|
||||
}
|
||||
config, err := loginIPWhitelistProvider.TenantLoginIPWhitelist(tenantID)
|
||||
if err != nil {
|
||||
if ok {
|
||||
result := cached.config
|
||||
return &result, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
if config == nil {
|
||||
config = &tenantLoginIPWhitelistConfig{TenantID: tenantID, AppList: []string{}, WebList: []string{}}
|
||||
}
|
||||
tenantLoginIPWhitelistCache.Lock()
|
||||
tenantLoginIPWhitelistCache.items[tenantID] = tenantLoginIPWhitelistCacheEntry{config: *config, expiresAt: now.Add(tenantLoginIPWhitelistCacheTTL)}
|
||||
tenantLoginIPWhitelistCache.Unlock()
|
||||
result := *config
|
||||
return &result, nil
|
||||
}
|
||||
|
||||
func tenantLoginIPChannel(platform string) string {
|
||||
if strings.EqualFold(strings.TrimSpace(platform), "web") {
|
||||
return "web"
|
||||
}
|
||||
return "app"
|
||||
}
|
||||
|
||||
func whitelistContainsIP(values []string, ipText string) bool {
|
||||
ip := net.ParseIP(strings.TrimSpace(ipText))
|
||||
if ip == nil {
|
||||
return false
|
||||
}
|
||||
for _, value := range values {
|
||||
value = strings.TrimSpace(value)
|
||||
if allowedIP := net.ParseIP(value); allowedIP != nil && allowedIP.Equal(ip) {
|
||||
return true
|
||||
}
|
||||
if _, block, err := net.ParseCIDR(value); err == nil && block.Contains(ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func loginAuditAccount(scheme, supplied, credential string) (string, string) {
|
||||
if parts := strings.SplitN(strings.TrimSpace(credential), ":", 2); len(parts) == 2 && parts[1] != "" {
|
||||
kind := strings.ToLower(strings.TrimSpace(parts[0]))
|
||||
|
||||
Reference in New Issue
Block a user