jwt for read access control

This commit is contained in:
Chris Lu 2019-06-06 00:29:02 -07:00
parent d344e0a035
commit 50aa769554
8 changed files with 69 additions and 20 deletions

View file

@ -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 = ""

View file

@ -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
}

View file

@ -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)

View file

@ -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))
}

View file

@ -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 {

View file

@ -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

View file

@ -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)

View file

@ -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
}