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 }