aboutsummaryrefslogtreecommitdiff
path: root/weed
diff options
context:
space:
mode:
authorChris Lu <chris.lu@gmail.com>2019-02-14 00:08:20 -0800
committerChris Lu <chris.lu@gmail.com>2019-02-14 00:08:20 -0800
commit215cd27b37d504aca255a54283e77c8cff6692ab (patch)
treed6f0cdd10d75d1881fad924c94ef7ed3ed947241 /weed
parent4ff4a147b258bb7787e492a74254f3993bb69d1a (diff)
downloadseaweedfs-215cd27b37d504aca255a54283e77c8cff6692ab.tar.xz
seaweedfs-215cd27b37d504aca255a54283e77c8cff6692ab.zip
add authorizing fileId write access
need to secure upload/update/delete for benchmark/filer/mount need to add secure grpc
Diffstat (limited to 'weed')
-rw-r--r--weed/command/scaffold.go10
-rw-r--r--weed/security/guard.go56
-rw-r--r--weed/security/jwt.go46
-rw-r--r--weed/server/master_server.go5
-rw-r--r--weed/server/master_server_handlers.go31
-rw-r--r--weed/server/volume_server.go24
-rw-r--r--weed/server/volume_server_handlers.go31
-rw-r--r--weed/server/volume_server_handlers_write.go14
8 files changed, 125 insertions, 92 deletions
diff --git a/weed/command/scaffold.go b/weed/command/scaffold.go
index 40e7437d2..22300d3ba 100644
--- a/weed/command/scaffold.go
+++ b/weed/command/scaffold.go
@@ -10,7 +10,7 @@ func init() {
}
var cmdScaffold = &Command{
- UsageLine: "scaffold [filer]",
+ UsageLine: "scaffold -config=[filer|notification|replication|security]",
Short: "generate basic configuration files",
Long: `Generate filer.toml with all possible configurations for you to customize.
@@ -244,10 +244,14 @@ directory = "/" # destination directory
`
SECURITY_TOML_EXAMPLE = `
+# Put this file to one of the location, with descending priority
+# ./security.toml
+# $HOME/.seaweedfs/security.toml
+# /etc/seaweedfs/security.toml
# this file is read by master, volume server, and filer
-[jwt]
-signing_key = ""
+[jwt.signing]
+key = ""
`
)
diff --git a/weed/security/guard.go b/weed/security/guard.go
index 2ae4ec5a9..84a415253 100644
--- a/weed/security/guard.go
+++ b/weed/security/guard.go
@@ -41,21 +41,21 @@ https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go
*/
type Guard struct {
- whiteList []string
- SecretKey SigningKey
+ whiteList []string
+ SigningKey SigningKey
isActive bool
}
-func NewGuard(whiteList []string, secretKey string) *Guard {
- g := &Guard{whiteList: whiteList, SecretKey: SigningKey(secretKey)}
- g.isActive = len(g.whiteList) != 0 || len(g.SecretKey) != 0
+func NewGuard(whiteList []string, signingKey string) *Guard {
+ g := &Guard{whiteList: whiteList, SigningKey: SigningKey(signingKey)}
+ g.isActive = len(g.whiteList) != 0 || len(g.SigningKey) != 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
+ //if no security needed, just skip all checking
return f
}
return func(w http.ResponseWriter, r *http.Request) {
@@ -67,20 +67,6 @@ func (g *Guard) WhiteList(f func(w http.ResponseWriter, r *http.Request)) func(w
}
}
-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 == "" {
@@ -130,33 +116,3 @@ func (g *Guard) checkWhiteList(w http.ResponseWriter, r *http.Request) error {
glog.V(0).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 permission from %s", r.RemoteAddr)
-}
diff --git a/weed/security/jwt.go b/weed/security/jwt.go
index 844ffb77b..ba394c3bf 100644
--- a/weed/security/jwt.go
+++ b/weed/security/jwt.go
@@ -1,6 +1,7 @@
package security
import (
+ "fmt"
"net/http"
"strings"
@@ -11,21 +12,28 @@ import (
)
type EncodedJwt string
-type SigningKey string
+type SigningKey []byte
+
+type SeaweedFileIdClaims struct {
+ Fid string `json:"fid"`
+ jwt.StandardClaims
+}
func GenJwt(signingKey SigningKey, fileId string) EncodedJwt {
- if signingKey == "" {
+ if len(signingKey) == 0 {
return ""
}
- t := jwt.New(jwt.GetSigningMethod("HS256"))
- t.Claims = &jwt.StandardClaims{
- ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
- Subject: fileId,
+ claims := SeaweedFileIdClaims{
+ fileId,
+ jwt.StandardClaims{
+ ExpiresAt: time.Now().Add(time.Second * 10).Unix(),
+ },
}
- encoded, e := t.SignedString(signingKey)
+ t := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
+ encoded, e := t.SignedString([]byte(signingKey))
if e != nil {
- glog.V(0).Infof("Failed to sign claims: %v", t.Claims)
+ glog.V(0).Infof("Failed to sign claims %+v: %v", t.Claims, e)
return ""
}
return EncodedJwt(encoded)
@@ -44,31 +52,15 @@ func GetJwt(r *http.Request) EncodedJwt {
}
}
- // Get token from cookie
- if tokenStr == "" {
- cookie, err := r.Cookie("jwt")
- if err == nil {
- tokenStr = cookie.Value
- }
- }
-
return EncodedJwt(tokenStr)
}
-func EncodeJwt(signingKey SigningKey, claims *jwt.StandardClaims) (EncodedJwt, error) {
- if signingKey == "" {
- return "", nil
- }
-
- t := jwt.New(jwt.GetSigningMethod("HS256"))
- t.Claims = claims
- encoded, e := t.SignedString(signingKey)
- return EncodedJwt(encoded), e
-}
-
func DecodeJwt(signingKey SigningKey, tokenString EncodedJwt) (token *jwt.Token, err error) {
// check exp, nbf
return jwt.Parse(string(tokenString), func(token *jwt.Token) (interface{}, error) {
+ if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
+ return nil, fmt.Errorf("unknown token method")
+ }
return signingKey, nil
})
}
diff --git a/weed/server/master_server.go b/weed/server/master_server.go
index 19849ace6..06c959b92 100644
--- a/weed/server/master_server.go
+++ b/weed/server/master_server.go
@@ -15,6 +15,7 @@ import (
"github.com/chrislusf/seaweedfs/weed/topology"
"github.com/chrislusf/seaweedfs/weed/util"
"github.com/gorilla/mux"
+ "github.com/spf13/viper"
)
type MasterServer struct {
@@ -47,6 +48,10 @@ func NewMasterServer(r *mux.Router, port int, metaFolder string,
whiteList []string,
) *MasterServer {
+ LoadConfiguration("security", false)
+ v := viper.GetViper()
+ signingKey := v.GetString("jwt.signing.key")
+
var preallocateSize int64
if preallocate {
preallocateSize = int64(volumeSizeLimitMB) * (1 << 20)
diff --git a/weed/server/master_server_handlers.go b/weed/server/master_server_handlers.go
index a797dddfc..c4149e0cf 100644
--- a/weed/server/master_server_handlers.go
+++ b/weed/server/master_server_handlers.go
@@ -7,6 +7,7 @@ import (
"strings"
"github.com/chrislusf/seaweedfs/weed/operation"
+ "github.com/chrislusf/seaweedfs/weed/security"
"github.com/chrislusf/seaweedfs/weed/stats"
"github.com/chrislusf/seaweedfs/weed/storage"
)
@@ -40,12 +41,23 @@ func (ms *MasterServer) lookupVolumeId(vids []string, collection string) (volume
return
}
-// Takes one volumeId only, can not do batch lookup
+// If "fileId" is provided, this returns the fileId location and a JWT to update or delete the file.
+// If "volumeId" is provided, this only returns the volumeId location
func (ms *MasterServer) dirLookupHandler(w http.ResponseWriter, r *http.Request) {
vid := r.FormValue("volumeId")
- commaSep := strings.Index(vid, ",")
- if commaSep > 0 {
- vid = vid[0:commaSep]
+ if vid != "" {
+ // backward compatible
+ commaSep := strings.Index(vid, ",")
+ if commaSep > 0 {
+ vid = vid[0:commaSep]
+ }
+ }
+ fileId := r.FormValue("fileId")
+ if fileId != "" {
+ commaSep := strings.Index(fileId, ",")
+ if commaSep > 0 {
+ vid = fileId[0:commaSep]
+ }
}
vids := []string{vid}
collection := r.FormValue("collection") //optional, but can be faster if too many collections
@@ -54,6 +66,8 @@ func (ms *MasterServer) dirLookupHandler(w http.ResponseWriter, r *http.Request)
httpStatus := http.StatusOK
if location.Error != "" {
httpStatus = http.StatusNotFound
+ } else {
+ ms.maybeAddJwtAuthorization(w, fileId)
}
writeJsonQuiet(w, r, httpStatus, location)
}
@@ -88,8 +102,17 @@ func (ms *MasterServer) dirAssignHandler(w http.ResponseWriter, r *http.Request)
}
fid, count, dn, err := ms.Topo.PickForWrite(requestedCount, option)
if err == nil {
+ ms.maybeAddJwtAuthorization(w, fid)
writeJsonQuiet(w, r, http.StatusOK, operation.AssignResult{Fid: fid, Url: dn.Url(), PublicUrl: dn.PublicUrl, Count: count})
} else {
writeJsonQuiet(w, r, http.StatusNotAcceptable, operation.AssignResult{Error: err.Error()})
}
}
+
+func (ms *MasterServer) maybeAddJwtAuthorization(w http.ResponseWriter, fileId string) {
+ encodedJwt := security.GenJwt(ms.guard.SigningKey, fileId)
+ if encodedJwt == "" {
+ return
+ }
+ w.Header().Set("Authorization", "BEARER "+string(encodedJwt))
+}
diff --git a/weed/server/volume_server.go b/weed/server/volume_server.go
index 0914e81b0..d8ff01766 100644
--- a/weed/server/volume_server.go
+++ b/weed/server/volume_server.go
@@ -6,6 +6,7 @@ import (
"github.com/chrislusf/seaweedfs/weed/glog"
"github.com/chrislusf/seaweedfs/weed/security"
"github.com/chrislusf/seaweedfs/weed/storage"
+ "github.com/spf13/viper"
)
type VolumeServer struct {
@@ -31,6 +32,12 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
whiteList []string,
fixJpgOrientation bool,
readRedirect bool) *VolumeServer {
+
+ LoadConfiguration("security", false)
+ v := viper.GetViper()
+ signingKey := v.GetString("jwt.signing.key")
+ enableUiAccess := v.GetBool("access.ui")
+
vs := &VolumeServer{
pulseSeconds: pulseSeconds,
dataCenter: dataCenter,
@@ -42,14 +49,17 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
vs.MasterNodes = masterNodes
vs.store = storage.NewStore(port, ip, publicUrl, folders, maxCounts, vs.needleMapKind)
- vs.guard = security.NewGuard(whiteList, "")
+ vs.guard = security.NewGuard(whiteList, signingKey)
handleStaticResources(adminMux)
- adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler)
- adminMux.HandleFunc("/status", vs.guard.WhiteList(vs.statusHandler))
- adminMux.HandleFunc("/stats/counter", vs.guard.WhiteList(statsCounterHandler))
- adminMux.HandleFunc("/stats/memory", vs.guard.WhiteList(statsMemoryHandler))
- adminMux.HandleFunc("/stats/disk", vs.guard.WhiteList(vs.statsDiskHandler))
+ if signingKey == "" || enableUiAccess {
+ // only expose the volume server details for safe environments
+ adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler)
+ adminMux.HandleFunc("/status", vs.guard.WhiteList(vs.statusHandler))
+ adminMux.HandleFunc("/stats/counter", vs.guard.WhiteList(statsCounterHandler))
+ adminMux.HandleFunc("/stats/memory", vs.guard.WhiteList(statsMemoryHandler))
+ adminMux.HandleFunc("/stats/disk", vs.guard.WhiteList(vs.statsDiskHandler))
+ }
adminMux.HandleFunc("/", vs.privateStoreHandler)
if publicMux != adminMux {
// separated admin and public port
@@ -69,5 +79,5 @@ func (vs *VolumeServer) Shutdown() {
}
func (vs *VolumeServer) jwt(fileId string) security.EncodedJwt {
- return security.GenJwt(vs.guard.SecretKey, fileId)
+ return security.GenJwt(vs.guard.SigningKey, fileId)
}
diff --git a/weed/server/volume_server_handlers.go b/weed/server/volume_server_handlers.go
index 77b1274fd..0e9aaeb3b 100644
--- a/weed/server/volume_server_handlers.go
+++ b/weed/server/volume_server_handlers.go
@@ -3,6 +3,8 @@ package weed_server
import (
"net/http"
+ "github.com/chrislusf/seaweedfs/weed/glog"
+ "github.com/chrislusf/seaweedfs/weed/security"
"github.com/chrislusf/seaweedfs/weed/stats"
)
@@ -45,3 +47,32 @@ func (vs *VolumeServer) publicReadOnlyHandler(w http.ResponseWriter, r *http.Req
vs.GetOrHeadHandler(w, r)
}
}
+
+func (vs *VolumeServer) maybeCheckJwtAuthorization(r *http.Request, vid, fid string) bool {
+
+ if len(vs.guard.SigningKey) == 0 {
+ return true
+ }
+
+ tokenStr := security.GetJwt(r)
+ if tokenStr == "" {
+ glog.V(1).Infof("missing jwt from %s", r.RemoteAddr)
+ return false
+ }
+
+ token, err := security.DecodeJwt(vs.guard.SigningKey, tokenStr)
+ if err != nil {
+ glog.V(1).Infof("jwt verification error from %s: %v", r.RemoteAddr, err)
+ return false
+ }
+ if !token.Valid {
+ glog.V(1).Infof("jwt invalid from %s: %v", r.RemoteAddr, tokenStr)
+ return false
+ }
+
+ if sc, ok := token.Claims.(*security.SeaweedFileIdClaims); ok {
+ return sc.Fid == vid+","+fid
+ }
+ glog.V(1).Infof("unexpected jwt from %s: %v", r.RemoteAddr, tokenStr)
+ return false
+}
diff --git a/weed/server/volume_server_handlers_write.go b/weed/server/volume_server_handlers_write.go
index fd93142e1..1cfd9187e 100644
--- a/weed/server/volume_server_handlers_write.go
+++ b/weed/server/volume_server_handlers_write.go
@@ -20,13 +20,20 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
writeJsonError(w, r, http.StatusBadRequest, e)
return
}
- vid, _, _, _, _ := parseURLPath(r.URL.Path)
+
+ vid, fid, _, _, _ := parseURLPath(r.URL.Path)
volumeId, ve := storage.NewVolumeId(vid)
if ve != nil {
glog.V(0).Infoln("NewVolumeId error:", ve)
writeJsonError(w, r, http.StatusBadRequest, ve)
return
}
+
+ if !vs.maybeCheckJwtAuthorization(r, vid, fid) {
+ writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
+ return
+ }
+
needle, originalSize, ne := storage.CreateNeedleFromRequest(r, vs.FixJpgOrientation)
if ne != nil {
writeJsonError(w, r, http.StatusBadRequest, ne)
@@ -56,6 +63,11 @@ func (vs *VolumeServer) DeleteHandler(w http.ResponseWriter, r *http.Request) {
volumeId, _ := storage.NewVolumeId(vid)
n.ParsePath(fid)
+ if !vs.maybeCheckJwtAuthorization(r, vid, fid) {
+ writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
+ return
+ }
+
// glog.V(2).Infof("volume %s deleting %s", vid, n)
cookie := n.Cookie