Skip to content

Commit 0777e8f

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 07d20c1 commit 0777e8f

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
@@ -93,8 +93,12 @@ type Harness struct {
9393
VCRRecorderTF *recorder.Recorder
9494
VCRRecorderOauth *recorder.Recorder
9595

96-
client client.Client
97-
restConfig *rest.Config
96+
// userClient is a client for kube-apiserver, authenticating as a normal user.
97+
// The webhooks will not special case this user.
98+
userClient client.Client
99+
100+
// userRestConfig is the rest.Config that backs userClient
101+
userRestConfig *rest.Config
98102

99103
// gcpAccessToken is set to the oauth2 token to use for GCP, primarily when GCP is mocked.
100104
gcpAccessToken string
@@ -128,9 +132,9 @@ var httpRoundTripperKey httpRoundTripperKeyType
128132
// deprecated: Prefer NewHarness, which can construct a manager and mock gcp etc.
129133
func NewHarnessWithManager(ctx context.Context, t *testing.T, mgr manager.Manager) *Harness {
130134
h := &Harness{
131-
T: t,
132-
Ctx: ctx,
133-
client: mgr.GetClient(),
135+
T: t,
136+
Ctx: ctx,
137+
userClient: mgr.GetClient(),
134138
}
135139
return h
136140
}
@@ -229,6 +233,15 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
229233
if h.KubeTarget == "" {
230234
h.KubeTarget = os.Getenv("E2E_KUBE_TARGET")
231235
}
236+
237+
// userRestConfig is the restConfig for authenticating as a normal user.
238+
// The webhooks will not special-case this user.
239+
var userRestConfig *rest.Config
240+
241+
// controllerRestConfig is the restConfig for authenticating as the KCC controllers.
242+
// The webhooks special-case this user (e.g. immutable fields)
243+
var controllerRestConfig *rest.Config
244+
232245
if h.KubeTarget == "envtest" {
233246
whCfgs, err := cnrmwebhook.GetCommonWebhookConfigs()
234247
if err != nil {
@@ -244,7 +257,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
244257
testenvironment.ConfigureWebhookInstallOptions(env, whCfgs)
245258

246259
h.Logf("starting envtest apiserver")
247-
restConfig, err := env.Start()
260+
adminRestConfig, err := env.Start()
248261
if err != nil {
249262
h.Fatalf("error starting test environment: %v", err)
250263
}
@@ -255,7 +268,16 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
255268
}
256269
})
257270

258-
h.restConfig = restConfig
271+
// The admin user is not really a "normal" user, but we do want full permissions
272+
userRestConfig = adminRestConfig
273+
274+
controllerUser, err := env.ControlPlane.AddUser(envtest.User{
275+
Name: "system:serviceaccount:cnrm-system:cnrm-controller-manager",
276+
}, adminRestConfig)
277+
if err != nil {
278+
t.Fatalf("creating cnrm-controller-manager user: %v", err)
279+
}
280+
controllerRestConfig = controllerUser.Config()
259281

260282
webhookOptions := webhook.Options{
261283
Port: env.WebhookInstallOptions.LocalServingPort,
@@ -276,7 +298,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
276298
url := env.ControlPlane.GetAPIServer().SecureServing.URL("https", "debug/pprof/profile")
277299
url.RawQuery = "seconds=30"
278300
t.Logf("profiling with url %v", url)
279-
httpClient, err := rest.HTTPClientFor(restConfig)
301+
httpClient, err := rest.HTTPClientFor(adminRestConfig)
280302
if err != nil {
281303
return fmt.Errorf("building http client: %w", err)
282304
}
@@ -329,7 +351,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
329351
}
330352
})
331353

332-
h.restConfig = &rest.Config{
354+
userRestConfig = &rest.Config{
333355
Host: addr.String(),
334356
ContentConfig: rest.ContentConfig{
335357
ContentType: "application/json",
@@ -366,7 +388,8 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
366388
t := test.NewHTTPRecorder(rt, kubeEventSinks...)
367389
return t
368390
}
369-
h.restConfig.Wrap(wrapTransport)
391+
userRestConfig.Wrap(wrapTransport)
392+
controllerRestConfig.Wrap(wrapTransport)
370393
}
371394

372395
// Set up capture of GCP requests
@@ -385,12 +408,14 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
385408
h.Ctx = ctx
386409
}
387410

388-
if h.client == nil {
389-
client, err := client.New(h.restConfig, client.Options{})
411+
if h.userClient == nil {
412+
h.userRestConfig = userRestConfig
413+
414+
client, err := client.New(userRestConfig, client.Options{})
390415
if err != nil {
391416
h.Fatalf("error building client: %v", err)
392417
}
393-
h.client = client
418+
h.userClient = client
394419
}
395420

396421
logging.SetupLogger()
@@ -418,7 +443,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
418443

419444
go func() {
420445
defer wg.Done()
421-
if err := h.client.Create(ctx, crd.DeepCopy()); err != nil {
446+
if err := h.userClient.Create(ctx, crd.DeepCopy()); err != nil {
422447
errsMutex.Lock()
423448
defer errsMutex.Unlock()
424449
errs = append(errs, fmt.Errorf("error creating crd %v: %w", crd.GroupVersionKind(), err))
@@ -431,6 +456,8 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
431456
h.Fatalf("error creating crds: %v", errors.Join(errs...))
432457
}
433458
}
459+
460+
configureRBAC(h)
434461
}
435462

436463
var mockCloudGRPCClientConnection *grpc.ClientConn
@@ -440,7 +467,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
440467
if h.GCPTarget == GCPTargetModeMock {
441468
t.Logf("creating mock gcp")
442469

443-
mockCloud := mockgcp.NewMockRoundTripperForTest(t, h.client, storage.NewInMemoryStorage())
470+
mockCloud := mockgcp.NewMockRoundTripperForTest(t, h.userClient, storage.NewInMemoryStorage())
444471

445472
mockCloudGRPCClientConnection = mockCloud.NewGRPCConnection(ctx)
446473
h.MockGCP = mockCloud
@@ -722,7 +749,7 @@ func NewHarness(ctx context.Context, t *testing.T, opts ...HarnessOption) *Harne
722749

723750
krmtotf.SetUserAgentForTerraformProvider()
724751

725-
mgr, err := kccmanager.New(mgrContext, h.restConfig, kccConfig)
752+
mgr, err := kccmanager.New(mgrContext, controllerRestConfig, kccConfig)
726753
if err != nil {
727754
t.Fatalf("error creating new manager: %v", err)
728755
}
@@ -793,12 +820,15 @@ func (h *Harness) getCloudResourceManagerClient(httpClient *http.Client) *cloudr
793820
return s
794821
}
795822

823+
// GetClient returns a client for the kube-apiserver, authenticating as a normal user (not the controller)
824+
// The webhooks will not special-case this user.
796825
func (h *Harness) GetClient() client.Client {
797-
return h.client
826+
return h.userClient
798827
}
799828

800-
func (h *Harness) GetRESTConfig() *rest.Config {
801-
return h.restConfig
829+
// GetUserRestConfig returns the rest.Config that is the equivalent of GetClient()
830+
func (h *Harness) GetUserRESTConfig() *rest.Config {
831+
return h.userRestConfig
802832
}
803833

804834
func (h *Harness) GCPAuthorization() oauth2.TokenSource {
@@ -1305,3 +1335,33 @@ func (s *filterSink) WithName(name string) logr.LogSink {
13051335
func (s *filterSink) Error(err error, msg string, args ...any) {
13061336
s.sink.Error(err, msg, args...)
13071337
}
1338+
1339+
// configureRBAC will set up RBAC for the controller serviceAccount
1340+
func configureRBAC(h *Harness) {
1341+
roleBinding := &unstructured.Unstructured{}
1342+
roleBinding.SetAPIVersion("rbac.authorization.k8s.io/v1")
1343+
roleBinding.SetKind("ClusterRoleBinding")
1344+
roleBinding.SetName("manager-binding")
1345+
// TODO: should use cnrm-manager-cluster-role, but it's not readily available
1346+
// roleBinding.Object["roleRef"] = map[string]interface{}{
1347+
// "apiGroup": "rbac.authorization.k8s.io",
1348+
// "kind": "ClusterRole",
1349+
// "name": "cnrm-anager-cluster-role",
1350+
// }
1351+
1352+
roleBinding.Object["roleRef"] = map[string]interface{}{
1353+
"apiGroup": "rbac.authorization.k8s.io",
1354+
"kind": "ClusterRole",
1355+
"name": "cluster-admin",
1356+
}
1357+
roleBinding.Object["subjects"] = []map[string]interface{}{
1358+
{
1359+
"kind": "ServiceAccount",
1360+
"name": "cnrm-controller-manager",
1361+
"namespace": "cnrm-system",
1362+
},
1363+
}
1364+
if err := h.GetClient().Create(h.Ctx, roleBinding); err != nil {
1365+
h.T.Fatalf("creating cluster role binding: %v", err)
1366+
}
1367+
}

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)