@@ -10,14 +10,20 @@ import (
1010 "testing"
1111
1212 "github.com/openshift/api/features"
13+ operatorclient "github.com/openshift/cluster-ingress-operator/pkg/operator/client"
1314 operatorcontroller "github.com/openshift/cluster-ingress-operator/pkg/operator/controller"
1415 "github.com/openshift/cluster-ingress-operator/pkg/operator/controller/gatewayclass"
1516
1617 corev1 "k8s.io/api/core/v1"
18+ apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
1719 "k8s.io/apimachinery/pkg/api/errors"
1820 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+ "k8s.io/apimachinery/pkg/types"
1922 "k8s.io/apiserver/pkg/storage/names"
23+ "k8s.io/client-go/rest"
2024
25+ "sigs.k8s.io/controller-runtime/pkg/client"
26+ "sigs.k8s.io/controller-runtime/pkg/client/config"
2127 gatewayapiv1 "sigs.k8s.io/gateway-api/apis/v1"
2228)
2329
@@ -30,6 +36,8 @@ const (
3036 expectedCatalogSourceNamespace = "openshift-marketplace"
3137 // The test gateway name used in multiple places.
3238 testGatewayName = "test-gateway"
39+ // gwapiCRDVAPName is the name of the ingress operator's Validating Admission Policy (VAP).
40+ gwapiCRDVAPName = "openshift-ingress-operator-gatewayapi-crd-admission"
3341)
3442
3543var crdNames = []string {
@@ -51,8 +59,6 @@ var defaultRoutename = ""
5159// feature gate is still in effect, preface the test names with "TestGatewayAPI"
5260// so that they run via the openshift/release test configuration.
5361func TestGatewayAPI (t * testing.T ) {
54- t .Parallel ()
55-
5662 // Skip if feature is not enabled
5763 if gatewayAPIEnabled , err := isFeatureGateEnabled (features .FeatureGateGatewayAPI ); err != nil {
5864 t .Fatalf ("error checking feature gate enabled status: %v" , err )
@@ -84,6 +90,7 @@ func TestGatewayAPI(t *testing.T) {
8490 } else {
8591 t .Log ("Gateway API Controller not enabled, skipping testGatewayAPIObjects and testGatewayAPIIstioInstallation" )
8692 }
93+ t .Run ("testGatewayAPIResourcesProtection" , testGatewayAPIResourcesProtection )
8794}
8895
8996// testGatewayAPIResources tests that Gateway API Custom Resource Definitions are available.
@@ -167,6 +174,93 @@ func testGatewayAPIObjects(t *testing.T) {
167174 }
168175}
169176
177+ // testGatewayAPIResourcesProtection verifies that the ingress operator's Validating Admission Policy
178+ // denies admission requests attempting to modify Gateway API CRDs on behalf of a user
179+ // who is not the ingress operator's service account.
180+ func testGatewayAPIResourcesProtection (t * testing.T ) {
181+ t .Helper ()
182+
183+ // Get kube client which impersonates ingress operator's service account.
184+ kubeConfig , err := config .GetConfig ()
185+ if err != nil {
186+ t .Fatalf ("failed to get kube config: %v" , err )
187+ }
188+ kubeConfig .Impersonate = rest.ImpersonationConfig {
189+ UserName : "system:serviceaccount:openshift-ingress-operator:ingress-operator" ,
190+ }
191+ kubeClient , err := operatorclient .NewClient (kubeConfig )
192+ if err != nil {
193+ t .Fatalf ("failed to to create kube client: %v" , err )
194+ }
195+
196+ // Create test CRDs.
197+ var testCRDs []* apiextensionsv1.CustomResourceDefinition
198+ for _ , name := range crdNames {
199+ testCRDs = append (testCRDs , buildGWAPICRDFromName (name ))
200+ }
201+
202+ testCases := []struct {
203+ name string
204+ kclient client.Client
205+ expectedErrMsg string
206+ }{
207+ {
208+ name : "Ingress operator service account required" ,
209+ kclient : kclient ,
210+ expectedErrMsg : "Gateway API Custom Resource Definitions are managed by the Ingress Operator and may not be modified" ,
211+ },
212+ {
213+ name : "Pod binding required" ,
214+ kclient : kubeClient ,
215+ expectedErrMsg : "this user must have both \" authentication.kubernetes.io/node-name\" and \" authentication.kubernetes.io/pod-name\" claims" ,
216+ },
217+ }
218+
219+ for _ , tc := range testCases {
220+ t .Run (tc .name , func (t * testing.T ) {
221+ // Verify that GatewayAPI CRD creation is forbidden.
222+ for i := range testCRDs {
223+ if err := tc .kclient .Create (context .Background (), testCRDs [i ]); err != nil {
224+ if ! strings .Contains (err .Error (), tc .expectedErrMsg ) {
225+ t .Errorf ("unexpected error received while creating CRD %q: %v" , testCRDs [i ].Name , err )
226+ }
227+ } else {
228+ t .Errorf ("admission error is expected while creating CRD %q but not received" , testCRDs [i ].Name )
229+ }
230+ }
231+
232+ // Verify that GatewayAPI CRD update is forbidden.
233+ for i := range testCRDs {
234+ crdName := types.NamespacedName {Name : testCRDs [i ].Name }
235+ crd := & apiextensionsv1.CustomResourceDefinition {}
236+ if err := tc .kclient .Get (context .Background (), crdName , crd ); err != nil {
237+ t .Errorf ("failed to get %q CRD: %v" , crdName .Name , err )
238+ continue
239+ }
240+ crd .Spec = testCRDs [i ].Spec
241+ if err := tc .kclient .Update (context .Background (), crd ); err != nil {
242+ if ! strings .Contains (err .Error (), tc .expectedErrMsg ) {
243+ t .Errorf ("unexpected error received while updating CRD %q: %v" , testCRDs [i ].Name , err )
244+ }
245+ } else {
246+ t .Errorf ("admission error is expected while updating CRD %q but not received" , testCRDs [i ].Name )
247+ }
248+ }
249+
250+ // Verify that GatewayAPI CRD deletion is forbidden.
251+ for i := range testCRDs {
252+ if err := tc .kclient .Delete (context .Background (), testCRDs [i ]); err != nil {
253+ if ! strings .Contains (err .Error (), tc .expectedErrMsg ) {
254+ t .Errorf ("unexpected error received while deleting CRD %q: %v" , testCRDs [i ].Name , err )
255+ }
256+ } else {
257+ t .Errorf ("admission error is expected while deleting CRD %q but not received" , testCRDs [i ].Name )
258+ }
259+ }
260+ })
261+ }
262+ }
263+
170264// ensureCRDs tests that the Gateway API custom resource definitions exist.
171265func ensureCRDs (t * testing.T ) {
172266 t .Helper ()
@@ -182,6 +276,19 @@ func ensureCRDs(t *testing.T) {
182276// deleteCRDs deletes Gateway API custom resource definitions.
183277func deleteCRDs (t * testing.T ) {
184278 t .Helper ()
279+
280+ vm := newVAPManager (t , gwapiCRDVAPName )
281+ // Remove the ingress operator's Validating Admission Policy (VAP)
282+ // which prevents modifications of Gateway API CRDs
283+ // by anything other than the ingress operator.
284+ if err , recoverFn := vm .disable (); err != nil {
285+ defer recoverFn ()
286+ t .Fatalf ("failed to disable vap: %v" , err )
287+ }
288+ // Put back the VAP to ensure that it does not prevent
289+ // the ingress operator from managing Gateway API CRDs.
290+ defer vm .enable ()
291+
185292 for _ , crdName := range crdNames {
186293 err := deleteExistingCRD (t , crdName )
187294 if err != nil {
0 commit comments