Files
seaweedfs/weed/security/guard.go

147 lines
3.8 KiB
Go
Raw Normal View History

2015-01-05 14:20:04 -08:00
package security
import (
"errors"
"fmt"
"net"
"net/http"
"strings"
"github.com/seaweedfs/seaweedfs/weed/glog"
2015-01-05 14:20:04 -08:00
)
var (
ErrUnauthorized = errors.New("unauthorized token")
)
/*
Guard is to ensure data access security.
There are 2 ways to check access:
2022-09-14 23:06:44 -07:00
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"
2015-01-05 14:20:04 -08:00
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:
2022-09-14 23:06:44 -07:00
1. use HS256 to sign
2. optionally set "exp", "nbf" fields, in Unix time,
the number of seconds elapsed since January 1, 1970 UTC.
2015-01-05 14:20:04 -08:00
Referenced:
https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go
*/
type Guard struct {
whiteListIp map[string]struct{}
whiteListCIDR map[string]*net.IPNet
2019-06-06 00:29:02 -07:00
SigningKey SigningKey
ExpiresAfterSec int
ReadSigningKey SigningKey
ReadExpiresAfterSec int
2015-01-05 14:20:04 -08:00
isWriteActive bool
isEmptyWhiteList bool
2015-01-05 14:20:04 -08:00
}
2019-06-06 00:29:02 -07:00
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)
2015-01-05 14:20:04 -08:00
return g
}
func (g *Guard) WhiteList(f http.HandlerFunc) http.HandlerFunc {
2019-06-06 00:29:02 -07:00
if !g.isWriteActive {
//if no security needed, just skip all checking
2015-01-05 14:20:04 -08:00
return f
}
return func(w http.ResponseWriter, r *http.Request) {
2015-02-07 15:35:28 -08:00
if err := g.checkWhiteList(w, r); err != nil {
2015-01-05 14:20:04 -08:00
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
}
2015-02-07 15:35:28 -08:00
func (g *Guard) checkWhiteList(w http.ResponseWriter, r *http.Request) error {
if g.isEmptyWhiteList {
2015-02-07 15:35:28 -08:00
return nil
}
2015-01-05 14:20:04 -08:00
host := GetActualRemoteHost(r)
2015-10-19 22:03:18 -07:00
// 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
}
2015-01-05 14:20:04 -08:00
}
}
glog.V(0).Infof("Not in whitelist: %s (original RemoteAddr: %s)", host, r.RemoteAddr)
return fmt.Errorf("Not in whitelist: %s", host)
2015-02-07 15:35:28 -08:00
}
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
}