github.com/gravitational/teleport/api@v0.0.0-20240507183017-3110591cbafc/types/semaphore.go (about)

     1  /*
     2  Copyright 2020 Gravitational, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package types
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"time"
    23  
    24  	"github.com/gravitational/trace"
    25  
    26  	"github.com/gravitational/teleport/api/constants"
    27  )
    28  
    29  // SemaphoreKindConnection is the semaphore kind used by
    30  // the Concurrent Session Control feature to limit concurrent
    31  // connections (corresponds to the `max_connections`
    32  // role option).
    33  const SemaphoreKindConnection = "connection"
    34  
    35  // SemaphoreKindKubernetesConnection is the semaphore kind used by
    36  // the Concurrent Session Control feature to limit concurrent
    37  // connections for Kubernetes (corresponds to the `max_kubernetes_connections`
    38  // role option).
    39  const SemaphoreKindKubernetesConnection = "kubernetes_connection"
    40  
    41  // SemaphoreKindHostUserModification is the semaphore kind used to limit
    42  // the number of operations that can occur on a unix user to one at a time
    43  const SemaphoreKindHostUserModification = "host_user_modification"
    44  
    45  // SemaphoreKindAccessMonitoringLimiter is the semaphore kind used by
    46  // the Access Monitoring feature during handling user queries.
    47  const SemaphoreKindAccessMonitoringLimiter = "access_monitoring_limiter"
    48  
    49  // SemaphoreKindUploadCompleter is the semaphore kind used by the
    50  // auth server's upload completer to protect access to the shared
    51  // session recordings backend.
    52  const SemaphoreKindUploadCompleter = "upload_completer"
    53  
    54  // Semaphore represents distributed semaphore concept
    55  type Semaphore interface {
    56  	// Resource contains common resource values
    57  	Resource
    58  	// Contains checks if lease is member of this semaphore.
    59  	Contains(lease SemaphoreLease) bool
    60  	// Acquire attempts to acquire a lease with this semaphore.
    61  	Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
    62  	// KeepAlive attempts to update the expiry of an existent lease.
    63  	KeepAlive(lease SemaphoreLease) error
    64  	// Cancel attempts to cancel an existent lease.
    65  	Cancel(lease SemaphoreLease) error
    66  	// LeaseRefs grants access to the underlying list
    67  	// of lease references.
    68  	LeaseRefs() []SemaphoreLeaseRef
    69  	// RemoveExpiredLeases removes expired leases
    70  	RemoveExpiredLeases(now time.Time)
    71  }
    72  
    73  // ConfigureSemaphore configures an empty semaphore resource matching
    74  // these acquire parameters.
    75  func (s *AcquireSemaphoreRequest) ConfigureSemaphore() (Semaphore, error) {
    76  	sem := SemaphoreV3{
    77  		SubKind: s.SemaphoreKind,
    78  		Metadata: Metadata{
    79  			Name: s.SemaphoreName,
    80  		},
    81  	}
    82  	sem.SetExpiry(s.Expires)
    83  	if err := sem.CheckAndSetDefaults(); err != nil {
    84  		return nil, trace.Wrap(err)
    85  	}
    86  	return &sem, nil
    87  }
    88  
    89  // Check verifies that all required parameters have been supplied.
    90  func (s *AcquireSemaphoreRequest) Check() error {
    91  	if s.SemaphoreKind == "" {
    92  		return trace.BadParameter("missing parameter SemaphoreKind")
    93  	}
    94  	if s.SemaphoreName == "" {
    95  		return trace.BadParameter("missing parameter SemaphoreName")
    96  	}
    97  	if s.MaxLeases == 0 {
    98  		return trace.BadParameter("missing parameter MaxLeases")
    99  	}
   100  	if s.Expires.IsZero() {
   101  		return trace.BadParameter("missing parameter Expires")
   102  	}
   103  	return nil
   104  }
   105  
   106  // CheckAndSetDefaults checks and sets default values
   107  func (l *SemaphoreLease) CheckAndSetDefaults() error {
   108  	if l.SemaphoreKind == "" {
   109  		return trace.BadParameter("missing parameter SemaphoreKind")
   110  	}
   111  	if l.SemaphoreName == "" {
   112  		return trace.BadParameter("missing parameter SemaphoreName")
   113  	}
   114  	if l.LeaseID == "" {
   115  		return trace.BadParameter("missing parameter LeaseID")
   116  	}
   117  	if l.Expires.IsZero() {
   118  		return trace.BadParameter("missing lease expiry time")
   119  	}
   120  	return nil
   121  }
   122  
   123  // Contains checks if lease is member of this semaphore.
   124  func (c *SemaphoreV3) Contains(lease SemaphoreLease) bool {
   125  	if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
   126  		return false
   127  	}
   128  	for _, ref := range c.Spec.Leases {
   129  		if ref.LeaseID == lease.LeaseID {
   130  			return true
   131  		}
   132  	}
   133  	return false
   134  }
   135  
   136  // Acquire attempts to acquire a lease with this semaphore.
   137  func (c *SemaphoreV3) Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error) {
   138  	if params.SemaphoreKind != c.GetSubKind() || params.SemaphoreName != c.GetName() {
   139  		return nil, trace.BadParameter("cannot acquire, params do not match")
   140  	}
   141  
   142  	if c.leaseCount() >= params.MaxLeases {
   143  		return nil, trace.LimitExceeded("cannot acquire semaphore %s/%s (%s)",
   144  			c.GetSubKind(),
   145  			c.GetName(),
   146  			constants.MaxLeases,
   147  		)
   148  	}
   149  
   150  	for _, ref := range c.Spec.Leases {
   151  		if ref.LeaseID == leaseID {
   152  			return nil, trace.AlreadyExists("semaphore lease already exists: %q", leaseID)
   153  		}
   154  	}
   155  
   156  	if params.Expires.After(c.Expiry()) {
   157  		c.SetExpiry(params.Expires)
   158  	}
   159  
   160  	c.Spec.Leases = append(c.Spec.Leases, SemaphoreLeaseRef{
   161  		LeaseID: leaseID,
   162  		Expires: params.Expires,
   163  		Holder:  params.Holder,
   164  	})
   165  
   166  	return &SemaphoreLease{
   167  		SemaphoreKind: params.SemaphoreKind,
   168  		SemaphoreName: params.SemaphoreName,
   169  		LeaseID:       leaseID,
   170  		Expires:       params.Expires,
   171  	}, nil
   172  }
   173  
   174  // KeepAlive attempts to update the expiry of an existent lease.
   175  func (c *SemaphoreV3) KeepAlive(lease SemaphoreLease) error {
   176  	if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
   177  		return trace.BadParameter("cannot keepalive, lease does not match")
   178  	}
   179  	for i := range c.Spec.Leases {
   180  		if c.Spec.Leases[i].LeaseID == lease.LeaseID {
   181  			c.Spec.Leases[i].Expires = lease.Expires
   182  			if lease.Expires.After(c.Expiry()) {
   183  				c.SetExpiry(lease.Expires)
   184  			}
   185  			return nil
   186  		}
   187  	}
   188  	return trace.NotFound("cannot keepalive, lease not found: %q", lease.LeaseID)
   189  }
   190  
   191  // Cancel attempts to cancel an existent lease.
   192  func (c *SemaphoreV3) Cancel(lease SemaphoreLease) error {
   193  	if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
   194  		return trace.BadParameter("cannot cancel, lease does not match")
   195  	}
   196  	for i, ref := range c.Spec.Leases {
   197  		if ref.LeaseID == lease.LeaseID {
   198  			c.Spec.Leases = append(c.Spec.Leases[:i], c.Spec.Leases[i+1:]...)
   199  			return nil
   200  		}
   201  	}
   202  	return trace.NotFound("cannot cancel, lease not found: %q", lease.LeaseID)
   203  }
   204  
   205  // RemoveExpiredLeases removes expired leases
   206  func (c *SemaphoreV3) RemoveExpiredLeases(now time.Time) {
   207  	// See https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
   208  	filtered := c.Spec.Leases[:0]
   209  	for _, lease := range c.Spec.Leases {
   210  		if lease.Expires.After(now) {
   211  			filtered = append(filtered, lease)
   212  		}
   213  	}
   214  	c.Spec.Leases = filtered
   215  }
   216  
   217  // leaseCount returns the number of active leases
   218  func (c *SemaphoreV3) leaseCount() int64 {
   219  	return int64(len(c.Spec.Leases))
   220  }
   221  
   222  // LeaseRefs grants access to the underlying list
   223  // of lease references
   224  func (c *SemaphoreV3) LeaseRefs() []SemaphoreLeaseRef {
   225  	return c.Spec.Leases
   226  }
   227  
   228  // GetVersion returns resource version
   229  func (c *SemaphoreV3) GetVersion() string {
   230  	return c.Version
   231  }
   232  
   233  // GetSubKind returns resource subkind
   234  func (c *SemaphoreV3) GetSubKind() string {
   235  	return c.SubKind
   236  }
   237  
   238  // SetSubKind sets resource subkind
   239  func (c *SemaphoreV3) SetSubKind(sk string) {
   240  	c.SubKind = sk
   241  }
   242  
   243  // GetKind returns resource kind
   244  func (c *SemaphoreV3) GetKind() string {
   245  	return c.Kind
   246  }
   247  
   248  // GetResourceID returns resource ID
   249  func (c *SemaphoreV3) GetResourceID() int64 {
   250  	return c.Metadata.ID
   251  }
   252  
   253  // SetResourceID sets resource ID
   254  func (c *SemaphoreV3) SetResourceID(id int64) {
   255  	c.Metadata.ID = id
   256  }
   257  
   258  // GetRevision returns the revision
   259  func (c *SemaphoreV3) GetRevision() string {
   260  	return c.Metadata.GetRevision()
   261  }
   262  
   263  // SetRevision sets the revision
   264  func (c *SemaphoreV3) SetRevision(rev string) {
   265  	c.Metadata.SetRevision(rev)
   266  }
   267  
   268  // GetName returns the name of the cluster.
   269  func (c *SemaphoreV3) GetName() string {
   270  	return c.Metadata.Name
   271  }
   272  
   273  // SetName sets the name of the cluster.
   274  func (c *SemaphoreV3) SetName(e string) {
   275  	c.Metadata.Name = e
   276  }
   277  
   278  // Expiry returns object expiry setting
   279  func (c *SemaphoreV3) Expiry() time.Time {
   280  	return c.Metadata.Expiry()
   281  }
   282  
   283  // SetExpiry sets expiry time for the object
   284  func (c *SemaphoreV3) SetExpiry(expires time.Time) {
   285  	c.Metadata.SetExpiry(expires)
   286  }
   287  
   288  // GetMetadata returns object metadata
   289  func (c *SemaphoreV3) GetMetadata() Metadata {
   290  	return c.Metadata
   291  }
   292  
   293  // String represents a human readable version of the semaphore.
   294  func (c *SemaphoreV3) String() string {
   295  	return fmt.Sprintf("Semaphore(kind=%v, name=%v, leases=%v)",
   296  		c.SubKind, c.Metadata.Name, c.leaseCount())
   297  }
   298  
   299  // setStaticFields sets static resource header and metadata fields.
   300  func (c *SemaphoreV3) setStaticFields() {
   301  	c.Kind = KindSemaphore
   302  	c.Version = V3
   303  }
   304  
   305  // CheckAndSetDefaults checks validity of all parameters and sets defaults.
   306  func (c *SemaphoreV3) CheckAndSetDefaults() error {
   307  	c.setStaticFields()
   308  	if err := c.Metadata.CheckAndSetDefaults(); err != nil {
   309  		return trace.Wrap(err)
   310  	}
   311  
   312  	// While theoretically there are scenarios with non-expiring semaphores
   313  	// however the flow don't need them right now, and they add a lot of edge
   314  	// cases, so the code does not support them.
   315  	if c.Expiry().IsZero() {
   316  		return trace.BadParameter("set semaphore expiry time")
   317  	}
   318  	if c.SubKind == "" {
   319  		return trace.BadParameter("supply semaphore SubKind parameter")
   320  	}
   321  	return nil
   322  }
   323  
   324  // Semaphores provides ability to control
   325  // how many shared resources of some kind are acquired at the same time,
   326  // used to implement concurrent sessions control in a distributed environment
   327  type Semaphores interface {
   328  	// AcquireSemaphore acquires lease with requested resources from semaphore
   329  	AcquireSemaphore(ctx context.Context, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
   330  	// KeepAliveSemaphoreLease updates semaphore lease
   331  	KeepAliveSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
   332  	// CancelSemaphoreLease cancels semaphore lease early
   333  	CancelSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
   334  	// GetSemaphores returns a list of semaphores matching supplied filter.
   335  	GetSemaphores(ctx context.Context, filter SemaphoreFilter) ([]Semaphore, error)
   336  	// DeleteSemaphore deletes a semaphore matching supplied filter.
   337  	DeleteSemaphore(ctx context.Context, filter SemaphoreFilter) error
   338  }
   339  
   340  // Match checks if the supplied semaphore matches this filter.
   341  func (f *SemaphoreFilter) Match(sem Semaphore) bool {
   342  	if f.SemaphoreKind != "" && f.SemaphoreKind != sem.GetSubKind() {
   343  		return false
   344  	}
   345  	if f.SemaphoreName != "" && f.SemaphoreName != sem.GetName() {
   346  		return false
   347  	}
   348  	return true
   349  }