@@ -144,7 +144,10 @@ func (s *APIServer) installKubeSphereAPIs() {
|
||||
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
|
||||
//urlruntime.Must(tenantv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.DBClient.Database()))
|
||||
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
|
||||
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions))
|
||||
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(),
|
||||
s.InformerFactory.KubeSphereSharedInformerFactory()),
|
||||
am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()),
|
||||
s.Config.AuthenticationOptions))
|
||||
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
|
||||
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
|
||||
}
|
||||
@@ -190,12 +193,12 @@ func (s *APIServer) buildHandlerChain() {
|
||||
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
|
||||
|
||||
// union authorizers are ordered, don't change the order here
|
||||
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
|
||||
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory())))
|
||||
handler = filters.WithAuthorization(handler, authorizers)
|
||||
|
||||
// authenticators are unordered
|
||||
authn := unionauth.New(anonymous.NewAuthenticator(),
|
||||
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
|
||||
basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()))),
|
||||
bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
|
||||
handler = filters.WithAuthentication(handler, authn)
|
||||
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
||||
@@ -275,6 +278,10 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
|
||||
|
||||
ksGVRs := []schema.GroupVersionResource{
|
||||
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
|
||||
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "users"},
|
||||
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "roles"},
|
||||
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "rolebindings"},
|
||||
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "policyrules"},
|
||||
{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"},
|
||||
}
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username,
|
||||
return &authenticator.Response{
|
||||
User: &user.DefaultInfo{
|
||||
Name: providedUser.GetName(),
|
||||
UID: providedUser.GetUID(),
|
||||
UID: string(providedUser.GetUID()),
|
||||
Groups: []string{user.AllAuthenticated},
|
||||
},
|
||||
}, true, nil
|
||||
|
||||
@@ -21,7 +21,7 @@ package token
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dgrijalva/jwt-go"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/server/errors"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
@@ -69,7 +69,7 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &iam.User{Name: clm.Username, UID: clm.UID}, nil
|
||||
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
|
||||
}
|
||||
|
||||
func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) {
|
||||
|
||||
@@ -20,7 +20,7 @@ package token
|
||||
|
||||
import (
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
|
||||
"kubesphere.io/kubesphere/pkg/simple/client/cache"
|
||||
"testing"
|
||||
@@ -48,7 +48,7 @@ func TestJwtTokenIssuer(t *testing.T) {
|
||||
}
|
||||
|
||||
for _, testCase := range testCases {
|
||||
user := &iam.User{
|
||||
user := &user.DefaultInfo{
|
||||
Name: testCase.name,
|
||||
UID: testCase.uid,
|
||||
}
|
||||
|
||||
@@ -21,6 +21,9 @@ package authorizerfactory
|
||||
import (
|
||||
"context"
|
||||
"github.com/open-policy-agent/opa/rego"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/klog"
|
||||
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||
)
|
||||
@@ -29,115 +32,126 @@ type opaAuthorizer struct {
|
||||
am am.AccessManagementInterface
|
||||
}
|
||||
|
||||
const (
|
||||
permissionUndefined = "permission undefined"
|
||||
defaultRegoQuery = "data.authz.allow"
|
||||
)
|
||||
|
||||
// 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())
|
||||
// Error returned when an internal error occurs
|
||||
// Reason must be returned when access is denied
|
||||
globalRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.GlobalScope, "", attr.GetUser().GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return authorizer.DecisionDeny, err.Error(), nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
// check platform role policy rules
|
||||
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
|
||||
return authorized, reason, err
|
||||
if authorized, reason, err = o.makeDecision(globalRole, attr); authorized == authorizer.DecisionAllow {
|
||||
return authorized, reason, nil
|
||||
}
|
||||
|
||||
// it's not in cluster resource, permission denied
|
||||
if attr.GetCluster() == "" {
|
||||
return authorizer.DecisionDeny, "permission undefined", nil
|
||||
return authorizer.DecisionDeny, permissionUndefined, nil
|
||||
}
|
||||
|
||||
clusterRole, err := o.am.GetClusterRole(attr.GetCluster(), attr.GetUser().GetName())
|
||||
clusterRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.ClusterScope, attr.GetCluster(), attr.GetUser().GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return authorizer.DecisionDeny, err.Error(), nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
// check cluster role policy rules
|
||||
if a, r, e := makeDecision(clusterRole, attr); a == authorizer.DecisionAllow {
|
||||
return a, r, e
|
||||
if authorized, reason, err := o.makeDecision(clusterRole, attr); authorized == authorizer.DecisionAllow {
|
||||
return authorized, reason, nil
|
||||
} else if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
// it's not in cluster resource, permission denied
|
||||
if attr.GetWorkspace() == "" && attr.GetNamespace() == "" {
|
||||
return authorizer.DecisionDeny, "permission undefined", nil
|
||||
return authorizer.DecisionDeny, permissionUndefined, nil
|
||||
}
|
||||
|
||||
workspaceRole, err := o.am.GetWorkspaceRole(attr.GetWorkspace(), attr.GetUser().GetName())
|
||||
workspaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.WorkspaceScope, attr.GetWorkspace(), attr.GetUser().GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return authorizer.DecisionDeny, err.Error(), nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
// check workspace role policy rules
|
||||
if a, r, e := makeDecision(workspaceRole, attr); a == authorizer.DecisionAllow {
|
||||
return a, r, e
|
||||
if authorized, reason, err := o.makeDecision(workspaceRole, attr); authorized == authorizer.DecisionAllow {
|
||||
return authorized, reason, err
|
||||
} else if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
// it's not in workspace resource, permission denied
|
||||
if attr.GetNamespace() == "" {
|
||||
return authorizer.DecisionDeny, "permission undefined", nil
|
||||
return authorizer.DecisionDeny, permissionUndefined, nil
|
||||
}
|
||||
|
||||
if attr.GetNamespace() != "" {
|
||||
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
// check namespace role policy rules
|
||||
if a, r, e := makeDecision(namespaceRole, attr); a == authorizer.DecisionAllow {
|
||||
return a, r, e
|
||||
namespaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.NamespaceScope, attr.GetNamespace(), attr.GetUser().GetName())
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
return authorizer.DecisionDeny, err.Error(), nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
// check namespace role policy rules
|
||||
if authorized, reason, err := o.makeDecision(namespaceRole, attr); authorized == authorizer.DecisionAllow {
|
||||
return authorized, reason, err
|
||||
} else if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
return authorizer.DecisionDeny, "", nil
|
||||
return authorizer.DecisionDeny, permissionUndefined, nil
|
||||
}
|
||||
|
||||
// Make decision base on role
|
||||
func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
func (o *opaAuthorizer) makeDecision(role *iamv1alpha2.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||
|
||||
// Call the rego.New function to create an object that can be prepared or evaluated
|
||||
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
|
||||
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
|
||||
for _, ruleRef := range role.Rules {
|
||||
rule, err := o.am.GetPolicyRule(ruleRef.Name)
|
||||
if err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
continue
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
// Call the rego.New function to create an object that can be prepared or evaluated
|
||||
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
|
||||
query, err := rego.New(rego.Query(defaultRegoQuery), rego.Module("authz.rego", rule.Rego)).PrepareForEval(context.Background())
|
||||
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
if err != nil {
|
||||
klog.Errorf("rule syntax error:%s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
if err != nil {
|
||||
klog.Errorf("rule syntax error:%s", err)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(results) > 0 && results[0].Expressions[0].Value == true {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
}
|
||||
|
||||
// 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))
|
||||
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
|
||||
if len(results) > 0 && results[0].Expressions[0].Value == true {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
|
||||
return authorizer.DecisionDeny, "permission undefined", nil
|
||||
return authorizer.DecisionDeny, permissionUndefined, nil
|
||||
}
|
||||
|
||||
func NewOPAAuthorizer(am am.AccessManagementInterface) *opaAuthorizer {
|
||||
|
||||
@@ -19,22 +19,44 @@
|
||||
package authorizerfactory
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
iamvealpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
|
||||
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
|
||||
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
|
||||
"kubesphere.io/kubesphere/pkg/models/iam/am"
|
||||
"testing"
|
||||
)
|
||||
|
||||
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
|
||||
func prepare() (am.AccessManagementInterface, error) {
|
||||
rules := []*iamvealpha2.PolicyRule{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "always-allow",
|
||||
},
|
||||
Rego: "package authz\ndefault allow = true",
|
||||
}, {
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String(),
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "always-deny",
|
||||
},
|
||||
Rego: "package authz\ndefault allow = false",
|
||||
}, {
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "manage-cluster1-resources",
|
||||
},
|
||||
Rego: `package authz
|
||||
default allow = false
|
||||
allow {
|
||||
resources_in_cluster1
|
||||
@@ -42,11 +64,168 @@ allow {
|
||||
resources_in_cluster1 {
|
||||
input.Cluster == "cluster1"
|
||||
}`,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
operator := am.NewFakeAMOperator()
|
||||
operator.Prepare(platformRoles, nil, nil, nil)
|
||||
roles := []*iamvealpha2.Role{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "global-admin",
|
||||
},
|
||||
Target: iamvealpha2.Target{
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
Name: "",
|
||||
},
|
||||
Rules: []iamvealpha2.RuleRef{
|
||||
{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
Name: "always-allow",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "anonymous",
|
||||
},
|
||||
Target: iamvealpha2.Target{
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
Name: "",
|
||||
},
|
||||
Rules: []iamvealpha2.RuleRef{
|
||||
{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
Name: "always-deny",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cluster1-admin",
|
||||
},
|
||||
Target: iamvealpha2.Target{
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
Name: "",
|
||||
},
|
||||
Rules: []iamvealpha2.RuleRef{
|
||||
{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.PolicyRuleKind,
|
||||
Name: "manage-cluster1-resources",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
roleBindings := []*iamvealpha2.RoleBinding{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleBindingKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "global-admin",
|
||||
},
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
RoleRef: iamvealpha2.RoleRef{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
Name: "global-admin",
|
||||
},
|
||||
Subjects: []iamvealpha2.Subject{
|
||||
{
|
||||
Kind: iamvealpha2.UserKind,
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Name: "admin",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleBindingKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "anonymous",
|
||||
},
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
RoleRef: iamvealpha2.RoleRef{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
Name: "anonymous",
|
||||
},
|
||||
Subjects: []iamvealpha2.Subject{
|
||||
{
|
||||
Kind: iamvealpha2.UserKind,
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Name: user.Anonymous,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: iamvealpha2.RoleBindingKind,
|
||||
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "cluster1-admin",
|
||||
},
|
||||
Scope: iamvealpha2.GlobalScope,
|
||||
RoleRef: iamvealpha2.RoleRef{
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Kind: iamvealpha2.RoleKind,
|
||||
Name: "cluster1-admin",
|
||||
},
|
||||
Subjects: []iamvealpha2.Subject{
|
||||
{
|
||||
Kind: iamvealpha2.UserKind,
|
||||
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
|
||||
Name: "tom",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
ksClient := fake.NewSimpleClientset()
|
||||
informerFactory := externalversions.NewSharedInformerFactory(ksClient, 0)
|
||||
|
||||
for _, rule := range rules {
|
||||
err := informerFactory.Iam().V1alpha2().PolicyRules().Informer().GetIndexer().Add(rule)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add rule:%s", err)
|
||||
}
|
||||
}
|
||||
for _, role := range roles {
|
||||
err := informerFactory.Iam().V1alpha2().Roles().Informer().GetIndexer().Add(role)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add role:%s", err)
|
||||
}
|
||||
}
|
||||
for _, roleBinding := range roleBindings {
|
||||
err := informerFactory.Iam().V1alpha2().RoleBindings().Informer().GetIndexer().Add(roleBinding)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("add role binding:%s", err)
|
||||
}
|
||||
}
|
||||
|
||||
operator := am.NewAMOperator(ksClient, informerFactory)
|
||||
|
||||
return operator, nil
|
||||
}
|
||||
|
||||
func TestGlobalRole(t *testing.T) {
|
||||
|
||||
operator, err := prepare()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
opa := NewOPAAuthorizer(operator)
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package query
|
||||
|
||||
type Field string
|
||||
type Value string
|
||||
|
||||
const (
|
||||
FieldName = "name"
|
||||
FieldUID = "uid"
|
||||
FieldCreationTimeStamp = "creationTimestamp"
|
||||
FieldLastUpdateTimestamp = "lastUpdateTimestamp"
|
||||
FieldLabel = "label"
|
||||
FieldAnnotation = "annotation"
|
||||
FieldNamespace = "namespace"
|
||||
FieldStatus = "status"
|
||||
FieldApplication = "application"
|
||||
FieldOwner = "owner"
|
||||
FieldOwnerReference = "ownerReference"
|
||||
FieldOwnerKind = "ownerKind"
|
||||
)
|
||||
|
||||
@@ -23,9 +25,11 @@ var SortableFields = []Field{
|
||||
// Field contains all the query field that can be compared
|
||||
var ComparableFields = []Field{
|
||||
FieldName,
|
||||
FieldUID,
|
||||
FieldLabel,
|
||||
FieldAnnotation,
|
||||
FieldNamespace,
|
||||
FieldStatus,
|
||||
FieldApplication,
|
||||
FieldOwner,
|
||||
FieldOwnerReference,
|
||||
FieldOwnerKind,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ package query
|
||||
import (
|
||||
"github.com/emicklei/go-restful"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -16,24 +15,6 @@ const (
|
||||
ParameterAscending = "ascending"
|
||||
)
|
||||
|
||||
type Comparable interface {
|
||||
Compare(Comparable) int
|
||||
|
||||
Contains(Comparable) bool
|
||||
}
|
||||
|
||||
type ComparableString string
|
||||
|
||||
func (c ComparableString) Compare(comparable Comparable) int {
|
||||
other := comparable.(ComparableString)
|
||||
return strings.Compare(string(c), string(other))
|
||||
}
|
||||
|
||||
func (c ComparableString) Contains(comparable Comparable) bool {
|
||||
other := comparable.(ComparableString)
|
||||
return strings.Contains(string(c), string(other))
|
||||
}
|
||||
|
||||
// Query represents api search terms
|
||||
type Query struct {
|
||||
Pagination *Pagination
|
||||
@@ -52,29 +33,30 @@ type Pagination struct {
|
||||
// items per page
|
||||
Limit int
|
||||
|
||||
// page number
|
||||
Page int
|
||||
// offset
|
||||
Offset int
|
||||
}
|
||||
|
||||
var NoPagination = newPagination(-1, -1)
|
||||
var NoPagination = newPagination(-1, 0)
|
||||
|
||||
func newPagination(limit int, page int) *Pagination {
|
||||
// make sure that pagination is valid
|
||||
func newPagination(limit int, offset int) *Pagination {
|
||||
return &Pagination{
|
||||
Limit: limit,
|
||||
Page: page,
|
||||
Limit: limit,
|
||||
Offset: offset,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Pagination) IsValidPagintaion() bool {
|
||||
return p.Limit >= 0 && p.Page >= 0
|
||||
}
|
||||
func (p *Pagination) GetValidPagination(total int) (startIndex, endIndex int) {
|
||||
if p.Limit == NoPagination.Limit {
|
||||
return 0, total
|
||||
}
|
||||
|
||||
func (p *Pagination) IsPageAvailable(total, startIndex int) bool {
|
||||
return total > startIndex && p.Limit > 0
|
||||
}
|
||||
if p.Limit < 0 || p.Offset < 0 {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int) {
|
||||
startIndex = p.Limit * p.Page
|
||||
startIndex = p.Limit * p.Offset
|
||||
endIndex = startIndex + p.Limit
|
||||
|
||||
if endIndex > total {
|
||||
@@ -86,33 +68,33 @@ func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int)
|
||||
|
||||
func New() *Query {
|
||||
return &Query{
|
||||
Pagination: &Pagination{
|
||||
Limit: -1,
|
||||
Page: -1,
|
||||
},
|
||||
SortBy: "",
|
||||
Ascending: false,
|
||||
Filters: []Filter{},
|
||||
Pagination: NoPagination,
|
||||
SortBy: "",
|
||||
Ascending: false,
|
||||
Filters: []Filter{},
|
||||
}
|
||||
}
|
||||
|
||||
type Filter struct {
|
||||
Field Field
|
||||
Value Comparable
|
||||
Value Value
|
||||
}
|
||||
|
||||
func ParseQueryParameter(request *restful.Request) *Query {
|
||||
query := New()
|
||||
|
||||
limit, err := strconv.ParseInt(request.QueryParameter("limit"), 10, 0)
|
||||
limit, err := strconv.Atoi(request.QueryParameter("limit"))
|
||||
// equivalent to undefined, use the default value
|
||||
if err != nil {
|
||||
query.Pagination = NoPagination
|
||||
limit = -1
|
||||
}
|
||||
page, err := strconv.Atoi(request.QueryParameter("page"))
|
||||
// equivalent to undefined, use the default value
|
||||
if err != nil {
|
||||
page = 1
|
||||
}
|
||||
|
||||
page, err := strconv.ParseInt(request.QueryParameter("page"), 10, 0)
|
||||
if err == nil {
|
||||
query.Pagination = newPagination(int(limit), int(page-1))
|
||||
}
|
||||
query.Pagination = newPagination(limit, page-1)
|
||||
|
||||
query.SortBy = Field(defaultString(request.QueryParameter("sortBy"), FieldCreationTimeStamp))
|
||||
|
||||
@@ -128,13 +110,12 @@ func ParseQueryParameter(request *restful.Request) *Query {
|
||||
if len(f) != 0 {
|
||||
query.Filters = append(query.Filters, Filter{
|
||||
Field: field,
|
||||
Value: ComparableString(f),
|
||||
Value: Value(f),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return query
|
||||
|
||||
}
|
||||
|
||||
func defaultString(value, defaultValue string) string {
|
||||
|
||||
@@ -16,7 +16,7 @@ func TestParseQueryParameter(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
"test normal case",
|
||||
"name=foo&status=Running&application=book&page=1&limit=10&ascending=true",
|
||||
"label=app.kubernetes.io/name:book&name=foo&status=Running&page=1&limit=10&ascending=true",
|
||||
&Query{
|
||||
Pagination: newPagination(10, 0),
|
||||
SortBy: FieldCreationTimeStamp,
|
||||
@@ -24,15 +24,15 @@ func TestParseQueryParameter(t *testing.T) {
|
||||
Filters: []Filter{
|
||||
{
|
||||
FieldName,
|
||||
ComparableString("foo"),
|
||||
Value("foo"),
|
||||
},
|
||||
{
|
||||
FieldLabel,
|
||||
Value("app.kubernetes.io/name:book"),
|
||||
},
|
||||
{
|
||||
FieldStatus,
|
||||
ComparableString("Running"),
|
||||
},
|
||||
{
|
||||
FieldApplication,
|
||||
ComparableString("book"),
|
||||
Value("Running"),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -41,13 +41,10 @@ func TestParseQueryParameter(t *testing.T) {
|
||||
"test bad case",
|
||||
"xxxx=xxxx&dsfsw=xxxx&page=abc&limit=add&ascending=ssss",
|
||||
&Query{
|
||||
Pagination: &Pagination{
|
||||
Limit: -1,
|
||||
Page: -1,
|
||||
},
|
||||
SortBy: FieldCreationTimeStamp,
|
||||
Ascending: false,
|
||||
Filters: []Filter{},
|
||||
Pagination: NoPagination,
|
||||
SortBy: FieldCreationTimeStamp,
|
||||
Ascending: false,
|
||||
Filters: []Filter{},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user