aboutsummaryrefslogtreecommitdiff
path: root/weed/security
diff options
context:
space:
mode:
authorChris Lu <chris.lu@gmail.com>2016-06-02 18:09:14 -0700
committerChris Lu <chris.lu@gmail.com>2016-06-02 18:09:14 -0700
commit5ce6bbf07672bf3f3c8d26cd2ce0e3e853a47c44 (patch)
tree2e4dd2ad0a618ab2b7cdebcdb9c503526c31e2e8 /weed/security
parentcaeffa3998adc060fa66c4cd77af971ff2d26c57 (diff)
downloadseaweedfs-5ce6bbf07672bf3f3c8d26cd2ce0e3e853a47c44.tar.xz
seaweedfs-5ce6bbf07672bf3f3c8d26cd2ce0e3e853a47c44.zip
directory structure change to work with glide
glide has its own requirements. My previous workaround caused me some code checkin errors. Need to fix this.
Diffstat (limited to 'weed/security')
-rw-r--r--weed/security/guard.go162
-rw-r--r--weed/security/jwt.go72
2 files changed, 234 insertions, 0 deletions
diff --git a/weed/security/guard.go b/weed/security/guard.go
new file mode 100644
index 000000000..76f64cc0e
--- /dev/null
+++ b/weed/security/guard.go
@@ -0,0 +1,162 @@
+package security
+
+import (
+ "errors"
+ "fmt"
+ "net"
+ "net/http"
+ "strings"
+
+ "github.com/chrislusf/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 {
+ whiteList []string
+ SecretKey Secret
+
+ isActive bool
+}
+
+func NewGuard(whiteList []string, secretKey string) *Guard {
+ g := &Guard{whiteList: whiteList, SecretKey: Secret(secretKey)}
+ g.isActive = len(g.whiteList) != 0 || len(g.SecretKey) != 0
+ return g
+}
+
+func (g *Guard) WhiteList(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+ if !g.isActive {
+ //if no security needed, just skip all checkings
+ 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 (g *Guard) Secure(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
+ if !g.isActive {
+ //if no security needed, just skip all checkings
+ return f
+ }
+ return func(w http.ResponseWriter, r *http.Request) {
+ if err := g.checkJwt(w, r); err != nil {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+ f(w, r)
+ }
+}
+
+func GetActualRemoteHost(r *http.Request) (host string, err error) {
+ host = r.Header.Get("HTTP_X_FORWARDED_FOR")
+ if host == "" {
+ host = r.Header.Get("X-FORWARDED-FOR")
+ }
+ if strings.Contains(host, ",") {
+ host = host[0:strings.Index(host, ",")]
+ }
+ if host == "" {
+ host, _, err = net.SplitHostPort(r.RemoteAddr)
+ }
+ return
+}
+
+func (g *Guard) checkWhiteList(w http.ResponseWriter, r *http.Request) error {
+ if len(g.whiteList) == 0 {
+ return nil
+ }
+
+ host, err := GetActualRemoteHost(r)
+ if err == nil {
+ for _, ip := range g.whiteList {
+
+ // If the whitelist entry contains a "/" it
+ // is a CIDR range, and we should check the
+ // remote host is within it
+ if strings.Contains(ip, "/") {
+ _, cidrnet, err := net.ParseCIDR(ip)
+ if err != nil {
+ panic(err)
+ }
+ remote := net.ParseIP(host)
+ if cidrnet.Contains(remote) {
+ return nil
+ }
+ }
+
+ //
+ // Otherwise we're looking for a literal match.
+ //
+ if ip == host {
+ return nil
+ }
+ }
+ }
+
+ glog.V(1).Infof("Not in whitelist: %s", r.RemoteAddr)
+ return fmt.Errorf("Not in whitelis: %s", r.RemoteAddr)
+}
+
+func (g *Guard) checkJwt(w http.ResponseWriter, r *http.Request) error {
+ if g.checkWhiteList(w, r) == nil {
+ return nil
+ }
+
+ if len(g.SecretKey) == 0 {
+ return nil
+ }
+
+ tokenStr := GetJwt(r)
+
+ if tokenStr == "" {
+ return ErrUnauthorized
+ }
+
+ // Verify the token
+ token, err := DecodeJwt(g.SecretKey, tokenStr)
+ if err != nil {
+ glog.V(1).Infof("Token verification error from %s: %v", r.RemoteAddr, err)
+ return ErrUnauthorized
+ }
+ if !token.Valid {
+ glog.V(1).Infof("Token invliad from %s: %v", r.RemoteAddr, tokenStr)
+ return ErrUnauthorized
+ }
+
+ glog.V(1).Infof("No permission from %s", r.RemoteAddr)
+ return fmt.Errorf("No write permisson from %s", r.RemoteAddr)
+}
diff --git a/weed/security/jwt.go b/weed/security/jwt.go
new file mode 100644
index 000000000..a2472ca6e
--- /dev/null
+++ b/weed/security/jwt.go
@@ -0,0 +1,72 @@
+package security
+
+import (
+ "net/http"
+ "strings"
+
+ "time"
+
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ jwt "github.com/dgrijalva/jwt-go"
+)
+
+type EncodedJwt string
+type Secret string
+
+func GenJwt(secret Secret, fileId string) EncodedJwt {
+ if secret == "" {
+ return ""
+ }
+
+ t := jwt.New(jwt.GetSigningMethod("HS256"))
+ t.Claims["exp"] = time.Now().Unix() + 10
+ t.Claims["sub"] = fileId
+ encoded, e := t.SignedString(secret)
+ if e != nil {
+ glog.V(0).Infof("Failed to sign claims: %v", t.Claims)
+ return ""
+ }
+ return EncodedJwt(encoded)
+}
+
+func GetJwt(r *http.Request) EncodedJwt {
+
+ // Get token from query params
+ tokenStr := r.URL.Query().Get("jwt")
+
+ // Get token from authorization header
+ if tokenStr == "" {
+ bearer := r.Header.Get("Authorization")
+ if len(bearer) > 7 && strings.ToUpper(bearer[0:6]) == "BEARER" {
+ tokenStr = bearer[7:]
+ }
+ }
+
+ // Get token from cookie
+ if tokenStr == "" {
+ cookie, err := r.Cookie("jwt")
+ if err == nil {
+ tokenStr = cookie.Value
+ }
+ }
+
+ return EncodedJwt(tokenStr)
+}
+
+func EncodeJwt(secret Secret, claims map[string]interface{}) (EncodedJwt, error) {
+ if secret == "" {
+ return "", nil
+ }
+
+ t := jwt.New(jwt.GetSigningMethod("HS256"))
+ t.Claims = claims
+ encoded, e := t.SignedString(secret)
+ return EncodedJwt(encoded), e
+}
+
+func DecodeJwt(secret Secret, tokenString EncodedJwt) (token *jwt.Token, err error) {
+ // check exp, nbf
+ return jwt.Parse(string(tokenString), func(token *jwt.Token) (interface{}, error) {
+ return secret, nil
+ })
+}