You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							146 lines
						
					
					
						
							3.8 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							146 lines
						
					
					
						
							3.8 KiB
						
					
					
				| package security | |
| 
 | |
| import ( | |
| 	"errors" | |
| 	"fmt" | |
| 	"net" | |
| 	"net/http" | |
| 	"strings" | |
| 
 | |
| 	"github.com/seaweedfs/seaweedfs/weed/glog" | |
| ) | |
| 
 | |
| var ( | |
| 	ErrUnauthorized = errors.New("unauthorized token") | |
| ) | |
| 
 | |
| /* | |
| Guard is to ensure data access security. | |
| There are 2 ways to check access: | |
|  1. white list. It's checking request ip address. | |
|  2. JSON Web Token(JWT) generated from secretKey. | |
|     The jwt can come from: | |
|  1. url parameter jwt=... | |
|  2. request header "Authorization" | |
|  3. cookie with the name "jwt" | |
|  | |
| The white list is checked first because it is easy. | |
| Then the JWT is checked. | |
|  | |
| The Guard will also check these claims if provided: | |
| 1. "exp" Expiration Time | |
| 2. "nbf" Not Before | |
|  | |
| Generating JWT: | |
|  1. use HS256 to sign | |
|  2. optionally set "exp", "nbf" fields, in Unix time, | |
|     the number of seconds elapsed since January 1, 1970 UTC. | |
|  | |
| Referenced: | |
| https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go | |
| */ | |
| type Guard struct { | |
| 	whiteListIp         map[string]struct{} | |
| 	whiteListCIDR       map[string]*net.IPNet | |
| 	SigningKey          SigningKey | |
| 	ExpiresAfterSec     int | |
| 	ReadSigningKey      SigningKey | |
| 	ReadExpiresAfterSec int | |
| 
 | |
| 	isWriteActive    bool | |
| 	isEmptyWhiteList bool | |
| } | |
| 
 | |
| func NewGuard(whiteList []string, signingKey string, expiresAfterSec int, readSigningKey string, readExpiresAfterSec int) *Guard { | |
| 	g := &Guard{ | |
| 		SigningKey:          SigningKey(signingKey), | |
| 		ExpiresAfterSec:     expiresAfterSec, | |
| 		ReadSigningKey:      SigningKey(readSigningKey), | |
| 		ReadExpiresAfterSec: readExpiresAfterSec, | |
| 	} | |
| 	g.UpdateWhiteList(whiteList) | |
| 	return g | |
| } | |
| 
 | |
| func (g *Guard) WhiteList(f http.HandlerFunc) http.HandlerFunc { | |
| 	if !g.isWriteActive { | |
| 		//if no security needed, just skip all checking | |
| 		return f | |
| 	} | |
| 	return func(w http.ResponseWriter, r *http.Request) { | |
| 		if err := g.checkWhiteList(w, r); err != nil { | |
| 			w.WriteHeader(http.StatusUnauthorized) | |
| 			return | |
| 		} | |
| 		f(w, r) | |
| 	} | |
| } | |
| 
 | |
| func GetActualRemoteHost(r *http.Request) string { | |
| 	// For security reasons, only use RemoteAddr to determine the client's IP address. | |
| 	// Do not trust headers like X-Forwarded-For, as they can be easily spoofed by clients. | |
| 	host, _, err := net.SplitHostPort(r.RemoteAddr) | |
| 	if err == nil { | |
| 		return host | |
| 	} | |
| 
 | |
| 	// If SplitHostPort fails, it may be because of a missing port. | |
| 	// We try to parse RemoteAddr as a raw host (IP or hostname). | |
| 	host = strings.TrimSpace(r.RemoteAddr) | |
| 	// It might be an IPv6 address without a port, but with brackets. | |
| 	// e.g. "[::1]" | |
| 	if strings.HasPrefix(host, "[") && strings.HasSuffix(host, "]") { | |
| 		host = host[1 : len(host)-1] | |
| 	} | |
| 
 | |
| 	// Return the host (can be IP or hostname, just like headers) | |
| 	return host | |
| } | |
| 
 | |
| func (g *Guard) checkWhiteList(w http.ResponseWriter, r *http.Request) error { | |
| 	if g.isEmptyWhiteList { | |
| 		return nil | |
| 	} | |
| 
 | |
| 	host := GetActualRemoteHost(r) | |
| 
 | |
| 	// Check exact match first (works for both IPs and hostnames) | |
| 	if _, ok := g.whiteListIp[host]; ok { | |
| 		return nil | |
| 	} | |
| 
 | |
| 	// Check CIDR ranges (only for valid IP addresses) | |
| 	remote := net.ParseIP(host) | |
| 	if remote != nil { | |
| 		for _, cidrnet := range g.whiteListCIDR { | |
| 			// If the whitelist entry contains a "/" it | |
| 			// is a CIDR range, and we should check the | |
| 			if cidrnet.Contains(remote) { | |
| 				return nil | |
| 			} | |
| 		} | |
| 	} | |
| 
 | |
| 	glog.V(0).Infof("Not in whitelist: %s (original RemoteAddr: %s)", host, r.RemoteAddr) | |
| 	return fmt.Errorf("Not in whitelist: %s", host) | |
| } | |
| 
 | |
| func (g *Guard) UpdateWhiteList(whiteList []string) { | |
| 	whiteListIp := make(map[string]struct{}) | |
| 	whiteListCIDR := make(map[string]*net.IPNet) | |
| 	for _, ip := range whiteList { | |
| 		if strings.Contains(ip, "/") { | |
| 			_, cidrnet, err := net.ParseCIDR(ip) | |
| 			if err != nil { | |
| 				glog.Errorf("Parse CIDR %s in whitelist failed: %v", ip, err) | |
| 			} | |
| 			whiteListCIDR[ip] = cidrnet | |
| 		} else { | |
| 			whiteListIp[ip] = struct{}{} | |
| 		} | |
| 	} | |
| 	g.isEmptyWhiteList = len(whiteListIp) == 0 && len(whiteListCIDR) == 0 | |
| 	g.isWriteActive = !g.isEmptyWhiteList || len(g.SigningKey) != 0 | |
| 	g.whiteListIp = whiteListIp | |
| 	g.whiteListCIDR = whiteListCIDR | |
| }
 |