github.com/Tyktechnologies/tyk@v2.9.5+incompatible/gateway/mw_auth_key_test.go (about)

     1  package gateway
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"net/http"
     7  	"net/http/httptest"
     8  	"net/url"
     9  	"sync"
    10  	"testing"
    11  	"time"
    12  
    13  	"github.com/justinas/alice"
    14  	"github.com/lonelycode/go-uuid/uuid"
    15  
    16  	"github.com/TykTechnologies/tyk/config"
    17  	"github.com/TykTechnologies/tyk/signature_validator"
    18  	"github.com/TykTechnologies/tyk/test"
    19  	"github.com/TykTechnologies/tyk/user"
    20  )
    21  
    22  func TestMurmur3CharBug(t *testing.T) {
    23  	defer ResetTestConfig()
    24  	ts := StartTest()
    25  	defer ts.Close()
    26  
    27  	api := BuildAPI(func(spec *APISpec) {
    28  		spec.UseKeylessAccess = false
    29  		spec.Proxy.ListenPath = "/"
    30  	})[0]
    31  
    32  	genTestCase := func(key string, status int) test.TestCase {
    33  		return test.TestCase{Path: "/", Headers: map[string]string{"Authorization": key}, Code: status}
    34  	}
    35  
    36  	t.Run("Without hashing", func(t *testing.T) {
    37  		globalConf := config.Global()
    38  		globalConf.HashKeys = false
    39  		config.SetGlobal(globalConf)
    40  
    41  		LoadAPI(api)
    42  
    43  		key := CreateSession()
    44  
    45  		ts.Run(t, []test.TestCase{
    46  			genTestCase("wrong", 403),
    47  			genTestCase(key+"abc", 403),
    48  			genTestCase(key, 200),
    49  		}...)
    50  	})
    51  
    52  	t.Run("murmur32 hashing, legacy", func(t *testing.T) {
    53  		globalConf := config.Global()
    54  		globalConf.HashKeys = true
    55  		globalConf.HashKeyFunction = ""
    56  		config.SetGlobal(globalConf)
    57  
    58  		LoadAPI(api)
    59  
    60  		key := CreateSession()
    61  
    62  		ts.Run(t, []test.TestCase{
    63  			genTestCase("wrong", 403),
    64  			genTestCase(key+"abc", 403),
    65  			genTestCase(key, 200),
    66  		}...)
    67  	})
    68  
    69  	t.Run("murmur32 hashing, json keys", func(t *testing.T) {
    70  		globalConf := config.Global()
    71  		globalConf.HashKeys = true
    72  		globalConf.HashKeyFunction = "murmur32"
    73  		config.SetGlobal(globalConf)
    74  
    75  		LoadAPI(api)
    76  
    77  		key := CreateSession()
    78  
    79  		ts.Run(t, []test.TestCase{
    80  			genTestCase("wrong", 403),
    81  			// Should reject instead, just to show bug
    82  			genTestCase(key+"abc", 200),
    83  			genTestCase(key, 200),
    84  		}...)
    85  	})
    86  
    87  	t.Run("murmur64 hashing", func(t *testing.T) {
    88  		globalConf := config.Global()
    89  		globalConf.HashKeys = true
    90  		globalConf.HashKeyFunction = "murmur64"
    91  		config.SetGlobal(globalConf)
    92  
    93  		LoadAPI(api)
    94  
    95  		key := CreateSession()
    96  
    97  		ts.Run(t, []test.TestCase{
    98  			genTestCase("wrong", 403),
    99  			// New hashing fixes the bug
   100  			genTestCase(key+"abc", 403),
   101  			genTestCase(key, 200),
   102  		}...)
   103  	})
   104  }
   105  
   106  func TestSignatureValidation(t *testing.T) {
   107  	defer ResetTestConfig()
   108  	ts := StartTest()
   109  	defer ts.Close()
   110  
   111  	api := BuildAPI(func(spec *APISpec) {
   112  		spec.UseKeylessAccess = false
   113  		spec.Proxy.ListenPath = "/"
   114  		spec.Auth.ValidateSignature = true
   115  		spec.Auth.Signature.Algorithm = "MasheryMD5"
   116  		spec.Auth.Signature.Header = "Signature"
   117  		spec.Auth.Signature.Secret = "foobar"
   118  		spec.Auth.Signature.AllowedClockSkew = 1
   119  	})[0]
   120  
   121  	t.Run("Static signature", func(t *testing.T) {
   122  		api.Auth.Signature.Secret = "foobar"
   123  		LoadAPI(api)
   124  
   125  		key := CreateSession()
   126  		hasher := signature_validator.MasheryMd5sum{}
   127  		validHash := hasher.Hash(key, "foobar", time.Now().Unix())
   128  
   129  		validSigHeader := map[string]string{
   130  			"authorization": key,
   131  			"signature":     hex.EncodeToString(validHash),
   132  		}
   133  
   134  		invalidSigHeader := map[string]string{
   135  			"authorization": key,
   136  			"signature":     "junk",
   137  		}
   138  
   139  		emptySigHeader := map[string]string{
   140  			"authorization": key,
   141  		}
   142  
   143  		ts.Run(t, []test.TestCase{
   144  			{Headers: emptySigHeader, Code: 401},
   145  			{Headers: invalidSigHeader, Code: 401},
   146  			{Headers: validSigHeader, Code: 200},
   147  		}...)
   148  	})
   149  
   150  	t.Run("Dynamic signature", func(t *testing.T) {
   151  		api.Auth.Signature.Secret = "$tyk_meta.signature_secret"
   152  		LoadAPI(api)
   153  
   154  		key := CreateSession(func(s *user.SessionState) {
   155  			s.MetaData = map[string]interface{}{
   156  				"signature_secret": "foobar",
   157  			}
   158  			s.Mutex = &sync.RWMutex{}
   159  		})
   160  
   161  		hasher := signature_validator.MasheryMd5sum{}
   162  		validHash := hasher.Hash(key, "foobar", time.Now().Unix())
   163  
   164  		validSigHeader := map[string]string{
   165  			"authorization": key,
   166  			"signature":     hex.EncodeToString(validHash),
   167  		}
   168  
   169  		invalidSigHeader := map[string]string{
   170  			"authorization": key,
   171  			"signature":     "junk",
   172  		}
   173  
   174  		ts.Run(t, []test.TestCase{
   175  			{Headers: invalidSigHeader, Code: 401},
   176  			{Headers: validSigHeader, Code: 200},
   177  		}...)
   178  	})
   179  }
   180  
   181  func createAuthKeyAuthSession(isBench bool) *user.SessionState {
   182  	session := new(user.SessionState)
   183  	// essentially non-throttled
   184  	session.Rate = 100.0
   185  	session.Allowance = session.Rate
   186  	session.LastCheck = time.Now().Unix()
   187  	session.Per = 1.0
   188  	session.QuotaRenewalRate = 300 // 5 minutes
   189  	session.QuotaRenews = time.Now().Unix()
   190  	if isBench {
   191  		session.QuotaRemaining = 100000000
   192  		session.QuotaMax = 100000000
   193  	} else {
   194  		session.QuotaRemaining = 10
   195  		session.QuotaMax = 10
   196  	}
   197  	session.AccessRights = map[string]user.AccessDefinition{"31": {APIName: "Tyk Auth Key Test", APIID: "31", Versions: []string{"default"}}}
   198  	session.Mutex = &sync.RWMutex{}
   199  	return session
   200  }
   201  
   202  func getAuthKeyChain(spec *APISpec) http.Handler {
   203  	remote, _ := url.Parse(spec.Proxy.TargetURL)
   204  	proxy := TykNewSingleHostReverseProxy(remote, spec, nil)
   205  	proxyHandler := ProxyHandler(proxy, spec)
   206  	baseMid := BaseMiddleware{Spec: spec, Proxy: proxy}
   207  	chain := alice.New(mwList(
   208  		&IPWhiteListMiddleware{baseMid},
   209  		&IPBlackListMiddleware{BaseMiddleware: baseMid},
   210  		&AuthKey{baseMid},
   211  		&VersionCheck{BaseMiddleware: baseMid},
   212  		&KeyExpired{baseMid},
   213  		&AccessRightsCheck{baseMid},
   214  		&RateLimitAndQuotaCheck{baseMid},
   215  	)...).Then(proxyHandler)
   216  	return chain
   217  }
   218  
   219  func testPrepareAuthKeySession(apiDef string, isBench bool) (string, *APISpec) {
   220  	spec := LoadSampleAPI(apiDef)
   221  
   222  	session := createAuthKeyAuthSession(isBench)
   223  	customToken := ""
   224  	if isBench {
   225  		customToken = uuid.New()
   226  	} else {
   227  		customToken = "54321111"
   228  	}
   229  	// AuthKey sessions are stored by {token}
   230  	spec.SessionManager.UpdateSession(customToken, session, 60, false)
   231  	return customToken, spec
   232  }
   233  
   234  func TestBearerTokenAuthKeySession(t *testing.T) {
   235  	customToken, spec := testPrepareAuthKeySession(authKeyDef, false)
   236  
   237  	recorder := httptest.NewRecorder()
   238  	req := TestReq(t, "GET", "/auth_key_test/", nil)
   239  
   240  	req.Header.Set("authorization", "Bearer "+customToken)
   241  
   242  	chain := getAuthKeyChain(spec)
   243  	chain.ServeHTTP(recorder, req)
   244  
   245  	if recorder.Code != 200 {
   246  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   247  		t.Error(recorder.Body.String())
   248  	}
   249  }
   250  
   251  func BenchmarkBearerTokenAuthKeySession(b *testing.B) {
   252  	b.ReportAllocs()
   253  
   254  	customToken, spec := testPrepareAuthKeySession(authKeyDef, true)
   255  
   256  	recorder := httptest.NewRecorder()
   257  	req := TestReq(b, "GET", "/auth_key_test/", nil)
   258  
   259  	req.Header.Set("authorization", "Bearer "+customToken)
   260  
   261  	chain := getAuthKeyChain(spec)
   262  
   263  	for i := 0; i < b.N; i++ {
   264  		chain.ServeHTTP(recorder, req)
   265  		if recorder.Code != 200 {
   266  			b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   267  			b.Error(recorder.Body.String())
   268  		}
   269  	}
   270  }
   271  
   272  const authKeyDef = `{
   273  	"api_id": "31",
   274  	"org_id": "default",
   275  	"auth": {"auth_header_name": "authorization"},
   276  	"version_data": {
   277  		"not_versioned": true,
   278  		"versions": {
   279  			"v1": {"name": "v1"}
   280  		}
   281  	},
   282  	"proxy": {
   283  		"listen_path": "/auth_key_test/",
   284  		"target_url": "` + TestHttpAny + `"
   285  	}
   286  }`
   287  
   288  func TestMultiAuthBackwardsCompatibleSession(t *testing.T) {
   289  	customToken, spec := testPrepareAuthKeySession(multiAuthBackwardsCompatible, false)
   290  
   291  	recorder := httptest.NewRecorder()
   292  	req := TestReq(t, "GET", fmt.Sprintf("/auth_key_test/?token=%s", customToken), nil)
   293  
   294  	chain := getAuthKeyChain(spec)
   295  	chain.ServeHTTP(recorder, req)
   296  
   297  	if recorder.Code != 200 {
   298  		t.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   299  		t.Error(recorder.Body.String())
   300  	}
   301  }
   302  
   303  func BenchmarkMultiAuthBackwardsCompatibleSession(b *testing.B) {
   304  	b.ReportAllocs()
   305  
   306  	customToken, spec := testPrepareAuthKeySession(multiAuthBackwardsCompatible, true)
   307  
   308  	recorder := httptest.NewRecorder()
   309  	req := TestReq(b, "GET", fmt.Sprintf("/auth_key_test/?token=%s", customToken), nil)
   310  
   311  	chain := getAuthKeyChain(spec)
   312  
   313  	for i := 0; i < b.N; i++ {
   314  		chain.ServeHTTP(recorder, req)
   315  		if recorder.Code != 200 {
   316  			b.Error("Initial request failed with non-200 code, should have gone through!: \n", recorder.Code)
   317  			b.Error(recorder.Body.String())
   318  		}
   319  	}
   320  }
   321  
   322  const multiAuthBackwardsCompatible = `{
   323  	"api_id": "31",
   324  	"org_id": "default",
   325  	"auth": {
   326  		"auth_header_name": "token",
   327  		"use_param": true
   328  	},
   329  	"version_data": {
   330  		"not_versioned": true,
   331  		"versions": {
   332  			"v1": {"name": "v1"}
   333  		}
   334  	},
   335  	"proxy": {
   336  		"listen_path": "/auth_key_test/",
   337  		"target_url": "` + TestHttpAny + `"
   338  	}
   339  }`
   340  
   341  func TestMultiAuthSession(t *testing.T) {
   342  	spec := LoadSampleAPI(multiAuthDef)
   343  	session := createAuthKeyAuthSession(false)
   344  	customToken := "54321111"
   345  	// AuthKey sessions are stored by {token}
   346  	spec.SessionManager.UpdateSession(customToken, session, 60, false)
   347  
   348  	// Set the url param
   349  	recorder := httptest.NewRecorder()
   350  	req := TestReq(t, "GET", fmt.Sprintf("/auth_key_test/?token=%s", customToken), nil)
   351  
   352  	chain := getAuthKeyChain(spec)
   353  	chain.ServeHTTP(recorder, req)
   354  
   355  	if recorder.Code != 200 {
   356  		t.Error("First request failed with non-200 code, should have gone through!: \n", recorder.Code)
   357  		t.Error(recorder.Body.String())
   358  	}
   359  
   360  	// Set the header
   361  	recorder = httptest.NewRecorder()
   362  	req = TestReq(t, "GET", "/auth_key_test/?token=", nil)
   363  	req.Header.Set("authorization", customToken)
   364  
   365  	chain.ServeHTTP(recorder, req)
   366  
   367  	if recorder.Code != 200 {
   368  		t.Error("Second request failed with non-200 code, should have gone through!: \n", recorder.Code)
   369  		t.Error(recorder.Body.String())
   370  	}
   371  
   372  	// Set the cookie
   373  	recorder = httptest.NewRecorder()
   374  	req = TestReq(t, "GET", "/auth_key_test/?token=", nil)
   375  	req.AddCookie(&http.Cookie{Name: "oreo", Value: customToken})
   376  
   377  	chain.ServeHTTP(recorder, req)
   378  
   379  	if recorder.Code != 200 {
   380  		t.Error("Third request failed with non-200 code, should have gone through!: \n", recorder.Code)
   381  		t.Error(recorder.Body.String())
   382  	}
   383  
   384  	// No header, param or cookie
   385  	recorder = httptest.NewRecorder()
   386  	req = TestReq(t, "GET", "/auth_key_test/", nil)
   387  
   388  	chain.ServeHTTP(recorder, req)
   389  
   390  	if recorder.Code == 200 {
   391  		t.Error("Request returned 200 code, should NOT have gone through!: \n", recorder.Code)
   392  		t.Error(recorder.Body.String())
   393  	}
   394  }
   395  
   396  const multiAuthDef = `{
   397  	"api_id": "31",
   398  	"org_id": "default",
   399  	"auth": {
   400  		"auth_header_name": "authorization",
   401  		"param_name": "token",
   402  		"cookie_name": "oreo"
   403  	},
   404  	"version_data": {
   405  		"not_versioned": true,
   406  		"versions": {
   407  			"v1": {"name": "v1"}
   408  		}
   409  	},
   410  	"proxy": {
   411  		"listen_path": "/auth_key_test/",
   412  		"target_url": "` + TestHttpAny + `"
   413  	}
   414  }`
   415  
   416  func TestStripBearer(t *testing.T) {
   417  	var bearerTests = []struct {
   418  		in  string
   419  		out string
   420  	}{
   421  		{"Bearer abc", "abc"},
   422  		{"bearer abc", "abc"},
   423  		{"bEaReR abc", "abc"},
   424  		{"Bearer: abc", "Bearer: abc"}, // invalid
   425  		{"Basic abc", "Basic abc"},
   426  		{"abc", "abc"},
   427  	}
   428  
   429  	for _, tt := range bearerTests {
   430  		t.Run(tt.in, func(t *testing.T) {
   431  			out := stripBearer(tt.in)
   432  			if out != tt.out {
   433  				t.Errorf("got %q, want %q", out, tt.out)
   434  			}
   435  		})
   436  	}
   437  }
   438  
   439  func BenchmarkStripBearer(b *testing.B) {
   440  	b.ReportAllocs()
   441  
   442  	for i := 0; i < b.N; i++ {
   443  		_ = stripBearer("Bearer abcdefghijklmnopqrstuvwxyz12345678910")
   444  	}
   445  }