Skip to content

Commit fcc4a15

Browse files
Per Goncalves da Silvaperdasilva
Per Goncalves da Silva
authored andcommitted
Move FG check to main
Signed-off-by: Per Goncalves da Silva <[email protected]>
1 parent 515cc48 commit fcc4a15

File tree

3 files changed

+179
-88
lines changed

3 files changed

+179
-88
lines changed

cmd/operator-controller/main.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,10 @@ func run() error {
304304
return err
305305
}
306306
tokenGetter := authentication.NewTokenGetter(coreClient, authentication.WithExpirationDuration(1*time.Hour))
307-
clientRestConfigMapper := action.ClusterExtensionUserRestConfigMapper(tokenGetter)
307+
clientRestConfigMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
308+
if features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions) {
309+
clientRestConfigMapper = action.SyntheticUserRestConfigMapper(clientRestConfigMapper)
310+
}
308311

309312
cfgGetter, err := helmclient.NewActionConfigGetter(mgr.GetConfig(), mgr.GetRESTMapper(),
310313
helmclient.StorageDriverMapper(action.ChunkedStorageDriverMapper(coreClient, mgr.GetAPIReader(), cfg.systemNamespace)),

internal/operator-controller/action/restconfig.go

+35-32
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package action
22

33
import (
44
"context"
5+
"fmt"
56
"net/http"
67

78
"k8s.io/apimachinery/pkg/types"
@@ -11,38 +12,22 @@ import (
1112

1213
ocv1 "github.com/operator-framework/operator-controller/api/v1"
1314
"github.com/operator-framework/operator-controller/internal/operator-controller/authentication"
14-
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
1515
)
1616

1717
const syntheticServiceAccountName = "olm.synthetic-user"
1818

19-
type clusterExtensionRestConfigMapper struct {
20-
saRestConfigMapper func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error)
21-
synthUserRestConfigMapper func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error)
22-
}
23-
24-
func (m *clusterExtensionRestConfigMapper) mapper() func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
25-
synthAuthFeatureEnabled := features.OperatorControllerFeatureGate.Enabled(features.SyntheticPermissions)
19+
// 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
22+
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) {
2623
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
27-
cExt := o.(*ocv1.ClusterExtension)
28-
if synthAuthFeatureEnabled && cExt.Spec.ServiceAccount.Name == syntheticServiceAccountName {
29-
return m.synthUserRestConfigMapper(ctx, o, c)
24+
cExt, err := validate(o, c)
25+
if err != nil {
26+
return nil, err
27+
}
28+
if cExt.Spec.ServiceAccount.Name != syntheticServiceAccountName {
29+
return defaultAuthMapper(ctx, cExt, c)
3030
}
31-
return m.saRestConfigMapper(ctx, o, c)
32-
}
33-
}
34-
35-
func ClusterExtensionUserRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
36-
m := &clusterExtensionRestConfigMapper{
37-
saRestConfigMapper: serviceAccountRestConfigMapper(tokenGetter),
38-
synthUserRestConfigMapper: syntheticUserRestConfigMapper(),
39-
}
40-
return m.mapper()
41-
}
42-
43-
func syntheticUserRestConfigMapper() func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
44-
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
45-
cExt := o.(*ocv1.ClusterExtension)
4631
cc := rest.CopyConfig(c)
4732
cc.Wrap(func(rt http.RoundTripper) http.RoundTripper {
4833
return transport.NewImpersonatingRoundTripper(authentication.SyntheticImpersonationConfig(*cExt), rt)
@@ -51,21 +36,39 @@ func syntheticUserRestConfigMapper() func(ctx context.Context, o client.Object,
5136
}
5237
}
5338

54-
func serviceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
39+
// ServiceAccountRestConfigMapper returns an AuthConfigMapper scoped to the service account defined in o, which is expected to
40+
// be a ClusterExtension
41+
func ServiceAccountRestConfigMapper(tokenGetter *authentication.TokenGetter) func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
5542
return func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
56-
cExt := o.(*ocv1.ClusterExtension)
57-
saKey := types.NamespacedName{
58-
Name: cExt.Spec.ServiceAccount.Name,
59-
Namespace: cExt.Spec.Namespace,
43+
cExt, err := validate(o, c)
44+
if err != nil {
45+
return nil, err
6046
}
6147
saConfig := rest.AnonymousClientConfig(c)
6248
saConfig.Wrap(func(rt http.RoundTripper) http.RoundTripper {
6349
return &authentication.TokenInjectingRoundTripper{
6450
Tripper: rt,
6551
TokenGetter: tokenGetter,
66-
Key: saKey,
52+
Key: types.NamespacedName{
53+
Name: cExt.Spec.ServiceAccount.Name,
54+
Namespace: cExt.Spec.Namespace,
55+
},
6756
}
6857
})
6958
return saConfig, nil
7059
}
7160
}
61+
62+
func validate(o client.Object, c *rest.Config) (*ocv1.ClusterExtension, error) {
63+
if c == nil {
64+
return nil, fmt.Errorf("rest config is nil")
65+
}
66+
if o == nil {
67+
return nil, fmt.Errorf("object is nil")
68+
}
69+
cExt, ok := o.(*ocv1.ClusterExtension)
70+
if !ok {
71+
return nil, fmt.Errorf("object is not a ClusterExtension")
72+
}
73+
return cExt, nil
74+
}
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,177 @@
1-
package action
1+
package action_test
22

33
import (
44
"context"
5+
"errors"
6+
"net/http"
57
"testing"
68

79
"github.com/stretchr/testify/require"
10+
corev1 "k8s.io/api/core/v1"
11+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
12+
"k8s.io/apimachinery/pkg/types"
813
"k8s.io/client-go/rest"
9-
featuregatetesting "k8s.io/component-base/featuregate/testing"
1014
"sigs.k8s.io/controller-runtime/pkg/client"
1115

1216
ocv1 "github.com/operator-framework/operator-controller/api/v1"
13-
"github.com/operator-framework/operator-controller/internal/operator-controller/features"
17+
"github.com/operator-framework/operator-controller/internal/operator-controller/action"
18+
"github.com/operator-framework/operator-controller/internal/operator-controller/authentication"
1419
)
1520

16-
const (
17-
saAccountWrapper = "service account wrapper"
18-
synthUserWrapper = "synthetic user wrapper"
19-
)
20-
21-
func fakeRestConfigWrapper() clusterExtensionRestConfigMapper {
22-
// The rest config's host field is artificially used to differentiate between the wrappers
23-
return clusterExtensionRestConfigMapper{
24-
saRestConfigMapper: func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
25-
return &rest.Config{
26-
Host: saAccountWrapper,
27-
}, nil
28-
},
29-
synthUserRestConfigMapper: func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
30-
return &rest.Config{
31-
Host: synthUserWrapper,
32-
}, nil
21+
func Test_ServiceAccountRestConfigMapper(t *testing.T) {
22+
for _, tc := range []struct {
23+
description string
24+
obj client.Object
25+
cfg *rest.Config
26+
expectedError error
27+
}{
28+
{
29+
description: "return error if object is nil",
30+
cfg: &rest.Config{},
31+
expectedError: errors.New("object is nil"),
32+
}, {
33+
description: "return error if cfg is nil",
34+
obj: &ocv1.ClusterExtension{},
35+
expectedError: errors.New("rest config is nil"),
36+
}, {
37+
description: "return error if object is not a ClusterExtension",
38+
obj: &corev1.Secret{},
39+
cfg: &rest.Config{},
40+
expectedError: errors.New("object is not a ClusterExtension"),
41+
}, {
42+
description: "succeeds if object is not a ClusterExtension",
43+
obj: &ocv1.ClusterExtension{
44+
ObjectMeta: metav1.ObjectMeta{
45+
Name: "my-clusterextension",
46+
},
47+
Spec: ocv1.ClusterExtensionSpec{
48+
ServiceAccount: ocv1.ServiceAccountReference{
49+
Name: "my-service-account",
50+
},
51+
Namespace: "my-namespace",
52+
},
53+
},
54+
cfg: &rest.Config{},
3355
},
56+
} {
57+
t.Run(tc.description, func(t *testing.T) {
58+
tokenGetter := &authentication.TokenGetter{}
59+
saMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
60+
actualCfg, err := saMapper(context.Background(), tc.obj, tc.cfg)
61+
if tc.expectedError != nil {
62+
require.Nil(t, actualCfg)
63+
require.EqualError(t, err, tc.expectedError.Error())
64+
} else {
65+
require.NoError(t, err)
66+
transport, err := rest.TransportFor(actualCfg)
67+
require.NoError(t, err)
68+
require.NotNil(t, transport)
69+
tokenInjectionRoundTripper, ok := transport.(*authentication.TokenInjectingRoundTripper)
70+
require.True(t, ok)
71+
require.Equal(t, tokenGetter, tokenInjectionRoundTripper.TokenGetter)
72+
require.Equal(t, types.NamespacedName{Name: "my-service-account", Namespace: "my-namespace"}, tokenInjectionRoundTripper.Key)
73+
}
74+
})
3475
}
3576
}
3677

37-
func TestMapper_SyntheticPermissionsEnabled(t *testing.T) {
38-
featuregatetesting.SetFeatureGateDuringTest(t, features.OperatorControllerFeatureGate, features.SyntheticPermissions, true)
39-
78+
func Test_SyntheticUserRestConfigMapper_Fails(t *testing.T) {
4079
for _, tc := range []struct {
41-
description string
42-
serviceAccountName string
43-
expectedMapper string
44-
fgEnabled bool
80+
description string
81+
obj client.Object
82+
cfg *rest.Config
83+
expectedError error
4584
}{
4685
{
47-
description: "user service account wrapper if extension service account is _not_ called olm.synthetic-user",
48-
serviceAccountName: "not.olm.synthetic-user",
49-
expectedMapper: saAccountWrapper,
50-
fgEnabled: true,
86+
description: "return error if object is nil",
87+
cfg: &rest.Config{},
88+
expectedError: errors.New("object is nil"),
5189
}, {
52-
description: "user synthetic user wrapper is extension service account is called olm.synthetic-user",
53-
serviceAccountName: "olm.synthetic-user",
54-
expectedMapper: synthUserWrapper,
55-
fgEnabled: true,
90+
description: "return error if cfg is nil",
91+
obj: &ocv1.ClusterExtension{},
92+
expectedError: errors.New("rest config is nil"),
93+
}, {
94+
description: "return error if object is not a ClusterExtension",
95+
obj: &corev1.Secret{},
96+
cfg: &rest.Config{},
97+
expectedError: errors.New("object is not a ClusterExtension"),
5698
},
5799
} {
58100
t.Run(tc.description, func(t *testing.T) {
59-
m := fakeRestConfigWrapper()
60-
mapper := m.mapper()
61-
ext := &ocv1.ClusterExtension{
62-
Spec: ocv1.ClusterExtensionSpec{
63-
ServiceAccount: ocv1.ServiceAccountReference{
64-
Name: tc.serviceAccountName,
65-
},
66-
},
101+
tokenGetter := &authentication.TokenGetter{}
102+
saMapper := action.ServiceAccountRestConfigMapper(tokenGetter)
103+
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)
67116
}
68-
cfg, err := mapper(context.Background(), ext, &rest.Config{})
69-
require.NoError(t, err)
70-
71-
// The rest config's host field is artificially used to differentiate between the wrappers
72-
require.Equal(t, tc.expectedMapper, cfg.Host)
73117
})
74118
}
75119
}
120+
func Test_SyntheticUserRestConfigMapper_UsesDefaultConfigMapper(t *testing.T) {
121+
isDefaultRequestMapperUsed := false
122+
defaultServiceMapper := func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
123+
isDefaultRequestMapperUsed = true
124+
return c, nil
125+
}
126+
syntheticAuthServiceMapper := action.SyntheticUserRestConfigMapper(defaultServiceMapper)
127+
obj := &ocv1.ClusterExtension{
128+
ObjectMeta: metav1.ObjectMeta{
129+
Name: "my-clusterextension",
130+
},
131+
Spec: ocv1.ClusterExtensionSpec{
132+
ServiceAccount: ocv1.ServiceAccountReference{
133+
Name: "my-service-account",
134+
},
135+
Namespace: "my-namespace",
136+
},
137+
}
138+
actualCfg, err := syntheticAuthServiceMapper(context.Background(), obj, &rest.Config{})
139+
require.NoError(t, err)
140+
require.NotNil(t, actualCfg)
141+
require.True(t, isDefaultRequestMapperUsed)
142+
}
76143

77-
func TestMapper_SyntheticPermissionsDisabled(t *testing.T) {
78-
m := fakeRestConfigWrapper()
79-
mapper := m.mapper()
80-
ext := &ocv1.ClusterExtension{
144+
func Test_SyntheticUserRestConfigMapper_UsesSyntheticAuthMapper(t *testing.T) {
145+
syntheticAuthServiceMapper := action.SyntheticUserRestConfigMapper(func(ctx context.Context, o client.Object, c *rest.Config) (*rest.Config, error) {
146+
return c, nil
147+
})
148+
obj := &ocv1.ClusterExtension{
149+
ObjectMeta: metav1.ObjectMeta{
150+
Name: "my-clusterextension",
151+
},
81152
Spec: ocv1.ClusterExtensionSpec{
82153
ServiceAccount: ocv1.ServiceAccountReference{
83154
Name: "olm.synthetic-user",
84155
},
156+
Namespace: "my-namespace",
85157
},
86158
}
87-
cfg, err := mapper(context.Background(), ext, &rest.Config{})
159+
actualCfg, err := syntheticAuthServiceMapper(context.Background(), obj, &rest.Config{})
88160
require.NoError(t, err)
161+
require.NotNil(t, actualCfg)
162+
163+
// test that the impersonation headers are appropriately injected into the request
164+
// by wrapping a fake round tripper around the returned configurations transport
165+
// nolint:bodyclose
166+
_, _ = actualCfg.WrapTransport(fakeRoundTripper(func(req *http.Request) (*http.Response, error) {
167+
require.Equal(t, "olm:clusterextension:my-clusterextension", req.Header.Get("Impersonate-User"))
168+
require.Equal(t, "olm:clusterextensions", req.Header.Get("Impersonate-Group"))
169+
return &http.Response{}, nil
170+
})).RoundTrip(&http.Request{})
171+
}
172+
173+
type fakeRoundTripper func(req *http.Request) (*http.Response, error)
89174

90-
// The rest config's host field is artificially used to differentiate between the wrappers
91-
require.Equal(t, saAccountWrapper, cfg.Host)
175+
func (f fakeRoundTripper) RoundTrip(request *http.Request) (*http.Response, error) {
176+
return f(request)
92177
}

0 commit comments

Comments
 (0)