mirror of
https://github.com/gravitational/teleport.git
synced 2026-11-01 13:39:37 +08:00
300 lines
8.5 KiB
Go
300 lines
8.5 KiB
Go
/*
|
|
Copyright 2016 Gravitational, Inc.
|
|
|
|
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 local
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gravitational/trace"
|
|
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
)
|
|
|
|
// AccessService manages roles
|
|
type AccessService struct {
|
|
backend.Backend
|
|
}
|
|
|
|
// NewAccessService returns new access service instance
|
|
func NewAccessService(backend backend.Backend) *AccessService {
|
|
return &AccessService{Backend: backend}
|
|
}
|
|
|
|
// DeleteAllRoles deletes all roles
|
|
func (s *AccessService) DeleteAllRoles() error {
|
|
return s.DeleteRange(context.TODO(), backend.Key(rolesPrefix), backend.RangeEnd(backend.Key(rolesPrefix)))
|
|
}
|
|
|
|
// GetRoles returns a list of roles registered with the local auth server
|
|
func (s *AccessService) GetRoles(ctx context.Context) ([]types.Role, error) {
|
|
result, err := s.GetRange(ctx, backend.Key(rolesPrefix), backend.RangeEnd(backend.Key(rolesPrefix)), backend.NoLimit)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
out := make([]types.Role, 0, len(result.Items))
|
|
for _, item := range result.Items {
|
|
role, err := services.UnmarshalRole(item.Value,
|
|
services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
if err != nil {
|
|
// Try to get the role name for the error, it allows admins to take action
|
|
// against the "bad" role.
|
|
h := &types.ResourceHeader{}
|
|
_ = json.Unmarshal(item.Value, h)
|
|
return nil, trace.WrapWithMessage(err, "role %q", h.GetName())
|
|
}
|
|
out = append(out, role)
|
|
}
|
|
sort.Sort(services.SortedRoles(out))
|
|
return out, nil
|
|
}
|
|
|
|
// CreateRole creates a role on the backend.
|
|
func (s *AccessService) CreateRole(ctx context.Context, role types.Role) error {
|
|
err := services.ValidateRoleName(role)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
value, err := services.MarshalRole(role)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
item := backend.Item{
|
|
Key: backend.Key(rolesPrefix, role.GetName(), paramsPrefix),
|
|
Value: value,
|
|
Expires: role.Expiry(),
|
|
}
|
|
|
|
_, err = s.Create(ctx, item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// UpsertRole updates parameters about role
|
|
func (s *AccessService) UpsertRole(ctx context.Context, role types.Role) error {
|
|
err := services.ValidateRoleName(role)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
value, err := services.MarshalRole(role)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
item := backend.Item{
|
|
Key: backend.Key(rolesPrefix, role.GetName(), paramsPrefix),
|
|
Value: value,
|
|
Expires: role.Expiry(),
|
|
ID: role.GetResourceID(),
|
|
}
|
|
|
|
_, err = s.Put(ctx, item)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// GetRole returns a role by name
|
|
func (s *AccessService) GetRole(ctx context.Context, name string) (types.Role, error) {
|
|
if name == "" {
|
|
return nil, trace.BadParameter("missing role name")
|
|
}
|
|
item, err := s.Get(ctx, backend.Key(rolesPrefix, name, paramsPrefix))
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return nil, trace.NotFound("role %v is not found", name)
|
|
}
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return services.UnmarshalRole(item.Value,
|
|
services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
}
|
|
|
|
// DeleteRole deletes a role from the backend
|
|
func (s *AccessService) DeleteRole(ctx context.Context, name string) error {
|
|
if name == "" {
|
|
return trace.BadParameter("missing role name")
|
|
}
|
|
err := s.Delete(ctx, backend.Key(rolesPrefix, name, paramsPrefix))
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return trace.NotFound("role %q is not found", name)
|
|
}
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// GetLock gets a lock by name.
|
|
func (s *AccessService) GetLock(ctx context.Context, name string) (types.Lock, error) {
|
|
if name == "" {
|
|
return nil, trace.BadParameter("missing lock name")
|
|
}
|
|
item, err := s.Get(ctx, backend.Key(locksPrefix, name))
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return nil, trace.NotFound("lock %q is not found", name)
|
|
}
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
return services.UnmarshalLock(item.Value, services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
}
|
|
|
|
// GetLocks gets all/in-force locks that match at least one of the targets when specified.
|
|
func (s *AccessService) GetLocks(ctx context.Context, inForceOnly bool, targets ...types.LockTarget) ([]types.Lock, error) {
|
|
startKey := backend.Key(locksPrefix)
|
|
result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
|
|
out := []types.Lock{}
|
|
for _, item := range result.Items {
|
|
lock, err := services.UnmarshalLock(item.Value, services.WithResourceID(item.ID), services.WithExpires(item.Expires))
|
|
if err != nil {
|
|
return nil, trace.Wrap(err)
|
|
}
|
|
if inForceOnly && !lock.IsInForce(s.Clock().Now()) {
|
|
continue
|
|
}
|
|
// If no targets specified, return all of the found/in-force locks.
|
|
if len(targets) == 0 {
|
|
out = append(out, lock)
|
|
continue
|
|
}
|
|
// Otherwise, use the targets as filters.
|
|
for _, target := range targets {
|
|
if target.Match(lock) {
|
|
out = append(out, lock)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// UpsertLock upserts a lock.
|
|
func (s *AccessService) UpsertLock(ctx context.Context, lock types.Lock) error {
|
|
value, err := services.MarshalLock(lock)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(locksPrefix, lock.GetName()),
|
|
Value: value,
|
|
Expires: lock.Expiry(),
|
|
ID: lock.GetResourceID(),
|
|
}
|
|
|
|
if _, err = s.Put(ctx, item); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// DeleteLock deletes a lock.
|
|
func (s *AccessService) DeleteLock(ctx context.Context, name string) error {
|
|
if name == "" {
|
|
return trace.BadParameter("missing lock name")
|
|
}
|
|
err := s.Delete(ctx, backend.Key(locksPrefix, name))
|
|
if err != nil {
|
|
if trace.IsNotFound(err) {
|
|
return trace.NotFound("lock %q is not found", name)
|
|
}
|
|
}
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
// DeleteLock deletes all/in-force locks.
|
|
func (s *AccessService) DeleteAllLocks(ctx context.Context) error {
|
|
startKey := backend.Key(locksPrefix)
|
|
return s.DeleteRange(ctx, startKey, backend.RangeEnd(startKey))
|
|
}
|
|
|
|
// ReplaceRemoteLocks replaces the set of locks associated with a remote cluster.
|
|
func (s *AccessService) ReplaceRemoteLocks(ctx context.Context, clusterName string, newRemoteLocks []types.Lock) error {
|
|
return backend.RunWhileLocked(ctx, s.Backend, "ReplaceRemoteLocks/"+clusterName, time.Minute, func(ctx context.Context) error {
|
|
remoteLocksKey := backend.Key(locksPrefix, clusterName)
|
|
origRemoteLocks, err := s.GetRange(ctx, remoteLocksKey, backend.RangeEnd(remoteLocksKey), backend.NoLimit)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
|
|
newRemoteLocksToStore := make(map[string]backend.Item, len(newRemoteLocks))
|
|
for _, lock := range newRemoteLocks {
|
|
if !strings.HasPrefix(lock.GetName(), clusterName) {
|
|
lock.SetName(clusterName + "/" + lock.GetName())
|
|
}
|
|
value, err := services.MarshalLock(lock)
|
|
if err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
item := backend.Item{
|
|
Key: backend.Key(locksPrefix, lock.GetName()),
|
|
Value: value,
|
|
Expires: lock.Expiry(),
|
|
ID: lock.GetResourceID(),
|
|
}
|
|
newRemoteLocksToStore[string(item.Key)] = item
|
|
}
|
|
|
|
for _, origLockItem := range origRemoteLocks.Items {
|
|
// If one of the new remote locks to store is already known,
|
|
// perform a CompareAndSwap.
|
|
key := string(origLockItem.Key)
|
|
if newLockItem, ok := newRemoteLocksToStore[key]; ok {
|
|
if _, err := s.CompareAndSwap(ctx, origLockItem, newLockItem); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
delete(newRemoteLocksToStore, key)
|
|
continue
|
|
}
|
|
|
|
// If an originally stored lock is not among the new locks,
|
|
// delete it from the backend.
|
|
if err := s.Delete(ctx, origLockItem.Key); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
|
|
// Store the remaining new locks.
|
|
for _, newLockItem := range newRemoteLocksToStore {
|
|
if _, err := s.Put(ctx, newLockItem); err != nil {
|
|
return trace.Wrap(err)
|
|
}
|
|
}
|
|
return nil
|
|
})
|
|
}
|
|
|
|
const (
|
|
rolesPrefix = "roles"
|
|
paramsPrefix = "params"
|
|
locksPrefix = "locks"
|
|
)
|