2016-06-07 21:45:29 +03:00
|
|
|
// Copyright 2016 The etcd Authors
|
|
|
|
//
|
|
|
|
// Licensed 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 (
|
2016-06-14 00:17:10 +03:00
|
|
|
"bytes"
|
2016-06-07 21:45:29 +03:00
|
|
|
"sort"
|
|
|
|
|
|
|
|
"github.com/coreos/etcd/auth/authpb"
|
|
|
|
"github.com/coreos/etcd/mvcc/backend"
|
|
|
|
)
|
|
|
|
|
2016-09-12 05:49:06 +03:00
|
|
|
// isSubset returns true if a is a subset of b.
|
|
|
|
// If a is a prefix of b, then a is a subset of b.
|
|
|
|
// Given intervals [a1,a2) and [b1,b2), is
|
|
|
|
// the a interval a subset of b?
|
2016-06-07 21:45:29 +03:00
|
|
|
func isSubset(a, b *rangePerm) bool {
|
2016-06-13 22:57:14 +03:00
|
|
|
switch {
|
|
|
|
case len(a.end) == 0 && len(b.end) == 0:
|
|
|
|
// a, b are both keys
|
2016-06-14 01:42:49 +03:00
|
|
|
return bytes.Equal(a.begin, b.begin)
|
2016-06-13 22:57:14 +03:00
|
|
|
case len(b.end) == 0:
|
|
|
|
// b is a key, a is a range
|
|
|
|
return false
|
|
|
|
case len(a.end) == 0:
|
2016-09-12 05:49:06 +03:00
|
|
|
// a is a key, b is a range. need b1 <= a1 and a1 < b2
|
|
|
|
return bytes.Compare(b.begin, a.begin) <= 0 && bytes.Compare(a.begin, b.end) < 0
|
2016-06-13 22:57:14 +03:00
|
|
|
default:
|
2016-09-12 05:49:06 +03:00
|
|
|
// both are ranges. need b1 <= a1 and a2 <= b2
|
|
|
|
return bytes.Compare(b.begin, a.begin) <= 0 && bytes.Compare(a.end, b.end) <= 0
|
2016-06-13 22:57:14 +03:00
|
|
|
}
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-14 03:13:55 +03:00
|
|
|
func isRangeEqual(a, b *rangePerm) bool {
|
|
|
|
return bytes.Equal(a.begin, b.begin) && bytes.Equal(a.end, b.end)
|
|
|
|
}
|
|
|
|
|
2016-06-11 01:21:04 +03:00
|
|
|
// removeSubsetRangePerms removes any rangePerms that are subsets of other rangePerms.
|
2016-06-14 03:13:55 +03:00
|
|
|
// If there are equal ranges, removeSubsetRangePerms only keeps one of them.
|
2016-12-14 02:42:12 +03:00
|
|
|
// It returns a sorted rangePerm slice.
|
|
|
|
func removeSubsetRangePerms(perms []*rangePerm) (newp []*rangePerm) {
|
|
|
|
sort.Sort(RangePermSliceByBegin(perms))
|
|
|
|
var prev *rangePerm
|
2016-06-07 21:45:29 +03:00
|
|
|
for i := range perms {
|
2016-12-14 02:42:12 +03:00
|
|
|
if i == 0 {
|
|
|
|
prev = perms[i]
|
|
|
|
newp = append(newp, perms[i])
|
|
|
|
continue
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
2016-12-14 02:42:12 +03:00
|
|
|
if isRangeEqual(perms[i], prev) {
|
2016-06-07 21:45:29 +03:00
|
|
|
continue
|
|
|
|
}
|
2016-12-14 02:42:12 +03:00
|
|
|
if isSubset(perms[i], prev) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if isSubset(prev, perms[i]) {
|
|
|
|
prev = perms[i]
|
|
|
|
newp[len(newp)-1] = perms[i]
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
prev = perms[i]
|
2016-06-11 01:21:04 +03:00
|
|
|
newp = append(newp, perms[i])
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
2016-06-11 01:21:04 +03:00
|
|
|
return newp
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 01:21:04 +03:00
|
|
|
// mergeRangePerms merges adjacent rangePerms.
|
|
|
|
func mergeRangePerms(perms []*rangePerm) []*rangePerm {
|
2016-06-30 18:13:46 +03:00
|
|
|
var merged []*rangePerm
|
2016-06-11 01:21:04 +03:00
|
|
|
perms = removeSubsetRangePerms(perms)
|
2016-06-07 21:45:29 +03:00
|
|
|
|
|
|
|
i := 0
|
|
|
|
for i < len(perms) {
|
2016-06-11 01:21:04 +03:00
|
|
|
begin, next := i, i
|
2016-09-12 05:49:06 +03:00
|
|
|
for next+1 < len(perms) && bytes.Compare(perms[next].end, perms[next+1].begin) >= 0 {
|
2016-06-11 01:21:04 +03:00
|
|
|
next++
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
2016-09-12 05:49:06 +03:00
|
|
|
// don't merge ["a", "b") with ["b", ""), because perms[next+1].end is empty.
|
|
|
|
if next != begin && len(perms[next].end) > 0 {
|
|
|
|
merged = append(merged, &rangePerm{begin: perms[begin].begin, end: perms[next].end})
|
|
|
|
} else {
|
|
|
|
merged = append(merged, perms[begin])
|
|
|
|
if next != begin {
|
|
|
|
merged = append(merged, perms[next])
|
|
|
|
}
|
|
|
|
}
|
2016-06-11 01:21:04 +03:00
|
|
|
i = next + 1
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 01:21:04 +03:00
|
|
|
return merged
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 02:36:51 +03:00
|
|
|
func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermissions {
|
2016-06-07 21:45:29 +03:00
|
|
|
user := getUser(tx, userName)
|
|
|
|
if user == nil {
|
|
|
|
plog.Errorf("invalid user name %s", userName)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
var readPerms, writePerms []*rangePerm
|
|
|
|
|
|
|
|
for _, roleName := range user.Roles {
|
2016-06-10 20:59:34 +03:00
|
|
|
role := getRole(tx, roleName)
|
|
|
|
if role == nil {
|
|
|
|
continue
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, perm := range role.KeyPermission {
|
2016-06-14 00:17:10 +03:00
|
|
|
rp := &rangePerm{begin: perm.Key, end: perm.RangeEnd}
|
2016-06-07 21:45:29 +03:00
|
|
|
|
2016-06-11 02:36:51 +03:00
|
|
|
switch perm.PermType {
|
|
|
|
case authpb.READWRITE:
|
|
|
|
readPerms = append(readPerms, rp)
|
|
|
|
writePerms = append(writePerms, rp)
|
|
|
|
|
|
|
|
case authpb.READ:
|
|
|
|
readPerms = append(readPerms, rp)
|
2016-06-07 21:45:29 +03:00
|
|
|
|
2016-06-11 02:36:51 +03:00
|
|
|
case authpb.WRITE:
|
|
|
|
writePerms = append(writePerms, rp)
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-06-11 02:36:51 +03:00
|
|
|
return &unifiedRangePermissions{
|
|
|
|
readPerms: mergeRangePerms(readPerms),
|
|
|
|
writePerms: mergeRangePerms(writePerms),
|
|
|
|
}
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-14 00:17:10 +03:00
|
|
|
func checkKeyPerm(cachedPerms *unifiedRangePermissions, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
|
2016-06-11 05:18:07 +03:00
|
|
|
var tocheck []*rangePerm
|
|
|
|
|
|
|
|
switch permtyp {
|
|
|
|
case authpb.READ:
|
|
|
|
tocheck = cachedPerms.readPerms
|
|
|
|
case authpb.WRITE:
|
|
|
|
tocheck = cachedPerms.writePerms
|
|
|
|
default:
|
|
|
|
plog.Panicf("unknown auth type: %v", permtyp)
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-13 22:57:14 +03:00
|
|
|
requiredPerm := &rangePerm{begin: key, end: rangeEnd}
|
2016-06-11 05:18:07 +03:00
|
|
|
|
2016-06-13 22:57:14 +03:00
|
|
|
for _, perm := range tocheck {
|
|
|
|
if isSubset(requiredPerm, perm) {
|
2016-06-11 05:18:07 +03:00
|
|
|
return true
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2016-06-14 00:17:10 +03:00
|
|
|
func (as *authStore) isRangeOpPermitted(tx backend.BatchTx, userName string, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
|
2016-06-07 21:45:29 +03:00
|
|
|
// assumption: tx is Lock()ed
|
|
|
|
_, ok := as.rangePermCache[userName]
|
2016-06-11 05:18:07 +03:00
|
|
|
if !ok {
|
|
|
|
perms := getMergedPerms(tx, userName)
|
|
|
|
if perms == nil {
|
|
|
|
plog.Errorf("failed to create a unified permission of user %s", userName)
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
as.rangePermCache[userName] = perms
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
2016-06-11 05:18:07 +03:00
|
|
|
return checkKeyPerm(as.rangePermCache[userName], key, rangeEnd, permtyp)
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func (as *authStore) clearCachedPerm() {
|
|
|
|
as.rangePermCache = make(map[string]*unifiedRangePermissions)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (as *authStore) invalidateCachedPerm(userName string) {
|
|
|
|
delete(as.rangePermCache, userName)
|
|
|
|
}
|
|
|
|
|
|
|
|
type unifiedRangePermissions struct {
|
|
|
|
// readPerms[i] and readPerms[j] (i != j) don't overlap
|
|
|
|
readPerms []*rangePerm
|
|
|
|
// writePerms[i] and writePerms[j] (i != j) don't overlap, too
|
|
|
|
writePerms []*rangePerm
|
|
|
|
}
|
|
|
|
|
|
|
|
type rangePerm struct {
|
2016-06-14 00:17:10 +03:00
|
|
|
begin, end []byte
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type RangePermSliceByBegin []*rangePerm
|
|
|
|
|
|
|
|
func (slice RangePermSliceByBegin) Len() int {
|
|
|
|
return len(slice)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (slice RangePermSliceByBegin) Less(i, j int) bool {
|
2016-06-14 00:17:10 +03:00
|
|
|
switch bytes.Compare(slice[i].begin, slice[j].begin) {
|
|
|
|
case 0: // begin(i) == begin(j)
|
|
|
|
return bytes.Compare(slice[i].end, slice[j].end) == -1
|
|
|
|
|
|
|
|
case -1: // begin(i) < begin(j)
|
|
|
|
return true
|
|
|
|
|
|
|
|
default:
|
|
|
|
return false
|
2016-06-07 21:45:29 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (slice RangePermSliceByBegin) Swap(i, j int) {
|
|
|
|
slice[i], slice[j] = slice[j], slice[i]
|
|
|
|
}
|