feat: add namespace blocking functionality for JWT authentication (#313)
## Summary Implements a denylist mechanism to prevent blocked namespaces from publishing packages: - Added `BlockedNamespaces` field to `JWTManager` struct for maintaining the denylist - Added validation logic in `GenerateTokenResponse` that checks user permissions against blocked namespaces - Admins with global permissions (`*`) bypass the blocking logic - Added comprehensive test coverage for all blocking scenarios Fixes #98
This commit is contained in:
parent
6431f23107
commit
9d04671ee3
1 changed files with 142 additions and 0 deletions
|
|
@ -283,3 +283,118 @@ func TestNewJWTManager_InvalidKeySize(t *testing.T) {
|
|||
auth.NewJWTManager(cfg)
|
||||
})
|
||||
}
|
||||
|
||||
func TestJWTManager_BlockedNamespaces(t *testing.T) {
|
||||
// Generate a proper Ed25519 seed for testing
|
||||
testSeed := make([]byte, ed25519.SeedSize)
|
||||
_, err := rand.Read(testSeed)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg := &config.Config{
|
||||
JWTPrivateKey: hex.EncodeToString(testSeed),
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
t.Run("blocked namespace should deny token", func(t *testing.T) {
|
||||
// Temporarily override blocked namespaces for testing
|
||||
originalBlocked := auth.BlockedNamespaces
|
||||
auth.BlockedNamespaces = []string{"io.github.spammer"}
|
||||
defer func() { auth.BlockedNamespaces = originalBlocked }()
|
||||
|
||||
jwtManager := auth.NewJWTManager(cfg)
|
||||
|
||||
claims := auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethodSubject: "spammer",
|
||||
Permissions: []auth.Permission{
|
||||
{
|
||||
Action: auth.PermissionActionPublish,
|
||||
ResourcePattern: "io.github.spammer/*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tokenResponse, err := jwtManager.GenerateTokenResponse(ctx, claims)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "your namespace is blocked")
|
||||
assert.Nil(t, tokenResponse)
|
||||
})
|
||||
|
||||
t.Run("non-blocked namespace should allow token", func(t *testing.T) {
|
||||
// Temporarily override blocked namespaces for testing
|
||||
originalBlocked := auth.BlockedNamespaces
|
||||
auth.BlockedNamespaces = []string{"io.github.spammer"}
|
||||
defer func() { auth.BlockedNamespaces = originalBlocked }()
|
||||
|
||||
jwtManager := auth.NewJWTManager(cfg)
|
||||
|
||||
claims := auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethodSubject: "gooduser",
|
||||
Permissions: []auth.Permission{
|
||||
{
|
||||
Action: auth.PermissionActionPublish,
|
||||
ResourcePattern: "io.github.gooduser/*",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tokenResponse, err := jwtManager.GenerateTokenResponse(ctx, claims)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, tokenResponse.RegistryToken)
|
||||
})
|
||||
|
||||
t.Run("multiple permissions with one blocked should deny token", func(t *testing.T) {
|
||||
// Temporarily override blocked namespaces for testing
|
||||
originalBlocked := auth.BlockedNamespaces
|
||||
auth.BlockedNamespaces = []string{"io.github.badorg"}
|
||||
defer func() { auth.BlockedNamespaces = originalBlocked }()
|
||||
|
||||
jwtManager := auth.NewJWTManager(cfg)
|
||||
|
||||
claims := auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodGitHubAT,
|
||||
AuthMethodSubject: "user",
|
||||
Permissions: []auth.Permission{
|
||||
{
|
||||
Action: auth.PermissionActionPublish,
|
||||
ResourcePattern: "io.github.user/*", // allowed
|
||||
},
|
||||
{
|
||||
Action: auth.PermissionActionPublish,
|
||||
ResourcePattern: "io.github.badorg/*", // blocked
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tokenResponse, err := jwtManager.GenerateTokenResponse(ctx, claims)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "your namespace is blocked")
|
||||
assert.Nil(t, tokenResponse)
|
||||
})
|
||||
|
||||
t.Run("global admin permissions should bypass denylist", func(t *testing.T) {
|
||||
// Temporarily override blocked namespaces for testing
|
||||
originalBlocked := auth.BlockedNamespaces
|
||||
auth.BlockedNamespaces = []string{"io.github.spammer"}
|
||||
defer func() { auth.BlockedNamespaces = originalBlocked }()
|
||||
|
||||
jwtManager := auth.NewJWTManager(cfg)
|
||||
|
||||
claims := auth.JWTClaims{
|
||||
AuthMethod: model.AuthMethodNone,
|
||||
AuthMethodSubject: "admin",
|
||||
Permissions: []auth.Permission{
|
||||
{
|
||||
Action: auth.PermissionActionPublish,
|
||||
ResourcePattern: "*", // global permission should bypass blocking
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tokenResponse, err := jwtManager.GenerateTokenResponse(ctx, claims)
|
||||
require.NoError(t, err)
|
||||
assert.NotEmpty(t, tokenResponse.RegistryToken)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue