Skip to content

Commit 7e665ec

Browse files
committed
tests: run controllers under special user
This is how our webhooks identify the KCC controllers, and allow them to make changes that would otherwise be immutable.
1 parent 78ce3e8 commit 7e665ec

File tree

3 files changed

+81
-21
lines changed

3 files changed

+81
-21
lines changed

config/tests/samples/create/harness.go

Lines changed: 79 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -87,8 +87,12 @@ type Harness struct {
8787
VCRRecorderTF *recorder.Recorder
8888
VCRRecorderOauth *recorder.Recorder
8989

90-
client client.Client
91-
restConfig *rest.Config
90+
// userClient is a client for kube-apiserver, authenticating as a normal user.
91+
// The webhooks will not special case this user.
92+
userClient client.Client
93+
94+
// userRestConfig is the rest.Config that backs userClient
95+
userRestConfig *rest.Config
9296

9397
// gcpAccessToken is set to the oauth2 token to use for GCP, primarily when GCP is mocked.
9498
gcpAccessToken string
@@ -122,9 +126,9 @@ var httpRoundTripperKey httpRoundTripperKeyType
122126
// deprecated: Prefer NewHarness, which can construct a manager and mock gcp etc.
123127
func NewHarnessWithManager(ctx context.Context, t *testing.T, mgr manager.Manager) *Harness {
124128
h := &Harness{
125-
T: t,
126-
Ctx: ctx,
127-
client: mgr.GetClient(),
129+
T: t,
130+
Ctx: ctx,
131+
userClient: mgr.GetClient(),
128132
}
129133
return h
130134
}
@@ -197,6 +201,15 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
197201
if h.KubeTarget == "" {
198202
h.KubeTarget = os.Getenv("E2E_KUBE_TARGET")
199203
}
204+
205+
// userRestConfig is the restConfig for authenticating as a normal user.
206+
// The webhooks will not special-case this user.
207+
var userRestConfig *rest.Config
208+
209+
// controllerRestConfig is the restConfig for authenticating as the KCC controllers.
210+
// The webhooks special-case this user (e.g. immutable fields)
211+
var controllerRestConfig *rest.Config
212+
200213
if h.KubeTarget == "envtest" {
201214
whCfgs, err := cnrmwebhook.GetCommonWebhookConfigs()
202215
if err != nil {
@@ -212,7 +225,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
212225
testenvironment.ConfigureWebhookInstallOptions(env, whCfgs)
213226

214227
h.Logf("starting envtest apiserver")
215-
restConfig, err := env.Start()
228+
adminRestConfig, err := env.Start()
216229
if err != nil {
217230
h.Fatalf("error starting test environment: %v", err)
218231
}
@@ -223,7 +236,16 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
223236
}
224237
})
225238

226-
h.restConfig = restConfig
239+
// The admin user is not really a "normal" user, but we do want full permissions
240+
userRestConfig = adminRestConfig
241+
242+
controllerUser, err := env.ControlPlane.AddUser(envtest.User{
243+
Name: "system:serviceaccount:test-namespace:cnrm-controller-manager",
244+
}, adminRestConfig)
245+
if err != nil {
246+
t.Fatalf("creating cnrm-controller-manager user: %v", err)
247+
}
248+
controllerRestConfig = controllerUser.Config()
227249

228250
webhookOptions := webhook.Options{
229251
Port: env.WebhookInstallOptions.LocalServingPort,
@@ -244,7 +266,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
244266
url := env.ControlPlane.GetAPIServer().SecureServing.URL("https", "debug/pprof/profile")
245267
url.RawQuery = "seconds=30"
246268
t.Logf("profiling with url %v", url)
247-
httpClient, err := rest.HTTPClientFor(restConfig)
269+
httpClient, err := rest.HTTPClientFor(adminRestConfig)
248270
if err != nil {
249271
return fmt.Errorf("building http client: %w", err)
250272
}
@@ -297,7 +319,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
297319
}
298320
})
299321

300-
h.restConfig = &rest.Config{
322+
userRestConfig = &rest.Config{
301323
Host: addr.String(),
302324
ContentConfig: rest.ContentConfig{
303325
ContentType: "application/json",
@@ -334,7 +356,8 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
334356
t := test.NewHTTPRecorder(rt, kubeEventSinks...)
335357
return t
336358
}
337-
h.restConfig.Wrap(wrapTransport)
359+
userRestConfig.Wrap(wrapTransport)
360+
controllerRestConfig.Wrap(wrapTransport)
338361
}
339362

340363
// Set up capture of GCP requests
@@ -353,12 +376,14 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
353376
h.Ctx = ctx
354377
}
355378

356-
if h.client == nil {
357-
client, err := client.New(h.restConfig, client.Options{})
379+
if h.userClient == nil {
380+
h.userRestConfig = userRestConfig
381+
382+
client, err := client.New(userRestConfig, client.Options{})
358383
if err != nil {
359384
h.Fatalf("error building client: %v", err)
360385
}
361-
h.client = client
386+
h.userClient = client
362387
}
363388

364389
logging.SetupLogger()
@@ -386,7 +411,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
386411

387412
go func() {
388413
defer wg.Done()
389-
if err := h.client.Create(ctx, crd.DeepCopy()); err != nil {
414+
if err := h.userClient.Create(ctx, crd.DeepCopy()); err != nil {
390415
errsMutex.Lock()
391416
defer errsMutex.Unlock()
392417
errs = append(errs, fmt.Errorf("error creating crd %v: %w", crd.GroupVersionKind(), err))
@@ -399,6 +424,8 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
399424
h.Fatalf("error creating crds: %v", errors.Join(errs...))
400425
}
401426
}
427+
428+
configureRBAC(h)
402429
}
403430

404431
var mockCloudGRPCClientConnection *grpc.ClientConn
@@ -408,7 +435,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
408435
if h.GCPTarget == GCPTargetModeMock {
409436
t.Logf("creating mock gcp")
410437

411-
mockCloud := mockgcp.NewMockRoundTripperForTest(t, h.client, storage.NewInMemoryStorage())
438+
mockCloud := mockgcp.NewMockRoundTripperForTest(t, h.userClient, storage.NewInMemoryStorage())
412439

413440
mockCloudGRPCClientConnection = mockCloud.NewGRPCConnection(ctx)
414441
h.MockGCP = mockCloud
@@ -690,7 +717,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
690717

691718
krmtotf.SetUserAgentForTerraformProvider()
692719

693-
mgr, err := kccmanager.New(mgrContext, h.restConfig, kccConfig)
720+
mgr, err := kccmanager.New(mgrContext, controllerRestConfig, kccConfig)
694721
if err != nil {
695722
t.Fatalf("error creating new manager: %v", err)
696723
}
@@ -761,12 +788,15 @@ func (h *Harness) getCloudResourceManagerClient(httpClient *http.Client) *cloudr
761788
return s
762789
}
763790

791+
// GetClient returns a client for the kube-apiserver, authenticating as a normal user (not the controller)
792+
// The webhooks will not special-case this user.
764793
func (h *Harness) GetClient() client.Client {
765-
return h.client
794+
return h.userClient
766795
}
767796

768-
func (h *Harness) GetRESTConfig() *rest.Config {
769-
return h.restConfig
797+
// GetUserRestConfig returns the rest.Config that is the equivalent of GetClient()
798+
func (h *Harness) GetUserRESTConfig() *rest.Config {
799+
return h.userRestConfig
770800
}
771801

772802
func (h *Harness) GCPAuthorization() oauth2.TokenSource {
@@ -1273,3 +1303,33 @@ func (s *filterSink) WithName(name string) logr.LogSink {
12731303
func (s *filterSink) Error(err error, msg string, args ...any) {
12741304
s.sink.Error(err, msg, args...)
12751305
}
1306+
1307+
// configureRBAC will set up RBAC for the controller serviceAccount
1308+
func configureRBAC(h *Harness) {
1309+
roleBinding := &unstructured.Unstructured{}
1310+
roleBinding.SetAPIVersion("rbac.authorization.k8s.io/v1")
1311+
roleBinding.SetKind("ClusterRoleBinding")
1312+
roleBinding.SetName("manager-binding")
1313+
// TODO: should use cnrm-manager-cluster-role, but it's not readily available
1314+
// roleBinding.Object["roleRef"] = map[string]interface{}{
1315+
// "apiGroup": "rbac.authorization.k8s.io",
1316+
// "kind": "ClusterRole",
1317+
// "name": "cnrm-anager-cluster-role",
1318+
// }
1319+
1320+
roleBinding.Object["roleRef"] = map[string]interface{}{
1321+
"apiGroup": "rbac.authorization.k8s.io",
1322+
"kind": "ClusterRole",
1323+
"name": "cluster-admin",
1324+
}
1325+
roleBinding.Object["subjects"] = []map[string]interface{}{
1326+
{
1327+
"kind": "ServiceAccount",
1328+
"name": "cnrm-controller-manager",
1329+
"namespace": "cnrm-system",
1330+
},
1331+
}
1332+
if err := h.GetClient().Create(h.Ctx, roleBinding); err != nil {
1333+
h.T.Fatalf("creating cluster role binding: %v", err)
1334+
}
1335+
}

pkg/cli/preview/preview_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ spec:
102102
}
103103

104104
// Now we can run our test ... let's run the preview mode, we expect a read of the GCP object but no write
105-
upstreamRESTConfig := harness.GetRESTConfig()
105+
upstreamRESTConfig := harness.GetUserRESTConfig()
106106

107107
recorder := NewRecorder()
108108

tests/e2e/script_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -710,7 +710,7 @@ func runCLI(h *create.Harness, args []string, uniqueID string, baseOutputPath st
710710
tempDir := t.TempDir()
711711
p := filepath.Join(tempDir, "kubeconfig")
712712

713-
kubeconfig, err := createKubeconfigFromRestConfig(h.GetRESTConfig())
713+
kubeconfig, err := createKubeconfigFromRestConfig(h.GetUserRESTConfig())
714714
if err != nil {
715715
t.Fatalf("error creating kubeconfig: %v", err)
716716
}

0 commit comments

Comments
 (0)