From b095c92cfd1efbdc2e6b6498cd2c7fa1d2403ac4 Mon Sep 17 00:00:00 2001 From: simon987 Date: Sat, 9 Mar 2019 13:05:31 -0500 Subject: [PATCH] Auth with global secret for admin endpoints --- README.md | 25 +++++++++++++++++++- api/auth.go | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++ api/models.go | 3 ++- api/slot.go | 12 ++++++++-- test/common.go | 29 +++++++++++++++-------- 5 files changed, 118 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index a77115c..c276472 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,32 @@ | `WS_BUCKET_LOGLEVEL` | `trace` | | `WS_BUCKET_CONNSTR` | `host=localhost user=ws_bucket dbname=ws_bucket password=ws_bucket sslmode=disable` | | `WS_BUCKET_DIALECT` | `postgres` | +| `WS_BUCKET_SECRET` | `default_secret`* | + +\* You should change this value! ### Running tests ```bash +export WS_BUCKET_ADDR=0.0.0.0:3021 +export WS_BUCKET_WORKDIR=. + cd test/ go test -``` \ No newline at end of file +``` + +### Auth +Administration endpoints require HMAC_SHA256 authentication. +Request header: +``` +{ + "Timestamp": + "X-Signature": +} +``` + +Upload endpoint requires a valid upload token: +``` +{ + "X-Upload-Token": +} +``` diff --git a/api/auth.go b/api/auth.go index 778f64e..f0a0939 100644 --- a/api/auth.go +++ b/api/auth.go @@ -1 +1,64 @@ package api + +import ( + "bytes" + "crypto" + "crypto/hmac" + "encoding/hex" + "errors" + "github.com/valyala/fasthttp" + "math" + "os" + "time" +) + +var Secret = []byte(getApiSecret()) + +func getApiSecret() string { + + secret := os.Getenv("WS_BUCKET_SECRET") + if secret == "" { + return "default_secret" + } else { + return secret + } +} + +func validateRequest(ctx *fasthttp.RequestCtx) error { + + signature := ctx.Request.Header.Peek("X-Signature") + timeStampStr := string(ctx.Request.Header.Peek("Timestamp")) + + if timeStampStr == "" { + return errors.New("date is not specified") + } + + timestamp, err := time.Parse(time.RFC1123, timeStampStr) + if err != nil { + return err + } + + if math.Abs(float64(timestamp.Unix()-time.Now().Unix())) > 60 { + return errors.New("invalid Timestamp") + } + + var body []byte + if ctx.Request.Header.IsGet() { + body = ctx.Request.RequestURI() + } else { + body = ctx.Request.Body() + } + + mac := hmac.New(crypto.SHA256.New, Secret) + mac.Write(body) + mac.Write([]byte(timeStampStr)) + + expectedMac := make([]byte, 64) + hex.Encode(expectedMac, mac.Sum(nil)) + matches := bytes.Compare(expectedMac, signature) == 0 + + if !matches { + return errors.New("signature does not match") + } + return nil +} diff --git a/api/models.go b/api/models.go index 2492ce8..869da1d 100644 --- a/api/models.go +++ b/api/models.go @@ -7,7 +7,8 @@ import ( ) type GenericResponse struct { - Ok bool `json:"ok"` + Ok bool `json:"ok"` + Message string `json:"message,omitempty"` } type AllocateUploadSlotRequest struct { diff --git a/api/slot.go b/api/slot.go index 1e1b9e1..9cff611 100644 --- a/api/slot.go +++ b/api/slot.go @@ -22,10 +22,18 @@ var upgrader = websocket.FastHTTPUpgrader{ func (api *WebApi) AllocateUploadSlot(ctx *fasthttp.RequestCtx) { - //todo auth + err := validateRequest(ctx) + if err != nil { + ctx.Response.Header.SetStatusCode(401) + Json(GenericResponse{ + Ok: false, + Message: err.Error(), + }, ctx) + return + } req := &AllocateUploadSlotRequest{} - err := json.Unmarshal(ctx.Request.Body(), req) + err = json.Unmarshal(ctx.Request.Body(), req) if err != nil { ctx.Response.Header.SetStatusCode(400) Json(GenericResponse{ diff --git a/test/common.go b/test/common.go index 50fe86b..a418f55 100644 --- a/test/common.go +++ b/test/common.go @@ -2,11 +2,15 @@ package test import ( "bytes" + "crypto" + "crypto/hmac" + "encoding/hex" "encoding/json" "fmt" "github.com/simon987/ws_bucket/api" "io/ioutil" "net/http" + "time" ) func Post(path string, x interface{}) *http.Response { @@ -19,16 +23,13 @@ func Post(path string, x interface{}) *http.Response { req, err := http.NewRequest("POST", "http://"+api.GetServerAddress()+path, buf) handleErr(err) - //ts := time.Now().Format(time.RFC1123) - // - //mac := hmac.New(crypto.SHA256.New, worker.Secret) - //mac.Write(body) - //mac.Write([]byte(ts)) - //sig := hex.EncodeToString(mac.Sum(nil)) - // - //req.Header.Add("X-Worker-Id", strconv.FormatInt(worker.Id, 10)) - //req.Header.Add("X-Signature", sig) - //req.Header.Add("Timestamp", ts) + ts := time.Now().Format(time.RFC1123) + mac := hmac.New(crypto.SHA256.New, []byte("default_secret")) + mac.Write(body) + mac.Write([]byte(ts)) + sig := hex.EncodeToString(mac.Sum(nil)) + req.Header.Add("X-Signature", sig) + req.Header.Add("Timestamp", ts) r, err := s.Do(req) handleErr(err) @@ -43,6 +44,14 @@ func Get(path string, token string) *http.Response { req, err := http.NewRequest("GET", "http://"+api.GetServerAddress()+path, nil) handleErr(err) + ts := time.Now().Format(time.RFC1123) + mac := hmac.New(crypto.SHA256.New, []byte("default_secret")) + mac.Write([]byte(path)) + mac.Write([]byte(ts)) + sig := hex.EncodeToString(mac.Sum(nil)) + req.Header.Add("X-Signature", sig) + req.Header.Add("Timestamp", ts) + req.Header.Set("X-Upload-Token", token) r, err := s.Do(req)