mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-30 06:54:01 +00:00
Merge pull request #3248 from derekwaynecarr/admission_control_hooks
Implement basic admission control framework
This commit is contained in:
commit
c2b3d678c0
@ -46,4 +46,9 @@
|
||||
{% endif -%}
|
||||
{% endif -%}
|
||||
|
||||
DAEMON_ARGS="{{daemon_args}} {{address}} {{etcd_servers}} {{ cloud_provider }} --allow_privileged={{pillar['allow_privileged']}} {{portal_net}} {{cert_file}} {{key_file}} {{secure_port}} {{token_auth_file}} {{publicAddressOverride}} {{pillar['log_level']}}"
|
||||
{% set admission_control = "" -%}
|
||||
{% if grains.admission_control is defined -%}
|
||||
{% set admission_control = "-admission_control=" + grains.admission_control -%}
|
||||
{% endif -%}
|
||||
|
||||
DAEMON_ARGS="{{daemon_args}} {{address}} {{etcd_servers}} {{ cloud_provider }} {{admission_control}} --allow_privileged={{pillar['allow_privileged']}} {{portal_net}} {{cert_file}} {{key_file}} {{secure_port}} {{token_auth_file}} {{publicAddressOverride}} {{pillar['log_level']}}"
|
||||
|
@ -75,6 +75,7 @@ grains:
|
||||
cloud_provider: vagrant
|
||||
roles:
|
||||
- kubernetes-master
|
||||
admission_control: AlwaysAdmit
|
||||
EOF
|
||||
|
||||
mkdir -p /srv/salt-overlay/pillar
|
||||
|
@ -47,6 +47,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/standalone"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"
|
||||
|
||||
@ -162,10 +163,10 @@ func startComponents(manifestURL string) (apiServerURL string) {
|
||||
EnableLogsSupport: false,
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
|
||||
ReadWritePort: portNumber,
|
||||
ReadOnlyPort: portNumber,
|
||||
PublicAddress: host,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
ReadWritePort: portNumber,
|
||||
ReadOnlyPort: portNumber,
|
||||
PublicAddress: host,
|
||||
})
|
||||
handler.delegate = m.Handler
|
||||
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
@ -63,23 +64,25 @@ var (
|
||||
"File containing x509 Certificate for HTTPS. (CA cert, if any, concatenated after server cert). "+
|
||||
"If HTTPS serving is enabled, and --tls_cert_file and --tls_private_key_file are not provided, "+
|
||||
"a self-signed certificate and key are generated for the public address and saved to /var/run/kubernetes.")
|
||||
tlsPrivateKeyFile = flag.String("tls_private_key_file", "", "File containing x509 private key matching --tls_cert_file.")
|
||||
apiPrefix = flag.String("api_prefix", "/api", "The prefix for API requests on the server. Default '/api'.")
|
||||
storageVersion = flag.String("storage_version", "", "The version to store resources with. Defaults to server preferred")
|
||||
cloudProvider = flag.String("cloud_provider", "", "The provider for cloud services. Empty string for no provider.")
|
||||
cloudConfigFile = flag.String("cloud_config", "", "The path to the cloud provider configuration file. Empty string for no configuration file.")
|
||||
healthCheckMinions = flag.Bool("health_check_minions", true, "If true, health check minions and filter unhealthy ones. Default true.")
|
||||
eventTTL = flag.Duration("event_ttl", 48*time.Hour, "Amount of time to retain events. Default 2 days.")
|
||||
tokenAuthFile = flag.String("token_auth_file", "", "If set, the file that will be used to secure the secure port of the API server via token authentication.")
|
||||
authorizationMode = flag.String("authorization_mode", "AlwaysAllow", "Selects how to do authorization on the secure port. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
|
||||
authorizationPolicyFile = flag.String("authorization_policy_file", "", "File with authorization policy in csv format, used with --authorization_mode=ABAC, on the secure port.")
|
||||
etcdServerList util.StringList
|
||||
etcdConfigFile = flag.String("etcd_config", "", "The config file for the etcd client. Mutually exclusive with -etcd_servers.")
|
||||
corsAllowedOriginList util.StringList
|
||||
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
|
||||
portalNet util.IPNet // TODO: make this a list
|
||||
enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection")
|
||||
kubeletConfig = client.KubeletConfig{
|
||||
tlsPrivateKeyFile = flag.String("tls_private_key_file", "", "File containing x509 private key matching --tls_cert_file.")
|
||||
apiPrefix = flag.String("api_prefix", "/api", "The prefix for API requests on the server. Default '/api'.")
|
||||
storageVersion = flag.String("storage_version", "", "The version to store resources with. Defaults to server preferred")
|
||||
cloudProvider = flag.String("cloud_provider", "", "The provider for cloud services. Empty string for no provider.")
|
||||
cloudConfigFile = flag.String("cloud_config", "", "The path to the cloud provider configuration file. Empty string for no configuration file.")
|
||||
healthCheckMinions = flag.Bool("health_check_minions", true, "If true, health check minions and filter unhealthy ones. Default true.")
|
||||
eventTTL = flag.Duration("event_ttl", 48*time.Hour, "Amount of time to retain events. Default 2 days.")
|
||||
tokenAuthFile = flag.String("token_auth_file", "", "If set, the file that will be used to secure the secure port of the API server via token authentication.")
|
||||
authorizationMode = flag.String("authorization_mode", "AlwaysAllow", "Selects how to do authorization on the secure port. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
|
||||
authorizationPolicyFile = flag.String("authorization_policy_file", "", "File with authorization policy in csv format, used with --authorization_mode=ABAC, on the secure port.")
|
||||
admissionControl = flag.String("admission_control", "AlwaysAdmit", "Ordered list of plug-ins to do admission control of resources into cluster. Comma-delimited list of: "+strings.Join(admission.GetPlugins(), ", "))
|
||||
admissionControlConfigFile = flag.String("admission_control_config_file", "", "File with admission control configuration.")
|
||||
etcdServerList util.StringList
|
||||
etcdConfigFile = flag.String("etcd_config", "", "The config file for the etcd client. Mutually exclusive with -etcd_servers.")
|
||||
corsAllowedOriginList util.StringList
|
||||
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
|
||||
portalNet util.IPNet // TODO: make this a list
|
||||
enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection")
|
||||
kubeletConfig = client.KubeletConfig{
|
||||
Port: 10250,
|
||||
EnableHttps: false,
|
||||
}
|
||||
@ -164,6 +167,9 @@ func main() {
|
||||
glog.Fatalf("Invalid Authorization Config: %v", err)
|
||||
}
|
||||
|
||||
admissionControlPluginNames := strings.Split(*admissionControl, ",")
|
||||
admissionController := admission.NewFromPlugins(client, admissionControlPluginNames, *admissionControlConfigFile)
|
||||
|
||||
config := &master.Config{
|
||||
Client: client,
|
||||
Cloud: cloud,
|
||||
@ -182,6 +188,7 @@ func main() {
|
||||
PublicAddress: *publicAddressOverride,
|
||||
Authenticator: authenticator,
|
||||
Authorizer: authorizer,
|
||||
AdmissionControl: admissionController,
|
||||
}
|
||||
m := master.New(config)
|
||||
|
||||
|
@ -25,4 +25,7 @@ import (
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/openstack"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
|
||||
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
||||
)
|
||||
|
53
pkg/admission/attributes.go
Normal file
53
pkg/admission/attributes.go
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
type attributesRecord struct {
|
||||
namespace string
|
||||
kind string
|
||||
operation string
|
||||
object runtime.Object
|
||||
}
|
||||
|
||||
func NewAttributesRecord(object runtime.Object, namespace, kind, operation string) Attributes {
|
||||
return &attributesRecord{
|
||||
namespace: namespace,
|
||||
kind: kind,
|
||||
operation: operation,
|
||||
object: object,
|
||||
}
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetNamespace() string {
|
||||
return record.namespace
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetKind() string {
|
||||
return record.kind
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetOperation() string {
|
||||
return record.operation
|
||||
}
|
||||
|
||||
func (record *attributesRecord) GetObject() runtime.Object {
|
||||
return record.object
|
||||
}
|
47
pkg/admission/chain.go
Normal file
47
pkg/admission/chain.go
Normal file
@ -0,0 +1,47 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
// chainAdmissionHandler is an instance of admission.Interface that performs admission control using a chain of admission handlers
|
||||
type chainAdmissionHandler []Interface
|
||||
|
||||
// New returns an admission.Interface that will enforce admission control decisions
|
||||
func NewFromPlugins(client client.Interface, pluginNames []string, configFilePath string) Interface {
|
||||
plugins := []Interface{}
|
||||
for _, pluginName := range pluginNames {
|
||||
plugin := InitPlugin(pluginName, client, configFilePath)
|
||||
if plugin != nil {
|
||||
plugins = append(plugins, plugin)
|
||||
}
|
||||
}
|
||||
return chainAdmissionHandler(plugins)
|
||||
}
|
||||
|
||||
// Admit performs an admission control check using a chain of handlers, and returns immediately on first error
|
||||
func (admissionHandler chainAdmissionHandler) Admit(a Attributes) (err error) {
|
||||
for _, handler := range admissionHandler {
|
||||
err := handler.Admit(a)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
36
pkg/admission/interfaces.go
Normal file
36
pkg/admission/interfaces.go
Normal file
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// Attributes is an interface used by AdmissionController to get information about a request
|
||||
// that is used to make an admission decision.
|
||||
type Attributes interface {
|
||||
GetNamespace() string
|
||||
GetKind() string
|
||||
GetOperation() string
|
||||
GetObject() runtime.Object
|
||||
}
|
||||
|
||||
// Interface is an abstract, pluggable interface for Admission Control decisions.
|
||||
type Interface interface {
|
||||
// Admit makes an admission decision based on the request attributes
|
||||
Admit(a Attributes) (err error)
|
||||
}
|
107
pkg/admission/plugins.go
Normal file
107
pkg/admission/plugins.go
Normal file
@ -0,0 +1,107 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admission
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// Factory is a function that returns a Interface for admission decisions.
|
||||
// The config parameter provides an io.Reader handler to the factory in
|
||||
// order to load specific configurations. If no configuration is provided
|
||||
// the parameter is nil.
|
||||
type Factory func(client client.Interface, config io.Reader) (Interface, error)
|
||||
|
||||
// All registered admission options.
|
||||
var pluginsMutex sync.Mutex
|
||||
var plugins = make(map[string]Factory)
|
||||
|
||||
// GetPlugins enumerates the
|
||||
func GetPlugins() []string {
|
||||
pluginsMutex.Lock()
|
||||
defer pluginsMutex.Unlock()
|
||||
keys := []string{}
|
||||
for k := range plugins {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
return keys
|
||||
}
|
||||
|
||||
// RegisterPlugin registers a plugin Factory by name. This
|
||||
// is expected to happen during app startup.
|
||||
func RegisterPlugin(name string, plugin Factory) {
|
||||
pluginsMutex.Lock()
|
||||
defer pluginsMutex.Unlock()
|
||||
_, found := plugins[name]
|
||||
if found {
|
||||
glog.Fatalf("Admission plugin %q was registered twice", name)
|
||||
}
|
||||
glog.V(1).Infof("Registered admission plugin %q", name)
|
||||
plugins[name] = plugin
|
||||
}
|
||||
|
||||
// GetInterface creates an instance of the named plugin, or nil if
|
||||
// the name is not known. The error return is only used if the named provider
|
||||
// was known but failed to initialize. The config parameter specifies the
|
||||
// io.Reader handler of the configuration file for the cloud provider, or nil
|
||||
// for no configuration.
|
||||
func GetPlugin(name string, client client.Interface, config io.Reader) (Interface, error) {
|
||||
pluginsMutex.Lock()
|
||||
defer pluginsMutex.Unlock()
|
||||
f, found := plugins[name]
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
return f(client, config)
|
||||
}
|
||||
|
||||
// InitPlugin creates an instance of the named interface
|
||||
func InitPlugin(name string, client client.Interface, configFilePath string) Interface {
|
||||
var config *os.File
|
||||
|
||||
if name == "" {
|
||||
glog.Info("No admission plugin specified.")
|
||||
return nil
|
||||
}
|
||||
|
||||
if configFilePath != "" {
|
||||
var err error
|
||||
|
||||
config, err = os.Open(configFilePath)
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't open admission plugin configuration %s: %#v",
|
||||
configFilePath, err)
|
||||
}
|
||||
|
||||
defer config.Close()
|
||||
}
|
||||
|
||||
plugin, err := GetPlugin(name, client, config)
|
||||
if err != nil {
|
||||
glog.Fatalf("Couldn't init admission plugin %q: %v", name, err)
|
||||
}
|
||||
if plugin == nil {
|
||||
glog.Fatalf("Unknown admission plugin: %s", name)
|
||||
}
|
||||
|
||||
return plugin
|
||||
}
|
@ -92,6 +92,20 @@ func NewAlreadyExists(kind, name string) error {
|
||||
}}
|
||||
}
|
||||
|
||||
// NewForbidden returns an error indicating the requested action was forbidden
|
||||
func NewForbidden(kind, name string, err error) error {
|
||||
return &StatusError{api.Status{
|
||||
Status: api.StatusFailure,
|
||||
Code: http.StatusForbidden,
|
||||
Reason: api.StatusReasonForbidden,
|
||||
Details: &api.StatusDetails{
|
||||
Kind: kind,
|
||||
ID: name,
|
||||
},
|
||||
Message: fmt.Sprintf("%s %q is forbidden", kind, name),
|
||||
}}
|
||||
}
|
||||
|
||||
// NewConflict returns an error indicating the item can't be updated as provided.
|
||||
func NewConflict(kind, name string, err error) error {
|
||||
return &StatusError{api.Status{
|
||||
|
@ -877,6 +877,17 @@ const (
|
||||
// Status code 202
|
||||
StatusReasonWorking StatusReason = "Working"
|
||||
|
||||
// StatusReasonForbidden means the server can be reached and understood the request, but refuses
|
||||
// to take any further action. It is the result of the server being configured to deny access for some reason
|
||||
// to the requested resource by the client.
|
||||
// Details (optional):
|
||||
// "kind" string - the kind attribute of the forbidden resource
|
||||
// on some operations may differ from the requested
|
||||
// resource.
|
||||
// "id" string - the identifier of the forbidden resource
|
||||
// Status code 403
|
||||
StatusReasonForbidden StatusReason = "Forbidden"
|
||||
|
||||
// StatusReasonNotFound means one or more resources required for this operation
|
||||
// could not be found.
|
||||
// Details (optional):
|
||||
|
@ -27,6 +27,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
|
||||
@ -55,9 +56,9 @@ const (
|
||||
// Handle returns a Handler function that exposes the provided storage interfaces
|
||||
// as RESTful resources at prefix, serialized by codec, and also includes the support
|
||||
// http resources.
|
||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker) http.Handler {
|
||||
func Handle(storage map[string]RESTStorage, codec runtime.Codec, root string, version string, selfLinker runtime.SelfLinker, admissionControl admission.Interface) http.Handler {
|
||||
prefix := root + "/" + version
|
||||
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker)
|
||||
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl)
|
||||
container := restful.NewContainer()
|
||||
mux := container.ServeMux
|
||||
group.InstallREST(container, root, version)
|
||||
@ -83,13 +84,14 @@ type APIGroupVersion struct {
|
||||
// This is a helper method for registering multiple sets of REST handlers under different
|
||||
// prefixes onto a server.
|
||||
// TODO: add multitype codec serialization
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker) *APIGroupVersion {
|
||||
func NewAPIGroupVersion(storage map[string]RESTStorage, codec runtime.Codec, canonicalPrefix string, selfLinker runtime.SelfLinker, admissionControl admission.Interface) *APIGroupVersion {
|
||||
return &APIGroupVersion{RESTHandler{
|
||||
storage: storage,
|
||||
codec: codec,
|
||||
canonicalPrefix: canonicalPrefix,
|
||||
selfLinker: selfLinker,
|
||||
ops: NewOperations(),
|
||||
storage: storage,
|
||||
codec: codec,
|
||||
canonicalPrefix: canonicalPrefix,
|
||||
selfLinker: selfLinker,
|
||||
ops: NewOperations(),
|
||||
admissionControl: admissionControl,
|
||||
// Delay just long enough to handle most simple write operations
|
||||
asyncOpWait: time.Millisecond * 25,
|
||||
}}
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
|
||||
@ -38,6 +39,8 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
|
||||
)
|
||||
|
||||
func convert(obj runtime.Object) (runtime.Object, error) {
|
||||
@ -53,6 +56,7 @@ var accessor = meta.NewAccessor()
|
||||
var versioner runtime.ResourceVersioner = accessor
|
||||
var selfLinker runtime.SelfLinker = accessor
|
||||
var mapper meta.RESTMapper
|
||||
var admissionControl admission.Interface
|
||||
|
||||
func interfacesFor(version string) (*meta.VersionInterfaces, error) {
|
||||
switch version {
|
||||
@ -92,6 +96,7 @@ func init() {
|
||||
)
|
||||
defMapper.Add(api.Scheme, true, versions...)
|
||||
mapper = defMapper
|
||||
admissionControl = admit.NewAlwaysAdmit()
|
||||
}
|
||||
|
||||
type Simple struct {
|
||||
@ -262,7 +267,7 @@ func TestNotFound(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &SimpleRESTStorage{},
|
||||
}, codec, "/prefix", testVersion, selfLinker)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -284,7 +289,7 @@ func TestNotFound(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestVersion(t *testing.T) {
|
||||
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -319,7 +324,7 @@ func TestSimpleList(t *testing.T) {
|
||||
namespace: "other",
|
||||
expectedSet: "/prefix/version/simple?namespace=other",
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -342,7 +347,7 @@ func TestErrorList(t *testing.T) {
|
||||
errors: map[string]error{"list": fmt.Errorf("test Error")},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -368,7 +373,7 @@ func TestNonEmptyList(t *testing.T) {
|
||||
},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -414,7 +419,7 @@ func TestGet(t *testing.T) {
|
||||
expectedSet: "/prefix/version/simple/id",
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -439,7 +444,7 @@ func TestGetMissing(t *testing.T) {
|
||||
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -458,7 +463,7 @@ func TestDelete(t *testing.T) {
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -474,6 +479,26 @@ func TestDelete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteInvokesAdmissionControl(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
client := http.Client{}
|
||||
request, err := http.NewRequest("DELETE", server.URL+"/prefix/version/simple/"+ID, nil)
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if response.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeleteMissing(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
ID := "id"
|
||||
@ -481,7 +506,7 @@ func TestDeleteMissing(t *testing.T) {
|
||||
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -506,7 +531,7 @@ func TestUpdate(t *testing.T) {
|
||||
t: t,
|
||||
expectedSet: "/prefix/version/simple/" + ID,
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -534,6 +559,39 @@ func TestUpdate(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateInvokesAdmissionControl(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
simpleStorage := SimpleRESTStorage{}
|
||||
ID := "id"
|
||||
storage["simple"] = &simpleStorage
|
||||
selfLinker := &setTestSelfLinker{
|
||||
t: t,
|
||||
expectedSet: "/prefix/version/simple/" + ID,
|
||||
}
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
item := &Simple{
|
||||
Other: "bar",
|
||||
}
|
||||
body, err := codec.Encode(item)
|
||||
if err != nil {
|
||||
// The following cases will fail, so die now
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
client := http.Client{}
|
||||
request, err := http.NewRequest("PUT", server.URL+"/prefix/version/simple/"+ID, bytes.NewReader(body))
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if response.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpdateMissing(t *testing.T) {
|
||||
storage := map[string]RESTStorage{}
|
||||
ID := "id"
|
||||
@ -541,7 +599,7 @@ func TestUpdateMissing(t *testing.T) {
|
||||
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
|
||||
}
|
||||
storage["simple"] = &simpleStorage
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -576,7 +634,7 @@ func TestCreate(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", testVersion, selfLinker)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
@ -612,6 +670,41 @@ func TestCreate(t *testing.T) {
|
||||
wait.Done()
|
||||
}
|
||||
|
||||
func TestCreateInvokesAdmissionControl(t *testing.T) {
|
||||
wait := sync.WaitGroup{}
|
||||
wait.Add(1)
|
||||
simpleStorage := &SimpleRESTStorage{
|
||||
injectedFunction: func(obj runtime.Object) (returnObj runtime.Object, err error) {
|
||||
wait.Wait()
|
||||
return &Simple{}, nil
|
||||
},
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", testVersion, selfLinker, deny.NewAlwaysDeny())
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
|
||||
simple := &Simple{
|
||||
Other: "foo",
|
||||
}
|
||||
data, _ := codec.Encode(simple)
|
||||
request, err := http.NewRequest("POST", server.URL+"/prefix/version/foo", bytes.NewBuffer(data))
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
response, err := client.Do(request)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if response.StatusCode != http.StatusForbidden {
|
||||
t.Errorf("Unexpected response %#v", response)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateNotFound(t *testing.T) {
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"simple": &SimpleRESTStorage{
|
||||
@ -619,7 +712,7 @@ func TestCreateNotFound(t *testing.T) {
|
||||
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
|
||||
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
|
||||
},
|
||||
}, codec, "/prefix", testVersion, selfLinker)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -687,7 +780,7 @@ func TestSyncCreate(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -760,7 +853,7 @@ func TestAsyncDelayReturnsError(t *testing.T) {
|
||||
return nil, apierrs.NewAlreadyExists("foo", "bar")
|
||||
},
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
@ -784,7 +877,7 @@ func TestAsyncCreateError(t *testing.T) {
|
||||
name: "bar",
|
||||
expectedSet: "/prefix/version/foo/bar",
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker)
|
||||
handler := Handle(map[string]RESTStorage{"foo": &storage}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
@ -884,7 +977,7 @@ func TestSyncCreateTimeout(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": &storage,
|
||||
}, codec, "/prefix", testVersion, selfLinker)
|
||||
}, codec, "/prefix", testVersion, selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -916,7 +1009,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
|
||||
}
|
||||
|
||||
handler := CORS(
|
||||
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker),
|
||||
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl),
|
||||
allowedOriginRegexps, nil, nil, "true",
|
||||
)
|
||||
server := httptest.NewServer(handler)
|
||||
|
@ -113,7 +113,7 @@ func TestOperationsList(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
@ -170,7 +170,7 @@ func TestOpGet(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
||||
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
@ -182,7 +182,7 @@ func TestProxy(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
|
@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -84,7 +84,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
|
||||
}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/prefix", "version", selfLinker)
|
||||
}, codec, "/prefix", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
@ -31,12 +32,13 @@ import (
|
||||
|
||||
// RESTHandler implements HTTP verbs on a set of RESTful resources identified by name.
|
||||
type RESTHandler struct {
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
canonicalPrefix string
|
||||
selfLinker runtime.SelfLinker
|
||||
ops *Operations
|
||||
asyncOpWait time.Duration
|
||||
storage map[string]RESTStorage
|
||||
codec runtime.Codec
|
||||
canonicalPrefix string
|
||||
selfLinker runtime.SelfLinker
|
||||
ops *Operations
|
||||
asyncOpWait time.Duration
|
||||
admissionControl admission.Interface
|
||||
}
|
||||
|
||||
// ServeHTTP handles requests to all RESTStorage objects.
|
||||
@ -205,6 +207,14 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
// invoke admission control
|
||||
err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "CREATE"))
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := storage.Create(ctx, obj)
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
@ -218,6 +228,14 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
notFound(w, req)
|
||||
return
|
||||
}
|
||||
|
||||
// invoke admission control
|
||||
err := h.admissionControl.Admit(admission.NewAttributesRecord(nil, namespace, parts[0], "DELETE"))
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := storage.Delete(ctx, parts[1])
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
@ -242,6 +260,14 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
// invoke admission control
|
||||
err = h.admissionControl.Admit(admission.NewAttributesRecord(obj, namespace, parts[0], "UPDATE"))
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
return
|
||||
}
|
||||
|
||||
out, err := storage.Update(ctx, obj)
|
||||
if err != nil {
|
||||
errorJSON(err, h.codec, w)
|
||||
|
@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) {
|
||||
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
client := http.Client{}
|
||||
@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
|
||||
@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) {
|
||||
simpleStorage := &SimpleRESTStorage{}
|
||||
handler := Handle(map[string]RESTStorage{
|
||||
"foo": simpleStorage,
|
||||
}, codec, "/api", "version", selfLinker)
|
||||
}, codec, "/api", "version", selfLinker, admissionControl)
|
||||
server := httptest.NewServer(handler)
|
||||
defer server.Close()
|
||||
defer server.CloseClientConnections()
|
||||
|
@ -28,6 +28,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
|
||||
@ -75,6 +76,7 @@ type Config struct {
|
||||
CorsAllowedOriginList util.StringList
|
||||
Authenticator authenticator.Request
|
||||
Authorizer authorizer.Authorizer
|
||||
AdmissionControl admission.Interface
|
||||
|
||||
// If specified, all web services will be registered into this container
|
||||
RestfulContainer *restful.Container
|
||||
@ -118,6 +120,7 @@ type Master struct {
|
||||
corsAllowedOriginList util.StringList
|
||||
authenticator authenticator.Request
|
||||
authorizer authorizer.Authorizer
|
||||
admissionControl admission.Interface
|
||||
masterCount int
|
||||
|
||||
readOnlyServer string
|
||||
@ -248,6 +251,7 @@ func New(c *Config) *Master {
|
||||
corsAllowedOriginList: c.CorsAllowedOriginList,
|
||||
authenticator: c.Authenticator,
|
||||
authorizer: c.Authorizer,
|
||||
admissionControl: c.AdmissionControl,
|
||||
|
||||
masterCount: c.MasterCount,
|
||||
readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))),
|
||||
@ -462,19 +466,19 @@ func (m *Master) getServersToValidate(c *Config) map[string]apiserver.Server {
|
||||
}
|
||||
|
||||
// API_v1beta1 returns the resources and codec for API version v1beta1.
|
||||
func (m *Master) API_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker) {
|
||||
func (m *Master) API_v1beta1() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker
|
||||
return storage, v1beta1.Codec, "/api/v1beta1", latest.SelfLinker, m.admissionControl
|
||||
}
|
||||
|
||||
// API_v1beta2 returns the resources and codec for API version v1beta2.
|
||||
func (m *Master) API_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker) {
|
||||
func (m *Master) API_v1beta2() (map[string]apiserver.RESTStorage, runtime.Codec, string, runtime.SelfLinker, admission.Interface) {
|
||||
storage := make(map[string]apiserver.RESTStorage)
|
||||
for k, v := range m.storage {
|
||||
storage[k] = v
|
||||
}
|
||||
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker
|
||||
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl
|
||||
}
|
||||
|
42
plugin/pkg/admission/admit/admission.go
Normal file
42
plugin/pkg/admission/admit/admission.go
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admit
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("AlwaysAdmit", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysAdmit(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysAdmit is an implementation of admission.Interface which always says yes to an admit request.
|
||||
// It is useful in tests and when using kubernetes in an open manner.
|
||||
type alwaysAdmit struct{}
|
||||
|
||||
func (alwaysAdmit) Admit(a admission.Attributes) (err error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewAlwaysAdmit() admission.Interface {
|
||||
return new(alwaysAdmit)
|
||||
}
|
29
plugin/pkg/admission/admit/admission_test.go
Normal file
29
plugin/pkg/admission/admit/admission_test.go
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 admit
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysAdmit()
|
||||
err := handler.Admit(nil)
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error returned from admission handler")
|
||||
}
|
||||
}
|
44
plugin/pkg/admission/deny/admission.go
Normal file
44
plugin/pkg/admission/deny/admission.go
Normal file
@ -0,0 +1,44 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 deny
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
apierrors "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
)
|
||||
|
||||
func init() {
|
||||
admission.RegisterPlugin("AlwaysDeny", func(client client.Interface, config io.Reader) (admission.Interface, error) {
|
||||
return NewAlwaysDeny(), nil
|
||||
})
|
||||
}
|
||||
|
||||
// alwaysDeny is an implementation of admission.Interface which always says no to an admission request.
|
||||
// It is useful in unit tests to force an operation to be forbidden.
|
||||
type alwaysDeny struct{}
|
||||
|
||||
func (alwaysDeny) Admit(a admission.Attributes) (err error) {
|
||||
return apierrors.NewForbidden(a.GetKind(), "", errors.New("Admission control is denying all modifications"))
|
||||
}
|
||||
|
||||
func NewAlwaysDeny() admission.Interface {
|
||||
return new(alwaysDeny)
|
||||
}
|
31
plugin/pkg/admission/deny/admission_test.go
Normal file
31
plugin/pkg/admission/deny/admission_test.go
Normal file
@ -0,0 +1,31 @@
|
||||
/*
|
||||
Copyright 2014 Google Inc. All rights reserved.
|
||||
|
||||
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 deny
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
|
||||
)
|
||||
|
||||
func TestAdmission(t *testing.T) {
|
||||
handler := NewAlwaysDeny()
|
||||
err := handler.Admit(admission.NewAttributesRecord(nil, "foo", "Pod", "ignored"))
|
||||
if err == nil {
|
||||
t.Errorf("Expected error returned from admission handler")
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest"
|
||||
)
|
||||
|
||||
@ -306,6 +307,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -356,6 +358,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysDenyAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -421,6 +424,7 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -480,6 +484,7 @@ func TestBobIsForbidden(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -539,6 +544,7 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: allowAliceAuthorizer{},
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -617,6 +623,7 @@ func TestNamespaceAuthorization(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -700,6 +707,7 @@ func TestKindAuthorization(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
@ -777,6 +785,7 @@ func TestReadOnlyAuthorization(t *testing.T) {
|
||||
APIPrefix: "/api",
|
||||
Authenticator: getTestTokenAuth(),
|
||||
Authorizer: a,
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
transport := http.DefaultTransport
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/pkg/version"
|
||||
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
|
||||
)
|
||||
|
||||
func init() {
|
||||
@ -56,6 +57,7 @@ func TestClient(t *testing.T) {
|
||||
EnableUISupport: false,
|
||||
APIPrefix: "/api",
|
||||
Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
|
||||
AdmissionControl: admit.NewAlwaysAdmit(),
|
||||
})
|
||||
|
||||
testCases := []string{
|
||||
|
Loading…
Reference in New Issue
Block a user