mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-27 13:37:30 +00:00
Initial addition of groups to user/policy
This commit is contained in:
parent
3f926e943a
commit
9d8d313113
@ -37,8 +37,8 @@ import (
|
|||||||
// body.Namespace, if we want to add that feature, without affecting the
|
// body.Namespace, if we want to add that feature, without affecting the
|
||||||
// meta.Namespace.
|
// meta.Namespace.
|
||||||
type policy struct {
|
type policy struct {
|
||||||
User string `json:"user,omitempty"`
|
User string `json:"user,omitempty"`
|
||||||
// TODO: add support for groups as well as users.
|
Group string `json:"group,omitempty"`
|
||||||
// TODO: add support for robot accounts as well as human user accounts.
|
// TODO: add support for robot accounts as well as human user accounts.
|
||||||
// TODO: decide how to namespace user names when multiple authentication
|
// TODO: decide how to namespace user names when multiple authentication
|
||||||
// providers are in use. Either add "Realm", or assume "user@example.com"
|
// providers are in use. Either add "Realm", or assume "user@example.com"
|
||||||
@ -98,7 +98,7 @@ func NewFromFile(path string) (policyList, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p policy) matches(a authorizer.Attributes) bool {
|
func (p policy) matches(a authorizer.Attributes) bool {
|
||||||
if p.User == "" || p.User == a.GetUserName() {
|
if p.subjectMatches(a) {
|
||||||
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
|
if p.Readonly == false || (p.Readonly == a.IsReadOnly()) {
|
||||||
if p.Kind == "" || (p.Kind == a.GetKind()) {
|
if p.Kind == "" || (p.Kind == a.GetKind()) {
|
||||||
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
|
if p.Namespace == "" || (p.Namespace == a.GetNamespace()) {
|
||||||
@ -110,6 +110,27 @@ func (p policy) matches(a authorizer.Attributes) bool {
|
|||||||
return false
|
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
|
// Authorizer implements authorizer.Authorize
|
||||||
func (pl policyList) Authorize(a authorizer.Attributes) error {
|
func (pl policyList) Authorize(a authorizer.Attributes) error {
|
||||||
for _, p := range pl {
|
for _, p := range pl {
|
||||||
|
@ -131,6 +131,126 @@ func NotTestAuthorize(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSubjectMatches(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
User user.DefaultInfo
|
||||||
|
PolicyUser string
|
||||||
|
PolicyGroup string
|
||||||
|
ExpectMatch bool
|
||||||
|
}{
|
||||||
|
"empty policy matches unauthed user": {
|
||||||
|
User: user.DefaultInfo{},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
"empty policy matches authed user": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo"},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
"empty policy matches authed user with groups": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"a", "b"}},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"user policy does not match unauthed user": {
|
||||||
|
User: user.DefaultInfo{},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user policy does not match different user": {
|
||||||
|
User: user.DefaultInfo{Name: "Bar"},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user policy is case-sensitive": {
|
||||||
|
User: user.DefaultInfo{Name: "foo"},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user policy does not match substring": {
|
||||||
|
User: user.DefaultInfo{Name: "FooBar"},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user policy matches username": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo"},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"group policy does not match unauthed user": {
|
||||||
|
User: user.DefaultInfo{},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "Foo",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"group policy does not match user in different group": {
|
||||||
|
User: user.DefaultInfo{Name: "FooBar", Groups: []string{"B"}},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "A",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"group policy is case-sensitive": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "b",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"group policy does not match substring": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "BBB", "C"}},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "B",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"group policy matches user in group": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||||
|
PolicyUser: "",
|
||||||
|
PolicyGroup: "B",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
|
||||||
|
"user and group policy requires user match": {
|
||||||
|
User: user.DefaultInfo{Name: "Bar", Groups: []string{"A", "B", "C"}},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "B",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user and group policy requires group match": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "D",
|
||||||
|
ExpectMatch: false,
|
||||||
|
},
|
||||||
|
"user and group policy matches": {
|
||||||
|
User: user.DefaultInfo{Name: "Foo", Groups: []string{"A", "B", "C"}},
|
||||||
|
PolicyUser: "Foo",
|
||||||
|
PolicyGroup: "B",
|
||||||
|
ExpectMatch: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, tc := range testCases {
|
||||||
|
attr := authorizer.AttributesRecord{
|
||||||
|
User: &tc.User,
|
||||||
|
}
|
||||||
|
actualMatch := policy{User: tc.PolicyUser, Group: tc.PolicyGroup}.subjectMatches(attr)
|
||||||
|
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) (authorizer.Authorizer, error) {
|
func newWithContents(t *testing.T, contents string) (authorizer.Authorizer, error) {
|
||||||
f, err := ioutil.TempFile("", "abac_test")
|
f, err := ioutil.TempFile("", "abac_test")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -26,7 +26,11 @@ type Attributes interface {
|
|||||||
// The user string which the request was authenticated as, or empty if
|
// The user string which the request was authenticated as, or empty if
|
||||||
// no authentication occured and the request was allowed to proceed.
|
// no authentication occured and the request was allowed to proceed.
|
||||||
GetUserName() string
|
GetUserName() string
|
||||||
// TODO: add groups, e.g. GetGroups() []string
|
|
||||||
|
// The list of group names the authenticated user is a member of. Can be
|
||||||
|
// empty if the authenticated user is not in any groups, or if no
|
||||||
|
// authentication occurred.
|
||||||
|
GetGroups() []string
|
||||||
|
|
||||||
// When IsReadOnly() == true, the request has no side effects, other than
|
// When IsReadOnly() == true, the request has no side effects, other than
|
||||||
// caching, logging, and other incidentals.
|
// caching, logging, and other incidentals.
|
||||||
@ -58,6 +62,10 @@ func (a AttributesRecord) GetUserName() string {
|
|||||||
return a.User.GetName()
|
return a.User.GetName()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetGroups() []string {
|
||||||
|
return a.User.GetGroups()
|
||||||
|
}
|
||||||
|
|
||||||
func (a AttributesRecord) IsReadOnly() bool {
|
func (a AttributesRecord) IsReadOnly() bool {
|
||||||
return a.ReadOnly
|
return a.ReadOnly
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,16 @@ type Info interface {
|
|||||||
// if the user is removed from the system and another user is added with
|
// if the user is removed from the system and another user is added with
|
||||||
// the same name.
|
// the same name.
|
||||||
GetUID() string
|
GetUID() string
|
||||||
|
// GetGroups returns the names of the groups the user is a member of
|
||||||
|
GetGroups() []string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultInfo provides a simple user information exchange object
|
// DefaultInfo provides a simple user information exchange object
|
||||||
// for components that implement the UserInfo interface.
|
// for components that implement the UserInfo interface.
|
||||||
type DefaultInfo struct {
|
type DefaultInfo struct {
|
||||||
Name string
|
Name string
|
||||||
UID string
|
UID string
|
||||||
|
Groups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (i *DefaultInfo) GetName() string {
|
func (i *DefaultInfo) GetName() string {
|
||||||
@ -41,3 +44,7 @@ func (i *DefaultInfo) GetName() string {
|
|||||||
func (i *DefaultInfo) GetUID() string {
|
func (i *DefaultInfo) GetUID() string {
|
||||||
return i.UID
|
return i.UID
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *DefaultInfo) GetGroups() []string {
|
||||||
|
return i.Groups
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user