jwt for read access control
This commit is contained in:
@@ -277,8 +277,14 @@ directory = "/" # destination directory
|
|||||||
key = ""
|
key = ""
|
||||||
expires_after_seconds = 10 # seconds
|
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
|
# all grpc tls authentications are mutual
|
||||||
# the values for the following ca, cert, and key are paths to the PERM files.
|
# 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]
|
[grpc]
|
||||||
ca = ""
|
ca = ""
|
||||||
|
|
||||||
|
|||||||
@@ -41,21 +41,29 @@ https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go
|
|||||||
|
|
||||||
*/
|
*/
|
||||||
type Guard struct {
|
type Guard struct {
|
||||||
whiteList []string
|
whiteList []string
|
||||||
SigningKey SigningKey
|
SigningKey SigningKey
|
||||||
ExpiresAfterSec int
|
ExpiresAfterSec int
|
||||||
|
ReadSigningKey SigningKey
|
||||||
|
ReadExpiresAfterSec int
|
||||||
|
|
||||||
isActive bool
|
isWriteActive bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGuard(whiteList []string, signingKey string, expiresAfterSec int) *Guard {
|
func NewGuard(whiteList []string, signingKey string, expiresAfterSec int, readSigningKey string, readExpiresAfterSec int) *Guard {
|
||||||
g := &Guard{whiteList: whiteList, SigningKey: SigningKey(signingKey), ExpiresAfterSec: expiresAfterSec}
|
g := &Guard{
|
||||||
g.isActive = len(g.whiteList) != 0 || len(g.SigningKey) != 0
|
whiteList: whiteList,
|
||||||
|
SigningKey: SigningKey(signingKey),
|
||||||
|
ExpiresAfterSec: expiresAfterSec,
|
||||||
|
ReadSigningKey: SigningKey(readSigningKey),
|
||||||
|
ReadExpiresAfterSec: readExpiresAfterSec,
|
||||||
|
}
|
||||||
|
g.isWriteActive = len(g.whiteList) != 0 || len(g.SigningKey) != 0
|
||||||
return g
|
return g
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *Guard) WhiteList(f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
|
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
|
//if no security needed, just skip all checking
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ func NewMasterServer(r *mux.Router, port int, metaFolder string,
|
|||||||
v.SetDefault("jwt.signing.expires_after_seconds", 10)
|
v.SetDefault("jwt.signing.expires_after_seconds", 10)
|
||||||
expiresAfterSec := v.GetInt("jwt.signing.expires_after_seconds")
|
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
|
var preallocateSize int64
|
||||||
if preallocate {
|
if preallocate {
|
||||||
preallocateSize = int64(volumeSizeLimitMB) * (1 << 20)
|
preallocateSize = int64(volumeSizeLimitMB) * (1 << 20)
|
||||||
@@ -83,7 +87,7 @@ func NewMasterServer(r *mux.Router, port int, metaFolder string,
|
|||||||
ms.vg = topology.NewDefaultVolumeGrowth()
|
ms.vg = topology.NewDefaultVolumeGrowth()
|
||||||
glog.V(0).Infoln("Volume Size Limit is", volumeSizeLimitMB, "MB")
|
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 {
|
if !disableHttp {
|
||||||
handleStaticResources2(r)
|
handleStaticResources2(r)
|
||||||
|
|||||||
@@ -67,7 +67,9 @@ func (ms *MasterServer) dirLookupHandler(w http.ResponseWriter, r *http.Request)
|
|||||||
if location.Error != "" {
|
if location.Error != "" {
|
||||||
httpStatus = http.StatusNotFound
|
httpStatus = http.StatusNotFound
|
||||||
} else {
|
} else {
|
||||||
ms.maybeAddJwtAuthorization(w, fileId)
|
forRead := r.FormValue("read")
|
||||||
|
isRead := forRead == "yes"
|
||||||
|
ms.maybeAddJwtAuthorization(w, fileId, !isRead)
|
||||||
}
|
}
|
||||||
writeJsonQuiet(w, r, httpStatus, location)
|
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)
|
fid, count, dn, err := ms.Topo.PickForWrite(requestedCount, option)
|
||||||
if err == nil {
|
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})
|
writeJsonQuiet(w, r, http.StatusOK, operation.AssignResult{Fid: fid, Url: dn.Url(), PublicUrl: dn.PublicUrl, Count: count})
|
||||||
} else {
|
} else {
|
||||||
writeJsonQuiet(w, r, http.StatusNotAcceptable, operation.AssignResult{Error: err.Error()})
|
writeJsonQuiet(w, r, http.StatusNotAcceptable, operation.AssignResult{Error: err.Error()})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ms *MasterServer) maybeAddJwtAuthorization(w http.ResponseWriter, fileId string) {
|
func (ms *MasterServer) maybeAddJwtAuthorization(w http.ResponseWriter, fileId string, isWrite bool) {
|
||||||
encodedJwt := security.GenJwt(ms.guard.SigningKey, ms.guard.ExpiresAfterSec, fileId)
|
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 == "" {
|
if encodedJwt == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
w.Header().Set("Authorization", "BEARER "+string(encodedJwt))
|
w.Header().Set("Authorization", "BEARER "+string(encodedJwt))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,6 +44,10 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
|
|||||||
expiresAfterSec := v.GetInt("jwt.signing.expires_after_seconds")
|
expiresAfterSec := v.GetInt("jwt.signing.expires_after_seconds")
|
||||||
enableUiAccess := v.GetBool("access.ui")
|
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{
|
vs := &VolumeServer{
|
||||||
pulseSeconds: pulseSeconds,
|
pulseSeconds: pulseSeconds,
|
||||||
dataCenter: dataCenter,
|
dataCenter: dataCenter,
|
||||||
@@ -57,7 +61,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
|
|||||||
vs.SeedMasterNodes = masterNodes
|
vs.SeedMasterNodes = masterNodes
|
||||||
vs.store = storage.NewStore(vs.grpcDialOption, port, ip, publicUrl, folders, maxCounts, vs.needleMapKind)
|
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)
|
handleStaticResources(adminMux)
|
||||||
if signingKey == "" || enableUiAccess {
|
if signingKey == "" || enableUiAccess {
|
||||||
|
|||||||
@@ -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 {
|
var signingKey security.SigningKey
|
||||||
return true
|
|
||||||
|
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)
|
tokenStr := security.GetJwt(r)
|
||||||
@@ -61,7 +73,7 @@ func (vs *VolumeServer) maybeCheckJwtAuthorization(r *http.Request, vid, fid str
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := security.DecodeJwt(vs.guard.SigningKey, tokenStr)
|
token, err := security.DecodeJwt(signingKey, tokenStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(1).Infof("jwt verification error from %s: %v", r.RemoteAddr, err)
|
glog.V(1).Infof("jwt verification error from %s: %v", r.RemoteAddr, err)
|
||||||
return false
|
return false
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package weed_server
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"mime/multipart"
|
"mime/multipart"
|
||||||
@@ -27,6 +28,12 @@ var fileNameEscaper = strings.NewReplacer("\\", "\\\\", "\"", "\\\"")
|
|||||||
func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
|
func (vs *VolumeServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
n := new(needle.Needle)
|
n := new(needle.Needle)
|
||||||
vid, fid, filename, ext, _ := parseURLPath(r.URL.Path)
|
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)
|
volumeId, err := needle.NewVolumeId(vid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
glog.V(2).Infoln("parsing error:", err, r.URL.Path)
|
glog.V(2).Infoln("parsing error:", err, r.URL.Path)
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ func (vs *VolumeServer) PostHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !vs.maybeCheckJwtAuthorization(r, vid, fid) {
|
if !vs.maybeCheckJwtAuthorization(r, vid, fid, true) {
|
||||||
writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
|
writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -65,7 +65,7 @@ func (vs *VolumeServer) DeleteHandler(w http.ResponseWriter, r *http.Request) {
|
|||||||
volumeId, _ := needle.NewVolumeId(vid)
|
volumeId, _ := needle.NewVolumeId(vid)
|
||||||
n.ParsePath(fid)
|
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"))
|
writeJsonError(w, r, http.StatusUnauthorized, errors.New("wrong jwt"))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user