Skip to content

Commit

Permalink
Merge master into default-murmur64
Browse files Browse the repository at this point in the history
  • Loading branch information
tykbot[bot] committed Jul 31, 2019
2 parents 7b39336 + f87150f commit 5aed02a
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 4 deletions.
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ install:
- go get github.com/go-swagger/go-swagger/cmd/swagger

script:
- pip3 install setuptools
- sudo pip3 install google
- sudo pip3 install protobuf
### Needed to convert the swagger 2.0 file to openapi 3.0
Expand Down
8 changes: 8 additions & 0 deletions apidef/api_definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,7 @@ type APIDefinition struct {
EnableSignatureChecking bool `bson:"enable_signature_checking" json:"enable_signature_checking"`
HmacAllowedClockSkew float64 `bson:"hmac_allowed_clock_skew" json:"hmac_allowed_clock_skew"`
HmacAllowedAlgorithms []string `bson:"hmac_allowed_algorithms" json:"hmac_allowed_algorithms"`
RequestSigning RequestSigningMeta `bson:"request_signing" json:"request_signing"`
BaseIdentityProvidedBy AuthTypeEnum `bson:"base_identity_provided_by" json:"base_identity_provided_by"`
VersionDefinition struct {
Location string `bson:"location" json:"location"`
Expand Down Expand Up @@ -496,6 +497,13 @@ type BundleManifest struct {
Signature string `bson:"signature" json:"signature"`
}

type RequestSigningMeta struct {
IsEnabled bool `bson:"is_enabled" json:"is_enabled"`
Secret string `bson:"secret" json:"secret"`
KeyId string `bson:"key_id" json:"key_id"`
Algorithm string `bson:"algorithm" json:"algorithm"`
}

// Clean will URL encode map[string]struct variables for saving
func (a *APIDefinition) EncodeForDB() {
newVersion := make(map[string]VersionInfo)
Expand Down
20 changes: 20 additions & 0 deletions apidef/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,26 @@ const Schema = `{
"type": "number"
}
}
},
"request_signing": {
"type": ["object", "null"],
"properties": {
"is_enabled": {
"type": "boolean"
},
"secret": {
"type": "string"
},
"key_id": {
"type": "string"
},
"algorithm": {
"type": "string"
}
},
"required": [
"is_enabled"
]
}
},
"required": [
Expand Down
1 change: 1 addition & 0 deletions gateway/api_loader.go
Original file line number Diff line number Diff line change
Expand Up @@ -396,6 +396,7 @@ func processSpec(spec *APISpec, apisByListen map[string]int,
mwAppendEnabled(&chainArray, &TransformMethod{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &RedisCacheMiddleware{BaseMiddleware: baseMid, CacheStore: &cacheStore})
mwAppendEnabled(&chainArray, &VirtualEndpoint{BaseMiddleware: baseMid})
mwAppendEnabled(&chainArray, &RequestSigning{BaseMiddleware: baseMid})

for _, obj := range mwPostFuncs {
if mwDriver == apidef.GoPluginDriver {
Expand Down
8 changes: 4 additions & 4 deletions gateway/mw_hmac.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func (hm *HMACMiddleware) ProcessRequest(w http.ResponseWriter, r *http.Request,
}

// Generate a signature string
signatureString, err := generateHMACSignatureStringFromRequest(r, fieldValues)
signatureString, err := generateHMACSignatureStringFromRequest(r, fieldValues.Headers)
if err != nil {
logger.WithError(err).WithField("signature_string", signatureString).Error("Signature string generation failed")
return hm.authorizationError(r)
Expand Down Expand Up @@ -296,9 +296,9 @@ func getFieldValues(authHeader string) (*HMACFieldValues, error) {

// "Signature keyId="9876",algorithm="hmac-sha1",headers="x-test x-test-2",signature="queryEscape(base64(sig))"")

func generateHMACSignatureStringFromRequest(r *http.Request, fieldValues *HMACFieldValues) (string, error) {
func generateHMACSignatureStringFromRequest(r *http.Request, headers []string) (string, error) {
signatureString := ""
for i, header := range fieldValues.Headers {
for i, header := range headers {
loweredHeader := strings.TrimSpace(strings.ToLower(header))
if loweredHeader == "(request-target)" {
requestHeaderField := "(request-target): " + strings.ToLower(r.Method) + " " + r.URL.Path
Expand All @@ -313,7 +313,7 @@ func generateHMACSignatureStringFromRequest(r *http.Request, fieldValues *HMACFi
signatureString += headerField
}

if i != len(fieldValues.Headers)-1 {
if i != len(headers)-1 {
signatureString += "\n"
}
}
Expand Down
98 changes: 98 additions & 0 deletions gateway/mw_request_signing.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package gateway

import (
"errors"
"net/http"
"strings"
"time"
)

type RequestSigning struct {
BaseMiddleware
}

func (s *RequestSigning) Name() string {
return "RequestSigning"
}

func (s *RequestSigning) EnabledForSpec() bool {
return s.Spec.RequestSigning.IsEnabled
}

var supportedAlgorithms = []string{"hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512"}

func generateHeaderList(r *http.Request) []string {
headers := make([]string, len(r.Header)+1)

headers[0] = "(request-target)"
i := 1

for k := range r.Header {
loweredCaseHeader := strings.ToLower(k)
headers[i] = strings.TrimSpace(loweredCaseHeader)
i++
}

//Date header is must as per Signing HTTP Messages Draft
if r.Header.Get("date") == "" {
refDate := "Mon, 02 Jan 2006 15:04:05 MST"
tim := time.Now().Format(refDate)

r.Header.Set("date", tim)
headers = append(headers, "date")
}

return headers
}

func (s *RequestSigning) ProcessRequest(w http.ResponseWriter, r *http.Request, _ interface{}) (error, int) {
if s.Spec.RequestSigning.Secret == "" || s.Spec.RequestSigning.KeyId == "" || s.Spec.RequestSigning.Algorithm == "" {
log.Error("Fields required for signing the request are missing")
return errors.New("Fields required for signing the request are missing"), http.StatusInternalServerError
}

var algoList []string
if len(s.Spec.HmacAllowedAlgorithms) > 0 {
algoList = s.Spec.HmacAllowedAlgorithms
} else {
algoList = supportedAlgorithms
}

algorithmAllowed := false
for _, alg := range algoList {
if alg == s.Spec.RequestSigning.Algorithm {
algorithmAllowed = true
break
}
}
if !algorithmAllowed {
log.WithField("algorithm", s.Spec.RequestSigning.Algorithm).Error("Algorithm not supported")
return errors.New("Request signing Algorithm is not supported"), http.StatusInternalServerError
}

headers := generateHeaderList(r)
signatureString, err := generateHMACSignatureStringFromRequest(r, headers)
if err != nil {
log.Error(err)
return err, http.StatusInternalServerError
}

strHeaders := strings.Join(headers, " ")
encodedSignature := generateEncodedSignature(signatureString, s.Spec.RequestSigning.Secret, s.Spec.RequestSigning.Algorithm)

//Generate Authorization header
authHeader := "Signature "
//Append keyId
authHeader += "keyId=\"" + s.Spec.RequestSigning.KeyId + "\","
//Append algorithm
authHeader += "algorithm=\"" + s.Spec.RequestSigning.Algorithm + "\","
//Append Headers
authHeader += "headers=\"" + strHeaders + "\","
//Append signature
authHeader += "signature=\"" + encodedSignature + "\""

r.Header.Set("Authorization", authHeader)
log.Debug("Setting Authorization headers as =", authHeader)

return nil, http.StatusOK
}
94 changes: 94 additions & 0 deletions gateway/mw_request_signing_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package gateway

import (
"encoding/json"
"testing"

"github.com/TykTechnologies/tyk/test"
"github.com/TykTechnologies/tyk/user"
)

var algoList = [4]string{"hmac-sha1", "hmac-sha256", "hmac-sha384", "hmac-sha512"}

func generateSpec(algo string) {
sessionKey := CreateSession(func(s *user.SessionState) {
s.HMACEnabled = true
s.HmacSecret = "9879879878787878"

s.AccessRights = map[string]user.AccessDefinition{"protected": {APIID: "protected", Versions: []string{"v1"}}}

})

BuildAndLoadAPI(func(spec *APISpec) {
spec.APIID = "protected"
spec.Name = "protected api"
spec.Proxy.ListenPath = "/something"
spec.EnableSignatureChecking = true
spec.Auth.AuthHeaderName = "authorization"
spec.HmacAllowedClockSkew = 5000
spec.UseKeylessAccess = false
spec.UseBasicAuth = false
spec.UseOauth2 = false

version := spec.VersionData.Versions["v1"]
version.UseExtendedPaths = true
spec.VersionData.Versions["v1"] = version
}, func(spec *APISpec) {
spec.Proxy.ListenPath = "/test"
spec.RequestSigning.IsEnabled = true
spec.RequestSigning.KeyId = sessionKey
spec.RequestSigning.Secret = "9879879878787878"
spec.RequestSigning.Algorithm = algo

version := spec.VersionData.Versions["v1"]
json.Unmarshal([]byte(`{
"use_extended_paths": true,
"extended_paths": {
"url_rewrites": [{
"path": "/by_name",
"match_pattern": "/by_name(.*)",
"method": "GET",
"rewrite_to": "tyk://protected api/get"
}]
}
}`), &version)

spec.VersionData.Versions["v1"] = version
})

}

func TestRequestSigning(t *testing.T) {
ts := StartTest()
defer ts.Close()

for _, algo := range algoList {
name := "Test with " + algo
t.Run(name, func(t *testing.T) {

generateSpec(algo)

ts.Run(t, []test.TestCase{
{Path: "/test/by_name", Code: 200},
}...)
})
}

t.Run("Invalid algorithm", func(t *testing.T) {
generateSpec("random")

ts.Run(t, []test.TestCase{
{Path: "/test/by_name", Code: 500},
}...)
})

t.Run("Invalid Date field", func(t *testing.T) {
generateSpec("hmac-sha1")

headers := map[string]string{"date": "Mon, 02 Jan 2006 15:04:05 GMT"}

ts.Run(t, []test.TestCase{
{Path: "/test/by_name", Headers: headers, Code: 400},
}...)
})
}

0 comments on commit 5aed02a

Please sign in to comment.