mirror of
https://github.com/gravitational/teleport
synced 2024-10-20 17:23:22 +00:00
concurrent session control
Adds support for Concurrent Session Control and a new semaphore API. Roles now support two new configuration options, `max_ssh_connections` and `max_ssh_sessions` which correspond to the total number of authenticated ssh connections per cluster, and the number of ssh sessions within a connection respectively. Attempting to exceed these limits generate variants of the `session.rejected` audit event and cause the connection/session to be rejected.
This commit is contained in:
parent
0f4e82548f
commit
ae2336dfd0
|
@ -589,6 +589,11 @@ const (
|
|||
// NodeIsAmbiguous serves as an identifying error string indicating that
|
||||
// the proxy subsystem found multiple nodes matching the specified hostname.
|
||||
NodeIsAmbiguous = "err-node-is-ambiguous"
|
||||
|
||||
// MaxLeases serves as an identifying error string indicating that the
|
||||
// semaphore system is rejecting an acquisition attempt due to max
|
||||
// leases having already been reached.
|
||||
MaxLeases = "err-max-leases"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -895,6 +895,10 @@ type disconnectTestCase struct {
|
|||
recordingMode string
|
||||
options services.RoleOptions
|
||||
disconnectTimeout time.Duration
|
||||
concurrentConns int
|
||||
sessCtlTimeout time.Duration
|
||||
assertExpected func(*check.C, error)
|
||||
postFunc func(context.Context, *check.C, *TeleInstance)
|
||||
}
|
||||
|
||||
// TestDisconnectScenarios tests multiple scenarios with client disconnects
|
||||
|
@ -935,6 +939,78 @@ func (s *IntSuite) TestDisconnectScenarios(c *check.C) {
|
|||
},
|
||||
disconnectTimeout: 4 * time.Second,
|
||||
},
|
||||
{ // verify that concurrent connection limits are applied when recording at node
|
||||
recordingMode: services.RecordAtNode,
|
||||
options: services.RoleOptions{
|
||||
MaxConnections: 1,
|
||||
},
|
||||
disconnectTimeout: 1 * time.Second,
|
||||
concurrentConns: 2,
|
||||
assertExpected: func(c *check.C, err error) {
|
||||
if err == nil || !strings.Contains(err.Error(), "administratively prohibited") {
|
||||
c.Fatalf("Expected 'administratively prohibited', got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ // verify that concurrent connection limits are applied when recording at proxy
|
||||
recordingMode: services.RecordAtProxy,
|
||||
options: services.RoleOptions{
|
||||
ForwardAgent: services.NewBool(true),
|
||||
MaxConnections: 1,
|
||||
},
|
||||
disconnectTimeout: 1 * time.Second,
|
||||
concurrentConns: 2,
|
||||
assertExpected: func(c *check.C, err error) {
|
||||
if err == nil || !strings.Contains(err.Error(), "administratively prohibited") {
|
||||
c.Fatalf("Expected 'administratively prohibited', got: %v", err)
|
||||
}
|
||||
},
|
||||
},
|
||||
{ // verify that lost connections to auth server terminate controlled conns
|
||||
recordingMode: services.RecordAtNode,
|
||||
options: services.RoleOptions{
|
||||
MaxConnections: 1,
|
||||
},
|
||||
disconnectTimeout: time.Second,
|
||||
sessCtlTimeout: 500 * time.Millisecond,
|
||||
// use postFunc to wait for the semaphore to be acquired and a session
|
||||
// to be started, then shut down the auth server.
|
||||
postFunc: func(ctx context.Context, c *check.C, t *TeleInstance) {
|
||||
site := t.GetSiteAPI(Site)
|
||||
var sems []services.Semaphore
|
||||
var err error
|
||||
for i := 0; i < 6; i++ {
|
||||
sems, err = site.GetSemaphores(ctx, services.SemaphoreFilter{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
})
|
||||
if err == nil && len(sems) > 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(sems), check.Equals, 1)
|
||||
var ss []session.Session
|
||||
for i := 0; i < 6; i++ {
|
||||
ss, err = site.GetSessions(defaults.Namespace)
|
||||
if err == nil && len(ss) > 0 {
|
||||
break
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 100):
|
||||
case <-ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(len(ss), check.Equals, 1)
|
||||
c.Assert(t.StopAuth(false), check.IsNil)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tc := range testCases {
|
||||
s.runDisconnectTest(c, tc)
|
||||
|
@ -962,8 +1038,9 @@ func (s *IntSuite) runDisconnectTest(c *check.C, tc disconnectTestCase) {
|
|||
t.AddUserWithRole(username, role)
|
||||
|
||||
clusterConfig, err := services.NewClusterConfig(services.ClusterConfigSpecV3{
|
||||
SessionRecording: tc.recordingMode,
|
||||
LocalAuth: services.NewBool(true),
|
||||
SessionRecording: tc.recordingMode,
|
||||
LocalAuth: services.NewBool(true),
|
||||
SessionControlTimeout: services.Duration(tc.sessCtlTimeout),
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
|
@ -983,35 +1060,59 @@ func (s *IntSuite) runDisconnectTest(c *check.C, tc disconnectTestCase) {
|
|||
site := t.GetSiteAPI(Site)
|
||||
c.Assert(site, check.NotNil)
|
||||
|
||||
person := NewTerminal(250)
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
|
||||
// PersonA: SSH into the server, wait one second, then type some commands on stdin:
|
||||
sessionCtx, sessionCancel := context.WithCancel(context.TODO())
|
||||
openSession := func() {
|
||||
defer sessionCancel()
|
||||
cl, err := t.NewClient(ClientConfig{Login: username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
||||
c.Assert(err, check.IsNil)
|
||||
cl.Stdout = person
|
||||
cl.Stdin = person
|
||||
|
||||
err = cl.SSH(context.TODO(), []string{}, false)
|
||||
if err != nil && err != io.EOF {
|
||||
c.Fatalf("expected EOF or nil, got %v instead", err)
|
||||
}
|
||||
if tc.concurrentConns < 1 {
|
||||
// test cases that don't specify concurrentConns are single-connection tests.
|
||||
tc.concurrentConns = 1
|
||||
}
|
||||
|
||||
go openSession()
|
||||
for i := 0; i < tc.concurrentConns; i++ {
|
||||
person := NewTerminal(250)
|
||||
|
||||
openSession := func() {
|
||||
defer cancel()
|
||||
cl, err := t.NewClient(ClientConfig{Login: username, Cluster: Site, Host: Host, Port: t.GetPortSSHInt()})
|
||||
c.Assert(err, check.IsNil)
|
||||
cl.Stdout = person
|
||||
cl.Stdin = person
|
||||
|
||||
err = cl.SSH(ctx, []string{}, false)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
// either we timed out, or a different session
|
||||
// triggered closure.
|
||||
return
|
||||
default:
|
||||
}
|
||||
if tc.assertExpected != nil {
|
||||
tc.assertExpected(c, err)
|
||||
} else if err != nil && !trace.IsEOF(err) {
|
||||
c.Fatalf("expected EOF or nil, got %v instead", err)
|
||||
}
|
||||
}
|
||||
|
||||
go openSession()
|
||||
|
||||
go enterInput(ctx, c, person, "echo start \r\n", ".*start.*")
|
||||
}
|
||||
|
||||
if tc.postFunc != nil {
|
||||
// test case modifies the teleport instance after session start
|
||||
tc.postFunc(ctx, c, t)
|
||||
}
|
||||
|
||||
enterInput(c, person, "echo start \r\n", ".*start.*")
|
||||
select {
|
||||
case <-time.After(tc.disconnectTimeout):
|
||||
c.Fatalf("timeout waiting for session to exit")
|
||||
case <-sessionCtx.Done():
|
||||
// session closed
|
||||
case <-time.After(tc.disconnectTimeout + time.Second):
|
||||
c.Fatalf("timeout waiting for session to exit: %+v", tc)
|
||||
case <-ctx.Done():
|
||||
// session closed. a test case is successful if the first
|
||||
// session to close encountered the expected error variant.
|
||||
}
|
||||
}
|
||||
|
||||
func enterInput(c *check.C, person *Terminal, command, pattern string) {
|
||||
func enterInput(ctx context.Context, c *check.C, person *Terminal, command, pattern string) {
|
||||
person.Type(command)
|
||||
abortTime := time.Now().Add(10 * time.Second)
|
||||
var matched bool
|
||||
|
@ -1020,16 +1121,19 @@ func enterInput(c *check.C, person *Terminal, command, pattern string) {
|
|||
output = replaceNewlines(person.Output(1000))
|
||||
matched, _ = regexp.MatchString(pattern, output)
|
||||
if matched {
|
||||
break
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-time.After(time.Millisecond * 50):
|
||||
case <-ctx.Done():
|
||||
// cancellation means that we don't care about the input being
|
||||
// confirmed anymore; not equivalent to a timeout.
|
||||
return
|
||||
}
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
if time.Now().After(abortTime) {
|
||||
c.Fatalf("failed to capture pattern %q in %q", pattern, output)
|
||||
}
|
||||
}
|
||||
if !matched {
|
||||
c.Fatalf("output %q does not match pattern %q", output, pattern)
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvalidLogins validates that you can't login with invalid login or
|
||||
|
|
|
@ -1117,7 +1117,7 @@ func (s *KubeSuite) runKubeDisconnectTest(c *check.C, tc disconnectTestCase) {
|
|||
}()
|
||||
|
||||
// lets type something followed by "enter" and then hang the session
|
||||
enterInput(c, term, "echo boring platapus\r\n", ".*boring platapus.*")
|
||||
enterInput(sessionCtx, c, term, "echo boring platapus\r\n", ".*boring platapus.*")
|
||||
time.Sleep(tc.disconnectTimeout)
|
||||
select {
|
||||
case <-time.After(tc.disconnectTimeout):
|
||||
|
|
|
@ -103,6 +103,9 @@ type AccessPoint interface {
|
|||
// Announcer adds methods used to announce presence
|
||||
Announcer
|
||||
|
||||
// Semaphores provides semaphore operations
|
||||
services.Semaphores
|
||||
|
||||
// UpsertTunnelConnection upserts tunnel connection
|
||||
UpsertTunnelConnection(conn services.TunnelConnection) error
|
||||
|
||||
|
@ -144,56 +147,80 @@ type AuthCache interface {
|
|||
}
|
||||
|
||||
// NewWrapper returns new access point wrapper
|
||||
func NewWrapper(writer AccessPoint, cache ReadAccessPoint) AccessPoint {
|
||||
func NewWrapper(base AccessPoint, cache ReadAccessPoint) AccessPoint {
|
||||
return &Wrapper{
|
||||
Write: writer,
|
||||
NoCache: base,
|
||||
ReadAccessPoint: cache,
|
||||
}
|
||||
}
|
||||
|
||||
// Wrapper wraps access point and auth cache in one client
|
||||
// so that update operations are going through access point
|
||||
// and read operations are going though cache
|
||||
// so that reads of cached values can be intercepted.
|
||||
type Wrapper struct {
|
||||
ReadAccessPoint
|
||||
Write AccessPoint
|
||||
NoCache AccessPoint
|
||||
}
|
||||
|
||||
// Close closes all associated resources
|
||||
func (w *Wrapper) Close() error {
|
||||
err := w.Write.Close()
|
||||
err := w.NoCache.Close()
|
||||
err2 := w.ReadAccessPoint.Close()
|
||||
return trace.NewAggregate(err, err2)
|
||||
}
|
||||
|
||||
// UpsertNode is part of auth.AccessPoint implementation
|
||||
func (w *Wrapper) UpsertNode(s services.Server) (*services.KeepAlive, error) {
|
||||
return w.Write.UpsertNode(s)
|
||||
return w.NoCache.UpsertNode(s)
|
||||
}
|
||||
|
||||
// UpsertAuthServer is part of auth.AccessPoint implementation
|
||||
func (w *Wrapper) UpsertAuthServer(s services.Server) error {
|
||||
return w.Write.UpsertAuthServer(s)
|
||||
return w.NoCache.UpsertAuthServer(s)
|
||||
}
|
||||
|
||||
// NewKeepAliver returns a new instance of keep aliver
|
||||
func (w *Wrapper) NewKeepAliver(ctx context.Context) (services.KeepAliver, error) {
|
||||
return w.Write.NewKeepAliver(ctx)
|
||||
return w.NoCache.NewKeepAliver(ctx)
|
||||
}
|
||||
|
||||
// UpsertProxy is part of auth.AccessPoint implementation
|
||||
func (w *Wrapper) UpsertProxy(s services.Server) error {
|
||||
return w.Write.UpsertProxy(s)
|
||||
return w.NoCache.UpsertProxy(s)
|
||||
}
|
||||
|
||||
// UpsertTunnelConnection is a part of auth.AccessPoint implementation
|
||||
func (w *Wrapper) UpsertTunnelConnection(conn services.TunnelConnection) error {
|
||||
return w.Write.UpsertTunnelConnection(conn)
|
||||
return w.NoCache.UpsertTunnelConnection(conn)
|
||||
}
|
||||
|
||||
// DeleteTunnelConnection is a part of auth.AccessPoint implementation
|
||||
func (w *Wrapper) DeleteTunnelConnection(clusterName, connName string) error {
|
||||
return w.Write.DeleteTunnelConnection(clusterName, connName)
|
||||
return w.NoCache.DeleteTunnelConnection(clusterName, connName)
|
||||
}
|
||||
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore
|
||||
func (w *Wrapper) AcquireSemaphore(ctx context.Context, params services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
return w.NoCache.AcquireSemaphore(ctx, params)
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease
|
||||
func (w *Wrapper) KeepAliveSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
return w.NoCache.KeepAliveSemaphoreLease(ctx, lease)
|
||||
}
|
||||
|
||||
// CancelSemaphoreLease cancels semaphore lease early
|
||||
func (w *Wrapper) CancelSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
return w.NoCache.CancelSemaphoreLease(ctx, lease)
|
||||
}
|
||||
|
||||
// GetSemaphores returns a list of semaphores matching supplied filter.
|
||||
func (w *Wrapper) GetSemaphores(ctx context.Context, filter services.SemaphoreFilter) ([]services.Semaphore, error) {
|
||||
return w.NoCache.GetSemaphores(ctx, filter)
|
||||
}
|
||||
|
||||
// DeleteSemaphore deletes a semaphore matching supplied filter.
|
||||
func (w *Wrapper) DeleteSemaphore(ctx context.Context, filter services.SemaphoreFilter) error {
|
||||
return w.NoCache.DeleteSemaphore(ctx, filter)
|
||||
}
|
||||
|
||||
// NewCachingAcessPoint returns new caching access point using
|
||||
|
|
|
@ -1772,6 +1772,52 @@ func (a *AuthWithRoles) DeleteAllRemoteClusters() error {
|
|||
return a.authServer.DeleteAllRemoteClusters()
|
||||
}
|
||||
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
func (a *AuthWithRoles) AcquireSemaphore(ctx context.Context, params services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbCreate); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbUpdate); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.AcquireSemaphore(ctx, params)
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
func (a *AuthWithRoles) KeepAliveSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbUpdate); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.KeepAliveSemaphoreLease(ctx, lease)
|
||||
}
|
||||
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
func (a *AuthWithRoles) CancelSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbUpdate); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.CancelSemaphoreLease(ctx, lease)
|
||||
}
|
||||
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
func (a *AuthWithRoles) GetSemaphores(ctx context.Context, filter services.SemaphoreFilter) ([]services.Semaphore, error) {
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbReadNoSecrets); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbList); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.GetSemaphores(ctx, filter)
|
||||
}
|
||||
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
func (a *AuthWithRoles) DeleteSemaphore(ctx context.Context, filter services.SemaphoreFilter) error {
|
||||
if err := a.action(defaults.Namespace, services.KindSemaphore, services.VerbDelete); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return a.authServer.DeleteSemaphore(ctx, filter)
|
||||
}
|
||||
|
||||
// ProcessKubeCSR processes CSR request against Kubernetes CA, returns
|
||||
// signed certificate if successful.
|
||||
func (a *AuthWithRoles) ProcessKubeCSR(req KubeCSR) (*KubeCSRResponse, error) {
|
||||
|
|
|
@ -2695,6 +2695,72 @@ func (c *Client) Ping(ctx context.Context) (proto.PingResponse, error) {
|
|||
return *rsp, nil
|
||||
}
|
||||
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
func (c *Client) AcquireSemaphore(ctx context.Context, params services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
clt, err := c.grpc()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
lease, err := clt.AcquireSemaphore(ctx, ¶ms)
|
||||
if err != nil {
|
||||
return nil, trail.FromGRPC(err)
|
||||
}
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
func (c *Client) KeepAliveSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
clt, err := c.grpc()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if _, err := clt.KeepAliveSemaphoreLease(ctx, &lease); err != nil {
|
||||
return trail.FromGRPC(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
func (c *Client) CancelSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
clt, err := c.grpc()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if _, err := clt.CancelSemaphoreLease(ctx, &lease); err != nil {
|
||||
return trail.FromGRPC(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
func (c *Client) GetSemaphores(ctx context.Context, filter services.SemaphoreFilter) ([]services.Semaphore, error) {
|
||||
clt, err := c.grpc()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
rsp, err := clt.GetSemaphores(ctx, &filter)
|
||||
if err != nil {
|
||||
return nil, trail.FromGRPC(err)
|
||||
}
|
||||
sems := make([]services.Semaphore, 0, len(rsp.Semaphores))
|
||||
for _, s := range rsp.Semaphores {
|
||||
sems = append(sems, s)
|
||||
}
|
||||
return sems, nil
|
||||
}
|
||||
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
func (c *Client) DeleteSemaphore(ctx context.Context, filter services.SemaphoreFilter) error {
|
||||
clt, err := c.grpc()
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if _, err := clt.DeleteSemaphore(ctx, &filter); err != nil {
|
||||
return trail.FromGRPC(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// WebService implements features used by Web UI clients
|
||||
type WebService interface {
|
||||
// GetWebSessionInfo checks if a web sesion is valid, returns session id in case if
|
||||
|
|
|
@ -428,6 +428,76 @@ func (g *GRPCServer) DeleteUser(ctx context.Context, req *proto.DeleteUserReques
|
|||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
func (g *GRPCServer) AcquireSemaphore(ctx context.Context, params *services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
auth, err := g.authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
|
||||
lease, err := auth.AcquireSemaphore(ctx, *params)
|
||||
return lease, trail.ToGRPC(err)
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
func (g *GRPCServer) KeepAliveSemaphoreLease(ctx context.Context, req *services.SemaphoreLease) (*empty.Empty, error) {
|
||||
auth, err := g.authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
if err := auth.KeepAliveSemaphoreLease(ctx, *req); err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
func (g *GRPCServer) CancelSemaphoreLease(ctx context.Context, req *services.SemaphoreLease) (*empty.Empty, error) {
|
||||
auth, err := g.authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
if err := auth.CancelSemaphoreLease(ctx, *req); err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
func (g *GRPCServer) GetSemaphores(ctx context.Context, req *services.SemaphoreFilter) (*proto.Semaphores, error) {
|
||||
auth, err := g.authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
semaphores, err := auth.GetSemaphores(ctx, *req)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
ss := make([]*services.SemaphoreV3, 0, len(semaphores))
|
||||
for _, sem := range semaphores {
|
||||
s, ok := sem.(*services.SemaphoreV3)
|
||||
if !ok {
|
||||
return nil, trail.ToGRPC(trace.BadParameter("unexpected semaphore type: %T", sem))
|
||||
}
|
||||
ss = append(ss, s)
|
||||
}
|
||||
return &proto.Semaphores{
|
||||
Semaphores: ss,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
func (g *GRPCServer) DeleteSemaphore(ctx context.Context, req *services.SemaphoreFilter) (*empty.Empty, error) {
|
||||
auth, err := g.authenticate(ctx)
|
||||
if err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
if err := auth.DeleteSemaphore(ctx, *req); err != nil {
|
||||
return nil, trail.ToGRPC(err)
|
||||
}
|
||||
return &empty.Empty{}, nil
|
||||
}
|
||||
|
||||
type grpcContext struct {
|
||||
*AuthContext
|
||||
*AuthWithRoles
|
||||
|
|
|
@ -251,6 +251,7 @@ func GetCheckerForBuiltinRole(clusterName string, clusterConfig services.Cluster
|
|||
services.NewRule(services.KindReverseTunnel, services.RW()),
|
||||
services.NewRule(services.KindTunnelConnection, services.RO()),
|
||||
services.NewRule(services.KindClusterConfig, services.RO()),
|
||||
services.NewRule(services.KindSemaphore, services.RW()),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -288,6 +289,7 @@ func GetCheckerForBuiltinRole(clusterName string, clusterConfig services.Cluster
|
|||
services.NewRule(services.KindTunnelConnection, services.RW()),
|
||||
services.NewRule(services.KindHostCert, services.RW()),
|
||||
services.NewRule(services.KindRemoteCluster, services.RO()),
|
||||
services.NewRule(services.KindSemaphore, services.RW()),
|
||||
// this rule allows local proxy to update the remote cluster's host certificate authorities
|
||||
// during certificates renewal
|
||||
{
|
||||
|
@ -338,6 +340,7 @@ func GetCheckerForBuiltinRole(clusterName string, clusterConfig services.Cluster
|
|||
services.NewRule(services.KindStaticTokens, services.RO()),
|
||||
services.NewRule(services.KindTunnelConnection, services.RW()),
|
||||
services.NewRule(services.KindRemoteCluster, services.RO()),
|
||||
services.NewRule(services.KindSemaphore, services.RW()),
|
||||
// this rule allows local proxy to update the remote cluster's host certificate authorities
|
||||
// during certificates renewal
|
||||
{
|
||||
|
|
|
@ -60,7 +60,7 @@ func (x Operation) String() string {
|
|||
return proto.EnumName(Operation_name, int32(x))
|
||||
}
|
||||
func (Operation) EnumDescriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{0}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{0}
|
||||
}
|
||||
|
||||
// Event returns cluster event
|
||||
|
@ -93,7 +93,7 @@ func (m *Event) Reset() { *m = Event{} }
|
|||
func (m *Event) String() string { return proto.CompactTextString(m) }
|
||||
func (*Event) ProtoMessage() {}
|
||||
func (*Event) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{0}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{0}
|
||||
}
|
||||
func (m *Event) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -583,7 +583,7 @@ func (m *Watch) Reset() { *m = Watch{} }
|
|||
func (m *Watch) String() string { return proto.CompactTextString(m) }
|
||||
func (*Watch) ProtoMessage() {}
|
||||
func (*Watch) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{1}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{1}
|
||||
}
|
||||
func (m *Watch) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -641,7 +641,7 @@ func (m *WatchKind) Reset() { *m = WatchKind{} }
|
|||
func (m *WatchKind) String() string { return proto.CompactTextString(m) }
|
||||
func (*WatchKind) ProtoMessage() {}
|
||||
func (*WatchKind) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{2}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{2}
|
||||
}
|
||||
func (m *WatchKind) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -713,7 +713,7 @@ func (m *Certs) Reset() { *m = Certs{} }
|
|||
func (m *Certs) String() string { return proto.CompactTextString(m) }
|
||||
func (*Certs) ProtoMessage() {}
|
||||
func (*Certs) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{3}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{3}
|
||||
}
|
||||
func (m *Certs) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -787,7 +787,7 @@ func (m *UserCertsRequest) Reset() { *m = UserCertsRequest{} }
|
|||
func (m *UserCertsRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*UserCertsRequest) ProtoMessage() {}
|
||||
func (*UserCertsRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{4}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{4}
|
||||
}
|
||||
func (m *UserCertsRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -873,7 +873,7 @@ func (m *GetUserRequest) Reset() { *m = GetUserRequest{} }
|
|||
func (m *GetUserRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetUserRequest) ProtoMessage() {}
|
||||
func (*GetUserRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{5}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{5}
|
||||
}
|
||||
func (m *GetUserRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -929,7 +929,7 @@ func (m *GetUsersRequest) Reset() { *m = GetUsersRequest{} }
|
|||
func (m *GetUsersRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetUsersRequest) ProtoMessage() {}
|
||||
func (*GetUsersRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{6}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{6}
|
||||
}
|
||||
func (m *GetUsersRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -977,7 +977,7 @@ func (m *AccessRequests) Reset() { *m = AccessRequests{} }
|
|||
func (m *AccessRequests) String() string { return proto.CompactTextString(m) }
|
||||
func (*AccessRequests) ProtoMessage() {}
|
||||
func (*AccessRequests) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{7}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{7}
|
||||
}
|
||||
func (m *AccessRequests) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1025,7 +1025,7 @@ func (m *PluginDataSeq) Reset() { *m = PluginDataSeq{} }
|
|||
func (m *PluginDataSeq) String() string { return proto.CompactTextString(m) }
|
||||
func (*PluginDataSeq) ProtoMessage() {}
|
||||
func (*PluginDataSeq) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{8}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{8}
|
||||
}
|
||||
func (m *PluginDataSeq) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1081,7 +1081,7 @@ func (m *RequestStateSetter) Reset() { *m = RequestStateSetter{} }
|
|||
func (m *RequestStateSetter) String() string { return proto.CompactTextString(m) }
|
||||
func (*RequestStateSetter) ProtoMessage() {}
|
||||
func (*RequestStateSetter) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{9}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{9}
|
||||
}
|
||||
func (m *RequestStateSetter) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1143,7 +1143,7 @@ func (m *RequestID) Reset() { *m = RequestID{} }
|
|||
func (m *RequestID) String() string { return proto.CompactTextString(m) }
|
||||
func (*RequestID) ProtoMessage() {}
|
||||
func (*RequestID) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{10}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{10}
|
||||
}
|
||||
func (m *RequestID) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1193,7 +1193,7 @@ func (m *RotateResetPasswordTokenSecretsRequest) Reset() {
|
|||
func (m *RotateResetPasswordTokenSecretsRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*RotateResetPasswordTokenSecretsRequest) ProtoMessage() {}
|
||||
func (*RotateResetPasswordTokenSecretsRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{11}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{11}
|
||||
}
|
||||
func (m *RotateResetPasswordTokenSecretsRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1241,7 +1241,7 @@ func (m *GetResetPasswordTokenRequest) Reset() { *m = GetResetPasswordTo
|
|||
func (m *GetResetPasswordTokenRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*GetResetPasswordTokenRequest) ProtoMessage() {}
|
||||
func (*GetResetPasswordTokenRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{12}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{12}
|
||||
}
|
||||
func (m *GetResetPasswordTokenRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1295,7 +1295,7 @@ func (m *CreateResetPasswordTokenRequest) Reset() { *m = CreateResetPass
|
|||
func (m *CreateResetPasswordTokenRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*CreateResetPasswordTokenRequest) ProtoMessage() {}
|
||||
func (*CreateResetPasswordTokenRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{13}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{13}
|
||||
}
|
||||
func (m *CreateResetPasswordTokenRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1356,7 +1356,7 @@ func (m *PingRequest) Reset() { *m = PingRequest{} }
|
|||
func (m *PingRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*PingRequest) ProtoMessage() {}
|
||||
func (*PingRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{14}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{14}
|
||||
}
|
||||
func (m *PingRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1400,7 +1400,7 @@ func (m *PingResponse) Reset() { *m = PingResponse{} }
|
|||
func (m *PingResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*PingResponse) ProtoMessage() {}
|
||||
func (*PingResponse) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{15}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{15}
|
||||
}
|
||||
func (m *PingResponse) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1456,7 +1456,7 @@ func (m *DeleteUserRequest) Reset() { *m = DeleteUserRequest{} }
|
|||
func (m *DeleteUserRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*DeleteUserRequest) ProtoMessage() {}
|
||||
func (*DeleteUserRequest) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_cc9d777e75d42d1b, []int{16}
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{16}
|
||||
}
|
||||
func (m *DeleteUserRequest) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
|
@ -1492,6 +1492,54 @@ func (m *DeleteUserRequest) GetName() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
// Semaphores is a sequence of Semaphore resources.
|
||||
type Semaphores struct {
|
||||
Semaphores []*services.SemaphoreV3 `protobuf:"bytes,1,rep,name=Semaphores" json:"semaphores"`
|
||||
XXX_NoUnkeyedLiteral struct{} `json:"-"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
XXX_sizecache int32 `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Semaphores) Reset() { *m = Semaphores{} }
|
||||
func (m *Semaphores) String() string { return proto.CompactTextString(m) }
|
||||
func (*Semaphores) ProtoMessage() {}
|
||||
func (*Semaphores) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_auth_df26b5d43b9135f6, []int{17}
|
||||
}
|
||||
func (m *Semaphores) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *Semaphores) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
if deterministic {
|
||||
return xxx_messageInfo_Semaphores.Marshal(b, m, deterministic)
|
||||
} else {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalTo(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
}
|
||||
func (dst *Semaphores) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_Semaphores.Merge(dst, src)
|
||||
}
|
||||
func (m *Semaphores) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *Semaphores) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_Semaphores.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_Semaphores proto.InternalMessageInfo
|
||||
|
||||
func (m *Semaphores) GetSemaphores() []*services.SemaphoreV3 {
|
||||
if m != nil {
|
||||
return m.Semaphores
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Event)(nil), "proto.Event")
|
||||
proto.RegisterType((*Watch)(nil), "proto.Watch")
|
||||
|
@ -1511,6 +1559,7 @@ func init() {
|
|||
proto.RegisterType((*PingRequest)(nil), "proto.PingRequest")
|
||||
proto.RegisterType((*PingResponse)(nil), "proto.PingResponse")
|
||||
proto.RegisterType((*DeleteUserRequest)(nil), "proto.DeleteUserRequest")
|
||||
proto.RegisterType((*Semaphores)(nil), "proto.Semaphores")
|
||||
proto.RegisterEnum("proto.Operation", Operation_name, Operation_value)
|
||||
}
|
||||
|
||||
|
@ -1564,8 +1613,18 @@ type AuthServiceClient interface {
|
|||
CreateUser(ctx context.Context, in *services.UserV2, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
// UpdateUser updates an existing user in a backend.
|
||||
UpdateUser(ctx context.Context, in *services.UserV2, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
// DeleteUser deletes an exisitng user in a backend by username.
|
||||
// DeleteUser deletes an existing user in a backend by username.
|
||||
DeleteUser(ctx context.Context, in *DeleteUserRequest, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
AcquireSemaphore(ctx context.Context, in *services.AcquireSemaphoreRequest, opts ...grpc.CallOption) (*services.SemaphoreLease, error)
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
KeepAliveSemaphoreLease(ctx context.Context, in *services.SemaphoreLease, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
CancelSemaphoreLease(ctx context.Context, in *services.SemaphoreLease, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
GetSemaphores(ctx context.Context, in *services.SemaphoreFilter, opts ...grpc.CallOption) (*Semaphores, error)
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
DeleteSemaphore(ctx context.Context, in *services.SemaphoreFilter, opts ...grpc.CallOption) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
type authServiceClient struct {
|
||||
|
@ -1818,6 +1877,51 @@ func (c *authServiceClient) DeleteUser(ctx context.Context, in *DeleteUserReques
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authServiceClient) AcquireSemaphore(ctx context.Context, in *services.AcquireSemaphoreRequest, opts ...grpc.CallOption) (*services.SemaphoreLease, error) {
|
||||
out := new(services.SemaphoreLease)
|
||||
err := c.cc.Invoke(ctx, "/proto.AuthService/AcquireSemaphore", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authServiceClient) KeepAliveSemaphoreLease(ctx context.Context, in *services.SemaphoreLease, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/proto.AuthService/KeepAliveSemaphoreLease", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authServiceClient) CancelSemaphoreLease(ctx context.Context, in *services.SemaphoreLease, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/proto.AuthService/CancelSemaphoreLease", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authServiceClient) GetSemaphores(ctx context.Context, in *services.SemaphoreFilter, opts ...grpc.CallOption) (*Semaphores, error) {
|
||||
out := new(Semaphores)
|
||||
err := c.cc.Invoke(ctx, "/proto.AuthService/GetSemaphores", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *authServiceClient) DeleteSemaphore(ctx context.Context, in *services.SemaphoreFilter, opts ...grpc.CallOption) (*empty.Empty, error) {
|
||||
out := new(empty.Empty)
|
||||
err := c.cc.Invoke(ctx, "/proto.AuthService/DeleteSemaphore", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Server API for AuthService service
|
||||
|
||||
type AuthServiceServer interface {
|
||||
|
@ -1860,8 +1964,18 @@ type AuthServiceServer interface {
|
|||
CreateUser(context.Context, *services.UserV2) (*empty.Empty, error)
|
||||
// UpdateUser updates an existing user in a backend.
|
||||
UpdateUser(context.Context, *services.UserV2) (*empty.Empty, error)
|
||||
// DeleteUser deletes an exisitng user in a backend by username.
|
||||
// DeleteUser deletes an existing user in a backend by username.
|
||||
DeleteUser(context.Context, *DeleteUserRequest) (*empty.Empty, error)
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
AcquireSemaphore(context.Context, *services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error)
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
KeepAliveSemaphoreLease(context.Context, *services.SemaphoreLease) (*empty.Empty, error)
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
CancelSemaphoreLease(context.Context, *services.SemaphoreLease) (*empty.Empty, error)
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
GetSemaphores(context.Context, *services.SemaphoreFilter) (*Semaphores, error)
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
DeleteSemaphore(context.Context, *services.SemaphoreFilter) (*empty.Empty, error)
|
||||
}
|
||||
|
||||
func RegisterAuthServiceServer(s *grpc.Server, srv AuthServiceServer) {
|
||||
|
@ -2224,6 +2338,96 @@ func _AuthService_DeleteUser_Handler(srv interface{}, ctx context.Context, dec f
|
|||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthService_AcquireSemaphore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(services.AcquireSemaphoreRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthServiceServer).AcquireSemaphore(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.AuthService/AcquireSemaphore",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthServiceServer).AcquireSemaphore(ctx, req.(*services.AcquireSemaphoreRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthService_KeepAliveSemaphoreLease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(services.SemaphoreLease)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthServiceServer).KeepAliveSemaphoreLease(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.AuthService/KeepAliveSemaphoreLease",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthServiceServer).KeepAliveSemaphoreLease(ctx, req.(*services.SemaphoreLease))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthService_CancelSemaphoreLease_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(services.SemaphoreLease)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthServiceServer).CancelSemaphoreLease(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.AuthService/CancelSemaphoreLease",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthServiceServer).CancelSemaphoreLease(ctx, req.(*services.SemaphoreLease))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthService_GetSemaphores_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(services.SemaphoreFilter)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthServiceServer).GetSemaphores(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.AuthService/GetSemaphores",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthServiceServer).GetSemaphores(ctx, req.(*services.SemaphoreFilter))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _AuthService_DeleteSemaphore_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(services.SemaphoreFilter)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(AuthServiceServer).DeleteSemaphore(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/proto.AuthService/DeleteSemaphore",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(AuthServiceServer).DeleteSemaphore(ctx, req.(*services.SemaphoreFilter))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
var _AuthService_serviceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "proto.AuthService",
|
||||
HandlerType: (*AuthServiceServer)(nil),
|
||||
|
@ -2292,6 +2496,26 @@ var _AuthService_serviceDesc = grpc.ServiceDesc{
|
|||
MethodName: "DeleteUser",
|
||||
Handler: _AuthService_DeleteUser_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "AcquireSemaphore",
|
||||
Handler: _AuthService_AcquireSemaphore_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "KeepAliveSemaphoreLease",
|
||||
Handler: _AuthService_KeepAliveSemaphoreLease_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "CancelSemaphoreLease",
|
||||
Handler: _AuthService_CancelSemaphoreLease_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetSemaphores",
|
||||
Handler: _AuthService_GetSemaphores_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "DeleteSemaphore",
|
||||
Handler: _AuthService_DeleteSemaphore_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{
|
||||
{
|
||||
|
@ -3094,6 +3318,39 @@ func (m *DeleteUserRequest) MarshalTo(dAtA []byte) (int, error) {
|
|||
return i, nil
|
||||
}
|
||||
|
||||
func (m *Semaphores) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Semaphores) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Semaphores) > 0 {
|
||||
for _, msg := range m.Semaphores {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintAuth(dAtA, i, uint64(msg.Size()))
|
||||
n, err := msg.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(dAtA[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeVarintAuth(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
|
@ -3504,6 +3761,21 @@ func (m *DeleteUserRequest) Size() (n int) {
|
|||
return n
|
||||
}
|
||||
|
||||
func (m *Semaphores) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Semaphores) > 0 {
|
||||
for _, e := range m.Semaphores {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovAuth(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAuth(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
|
@ -5744,6 +6016,88 @@ func (m *DeleteUserRequest) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Semaphores) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Semaphores: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Semaphores: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Semaphores", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Semaphores = append(m.Semaphores, &services.SemaphoreV3{})
|
||||
if err := m.Semaphores[len(m.Semaphores)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAuth(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, dAtA[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipAuth(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
@ -5849,118 +6203,125 @@ var (
|
|||
ErrIntOverflowAuth = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("auth.proto", fileDescriptor_auth_cc9d777e75d42d1b) }
|
||||
func init() { proto.RegisterFile("auth.proto", fileDescriptor_auth_df26b5d43b9135f6) }
|
||||
|
||||
var fileDescriptor_auth_cc9d777e75d42d1b = []byte{
|
||||
// 1758 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x57, 0xdd, 0x6e, 0xdb, 0xc8,
|
||||
0x15, 0x0e, 0x2d, 0xcb, 0x96, 0x8e, 0x6c, 0x45, 0x19, 0x3b, 0x36, 0xa3, 0x38, 0xa6, 0xa1, 0x60,
|
||||
0x17, 0xc6, 0x36, 0x95, 0xb6, 0x32, 0x36, 0x4d, 0x83, 0xa2, 0x41, 0x68, 0x2b, 0xb6, 0x37, 0x6e,
|
||||
0xea, 0x52, 0x8a, 0x52, 0xb4, 0x05, 0x04, 0x5a, 0x9a, 0xc8, 0x84, 0x29, 0x52, 0xcb, 0x19, 0x29,
|
||||
0x35, 0xd0, 0xab, 0xa2, 0x0f, 0xd0, 0xcb, 0x5e, 0xf4, 0x05, 0xfa, 0x14, 0xbd, 0xcd, 0x65, 0x9f,
|
||||
0x80, 0x6d, 0xd3, 0x3b, 0x3e, 0x42, 0xd1, 0x8b, 0xc5, 0xfc, 0x50, 0xe4, 0x50, 0x56, 0x36, 0xb9,
|
||||
0x12, 0xe7, 0xfc, 0x7c, 0xe7, 0xcc, 0x99, 0x33, 0xdf, 0x1c, 0x01, 0xd8, 0x13, 0x7a, 0x59, 0x1f,
|
||||
0x07, 0x3e, 0xf5, 0x51, 0x9e, 0xff, 0x54, 0x9f, 0x0e, 0x1d, 0x7a, 0x39, 0xb9, 0xa8, 0xf7, 0xfd,
|
||||
0x51, 0x63, 0x18, 0xd8, 0x53, 0x87, 0xda, 0xd4, 0xf1, 0x3d, 0xdb, 0x6d, 0x50, 0xec, 0xe2, 0xb1,
|
||||
0x1f, 0xd0, 0x86, 0xeb, 0x5c, 0x34, 0x08, 0x0e, 0xa6, 0x4e, 0x1f, 0x93, 0x06, 0xbd, 0x1e, 0x63,
|
||||
0x22, 0x20, 0xaa, 0x9b, 0x43, 0x7f, 0xe8, 0xf3, 0xcf, 0x06, 0xfb, 0x92, 0xd2, 0xfb, 0x43, 0xdf,
|
||||
0x1f, 0xba, 0xb8, 0xc1, 0x57, 0x17, 0x93, 0xb7, 0x0d, 0x3c, 0x1a, 0xd3, 0x6b, 0xa9, 0x34, 0xb2,
|
||||
0x4a, 0xea, 0x8c, 0x30, 0xa1, 0xf6, 0x68, 0x2c, 0x0c, 0x6a, 0xff, 0x28, 0x42, 0xbe, 0x35, 0xc5,
|
||||
0x1e, 0x45, 0x4f, 0x60, 0xb9, 0x73, 0x3d, 0xc6, 0xba, 0xb6, 0xa7, 0xed, 0x97, 0x9b, 0x15, 0xa1,
|
||||
0xaf, 0xff, 0x6a, 0x8c, 0x03, 0x9e, 0xa1, 0x89, 0xa2, 0xd0, 0x28, 0xb3, 0x74, 0x1e, 0xf9, 0x23,
|
||||
0x87, 0xf2, 0x20, 0x16, 0xf7, 0x40, 0xbf, 0x85, 0xb2, 0x85, 0x89, 0x3f, 0x09, 0xfa, 0xf8, 0x04,
|
||||
0xdb, 0x03, 0x1c, 0xe8, 0x4b, 0x7b, 0xda, 0x7e, 0xa9, 0xa9, 0xd7, 0xe3, 0x6d, 0xd4, 0x55, 0xbd,
|
||||
0xb9, 0x15, 0x85, 0x06, 0x0a, 0xa4, 0x2c, 0xc1, 0x3b, 0xb9, 0x65, 0x65, 0x90, 0x50, 0x0f, 0xd6,
|
||||
0x0f, 0x71, 0x40, 0x9f, 0x4f, 0xe8, 0xa5, 0x1f, 0x38, 0xf4, 0x5a, 0xcf, 0x71, 0xe8, 0x7b, 0x09,
|
||||
0xb4, 0xa2, 0xee, 0x36, 0xcd, 0x9d, 0x28, 0x34, 0xf4, 0x3e, 0x0e, 0x68, 0xcf, 0x8e, 0xa5, 0x4a,
|
||||
0x04, 0x15, 0x0f, 0xfd, 0x0e, 0xd6, 0xda, 0xec, 0x0c, 0xfa, 0x1d, 0xff, 0x0a, 0x7b, 0x44, 0x5f,
|
||||
0xce, 0xa6, 0x9e, 0xd6, 0x76, 0x9b, 0xe6, 0xfd, 0x28, 0x34, 0xb6, 0x09, 0x97, 0xf5, 0x28, 0x17,
|
||||
0x2a, 0xe8, 0x0a, 0x18, 0xea, 0x43, 0xf9, 0x3c, 0xf0, 0xa7, 0x0e, 0x71, 0x7c, 0x8f, 0x8b, 0xf4,
|
||||
0x3c, 0x87, 0xaf, 0x26, 0xf0, 0xaa, 0xbe, 0xdb, 0x34, 0x1f, 0x44, 0xa1, 0x71, 0x6f, 0x1c, 0x4b,
|
||||
0x45, 0x0c, 0xb5, 0x44, 0xaa, 0x0b, 0x7a, 0x03, 0xa5, 0x43, 0x77, 0x42, 0x28, 0x0e, 0x5e, 0xd9,
|
||||
0x23, 0xac, 0xaf, 0xf0, 0x08, 0xdb, 0xa9, 0x02, 0x25, 0xca, 0x6e, 0xd3, 0xac, 0x46, 0xa1, 0xb1,
|
||||
0xd5, 0x17, 0xa2, 0x9e, 0x67, 0x8f, 0xd4, 0xf2, 0xa7, 0x91, 0x78, 0xed, 0xc5, 0xf2, 0xd0, 0xf7,
|
||||
0xde, 0x3a, 0x43, 0x7d, 0x75, 0xae, 0xf6, 0x69, 0x75, 0xf7, 0x40, 0xd6, 0x5e, 0x82, 0xf7, 0xb9,
|
||||
0x34, 0x53, 0xfb, 0xb4, 0x03, 0x7a, 0x0a, 0xcb, 0xaf, 0x09, 0x0e, 0xf4, 0x02, 0xc7, 0xad, 0x24,
|
||||
0xb8, 0x4c, 0xda, 0x6d, 0x8a, 0x96, 0x9b, 0x10, 0x1c, 0x28, 0x20, 0xdc, 0x87, 0xf9, 0x5a, 0xbe,
|
||||
0x8b, 0xf5, 0x62, 0xd6, 0x97, 0x49, 0xbb, 0x07, 0xc2, 0x37, 0xf0, 0x5d, 0x75, 0x7f, 0xdc, 0x07,
|
||||
0x9d, 0x41, 0x91, 0x6d, 0x90, 0x8c, 0xed, 0x3e, 0xd6, 0x81, 0x03, 0x6c, 0x24, 0x00, 0x33, 0x95,
|
||||
0xb9, 0x1d, 0x85, 0xc6, 0x86, 0x17, 0x2f, 0x15, 0xa0, 0x04, 0x00, 0x99, 0xb0, 0xd2, 0xc6, 0xc1,
|
||||
0x14, 0x07, 0x7a, 0x89, 0x43, 0xa1, 0x54, 0xef, 0x70, 0x79, 0xb7, 0x69, 0x6e, 0x46, 0xa1, 0x51,
|
||||
0x21, 0x7c, 0xa5, 0xc0, 0x48, 0x4f, 0x56, 0x6a, 0x0b, 0x4f, 0x71, 0x40, 0x70, 0x67, 0xe2, 0x79,
|
||||
0xd8, 0xd5, 0xd7, 0xb2, 0xa5, 0x56, 0xd4, 0x71, 0x9b, 0x07, 0x42, 0xd8, 0xa3, 0x5c, 0xaa, 0x96,
|
||||
0x5a, 0x71, 0x40, 0x57, 0x50, 0x11, 0x5f, 0x87, 0xbe, 0xe7, 0xe1, 0x3e, 0xbb, 0xd1, 0xfa, 0x3a,
|
||||
0x8f, 0xb1, 0x93, 0xc4, 0xc8, 0x5a, 0x74, 0x9b, 0xa6, 0x11, 0x85, 0xc6, 0x7d, 0x01, 0xcf, 0x0e,
|
||||
0x54, 0x2a, 0x94, 0x48, 0x73, 0xc0, 0x6c, 0x37, 0xcf, 0xfb, 0x7d, 0x4c, 0x88, 0x85, 0xbf, 0x9b,
|
||||
0x60, 0x42, 0xf5, 0x72, 0x76, 0x37, 0x8a, 0x3a, 0x6e, 0x1c, 0x9b, 0x0b, 0x7b, 0x81, 0x90, 0xaa,
|
||||
0xbb, 0x51, 0x1c, 0x4c, 0x80, 0x42, 0xcc, 0x13, 0xb5, 0x13, 0xc8, 0xbf, 0xb1, 0x69, 0xff, 0x12,
|
||||
0x3d, 0x83, 0xfc, 0x4b, 0xc7, 0x1b, 0x10, 0x5d, 0xdb, 0xcb, 0xf1, 0x96, 0x10, 0x0c, 0xc6, 0x95,
|
||||
0x4c, 0x61, 0x6e, 0xbf, 0x0f, 0x8d, 0x5b, 0x51, 0x68, 0xdc, 0xbe, 0x62, 0x66, 0x29, 0x1a, 0x13,
|
||||
0x7e, 0xb5, 0x3f, 0x2d, 0x41, 0x71, 0x66, 0x8d, 0x76, 0x60, 0x99, 0xfd, 0x72, 0x3e, 0x2c, 0x9a,
|
||||
0x85, 0x28, 0x34, 0x96, 0x99, 0x9f, 0xc5, 0xa5, 0xa8, 0x09, 0xa5, 0x33, 0xdf, 0x1e, 0xb4, 0x71,
|
||||
0x3f, 0xc0, 0x94, 0x70, 0xc2, 0x2b, 0x98, 0x95, 0x28, 0x34, 0xd6, 0x5c, 0xdf, 0x1e, 0xf4, 0x88,
|
||||
0x90, 0x5b, 0x69, 0x23, 0x86, 0xc8, 0x6f, 0x68, 0x2e, 0x41, 0x64, 0xcd, 0x65, 0x71, 0x29, 0xfa,
|
||||
0x16, 0x56, 0x5e, 0x38, 0x2e, 0xc5, 0x81, 0xbe, 0xcc, 0xf3, 0xdf, 0xc9, 0xe6, 0x5f, 0x17, 0xea,
|
||||
0x96, 0x47, 0x83, 0x6b, 0xd1, 0x50, 0x6f, 0xb9, 0x20, 0xb5, 0x11, 0x89, 0x50, 0xfd, 0x19, 0x94,
|
||||
0x52, 0xc6, 0xa8, 0x02, 0xb9, 0x2b, 0x7c, 0x2d, 0x76, 0x62, 0xb1, 0x4f, 0xb4, 0x09, 0xf9, 0xa9,
|
||||
0xed, 0x4e, 0x30, 0x4f, 0xbc, 0x68, 0x89, 0xc5, 0xd3, 0xa5, 0x27, 0x5a, 0xed, 0xd7, 0x90, 0x67,
|
||||
0x04, 0x49, 0xd0, 0x43, 0xc8, 0xb5, 0xdb, 0x27, 0xdc, 0x69, 0xcd, 0xbc, 0x13, 0x85, 0xc6, 0x3a,
|
||||
0x21, 0x97, 0xa9, 0x58, 0x4c, 0xcb, 0x8c, 0x3a, 0x67, 0x6d, 0x8e, 0x22, 0x8d, 0xa8, 0x9b, 0xae,
|
||||
0x2c, 0xd3, 0xd6, 0xfe, 0xbf, 0x04, 0x15, 0x76, 0x67, 0x39, 0xae, 0x3c, 0x42, 0xf4, 0x08, 0x8a,
|
||||
0xe7, 0x93, 0x0b, 0xd7, 0xe9, 0xbf, 0x94, 0x99, 0xad, 0x99, 0xe5, 0x28, 0x34, 0x60, 0xcc, 0x85,
|
||||
0xbd, 0x2b, 0x7c, 0x6d, 0x25, 0x06, 0x68, 0x1f, 0x0a, 0x0c, 0x81, 0x95, 0x4b, 0xa4, 0x6c, 0xae,
|
||||
0x45, 0xa1, 0x51, 0x98, 0x48, 0x99, 0x35, 0xd3, 0xa2, 0x36, 0xac, 0xb6, 0xfe, 0x30, 0x76, 0x02,
|
||||
0x4c, 0xe4, 0x53, 0x51, 0xad, 0x8b, 0x37, 0xb0, 0x1e, 0xbf, 0x81, 0xf5, 0x4e, 0xfc, 0x06, 0x9a,
|
||||
0x0f, 0x64, 0x47, 0xdc, 0xc1, 0xc2, 0x25, 0xc9, 0xfc, 0x2f, 0xff, 0x32, 0x34, 0x2b, 0x46, 0x42,
|
||||
0x8f, 0x60, 0xe5, 0x85, 0x1f, 0x8c, 0x6c, 0xca, 0x9f, 0x87, 0xa2, 0xac, 0x3e, 0x97, 0x28, 0xd5,
|
||||
0xe7, 0x12, 0xf4, 0x02, 0xca, 0x96, 0x3f, 0xa1, 0xb8, 0xe3, 0x4b, 0xba, 0xe3, 0xac, 0x5f, 0x34,
|
||||
0x77, 0xa3, 0xd0, 0xa8, 0x06, 0x4c, 0xd3, 0xa3, 0x7e, 0x4f, 0xd2, 0x64, 0xca, 0x3f, 0xe3, 0x85,
|
||||
0x5a, 0x50, 0x56, 0xda, 0x9e, 0xe8, 0x2b, 0x7b, 0xb9, 0xfd, 0xa2, 0x78, 0x21, 0xd4, 0xcb, 0x92,
|
||||
0xae, 0x79, 0xc6, 0xa9, 0xe6, 0x42, 0xf9, 0x18, 0x53, 0x56, 0xa0, 0xb8, 0xf6, 0x71, 0x23, 0x6a,
|
||||
0x37, 0x36, 0xe2, 0xcf, 0xa1, 0xf4, 0xc6, 0xa1, 0x97, 0x6a, 0x6b, 0xf3, 0x67, 0xe3, 0x9d, 0x43,
|
||||
0x2f, 0xe3, 0xd6, 0x4e, 0x05, 0x4c, 0x9b, 0xd7, 0x5a, 0x70, 0x5b, 0x46, 0x9b, 0x1d, 0x75, 0x53,
|
||||
0x05, 0xd4, 0x92, 0xbb, 0x92, 0x06, 0x54, 0x61, 0x2e, 0xb3, 0x7b, 0x47, 0xdd, 0xb9, 0x6a, 0x88,
|
||||
0x7b, 0xfe, 0x11, 0x56, 0xd9, 0x60, 0x97, 0x3d, 0x53, 0xa8, 0xb9, 0xf2, 0xfc, 0x06, 0xd6, 0xcf,
|
||||
0xdd, 0xc9, 0xd0, 0xf1, 0x8e, 0x6c, 0x6a, 0xb7, 0xf1, 0x77, 0xe8, 0x18, 0x20, 0x11, 0xc8, 0x20,
|
||||
0x5b, 0xa9, 0x07, 0x7b, 0xa6, 0xeb, 0x1e, 0x98, 0xb7, 0xa3, 0xd0, 0x28, 0x8d, 0xb9, 0xa4, 0x37,
|
||||
0xb0, 0xa9, 0x6d, 0xa5, 0x5c, 0x6b, 0x7f, 0xd3, 0x00, 0xc9, 0x30, 0x6c, 0x2a, 0xc0, 0x6d, 0x4c,
|
||||
0xd9, 0xb1, 0x6e, 0xc1, 0xd2, 0xe9, 0x91, 0xac, 0xfd, 0x4a, 0x14, 0x1a, 0x4b, 0xce, 0xc0, 0x5a,
|
||||
0x3a, 0x3d, 0x42, 0x3f, 0x85, 0x3c, 0x37, 0xe3, 0x15, 0x2f, 0xa7, 0x43, 0xa6, 0x41, 0xcc, 0x62,
|
||||
0x14, 0x1a, 0x79, 0x36, 0x80, 0x60, 0x4b, 0xd8, 0xa3, 0x6f, 0xa0, 0x78, 0x84, 0x5d, 0x3c, 0xb4,
|
||||
0xa9, 0x1f, 0x48, 0x72, 0xe1, 0x2f, 0xd7, 0x20, 0x16, 0xa6, 0xce, 0x2a, 0xb1, 0xac, 0x3d, 0x84,
|
||||
0xa2, 0x04, 0x3e, 0x3d, 0x5a, 0x94, 0x54, 0xed, 0x97, 0xf0, 0xa5, 0xe5, 0xf3, 0x60, 0x98, 0x60,
|
||||
0x7a, 0x6e, 0x13, 0xf2, 0xce, 0x0f, 0x06, 0x7c, 0xf0, 0x90, 0x47, 0x15, 0x9f, 0xf2, 0x43, 0x58,
|
||||
0xe5, 0xe2, 0x19, 0x0c, 0x4f, 0x94, 0x8f, 0x2f, 0x56, 0xac, 0xa9, 0x1d, 0xc2, 0xce, 0x31, 0xa6,
|
||||
0xf3, 0x58, 0x9f, 0x05, 0xf2, 0x67, 0x0d, 0x8c, 0xc3, 0x00, 0xdf, 0x98, 0xd4, 0xa7, 0xb5, 0xf8,
|
||||
0x8e, 0x9c, 0x75, 0x97, 0x12, 0x2d, 0x9b, 0x6c, 0xe5, 0x3c, 0xfb, 0x05, 0xe4, 0x3a, 0x9d, 0x33,
|
||||
0x5e, 0xc9, 0x1c, 0xef, 0xa1, 0x1c, 0xa5, 0xee, 0xff, 0x42, 0xa3, 0x70, 0x34, 0x11, 0xb3, 0xb0,
|
||||
0xc5, 0xf4, 0xb5, 0x75, 0x28, 0x9d, 0x3b, 0xde, 0x50, 0x46, 0xac, 0xfd, 0x11, 0xd6, 0xc4, 0x92,
|
||||
0x8c, 0x7d, 0x8f, 0x60, 0xd6, 0xf5, 0xe9, 0xb1, 0x4c, 0x24, 0xc2, 0xbb, 0x3e, 0x3d, 0x7d, 0xa9,
|
||||
0x13, 0xd7, 0x13, 0x58, 0x97, 0x23, 0x03, 0x0e, 0xd8, 0x80, 0x27, 0x13, 0xe4, 0xb3, 0x8c, 0x98,
|
||||
0x1e, 0x7a, 0x53, 0xa1, 0xb1, 0x54, 0xc3, 0xda, 0x4f, 0xe0, 0x0e, 0x3b, 0x59, 0x8a, 0x3f, 0xf9,
|
||||
0x9e, 0x7f, 0xf5, 0x15, 0x14, 0x67, 0xd3, 0x3d, 0x2a, 0xc0, 0xf2, 0xe9, 0xab, 0xd3, 0x4e, 0xe5,
|
||||
0x16, 0x5a, 0x85, 0xdc, 0xf9, 0xeb, 0x4e, 0x45, 0x43, 0x00, 0x2b, 0x47, 0xad, 0xb3, 0x56, 0xa7,
|
||||
0x55, 0x59, 0x6a, 0xfe, 0x1d, 0xa0, 0xc4, 0x66, 0xe6, 0xb6, 0x68, 0x49, 0xf4, 0x0c, 0xca, 0x6d,
|
||||
0xec, 0x0d, 0x5e, 0x62, 0x3c, 0x7e, 0xee, 0x3a, 0x53, 0x4c, 0x50, 0x6a, 0x80, 0x9a, 0x49, 0xab,
|
||||
0x5b, 0x73, 0xdc, 0xdb, 0x62, 0x5d, 0xb8, 0xaf, 0xa1, 0x1f, 0x41, 0x89, 0x3f, 0x6c, 0xfc, 0xbf,
|
||||
0x07, 0x41, 0x6b, 0xe9, 0xc7, 0xae, 0x1a, 0xaf, 0xb8, 0xf2, 0x6b, 0x0d, 0x7d, 0x03, 0xf0, 0x7a,
|
||||
0x4c, 0x70, 0x40, 0x5f, 0xf9, 0x03, 0x8c, 0x6e, 0x98, 0xaf, 0xaa, 0x37, 0x45, 0x47, 0x4f, 0xe1,
|
||||
0xce, 0x31, 0xf6, 0xd8, 0x0e, 0xf1, 0xec, 0xf9, 0x41, 0xdb, 0x12, 0x3b, 0xfb, 0x20, 0xcd, 0x82,
|
||||
0x0a, 0xb3, 0x26, 0xac, 0x4a, 0x1a, 0x43, 0x77, 0xa5, 0x42, 0x25, 0xd1, 0xea, 0xdc, 0xb8, 0x8a,
|
||||
0x1e, 0x43, 0x21, 0xa6, 0x3e, 0xb4, 0xa5, 0x3a, 0x91, 0x85, 0x5e, 0x5f, 0x6b, 0xe8, 0x94, 0xe5,
|
||||
0x49, 0x33, 0x74, 0xf7, 0x60, 0x01, 0xad, 0xc9, 0x17, 0x3e, 0x4e, 0x2a, 0xe3, 0x75, 0x02, 0x1b,
|
||||
0xe2, 0x66, 0x28, 0x72, 0xb4, 0x98, 0x23, 0x17, 0x1d, 0x11, 0x7a, 0x06, 0x1b, 0xa2, 0xa1, 0x54,
|
||||
0xa4, 0x78, 0xaa, 0x9a, 0x31, 0xc7, 0x42, 0x80, 0x6f, 0xe1, 0x6e, 0x3b, 0xb3, 0x2b, 0x41, 0x57,
|
||||
0xf7, 0x54, 0x88, 0x14, 0x35, 0x2e, 0xc4, 0x7a, 0x0e, 0xeb, 0xc7, 0x98, 0x26, 0xd4, 0x8a, 0xaa,
|
||||
0x37, 0xf1, 0xb1, 0x2c, 0xcd, 0xa6, 0xc4, 0x57, 0x59, 0xfd, 0x0c, 0x2a, 0xaf, 0xc7, 0x03, 0x9b,
|
||||
0xe2, 0x14, 0xca, 0xde, 0x4d, 0x28, 0xd2, 0xca, 0x0e, 0xec, 0x11, 0x59, 0x98, 0x50, 0x03, 0x96,
|
||||
0xd9, 0x65, 0x47, 0x28, 0x8e, 0x95, 0x10, 0x41, 0x75, 0x43, 0x91, 0x49, 0x36, 0x78, 0x07, 0xc6,
|
||||
0x0f, 0xf0, 0x28, 0xfa, 0x71, 0x5c, 0x97, 0x4f, 0xe2, 0xdb, 0xea, 0x17, 0xca, 0xbf, 0xeb, 0x9b,
|
||||
0x6d, 0xbb, 0x07, 0xe8, 0xf7, 0x70, 0xf7, 0x46, 0xc6, 0x45, 0x0f, 0x93, 0x0e, 0x5d, 0x48, 0xa3,
|
||||
0xd5, 0xdd, 0x8f, 0x05, 0xe9, 0x1e, 0xa0, 0x0b, 0xd0, 0x17, 0x31, 0x31, 0xfa, 0x32, 0xbe, 0x50,
|
||||
0x1f, 0xa7, 0xea, 0x1f, 0x8c, 0xf1, 0x18, 0x40, 0x40, 0xf0, 0xdb, 0x38, 0x77, 0x81, 0x16, 0x9e,
|
||||
0xd1, 0x63, 0xc6, 0x1a, 0x83, 0xcf, 0xf7, 0xfb, 0x05, 0x40, 0x42, 0xa5, 0x48, 0x97, 0xbb, 0x98,
|
||||
0x63, 0xd7, 0x45, 0xfe, 0xe6, 0xe6, 0xfb, 0xff, 0xec, 0x6a, 0xef, 0x3f, 0xec, 0x6a, 0xff, 0xfc,
|
||||
0xb0, 0xab, 0xfd, 0xfb, 0xc3, 0xae, 0xf6, 0xd7, 0xff, 0xee, 0xde, 0xba, 0x58, 0xe1, 0x56, 0x07,
|
||||
0xdf, 0x07, 0x00, 0x00, 0xff, 0xff, 0x76, 0xfd, 0xe5, 0x52, 0x14, 0x12, 0x00, 0x00,
|
||||
var fileDescriptor_auth_df26b5d43b9135f6 = []byte{
|
||||
// 1865 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x57, 0x4f, 0x6f, 0xdb, 0xc8,
|
||||
0x15, 0x8f, 0x2c, 0xdb, 0xb1, 0x9e, 0x6c, 0x45, 0x1e, 0x3b, 0x36, 0xa3, 0x38, 0xa6, 0xab, 0x60,
|
||||
0x17, 0xc6, 0x36, 0xb5, 0xb7, 0x32, 0x36, 0x4d, 0x83, 0xb6, 0x81, 0x69, 0x2b, 0xb6, 0x37, 0x6e,
|
||||
0xd6, 0xa5, 0x14, 0xa5, 0x68, 0x0b, 0x08, 0xb4, 0xf4, 0x22, 0x13, 0xa6, 0x48, 0x85, 0x33, 0x72,
|
||||
0x6a, 0xa0, 0xa7, 0xa2, 0x1f, 0xa0, 0xa7, 0xa2, 0x87, 0x7e, 0x97, 0x5e, 0x73, 0xec, 0x27, 0x60,
|
||||
0xdb, 0xf4, 0xc6, 0x8f, 0x50, 0xf4, 0x50, 0xcc, 0x1f, 0x8a, 0x1c, 0x59, 0xca, 0x66, 0xb1, 0x27,
|
||||
0x71, 0xde, 0x9f, 0xdf, 0x7b, 0xf3, 0xe6, 0xcd, 0x6f, 0x9e, 0x00, 0x9c, 0x21, 0xbb, 0xd8, 0x19,
|
||||
0x84, 0x01, 0x0b, 0xc8, 0x9c, 0xf8, 0xa9, 0x3c, 0xed, 0xb9, 0xec, 0x62, 0x78, 0xbe, 0xd3, 0x09,
|
||||
0xfa, 0xbb, 0xbd, 0xd0, 0xb9, 0x72, 0x99, 0xc3, 0xdc, 0xc0, 0x77, 0xbc, 0x5d, 0x86, 0x1e, 0x0e,
|
||||
0x82, 0x90, 0xed, 0x7a, 0xee, 0xf9, 0x2e, 0xc5, 0xf0, 0xca, 0xed, 0x20, 0xdd, 0x65, 0xd7, 0x03,
|
||||
0xa4, 0x12, 0xa2, 0xb2, 0xda, 0x0b, 0x7a, 0x81, 0xf8, 0xdc, 0xe5, 0x5f, 0x4a, 0x7a, 0xbf, 0x17,
|
||||
0x04, 0x3d, 0x0f, 0x77, 0xc5, 0xea, 0x7c, 0xf8, 0x66, 0x17, 0xfb, 0x03, 0x76, 0xad, 0x94, 0xe6,
|
||||
0xb8, 0x92, 0xb9, 0x7d, 0xa4, 0xcc, 0xe9, 0x0f, 0xa4, 0x41, 0xf5, 0xef, 0x05, 0x98, 0xab, 0x5f,
|
||||
0xa1, 0xcf, 0xc8, 0x13, 0x98, 0x6d, 0x5e, 0x0f, 0xd0, 0xc8, 0x6d, 0xe5, 0xb6, 0x4b, 0xb5, 0xb2,
|
||||
0xd4, 0xef, 0x7c, 0x33, 0xc0, 0x50, 0x64, 0x68, 0x91, 0x38, 0x32, 0x4b, 0x3c, 0x9d, 0x47, 0x41,
|
||||
0xdf, 0x65, 0x22, 0x88, 0x2d, 0x3c, 0xc8, 0x6f, 0xa0, 0x64, 0x23, 0x0d, 0x86, 0x61, 0x07, 0x8f,
|
||||
0xd1, 0xe9, 0x62, 0x68, 0xcc, 0x6c, 0xe5, 0xb6, 0x8b, 0x35, 0x63, 0x27, 0xd9, 0xc6, 0x8e, 0xae,
|
||||
0xb7, 0xd6, 0xe2, 0xc8, 0x24, 0xa1, 0x92, 0xa5, 0x78, 0xc7, 0xb7, 0xec, 0x31, 0x24, 0xd2, 0x86,
|
||||
0xa5, 0x03, 0x0c, 0xd9, 0xfe, 0x90, 0x5d, 0x04, 0xa1, 0xcb, 0xae, 0x8d, 0xbc, 0x80, 0xbe, 0x97,
|
||||
0x42, 0x6b, 0xea, 0x56, 0xcd, 0xda, 0x88, 0x23, 0xd3, 0xe8, 0x60, 0xc8, 0xda, 0x4e, 0x22, 0xd5,
|
||||
0x22, 0xe8, 0x78, 0xe4, 0xb7, 0xb0, 0xd8, 0xe0, 0x67, 0xd0, 0x69, 0x06, 0x97, 0xe8, 0x53, 0x63,
|
||||
0x76, 0x3c, 0xf5, 0xac, 0xb6, 0x55, 0xb3, 0xee, 0xc7, 0x91, 0xb9, 0x4e, 0x85, 0xac, 0xcd, 0x84,
|
||||
0x50, 0x43, 0xd7, 0xc0, 0x48, 0x07, 0x4a, 0x67, 0x61, 0x70, 0xe5, 0x52, 0x37, 0xf0, 0x85, 0xc8,
|
||||
0x98, 0x13, 0xf0, 0x95, 0x14, 0x5e, 0xd7, 0xb7, 0x6a, 0xd6, 0x83, 0x38, 0x32, 0xef, 0x0d, 0x12,
|
||||
0xa9, 0x8c, 0xa1, 0x97, 0x48, 0x77, 0x21, 0xaf, 0xa1, 0x78, 0xe0, 0x0d, 0x29, 0xc3, 0xf0, 0xa5,
|
||||
0xd3, 0x47, 0x63, 0x5e, 0x44, 0x58, 0xcf, 0x14, 0x28, 0x55, 0xb6, 0x6a, 0x56, 0x25, 0x8e, 0xcc,
|
||||
0xb5, 0x8e, 0x14, 0xb5, 0x7d, 0xa7, 0xaf, 0x97, 0x3f, 0x8b, 0x24, 0x6a, 0x2f, 0x97, 0x07, 0x81,
|
||||
0xff, 0xc6, 0xed, 0x19, 0xb7, 0x6f, 0xd4, 0x3e, 0xab, 0x6e, 0xed, 0xa9, 0xda, 0x2b, 0xf0, 0x8e,
|
||||
0x90, 0x8e, 0xd5, 0x3e, 0xeb, 0x40, 0x9e, 0xc2, 0xec, 0x2b, 0x8a, 0xa1, 0xb1, 0x20, 0x70, 0xcb,
|
||||
0x29, 0x2e, 0x97, 0xb6, 0x6a, 0xb2, 0xe5, 0x86, 0x14, 0x43, 0x0d, 0x44, 0xf8, 0x70, 0x5f, 0x3b,
|
||||
0xf0, 0xd0, 0x28, 0x8c, 0xfb, 0x72, 0x69, 0x6b, 0x4f, 0xfa, 0x86, 0x81, 0xa7, 0xef, 0x4f, 0xf8,
|
||||
0x90, 0x53, 0x28, 0xf0, 0x0d, 0xd2, 0x81, 0xd3, 0x41, 0x03, 0x04, 0xc0, 0x4a, 0x0a, 0x30, 0x52,
|
||||
0x59, 0xeb, 0x71, 0x64, 0xae, 0xf8, 0xc9, 0x52, 0x03, 0x4a, 0x01, 0x88, 0x05, 0xf3, 0x0d, 0x0c,
|
||||
0xaf, 0x30, 0x34, 0x8a, 0x02, 0x8a, 0x64, 0x7a, 0x47, 0xc8, 0x5b, 0x35, 0x6b, 0x35, 0x8e, 0xcc,
|
||||
0x32, 0x15, 0x2b, 0x0d, 0x46, 0x79, 0xf2, 0x52, 0xdb, 0x78, 0x85, 0x21, 0xc5, 0xe6, 0xd0, 0xf7,
|
||||
0xd1, 0x33, 0x16, 0xc7, 0x4b, 0xad, 0xa9, 0x93, 0x36, 0x0f, 0xa5, 0xb0, 0xcd, 0x84, 0x54, 0x2f,
|
||||
0xb5, 0xe6, 0x40, 0x2e, 0xa1, 0x2c, 0xbf, 0x0e, 0x02, 0xdf, 0xc7, 0x0e, 0xbf, 0xd1, 0xc6, 0x92,
|
||||
0x88, 0xb1, 0x91, 0xc6, 0x18, 0xb7, 0x68, 0xd5, 0x2c, 0x33, 0x8e, 0xcc, 0xfb, 0x12, 0x9e, 0x1f,
|
||||
0xa8, 0x52, 0x68, 0x91, 0x6e, 0x00, 0xf3, 0xdd, 0xec, 0x77, 0x3a, 0x48, 0xa9, 0x8d, 0x6f, 0x87,
|
||||
0x48, 0x99, 0x51, 0x1a, 0xdf, 0x8d, 0xa6, 0x4e, 0x1a, 0xc7, 0x11, 0xc2, 0x76, 0x28, 0xa5, 0xfa,
|
||||
0x6e, 0x34, 0x07, 0x0b, 0x60, 0x21, 0xe1, 0x89, 0xea, 0x31, 0xcc, 0xbd, 0x76, 0x58, 0xe7, 0x82,
|
||||
0x3c, 0x83, 0xb9, 0x17, 0xae, 0xdf, 0xa5, 0x46, 0x6e, 0x2b, 0x2f, 0x5a, 0x42, 0x32, 0x98, 0x50,
|
||||
0x72, 0x85, 0xb5, 0xfe, 0x3e, 0x32, 0x6f, 0xc5, 0x91, 0x79, 0xe7, 0x92, 0x9b, 0x65, 0x68, 0x4c,
|
||||
0xfa, 0x55, 0xff, 0x38, 0x03, 0x85, 0x91, 0x35, 0xd9, 0x80, 0x59, 0xfe, 0x2b, 0xf8, 0xb0, 0x60,
|
||||
0x2d, 0xc4, 0x91, 0x39, 0xcb, 0xfd, 0x6c, 0x21, 0x25, 0x35, 0x28, 0x9e, 0x06, 0x4e, 0xb7, 0x81,
|
||||
0x9d, 0x10, 0x19, 0x15, 0x84, 0xb7, 0x60, 0x95, 0xe3, 0xc8, 0x5c, 0xf4, 0x02, 0xa7, 0xdb, 0xa6,
|
||||
0x52, 0x6e, 0x67, 0x8d, 0x38, 0xa2, 0xb8, 0xa1, 0xf9, 0x14, 0x91, 0x37, 0x97, 0x2d, 0xa4, 0xe4,
|
||||
0x6b, 0x98, 0x7f, 0xee, 0x7a, 0x0c, 0x43, 0x63, 0x56, 0xe4, 0xbf, 0x31, 0x9e, 0xff, 0x8e, 0x54,
|
||||
0xd7, 0x7d, 0x16, 0x5e, 0xcb, 0x86, 0x7a, 0x23, 0x04, 0x99, 0x8d, 0x28, 0x84, 0xca, 0x4f, 0xa1,
|
||||
0x98, 0x31, 0x26, 0x65, 0xc8, 0x5f, 0xe2, 0xb5, 0xdc, 0x89, 0xcd, 0x3f, 0xc9, 0x2a, 0xcc, 0x5d,
|
||||
0x39, 0xde, 0x10, 0x45, 0xe2, 0x05, 0x5b, 0x2e, 0x9e, 0xce, 0x3c, 0xc9, 0x55, 0x7f, 0x05, 0x73,
|
||||
0x9c, 0x20, 0x29, 0x79, 0x08, 0xf9, 0x46, 0xe3, 0x58, 0x38, 0x2d, 0x5a, 0xcb, 0x71, 0x64, 0x2e,
|
||||
0x51, 0x7a, 0x91, 0x89, 0xc5, 0xb5, 0xdc, 0xa8, 0x79, 0xda, 0x10, 0x28, 0xca, 0x88, 0x79, 0xd9,
|
||||
0xca, 0x72, 0x6d, 0xf5, 0x7f, 0x33, 0x50, 0xe6, 0x77, 0x56, 0xe0, 0xaa, 0x23, 0x24, 0x8f, 0xa0,
|
||||
0x70, 0x36, 0x3c, 0xf7, 0xdc, 0xce, 0x0b, 0x95, 0xd9, 0xa2, 0x55, 0x8a, 0x23, 0x13, 0x06, 0x42,
|
||||
0xd8, 0xbe, 0xc4, 0x6b, 0x3b, 0x35, 0x20, 0xdb, 0xb0, 0xc0, 0x11, 0x78, 0xb9, 0x64, 0xca, 0xd6,
|
||||
0x62, 0x1c, 0x99, 0x0b, 0x43, 0x25, 0xb3, 0x47, 0x5a, 0xd2, 0x80, 0xdb, 0xf5, 0xdf, 0x0f, 0xdc,
|
||||
0x10, 0xa9, 0x7a, 0x2a, 0x2a, 0x3b, 0xf2, 0x0d, 0xdc, 0x49, 0xde, 0xc0, 0x9d, 0x66, 0xf2, 0x06,
|
||||
0x5a, 0x0f, 0x54, 0x47, 0x2c, 0xa3, 0x74, 0x49, 0x33, 0xff, 0xf3, 0x3f, 0xcd, 0x9c, 0x9d, 0x20,
|
||||
0x91, 0x47, 0x30, 0xff, 0x3c, 0x08, 0xfb, 0x0e, 0x13, 0xcf, 0x43, 0x41, 0x55, 0x5f, 0x48, 0xb4,
|
||||
0xea, 0x0b, 0x09, 0x79, 0x0e, 0x25, 0x3b, 0x18, 0x32, 0x6c, 0x06, 0x8a, 0xee, 0x04, 0xeb, 0x17,
|
||||
0xac, 0xcd, 0x38, 0x32, 0x2b, 0x21, 0xd7, 0xb4, 0x59, 0xd0, 0x56, 0x34, 0x99, 0xf1, 0x1f, 0xf3,
|
||||
0x22, 0x75, 0x28, 0x69, 0x6d, 0x4f, 0x8d, 0xf9, 0xad, 0xfc, 0x76, 0x41, 0xbe, 0x10, 0xfa, 0x65,
|
||||
0xc9, 0xd6, 0x7c, 0xcc, 0xa9, 0xea, 0x41, 0xe9, 0x08, 0x19, 0x2f, 0x50, 0x52, 0xfb, 0xa4, 0x11,
|
||||
0x73, 0x13, 0x1b, 0xf1, 0x67, 0x50, 0x7c, 0xed, 0xb2, 0x0b, 0xbd, 0xb5, 0xc5, 0xb3, 0xf1, 0xce,
|
||||
0x65, 0x17, 0x49, 0x6b, 0x67, 0x02, 0x66, 0xcd, 0xab, 0x75, 0xb8, 0xa3, 0xa2, 0x8d, 0x8e, 0xba,
|
||||
0xa6, 0x03, 0xe6, 0xd2, 0xbb, 0x92, 0x05, 0xd4, 0x61, 0x2e, 0xc6, 0xf7, 0x4e, 0x5a, 0x37, 0xaa,
|
||||
0x21, 0xef, 0xf9, 0x47, 0x58, 0x65, 0x85, 0x5f, 0xf6, 0xb1, 0x42, 0xdd, 0x28, 0xcf, 0xaf, 0x61,
|
||||
0xe9, 0xcc, 0x1b, 0xf6, 0x5c, 0xff, 0xd0, 0x61, 0x4e, 0x03, 0xdf, 0x92, 0x23, 0x80, 0x54, 0xa0,
|
||||
0x82, 0xac, 0x65, 0x1e, 0xec, 0x91, 0xae, 0xb5, 0x67, 0xdd, 0x89, 0x23, 0xb3, 0x38, 0x10, 0x92,
|
||||
0x76, 0xd7, 0x61, 0x8e, 0x9d, 0x71, 0xad, 0xfe, 0x2d, 0x07, 0x44, 0x85, 0xe1, 0x53, 0x01, 0x36,
|
||||
0x90, 0xf1, 0x63, 0x5d, 0x83, 0x99, 0x93, 0x43, 0x55, 0xfb, 0xf9, 0x38, 0x32, 0x67, 0xdc, 0xae,
|
||||
0x3d, 0x73, 0x72, 0x48, 0x7e, 0x02, 0x73, 0xc2, 0x4c, 0x54, 0xbc, 0x94, 0x0d, 0x99, 0x05, 0xb1,
|
||||
0x0a, 0x71, 0x64, 0xce, 0xf1, 0x01, 0x04, 0x6d, 0x69, 0x4f, 0xbe, 0x82, 0xc2, 0x21, 0x7a, 0xd8,
|
||||
0x73, 0x58, 0x10, 0x2a, 0x72, 0x11, 0x2f, 0x57, 0x37, 0x11, 0x66, 0xce, 0x2a, 0xb5, 0xac, 0x3e,
|
||||
0x84, 0x82, 0x02, 0x3e, 0x39, 0x9c, 0x96, 0x54, 0xf5, 0x97, 0xf0, 0xb9, 0x1d, 0x88, 0x60, 0x48,
|
||||
0x91, 0x9d, 0x39, 0x94, 0xbe, 0x0b, 0xc2, 0xae, 0x18, 0x3c, 0xd4, 0x51, 0x25, 0xa7, 0xfc, 0x10,
|
||||
0x6e, 0x0b, 0xf1, 0x08, 0x46, 0x24, 0x2a, 0xc6, 0x17, 0x3b, 0xd1, 0x54, 0x0f, 0x60, 0xe3, 0x08,
|
||||
0xd9, 0x4d, 0xac, 0xef, 0x04, 0xf2, 0xa7, 0x1c, 0x98, 0x07, 0x21, 0x4e, 0x4c, 0xea, 0xd3, 0x5a,
|
||||
0x7c, 0x43, 0xcd, 0xba, 0x33, 0xa9, 0x96, 0x4f, 0xb6, 0x6a, 0x9e, 0xfd, 0x0c, 0xf2, 0xcd, 0xe6,
|
||||
0xa9, 0xa8, 0x64, 0x5e, 0xf4, 0x50, 0x9e, 0x31, 0xef, 0xbf, 0x91, 0xb9, 0x70, 0x38, 0x94, 0xb3,
|
||||
0xb0, 0xcd, 0xf5, 0xd5, 0x25, 0x28, 0x9e, 0xb9, 0x7e, 0x4f, 0x45, 0xac, 0xfe, 0x01, 0x16, 0xe5,
|
||||
0x92, 0x0e, 0x02, 0x9f, 0x22, 0xef, 0xfa, 0xec, 0x58, 0x26, 0x13, 0x11, 0x5d, 0x9f, 0x9d, 0xbe,
|
||||
0xf4, 0x89, 0xeb, 0x09, 0x2c, 0xa9, 0x91, 0x01, 0x43, 0x3e, 0xe0, 0xa9, 0x04, 0xc5, 0x2c, 0x23,
|
||||
0xa7, 0x87, 0xf6, 0x95, 0xd4, 0xd8, 0xba, 0x61, 0xf5, 0xc7, 0xb0, 0xcc, 0x4f, 0x96, 0xe1, 0x27,
|
||||
0xdf, 0xf3, 0x6a, 0x03, 0xa0, 0x81, 0x7d, 0x67, 0x70, 0x11, 0x70, 0x8a, 0xab, 0x67, 0x57, 0xaa,
|
||||
0xeb, 0xef, 0x66, 0x27, 0x19, 0xa5, 0x6b, 0xed, 0x49, 0x9e, 0xa6, 0x23, 0x63, 0x3b, 0xe3, 0xf8,
|
||||
0xc5, 0x17, 0x50, 0x18, 0xfd, 0x65, 0x20, 0x0b, 0x30, 0x7b, 0xf2, 0xf2, 0xa4, 0x59, 0xbe, 0x45,
|
||||
0x6e, 0x43, 0xfe, 0xec, 0x55, 0xb3, 0x9c, 0x23, 0x00, 0xf3, 0x87, 0xf5, 0xd3, 0x7a, 0xb3, 0x5e,
|
||||
0x9e, 0xa9, 0xfd, 0x65, 0x09, 0x8a, 0x7c, 0x10, 0x6f, 0xc8, 0x20, 0xe4, 0x19, 0x94, 0x1a, 0xe8,
|
||||
0x77, 0x5f, 0x20, 0x0e, 0xf6, 0x3d, 0xf7, 0x0a, 0x29, 0xc9, 0x4c, 0x65, 0x23, 0x69, 0x65, 0xed,
|
||||
0x06, 0xa1, 0xd7, 0x79, 0x6b, 0x6f, 0xe7, 0xc8, 0x0f, 0xa1, 0x28, 0x5e, 0x4b, 0xf1, 0x87, 0x86,
|
||||
0x92, 0xc5, 0xec, 0x0b, 0x5a, 0x49, 0x56, 0x42, 0xf9, 0x65, 0x8e, 0x7c, 0x05, 0xf0, 0x6a, 0x40,
|
||||
0x31, 0x64, 0x2f, 0x83, 0x2e, 0x92, 0x09, 0x43, 0x5b, 0x65, 0x52, 0x74, 0xf2, 0x14, 0x96, 0x8f,
|
||||
0xd0, 0xe7, 0x3b, 0xc4, 0xd1, 0x9b, 0x46, 0xd6, 0x15, 0xf6, 0xf8, 0x2b, 0x37, 0x0a, 0x2a, 0xcd,
|
||||
0x6a, 0x70, 0x5b, 0x71, 0x23, 0xb9, 0xab, 0x14, 0x3a, 0x33, 0x57, 0x6e, 0xcc, 0xc0, 0xe4, 0x31,
|
||||
0x2c, 0x24, 0x7c, 0x4a, 0xd6, 0x74, 0x27, 0x3a, 0xd5, 0xeb, 0xcb, 0x1c, 0x39, 0xe1, 0x79, 0xb2,
|
||||
0x31, 0x0e, 0x7d, 0x30, 0x85, 0x2b, 0xd5, 0xd8, 0x90, 0x24, 0x35, 0xe6, 0x75, 0x0c, 0x2b, 0xf2,
|
||||
0xba, 0x69, 0x72, 0x32, 0x9d, 0x78, 0xa7, 0x1d, 0x11, 0x79, 0x06, 0x2b, 0xb2, 0x4b, 0x75, 0xa4,
|
||||
0x64, 0x54, 0x1b, 0xd1, 0xd1, 0x54, 0x80, 0xaf, 0xe1, 0x6e, 0x63, 0x6c, 0x57, 0x92, 0x03, 0xef,
|
||||
0xe9, 0x10, 0x19, 0xbe, 0x9d, 0x8a, 0xb5, 0x0f, 0x4b, 0x47, 0xc8, 0x52, 0xbe, 0x26, 0x95, 0x49,
|
||||
0x24, 0xaf, 0x4a, 0xb3, 0xaa, 0xf0, 0xf5, 0xa7, 0xe2, 0x14, 0xca, 0xaf, 0x06, 0x5d, 0x87, 0x61,
|
||||
0x06, 0x65, 0x6b, 0x12, 0x8a, 0xb2, 0x72, 0x42, 0xa7, 0x4f, 0xa7, 0x26, 0xb4, 0x0b, 0xb3, 0x9c,
|
||||
0x41, 0x08, 0x49, 0x62, 0xa5, 0xec, 0x52, 0x59, 0xd1, 0x64, 0x8a, 0x62, 0xde, 0x81, 0xf9, 0x2d,
|
||||
0xe4, 0x4c, 0x7e, 0x94, 0xd4, 0xe5, 0x93, 0x48, 0xbc, 0xf2, 0x99, 0xf6, 0x97, 0x7d, 0xb2, 0x6d,
|
||||
0x6b, 0x8f, 0xfc, 0x0e, 0xee, 0x4e, 0xa4, 0x71, 0xf2, 0x30, 0xed, 0xd0, 0xa9, 0xdc, 0x5c, 0xd9,
|
||||
0xfc, 0x58, 0x90, 0xd6, 0x1e, 0x39, 0x07, 0x63, 0x1a, 0xbd, 0x93, 0xcf, 0x93, 0x0b, 0xf5, 0x71,
|
||||
0xfe, 0xff, 0xd6, 0x18, 0x8f, 0x01, 0x24, 0x84, 0xb8, 0x8d, 0x37, 0x2e, 0xd0, 0xd4, 0x33, 0x7a,
|
||||
0xcc, 0x59, 0xa3, 0xfb, 0xdd, 0xfd, 0x7e, 0x01, 0x90, 0xf2, 0x33, 0x31, 0xd4, 0x2e, 0x6e, 0x50,
|
||||
0xf6, 0x54, 0xff, 0x6f, 0xa0, 0xbc, 0xdf, 0x79, 0x3b, 0x74, 0x43, 0x1c, 0x91, 0x2d, 0xf9, 0x41,
|
||||
0xf6, 0x02, 0xea, 0xba, 0x04, 0xce, 0x98, 0xc0, 0xe0, 0xa7, 0xe8, 0x50, 0x24, 0x2f, 0x60, 0x7d,
|
||||
0x44, 0x6a, 0x63, 0xaa, 0xa9, 0x4e, 0x53, 0xb3, 0x3b, 0x86, 0xd5, 0x03, 0xc7, 0xef, 0xa0, 0xf7,
|
||||
0xbd, 0x91, 0x7e, 0x2e, 0x2e, 0x65, 0xe6, 0x5d, 0xba, 0x37, 0x01, 0x42, 0xdd, 0xc9, 0x65, 0x55,
|
||||
0xc5, 0x8c, 0xf5, 0x21, 0xdc, 0x91, 0x35, 0x4d, 0xab, 0xf4, 0x11, 0x80, 0x29, 0x49, 0x58, 0xab,
|
||||
0xef, 0xff, 0xbd, 0x99, 0x7b, 0xff, 0x61, 0x33, 0xf7, 0x8f, 0x0f, 0x9b, 0xb9, 0x7f, 0x7d, 0xd8,
|
||||
0xcc, 0xfd, 0xf5, 0x3f, 0x9b, 0xb7, 0xce, 0xe7, 0x85, 0xd5, 0xde, 0xff, 0x03, 0x00, 0x00, 0xff,
|
||||
0xff, 0x55, 0x42, 0x2d, 0x6e, 0xd6, 0x13, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
@ -203,6 +203,11 @@ message DeleteUserRequest {
|
|||
string Name = 1 [ (gogoproto.jsontag) = "name" ];
|
||||
}
|
||||
|
||||
// Semaphores is a sequence of Semaphore resources.
|
||||
message Semaphores {
|
||||
repeated services.SemaphoreV3 Semaphores = 1 [ (gogoproto.jsontag) = "semaphores" ];
|
||||
}
|
||||
|
||||
// AuthService is authentication/authorization service implementation
|
||||
service AuthService {
|
||||
// SendKeepAlives allows node to send a stream of keep alive requests
|
||||
|
@ -246,6 +251,16 @@ service AuthService {
|
|||
rpc CreateUser(services.UserV2) returns (google.protobuf.Empty);
|
||||
// UpdateUser updates an existing user in a backend.
|
||||
rpc UpdateUser(services.UserV2) returns (google.protobuf.Empty);
|
||||
// DeleteUser deletes an exisitng user in a backend by username.
|
||||
// DeleteUser deletes an existing user in a backend by username.
|
||||
rpc DeleteUser(DeleteUserRequest) returns (google.protobuf.Empty);
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore.
|
||||
rpc AcquireSemaphore(services.AcquireSemaphoreRequest) returns (services.SemaphoreLease);
|
||||
// KeepAliveSemaphoreLease updates semaphore lease.
|
||||
rpc KeepAliveSemaphoreLease(services.SemaphoreLease) returns (google.protobuf.Empty);
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
rpc CancelSemaphoreLease(services.SemaphoreLease) returns (google.protobuf.Empty);
|
||||
// GetSemaphores returns a list of all semaphores matching the supplied filter.
|
||||
rpc GetSemaphores(services.SemaphoreFilter) returns (Semaphores);
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter.
|
||||
rpc DeleteSemaphore(services.SemaphoreFilter) returns (google.protobuf.Empty);
|
||||
}
|
||||
|
|
|
@ -440,6 +440,7 @@ func applyAuthConfig(fc *FileConfig, cfg *service.Config) error {
|
|||
KeepAliveInterval: fc.Auth.KeepAliveInterval,
|
||||
KeepAliveCountMax: fc.Auth.KeepAliveCountMax,
|
||||
LocalAuth: localAuth,
|
||||
SessionControlTimeout: fc.Auth.SessionControlTimeout,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
|
|
@ -151,6 +151,7 @@ var (
|
|||
"pam": true,
|
||||
"service_name": false,
|
||||
"client_idle_timeout": false,
|
||||
"session_control_timeout": false,
|
||||
"disconnect_expired_cert": false,
|
||||
"ciphersuites": false,
|
||||
"ca_pin": false,
|
||||
|
@ -572,6 +573,10 @@ type Auth struct {
|
|||
// if true, connections with expired client certificates will get disconnected
|
||||
DisconnectExpiredCert services.Bool `yaml:"disconnect_expired_cert,omitempty"`
|
||||
|
||||
// SessionControlTimeout specifies the maximum amount of time a node can be out
|
||||
// of contact with the auth server before it starts terminating controlled sessions.
|
||||
SessionControlTimeout services.Duration `yaml:"session_control_timeout,omitempty"`
|
||||
|
||||
// KubeconfigFile is an optional path to kubeconfig file,
|
||||
// if specified, teleport will use API server address and
|
||||
// trusted certificate authority information from it
|
||||
|
|
|
@ -87,16 +87,6 @@ const (
|
|||
// connection attempts
|
||||
DefaultDialTimeout = 30 * time.Second
|
||||
|
||||
// SemaphoreTimeout is a timeout for acquiring semaphore
|
||||
SemaphoreTimeout = DefaultDialTimeout / 2
|
||||
|
||||
// SemaphoreRetryPeriod is a period to retry semaphore
|
||||
SemaphoreRetryPeriod = 500 * time.Millisecond
|
||||
|
||||
// SemaphoreRetries is a default amount of semaphore retries
|
||||
// on contention errors
|
||||
SemaphoreRetries = 3
|
||||
|
||||
// HTTPMaxIdleConns is the max idle connections across all hosts.
|
||||
HTTPMaxIdleConns = 2000
|
||||
|
||||
|
@ -360,6 +350,11 @@ var (
|
|||
// CASignatureAlgorithm is the default signing algorithm to use when
|
||||
// creating new SSH CAs.
|
||||
CASignatureAlgorithm = ssh.SigAlgoRSASHA2512
|
||||
|
||||
// SessionControlTimeout is the maximum amount of time a controlled session
|
||||
// may persist after contact with the auth server is lost (sessctl semaphore
|
||||
// leases are refreshed at a rate of ~1/2 this duration).
|
||||
SessionControlTimeout = time.Minute * 2
|
||||
)
|
||||
|
||||
// Default connection limits, they can be applied separately on any of the Teleport
|
||||
|
|
|
@ -326,6 +326,21 @@ const (
|
|||
SAMLConnectorCreatedEvent = "saml.created"
|
||||
// SAMLConnectorDeletedEvent fires when SAML connector is deleted.
|
||||
SAMLConnectorDeletedEvent = "saml.deleted"
|
||||
|
||||
// SessionRejected fires when a user's attempt to create an authenticated
|
||||
// session has been rejected due to exceeding a session control limit.
|
||||
SessionRejectedEvent = "session.rejected"
|
||||
|
||||
// SessionRejectedReasonMaxConnections indicates that a session.rejected event
|
||||
// corresponds to enforcement of the max_connections control.
|
||||
SessionRejectedReasonMaxConnections = "max_connections limit reached"
|
||||
// SessionRejectedReasonMaxSessions indicates that a session.rejected event
|
||||
// corresponds to enforcement of the max_sessions control.
|
||||
SessionRejectedReasonMaxSessions = "max_sessions limit reached"
|
||||
|
||||
// Maximum is an event field specifying a maximal value (e.g. the value
|
||||
// of `max_connections` for a `session.rejected` event).
|
||||
Maximum = "max"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -257,6 +257,11 @@ var (
|
|||
Name: SAMLConnectorDeletedEvent,
|
||||
Code: SAMLConnectorDeletedCode,
|
||||
}
|
||||
// SessionRejected is emitted when a user hits `max_connections`.
|
||||
SessionRejected = Event{
|
||||
Name: SessionRejectedEvent,
|
||||
Code: SessionRejectedCode,
|
||||
}
|
||||
)
|
||||
|
||||
// There is no strict algorithm for picking an event code, however existing
|
||||
|
@ -286,6 +291,10 @@ const (
|
|||
// UserPasswordChangeCode is an event code for when user changes their own password.
|
||||
UserPasswordChangeCode = "T1005I"
|
||||
|
||||
// SessionRejectedCode is an event code for when a user's attempt to create an
|
||||
// session/connection has been rejected.
|
||||
SessionRejectedCode = "T1006W"
|
||||
|
||||
// SessionStartCode is the session start event code.
|
||||
SessionStartCode = "T2000I"
|
||||
// SessionJoinCode is the session join event code.
|
||||
|
|
|
@ -67,6 +67,12 @@ type ClusterConfig interface {
|
|||
// SetClientIdleTimeout sets client idle timeout setting
|
||||
SetClientIdleTimeout(t time.Duration)
|
||||
|
||||
// GetSessionControlTimeout gets the session control timeout.
|
||||
GetSessionControlTimeout() time.Duration
|
||||
|
||||
// SetSessionControlTimeout sets the session control timeout.
|
||||
SetSessionControlTimeout(t time.Duration)
|
||||
|
||||
// GetDisconnectExpiredCert returns disconnect expired certificate setting
|
||||
GetDisconnectExpiredCert() bool
|
||||
|
||||
|
@ -287,6 +293,16 @@ func (c *ClusterConfigV3) SetClientIdleTimeout(d time.Duration) {
|
|||
c.Spec.ClientIdleTimeout = Duration(d)
|
||||
}
|
||||
|
||||
// GetSessionControlTimeout gets the session control timeout.
|
||||
func (c *ClusterConfigV3) GetSessionControlTimeout() time.Duration {
|
||||
return c.Spec.SessionControlTimeout.Duration()
|
||||
}
|
||||
|
||||
// SetSessionControlTimeout sets the session control timeout.
|
||||
func (c *ClusterConfigV3) SetSessionControlTimeout(d time.Duration) {
|
||||
c.Spec.SessionControlTimeout = Duration(d)
|
||||
}
|
||||
|
||||
// GetDisconnectExpiredCert returns disconnect expired certificate setting
|
||||
func (c *ClusterConfigV3) GetDisconnectExpiredCert() bool {
|
||||
return c.Spec.DisconnectExpiredCert.Value()
|
||||
|
@ -399,6 +415,9 @@ const ClusterConfigSpecSchemaTemplate = `{
|
|||
"client_idle_timeout": {
|
||||
"type": "string"
|
||||
},
|
||||
"session_control_timeout": {
|
||||
"type": "string"
|
||||
},
|
||||
"disconnect_expired_cert": {
|
||||
"anyOf": [{"type": "string"}, { "type": "boolean"}]
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
Copyright 2015-2019 Gravitational, Inc.
|
||||
Copyright 2015 Gravitational, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
@ -25,15 +25,18 @@ import (
|
|||
|
||||
"github.com/gravitational/teleport/lib/backend"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// PresenceService records and reports the presence of all components
|
||||
// of the cluster - Nodes, Proxies and SSH nodes
|
||||
type PresenceService struct {
|
||||
log *logrus.Entry
|
||||
log *logrus.Entry
|
||||
jitter utils.Jitter
|
||||
backend.Backend
|
||||
}
|
||||
|
||||
|
@ -41,6 +44,7 @@ type PresenceService struct {
|
|||
func NewPresenceService(b backend.Backend) *PresenceService {
|
||||
return &PresenceService{
|
||||
log: logrus.WithFields(logrus.Fields{trace.Component: "Presence"}),
|
||||
jitter: utils.NewJitter(),
|
||||
Backend: b,
|
||||
}
|
||||
}
|
||||
|
@ -674,98 +678,127 @@ func (s *PresenceService) DeleteAllRemoteClusters() error {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
// TryAcquireSemaphore tries to acquire semaphore resources using optimistic concurrency
|
||||
// (hoping that compare and swap will succeed).
|
||||
//
|
||||
// In case of unsuccessfull concurrent operation returns CompareFailed or AlreadyExists,
|
||||
// what indicates to the client that it can retry the operation with the existing values.
|
||||
//
|
||||
// In case if the operation returns trace.LimitExceeded, it means that semaphore resources
|
||||
// acquired by other clients and it's up to client whether to keep trying or reject
|
||||
// the connection.
|
||||
//
|
||||
// In all other cases the error indicates the networking, backend or parameter validation problem
|
||||
//
|
||||
func (s *PresenceService) TryAcquireSemaphore(ctx context.Context, sem services.Semaphore, l services.SemaphoreLease) (*services.SemaphoreLease, error) {
|
||||
if err := sem.CheckAndSetDefaults(); err != nil {
|
||||
// AcquireSemaphore attempts to acquire the specified semaphore. AcquireSemaphore will automatically handle
|
||||
// retry on contention. If the semaphore has already reached MaxLeases, or there is too much contention,
|
||||
// a LimitExceeded error is returned (contention in this context means concurrent attempts to update the
|
||||
// *same* semaphore, separate semaphores can be modified concurrently without issue). Note that this function
|
||||
// is the only semaphore method that handles retries internally. This is because this method both blocks
|
||||
// user-facing operations, and contains multiple different potential contention points.
|
||||
func (s *PresenceService) AcquireSemaphore(ctx context.Context, req services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
// this combination of backoff parameters leads to worst-case total time spent
|
||||
// in backoff between 1200ms and 2400ms depending on jitter. tests are in
|
||||
// place to verify that this is sufficient to resolve a 20-lease contention
|
||||
// event, which is worse than should ever occur in practice.
|
||||
const baseBackoff = time.Millisecond * 300
|
||||
const acquireAttempts int64 = 6
|
||||
|
||||
if err := req.Check(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
l.SemaphoreName = sem.GetName()
|
||||
l.SemaphoreSubKind = sem.GetSubKind()
|
||||
if err := l.CheckAndSetDefaults(); err != nil {
|
||||
|
||||
if req.Expires.Before(s.Clock().Now().UTC()) {
|
||||
return nil, trace.BadParameter("cannot acquire expired semaphore lease")
|
||||
}
|
||||
|
||||
leaseID := uuid.New()
|
||||
|
||||
// key is not modified, so allocate it once
|
||||
key := backend.Key(semaphoresPrefix, req.SemaphoreKind, req.SemaphoreName)
|
||||
|
||||
Acquire:
|
||||
for i := int64(0); i < acquireAttempts; i++ {
|
||||
if i > 0 {
|
||||
// Not our first attempt, apply backoff. If we knew that we were only in
|
||||
// contention with one other acquire attempt we could retry immediately
|
||||
// since we got here because some other attempt *succeeded*. It is safer,
|
||||
// however, to assume that we are under high contention and attempt to
|
||||
// spread out retries via random backoff.
|
||||
select {
|
||||
case <-time.After(s.jitter(baseBackoff * time.Duration(i))):
|
||||
case <-ctx.Done():
|
||||
return nil, trace.Wrap(ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
// attempt to acquire an existing semaphore
|
||||
lease, err := s.acquireSemaphore(ctx, key, leaseID, req)
|
||||
switch {
|
||||
case err == nil:
|
||||
// acquire was successful, return the lease.
|
||||
return lease, nil
|
||||
case trace.IsNotFound(err):
|
||||
// semaphore does not exist, attempt to perform a
|
||||
// simultaneous init+acquire.
|
||||
lease, err = s.initSemaphore(ctx, key, leaseID, req)
|
||||
if err != nil {
|
||||
if trace.IsAlreadyExists(err) {
|
||||
// semaphore was concurrently created
|
||||
continue Acquire
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return lease, nil
|
||||
case trace.IsCompareFailed(err):
|
||||
// semaphore was concurrently updated
|
||||
continue Acquire
|
||||
default:
|
||||
// If we get here then we encountered an error other than NotFound or CompareFailed,
|
||||
// meaning that contention isn't the issue. No point in re-attempting.
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
}
|
||||
return nil, trace.LimitExceeded("too much contention on semaphore %s/%s", req.SemaphoreKind, req.SemaphoreName)
|
||||
}
|
||||
|
||||
// initSemaphore attempts to initialize/acquire a semaphore which does not yet exist.
|
||||
// Returns AlreadyExistsError if the semaphore is concurrently created.
|
||||
func (s *PresenceService) initSemaphore(ctx context.Context, key []byte, leaseID string, req services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
// create a new empty semaphore resource configured to specifically match
|
||||
// this acquire request.
|
||||
sem, err := req.ConfigureSemaphore()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
lease, err := sem.Acquire(leaseID, req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
value, err := services.GetSemaphoreMarshaler().Marshal(sem)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
item := backend.Item{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Expires: sem.Expiry(),
|
||||
}
|
||||
_, err = s.Create(ctx, item)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
// acquireSemaphore attempts to acquire an existing semaphore. Returns NotFoundError if no semaphore exists,
|
||||
// and CompareFailed if the semaphore was concurrently updated.
|
||||
func (s *PresenceService) acquireSemaphore(ctx context.Context, key []byte, leaseID string, req services.AcquireSemaphoreRequest) (*services.SemaphoreLease, error) {
|
||||
item, err := s.Get(ctx, key)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
sem, err := services.GetSemaphoreMarshaler().Unmarshal(item.Value)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
sem.RemoveExpiredLeases(s.Clock().Now().UTC())
|
||||
|
||||
if l.Resources > sem.GetMaxResources()-sem.AcquiredResources() {
|
||||
return nil, trace.LimitExceeded(
|
||||
"lease %v can't acquire %v resources, semaphore %v has %v available",
|
||||
l.ID, l.Resources, sem.GetName(), sem.GetMaxResources()-sem.AcquiredResources(),
|
||||
)
|
||||
}
|
||||
|
||||
if !l.Expires.IsZero() && l.Expires.Before(s.Clock().Now()) {
|
||||
return nil, trace.BadParameter("the lease %v has expired at %v", l.ID, l.Expires)
|
||||
}
|
||||
|
||||
key := backend.Key(semaphoresPrefix, sem.GetSubKind(), sem.GetName())
|
||||
item, err := s.Get(ctx, key)
|
||||
if err != nil {
|
||||
if !trace.IsNotFound(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
// Semaphore does not exist, create a new semaphore from the specs with
|
||||
// acquired lease
|
||||
sem.SetLeases([]services.SemaphoreLease{l})
|
||||
value, err := services.GetSemaphoreMarshaler().Marshal(sem)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
item := backend.Item{
|
||||
Key: key,
|
||||
Value: value,
|
||||
Expires: sem.Expiry(),
|
||||
}
|
||||
_, err = s.Create(ctx, item)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &l, nil
|
||||
}
|
||||
|
||||
existing, err := services.GetSemaphoreMarshaler().Unmarshal(item.Value)
|
||||
lease, err := sem.Acquire(leaseID, req)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
existing.RemoveExpiredLeases(s.Clock().Now().UTC())
|
||||
|
||||
if l.Resources > existing.GetMaxResources()-existing.AcquiredResources() {
|
||||
return nil, trace.LimitExceeded(
|
||||
"lease %v can't acquire %v resources, semaphore %v has %v available",
|
||||
l.ID, l.Resources, sem.GetName(), existing.GetMaxResources()-existing.AcquiredResources(),
|
||||
)
|
||||
}
|
||||
|
||||
for _, lease := range existing.GetLeases() {
|
||||
if lease.ID == l.ID {
|
||||
return nil, trace.BadParameter(
|
||||
"semaphore %v already has lease %v, use KeepAliveSemaphoreLease to renew the lease",
|
||||
sem, l.ID,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if sem.Expiry().After(existing.Expiry()) {
|
||||
existing.SetExpiry(sem.Expiry())
|
||||
}
|
||||
if l.Expires.After(existing.Expiry()) {
|
||||
existing.SetExpiry(l.Expires)
|
||||
}
|
||||
existing.AddLease(l)
|
||||
|
||||
newValue, err := services.GetSemaphoreMarshaler().Marshal(existing)
|
||||
newValue, err := services.GetSemaphoreMarshaler().Marshal(sem)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
@ -773,31 +806,78 @@ func (s *PresenceService) TryAcquireSemaphore(ctx context.Context, sem services.
|
|||
newItem := backend.Item{
|
||||
Key: key,
|
||||
Value: newValue,
|
||||
Expires: existing.Expiry(),
|
||||
Expires: sem.Expiry(),
|
||||
}
|
||||
|
||||
if _, err := s.CompareAndSwap(ctx, *item, newItem); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return lease, nil
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease, if the lease expiry is updated,
|
||||
// semaphore is renewed
|
||||
func (s *PresenceService) KeepAliveSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
if err := lease.CheckAndSetDefaults(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if lease.Expires.Before(s.Clock().Now().UTC()) {
|
||||
return trace.BadParameter("lease %v has expired at %v", lease.LeaseID, lease.Expires)
|
||||
}
|
||||
|
||||
key := backend.Key(semaphoresPrefix, lease.SemaphoreKind, lease.SemaphoreName)
|
||||
item, err := s.Get(ctx, key)
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return trace.NotFound("cannot keepalive, semaphore not found: %s/%s", lease.SemaphoreKind, lease.SemaphoreName)
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
sem, err := services.GetSemaphoreMarshaler().Unmarshal(item.Value)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
sem.RemoveExpiredLeases(s.Clock().Now().UTC())
|
||||
|
||||
if err := sem.KeepAlive(lease); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
newValue, err := services.GetSemaphoreMarshaler().Marshal(sem)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
newItem := backend.Item{
|
||||
Key: key,
|
||||
Value: newValue,
|
||||
Expires: sem.Expiry(),
|
||||
}
|
||||
|
||||
_, err = s.CompareAndSwap(ctx, *item, newItem)
|
||||
if err != nil {
|
||||
if trace.IsCompareFailed(err) {
|
||||
return nil, trace.CompareFailed("semaphore %v hase been concurrently updated, try again", existing.GetName())
|
||||
return trace.CompareFailed("semaphore %v/%v has been concurrently updated, try again", sem.GetSubKind(), sem.GetName())
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return &l, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// KeepAliveSemaphoreLease updates semaphore lease, if the lease expiry is updated,
|
||||
// semaphore is renewed
|
||||
func (s *PresenceService) KeepAliveSemaphoreLease(ctx context.Context, l services.SemaphoreLease) error {
|
||||
if err := l.CheckAndSetDefaults(); err != nil {
|
||||
// CancelSemaphoreLease cancels semaphore lease early.
|
||||
func (s *PresenceService) CancelSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
if err := lease.CheckAndSetDefaults(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if l.Expires.Before(s.Clock().Now()) {
|
||||
return trace.BadParameter("the lease %v has expired at %v", l.ID, l.Expires)
|
||||
if lease.Expires.Before(s.Clock().Now()) {
|
||||
return trace.BadParameter("the lease %v has expired at %v", lease.LeaseID, lease.Expires)
|
||||
}
|
||||
|
||||
key := backend.Key(semaphoresPrefix, l.SemaphoreSubKind, l.SemaphoreName)
|
||||
key := backend.Key(semaphoresPrefix, lease.SemaphoreKind, lease.SemaphoreName)
|
||||
item, err := s.Get(ctx, key)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
|
@ -808,23 +888,8 @@ func (s *PresenceService) KeepAliveSemaphoreLease(ctx context.Context, l service
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
sem.RemoveExpiredLeases(s.Clock().Now().UTC())
|
||||
leases := sem.GetLeases()
|
||||
leaseIndex := -1
|
||||
for i := range leases {
|
||||
if leases[i].ID == l.ID {
|
||||
leaseIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
if leaseIndex == -1 {
|
||||
return trace.NotFound("the lease %v was not found in semaphore %v", l.ID, l.SemaphoreName)
|
||||
}
|
||||
leases[leaseIndex] = l
|
||||
sem.SetLeases(leases)
|
||||
|
||||
if l.Expires.After(sem.Expiry()) {
|
||||
sem.SetExpiry(l.Expires)
|
||||
if err := sem.Cancel(lease); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
newValue, err := services.GetSemaphoreMarshaler().Marshal(sem)
|
||||
|
@ -833,46 +898,72 @@ func (s *PresenceService) KeepAliveSemaphoreLease(ctx context.Context, l service
|
|||
}
|
||||
|
||||
newItem := backend.Item{
|
||||
Key: key,
|
||||
Value: newValue,
|
||||
Key: key,
|
||||
Value: newValue,
|
||||
Expires: sem.Expiry(),
|
||||
}
|
||||
|
||||
_, err = s.CompareAndSwap(ctx, *item, newItem)
|
||||
if err != nil {
|
||||
if trace.IsCompareFailed(err) {
|
||||
return trace.CompareFailed("semaphore %v hase been concurrently updated, try again", sem.GetName())
|
||||
return trace.CompareFailed("semaphore %v/%v has been concurrently updated, try again", sem.GetSubKind(), sem.GetName())
|
||||
}
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAllSemaphores returns a list of all semaphores in the system
|
||||
func (s *PresenceService) GetAllSemaphores(ctx context.Context, opts ...services.MarshalOption) ([]services.Semaphore, error) {
|
||||
startKey := backend.Key(semaphoresPrefix)
|
||||
result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
// GetSemaphores returns all semaphores matching the supplied filter.
|
||||
func (s *PresenceService) GetSemaphores(ctx context.Context, filter services.SemaphoreFilter) ([]services.Semaphore, error) {
|
||||
var items []backend.Item
|
||||
if filter.SemaphoreKind != "" && filter.SemaphoreName != "" {
|
||||
// special case: filter corresponds to a single semaphore
|
||||
item, err := s.Get(ctx, backend.Key(semaphoresPrefix, filter.SemaphoreKind, filter.SemaphoreName))
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
items = append(items, *item)
|
||||
} else {
|
||||
var startKey []byte
|
||||
if filter.SemaphoreKind != "" {
|
||||
startKey = backend.Key(semaphoresPrefix, filter.SemaphoreKind)
|
||||
} else {
|
||||
startKey = backend.Key(semaphoresPrefix)
|
||||
}
|
||||
result, err := s.GetRange(ctx, startKey, backend.RangeEnd(startKey), backend.NoLimit)
|
||||
if err != nil {
|
||||
if trace.IsNotFound(err) {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
items = result.Items
|
||||
}
|
||||
|
||||
sems := make([]services.Semaphore, len(result.Items))
|
||||
for i, item := range result.Items {
|
||||
conn, err := services.GetSemaphoreMarshaler().Unmarshal(item.Value,
|
||||
services.AddOptions(opts,
|
||||
services.WithResourceID(item.ID),
|
||||
services.WithExpires(item.Expires))...)
|
||||
sems := make([]services.Semaphore, 0, len(items))
|
||||
|
||||
for _, item := range items {
|
||||
sem, err := services.GetSemaphoreMarshaler().Unmarshal(item.Value)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
sems[i] = conn
|
||||
if filter.Match(sem) {
|
||||
sems = append(sems, sem)
|
||||
}
|
||||
}
|
||||
|
||||
return sems, nil
|
||||
}
|
||||
|
||||
// DeleteAllSemaphores deletes all semaphores in the system
|
||||
func (s *PresenceService) DeleteAllSemaphores(ctx context.Context) error {
|
||||
return s.DeleteRange(ctx, backend.Key(semaphoresPrefix), backend.RangeEnd(backend.Key(semaphoresPrefix)))
|
||||
// DeleteSemaphore deletes a semaphore matching the supplied filter
|
||||
func (s *PresenceService) DeleteSemaphore(ctx context.Context, filter services.SemaphoreFilter) error {
|
||||
if filter.SemaphoreKind == "" || filter.SemaphoreName == "" {
|
||||
return trace.BadParameter("semaphore kind and name must be specified for deletion")
|
||||
}
|
||||
return trace.Wrap(s.Delete(ctx, backend.Key(semaphoresPrefix, filter.SemaphoreKind, filter.SemaphoreName)))
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
@ -165,14 +165,6 @@ func (s *ServicesSuite) TestProxyWatcher(c *check.C) {
|
|||
s.suite.ProxyWatcher(c)
|
||||
}
|
||||
|
||||
func (s *ServicesSuite) TestSemaphore(c *check.C) {
|
||||
s.suite.Semaphore(c)
|
||||
}
|
||||
|
||||
func (s *ServicesSuite) TestSemaphoreExpiry(c *check.C) {
|
||||
s.suite.SemaphoreExpiry(c)
|
||||
}
|
||||
|
||||
func (s *ServicesSuite) TestSemaphoreLock(c *check.C) {
|
||||
s.suite.SemaphoreLock(c)
|
||||
}
|
||||
|
@ -180,3 +172,11 @@ func (s *ServicesSuite) TestSemaphoreLock(c *check.C) {
|
|||
func (s *ServicesSuite) TestSemaphoreConcurrency(c *check.C) {
|
||||
s.suite.SemaphoreConcurrency(c)
|
||||
}
|
||||
|
||||
func (s *ServicesSuite) TestSemaphoreContention(c *check.C) {
|
||||
s.suite.SemaphoreContention(c)
|
||||
}
|
||||
|
||||
func (s *ServicesSuite) TestSemaphoreFlakiness(c *check.C) {
|
||||
s.suite.SemaphoreFlakiness(c)
|
||||
}
|
||||
|
|
|
@ -761,6 +761,8 @@ func ParseShortcut(in string) (string, error) {
|
|||
return KindClusterAuthPreference, nil
|
||||
case KindRemoteCluster, "remote_clusters", "rc", "rcs":
|
||||
return KindRemoteCluster, nil
|
||||
case KindSemaphore, "semaphores", "sem", "sems":
|
||||
return KindSemaphore, nil
|
||||
}
|
||||
return "", trace.BadParameter("unsupported resource: %q - resources should be expressed as 'type/name', for example 'connector/github'", in)
|
||||
}
|
||||
|
@ -784,6 +786,12 @@ func ParseRef(ref string) (*Ref, error) {
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &Ref{Kind: shortcut, Name: parts[1]}, nil
|
||||
case 3:
|
||||
shortcut, err := ParseShortcut(parts[0])
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &Ref{Kind: shortcut, SubKind: parts[1], Name: parts[2]}, nil
|
||||
}
|
||||
return nil, trace.BadParameter("failed to parse '%v'", ref)
|
||||
}
|
||||
|
@ -797,10 +805,12 @@ func isDelimiter(r rune) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
// Ref is a resource reference
|
||||
// Ref is a resource reference. Typically of the form kind/name,
|
||||
// but sometimes of the form kind/subkind/name.
|
||||
type Ref struct {
|
||||
Kind string
|
||||
Name string
|
||||
Kind string
|
||||
SubKind string
|
||||
Name string
|
||||
}
|
||||
|
||||
// IsEmpty checks whether the provided resource name is empty
|
||||
|
@ -819,7 +829,11 @@ func (r *Ref) Set(v string) error {
|
|||
}
|
||||
|
||||
func (r *Ref) String() string {
|
||||
return fmt.Sprintf("%s/%s", r.Kind, r.Name)
|
||||
if r.SubKind == "" {
|
||||
return fmt.Sprintf("%s/%s", r.Kind, r.Name)
|
||||
} else {
|
||||
return fmt.Sprintf("%s/%s/%s", r.Kind, r.SubKind, r.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Refs is a set of resource references
|
||||
|
|
|
@ -1635,6 +1635,32 @@ func (set RoleSet) AdjustSessionTTL(ttl time.Duration) time.Duration {
|
|||
return ttl
|
||||
}
|
||||
|
||||
// MaxConnections returns the maximum number of concurrent ssh connections
|
||||
// allowed. If MaxConnections is zero then no maximum was defined
|
||||
// and the number of concurrent connections is unconstrained.
|
||||
func (set RoleSet) MaxConnections() int64 {
|
||||
var mcs int64
|
||||
for _, role := range set {
|
||||
if m := role.GetOptions().MaxConnections; m != 0 && (m < mcs || mcs == 0) {
|
||||
mcs = m
|
||||
}
|
||||
}
|
||||
return mcs
|
||||
}
|
||||
|
||||
// MaxSessions returns the maximum number of concurrent ssh sessions
|
||||
// per connection. If MaxSessions is zero then no maximum was defined
|
||||
// and the number of sessions is unconstrained.
|
||||
func (set RoleSet) MaxSessions() int64 {
|
||||
var ms int64
|
||||
for _, role := range set {
|
||||
if m := role.GetOptions().MaxSessions; m != 0 && (m < ms || ms == 0) {
|
||||
ms = m
|
||||
}
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
// AdjustClientIdleTimeout adjusts requested idle timeout
|
||||
// to the lowest max allowed timeout, the most restrictive
|
||||
// option will be picked, negative values will be assumed as 0
|
||||
|
@ -2301,7 +2327,9 @@ const RoleSpecV3SchemaTemplate = `{
|
|||
"enhanced_recording": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"max_connections": { "type": "number" },
|
||||
"max_sessions": {"type": "number"}
|
||||
}
|
||||
},
|
||||
"allow": { "$ref": "#/definitions/role_condition" },
|
||||
|
|
|
@ -48,6 +48,58 @@ func (s *RoleSuite) SetUpSuite(c *C) {
|
|||
utils.InitLoggerForTests()
|
||||
}
|
||||
|
||||
// TestConnAndSessLimits verifies that role sets correctly calculate
|
||||
// a user's MaxConnections and MaxSessions values from multiple
|
||||
// roles with different individual values. These are tested together since
|
||||
// both values use the same resolution rules.
|
||||
func (s *RoleSuite) TestConnAndSessLimits(c *C) {
|
||||
tts := []struct {
|
||||
desc string
|
||||
vals []int64
|
||||
want int64
|
||||
}{
|
||||
{
|
||||
desc: "smallest nonzero value is selected from mixed values",
|
||||
vals: []int64{8, 6, 7, 5, 3, 0, 9},
|
||||
want: 3,
|
||||
},
|
||||
{
|
||||
desc: "smallest value selected from all nonzero values",
|
||||
vals: []int64{5, 6, 7, 8},
|
||||
want: 5,
|
||||
},
|
||||
{
|
||||
desc: "all zero values results in a zero value",
|
||||
vals: []int64{0, 0, 0, 0, 0, 0, 0},
|
||||
want: 0,
|
||||
},
|
||||
}
|
||||
for ti, tt := range tts {
|
||||
cmt := Commentf("test case %d: %s", ti, tt.desc)
|
||||
var set RoleSet
|
||||
for i, val := range tt.vals {
|
||||
role := &RoleV3{
|
||||
Kind: KindRole,
|
||||
Version: V3,
|
||||
Metadata: Metadata{
|
||||
Name: fmt.Sprintf("role-%d", i),
|
||||
Namespace: defaults.Namespace,
|
||||
},
|
||||
Spec: RoleSpecV3{
|
||||
Options: RoleOptions{
|
||||
MaxConnections: val,
|
||||
MaxSessions: val,
|
||||
},
|
||||
},
|
||||
}
|
||||
c.Assert(role.CheckAndSetDefaults(), IsNil, cmt)
|
||||
set = append(set, role)
|
||||
}
|
||||
c.Assert(set.MaxConnections(), Equals, tt.want, cmt)
|
||||
c.Assert(set.MaxSessions(), Equals, tt.want, cmt)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *RoleSuite) TestRoleExtension(c *C) {
|
||||
type Spec struct {
|
||||
RoleSpecV2
|
||||
|
|
|
@ -3,243 +3,412 @@ package services
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/pborman/uuid"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// SemaphoreKindConnection is the semaphore kind used by
|
||||
// the Concurrent Session Control feature to limit concurrent
|
||||
// connections (corresponds to the `max_connections`
|
||||
// role option).
|
||||
const SemaphoreKindConnection = "connection"
|
||||
|
||||
// Semaphores provides ability to control
|
||||
// how many shared resources of some kind are acquired at the same time,
|
||||
// used to implement concurrent sessions control in a distributed environment
|
||||
type Semaphores interface {
|
||||
// TryAcquireSemaphore acquires lease with requested resources from semaphore
|
||||
TryAcquireSemaphore(ctx context.Context, sem Semaphore, l SemaphoreLease) (*SemaphoreLease, error)
|
||||
// AcquireSemaphore acquires lease with requested resources from semaphore
|
||||
AcquireSemaphore(ctx context.Context, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
|
||||
// KeepAliveSemaphoreLease updates semaphore lease
|
||||
KeepAliveSemaphoreLease(ctx context.Context, l SemaphoreLease) error
|
||||
// GetAllSemaphores returns a list of all semaphores in the system
|
||||
GetAllSemaphores(ctx context.Context, opts ...MarshalOption) ([]Semaphore, error)
|
||||
// DeleteAllSemaphores deletes all semaphores in the system
|
||||
DeleteAllSemaphores(ctx context.Context) error
|
||||
KeepAliveSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
|
||||
// CancelSemaphoreLease cancels semaphore lease early
|
||||
CancelSemaphoreLease(ctx context.Context, lease SemaphoreLease) error
|
||||
// GetSemaphores returns a list of semaphores matching supplied filter.
|
||||
GetSemaphores(ctx context.Context, filter SemaphoreFilter) ([]Semaphore, error)
|
||||
// DeleteSemaphore deletes a semaphore matching supplied filter.
|
||||
DeleteSemaphore(ctx context.Context, filter SemaphoreFilter) error
|
||||
}
|
||||
|
||||
// Match checks if the supplied semaphore matches this filter.
|
||||
func (f *SemaphoreFilter) Match(sem Semaphore) bool {
|
||||
if f.SemaphoreKind != "" && f.SemaphoreKind != sem.GetSubKind() {
|
||||
return false
|
||||
}
|
||||
if f.SemaphoreName != "" && f.SemaphoreName != sem.GetName() {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// SemaphoreLockConfig holds semaphore lock configuration
|
||||
type SemaphoreLockConfig struct {
|
||||
// Service is a semaphores service
|
||||
// Service is the service against which all semaphore
|
||||
// operations are performed.
|
||||
Service Semaphores
|
||||
// Lease is a lease name to lock, will
|
||||
// be auto generated to UUID if empty
|
||||
Lease SemaphoreLease
|
||||
// Semaphore is a semaphore name/kind to lock
|
||||
Semaphore Semaphore
|
||||
// Clock helps to override time in tests
|
||||
Clock clockwork.Clock
|
||||
// Retry is semaphore locking retry logic
|
||||
Retry utils.Retry
|
||||
// TTL is a TTL to set for lease
|
||||
TTL time.Duration
|
||||
// Retries is approximate amount of lock retries on contention
|
||||
// or other errors
|
||||
Retries int
|
||||
// Expiry is an optional lease expiry parameter.
|
||||
Expiry time.Duration
|
||||
// TickRate is the rate at which lease renewals are attempted
|
||||
// and defaults to 1/2 expiry. Used to accelerate tests.
|
||||
TickRate time.Duration
|
||||
// Params holds the semaphore lease acquisition parameters.
|
||||
Params AcquireSemaphoreRequest
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks and sets default values
|
||||
func (s *SemaphoreLockConfig) CheckAndSetDefaults() error {
|
||||
if s.Service == nil {
|
||||
return trace.BadParameter("missing parameter Service")
|
||||
// CheckAndSetDefaults checks and sets default parameters
|
||||
func (l *SemaphoreLockConfig) CheckAndSetDefaults() error {
|
||||
if l.Service == nil {
|
||||
return trace.BadParameter("missing semaphore service")
|
||||
}
|
||||
if s.Semaphore == nil {
|
||||
return trace.BadParameter("missing parameter semaphore")
|
||||
if l.Expiry == 0 {
|
||||
l.Expiry = defaults.SessionControlTimeout
|
||||
}
|
||||
if s.Clock == nil {
|
||||
s.Clock = clockwork.NewRealClock()
|
||||
if l.Expiry < time.Millisecond {
|
||||
return trace.BadParameter("sub-millisecond lease expiry is not supported: %v", l.Expiry)
|
||||
}
|
||||
if s.Lease.ID == "" {
|
||||
s.Lease.ID = uuid.New()
|
||||
if l.TickRate == 0 {
|
||||
l.TickRate = l.Expiry / 2
|
||||
}
|
||||
if s.Retries == 0 {
|
||||
s.Retries = defaults.SemaphoreRetries
|
||||
if l.TickRate >= l.Expiry {
|
||||
return trace.BadParameter("tick-rate must be less than expiry")
|
||||
}
|
||||
if s.Retry == nil {
|
||||
var err error
|
||||
s.Retry, err = utils.NewLinear(utils.LinearConfig{
|
||||
Step: defaults.SemaphoreRetryPeriod,
|
||||
Max: defaults.SemaphoreTimeout,
|
||||
Jitter: utils.NewJitter(),
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
if l.Params.Expires.IsZero() {
|
||||
l.Params.Expires = time.Now().UTC().Add(l.Expiry)
|
||||
}
|
||||
if s.TTL == 0 {
|
||||
s.TTL = s.Retry.MaxPeriod() * time.Duration(s.Retries)
|
||||
if s.TTL < time.Second {
|
||||
return trace.BadParameter(
|
||||
"TTL should be >= 1 second, because it is a minimum granular unit backend understands, got %v", s.TTL)
|
||||
}
|
||||
if err := l.Params.Check(); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SemaphoreLock is a high level object
|
||||
// that acquires semaphore lease and keeps the lease until is closed
|
||||
// SemaphoreLock provides a convenient interface for managing
|
||||
// semaphore lease keepalive operations.
|
||||
type SemaphoreLock struct {
|
||||
SemaphoreLockConfig
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
cfg SemaphoreLockConfig
|
||||
lease0 SemaphoreLease
|
||||
retry utils.Retry
|
||||
ticker *time.Ticker
|
||||
doneC chan struct{}
|
||||
closeOnce sync.Once
|
||||
renewalC chan struct{}
|
||||
cond *sync.Cond
|
||||
err error
|
||||
fin bool
|
||||
}
|
||||
|
||||
// KeepAliveCycle runs one keep alive cycle
|
||||
func (s *SemaphoreLock) KeepAliveCycle() error {
|
||||
s.Lease.Expires = s.Clock.Now().UTC().Add(s.TTL)
|
||||
return s.Service.KeepAliveSemaphoreLease(s.ctx, s.Lease)
|
||||
// finish registers the final result of the background
|
||||
// goroutine. must be called even if err is nil in
|
||||
// order to wake any goroutines waiting on the error
|
||||
// and mark the lock as finished.
|
||||
func (l *SemaphoreLock) finish(err error) {
|
||||
l.cond.L.Lock()
|
||||
defer l.cond.L.Unlock()
|
||||
l.err = err
|
||||
l.fin = true
|
||||
l.cond.Broadcast()
|
||||
}
|
||||
|
||||
// KeepAlive keeps the acquired lease alive, by running
|
||||
// KeepAliveAttempt in a cycle
|
||||
func (s *SemaphoreLock) KeepAlive() {
|
||||
defer s.cancel()
|
||||
// Done signals that lease keepalive operations
|
||||
// have stopped.
|
||||
func (l *SemaphoreLock) Done() <-chan struct{} {
|
||||
return l.doneC
|
||||
}
|
||||
|
||||
// Wait blocks until the final result is available. Note that
|
||||
// this method may block longer than desired since cancellation of
|
||||
// the parent context triggers the *start* of the release operation.
|
||||
func (l *SemaphoreLock) Wait() error {
|
||||
l.cond.L.Lock()
|
||||
defer l.cond.L.Unlock()
|
||||
for !l.fin {
|
||||
l.cond.Wait()
|
||||
}
|
||||
return l.err
|
||||
}
|
||||
|
||||
// Stop stops associated lease keepalive.
|
||||
func (l *SemaphoreLock) Stop() {
|
||||
l.closeOnce.Do(func() {
|
||||
l.ticker.Stop()
|
||||
close(l.doneC)
|
||||
})
|
||||
}
|
||||
|
||||
// Renewed notifies on next successful lease keepalive.
|
||||
// Used in tests to block until next renewal.
|
||||
func (l *SemaphoreLock) Renewed() <-chan struct{} {
|
||||
return l.renewalC
|
||||
}
|
||||
|
||||
func (lock *SemaphoreLock) KeepAlive(ctx context.Context) {
|
||||
var nodrop bool
|
||||
var err error
|
||||
lease := lock.lease0
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
defer func() {
|
||||
cancel()
|
||||
lock.Stop()
|
||||
defer lock.finish(err)
|
||||
if nodrop {
|
||||
// non-standard exit conditions; don't bother handling
|
||||
// cancellation/expiry.
|
||||
return
|
||||
}
|
||||
if lease.Expires.After(time.Now().UTC()) {
|
||||
// parent context is closed. create orphan context with generous
|
||||
// timeout for lease cancellation scope. this will not block any
|
||||
// caller that is not explicitly waiting on the final error value.
|
||||
cancelContext, cancel := context.WithTimeout(context.Background(), lock.cfg.Expiry/4)
|
||||
defer cancel()
|
||||
err = lock.cfg.Service.CancelSemaphoreLease(cancelContext, lease)
|
||||
if err != nil {
|
||||
log.Warnf("Failed to cancel semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
||||
}
|
||||
} else {
|
||||
log.Errorf("Semaphore lease expired: %s/%s", lease.SemaphoreKind, lease.SemaphoreName)
|
||||
}
|
||||
}()
|
||||
Outer:
|
||||
for {
|
||||
select {
|
||||
case <-s.Clock.After(s.TTL / time.Duration(s.Retries)):
|
||||
err := s.KeepAliveCycle()
|
||||
if err == nil {
|
||||
continue
|
||||
case tick := <-lock.ticker.C:
|
||||
leaseContext, leaseCancel := context.WithDeadline(ctx, lease.Expires)
|
||||
nextLease := lease
|
||||
nextLease.Expires = tick.Add(lock.cfg.Expiry)
|
||||
for {
|
||||
err = lock.cfg.Service.KeepAliveSemaphoreLease(leaseContext, nextLease)
|
||||
if trace.IsNotFound(err) {
|
||||
leaseCancel()
|
||||
// semaphore and/or lease no longer exist; best to log the error
|
||||
// and exit immediately.
|
||||
log.Warnf("Halting keepalive on semaphore %s/%s early: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
||||
nodrop = true
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
leaseCancel()
|
||||
lease = nextLease
|
||||
lock.retry.Reset()
|
||||
select {
|
||||
case lock.renewalC <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
continue Outer
|
||||
}
|
||||
log.Debugf("Failed to renew semaphore lease %s/%s: %v", lease.SemaphoreKind, lease.SemaphoreName, err)
|
||||
lock.retry.Inc()
|
||||
select {
|
||||
case <-lock.retry.After():
|
||||
case <-leaseContext.Done():
|
||||
leaseCancel() // demanded by linter
|
||||
return
|
||||
case <-lock.Done():
|
||||
leaseCancel()
|
||||
return
|
||||
}
|
||||
}
|
||||
if trace.IsCompareFailed(err) {
|
||||
log.WithError(err).Debugf("Semaphore is being modified, retrying %v.", s.Lease)
|
||||
continue
|
||||
}
|
||||
if trace.IsConnectionProblem(err) {
|
||||
log.WithError(err).Debugf("Connection problem, retrying %v.", s.Lease)
|
||||
continue
|
||||
}
|
||||
log.WithError(err).Warningf("Failed to keep alive lease %v.", s.Lease)
|
||||
case <-ctx.Done():
|
||||
return
|
||||
case <-s.ctx.Done():
|
||||
case <-lock.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Done indicates that either semaphore has been closed
|
||||
// or some other error occured and semaphore no longer holds the lock
|
||||
func (s *SemaphoreLock) Done() <-chan struct{} {
|
||||
return s.ctx.Done()
|
||||
}
|
||||
|
||||
// Close stops lease renewal attempts
|
||||
func (s *SemaphoreLock) Close() error {
|
||||
s.cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AcquireSemaphore blocks until the context expires or it successfully acquires required amount of resources
|
||||
// if lease.ID is not specified, this method generates one.
|
||||
// KeepAlive should be started in a separate goroutine explicitly
|
||||
// Lock should be closed if to release associated KeepAlive goroutine
|
||||
func AcquireSemaphore(ctx context.Context, lock SemaphoreLockConfig) (*SemaphoreLock, error) {
|
||||
if err := lock.CheckAndSetDefaults(); err != nil {
|
||||
// AcquireSemaphoreLock attempts to acquire and hold a semaphore lease. If successfully acquired,
|
||||
// background keepalive processes are started and an associated lock handle is returned. Cancelling
|
||||
// the supplied context releases the semaphore.
|
||||
func AcquireSemaphoreLock(ctx context.Context, cfg SemaphoreLockConfig) (*SemaphoreLock, error) {
|
||||
if err := cfg.CheckAndSetDefaults(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
for {
|
||||
lock.Lease.Expires = lock.Clock.Now().UTC().Add(lock.TTL)
|
||||
out, err := lock.Service.TryAcquireSemaphore(ctx, lock.Semaphore, lock.Lease)
|
||||
if err == nil {
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
lock.Lease = *out
|
||||
l := &SemaphoreLock{
|
||||
SemaphoreLockConfig: lock,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
return l, nil
|
||||
}
|
||||
// Optimistic locking failed, retry after wait
|
||||
if !trace.IsCompareFailed(err) {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
log.WithError(err).Debugf("Failed to acquire, going to sleep for %v", lock.Retry.Duration())
|
||||
select {
|
||||
case <-lock.Clock.After(lock.Retry.Duration()):
|
||||
continue
|
||||
case <-ctx.Done():
|
||||
return nil, trace.ConnectionProblem(ctx.Err(), "context is cancelled")
|
||||
}
|
||||
// set up retry with a ratio which will result in 3-4 retries before the lease expires
|
||||
retry, err := utils.NewLinear(utils.LinearConfig{
|
||||
Max: cfg.Expiry / 4,
|
||||
Step: cfg.Expiry / 16,
|
||||
Jitter: utils.NewJitter(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
lease, err := cfg.Service.AcquireSemaphore(ctx, cfg.Params)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
lock := &SemaphoreLock{
|
||||
cfg: cfg,
|
||||
lease0: *lease,
|
||||
retry: retry,
|
||||
ticker: time.NewTicker(cfg.TickRate),
|
||||
doneC: make(chan struct{}),
|
||||
renewalC: make(chan struct{}),
|
||||
cond: sync.NewCond(&sync.Mutex{}),
|
||||
}
|
||||
return lock, nil
|
||||
}
|
||||
|
||||
// NewSemaphore is a convenience wrapper to create a Semaphore
|
||||
func NewSemaphore(name, subKind string, expires time.Time, spec SemaphoreSpecV3) (Semaphore, error) {
|
||||
cc := SemaphoreV3{
|
||||
// Check verifies that all required parameters have been supplied.
|
||||
func (s *AcquireSemaphoreRequest) Check() error {
|
||||
if s.SemaphoreKind == "" {
|
||||
return trace.BadParameter("missing parameter SemaphoreKind")
|
||||
}
|
||||
if s.SemaphoreName == "" {
|
||||
return trace.BadParameter("missing parameter SemaphoreName")
|
||||
}
|
||||
if s.MaxLeases == 0 {
|
||||
return trace.BadParameter("missing parameter MaxLeases")
|
||||
}
|
||||
if s.Expires.IsZero() {
|
||||
return trace.BadParameter("missing parameter Expires")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ConfigureSemaphore configures an empty semaphore resource matching
|
||||
// these acquire parameters.
|
||||
func (s *AcquireSemaphoreRequest) ConfigureSemaphore() (Semaphore, error) {
|
||||
sem := SemaphoreV3{
|
||||
Kind: KindSemaphore,
|
||||
SubKind: subKind,
|
||||
SubKind: s.SemaphoreKind,
|
||||
Version: V3,
|
||||
Metadata: Metadata{
|
||||
Name: name,
|
||||
Name: s.SemaphoreName,
|
||||
Namespace: defaults.Namespace,
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
cc.SetExpiry(expires)
|
||||
if err := cc.CheckAndSetDefaults(); err != nil {
|
||||
sem.SetExpiry(s.Expires)
|
||||
if err := sem.CheckAndSetDefaults(); err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &cc, nil
|
||||
return &sem, nil
|
||||
}
|
||||
|
||||
// Semaphore represents distributed semaphore concept
|
||||
type Semaphore interface {
|
||||
// Resource contains common resource values
|
||||
Resource
|
||||
// CheckAndSetDefaults checks and sets default parameter
|
||||
// CheckAndSetDefaults checks and sets default parameters
|
||||
CheckAndSetDefaults() error
|
||||
// GetMaxResources returns maximum available amount
|
||||
// of resources consumed by the semaphore
|
||||
GetMaxResources() int64
|
||||
// AddLease adds a lease to the list
|
||||
AddLease(SemaphoreLease)
|
||||
// SetLeases sets the lease list to the value
|
||||
SetLeases([]SemaphoreLease)
|
||||
// RemoveLease removes lease from the list
|
||||
RemoveLease(l SemaphoreLease) error
|
||||
// GetLeases returns all leases used by semaphore
|
||||
GetLeases() []SemaphoreLease
|
||||
// AcquiredResources computes and returns the amount of acquired resources
|
||||
AcquiredResources() int64
|
||||
// Contains checks if lease is member of this semaphore.
|
||||
Contains(lease SemaphoreLease) bool
|
||||
// Acquire attempts to acquire a lease with this semaphore.
|
||||
Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error)
|
||||
// KeepAlive attempts to update the expiry of an existent lease.
|
||||
KeepAlive(lease SemaphoreLease) error
|
||||
// Cancel attempts to cancel an existent lease.
|
||||
Cancel(lease SemaphoreLease) error
|
||||
// LeaseRefs grants access to the underlying list
|
||||
// of lease references.
|
||||
LeaseRefs() []SemaphoreLeaseRef
|
||||
// RemoveExpiredLeases removes expired leases
|
||||
RemoveExpiredLeases(time.Time)
|
||||
RemoveExpiredLeases(now time.Time)
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks and sets default values
|
||||
func (l *SemaphoreLease) CheckAndSetDefaults() error {
|
||||
if l == nil {
|
||||
return trace.BadParameter("missing lease object")
|
||||
}
|
||||
if l.Resources <= 0 {
|
||||
return trace.BadParameter("parameter Resources should be >= 0")
|
||||
if l.SemaphoreKind == "" {
|
||||
return trace.BadParameter("missing parameter SemaphoreKind")
|
||||
}
|
||||
if l.SemaphoreName == "" {
|
||||
return trace.BadParameter("missing parameter SemaphoreName")
|
||||
}
|
||||
if l.SemaphoreSubKind == "" {
|
||||
return trace.BadParameter("missing parameter SemaphoreSubKind")
|
||||
}
|
||||
if l.ID == "" {
|
||||
return trace.BadParameter("missing parameter ID")
|
||||
if l.LeaseID == "" {
|
||||
return trace.BadParameter("missing parameter LeaseID")
|
||||
}
|
||||
if l.Expires.IsZero() {
|
||||
return trace.BadParameter("set lease expiry time")
|
||||
return trace.BadParameter("missing lease expiry time")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Contains checks if lease is member of this semaphore.
|
||||
func (c *SemaphoreV3) Contains(lease SemaphoreLease) bool {
|
||||
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
||||
return false
|
||||
}
|
||||
for _, ref := range c.Spec.Leases {
|
||||
if ref.LeaseID == lease.LeaseID {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Acquire attempts to acquire a lease with this semaphore.
|
||||
func (c *SemaphoreV3) Acquire(leaseID string, params AcquireSemaphoreRequest) (*SemaphoreLease, error) {
|
||||
if params.SemaphoreKind != c.GetSubKind() || params.SemaphoreName != c.GetName() {
|
||||
return nil, trace.BadParameter("cannot acquire, params do not match")
|
||||
}
|
||||
|
||||
if c.leaseCount() >= params.MaxLeases {
|
||||
return nil, trace.LimitExceeded("cannot acquire semaphore %s/%s (%s)",
|
||||
c.GetSubKind(),
|
||||
c.GetName(),
|
||||
teleport.MaxLeases,
|
||||
)
|
||||
}
|
||||
|
||||
for _, ref := range c.Spec.Leases {
|
||||
if ref.LeaseID == leaseID {
|
||||
return nil, trace.AlreadyExists("semaphore lease already exists: %q", leaseID)
|
||||
}
|
||||
}
|
||||
|
||||
if params.Expires.After(c.Expiry()) {
|
||||
c.SetExpiry(params.Expires)
|
||||
}
|
||||
|
||||
c.Spec.Leases = append(c.Spec.Leases, SemaphoreLeaseRef{
|
||||
LeaseID: leaseID,
|
||||
Expires: params.Expires,
|
||||
Holder: params.Holder,
|
||||
})
|
||||
|
||||
return &SemaphoreLease{
|
||||
SemaphoreKind: params.SemaphoreKind,
|
||||
SemaphoreName: params.SemaphoreName,
|
||||
LeaseID: leaseID,
|
||||
Expires: params.Expires,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// KeepAlive attempts to update the expiry of an existent lease.
|
||||
func (c *SemaphoreV3) KeepAlive(lease SemaphoreLease) error {
|
||||
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
||||
return trace.BadParameter("cannot keepalive, lease does not match")
|
||||
}
|
||||
for i := range c.Spec.Leases {
|
||||
if c.Spec.Leases[i].LeaseID == lease.LeaseID {
|
||||
c.Spec.Leases[i].Expires = lease.Expires
|
||||
if lease.Expires.After(c.Expiry()) {
|
||||
c.SetExpiry(lease.Expires)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return trace.NotFound("cannot keepalive, lease not found: %q", lease.LeaseID)
|
||||
}
|
||||
|
||||
// Cancel attempts to cancel an existent lease.
|
||||
func (c *SemaphoreV3) Cancel(lease SemaphoreLease) error {
|
||||
if lease.SemaphoreKind != c.GetSubKind() || lease.SemaphoreName != c.GetName() {
|
||||
return trace.BadParameter("cannot cancel, lease does not match")
|
||||
}
|
||||
for i, ref := range c.Spec.Leases {
|
||||
if ref.LeaseID == lease.LeaseID {
|
||||
c.Spec.Leases = append(c.Spec.Leases[:i], c.Spec.Leases[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return trace.NotFound("cannot cancel, lease not found: %q", lease.LeaseID)
|
||||
}
|
||||
|
||||
// RemoveExpiredLeases removes expired leases
|
||||
func (c *SemaphoreV3) RemoveExpiredLeases(now time.Time) {
|
||||
// See https://github.com/golang/go/wiki/SliceTricks#filtering-without-allocating
|
||||
|
@ -252,45 +421,17 @@ func (c *SemaphoreV3) RemoveExpiredLeases(now time.Time) {
|
|||
c.Spec.Leases = filtered
|
||||
}
|
||||
|
||||
// AcquiredResources computes and returns the amount of acquired resources
|
||||
func (c *SemaphoreV3) AcquiredResources() int64 {
|
||||
total := int64(0)
|
||||
for _, lease := range c.Spec.Leases {
|
||||
total += lease.Resources
|
||||
}
|
||||
return total
|
||||
// leaseCount returns the number of active leases
|
||||
func (c *SemaphoreV3) leaseCount() int64 {
|
||||
return int64(len(c.Spec.Leases))
|
||||
}
|
||||
|
||||
// AddLease adds a lease to the list
|
||||
func (c *SemaphoreV3) AddLease(l SemaphoreLease) {
|
||||
c.Spec.Leases = append(c.Spec.Leases, l)
|
||||
}
|
||||
|
||||
// SetLeases sets leases list
|
||||
func (c *SemaphoreV3) SetLeases(l []SemaphoreLease) {
|
||||
c.Spec.Leases = l
|
||||
}
|
||||
|
||||
// RemoveLease removes lease from the list
|
||||
func (c *SemaphoreV3) RemoveLease(l SemaphoreLease) error {
|
||||
for i := range c.Spec.Leases {
|
||||
if c.Spec.Leases[i].ID == l.ID {
|
||||
c.Spec.Leases = append(c.Spec.Leases[:i], c.Spec.Leases[i+1:]...)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return trace.NotFound("lease %v is not found", l.ID)
|
||||
}
|
||||
|
||||
// GetLeases returns all leases used by semaphore
|
||||
func (c *SemaphoreV3) GetLeases() []SemaphoreLease {
|
||||
// LeaseRefs grants access to the underlying list
|
||||
// of lease references
|
||||
func (c *SemaphoreV3) LeaseRefs() []SemaphoreLeaseRef {
|
||||
return c.Spec.Leases
|
||||
}
|
||||
|
||||
func (c *SemaphoreV3) GetMaxResources() int64 {
|
||||
return c.Spec.MaxResources
|
||||
}
|
||||
|
||||
// GetVersion returns resource version
|
||||
func (c *SemaphoreV3) GetVersion() string {
|
||||
return c.Version
|
||||
|
@ -353,8 +494,8 @@ func (c *SemaphoreV3) GetMetadata() Metadata {
|
|||
|
||||
// String represents a human readable version of the semaphore.
|
||||
func (c *SemaphoreV3) String() string {
|
||||
return fmt.Sprintf("Semaphore(%v, subKind=%v, maxResources=%v)",
|
||||
c.Metadata.Name, c.SubKind, c.Spec.MaxResources)
|
||||
return fmt.Sprintf("Semaphore(kind=%v, name=%v, leases=%v)",
|
||||
c.SubKind, c.Metadata.Name, c.leaseCount())
|
||||
}
|
||||
|
||||
// CheckAndSetDefaults checks validity of all parameters and sets defaults.
|
||||
|
@ -379,12 +520,23 @@ func (c *SemaphoreV3) CheckAndSetDefaults() error {
|
|||
// SemaphoreSpecSchemaTemplate is a template for Semaphore schema.
|
||||
const SemaphoreSpecSchemaTemplate = `{
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"leases": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lease_id": { "type": "string" },
|
||||
"expires": { "type": "string" },
|
||||
"holder": { "type": "string" }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// GetSemaphoreSchema returns the validattion schema for this object
|
||||
// GetSemaphoreSchema returns the validation schema for this object
|
||||
func GetSemaphoreSchema() string {
|
||||
return fmt.Sprintf(V2SchemaTemplate, MetadataSchema, SemaphoreSpecSchemaTemplate, DefaultDefinitions)
|
||||
}
|
||||
|
|
82
lib/services/semaphore_test.go
Normal file
82
lib/services/semaphore_test.go
Normal file
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
Copyright 2020 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 services
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
|
||||
"gopkg.in/check.v1"
|
||||
)
|
||||
|
||||
type SemaphoreSuite struct {
|
||||
}
|
||||
|
||||
var _ = check.Suite(&SemaphoreSuite{})
|
||||
|
||||
func (s *SemaphoreSuite) SetUpSuite(c *check.C) {
|
||||
utils.InitLoggerForTests()
|
||||
}
|
||||
|
||||
func (s *SemaphoreSuite) TestAcquireSemaphoreRequest(c *check.C) {
|
||||
ok := AcquireSemaphoreRequest{
|
||||
SemaphoreKind: "foo",
|
||||
SemaphoreName: "bar",
|
||||
MaxLeases: 1,
|
||||
Expires: time.Now(),
|
||||
}
|
||||
ok2 := ok
|
||||
c.Assert(ok.Check(), check.IsNil)
|
||||
c.Assert(ok2.Check(), check.IsNil)
|
||||
|
||||
// Check that all the required fields have their
|
||||
// zero values rejected.
|
||||
bad := ok
|
||||
bad.SemaphoreKind = ""
|
||||
c.Assert(bad.Check(), check.NotNil)
|
||||
bad = ok
|
||||
bad.SemaphoreName = ""
|
||||
c.Assert(bad.Check(), check.NotNil)
|
||||
bad = ok
|
||||
bad.MaxLeases = 0
|
||||
c.Assert(bad.Check(), check.NotNil)
|
||||
bad = ok
|
||||
bad.Expires = time.Time{}
|
||||
c.Assert(bad.Check(), check.NotNil)
|
||||
|
||||
// ensure that well formed acquire params can configure
|
||||
// a well formed semaphore.
|
||||
sem, err := ok.ConfigureSemaphore()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// verify acquisition works and semaphore state is
|
||||
// correctly updated.
|
||||
lease, err := sem.Acquire("sem-id", ok)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sem.Contains(*lease), check.Equals, true)
|
||||
|
||||
// verify keepalive succeeds and correctly updates
|
||||
// semaphore expiry.
|
||||
newLease := *lease
|
||||
newLease.Expires = sem.Expiry().Add(time.Second)
|
||||
c.Assert(sem.KeepAlive(newLease), check.IsNil)
|
||||
c.Assert(sem.Expiry(), check.Equals, newLease.Expires)
|
||||
|
||||
c.Assert(sem.Cancel(newLease), check.IsNil)
|
||||
c.Assert(sem.Contains(newLease), check.Equals, false)
|
||||
}
|
|
@ -25,6 +25,8 @@ import (
|
|||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
|
@ -1011,261 +1013,205 @@ func (s *ServicesTestSuite) ClusterConfig(c *check.C, opts ...SuiteOption) {
|
|||
fixtures.DeepCompare(c, clusterName, gotName)
|
||||
}
|
||||
|
||||
// Semaphore tests semaphore basic operations
|
||||
func (s *ServicesTestSuite) Semaphore(c *check.C) {
|
||||
// non-expiring semaphores are not allowed
|
||||
_, err := services.NewSemaphore("alice", services.KindUser, time.Time{}, services.SemaphoreSpecV3{
|
||||
MaxResources: 2,
|
||||
})
|
||||
fixtures.ExpectBadParameter(c, err)
|
||||
|
||||
expires := s.Clock.Now().Add(time.Hour).UTC()
|
||||
sem, err := services.NewSemaphore("alice", services.KindUser, expires, services.SemaphoreSpecV3{
|
||||
MaxResources: 2,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
lease := services.SemaphoreLease{
|
||||
ID: "1",
|
||||
Resources: 1,
|
||||
Expires: s.Clock.Now().Add(time.Hour).UTC(),
|
||||
}
|
||||
out, err := s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, lease)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(out, check.NotNil)
|
||||
|
||||
sems, err := s.PresenceS.GetAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sems, check.HasLen, 1)
|
||||
|
||||
// lease tries to acquire too many resources
|
||||
newLease := services.SemaphoreLease{
|
||||
ID: "2",
|
||||
Resources: 2,
|
||||
Expires: s.Clock.Now().Add(time.Hour).UTC(),
|
||||
}
|
||||
out, err = s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, newLease)
|
||||
fixtures.ExpectLimitExceeded(c, err)
|
||||
c.Assert(out, check.IsNil)
|
||||
|
||||
// lease acquires enough resources
|
||||
newLease = services.SemaphoreLease{
|
||||
SemaphoreName: sem.GetName(),
|
||||
SemaphoreSubKind: sem.GetSubKind(),
|
||||
ID: "2",
|
||||
Resources: 1,
|
||||
Expires: s.Clock.Now().Add(time.Hour).UTC(),
|
||||
}
|
||||
out, err = s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, newLease)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(out, check.NotNil)
|
||||
|
||||
// Renew the lease
|
||||
newLease.Expires = s.Clock.Now().Add(time.Hour).UTC()
|
||||
err = s.PresenceS.KeepAliveSemaphoreLease(context.TODO(), newLease)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// Can't renew nonexistent lease
|
||||
badLease := newLease
|
||||
badLease.ID = "not here"
|
||||
err = s.PresenceS.KeepAliveSemaphoreLease(context.TODO(), badLease)
|
||||
fixtures.ExpectNotFound(c, err)
|
||||
|
||||
// Can't renew expired lease
|
||||
expiredLease := newLease
|
||||
expiredLease.Expires = time.Now().Add(-1 * time.Hour)
|
||||
err = s.PresenceS.KeepAliveSemaphoreLease(context.TODO(), expiredLease)
|
||||
fixtures.ExpectBadParameter(c, err)
|
||||
|
||||
err = s.PresenceS.DeleteAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
sems, err = s.PresenceS.GetAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sems, check.HasLen, 0)
|
||||
// sem wrapper is a helper for overriding the keepalive
|
||||
// method on the semaphore service.
|
||||
type semWrapper struct {
|
||||
services.Semaphores
|
||||
keepAlive func(context.Context, services.SemaphoreLease) error
|
||||
}
|
||||
|
||||
// SemaphoreExpiry tests semaphore and lease expiry
|
||||
func (s *ServicesTestSuite) SemaphoreExpiry(c *check.C) {
|
||||
expires := s.Clock.Now().Add(1 * time.Second).UTC()
|
||||
sem, err := services.NewSemaphore("alice", services.KindUser, expires, services.SemaphoreSpecV3{
|
||||
MaxResources: 2,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// lease acquires all the resources
|
||||
lease := services.SemaphoreLease{
|
||||
ID: "1",
|
||||
Resources: 2,
|
||||
Expires: expires,
|
||||
}
|
||||
out, err := s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, lease)
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(out, check.NotNil)
|
||||
|
||||
// lease renews the lease
|
||||
out.Expires = expires.Add(3 * time.Second)
|
||||
err = s.PresenceS.KeepAliveSemaphoreLease(context.TODO(), *out)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// If previous keep alive haven't reneweed the lease, semaphore would have expired
|
||||
s.Clock.Advance(2 * time.Second)
|
||||
|
||||
sems, err := s.PresenceS.GetAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sems, check.HasLen, 1)
|
||||
|
||||
// lease 2 can't acquire resources
|
||||
lease2 := services.SemaphoreLease{
|
||||
ID: "2",
|
||||
Resources: 2,
|
||||
Expires: s.Clock.Now().Add(3 * time.Second),
|
||||
}
|
||||
_, err = s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, lease2)
|
||||
fixtures.ExpectLimitExceeded(c, err)
|
||||
|
||||
// wait until lease expries semaphore should be reacquired
|
||||
lease2.Expires = s.Clock.Now().Add(10 * time.Second)
|
||||
s.Clock.Advance(5 * time.Second)
|
||||
_, err = s.PresenceS.TryAcquireSemaphore(context.TODO(), sem, lease2)
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// wait until semaphore expires
|
||||
sems, err = s.PresenceS.GetAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sems, check.HasLen, 1)
|
||||
|
||||
s.Clock.Advance(11 * time.Second)
|
||||
|
||||
sems, err = s.PresenceS.GetAllSemaphores(context.TODO())
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(sems, check.HasLen, 0)
|
||||
func (w *semWrapper) KeepAliveSemaphoreLease(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
return w.keepAlive(ctx, lease)
|
||||
}
|
||||
|
||||
// SemaphoreLock tests semaphore lock high level methods
|
||||
func (s *ServicesTestSuite) SemaphoreLock(c *check.C) {
|
||||
expires := s.Clock.Now().Add(1 * time.Second).UTC()
|
||||
sem, err := services.NewSemaphore("alice", services.KindUser, expires, services.SemaphoreSpecV3{
|
||||
MaxResources: 2,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
ctx := context.TODO()
|
||||
lock, err := services.AcquireSemaphore(ctx, services.SemaphoreLockConfig{
|
||||
Clock: s.Clock,
|
||||
Service: s.PresenceS,
|
||||
Semaphore: sem,
|
||||
Lease: services.SemaphoreLease{
|
||||
ID: "lease-1",
|
||||
Resources: 1,
|
||||
func (s *ServicesTestSuite) SemaphoreFlakiness(c *check.C) {
|
||||
const renewals = 3
|
||||
// wrap our services.Semaphores instance to cause two out of three lease
|
||||
// keepalive attempts fail with a meaningless error. Locks should make
|
||||
// at *least* three attempts before their expiry, so locks should not
|
||||
// fail under these conditions.
|
||||
keepAlives := new(uint64)
|
||||
wrapper := &semWrapper{
|
||||
Semaphores: s.PresenceS,
|
||||
keepAlive: func(ctx context.Context, lease services.SemaphoreLease) error {
|
||||
kn := atomic.AddUint64(keepAlives, 1)
|
||||
if kn%3 == 0 {
|
||||
return s.PresenceS.KeepAliveSemaphoreLease(ctx, lease)
|
||||
}
|
||||
return trace.Errorf("uh-oh!")
|
||||
},
|
||||
})
|
||||
defer lock.Close()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(lock, check.NotNil)
|
||||
}
|
||||
|
||||
// lease attempts to acquire too many resources
|
||||
_, err = services.AcquireSemaphore(ctx, services.SemaphoreLockConfig{
|
||||
Clock: s.Clock,
|
||||
Service: s.PresenceS,
|
||||
Semaphore: sem,
|
||||
Lease: services.SemaphoreLease{
|
||||
ID: "lease-greedy",
|
||||
Resources: 2,
|
||||
cfg := services.SemaphoreLockConfig{
|
||||
Service: wrapper,
|
||||
Expiry: time.Second,
|
||||
TickRate: time.Millisecond * 50,
|
||||
Params: services.AcquireSemaphoreRequest{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
SemaphoreName: "alice",
|
||||
MaxLeases: 1,
|
||||
},
|
||||
})
|
||||
fixtures.ExpectLimitExceeded(c, err)
|
||||
}
|
||||
|
||||
// acquire one resource left
|
||||
lock2, err := services.AcquireSemaphore(ctx, services.SemaphoreLockConfig{
|
||||
Clock: s.Clock,
|
||||
Service: s.PresenceS,
|
||||
Semaphore: sem,
|
||||
Lease: services.SemaphoreLease{
|
||||
ID: "lease-2",
|
||||
Resources: 1,
|
||||
},
|
||||
})
|
||||
defer lock2.Close()
|
||||
c.Assert(err, check.IsNil)
|
||||
c.Assert(lock2, check.NotNil)
|
||||
|
||||
// Advance the clock and run keep-alive cycle loop on the first lock
|
||||
s.Clock.Advance(2 * lock.TTL / 3)
|
||||
err = lock.KeepAliveCycle()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// Advance the clock past initial TTL boundary, the lock 2 should expire
|
||||
// and attempt to renew the lease on it should fail
|
||||
s.Clock.Advance(lock.TTL/3 + time.Second)
|
||||
|
||||
err = lock2.KeepAliveCycle()
|
||||
fixtures.ExpectNotFound(c, err)
|
||||
|
||||
// this one should succeed
|
||||
err = lock.KeepAliveCycle()
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// After TTL passed, the lease is not found and semaphore has expired
|
||||
s.Clock.Advance(lock.TTL * 2)
|
||||
err = lock.KeepAliveCycle()
|
||||
fixtures.ExpectNotFound(c, err)
|
||||
}
|
||||
|
||||
// SemaphoreConcurrency tests concurrent lock functionality
|
||||
func (s *ServicesTestSuite) SemaphoreConcurrency(c *check.C) {
|
||||
const maxResources = 2
|
||||
expires := s.Clock.Now().Add(1 * time.Second).UTC()
|
||||
sem, err := services.NewSemaphore("bob", services.KindUser, expires, services.SemaphoreSpecV3{
|
||||
MaxResources: maxResources,
|
||||
})
|
||||
c.Assert(err, check.IsNil)
|
||||
|
||||
// out of 20 locks, 2 should succeed, others should time out
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 2*time.Second)
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
locksC := make(chan *services.SemaphoreLock, 2)
|
||||
for i := 0; i < 20; i++ {
|
||||
go func(threadID int) {
|
||||
lock, err := services.AcquireSemaphore(ctx, services.SemaphoreLockConfig{
|
||||
Service: s.PresenceS,
|
||||
Semaphore: sem,
|
||||
Lease: services.SemaphoreLease{
|
||||
ID: fmt.Sprintf("lease-%v", threadID),
|
||||
Resources: 1,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
lock, err := services.AcquireSemaphoreLock(ctx, cfg)
|
||||
c.Assert(err, check.IsNil)
|
||||
go lock.KeepAlive(ctx)
|
||||
|
||||
for i := 0; i < renewals; i++ {
|
||||
select {
|
||||
case <-lock.Renewed():
|
||||
continue
|
||||
case <-lock.Done():
|
||||
c.Fatalf("Lost semaphore lock: %v", lock.Wait())
|
||||
case <-time.After(time.Second):
|
||||
c.Fatalf("Timeout waiting for renewals")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SemaphoreContention checks that a large number of concurrent acquisitions
|
||||
// all succeed if MaxLeases is sufficiently high. Note that we do not test
|
||||
// the same property holds for releasing semaphores; release operations are
|
||||
// best-effort and allowed to fail. Also, "large" in this context is still
|
||||
// fairly small. Semaphores aren't cheap and the auth server is expected
|
||||
// to start returning "too much contention" errors at around 100 concurrent
|
||||
// attempts.
|
||||
func (s *ServicesTestSuite) SemaphoreContention(c *check.C) {
|
||||
const locks int64 = 50
|
||||
const iters = 5
|
||||
for i := 0; i < iters; i++ {
|
||||
cfg := services.SemaphoreLockConfig{
|
||||
Service: s.PresenceS,
|
||||
Expiry: time.Hour,
|
||||
Params: services.AcquireSemaphoreRequest{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
SemaphoreName: "alice",
|
||||
MaxLeases: locks,
|
||||
},
|
||||
}
|
||||
// we leak lock handles in the spawned goroutines, so
|
||||
// context-based cancellation is needed to cleanup the
|
||||
// background keepalive activity.
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
var wg sync.WaitGroup
|
||||
for i := int64(0); i < locks; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
lock, err := services.AcquireSemaphoreLock(ctx, cfg)
|
||||
c.Assert(err, check.IsNil)
|
||||
go lock.KeepAlive(ctx)
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
cancel()
|
||||
c.Assert(s.PresenceS.DeleteSemaphore(context.TODO(), services.SemaphoreFilter{
|
||||
SemaphoreKind: cfg.Params.SemaphoreKind,
|
||||
SemaphoreName: cfg.Params.SemaphoreName,
|
||||
}), check.IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
// SemaphoreConcurrency verifies that a large number of concurrent
|
||||
// acquisitions result in the correct number of successful acquisitions.
|
||||
func (s *ServicesTestSuite) SemaphoreConcurrency(c *check.C) {
|
||||
const maxLeases int64 = 20
|
||||
const attempts int64 = 200
|
||||
cfg := services.SemaphoreLockConfig{
|
||||
Service: s.PresenceS,
|
||||
Expiry: time.Hour,
|
||||
Params: services.AcquireSemaphoreRequest{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
SemaphoreName: "alice",
|
||||
MaxLeases: maxLeases,
|
||||
},
|
||||
}
|
||||
// we leak lock handles in the spawned goroutines, so
|
||||
// context-based cancellation is needed to cleanup the
|
||||
// background keepalive activity.
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
var success int64
|
||||
var failure int64
|
||||
var wg sync.WaitGroup
|
||||
for i := int64(0); i < attempts; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
lock, err := services.AcquireSemaphoreLock(ctx, cfg)
|
||||
if err == nil {
|
||||
go lock.KeepAlive(ctx)
|
||||
atomic.AddInt64(&success, 1)
|
||||
} else {
|
||||
atomic.AddInt64(&failure, 1)
|
||||
}
|
||||
select {
|
||||
case locksC <- lock:
|
||||
case <-ctx.Done():
|
||||
lock.Close()
|
||||
return
|
||||
}
|
||||
}(i)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
c.Assert(atomic.LoadInt64(&success), check.Equals, maxLeases)
|
||||
c.Assert(atomic.LoadInt64(&failure), check.Equals, attempts-maxLeases)
|
||||
}
|
||||
|
||||
// SemaphoreLock verifies correct functionality of the basic
|
||||
// semaphore lock scenarios.
|
||||
func (s *ServicesTestSuite) SemaphoreLock(c *check.C) {
|
||||
cfg := services.SemaphoreLockConfig{
|
||||
Service: s.PresenceS,
|
||||
Expiry: time.Hour,
|
||||
Params: services.AcquireSemaphoreRequest{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
SemaphoreName: "alice",
|
||||
MaxLeases: 1,
|
||||
},
|
||||
}
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
defer cancel()
|
||||
lock, err := services.AcquireSemaphoreLock(ctx, cfg)
|
||||
c.Assert(err, check.IsNil)
|
||||
go lock.KeepAlive(ctx)
|
||||
|
||||
// MaxLeases is 1, so second acquire op fails.
|
||||
_, err = services.AcquireSemaphoreLock(ctx, cfg)
|
||||
fixtures.ExpectLimitExceeded(c, err)
|
||||
|
||||
// Lock is successfully released.
|
||||
lock.Stop()
|
||||
c.Assert(lock.Wait(), check.IsNil)
|
||||
|
||||
// Acquire new lock with short expiry
|
||||
// and high tick rate to verify renewals.
|
||||
cfg.Expiry = time.Second
|
||||
cfg.TickRate = time.Millisecond * 50
|
||||
lock, err = services.AcquireSemaphoreLock(ctx, cfg)
|
||||
c.Assert(err, check.IsNil)
|
||||
go lock.KeepAlive(ctx)
|
||||
|
||||
timeout := time.After(time.Second)
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
select {
|
||||
case <-lock.Done():
|
||||
c.Fatalf("Unexpected lock failure: %v", lock.Wait())
|
||||
case <-timeout:
|
||||
c.Fatalf("Timeout waiting for lock renewal %d", i)
|
||||
case <-lock.Renewed():
|
||||
}
|
||||
}
|
||||
|
||||
locks := []*services.SemaphoreLock{}
|
||||
defer func() {
|
||||
for _, l := range locks {
|
||||
l.Close()
|
||||
}
|
||||
}()
|
||||
for i := 0; i < maxResources; i++ {
|
||||
select {
|
||||
case lock := <-locksC:
|
||||
locks = append(locks, lock)
|
||||
case <-ctx.Done():
|
||||
c.Fatalf("Timeout waiting for acquire lock to complete")
|
||||
}
|
||||
}
|
||||
// make sure no additional locks are acquired
|
||||
// forcibly delete the semaphore
|
||||
c.Assert(s.PresenceS.DeleteSemaphore(context.TODO(), services.SemaphoreFilter{
|
||||
SemaphoreKind: cfg.Params.SemaphoreKind,
|
||||
SemaphoreName: cfg.Params.SemaphoreName,
|
||||
}), check.IsNil)
|
||||
|
||||
select {
|
||||
case lock := <-locksC:
|
||||
c.Fatalf("Unexpected lock acquisition: %+v", lock)
|
||||
case <-ctx.Done():
|
||||
case <-lock.Done():
|
||||
fixtures.ExpectNotFound(c, lock.Wait())
|
||||
case <-time.After(time.Millisecond * 1500):
|
||||
c.Errorf("timeout waiting for semaphore lock failure")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -396,6 +396,12 @@ message ClusterConfigSpecV3 {
|
|||
|
||||
// LocalAuth is true if local authentication is enabled.
|
||||
bool LocalAuth = 9 [ (gogoproto.jsontag) = "local_auth", (gogoproto.casttype) = "Bool" ];
|
||||
|
||||
// SessionControlTimeout is the session control lease expiry and defines
|
||||
// the upper limit of how long a node may be out of contact with the auth
|
||||
// server before it begins terminating controlled sessions.
|
||||
int64 SessionControlTimeout = 10
|
||||
[ (gogoproto.jsontag) = "session_control_timeout", (gogoproto.casttype) = "Duration" ];
|
||||
}
|
||||
|
||||
// AuditConfig represents audit log settings in the cluster
|
||||
|
@ -701,6 +707,14 @@ message RoleOptions {
|
|||
(gogoproto.jsontag) = "permit_x11_forwarding,omitempty",
|
||||
(gogoproto.casttype) = "Bool"
|
||||
];
|
||||
|
||||
// MaxConnections defines the maximum number of
|
||||
// concurrent connections a user may hold.
|
||||
int64 MaxConnections = 9 [ (gogoproto.jsontag) = "max_connections,omitempty" ];
|
||||
|
||||
// MaxSessions defines the maximum number of
|
||||
// concurrent sessions per connection.
|
||||
int64 MaxSessions = 10 [ (gogoproto.jsontag) = "max_sessions,omitempty" ];
|
||||
}
|
||||
|
||||
// RoleConditions is a set of conditions that must all match to be allowed or
|
||||
|
@ -976,29 +990,65 @@ message TunnelConnectionSpecV2 {
|
|||
string Type = 4 [ (gogoproto.jsontag) = "type", (gogoproto.casttype) = "TunnelType" ];
|
||||
}
|
||||
|
||||
// SemaphoreLeaseOwner holds semaphore lease owner information
|
||||
message SemaphoreLeaseOwner {
|
||||
// ServerName is a name of the server
|
||||
string ServerName = 1 [ (gogoproto.jsontag) = "server_name" ];
|
||||
// SemaphoreFilter encodes semaphore filtering params.
|
||||
// A semaphore filter matches a semaphore if all nonzero fields
|
||||
// match the corresponding semaphore fileds (e.g. a filter which
|
||||
// specifies only `kind=foo` would match all semaphores of
|
||||
// kind `foo`).
|
||||
message SemaphoreFilter {
|
||||
// SemaphoreKind is the kind of the semaphore.
|
||||
string SemaphoreKind = 1 [ (gogoproto.jsontag) = "kind" ];
|
||||
// SemaphoreName is the name of the semaphore.
|
||||
string SemaphoreName = 2 [ (gogoproto.jsontag) = "name" ];
|
||||
}
|
||||
|
||||
// AcquireSemaphoreRequest holds semaphore lease acquisition parameters.
|
||||
message AcquireSemaphoreRequest {
|
||||
// SemaphoreKind is the kind of the semaphore.
|
||||
string SemaphoreKind = 1 [ (gogoproto.jsontag) = "kind" ];
|
||||
// SemaphoreName is the name of the semaphore.
|
||||
string SemaphoreName = 2 [ (gogoproto.jsontag) = "name" ];
|
||||
// MaxLeases is the maximum number of concurrent leases. If acquisition
|
||||
// would cause more than MaxLeases to exist, acquisition must fail.
|
||||
int64 MaxLeases = 3 [ (gogoproto.jsontag) = "max_resources" ];
|
||||
// Expires is the time at which this lease expires.
|
||||
google.protobuf.Timestamp Expires = 4 [
|
||||
(gogoproto.stdtime) = true,
|
||||
(gogoproto.nullable) = false,
|
||||
(gogoproto.jsontag) = "expires"
|
||||
];
|
||||
// Holder identifies the entitiy holding the lease.
|
||||
string Holder = 5 [ (gogoproto.jsontag) = "holder" ];
|
||||
}
|
||||
|
||||
// SemaphoreLease represents lease acquired for semaphore
|
||||
message SemaphoreLease {
|
||||
// ID is an auto generated ID of the lease
|
||||
string ID = 1 [ (gogoproto.jsontag) = "id" ];
|
||||
// SemaphoreName is a name of the semaphore
|
||||
string SemaphoreName = 2 [ (gogoproto.jsontag) = "semaphore_name" ];
|
||||
// SemaphoreSubKind is a subkind of the semaphore
|
||||
string SemaphoreSubKind = 3 [ (gogoproto.jsontag) = "semaphore_subkind" ];
|
||||
// Resources is amount of resources acquired by this lease
|
||||
int64 Resources = 4 [ (gogoproto.jsontag) = "resources" ];
|
||||
// Expires is set to update expiry time
|
||||
// SemaphoreKind is the kind of the semaphore.
|
||||
string SemaphoreKind = 1 [ (gogoproto.jsontag) = "kind" ];
|
||||
// SemaphoreName is the name of the semaphore.
|
||||
string SemaphoreName = 2 [ (gogoproto.jsontag) = "name" ];
|
||||
// LeaseID uniquely identifies this lease.
|
||||
string LeaseID = 3 [ (gogoproto.jsontag) = "lease_id" ];
|
||||
// Expires is the time at which this lease expires.
|
||||
google.protobuf.Timestamp Expires = 5 [
|
||||
(gogoproto.stdtime) = true,
|
||||
(gogoproto.nullable) = false,
|
||||
(gogoproto.jsontag) = "expires"
|
||||
];
|
||||
SemaphoreLeaseOwner Owner = 6 [ (gogoproto.jsontag) = "owner" ];
|
||||
}
|
||||
|
||||
// SemaphoreLeaseRef identifies an existent lease.
|
||||
message SemaphoreLeaseRef {
|
||||
// LeaseID is the unique ID of the lease.
|
||||
string LeaseID = 1 [ (gogoproto.jsontag) = "lease_id" ];
|
||||
// Expires is the time at which the lease expires.
|
||||
google.protobuf.Timestamp Expires = 2 [
|
||||
(gogoproto.stdtime) = true,
|
||||
(gogoproto.nullable) = false,
|
||||
(gogoproto.jsontag) = "expires"
|
||||
];
|
||||
// Holder identifies the lease holder.
|
||||
string Holder = 3 [ (gogoproto.jsontag) = "holder" ];
|
||||
}
|
||||
|
||||
// SemaphoreV3 implements Semaphore interface
|
||||
|
@ -1012,7 +1062,7 @@ message SemaphoreV3 {
|
|||
string SubKind = 2 [ (gogoproto.jsontag) = "sub_kind,omitempty" ];
|
||||
// Version is version
|
||||
string Version = 3 [ (gogoproto.jsontag) = "version" ];
|
||||
// Metadata is User metadata
|
||||
// Metadata is Semaphore metadata
|
||||
Metadata Metadata = 4 [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "metadata" ];
|
||||
// Spec is a lease V3 spec
|
||||
SemaphoreSpecV3 Spec = 5 [ (gogoproto.nullable) = false, (gogoproto.jsontag) = "spec" ];
|
||||
|
@ -1020,9 +1070,7 @@ message SemaphoreV3 {
|
|||
|
||||
// SemaphoreSpecV3 contains the data about lease
|
||||
message SemaphoreSpecV3 {
|
||||
// MaxResources is a maximum number of allowed leases for this semaphore
|
||||
int64 MaxResources = 1 [ (gogoproto.jsontag) = "max_resources" ];
|
||||
// Leases is a list of leases acquired
|
||||
repeated SemaphoreLease Leases = 2
|
||||
// Leases is a list of all currently acquired leases.
|
||||
repeated SemaphoreLeaseRef Leases = 1
|
||||
[ (gogoproto.nullable) = false, (gogoproto.jsontag) = "leases" ];
|
||||
}
|
||||
|
|
|
@ -741,9 +741,10 @@ func (s *Server) handleSessionChannel(ctx context.Context, nch ssh.NewChannel) {
|
|||
scx.ChannelType = teleport.ChanSession
|
||||
defer scx.Close()
|
||||
|
||||
ch = scx.TrackActivity(ch)
|
||||
|
||||
// Create a "session" channel on the remote host.
|
||||
// Create a "session" channel on the remote host. Note that we
|
||||
// create the remote session channel before accepting the local
|
||||
// channel request; this allows us to propagate the rejection
|
||||
// reason/message in the event the channel is rejected.
|
||||
remoteSession, err := s.remoteClient.NewSession()
|
||||
if err != nil {
|
||||
s.log.Warnf("Remote session open failed: %v", err)
|
||||
|
@ -758,6 +759,19 @@ func (s *Server) handleSessionChannel(ctx context.Context, nch ssh.NewChannel) {
|
|||
}
|
||||
scx.RemoteSession = remoteSession
|
||||
|
||||
// Accept the session channel request
|
||||
ch, in, err := nch.Accept()
|
||||
if err != nil {
|
||||
s.log.Warnf("Unable to accept channel: %v", err)
|
||||
if err := nch.Reject(ssh.ConnectionFailed, fmt.Sprintf("unable to accept channel: %v", err)); err != nil {
|
||||
s.log.Warnf("Failed to reject channel: %v", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
scx.AddCloser(ch)
|
||||
|
||||
ch = scx.TrackActivity(ch)
|
||||
|
||||
s.log.Debugf("Opening session request to %v in context %v.", s.sconn.RemoteAddr(), scx.ID())
|
||||
defer s.log.Debugf("Closing session request to %v in context %v.", s.sconn.RemoteAddr(), scx.ID())
|
||||
|
||||
|
|
|
@ -561,6 +561,7 @@ func New(addr utils.NetAddr,
|
|||
sshutils.AuthMethods{PublicKey: s.authHandlers.UserKeyAuth},
|
||||
sshutils.SetLimiter(s.limiter),
|
||||
sshutils.SetRequestHandler(s),
|
||||
sshutils.SetNewConnHandler(s),
|
||||
sshutils.SetCiphers(s.ciphers),
|
||||
sshutils.SetKEXAlgorithms(s.kexAlgorithms),
|
||||
sshutils.SetMACAlgorithms(s.macAlgorithms),
|
||||
|
@ -860,6 +861,74 @@ func (s *Server) HandleRequest(r *ssh.Request) {
|
|||
}
|
||||
}
|
||||
|
||||
// HandleNewConn is called by sshutils.Server once for each new incoming connection,
|
||||
// prior to handling any channels or requests. Currently this callback's only
|
||||
// function is to apply concurrent session control limits.
|
||||
func (s *Server) HandleNewConn(ctx context.Context, ccx *sshutils.ConnectionContext) (context.Context, error) {
|
||||
// we don't currently have any work to do in non-node contexts.
|
||||
if s.Component() != teleport.ComponentNode {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
identityContext, err := s.authHandlers.CreateIdentityContext(ccx.ServerConn)
|
||||
if err != nil {
|
||||
return ctx, trace.Wrap(err)
|
||||
}
|
||||
|
||||
maxConnections := identityContext.RoleSet.MaxConnections()
|
||||
|
||||
if maxConnections == 0 {
|
||||
// concurrent session control is not active, nothing
|
||||
// else needs to be done here.
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
cfg, err := s.authService.GetClusterConfig()
|
||||
if err != nil {
|
||||
return ctx, trace.Wrap(err)
|
||||
}
|
||||
|
||||
lock, err := services.AcquireSemaphoreLock(ctx, services.SemaphoreLockConfig{
|
||||
Service: s.authService,
|
||||
Expiry: cfg.GetSessionControlTimeout(),
|
||||
Params: services.AcquireSemaphoreRequest{
|
||||
SemaphoreKind: services.SemaphoreKindConnection,
|
||||
SemaphoreName: identityContext.TeleportUser,
|
||||
MaxLeases: maxConnections,
|
||||
Holder: s.uuid,
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), teleport.MaxLeases) {
|
||||
// user has exceeded their max concurrent ssh connections.
|
||||
s.EmitAuditEvent(events.SessionRejected, events.EventFields{
|
||||
events.Reason: events.SessionRejectedReasonMaxConnections,
|
||||
events.Maximum: maxConnections,
|
||||
events.EventProtocol: events.EventProtocolSSH,
|
||||
events.EventUser: identityContext.TeleportUser,
|
||||
events.SessionServerID: s.uuid,
|
||||
})
|
||||
err = trace.AccessDenied("too many concurrent ssh connections for user %q (max=%d)",
|
||||
identityContext.TeleportUser,
|
||||
maxConnections,
|
||||
)
|
||||
}
|
||||
return ctx, trace.Wrap(err)
|
||||
}
|
||||
go lock.KeepAlive(ctx)
|
||||
// ensure that losing the lock closes the connection context. Under normal
|
||||
// conditions, cancellation propagates from the connection context to the
|
||||
// lock, but if we lose the lock due to some error (e.g. poor connectivity
|
||||
// to auth server) then cancellation propagates in the other direction.
|
||||
go func() {
|
||||
// TODO(fspmarshall): If lock was lost due to error, find a way to propagate
|
||||
// an error message to user.
|
||||
<-lock.Done()
|
||||
ccx.Close()
|
||||
}()
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
// HandleNewChan is called when new channel is opened
|
||||
func (s *Server) HandleNewChan(ctx context.Context, ccx *sshutils.ConnectionContext, nch ssh.NewChannel) {
|
||||
identityContext, err := s.authHandlers.CreateIdentityContext(ccx.ServerConn)
|
||||
|
@ -910,13 +979,38 @@ func (s *Server) HandleNewChan(ctx context.Context, ccx *sshutils.ConnectionCont
|
|||
// Channels of type "session" handle requests that are involved in running
|
||||
// commands on a server, subsystem requests, and agent forwarding.
|
||||
case teleport.ChanSession:
|
||||
var decr func()
|
||||
if max := identityContext.RoleSet.MaxSessions(); max != 0 {
|
||||
d, ok := ccx.IncrSessions(max)
|
||||
if !ok {
|
||||
// user has exceeded their max concurrent ssh sessions.
|
||||
s.EmitAuditEvent(events.SessionRejected, events.EventFields{
|
||||
events.Reason: events.SessionRejectedReasonMaxSessions,
|
||||
events.Maximum: max,
|
||||
events.EventProtocol: events.EventProtocolSSH,
|
||||
events.EventUser: identityContext.TeleportUser,
|
||||
events.SessionServerID: s.uuid,
|
||||
})
|
||||
rejectChannel(nch, ssh.Prohibited, fmt.Sprintf("too many session channels for user %q (max=%d)", identityContext.TeleportUser, max))
|
||||
return
|
||||
}
|
||||
decr = d
|
||||
}
|
||||
ch, requests, err := nch.Accept()
|
||||
if err != nil {
|
||||
log.Warnf("Unable to accept channel: %v.", err)
|
||||
rejectChannel(nch, ssh.ConnectionFailed, fmt.Sprintf("unable to accept channel: %v", err))
|
||||
if decr != nil {
|
||||
decr()
|
||||
}
|
||||
return
|
||||
}
|
||||
go s.handleSessionRequests(ctx, ccx, identityContext, ch, requests)
|
||||
go func() {
|
||||
s.handleSessionRequests(ctx, ccx, identityContext, ch, requests)
|
||||
if decr != nil {
|
||||
decr()
|
||||
}
|
||||
}()
|
||||
// Channels of type "direct-tcpip" handles request for port forwarding.
|
||||
case teleport.ChanDirectTCPIP:
|
||||
req, err := sshutils.ParseDirectTCPIPReq(nch.ExtraData())
|
||||
|
|
|
@ -304,6 +304,39 @@ func (s *SrvSuite) TestAgentForwardPermission(c *C) {
|
|||
c.Assert(strings.Contains(string(output), "SSH_AUTH_SOCK"), Equals, false)
|
||||
}
|
||||
|
||||
// TestMaxSesssions makes sure that MaxSessions RBAC rules prevent
|
||||
// too many concurrent sessions.
|
||||
func (s *SrvSuite) TestMaxSessions(c *C) {
|
||||
const maxSessions int64 = 2
|
||||
ctx := context.Background()
|
||||
// make sure the role does not allow agent forwarding
|
||||
roleName := services.RoleNameForUser(s.user)
|
||||
role, err := s.server.Auth().GetRole(roleName)
|
||||
c.Assert(err, IsNil)
|
||||
roleOptions := role.GetOptions()
|
||||
roleOptions.MaxSessions = maxSessions
|
||||
role.SetOptions(roleOptions)
|
||||
err = s.server.Auth().UpsertRole(ctx, role)
|
||||
c.Assert(err, IsNil)
|
||||
|
||||
for i := int64(0); i < maxSessions; i++ {
|
||||
se, err := s.clt.NewSession()
|
||||
c.Assert(err, IsNil)
|
||||
defer se.Close()
|
||||
}
|
||||
|
||||
_, err = s.clt.NewSession()
|
||||
c.Assert(err, NotNil)
|
||||
c.Assert(strings.Contains(err.Error(), "too many session channels"), Equals, true)
|
||||
|
||||
// verfiy that max sessions does not affect max connections.
|
||||
for i := int64(0); i <= maxSessions; i++ {
|
||||
clt, err := ssh.Dial("tcp", s.srv.Addr(), s.cltConfig)
|
||||
c.Assert(err, IsNil)
|
||||
c.Assert(clt.Close(), IsNil)
|
||||
}
|
||||
}
|
||||
|
||||
// TestOpenExecSessionSetsSession tests that OpenExecSession()
|
||||
// sets ServerContext session.
|
||||
func (s *SrvSuite) TestOpenExecSessionSetsSession(c *C) {
|
||||
|
|
|
@ -49,6 +49,10 @@ type ConnectionContext struct {
|
|||
// been requested for this connection.
|
||||
forwardAgent bool
|
||||
|
||||
// sessions is the number of currently active session channels; only tracked
|
||||
// when handling node-side connections for users with MaxSessions applied.
|
||||
sessions int64
|
||||
|
||||
// closers is a list of io.Closer that will be called when session closes
|
||||
// this is handy as sometimes client closes session, in this case resources
|
||||
// will be properly closed and deallocated, otherwise they could be kept hanging.
|
||||
|
@ -143,6 +147,30 @@ func (c *ConnectionContext) GetForwardAgent() bool {
|
|||
return c.forwardAgent
|
||||
}
|
||||
|
||||
// TryIncrSessions tries to increment the active session count; if ok the
|
||||
// returned decr function *must* be called when the associated session is closed.
|
||||
func (c *ConnectionContext) IncrSessions(max int64) (decr func(), ok bool) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if c.sessions >= max {
|
||||
return func() {}, false
|
||||
}
|
||||
c.sessions++
|
||||
var decrOnce sync.Once
|
||||
return func() {
|
||||
decrOnce.Do(c.decrSessions)
|
||||
}, true
|
||||
}
|
||||
|
||||
func (c *ConnectionContext) decrSessions() {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
c.sessions--
|
||||
if c.sessions < 0 {
|
||||
panic("underflow")
|
||||
}
|
||||
}
|
||||
|
||||
// AddCloser adds any closer in ctx that will be called
|
||||
// when the underlying connection is closed.
|
||||
func (c *ConnectionContext) AddCloser(closer io.Closer) {
|
||||
|
|
|
@ -59,6 +59,7 @@ type Server struct {
|
|||
|
||||
newChanHandler NewChanHandler
|
||||
reqHandler RequestHandler
|
||||
newConnHandler NewConnHandler
|
||||
|
||||
cfg ssh.ServerConfig
|
||||
limiter *limiter.Limiter
|
||||
|
@ -196,6 +197,13 @@ func SetRequestHandler(req RequestHandler) ServerOption {
|
|||
}
|
||||
}
|
||||
|
||||
func SetNewConnHandler(handler NewConnHandler) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.newConnHandler = handler
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func SetCiphers(ciphers []string) ServerOption {
|
||||
return func(s *Server) error {
|
||||
s.Debugf("Supported ciphers: %q.", ciphers)
|
||||
|
@ -444,6 +452,32 @@ func (s *Server) HandleConnection(conn net.Conn) {
|
|||
ctx, ccx := NewConnectionContext(context.Background(), wconn, sconn)
|
||||
defer ccx.Close()
|
||||
|
||||
if s.newConnHandler != nil {
|
||||
// if newConnHandler was set, then we have additional setup work
|
||||
// to do before we can begin serving normally. Errors returned
|
||||
// from a NewConnHandler are rejections.
|
||||
ctx, err = s.newConnHandler.HandleNewConn(ctx, ccx)
|
||||
if err != nil {
|
||||
s.Warnf("Dropping inbound ssh connection due to error: %v", err)
|
||||
// Immediately dropping the ssh connection results in an
|
||||
// EOF error for the client. We therefore wait briefly
|
||||
// to see if the client opens a channel, which will give
|
||||
// us the opportunity to respond with a human-readable
|
||||
// error.
|
||||
select {
|
||||
case firstChan := <-chans:
|
||||
if firstChan != nil {
|
||||
firstChan.Reject(ssh.Prohibited, err.Error())
|
||||
}
|
||||
case <-s.closeContext.Done():
|
||||
case <-time.After(time.Second * 1):
|
||||
}
|
||||
sconn.Close()
|
||||
conn.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
// handle out of band ssh requests
|
||||
|
@ -472,6 +506,7 @@ func (s *Server) HandleConnection(conn net.Conn) {
|
|||
}
|
||||
case <-ctx.Done():
|
||||
log.Debugf("Connection context canceled: %v -> %v", conn.RemoteAddr(), conn.LocalAddr())
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -490,6 +525,13 @@ func (f NewChanHandlerFunc) HandleNewChan(ctx context.Context, ccx *ConnectionCo
|
|||
f(ctx, ccx, ch)
|
||||
}
|
||||
|
||||
// NewConnHandler is called once per incoming connection.
|
||||
// Errors terminate the incoming connection. The returned context
|
||||
// must be the same as, or a child of, the passed in context.
|
||||
type NewConnHandler interface {
|
||||
HandleNewConn(ctx context.Context, ccx *ConnectionContext) (context.Context, error)
|
||||
}
|
||||
|
||||
type AuthMethods struct {
|
||||
PublicKey PublicKeyFunc
|
||||
Password PasswordFunc
|
||||
|
|
|
@ -60,8 +60,6 @@ type Retry interface {
|
|||
// that fires after Duration delay,
|
||||
// could fire right away if Duration is 0
|
||||
After() <-chan time.Time
|
||||
// MaxPeriod returns maximum period between retries
|
||||
MaxPeriod() time.Duration
|
||||
// Clone creates a copy of this retry in a
|
||||
// reset state.
|
||||
Clone() Retry
|
||||
|
@ -127,11 +125,6 @@ type Linear struct {
|
|||
closedChan chan time.Time
|
||||
}
|
||||
|
||||
// MaxPeriod returns maximum period between retries
|
||||
func (r *Linear) MaxPeriod() time.Duration {
|
||||
return r.Max
|
||||
}
|
||||
|
||||
// Reset resetes retry period to initial state
|
||||
func (r *Linear) Reset() {
|
||||
r.attempt = 0
|
||||
|
|
|
@ -642,3 +642,47 @@ func (c *remoteClusterCollection) toMarshal() interface{} {
|
|||
func (c *remoteClusterCollection) writeYAML(w io.Writer) error {
|
||||
return utils.WriteYAML(w, c.toMarshal())
|
||||
}
|
||||
|
||||
type semaphoreCollection struct {
|
||||
sems []services.Semaphore
|
||||
}
|
||||
|
||||
func (c *semaphoreCollection) resources() (r []services.Resource) {
|
||||
for _, resource := range c.sems {
|
||||
r = append(r, resource)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *semaphoreCollection) writeText(w io.Writer) error {
|
||||
t := asciitable.MakeTable([]string{"Kind", "Name", "LeaseID", "Holder", "Expires"})
|
||||
for _, sem := range c.sems {
|
||||
for _, ref := range sem.LeaseRefs() {
|
||||
t.AddRow([]string{
|
||||
sem.GetSubKind(), sem.GetName(), ref.LeaseID, ref.Holder, ref.Expires.Format(time.RFC822),
|
||||
})
|
||||
}
|
||||
}
|
||||
_, err := t.AsBuffer().WriteTo(w)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *semaphoreCollection) writeJSON(w io.Writer) error {
|
||||
data, err := json.MarshalIndent(c.toMarshal(), "", " ")
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
_, err = w.Write(data)
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
func (c *semaphoreCollection) toMarshal() interface{} {
|
||||
if len(c.sems) == 1 {
|
||||
return c.sems[0]
|
||||
}
|
||||
return c.sems
|
||||
}
|
||||
|
||||
func (c *semaphoreCollection) writeYAML(w io.Writer) error {
|
||||
return utils.WriteYAML(w, c.toMarshal())
|
||||
}
|
||||
|
|
|
@ -398,6 +398,21 @@ func (rc *ResourceCommand) Delete(client auth.ClientI) (err error) {
|
|||
return trace.Wrap(err)
|
||||
}
|
||||
fmt.Printf("remote cluster %q has been deleted\n", rc.ref.Name)
|
||||
case services.KindSemaphore:
|
||||
if rc.ref.SubKind == "" || rc.ref.Name == "" {
|
||||
return trace.BadParameter(
|
||||
"full semaphore path must be specified (e.g. '%s/%s/alice@example.com')",
|
||||
services.KindSemaphore, services.SemaphoreKindConnection,
|
||||
)
|
||||
}
|
||||
err := client.DeleteSemaphore(ctx, services.SemaphoreFilter{
|
||||
SemaphoreKind: rc.ref.SubKind,
|
||||
SemaphoreName: rc.ref.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
fmt.Printf("semaphore '%s/%s' has been deleted\n", rc.ref.SubKind, rc.ref.Name)
|
||||
default:
|
||||
return trace.BadParameter("deleting resources of type %q is not supported", rc.ref.Kind)
|
||||
}
|
||||
|
@ -556,6 +571,15 @@ func (rc *ResourceCommand) getCollection(client auth.ClientI) (c ResourceCollect
|
|||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &remoteClusterCollection{remoteClusters: []services.RemoteCluster{remoteCluster}}, nil
|
||||
case services.KindSemaphore:
|
||||
sems, err := client.GetSemaphores(context.TODO(), services.SemaphoreFilter{
|
||||
SemaphoreKind: rc.ref.SubKind,
|
||||
SemaphoreName: rc.ref.Name,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
return &semaphoreCollection{sems: sems}, nil
|
||||
}
|
||||
return nil, trace.BadParameter("'%v' is not supported", rc.ref.Kind)
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue