Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions lib/auth/accesspoint/accesspoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type Config struct {
AccessMonitoringRules services.AccessMonitoringRules
AppSession services.AppSession
Apps services.Apps
BotInstance services.BotInstance
ClusterConfig services.ClusterConfiguration
CrownJewels services.CrownJewels
DatabaseObjects services.DatabaseObjects
Expand Down Expand Up @@ -194,6 +195,7 @@ func NewCache(cfg Config) (*cache.Cache, error) {
PluginStaticCredentials: cfg.PluginStaticCredentials,
GitServers: cfg.GitServers,
HealthCheckConfig: cfg.HealthCheckConfig,
BotInstanceService: cfg.BotInstance,
}

return cache.New(cfg.Setup(cacheCfg))
Expand Down
6 changes: 6 additions & 0 deletions lib/auth/authclient/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1226,6 +1226,12 @@ type Cache interface {
// HealthCheckConfigReader defines methods for fetching health checkc config
// resources.
services.HealthCheckConfigReader

// GetBotInstance returns the specified BotInstance resource.
GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error)

// ListBotInstances returns a page of BotInstance resources.
ListBotInstances(ctx context.Context, botName string, pageSize int, lastToken string, search string) ([]*machineidv1.BotInstance, string, error)
}

type NodeWrapper struct {
Expand Down
1 change: 1 addition & 0 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -5368,6 +5368,7 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) {

botInstanceService, err := machineidv1.NewBotInstanceService(machineidv1.BotInstanceServiceConfig{
Authorizer: cfg.Authorizer,
Cache: cfg.AuthServer.Cache,
Backend: cfg.AuthServer.Services.BotInstance,
Clock: cfg.AuthServer.GetClock(),
})
Expand Down
1 change: 1 addition & 0 deletions lib/auth/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -552,6 +552,7 @@ func InitTestAuthCache(p TestAuthCacheParams) error {
PluginStaticCredentials: p.AuthServer.Services.PluginStaticCredentials,
GitServers: p.AuthServer.Services.GitServers,
HealthCheckConfig: p.AuthServer.Services.HealthCheckConfig,
BotInstance: p.AuthServer.Services.BotInstance,
})
if err != nil {
return trace.Wrap(err)
Expand Down
18 changes: 16 additions & 2 deletions lib/auth/machineid/machineidv1/bot_instance_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,20 @@ const (
ExpiryMargin = time.Minute * 5
)

// BotInstancesCache is the subset of the cached resources that the Service queries.
type BotInstancesCache interface {
// GetBotInstance returns the specified BotInstance resource.
GetBotInstance(ctx context.Context, botName, instanceID string) (*pb.BotInstance, error)

// ListBotInstances returns a page of BotInstance resources.
ListBotInstances(ctx context.Context, botName string, pageSize int, lastToken string, search string) ([]*pb.BotInstance, string, error)
}

// BotInstanceServiceConfig holds configuration options for the BotInstance gRPC
// service.
type BotInstanceServiceConfig struct {
Authorizer authz.Authorizer
Cache BotInstancesCache
Backend services.BotInstance
Logger *slog.Logger
Clock clockwork.Clock
Expand All @@ -64,6 +74,8 @@ func NewBotInstanceService(cfg BotInstanceServiceConfig) (*BotInstanceService, e
return nil, trace.BadParameter("backend service is required")
case cfg.Authorizer == nil:
return nil, trace.BadParameter("authorizer is required")
case cfg.Cache == nil:
return nil, trace.BadParameter("cache service is required")
}

if cfg.Logger == nil {
Expand All @@ -76,6 +88,7 @@ func NewBotInstanceService(cfg BotInstanceServiceConfig) (*BotInstanceService, e
return &BotInstanceService{
logger: cfg.Logger,
authorizer: cfg.Authorizer,
cache: cfg.Cache,
backend: cfg.Backend,
clock: cfg.Clock,
}, nil
Expand All @@ -87,6 +100,7 @@ type BotInstanceService struct {

backend services.BotInstance
authorizer authz.Authorizer
cache BotInstancesCache
logger *slog.Logger
clock clockwork.Clock
}
Expand Down Expand Up @@ -124,7 +138,7 @@ func (b *BotInstanceService) GetBotInstance(ctx context.Context, req *pb.GetBotI
return nil, trace.Wrap(err)
}

res, err := b.backend.GetBotInstance(ctx, req.BotName, req.InstanceId)
res, err := b.cache.GetBotInstance(ctx, req.BotName, req.InstanceId)
if err != nil {
return nil, trace.Wrap(err)
}
Expand All @@ -143,7 +157,7 @@ func (b *BotInstanceService) ListBotInstances(ctx context.Context, req *pb.ListB
return nil, trace.Wrap(err)
}

res, nextToken, err := b.backend.ListBotInstances(ctx, req.FilterBotName, int(req.PageSize), req.PageToken, req.FilterSearchTerm)
res, nextToken, err := b.cache.ListBotInstances(ctx, req.FilterBotName, int(req.PageSize), req.PageToken, req.FilterSearchTerm)
if err != nil {
return nil, trace.Wrap(err)
}
Expand Down
3 changes: 3 additions & 0 deletions lib/auth/machineid/machineidv1/bot_instance_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,7 @@ func TestBotInstanceServiceSubmitHeartbeat(t *testing.T) {
backend := newBotInstanceBackend(t)
service, err := NewBotInstanceService(BotInstanceServiceConfig{
Backend: backend,
Cache: backend,
Authorizer: authz.AuthorizerFunc(func(ctx context.Context) (*authz.Context, error) {
return &authz.Context{
Identity: identityGetterFn(func() tlsca.Identity {
Expand Down Expand Up @@ -391,6 +392,7 @@ func TestBotInstanceServiceSubmitHeartbeat_HeartbeatLimit(t *testing.T) {
backend := newBotInstanceBackend(t)
service, err := NewBotInstanceService(BotInstanceServiceConfig{
Backend: backend,
Cache: backend,
Authorizer: authz.AuthorizerFunc(func(ctx context.Context) (*authz.Context, error) {
return &authz.Context{
Identity: identityGetterFn(func() tlsca.Identity {
Expand Down Expand Up @@ -583,6 +585,7 @@ func newBotInstanceService(
service, err := NewBotInstanceService(BotInstanceServiceConfig{
Authorizer: authorizer,
Backend: backendService,
Cache: backendService,
})
require.NoError(t, err)

Expand Down
155 changes: 155 additions & 0 deletions lib/cache/bot_instance.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Teleport
// Copyright (C) 2025 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 cache

import (
"context"
"slices"
"strings"

"github.com/gravitational/trace"
"google.golang.org/protobuf/proto"

"github.com/gravitational/teleport/api/defaults"
machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1"
"github.com/gravitational/teleport/api/types"
"github.com/gravitational/teleport/api/utils/clientutils"
"github.com/gravitational/teleport/lib/services"
)

type botInstanceIndex string

const (
botInstanceNameIndex botInstanceIndex = "name"
)

func keyForNameIndex(botInstance *machineidv1.BotInstance) string {
return makeNameIndexKey(
botInstance.GetSpec().GetBotName(),
botInstance.GetMetadata().GetName(),
)
}

func makeNameIndexKey(botName string, instanceID string) string {
return botName + "/" + instanceID
}

func newBotInstanceCollection(upstream services.BotInstance, w types.WatchKind) (*collection[*machineidv1.BotInstance, botInstanceIndex], error) {
if upstream == nil {
return nil, trace.BadParameter("missing parameter upstream (BotInstance)")
}

return &collection[*machineidv1.BotInstance, botInstanceIndex]{
store: newStore(
types.KindBotInstance,
proto.CloneOf[*machineidv1.BotInstance],
map[botInstanceIndex]func(*machineidv1.BotInstance) string{
// Index on a combination of bot name and instance name
botInstanceNameIndex: keyForNameIndex,
}),
fetcher: func(ctx context.Context, loadSecrets bool) ([]*machineidv1.BotInstance, error) {
var out []*machineidv1.BotInstance
clientutils.IterateResources(ctx,
func(ctx context.Context, limit int, start string) ([]*machineidv1.BotInstance, string, error) {
return upstream.ListBotInstances(ctx, "", limit, start, "")
},
func(hcc *machineidv1.BotInstance) error {
out = append(out, hcc)
return nil
},
)
return out, nil
Comment on lines +65 to +75
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/gravitational/teleport/blob/master/lib/cache/bot_instance.go#L86-L91

Suggested change
var out []*machineidv1.BotInstance
clientutils.IterateResources(ctx,
func(ctx context.Context, limit int, start string) ([]*machineidv1.BotInstance, string, error) {
return upstream.ListBotInstances(ctx, "", limit, start, "")
},
func(hcc *machineidv1.BotInstance) error {
out = append(out, hcc)
return nil
},
)
return out, nil
out, err := stream.Collect(clientutils.Resources(ctx,
func(ctx context.Context, limit int, start string) ([]*machineidv1.BotInstance, string, error) {
return upstream.ListBotInstances(ctx, "", limit, start, "", nil)
},
))
return out, trace.Wrap(err)

},
watch: w,
}, nil
}

// GetBotInstance returns the specified BotInstance resource.
func (c *Cache) GetBotInstance(ctx context.Context, botName, instanceID string) (*machineidv1.BotInstance, error) {
ctx, span := c.Tracer.Start(ctx, "cache/GetBotInstance")
defer span.End()

getter := genericGetter[*machineidv1.BotInstance, botInstanceIndex]{
cache: c,
collection: c.collections.botInstances,
index: botInstanceNameIndex,
upstreamGet: func(ctx context.Context, _ string) (*machineidv1.BotInstance, error) {
return c.Config.BotInstanceService.GetBotInstance(ctx, botName, instanceID)
},
}

out, err := getter.get(ctx, makeNameIndexKey(botName, instanceID))
return out, trace.Wrap(err)
}

// ListBotInstances returns a page of BotInstance resources.
func (c *Cache) ListBotInstances(ctx context.Context, botName string, pageSize int, lastToken string, search string) ([]*machineidv1.BotInstance, string, error) {
ctx, span := c.Tracer.Start(ctx, "cache/ListBotInstances")
defer span.End()

lister := genericLister[*machineidv1.BotInstance, botInstanceIndex]{
cache: c,
collection: c.collections.botInstances,
index: botInstanceNameIndex,
defaultPageSize: defaults.DefaultChunkSize,
upstreamList: func(ctx context.Context, limit int, start string) ([]*machineidv1.BotInstance, string, error) {
return c.Config.BotInstanceService.ListBotInstances(ctx, botName, limit, start, search)
},
filter: func(b *machineidv1.BotInstance) bool {
return matchBotInstance(b, botName, search)
},
nextToken: func(b *machineidv1.BotInstance) string {
return keyForNameIndex(b)
},
}
out, next, err := lister.list(ctx,
pageSize,
lastToken,
)
return out, next, trace.Wrap(err)
}

func matchBotInstance(b *machineidv1.BotInstance, botName string, search string) bool {
// If updating this, ensure it's consistent with the upstream search logic in `lib/services/local/bot_instance.go`.

if botName != "" && b.Spec.BotName != botName {
return false
}

if search == "" {
return true
}

latestHeartbeats := b.GetStatus().GetLatestHeartbeats()
heartbeat := b.Status.InitialHeartbeat // Use initial heartbeat as a fallback
if len(latestHeartbeats) > 0 {
heartbeat = latestHeartbeats[len(latestHeartbeats)-1]
}

values := []string{
b.Spec.BotName,
b.Spec.InstanceId,
}

if heartbeat != nil {
values = append(values, heartbeat.Hostname, heartbeat.JoinMethod, heartbeat.Version, "v"+heartbeat.Version)
}

return slices.ContainsFunc(values, func(val string) bool {
return strings.Contains(strings.ToLower(val), strings.ToLower(search))
})
}
Loading
Loading