Skip to content

Commit

Permalink
Merging to release-5-lts: TT-12347 fix management of custom keys in m…
Browse files Browse the repository at this point in the history
…dcb installations (#6353)

TT-12347 fix management of custom keys in mdcb installations (#6353)

### **User description**
<!-- Provide a general summary of your changes in the Title above -->

## Description

<!-- Describe your changes in detail -->

## Related Issue

https://tyktech.atlassian.net/browse/TT-12347

## Motivation and Context

<!-- Why is this change required? What problem does it solve? -->

## How This Has Been Tested

<!-- Please describe in detail how you tested your changes -->
<!-- Include details of your testing environment, and the tests -->
<!-- you ran to see how your change affects other areas of the code,
etc. -->
<!-- This information is helpful for reviewers and QA. -->

## Screenshots (if appropriate)

## Types of changes

<!-- What types of changes does your code introduce? Put an `x` in all
the boxes that apply: -->

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing
functionality to change)
- [ ] Refactoring or add test (improvements in base code or adds test
coverage to functionality)

## Checklist

<!-- Go over all the following points, and put an `x` in all the boxes
that apply -->
<!-- If there are no documentation updates required, mark the item as
checked. -->
<!-- Raise up any additional concerns not covered by the checklist. -->

- [ ] I ensured that the documentation is up to date
- [ ] I explained why this PR updates go.mod in detail with reasoning
why it's required
- [ ] I would like a code coverage CI quality gate exception and have
explained why


___

### **PR Type**
Enhancement


___

### **Description**
- Changed the order of key search logic in the `SessionDetail` method of
`DefaultSessionManager`.
- Moved `keyName` to be appended after generating token with `orgID`.


___



### **Changes walkthrough** 📝
<table><thead><tr><th></th><th align="left">Relevant
files</th></tr></thead><tbody><tr><td><strong>Enhancement
</strong></td><td><table>
<tr>
  <td>
    <details>
<summary><strong>auth_manager.go</strong><dd><code>Modify key search
order in `SessionDetail` method</code>&nbsp; &nbsp; &nbsp; &nbsp; &nbsp;
&nbsp; &nbsp; &nbsp; </dd></summary>
<hr>

gateway/auth_manager.go
<li>Changed the order of key search logic in <code>SessionDetail</code>
method.<br> <li> Moved <code>keyName</code> to be appended after
generating token with <code>orgID</code>.<br>


</details>
    

  </td>
<td><a
href="https://github.com/TykTechnologies/tyk/pull/6353/files#diff-311e18b071d244ed1615c0019215b633278679df373288b3451bcc5cf7c52c4e">+3/-1</a>&nbsp;
&nbsp; &nbsp; </td>
</tr>                    
</table></td></tr></tr></tbody></table>

___

> 💡 **PR-Agent usage**:
>Comment `/help` on the PR to get a list of all available PR-Agent tools
and their descriptions
  • Loading branch information
buger authored Jun 19, 2024
1 parent 27bd9b5 commit 26167e8
Show file tree
Hide file tree
Showing 4 changed files with 182 additions and 3 deletions.
4 changes: 2 additions & 2 deletions gateway/auth_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,12 @@ func (b *DefaultSessionManager) SessionDetail(orgID string, keyName string, hash
} else {
if storage.TokenOrg(keyName) != orgID {
// try to get legacy and new format key at once
toSearchList := []string{keyName}
toSearchList := []string{}
if !b.Gw.GetConfig().DisableKeyActionsByUsername {
toSearchList = append(toSearchList, b.Gw.generateToken(orgID, keyName))
}

toSearchList = append(toSearchList, keyName)
for _, fallback := range b.Gw.GetConfig().HashKeyFunctionFallback {
if !b.Gw.GetConfig().DisableKeyActionsByUsername {
toSearchList = append(toSearchList, b.Gw.generateToken(orgID, keyName, fallback))
Expand All @@ -164,7 +165,6 @@ func (b *DefaultSessionManager) SessionDetail(orgID string, keyName string, hash
var jsonKeyValList []string

jsonKeyValList, err = b.store.GetMultiKey(toSearchList)

// pick the 1st non empty from the returned list
for idx, val := range jsonKeyValList {
if val != "" {
Expand Down
98 changes: 98 additions & 0 deletions gateway/auth_manager_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@ package gateway

import (
"crypto/x509"
"encoding/json"
"fmt"
"io"
"net/http"
"testing"

Expand Down Expand Up @@ -283,3 +286,98 @@ func TestResetQuotaObfuscate(t *testing.T) {
assert.Equal(t, "481408ygjkbs", actual)
})
}

// TestCustomKeysEdgeGw check that custom keys are processed
// by edge gw when a keySpace signal is received
func TestCustomKeysEdgeGw(t *testing.T) {

const customKey = "my-custom-key"
orgId := "default"

testCases := []struct {
name string
useCustomKey bool
}{
{
name: "sending event with custom key",
useCustomKey: true,
},
{
name: "sending event with base64 representation",
useCustomKey: false,
},
}

hashKeys := []bool{true, false}
for _, hashed := range hashKeys {
t.Run(fmt.Sprintf("HashKeys: %v", hashed), func(t *testing.T) {
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
// 1- execute an edge gw
ts := StartTest(func(globalConf *config.Config) {
globalConf.SlaveOptions.GroupID = "group"
globalConf.LivenessCheck.CheckDuration = 1000000000
globalConf.SlaveOptions.APIKey = "apikey-test"
globalConf.HashKeys = hashed
globalConf.SlaveOptions.SynchroniserEnabled = hashed
})
defer ts.Close()

rpcListener := RPCStorageHandler{
KeyPrefix: "rpc.listener.",
SuppressRegister: true,
HashKeys: hashed,
Gw: ts.Gw,
}

key := customKey
if !tc.useCustomKey {
key = ts.Gw.generateToken(orgId, key)
}

// 2- creates a custom key
session := CreateStandardSession()
session.AccessRights = map[string]user.AccessDefinition{"test": {
APIID: "test", Versions: []string{"v1"},
}}
client := GetTLSClient(nil, nil)

resp, err := ts.Run(t, test.TestCase{AdminAuth: true, Method: http.MethodPost, Path: "/tyk/keys/" + customKey,
Data: session, Client: client, Code: http.StatusOK})
assert.Nil(t, err)
defer func() {
err = resp.Body.Close()
assert.Nil(t, err)
}()

body, err := io.ReadAll(resp.Body)
assert.Nil(t, err)
keyResp := apiModifyKeySuccess{}
err = json.Unmarshal(body, &keyResp)
assert.NoError(t, err)

if hashed {
key = keyResp.KeyHash
}

// 3- double check that key exists
_, found := ts.Gw.GlobalSessionManager.SessionDetail(orgId, key, hashed)
assert.True(t, found)

keyEvent := key
if hashed {
keyEvent += ":hashed"
}
// 4- emit events so edge process it
rpcListener.ProcessKeySpaceChanges([]string{keyEvent}, orgId)

// 5- key should not exist in edge as it was removed
_, found = ts.Gw.GlobalSessionManager.SessionDetail(orgId, key, hashed)
assert.False(t, found)
})
}
})

}

}
19 changes: 18 additions & 1 deletion gateway/rpc_storage_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -1049,17 +1049,23 @@ func (r *RPCStorageHandler) ProcessKeySpaceChanges(keys []string, orgId string)

isHashed := len(splitKeys) > 1 && splitKeys[1] == "hashed"
var status int
var err error
if isHashed {
log.Info("--> removing cached (hashed) key: ", splitKeys[0])
key = splitKeys[0]
_, status = r.Gw.handleDeleteHashedKey(key, orgId, "", resetQuota)
} else {
log.Info("--> removing cached key: ", r.Gw.obfuscateKey(key))
// in case it's an username (basic auth) or custom-key then generate the token
// in case it's a username (basic auth) or custom-key then generate the token
if storage.TokenOrg(key) == "" {
key = r.Gw.generateToken(orgId, key)
}
_, status = r.Gw.handleDeleteKey(key, orgId, "-1", resetQuota)
// check if we must remove the key by custom key id
status, err = r.deleteUsingTokenID(key, orgId, resetQuota, status)
if err != nil {
log.Debugf("cannot remove key:%v status: %v", key, status)
}
}

// if key not found locally and synchroniser disabled then we should not pull it from management layer
Expand Down Expand Up @@ -1089,6 +1095,17 @@ func (r *RPCStorageHandler) ProcessKeySpaceChanges(keys []string, orgId string)
r.Gw.MainNotifier.Notify(n)
}

// Function to handle fallback deletion using token ID
func (r *RPCStorageHandler) deleteUsingTokenID(key, orgId string, resetQuota bool, status int) (int, error) {
if status == http.StatusNotFound {
id, err := storage.TokenID(key)
if err == nil {
_, status = r.Gw.handleDeleteKey(id, orgId, "-1", resetQuota)
}
}
return status, nil
}

func (r *RPCStorageHandler) DeleteScanMatch(pattern string) bool {
log.Error("RPCStorageHandler.DeleteScanMatch - Not implemented")
return false
Expand Down
64 changes: 64 additions & 0 deletions gateway/rpc_storage_handler_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package gateway

import (
"encoding/json"
"fmt"
"io"
"net/http"
"testing"

Expand Down Expand Up @@ -415,3 +417,65 @@ func TestGetRawKey(t *testing.T) {
assert.Equal(t, storage.ErrMDCBConnectionLost, err)
})
}

func TestDeleteUsingTokenID(t *testing.T) {
ts := StartTest(func(globalConf *config.Config) {
globalConf.SlaveOptions.EnableRPCCache = true
})
defer ts.Close()

rpcListener := RPCStorageHandler{
KeyPrefix: "rpc.listener.",
SuppressRegister: true,
Gw: ts.Gw,
}

t.Run("key could not be removed by base64 key ID but exist by custom key ID", func(t *testing.T) {
// create a custom key
const customKey = "my-custom-key"
orgId := "default"
session := CreateStandardSession()
session.AccessRights = map[string]user.AccessDefinition{"test": {
APIID: "test", Versions: []string{"v1"},
}}
client := GetTLSClient(nil, nil)

// creates a key and rename it
resp, err := ts.Run(t, test.TestCase{AdminAuth: true, Method: http.MethodPost, Path: "/tyk/keys/" + customKey,
Data: session, Client: client, Code: http.StatusOK})
assert.Nil(t, err)
defer func() {
err = resp.Body.Close()
assert.Nil(t, err)
}()
body, err := io.ReadAll(resp.Body)
assert.Nil(t, err)
keyResp := apiModifyKeySuccess{}

err = json.Unmarshal(body, &keyResp)
assert.NoError(t, err)

// trick to change key name from base64 to custom (emulates keys as apikey-mycustomkey)
val, err := ts.Gw.GlobalSessionManager.Store().GetRawKey("apikey-" + keyResp.Key)
assert.Nil(t, err)
err = ts.Gw.GlobalSessionManager.Store().SetKey(customKey, val, -1)
assert.Nil(t, err)

removed := ts.Gw.GlobalSessionManager.Store().DeleteRawKey("apikey-" + keyResp.Key)
assert.True(t, removed)

status, err := rpcListener.deleteUsingTokenID(keyResp.Key, orgId, false, 404)
assert.Nil(t, err)
// it was found
assert.Equal(t, http.StatusOK, status)
// it should not exist anymore
_, err = ts.Gw.GlobalSessionManager.Store().GetKey(customKey)
assert.ErrorIs(t, storage.ErrKeyNotFound, err)
})

t.Run("status not found and TokenID do not exist", func(t *testing.T) {
status, err := rpcListener.deleteUsingTokenID("custom-key", "orgID", false, 404)
assert.Nil(t, err)
assert.Equal(t, 404, status)
})
}

0 comments on commit 26167e8

Please sign in to comment.