mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-06 16:06:51 +00:00
Add non-resource and API group support to ABAC authorizer, version ABAC policy rules
This commit is contained in:
@@ -21,50 +21,35 @@ package abac
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/glog"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/abac"
|
||||
"k8s.io/kubernetes/pkg/apis/abac/latest"
|
||||
"k8s.io/kubernetes/pkg/apis/abac/v0"
|
||||
_ "k8s.io/kubernetes/pkg/apis/abac/v1beta1"
|
||||
"k8s.io/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"`
|
||||
Group string `json:"group,omitempty"`
|
||||
// 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 (nodes, 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"`
|
||||
Resource string `json:"resource,omitempty"`
|
||||
Namespace string `json:"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 policyLoadError struct {
|
||||
path string
|
||||
line int
|
||||
data []byte
|
||||
err error
|
||||
}
|
||||
|
||||
type policyList []policy
|
||||
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 []*api.Policy
|
||||
|
||||
// TODO: Have policies be created via an API call and stored in REST storage.
|
||||
func NewFromFile(path string) (policyList, error) {
|
||||
@@ -79,29 +64,151 @@ func NewFromFile(path string) (policyList, error) {
|
||||
scanner := bufio.NewScanner(file)
|
||||
pl := make(policyList, 0)
|
||||
|
||||
i := 0
|
||||
unversionedLines := 0
|
||||
for scanner.Scan() {
|
||||
var p policy
|
||||
i++
|
||||
p := &api.Policy{}
|
||||
b := scanner.Bytes()
|
||||
// TODO: skip comment lines.
|
||||
err = json.Unmarshal(b, &p)
|
||||
if err != nil {
|
||||
// TODO: line number in errors.
|
||||
return nil, err
|
||||
|
||||
// skip comment lines and blank lines
|
||||
trimmed := strings.TrimSpace(string(b))
|
||||
if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") {
|
||||
continue
|
||||
}
|
||||
|
||||
version, kind, err := api.Scheme.DataVersionAndKind(b)
|
||||
if err != nil {
|
||||
return nil, policyLoadError{path, i, b, err}
|
||||
}
|
||||
|
||||
if version == "" && kind == "" {
|
||||
unversionedLines++
|
||||
// Migrate unversioned policy object
|
||||
oldPolicy := &v0.Policy{}
|
||||
if err := latest.Codec.DecodeInto(b, oldPolicy); err != nil {
|
||||
return nil, policyLoadError{path, i, b, err}
|
||||
}
|
||||
if err := api.Scheme.Convert(oldPolicy, p); err != nil {
|
||||
return nil, policyLoadError{path, i, b, err}
|
||||
}
|
||||
} else {
|
||||
decodedObj, err := latest.Codec.Decode(b)
|
||||
if err != nil {
|
||||
return nil, policyLoadError{path, i, b, err}
|
||||
}
|
||||
decodedPolicy, ok := decodedObj.(*api.Policy)
|
||||
if !ok {
|
||||
return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)}
|
||||
}
|
||||
p = decodedPolicy
|
||||
}
|
||||
|
||||
pl = append(pl, p)
|
||||
}
|
||||
|
||||
if unversionedLines > 0 {
|
||||
glog.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, err
|
||||
return nil, policyLoadError{path, -1, nil, err}
|
||||
}
|
||||
return pl, nil
|
||||
}
|
||||
|
||||
func (p policy) matches(a authorizer.Attributes) bool {
|
||||
if p.subjectMatches(a) {
|
||||
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
|
||||
if p.Resource == "" || (p.Resource == a.GetResource()) {
|
||||
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
|
||||
func matches(p api.Policy, a authorizer.Attributes) bool {
|
||||
if subjectMatches(p, a) {
|
||||
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 api.Policy, a authorizer.Attributes) bool {
|
||||
matched := false
|
||||
|
||||
// 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 == a.GetUserName()
|
||||
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 a.GetGroups() {
|
||||
if p.Spec.Group == group {
|
||||
matched = true
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return matched
|
||||
}
|
||||
|
||||
func verbMatches(p api.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 api.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 api.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
|
||||
}
|
||||
}
|
||||
@@ -110,31 +217,10 @@ func (p policy) matches(a authorizer.Attributes) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (p policy) subjectMatches(a authorizer.Attributes) bool {
|
||||
if p.User != "" {
|
||||
// Require user match
|
||||
if p.User != a.GetUserName() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
if p.Group != "" {
|
||||
// Require group match
|
||||
for _, group := range a.GetGroups() {
|
||||
if p.Group == group {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Authorizer implements authorizer.Authorize
|
||||
func (pl policyList) Authorize(a authorizer.Attributes) error {
|
||||
for _, p := range pl {
|
||||
if p.matches(a) {
|
||||
if matches(*p, a) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,8 +21,12 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/abac"
|
||||
"k8s.io/kubernetes/pkg/apis/abac/v0"
|
||||
"k8s.io/kubernetes/pkg/apis/abac/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/auth/authorizer"
|
||||
"k8s.io/kubernetes/pkg/auth/user"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestEmptyFile(t *testing.T) {
|
||||
@@ -56,7 +60,7 @@ func TestExampleFile(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotAuthorized(t *testing.T) {
|
||||
func TestAuthorizeV0(t *testing.T) {
|
||||
a, err := newWithContents(t, `{ "readonly": true, "resource": "events" }
|
||||
{"user":"scheduler", "readonly": true, "resource": "pods" }
|
||||
{"user":"scheduler", "resource": "bindings" }
|
||||
@@ -78,6 +82,102 @@ func TestNotAuthorized(t *testing.T) {
|
||||
Verb string
|
||||
Resource string
|
||||
NS string
|
||||
APIGroup string
|
||||
Path string
|
||||
ExpectAllow bool
|
||||
}{
|
||||
// Scheduler can read pods
|
||||
{User: uScheduler, Verb: "list", Resource: "pods", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, Verb: "list", Resource: "pods", NS: "", ExpectAllow: true},
|
||||
// Scheduler cannot write pods
|
||||
{User: uScheduler, Verb: "create", Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uScheduler, Verb: "create", Resource: "pods", NS: "", ExpectAllow: false},
|
||||
// Scheduler can write bindings
|
||||
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "ns1", ExpectAllow: true},
|
||||
{User: uScheduler, Verb: "get", Resource: "bindings", NS: "", ExpectAllow: true},
|
||||
|
||||
// Alice can read and write anything in the right namespace.
|
||||
{User: uAlice, Verb: "get", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "get", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "get", Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "update", Resource: "widgets", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "update", Resource: "", NS: "projectCaribou", ExpectAllow: true},
|
||||
{User: uAlice, Verb: "update", Resource: "foo", NS: "projectCaribou", APIGroup: "bar", ExpectAllow: true},
|
||||
// .. but not the wrong namespace.
|
||||
{User: uAlice, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false},
|
||||
|
||||
// Chuck can read events, since anyone can.
|
||||
{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true},
|
||||
// Chuck can't do other things.
|
||||
{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false},
|
||||
// Chunk can't access things with no kind or namespace
|
||||
{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false},
|
||||
}
|
||||
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,
|
||||
}
|
||||
err := a.Authorize(attr)
|
||||
actualAllow := bool(err == nil)
|
||||
if tc.ExpectAllow != actualAllow {
|
||||
t.Logf("tc: %v -> attr %v", tc, attr)
|
||||
t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
|
||||
i, tc.ExpectAllow, actualAllow, tc)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
uScheduler := user.DefaultInfo{Name: "scheduler", UID: "uid1"}
|
||||
uAlice := user.DefaultInfo{Name: "alice", UID: "uid3"}
|
||||
uChuck := user.DefaultInfo{Name: "chuck", UID: "uid5"}
|
||||
uDebbie := user.DefaultInfo{Name: "debbie", UID: "uid6"}
|
||||
uNoResource := user.DefaultInfo{Name: "noresource", UID: "uid7"}
|
||||
uAPIGroup := user.DefaultInfo{Name: "apigroupuser", UID: "uid8"}
|
||||
|
||||
testCases := []struct {
|
||||
User user.DefaultInfo
|
||||
Verb string
|
||||
Resource string
|
||||
APIGroup string
|
||||
NS string
|
||||
Path string
|
||||
ExpectAllow bool
|
||||
}{
|
||||
// Scheduler can read pods
|
||||
@@ -102,6 +202,9 @@ func TestNotAuthorized(t *testing.T) {
|
||||
{User: uAlice, Verb: "get", Resource: "widgets", NS: "ns1", ExpectAllow: false},
|
||||
{User: uAlice, Verb: "get", Resource: "", NS: "ns1", ExpectAllow: false},
|
||||
|
||||
// Debbie can write to pods in the right namespace
|
||||
{User: uDebbie, Verb: "update", Resource: "pods", NS: "projectCaribou", ExpectAllow: true},
|
||||
|
||||
// Chuck can read events, since anyone can.
|
||||
{User: uChuck, Verb: "get", Resource: "events", NS: "ns1", ExpectAllow: true},
|
||||
{User: uChuck, Verb: "get", Resource: "events", NS: "", ExpectAllow: true},
|
||||
@@ -109,24 +212,49 @@ func TestNotAuthorized(t *testing.T) {
|
||||
{User: uChuck, Verb: "update", Resource: "events", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, Verb: "get", Resource: "pods", NS: "ns1", ExpectAllow: false},
|
||||
{User: uChuck, Verb: "get", Resource: "floop", NS: "ns1", ExpectAllow: false},
|
||||
// Chunk can't access things with no kind or namespace
|
||||
// TODO: find a way to give someone access to miscellaneous endpoints, such as
|
||||
// /healthz, /version, etc.
|
||||
{User: uChuck, Verb: "get", Resource: "", NS: "", ExpectAllow: false},
|
||||
// Chuck can't access things with no resource or namespace
|
||||
{User: uChuck, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: false},
|
||||
// but can access /api
|
||||
{User: uChuck, Verb: "get", Path: "/api", Resource: "", NS: "", ExpectAllow: true},
|
||||
// though he cannot write to it
|
||||
{User: uChuck, Verb: "create", Path: "/api", Resource: "", NS: "", ExpectAllow: false},
|
||||
// while he can write to /custom
|
||||
{User: uChuck, Verb: "update", Path: "/custom", Resource: "", NS: "", ExpectAllow: true},
|
||||
// he cannot get "/root"
|
||||
{User: uChuck, Verb: "get", Path: "/root", Resource: "", NS: "", ExpectAllow: false},
|
||||
// but can get any subpath
|
||||
{User: uChuck, Verb: "get", Path: "/root/", Resource: "", NS: "", ExpectAllow: true},
|
||||
{User: uChuck, Verb: "get", Path: "/root/test/1/2/3", Resource: "", NS: "", ExpectAllow: true},
|
||||
|
||||
// the user "noresource" can get any non-resource request
|
||||
{User: uNoResource, Verb: "get", Path: "", Resource: "", NS: "", ExpectAllow: true},
|
||||
{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "", ExpectAllow: true},
|
||||
{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "", NS: "", ExpectAllow: true},
|
||||
// but cannot get any request where IsResourceRequest() == true
|
||||
{User: uNoResource, Verb: "get", Path: "/", Resource: "", NS: "bar", ExpectAllow: false},
|
||||
{User: uNoResource, Verb: "get", Path: "/foo/bar/baz", Resource: "foo", NS: "bar", ExpectAllow: false},
|
||||
|
||||
// Test APIGroup matching
|
||||
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectAnyGroup", ExpectAllow: true},
|
||||
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectEmptyGroup", ExpectAllow: false},
|
||||
{User: uAPIGroup, Verb: "get", APIGroup: "x", Resource: "foo", NS: "projectXGroup", ExpectAllow: true},
|
||||
}
|
||||
for i, tc := range testCases {
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
Verb: tc.Verb,
|
||||
Resource: tc.Resource,
|
||||
Namespace: tc.NS,
|
||||
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: %v -> attr %v", tc, attr)
|
||||
// t.Logf("tc %2v: %v -> attr %v", i, tc, attr)
|
||||
err := a.Authorize(attr)
|
||||
actualAllow := bool(err == nil)
|
||||
if tc.ExpectAllow != actualAllow {
|
||||
t.Errorf("%d: Expected allowed=%v but actually allowed=%v\n\t%v",
|
||||
i, tc.ExpectAllow, actualAllow, tc)
|
||||
t.Errorf("%d: Expected allowed=%v but actually allowed=%v, for case %+v & %+v",
|
||||
i, tc.ExpectAllow, actualAllow, tc, attr)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,116 +262,316 @@ func TestNotAuthorized(t *testing.T) {
|
||||
func TestSubjectMatches(t *testing.T) {
|
||||
testCases := map[string]struct {
|
||||
User user.DefaultInfo
|
||||
PolicyUser string
|
||||
PolicyGroup string
|
||||
Policy runtime.Object
|
||||
ExpectMatch bool
|
||||
}{
|
||||
"empty policy matches unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "",
|
||||
"v0 empty policy matches unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
"empty policy matches authed user": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "",
|
||||
"v0 empty policy matches authed user": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
"empty policy matches authed user with groups": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "",
|
||||
"v0 empty policy matches authed user with groups": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
|
||||
"user policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "",
|
||||
"v0 user policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user policy does not match different user": {
|
||||
User: user.DefaultInfo{Name: "Bar"},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "",
|
||||
"v0 user policy does not match different user": {
|
||||
User: user.DefaultInfo{Name: "Bar"},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user policy is case-sensitive": {
|
||||
User: user.DefaultInfo{Name: "foo"},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "",
|
||||
"v0 user policy is case-sensitive": {
|
||||
User: user.DefaultInfo{Name: "foo"},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user policy does not match substring": {
|
||||
User: user.DefaultInfo{Name: "FooBar"},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "",
|
||||
"v0 user policy does not match substring": {
|
||||
User: user.DefaultInfo{Name: "FooBar"},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user policy matches username": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "",
|
||||
"v0 user policy matches username": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
|
||||
"group policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "Foo",
|
||||
"v0 group policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "Foo",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"group policy does not match user in different group": {
|
||||
User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "A",
|
||||
"v0 group policy does not match user in different group": {
|
||||
User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "A",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"group policy is case-sensitive": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "b",
|
||||
"v0 group policy is case-sensitive": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "b",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"group policy does not match substring": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "B",
|
||||
"v0 group policy does not match substring": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "B",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"group policy matches user in group": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
PolicyUser: "",
|
||||
PolicyGroup: "B",
|
||||
"v0 group policy matches user in group": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "",
|
||||
Group: "B",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
|
||||
"user and group policy requires user match": {
|
||||
User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "B",
|
||||
"v0 user and group policy requires user match": {
|
||||
User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "B",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user and group policy requires group match": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "D",
|
||||
"v0 user and group policy requires group match": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "D",
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"user and group policy matches": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
PolicyUser: "Foo",
|
||||
PolicyGroup: "B",
|
||||
"v0 user and group policy matches": {
|
||||
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||
Policy: &v0.Policy{
|
||||
User: "Foo",
|
||||
Group: "B",
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
|
||||
"v1 empty policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"v1 empty policy does not match authed user": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
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"}},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
|
||||
"v1 user policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"v1 user policy does not match different user": {
|
||||
User: user.DefaultInfo{Name: "Bar"},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"v1 user policy is case-sensitive": {
|
||||
User: user.DefaultInfo{Name: "foo"},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"v1 user policy does not match substring": {
|
||||
User: user.DefaultInfo{Name: "FooBar"},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: false,
|
||||
},
|
||||
"v1 user policy matches username": {
|
||||
User: user.DefaultInfo{Name: "Foo"},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "",
|
||||
},
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
|
||||
"v1 group policy does not match unauthed user": {
|
||||
User: user.DefaultInfo{},
|
||||
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"}},
|
||||
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"}},
|
||||
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"}},
|
||||
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"}},
|
||||
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"}},
|
||||
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"}},
|
||||
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"}},
|
||||
Policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "Foo",
|
||||
Group: "B",
|
||||
},
|
||||
},
|
||||
ExpectMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for k, tc := range testCases {
|
||||
policy := &api.Policy{}
|
||||
if err := api.Scheme.Convert(tc.Policy, policy); err != nil {
|
||||
t.Errorf("%s: error converting: %v", k, err)
|
||||
continue
|
||||
}
|
||||
attr := authorizer.AttributesRecord{
|
||||
User: &tc.User,
|
||||
}
|
||||
actualMatch := policy{User: tc.PolicyUser, Group: tc.PolicyGroup}.subjectMatches(attr)
|
||||
actualMatch := subjectMatches(*policy, attr)
|
||||
if tc.ExpectMatch != actualMatch {
|
||||
t.Errorf("%v: Expected actorMatches=%v but actually got=%v",
|
||||
k, tc.ExpectMatch, actualMatch)
|
||||
@@ -269,27 +597,30 @@ func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, erro
|
||||
|
||||
func TestPolicy(t *testing.T) {
|
||||
tests := []struct {
|
||||
policy policy
|
||||
policy runtime.Object
|
||||
attr authorizer.Attributes
|
||||
matches bool
|
||||
name string
|
||||
}{
|
||||
// v0
|
||||
{
|
||||
policy: policy{},
|
||||
policy: &v0.Policy{},
|
||||
attr: authorizer.AttributesRecord{},
|
||||
matches: true,
|
||||
name: "null",
|
||||
name: "v0 null",
|
||||
},
|
||||
|
||||
// v0 mismatches
|
||||
{
|
||||
policy: policy{
|
||||
policy: &v0.Policy{
|
||||
Readonly: true,
|
||||
},
|
||||
attr: authorizer.AttributesRecord{},
|
||||
matches: false,
|
||||
name: "read-only mismatch",
|
||||
name: "v0 read-only mismatch",
|
||||
},
|
||||
{
|
||||
policy: policy{
|
||||
policy: &v0.Policy{
|
||||
User: "foo",
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
@@ -298,20 +629,21 @@ func TestPolicy(t *testing.T) {
|
||||
},
|
||||
},
|
||||
matches: false,
|
||||
name: "user name mis-match",
|
||||
name: "v0 user name mis-match",
|
||||
},
|
||||
{
|
||||
policy: policy{
|
||||
policy: &v0.Policy{
|
||||
Resource: "foo",
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Resource: "bar",
|
||||
Resource: "bar",
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: false,
|
||||
name: "resource mis-match",
|
||||
name: "v0 resource mis-match",
|
||||
},
|
||||
{
|
||||
policy: policy{
|
||||
policy: &v0.Policy{
|
||||
User: "foo",
|
||||
Resource: "foo",
|
||||
Namespace: "foo",
|
||||
@@ -320,27 +652,314 @@ func TestPolicy(t *testing.T) {
|
||||
User: &user.DefaultInfo{
|
||||
Name: "foo",
|
||||
},
|
||||
Resource: "foo",
|
||||
Namespace: "foo",
|
||||
Resource: "foo",
|
||||
Namespace: "foo",
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "namespace mis-match",
|
||||
name: "v0 namespace mis-match",
|
||||
},
|
||||
|
||||
// v0 matches
|
||||
{
|
||||
policy: &v0.Policy{},
|
||||
attr: authorizer.AttributesRecord{ResourceRequest: true},
|
||||
matches: true,
|
||||
name: "v0 null resource",
|
||||
},
|
||||
{
|
||||
policy: policy{
|
||||
Namespace: "foo",
|
||||
policy: &v0.Policy{
|
||||
Readonly: true,
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Namespace: "bar",
|
||||
Verb: "get",
|
||||
},
|
||||
matches: true,
|
||||
name: "v0 read-only match",
|
||||
},
|
||||
{
|
||||
policy: &v0.Policy{
|
||||
User: "foo",
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
User: &user.DefaultInfo{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
matches: true,
|
||||
name: "v0 user name match",
|
||||
},
|
||||
{
|
||||
policy: &v0.Policy{
|
||||
Resource: "foo",
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Resource: "foo",
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "v0 resource match",
|
||||
},
|
||||
|
||||
// v1 mismatches
|
||||
{
|
||||
policy: &v1beta1.Policy{},
|
||||
attr: authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: false,
|
||||
name: "resource mis-match",
|
||||
name: "v1 null",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "foo",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
User: &user.DefaultInfo{
|
||||
Name: "bar",
|
||||
},
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: false,
|
||||
name: "v1 user name mis-match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
Readonly: true,
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: false,
|
||||
name: "v1 read-only mismatch",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
Resource: "foo",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
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",
|
||||
},
|
||||
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{
|
||||
Path: "/api2",
|
||||
ResourceRequest: false,
|
||||
},
|
||||
matches: false,
|
||||
name: "v1 non-resource mis-match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
NonResourcePath: "/api/*",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
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",
|
||||
},
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 user match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
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"},
|
||||
},
|
||||
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"},
|
||||
},
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 group wildcard match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
Readonly: true,
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Verb: "get",
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 read-only match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
Resource: "foo",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
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",
|
||||
},
|
||||
Namespace: "bar",
|
||||
Resource: "baz",
|
||||
ResourceRequest: true,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 namespace match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
NonResourcePath: "/api",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Path: "/api",
|
||||
ResourceRequest: false,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 non-resource match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
NonResourcePath: "*",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Path: "/api",
|
||||
ResourceRequest: false,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 non-resource wildcard match",
|
||||
},
|
||||
{
|
||||
policy: &v1beta1.Policy{
|
||||
Spec: v1beta1.PolicySpec{
|
||||
User: "*",
|
||||
NonResourcePath: "/api/*",
|
||||
},
|
||||
},
|
||||
attr: authorizer.AttributesRecord{
|
||||
Path: "/api/foo",
|
||||
ResourceRequest: false,
|
||||
},
|
||||
matches: true,
|
||||
name: "v1 non-resource wildcard subpath match",
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
matches := test.policy.matches(test.attr)
|
||||
policy := &api.Policy{}
|
||||
if err := api.Scheme.Convert(test.policy, policy); err != nil {
|
||||
t.Errorf("%s: error converting: %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
matches := matches(*policy, test.attr)
|
||||
if test.matches != matches {
|
||||
t.Errorf("unexpected value for %s, expected: %t, saw: %t", test.name, test.matches, matches)
|
||||
t.Errorf("%s: expected: %t, saw: %t", test.name, test.matches, matches)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
{"user":"admin"}
|
||||
{"user":"scheduler", "readonly": true, "resource": "pods"}
|
||||
{"user":"scheduler", "resource": "bindings"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "pods"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "services"}
|
||||
{"user":"kubelet", "readonly": true, "resource": "endpoints"}
|
||||
{"user":"kubelet", "resource": "events"}
|
||||
{"user":"alice", "namespace": "projectCaribou"}
|
||||
{"user":"bob", "readonly": true, "namespace": "projectCaribou"}
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"*", "nonResourcePath": "*", "readonly": true}
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"admin", "namespace": "*", "resource": "*", "apiGroup": "*" }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"scheduler", "namespace": "*", "resource": "pods", "readonly": true }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"scheduler", "namespace": "*", "resource": "bindings" }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet", "namespace": "*", "resource": "pods", "readonly": true }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet", "namespace": "*", "resource": "services", "readonly": true }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet", "namespace": "*", "resource": "endpoints", "readonly": true }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"kubelet", "namespace": "*", "resource": "events" }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"alice", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*" }
|
||||
{"apiVersion": "abac.authorization.kubernetes.io/v1beta1", "kind": "Policy", "user":"bob", "namespace": "projectCaribou", "resource": "*", "apiGroup": "*", "readonly": true }
|
||||
@@ -50,6 +50,13 @@ type Attributes interface {
|
||||
|
||||
// The group of the resource, if a request is for a REST object.
|
||||
GetAPIGroup() string
|
||||
|
||||
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
|
||||
// and false for non-resource endpoints like /api, /healthz, and /swaggerapi
|
||||
IsResourceRequest() bool
|
||||
|
||||
// GetPath returns the path of the request
|
||||
GetPath() string
|
||||
}
|
||||
|
||||
// Authorizer makes an authorization decision based on information gained by making
|
||||
@@ -72,11 +79,13 @@ type RequestAttributesGetter interface {
|
||||
|
||||
// AttributesRecord implements Attributes interface.
|
||||
type AttributesRecord struct {
|
||||
User user.Info
|
||||
Verb string
|
||||
Namespace string
|
||||
APIGroup string
|
||||
Resource string
|
||||
User user.Info
|
||||
Verb string
|
||||
Namespace string
|
||||
APIGroup string
|
||||
Resource string
|
||||
ResourceRequest bool
|
||||
Path string
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetUserName() string {
|
||||
@@ -106,3 +115,11 @@ func (a AttributesRecord) GetResource() string {
|
||||
func (a AttributesRecord) GetAPIGroup() string {
|
||||
return a.APIGroup
|
||||
}
|
||||
|
||||
func (a AttributesRecord) IsResourceRequest() bool {
|
||||
return a.ResourceRequest
|
||||
}
|
||||
|
||||
func (a AttributesRecord) GetPath() string {
|
||||
return a.Path
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user