Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,24 @@ func TestIntegration(t *testing.T) {
method: "GET",
wantCode: codes.OK,
},
{
name: "admin cannot access operator endpoints",
path: "/api/agent/listener/status",
method: "GET",
wantCode: codes.PermissionDenied,
},
{
name: "admin cannot access logger endpoints",
path: "/api/logger/workflow/test-workflow/osmo_ctrl/logs",
method: "GET",
wantCode: codes.PermissionDenied,
},
{
name: "admin cannot access router backend endpoints",
path: "/api/router/exec/test-workflow/backend/connect",
method: "WEBSOCKET",
wantCode: codes.PermissionDenied,
},
}

for _, tt := range tests {
Expand Down
38 changes: 21 additions & 17 deletions src/service/authz_sidecar/server/authz_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -516,23 +516,21 @@ func TestDefaultRoleAccess(t *testing.T) {
}

func TestAdminRoleAccess(t *testing.T) {
// Legacy admin role format - will be converted to semantic
// Note: deny patterns are ignored during conversion, so admin gets full access
adminRole := &roles.Role{
Name: "osmo-admin",
Policies: []roles.RolePolicy{
{
Actions: []roles.RoleAction{
{Base: "http", Path: "*", Method: "*"},
// These deny patterns are ignored during conversion
{Base: "http", Path: "!/api/agent/*", Method: "*"},
{Base: "http", Path: "!/api/logger/*", Method: "*"},
},
Effect: roles.EffectAllow,
Actions: roles.RoleActions{{Action: "*:*"}},
Resources: []string{"*"},
},
{
Effect: roles.EffectDeny,
Actions: roles.RoleActions{{Action: "internal:*"}},
Resources: []string{"*"},
},
},
}
// Convert to semantic - deny patterns are ignored
adminRole = roles.ConvertRoleToSemantic(adminRole)

tests := []struct {
name string
Expand All @@ -548,26 +546,32 @@ func TestAdminRoleAccess(t *testing.T) {
},
{
name: "workflow POST accessible",
path: "/api/workflow",
path: "/api/pool/default/workflow",
method: "POST",
wantAccess: true,
},
{
name: "agent endpoint accessible (deny ignored in conversion)",
name: "agent endpoint denied",
path: "/api/agent/listener/status",
method: "GET",
wantAccess: true, // Deny patterns are ignored during conversion
wantAccess: false,
},
{
name: "logger endpoint accessible (deny ignored in conversion)",
path: "/api/logger/workflow/logs",
name: "logger endpoint denied",
path: "/api/logger/workflow/test-workflow/osmo_ctrl/logs",
method: "GET",
wantAccess: true, // Deny patterns are ignored during conversion
wantAccess: false,
},
{
name: "router backend endpoint denied",
path: "/api/router/exec/test-workflow/backend/connect",
method: "WEBSOCKET",
wantAccess: false,
},
{
name: "router client endpoint accessible",
path: "/api/router/exec/abc/client/connect",
method: "GET",
method: "WEBSOCKET",
wantAccess: true,
},
}
Expand Down
7 changes: 4 additions & 3 deletions src/service/authz_sidecar/server/testdata/seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,13 @@ INSERT INTO roles (name, description, policies, immutable) VALUES (
TRUE
);

-- Admin role: full access
-- Admin role: full access except internal endpoints
INSERT INTO roles (name, description, policies, immutable) VALUES (
'osmo-admin',
'Full admin access',
'Full admin access except internal endpoints',
ARRAY[
'{"actions": ["*:*"], "resources": ["*"]}'::jsonb
'{"actions": ["*:*"], "resources": ["*"]}'::jsonb,
'{"effect": "Deny", "actions": ["internal:*"], "resources": ["*"]}'::jsonb
],
TRUE
);
Expand Down
8 changes: 3 additions & 5 deletions src/utils/connectors/postgres.py
Original file line number Diff line number Diff line change
Expand Up @@ -4848,11 +4848,9 @@ def merge_default_role_policies(existing_role: Role, default_role: Role) -> bool
resources=['*']
),
role.RolePolicy(
actions=[
# Deny internal actions (handled via authz_sidecar deny logic)
# Note: Deny is implicit - admin doesn't get internal:* actions
],
resources=[]
effect=role.PolicyEffect.DENY,
actions=['internal:*'],
resources=['*']
)
],
immutable=True
Expand Down
30 changes: 30 additions & 0 deletions src/utils/connectors/tests/test_default_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,6 +317,36 @@ def test_returns_false_when_both_existing_and_default_have_no_policies(self):
self.assertFalse(did_update)
self.assertEqual(existing_role.policies, [])

def test_osmo_admin_default_role_denies_internal_actions(self):
osmo_admin = connectors.DEFAULT_ROLES['osmo-admin']

self.assertEqual(osmo_admin.policies[0].effect, role.PolicyEffect.ALLOW)
self.assertEqual(osmo_admin.policies[0].actions, ['*:*'])
self.assertEqual(osmo_admin.policies[0].resources, ['*'])

self.assertEqual(osmo_admin.policies[1].effect, role.PolicyEffect.DENY)
self.assertEqual(osmo_admin.policies[1].actions, ['internal:*'])
self.assertEqual(osmo_admin.policies[1].resources, ['*'])

def test_default_role_merge_appends_admin_internal_deny(self):
existing_role = connectors.Role(
name='osmo-admin',
description='Administrator with full access except internal endpoints',
policies=[
role.RolePolicy(actions=['*:*'], resources=['*']),
role.RolePolicy(actions=[], resources=[]),
],
)
default_role = connectors.DEFAULT_ROLES['osmo-admin']

did_update = connectors.merge_default_role_policies(existing_role, default_role)

self.assertTrue(did_update)
self.assertEqual(len(existing_role.policies), 3)
self.assertEqual(existing_role.policies[2].effect, role.PolicyEffect.DENY)
self.assertEqual(existing_role.policies[2].actions, ['internal:*'])
self.assertEqual(existing_role.policies[2].resources, ['*'])

def test_osmo_user_default_role_allows_only_workflow_read_list_on_all_pools(self):
osmo_user = connectors.DEFAULT_ROLES['osmo-user']

Expand Down