Skip to content

Commit 41b9fdf

Browse files
probelabs[bot]andrei-tykpvormste
authored
Merging to release-5.11.0: [TT-16296] fixed keys being set automatically as active (#7642) (#7649)
[TT-16296] fixed keys being set automatically as active (#7642) ## Description Fixes a regression introduced in PR #7431 where keys without policies would not respect the is_inactive flag set via API. The issue was that Apply() unconditionally reset session.IsInactive to false based solely on policy states, ignoring the session's own inactive state when no policies were applied. This caused keys without policies to remain active even after being explicitly deactivated via the API. The fix preserves the session's IsInactive value when no policies are applied, while still allowing policies to control the inactive state when present. ## Related Issue TT-16296 ## How This Has Been Tested • Added unit test InactiveNoPolicies in internal/policy/apply_test.go • Added integration tests TestKeyInactiveWithoutPolicy and TestKeyInactiveWithoutPolicyWithCache in gateway/mw_key_expired_check_test.go that follow the exact reproduction steps from the ticket ## 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 <!---TykTechnologies/jira-linter starts here--> ### Ticket Details <details> <summary> <a href="https://tyktech.atlassian.net/browse/TT-16296" title="TT-16296" target="_blank">TT-16296</a> </summary> | | | |---------|----| | Status | In Dev | | Summary | Key: Inactive keys are still active | Generated at: 2025-12-15 17:48:43 </details> <!---TykTechnologies/jira-linter ends here--> Co-authored-by: Patric Vormstein <pvormstein@googlemail.com> [TT-16296]: https://tyktech.atlassian.net/browse/TT-16296?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ Co-authored-by: andrei-tyk <97896463+andrei-tyk@users.noreply.github.com> Co-authored-by: Patric Vormstein <pvormstein@googlemail.com>
1 parent 8c09022 commit 41b9fdf

File tree

3 files changed

+149
-1
lines changed

3 files changed

+149
-1
lines changed
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package gateway
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/TykTechnologies/tyk/config"
8+
"github.com/TykTechnologies/tyk/storage"
9+
"github.com/TykTechnologies/tyk/test"
10+
"github.com/TykTechnologies/tyk/user"
11+
)
12+
13+
// TestKeyInactiveWithoutPolicy tests the scenario where a key without policy
14+
// is set to inactive via API and should be rejected by the gateway.
15+
// This is a regression test for TT-16296.
16+
func TestKeyInactiveWithoutPolicy(t *testing.T) {
17+
// Disable session cache to ensure we always read fresh data from storage
18+
conf := func(globalConf *config.Config) {
19+
globalConf.LocalSessionCache.DisableCacheSessionState = true
20+
}
21+
22+
ts := StartTest(conf)
23+
defer ts.Close()
24+
25+
// Create API that requires authentication
26+
api := BuildAPI(func(spec *APISpec) {
27+
spec.UseKeylessAccess = false
28+
spec.Proxy.ListenPath = "/"
29+
})[0]
30+
31+
ts.Gw.LoadAPI(api)
32+
33+
// Step 1: Create API KEY without POLICY
34+
key := CreateSession(ts.Gw, func(s *user.SessionState) {
35+
// Ensure no policies are applied
36+
s.ApplyPolicies = nil
37+
})
38+
39+
authHeader := map[string]string{"Authorization": key}
40+
41+
// Step 2: Send traffic - should succeed
42+
ts.Run(t, test.TestCase{
43+
Path: "/",
44+
Headers: authHeader,
45+
Code: http.StatusOK,
46+
})
47+
48+
// Step 3: Update key to set is_inactive: true
49+
hashKeys := ts.Gw.GetConfig().HashKeys
50+
hashedKey := storage.HashKey(key, hashKeys)
51+
52+
// Get current session and set it to inactive
53+
session, _ := ts.Gw.GlobalSessionManager.SessionDetail("default", hashedKey, true)
54+
session.IsInactive = true
55+
56+
err := ts.Gw.GlobalSessionManager.UpdateSession(hashedKey, &session, 60, true)
57+
if err != nil {
58+
t.Fatalf("Failed to update session: %v", err)
59+
}
60+
61+
// Step 4: Send traffic - should be rejected with 403 Forbidden
62+
ts.Run(t, test.TestCase{
63+
Path: "/",
64+
Headers: authHeader,
65+
Code: http.StatusForbidden,
66+
BodyMatch: "Key is inactive",
67+
})
68+
}
69+
70+
// TestKeyInactiveWithoutPolicyWithCache tests the same scenario but with
71+
// session cache enabled to ensure cache invalidation works correctly.
72+
func TestKeyInactiveWithoutPolicyWithCache(t *testing.T) {
73+
// Enable session cache (default behavior)
74+
conf := func(globalConf *config.Config) {
75+
globalConf.LocalSessionCache.DisableCacheSessionState = false
76+
}
77+
78+
ts := StartTest(conf)
79+
defer ts.Close()
80+
81+
// Create API that requires authentication
82+
api := BuildAPI(func(spec *APISpec) {
83+
spec.UseKeylessAccess = false
84+
spec.Proxy.ListenPath = "/"
85+
})[0]
86+
87+
ts.Gw.LoadAPI(api)
88+
89+
// Step 1: Create API KEY without POLICY
90+
key := CreateSession(ts.Gw, func(s *user.SessionState) {
91+
// Ensure no policies are applied
92+
s.ApplyPolicies = nil
93+
})
94+
95+
authHeader := map[string]string{"Authorization": key}
96+
97+
// Step 2: Send traffic - should succeed (this also caches the session)
98+
ts.Run(t, test.TestCase{
99+
Path: "/",
100+
Headers: authHeader,
101+
Code: http.StatusOK,
102+
})
103+
104+
// Step 3: Update key to set is_inactive: true
105+
hashKeys := ts.Gw.GetConfig().HashKeys
106+
hashedKey := storage.HashKey(key, hashKeys)
107+
108+
// Get current session and set it to inactive
109+
session, _ := ts.Gw.GlobalSessionManager.SessionDetail("default", hashedKey, true)
110+
session.IsInactive = true
111+
112+
err := ts.Gw.GlobalSessionManager.UpdateSession(hashedKey, &session, 60, true)
113+
if err != nil {
114+
t.Fatalf("Failed to update session: %v", err)
115+
}
116+
117+
// Flush the session cache to simulate cache invalidation that happens
118+
// when keys are updated via the API
119+
cacheKey := key
120+
if hashKeys {
121+
cacheKey = storage.HashStr(key, storage.HashMurmur64)
122+
}
123+
ts.Gw.SessionCache.Delete(cacheKey)
124+
125+
// Step 4: Send traffic - should be rejected with 403 Forbidden
126+
ts.Run(t, test.TestCase{
127+
Path: "/",
128+
Headers: authHeader,
129+
Code: http.StatusForbidden,
130+
BodyMatch: "Key is inactive",
131+
})
132+
}

internal/policy/apply.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,12 @@ func (t *Service) Apply(session *user.SessionState) error {
111111
}
112112

113113
// Only the status of policies applied to a key should determine the validity of the key.
114-
sessionInactiveState := false
114+
// If no policies are applied, preserve the session's own IsInactive state.
115+
sessionInactiveState := session.IsInactive
116+
hasPolicies := len(policyIDs) > 0
117+
if hasPolicies {
118+
sessionInactiveState = false
119+
}
115120

116121
for _, polID := range policyIDs {
117122
policy, ok := storage.PolicyByID(polID)

internal/policy/apply_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -504,6 +504,17 @@ func testPrepareApplyPolicies(tb testing.TB) (*policy.Service, []testApplyPolici
504504
tests = append(tests, aclPartitionTCs...)
505505

506506
inactiveTCs := []testApplyPoliciesData{
507+
{
508+
"InactiveNoPolicies", []string{},
509+
"", func(t *testing.T, s *user.SessionState) {
510+
t.Helper()
511+
if !s.IsInactive {
512+
t.Fatalf("key without policies should preserve IsInactive=true from session")
513+
}
514+
}, &user.SessionState{
515+
IsInactive: true,
516+
}, false,
517+
},
507518
{
508519
"InactiveMergeOne", []string{"tags1", "inactive1"},
509520
"", func(t *testing.T, s *user.SessionState) {

0 commit comments

Comments
 (0)