Skip to content

Commit 2840709

Browse files
joelanfordclaude
andcommitted
Enable SyntheticPermissions feature gate for experimental channel
This commit enables the SyntheticPermissions feature to allow ClusterExtensions to operate without specifying a ServiceAccount by authenticating as synthetic users. Feature gate changes: - Enabled SyntheticPermissions in experimental channel (helm) - Enabled SyntheticPermissions in tilt configuration RBAC changes: - Added conditional RBAC rules for impersonating users and the "olm:clusterextensions" group when SyntheticPermissions is enabled - Kept existing serviceaccounts/token permissions for token-based auth Logic changes: - Updated SyntheticUserRestConfigMapper to check for empty SA name instead of synthetic-user sentinel value - Modified NewRBACPreAuthorizer to accept a userInfoMapper function - Implemented userInfoMapper in main.go to return synthetic user info when SA name is empty and feature gate is enabled, otherwise returns standard service account user info - Updated authorization tests to work with new userInfoMapper pattern With these changes: - When ServiceAccount.Name is empty and SyntheticPermissions is enabled: authenticates as "olm:clusterextension:<name>" with group "olm:clusterextensions" using impersonation - When ServiceAccount.Name is specified: uses existing token-based authentication (unchanged) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent b82b593 commit 2840709

File tree

13 files changed

+63
-170
lines changed

13 files changed

+63
-170
lines changed

cmd/operator-controller/main.go

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ import (
3636
k8slabels "k8s.io/apimachinery/pkg/labels"
3737
k8stypes "k8s.io/apimachinery/pkg/types"
3838
apimachineryrand "k8s.io/apimachinery/pkg/util/rand"
39+
"k8s.io/apiserver/pkg/authentication/user"
3940
"k8s.io/client-go/discovery"
4041
"k8s.io/client-go/discovery/cached/memory"
4142
corev1client "k8s.io/client-go/kubernetes/typed/core/v1"
@@ -671,7 +672,15 @@ func setupHelm(
671672
// determine if PreAuthorizer should be enabled based on feature gate
672673
var preAuth authorization.PreAuthorizer
673674
if features.OperatorControllerFeatureGate.Enabled(features.PreflightPermissions) {
674-
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient())
675+
preAuth = authorization.NewRBACPreAuthorizer(mgr.GetClient(), func(ext *ocv1.ClusterExtension) (*user.DefaultInfo, error) {
676+
if ext.Spec.ServiceAccount.Name == "" && features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
677+
syntheticConfig := authentication.SyntheticImpersonationConfig(*ext)
678+
return &user.DefaultInfo{Name: syntheticConfig.UserName, Groups: syntheticConfig.Groups}, nil
679+
} else if ext.Spec.ServiceAccount.Name == "" || ext.Spec.Namespace == "" {
680+
return nil, errors.New("service account name and namespace must be specified")
681+
}
682+
return &user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)}, nil
683+
})
675684
}
676685

677686
cm := contentmanager.NewManager(clientRestConfigMapper, mgr.GetConfig(), mgr.GetRESTMapper())

docs/draft/howto/use-synthetic-permissions.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Synthetic user permissions enables fine-grained configuration of ClusterExtensio
88
User can not only configure RBAC permissions governing the management across all ClusterExtensions, but also on a
99
case-by-case basis.
1010

11-
### Run OLM v1with Experimental Features Enabled
11+
### Run OLM v1 with Experimental Features Enabled
1212

1313
```terminal title=Enable Experimental Features in a New Kind Cluster
1414
make run-experimental
@@ -20,10 +20,11 @@ kubectl rollout status -n olmv1-system deployment/operator-controller-controller
2020

2121
### How does it work?
2222

23-
When managing a ClusterExtension, OLM will assume the identity of user "olm:clusterextensions:<clusterextension-name>"
24-
and group "olm:clusterextensions" limiting Kubernetes API access scope to those defined for this user and group. These
25-
users and group do not exist beyond being defined in Cluster/RoleBinding(s) and can only be impersonated by clients with
26-
`impersonate` verb permissions on the `users` and `groups` resources.
23+
When managing a ClusterExtension that does not specify a service account, OLM will assume the identity of user
24+
"olm:clusterextension:<clusterextension-name>" and group "olm:clusterextensions" limiting Kubernetes API access scope
25+
to those defined for this user and group. These users and group do not exist beyond being defined in
26+
Cluster/RoleBinding(s) and can only be impersonated by clients with `impersonate` verb permissions on the `users` and
27+
`groups` resources.
2728

2829
### Demo
2930

@@ -50,7 +51,7 @@ subjects:
5051
name: "olm:clusterextensions"
5152
```
5253

53-
##### Scoped olm:clusterextension group + Added perms on specific extensions
54+
##### Scoped olm:clusterextensions group + Added perms on specific extensions
5455

5556
Give ClusterExtension management group broad permissions to manage ClusterExtensions denying potentially dangerous
5657
permissions such as being able to read cluster wide secrets:

hack/demo/resources/synthetic-user-perms/argocd-clusterextension.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ metadata:
44
name: argocd-operator
55
spec:
66
namespace: argocd-system
7-
serviceAccount:
8-
name: "olm.synthetic-user"
97
source:
108
sourceType: Catalog
119
catalog:

hack/demo/synthetic-user-cluster-admin-demo-script.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ bat --style=plain ${DEMO_RESOURCE_DIR}/synthetic-user-perms/cegroup-admin-bindin
2020
# apply cluster role binding
2121
kubectl apply -f ${DEMO_RESOURCE_DIR}/synthetic-user-perms/cegroup-admin-binding.yaml
2222

23-
# install cluster extension - for now .spec.serviceAccount = "olm.synthetic-user"
23+
# install cluster extension without specifying .spec.serviceAccount
2424
bat --style=plain ${DEMO_RESOURCE_DIR}/synthetic-user-perms/argocd-clusterextension.yaml
2525

2626
# apply cluster extension

helm/experimental.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ options:
1313
- PreflightPermissions
1414
- HelmChartSupport
1515
- BoxcutterRuntime
16+
- SyntheticPermissions
1617
disabled:
1718
- WebhookProviderOpenshiftServiceCA
1819
# List of enabled experimental features for catalogd

helm/olmv1/templates/rbac/clusterrole-operator-controller-manager-role.yml

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
{{- if and .Values.options.operatorController.enabled (not (has "BoxcutterRuntime" .Values.operatorConrollerFeatures)) }}
1+
{{- if and .Values.options.operatorController.enabled (not (has "BoxcutterRuntime" .Values.options.operatorController.features.enabled)) }}
22
apiVersion: rbac.authorization.k8s.io/v1
33
kind: ClusterRole
44
metadata:
@@ -15,6 +15,22 @@ rules:
1515
- serviceaccounts/token
1616
verbs:
1717
- create
18+
{{- if (has "SyntheticPermissions" .Values.options.operatorController.features.enabled) }}
19+
- apiGroups:
20+
- ""
21+
resources:
22+
- users
23+
verbs:
24+
- impersonate
25+
- apiGroups:
26+
- ""
27+
resources:
28+
- groups
29+
resourceNames:
30+
- olm:clusterextensions
31+
verbs:
32+
- impersonate
33+
{{- end }}
1834
- apiGroups:
1935
- apiextensions.k8s.io
2036
resources:

helm/tilt.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ options:
1717
- SingleOwnNamespaceInstallSupport
1818
- PreflightPermissions
1919
- HelmChartSupport
20+
- BoxcutterRuntime
21+
- SyntheticPermissions
2022
disabled:
2123
- WebhookProviderOpenshiftServiceCA
2224
catalogd:

internal/operator-controller/action/restconfig.go

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,15 @@ import (
1414
"github.com/operator-framework/operator-controller/internal/operator-controller/authentication"
1515
)
1616

17-
const syntheticServiceAccountName = "olm.synthetic-user"
18-
1917
// SyntheticUserRestConfigMapper returns an AuthConfigMapper that that impersonates synthetic users and groups for Object o.
20-
// o is expected to be a ClusterExtension. If the service account defined in o is different from 'olm.synthetic-user', the
21-
// defaultAuthMapper will be used
18+
// o is expected to be a ClusterExtension. If the service account is defined in o, the defaultAuthMapper will be used.
2219
func SyntheticUserRestConfigMapper(defaultAuthMapper func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error)) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
2320
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
2421
cExt, err := validate(o, c)
2522
if err != nil {
2623
return nil, err
2724
}
28-
if cExt.Spec.ServiceAccount.Name != syntheticServiceAccountName {
25+
if cExt.Spec.ServiceAccount.Name != "" {
2926
return defaultAuthMapper(ctx, cExt, c)
3027
}
3128
cc := rest.CopyConfig(c)

internal/operator-controller/action/restconfig_test.go

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -98,22 +98,10 @@ func Test_SyntheticUserRestConfigMapper_Fails(t *testing.T) {
9898
},
9999
} {
100100
t.Run(tc.description, func(t *testing.T) {
101-
tokenGetter := &authentication.TokenGetter{}
102-
saMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
101+
saMapper := action.SyntheticUserRestConfigMapper(nil)
103102
actualCfg, err := saMapper(context.Background(), tc.obj, tc.cfg)
104-
if tc.expectedError != nil {
105-
require.Nil(t, actualCfg)
106-
require.EqualError(t, err, tc.expectedError.Error())
107-
} else {
108-
require.NoError(t, err)
109-
transport, err := rest.TransportFor(actualCfg)
110-
require.NoError(t, err)
111-
require.NotNil(t, transport)
112-
tokenInjectionRoundTripper, ok := transport.(*authentication.TokenInjectingRoundTripper)
113-
require.True(t, ok)
114-
require.Equal(t, tokenGetter, tokenInjectionRoundTripper.TokenGetter)
115-
require.Equal(t, types.NamespacedName{Name: "my-service-account", Namespace: "my-namespace"}, tokenInjectionRoundTripper.Key)
116-
}
103+
require.Nil(t, actualCfg)
104+
require.EqualError(t, err, tc.expectedError.Error())
117105
})
118106
}
119107
}
@@ -150,9 +138,6 @@ func Test_SyntheticUserRestConfigMapper_UsesSyntheticAuthMapper(t *testing.T) {
150138
Name: "my-clusterextension",
151139
},
152140
Spec: ocv1.ClusterExtensionSpec{
153-
ServiceAccount: ocv1.ServiceAccountReference{
154-
Name: "olm.synthetic-user",
155-
},
156141
Namespace: "my-namespace",
157142
},
158143
}

internal/operator-controller/authorization/rbac.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,13 +55,17 @@ var namespacedCollectionVerbs = []string{"create"}
5555
var clusterCollectionVerbs = []string{"list", "watch"}
5656

5757
type rbacPreAuthorizer struct {
58+
getUserInfo userInfoMapper
5859
authorizer authorizer.Authorizer
5960
ruleResolver validation.AuthorizationRuleResolver
6061
restMapper meta.RESTMapper
6162
}
6263

63-
func NewRBACPreAuthorizer(cl client.Client) PreAuthorizer {
64+
type userInfoMapper func(extension *ocv1.ClusterExtension) (*user.DefaultInfo, error)
65+
66+
func NewRBACPreAuthorizer(cl client.Client, getUserInfo userInfoMapper) PreAuthorizer {
6467
return &rbacPreAuthorizer{
68+
getUserInfo: getUserInfo,
6569
authorizer: newRBACAuthorizer(cl),
6670
ruleResolver: newRBACRulesResolver(cl),
6771
restMapper: cl.RESTMapper(),
@@ -84,7 +88,11 @@ func (a *rbacPreAuthorizer) PreAuthorize(ctx context.Context, ext *ocv1.ClusterE
8488
if err != nil {
8589
return nil, err
8690
}
87-
manifestManager := &user.DefaultInfo{Name: fmt.Sprintf("system:serviceaccount:%s:%s", ext.Spec.Namespace, ext.Spec.ServiceAccount.Name)}
91+
92+
manifestManager, err := a.getUserInfo(ext)
93+
if err != nil {
94+
return nil, fmt.Errorf("error getting user info mapping from clusterextension: %v", err)
95+
}
8896
attributesRecords := dm.asAuthorizationAttributesRecordsForUser(manifestManager, ext)
8997

9098
var preAuthEvaluationErrors []error

0 commit comments

Comments
 (0)