@@ -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
corev1 "k8s.io/api/core/v1"
12
15
rbacv1 "k8s.io/api/rbac/v1"
@@ -89,8 +92,14 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
89
92
90
93
for _ , obj := range dm .rbacObjects () {
91
94
if err := ec .checkEscalation (ctx , manifestManager , obj ); err != nil {
92
- // In Kubernetes 1.32.2 the specialized PrivilegeEscalationError is gone.
93
- // Instead, we simply collect the error.
95
+ missingEscalationRules , namespace := parseEscalationErrorForMissingRules (err )
96
+ // Check if we already have these escalation PolicyRules, if so don't append
97
+ for i , rule := range missingEscalationRules {
98
+ previousRule := missingRules [namespace ][len (missingRules [namespace ])- len (missingEscalationRules )+ i ]
99
+ if ! arePolicyRulesEqual (previousRule , rule ) {
100
+ missingRules [namespace ] = append (missingRules [namespace ], missingEscalationRules ... )
101
+ }
102
+ }
94
103
preAuthEvaluationErrors = append (preAuthEvaluationErrors , err )
95
104
}
96
105
}
@@ -108,6 +117,11 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
108
117
allMissingPolicyRules = append (allMissingPolicyRules , ScopedPolicyRules {Namespace : ns , MissingRules : sortableRules })
109
118
}
110
119
120
+ // sort allMissingPolicyRules alphabetically by namespace
121
+ sort .Slice (allMissingPolicyRules , func (i , j int ) bool {
122
+ return allMissingPolicyRules [i ].Namespace < allMissingPolicyRules [j ].Namespace
123
+ })
124
+
111
125
if len (preAuthEvaluationErrors ) > 0 {
112
126
return allMissingPolicyRules , fmt .Errorf ("authorization evaluation errors: %w" , errors .Join (preAuthEvaluationErrors ... ))
113
127
}
@@ -504,6 +518,42 @@ var fullAuthority = []rbacv1.PolicyRule{
504
518
{Verbs : []string {"*" }, NonResourceURLs : []string {"*" }},
505
519
}
506
520
521
+ func parseEscalationErrorForMissingRules (ecError error ) ([]rbacv1.PolicyRule , string ) {
522
+ // Regex to capture namespace and serviceaccount
523
+ userRegex := regexp .MustCompile (`system:serviceaccount:(?P<Namespace>[^:]+):(?P<ServiceAccount>[^"]+)` )
524
+ // Regex to capture missing permissions
525
+ permRegex := regexp .MustCompile (`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}` )
526
+
527
+ userMatches := userRegex .FindStringSubmatch (ecError .Error ())
528
+ namespace := userMatches [1 ]
529
+
530
+ // Extract permissions
531
+ var permissions []rbacv1.PolicyRule
532
+ permMatches := permRegex .FindAllStringSubmatch (ecError .Error (), - 1 )
533
+ for _ , match := range permMatches {
534
+ permissions = append (permissions , rbacv1.PolicyRule {
535
+ APIGroups : []string {strings .Trim (match [1 ], `"` )},
536
+ Resources : []string {strings .Trim (match [2 ], `"` )},
537
+ Verbs : []string {strings .Trim (match [3 ], `"` )},
538
+ })
539
+ }
540
+
541
+ return permissions , namespace
542
+ }
543
+
544
+ func arePolicyRulesEqual (rule1 rbacv1.PolicyRule , rule2 rbacv1.PolicyRule ) bool {
545
+ sort .Strings (rule1 .Verbs )
546
+ sort .Strings (rule2 .Verbs )
547
+ sort .Strings (rule1 .APIGroups )
548
+ sort .Strings (rule2 .APIGroups )
549
+ sort .Strings (rule1 .Resources )
550
+ sort .Strings (rule2 .Resources )
551
+ sort .Strings (rule1 .ResourceNames )
552
+ sort .Strings (rule2 .ResourceNames )
553
+
554
+ return reflect .DeepEqual (rule1 , rule2 )
555
+ }
556
+
507
557
func hasAggregationRule (clusterRole * rbacv1.ClusterRole ) bool {
508
558
// Currently, an aggregation rule is considered present only if it has one or more selectors.
509
559
// An empty slice of ClusterRoleSelectors means no selectors were provided,
0 commit comments