Browse Source

Merge 188715df24 into bd97cbc523

pull/427/merge
Xiaodong Huo 9 years ago
committed by GitHub
parent
commit
7ce56dc096
  1. 256
      weed/security/guard.go
  2. 7
      weed/server/filer_server.go
  3. 2
      weed/server/master_server.go
  4. 2
      weed/server/volume_server.go

256
weed/security/guard.go

@ -1,10 +1,16 @@
package security package security
import ( import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net" "net"
"net/http" "net/http"
"net/url"
"sort"
"strings" "strings"
"github.com/chrislusf/seaweedfs/weed/glog" "github.com/chrislusf/seaweedfs/weed/glog"
@ -40,16 +46,52 @@ Referenced:
https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go https://github.com/pkieltyka/jwtauth/blob/master/jwtauth.go
*/ */
/*
Below sample config section should be modified per your needs and added into the filer.json config file if you want to
enable filer R/W API verfication like the AWS S3 RESTFUL authentication
{
"appInfo": [
{
"AppKeyId": "XVNVWEYQVNGXWWNXGLKP",
"AppKeySecret": "H8peDqD3A9Uc8KO28Iu99ywnW7TKQ8pii2PE8xpQ",
"bucketList": [
"yourbucketName"
]
}
]
}
*/
type AppContent struct {
AppKeySecret string
BucketList []string
}
type AppConf struct {
AppContent
AppKeyID string
}
type Guard struct { type Guard struct {
whiteList []string whiteList []string
SecretKey Secret SecretKey Secret
appKeyDict map[string]AppContent
isActive bool 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
func NewGuard(whiteList []string, secretKey string, appConfs []AppConf) *Guard {
appKeyDict := make(map[string]AppContent, len(appConfs))
for _, appConf := range appConfs {
appKeyDict[appConf.AppKeyID] = AppContent{
AppKeySecret: appConf.AppKeySecret,
BucketList: appConf.BucketList,
}
}
g := &Guard{whiteList: whiteList, SecretKey: Secret(secretKey), appKeyDict: appKeyDict}
g.isActive = len(g.whiteList) != 0 || len(g.SecretKey) != 0 || len(g.appKeyDict) != 0
return g return g
} }
@ -81,6 +123,23 @@ func (g *Guard) Secure(f func(w http.ResponseWriter, r *http.Request)) func(w ht
} }
} }
/*
Please refer to http://docs.aws.amazon.com/AmazonS3/latest/dev/RESTAuthentication.html for Rest Authentication details
*/
func (g *Guard) CheckAuthorization(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.checkAuthorization(w, r); err != nil {
w.WriteHeader(http.StatusUnauthorized)
return
}
f(w, r)
}
}
func GetActualRemoteHost(r *http.Request) (host string, err error) { func GetActualRemoteHost(r *http.Request) (host string, err error) {
host = r.Header.Get("HTTP_X_FORWARDED_FOR") host = r.Header.Get("HTTP_X_FORWARDED_FOR")
if host == "" { if host == "" {
@ -160,3 +219,194 @@ func (g *Guard) checkJwt(w http.ResponseWriter, r *http.Request) error {
glog.V(1).Infof("No permission from %s", r.RemoteAddr) glog.V(1).Infof("No permission from %s", r.RemoteAddr)
return fmt.Errorf("No write permisson from %s", r.RemoteAddr) return fmt.Errorf("No write permisson from %s", r.RemoteAddr)
} }
func ComputeHmac256(message string, secret string) string {
key := []byte(secret)
h := hmac.New(sha256.New, key)
h.Write([]byte(message))
return base64.StdEncoding.EncodeToString(h.Sum(nil))
}
/*
authentication sample:
Authorization: AWS-HMAC-SHA256 PLLZOBTTZXGBNOWUFHZZ:tuXu/KcggHWPAfEmraUHDwEUdiIPSXVRsO+T2rxomBQ=
*/
func VerifyAuthorizationHeader(authorization string) (accessKeyId, signature string, err error) {
authArr := strings.Split(authorization, " ")
if len(authArr) != 2 {
err = fmt.Errorf("authorization %s is not correct", authorization)
return
}
/*
This is just a sample, you could customize the signature algorithm freely
*/
if authArr[0] != "AWS-HMAC-SHA256" {
err = fmt.Errorf("algorithm %s is not supported yet", authArr[0])
return
}
sigArr := strings.Split(authArr[1], ":")
if len(sigArr) != 2 {
err = fmt.Errorf("signature %s is not correct", authArr[1])
return
}
accessKeyId = sigArr[0]
signature = sigArr[1]
if len(accessKeyId) == 0 || len(signature) == 0 {
err = fmt.Errorf("accessKeyId %s or signature %s is empty", accessKeyId, signature)
return
}
return
}
func (g *Guard) GetAccessKeyAppContent(accessKeyId string) (AppContent, error) {
if appContent, found := g.appKeyDict[accessKeyId]; !found {
return AppContent{}, fmt.Errorf("%s key Not Found", accessKeyId)
} else {
return appContent, nil
}
}
func GetBucketAndObjectName(r *http.Request) (bucketName, objectName string) {
/*
http://host:port/objectName?collection=bucketName
http://host:port/bucketName/objectName
*/
collection := r.URL.Query().Get("collection")
if collection != "" {
return collection, r.URL.Path[1:]
}
secondPos := strings.Index(r.URL.Path[1:], "/")
if secondPos == -1 {
secondPos = len(r.URL.Path)
} else {
secondPos += 1
}
bucketName = r.URL.Path[1:secondPos]
if secondPos+1 < len(r.URL.Path) {
objectName = r.URL.Path[secondPos+1:]
}
return
}
type headerSorter struct {
Keys []string
Vals []string
}
func newHeaderSorter(m map[string]string) *headerSorter {
hs := &headerSorter{
Keys: make([]string, 0, len(m)),
Vals: make([]string, 0, len(m)),
}
for k, v := range m {
hs.Keys = append(hs.Keys, k)
hs.Vals = append(hs.Vals, v)
}
return hs
}
func (hs *headerSorter) Sort() {
sort.Sort(hs)
}
func (hs *headerSorter) Len() int {
return len(hs.Vals)
}
func (hs *headerSorter) Less(i, j int) bool {
return bytes.Compare([]byte(hs.Keys[i]), []byte(hs.Keys[j])) < 0
}
func (hs *headerSorter) Swap(i, j int) {
hs.Vals[i], hs.Vals[j] = hs.Vals[j], hs.Vals[i]
hs.Keys[i], hs.Keys[j] = hs.Keys[j], hs.Keys[i]
}
func (g *Guard) GenerateSignature(appKeySecret string, r *http.Request) (string, error) {
unescaped_uri, err := url.QueryUnescape(r.RequestURI)
if err != nil {
glog.V(1).Infof("uri %s unescape err %s", unescaped_uri, err.Error())
return "", ErrUnauthorized
}
temp := make(map[string]string)
/*
if there are some header options which were not prepared to added into the canonicalizedOSSHeaders,
add a prefix as below and bypass
*/
for k, v := range r.Header {
if !strings.HasPrefix(strings.ToLower(k), "x-aws-") {
temp[strings.ToLower(k)] = v[0]
}
}
hs := newHeaderSorter(temp)
hs.Sort()
// Get the CanonicalizedOSSHeaders
canonicalizedOSSHeaders := ""
for i := range hs.Keys {
canonicalizedOSSHeaders += hs.Keys[i] + ":" + hs.Vals[i] + "\n"
}
string_to_sign := r.Method + "\n" +
r.Header.Get("Content-MD5") + "\n" +
r.Header.Get("Content-Type") + "\n" +
r.Header.Get("Date") + "\n" +
canonicalizedOSSHeaders +
unescaped_uri
return ComputeHmac256(string_to_sign, appKeySecret), nil
}
func (g *Guard) checkAuthorization(w http.ResponseWriter, r *http.Request) error {
authorization_string_from_client := r.Header.Get("Authorization")
if len(authorization_string_from_client) == 0 {
glog.V(1).Infof("Authorization in header field is empty")
return ErrUnauthorized
}
accessKeyId, signature, err := VerifyAuthorizationHeader(authorization_string_from_client)
if err != nil {
glog.V(1).Infof("verify authorization failed %s", err.Error())
return ErrUnauthorized
}
appContent, err := g.GetAccessKeyAppContent(accessKeyId)
if err != nil {
glog.V(1).Infof("accessKeyId %s is invalid", accessKeyId)
return ErrUnauthorized
}
bucketName, _ := GetBucketAndObjectName(r)
var found bool
for _, bucket := range appContent.BucketList {
if bucket == bucketName {
found = true
break
}
}
if !found {
glog.V(1).Infof("bucketName %s is not authorized for appKeyId %s", bucketName, accessKeyId)
return ErrUnauthorized
}
if my_sig, err := g.GenerateSignature(appContent.AppKeySecret, r); err != nil {
return err
} else {
if signature != my_sig {
glog.V(1).Infof("Authorization[%s]'s signature from client is not valid", authorization_string_from_client)
return ErrUnauthorized
} else {
glog.V(3).Infof("Authorization succeeded!")
}
}
return nil
}

7
weed/server/filer_server.go

@ -26,6 +26,7 @@ type filerConf struct {
MysqlConf []mysql_store.MySqlConf `json:"mysql"` MysqlConf []mysql_store.MySqlConf `json:"mysql"`
mysql_store.ShardingConf mysql_store.ShardingConf
PostgresConf *postgres_store.PostgresConf `json:"postgres"` PostgresConf *postgres_store.PostgresConf `json:"postgres"`
AppConf []security.AppConf `json:"appInfo"`
} }
func parseConfFile(confPath string) (*filerConf, error) { func parseConfFile(confPath string) (*filerConf, error) {
@ -55,6 +56,7 @@ type FilerServer struct {
filer filer.Filer filer filer.Filer
maxMB int maxMB int
masterNodes *storage.MasterNodes masterNodes *storage.MasterNodes
guard *security.Guard
} }
func NewFilerServer(r *http.ServeMux, ip string, port int, master string, dir string, collection string, func NewFilerServer(r *http.ServeMux, ip string, port int, master string, dir string, collection string,
@ -110,6 +112,11 @@ func NewFilerServer(r *http.ServeMux, ip string, port int, master string, dir st
r.HandleFunc("/__api__", fs.apiHandler) r.HandleFunc("/__api__", fs.apiHandler)
} }
/*
guard := security.NewGuard(nil, secret, setting.AppConf)
fs.guard = guard
r.HandleFunc("/", guard.CheckAuthorization(fs.filerHandler))
*/
r.HandleFunc("/", fs.filerHandler) r.HandleFunc("/", fs.filerHandler)
go func() { go func() {

2
weed/server/master_server.go

@ -61,7 +61,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, secureKey)
ms.guard = security.NewGuard(whiteList, secureKey, nil)
r.HandleFunc("/", ms.uiStatusHandler) r.HandleFunc("/", ms.uiStatusHandler)
r.HandleFunc("/ui/index.html", ms.uiStatusHandler) r.HandleFunc("/ui/index.html", ms.uiStatusHandler)

2
weed/server/volume_server.go

@ -46,7 +46,7 @@ func NewVolumeServer(adminMux, publicMux *http.ServeMux, ip string,
vs.store = storage.NewStore(port, ip, publicUrl, folders, maxCounts, vs.needleMapKind) vs.store = storage.NewStore(port, ip, publicUrl, folders, maxCounts, vs.needleMapKind)
storage.EnableBytesCache = enableBytesCache storage.EnableBytesCache = enableBytesCache
vs.guard = security.NewGuard(whiteList, "")
vs.guard = security.NewGuard(whiteList, "", nil)
adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler) adminMux.HandleFunc("/ui/index.html", vs.uiStatusHandler)
adminMux.HandleFunc("/status", vs.guard.WhiteList(vs.statusHandler)) adminMux.HandleFunc("/status", vs.guard.WhiteList(vs.statusHandler))

Loading…
Cancel
Save