Merge pull request #3248 from derekwaynecarr/admission_control_hooks

Implement basic admission control framework
This commit is contained in:
Eric Tune 2015-01-07 16:52:49 -08:00
commit c2b3d678c0
25 changed files with 634 additions and 67 deletions

View File

@ -46,4 +46,9 @@
{% endif -%} {% endif -%}
{% 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']}}"

View File

@ -75,6 +75,7 @@ grains:
cloud_provider: vagrant cloud_provider: vagrant
roles: roles:
- kubernetes-master - kubernetes-master
admission_control: AlwaysAdmit
EOF EOF
mkdir -p /srv/salt-overlay/pillar mkdir -p /srv/salt-overlay/pillar

View File

@ -47,6 +47,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/standalone" "github.com/GoogleCloudPlatform/kubernetes/pkg/standalone"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait" "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"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/scheduler/factory"
@ -162,10 +163,10 @@ func startComponents(manifestURL string) (apiServerURL string) {
EnableLogsSupport: false, EnableLogsSupport: false,
APIPrefix: "/api", APIPrefix: "/api",
Authorizer: apiserver.NewAlwaysAllowAuthorizer(), Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
AdmissionControl: admit.NewAlwaysAdmit(),
ReadWritePort: portNumber, ReadWritePort: portNumber,
ReadOnlyPort: portNumber, ReadOnlyPort: portNumber,
PublicAddress: host, PublicAddress: host,
}) })
handler.delegate = m.Handler handler.delegate = m.Handler

View File

@ -27,6 +27,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver" "github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities" "github.com/GoogleCloudPlatform/kubernetes/pkg/capabilities"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "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). "+ "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, "+ "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.") "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.") 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'.") 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") 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.") 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.") 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.") 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.") 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.") 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, ",")) 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.") 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 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(), ", "))
etcdConfigFile = flag.String("etcd_config", "", "The config file for the etcd client. Mutually exclusive with -etcd_servers.") admissionControlConfigFile = flag.String("admission_control_config_file", "", "File with admission control configuration.")
corsAllowedOriginList util.StringList etcdServerList util.StringList
allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.") etcdConfigFile = flag.String("etcd_config", "", "The config file for the etcd client. Mutually exclusive with -etcd_servers.")
portalNet util.IPNet // TODO: make this a list corsAllowedOriginList util.StringList
enableLogsSupport = flag.Bool("enable_logs_support", true, "Enables server endpoint for log collection") allowPrivileged = flag.Bool("allow_privileged", false, "If true, allow privileged containers.")
kubeletConfig = client.KubeletConfig{ 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, Port: 10250,
EnableHttps: false, EnableHttps: false,
} }
@ -164,6 +167,9 @@ func main() {
glog.Fatalf("Invalid Authorization Config: %v", err) glog.Fatalf("Invalid Authorization Config: %v", err)
} }
admissionControlPluginNames := strings.Split(*admissionControl, ",")
admissionController := admission.NewFromPlugins(client, admissionControlPluginNames, *admissionControlConfigFile)
config := &master.Config{ config := &master.Config{
Client: client, Client: client,
Cloud: cloud, Cloud: cloud,
@ -182,6 +188,7 @@ func main() {
PublicAddress: *publicAddressOverride, PublicAddress: *publicAddressOverride,
Authenticator: authenticator, Authenticator: authenticator,
Authorizer: authorizer, Authorizer: authorizer,
AdmissionControl: admissionController,
} }
m := master.New(config) m := master.New(config)

View File

@ -25,4 +25,7 @@ import (
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/openstack" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/openstack"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/ovirt"
_ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant" _ "github.com/GoogleCloudPlatform/kubernetes/pkg/cloudprovider/vagrant"
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
_ "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/deny"
) )

View 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
View 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
}

View 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
View 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
}

View File

@ -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. // NewConflict returns an error indicating the item can't be updated as provided.
func NewConflict(kind, name string, err error) error { func NewConflict(kind, name string, err error) error {
return &StatusError{api.Status{ return &StatusError{api.Status{

View File

@ -877,6 +877,17 @@ const (
// Status code 202 // Status code 202
StatusReasonWorking StatusReason = "Working" 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 // StatusReasonNotFound means one or more resources required for this operation
// could not be found. // could not be found.
// Details (optional): // Details (optional):

View File

@ -27,6 +27,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/healthz" "github.com/GoogleCloudPlatform/kubernetes/pkg/healthz"
"github.com/GoogleCloudPlatform/kubernetes/pkg/runtime" "github.com/GoogleCloudPlatform/kubernetes/pkg/runtime"
@ -55,9 +56,9 @@ const (
// Handle returns a Handler function that exposes the provided storage interfaces // Handle returns a Handler function that exposes the provided storage interfaces
// as RESTful resources at prefix, serialized by codec, and also includes the support // as RESTful resources at prefix, serialized by codec, and also includes the support
// http resources. // 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 prefix := root + "/" + version
group := NewAPIGroupVersion(storage, codec, prefix, selfLinker) group := NewAPIGroupVersion(storage, codec, prefix, selfLinker, admissionControl)
container := restful.NewContainer() container := restful.NewContainer()
mux := container.ServeMux mux := container.ServeMux
group.InstallREST(container, root, version) 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 // This is a helper method for registering multiple sets of REST handlers under different
// prefixes onto a server. // prefixes onto a server.
// TODO: add multitype codec serialization // 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{ return &APIGroupVersion{RESTHandler{
storage: storage, storage: storage,
codec: codec, codec: codec,
canonicalPrefix: canonicalPrefix, canonicalPrefix: canonicalPrefix,
selfLinker: selfLinker, selfLinker: selfLinker,
ops: NewOperations(), ops: NewOperations(),
admissionControl: admissionControl,
// Delay just long enough to handle most simple write operations // Delay just long enough to handle most simple write operations
asyncOpWait: time.Millisecond * 25, asyncOpWait: time.Millisecond * 25,
}} }}

View File

@ -30,6 +30,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors" apierrs "github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/meta"
@ -38,6 +39,8 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/util" "github.com/GoogleCloudPlatform/kubernetes/pkg/util"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/pkg/watch" "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) { func convert(obj runtime.Object) (runtime.Object, error) {
@ -53,6 +56,7 @@ var accessor = meta.NewAccessor()
var versioner runtime.ResourceVersioner = accessor var versioner runtime.ResourceVersioner = accessor
var selfLinker runtime.SelfLinker = accessor var selfLinker runtime.SelfLinker = accessor
var mapper meta.RESTMapper var mapper meta.RESTMapper
var admissionControl admission.Interface
func interfacesFor(version string) (*meta.VersionInterfaces, error) { func interfacesFor(version string) (*meta.VersionInterfaces, error) {
switch version { switch version {
@ -92,6 +96,7 @@ func init() {
) )
defMapper.Add(api.Scheme, true, versions...) defMapper.Add(api.Scheme, true, versions...)
mapper = defMapper mapper = defMapper
admissionControl = admit.NewAlwaysAdmit()
} }
type Simple struct { type Simple struct {
@ -262,7 +267,7 @@ func TestNotFound(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &SimpleRESTStorage{}, "foo": &SimpleRESTStorage{},
}, codec, "/prefix", testVersion, selfLinker) }, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -284,7 +289,7 @@ func TestNotFound(t *testing.T) {
} }
func TestVersion(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) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -319,7 +324,7 @@ func TestSimpleList(t *testing.T) {
namespace: "other", namespace: "other",
expectedSet: "/prefix/version/simple?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) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -342,7 +347,7 @@ func TestErrorList(t *testing.T) {
errors: map[string]error{"list": fmt.Errorf("test Error")}, errors: map[string]error{"list": fmt.Errorf("test Error")},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -368,7 +373,7 @@ func TestNonEmptyList(t *testing.T) {
}, },
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -414,7 +419,7 @@ func TestGet(t *testing.T) {
expectedSet: "/prefix/version/simple/id", expectedSet: "/prefix/version/simple/id",
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -439,7 +444,7 @@ func TestGetMissing(t *testing.T) {
errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")}, errors: map[string]error{"get": apierrs.NewNotFound("simple", "id")},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -458,7 +463,7 @@ func TestDelete(t *testing.T) {
simpleStorage := SimpleRESTStorage{} simpleStorage := SimpleRESTStorage{}
ID := "id" ID := "id"
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() 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) { func TestDeleteMissing(t *testing.T) {
storage := map[string]RESTStorage{} storage := map[string]RESTStorage{}
ID := "id" ID := "id"
@ -481,7 +506,7 @@ func TestDeleteMissing(t *testing.T) {
errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)}, errors: map[string]error{"delete": apierrs.NewNotFound("simple", ID)},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -506,7 +531,7 @@ func TestUpdate(t *testing.T) {
t: t, t: t,
expectedSet: "/prefix/version/simple/" + ID, expectedSet: "/prefix/version/simple/" + ID,
} }
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() 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) { func TestUpdateMissing(t *testing.T) {
storage := map[string]RESTStorage{} storage := map[string]RESTStorage{}
ID := "id" ID := "id"
@ -541,7 +599,7 @@ func TestUpdateMissing(t *testing.T) {
errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)}, errors: map[string]error{"update": apierrs.NewNotFound("simple", ID)},
} }
storage["simple"] = &simpleStorage storage["simple"] = &simpleStorage
handler := Handle(storage, codec, "/prefix", testVersion, selfLinker) handler := Handle(storage, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -576,7 +634,7 @@ func TestCreate(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", testVersion, selfLinker) }, codec, "/prefix", testVersion, selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -612,6 +670,41 @@ func TestCreate(t *testing.T) {
wait.Done() 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) { func TestCreateNotFound(t *testing.T) {
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"simple": &SimpleRESTStorage{ "simple": &SimpleRESTStorage{
@ -619,7 +712,7 @@ func TestCreateNotFound(t *testing.T) {
// See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092. // See https://github.com/GoogleCloudPlatform/kubernetes/pull/486#discussion_r15037092.
errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")}, errors: map[string]error{"create": apierrs.NewNotFound("simple", "id")},
}, },
}, codec, "/prefix", testVersion, selfLinker) }, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -687,7 +780,7 @@ func TestSyncCreate(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &storage, "foo": &storage,
}, codec, "/prefix", testVersion, selfLinker) }, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -760,7 +853,7 @@ func TestAsyncDelayReturnsError(t *testing.T) {
return nil, apierrs.NewAlreadyExists("foo", "bar") 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 handler.(*defaultAPIServer).group.handler.asyncOpWait = time.Millisecond / 2
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -784,7 +877,7 @@ func TestAsyncCreateError(t *testing.T) {
name: "bar", name: "bar",
expectedSet: "/prefix/version/foo/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 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -884,7 +977,7 @@ func TestSyncCreateTimeout(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": &storage, "foo": &storage,
}, codec, "/prefix", testVersion, selfLinker) }, codec, "/prefix", testVersion, selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -916,7 +1009,7 @@ func TestCORSAllowedOrigins(t *testing.T) {
} }
handler := CORS( handler := CORS(
Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker), Handle(map[string]RESTStorage{}, codec, "/prefix", testVersion, selfLinker, admissionControl),
allowedOriginRegexps, nil, nil, "true", allowedOriginRegexps, nil, nil, "true",
) )
server := httptest.NewServer(handler) server := httptest.NewServer(handler)

View File

@ -113,7 +113,7 @@ func TestOperationsList(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker) }, codec, "/prefix", "version", selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -170,7 +170,7 @@ func TestOpGet(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker) }, codec, "/prefix", "version", selfLinker, admissionControl)
handler.(*defaultAPIServer).group.handler.asyncOpWait = 0 handler.(*defaultAPIServer).group.handler.asyncOpWait = 0
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -182,7 +182,7 @@ func TestProxy(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker) }, codec, "/prefix", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -31,7 +31,7 @@ func TestRedirect(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker) }, codec, "/prefix", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -84,7 +84,7 @@ func TestRedirectWithNamespaces(t *testing.T) {
} }
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/prefix", "version", selfLinker) }, codec, "/prefix", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()

View File

@ -21,6 +21,7 @@ import (
"path" "path"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/httplog" "github.com/GoogleCloudPlatform/kubernetes/pkg/httplog"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
@ -31,12 +32,13 @@ import (
// RESTHandler implements HTTP verbs on a set of RESTful resources identified by name. // RESTHandler implements HTTP verbs on a set of RESTful resources identified by name.
type RESTHandler struct { type RESTHandler struct {
storage map[string]RESTStorage storage map[string]RESTStorage
codec runtime.Codec codec runtime.Codec
canonicalPrefix string canonicalPrefix string
selfLinker runtime.SelfLinker selfLinker runtime.SelfLinker
ops *Operations ops *Operations
asyncOpWait time.Duration asyncOpWait time.Duration
admissionControl admission.Interface
} }
// ServeHTTP handles requests to all RESTStorage objects. // 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) errorJSON(err, h.codec, w)
return 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) out, err := storage.Create(ctx, obj)
if err != nil { if err != nil {
errorJSON(err, h.codec, w) errorJSON(err, h.codec, w)
@ -218,6 +228,14 @@ func (h *RESTHandler) handleRESTStorage(parts []string, req *http.Request, w htt
notFound(w, req) notFound(w, req)
return 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]) out, err := storage.Delete(ctx, parts[1])
if err != nil { if err != nil {
errorJSON(err, h.codec, w) 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) errorJSON(err, h.codec, w)
return 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) out, err := storage.Update(ctx, obj)
if err != nil { if err != nil {
errorJSON(err, h.codec, w) errorJSON(err, h.codec, w)

View File

@ -50,7 +50,7 @@ func TestWatchWebsocket(t *testing.T) {
_ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work. _ = ResourceWatcher(simpleStorage) // Give compile error if this doesn't work.
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api", "version", selfLinker) }, codec, "/api", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -104,7 +104,7 @@ func TestWatchHTTP(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api", "version", selfLinker) }, codec, "/api", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
client := http.Client{} client := http.Client{}
@ -167,7 +167,7 @@ func TestWatchParamParsing(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api", "version", selfLinker) }, codec, "/api", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
@ -239,7 +239,7 @@ func TestWatchProtocolSelection(t *testing.T) {
simpleStorage := &SimpleRESTStorage{} simpleStorage := &SimpleRESTStorage{}
handler := Handle(map[string]RESTStorage{ handler := Handle(map[string]RESTStorage{
"foo": simpleStorage, "foo": simpleStorage,
}, codec, "/api", "version", selfLinker) }, codec, "/api", "version", selfLinker, admissionControl)
server := httptest.NewServer(handler) server := httptest.NewServer(handler)
defer server.Close() defer server.Close()
defer server.CloseClientConnections() defer server.CloseClientConnections()

View File

@ -28,6 +28,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/admission"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api" "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/latest"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
@ -75,6 +76,7 @@ type Config struct {
CorsAllowedOriginList util.StringList CorsAllowedOriginList util.StringList
Authenticator authenticator.Request Authenticator authenticator.Request
Authorizer authorizer.Authorizer Authorizer authorizer.Authorizer
AdmissionControl admission.Interface
// If specified, all web services will be registered into this container // If specified, all web services will be registered into this container
RestfulContainer *restful.Container RestfulContainer *restful.Container
@ -118,6 +120,7 @@ type Master struct {
corsAllowedOriginList util.StringList corsAllowedOriginList util.StringList
authenticator authenticator.Request authenticator authenticator.Request
authorizer authorizer.Authorizer authorizer authorizer.Authorizer
admissionControl admission.Interface
masterCount int masterCount int
readOnlyServer string readOnlyServer string
@ -248,6 +251,7 @@ func New(c *Config) *Master {
corsAllowedOriginList: c.CorsAllowedOriginList, corsAllowedOriginList: c.CorsAllowedOriginList,
authenticator: c.Authenticator, authenticator: c.Authenticator,
authorizer: c.Authorizer, authorizer: c.Authorizer,
admissionControl: c.AdmissionControl,
masterCount: c.MasterCount, masterCount: c.MasterCount,
readOnlyServer: net.JoinHostPort(c.PublicAddress, strconv.Itoa(int(c.ReadOnlyPort))), 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. // 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) storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage { for k, v := range m.storage {
storage[k] = v 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. // 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) storage := make(map[string]apiserver.RESTStorage)
for k, v := range m.storage { for k, v := range m.storage {
storage[k] = v storage[k] = v
} }
return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker return storage, v1beta2.Codec, "/api/v1beta2", latest.SelfLinker, m.admissionControl
} }

View 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)
}

View 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")
}
}

View 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)
}

View 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")
}
}

View File

@ -40,6 +40,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user" "github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client" "github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest" "github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/auth/authenticator/token/tokentest"
) )
@ -306,6 +307,7 @@ func TestAuthModeAlwaysAllow(t *testing.T) {
EnableUISupport: false, EnableUISupport: false,
APIPrefix: "/api", APIPrefix: "/api",
Authorizer: apiserver.NewAlwaysAllowAuthorizer(), Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -356,6 +358,7 @@ func TestAuthModeAlwaysDeny(t *testing.T) {
EnableUISupport: false, EnableUISupport: false,
APIPrefix: "/api", APIPrefix: "/api",
Authorizer: apiserver.NewAlwaysDenyAuthorizer(), Authorizer: apiserver.NewAlwaysDenyAuthorizer(),
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -421,6 +424,7 @@ func TestAliceNotForbiddenOrUnauthorized(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: allowAliceAuthorizer{}, Authorizer: allowAliceAuthorizer{},
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -480,6 +484,7 @@ func TestBobIsForbidden(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: allowAliceAuthorizer{}, Authorizer: allowAliceAuthorizer{},
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -539,6 +544,7 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: allowAliceAuthorizer{}, Authorizer: allowAliceAuthorizer{},
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -617,6 +623,7 @@ func TestNamespaceAuthorization(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: a, Authorizer: a,
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -700,6 +707,7 @@ func TestKindAuthorization(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: a, Authorizer: a,
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport
@ -777,6 +785,7 @@ func TestReadOnlyAuthorization(t *testing.T) {
APIPrefix: "/api", APIPrefix: "/api",
Authenticator: getTestTokenAuth(), Authenticator: getTestTokenAuth(),
Authorizer: a, Authorizer: a,
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
transport := http.DefaultTransport transport := http.DefaultTransport

View File

@ -30,6 +30,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels" "github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master" "github.com/GoogleCloudPlatform/kubernetes/pkg/master"
"github.com/GoogleCloudPlatform/kubernetes/pkg/version" "github.com/GoogleCloudPlatform/kubernetes/pkg/version"
"github.com/GoogleCloudPlatform/kubernetes/plugin/pkg/admission/admit"
) )
func init() { func init() {
@ -56,6 +57,7 @@ func TestClient(t *testing.T) {
EnableUISupport: false, EnableUISupport: false,
APIPrefix: "/api", APIPrefix: "/api",
Authorizer: apiserver.NewAlwaysAllowAuthorizer(), Authorizer: apiserver.NewAlwaysAllowAuthorizer(),
AdmissionControl: admit.NewAlwaysAdmit(),
}) })
testCases := []string{ testCases := []string{