Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

server/auth: implement role & role manager #3256

Merged
merged 10 commits into from
Dec 9, 2020
30 changes: 30 additions & 0 deletions errors.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,36 @@ error = '''
redirect failed
'''

["PD:auth:ErrInvalidPermissionAction"]
error = '''
invalid permission action: %s
'''

["PD:auth:ErrInvalidRoleName"]
error = '''
role name may only contain alphanumeric and underscores, and may only start with an alphabetic character.
'''

["PD:auth:ErrRoleExists"]
error = '''
role already exists: %s
'''

["PD:auth:ErrRoleHasPermission"]
error = '''
role %s already has permission: %s
'''

["PD:auth:ErrRoleMissingPermission"]
error = '''
role %s doesn't have permission: %s
'''

["PD:auth:ErrRoleNotFound"]
error = '''
role not found: %s
'''

["PD:autoscaling:ErrEmptyMetricsResponse"]
error = '''
metrics response from Prometheus is empty
Expand Down
10 changes: 10 additions & 0 deletions pkg/errs/errno.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,3 +287,13 @@ var (
ErrEncryptionSaveDataKeys = errors.Normalize("failed to save data keys", errors.RFCCodeText("PD:encryption:ErrEncryptionSaveDataKeys"))
ErrEncryptionKMS = errors.Normalize("KMS error", errors.RFCCodeText("PD:ErrEncryptionKMS"))
)

// auth
var (
ErrInvalidPermissionAction = errors.Normalize("invalid permission action: %s", errors.RFCCodeText("PD:auth:ErrInvalidPermissionAction"))
ErrRoleNotFound = errors.Normalize("role not found: %s", errors.RFCCodeText("PD:auth:ErrRoleNotFound"))
ErrRoleExists = errors.Normalize("role already exists: %s", errors.RFCCodeText("PD:auth:ErrRoleExists"))
ErrInvalidRoleName = errors.Normalize("role name may only contain alphanumeric and underscores, and may only start with an alphabetic character.", errors.RFCCodeText("PD:auth:ErrInvalidRoleName"))
ErrRoleHasPermission = errors.Normalize("role %s already has permission: %s", errors.RFCCodeText("PD:auth:ErrRoleHasPermission"))
ErrRoleMissingPermission = errors.Normalize("role %s doesn't have permission: %s", errors.RFCCodeText("PD:auth:ErrRoleMissingPermission"))
)
247 changes: 247 additions & 0 deletions server/auth/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
// Copyright 2020 TiKV Project 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,
// See the License for the specific language governing permissions and
// limitations under the License.

package auth

import (
"encoding/json"
"path"
"strings"
"sync"

"github.com/tikv/pd/pkg/errs"
"github.com/tikv/pd/server/kv"
"go.etcd.io/etcd/clientv3"
)

// RBACManager is used for the rbac storage, cache, management and enforcing logic.
type RBACManager struct {
roleManager
}

type roleManager struct {
kv kv.Base
mu sync.RWMutex
roles map[string]*Role
}

// newRoleManager creates a new roleManager.
func newRoleManager(kv kv.Base) *roleManager {
return &roleManager{kv: kv, roles: make(map[string]*Role)}
}

// GetRole returns a role.
func (m *roleManager) GetRole(name string) (*Role, error) {
m.mu.RLock()
defer m.mu.RUnlock()

role, ok := m.roles[name]
if !ok {
return nil, errs.ErrRoleNotFound.FastGenByArgs(name)
}

return role, nil
}

// GetRoles returns all available roles.
func (m *roleManager) GetRoles() map[string]*Role {
m.mu.RLock()
defer m.mu.RUnlock()

return m.roles
}

// CreateRole creates a new role.
func (m *roleManager) CreateRole(name string) error {
_, err := m.GetRole(name)
if err == nil {
return errs.ErrRoleExists.GenWithStackByArgs(name)
}

role, err := NewRole(name)
if err != nil {
return err
}

roleJSON, err := json.Marshal(role)
if err != nil {
return errs.ErrJSONMarshal.Wrap(err).GenWithStackByCause()
}
rolePath := path.Join(rolePrefix, name)

m.mu.Lock()
defer m.mu.Unlock()

// Add role to kv.
err = m.kv.Save(rolePath, string(roleJSON))
if err != nil {
return err
}

// Add role to memory cache.
m.roles[name] = role

return nil
}

// DeleteRole deletes a role.
func (m *roleManager) DeleteRole(name string) error {
_, err := m.GetRole(name)
if err != nil {
return err
}

rolePath := path.Join(rolePrefix, name)

m.mu.Lock()
defer m.mu.Unlock()

// Delete role from kv.
err = m.kv.Remove(rolePath)
if err != nil {
return err
}

// Delete role from memory cache.
delete(m.roles, name)

return nil
}

// RoleHasPermission checks whether a role has a specific permission.
func (m *roleManager) RoleHasPermission(name string, permission Permission) (bool, error) {
role, err := m.GetRole(name)
if err != nil {
return false, err
}

m.mu.RLock()
defer m.mu.RUnlock()

return role.hasPermission(permission), nil
}

// SetPermissions sets permissions of a role.
func (m *roleManager) SetPermissions(name string, permissions []Permission) error {
role, err := m.GetRole(name)
if err != nil {
return err
}

m.mu.Lock()
defer m.mu.Unlock()

role.Permissions = permissions

// Update role in kv
roleJSON, err := json.Marshal(role)
if err != nil {
return errs.ErrJSONMarshal.Wrap(err).GenWithStackByCause()
}
rolePath := path.Join(rolePrefix, name)

err = m.kv.Save(rolePath, string(roleJSON))
if err != nil {
return err
}

// No need to update role in memory cache again.
return nil
}

// AddPermission adds a permission to a role.
func (m *roleManager) AddPermission(name string, permission Permission) error {
role, err := m.GetRole(name)
if err != nil {
return err
}

m.mu.Lock()
defer m.mu.Unlock()

if ok := role.appendPermission(permission); !ok {
return errs.ErrRoleHasPermission.FastGenByArgs(name, permission)
}

roleJSON, err := json.Marshal(role)
if err != nil {
return errs.ErrJSONMarshal.Wrap(err).GenWithStackByCause()
}
rolePath := path.Join(rolePrefix, name)

// Update user in kv.
err = m.kv.Save(rolePath, string(roleJSON))
if err != nil {
return err
}

// Update user in memory cache.
m.roles[name] = role

return nil
}

// RemovePermission removes a permission from a role.
func (m *roleManager) RemovePermission(name string, permission Permission) error {
role, err := m.GetRole(name)
if err != nil {
return err
}

m.mu.Lock()
defer m.mu.Unlock()

if ok := role.removePermission(permission); !ok {
return errs.ErrRoleMissingPermission.FastGenByArgs(name, permission)
}

roleJSON, err := json.Marshal(role)
if err != nil {
return errs.ErrJSONMarshal.Wrap(err).GenWithStackByCause()
}
rolePath := path.Join(rolePrefix, name)

// Update user in kv.
err = m.kv.Save(rolePath, string(roleJSON))
if err != nil {
return err
}

// Update user in memory cache.
m.roles[name] = role

return nil
}

func (m *roleManager) UpdateCache() error {
m.mu.Lock()
defer m.mu.Unlock()

rolePath := strings.Join([]string{rolePrefix, ""}, "/")

keys, values, err := m.kv.LoadRange(rolePath, clientv3.GetPrefixRangeEnd(rolePath), 0)
if err != nil {
return err
}

m.roles = make(map[string]*Role)
for i := range keys {
value := values[i]
role, err := NewRoleFromJSON(value)
if err != nil {
return err
}
m.roles[role.Name] = role
}
return nil
}