Basic ACL file.

Added function to read basic ACL from a CSV file.
Added implementation of Authorize based on that file's policies.
Added docs on authentication and authorization.
Added example file and tested it.
pull/6/head
Eric Tune 2014-10-06 16:11:04 -07:00
parent f4cffdc7cf
commit 6e81e8c896
9 changed files with 457 additions and 57 deletions

View File

@ -57,21 +57,22 @@ var (
"The port from which to serve read-only resources. If 0, don't serve on a "+
"read-only address. It is assumed that firewall rules are set up such that "+
"this port is not reachable from outside of the cluster.")
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 API server via token authentication.")
authorizationMode = flag.String("authorization_mode", "AlwaysAllow", "Selects how to do authorization. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
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{
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 API server via token authentication.")
authorizationMode = flag.String("authorization_mode", "AlwaysAllow", "Selects how to do authorization. One of: "+strings.Join(apiserver.AuthorizationModeChoices, ","))
authorizationPolicyFile = flag.String("authorization_policy_file", "", "File with authorization policy in csv format, used with --authorization_mode=ABAC.")
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,
}
@ -146,7 +147,7 @@ func main() {
n := net.IPNet(portalNet)
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(*authorizationMode)
authorizer, err := apiserver.NewAuthorizerFromAuthorizationConfig(*authorizationMode, *authorizationPolicyFile)
if err != nil {
glog.Fatalf("Invalid Authorization Config: %v", err)
}

19
docs/authentication.md Normal file
View File

@ -0,0 +1,19 @@
# Authentication Plugins
Kubernetes uses tokens to authenticate users for API calls.
Authentication is enabled by passing the `--token_auth_file=SOMEFILE` option
to apiserver. Currently, tokens last indefinitely, and the token list cannot
be changed without restarting apiserver. We plan in the future for tokens to
be short-lived, and to be generated as needed rather than stored in a file.
The token file format is implemented in `pkg/auth/authenticator/tokenfile/...`
and is a csv file with 3 columns: token, user name, user uid.
## Plugin Development
We plan for the Kubernetes API server to issue tokens
after the user has been (re)authenticated by a *bedrock* authentication
provider external to Kubernetes. We plan to make it easy to develop modules
that interface between kubernetes and a bedrock authentication provider (e.g.
github.com, google.com, enterprise directory, kerberos, etc.)

103
docs/authorization.md Normal file
View File

@ -0,0 +1,103 @@
# Authorization Plugins
In Kubernetes, authorization happens as a separate step from authentication.
See the [authentication documentation](../authn_plugins/README.md) for an
overview of authentication.
Authorization applies to all HTTP accesses on the main apiserver port. (The
readonly port is not currently subject to authorization, but is planned to be
removed soon.)
The authorization check for any request compares attributes of the context of
the request, (such as user, resource kind, and namespace) with access
policies. An API call must be allowed by some policy in order to proceed.
The following implementations are available, and are selected by flag:
- `--authoriation_mode=AlwaysDeny`
- `--authoriation_mode=AlwaysAllow`
- `--authoriation_mode=ABAC`
`AlwaysDeny` blocks all requests (used in tests).
`AlwaysAllow` allows all requests; use if you don't need authorization.
`ABAC` allows for user-configured authorization policy. ABAC stands for Attribute-Based Access Control.
## ABAC Mode
### Request Attributes
A request has 4 attributes that can be considered for authorization:
- user (the user-string which a user was authenticated as).
- whether the request is readonly (GETs are readonly)
- what kind of object is being accessed
- applies only to the API endpoints, such as
`/api/v1beta1/pods`. For miscelaneous endpoints, like `/version`, the
kind is the empty string.
- the namespace of the object being access, or the empty string if the
endpoint does not support namespaced objects.
We anticipate adding more attributes to allow finer grained access control and
to assist in policy management.
### Policy File Format
For mode `ABAC`, also specify `--authorization_policy_file=SOME_FILENAME`.
The file format is [one JSON object per line](http://jsonlines.org/). There should be no enclosing list or map, just
one map per line.
Each line is a "policy object". A policy object is a map with the following properties:
- `user`, type string; the user-string from `--token_auth_file`
- `readonly`, type boolean, when true, means that the policy only applies to GET
operations.
- `kind`, type string; a kind of object, from an URL, such as `pods`.
- `namespace`, type string; a namespace string.
An unset property is the same as a property set to the zero value for its type (e.g. empty string, 0, false).
However, unset should be preferred for readability.
In the future, policies may be expressed in a JSON format, and managed via a REST
interface.
### Authorization Algorithm
A request has attributes which correspond to the properties of a policy object.
When a request is received, the attributes are determined. Unknown attributes
are set to the zero value of its type (e.g. empty string, 0, false).
An unset property will match any value of the corresponding
attribute. An unset attribute will match any value of the corresponding property.
The tuple of attributes is checked for a match against every policy in the policy file.
If at least one line matches the request attributes, then the request is authorized (but may fail later validation).
To permit any user to do something, write a policy with the user property unset.
To permit an action Policy with an unset namespace applies regardless of namespace.
### Examples
1. Alice can do anything: `{"user":"alice"}`
2. Kubelet can read any pods: `{"user":"kubelet", "kind": "pods", "readonly": true}`
3. Kubelet can read and write events: `{"user":"kubelet", "kind": "events"}`
4. Bob can just read pods in namespace "projectCaribou": `{"user":"bob", "kind": "pods", "readonly": true, "ns": "projectCaribou"}`
[Complete file example](../pkg/auth/authorizer/abac/example_policy_file.jsonl)
## Plugin Developement
Other implementations can be developed fairly easily.
The APIserver calls the Authorizer interface:
```go
type Authorizer interface {
Authorize(a Attributes) error
}
```
to determine whether or not to allow each API action.
An authorization plugin is a module that implements this interface.
Authorization plugin code goes in `pkg/auth/authorization/$MODULENAME`.
An authorization module can be completely implemented in go, or can call out
to a remote authorization service. Authorization modules can implement
their own caching to reduce the cost of repeated authorization calls with the
same or similar arguments. Developers should then consider the interaction between
caching and revokation of permissions.

View File

@ -20,6 +20,7 @@ import (
"errors"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer/abac"
)
// Attributes implements authorizer.Attributes interface.
@ -56,20 +57,26 @@ func NewAlwaysDenyAuthorizer() authorizer.Authorizer {
const (
ModeAlwaysAllow string = "AlwaysAllow"
ModeAlwaysDeny string = "AlwaysDeny"
ModeABAC string = "ABAC"
)
// Keep this list in sync with constant list above.
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny}
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC}
// NewAuthorizerFromAuthorizationConfig returns the right sort of authorizer.Authorizer
// based on the authorizationMode xor an error. authorizationMode should be one of AuthorizationModeChoices.
func NewAuthorizerFromAuthorizationConfig(authorizationMode string) (authorizer.Authorizer, error) {
func NewAuthorizerFromAuthorizationConfig(authorizationMode string, authorizationPolicyFile string) (authorizer.Authorizer, error) {
if authorizationPolicyFile != "" && authorizationMode != "ABAC" {
return nil, errors.New("Cannot specify --authorization_policy_file without mode ABAC")
}
// Keep cases in sync with constant list above.
switch authorizationMode {
case ModeAlwaysAllow:
return NewAlwaysAllowAuthorizer(), nil
case ModeAlwaysDeny:
return NewAlwaysDenyAuthorizer(), nil
case ModeABAC:
return abac.NewFromFile(authorizationPolicyFile)
default:
return nil, errors.New("Unknown authorization mode")
}

View File

@ -0,0 +1,124 @@
/*
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 abac
// Policy authorizes Kubernetes API actions using an Attribute-based access
// control scheme.
import (
"bufio"
"encoding/json"
"errors"
"os"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
)
// TODO: make this into a real API object. Note that when that happens, it
// will get MetaData. However, the Kind and Namespace in the struct below
// will be separate from the Kind and Namespace in the Metadata. Obviously,
// meta.Kind will be something like policy, and policy.Kind has to be allowed
// to be different. Less obviously, namespace needs to be different as well.
// This will allow wildcard matching strings to be used in the future for the
// body.Namespace, if we want to add that feature, without affecting the
// meta.Namespace.
type policy struct {
User string `json:"user,omitempty" yaml:"user,omitempty"`
// TODO: add support for groups as well as users.
// TODO: add support for robot accounts as well as human user accounts.
// TODO: decide how to namespace user names when multiple authentication
// providers are in use. Either add "Realm", or assume "user@example.com"
// format.
// TODO: Make the "cluster" Kinds be one API group (minions, bindings,
// events, endpoints). The "user" Kinds are another (pods, services,
// replicationControllers, operations) Make a "plugin", e.g. build
// controller, be another group. That way when we add a new object to a
// the API, we don't have to add lots of policy?
// TODO: make this a proper REST object with its own registry.
Readonly bool `json:"readonly,omitempty" yaml:"readonly,omitempty"`
Kind string `json:"kind,omitempty" yaml:"kind,omitempty"`
Namespace string `json:"namespace,omitempty" yaml:"namespace,omitempty"`
// TODO: "expires" string in RFC3339 format.
// TODO: want a way to allow some users to restart containers of a pod but
// not delete or modify it.
// TODO: want a way to allow a controller to create a pod based only on a
// certain podTemplates.
}
type policyList []policy
// TODO: Have policies be created via an API call and stored in REST storage.
func NewFromFile(path string) (policyList, error) {
// File format is one map per line. This allows easy concatentation of files,
// comments in files, and identification of errors by line number.
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
scanner := bufio.NewScanner(file)
pl := make(policyList, 0)
var p policy
for scanner.Scan() {
b := scanner.Bytes()
// TODO: skip comment lines.
err = json.Unmarshal(b, &p)
if err != nil {
// TODO: line number in errors.
return nil, err
}
pl = append(pl, p)
}
if err := scanner.Err(); err != nil {
return nil, err
}
return pl, nil
}
func (p policy) matches(a authorizer.Attributes) bool {
if p.User == "" || p.User == a.GetUserName() {
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
if p.Kind == "" || (p.Kind == a.GetKind()) {
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
return true
}
}
}
}
return false
}
// Authorizer implements authorizer.Authorize
func (pl policyList) Authorize(a authorizer.Attributes) error {
for _, p := range pl {
if p.matches(a) {
return nil
}
}
return errors.New("No policy matched.")
// TODO: Benchmark how much time policy matching takes with a medium size
// policy file, compared to other steps such as encoding/decoding.
// Then, add Caching only if needed.
}

View File

@ -0,0 +1,148 @@
/*
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 abac
import (
"io/ioutil"
"os"
"testing"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/user"
)
func TestEmptyFile(t *testing.T) {
_, err := newWithContents(t, "")
if err != nil {
t.Errorf("unable to read policy file: %v", err)
}
}
func TestOneLineFileNoNewLine(t *testing.T) {
_, err := newWithContents(t, `{"user":"scheduler", "readonly": true, "kind": "pods", "namespace":"ns1"}`)
if err != nil {
t.Errorf("unable to read policy file: %v", err)
}
}
func TestTwoLineFile(t *testing.T) {
_, err := newWithContents(t, `{"user":"scheduler", "readonly": true, "kind": "pods"}
{"user":"scheduler", "readonly": true, "kind": "services"}
`)
if err != nil {
t.Errorf("unable to read policy file: %v", err)
}
}
// Test the file that we will point users at as an example.
func TestExampleFile(t *testing.T) {
_, err := NewFromFile("./example_policy_file.jsonl")
if err != nil {
t.Errorf("unable to read policy file: %v", err)
}
}
func NotTestAuthorize(t *testing.T) {
a, err := newWithContents(t, `{ "readonly": true, "kind": "events"}
{"user":"scheduler", "readonly": true, "kind": "pods"}
{"user":"scheduler", "kind": "bindings"}
{"user":"kubelet", "readonly": true, "kind": "bindings"}
{"user":"kubelet", "kind": "events"}
{"user":"alice", "ns": "projectCaribou"}
{"user":"bob", "readonly": true, "ns": "projectCaribou"}
`)
if err != nil {
t.Fatalf("unable to read policy file: %v", err)
}
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1"}
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3"}
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5"}
testCases := []struct {
User user.DefaultInfo
RO bool
Kind string
NS string
ExpectAllow bool
}{
// Scheduler can read pods
{User: uScheduler, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: true},
{User: uScheduler, RO: true, Kind: "pods", NS: "", ExpectAllow: true},
// Scheduler cannot write pods
{User: uScheduler, RO: false, Kind: "pods", NS: "ns1", ExpectAllow: false},
{User: uScheduler, RO: false, Kind: "pods", NS: "", ExpectAllow: false},
// Scheduler can write bindings
{User: uScheduler, RO: true, Kind: "bindings", NS: "ns1", ExpectAllow: true},
{User: uScheduler, RO: true, Kind: "bindings", NS: "", ExpectAllow: true},
// Alice can read and write anything in the right namespace.
{User: uAlice, RO: true, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
{User: uAlice, RO: true, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
{User: uAlice, RO: true, Kind: "", NS: "projectCaribou", ExpectAllow: true},
{User: uAlice, RO: false, Kind: "pods", NS: "projectCaribou", ExpectAllow: true},
{User: uAlice, RO: false, Kind: "widgets", NS: "projectCaribou", ExpectAllow: true},
{User: uAlice, RO: false, Kind: "", NS: "projectCaribou", ExpectAllow: true},
// .. but not the wrong namespace.
{User: uAlice, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
{User: uAlice, RO: true, Kind: "widgets", NS: "ns1", ExpectAllow: false},
{User: uAlice, RO: true, Kind: "", NS: "ns1", ExpectAllow: false},
// Chuck can read events, since anyone can.
{User: uChuck, RO: true, Kind: "events", NS: "ns1", ExpectAllow: true},
{User: uChuck, RO: true, Kind: "events", NS: "", ExpectAllow: true},
// Chuck can't do other things.
{User: uChuck, RO: false, Kind: "events", NS: "ns1", ExpectAllow: false},
{User: uChuck, RO: true, Kind: "pods", NS: "ns1", ExpectAllow: false},
{User: uChuck, RO: true, Kind: "floop", NS: "ns1", ExpectAllow: false},
// Chunk can't access things with no kind or namespace
// TODO: find a way to give someone access to miscelaneous endpoints, such as
// /healthz, /version, etc.
{User: uChuck, RO: true, Kind: "", NS: "", ExpectAllow: false},
}
for _, tc := range testCases {
attr := authorizer.AttributesRecord{
User: &tc.User,
ReadOnly: tc.RO,
Kind: tc.Kind,
Namespace: tc.NS,
}
t.Logf("tc: %v -> attr %v", tc, attr)
err := a.Authorize(attr)
actualAllow := bool(err == nil)
if tc.ExpectAllow != actualAllow {
t.Errorf("Expected allowed=%v but actually allowed=%v, for case %v",
tc.ExpectAllow, actualAllow, tc)
}
}
}
func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, error) {
f, err := ioutil.TempFile("", "abac_test")
if err != nil {
t.Fatalf("unexpected error creating policyfile: %v", err)
}
f.Close()
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
t.Fatalf("unexpected error writing policyfile: %v", err)
}
pl, err := NewFromFile(f.Name())
return pl, err
}

View File

@ -0,0 +1,7 @@
{"user":"admin"}
{"user":"scheduler", "readonly": true, "kind": "pods"}
{"user":"scheduler", "kind": "bindings"}
{"user":"kubelet", "readonly": true, "kind": "bindings"}
{"user":"kubelet", "kind": "events"}
{"user":"alice", "ns": "projectCaribou"}
{"user":"bob", "readonly": true, "ns": "projectCaribou"}

View File

@ -54,18 +54,18 @@ type AttributesRecord struct {
Kind string
}
func (a *AttributesRecord) GetUserName() string {
func (a AttributesRecord) GetUserName() string {
return a.User.GetName()
}
func (a *AttributesRecord) IsReadOnly() bool {
func (a AttributesRecord) IsReadOnly() bool {
return a.ReadOnly
}
func (a *AttributesRecord) GetNamespace() string {
func (a AttributesRecord) GetNamespace() string {
return a.Namespace
}
func (a *AttributesRecord) GetKind() string {
func (a AttributesRecord) GetKind() string {
return a.Kind
}

View File

@ -34,6 +34,7 @@ import (
"github.com/GoogleCloudPlatform/kubernetes/pkg/apiserver"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer"
"github.com/GoogleCloudPlatform/kubernetes/pkg/auth/authorizer/abac"
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
"github.com/GoogleCloudPlatform/kubernetes/pkg/master"
)
@ -621,15 +622,23 @@ func TestUnknownUserIsUnauthorized(t *testing.T) {
}
}
// Inject into master an authorizer that uses namespace information.
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
type allowFooNamespaceAuthorizer struct{}
func (allowFooNamespaceAuthorizer) Authorize(a authorizer.Attributes) error {
if a.GetNamespace() == "foo" {
return nil
func newAuthorizerWithContents(t *testing.T, contents string) authorizer.Authorizer {
f, err := ioutil.TempFile("", "auth_test")
if err != nil {
t.Fatalf("unexpected error creating policyfile: %v", err)
}
return errors.New("I can't allow that. Try another namespace, buddy.")
f.Close()
defer os.Remove(f.Name())
if err := ioutil.WriteFile(f.Name(), []byte(contents), 0700); err != nil {
t.Fatalf("unexpected error writing policyfile: %v", err)
}
pl, err := abac.NewFromFile(f.Name())
if err != nil {
t.Fatalf("unexpected error creating authorizer from policyfile: %v", err)
}
return pl
}
// TestNamespaceAuthorization tests that authorization can be controlled
@ -641,13 +650,13 @@ func TestNamespaceAuthorization(t *testing.T) {
defer os.Remove(tokenFilename)
// This file has alice and bob in it.
// Set up a master
helper, err := master.NewEtcdHelper(newEtcdClient(), "v1beta1")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
a := newAuthorizerWithContents(t, `{"namespace": "foo"}
`)
m := master.New(&master.Config{
EtcdHelper: helper,
KubeletClient: client.FakeKubeletClient{},
@ -655,7 +664,7 @@ func TestNamespaceAuthorization(t *testing.T) {
EnableUISupport: false,
APIPrefix: "/api",
TokenAuthFile: tokenFilename,
Authorizer: allowFooNamespaceAuthorizer{},
Authorizer: a,
})
s := httptest.NewServer(m.Handler)
@ -706,17 +715,6 @@ func TestNamespaceAuthorization(t *testing.T) {
}
}
// Inject into master an authorizer that uses kind information.
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
type allowServicesAuthorizer struct{}
func (allowServicesAuthorizer) Authorize(a authorizer.Attributes) error {
if a.GetKind() == "services" {
return nil
}
return errors.New("I can't allow that. Hint: try services.")
}
// TestKindAuthorization tests that authorization can be controlled
// by namespace.
func TestKindAuthorization(t *testing.T) {
@ -733,6 +731,8 @@ func TestKindAuthorization(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
a := newAuthorizerWithContents(t, `{"kind": "services"}
`)
m := master.New(&master.Config{
EtcdHelper: helper,
KubeletClient: client.FakeKubeletClient{},
@ -740,7 +740,7 @@ func TestKindAuthorization(t *testing.T) {
EnableUISupport: false,
APIPrefix: "/api",
TokenAuthFile: tokenFilename,
Authorizer: allowServicesAuthorizer{},
Authorizer: a,
})
s := httptest.NewServer(m.Handler)
@ -786,17 +786,6 @@ func TestKindAuthorization(t *testing.T) {
}
}
// Inject into master an authorizer that uses ReadOnly information.
// TODO(etune): remove this test once a more comprehensive built-in authorizer is implemented.
type allowReadAuthorizer struct{}
func (allowReadAuthorizer) Authorize(a authorizer.Attributes) error {
if a.IsReadOnly() {
return nil
}
return errors.New("I'm afraid I can't let you do that.")
}
// TestReadOnlyAuthorization tests that authorization can be controlled
// by namespace.
func TestReadOnlyAuthorization(t *testing.T) {
@ -813,6 +802,8 @@ func TestReadOnlyAuthorization(t *testing.T) {
t.Fatalf("unexpected error: %v", err)
}
a := newAuthorizerWithContents(t, `{"readonly": true}
`)
m := master.New(&master.Config{
EtcdHelper: helper,
KubeletClient: client.FakeKubeletClient{},
@ -820,7 +811,7 @@ func TestReadOnlyAuthorization(t *testing.T) {
EnableUISupport: false,
APIPrefix: "/api",
TokenAuthFile: tokenFilename,
Authorizer: allowReadAuthorizer{},
Authorizer: a,
})
s := httptest.NewServer(m.Handler)