@@ -6,7 +6,10 @@ import (
6
6
"fmt"
7
7
"io"
8
8
"maps"
9
+ "reflect"
10
+ "regexp"
9
11
"sort"
12
+ "strings"
10
13
11
14
rbacv1 "k8s.io/api/rbac/v1"
12
15
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -91,8 +94,14 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
91
94
92
95
for _ , obj := range dm .rbacObjects () {
93
96
if err := ec .checkEscalation (ctx , manifestManager , obj ); err != nil {
94
- // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone.
95
- // Instead, we simply collect the error.
97
+ missingEscalationRules , namespace := parseEscalationErrorForMissingRules (err )
98
+ // Check if we already have these escalation PolicyRules, if so don't append
99
+ for i , rule := range missingEscalationRules {
100
+ previousRule := missingRules [namespace ][len (missingRules [namespace ])- len (missingEscalationRules )+ i ]
101
+ if ! arePolicyRulesEqual (previousRule , rule ) {
102
+ missingRules [namespace ] = append (missingRules [namespace ], missingEscalationRules ... )
103
+ }
104
+ }
96
105
preAuthEvaluationErrors = append (preAuthEvaluationErrors , err )
97
106
}
98
107
}
@@ -110,6 +119,11 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
110
119
allMissingPolicyRules = append (allMissingPolicyRules , ScopedPolicyRules {Namespace : ns , MissingRules : sortableRules })
111
120
}
112
121
122
+ // sort allMissingPolicyRules alphabetically by namespace
123
+ sort .Slice (allMissingPolicyRules , func (i , j int ) bool {
124
+ return allMissingPolicyRules [i ].Namespace < allMissingPolicyRules [j ].Namespace
125
+ })
126
+
113
127
if len (preAuthEvaluationErrors ) > 0 {
114
128
return allMissingPolicyRules , fmt .Errorf ("authorization evaluation errors: %w" , errors .Join (preAuthEvaluationErrors ... ))
115
129
}
@@ -484,6 +498,42 @@ var fullAuthority = []rbacv1.PolicyRule{
484
498
{Verbs : []string {"*" }, NonResourceURLs : []string {"*" }},
485
499
}
486
500
501
+ func parseEscalationErrorForMissingRules (ecError error ) ([]rbacv1.PolicyRule , string ) {
502
+ // Regex to capture namespace and serviceaccount
503
+ userRegex := regexp .MustCompile (`system:serviceaccount:(?P<Namespace>[^:]+):(?P<ServiceAccount>[^"]+)` )
504
+ // Regex to capture missing permissions
505
+ permRegex := regexp .MustCompile (`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}` )
506
+
507
+ userMatches := userRegex .FindStringSubmatch (ecError .Error ())
508
+ namespace := userMatches [1 ]
509
+
510
+ // Extract permissions
511
+ var permissions []rbacv1.PolicyRule
512
+ permMatches := permRegex .FindAllStringSubmatch (ecError .Error (), - 1 )
513
+ for _ , match := range permMatches {
514
+ permissions = append (permissions , rbacv1.PolicyRule {
515
+ APIGroups : []string {strings .Trim (match [1 ], `"` )},
516
+ Resources : []string {strings .Trim (match [2 ], `"` )},
517
+ Verbs : []string {strings .Trim (match [3 ], `"` )},
518
+ })
519
+ }
520
+
521
+ return permissions , namespace
522
+ }
523
+
524
+ func arePolicyRulesEqual (rule1 rbacv1.PolicyRule , rule2 rbacv1.PolicyRule ) bool {
525
+ sort .Strings (rule1 .Verbs )
526
+ sort .Strings (rule2 .Verbs )
527
+ sort .Strings (rule1 .APIGroups )
528
+ sort .Strings (rule2 .APIGroups )
529
+ sort .Strings (rule1 .Resources )
530
+ sort .Strings (rule2 .Resources )
531
+ sort .Strings (rule1 .ResourceNames )
532
+ sort .Strings (rule2 .ResourceNames )
533
+
534
+ return reflect .DeepEqual (rule1 , rule2 )
535
+ }
536
+
487
537
func hasAggregationRule (clusterRole * rbacv1.ClusterRole ) bool {
488
538
// Currently, an aggregation rule is considered present only if it has one or more selectors.
489
539
// An empty slice of ClusterRoleSelectors means no selectors were provided,
0 commit comments