k8s.io/apiserver@v0.31.1/pkg/admission/config_test.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package admission
    18  
    19  import (
    20  	"io"
    21  	"os"
    22  	"reflect"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/require"
    26  
    27  	"k8s.io/apimachinery/pkg/runtime"
    28  	"k8s.io/apimachinery/pkg/util/json"
    29  	"k8s.io/apiserver/pkg/apis/apiserver"
    30  	apiserverapiv1 "k8s.io/apiserver/pkg/apis/apiserver/v1"
    31  	apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
    32  )
    33  
    34  func TestReadAdmissionConfiguration(t *testing.T) {
    35  	// create a place holder file to hold per test config
    36  	configFile, err := os.CreateTemp("", "admission-plugin-config")
    37  	if err != nil {
    38  		t.Fatalf("unexpected err: %v", err)
    39  	}
    40  	defer os.Remove(configFile.Name())
    41  	if err = configFile.Close(); err != nil {
    42  		t.Fatalf("unexpected err: %v", err)
    43  	}
    44  	configFileName := configFile.Name()
    45  	// the location that will be fixed up to be relative to the test config file.
    46  	imagePolicyWebhookFile, err := makeAbs("image-policy-webhook.json", os.TempDir())
    47  	if err != nil {
    48  		t.Fatalf("unexpected err: %v", err)
    49  	}
    50  	// individual test scenarios
    51  	testCases := map[string]struct {
    52  		ConfigBody              string
    53  		ExpectedAdmissionConfig *apiserver.AdmissionConfiguration
    54  		PluginNames             []string
    55  	}{
    56  		"v1alpha1 configuration - path fixup": {
    57  			ConfigBody: `{
    58  "apiVersion": "apiserver.k8s.io/v1alpha1",
    59  "kind": "AdmissionConfiguration",
    60  "plugins": [
    61    {"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"},
    62    {"name": "ResourceQuota"}
    63  ]}`,
    64  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
    65  				Plugins: []apiserver.AdmissionPluginConfiguration{
    66  					{
    67  						Name: "ImagePolicyWebhook",
    68  						Path: imagePolicyWebhookFile,
    69  					},
    70  					{
    71  						Name: "ResourceQuota",
    72  					},
    73  				},
    74  			},
    75  			PluginNames: []string{},
    76  		},
    77  		"v1alpha1 configuration - abspath": {
    78  			ConfigBody: `{
    79  "apiVersion": "apiserver.k8s.io/v1alpha1",
    80  "kind": "AdmissionConfiguration",
    81  "plugins": [
    82    {"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"},
    83    {"name": "ResourceQuota"}
    84  ]}`,
    85  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
    86  				Plugins: []apiserver.AdmissionPluginConfiguration{
    87  					{
    88  						Name: "ImagePolicyWebhook",
    89  						Path: "/tmp/image-policy-webhook.json",
    90  					},
    91  					{
    92  						Name: "ResourceQuota",
    93  					},
    94  				},
    95  			},
    96  			PluginNames: []string{},
    97  		},
    98  		"v1 configuration - path fixup": {
    99  			ConfigBody: `{
   100  "apiVersion": "apiserver.config.k8s.io/v1",
   101  "kind": "AdmissionConfiguration",
   102  "plugins": [
   103    {"name": "ImagePolicyWebhook", "path": "image-policy-webhook.json"},
   104    {"name": "ResourceQuota"}
   105  ]}`,
   106  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
   107  				Plugins: []apiserver.AdmissionPluginConfiguration{
   108  					{
   109  						Name: "ImagePolicyWebhook",
   110  						Path: imagePolicyWebhookFile,
   111  					},
   112  					{
   113  						Name: "ResourceQuota",
   114  					},
   115  				},
   116  			},
   117  			PluginNames: []string{},
   118  		},
   119  		"v1 configuration - abspath": {
   120  			ConfigBody: `{
   121  "apiVersion": "apiserver.config.k8s.io/v1",
   122  "kind": "AdmissionConfiguration",
   123  "plugins": [
   124    {"name": "ImagePolicyWebhook", "path": "/tmp/image-policy-webhook.json"},
   125    {"name": "ResourceQuota"}
   126  ]}`,
   127  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
   128  				Plugins: []apiserver.AdmissionPluginConfiguration{
   129  					{
   130  						Name: "ImagePolicyWebhook",
   131  						Path: "/tmp/image-policy-webhook.json",
   132  					},
   133  					{
   134  						Name: "ResourceQuota",
   135  					},
   136  				},
   137  			},
   138  			PluginNames: []string{},
   139  		},
   140  		"legacy configuration with using legacy plugins": {
   141  			ConfigBody: `{
   142  "imagePolicy": {
   143    "kubeConfigFile": "/home/user/.kube/config",
   144    "allowTTL": 30,
   145    "denyTTL": 30,
   146    "retryBackoff": 500,
   147    "defaultAllow": true
   148  },
   149  "podNodeSelectorPluginConfig": {
   150    "clusterDefaultNodeSelector": ""
   151  }  
   152  }`,
   153  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{
   154  				Plugins: []apiserver.AdmissionPluginConfiguration{
   155  					{
   156  						Name: "ImagePolicyWebhook",
   157  						Path: configFileName,
   158  					},
   159  					{
   160  						Name: "PodNodeSelector",
   161  						Path: configFileName,
   162  					},
   163  				},
   164  			},
   165  			PluginNames: []string{"ImagePolicyWebhook", "PodNodeSelector"},
   166  		},
   167  		"legacy configuration not using legacy plugins": {
   168  			ConfigBody: `{
   169  "imagePolicy": {
   170    "kubeConfigFile": "/home/user/.kube/config",
   171    "allowTTL": 30,
   172    "denyTTL": 30,
   173    "retryBackoff": 500,
   174    "defaultAllow": true
   175  },
   176  "podNodeSelectorPluginConfig": {
   177    "clusterDefaultNodeSelector": ""
   178  }  
   179  }`,
   180  			ExpectedAdmissionConfig: &apiserver.AdmissionConfiguration{},
   181  			PluginNames:             []string{"NamespaceLifecycle", "InitialResources"},
   182  		},
   183  	}
   184  
   185  	scheme := runtime.NewScheme()
   186  	require.NoError(t, apiserver.AddToScheme(scheme))
   187  	require.NoError(t, apiserverapiv1alpha1.AddToScheme(scheme))
   188  	require.NoError(t, apiserverapiv1.AddToScheme(scheme))
   189  
   190  	for testName, testCase := range testCases {
   191  		if err = os.WriteFile(configFileName, []byte(testCase.ConfigBody), 0644); err != nil {
   192  			t.Fatalf("unexpected err writing temp file: %v", err)
   193  		}
   194  		config, err := ReadAdmissionConfiguration(testCase.PluginNames, configFileName, scheme)
   195  		if err != nil {
   196  			t.Fatalf("unexpected err: %v", err)
   197  		}
   198  		if !reflect.DeepEqual(config.(configProvider).config, testCase.ExpectedAdmissionConfig) {
   199  			t.Errorf("%s: Expected:\n\t%#v\nGot:\n\t%#v", testName, testCase.ExpectedAdmissionConfig, config.(configProvider).config)
   200  		}
   201  	}
   202  }
   203  
   204  func TestEmbeddedConfiguration(t *testing.T) {
   205  	// create a place holder file to hold per test config
   206  	configFile, err := os.CreateTemp("", "admission-plugin-config")
   207  	if err != nil {
   208  		t.Fatalf("unexpected err: %v", err)
   209  	}
   210  	defer os.Remove(configFile.Name())
   211  	if err = configFile.Close(); err != nil {
   212  		t.Fatalf("unexpected err: %v", err)
   213  	}
   214  	configFileName := configFile.Name()
   215  
   216  	testCases := map[string]struct {
   217  		ConfigBody     string
   218  		ExpectedConfig string
   219  	}{
   220  		"v1alpha1 versioned configuration": {
   221  			ConfigBody: `{
   222  				"apiVersion": "apiserver.k8s.io/v1alpha1",
   223  				"kind": "AdmissionConfiguration",
   224  				"plugins": [
   225  				  {
   226  					"name": "Foo",
   227  					"configuration": {
   228  					  "apiVersion": "foo.admission.k8s.io/v1alpha1",
   229  					  "kind": "Configuration",
   230  					  "foo": "bar"
   231  					}
   232  				  }
   233  				]}`,
   234  			ExpectedConfig: `{
   235  			  "apiVersion": "foo.admission.k8s.io/v1alpha1",
   236  			  "kind": "Configuration",
   237  			  "foo": "bar"
   238  			}`,
   239  		},
   240  		"v1alpha1 legacy configuration": {
   241  			ConfigBody: `{
   242  				"apiVersion": "apiserver.k8s.io/v1alpha1",
   243  				"kind": "AdmissionConfiguration",
   244  				"plugins": [
   245  				  {
   246  					"name": "Foo",
   247  					"configuration": {
   248  					  "foo": "bar"
   249  					}
   250  				  }
   251  				]}`,
   252  			ExpectedConfig: `{
   253  			  "foo": "bar"
   254  			}`,
   255  		},
   256  		"v1 versioned configuration": {
   257  			ConfigBody: `{
   258  				"apiVersion": "apiserver.config.k8s.io/v1",
   259  				"kind": "AdmissionConfiguration",
   260  				"plugins": [
   261  				  {
   262  					"name": "Foo",
   263  					"configuration": {
   264  					  "apiVersion": "foo.admission.k8s.io/v1alpha1",
   265  					  "kind": "Configuration",
   266  					  "foo": "bar"
   267  					}
   268  				  }
   269  				]}`,
   270  			ExpectedConfig: `{
   271  			  "apiVersion": "foo.admission.k8s.io/v1alpha1",
   272  			  "kind": "Configuration",
   273  			  "foo": "bar"
   274  			}`,
   275  		},
   276  		"v1 legacy configuration": {
   277  			ConfigBody: `{
   278  				"apiVersion": "apiserver.config.k8s.io/v1",
   279  				"kind": "AdmissionConfiguration",
   280  				"plugins": [
   281  				  {
   282  					"name": "Foo",
   283  					"configuration": {
   284  					  "foo": "bar"
   285  					}
   286  				  }
   287  				]}`,
   288  			ExpectedConfig: `{
   289  			  "foo": "bar"
   290  			}`,
   291  		},
   292  	}
   293  
   294  	for desc, test := range testCases {
   295  		scheme := runtime.NewScheme()
   296  		require.NoError(t, apiserver.AddToScheme(scheme))
   297  		require.NoError(t, apiserverapiv1alpha1.AddToScheme(scheme))
   298  		require.NoError(t, apiserverapiv1.AddToScheme(scheme))
   299  
   300  		if err = os.WriteFile(configFileName, []byte(test.ConfigBody), 0644); err != nil {
   301  			t.Errorf("[%s] unexpected err writing temp file: %v", desc, err)
   302  			continue
   303  		}
   304  		config, err := ReadAdmissionConfiguration([]string{"Foo"}, configFileName, scheme)
   305  		if err != nil {
   306  			t.Errorf("[%s] unexpected err: %v", desc, err)
   307  			continue
   308  		}
   309  		r, err := config.ConfigFor("Foo")
   310  		if err != nil {
   311  			t.Errorf("[%s] Failed to get Foo config: %v", desc, err)
   312  			continue
   313  		}
   314  		bs, err := io.ReadAll(r)
   315  		if err != nil {
   316  			t.Errorf("[%s] Failed to read Foo config data: %v", desc, err)
   317  			continue
   318  		}
   319  
   320  		if !equalJSON(test.ExpectedConfig, string(bs)) {
   321  			t.Errorf("Unexpected config: expected=%q got=%q", test.ExpectedConfig, string(bs))
   322  		}
   323  	}
   324  }
   325  
   326  func equalJSON(a, b string) bool {
   327  	var x, y interface{}
   328  	if err := json.Unmarshal([]byte(a), &x); err != nil {
   329  		return false
   330  	}
   331  	if err := json.Unmarshal([]byte(b), &y); err != nil {
   332  		return false
   333  	}
   334  	return reflect.DeepEqual(x, y)
   335  }