admission: do not leak admission config types outside of the plugins

pull/6/head
Dr. Stefan Schimanski 2017-11-27 14:44:04 +01:00
parent 2476aa410b
commit 1a552bbe14
14 changed files with 119 additions and 137 deletions

View File

@ -23,7 +23,6 @@ import (
"k8s.io/client-go/util/flowcontrol"
api "k8s.io/kubernetes/pkg/apis/core"
eventratelimitapi "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit"
eventratelimitapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/v1alpha1"
"k8s.io/kubernetes/plugin/pkg/admission/eventratelimit/apis/eventratelimit/validation"
)
@ -44,10 +43,6 @@ func Register(plugins *admission.Plugins) {
}
return newEventRateLimit(configuration, realClock{})
})
// add our config types
eventratelimitapi.AddToScheme(plugins.ConfigScheme)
eventratelimitapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// Plugin implements an admission controller that can enforce event rate limits

View File

@ -38,7 +38,6 @@ import (
"k8s.io/kubernetes/pkg/scheduler/algorithm"
"k8s.io/kubernetes/pkg/util/tolerations"
pluginapi "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction"
pluginapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/podtolerationrestriction/apis/podtolerationrestriction/v1alpha1"
)
// Register registers a plugin
@ -50,9 +49,6 @@ func Register(plugins *admission.Plugins) {
}
return NewPodTolerationsPlugin(pluginConfig), nil
})
// add our config types
pluginapi.AddToScheme(plugins.ConfigScheme)
pluginapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// The annotation keys for default and whitelist of tolerations

View File

@ -28,7 +28,6 @@ import (
kubeapiserveradmission "k8s.io/kubernetes/pkg/kubeapiserver/admission"
"k8s.io/kubernetes/pkg/quota"
resourcequotaapi "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota"
resourcequotaapiv1alpha1 "k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/v1alpha1"
"k8s.io/kubernetes/plugin/pkg/admission/resourcequota/apis/resourcequota/validation"
)
@ -49,10 +48,6 @@ func Register(plugins *admission.Plugins) {
}
return NewResourceQuota(configuration, 5, make(chan struct{}))
})
// add our config types
resourcequotaapi.AddToScheme(plugins.ConfigScheme)
resourcequotaapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// QuotaAdmission implements an admission controller that can enforce quota constraints

View File

@ -20,6 +20,7 @@ go_test(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/json:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library",
],

View File

@ -126,16 +126,10 @@ type configProvider struct {
}
// GetAdmissionPluginConfigurationFor returns a reader that holds the admission plugin configuration.
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration, scheme *runtime.Scheme) (io.Reader, error) {
// if there is nothing nested in the object, we return the named location
obj := pluginCfg.Configuration
if obj != nil {
// serialize the configuration and build a reader for it
content, err := writeYAML(obj, scheme)
if err != nil {
return nil, err
}
return bytes.NewBuffer(content), nil
func GetAdmissionPluginConfigurationFor(pluginCfg apiserver.AdmissionPluginConfiguration) (io.Reader, error) {
// if there is a nest object, return it directly
if pluginCfg.Configuration != nil {
return bytes.NewBuffer(pluginCfg.Configuration.Raw), nil
}
// there is nothing nested, so we delegate to path
if pluginCfg.Path != "" {
@ -162,7 +156,7 @@ func (p configProvider) ConfigFor(pluginName string) (io.Reader, error) {
if pluginName != pluginCfg.Name {
continue
}
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg, p.scheme)
pluginConfig, err := GetAdmissionPluginConfigurationFor(pluginCfg)
if err != nil {
return nil, err
}

View File

@ -23,6 +23,7 @@ import (
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/json"
"k8s.io/apiserver/pkg/apis/apiserver"
apiserverapi "k8s.io/apiserver/pkg/apis/apiserver"
apiserverapiv1alpha1 "k8s.io/apiserver/pkg/apis/apiserver/v1alpha1"
@ -49,7 +50,7 @@ func TestReadAdmissionConfiguration(t *testing.T) {
ExpectedAdmissionConfig *apiserver.AdmissionConfiguration
PluginNames []string
}{
"v1Alpha1 configuration - path fixup": {
"v1alpha1 configuration - path fixup": {
ConfigBody: `{
"apiVersion": "apiserver.k8s.io/v1alpha1",
"kind": "AdmissionConfiguration",
@ -70,7 +71,7 @@ func TestReadAdmissionConfiguration(t *testing.T) {
},
PluginNames: []string{},
},
"v1Alpha1 configuration - abspath": {
"v1alpha1 configuration - abspath": {
ConfigBody: `{
"apiVersion": "apiserver.k8s.io/v1alpha1",
"kind": "AdmissionConfiguration",
@ -153,3 +154,98 @@ func TestReadAdmissionConfiguration(t *testing.T) {
}
}
}
func TestEmbeddedConfiguration(t *testing.T) {
// create a place holder file to hold per test config
configFile, err := ioutil.TempFile("", "admission-plugin-config")
if err != nil {
t.Fatalf("unexpected err: %v", err)
}
if err = configFile.Close(); err != nil {
t.Fatalf("unexpected err: %v", err)
}
configFileName := configFile.Name()
testCases := map[string]struct {
ConfigBody string
ExpectedConfig string
}{
"versioned configuration": {
ConfigBody: `{
"apiVersion": "apiserver.k8s.io/v1alpha1",
"kind": "AdmissionConfiguration",
"plugins": [
{
"name": "Foo",
"configuration": {
"apiVersion": "foo.admission.k8s.io/v1alpha1",
"kind": "Configuration",
"foo": "bar"
}
}
]}`,
ExpectedConfig: `{
"apiVersion": "foo.admission.k8s.io/v1alpha1",
"kind": "Configuration",
"foo": "bar"
}`,
},
"legacy configuration": {
ConfigBody: `{
"apiVersion": "apiserver.k8s.io/v1alpha1",
"kind": "AdmissionConfiguration",
"plugins": [
{
"name": "Foo",
"configuration": {
"foo": "bar"
}
}
]}`,
ExpectedConfig: `{
"foo": "bar"
}`,
},
}
for desc, test := range testCases {
scheme := runtime.NewScheme()
apiserverapi.AddToScheme(scheme)
apiserverapiv1alpha1.AddToScheme(scheme)
if err = ioutil.WriteFile(configFileName, []byte(test.ConfigBody), 0644); err != nil {
t.Errorf("[%s] unexpected err writing temp file: %v", desc, err)
continue
}
config, err := ReadAdmissionConfiguration([]string{"Foo"}, configFileName, scheme)
if err != nil {
t.Errorf("[%s] unexpected err: %v", desc, err)
continue
}
r, err := config.ConfigFor("Foo")
if err != nil {
t.Errorf("[%s] Failed to get Foo config: %v", desc, err)
continue
}
bs, err := ioutil.ReadAll(r)
if err != nil {
t.Errorf("[%s] Failed to read Foo config data: %v", desc, err)
continue
}
if !equalJSON(test.ExpectedConfig, string(bs)) {
t.Errorf("Unexpected config: expected=%q got=%q", test.ExpectedConfig, string(bs))
}
}
}
func equalJSON(a, b string) bool {
var x, y interface{}
if err := json.Unmarshal([]byte(a), &x); err != nil {
return false
}
if err := json.Unmarshal([]byte(b), &y); err != nil {
return false
}
return reflect.DeepEqual(x, y)
}

View File

@ -23,8 +23,6 @@ go_library(
"//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/metrics:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library",
"//vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library",

View File

@ -40,8 +40,6 @@ import (
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
webhookadmissionapi "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission"
webhookadmissionapiv1alpha1 "k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1"
webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors"
"k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
"k8s.io/apiserver/pkg/admission/plugin/webhook/request"
@ -66,9 +64,6 @@ func Register(plugins *admission.Plugins) {
return plugin, nil
})
// add our config types
webhookadmissionapi.AddToScheme(plugins.ConfigScheme)
webhookadmissionapiv1alpha1.AddToScheme(plugins.ConfigScheme)
}
// WebhookSource can list dynamic webhook plugins.

View File

@ -25,8 +25,6 @@ import (
"sort"
"sync"
"k8s.io/apimachinery/pkg/runtime"
"github.com/golang/glog"
)
@ -39,16 +37,10 @@ type Factory func(config io.Reader) (Interface, error)
type Plugins struct {
lock sync.Mutex
registry map[string]Factory
// ConfigScheme is used to parse the admission plugin config file.
// It is exposed to act as a hook for extending server providing their own config.
ConfigScheme *runtime.Scheme
}
func NewPlugins() *Plugins {
return &Plugins{
ConfigScheme: runtime.NewScheme(),
}
return &Plugins{}
}
// All registered admission options.

View File

@ -46,5 +46,5 @@ type AdmissionPluginConfiguration struct {
// Configuration is an embedded configuration object to be used as the plugin's
// configuration. If present, it will be used instead of the path to the configuration file.
// +optional
Configuration runtime.Object
Configuration *runtime.Unknown
}

View File

@ -1,88 +0,0 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1alpha1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
var _ runtime.NestedObjectDecoder = &AdmissionConfiguration{}
// DecodeNestedObjects handles encoding RawExtensions on the AdmissionConfiguration, ensuring the
// objects are decoded with the provided decoder.
func (c *AdmissionConfiguration) DecodeNestedObjects(d runtime.Decoder) error {
// decoding failures result in a runtime.Unknown object being created in Object and passed
// to conversion
for k, v := range c.Plugins {
decodeNestedRawExtensionOrUnknown(d, &v.Configuration)
c.Plugins[k] = v
}
return nil
}
var _ runtime.NestedObjectEncoder = &AdmissionConfiguration{}
// EncodeNestedObjects handles encoding RawExtensions on the AdmissionConfiguration, ensuring the
// objects are encoded with the provided encoder.
func (c *AdmissionConfiguration) EncodeNestedObjects(e runtime.Encoder) error {
for k, v := range c.Plugins {
if err := encodeNestedRawExtension(e, &v.Configuration); err != nil {
return err
}
c.Plugins[k] = v
}
return nil
}
// decodeNestedRawExtensionOrUnknown decodes the raw extension into an object once. If called
// On a RawExtension that has already been decoded (has an object), it will not run again.
func decodeNestedRawExtensionOrUnknown(d runtime.Decoder, ext *runtime.RawExtension) {
if ext.Raw == nil || ext.Object != nil {
return
}
obj, gvk, err := d.Decode(ext.Raw, nil, nil)
if err != nil {
unk := &runtime.Unknown{Raw: ext.Raw}
if runtime.IsNotRegisteredError(err) {
if _, gvk, err := d.Decode(ext.Raw, nil, unk); err == nil {
unk.APIVersion = gvk.GroupVersion().String()
unk.Kind = gvk.Kind
ext.Object = unk
return
}
}
// TODO: record mime-type with the object
if gvk != nil {
unk.APIVersion = gvk.GroupVersion().String()
unk.Kind = gvk.Kind
}
obj = unk
}
ext.Object = obj
}
func encodeNestedRawExtension(e runtime.Encoder, ext *runtime.RawExtension) error {
if ext.Raw != nil || ext.Object == nil {
return nil
}
data, err := runtime.Encode(e, ext.Object)
if err != nil {
return err
}
ext.Raw = data
return nil
}

View File

@ -46,5 +46,5 @@ type AdmissionPluginConfiguration struct {
// Configuration is an embedded configuration object to be used as the plugin's
// configuration. If present, it will be used instead of the path to the configuration file.
// +optional
Configuration runtime.RawExtension `json:"configuration"`
Configuration *runtime.Unknown `json:"configuration"`
}

View File

@ -38,6 +38,13 @@ import (
"k8s.io/client-go/rest"
)
var scheme = runtime.NewScheme()
func init() {
apiserverapi.AddToScheme(scheme)
apiserverapiv1alpha1.AddToScheme(scheme)
}
// AdmissionOptions holds the admission options
type AdmissionOptions struct {
// RecommendedPluginOrder holds an ordered list of plugin names we recommend to use by default
@ -69,8 +76,6 @@ func NewAdmissionOptions() *AdmissionOptions {
RecommendedPluginOrder: []string{lifecycle.PluginName, initialization.PluginName, mutatingwebhook.PluginName, validatingwebhook.PluginName},
DefaultOffPlugins: []string{initialization.PluginName, mutatingwebhook.PluginName, validatingwebhook.PluginName},
}
apiserverapi.AddToScheme(options.Plugins.ConfigScheme)
apiserverapiv1alpha1.AddToScheme(options.Plugins.ConfigScheme)
server.RegisterAllAdmissionPlugins(options.Plugins)
return options
}
@ -120,7 +125,7 @@ func (a *AdmissionOptions) ApplyTo(
pluginNames = a.enabledPluginNames()
}
pluginsConfigProvider, err := admission.ReadAdmissionConfiguration(pluginNames, a.ConfigFile, a.Plugins.ConfigScheme)
pluginsConfigProvider, err := admission.ReadAdmissionConfiguration(pluginNames, a.ConfigFile, scheme)
if err != nil {
return fmt.Errorf("failed to read plugin config: %v", err)
}

View File

@ -25,7 +25,10 @@ filegroup(
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
srcs = [
":package-srcs",
"//vendor/github.com/jmespath/go-jmespath/cmd/jpgo:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)