From 50aa769554fcc36672900b5bf19501f5ae6a0133 Mon Sep 17 00:00:00 2001 From: Chris Lu Date: Thu, 6 Jun 2019 00:29:02 -0700 Subject: [PATCH] jwt for read access control --- weed/command/scaffold.go | 6 ++++++ weed/security/guard.go | 24 ++++++++++++++------- weed/server/master_server.go | 6 +++++- weed/server/master_server_handlers.go | 16 ++++++++++---- weed/server/volume_server.go | 6 +++++- weed/server/volume_server_handlers.go | 20 +++++++++++++---- weed/server/volume_server_handlers_read.go | 7 ++++++ weed/server/volume_server_handlers_write.go | 4 ++-- 8 files changed, 69 insertions(+), 20 deletions(-) diff --git a/weed/command/scaffold.go b/weed/command/scaffold.go index 242bdddd7..08efc50eb 100644 --- a/weed/command/scaffold.go +++ b/weed/command/scaffold.go @@ -277,8 +277,14 @@ directory = "/" # destination directory key = "" expires_after_seconds = 10 # seconds +# jwt for read is only supported with master+volume setup. Filer does not support this mode. +[jwt.signing.read] +key = "" +expires_after_seconds = 10 # seconds + # all grpc tls authentications are mutual # the values for the following ca, cert, and key are paths to the PERM files. +# the host name is not checked, so the PERM files can be shared. [grpc] ca = "" diff --git a/weed/security/guard.go b/weed/security/guard.go index 5d25d8327..17fe2ea9e 100644 --- a/weed/security/guard.go +++ b/weed/security/guard.go @@ -41,21 +41,29 @@ https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go */ type Guard struct { - whiteList []string - SigningKey SigningKey - ExpiresAfterSec int + whiteList []string + SigningKey SigningKey + ExpiresAfterSec int + ReadSigningKey SigningKey + ReadExpiresAfterSec int - isActive bool + isWriteActive bool } -func NewGuard(whiteList []string, signingKey string, expiresAfterSec int) *Guard { - g := &Guard{whiteList: whiteList, SigningKey: SigningKey(signingKey), ExpiresAfterSec: expiresAfterSec} - g.isActive = len(g.whiteList) != 0 || len(g.SigningKey) != 0 +func NewGuard(whiteList []string, signingKey string, expiresAfterSec int, readSigningKey string, readExpiresAfterSec int) *Guard { + g := &Guard{ + whiteList: whiteList, + SigningKey: SigningKey(signingKey), + ExpiresAfterSec: expiresAfterSec, + ReadSigningKey: SigningKey(readSigningKey), + ReadExpiresAfterSec: readExpiresAfterSec, + } + g.isWriteActive = 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 !g.isWriteActive { //if no security needed, just skip all checking return f } diff --git a/weed/server/master_server.go b/weed/server/master_server.go index 8a51dc828..0076ed1f1 100644 --- a/weed/server/master_server.go +++ b/weed/server/master_server.go @@ -63,6 +63,10 @@ func NewMasterServer(r *mux.Router, port int, metaFolder string, v.SetDefault("jwt.signing.expires_after_seconds", 10) expiresAfterSec := v.GetInt("jwt.signing.expires_after_seconds") + readSigningKey := v.GetString("jwt.signing.read.key") + v.SetDefault("jwt.signing.read.expires_after_seconds", 60) + readExpiresAfterSec := v.GetInt("jwt.signing.read.expires_after_seconds") + var preallocateSize int64 if preallocate { preallocateSize = int64(volumeSizeLimitMB) * (1 << 20) @@ -83,7 +87,7 @@ func NewMasterServer(r *mux.Router, port int, metaFolder string, ms.vg = topology.NewDefaultVolumeGrowth() glog.V(0).Infoln("Volume Size Limit is", volumeSizeLimitMB, "MB") - ms.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec) + ms.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec) if !disableHttp { handleStaticResources2(r) diff --git a/weed/server/master_server_handlers.go b/weed/server/master_server_handlers.go index 1c5b11565..5c7ff41cf 100644 --- a/weed/server/master_server_handlers.go +++ b/weed/server/master_server_handlers.go @@ -67,7 +67,9 @@ func (ms *MasterServer) dirLookupHandler(w http.ResponseWriter, r *http.Request) if location.Error != "" { httpStatus = http.StatusNotFound } else { - ms.maybeAddJwtAuthorization(w, fileId) + forRead := r.FormValue("read") + isRead := forRead == "yes" + ms.maybeAddJwtAuthorization(w, fileId, !isRead) } writeJsonQuiet(w, r, httpStatus, location) } @@ -102,17 +104,23 @@ 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) + ms.maybeAddJwtAuthorization(w, fid, true) 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, ms.guard.ExpiresAfterSec, fileId) +func (ms *MasterServer) maybeAddJwtAuthorization(w http.ResponseWriter, fileId string, isWrite bool) { + var encodedJwt security.EncodedJwt + if isWrite { + encodedJwt = security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fileId) + } else { + encodedJwt = security.GenJwt(ms.guard.ReadSigningKey, ms.guard.ReadExpiresAfterSec, 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 fab2edac0..f0da20323 100644 --- a/weed/server/volume_server.go +++ b/weed/server/volume_server.go @@ -44,6 +44,10 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, expiresAfterSec := v.GetInt("jwt.signing.expires_after_seconds") enableUiAccess := v.GetBool("access.ui") + readSigningKey := v.GetString("jwt.signing.read.key") + v.SetDefault("jwt.signing.read.expires_after_seconds", 60) + readExpiresAfterSec := v.GetInt("jwt.signing.read.expires_after_seconds") + vs := &VolumeServer{ pulseSeconds: pulseSeconds, dataCenter: dataCenter, @@ -57,7 +61,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string, vs.SeedMasterNodes = masterNodes vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, vs.needleMapKind) - vs.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec) + vs.guard = security.NewGuard(whiteList, signingKey, expiresAfterSec, readSigningKey, readExpiresAfterSec) handleStaticResources(adminMux) if signingKey == "" || enableUiAccess { diff --git a/weed/server/volume_server_handlers.go b/weed/server/volume_server_handlers.go index d23c08290..4197582fd 100644 --- a/weed/server/volume_server_handlers.go +++ b/weed/server/volume_server_handlers.go @@ -49,10 +49,22 @@ func (vs *VolumeServer) publicReadOnlyHandler(w http.ResponseWriter, r *http.Req } } -func (vs *VolumeServer) maybeCheckJwtAuthorization(r *http.Request, vid, fid string) bool { +func (vs *VolumeServer) maybeCheckJwtAuthorization(r *http.Request, vid, fid string, isWrite bool) bool { - if len(vs.guard.SigningKey) == 0 { - return true + var signingKey security.SigningKey + + if isWrite { + if len(vs.guard.SigningKey) == 0 { + return true + } else { + signingKey = vs.guard.SigningKey + } + }else { + if len(vs.guard.ReadSigningKey) == 0 { + return true + } else { + signingKey = vs.guard.ReadSigningKey + } } tokenStr := security.GetJwt(r) @@ -61,7 +73,7 @@ func (vs *VolumeServer) maybeCheckJwtAuthorization(r *http.Request, vid, fid str return false } - token, err := security.DecodeJwt(vs.guard.SigningKey, tokenStr) + token, err := security.DecodeJwt(signingKey, tokenStr) if err != nil { glog.V(1).Infof("jwt verification error from %s: %v", r.RemoteAddr, err) return false diff --git a/weed/server/volume_server_handlers_read.go b/weed/server/volume_server_handlers_read.go index 9bc436239..d2aaa4e24 100644 --- a/weed/server/volume_server_handlers_read.go +++ b/weed/server/volume_server_handlers_read.go @@ -3,6 +3,7 @@ package weed_server import ( "bytes" "context" + "errors" "io" "mime" "mime/multipart" @@ -27,6 +28,12 @@ var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"") func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) { n := new(needle.Needle) vid, fid, filename, ext, _ := parseURLPath(r.URL.Path) + + if !vs.maybeCheckJwtAuthorization(r, vid, fid, false) { + writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt")) + return + } + volumeId, err := needle.NewVolumeId(vid) if err != nil { glog.V(2).Infoln("parsing error:", err, r.URL.Path) diff --git a/weed/server/volume_server_handlers_write.go b/weed/server/volume_server_handlers_write.go index 188d88ddf..7c0e08554 100644 --- a/weed/server/volume_server_handlers_write.go +++ b/weed/server/volume_server_handlers_write.go @@ -29,7 +29,7 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) { return } - if !vs.maybeCheckJwtAuthorization(r, vid, fid) { + if !vs.maybeCheckJwtAuthorization(r, vid, fid, true) { writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt")) return } @@ -65,7 +65,7 @@ func (vs *VolumeServer) DeleteHandler(w http.ResponseWriter, r *http.Request) { volumeId, _ := needle.NewVolumeId(vid) n.ParsePath(fid) - if !vs.maybeCheckJwtAuthorization(r, vid, fid) { + if !vs.maybeCheckJwtAuthorization(r, vid, fid, true) { writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt")) return }