Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New token generation, support for custom hashing algorithms #1753

Merged
merged 15 commits into from
Jun 13, 2018
8 changes: 2 additions & 6 deletions api.go
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ func handleAddOrUpdate(keyName string, r *http.Request) (interface{}, int) {
// Only if it's NEW
switch r.Method {
case "POST":
keyName = newSession.OrgID + keyName
keyName = generateToken(newSession.OrgID, keyName)
// It's a create, so lets hash the password
setSessionPassword(&newSession)
case "PUT":
Expand Down Expand Up @@ -926,11 +926,7 @@ func createKeyHandler(w http.ResponseWriter, r *http.Request) {
}

if newSession.Certificate != "" {
newKey = newSession.OrgID + newSession.Certificate

if strings.HasPrefix(newSession.Certificate, newSession.OrgID) {
newKey = newSession.Certificate
}
newKey = generateToken(newSession.OrgID, newSession.Certificate)
}

newSession.LastUpdated = strconv.Itoa(int(time.Now().Unix()))
Expand Down
35 changes: 31 additions & 4 deletions api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,8 +172,6 @@ func TestKeyHandler(t *testing.T) {
}
withBadPolicyJSON, _ := json.Marshal(withBadPolicy)

knownKey := createSession()

t.Run("Create key", func(t *testing.T) {
ts.Run(t, []test.TestCase{
// Master keys should be disabled by default
Expand Down Expand Up @@ -215,6 +213,8 @@ func TestKeyHandler(t *testing.T) {
}...)
})

knownKey := createSession()

t.Run("Get key", func(t *testing.T) {
ts.Run(t, []test.TestCase{
{Method: "GET", Path: "/tyk/keys/unknown", AdminAuth: true, Code: 404},
Expand Down Expand Up @@ -257,9 +257,32 @@ func TestHashKeyHandler(t *testing.T) {
// enable hashed keys listing
globalConf.EnableHashedKeysListing = true
config.SetGlobal(globalConf)

defer resetTestConfig()

hashTests := []struct {
hashFunction string
expectedHashSize int
desc string
}{
{"", 8, " Legacy tokens, fallback to murmur32"},
{storage.HashMurmur32, 8, ""},
{storage.HashMurmur64, 16, ""},
{storage.HashMurmur128, 32, ""},
{storage.HashSha256, 64, ""},
{"wrong", 16, " Should fallback to murmur64 if wrong alg"},
}

for _, tc := range hashTests {
globalConf.HashKeyFunction = tc.hashFunction
config.SetGlobal(globalConf)

t.Run(fmt.Sprintf("%sHash fn: %s", tc.desc, tc.hashFunction), func(t *testing.T) {
testHashKeyHandlerHelper(t, tc.expectedHashSize)
})
}
}

func testHashKeyHandlerHelper(t *testing.T, expectedHashSize int) {
ts := newTykTestServer()
defer ts.Close()

Expand All @@ -271,9 +294,13 @@ func TestHashKeyHandler(t *testing.T) {
}}
withAccessJSON, _ := json.Marshal(withAccess)

myKey := "my_key_id"
myKey := generateToken("", "")
myKeyHash := storage.HashKey(myKey)

if len(myKeyHash) != expectedHashSize {
t.Errorf("Expected hash size: %d, got %d. Hash: %s. Key: %s", expectedHashSize, len(myKeyHash), myKeyHash, myKey)
}

t.Run("Create, get and delete key with key hashing", func(t *testing.T) {
ts.Run(t, []test.TestCase{
// create key
Expand Down
18 changes: 15 additions & 3 deletions auth_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,11 +185,23 @@ func (b *DefaultSessionManager) Sessions(filter string) []string {

type DefaultKeyGenerator struct{}

func generateToken(orgID, keyID string) string {
keyID = strings.TrimPrefix(keyID, orgID)
token, err := storage.GenerateToken(orgID, keyID, config.Global().HashKeyFunction)

if err != nil {
log.WithFields(logrus.Fields{
"prefix": "auth-mgr",
"orgID": orgID,
}).WithError(err).Warning("Issue during token generation")
}

return token
}

// GenerateAuthKey is a utility function for generating new auth keys. Returns the storage key name and the actual key
func (DefaultKeyGenerator) GenerateAuthKey(orgID string) string {
u5 := uuid.NewV4()
cleanSting := strings.Replace(u5.String(), "-", "", -1)
return orgID + cleanSting
return generateToken(orgID, "")
}

// GenerateHMACSecret is a utility function for generating new auth keys. Returns the storage key name and the actual key
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,7 @@ type Config struct {
UseAsyncSessionWrite bool `json:"optimisations_use_async_session_write"`
AllowMasterKeys bool `json:"allow_master_keys"`
HashKeys bool `json:"hash_keys"`
HashKeyFunction string `json:"hash_key_function"`
SuppressRedisSignalReload bool `json:"suppress_redis_signal_reload"`
SupressDefaultOrgStore bool `json:"suppress_default_org_store"`
UseRedisLog bool `json:"use_redis_log"`
Expand Down
2 changes: 1 addition & 1 deletion coprocess_grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func newTestGRPCServer() (s *grpc.Server) {
func loadTestGRPCAPIs() {
buildAndLoadAPI(func(spec *APISpec) {
spec.APIID = "1"
spec.OrgID = "default"
spec.OrgID = mockOrgID
spec.Auth = apidef.Auth{
AuthHeaderName: "authorization",
}
Expand Down
2 changes: 1 addition & 1 deletion coprocess_id_extractor.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ func (e *BaseExtractor) Error(r *http.Request, err error, message string) (retur
func (e *BaseExtractor) GenerateSessionID(input string, mw BaseMiddleware) (sessionID string) {
data := []byte(input)
tokenID := fmt.Sprintf("%x", md5.Sum(data))
sessionID = mw.Spec.OrgID + tokenID
sessionID = generateToken(mw.Spec.OrgID, tokenID)
return sessionID
}

Expand Down
22 changes: 10 additions & 12 deletions coprocess_id_extractor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import (
)

const (
extractorTestOrgID = "testorg"

extractorValueInput = "testkey"

extractorRegexExpr = "prefix-(.*)"
Expand All @@ -41,7 +39,7 @@ func createSpecTestFrom(t testing.TB, def *apidef.APIDefinition) *APISpec {

func prepareExtractor(t testing.TB, extractorSource apidef.IdExtractorSource, extractorType apidef.IdExtractorType, config map[string]interface{}) (IdExtractor, *APISpec) {
def := &apidef.APIDefinition{
OrgID: extractorTestOrgID,
OrgID: mockOrgID,
CustomMiddleware: apidef.MiddlewareSection{
IdExtractor: apidef.MiddlewareIdExtractor{
ExtractFrom: extractorSource,
Expand Down Expand Up @@ -87,7 +85,7 @@ func prepareExtractorFormRequest(values map[string]string) *http.Request {
func generateSessionID(input string) string {
data := []byte(input)
tokenID := fmt.Sprintf("%x", md5.Sum(data))
return extractorTestOrgID + tokenID
return generateToken(mockOrgID, tokenID)
}

func TestValueExtractor(t *testing.T) {
Expand All @@ -112,7 +110,7 @@ func TestValueExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand All @@ -139,7 +137,7 @@ func TestValueExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down Expand Up @@ -172,7 +170,7 @@ func TestRegexExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down Expand Up @@ -200,7 +198,7 @@ func TestRegexExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down Expand Up @@ -229,7 +227,7 @@ func TestRegexExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down Expand Up @@ -261,7 +259,7 @@ func TestXPathExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand All @@ -288,7 +286,7 @@ func TestXPathExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down Expand Up @@ -316,7 +314,7 @@ func TestXPathExtractor(t *testing.T) {
if sessionID != testSessionID {
t.Fatalf("session ID doesn't match, expected %s, got %s", testSessionID, sessionID)
}
if !strings.HasPrefix(sessionID, spec.OrgID) {
if storage.TokenOrg(sessionID) != spec.OrgID {
t.Fatalf("session ID doesn't contain the org ID, got %s", sessionID)
}
if overrides.ResponseCode != 0 {
Expand Down
1 change: 1 addition & 0 deletions gateway_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ var (
)

const defaultListenPort = 8080
const mockOrgID = "507f1f77bcf86cd799439011"

var defaultTestConfig config.Config
var testServerRouter *mux.Router
Expand Down
7 changes: 4 additions & 3 deletions helpers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (

"github.com/TykTechnologies/tyk/apidef"
"github.com/TykTechnologies/tyk/config"
"github.com/TykTechnologies/tyk/storage"
"github.com/TykTechnologies/tyk/test"
"github.com/TykTechnologies/tyk/user"
)
Expand Down Expand Up @@ -189,16 +190,16 @@ func withAuth(r *http.Request) *http.Request {

// TODO: replace with /tyk/keys/create call
func createSession(sGen ...func(s *user.SessionState)) string {
key := keyGen.GenerateAuthKey("")
key := generateToken("", "")
session := createStandardSession()
if len(sGen) > 0 {
sGen[0](session)
}
if session.Certificate != "" {
key = session.Certificate
key = generateToken("", session.Certificate)
}

FallbackKeySesionManager.UpdateSession(key, session, 60, false)
FallbackKeySesionManager.UpdateSession(storage.HashKey(key), session, 60, config.Global().HashKeys)
return key
}

Expand Down
4 changes: 4 additions & 0 deletions lint/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,10 @@ const confSchema = `{
"hash_keys": {
"type": "boolean"
},
"hash_key_function": {
"type": "string",
"enum": ["", "murmur32", "murmur64", "murmur128", "sha256"]
},
"health_check": {
"type": ["object", "null"],
"additionalProperties": false,
Expand Down
1 change: 1 addition & 0 deletions middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ func (t BaseMiddleware) CheckSessionAndIdentityForValidKey(key string, r *http.R
if t.Spec.GlobalConfig.HashKeys {
cacheKey = storage.HashStr(key)
}

// Check in-memory cache
if !t.Spec.GlobalConfig.LocalSessionCache.DisableCacheSessionState {
cachedVal, found := SessionCache.Get(cacheKey)
Expand Down
3 changes: 2 additions & 1 deletion multiauth_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,9 @@ func testPrepareMultiSessionBA(t testing.TB, isBench bool) (*APISpec, *http.Requ
username = "0987876"
}
password := "TEST"
keyName := generateToken("default", username)
// Basic auth sessions are stored as {org-id}{username}, so we need to append it here when we create the session.
spec.SessionManager.UpdateSession("default"+username, baSession, 60, false)
spec.SessionManager.UpdateSession(keyName, baSession, 60, false)

// Create key
session := createMultiAuthKeyAuthSession(isBench)
Expand Down
2 changes: 1 addition & 1 deletion mw_api_rate_limit.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (k *RateLimitForAPI) ProcessRequest(w http.ResponseWriter, r *http.Request,
storeRef,
true,
false,
k.Spec.GlobalConfig,
&k.Spec.GlobalConfig,
)

if reason == sessionFailRateLimit {
Expand Down
2 changes: 1 addition & 1 deletion mw_auth_key.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ func (k *AuthKey) ProcessRequest(w http.ResponseWriter, r *http.Request, _ inter

// If key not provided in header or cookie and client certificate is provided, try to find certificate based key
if config.UseCertificate && key == "" && r.TLS != nil && len(r.TLS.PeerCertificates) > 0 {
key = k.Spec.OrgID + certs.HexSHA256(r.TLS.PeerCertificates[0].Raw)
key = generateToken(k.Spec.OrgID, certs.HexSHA256(r.TLS.PeerCertificates[0].Raw))
}

if key == "" {
Expand Down
Loading