Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement basic admission control framework #3248

Merged
merged 6 commits into from
Jan 8, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion cluster/saltbase/salt/kube-apiserver/default
Original file line number Diff line number Diff line change
Expand Up @@ -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 -%}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need an else clause like some other options have above?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. Absence of a value will just cause kube-apiserver to take the default "AlwaysAdmit"


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']}}"
1 change: 1 addition & 0 deletions cluster/vagrant/provision-master.sh
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ grains:
cloud_provider: vagrant
roles:
- kubernetes-master
admission_control: AlwaysAdmit
EOF

mkdir -p /srv/salt-overlay/pillar
Expand Down
9 changes: 5 additions & 4 deletions cmd/integration/integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down Expand Up @@ -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

Expand Down
41 changes: 24 additions & 17 deletions cmd/kube-apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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,
}
Expand Down Expand Up @@ -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,
Expand All @@ -182,6 +188,7 @@ func main() {
PublicAddress: *publicAddressOverride,
Authenticator: authenticator,
Authorizer: authorizer,
AdmissionControl: admissionController,
}
m := master.New(config)

Expand Down
3 changes: 3 additions & 0 deletions cmd/kube-apiserver/plugins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 changes: 53 additions & 0 deletions pkg/admission/attributes.go
Original file line number Diff line number Diff line change
@@ -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 changes: 47 additions & 0 deletions pkg/admission/chain.go
Original file line number Diff line number Diff line change
@@ -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 changes: 36 additions & 0 deletions pkg/admission/interfaces.go
Original file line number Diff line number Diff line change
@@ -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 changes: 107 additions & 0 deletions pkg/admission/plugins.go
Original file line number Diff line number Diff line change
@@ -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
}
14 changes: 14 additions & 0 deletions pkg/api/errors/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The authorizer returns forbidden but it doesn't use NewForbidden. Not sure if that is a problem?

// 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{
Expand Down