Commit 6014ace9 authored by Darren Shepherd's avatar Darren Shepherd Committed by Erik Wilson

Remove ABAC

parent afa0802f
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["abac.go"],
importpath = "k8s.io/kubernetes/pkg/auth/authorizer/abac",
deps = [
"//pkg/apis/abac:go_default_library",
"//pkg/apis/abac/latest:go_default_library",
"//pkg/apis/abac/v0:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
],
)
filegroup(
name = "example_policy",
testonly = True,
srcs = [
"example_policy_file.jsonl",
],
)
go_test(
name = "go_default_test",
srcs = ["abac_test.go"],
data = [
":example_policy",
],
embed = [":go_default_library"],
deps = [
"//pkg/apis/abac:go_default_library",
"//pkg/apis/abac/v0:go_default_library",
"//pkg/apis/abac/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package abac
// Policy authorizes Kubernetes API actions using an Attribute-based access
// control scheme.
import (
"bufio"
"fmt"
"os"
"strings"
"k8s.io/klog"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/abac"
_ "k8s.io/kubernetes/pkg/apis/abac/latest"
"k8s.io/kubernetes/pkg/apis/abac/v0"
)
type policyLoadError struct {
path string
line int
data []byte
err error
}
func (p policyLoadError) Error() string {
if p.line >= 0 {
return fmt.Sprintf("error reading policy file %s, line %d: %s: %v", p.path, p.line, string(p.data), p.err)
}
return fmt.Sprintf("error reading policy file %s: %v", p.path, p.err)
}
type policyList []*abac.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 concatenation 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)
decoder := abac.Codecs.UniversalDecoder()
i := 0
unversionedLines := 0
for scanner.Scan() {
i++
p := &abac.Policy{}
b := scanner.Bytes()
// skip comment lines and blank lines
trimmed := strings.TrimSpace(string(b))
if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") {
continue
}
decodedObj, _, err := decoder.Decode(b, nil, nil)
if err != nil {
if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
return nil, policyLoadError{path, i, b, err}
}
unversionedLines++
// Migrate unversioned policy object
oldPolicy := &v0.Policy{}
if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil {
return nil, policyLoadError{path, i, b, err}
}
if err := abac.Scheme.Convert(oldPolicy, p, nil); err != nil {
return nil, policyLoadError{path, i, b, err}
}
pl = append(pl, p)
continue
}
decodedPolicy, ok := decodedObj.(*abac.Policy)
if !ok {
return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)}
}
pl = append(pl, decodedPolicy)
}
if unversionedLines > 0 {
klog.Warningf("Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details.", path)
}
if err := scanner.Err(); err != nil {
return nil, policyLoadError{path, -1, nil, err}
}
return pl, nil
}
func matches(p abac.Policy, a authorizer.Attributes) bool {
if subjectMatches(p, a.GetUser()) {
if verbMatches(p, a) {
// Resource and non-resource requests are mutually exclusive, at most one will match a policy
if resourceMatches(p, a) {
return true
}
if nonResourceMatches(p, a) {
return true
}
}
}
return false
}
// subjectMatches returns true if specified user and group properties in the policy match the attributes
func subjectMatches(p abac.Policy, user user.Info) bool {
matched := false
if user == nil {
return false
}
username := user.GetName()
groups := user.GetGroups()
// If the policy specified a user, ensure it matches
if len(p.Spec.User) > 0 {
if p.Spec.User == "*" {
matched = true
} else {
matched = p.Spec.User == username
if !matched {
return false
}
}
}
// If the policy specified a group, ensure it matches
if len(p.Spec.Group) > 0 {
if p.Spec.Group == "*" {
matched = true
} else {
matched = false
for _, group := range groups {
if p.Spec.Group == group {
matched = true
}
}
if !matched {
return false
}
}
}
return matched
}
func verbMatches(p abac.Policy, a authorizer.Attributes) bool {
// TODO: match on verb
// All policies allow read only requests
if a.IsReadOnly() {
return true
}
// Allow if policy is not readonly
if !p.Spec.Readonly {
return true
}
return false
}
func nonResourceMatches(p abac.Policy, a authorizer.Attributes) bool {
// A non-resource policy cannot match a resource request
if !a.IsResourceRequest() {
// Allow wildcard match
if p.Spec.NonResourcePath == "*" {
return true
}
// Allow exact match
if p.Spec.NonResourcePath == a.GetPath() {
return true
}
// Allow a trailing * subpath match
if strings.HasSuffix(p.Spec.NonResourcePath, "*") && strings.HasPrefix(a.GetPath(), strings.TrimRight(p.Spec.NonResourcePath, "*")) {
return true
}
}
return false
}
func resourceMatches(p abac.Policy, a authorizer.Attributes) bool {
// A resource policy cannot match a non-resource request
if a.IsResourceRequest() {
if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() {
if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() {
if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() {
return true
}
}
}
}
return false
}
// Authorizer implements authorizer.Authorize
func (pl policyList) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
for _, p := range pl {
if matches(*p, a) {
return authorizer.DecisionAllow, "", nil
}
}
return authorizer.DecisionNoOpinion, "No policy matched.", nil
// 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.
}
func (pl policyList) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
var (
resourceRules []authorizer.ResourceRuleInfo
nonResourceRules []authorizer.NonResourceRuleInfo
)
for _, p := range pl {
if subjectMatches(*p, user) {
if p.Spec.Namespace == "*" || p.Spec.Namespace == namespace {
if len(p.Spec.Resource) > 0 {
r := authorizer.DefaultResourceRuleInfo{
Verbs: getVerbs(p.Spec.Readonly),
APIGroups: []string{p.Spec.APIGroup},
Resources: []string{p.Spec.Resource},
}
var resourceRule authorizer.ResourceRuleInfo = &r
resourceRules = append(resourceRules, resourceRule)
}
if len(p.Spec.NonResourcePath) > 0 {
r := authorizer.DefaultNonResourceRuleInfo{
Verbs: getVerbs(p.Spec.Readonly),
NonResourceURLs: []string{p.Spec.NonResourcePath},
}
var nonResourceRule authorizer.NonResourceRuleInfo = &r
nonResourceRules = append(nonResourceRules, nonResourceRule)
}
}
}
}
return resourceRules, nonResourceRules, false, nil
}
func getVerbs(isReadOnly bool) []string {
if isReadOnly {
return []string{"get", "list", "watch"}
}
return []string{"*"}
}
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package abac
import (
"io/ioutil"
"os"
"reflect"
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/kubernetes/pkg/apis/abac"
"k8s.io/kubernetes/pkg/apis/abac/v0"
"k8s.io/kubernetes/pkg/apis/abac/v1beta1"
)
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, "resource": "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, "resource": "pods"}
{"user":"scheduler", "readonly": true, "resource": "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 TestAuthorizeV0(t *testing.T) {
a, err := newWithContents(t, `{ "readonly": true, "resource": "events" }
{"user":"scheduler", "readonly": true, "resource": "pods" }
{"user":"scheduler", "resource": "bindings" }
{"user":"kubelet", "readonly": true, "resource": "bindings" }
{"user":"kubelet", "resource": "events" }
{"user":"alice", "namespace": "projectCaribou"}
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
`)
if err != nil {
t.Fatalf("unable to read policy file: %v", err)
}
authenticatedGroup := []string{user.AllAuthenticated}
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1", Groups: authenticatedGroup}
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3", Groups: authenticatedGroup}
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: authenticatedGroup}
testCases := []struct {
User user.DefaultInfo
Verb string
Resource string
NS string
APIGroup string
Path string
ExpectDecision authorizer.Decision
}{
// Scheduler can read pods
{User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Scheduler cannot write pods
{User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
// Scheduler can write bindings
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Alice can read and write anything in the right namespace.
{User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectDecision: authorizer.DecisionAllow},
// .. but not the wrong namespace.
{User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
// Chuck can read events, since anyone can.
{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Chuck can't do other things.
{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
// Chunk can't access things with no kind or namespace
{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
}
for i, tc := range testCases {
attr := authorizer.AttributesRecord{
User: &tc.User,
Verb: tc.Verb,
Resource: tc.Resource,
Namespace: tc.NS,
APIGroup: tc.APIGroup,
Path: tc.Path,
ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0,
}
decision, _, _ := a.Authorize(attr)
if tc.ExpectDecision != decision {
t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
i, tc.ExpectDecision, decision, tc)
}
}
}
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultResourceRuleInfo{
Verbs: info.GetVerbs(),
APIGroups: info.GetAPIGroups(),
Resources: info.GetResources(),
ResourceNames: info.GetResourceNames(),
}
}
return rules
}
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
for i, info := range infos {
rules[i] = authorizer.DefaultNonResourceRuleInfo{
Verbs: info.GetVerbs(),
NonResourceURLs: info.GetNonResourceURLs(),
}
}
return rules
}
func TestRulesFor(t *testing.T) {
a, err := newWithContents(t, `
{ "readonly": true, "resource": "events" }
{"user":"scheduler", "readonly": true, "resource": "pods" }
{"user":"scheduler", "resource": "bindings" }
{"user":"kubelet", "readonly": true, "resource": "pods" }
{"user":"kubelet", "resource": "events" }
{"user":"alice", "namespace": "projectCaribou"}
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
{"user":"bob", "readonly": true, "nonResourcePath": "*"}
{"group":"a", "resource": "bindings" }
{"group":"b", "readonly": true, "nonResourcePath": "*"}
`)
if err != nil {
t.Fatalf("unable to read policy file: %v", err)
}
authenticatedGroup := []string{user.AllAuthenticated}
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1", Groups: authenticatedGroup}
uKubelet := user.DefaultInfo{Name: "kubelet", UID: "uid2", Groups: []string{"a", "b"}}
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3", Groups: authenticatedGroup}
uBob := user.DefaultInfo{Name: "bob", UID: "uid4", Groups: authenticatedGroup}
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: []string{"a", "b"}}
testCases := []struct {
User user.DefaultInfo
Namespace string
ExpectResourceRules []authorizer.DefaultResourceRuleInfo
ExpectNonResourceRules []authorizer.DefaultNonResourceRuleInfo
}{
{
User: uScheduler,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
},
{
User: uKubelet,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"pods"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
{
User: uAlice,
Namespace: "projectCaribou",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{},
},
{
User: uBob,
Namespace: "projectCaribou",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"events"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
{
User: uChuck,
Namespace: "ns1",
ExpectResourceRules: []authorizer.DefaultResourceRuleInfo{
{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"bindings"},
},
{
Verbs: []string{"get", "list", "watch"},
APIGroups: []string{"*"},
Resources: []string{"*"},
},
},
ExpectNonResourceRules: []authorizer.DefaultNonResourceRuleInfo{
{
Verbs: []string{"get", "list", "watch"},
NonResourceURLs: []string{"*"},
},
},
},
}
for i, tc := range testCases {
attr := authorizer.AttributesRecord{
User: &tc.User,
Namespace: tc.Namespace,
}
resourceRules, nonResourceRules, _, _ := a.RulesFor(attr.GetUser(), attr.GetNamespace())
actualResourceRules := getResourceRules(resourceRules)
if !reflect.DeepEqual(tc.ExpectResourceRules, actualResourceRules) {
t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
i, tc.ExpectResourceRules, actualResourceRules)
}
actualNonResourceRules := getNonResourceRules(nonResourceRules)
if !reflect.DeepEqual(tc.ExpectNonResourceRules, actualNonResourceRules) {
t.Logf("tc: %v -> attr %v", tc, attr)
t.Errorf("%d: Expected: \n%#v\n but actual: \n%#v\n",
i, tc.ExpectNonResourceRules, actualNonResourceRules)
}
}
}
func TestAuthorizeV1beta1(t *testing.T) {
a, err := newWithContents(t,
`
# Comment line, after a blank line
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "readonly": true, "nonResourcePath": "/api"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "nonResourcePath": "/custom"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "nonResourcePath": "/root/*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"noresource", "nonResourcePath": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"*", "readonly": true, "resource": "events", "namespace": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler", "readonly": true, "resource": "pods", "namespace": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"scheduler", "resource": "bindings", "namespace": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet", "readonly": true, "resource": "bindings", "namespace": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"kubelet", "resource": "events", "namespace": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"alice", "resource": "*", "namespace": "projectCaribou"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"bob", "readonly": true, "resource": "*", "namespace": "projectCaribou"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"debbie", "resource": "pods", "namespace": "projectCaribou"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectAnyGroup", "apiGroup": "*"}}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectEmptyGroup", "apiGroup": "" }}
{"apiVersion":"abac.authorization.kubernetes.io/v1beta1","kind":"Policy","spec":{"user":"apigroupuser", "resource": "*", "namespace": "projectXGroup", "apiGroup": "x"}}`)
if err != nil {
t.Fatalf("unable to read policy file: %v", err)
}
authenticatedGroup := []string{user.AllAuthenticated}
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1", Groups: authenticatedGroup}
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3", Groups: authenticatedGroup}
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5", Groups: authenticatedGroup}
uDebbie := user.DefaultInfo{Name: "debbie", UID: "uid6", Groups: authenticatedGroup}
uNoResource := user.DefaultInfo{Name: "noresource", UID: "uid7", Groups: authenticatedGroup}
uAPIGroup := user.DefaultInfo{Name: "apigroupuser", UID: "uid8", Groups: authenticatedGroup}
testCases := []struct {
User user.DefaultInfo
Verb string
Resource string
APIGroup string
NS string
Path string
ExpectDecision authorizer.Decision
}{
// Scheduler can read pods
{User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Scheduler cannot write pods
{User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
// Scheduler can write bindings
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Alice can read and write anything in the right namespace.
{User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
{User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
// .. but not the wrong namespace.
{User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
// Debbie can write to pods in the right namespace
{User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectDecision: authorizer.DecisionAllow},
// Chuck can read events, since anyone can.
{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionAllow},
{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectDecision: authorizer.DecisionAllow},
// Chuck can't do other things.
{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectDecision: authorizer.DecisionNoOpinion},
// Chuck can't access things with no resource or namespace
{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
// but can access /api
{User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
// though he cannot write to it
{User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
// while he can write to /custom
{User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
// he cannot get "/root"
{User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectDecision: authorizer.DecisionNoOpinion},
// but can get any subpath
{User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
{User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
// the user "noresource" can get any non-resource request
{User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectDecision: authorizer.DecisionAllow},
// but cannot get any request where IsResourceRequest() == true
{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectDecision: authorizer.DecisionNoOpinion},
// Test APIGroup matching
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectDecision: authorizer.DecisionAllow},
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectDecision: authorizer.DecisionNoOpinion},
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectDecision: authorizer.DecisionAllow},
}
for i, tc := range testCases {
attr := authorizer.AttributesRecord{
User: &tc.User,
Verb: tc.Verb,
Resource: tc.Resource,
APIGroup: tc.APIGroup,
Namespace: tc.NS,
ResourceRequest: len(tc.NS) > 0 || len(tc.Resource) > 0,
Path: tc.Path,
}
// t.Logf("tc %2v: %v -> attr %v", i, tc, attr)
decision, _, _ := a.Authorize(attr)
if tc.ExpectDecision != decision {
t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v",
i, tc.ExpectDecision, decision, tc, attr)
}
}
}
func TestSubjectMatches(t *testing.T) {
testCases := map[string]struct {
User user.DefaultInfo
Policy runtime.Object
ExpectMatch bool
}{
"v0 empty policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v0.Policy{
User: "",
Group: "",
},
ExpectMatch: false,
},
"v0 * user policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v0.Policy{
User: "*",
Group: "",
},
ExpectMatch: false,
},
"v0 * group policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v0.Policy{
User: "",
Group: "*",
},
ExpectMatch: false,
},
"v0 empty policy matches authed user": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "",
},
ExpectMatch: true,
},
"v0 empty policy matches authed user with groups": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "",
},
ExpectMatch: true,
},
"v0 user policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v0.Policy{
User: "Foo",
Group: "",
},
ExpectMatch: false,
},
"v0 user policy does not match different user": {
User: user.DefaultInfo{Name: "Bar", Groups: []string{user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "",
},
ExpectMatch: false,
},
"v0 user policy is case-sensitive": {
User: user.DefaultInfo{Name: "foo", Groups: []string{user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "",
},
ExpectMatch: false,
},
"v0 user policy does not match substring": {
User: user.DefaultInfo{Name: "FooBar", Groups: []string{user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "",
},
ExpectMatch: false,
},
"v0 user policy matches username": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "",
},
ExpectMatch: true,
},
"v0 group policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v0.Policy{
User: "",
Group: "Foo",
},
ExpectMatch: false,
},
"v0 group policy does not match user in different group": {
User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "A",
},
ExpectMatch: false,
},
"v0 group policy is case-sensitive": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "b",
},
ExpectMatch: false,
},
"v0 group policy does not match substring": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "B",
},
ExpectMatch: false,
},
"v0 group policy matches user in group": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "",
Group: "B",
},
ExpectMatch: true,
},
"v0 user and group policy requires user match": {
User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "B",
},
ExpectMatch: false,
},
"v0 user and group policy requires group match": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "D",
},
ExpectMatch: false,
},
"v0 user and group policy matches": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v0.Policy{
User: "Foo",
Group: "B",
},
ExpectMatch: true,
},
"v1 empty policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "",
},
},
ExpectMatch: false,
},
"v1 * user policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
Group: "",
},
},
ExpectMatch: false,
},
"v1 * group policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "*",
},
},
ExpectMatch: false,
},
"v1 empty policy does not match authed user": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "",
},
},
ExpectMatch: false,
},
"v1 empty policy does not match authed user with groups": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "",
},
},
ExpectMatch: false,
},
"v1 user policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "",
},
},
ExpectMatch: false,
},
"v1 user policy does not match different user": {
User: user.DefaultInfo{Name: "Bar", Groups: []string{user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "",
},
},
ExpectMatch: false,
},
"v1 user policy is case-sensitive": {
User: user.DefaultInfo{Name: "foo", Groups: []string{user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "",
},
},
ExpectMatch: false,
},
"v1 user policy does not match substring": {
User: user.DefaultInfo{Name: "FooBar", Groups: []string{user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "",
},
},
ExpectMatch: false,
},
"v1 user policy matches username": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "",
},
},
ExpectMatch: true,
},
"v1 group policy does not match unauthed user": {
User: user.DefaultInfo{Name: "system:anonymous", Groups: []string{"system:unauthenticated"}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "Foo",
},
},
ExpectMatch: false,
},
"v1 group policy does not match user in different group": {
User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "A",
},
},
ExpectMatch: false,
},
"v1 group policy is case-sensitive": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "b",
},
},
ExpectMatch: false,
},
"v1 group policy does not match substring": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "B",
},
},
ExpectMatch: false,
},
"v1 group policy matches user in group": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "",
Group: "B",
},
},
ExpectMatch: true,
},
"v1 user and group policy requires user match": {
User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "B",
},
},
ExpectMatch: false,
},
"v1 user and group policy requires group match": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "D",
},
},
ExpectMatch: false,
},
"v1 user and group policy matches": {
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C", user.AllAuthenticated}},
Policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "Foo",
Group: "B",
},
},
ExpectMatch: true,
},
}
for k, tc := range testCases {
policy := &abac.Policy{}
if err := abac.Scheme.Convert(tc.Policy, policy, nil); err != nil {
t.Errorf("%s: error converting: %v", k, err)
continue
}
attr := authorizer.AttributesRecord{
User: &tc.User,
}
actualMatch := subjectMatches(*policy, attr.GetUser())
if tc.ExpectMatch != actualMatch {
t.Errorf("%v: Expected actorMatches=%v but actually got=%v",
k, tc.ExpectMatch, actualMatch)
}
}
}
func newWithContents(t *testing.T, contents string) (policyList, 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
}
func TestPolicy(t *testing.T) {
tests := []struct {
policy runtime.Object
attr authorizer.Attributes
matches bool
name string
}{
// v0 mismatches
{
policy: &v0.Policy{
Readonly: true,
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Verb: "create",
},
matches: false,
name: "v0 read-only mismatch",
},
{
policy: &v0.Policy{
User: "foo",
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "bar",
Groups: []string{user.AllAuthenticated},
},
},
matches: false,
name: "v0 user name mis-match",
},
{
policy: &v0.Policy{
Resource: "foo",
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Resource: "bar",
ResourceRequest: true,
},
matches: false,
name: "v0 resource mis-match",
},
{
policy: &v0.Policy{
User: "foo",
Resource: "foo",
Namespace: "foo",
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Resource: "foo",
Namespace: "foo",
ResourceRequest: true,
},
matches: true,
name: "v0 namespace mis-match",
},
// v0 matches
{
policy: &v0.Policy{},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: true,
name: "v0 null resource",
},
{
policy: &v0.Policy{
Readonly: true,
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Verb: "get",
},
matches: true,
name: "v0 read-only match",
},
{
policy: &v0.Policy{
User: "foo",
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
},
matches: true,
name: "v0 user name match",
},
{
policy: &v0.Policy{
Resource: "foo",
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Resource: "foo",
ResourceRequest: true,
},
matches: true,
name: "v0 resource match",
},
// v1 mismatches
{
policy: &v1beta1.Policy{},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: false,
name: "v1 null",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "foo",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "bar",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: false,
name: "v1 user name mis-match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
Readonly: true,
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: false,
name: "v1 read-only mismatch",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
Resource: "foo",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Resource: "bar",
ResourceRequest: true,
},
matches: false,
name: "v1 resource mis-match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "foo",
Namespace: "barr",
Resource: "baz",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Namespace: "bar",
Resource: "baz",
ResourceRequest: true,
},
matches: false,
name: "v1 namespace mis-match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
NonResourcePath: "/api",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Path: "/api2",
ResourceRequest: false,
},
matches: false,
name: "v1 non-resource mis-match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
NonResourcePath: "/api/*",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Path: "/api2/foo",
ResourceRequest: false,
},
matches: false,
name: "v1 non-resource wildcard subpath mis-match",
},
// v1 matches
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "foo",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: true,
name: "v1 user match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: true,
name: "v1 user wildcard match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
Group: "bar",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{"bar", user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: true,
name: "v1 group match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
Group: "*",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{"bar", user.AllAuthenticated},
},
ResourceRequest: true,
},
matches: true,
name: "v1 group wildcard match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
Readonly: true,
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Verb: "get",
ResourceRequest: true,
},
matches: true,
name: "v1 read-only match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
Resource: "foo",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Resource: "foo",
ResourceRequest: true,
},
matches: true,
name: "v1 resource match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "foo",
Namespace: "bar",
Resource: "baz",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Namespace: "bar",
Resource: "baz",
ResourceRequest: true,
},
matches: true,
name: "v1 namespace match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
NonResourcePath: "/api",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Path: "/api",
ResourceRequest: false,
},
matches: true,
name: "v1 non-resource match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
NonResourcePath: "*",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Path: "/api",
ResourceRequest: false,
},
matches: true,
name: "v1 non-resource wildcard match",
},
{
policy: &v1beta1.Policy{
Spec: v1beta1.PolicySpec{
User: "*",
NonResourcePath: "/api/*",
},
},
attr: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "foo",
Groups: []string{user.AllAuthenticated},
},
Path: "/api/foo",
ResourceRequest: false,
},
matches: true,
name: "v1 non-resource wildcard subpath match",
},
}
for _, test := range tests {
policy := &abac.Policy{}
if err := abac.Scheme.Convert(test.policy, policy, nil); err != nil {
t.Errorf("%s: error converting: %v", test.name, err)
continue
}
matches := matches(*policy, test.attr)
if test.matches != matches {
t.Errorf("%s: expected: %t, saw: %t", test.name, test.matches, matches)
continue
}
}
}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:authenticated", "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"group":"system:unauthenticated", "nonResourcePath": "*", "readonly": true}}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"admin", "namespace": "*", "resource": "*", "apiGroup": "*" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"scheduler", "namespace": "*", "resource": "pods", "readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"scheduler", "namespace": "*", "resource": "bindings" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "pods", "readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "services", "readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "endpoints", "readonly": true }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"kubelet", "namespace": "*", "resource": "events" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"alice", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*" }}
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "spec": {"user":"bob", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*", "readonly": true }}
\ No newline at end of file
......@@ -25,7 +25,6 @@ import (
"k8s.io/apiserver/pkg/authorization/union"
"k8s.io/apiserver/plugin/pkg/authorizer/webhook"
versionedinformers "k8s.io/client-go/informers"
"k8s.io/kubernetes/pkg/auth/authorizer/abac"
"k8s.io/kubernetes/pkg/auth/nodeidentifier"
"k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
"k8s.io/kubernetes/plugin/pkg/auth/authorizer/node"
......@@ -39,9 +38,6 @@ type Config struct {
// Options for ModeABAC
// Path to an ABAC policy file.
PolicyFile string
// Options for ModeWebhook
// Kubeconfig file for Webhook authorization plugin.
......@@ -89,13 +85,6 @@ func (config Config) New() (authorizer.Authorizer, authorizer.RuleResolver, erro
alwaysDenyAuthorizer := authorizerfactory.NewAlwaysDenyAuthorizer()
authorizers = append(authorizers, alwaysDenyAuthorizer)
ruleResolvers = append(ruleResolvers, alwaysDenyAuthorizer)
case modes.ModeABAC:
abacAuthorizer, err := abac.NewFromFile(config.PolicyFile)
if err != nil {
return nil, nil, err
}
authorizers = append(authorizers, abacAuthorizer)
ruleResolvers = append(ruleResolvers, abacAuthorizer)
case modes.ModeWebhook:
webhookAuthorizer, err := webhook.New(config.WebhookConfigFile,
config.WebhookCacheAuthorizedTTL,
......
......@@ -23,8 +23,6 @@ const (
ModeAlwaysAllow string = "AlwaysAllow"
// ModeAlwaysDeny is the mode to set no requests as authorized
ModeAlwaysDeny string = "AlwaysDeny"
// ModeABAC is the mode to use Attribute Based Access Control to authorize
ModeABAC string = "ABAC"
// ModeWebhook is the mode to make an external webhook call to authorize
ModeWebhook string = "Webhook"
// ModeRBAC is the mode to use Role Based Access Control to authorize
......@@ -34,7 +32,7 @@ const (
)
// AuthorizationModeChoices is the list of supported authorization modes
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeABAC, ModeWebhook, ModeRBAC, ModeNode}
var AuthorizationModeChoices = []string{ModeAlwaysAllow, ModeAlwaysDeny, ModeWebhook, ModeRBAC, ModeNode}
// IsValidAuthorizationMode returns true if the given authorization mode is a valid one for the apiserver
func IsValidAuthorizationMode(authzMode string) bool {
......
......@@ -31,7 +31,6 @@ import (
type BuiltInAuthorizationOptions struct {
Modes []string
PolicyFile string
WebhookConfigFile string
WebhookCacheAuthorizedTTL time.Duration
WebhookCacheUnauthorizedTTL time.Duration
......@@ -61,11 +60,6 @@ func (s *BuiltInAuthorizationOptions) Validate() []error {
if !allowedModes.Has(mode) {
allErrors = append(allErrors, fmt.Errorf("authorization-mode %q is not a valid mode", mode))
}
if mode == authzmodes.ModeABAC {
if s.PolicyFile == "" {
allErrors = append(allErrors, fmt.Errorf("authorization-mode ABAC's authorization policy file not passed"))
}
}
if mode == authzmodes.ModeWebhook {
if s.WebhookConfigFile == "" {
allErrors = append(allErrors, fmt.Errorf("authorization-mode Webhook's authorization config file not passed"))
......@@ -73,10 +67,6 @@ func (s *BuiltInAuthorizationOptions) Validate() []error {
}
}
if s.PolicyFile != "" && !modes.Has(authzmodes.ModeABAC) {
allErrors = append(allErrors, fmt.Errorf("cannot specify --authorization-policy-file without mode ABAC"))
}
if s.WebhookConfigFile != "" && !modes.Has(authzmodes.ModeWebhook) {
allErrors = append(allErrors, fmt.Errorf("cannot specify --authorization-webhook-config-file without mode Webhook"))
}
......@@ -93,9 +83,6 @@ func (s *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
"Ordered list of plug-ins to do authorization on secure port. Comma-delimited list of: "+
strings.Join(authzmodes.AuthorizationModeChoices, ",")+".")
fs.StringVar(&s.PolicyFile, "authorization-policy-file", s.PolicyFile, ""+
"File with authorization policy in json line by line format, used with --authorization-mode=ABAC, on the secure port.")
fs.StringVar(&s.WebhookConfigFile, "authorization-webhook-config-file", s.WebhookConfigFile, ""+
"File with webhook configuration in kubeconfig format, used with --authorization-mode=Webhook. "+
"The API server will query the remote service to determine access on the API server's secure port.")
......@@ -112,7 +99,6 @@ func (s *BuiltInAuthorizationOptions) AddFlags(fs *pflag.FlagSet) {
func (s *BuiltInAuthorizationOptions) ToAuthorizationConfig(versionedInformerFactory versionedinformers.SharedInformerFactory) authorizer.Config {
return authorizer.Config{
AuthorizationModes: s.Modes,
PolicyFile: s.PolicyFile,
WebhookConfigFile: s.WebhookConfigFile,
WebhookCacheAuthorizedTTL: s.WebhookCacheAuthorizedTTL,
WebhookCacheUnauthorizedTTL: s.WebhookCacheUnauthorizedTTL,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment