44 "encoding/json"
55 "errors"
66 "fmt"
7+ "sort"
8+ "strings"
79
810 "github.com/flanksource/commons/collections"
911 "github.com/flanksource/commons/logger"
@@ -13,6 +15,7 @@ import (
1315 "github.com/flanksource/duty/rbac/policy"
1416 "github.com/flanksource/duty/rls"
1517 "github.com/flanksource/duty/types"
18+ "github.com/google/uuid"
1619 "github.com/samber/lo"
1720 "go.opentelemetry.io/otel/trace"
1821 "gorm.io/gorm"
@@ -106,23 +109,31 @@ func buildRLSPayloadFromScopes(ctx context.Context) (*rls.Payload, error) {
106109 return nil , fmt .Errorf ("failed to query permissions: %w" , err )
107110 }
108111
109- payload := & rls.Payload {}
112+ scopeIDs := map [uuid.UUID ]struct {}{}
113+ wildcards := map [rls.WildcardResourceScope ]struct {}{}
110114
111115 for _ , perm := range permissions {
112- if perm .ConfigID != nil {
113- payload . Config = append ( payload . Config , rls. Scope { ID : perm . ConfigID . String ()})
116+ if ! collections . MatchItems ( policy . ActionRead , strings . Split ( perm .Action , "," ) ... ) {
117+ continue
114118 }
115119
116- if perm .ComponentID != nil {
117- payload .Component = append (payload .Component , rls.Scope {ID : perm .ComponentID .String ()})
118- }
120+ permScopeID := perm .ID
119121
120- if perm .PlaybookID != nil {
121- payload . Playbook = append ( payload . Playbook , rls. Scope { ID : perm . PlaybookID . String ()})
122+ if perm .ConfigID != nil || perm . ComponentID != nil || perm . PlaybookID != nil || perm . CanaryID != nil {
123+ scopeIDs [ permScopeID ] = struct {}{}
122124 }
123125
124- if perm .CanaryID != nil {
125- payload .Canary = append (payload .Canary , rls.Scope {ID : perm .CanaryID .String ()})
126+ switch perm .Object {
127+ case policy .ObjectCatalog :
128+ wildcards [rls .WildcardResourceScopeConfig ] = struct {}{}
129+ case policy .ObjectTopology :
130+ wildcards [rls .WildcardResourceScopeComponent ] = struct {}{}
131+ case policy .ObjectCanary :
132+ wildcards [rls .WildcardResourceScopeCanary ] = struct {}{}
133+ case policy .ObjectPlaybooks :
134+ wildcards [rls .WildcardResourceScopePlaybook ] = struct {}{}
135+ case policy .ObjectViews :
136+ wildcards [rls .WildcardResourceScopeView ] = struct {}{}
126137 }
127138
128139 if len (perm .ObjectSelector ) == 0 {
@@ -137,34 +148,41 @@ func buildRLSPayloadFromScopes(ctx context.Context) (*rls.Payload, error) {
137148
138149 // Process scope references (indirect permissions)
139150 if len (selectors .Scopes ) > 0 {
140- if err := processScopeRefs (ctx , selectors .Scopes , payload ); err != nil {
151+ if err := processScopeRefs (ctx , selectors .Scopes , scopeIDs , wildcards ); err != nil {
141152 return nil , err
142153 }
143154 }
144155
145156 // Process direct resource selectors (configs, components, playbooks, etc.)
146- // Only use tags, name, and agent_id as per requirements
147157 if len (selectors .Configs ) > 0 {
148- for _ , selector := range selectors .Configs {
149- payload .Config = append (payload .Config , convertResourceSelectorToRLSScope (selector ))
158+ if hasWildcardSelector (selectors .Configs ) {
159+ wildcards [rls .WildcardResourceScopeConfig ] = struct {}{}
160+ } else {
161+ scopeIDs [permScopeID ] = struct {}{}
150162 }
151163 }
152164
153165 if len (selectors .Components ) > 0 {
154- for _ , selector := range selectors .Components {
155- payload .Component = append (payload .Component , convertResourceSelectorToRLSScope (selector ))
166+ if hasWildcardSelector (selectors .Components ) {
167+ wildcards [rls .WildcardResourceScopeComponent ] = struct {}{}
168+ } else {
169+ scopeIDs [permScopeID ] = struct {}{}
156170 }
157171 }
158172
159173 if len (selectors .Playbooks ) > 0 {
160- for _ , selector := range selectors .Playbooks {
161- payload .Playbook = append (payload .Playbook , convertResourceSelectorToRLSScope (selector ))
174+ if hasWildcardSelector (selectors .Playbooks ) {
175+ wildcards [rls .WildcardResourceScopePlaybook ] = struct {}{}
176+ } else {
177+ scopeIDs [permScopeID ] = struct {}{}
162178 }
163179 }
164180
165181 if len (selectors .Views ) > 0 {
166- for _ , viewRef := range selectors .Views {
167- payload .View = append (payload .View , convertViewScopeRefToRLSScope (viewRef ))
182+ if hasWildcardViewRef (selectors .Views ) {
183+ wildcards [rls .WildcardResourceScopeView ] = struct {}{}
184+ } else {
185+ scopeIDs [permScopeID ] = struct {}{}
168186 }
169187 }
170188
@@ -176,11 +194,16 @@ func buildRLSPayloadFromScopes(ctx context.Context) (*rls.Payload, error) {
176194 // }
177195 }
178196
197+ payload := & rls.Payload {
198+ Scopes : setToSortedUUIDSlice (scopeIDs ),
199+ WildcardScopes : setToSortedWildcardSlice (wildcards ),
200+ }
201+
179202 return payload , nil
180203}
181204
182- // processScopeRefs fetches scopes from database and adds their targets to the payload
183- func processScopeRefs (ctx context.Context , scopeRefs []dutyRBAC.NamespacedNameIDSelector , payload * rls.Payload ) error {
205+ // processScopeRefs fetches scopes from database and adds their IDs and wildcard types
206+ func processScopeRefs (ctx context.Context , scopeRefs []dutyRBAC.NamespacedNameIDSelector , scopeIDs map [uuid. UUID ] struct {}, wildcards map [ rls.WildcardResourceScope ] struct {} ) error {
184207 for _ , ref := range scopeRefs {
185208 var scope models.Scope
186209 err := ctx .DB ().
@@ -194,8 +217,8 @@ func processScopeRefs(ctx context.Context, scopeRefs []dutyRBAC.NamespacedNameID
194217 return fmt .Errorf ("failed to query scope %s/%s: %w" , ref .Namespace , ref .Name , err )
195218 }
196219
197- // Add scope UUID for view row-level grants
198- payload . Scopes = append ( payload . Scopes , scope . ID . String ())
220+ // Always include scope UUID for view row-level grants
221+ scopeIDs [ scope . ID ] = struct {}{}
199222
200223 var targets []v1.ScopeTarget
201224 if err := json .Unmarshal ([]byte (scope .Targets ), & targets ); err != nil {
@@ -204,93 +227,101 @@ func processScopeRefs(ctx context.Context, scopeRefs []dutyRBAC.NamespacedNameID
204227 }
205228
206229 for _ , target := range targets {
207- if target .Config != nil {
208- rlsScope := convertToRLSScope (target .Config )
209- payload .Config = append (payload .Config , rlsScope )
210- }
211- if target .Component != nil {
212- rlsScope := convertToRLSScope (target .Component )
213- payload .Component = append (payload .Component , rlsScope )
214- }
215- if target .Playbook != nil {
216- rlsScope := convertToRLSScope (target .Playbook )
217- payload .Playbook = append (payload .Playbook , rlsScope )
218- }
219- if target .Canary != nil {
220- rlsScope := convertToRLSScope (target .Canary )
221- payload .Canary = append (payload .Canary , rlsScope )
222- }
223- if target .View != nil {
224- rlsScope := convertToRLSScope (target .View )
225- payload .View = append (payload .View , rlsScope )
226- }
227- if target .Global != nil {
228- rlsScope := convertToRLSScope (target .Global )
229- payload .Config = append (payload .Config , rlsScope )
230- payload .Component = append (payload .Component , rlsScope )
231- payload .Playbook = append (payload .Playbook , rlsScope )
232- payload .Canary = append (payload .Canary , rlsScope )
233- payload .View = append (payload .View , rlsScope )
230+ switch {
231+ case target .Config != nil :
232+ if isWildcardScopeSelector (target .Config ) {
233+ wildcards [rls .WildcardResourceScopeConfig ] = struct {}{}
234+ }
235+ case target .Component != nil :
236+ if isWildcardScopeSelector (target .Component ) {
237+ wildcards [rls .WildcardResourceScopeComponent ] = struct {}{}
238+ }
239+ case target .Playbook != nil :
240+ if isWildcardScopeSelector (target .Playbook ) {
241+ wildcards [rls .WildcardResourceScopePlaybook ] = struct {}{}
242+ }
243+ case target .Canary != nil :
244+ if isWildcardScopeSelector (target .Canary ) {
245+ wildcards [rls .WildcardResourceScopeCanary ] = struct {}{}
246+ }
247+ case target .View != nil :
248+ if isWildcardScopeSelector (target .View ) {
249+ wildcards [rls .WildcardResourceScopeView ] = struct {}{}
250+ }
251+ case target .Global != nil :
252+ if isWildcardScopeSelector (target .Global ) {
253+ wildcards [rls .WildcardResourceScopeConfig ] = struct {}{}
254+ wildcards [rls .WildcardResourceScopeComponent ] = struct {}{}
255+ wildcards [rls .WildcardResourceScopePlaybook ] = struct {}{}
256+ wildcards [rls .WildcardResourceScopeCanary ] = struct {}{}
257+ wildcards [rls .WildcardResourceScopeView ] = struct {}{}
258+ }
234259 }
235260 }
236261 }
237262
238263 return nil
239264}
240265
241- func convertToRLSScope (selector * v1.ScopeResourceSelector ) rls.Scope {
242- rlsScope := rls.Scope {}
243-
244- if selector .Agent != "" {
245- rlsScope .Agents = []string {selector .Agent }
266+ func isWildcardScopeSelector (selector * v1.ScopeResourceSelector ) bool {
267+ if selector == nil {
268+ return false
246269 }
247270
248- if selector .Name != "" {
249- rlsScope .Names = []string {selector .Name }
250- }
271+ return selector .Name == "*" &&
272+ selector .Namespace == "" &&
273+ selector .Agent == "" &&
274+ selector .TagSelector == ""
275+ }
251276
252- if selector .TagSelector != "" {
253- rlsScope .Tags = collections .SelectorToMap (selector .TagSelector )
277+ func hasWildcardSelector (selectors []types.ResourceSelector ) bool {
278+ for _ , selector := range selectors {
279+ if selector .Wildcard () {
280+ return true
281+ }
254282 }
255-
256- return rlsScope
283+ return false
257284}
258285
259- // convertResourceSelectorToRLSScope converts a types.ResourceSelector to rls.Scope
260- // Only uses tags, name, and agent_id.
261- func convertResourceSelectorToRLSScope (selector types.ResourceSelector ) rls.Scope {
262- rlsScope := rls.Scope {}
263-
264- if selector .Agent != "" {
265- rlsScope .Agents = []string {selector .Agent }
286+ func hasWildcardViewRef (selectors []dutyRBAC.ViewRef ) bool {
287+ for _ , selector := range selectors {
288+ if selector .Name == "*" && selector .Namespace == "" && selector .ID == "" {
289+ return true
290+ }
266291 }
292+ return false
293+ }
267294
268- if selector .Name != "" {
269- rlsScope .Names = []string {selector .Name }
295+ func setToSortedUUIDSlice (set map [uuid.UUID ]struct {}) []uuid.UUID {
296+ if len (set ) == 0 {
297+ return nil
270298 }
271299
272- if selector .TagSelector != "" {
273- rlsScope .Tags = collections .SelectorToMap (selector .TagSelector )
300+ out := make ([]uuid.UUID , 0 , len (set ))
301+ for val := range set {
302+ out = append (out , val )
274303 }
275304
276- return rlsScope
277- }
305+ sort .Slice (out , func (i , j int ) bool {
306+ return out [i ].String () < out [j ].String ()
307+ })
278308
279- // convertViewScopeRefToRLSScope converts a view ViewRef (namespace/name) to rls.Scope
280- // Views only support id and name in match_scope (namespace is not supported)
281- func convertViewScopeRefToRLSScope (viewRef dutyRBAC.ViewRef ) rls.Scope {
282- rlsScope := rls.Scope {}
309+ return out
310+ }
283311
284- if viewRef .Name != "" {
285- rlsScope .Names = []string {viewRef .Name }
312+ func setToSortedWildcardSlice (set map [rls.WildcardResourceScope ]struct {}) []rls.WildcardResourceScope {
313+ if len (set ) == 0 {
314+ return nil
286315 }
287316
288- if viewRef .ID != "" {
289- rlsScope .ID = viewRef .ID
317+ out := make ([]rls.WildcardResourceScope , 0 , len (set ))
318+ for val := range set {
319+ out = append (out , val )
290320 }
291321
292- // Note: namespace is not supported by match_scope for views
293- // ID would be set if we have a direct ID reference, but ViewRef doesn't have ID field
322+ sort .Slice (out , func (i , j int ) bool {
323+ return string (out [i ]) < string (out [j ])
324+ })
294325
295- return rlsScope
326+ return out
296327}
0 commit comments