blob: f59d0608bcedf897aba18a6035a233fb216d84b4 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package admission
18
19import (
20 "bytes"
21 "fmt"
22 "io"
23 "io/ioutil"
24 "os"
25 "path"
26 "path/filepath"
27
28 "github.com/ghodss/yaml"
29 "github.com/golang/glog"
30
31 "k8s.io/apimachinery/pkg/runtime"
32 "k8s.io/apimachinery/pkg/runtime/serializer"
33 "k8s.io/apimachinery/pkg/util/sets"
34 "k8s.io/apiserver/pkg/apis/apiserver"
35 apiserverv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
36)
37
38func makeAbs(path, base string) (string, error) {
39 if filepath.IsAbs(path) {
40 return path, nil
41 }
42 if len(base) == 0 || base == "." {
43 cwd, err := os.Getwd()
44 if err != nil {
45 return "", err
46 }
47 base = cwd
48 }
49 return filepath.Join(base, path), nil
50}
51
52// ReadAdmissionConfiguration reads the admission configuration at the specified path.
53// It returns the loaded admission configuration if the input file aligns with the required syntax.
54// If it does not align with the provided syntax, it returns a default configuration for the enumerated
55// set of pluginNames whose config location references the specified configFilePath.
56// It does this to preserve backward compatibility when admission control files were opaque.
57// It returns an error if the file did not exist.
58func ReadAdmissionConfiguration(pluginNames []string, configFilePath string, configScheme *runtime.Scheme) (ConfigProvider, error) {
59 if configFilePath == "" {
60 return configProvider{config: &apiserver.AdmissionConfiguration{}}, nil
61 }
62 // a file was provided, so we just read it.
63 data, err := ioutil.ReadFile(configFilePath)
64 if err != nil {
65 return nil, fmt.Errorf("unable to read admission control configuration from %q [%v]", configFilePath, err)
66 }
67 codecs := serializer.NewCodecFactory(configScheme)
68 decoder := codecs.UniversalDecoder()
69 decodedObj, err := runtime.Decode(decoder, data)
70 // we were able to decode the file successfully
71 if err == nil {
72 decodedConfig, ok := decodedObj.(*apiserver.AdmissionConfiguration)
73 if !ok {
74 return nil, fmt.Errorf("unexpected type: %T", decodedObj)
75 }
76 baseDir := path.Dir(configFilePath)
77 for i := range decodedConfig.Plugins {
78 if decodedConfig.Plugins[i].Path == "" {
79 continue
80 }
81 // we update relative file paths to absolute paths
82 absPath, err := makeAbs(decodedConfig.Plugins[i].Path, baseDir)
83 if err != nil {
84 return nil, err
85 }
86 decodedConfig.Plugins[i].Path = absPath
87 }
88 return configProvider{
89 config: decodedConfig,
90 scheme: configScheme,
91 }, nil
92 }
93 // we got an error where the decode wasn't related to a missing type
94 if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
95 return nil, err
96 }
97
98 // Only tolerate load errors if the file appears to be one of the two legacy plugin configs
99 unstructuredData := map[string]interface{}{}
100 if err2 := yaml.Unmarshal(data, &unstructuredData); err2 != nil {
101 return nil, err
102 }
103 _, isLegacyImagePolicy := unstructuredData["imagePolicy"]
104 _, isLegacyPodNodeSelector := unstructuredData["podNodeSelectorPluginConfig"]
105 if !isLegacyImagePolicy && !isLegacyPodNodeSelector {
106 return nil, err
107 }
108
109 // convert the legacy format to the new admission control format
110 // in order to preserve backwards compatibility, we set plugins that
111 // previously read input from a non-versioned file configuration to the
112 // current input file.
113 legacyPluginsWithUnversionedConfig := sets.NewString("ImagePolicyWebhook", "PodNodeSelector")
114 externalConfig := &apiserverv1alpha1.AdmissionConfiguration{}
115 for _, pluginName := range pluginNames {
116 if legacyPluginsWithUnversionedConfig.Has(pluginName) {
117 externalConfig.Plugins = append(externalConfig.Plugins,
118 apiserverv1alpha1.AdmissionPluginConfiguration{
119 Name: pluginName,
120 Path: configFilePath})
121 }
122 }
123 configScheme.Default(externalConfig)
124 internalConfig := &apiserver.AdmissionConfiguration{}
125 if err := configScheme.Convert(externalConfig, internalConfig, nil); err != nil {
126 return nil, err
127 }
128 return configProvider{
129 config: internalConfig,
130 scheme: configScheme,
131 }, nil
132}
133
134type configProvider struct {
135 config *apiserver.AdmissionConfiguration
136 scheme *runtime.Scheme
137}
138
139// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
140func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
141 // if there is a nest object, return it directly
142 if pluginCfg.Configuration != nil {
143 return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil
144 }
145 // there is nothing nested, so we delegate to path
146 if pluginCfg.Path != "" {
147 content, err := ioutil.ReadFile(pluginCfg.Path)
148 if err != nil {
149 glog.Fatalf("Couldn't open admission plugin configuration %s: %#v", pluginCfg.Path, err)
150 return nil, err
151 }
152 return bytes.NewBuffer(content), nil
153 }
154 // there is no special config at all
155 return nil, nil
156}
157
158// ConfigFor returns a reader for the specified plugin.
159// If no specific configuration is present, we return a nil reader.
160func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
161 // there is no config, so there is no potential config
162 if p.config == nil {
163 return nil, nil
164 }
165 // look for matching plugin and get configuration
166 for _, pluginCfg := range p.config.Plugins {
167 if pluginName != pluginCfg.Name {
168 continue
169 }
170 pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
171 if err != nil {
172 return nil, err
173 }
174 return pluginConfig, nil
175 }
176 // there is no registered config that matches on plugin name.
177 return nil, nil
178}