Skip to content

Commit d2973a6

Browse files
committed
Clean up escalation error parsing and fix tests
Pass in the clusterextension to PreAuthorize instead of the user.Info since we need the extension to create the clusterextension/finalizer Signed-off-by: Tayler Geiger <[email protected]>
1 parent cc2ef53 commit d2973a6

File tree

4 files changed

+58
-50
lines changed

4 files changed

+58
-50
lines changed

internal/operator-controller/applier/helm.go

+1-3
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919
rbacv1 "k8s.io/api/rbac/v1"
2020
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2121
apimachyaml "k8s.io/apimachinery/pkg/util/yaml"
22-
"k8s.io/apiserver/pkg/authentication/user"
2322
"sigs.k8s.io/controller-runtime/pkg/client"
2423
"sigs.k8s.io/controller-runtime/pkg/log"
2524

@@ -100,8 +99,7 @@ func (h *Helm) Apply(ctx context.Context, contentFS fs.FS, ext *ocv1.ClusterExte
10099
return nil, "", fmt.Errorf("failed to get release state using client-only dry-run: %w", err)
101100
}
102101

103-
ceServiceAccount := user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)}
104-
missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, &ceServiceAccount, strings.NewReader(tmplRel.Manifest))
102+
missingRules, err := h.PreAuthorizer.PreAuthorize(ctx, ext, strings.NewReader(tmplRel.Manifest))
105103

106104
var preAuthErrors []error
107105
if len(missingRules) > 0 {

internal/operator-controller/applier/helm_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ import (
1717
"helm.sh/helm/v3/pkg/storage/driver"
1818
corev1 "k8s.io/api/core/v1"
1919
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20-
"k8s.io/apiserver/pkg/authentication/user"
2120
featuregatetesting "k8s.io/component-base/featuregate/testing"
2221
"sigs.k8s.io/controller-runtime/pkg/client"
2322

@@ -39,7 +38,7 @@ type noOpPreAuthorizer struct{}
3938

4039
func (p *noOpPreAuthorizer) PreAuthorize(
4140
ctx context.Context,
42-
manifestManager user.Info,
41+
ext *ocv1.ClusterExtension,
4342
manifestReader io.Reader,
4443
) ([]authorization.ScopedPolicyRules, error) {
4544
// No-op: always return an empty map and no error

internal/operator-controller/authorization/rbac.go

+39-35
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@ import (
66
"fmt"
77
"io"
88
"maps"
9-
"reflect"
109
"regexp"
1110
"sort"
1211
"strings"
1312

13+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
1414
corev1 "k8s.io/api/core/v1"
1515
rbacv1 "k8s.io/api/rbac/v1"
1616
apierrors "k8s.io/apimachinery/pkg/api/errors"
@@ -34,7 +34,7 @@ import (
3434
)
3535

3636
type PreAuthorizer interface {
37-
PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error)
37+
PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error)
3838
}
3939

4040
type ScopedPolicyRules struct {
@@ -69,13 +69,14 @@ func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer {
6969
// completes successfully but identifies missing rules, then a nil error is returned along with
7070
// the list (or slice) of missing rules. Note that in some cases the error may encapsulate multiple
7171
// evaluation failures
72-
func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager user.Info, manifestReader io.Reader) ([]ScopedPolicyRules, error) {
72+
func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterExtension, manifestReader io.Reader) ([]ScopedPolicyRules, error) {
7373
var allMissingPolicyRules = []ScopedPolicyRules{}
7474
dm, err := a.decodeManifest(manifestReader)
7575
if err != nil {
7676
return nil, err
7777
}
78-
attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager)
78+
manifestManager := &user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)}
79+
attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager, ext)
7980

8081
var preAuthEvaluationErrors []error
8182
missingRules, err := a.authorizeAttributesRecords(ctx, attributesRecords)
@@ -92,14 +93,8 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
9293

9394
for _, obj := range dm.rbacObjects() {
9495
if err := ec.checkEscalation(ctx, manifestManager, obj); err != nil {
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-
}
96+
missingEscalationRules, err := parseEscalationErrorForMissingRules(err)
97+
missingRules[obj.GetNamespace()] = append(missingRules[obj.GetNamespace()], missingEscalationRules...)
10398
preAuthEvaluationErrors = append(preAuthEvaluationErrors, err)
10499
}
105100
}
@@ -112,7 +107,20 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, manifestManager us
112107
if compactMissingRules, err := validation.CompactRules(nsMissingRules); err == nil {
113108
missingRules[ns] = compactMissingRules
114109
}
115-
sortableRules := rbacv1helpers.SortableRuleSlice(missingRules[ns])
110+
111+
missingRulesWithDeduplicatedVerbs := []rbacv1.PolicyRule{}
112+
for _, rule := range missingRules[ns] {
113+
verbSet := sets.New[string](rule.Verbs...)
114+
if verbSet.Has("*") {
115+
rule.Verbs = []string{"*"}
116+
} else {
117+
rule.Verbs = sets.List(verbSet)
118+
}
119+
missingRulesWithDeduplicatedVerbs = append(missingRulesWithDeduplicatedVerbs, rule)
120+
}
121+
122+
sortableRules := rbacv1helpers.SortableRuleSlice(missingRulesWithDeduplicatedVerbs)
123+
116124
sort.Sort(sortableRules)
117125
allMissingPolicyRules = append(allMissingPolicyRules, ScopedPolicyRules{Namespace: ns, MissingRules: sortableRules})
118126
}
@@ -284,7 +292,7 @@ func (dm *decodedManifest) rbacObjects() []client.Object {
284292
return objects
285293
}
286294

287-
func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info) []authorizer.AttributesRecord {
295+
func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManager user.Info, ext *ocv1.ClusterExtension) []authorizer.AttributesRecord {
288296
var attributeRecords []authorizer.AttributesRecord
289297

290298
// Here we are splitting collection verbs based on required scope
@@ -338,6 +346,18 @@ func (dm *decodedManifest) asAuthorizationAttributesRecordsForUser(manifestManag
338346
Verb: v,
339347
})
340348
}
349+
350+
for _, verb := range []string{"update", "patch"} {
351+
attributeRecords = append(attributeRecords, authorizer.AttributesRecord{
352+
User: manifestManager,
353+
Name: ext.Name,
354+
APIGroup: ext.GroupVersionKind().Group,
355+
APIVersion: ext.GroupVersionKind().Version,
356+
Resource: "clusterextensions/finalizers",
357+
ResourceRequest: true,
358+
Verb: verb,
359+
})
360+
}
341361
}
342362
return attributeRecords
343363
}
@@ -518,17 +538,14 @@ var fullAuthority = []rbacv1.PolicyRule{
518538
{Verbs: []string{"*"}, NonResourceURLs: []string{"*"}},
519539
}
520540

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
541+
func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, error) {
542+
errRegex := regexp.MustCompile(`(?s)^(user \".*\" \(groups=.*\) is attempting to grant RBAC permissions not currently held):.*?$`)
525543
permRegex := regexp.MustCompile(`{APIGroups:\[("[^"]*")\], Resources:\[("[^"]*")\], Verbs:\[("[^"]*")\]}`)
526544

527-
userMatches := userRegex.FindStringSubmatch(ecError.Error())
528-
namespace := userMatches[1]
545+
errMatches := errRegex.FindAllStringSubmatch(ecError.Error(), -1)
529546

530547
// Extract permissions
531-
var permissions []rbacv1.PolicyRule
548+
permissions := []rbacv1.PolicyRule{}
532549
permMatches := permRegex.FindAllStringSubmatch(ecError.Error(), -1)
533550
for _, match := range permMatches {
534551
permissions = append(permissions, rbacv1.PolicyRule{
@@ -538,20 +555,7 @@ func parseEscalationErrorForMissingRules(ecError error) ([]rbacv1.PolicyRule, st
538555
})
539556
}
540557

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)
558+
return permissions, errors.New(errMatches[0][1])
555559
}
556560

557561
func hasAggregationRule(clusterRole *rbacv1.ClusterRole) bool {

internal/operator-controller/authorization/rbac_test.go

+17-10
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,16 @@ package authorization
22

33
import (
44
"context"
5-
"fmt"
65
"strings"
76
"testing"
87

8+
ocv1 "github.com/operator-framework/operator-controller/api/v1"
99
"github.com/stretchr/testify/require"
1010
corev1 "k8s.io/api/core/v1"
1111
rbacv1 "k8s.io/api/rbac/v1"
1212
"k8s.io/apimachinery/pkg/api/meta/testrestmapper"
1313
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
1414
"k8s.io/apimachinery/pkg/runtime"
15-
"k8s.io/apiserver/pkg/authentication/user"
1615
featuregatetesting "k8s.io/component-base/featuregate/testing"
1716
"sigs.k8s.io/controller-runtime/pkg/client"
1817
"sigs.k8s.io/controller-runtime/pkg/client/fake"
@@ -127,9 +126,17 @@ subjects:
127126
namespace: a-test-namespace
128127
`
129128

130-
saName = "test-serviceaccount"
131-
ns = "test-namespace"
132-
testServiceAccount = user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ns, saName)}
129+
saName = "test-serviceaccount"
130+
ns = "test-namespace"
131+
exampleClusterExtension = ocv1.ClusterExtension{
132+
ObjectMeta: metav1.ObjectMeta{Name: "test-cluster-extension"},
133+
Spec: ocv1.ClusterExtensionSpec{
134+
Namespace: ns,
135+
ServiceAccount: ocv1.ServiceAccountReference{
136+
Name: saName,
137+
},
138+
},
139+
}
133140

134141
objects = []client.Object{
135142
&corev1.Namespace{
@@ -194,7 +201,7 @@ subjects:
194201
Rules: []rbacv1.PolicyRule{
195202
{
196203
APIGroups: []string{"*"},
197-
Resources: []string{"serviceaccounts", "services"},
204+
Resources: []string{"serviceaccounts", "services", "clusterextensions/finalizers"},
198205
Verbs: []string{"*"},
199206
},
200207
{
@@ -221,7 +228,7 @@ func TestPreAuthorize_Success(t *testing.T) {
221228
featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true)
222229
fakeClient := setupFakeClient(privilegedClusterRole)
223230
preAuth := NewRBACPreAuthorizer(fakeClient)
224-
missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest))
231+
missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest))
225232
require.NoError(t, err)
226233
require.Equal(t, []ScopedPolicyRules{}, missingRules)
227234
})
@@ -232,7 +239,7 @@ func TestPreAuthorize_Failure(t *testing.T) {
232239
featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true)
233240
fakeClient := setupFakeClient(limitedClusterRole)
234241
preAuth := NewRBACPreAuthorizer(fakeClient)
235-
missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest))
242+
missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest))
236243
require.Error(t, err)
237244
require.NotEqual(t, []ScopedPolicyRules{}, missingRules)
238245
})
@@ -243,7 +250,7 @@ func TestPreAuthorizeMultiNamespace_Failure(t *testing.T) {
243250
featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true)
244251
fakeClient := setupFakeClient(limitedClusterRole)
245252
preAuth := NewRBACPreAuthorizer(fakeClient)
246-
missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifestMultiNamespace))
253+
missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifestMultiNamespace))
247254
require.Error(t, err)
248255
require.NotEqual(t, []ScopedPolicyRules{}, missingRules)
249256
})
@@ -254,7 +261,7 @@ func TestPreAuthorize_CheckEscalation(t *testing.T) {
254261
featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.PreflightPermissions, true)
255262
fakeClient := setupFakeClient(escalatingClusterRole)
256263
preAuth := NewRBACPreAuthorizer(fakeClient)
257-
missingRules, err := preAuth.PreAuthorize(context.TODO(), &testServiceAccount, strings.NewReader(testManifest))
264+
missingRules, err := preAuth.PreAuthorize(context.TODO(), &exampleClusterExtension, strings.NewReader(testManifest))
258265
require.NoError(t, err)
259266
require.Equal(t, []ScopedPolicyRules{}, missingRules)
260267
})

0 commit comments

Comments
 (0)