mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-31 03:27:49 +08:00
246 lines
6.7 KiB
Go
246 lines
6.7 KiB
Go
/*
|
|
* Teleport
|
|
* Copyright (C) 2024 Gravitational, Inc.
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU Affero General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Affero General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
package services_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/gravitational/teleport/api/client/proto"
|
|
"github.com/gravitational/teleport/api/types"
|
|
"github.com/gravitational/teleport/lib/backend/memory"
|
|
"github.com/gravitational/teleport/lib/services"
|
|
"github.com/gravitational/teleport/lib/services/local"
|
|
)
|
|
|
|
type accessRequestServices struct {
|
|
types.Events
|
|
services.DynamicAccessExt
|
|
}
|
|
|
|
func newAccessRequestPack(t *testing.T) (accessRequestServices, *services.AccessRequestCache) {
|
|
bk, err := memory.New(memory.Config{})
|
|
require.NoError(t, err)
|
|
|
|
svcs := accessRequestServices{
|
|
Events: local.NewEventsService(bk),
|
|
DynamicAccessExt: local.NewDynamicAccessService(bk),
|
|
}
|
|
|
|
cache, err := services.NewAccessRequestCache(services.AccessRequestCacheConfig{
|
|
Events: svcs,
|
|
Getter: svcs,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return svcs, cache
|
|
}
|
|
|
|
// TestAccessRequestCacheBasics verifies the basic expected behaviors of the access request cache,
|
|
// including correct sorting and handling of put/delete events.
|
|
func TestAccessRequestCacheBasics(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
svcs, cache := newAccessRequestPack(t)
|
|
defer cache.Close()
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
defer cancel()
|
|
|
|
// describe a set of basic test resources that we can use to
|
|
// verify various sort scenarios (request are inserted with
|
|
// creation times 1 second apart, according to the order in
|
|
// which they are defined).
|
|
rrs := []struct {
|
|
id string
|
|
state types.RequestState
|
|
}{
|
|
{
|
|
id: "00000000-0000-0000-0000-000000000005",
|
|
state: types.RequestState_PENDING,
|
|
},
|
|
{
|
|
id: "00000000-0000-0000-0000-000000000004",
|
|
state: types.RequestState_APPROVED,
|
|
},
|
|
{
|
|
id: "00000000-0000-0000-0000-000000000003",
|
|
state: types.RequestState_DENIED,
|
|
},
|
|
{
|
|
id: "00000000-0000-0000-0000-000000000002",
|
|
state: types.RequestState_APPROVED,
|
|
},
|
|
{
|
|
id: "00000000-0000-0000-0000-000000000001",
|
|
state: types.RequestState_PENDING,
|
|
},
|
|
}
|
|
|
|
tts := []struct {
|
|
Sort proto.AccessRequestSort
|
|
Descending bool
|
|
Expect []string
|
|
}{
|
|
{
|
|
Sort: proto.AccessRequestSort_DEFAULT,
|
|
Descending: false,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000001",
|
|
"00000000-0000-0000-0000-000000000002",
|
|
"00000000-0000-0000-0000-000000000003",
|
|
"00000000-0000-0000-0000-000000000004",
|
|
"00000000-0000-0000-0000-000000000005",
|
|
},
|
|
},
|
|
{
|
|
Sort: proto.AccessRequestSort_DEFAULT,
|
|
Descending: true,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000005",
|
|
"00000000-0000-0000-0000-000000000004",
|
|
"00000000-0000-0000-0000-000000000003",
|
|
"00000000-0000-0000-0000-000000000002",
|
|
"00000000-0000-0000-0000-000000000001",
|
|
},
|
|
},
|
|
{
|
|
Sort: proto.AccessRequestSort_CREATED,
|
|
Descending: false,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000005",
|
|
"00000000-0000-0000-0000-000000000004",
|
|
"00000000-0000-0000-0000-000000000003",
|
|
"00000000-0000-0000-0000-000000000002",
|
|
"00000000-0000-0000-0000-000000000001",
|
|
},
|
|
},
|
|
{
|
|
Sort: proto.AccessRequestSort_CREATED,
|
|
Descending: true,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000001",
|
|
"00000000-0000-0000-0000-000000000002",
|
|
"00000000-0000-0000-0000-000000000003",
|
|
"00000000-0000-0000-0000-000000000004",
|
|
"00000000-0000-0000-0000-000000000005",
|
|
},
|
|
},
|
|
{
|
|
Sort: proto.AccessRequestSort_STATE,
|
|
Descending: false,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000002", // approved
|
|
"00000000-0000-0000-0000-000000000004", // approved
|
|
"00000000-0000-0000-0000-000000000003", // denied
|
|
"00000000-0000-0000-0000-000000000001", // pending
|
|
"00000000-0000-0000-0000-000000000005", // pending
|
|
},
|
|
},
|
|
{
|
|
Sort: proto.AccessRequestSort_STATE,
|
|
Descending: true,
|
|
Expect: []string{
|
|
"00000000-0000-0000-0000-000000000005", // pending
|
|
"00000000-0000-0000-0000-000000000001", // pending
|
|
"00000000-0000-0000-0000-000000000003", // denied
|
|
"00000000-0000-0000-0000-000000000004", // approved
|
|
"00000000-0000-0000-0000-000000000002", // approved
|
|
},
|
|
},
|
|
}
|
|
|
|
created := time.Now()
|
|
for _, rr := range rrs {
|
|
r, err := types.NewAccessRequest(rr.id, "alice@example.com", "some-role")
|
|
require.NoError(t, err)
|
|
require.NoError(t, r.SetState(rr.state))
|
|
r.SetCreationTime(created.UTC())
|
|
created = created.Add(time.Second)
|
|
_, err = svcs.CreateAccessRequestV2(ctx, r)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
timeout := time.After(time.Second * 30)
|
|
|
|
for {
|
|
rsp, err := cache.ListAccessRequests(ctx, &proto.ListAccessRequestsRequest{
|
|
Limit: int32(len(rrs)),
|
|
})
|
|
require.NoError(t, err)
|
|
if len(rsp.AccessRequests) == len(rrs) {
|
|
break
|
|
}
|
|
|
|
select {
|
|
case <-timeout:
|
|
require.FailNow(t, "timeout waiting for access request cache to populate")
|
|
case <-time.After(time.Millisecond * 200):
|
|
}
|
|
}
|
|
|
|
for _, tt := range tts {
|
|
|
|
var nextKey string
|
|
var reqIDs []string
|
|
for {
|
|
rsp, err := cache.ListAccessRequests(ctx, &proto.ListAccessRequestsRequest{
|
|
StartKey: nextKey,
|
|
Limit: 3,
|
|
Sort: tt.Sort,
|
|
Descending: tt.Descending,
|
|
})
|
|
require.NoError(t, err)
|
|
for _, r := range rsp.AccessRequests {
|
|
reqIDs = append(reqIDs, r.GetName())
|
|
}
|
|
nextKey = rsp.NextKey
|
|
if nextKey == "" {
|
|
break
|
|
}
|
|
}
|
|
|
|
require.Equal(t, tt.Expect, reqIDs, "index=%s, descending=%v", tt.Sort.String(), tt.Descending)
|
|
}
|
|
|
|
// verify that delete events are correctly processed
|
|
timeout = time.After(time.Second * 30)
|
|
for i, rr := range rrs {
|
|
require.NoError(t, svcs.DeleteAccessRequest(ctx, rr.id))
|
|
WaitForReplication:
|
|
for {
|
|
rsp, err := cache.ListAccessRequests(ctx, &proto.ListAccessRequestsRequest{
|
|
Limit: int32(len(rrs)),
|
|
})
|
|
require.NoError(t, err)
|
|
if len(rsp.AccessRequests) == len(rrs)-(i+1) {
|
|
break WaitForReplication
|
|
}
|
|
|
|
select {
|
|
case <-timeout:
|
|
require.FailNow(t, "timeout waiting for cache to to reach expected resource count", "have=%d, want=%d", len(rsp.AccessRequests), len(rrs)-(i+1))
|
|
case <-time.After(time.Millisecond * 200):
|
|
}
|
|
}
|
|
}
|
|
}
|