|
1 |
| -package action |
| 1 | +package action_test |
2 | 2 |
|
3 | 3 | import (
|
4 | 4 | "context"
|
| 5 | + "errors" |
| 6 | + "net/http" |
5 | 7 | "testing"
|
6 | 8 |
|
7 | 9 | "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" |
8 | 13 | "k8s.io/client-go/rest"
|
9 |
| - featuregatetesting "k8s.io/component-base/featuregate/testing" |
10 | 14 | "sigs.k8s.io/controller-runtime/pkg/client"
|
11 | 15 |
|
12 | 16 | 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" |
14 | 19 | )
|
15 | 20 |
|
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{}, |
33 | 55 | },
|
| 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 | + }) |
34 | 75 | }
|
35 | 76 | }
|
36 | 77 |
|
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) { |
40 | 79 | 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 |
45 | 84 | }{
|
46 | 85 | {
|
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"), |
51 | 89 | }, {
|
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"), |
56 | 98 | },
|
57 | 99 | } {
|
58 | 100 | 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) |
67 | 116 | }
|
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) |
73 | 117 | })
|
74 | 118 | }
|
75 | 119 | }
|
| 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 | +} |
76 | 143 |
|
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 | + }, |
81 | 152 | Spec: ocv1.ClusterExtensionSpec{
|
82 | 153 | ServiceAccount: ocv1.ServiceAccountReference{
|
83 | 154 | Name: "olm.synthetic-user",
|
84 | 155 | },
|
| 156 | + Namespace: "my-namespace", |
85 | 157 | },
|
86 | 158 | }
|
87 |
| - cfg, err := mapper(context.Background(), ext, &rest.Config{}) |
| 159 | + actualCfg, err := syntheticAuthServiceMapper(context.Background(), obj, &rest.Config{}) |
88 | 160 | 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) |
89 | 174 |
|
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) |
92 | 177 | }
|
0 commit comments