k8s.io/apiserver@v0.31.1/pkg/admission/config.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 "bytes" 21 "fmt" 22 "io" 23 "os" 24 "path" 25 "path/filepath" 26 27 "k8s.io/klog/v2" 28 "sigs.k8s.io/yaml" 29 30 "k8s.io/apimachinery/pkg/runtime" 31 "k8s.io/apimachinery/pkg/runtime/serializer" 32 "k8s.io/apimachinery/pkg/util/sets" 33 "k8s.io/apiserver/pkg/apis/apiserver" 34 apiserverv1 "k8s.io/apiserver/pkg/apis/apiserver/v1" 35 ) 36 37 func makeAbs(path, base string) (string, error) { 38 if filepath.IsAbs(path) { 39 return path, nil 40 } 41 if len(base) == 0 || base == "." { 42 cwd, err := os.Getwd() 43 if err != nil { 44 return "", err 45 } 46 base = cwd 47 } 48 return filepath.Join(base, path), nil 49 } 50 51 // ReadAdmissionConfiguration reads the admission configuration at the specified path. 52 // It returns the loaded admission configuration if the input file aligns with the required syntax. 53 // If it does not align with the provided syntax, it returns a default configuration for the enumerated 54 // set of pluginNames whose config location references the specified configFilePath. 55 // It does this to preserve backward compatibility when admission control files were opaque. 56 // It returns an error if the file did not exist. 57 func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) { 58 if configFilePath == "" { 59 return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil 60 } 61 // a file was provided, so we just read it. 62 data, err := os.ReadFile(configFilePath) 63 if err != nil { 64 return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err) 65 } 66 codecs := serializer.NewCodecFactory(configScheme) 67 decoder := codecs.UniversalDecoder() 68 decodedObj, err := runtime.Decode(decoder, data) 69 // we were able to decode the file successfully 70 if err == nil { 71 decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration) 72 if !ok { 73 return nil, fmt.Errorf("unexpected type: %T", decodedObj) 74 } 75 baseDir := path.Dir(configFilePath) 76 for i := range decodedConfig.Plugins { 77 if decodedConfig.Plugins[i].Path == "" { 78 continue 79 } 80 // we update relative file paths to absolute paths 81 absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir) 82 if err != nil { 83 return nil, err 84 } 85 decodedConfig.Plugins[i].Path = absPath 86 } 87 return configProvider{ 88 config: decodedConfig, 89 }, nil 90 } 91 // we got an error where the decode wasn't related to a missing type 92 if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) { 93 return nil, err 94 } 95 96 // Only tolerate load errors if the file appears to be one of the two legacy plugin configs 97 unstructuredData := map[string]interface{}{} 98 if err2 := yaml.Unmarshal(data, &unstructuredData); err2 != nil { 99 return nil, err 100 } 101 _, isLegacyImagePolicy := unstructuredData["imagePolicy"] 102 _, isLegacyPodNodeSelector := unstructuredData["podNodeSelectorPluginConfig"] 103 if !isLegacyImagePolicy && !isLegacyPodNodeSelector { 104 return nil, err 105 } 106 107 // convert the legacy format to the new admission control format 108 // in order to preserve backwards compatibility, we set plugins that 109 // previously read input from a non-versioned file configuration to the 110 // current input file. 111 legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector") 112 externalConfig := &apiserverv1.AdmissionConfiguration{} 113 for _, pluginName := range pluginNames { 114 if legacyPluginsWithUnversionedConfig.Has(pluginName) { 115 externalConfig.Plugins = append(externalConfig.Plugins, 116 apiserverv1.AdmissionPluginConfiguration{ 117 Name: pluginName, 118 Path: configFilePath}) 119 } 120 } 121 configScheme.Default(externalConfig) 122 internalConfig := &apiserver.AdmissionConfiguration{} 123 if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil { 124 return nil, err 125 } 126 return configProvider{ 127 config: internalConfig, 128 }, nil 129 } 130 131 type configProvider struct { 132 config *apiserver.AdmissionConfiguration 133 } 134 135 // GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration. 136 func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) { 137 // if there is a nest object, return it directly 138 if pluginCfg.Configuration != nil { 139 return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil 140 } 141 // there is nothing nested, so we delegate to path 142 if pluginCfg.Path != "" { 143 content, err := os.ReadFile(pluginCfg.Path) 144 if err != nil { 145 klog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err) 146 return nil, err 147 } 148 return bytes.NewBuffer(content), nil 149 } 150 // there is no special config at all 151 return nil, nil 152 } 153 154 // ConfigFor returns a reader for the specified plugin. 155 // If no specific configuration is present, we return a nil reader. 156 func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) { 157 // there is no config, so there is no potential config 158 if p.config == nil { 159 return nil, nil 160 } 161 // look for matching plugin and get configuration 162 for _, pluginCfg := range p.config.Plugins { 163 if pluginName != pluginCfg.Name { 164 continue 165 } 166 pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg) 167 if err != nil { 168 return nil, err 169 } 170 return pluginConfig, nil 171 } 172 // there is no registered config that matches on plugin name. 173 return nil, nil 174 }