This is an automated email from the ASF dual-hosted git repository.
littlecui pushed a commit to branch dev
in repository https://gitbox.apache.org/repos/asf/servicecomb-kie.git
The following commit(s) were added to refs/heads/dev by this push:
new c589029 Feature:新增rbac认证 (#262)
c589029 is described below
commit c589029525e1588514f6e787f0b9db6fd605673c
Author: little-cui <[email protected]>
AuthorDate: Thu Nov 10 01:05:04 2022 +0800
Feature:新增rbac认证 (#262)
* 增加RBAC认证
* 增加RBAC认证
Co-authored-by: 123yangxiong <[email protected]>
---
go.sum | 5 --
pkg/util/concurrent_map_go19.go | 98 +++++++++++++++++++++
pkg/util/context.go | 106 ++++++++++++++++++++++
server/datasource/auth/account_role.go | 106 ++++++++++++++++++++++
server/datasource/auth/checkKVPerm.go | 75 ++++++++++++++++
server/datasource/auth/decision.go | 155 +++++++++++++++++++++++++++++++++
server/datasource/auth/filter_kvdoc.go | 53 +++++++++++
server/datasource/auth/makeScope.go | 28 ++++++
server/datasource/auth/perm.go | 36 ++++++++
server/datasource/auth/rbac.go | 36 ++++++++
server/datasource/etcd/kv/kv_dao.go | 43 ++++++++-
server/datasource/etcd/rbac/role.go | 60 +++++++++++++
server/datasource/mongo/rbac/role.go | 60 +++++++++++++
server/datasource/rbac/rbac.go | 13 +++
server/rbac/rbac.go | 2 +
server/resource/v1/kv_resource.go | 1 +
16 files changed, 871 insertions(+), 6 deletions(-)
diff --git a/go.sum b/go.sum
index a24292e..170a95d 100644
--- a/go.sum
+++ b/go.sum
@@ -117,7 +117,6 @@ github.com/dustin/go-humanize v1.0.0/go.mod
h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25Kn
github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod
h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod
h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
-github.com/emicklei/go-restful v2.12.0+incompatible
h1:SIvoTSbsMEwuM3dzFirLwKc4BH6VXP5CNf+G1FfJVr4=
github.com/emicklei/go-restful v2.12.0+incompatible/go.mod
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/emicklei/go-restful
v2.15.1-0.20220703112237-d9c71e118c95+incompatible
h1:o87Jf90Yb9ZvrdJKy2jYAdzosa7MtC2H214tJxJx7As=
github.com/emicklei/go-restful
v2.15.1-0.20220703112237-d9c71e118c95+incompatible/go.mod
h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
@@ -160,11 +159,8 @@ github.com/go-chassis/go-archaius v1.5.1/go.mod
h1:QPwvvtBxvwiC48rmydoAqxopqOr93
github.com/go-chassis/go-archaius v1.5.2-0.20210301074935-e4694f6b077b
h1:0u2kNkdw+J8OublV27I1Xn6berg3MiUm5GSl/T3qXSg=
github.com/go-chassis/go-archaius v1.5.2-0.20210301074935-e4694f6b077b/go.mod
h1:qjfG7opNF/QTzj7SyVIn/eIawaPhl7xeGgg8kxzFsDw=
github.com/go-chassis/go-chassis/v2 v2.3.0/go.mod
h1:iyJ2DWSkqfnCmad/0Il9nXWHaob7RcwPGlIDRNxccH0=
-github.com/go-chassis/go-chassis/v2 v2.3.1-0.20211217084436-360a6a6a0ef3
h1:Ctxgyo/gc5sogcCJLoIvIKj9kEUzJOlbENpYO/nSPWA=
-github.com/go-chassis/go-chassis/v2
v2.3.1-0.20211217084436-360a6a6a0ef3/go.mod
h1:oMnRaz2P+OPTtEfh2HEuiF9YzdYHQrNVPXdnbKzKO9w=
github.com/go-chassis/go-chassis/v2 v2.5.2
h1:Au+rNk3ZYoYHp73HHWMVrI5FIIOBVlt12SyXZjDnC2Q=
github.com/go-chassis/go-chassis/v2 v2.5.2/go.mod
h1:Ru3kB+ndcxxFZl3clnuihKVv+gOi7lOVR9Ghhtw8jaQ=
-github.com/go-chassis/go-restful-swagger20 v1.0.3
h1:kWfeLwMwJZVkXP1zNyFpkmR41UZ55UTcOptTteXhvEs=
github.com/go-chassis/go-restful-swagger20 v1.0.3/go.mod
h1:eW62fYuzlNFDvIacB6AV8bhUDCTy4myfTCv0bT9Gbb0=
github.com/go-chassis/go-restful-swagger20
v1.0.4-0.20220704025524-9243cbee26b7
h1:EOIGW+inOz52zh6vgr9EQHvvgL2w/VghAeCQIFOVUSE=
github.com/go-chassis/go-restful-swagger20
v1.0.4-0.20220704025524-9243cbee26b7/go.mod
h1:pSGkT+ksxlMgytyJb4IAz8aZih6OLE1++d9CE6aO9Hg=
@@ -768,7 +764,6 @@ golang.org/x/text v0.3.0/go.mod
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod
h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
diff --git a/pkg/util/concurrent_map_go19.go b/pkg/util/concurrent_map_go19.go
new file mode 100644
index 0000000..6dc0518
--- /dev/null
+++ b/pkg/util/concurrent_map_go19.go
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import "sync"
+
+type MapItem struct {
+ Key interface{}
+ Value interface{}
+}
+
+type ConcurrentMap struct {
+ mapper sync.Map
+ fetchLock sync.RWMutex
+}
+
+func (cm *ConcurrentMap) Put(key, val interface{}) {
+ cm.fetchLock.RLock()
+ cm.mapper.Store(key, val)
+ cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) PutIfAbsent(key, val interface{}) (exist interface{})
{
+ cm.fetchLock.RLock()
+ exist, _ = cm.mapper.LoadOrStore(key, val)
+ cm.fetchLock.RUnlock()
+ return
+}
+
+func (cm *ConcurrentMap) Fetch(key interface{}, f func() (interface{}, error))
(v interface{}, err error) {
+ if exist, b := cm.mapper.Load(key); b {
+ return exist, nil
+ }
+
+ cm.fetchLock.Lock()
+ if exist, b := cm.mapper.Load(key); b {
+ cm.fetchLock.Unlock()
+ return exist, nil
+ }
+
+ if v, err = f(); err != nil {
+ cm.fetchLock.Unlock()
+ return nil, err
+ }
+
+ cm.mapper.Store(key, v)
+ cm.fetchLock.Unlock()
+ return
+}
+
+func (cm *ConcurrentMap) Get(key interface{}) (val interface{}, b bool) {
+ return cm.mapper.Load(key)
+}
+
+func (cm *ConcurrentMap) Remove(key interface{}) {
+ cm.fetchLock.RLock()
+ cm.mapper.Delete(key)
+ cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) Clear() {
+ cm.fetchLock.RLock()
+ cm.mapper = sync.Map{}
+ cm.fetchLock.RUnlock()
+}
+
+func (cm *ConcurrentMap) Size() (s int) {
+ cm.mapper.Range(func(_, _ interface{}) bool {
+ s++
+ return true
+ })
+ return
+}
+
+func (cm *ConcurrentMap) ForEach(f func(item MapItem) (next bool)) {
+ cm.mapper.Range(func(key, value interface{}) bool {
+ return f(MapItem{key, value})
+ })
+}
+
+func NewConcurrentMap(_ int) *ConcurrentMap {
+ return new(ConcurrentMap)
+}
diff --git a/pkg/util/context.go b/pkg/util/context.go
new file mode 100644
index 0000000..3b6c168
--- /dev/null
+++ b/pkg/util/context.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package util
+
+import (
+ "context"
+ "google.golang.org/grpc/metadata"
+ "net/http"
+ "time"
+)
+
+type StringContext struct {
+ parentCtx context.Context
+ kv *ConcurrentMap
+}
+
+type CtxKey string
+
+func (c *StringContext) Deadline() (deadline time.Time, ok bool) {
+ return c.parentCtx.Deadline()
+}
+
+func (c *StringContext) Done() <-chan struct{} {
+ return c.parentCtx.Done()
+}
+
+func (c *StringContext) Err() error {
+ return c.parentCtx.Err()
+}
+
+func (c *StringContext) Value(key interface{}) interface{} {
+ k, ok := key.(CtxKey)
+ if !ok {
+ return c.parentCtx.Value(key)
+ }
+ v, ok := c.kv.Get(k)
+ if !ok {
+ return FromContext(c.parentCtx, k)
+ }
+ return v
+}
+
+func (c *StringContext) SetKV(key CtxKey, val interface{}) {
+ c.kv.Put(key, val)
+}
+
+func NewStringContext(ctx context.Context) *StringContext {
+ strCtx, ok := ctx.(*StringContext)
+ if !ok {
+ strCtx = &StringContext{
+ parentCtx: ctx,
+ kv: NewConcurrentMap(0),
+ }
+ }
+ return strCtx
+}
+
+func SetContext(ctx context.Context, key CtxKey, val interface{})
context.Context {
+ strCtx := NewStringContext(ctx)
+ strCtx.SetKV(key, val)
+ return strCtx
+}
+
+// FromContext return the value from ctx, return empty STRING if not found
+func FromContext(ctx context.Context, key CtxKey) interface{} {
+ if v := ctx.Value(key); v != nil {
+ return v
+ }
+ return FromMetadata(ctx, key)
+}
+
+func SetRequestContext(r *http.Request, key CtxKey, val interface{})
*http.Request {
+ ctx := r.Context()
+ ctx = SetContext(ctx, key, val)
+ if ctx != r.Context() {
+ nr := r.WithContext(ctx)
+ *r = *nr
+ }
+ return r
+}
+
+func FromMetadata(ctx context.Context, key CtxKey) string {
+ md, ok := metadata.FromIncomingContext(ctx)
+ if !ok {
+ return ""
+ }
+ if values, ok := md[string(key)]; ok && len(values) > 0 {
+ return values[0]
+ }
+ return ""
+}
diff --git a/server/datasource/auth/account_role.go
b/server/datasource/auth/account_role.go
new file mode 100644
index 0000000..5235af2
--- /dev/null
+++ b/server/datasource/auth/account_role.go
@@ -0,0 +1,106 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "strings"
+
+ "github.com/apache/servicecomb-kie/pkg/util"
+ rbacmodel "github.com/go-chassis/cari/rbac"
+ "github.com/go-chassis/go-chassis/v2/security/authr"
+ "github.com/go-chassis/go-chassis/v2/server/restful"
+ "github.com/go-chassis/openlog"
+)
+
+const (
+ RootName = "root"
+)
+
+var ErrNoRoles = errors.New("no role found in token")
+
+func GetAccountFromReq(ctx context.Context) (*rbacmodel.Account, error) {
+ v, ok := util.FromContext(ctx, restful.HeaderAuth).(string)
+ if !ok || v == "" {
+ return nil, rbacmodel.NewError(rbacmodel.ErrNoAuthHeader, "")
+ }
+
+ accountExist(ctx, v)
+
+ s := strings.Split(v, " ")
+ if len(s) != 2 {
+ return nil, rbacmodel.ErrInvalidHeader
+ }
+ to := s[1]
+
+ claims, err := authr.Authenticate(ctx, to)
+ if err != nil {
+ return nil, err
+ }
+
+ m, ok := claims.(map[string]interface{})
+ if !ok {
+ openlog.Error("claims convert failed",
openlog.WithErr(rbacmodel.ErrConvert))
+ return nil, rbacmodel.ErrConvert
+ }
+ account, err := rbacmodel.GetAccount(m)
+ if err != nil {
+ openlog.Error("get account from token failed",
openlog.WithErr(err))
+ return nil, err
+ }
+ if len(account.Roles) == 0 {
+ openlog.Error("no role found in token")
+ return nil, ErrNoRoles
+ }
+
+ err = accountExist(ctx, account.Name)
+ if err != nil {
+ return nil, err
+ }
+
+ return account, nil
+}
+
+func accountExist(ctx context.Context, user string) error {
+ // if root should pass, cause of root initialization
+ if user == RootName {
+ return nil
+ }
+ exist, err := dbacInstance.AccountExist(ctx, user)
+ if err != nil {
+ return err
+ }
+ if !exist {
+ msg := fmt.Sprintf("account [%s] is deleted", user)
+ return
rbacmodel.NewError(rbacmodel.ErrTokenOwnedAccountDeleted, msg)
+ }
+ return nil
+}
+
+func filterRoles(roleList []string) (hasAdmin bool, normalRoles []string) {
+ for _, r := range roleList {
+ if r == rbacmodel.RoleAdmin {
+ hasAdmin = true
+ return
+ }
+ normalRoles = append(normalRoles, r)
+ }
+ return
+}
diff --git a/server/datasource/auth/checkKVPerm.go
b/server/datasource/auth/checkKVPerm.go
new file mode 100644
index 0000000..87e3c74
--- /dev/null
+++ b/server/datasource/auth/checkKVPerm.go
@@ -0,0 +1,75 @@
+package auth
+
+import (
+ "context"
+ "github.com/apache/servicecomb-kie/pkg/model"
+)
+
+func CheckGetOneKV(ctx context.Context, kv *model.KVDoc) error {
+ var labelsList []map[string]string
+ if kv.Labels != nil {
+ labelsList = append(labelsList, kv.Labels)
+ }
+ scop := &ResourceScope{
+ Type: "config",
+ Verb: "get",
+ Labels: labelsList,
+ }
+ _, err := CheckPermByReq(ctx, scop)
+ return err
+}
+
+func FilterKVList(ctx context.Context, kvs []*model.KVDoc) ([]*model.KVDoc,
error) {
+ scop := &ResourceScope{
+ Type: "config",
+ Verb: "get",
+ }
+ labels, err := CheckPermByReq(ctx, scop)
+ if err != nil {
+ return nil, err
+ }
+
+ return FilterKV(kvs, labels), err
+}
+
+func CheckCreateOneKV(ctx context.Context, kv *model.KVDoc) error {
+ var labelsList []map[string]string
+ if kv.Labels != nil {
+ labelsList = append(labelsList, kv.Labels)
+ }
+ scop := &ResourceScope{
+ Type: "config",
+ Verb: "create",
+ Labels: labelsList,
+ }
+ _, err := CheckPermByReq(ctx, scop)
+ return err
+}
+
+func CheckDeleteOneKV(ctx context.Context, kv *model.KVDoc) error {
+ var labelsList []map[string]string
+ if kv.Labels != nil {
+ labelsList = append(labelsList, kv.Labels)
+ }
+ scop := &ResourceScope{
+ Type: "config",
+ Verb: "delete",
+ Labels: labelsList,
+ }
+ _, err := CheckPermByReq(ctx, scop)
+ return err
+}
+
+func CheckUpdateOneKV(ctx context.Context, kv *model.KVDoc) error {
+ var labelsList []map[string]string
+ if kv.Labels != nil {
+ labelsList = append(labelsList, kv.Labels)
+ }
+ scop := &ResourceScope{
+ Type: "config",
+ Verb: "update",
+ Labels: labelsList,
+ }
+ _, err := CheckPermByReq(ctx, scop)
+ return err
+}
diff --git a/server/datasource/auth/decision.go
b/server/datasource/auth/decision.go
new file mode 100644
index 0000000..ae4f639
--- /dev/null
+++ b/server/datasource/auth/decision.go
@@ -0,0 +1,155 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import (
+ "context"
+ "fmt"
+
+ rbacmodel "github.com/go-chassis/cari/rbac"
+ "github.com/go-chassis/openlog"
+)
+
+// Allow return: matched labels(empty if no label defined), error
+func Allow(ctx context.Context, roleList []string,
+ targetResource *ResourceScope) ([]map[string]string, error) {
+ //TODO check project
+ allPerms, err := getPermsByRoles(ctx, roleList)
+ if err != nil {
+ openlog.Error("get role list errors", openlog.WithErr(err))
+ return nil, err
+ }
+ if len(allPerms) == 0 {
+ openlog.Warn("role list has no any permissions")
+ return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission, "role
has no any permissions")
+ }
+ allow, labelList := GetLabel(allPerms, targetResource.Type,
targetResource.Verb)
+ if !allow {
+ return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission,
+ fmt.Sprintf("role has no permissions[%s:%s]",
targetResource.Type, targetResource.Verb))
+ }
+ // allow, but no label found, means we can ignore the labels
+ if len(labelList) == 0 {
+ return nil, nil
+ }
+ // target resource needs no label, return without filter
+ if len(targetResource.Labels) == 0 {
+ return labelList, nil
+ }
+ // allow, and labels found, filter the labels
+ filteredLabelList := FilterLabel(targetResource.Labels, labelList)
+ // target resource label matches no label in permission, means not allow
+ if len(filteredLabelList) == 0 {
+ return nil, rbacmodel.NewError(rbacmodel.ErrNoPermission,
+ fmt.Sprintf("role has no permissions[%s:%s] for labels
%v",
+ targetResource.Type, targetResource.Verb,
targetResource.Labels))
+ }
+ return filteredLabelList, nil
+}
+
+func FilterLabel(targetResourceLabel []map[string]string, permLabelList
[]map[string]string) []map[string]string {
+ l := make([]map[string]string, 0)
+ for _, resourceLabel := range targetResourceLabel {
+ for _, label := range permLabelList {
+ if LabelMatched(resourceLabel, label) {
+ l = append(l, label)
+ }
+ }
+ }
+ return l
+}
+
+func LabelMatched(targetResourceLabel map[string]string, permLabel
map[string]string) bool {
+ for k, v := range permLabel {
+ if vv := targetResourceLabel[k]; vv != v {
+ return false
+ }
+ }
+ return true
+}
+
+func getPermsByRoles(ctx context.Context, roleList []string)
([]*rbacmodel.Permission, error) {
+ var allPerms = make([]*rbacmodel.Permission, 0)
+ for _, name := range roleList {
+ r, err := dbacInstance.GetRole(ctx, name)
+ if err == nil {
+ allPerms = append(allPerms, r.Perms...)
+ continue
+ }
+ if err.Error() == "role not exist" {
+ openlog.Warn(fmt.Sprintf("role [%s] not exist", name))
+ continue
+ }
+ openlog.Error(fmt.Sprintf("get role [%s] failed", name),
openlog.WithErr(err))
+ return nil, err
+ }
+ return allPerms, nil
+}
+
+// GetLabel checks if the perms have permission to operate the resource(ignore
label),
+// if one perm have the permission, add it's label to the result.
+func GetLabel(perms []*rbacmodel.Permission, targetResource, verb string)
(allow bool, labelList []map[string]string) {
+ for _, perm := range perms {
+ a, l := GetLabelFromSinglePerm(perm, targetResource, verb)
+ if !a {
+ continue
+ }
+ allow = true
+ // allow and has no label, return fast
+ if len(l) == 0 {
+ return true, nil
+ }
+ labelList = append(labelList, l...)
+ }
+ return
+}
+
+// GetLabel checks if the perm have permission to operate the resource(ignore
label),
+// if the perm have the permission, return it's label.
+func GetLabelFromSinglePerm(perm *rbacmodel.Permission, targetResource, verb
string) (allow bool, labelList []map[string]string) {
+ if !allowVerb(perm.Verbs, verb) {
+ return false, nil
+ }
+
+ return getResourceLabel(perm.Resources, targetResource)
+}
+
+func allowVerb(haystack []string, needle string) bool {
+ for _, e := range haystack {
+ if e == "*" || e == needle {
+ return true
+ }
+ }
+ return false
+}
+
+func getResourceLabel(resources []*rbacmodel.Resource, needle string) (allow
bool, labelList []map[string]string) {
+ for _, resource := range resources {
+ // filter the same resource
+ if resource.Type != needle {
+ continue
+ }
+ // has no label, return fast
+ if len(resource.Labels) == 0 {
+ return true, nil
+ }
+ labelList = append(labelList, resource.Labels)
+ allow = true
+ }
+ return
+}
diff --git a/server/datasource/auth/filter_kvdoc.go
b/server/datasource/auth/filter_kvdoc.go
new file mode 100644
index 0000000..7c4fd13
--- /dev/null
+++ b/server/datasource/auth/filter_kvdoc.go
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import "github.com/apache/servicecomb-kie/pkg/model"
+
+func FilterKV(kvs []*model.KVDoc, labelsList []map[string]string)
[]*model.KVDoc {
+ var permKVs []*model.KVDoc
+ for _, kv := range kvs {
+ for _, labels := range labelsList {
+ if !matchOne(kv, labels) {
+ continue
+ }
+ permKVs = append(permKVs, kv)
+ break
+ }
+ }
+ return permKVs
+}
+
+func matchOne(kv *model.KVDoc, labels map[string]string) bool {
+ for lk, lv := range labels {
+ if v, ok := kv.Labels[lk]; ok && v != lv {
+ return false
+ }
+ }
+ return true
+}
+
+func MatchLabelsList(kv *model.KVDoc, labelsList []map[string]string) bool {
+ for _, labels := range labelsList {
+ if !matchOne(kv, labels) {
+ continue
+ }
+ return true
+ }
+ return false
+}
diff --git a/server/datasource/auth/makeScope.go
b/server/datasource/auth/makeScope.go
new file mode 100644
index 0000000..0dbba46
--- /dev/null
+++ b/server/datasource/auth/makeScope.go
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+// ResourceScope is the resource scope parsed from request
+type ResourceScope struct {
+ Type string
+ // Labels is a map used to filter resource permissions during pre
verification.
+ // If a key of permission set is missing in the Labels, pre
verification will pass this key
+ Labels []map[string]string
+ // Verb is the apply resource action, e.g. "get", "create"
+ Verb string
+}
diff --git a/server/datasource/auth/perm.go b/server/datasource/auth/perm.go
new file mode 100644
index 0000000..98ca846
--- /dev/null
+++ b/server/datasource/auth/perm.go
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package auth
+
+import (
+ "context"
+)
+
+func CheckPermByReq(ctx context.Context, targetResource *ResourceScope)
([]map[string]string, error) {
+ account, err := GetAccountFromReq(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ hasAdmin, normalRoles := filterRoles(account.Roles)
+ if hasAdmin {
+ return nil, nil
+ }
+
+ return Allow(ctx, normalRoles, targetResource)
+}
diff --git a/server/datasource/auth/rbac.go b/server/datasource/auth/rbac.go
new file mode 100644
index 0000000..76cc3eb
--- /dev/null
+++ b/server/datasource/auth/rbac.go
@@ -0,0 +1,36 @@
+package auth
+
+import (
+ "context"
+ "github.com/apache/servicecomb-kie/pkg/util"
+ crbac "github.com/go-chassis/cari/rbac"
+ "github.com/go-chassis/go-chassis/v2/server/restful"
+ "net/http"
+ "os"
+
+ etcd "github.com/apache/servicecomb-kie/server/datasource/etcd/rbac"
+ mongo "github.com/apache/servicecomb-kie/server/datasource/mongo/rbac"
+)
+
+type DBAC_DB interface {
+ GetRole(ctx context.Context, name string) (*crbac.Role, error)
+ GenerateRBACRoleKey(name string) string
+ AccountExist(ctx context.Context, name string) (bool, error)
+ GenerateRBACAccountKey(name string) string
+}
+
+var dbacInstance DBAC_DB
+
+func init() {
+ d := os.Getenv("DBAC_DB")
+ if d == "mongodb" {
+ dbacInstance = &mongo.RBAC_Mongo{}
+ } else {
+ dbacInstance = &etcd.RBAC_ETCD{}
+ }
+}
+
+func SetContext(req *http.Request) {
+ v := req.Header.Get(restful.HeaderAuth)
+ util.SetRequestContext(req, restful.HeaderAuth, v)
+}
diff --git a/server/datasource/etcd/kv/kv_dao.go
b/server/datasource/etcd/kv/kv_dao.go
index 6296e08..e97a06c 100644
--- a/server/datasource/etcd/kv/kv_dao.go
+++ b/server/datasource/etcd/kv/kv_dao.go
@@ -20,6 +20,7 @@ package kv
import (
"context"
"encoding/json"
+ "github.com/apache/servicecomb-kie/server/datasource/auth"
"regexp"
"strings"
@@ -38,6 +39,11 @@ type Dao struct {
}
func (s *Dao) Create(ctx context.Context, kv *model.KVDoc, options
...datasource.WriteOption) (*model.KVDoc, error) {
+ //rbac
+ if err := auth.CheckCreateOneKV(ctx, kv); err != nil {
+ return nil, err
+ }
+
opts := datasource.NewWriteOptions(options...)
var exist bool
var err error
@@ -116,6 +122,12 @@ func (s *Dao) Update(ctx context.Context, kv *model.KVDoc,
options ...datasource
openlog.Error(err.Error())
return err
}
+
+ //rbac
+ if err := auth.CheckUpdateOneKV(ctx, &oldKV); err != nil {
+ return err
+ }
+
oldKV.LabelFormat = kv.LabelFormat
oldKV.Value = kv.Value
oldKV.Status = kv.Status
@@ -215,6 +227,12 @@ func (s *Dao) FindOneAndDelete(ctx context.Context, kvID,
project, domain string
func findOneAndDelete(ctx context.Context, kvID, project, domain string)
(*model.KVDoc, error) {
kvKey := key.KV(domain, project, kvID)
kvDoc := model.KVDoc{}
+
+ //rbac check
+ if _, err := getKVDoc(ctx, domain, project, kvID); err != nil {
+ return nil, err
+ }
+
resp, err := etcdadpt.ListAndDelete(ctx, kvKey)
if err != nil {
openlog.Error("delete Key error: " + err.Error())
@@ -269,7 +287,7 @@ func txnFindOneAndDelete(ctx context.Context, kvID,
project, domain string) (*mo
return kvDoc, nil
}
-// getKVDoc is to get kv
+// getKVDoc is to get kv for delete
func getKVDoc(ctx context.Context, domain, project, kvID string)
(*model.KVDoc, error) {
resp, err := etcdadpt.Get(ctx, key.KV(domain, project, kvID))
if err != nil {
@@ -285,6 +303,12 @@ func getKVDoc(ctx context.Context, domain, project, kvID
string) (*model.KVDoc,
openlog.Error("decode error: " + err.Error())
return nil, err
}
+
+ //rbac
+ if err := auth.CheckDeleteOneKV(ctx, curKV); err != nil {
+ return nil, err
+ }
+
return curKV, nil
}
@@ -302,6 +326,9 @@ func findManyAndDelete(ctx context.Context, kvIDs []string,
project, domain stri
var docs []*model.KVDoc
var opOptions []etcdadpt.OpOptions
for _, id := range kvIDs {
+ if _, err := getKVDoc(ctx, domain, project, id); err != nil {
+ continue
+ }
opOptions = append(opOptions,
etcdadpt.OpDel(etcdadpt.WithStrKey(key.KV(domain, project, id))))
}
resp, err := etcdadpt.ListAndDeleteMany(ctx, opOptions...)
@@ -412,6 +439,12 @@ func (s *Dao) Get(ctx context.Context, req
*model.GetKVRequest) (*model.KVDoc, e
openlog.Error("decode error: " + err.Error())
return nil, err
}
+
+ //rbac
+ if err := auth.CheckGetOneKV(ctx, curKV); err != nil {
+ return nil, err
+ }
+
return curKV, nil
}
@@ -465,6 +498,14 @@ func (s *Dao) List(ctx context.Context, project, domain
string, options ...datas
break
}
}
+
+ filterKVs, err := auth.FilterKVList(ctx, result.Data)
+ if err != nil {
+ return nil, err
+ }
+
+ result.Data = filterKVs
+
return pagingResult(result, opts), nil
}
diff --git a/server/datasource/etcd/rbac/role.go
b/server/datasource/etcd/rbac/role.go
new file mode 100644
index 0000000..204b6ac
--- /dev/null
+++ b/server/datasource/etcd/rbac/role.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rbac
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "github.com/go-chassis/openlog"
+
+ crbac "github.com/go-chassis/cari/rbac"
+ "github.com/little-cui/etcdadpt"
+)
+
+type RBAC_ETCD struct {
+}
+
+func (re *RBAC_ETCD) GetRole(ctx context.Context, name string) (*crbac.Role,
error) {
+ kv, err := etcdadpt.Get(ctx, re.GenerateRBACRoleKey(name))
+ if err != nil {
+ return nil, err
+ }
+ if kv == nil {
+ return nil, errors.New("role not exist")
+ }
+ role := &crbac.Role{}
+ err = json.Unmarshal(kv.Value, role)
+ if err != nil {
+ openlog.Error("role info format invalid", openlog.WithErr(err))
+ return nil, err
+ }
+ return role, nil
+}
+
+func (re *RBAC_ETCD) GenerateRBACRoleKey(name string) string {
+ return "/cse-sr/roles/" + name
+}
+
+func (re *RBAC_ETCD) AccountExist(ctx context.Context, name string) (bool,
error) {
+ return etcdadpt.Exist(ctx, re.GenerateRBACAccountKey(name))
+}
+
+func (re *RBAC_ETCD) GenerateRBACAccountKey(name string) string {
+ return "/cse-sr/accounts/" + name
+}
diff --git a/server/datasource/mongo/rbac/role.go
b/server/datasource/mongo/rbac/role.go
new file mode 100644
index 0000000..705666a
--- /dev/null
+++ b/server/datasource/mongo/rbac/role.go
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package rbac
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "github.com/go-chassis/openlog"
+
+ crbac "github.com/go-chassis/cari/rbac"
+ "github.com/little-cui/etcdadpt"
+)
+
+type RBAC_Mongo struct {
+}
+
+func (rm *RBAC_Mongo) GetRole(ctx context.Context, name string) (*crbac.Role,
error) {
+ kv, err := etcdadpt.Get(ctx, rm.GenerateRBACRoleKey(name))
+ if err != nil {
+ return nil, err
+ }
+ if kv == nil {
+ return nil, errors.New("role not exist")
+ }
+ role := &crbac.Role{}
+ err = json.Unmarshal(kv.Value, role)
+ if err != nil {
+ openlog.Error("role info format invalid", openlog.WithErr(err))
+ return nil, err
+ }
+ return role, nil
+}
+
+func (rm *RBAC_Mongo) GenerateRBACRoleKey(name string) string {
+ return "/cse-sr/roles/" + name
+}
+
+func (rm *RBAC_Mongo) AccountExist(ctx context.Context, name string) (bool,
error) {
+ return etcdadpt.Exist(ctx, rm.GenerateRBACAccountKey(name))
+}
+
+func (rm *RBAC_Mongo) GenerateRBACAccountKey(name string) string {
+ return "/cse-sr/accounts/" + name
+}
diff --git a/server/datasource/rbac/rbac.go b/server/datasource/rbac/rbac.go
new file mode 100644
index 0000000..a017ec0
--- /dev/null
+++ b/server/datasource/rbac/rbac.go
@@ -0,0 +1,13 @@
+package rbac
+
+import (
+ "context"
+ crbac "github.com/go-chassis/cari/rbac"
+)
+
+type DBAC_DB interface {
+ GetRole(ctx context.Context, name string) (*crbac.Role, error)
+ GenerateRBACRoleKey(name string) string
+ AccountExist(ctx context.Context, name string) (bool, error)
+ GenerateRBACAccountKey(name string) string
+}
diff --git a/server/rbac/rbac.go b/server/rbac/rbac.go
index b7614fa..8d38c74 100644
--- a/server/rbac/rbac.go
+++ b/server/rbac/rbac.go
@@ -18,6 +18,7 @@
package rbac
import (
+ "github.com/apache/servicecomb-kie/server/datasource/auth"
"net/http"
"os"
"path/filepath"
@@ -67,6 +68,7 @@ func Init() {
newReq :=
req.WithContext(rbac.NewContext(req.Context(), payload))
*req = *newReq
//TODO role perm check
+ auth.SetContext(req)
return nil
},
})
diff --git a/server/resource/v1/kv_resource.go
b/server/resource/v1/kv_resource.go
index 1fc087a..55126d5 100644
--- a/server/resource/v1/kv_resource.go
+++ b/server/resource/v1/kv_resource.go
@@ -91,6 +91,7 @@ func (r *KVResource) Post(rctx *restful.Context) {
// Put update a kv
func (r *KVResource) Put(rctx *restful.Context) {
var err error
+
kvID := rctx.ReadPathParameter(common.PathParamKVID)
project := rctx.ReadPathParameter(common.PathParameterProject)
kvReq := new(model.UpdateKVRequest)