未验证 提交 cae78438 编写于 作者: H hongming

update

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 b9bdcd82
......@@ -3,6 +3,7 @@ package token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/server/errors"
"time"
)
......@@ -12,9 +13,9 @@ const DefaultIssuerName = "kubesphere"
var errInvalidToken = errors.New("invalid token")
type claims struct {
Username string `json:"username"`
UID string `json:"uid"`
Groups []string `json:"groups"`
Username string `json:"username"`
UID string `json:"uid"`
Email string `json:"email"`
// Currently, we are not using any field in jwt.StandardClaims
jwt.StandardClaims
}
......@@ -37,14 +38,14 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
return nil, err
}
return &AuthUser{Name: clm.Username, UID: clm.UID, Groups: clm.Groups}, nil
return &iam.User{Name: clm.Username, UID: clm.UID, Email: clm.Email}, nil
}
func (s *jwtTokenIssuer) IssueTo(user User) (string, error) {
clm := &claims{
Username: user.GetName(),
UID: user.GetUID(),
Groups: user.GetGroups(),
Email: user.GetEmail(),
StandardClaims: jwt.StandardClaims{
IssuedAt: time.Now().Unix(),
Issuer: s.name,
......
......@@ -2,6 +2,7 @@ package token
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/iam"
"testing"
)
......@@ -12,19 +13,22 @@ func TestJwtTokenIssuer(t *testing.T) {
description string
name string
uid string
email string
}{
{
name: "admin",
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
name: "admin",
uid: "b8be6edd-2c92-4535-9b2a-df6326474458",
email: "admin@kubesphere.io",
},
{
name: "bar",
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
name: "bar",
uid: "b8be6edd-2c92-4535-9b2a-df6326474452",
email: "bar@kubesphere.io",
},
}
for _, testCase := range testCases {
user := &AuthUser{
user := &iam.User{
Name: testCase.name,
UID: testCase.uid,
}
......
......@@ -7,24 +7,6 @@ type User interface {
// UID
GetUID() string
// Groups
GetGroups() []string
}
type AuthUser struct {
Name string
UID string
Groups []string
}
func (a AuthUser) GetName() string {
return a.Name
}
func (a AuthUser) GetUID() string {
return a.UID
}
func (a AuthUser) GetGroups() []string {
return a.Groups
// Email
GetEmail() string
}
......@@ -6,30 +6,30 @@ import (
)
type User struct {
Username string `json:"username"`
Name string `json:"username"`
UID string `json:"uid"`
Email string `json:"email"`
Lang string `json:"lang,omitempty"`
Description string `json:"description"`
CreateTime time.Time `json:"create_time"`
CreateTime time.Time `json:"createTime"`
Groups []string `json:"groups,omitempty"`
Password string `json:"password,omitempty"`
}
func (u *User) GetName() string {
return u.Username
return u.Name
}
func (u *User) GetUID() string {
return u.UID
}
func (u *User) GetGroups() []string {
return u.Groups
func (u *User) GetEmail() string {
return u.Email
}
func (u *User) Validate() error {
if u.Username == "" {
if u.Name == "" {
return errors.New("username can not be empty")
}
......
......@@ -29,20 +29,21 @@ type opaAuthorizer struct {
am am.AccessManagementInterface
}
// Make decision by request attributes
func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Make decisions based on the authorization policy of different levels of roles
platformRole, err := o.am.GetPlatformRole(attr.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check platform role policy rules
if a, r, e := makeDecision(platformRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
}
// it's not in cluster resource, permission denied
// TODO declare implicit cluster info in request Info
if attr.GetCluster() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
}
......@@ -78,7 +79,7 @@ func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized author
}
if attr.GetNamespace() != "" {
namespaceRole, err := o.am.GetNamespaceRole(attr.GetNamespace(), attr.GetUser().GetName())
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
......@@ -102,6 +103,29 @@ func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.
return authorizer.DecisionDeny, "", err
}
// data example
//{
// "User": {
// "Name": "admin",
// "UID": "0",
// "Groups": [
// "admin"
// ],
// "Extra": null
// },
// "Verb": "list",
// "Cluster": "cluster1",
// "Workspace": "",
// "Namespace": "",
// "APIGroup": "",
// "APIVersion": "v1",
// "Resource": "nodes",
// "Subresource": "",
// "Name": "",
// "KubernetesRequest": true,
// "ResourceRequest": true,
// "Path": "/api/v1/nodes"
//}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
......
......@@ -27,8 +27,29 @@ import (
)
func TestPlatformRole(t *testing.T) {
platformRoles := map[string]am.FakeRole{"admin": {
Name: "admin",
Rego: "package authz\ndefault allow = true",
}, "anonymous": {
Name: "anonymous",
Rego: "package authz\ndefault allow = false",
}, "tom": {
Name: "tom",
Rego: `package authz
default allow = false
allow {
resources_in_cluster1
}
resources_in_cluster1 {
input.Cluster == "cluster1"
}`,
},
}
operator := am.NewFakeAMOperator(cache.NewSimpleCache())
operator.Prepare(platformRoles, nil, nil, nil)
opa := NewOPAAuthorizer(am.NewFakeAMOperator(cache.NewSimpleCache()))
opa := NewOPAAuthorizer(operator)
tests := []struct {
name string
......@@ -36,7 +57,7 @@ func TestPlatformRole(t *testing.T) {
expectedDecision authorizer.Decision
}{
{
name: "list nodes",
name: "admin can list nodes",
request: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "admin",
......@@ -60,7 +81,7 @@ func TestPlatformRole(t *testing.T) {
expectedDecision: authorizer.DecisionAllow,
},
{
name: "list nodes",
name: "anonymous can not list nodes",
request: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: user.Anonymous,
......@@ -82,13 +103,54 @@ func TestPlatformRole(t *testing.T) {
Path: "/api/v1/nodes",
},
expectedDecision: authorizer.DecisionDeny,
}, {
name: "tom can list nodes in cluster1",
request: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "tom",
},
Verb: "list",
Cluster: "cluster1",
Workspace: "",
Namespace: "",
APIGroup: "",
APIVersion: "v1",
Resource: "nodes",
Subresource: "",
Name: "",
KubernetesRequest: true,
ResourceRequest: true,
Path: "/api/v1/clusters/cluster1/nodes",
},
expectedDecision: authorizer.DecisionAllow,
},
{
name: "tom can not list nodes in cluster2",
request: authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "tom",
},
Verb: "list",
Cluster: "cluster2",
Workspace: "",
Namespace: "",
APIGroup: "",
APIVersion: "v1",
Resource: "nodes",
Subresource: "",
Name: "",
KubernetesRequest: true,
ResourceRequest: true,
Path: "/api/v1/clusters/cluster2/nodes",
},
expectedDecision: authorizer.DecisionDeny,
},
}
for _, test := range tests {
decision, _, err := opa.Authorize(test.request)
if err != nil {
t.Error(err)
t.Errorf("test failed: %s, %v", test.name, err)
}
if decision != test.expectedDecision {
t.Errorf("%s: expected decision %v, actual %+v", test.name, test.expectedDecision, decision)
......
......@@ -65,7 +65,7 @@ func (h *oauthHandler) TokenReviewHandler(req *restful.Request, resp *restful.Re
Kind: auth.KindTokenReview,
Status: &auth.Status{
Authenticated: true,
User: map[string]interface{}{"username": user.GetName(), "uid": user.GetUID(), "groups": user.GetGroups()},
User: map[string]interface{}{"username": user.GetName(), "uid": user.GetUID()},
},
}
......
......@@ -36,7 +36,7 @@ type AccessManagementInterface interface {
GetPlatformRole(username string) (Role, error)
GetClusterRole(cluster, username string) (Role, error)
GetWorkspaceRole(workspace, username string) (Role, error)
GetNamespaceRole(namespace, username string) (Role, error)
GetNamespaceRole(cluster, namespace, username string) (Role, error)
}
type Role interface {
......@@ -73,10 +73,6 @@ func (am *amOperator) GetWorkspaceRole(workspace, username string) (Role, error)
panic("implement me")
}
func (am *amOperator) GetNamespaceRole(namespace, username string) (Role, error) {
panic("implement me")
}
func (am *amOperator) GetDevOpsRole(namespace, username string) (Role, error) {
func (am *amOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
panic("implement me")
}
......@@ -19,55 +19,111 @@
package am
import (
"k8s.io/apiserver/pkg/authentication/user"
"encoding/json"
"fmt"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
)
type fakeRole struct {
type FakeRole struct {
Name string
Rego string
}
type fakeOperator struct {
type FakeOperator struct {
cache cache.Interface
}
func newFakeRole(username string) Role {
if username == user.Anonymous {
return &fakeRole{
Name: "anonymous",
Rego: "package authz\ndefault allow = false",
func (f FakeOperator) queryFakeRole(cacheKey string) (Role, error) {
data, err := f.cache.Get(cacheKey)
if err != nil {
if err == cache.ErrNoSuchKey {
return &FakeRole{
Name: "DenyAll",
Rego: "package authz\ndefault allow = false",
}, nil
}
return nil, err
}
return &fakeRole{
Name: "admin",
Rego: "package authz\ndefault allow = true",
var role FakeRole
err = json.Unmarshal([]byte(data), &role)
if err != nil {
return nil, err
}
return role, nil
}
func (f fakeOperator) GetPlatformRole(username string) (Role, error) {
return newFakeRole(username), nil
func (f FakeOperator) saveFakeRole(cacheKey string, role FakeRole) error {
data, err := json.Marshal(role)
if err != nil {
return err
}
return f.cache.Set(cacheKey, string(data), 0)
}
func (f FakeOperator) GetPlatformRole(username string) (Role, error) {
return f.queryFakeRole(platformRoleCacheKey(username))
}
func (f FakeOperator) GetClusterRole(cluster, username string) (Role, error) {
return f.queryFakeRole(clusterRoleCacheKey(cluster, username))
}
func (f FakeOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
return f.queryFakeRole(workspaceRoleCacheKey(workspace, username))
}
func (f FakeOperator) GetNamespaceRole(cluster, namespace, username string) (Role, error) {
return f.queryFakeRole(namespaceRoleCacheKey(cluster, namespace, username))
}
func (f FakeOperator) Prepare(platformRoles map[string]FakeRole, clusterRoles map[string]map[string]FakeRole, workspaceRoles map[string]map[string]FakeRole, namespaceRoles map[string]map[string]map[string]FakeRole) {
for username, role := range platformRoles {
f.saveFakeRole(platformRoleCacheKey(username), role)
}
for cluster, roles := range clusterRoles {
for username, role := range roles {
f.saveFakeRole(clusterRoleCacheKey(cluster, username), role)
}
}
for workspace, roles := range workspaceRoles {
for username, role := range roles {
f.saveFakeRole(workspaceRoleCacheKey(workspace, username), role)
}
}
for cluster, nsRoles := range namespaceRoles {
for namespace, roles := range nsRoles {
for username, role := range roles {
f.saveFakeRole(namespaceRoleCacheKey(cluster, namespace, username), role)
}
}
}
}
func (f fakeOperator) GetClusterRole(cluster, username string) (Role, error) {
return newFakeRole(username), nil
func namespaceRoleCacheKey(cluster, namespace, username string) string {
return fmt.Sprintf("cluster.%s.namespaces.%s.roles.%s", cluster, namespace, username)
}
func (f fakeOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
return newFakeRole(username), nil
func clusterRoleCacheKey(cluster, username string) string {
return fmt.Sprintf("cluster.%s.roles.%s", cluster, username)
}
func workspaceRoleCacheKey(workspace, username string) string {
return fmt.Sprintf("workspace.%s.roles.%s", workspace, username)
}
func (f fakeOperator) GetNamespaceRole(namespace, username string) (Role, error) {
return newFakeRole(username), nil
func platformRoleCacheKey(username string) string {
return fmt.Sprintf("platform.roles.%s", username)
}
func (f fakeRole) GetName() string {
func (f FakeRole) GetName() string {
return f.Name
}
func (f fakeRole) GetRego() string {
func (f FakeRole) GetRego() string {
return f.Rego
}
func NewFakeAMOperator(cache cache.Interface) AccessManagementInterface {
return &fakeOperator{cache: cache}
func NewFakeAMOperator(cache cache.Interface) *FakeOperator {
return &FakeOperator{cache: cache}
}
......@@ -75,13 +75,13 @@ func (im *imOperator) ModifyUser(user *iam.User) (*iam.User, error) {
// clear auth failed record
if user.Password != "" {
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(user.Username, "*"))
records, err := im.cacheClient.Keys(authenticationFailedKeyForUsername(user.Name, "*"))
if err == nil {
im.cacheClient.Del(records...)
}
}
return im.ldapClient.Get(user.Username)
return im.ldapClient.Get(user.Name)
}
func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error) {
......@@ -100,7 +100,7 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error
return nil, err
}
err = im.ldapClient.Verify(user.Username, password)
err = im.ldapClient.Verify(user.Name, password)
if err != nil {
if err == ldap.ErrInvalidCredentials {
im.cacheClient.Set(authenticationFailedKeyForUsername(username, fmt.Sprintf("%d", time.Now().UnixNano())), "", 30*time.Minute)
......@@ -114,7 +114,7 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error
}
// TODO: I think we should come up with a better strategy to prevent multiple login.
tokenKey := tokenKeyForUsername(user.Username, issuedToken)
tokenKey := tokenKeyForUsername(user.Name, issuedToken)
if !im.authenticateOptions.MultipleLogin {
// multi login not allowed, remove the previous token
sessions, err := im.cacheClient.Keys(tokenKey)
......@@ -136,7 +136,7 @@ func (im *imOperator) Login(username, password, ip string) (*oauth2.Token, error
return nil, err
}
im.logLogin(user.Username, ip, time.Now())
im.logLogin(user.Name, ip, time.Now())
return &oauth2.Token{AccessToken: issuedToken}, nil
}
......
......@@ -216,7 +216,7 @@ func (l *ldapInterfaceImpl) Get(name string) (*iam.User, error) {
userEntry := searchResults.Entries[0]
user := &iam.User{
Username: userEntry.GetAttributeValue(ldapAttributeUserID),
Name: userEntry.GetAttributeValue(ldapAttributeUserID),
Email: userEntry.GetAttributeValue(ldapAttributeMail),
Lang: userEntry.GetAttributeValue(ldapAttributePreferredLanguage),
Description: userEntry.GetAttributeValue(ldapAttributeDescription),
......@@ -229,12 +229,12 @@ func (l *ldapInterfaceImpl) Get(name string) (*iam.User, error) {
}
func (l *ldapInterfaceImpl) Create(user *iam.User) error {
if _, err := l.Get(user.Username); err != nil {
if _, err := l.Get(user.Name); err != nil {
return ErrUserAlreadyExisted
}
createRequest := &ldap.AddRequest{
DN: l.dnForUsername(user.Username),
DN: l.dnForUsername(user.Name),
Attributes: []ldap.Attribute{
{
Type: ldapAttributeObjectClass,
......@@ -242,7 +242,7 @@ func (l *ldapInterfaceImpl) Create(user *iam.User) error {
},
{
Type: ldapAttributeCommonName,
Vals: []string{user.Username},
Vals: []string{user.Name},
},
{
Type: ldapAttributeSerialNumber,
......@@ -254,11 +254,11 @@ func (l *ldapInterfaceImpl) Create(user *iam.User) error {
},
{
Type: ldapAttributeHomeDirectory,
Vals: []string{"/home/" + user.Username},
Vals: []string{"/home/" + user.Name},
},
{
Type: ldapAttributeUserID,
Vals: []string{user.Username},
Vals: []string{user.Name},
},
{
Type: ldapAttributeUserIDNumber,
......@@ -322,13 +322,13 @@ func (l *ldapInterfaceImpl) Update(newUser *iam.User) error {
defer conn.Close()
// check user existed
_, err = l.Get(newUser.Username)
_, err = l.Get(newUser.Name)
if err != nil {
return err
}
modifyRequest := &ldap.ModifyRequest{
DN: l.dnForUsername(newUser.Username),
DN: l.dnForUsername(newUser.Name),
}
if newUser.Description != "" {
......
......@@ -17,7 +17,7 @@ func NewSimpleLdap() Interface {
// initialize with a admin user
admin := &iam.User{
Username: "admin",
Name: "admin",
Email: "admin@kubesphere.io",
Lang: "eng",
Description: "administrator",
......@@ -25,21 +25,21 @@ func NewSimpleLdap() Interface {
Groups: nil,
Password: "P@88w0rd",
}
sl.store[admin.Username] = admin
sl.store[admin.Name] = admin
return sl
}
func (s simpleLdap) Create(user *iam.User) error {
s.store[user.Username] = user
s.store[user.Name] = user
return nil
}
func (s simpleLdap) Update(user *iam.User) error {
_, err := s.Get(user.Username)
_, err := s.Get(user.Name)
if err != nil {
return err
}
s.store[user.Username] = user
s.store[user.Name] = user
return nil
}
......
......@@ -11,7 +11,7 @@ func TestSimpleLdap(t *testing.T) {
ldapClient := NewSimpleLdap()
foo := &iam.User{
Username: "jerry",
Name: "jerry",
Email: "jerry@kubesphere.io",
Lang: "en",
Description: "Jerry is kind and gentle.",
......@@ -27,7 +27,7 @@ func TestSimpleLdap(t *testing.T) {
}
// check if user really created
user, err := ldapClient.Get(foo.Username)
user, err := ldapClient.Get(foo.Name)
if err != nil {
t.Fatal(err)
}
......@@ -35,7 +35,7 @@ func TestSimpleLdap(t *testing.T) {
t.Fatalf("%T differ (-got, +want): %s", user, diff)
}
_ = ldapClient.Delete(foo.Username)
_ = ldapClient.Delete(foo.Name)
})
t.Run("should update user", func(t *testing.T) {
......@@ -51,7 +51,7 @@ func TestSimpleLdap(t *testing.T) {
}
// check if user really created
user, err := ldapClient.Get(foo.Username)
user, err := ldapClient.Get(foo.Name)
if err != nil {
t.Fatal(err)
}
......@@ -59,7 +59,7 @@ func TestSimpleLdap(t *testing.T) {
t.Fatalf("%T differ (-got, +want): %s", user, diff)
}
_ = ldapClient.Delete(foo.Username)
_ = ldapClient.Delete(foo.Name)
})
t.Run("should delete user", func(t *testing.T) {
......@@ -68,12 +68,12 @@ func TestSimpleLdap(t *testing.T) {
t.Fatal(err)
}
err = ldapClient.Delete(foo.Username)
err = ldapClient.Delete(foo.Name)
if err != nil {
t.Fatal(err)
}
_, err = ldapClient.Get(foo.Username)
_, err = ldapClient.Get(foo.Name)
if err == nil || err != ErrUserNotExists {
t.Fatalf("expected ErrUserNotExists error, got %v", err)
}
......@@ -85,12 +85,12 @@ func TestSimpleLdap(t *testing.T) {
t.Fatal(err)
}
err = ldapClient.Verify(foo.Username, foo.Password)
err = ldapClient.Verify(foo.Name, foo.Password)
if err != nil {
t.Fatalf("should pass but got an error %v", err)
}
err = ldapClient.Verify(foo.Username, "gibberish")
err = ldapClient.Verify(foo.Name, "gibberish")
if err == nil || err != ErrInvalidCredentials {
t.Fatalf("expected error ErrInvalidCrenentials but got %v", err)
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册