Skip to content

Commit d76c373

Browse files
committed
Added RBAC authorization support
1 parent 440ecda commit d76c373

File tree

4 files changed

+1493
-3
lines changed

4 files changed

+1493
-3
lines changed

internal/server/rbac.go

Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright (c) ClaceIO, LLC
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package server
5+
6+
import (
7+
"fmt"
8+
"slices"
9+
"strings"
10+
"sync"
11+
12+
"github.com/openrundev/openrun/internal/types"
13+
)
14+
15+
const RBAC_AUTH_PREFIX = "rbac:"
16+
const RBAC_GROUP_PREFIX = "group:"
17+
const RBAC_ROLE_PREFIX = "role:"
18+
19+
type RBACManager struct {
20+
*types.Logger
21+
rbacConfig *types.RBACConfig
22+
serverConfig *types.ServerConfig
23+
mu sync.RWMutex
24+
25+
groups map[string][]string // group name to user ids (with group hierarchy resolved)
26+
roles map[string][]types.RBACPermission // role name to permissions (with role: hierarchy resolved)
27+
}
28+
29+
func NewRBACHandler(logger *types.Logger, rbacConfig *types.RBACConfig, serverConfig *types.ServerConfig) (*RBACManager, error) {
30+
rbacManager := &RBACManager{
31+
Logger: logger,
32+
rbacConfig: rbacConfig,
33+
serverConfig: serverConfig,
34+
}
35+
36+
err := rbacManager.UpdateRBACConfig(rbacConfig)
37+
if err != nil {
38+
return nil, err
39+
}
40+
return rbacManager, nil
41+
}
42+
43+
func (h *RBACManager) AuthorizeAccess(user string, appPathDomain types.AppPathDomain, appAuthSetting string, permission types.RBACPermission) (bool, error) {
44+
h.mu.RLock()
45+
defer h.mu.RUnlock()
46+
47+
if !h.rbacConfig.Enabled {
48+
// rbac is not enabled, authorize all access
49+
return true, nil
50+
}
51+
52+
if user != "" && user == h.serverConfig.AdminUser {
53+
// admin user is always authorized if enabled
54+
return true, nil
55+
}
56+
57+
if !strings.HasPrefix(appAuthSetting, RBAC_AUTH_PREFIX) {
58+
// if app auth is not rbac enabled, authorize access
59+
return true, nil
60+
}
61+
62+
return h.checkGrants(user, appPathDomain, permission)
63+
}
64+
65+
func (h *RBACManager) checkGrants(inputUser string, appPathDomain types.AppPathDomain, inputPermission types.RBACPermission) (bool, error) {
66+
for _, grant := range h.rbacConfig.Grants {
67+
match, err := h.checkGrant(grant, inputUser, appPathDomain, inputPermission)
68+
if err != nil {
69+
return false, err
70+
}
71+
if match {
72+
// User, role and target matched. This is a valid grant.
73+
h.Trace().Msgf("Allowed user %s access to app %s with permission %s using grant %s",
74+
inputUser, appPathDomain.String(), inputPermission, grant.Description)
75+
return true, nil
76+
}
77+
}
78+
h.Debug().Msgf("Denied user %s access to app %s with permission %s", inputUser, appPathDomain.String(), inputPermission)
79+
return false, nil
80+
}
81+
82+
func (h *RBACManager) checkGrant(grant types.RBACGrant, inputUser string, appPathDomain types.AppPathDomain, inputPermission types.RBACPermission) (bool, error) {
83+
userMatched := false
84+
for _, user := range grant.Users {
85+
if strings.HasPrefix(user, RBAC_GROUP_PREFIX) {
86+
refGroupName := user[len(RBAC_GROUP_PREFIX):]
87+
if slices.Contains(h.groups[refGroupName], inputUser) {
88+
userMatched = true
89+
break
90+
}
91+
} else if user == inputUser {
92+
userMatched = true
93+
break
94+
}
95+
}
96+
97+
if !userMatched {
98+
return false, nil
99+
}
100+
101+
// user matched, check if role matches
102+
roleMatched := false
103+
for _, role := range grant.Roles {
104+
if slices.Contains(h.roles[role], inputPermission) {
105+
roleMatched = true
106+
break
107+
}
108+
}
109+
110+
if !roleMatched {
111+
return false, nil
112+
}
113+
114+
targetMatched := false
115+
for _, target := range grant.Targets {
116+
match, err := MatchGlob(target, appPathDomain)
117+
if err != nil {
118+
return false, err
119+
}
120+
if match {
121+
targetMatched = true
122+
break
123+
}
124+
}
125+
126+
if !targetMatched {
127+
return false, nil
128+
}
129+
130+
return true, nil
131+
}
132+
133+
func (h *RBACManager) initGroupInfo(rbacConfig *types.RBACConfig) (map[string][]string, error) {
134+
groupMembers := make(map[string][]string)
135+
136+
// Initialize all groups
137+
for group := range rbacConfig.Groups {
138+
groupMembers[group] = make([]string, 0)
139+
}
140+
141+
// Helper function to recursively resolve group membership
142+
var resolveGroup func(groupName string, visited map[string]bool) ([]string, error)
143+
resolveGroup = func(groupName string, visited map[string]bool) ([]string, error) {
144+
if visited[groupName] {
145+
return nil, fmt.Errorf("circular group reference detected for group: %s", groupName)
146+
}
147+
148+
users, exists := rbacConfig.Groups[groupName]
149+
if !exists {
150+
return nil, fmt.Errorf("group: %s is not defined", groupName)
151+
}
152+
153+
visited[groupName] = true
154+
defer func() { visited[groupName] = false }()
155+
156+
var members []string
157+
for _, user := range users {
158+
if strings.HasPrefix(user, RBAC_GROUP_PREFIX) {
159+
refGroupName := user[len(RBAC_GROUP_PREFIX):]
160+
refMembers, err := resolveGroup(refGroupName, visited)
161+
if err != nil {
162+
return nil, err
163+
}
164+
members = append(members, refMembers...)
165+
} else {
166+
members = append(members, user)
167+
}
168+
}
169+
170+
return members, nil
171+
}
172+
173+
// Resolve all groups
174+
for group := range rbacConfig.Groups {
175+
visited := make(map[string]bool)
176+
members, err := resolveGroup(group, visited)
177+
if err != nil {
178+
return nil, err
179+
}
180+
groupMembers[group] = members
181+
}
182+
183+
return groupMembers, nil
184+
}
185+
186+
func (h *RBACManager) initRoleInfo(rbacConfig *types.RBACConfig) (map[string][]types.RBACPermission, error) {
187+
roles := make(map[string][]types.RBACPermission)
188+
189+
// Initialize all roles
190+
for role := range rbacConfig.Roles {
191+
roles[role] = make([]types.RBACPermission, 0)
192+
}
193+
194+
// Helper function to recursively resolve role permissions
195+
var resolveRole func(roleName string, visited map[string]bool) ([]types.RBACPermission, error)
196+
resolveRole = func(roleName string, visited map[string]bool) ([]types.RBACPermission, error) {
197+
if visited[roleName] {
198+
return nil, fmt.Errorf("circular role reference detected for role: %s", roleName)
199+
}
200+
201+
perms, exists := rbacConfig.Roles[roleName]
202+
if !exists {
203+
return nil, fmt.Errorf("role: %s is not defined", roleName)
204+
}
205+
206+
visited[roleName] = true
207+
defer func() { visited[roleName] = false }()
208+
209+
var permissions []types.RBACPermission
210+
for _, perm := range perms {
211+
if strings.HasPrefix(string(perm), RBAC_ROLE_PREFIX) {
212+
refRoleName := string(perm)[len(RBAC_ROLE_PREFIX):]
213+
refPermissions, err := resolveRole(refRoleName, visited)
214+
if err != nil {
215+
return nil, err
216+
}
217+
permissions = append(permissions, refPermissions...)
218+
} else {
219+
permissions = append(permissions, perm)
220+
}
221+
}
222+
223+
return permissions, nil
224+
}
225+
226+
// Resolve all roles
227+
for role := range rbacConfig.Roles {
228+
visited := make(map[string]bool)
229+
permissions, err := resolveRole(role, visited)
230+
if err != nil {
231+
return nil, err
232+
}
233+
roles[role] = permissions
234+
}
235+
236+
return roles, nil
237+
}
238+
func (h *RBACManager) validateGrants(rbacConfig *types.RBACConfig) error {
239+
// Skip validation if RBAC is disabled
240+
if !rbacConfig.Enabled {
241+
return nil
242+
}
243+
244+
for i, grant := range rbacConfig.Grants {
245+
// Validate group references in Users
246+
for _, user := range grant.Users {
247+
if strings.HasPrefix(user, RBAC_GROUP_PREFIX) {
248+
groupName := user[len(RBAC_GROUP_PREFIX):]
249+
if _, exists := rbacConfig.Groups[groupName]; !exists {
250+
return fmt.Errorf("grant %d ('%s'): Users references undefined group '%s'", i, grant.Description, groupName)
251+
}
252+
}
253+
}
254+
255+
// Validate role references in Roles
256+
for _, role := range grant.Roles {
257+
if _, exists := rbacConfig.Roles[role]; !exists {
258+
return fmt.Errorf("grant %d ('%s'): Roles references undefined role '%s'", i, grant.Description, role)
259+
}
260+
}
261+
}
262+
return nil
263+
}
264+
265+
func (h *RBACManager) UpdateRBACConfig(rbacConfig *types.RBACConfig) error {
266+
h.mu.Lock()
267+
defer h.mu.Unlock()
268+
269+
h.rbacConfig = rbacConfig
270+
271+
var err error
272+
h.groups, err = h.initGroupInfo(rbacConfig)
273+
if err != nil {
274+
return fmt.Errorf("error initializing rbac group info: %w", err)
275+
}
276+
277+
h.roles, err = h.initRoleInfo(rbacConfig)
278+
if err != nil {
279+
return fmt.Errorf("error initializing rbac role info: %w", err)
280+
}
281+
282+
err = h.validateGrants(rbacConfig)
283+
if err != nil {
284+
return fmt.Errorf("error validating rbac grants: %w", err)
285+
}
286+
287+
return nil
288+
}

0 commit comments

Comments
 (0)