From f62799f4e56cba4f19e9c346de703c9c958d3f70 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 5 Aug 2024 11:20:55 +0100 Subject: [PATCH 01/27] Start adding `lib/services` content for SPIFFEFederation type --- lib/services/local/events.go | 30 ++++++ lib/services/local/spiffe_federations.go | 112 ++++++++++++++++++++ lib/services/spiffe_federations.go | 124 +++++++++++++++++++++++ 3 files changed, 266 insertions(+) create mode 100644 lib/services/local/spiffe_federations.go create mode 100644 lib/services/spiffe_federations.go diff --git a/lib/services/local/events.go b/lib/services/local/events.go index 0cc0d9938a10c..e2fb84c230518 100644 --- a/lib/services/local/events.go +++ b/lib/services/local/events.go @@ -224,6 +224,8 @@ func (e *EventsService) NewWatcher(ctx context.Context, watch types.Watch) (type parser = newAccessGraphSecretAuthorizedKeyParser() case types.KindAccessGraphSettings: parser = newAccessGraphSettingsParser() + case types.KindSPIFFEFederation: + parser = newSPIFFEFederationParser() default: if watch.AllowPartialSuccess { continue @@ -2467,3 +2469,31 @@ func (p *accessGraphSettingsParser) parse(event backend.Event) (types.Resource, return nil, trace.BadParameter("event %v is not supported", event.Type) } } + +func newSPIFFEFederationParser() *spiffeFederationParser { + return &spiffeFederationParser{ + baseParser: newBaseParser(backend.Key(spiffeFederationPrefix)), + } +} + +type spiffeFederationParser struct { + baseParser +} + +func (p *spiffeFederationParser) parse(event backend.Event) (types.Resource, error) { + switch event.Type { + case types.OpDelete: + return resourceHeader(event, types.KindSPIFFEFederation, types.V1, 0) + case types.OpPut: + federation, err := services.UnmarshalSPIFFEFederation( + event.Item.Value, + services.WithExpires(event.Item.Expires), + services.WithRevision(event.Item.Revision)) + if err != nil { + return nil, trace.Wrap(err, "unmarshalling resource from event") + } + return types.Resource153ToLegacy(federation), nil + default: + return nil, trace.BadParameter("event %v is not supported", event.Type) + } +} diff --git a/lib/services/local/spiffe_federations.go b/lib/services/local/spiffe_federations.go new file mode 100644 index 0000000000000..67910e22ac835 --- /dev/null +++ b/lib/services/local/spiffe_federations.go @@ -0,0 +1,112 @@ +// 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 . + +package local + +import ( + "context" + + "github.com/gravitational/trace" + + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/services" + "github.com/gravitational/teleport/lib/services/local/generic" +) + +const ( + spiffeFederationPrefix = "spiffe_federation" +) + +// SPIFFEFederationService exposes backend functionality for storing +// SPIFFEFederations +type SPIFFEFederationService struct { + service *generic.ServiceWrapper[*machineidv1.SPIFFEFederation] +} + +// NewSPIFFEFederationService creates a new SPIFFEFederationService. +func NewSPIFFEFederationService( + backend backend.Backend, +) (*SPIFFEFederationService, error) { + service, err := generic.NewServiceWrapper(backend, + types.KindSPIFFEFederation, + spiffeFederationPrefix, + services.MarshalSPIFFEFederation, + services.UnmarshalSPIFFEFederation, + ) + if err != nil { + return nil, trace.Wrap(err) + } + return &SPIFFEFederationService{ + service: service, + }, nil +} + +// CreateSPIFFEFederation inserts a new SPIFFEFederation into the backend. +func (b *SPIFFEFederationService) CreateSPIFFEFederation( + ctx context.Context, federation *machineidv1.SPIFFEFederation, +) (*machineidv1.SPIFFEFederation, error) { + if err := services.ValidateSPIFFEFederation(federation); err != nil { + return nil, trace.Wrap(err) + } + created, err := b.service.CreateResource(ctx, federation) + return created, trace.Wrap(err) +} + +// GetSPIFFEFederation retrieves a specific SPIFFEFederation given a name +func (b *SPIFFEFederationService) GetSPIFFEFederation( + ctx context.Context, name string, +) (*machineidv1.SPIFFEFederation, error) { + federation, err := b.service.GetResource(ctx, name) + return federation, trace.Wrap(err) +} + +// ListSPIFFEFederations lists all SPIFFEFederations using a given page size +// and last key. +func (b *SPIFFEFederationService) ListSPIFFEFederations( + ctx context.Context, pageSize int, currentToken string, +) ([]*machineidv1.SPIFFEFederation, string, error) { + r, nextToken, err := b.service.ListResources(ctx, pageSize, currentToken) + return r, nextToken, trace.Wrap(err) +} + +// DeleteSPIFFEFederation deletes a specific SPIFFEFederations. +func (b *SPIFFEFederationService) DeleteSPIFFEFederation( + ctx context.Context, name string, +) error { + return trace.Wrap(b.service.DeleteResource(ctx, name)) +} + +// DeleteAllSPIFFEFederations deletes all SPIFFE federations, this is typically +// only meant to be used by the cache. +func (b *SPIFFEFederationService) DeleteAllSPIFFEFederations( + ctx context.Context, +) error { + return trace.Wrap(b.service.DeleteAllResources(ctx)) +} + +// UpsertSPIFFEFederation upserts a SPIFFEFederations. Prefer using +// CreateSPIFFEFederation. This is only designed for usage by the cache. +func (b *SPIFFEFederationService) UpsertSPIFFEFederation( + ctx context.Context, federation *machineidv1.SPIFFEFederation, +) (*machineidv1.SPIFFEFederation, error) { + if err := services.ValidateSPIFFEFederation(federation); err != nil { + return nil, trace.Wrap(err) + } + upserted, err := b.service.UpsertResource(ctx, federation) + return upserted, trace.Wrap(err) +} diff --git a/lib/services/spiffe_federations.go b/lib/services/spiffe_federations.go new file mode 100644 index 0000000000000..d47e59b4fb7ef --- /dev/null +++ b/lib/services/spiffe_federations.go @@ -0,0 +1,124 @@ +// 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 . + +package services + +import ( + "context" + "net/url" + "strings" + + "github.com/gravitational/trace" + "github.com/spiffe/go-spiffe/v2/bundle/spiffebundle" + "github.com/spiffe/go-spiffe/v2/spiffeid" + + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" +) + +// SPIFFEFederations is an interface over the SPIFFEFederations service. This +// interface may also be implemented by a client to allow remote and local +// consumers to access the resource in a similar way. +type SPIFFEFederations interface { + // GetSPIFFEFederation gets a SPIFFE Federation by name. + GetSPIFFEFederation( + ctx context.Context, name string, + ) (*machineidv1.SPIFFEFederation, error) + // ListSPIFFEFederations lists all SPIFFE Federations using Google style + // pagination. + ListSPIFFEFederations( + ctx context.Context, pageSize int, lastToken string, + ) ([]*machineidv1.SPIFFEFederation, string, error) + // CreateSPIFFEFederation creates a new SPIFFE Federation. + CreateSPIFFEFederation( + ctx context.Context, spiffeFederation *machineidv1.SPIFFEFederation, + ) (*machineidv1.SPIFFEFederation, error) + // DeleteSPIFFEFederation deletes a SPIFFE Federation by name. + DeleteSPIFFEFederation(ctx context.Context, name string) error +} + +// MarshalSPIFFEFederation marshals the SPIFFEFederation object into a JSON byte +// array. +func MarshalSPIFFEFederation(object *machineidv1.SPIFFEFederation, opts ...MarshalOption) ([]byte, error) { + object.Version = types.V1 + object.Kind = types.KindSPIFFEFederation + return MarshalProtoResource(object, opts...) +} + +// UnmarshalSPIFFEFederation unmarshals the SPIFFEFederation object from a +// JSON byte array. +func UnmarshalSPIFFEFederation( + data []byte, opts ...MarshalOption, +) (*machineidv1.SPIFFEFederation, error) { + return UnmarshalProtoResource[*machineidv1.SPIFFEFederation](data, opts...) +} + +// ValidateSPIFFEFederation validates the SPIFFEFederation object. +func ValidateSPIFFEFederation(s *machineidv1.SPIFFEFederation) error { + switch { + case s == nil: + return trace.BadParameter("object cannot be nil") + case s.Metadata.Name == "": + return trace.BadParameter("metadata.name: is required") + case s.Spec == nil: + return trace.BadParameter("spec: is required") + case s.Spec.BundleSource == nil: + return trace.BadParameter("spec.bundle_source: is required") + case s.Spec.BundleSource.HttpsWeb != nil && s.Spec.BundleSource.Static != nil: + return trace.BadParameter("spec.bundle_source: at most one of https_web or static can be set") + case s.Spec.BundleSource.HttpsWeb == nil && s.Spec.BundleSource.Static == nil: + return trace.BadParameter("spec.bundle_source: at least one of https_web or static must be set") + } + + // Validate name is valid SPIFFE Trust Domain name without the "spiffe://" + name := s.Metadata.Name + if strings.HasPrefix(name, "spiffe://") { + return trace.BadParameter( + "metadata.name: must not include the spiffe:// prefix", + ) + } + td, err := spiffeid.TrustDomainFromString(name) + if err != nil { + return trace.Wrap(err, "validating metadata.name") + } + + // Validate Static + if s.Spec.BundleSource.Static != nil { + if s.Spec.BundleSource.Static.Bundle == "" { + return trace.BadParameter("spec.bundle_source.static.bundle: is required") + } + // Validate contents + // TODO(noah): Is this a bit intense to run on every validation? + // This could easily be moved into reconciliation... + _, err := spiffebundle.Parse(td, []byte(s.Spec.BundleSource.Static.Bundle)) + if err != nil { + return trace.Wrap(err, "validating spec.bundle_source.static.bundle") + } + } + + // Validate HTTPSWeb + if s.Spec.BundleSource.HttpsWeb != nil { + if s.Spec.BundleSource.HttpsWeb.BundleEndpointUrl == "" { + return trace.BadParameter("spec.bundle_source.https_web.bundle_endpoint_url: is required") + } + _, err := url.Parse(s.Spec.BundleSource.HttpsWeb.BundleEndpointUrl) + if err != nil { + return trace.Wrap(err, "validating spec.bundle_source.https_web.bundle_endpoint_url") + } + } + + return nil +} From d11924c8cc0039fada6e17c8a32c49b1da0adf24 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 5 Aug 2024 11:37:37 +0100 Subject: [PATCH 02/27] Add SPIFFEFederation resource to cache --- lib/cache/cache.go | 11 +++ lib/cache/collections.go | 11 +++ lib/cache/resource_spiffe_federation.go | 119 ++++++++++++++++++++++++ 3 files changed, 141 insertions(+) create mode 100644 lib/cache/resource_spiffe_federation.go diff --git a/lib/cache/cache.go b/lib/cache/cache.go index a744ea0df5f25..6c938f571d0d5 100644 --- a/lib/cache/cache.go +++ b/lib/cache/cache.go @@ -182,6 +182,7 @@ func ForAuth(cfg Config) Config { {Kind: types.KindAccessMonitoringRule}, {Kind: types.KindDatabaseObject}, {Kind: types.KindAccessGraphSettings}, + {Kind: types.KindSPIFFEFederation}, } cfg.QueueSize = defaults.AuthQueueSize // We don't want to enable partial health for auth cache because auth uses an event stream @@ -519,6 +520,7 @@ type Cache struct { kubeWaitingContsCache *local.KubeWaitingContainerService notificationsCache services.Notifications accessMontoringRuleCache services.AccessMonitoringRules + spiffeFederationCache spiffeFederationCacher // closed indicates that the cache has been closed closed atomic.Bool @@ -691,6 +693,8 @@ type Config struct { Notifications services.Notifications // AccessMonitoringRules is the access monitoring rules service. AccessMonitoringRules services.AccessMonitoringRules + // SPIFFEFederations is the SPIFFE federations service. + SPIFFEFederations spiffeFederationReader // Backend is a backend for local cache Backend backend.Backend // MaxRetryPeriod is the maximum period between cache retries on failures @@ -926,6 +930,12 @@ func New(config Config) (*Cache, error) { return nil, trace.Wrap(err) } + spiffeFederationCache, err := local.NewSPIFFEFederationService(config.Backend) + if err != nil { + cancel() + return nil, trace.Wrap(err) + } + cs := &Cache{ ctx: ctx, cancel: cancel, @@ -966,6 +976,7 @@ func New(config Config) (*Cache, error) { eventsFanout: fanout, lowVolumeEventsFanout: utils.NewRoundRobin(lowVolumeFanouts), kubeWaitingContsCache: kubeWaitingContsCache, + spiffeFederationCache: spiffeFederationCache, Logger: log.WithFields(log.Fields{ teleport.ComponentKey: config.Component, }), diff --git a/lib/cache/collections.go b/lib/cache/collections.go index f4e8592c53d85..7ba7d5511297f 100644 --- a/lib/cache/collections.go +++ b/lib/cache/collections.go @@ -33,6 +33,7 @@ import ( crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" dbobjectv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/dbobject/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" notificationsv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/notifications/v1" userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" "github.com/gravitational/teleport/api/types" @@ -250,6 +251,7 @@ type cacheCollections struct { accessGraphSettings collectionReader[accessGraphSettingsGetter] globalNotifications collectionReader[notificationGetter] accessMonitoringRules collectionReader[accessMonitoringRuleGetter] + spiffeFederations collectionReader[spiffeFederationReader] } // setupCollections returns a registry of collections. @@ -742,6 +744,15 @@ func setupCollections(c *Cache, watches []types.WatchKind) (*cacheCollections, e watch: watch, } collections.byKind[resourceKind] = collections.accessGraphSettings + case types.KindSPIFFEFederation: + if c.Config.SPIFFEFederations == nil { + return nil, trace.BadParameter("missing parameter SPIFFEFederations") + } + collections.spiffeFederations = &genericCollection[*machineidv1.SPIFFEFederation, spiffeFederationReader, spiffeFederationExecutor]{ + cache: c, + watch: watch, + } + collections.byKind[resourceKind] = collections.spiffeFederations default: return nil, trace.BadParameter("resource %q is not supported", watch.Kind) } diff --git a/lib/cache/resource_spiffe_federation.go b/lib/cache/resource_spiffe_federation.go new file mode 100644 index 0000000000000..c767543564582 --- /dev/null +++ b/lib/cache/resource_spiffe_federation.go @@ -0,0 +1,119 @@ +// 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 . + +package cache + +import ( + "context" + + "github.com/gravitational/trace" + + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" +) + +// spiffeFederationReader is an interface that defines the methods for getting +// SPIFFE federations. This is returned as the reader for the SPIFFEFederations +// collection but is also used by the executor to read the full list of +// SPIFFE Federations on initialization. +type spiffeFederationReader interface { + ListSPIFFEFederations(ctx context.Context, pageSize int, nextToken string) ([]*machineidv1.SPIFFEFederation, string, error) + GetSPIFFEFederation(ctx context.Context, name string) (*machineidv1.SPIFFEFederation, error) +} + +// spiffeFederationCacher is used for storing and retrieving SPIFFE federations +// from the cache's local backend. +type spiffeFederationCacher interface { + spiffeFederationReader + UpsertSPIFFEFederation(ctx context.Context, federation *machineidv1.SPIFFEFederation) (*machineidv1.SPIFFEFederation, error) + DeleteSPIFFEFederation(ctx context.Context, name string) error + DeleteAllSPIFFEFederations(ctx context.Context) error +} + +type spiffeFederationExecutor struct{} + +var _ executor[*machineidv1.SPIFFEFederation, spiffeFederationReader] = spiffeFederationExecutor{} + +func (spiffeFederationExecutor) getAll(ctx context.Context, cache *Cache, loadSecrets bool) ([]*machineidv1.SPIFFEFederation, error) { + var out []*machineidv1.SPIFFEFederation + var nextToken string + for { + var page []*machineidv1.SPIFFEFederation + var err error + + page, nextToken, err = cache.Config.SPIFFEFederations.ListSPIFFEFederations(ctx, 0 /* default page size */, nextToken) + if err != nil { + return nil, trace.Wrap(err) + } + out = append(out, page...) + if nextToken == "" { + break + } + } + return out, nil +} + +func (spiffeFederationExecutor) upsert(ctx context.Context, cache *Cache, resource *machineidv1.SPIFFEFederation) error { + _, err := cache.spiffeFederationCache.UpsertSPIFFEFederation(ctx, resource) + return trace.Wrap(err) +} + +func (spiffeFederationExecutor) deleteAll(ctx context.Context, cache *Cache) error { + return trace.Wrap(cache.spiffeFederationCache.DeleteAllSPIFFEFederations(ctx)) +} + +func (spiffeFederationExecutor) delete(ctx context.Context, cache *Cache, resource types.Resource) error { + return trace.Wrap(cache.spiffeFederationCache.DeleteSPIFFEFederation(ctx, resource.GetName())) +} + +func (spiffeFederationExecutor) isSingleton() bool { return false } + +func (spiffeFederationExecutor) getReader(cache *Cache, cacheOK bool) spiffeFederationReader { + if cacheOK { + return cache.spiffeFederationCache + } + return cache.Config.SPIFFEFederations +} + +// ListSPIFFEFederations returns a paginated list of SPIFFE federations +func (c *Cache) ListSPIFFEFederations(ctx context.Context, pageSize int, nextToken string) ([]*machineidv1.SPIFFEFederation, string, error) { + ctx, span := c.Tracer.Start(ctx, "cache/ListSPIFFEFederations") + defer span.End() + + rg, err := readCollectionCache(c, c.collections.spiffeFederations) + + if err != nil { + return nil, "", trace.Wrap(err) + } + defer rg.Release() + out, nextKey, err := rg.reader.ListSPIFFEFederations(ctx, pageSize, nextToken) + return out, nextKey, trace.Wrap(err) +} + +// GetSPIFFEFederation returns a single SPIFFE federation by name +func (c *Cache) GetSPIFFEFederation(ctx context.Context, name string) (*machineidv1.SPIFFEFederation, error) { + ctx, span := c.Tracer.Start(ctx, "cache/GetSPIFFEFederation") + defer span.End() + + rg, err := readCollectionCache(c, c.collections.spiffeFederations) + + if err != nil { + return nil, trace.Wrap(err) + } + defer rg.Release() + out, err := rg.reader.GetSPIFFEFederation(ctx, name) + return out, trace.Wrap(err) +} From 61db4ebb4c1e67e5f3d45e27b167a142a87100db Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 5 Aug 2024 11:53:32 +0100 Subject: [PATCH 03/27] Wire SPIFFEFederation into cache --- lib/auth/accesspoint/accesspoint.go | 5 +++++ lib/auth/auth.go | 8 ++++++++ lib/auth/authclient/api.go | 7 +++++++ lib/auth/init.go | 3 +++ lib/cache/resource_spiffe_federation.go | 2 -- lib/service/service.go | 11 ++++++----- 6 files changed, 29 insertions(+), 7 deletions(-) diff --git a/lib/auth/accesspoint/accesspoint.go b/lib/auth/accesspoint/accesspoint.go index 9ed7fb66097c4..c2059e36cbf73 100644 --- a/lib/auth/accesspoint/accesspoint.go +++ b/lib/auth/accesspoint/accesspoint.go @@ -47,6 +47,10 @@ type AccessCacheConfig struct { // Services is a collection of upstream services from which // the access cache will derive its state. Services services.Services + // SPIFFEFederationsService is the service used to manage SPIFFE + // federations. This will be used by the cache to derive the state for the + // SPIFFEFederations collection. + SPIFFEFederationsService services.SPIFFEFederations // Setup is a function that takes cache configuration and // modifies it to support a specific teleport service. Setup cache.SetupConfigFn @@ -154,6 +158,7 @@ func NewAccessCache(cfg AccessCacheConfig) (*cache.Cache, error) { WebSession: cfg.Services.WebSessions(), WebToken: cfg.Services.WebTokens(), KubeWaitingContainers: cfg.Services, + SPIFFEFederations: cfg.SPIFFEFederationsService, Component: teleport.Component(component...), MetricComponent: teleport.Component(metricComponent...), Tracer: tracer, diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 8d395e3977246..03716921c3bbd 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -342,6 +342,12 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { return nil, trace.Wrap(err) } } + if cfg.SPIFFEFederations == nil { + cfg.SPIFFEFederations, err = local.NewSPIFFEFederationService(cfg.Backend) + if err != nil { + return nil, trace.Wrap(err, "creating SPIFFEFederation service") + } + } limiter, err := limiter.NewConnectionsLimiter(limiter.Config{ MaxConnections: defaults.LimiterMaxConcurrentSignatures, @@ -428,6 +434,7 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) { AccessMonitoringRules: cfg.AccessMonitoringRules, CrownJewels: cfg.CrownJewels, BotInstance: cfg.BotInstance, + SPIFFEFederations: cfg.SPIFFEFederations, } as := Server{ @@ -619,6 +626,7 @@ type Services struct { services.BotInstance services.AccessGraphSecretsGetter services.DevicesGetter + services.SPIFFEFederations } // SecReportsClient returns the security reports client. diff --git a/lib/auth/authclient/api.go b/lib/auth/authclient/api.go index 7f0f89ab4e81b..7b9edd0712ec3 100644 --- a/lib/auth/authclient/api.go +++ b/lib/auth/authclient/api.go @@ -32,6 +32,7 @@ import ( crownjewelv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/crownjewel/v1" integrationpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/integration/v1" kubewaitingcontainerpb "github.com/gravitational/teleport/api/gen/proto/go/teleport/kubewaitingcontainer/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" userspb "github.com/gravitational/teleport/api/gen/proto/go/teleport/users/v1" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/api/types/accesslist" @@ -1176,6 +1177,12 @@ type Cache interface { // GetAccessGraphSettings returns the access graph settings. GetAccessGraphSettings(context.Context) (*clusterconfigpb.AccessGraphSettings, error) + + // GetSPIFFEFederation gets a SPIFFE Federation by name. + GetSPIFFEFederation(ctx context.Context, name string) (*machineidv1.SPIFFEFederation, error) + // ListSPIFFEFederations lists all SPIFFE Federations using Google style + // pagination. + ListSPIFFEFederations(ctx context.Context, pageSize int, lastToken string) ([]*machineidv1.SPIFFEFederation, string, error) } type NodeWrapper struct { diff --git a/lib/auth/init.go b/lib/auth/init.go index 02e598816eb47..40ffecd563591 100644 --- a/lib/auth/init.go +++ b/lib/auth/init.go @@ -306,6 +306,9 @@ type InitConfig struct { // BotInstance is a service that manages Machine ID bot instances BotInstance services.BotInstance + + // SPIFFEFederations is a service that manages storing SPIFFE federations. + SPIFFEFederations services.SPIFFEFederations } // Init instantiates and configures an instance of AuthServer diff --git a/lib/cache/resource_spiffe_federation.go b/lib/cache/resource_spiffe_federation.go index c767543564582..b310fd56b2366 100644 --- a/lib/cache/resource_spiffe_federation.go +++ b/lib/cache/resource_spiffe_federation.go @@ -94,7 +94,6 @@ func (c *Cache) ListSPIFFEFederations(ctx context.Context, pageSize int, nextTok defer span.End() rg, err := readCollectionCache(c, c.collections.spiffeFederations) - if err != nil { return nil, "", trace.Wrap(err) } @@ -109,7 +108,6 @@ func (c *Cache) GetSPIFFEFederation(ctx context.Context, name string) (*machinei defer span.End() rg, err := readCollectionCache(c, c.collections.spiffeFederations) - if err != nil { return nil, trace.Wrap(err) } diff --git a/lib/service/service.go b/lib/service/service.go index 0ed957687395b..132aed48dc1d4 100644 --- a/lib/service/service.go +++ b/lib/service/service.go @@ -2180,11 +2180,12 @@ func (process *TeleportProcess) initAuthService() error { } cache, err := process.newAccessCache(accesspoint.AccessCacheConfig{ - Services: as.Services, - Setup: cache.ForAuth, - CacheName: []string{teleport.ComponentAuth}, - Events: true, - Unstarted: true, + Services: as.Services, + SPIFFEFederationsService: as.Services.SPIFFEFederations, + Setup: cache.ForAuth, + CacheName: []string{teleport.ComponentAuth}, + Events: true, + Unstarted: true, }) if err != nil { return trace.Wrap(err) From f126adb30fd7788263b4b4cf237272e1102f9571 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 5 Aug 2024 11:59:35 +0100 Subject: [PATCH 04/27] Fix NewTestAuthServer --- lib/auth/helpers.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/auth/helpers.go b/lib/auth/helpers.go index 6adf57e0975b8..56b520c37303c 100644 --- a/lib/auth/helpers.go +++ b/lib/auth/helpers.go @@ -311,12 +311,13 @@ func NewTestAuthServer(cfg TestAuthServerConfig) (*TestAuthServer, error) { if cfg.CacheEnabled { srv.AuthServer.Cache, err = accesspoint.NewAccessCache(accesspoint.AccessCacheConfig{ - Context: srv.AuthServer.CloseContext(), - Services: srv.AuthServer.Services, - Setup: cache.ForAuth, - CacheName: []string{teleport.ComponentAuth}, - Events: true, - Unstarted: true, + Context: srv.AuthServer.CloseContext(), + Services: srv.AuthServer.Services, + SPIFFEFederationsService: srv.AuthServer.Services.SPIFFEFederations, + Setup: cache.ForAuth, + CacheName: []string{teleport.ComponentAuth}, + Events: true, + Unstarted: true, }) if err != nil { return nil, trace.Wrap(err) From 7c8a4b81bfeed4b82883348234d0e4621fc42de4 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 5 Aug 2024 21:51:43 +0100 Subject: [PATCH 05/27] Start writing tests --- lib/services/spiffe_federations_test.go | 139 ++++++++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 lib/services/spiffe_federations_test.go diff --git a/lib/services/spiffe_federations_test.go b/lib/services/spiffe_federations_test.go new file mode 100644 index 0000000000000..e61683dbfffcc --- /dev/null +++ b/lib/services/spiffe_federations_test.go @@ -0,0 +1,139 @@ +// 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 . + +package services + +import ( + "testing" + "time" + + "github.com/google/go-cmp/cmp" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" +) + +func TestValidateSPIFFEFederation(t *testing.T) { + t.Parallel() + _ = time.Date(2000, 11, 2, 12, 0, 0, 0, time.UTC) + + var errContains = func(contains string) require.ErrorAssertionFunc { + return func(t require.TestingT, err error, msgAndArgs ...interface{}) { + require.ErrorContains(t, err, contains, msgAndArgs...) + } + } + + testCases := []struct { + name string + in *machineidv1.SPIFFEFederation + requireErr require.ErrorAssertionFunc + }{ + { + name: "success - https_web", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: require.NoError, + }, + { + name: "success - static", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + Static: &machineidv1.SPIFFEFederationBundleSourceStatic{ + Bundle: `{"keys":[{"use":"x509-svid","kty":"RSA","n":"1AgwZOvyaX_rdEzZsTk6WPAmW0rkz_yM2KTo_6tp8Qck7F1O75ssLUWRJh7IIZlWjXA0Nfc7DQiJw40ClGRds2kD-hJnsVa1UhP0QF9a02dP4ormhoCtOQMRsOJq4CkiuzowfkIRNkc1As5cMocAHhIKcu9H15fYEve390Oy7k3cJwTroRL0JXx8eYS32ae_d5S5QtgXYJvNpB1IumC2hJrkddTW97ozP53H6Vt6JdFpnZNqLXTCKm-pUebzEQ6RCCeLbKNS_NLvixL-4hlPelokUaMaPWnqZvJ0u4txhTSDbcwzjFXznqs6C9LUt3mzUQ_OudX1nsDk0wPab32HgQ","e":"AQAB","x5c":["MIIDnjCCAoagAwIBAgIQQwsx6y8q17q9cU6TsyuQ6zANBgkqhkiG9w0BAQsFADBpMRowGAYDVQQKExFsZWFmLnRlbGUub3R0ci5zaDEaMBgGA1UEAxMRbGVhZi50ZWxlLm90dHIuc2gxLzAtBgNVBAUTJjg5MTE2NDAzNDU0MzE5NjA1NDcyMDA1MDQyMDc3NDU1NTg1NTE1MB4XDTI0MDgwMTA5NDQ0NloXDTM0MDczMDA5NDQ0NlowaTEaMBgGA1UEChMRbGVhZi50ZWxlLm90dHIuc2gxGjAYBgNVBAMTEWxlYWYudGVsZS5vdHRyLnNoMS8wLQYDVQQFEyY4OTExNjQwMzQ1NDMxOTYwNTQ3MjAwNTA0MjA3NzQ1NTU4NTUxNTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANQIMGTr8ml/63RM2bE5OljwJltK5M/8jNik6P+rafEHJOxdTu+bLC1FkSYeyCGZVo1wNDX3Ow0IicONApRkXbNpA/oSZ7FWtVIT9EBfWtNnT+KK5oaArTkDEbDiauApIrs6MH5CETZHNQLOXDKHAB4SCnLvR9eX2BL3t/dDsu5N3CcE66ES9CV8fHmEt9mnv3eUuULYF2CbzaQdSLpgtoSa5HXU1ve6Mz+dx+lbeiXRaZ2Tai10wipvqVHm8xEOkQgni2yjUvzS74sS/uIZT3paJFGjGj1p6mbydLuLcYU0g23MM4xV856rOgvS1Ld5s1EPzrnV9Z7A5NMD2m99h4ECAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgGmMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFrGpt59RUgaAoD5MQEa8McxNOIPMA0GCSqGSIb3DQEBCwUAA4IBAQBRhYIeYU7YW+DxQgIB4gtoM+d0+FOCbteq2IAOjCzZC0rqds4nO1hCFSLaBa8a7GIrW4KoObBJDANRXUokrbbm+zwzj66Rcjj9cKOw/YTbYDu/FXGxY4nNwuI0oFg5CWV+XGepQ5xtGavISTqFK+ctrrIhlvhw+z4xMz4kw6IsMta+WrYPJv1DDoAm/qdZ8Ituvx1cx8THLCSxiXCVgm+AwzlKV4CXU12oY6xrBbHrSxUb/EfnX1KkvFGqQgjDZE+PjClNIN1qS0G2BuWYtjl3pRhiuA9I4B4u31F3wX7LtSlNdzesJeTEGJzcmgHjVr5voozHIeNq/fV/SgjYz2Do"]}],"spiffe_refresh_hint":300}`, + }, + }, + }, + }, + requireErr: require.NoError, + }, + { + name: "fail - null", + in: nil, + requireErr: errContains(""), + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + err := ValidateSPIFFEFederation(tc.in) + tc.requireErr(t, err) + }) + } +} + +func TestSPIFFEFederationMarshaling(t *testing.T) { + t.Parallel() + + testTime := time.Date(2000, 11, 2, 12, 0, 0, 0, time.UTC) + testCases := []struct { + name string + in *machineidv1.SPIFFEFederation + }{ + { + name: "normal", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com", + }, + }, + }, + Status: &machineidv1.SPIFFEFederationStatus{ + CurrentBundle: "xyzzy", + CurrentBundleSyncedAt: timestamppb.New(testTime), + }, + }, + }, + } + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + gotBytes, err := MarshalSPIFFEFederation(tc.in) + require.NoError(t, err) + // Test that unmarshalling gives us the same object + got, err := UnmarshalSPIFFEFederation(gotBytes) + require.NoError(t, err) + require.Empty(t, cmp.Diff(tc.in, got, protocmp.Transform())) + }) + } +} From 49ab510df1ecb960678129f38081b359d7d6a6f0 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Tue, 6 Aug 2024 09:48:26 +0100 Subject: [PATCH 06/27] Add more test cases to validation --- lib/services/spiffe_federations.go | 2 + lib/services/spiffe_federations_test.go | 89 ++++++++++++++++++++++++- 2 files changed, 90 insertions(+), 1 deletion(-) diff --git a/lib/services/spiffe_federations.go b/lib/services/spiffe_federations.go index d47e59b4fb7ef..147bb91745e09 100644 --- a/lib/services/spiffe_federations.go +++ b/lib/services/spiffe_federations.go @@ -71,6 +71,8 @@ func ValidateSPIFFEFederation(s *machineidv1.SPIFFEFederation) error { switch { case s == nil: return trace.BadParameter("object cannot be nil") + case s.Metadata == nil: + return trace.BadParameter("metadata: is required") case s.Metadata.Name == "": return trace.BadParameter("metadata.name: is required") case s.Spec == nil: diff --git a/lib/services/spiffe_federations_test.go b/lib/services/spiffe_federations_test.go index e61683dbfffcc..1dd540a583c6b 100644 --- a/lib/services/spiffe_federations_test.go +++ b/lib/services/spiffe_federations_test.go @@ -84,7 +84,94 @@ func TestValidateSPIFFEFederation(t *testing.T) { { name: "fail - null", in: nil, - requireErr: errContains(""), + requireErr: errContains("object cannot be nil"), + }, + { + name: "fail - nil metadata", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: errContains("metadata: is required"), + }, + { + name: "fail - no name", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: errContains("metadata.name: is required"), + }, + { + name: "fail - bad url", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: ":::::", + }, + }, + }, + }, + requireErr: errContains("validating spec.bundle_source.https_web.bundle_endpoint_url"), + }, + { + name: "fail - bad bundle", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + Static: &machineidv1.SPIFFEFederationBundleSourceStatic{ + Bundle: "xyzzy", + }, + }, + }, + }, + requireErr: errContains("validating spec.bundle_source.static.bundle"), + }, + { + name: "fail - name contains prefix", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "spiffe://example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: errContains("metadata.name: must not include the spiffe:// prefix"), }, } From 859e88c7d90403f375644acada7aca5c7e0125e3 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Tue, 6 Aug 2024 10:44:03 +0100 Subject: [PATCH 07/27] Add tests to cache for SPIFFEFederation --- api/client/events.go | 7 + api/client/proto/event.pb.go | 580 +++++++++--------- .../teleport/legacy/client/proto/event.proto | 3 + lib/cache/cache_test.go | 13 + lib/cache/resource_spiffe_federation_test.go | 76 +++ 5 files changed, 402 insertions(+), 277 deletions(-) create mode 100644 lib/cache/resource_spiffe_federation_test.go diff --git a/api/client/events.go b/api/client/events.go index 2f1be30909bce..1a59f92c4985d 100644 --- a/api/client/events.go +++ b/api/client/events.go @@ -90,6 +90,10 @@ func EventToGRPC(in types.Event) (*proto.Event, error) { out.Resource = &proto.Event_AccessGraphSettings{ AccessGraphSettings: r, } + case *machineidv1.SPIFFEFederation: + out.Resource = &proto.Event_SPIFFEFederation{ + SPIFFEFederation: r, + } default: return nil, trace.BadParameter("resource type %T is not supported", r) } @@ -527,6 +531,9 @@ func EventFromGRPC(in *proto.Event) (*types.Event, error) { } else if r := in.GetAccessGraphSettings(); r != nil { out.Resource = types.Resource153ToLegacy(r) return &out, nil + } else if r := in.GetSPIFFEFederation(); r != nil { + out.Resource = types.Resource153ToLegacy(r) + return &out, nil } else { return nil, trace.BadParameter("received unsupported resource %T", in.Resource) } diff --git a/api/client/proto/event.pb.go b/api/client/proto/event.pb.go index a37a85f520e2f..7f9f4ceaf8994 100644 --- a/api/client/proto/event.pb.go +++ b/api/client/proto/event.pb.go @@ -170,6 +170,7 @@ type Event struct { // *Event_DatabaseObject // *Event_BotInstance // *Event_AccessGraphSettings + // *Event_SPIFFEFederation Resource isEvent_Resource `protobuf_oneof:"Resource"` } @@ -625,6 +626,13 @@ func (x *Event) GetAccessGraphSettings() *v110.AccessGraphSettings { return nil } +func (x *Event) GetSPIFFEFederation() *v19.SPIFFEFederation { + if x, ok := x.GetResource().(*Event_SPIFFEFederation); ok { + return x.SPIFFEFederation + } + return nil +} + type isEvent_Resource interface { isEvent_Resource() } @@ -922,6 +930,11 @@ type Event_AccessGraphSettings struct { AccessGraphSettings *v110.AccessGraphSettings `protobuf:"bytes,61,opt,name=AccessGraphSettings,proto3,oneof"` } +type Event_SPIFFEFederation struct { + // SPIFFEFederation is a resource for SPIFFE federation. + SPIFFEFederation *v19.SPIFFEFederation `protobuf:"bytes,62,opt,name=SPIFFEFederation,proto3,oneof"` +} + func (*Event_ResourceHeader) isEvent_Resource() {} func (*Event_CertAuthority) isEvent_Resource() {} @@ -1038,6 +1051,8 @@ func (*Event_BotInstance) isEvent_Resource() {} func (*Event_AccessGraphSettings) isEvent_Resource() {} +func (*Event_SPIFFEFederation) isEvent_Resource() {} + var File_teleport_legacy_client_proto_event_proto protoreflect.FileDescriptor var file_teleport_legacy_client_proto_event_proto_rawDesc = []byte{ @@ -1070,278 +1085,286 @@ var file_teleport_legacy_client_proto_event_proto_rawDesc = []byte{ 0x79, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x28, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x62, 0x6f, 0x74, 0x5f, 0x69, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x74, - 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, - 0x75, 0x73, 0x65, 0x72, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2f, 0x76, - 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x74, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xf2, 0x1f, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, - 0x12, 0x24, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3f, 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, - 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0d, 0x43, 0x65, 0x72, 0x74, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, - 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0d, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x69, - 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x73, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, - 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, - 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x38, 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, - 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, - 0x56, 0x32, 0x48, 0x00, 0x52, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, - 0x65, 0x12, 0x23, 0x0a, 0x04, 0x55, 0x73, 0x65, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x0d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x56, 0x32, 0x48, 0x00, - 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x23, 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x18, 0x09, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x6f, 0x6c, - 0x65, 0x56, 0x36, 0x48, 0x00, 0x52, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x09, 0x4e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, - 0x48, 0x00, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x29, 0x0a, - 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x32, 0x48, 0x00, - 0x52, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x65, - 0x72, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x54, - 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0d, 0x52, 0x65, 0x76, 0x65, 0x72, - 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x12, 0x47, 0x0a, 0x10, 0x54, 0x75, 0x6e, 0x6e, - 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, - 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, - 0x10, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x3e, 0x0a, 0x0d, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, - 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x33, - 0x48, 0x00, 0x52, 0x0d, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x12, 0x35, 0x0a, 0x0a, 0x41, 0x70, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, - 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, - 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0a, 0x41, 0x70, - 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, - 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, - 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x33, 0x48, 0x00, 0x52, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, - 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x41, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, - 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x33, 0x48, 0x00, 0x52, 0x0e, 0x44, 0x61, 0x74, - 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0a, 0x57, - 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0a, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, - 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x08, 0x57, 0x65, 0x62, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x13, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x56, 0x33, 0x48, 0x00, 0x52, 0x08, 0x57, 0x65, 0x62, 0x54, 0x6f, - 0x6b, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x17, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x65, - 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x14, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, - 0x73, 0x74, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x56, 0x32, 0x48, 0x00, 0x52, 0x17, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, - 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x12, 0x59, 0x0a, 0x16, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, - 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, - 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, - 0x56, 0x32, 0x48, 0x00, 0x52, 0x16, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, - 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x0e, - 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x16, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, - 0x68, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x56, 0x32, 0x48, 0x00, 0x52, - 0x0e, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, - 0x4d, 0x0a, 0x12, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x17, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x79, - 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x32, 0x48, 0x00, 0x52, 0x12, 0x43, 0x6c, 0x75, 0x73, - 0x74, 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, - 0x0a, 0x04, 0x4c, 0x6f, 0x63, 0x6b, 0x18, 0x18, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x63, 0x6b, 0x56, 0x32, 0x48, 0x00, 0x52, 0x04, 0x4c, - 0x6f, 0x63, 0x6b, 0x12, 0x50, 0x0a, 0x13, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, - 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, - 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x34, 0x48, 0x00, - 0x52, 0x13, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x56, 0x0a, 0x15, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, - 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x1a, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x69, 0x6e, - 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, - 0x63, 0x65, 0x56, 0x33, 0x48, 0x00, 0x52, 0x15, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, - 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, - 0x0e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x18, - 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x69, - 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x56, 0x33, 0x48, 0x00, - 0x52, 0x0e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, - 0x12, 0x2f, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x18, 0x1c, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, - 0x61, 0x73, 0x65, 0x56, 0x33, 0x48, 0x00, 0x52, 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, - 0x65, 0x12, 0x32, 0x0a, 0x09, 0x41, 0x70, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x1d, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x70, 0x70, - 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x33, 0x48, 0x00, 0x52, 0x09, 0x41, 0x70, 0x70, 0x53, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, 0x03, 0x41, 0x70, 0x70, 0x18, 0x1e, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x56, 0x33, - 0x48, 0x00, 0x52, 0x03, 0x41, 0x70, 0x70, 0x12, 0x41, 0x0a, 0x10, 0x53, 0x6e, 0x6f, 0x77, 0x66, - 0x6c, 0x61, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x1f, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, - 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x10, 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, - 0x61, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x10, 0x4b, 0x75, - 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x20, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4b, 0x75, 0x62, - 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x33, 0x48, - 0x00, 0x52, 0x10, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, - 0x76, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x11, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, - 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, - 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, - 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x33, 0x48, 0x00, 0x52, 0x11, 0x4b, 0x75, - 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, - 0x32, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x22, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6c, 0x6c, 0x65, 0x72, 0x56, 0x31, 0x48, 0x00, 0x52, 0x09, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, - 0x6c, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, - 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x23, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, - 0x76, 0x69, 0x63, 0x65, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, - 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x59, 0x0a, 0x16, 0x53, 0x41, 0x4d, - 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, - 0x64, 0x65, 0x72, 0x18, 0x24, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, - 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x56, 0x31, 0x48, 0x00, 0x52, 0x16, 0x53, 0x41, - 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, - 0x69, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0e, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, - 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x25, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, - 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, - 0x32, 0x48, 0x00, 0x52, 0x0e, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x73, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x09, 0x55, 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, - 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, - 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x56, 0x31, 0x48, 0x00, 0x52, 0x09, 0x55, 0x73, - 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x2f, 0x0a, 0x08, 0x55, 0x49, 0x43, 0x6f, 0x6e, - 0x66, 0x69, 0x67, 0x18, 0x27, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, - 0x73, 0x2e, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x31, 0x48, 0x00, 0x52, 0x08, - 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x0e, 0x4f, 0x6b, 0x74, 0x61, - 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4f, 0x6b, 0x74, 0x61, 0x49, 0x6d, 0x70, - 0x6f, 0x72, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0e, 0x4f, 0x6b, 0x74, - 0x61, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x4f, - 0x6b, 0x74, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x29, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4f, 0x6b, 0x74, 0x61, - 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0e, - 0x4f, 0x6b, 0x74, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, - 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x2a, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, - 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0b, 0x49, 0x6e, 0x74, - 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x0b, 0x57, 0x61, 0x74, 0x63, - 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x2b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, - 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0b, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x57, 0x0a, 0x16, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, - 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x2c, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x6c, - 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x48, 0x00, 0x52, 0x16, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x0a, 0x41, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, - 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, - 0x74, 0x12, 0x54, 0x0a, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x53, 0x74, - 0x61, 0x74, 0x65, 0x18, 0x2e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x73, 0x74, - 0x61, 0x74, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, - 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, - 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, - 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x18, 0x2f, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, - 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, - 0x72, 0x48, 0x00, 0x52, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, - 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x58, 0x0a, 0x0f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, - 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x30, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, - 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, - 0x65, 0x72, 0x79, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, - 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x0f, - 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, - 0x44, 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x32, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x73, - 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x64, - 0x69, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x48, 0x00, 0x52, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, - 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x38, 0x0a, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, - 0x33, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, - 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, - 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, - 0x47, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x34, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x52, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, 0x4c, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x18, 0x35, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, - 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, - 0x65, 0x77, 0x48, 0x00, 0x52, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, - 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x6d, 0x0a, 0x14, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, - 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x18, 0x36, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, - 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, - 0x72, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, - 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x48, 0x00, 0x52, - 0x14, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, - 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x7e, 0x0a, 0x1a, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, - 0x74, 0x65, 0x73, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, - 0x6e, 0x65, 0x72, 0x18, 0x37, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x65, 0x6c, 0x65, - 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6b, 0x75, 0x62, 0x65, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, - 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x75, 0x62, - 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, - 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x48, 0x00, 0x52, 0x1a, 0x4b, 0x75, 0x62, 0x65, 0x72, - 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, - 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x55, 0x0a, 0x10, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, - 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x38, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, - 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x10, 0x55, 0x73, 0x65, 0x72, - 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5f, 0x0a, 0x12, - 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x39, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, - 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, - 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, - 0x0a, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x18, 0x3a, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, - 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, - 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x48, 0x00, 0x52, 0x0a, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, - 0x77, 0x65, 0x6c, 0x12, 0x4e, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, - 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x3b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, - 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, 0x62, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, - 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, - 0x74, 0x48, 0x00, 0x52, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, 0x62, 0x6a, - 0x65, 0x63, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, - 0x63, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, 0x2e, 0x76, 0x31, - 0x2e, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0b, - 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, - 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, - 0x67, 0x73, 0x18, 0x3d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, - 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x66, 0x69, - 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, - 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x48, 0x00, 0x52, 0x13, 0x41, 0x63, 0x63, 0x65, - 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x42, - 0x0a, 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4a, 0x04, 0x08, 0x07, 0x10, - 0x08, 0x4a, 0x04, 0x08, 0x31, 0x10, 0x32, 0x52, 0x12, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x75, 0x64, 0x69, 0x74, 0x2a, 0x2a, 0x0a, 0x09, 0x4f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, - 0x10, 0x00, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, - 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, - 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x26, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, + 0x64, 0x2f, 0x76, 0x31, 0x2f, 0x66, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2e, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2d, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, + 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2f, 0x76, 0x31, + 0x2f, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x27, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x73, + 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2f, 0x76, 0x31, 0x2f, 0x73, 0x65, 0x63, + 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x2f, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x6f, 0x67, 0x69, + 0x6e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x31, 0x2f, 0x75, 0x73, 0x65, 0x72, 0x6c, 0x6f, + 0x67, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc9, + 0x20, 0x0a, 0x05, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x04, 0x54, 0x79, 0x70, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x10, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4f, + 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x04, 0x54, 0x79, 0x70, 0x65, 0x12, 0x3f, + 0x0a, 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, + 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x48, 0x00, 0x52, + 0x0e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x65, 0x61, 0x64, 0x65, 0x72, 0x12, + 0x3e, 0x0a, 0x0d, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, + 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x56, 0x32, 0x48, 0x00, + 0x52, 0x0d, 0x43, 0x65, 0x72, 0x74, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, + 0x3b, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x74, + 0x61, 0x74, 0x69, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0c, + 0x53, 0x74, 0x61, 0x74, 0x69, 0x63, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x73, 0x12, 0x41, 0x0a, 0x0e, + 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x50, 0x72, 0x6f, + 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, + 0x0e, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x73, 0x69, 0x6f, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, + 0x38, 0x0a, 0x0b, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, + 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0b, 0x43, 0x6c, + 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x23, 0x0a, 0x04, 0x55, 0x73, 0x65, + 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x55, 0x73, 0x65, 0x72, 0x56, 0x32, 0x48, 0x00, 0x52, 0x04, 0x55, 0x73, 0x65, 0x72, 0x12, 0x23, + 0x0a, 0x04, 0x52, 0x6f, 0x6c, 0x65, 0x18, 0x09, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x52, 0x6f, 0x6c, 0x65, 0x56, 0x36, 0x48, 0x00, 0x52, 0x04, 0x52, + 0x6f, 0x6c, 0x65, 0x12, 0x30, 0x0a, 0x09, 0x4e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, + 0x18, 0x0a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4e, + 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x48, 0x00, 0x52, 0x09, 0x4e, 0x61, 0x6d, 0x65, + 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x29, 0x0a, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, + 0x0b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x65, + 0x72, 0x76, 0x65, 0x72, 0x56, 0x32, 0x48, 0x00, 0x52, 0x06, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, + 0x12, 0x3e, 0x0a, 0x0d, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, + 0x6c, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x56, 0x32, 0x48, + 0x00, 0x52, 0x0d, 0x52, 0x65, 0x76, 0x65, 0x72, 0x73, 0x65, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, + 0x12, 0x47, 0x0a, 0x10, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x0d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x10, 0x54, 0x75, 0x6e, 0x6e, 0x65, 0x6c, 0x43, + 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x3e, 0x0a, 0x0d, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, 0x33, 0x48, 0x00, 0x52, 0x0d, 0x41, 0x63, 0x63, 0x65, + 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x35, 0x0a, 0x0a, 0x41, 0x70, 0x70, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x0f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x56, 0x32, 0x48, 0x00, 0x52, 0x0a, 0x41, 0x70, 0x70, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x3e, 0x0a, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x18, 0x10, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x56, 0x33, 0x48, + 0x00, 0x52, 0x0d, 0x52, 0x65, 0x6d, 0x6f, 0x74, 0x65, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x12, 0x41, 0x0a, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, + 0x65, 0x72, 0x18, 0x11, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, + 0x33, 0x48, 0x00, 0x52, 0x0e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x65, 0x72, 0x12, 0x35, 0x0a, 0x0a, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x12, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, + 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0a, + 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x2f, 0x0a, 0x08, 0x57, 0x65, + 0x62, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x13, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x56, 0x33, 0x48, + 0x00, 0x52, 0x08, 0x57, 0x65, 0x62, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x5c, 0x0a, 0x17, 0x43, + 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x14, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x20, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, + 0x6f, 0x72, 0x6b, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x32, 0x48, 0x00, + 0x52, 0x17, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, + 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x59, 0x0a, 0x16, 0x53, 0x65, 0x73, + 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, + 0x66, 0x69, 0x67, 0x18, 0x15, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x32, 0x48, 0x00, 0x52, 0x16, 0x53, 0x65, + 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x67, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x12, 0x41, 0x0a, 0x0e, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x18, 0x16, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, + 0x6e, 0x63, 0x65, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0e, 0x41, 0x75, 0x74, 0x68, 0x50, 0x72, 0x65, + 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x4d, 0x0a, 0x12, 0x43, 0x6c, 0x75, 0x73, 0x74, + 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x17, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x56, 0x32, + 0x48, 0x00, 0x52, 0x12, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x41, 0x75, 0x64, 0x69, 0x74, + 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x23, 0x0a, 0x04, 0x4c, 0x6f, 0x63, 0x6b, 0x18, 0x18, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4c, 0x6f, 0x63, + 0x6b, 0x56, 0x32, 0x48, 0x00, 0x52, 0x04, 0x4c, 0x6f, 0x63, 0x6b, 0x12, 0x50, 0x0a, 0x13, 0x4e, + 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x18, 0x19, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, 0x6b, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x56, 0x34, 0x48, 0x00, 0x52, 0x13, 0x4e, 0x65, 0x74, 0x77, 0x6f, 0x72, + 0x6b, 0x52, 0x65, 0x73, 0x74, 0x72, 0x69, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x56, 0x0a, + 0x15, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x53, + 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x1a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, + 0x74, 0x6f, 0x70, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x33, 0x48, 0x00, 0x52, 0x15, + 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x53, 0x65, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, + 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x18, 0x1b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x73, 0x44, 0x65, 0x73, + 0x6b, 0x74, 0x6f, 0x70, 0x56, 0x33, 0x48, 0x00, 0x52, 0x0e, 0x57, 0x69, 0x6e, 0x64, 0x6f, 0x77, + 0x73, 0x44, 0x65, 0x73, 0x6b, 0x74, 0x6f, 0x70, 0x12, 0x2f, 0x0a, 0x08, 0x44, 0x61, 0x74, 0x61, + 0x62, 0x61, 0x73, 0x65, 0x18, 0x1c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x56, 0x33, 0x48, 0x00, 0x52, + 0x08, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x09, 0x41, 0x70, 0x70, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x1d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x33, + 0x48, 0x00, 0x52, 0x09, 0x41, 0x70, 0x70, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x20, 0x0a, + 0x03, 0x41, 0x70, 0x70, 0x18, 0x1e, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x41, 0x70, 0x70, 0x56, 0x33, 0x48, 0x00, 0x52, 0x03, 0x41, 0x70, 0x70, 0x12, + 0x41, 0x0a, 0x10, 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, + 0x69, 0x6f, 0x6e, 0x18, 0x1f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, + 0x73, 0x2e, 0x57, 0x65, 0x62, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, + 0x52, 0x10, 0x53, 0x6e, 0x6f, 0x77, 0x66, 0x6c, 0x61, 0x6b, 0x65, 0x53, 0x65, 0x73, 0x73, 0x69, + 0x6f, 0x6e, 0x12, 0x47, 0x0a, 0x10, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, + 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x20, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x74, + 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x56, 0x33, 0x48, 0x00, 0x52, 0x10, 0x4b, 0x75, 0x62, 0x65, 0x72, + 0x6e, 0x65, 0x74, 0x65, 0x73, 0x53, 0x65, 0x72, 0x76, 0x65, 0x72, 0x12, 0x4a, 0x0a, 0x11, 0x4b, + 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x18, 0x21, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x4b, + 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, + 0x56, 0x33, 0x48, 0x00, 0x52, 0x11, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, + 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x12, 0x32, 0x0a, 0x09, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6c, 0x6c, 0x65, 0x72, 0x18, 0x22, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x56, 0x31, 0x48, 0x00, + 0x52, 0x09, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x12, 0x44, 0x0a, 0x0f, 0x44, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x23, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x44, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x56, 0x31, 0x48, 0x00, + 0x52, 0x0f, 0x44, 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, + 0x65, 0x12, 0x59, 0x0a, 0x16, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x18, 0x24, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x1f, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, + 0x50, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, + 0x56, 0x31, 0x48, 0x00, 0x52, 0x16, 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x50, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x0e, + 0x53, 0x41, 0x4d, 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x25, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x13, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x65, 0x62, + 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x56, 0x32, 0x48, 0x00, 0x52, 0x0e, 0x53, 0x41, 0x4d, + 0x4c, 0x49, 0x64, 0x50, 0x53, 0x65, 0x73, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x32, 0x0a, 0x09, 0x55, + 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x26, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, + 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, + 0x56, 0x31, 0x48, 0x00, 0x52, 0x09, 0x55, 0x73, 0x65, 0x72, 0x47, 0x72, 0x6f, 0x75, 0x70, 0x12, + 0x2f, 0x0a, 0x08, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x27, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x11, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, + 0x69, 0x67, 0x56, 0x31, 0x48, 0x00, 0x52, 0x08, 0x55, 0x49, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x12, 0x41, 0x0a, 0x0e, 0x4f, 0x6b, 0x74, 0x61, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x75, + 0x6c, 0x65, 0x18, 0x28, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, + 0x2e, 0x4f, 0x6b, 0x74, 0x61, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, 0x75, 0x6c, 0x65, 0x56, + 0x31, 0x48, 0x00, 0x52, 0x0e, 0x4f, 0x6b, 0x74, 0x61, 0x49, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x52, + 0x75, 0x6c, 0x65, 0x12, 0x41, 0x0a, 0x0e, 0x4f, 0x6b, 0x74, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, + 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x29, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x4f, 0x6b, 0x74, 0x61, 0x41, 0x73, 0x73, 0x69, 0x67, 0x6e, 0x6d, 0x65, + 0x6e, 0x74, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0e, 0x4f, 0x6b, 0x74, 0x61, 0x41, 0x73, 0x73, 0x69, + 0x67, 0x6e, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x38, 0x0a, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x2a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, + 0x70, 0x65, 0x73, 0x2e, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x56, + 0x31, 0x48, 0x00, 0x52, 0x0b, 0x49, 0x6e, 0x74, 0x65, 0x67, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x12, 0x38, 0x0a, 0x0b, 0x57, 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x2b, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x57, 0x61, + 0x74, 0x63, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x56, 0x31, 0x48, 0x00, 0x52, 0x0b, 0x57, + 0x61, 0x74, 0x63, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x57, 0x0a, 0x16, 0x48, 0x65, + 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x2c, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x79, 0x70, + 0x65, 0x73, 0x2e, 0x48, 0x65, 0x61, 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x16, 0x48, 0x65, 0x61, + 0x64, 0x6c, 0x65, 0x73, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x0a, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, + 0x74, 0x18, 0x2d, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x48, 0x00, 0x52, 0x0a, 0x41, + 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x54, 0x0a, 0x0e, 0x55, 0x73, 0x65, + 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x2e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, + 0x72, 0x6c, 0x6f, 0x67, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x74, 0x65, 0x2e, 0x76, 0x31, 0x2e, 0x55, + 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x48, 0x00, 0x52, + 0x0e, 0x55, 0x73, 0x65, 0x72, 0x4c, 0x6f, 0x67, 0x69, 0x6e, 0x53, 0x74, 0x61, 0x74, 0x65, 0x12, + 0x4c, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, + 0x62, 0x65, 0x72, 0x18, 0x2f, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, 0x2e, + 0x76, 0x31, 0x2e, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x48, 0x00, 0x52, 0x10, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x4d, 0x65, 0x6d, 0x62, 0x65, 0x72, 0x12, 0x58, 0x0a, + 0x0f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x18, 0x30, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x2c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x2e, 0x64, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x63, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x79, 0x43, 0x6f, + 0x6e, 0x66, 0x69, 0x67, 0x48, 0x00, 0x52, 0x0f, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, + 0x79, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x44, 0x0a, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, + 0x51, 0x75, 0x65, 0x72, 0x79, 0x18, 0x32, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, + 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, 0x74, + 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x75, 0x64, 0x69, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x48, + 0x00, 0x52, 0x0a, 0x41, 0x75, 0x64, 0x69, 0x74, 0x51, 0x75, 0x65, 0x72, 0x79, 0x12, 0x38, 0x0a, + 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x33, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, + 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x48, 0x00, 0x52, + 0x06, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x12, 0x47, 0x0a, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, 0x18, 0x34, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x73, 0x65, 0x63, 0x72, 0x65, 0x70, 0x6f, 0x72, + 0x74, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, + 0x65, 0x48, 0x00, 0x52, 0x0b, 0x52, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x53, 0x74, 0x61, 0x74, 0x65, + 0x12, 0x4c, 0x0a, 0x10, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x76, 0x69, 0x65, 0x77, 0x18, 0x35, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x74, 0x65, 0x6c, + 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6c, 0x69, 0x73, 0x74, + 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x48, 0x00, 0x52, 0x10, 0x41, 0x63, + 0x63, 0x65, 0x73, 0x73, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x76, 0x69, 0x65, 0x77, 0x12, 0x6d, + 0x0a, 0x14, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, + 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x18, 0x36, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x37, 0x2e, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x61, 0x63, 0x63, 0x65, 0x73, 0x73, 0x6d, 0x6f, + 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x72, 0x75, 0x6c, 0x65, 0x73, 0x2e, 0x76, 0x31, + 0x2e, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, + 0x67, 0x52, 0x75, 0x6c, 0x65, 0x48, 0x00, 0x52, 0x14, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x4d, + 0x6f, 0x6e, 0x69, 0x74, 0x6f, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x75, 0x6c, 0x65, 0x12, 0x7e, 0x0a, + 0x1a, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, 0x61, 0x69, 0x74, 0x69, + 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x18, 0x37, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x3c, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6b, 0x75, 0x62, + 0x65, 0x77, 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x63, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, + 0x61, 0x69, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x48, + 0x00, 0x52, 0x1a, 0x4b, 0x75, 0x62, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x65, 0x73, 0x57, 0x61, 0x69, + 0x74, 0x69, 0x6e, 0x67, 0x43, 0x6f, 0x6e, 0x74, 0x61, 0x69, 0x6e, 0x65, 0x72, 0x12, 0x55, 0x0a, + 0x10, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x18, 0x38, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, + 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x2e, 0x76, 0x31, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x48, 0x00, 0x52, 0x10, 0x55, 0x73, 0x65, 0x72, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x5f, 0x0a, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, + 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x39, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2d, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6e, 0x6f, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x47, 0x6c, 0x6f, + 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, + 0x00, 0x52, 0x12, 0x47, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x69, 0x63, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x44, 0x0a, 0x0a, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, + 0x77, 0x65, 0x6c, 0x18, 0x3a, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, + 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x72, 0x6f, 0x77, 0x6e, 0x6a, 0x65, 0x77, 0x65, 0x6c, 0x2e, + 0x76, 0x31, 0x2e, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x48, 0x00, 0x52, + 0x0a, 0x43, 0x72, 0x6f, 0x77, 0x6e, 0x4a, 0x65, 0x77, 0x65, 0x6c, 0x12, 0x4e, 0x0a, 0x0e, 0x44, + 0x61, 0x74, 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x18, 0x3b, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x64, + 0x62, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x61, 0x74, 0x61, 0x62, + 0x61, 0x73, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x48, 0x00, 0x52, 0x0e, 0x44, 0x61, 0x74, + 0x61, 0x62, 0x61, 0x73, 0x65, 0x4f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x12, 0x46, 0x0a, 0x0b, 0x42, + 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x18, 0x3c, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x22, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6d, 0x61, 0x63, 0x68, + 0x69, 0x6e, 0x65, 0x69, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x48, 0x00, 0x52, 0x0b, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x12, 0x62, 0x0a, 0x13, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, + 0x70, 0x68, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x18, 0x3d, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x2e, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x63, 0x6c, 0x75, 0x73, + 0x74, 0x65, 0x72, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x2e, 0x76, 0x31, 0x2e, 0x41, 0x63, 0x63, + 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x53, 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, + 0x48, 0x00, 0x52, 0x13, 0x41, 0x63, 0x63, 0x65, 0x73, 0x73, 0x47, 0x72, 0x61, 0x70, 0x68, 0x53, + 0x65, 0x74, 0x74, 0x69, 0x6e, 0x67, 0x73, 0x12, 0x55, 0x0a, 0x10, 0x53, 0x50, 0x49, 0x46, 0x46, + 0x45, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x3e, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x27, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x6d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x50, 0x49, 0x46, 0x46, 0x45, + 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x48, 0x00, 0x52, 0x10, 0x53, 0x50, + 0x49, 0x46, 0x46, 0x45, 0x46, 0x65, 0x64, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0x0a, + 0x0a, 0x08, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, + 0x4a, 0x04, 0x08, 0x31, 0x10, 0x32, 0x52, 0x12, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, + 0x43, 0x6c, 0x6f, 0x75, 0x64, 0x41, 0x75, 0x64, 0x69, 0x74, 0x2a, 0x2a, 0x0a, 0x09, 0x4f, 0x70, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x08, 0x0a, 0x04, 0x49, 0x4e, 0x49, 0x54, 0x10, + 0x00, 0x12, 0x07, 0x0a, 0x03, 0x50, 0x55, 0x54, 0x10, 0x01, 0x12, 0x0a, 0x0a, 0x06, 0x44, 0x45, + 0x4c, 0x45, 0x54, 0x45, 0x10, 0x02, 0x42, 0x34, 0x5a, 0x32, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x76, 0x69, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x61, 0x6c, 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -1416,6 +1439,7 @@ var file_teleport_legacy_client_proto_event_proto_goTypes = []any{ (*v18.DatabaseObject)(nil), // 54: teleport.dbobject.v1.DatabaseObject (*v19.BotInstance)(nil), // 55: teleport.machineid.v1.BotInstance (*v110.AccessGraphSettings)(nil), // 56: teleport.clusterconfig.v1.AccessGraphSettings + (*v19.SPIFFEFederation)(nil), // 57: teleport.machineid.v1.SPIFFEFederation } var file_teleport_legacy_client_proto_event_proto_depIdxs = []int32{ 0, // 0: proto.Event.Type:type_name -> proto.Operation @@ -1477,11 +1501,12 @@ var file_teleport_legacy_client_proto_event_proto_depIdxs = []int32{ 54, // 56: proto.Event.DatabaseObject:type_name -> teleport.dbobject.v1.DatabaseObject 55, // 57: proto.Event.BotInstance:type_name -> teleport.machineid.v1.BotInstance 56, // 58: proto.Event.AccessGraphSettings:type_name -> teleport.clusterconfig.v1.AccessGraphSettings - 59, // [59:59] is the sub-list for method output_type - 59, // [59:59] is the sub-list for method input_type - 59, // [59:59] is the sub-list for extension type_name - 59, // [59:59] is the sub-list for extension extendee - 0, // [0:59] is the sub-list for field type_name + 57, // 59: proto.Event.SPIFFEFederation:type_name -> teleport.machineid.v1.SPIFFEFederation + 60, // [60:60] is the sub-list for method output_type + 60, // [60:60] is the sub-list for method input_type + 60, // [60:60] is the sub-list for extension type_name + 60, // [60:60] is the sub-list for extension extendee + 0, // [0:60] is the sub-list for field type_name } func init() { file_teleport_legacy_client_proto_event_proto_init() } @@ -1562,6 +1587,7 @@ func file_teleport_legacy_client_proto_event_proto_init() { (*Event_DatabaseObject)(nil), (*Event_BotInstance)(nil), (*Event_AccessGraphSettings)(nil), + (*Event_SPIFFEFederation)(nil), } type x struct{} out := protoimpl.TypeBuilder{ diff --git a/api/proto/teleport/legacy/client/proto/event.proto b/api/proto/teleport/legacy/client/proto/event.proto index 16bc210191c3e..13934f3429e18 100644 --- a/api/proto/teleport/legacy/client/proto/event.proto +++ b/api/proto/teleport/legacy/client/proto/event.proto @@ -25,6 +25,7 @@ import "teleport/discoveryconfig/v1/discoveryconfig.proto"; import "teleport/kubewaitingcontainer/v1/kubewaitingcontainer.proto"; import "teleport/legacy/types/types.proto"; import "teleport/machineid/v1/bot_instance.proto"; +import "teleport/machineid/v1/federation.proto"; import "teleport/notifications/v1/notifications.proto"; import "teleport/secreports/v1/secreports.proto"; import "teleport/userloginstate/v1/userloginstate.proto"; @@ -171,5 +172,7 @@ message Event { teleport.machineid.v1.BotInstance BotInstance = 60; // AccessGraphSettings is a resource for access graph settings. teleport.clusterconfig.v1.AccessGraphSettings AccessGraphSettings = 61; + // SPIFFEFederation is a resource for SPIFFE federation. + teleport.machineid.v1.SPIFFEFederation SPIFFEFederation = 62; } } diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index 981eb343dc13a..bf22eb3c62dcd 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -126,6 +126,7 @@ type testPack struct { accessMonitoringRules services.AccessMonitoringRules crownJewels services.CrownJewels databaseObjects services.DatabaseObjects + spiffeFederations *local.SPIFFEFederationService } // testFuncs are functions to support testing an object in a cache. @@ -325,6 +326,12 @@ func newPackWithoutCache(dir string, opts ...packOption) (*testPack, error) { } p.crownJewels = crownJewelsSvc + spiffeFederationsSvc, err := local.NewSPIFFEFederationService(p.backend) + if err != nil { + return nil, trace.Wrap(err) + } + p.spiffeFederations = spiffeFederationsSvc + databaseObjectsSvc, err := local.NewDatabaseObjectService(p.backend) if err != nil { return nil, trace.Wrap(err) @@ -387,6 +394,7 @@ func newPack(dir string, setupConfig func(c Config) Config, opts ...packOption) Notifications: p.notifications, AccessMonitoringRules: p.accessMonitoringRules, CrownJewels: p.crownJewels, + SPIFFEFederations: p.spiffeFederations, DatabaseObjects: p.databaseObjects, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, @@ -791,6 +799,7 @@ func TestCompletenessInit(t *testing.T) { AccessMonitoringRules: p.accessMonitoringRules, CrownJewels: p.crownJewels, DatabaseObjects: p.databaseObjects, + SPIFFEFederations: p.spiffeFederations, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -868,6 +877,7 @@ func TestCompletenessReset(t *testing.T) { AccessMonitoringRules: p.accessMonitoringRules, CrownJewels: p.crownJewels, DatabaseObjects: p.databaseObjects, + SPIFFEFederations: p.spiffeFederations, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -1057,6 +1067,7 @@ func TestListResources_NodesTTLVariant(t *testing.T) { AccessMonitoringRules: p.accessMonitoringRules, CrownJewels: p.crownJewels, DatabaseObjects: p.databaseObjects, + SPIFFEFederations: p.spiffeFederations, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, neverOK: true, // ensure reads are never healthy @@ -1145,6 +1156,7 @@ func initStrategy(t *testing.T) { AccessMonitoringRules: p.accessMonitoringRules, CrownJewels: p.crownJewels, DatabaseObjects: p.databaseObjects, + SPIFFEFederations: p.spiffeFederations, MaxRetryPeriod: 200 * time.Millisecond, EventsC: p.eventsC, })) @@ -3220,6 +3232,7 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) { types.KindCrownJewel: types.Resource153ToLegacy(newCrownJewel(t, "test")), types.KindDatabaseObject: types.Resource153ToLegacy(newDatabaseObject(t, "test")), types.KindAccessGraphSettings: types.Resource153ToLegacy(newAccessGraphSettings(t)), + types.KindSPIFFEFederation: types.Resource153ToLegacy(newSPIFFEFederation(t, "test")), } for name, cfg := range cases { diff --git a/lib/cache/resource_spiffe_federation_test.go b/lib/cache/resource_spiffe_federation_test.go new file mode 100644 index 0000000000000..b6667a1229d6c --- /dev/null +++ b/lib/cache/resource_spiffe_federation_test.go @@ -0,0 +1,76 @@ +// 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 . + +package cache + +import ( + "context" + "testing" + + "github.com/gravitational/trace" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" +) + +func newSPIFFEFederation(t *testing.T, name string) *machineidv1.SPIFFEFederation { + return &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: name, + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + } +} + +func TestSPIFFEFederations(t *testing.T) { + t.Parallel() + + p := newTestPack(t, ForAuth) + t.Cleanup(p.Close) + + testResources153(t, p, testFuncs153[*machineidv1.SPIFFEFederation]{ + newResource: func(s string) (*machineidv1.SPIFFEFederation, error) { + return newSPIFFEFederation(t, s), nil + }, + + create: func(ctx context.Context, item *machineidv1.SPIFFEFederation) error { + _, err := p.spiffeFederations.CreateSPIFFEFederation(ctx, item) + return trace.Wrap(err) + }, + list: func(ctx context.Context) ([]*machineidv1.SPIFFEFederation, error) { + items, _, err := p.spiffeFederations.ListSPIFFEFederations(ctx, 0, "") + return items, trace.Wrap(err) + }, + deleteAll: func(ctx context.Context) error { + return p.spiffeFederations.DeleteAllSPIFFEFederations(ctx) + }, + + cacheList: func(ctx context.Context) ([]*machineidv1.SPIFFEFederation, error) { + items, _, err := p.cache.ListSPIFFEFederations(ctx, 0, "") + return items, trace.Wrap(err) + }, + cacheGet: p.cache.GetSPIFFEFederation, + }) +} From 718e533345cba18825b29ce99600578957d7e59f Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Tue, 6 Aug 2024 14:43:12 +0100 Subject: [PATCH 08/27] More test coverage --- lib/services/local/spiffe_federations_test.go | 205 ++++++++++++++++++ 1 file changed, 205 insertions(+) create mode 100644 lib/services/local/spiffe_federations_test.go diff --git a/lib/services/local/spiffe_federations_test.go b/lib/services/local/spiffe_federations_test.go new file mode 100644 index 0000000000000..050df557b40dc --- /dev/null +++ b/lib/services/local/spiffe_federations_test.go @@ -0,0 +1,205 @@ +// 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 . + +package local + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/backend" + "github.com/gravitational/teleport/lib/backend/memory" +) + +func newSPIFFEFederation(name string) *machineidv1.SPIFFEFederation { + return &machineidv1.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: name, + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + } +} + +func setupSPIFFEFederationTest( + t *testing.T, +) (context.Context, *SPIFFEFederationService) { + t.Parallel() + ctx := context.Background() + clock := clockwork.NewFakeClock() + mem, err := memory.New(memory.Config{ + Context: ctx, + Clock: clock, + }) + require.NoError(t, err) + service, err := NewSPIFFEFederationService(backend.NewSanitizer(mem)) + require.NoError(t, err) + return ctx, service +} + +func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + + t.Run("ok", func(t *testing.T) { + want := newSPIFFEFederation("example.com") + got, err := service.CreateSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(want).(*machineidv1.SPIFFEFederation), + ) + require.NoError(t, err) + require.NotEmpty(t, got.Metadata.Revision) + require.Empty(t, cmp.Diff( + want, + got, + protocmp.Transform(), + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + )) + }) + t.Run("validation occurs", func(t *testing.T) { + out, err := service.CreateSPIFFEFederation(ctx, newSPIFFEFederation("spiffe://i-will-fail")) + require.ErrorContains(t, err, "metadata.name: must not include the spiffe:// prefix") + require.Nil(t, out) + }) + t.Run("no upsert", func(t *testing.T) { + res := newSPIFFEFederation("twoofme.com") + _, err := service.CreateSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(res).(*machineidv1.SPIFFEFederation), + ) + require.NoError(t, err) + _, err = service.CreateSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(res).(*machineidv1.SPIFFEFederation), + ) + require.Error(t, err) + require.True(t, trace.IsAlreadyExists(err)) + }) +} + +func TestSPIFFEFederationService_UpsertSPIFFEFederation(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + + t.Run("ok", func(t *testing.T) { + want := newSPIFFEFederation("example.com") + got, err := service.UpsertSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(want).(*machineidv1.SPIFFEFederation), + ) + require.NoError(t, err) + require.NotEmpty(t, got.Metadata.Revision) + require.Empty(t, cmp.Diff( + want, + got, + protocmp.Transform(), + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + )) + + // Ensure we can upsert over an existing resource + _, err = service.UpsertSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(want).(*machineidv1.SPIFFEFederation), + ) + require.NoError(t, err) + }) + t.Run("validation occurs", func(t *testing.T) { + out, err := service.UpsertSPIFFEFederation(ctx, newSPIFFEFederation("spiffe://i-will-fail")) + require.ErrorContains(t, err, "metadata.name: must not include the spiffe:// prefix") + require.Nil(t, out) + }) +} + +func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { + +} + +func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + + t.Run("ok", func(t *testing.T) { + want := newSPIFFEFederation("example.com") + _, err := service.CreateSPIFFEFederation( + ctx, + // Clone to avoid Marshalling modifying want + proto.Clone(want).(*machineidv1.SPIFFEFederation), + ) + require.NoError(t, err) + got, err := service.GetSPIFFEFederation(ctx, "example.com") + require.NoError(t, err) + require.NotEmpty(t, got.Metadata.Revision) + require.Empty(t, cmp.Diff( + want, + got, + protocmp.Transform(), + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + )) + }) + t.Run("not found", func(t *testing.T) { + _, err := service.GetSPIFFEFederation(ctx, "foo.example.com") + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }) +} + +func TestSPIFFEFederationService_DeleteSPIFFEFederation(t *testing.T) { + +} + +func TestSPIFFEFederationService_DeleteAllSPIFFEFederation(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + _, err := service.CreateSPIFFEFederation( + ctx, + newSPIFFEFederation("1"), + ) + require.NoError(t, err) + _, err = service.CreateSPIFFEFederation( + ctx, + newSPIFFEFederation("2"), + ) + require.NoError(t, err) + + page, _, err := service.ListSPIFFEFederations(ctx, 0, "") + require.NoError(t, err) + require.Len(t, page, 2) + + err = service.DeleteAllSPIFFEFederations(ctx) + require.NoError(t, err) + + page, _, err = service.ListSPIFFEFederations(ctx, 0, "") + require.NoError(t, err) + require.Len(t, page, 0) +} From 441c53268ecea3e4c9d4056cd6b4c7fde979b525 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Wed, 7 Aug 2024 21:15:50 +0100 Subject: [PATCH 09/27] Add test for DeleteSPIFFEFederations --- lib/services/local/spiffe_federations_test.go | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/lib/services/local/spiffe_federations_test.go b/lib/services/local/spiffe_federations_test.go index 050df557b40dc..99bf7419277c1 100644 --- a/lib/services/local/spiffe_federations_test.go +++ b/lib/services/local/spiffe_federations_test.go @@ -176,7 +176,30 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { } func TestSPIFFEFederationService_DeleteSPIFFEFederation(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + + t.Run("ok", func(t *testing.T) { + _, err := service.CreateSPIFFEFederation( + ctx, + newSPIFFEFederation("example.com"), + ) + require.NoError(t, err) + _, err = service.GetSPIFFEFederation(ctx, "example.com") + require.NoError(t, err) + + err = service.DeleteSPIFFEFederation(ctx, "example.com") + require.NoError(t, err) + + _, err = service.GetSPIFFEFederation(ctx, "example.com") + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }) + t.Run("not found", func(t *testing.T) { + _, err := service.GetSPIFFEFederation(ctx, "foo.example.com") + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }) } func TestSPIFFEFederationService_DeleteAllSPIFFEFederation(t *testing.T) { From 037fa631fbcfd0384eaae74dcf68a24726836282 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 09:22:36 +0100 Subject: [PATCH 10/27] Finish off tests for SPIFFEFederation resource --- lib/services/local/spiffe_federations_test.go | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/lib/services/local/spiffe_federations_test.go b/lib/services/local/spiffe_federations_test.go index 99bf7419277c1..e944fd118353b 100644 --- a/lib/services/local/spiffe_federations_test.go +++ b/lib/services/local/spiffe_federations_test.go @@ -18,6 +18,8 @@ package local import ( "context" + "fmt" + "slices" "testing" "github.com/google/go-cmp/cmp" @@ -144,7 +146,55 @@ func TestSPIFFEFederationService_UpsertSPIFFEFederation(t *testing.T) { } func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { + ctx, service := setupSPIFFEFederationTest(t) + // Create entities to list + createdObjects := []*machineidv1.SPIFFEFederation{} + // Create 49 entities to test an incomplete page at the end. + for i := 0; i < 49; i++ { + created, err := service.CreateSPIFFEFederation( + ctx, + newSPIFFEFederation(fmt.Sprintf("%d.example.com", i)), + ) + require.NoError(t, err) + createdObjects = append(createdObjects, created) + } + t.Run("default page size", func(t *testing.T) { + page, nextToken, err := service.ListSPIFFEFederations(ctx, 0, "") + require.NoError(t, err) + require.Len(t, page, 49) + require.Empty(t, nextToken) + + // Expect that we get all the things we have created + for _, created := range createdObjects { + slices.ContainsFunc(page, func(federation *machineidv1.SPIFFEFederation) bool { + return proto.Equal(created, federation) + }) + } + }) + t.Run("pagination", func(t *testing.T) { + fetched := []*machineidv1.SPIFFEFederation{} + token := "" + iterations := 0 + for { + iterations++ + page, nextToken, err := service.ListSPIFFEFederations(ctx, 10, token) + require.NoError(t, err) + fetched = append(fetched, page...) + if nextToken == "" { + break + } + token = nextToken + } + require.Equal(t, 5, iterations) + require.Len(t, fetched, 49) + // Expect that we get all the things we have created + for _, created := range createdObjects { + slices.ContainsFunc(fetched, func(federation *machineidv1.SPIFFEFederation) bool { + return proto.Equal(created, federation) + }) + } + }) } func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { From 826d8f3cf10c91f46f83e885d28684878f3e333d Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 09:36:48 +0100 Subject: [PATCH 11/27] go mod tidy --- integrations/terraform/go.mod | 2 ++ 1 file changed, 2 insertions(+) diff --git a/integrations/terraform/go.mod b/integrations/terraform/go.mod index 468252b434b35..fac50bf65fec5 100644 --- a/integrations/terraform/go.mod +++ b/integrations/terraform/go.mod @@ -149,6 +149,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.2.6 // indirect @@ -316,6 +317,7 @@ require ( github.com/yuin/goldmark-meta v1.1.0 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect github.com/zclconf/go-cty v1.14.4 // indirect + github.com/zeebo/errs v1.3.0 // indirect github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect github.com/zmap/zlint/v3 v3.6.0 // indirect go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect From 3c4e0ee318102077902553d33495aa5e0bc44f7c Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 09:40:25 +0100 Subject: [PATCH 12/27] Go mod tidy --- integrations/event-handler/go.mod | 3 +++ integrations/event-handler/go.sum | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/integrations/event-handler/go.mod b/integrations/event-handler/go.mod index 223d8cbc39f77..b1ff6d46c4a83 100644 --- a/integrations/event-handler/go.mod +++ b/integrations/event-handler/go.mod @@ -132,6 +132,7 @@ require ( github.com/go-errors/errors v1.4.2 // indirect github.com/go-gorp/gorp/v3 v3.1.0 // indirect github.com/go-jose/go-jose/v3 v3.0.3 // indirect + github.com/go-jose/go-jose/v4 v4.0.2 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect @@ -246,6 +247,7 @@ require ( github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.1 // indirect github.com/spf13/pflag v1.0.5 // indirect + github.com/spiffe/go-spiffe/v2 v2.3.0 // indirect github.com/stretchr/objx v0.5.2 // indirect github.com/thales-e-security/pool v0.0.2 // indirect github.com/vulcand/predicate v1.2.0 // indirect @@ -257,6 +259,7 @@ require ( github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xhit/go-str2duration/v2 v2.1.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect + github.com/zeebo/errs v1.3.0 // indirect github.com/zmap/zcrypto v0.0.0-20231219022726-a1f61fb1661c // indirect github.com/zmap/zlint/v3 v3.6.0 // indirect go.mongodb.org/mongo-driver v1.14.0 // indirect diff --git a/integrations/event-handler/go.sum b/integrations/event-handler/go.sum index 446ed51cdf385..7e5d062c525a1 100644 --- a/integrations/event-handler/go.sum +++ b/integrations/event-handler/go.sum @@ -962,6 +962,8 @@ github.com/go-gorp/gorp/v3 v3.1.0 h1:ItKF/Vbuj31dmV4jxA1qblpSwkl9g1typ24xoe70IGs github.com/go-gorp/gorp/v3 v3.1.0/go.mod h1:dLEjIyyRNiXvNZ8PSmzpt1GsWAUK8kjVhEpjH8TixEw= github.com/go-jose/go-jose/v3 v3.0.3 h1:fFKWeig/irsp7XD2zBxvnmA/XaRWp5V3CBsZXJF7G7k= github.com/go-jose/go-jose/v3 v3.0.3/go.mod h1:5b+7YgP7ZICgJDBdfjZaIt+H/9L9T/YQrVfLAMboGkQ= +github.com/go-jose/go-jose/v4 v4.0.2 h1:R3l3kkBds16bO7ZFAEEcofK0MkrAJt3jlJznWZG0nvk= +github.com/go-jose/go-jose/v4 v4.0.2/go.mod h1:WVf9LFMHh/QVrmqrOfqun0C45tMe3RoiKJMPvgWwLfY= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-latex/latex v0.0.0-20210118124228-b3d85cf34e07/go.mod h1:CO1AlKB2CSIqUrmQPqA0gdRIlnLEY0gK5JGjh37zN5U= github.com/go-latex/latex v0.0.0-20210823091927-c0d11ff05a81/go.mod h1:SX0U8uGpxhq9o2S/CELCSUxEWWAuoCUcVCQWv7G2OCk= @@ -1539,6 +1541,8 @@ github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spiffe/go-spiffe/v2 v2.3.0 h1:g2jYNb/PDMB8I7mBGL2Zuq/Ur6hUhoroxGQFyD6tTj8= +github.com/spiffe/go-spiffe/v2 v2.3.0/go.mod h1:Oxsaio7DBgSNqhAO9i/9tLClaVlfRok7zvJnTV8ZyIY= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= @@ -1602,6 +1606,8 @@ github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPS github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f h1:ERexzlUfuTvpE74urLSbIQW0Z/6hF9t8U4NsJLaioAY= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= github.com/zeebo/assert v1.3.0/go.mod h1:Pq9JiuJQpG8JLJdtkwrJESF0Foym2/D9XMU5ciN/wJ0= +github.com/zeebo/errs v1.3.0 h1:hmiaKqgYZzcVgRL1Vkc1Mn2914BbzB0IBxs+ebeutGs= +github.com/zeebo/errs v1.3.0/go.mod h1:sgbWHsvVuTPHcqJJGQ1WhI5KbWlHYz+2+2C/LSEtCw4= github.com/zeebo/xxh3 v1.0.2/go.mod h1:5NWz9Sef7zIDm2JHfFlcQvNekmcEl9ekUZQQKCYaDcA= github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= github.com/zmap/rc2 v0.0.0-20190804163417-abaa70531248/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE= From 651867fbfc71ead62e2449561d166d1f2dae189b Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 10:59:44 +0100 Subject: [PATCH 13/27] Appease linter --- lib/cache/cache_test.go | 2 +- lib/cache/resource_spiffe_federation_test.go | 5 +++-- lib/services/local/spiffe_federations_test.go | 12 ++++++------ 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/cache/cache_test.go b/lib/cache/cache_test.go index bf22eb3c62dcd..c733695edeab1 100644 --- a/lib/cache/cache_test.go +++ b/lib/cache/cache_test.go @@ -3232,7 +3232,7 @@ func TestCacheWatchKindExistsInEvents(t *testing.T) { types.KindCrownJewel: types.Resource153ToLegacy(newCrownJewel(t, "test")), types.KindDatabaseObject: types.Resource153ToLegacy(newDatabaseObject(t, "test")), types.KindAccessGraphSettings: types.Resource153ToLegacy(newAccessGraphSettings(t)), - types.KindSPIFFEFederation: types.Resource153ToLegacy(newSPIFFEFederation(t, "test")), + types.KindSPIFFEFederation: types.Resource153ToLegacy(newSPIFFEFederation("test")), } for name, cfg := range cases { diff --git a/lib/cache/resource_spiffe_federation_test.go b/lib/cache/resource_spiffe_federation_test.go index b6667a1229d6c..491646b8ad144 100644 --- a/lib/cache/resource_spiffe_federation_test.go +++ b/lib/cache/resource_spiffe_federation_test.go @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//nolint:unused // Because the executors generate a large amount of false positives. package cache import ( @@ -27,7 +28,7 @@ import ( "github.com/gravitational/teleport/api/types" ) -func newSPIFFEFederation(t *testing.T, name string) *machineidv1.SPIFFEFederation { +func newSPIFFEFederation(name string) *machineidv1.SPIFFEFederation { return &machineidv1.SPIFFEFederation{ Kind: types.KindSPIFFEFederation, Version: types.V1, @@ -52,7 +53,7 @@ func TestSPIFFEFederations(t *testing.T) { testResources153(t, p, testFuncs153[*machineidv1.SPIFFEFederation]{ newResource: func(s string) (*machineidv1.SPIFFEFederation, error) { - return newSPIFFEFederation(t, s), nil + return newSPIFFEFederation(s), nil }, create: func(ctx context.Context, item *machineidv1.SPIFFEFederation) error { diff --git a/lib/services/local/spiffe_federations_test.go b/lib/services/local/spiffe_federations_test.go index e944fd118353b..86021fa9cae33 100644 --- a/lib/services/local/spiffe_federations_test.go +++ b/lib/services/local/spiffe_federations_test.go @@ -97,13 +97,13 @@ func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { res := newSPIFFEFederation("twoofme.com") _, err := service.CreateSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(res).(*machineidv1.SPIFFEFederation), ) require.NoError(t, err) _, err = service.CreateSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(res).(*machineidv1.SPIFFEFederation), ) require.Error(t, err) @@ -118,7 +118,7 @@ func TestSPIFFEFederationService_UpsertSPIFFEFederation(t *testing.T) { want := newSPIFFEFederation("example.com") got, err := service.UpsertSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(want).(*machineidv1.SPIFFEFederation), ) require.NoError(t, err) @@ -133,7 +133,7 @@ func TestSPIFFEFederationService_UpsertSPIFFEFederation(t *testing.T) { // Ensure we can upsert over an existing resource _, err = service.UpsertSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(want).(*machineidv1.SPIFFEFederation), ) require.NoError(t, err) @@ -204,7 +204,7 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { want := newSPIFFEFederation("example.com") _, err := service.CreateSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(want).(*machineidv1.SPIFFEFederation), ) require.NoError(t, err) @@ -274,5 +274,5 @@ func TestSPIFFEFederationService_DeleteAllSPIFFEFederation(t *testing.T) { page, _, err = service.ListSPIFFEFederations(ctx, 0, "") require.NoError(t, err) - require.Len(t, page, 0) + require.Empty(t, page) } From a610cbb0550ed1078506ccb476e75216ba64effb Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 11:52:55 +0100 Subject: [PATCH 14/27] Avoid forcing kind/version in MarshalSPIFFEFederation --- lib/services/spiffe_federations.go | 6 +++-- lib/services/spiffe_federations_test.go | 36 +++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/services/spiffe_federations.go b/lib/services/spiffe_federations.go index 147bb91745e09..58d24f4662cb3 100644 --- a/lib/services/spiffe_federations.go +++ b/lib/services/spiffe_federations.go @@ -53,8 +53,6 @@ type SPIFFEFederations interface { // MarshalSPIFFEFederation marshals the SPIFFEFederation object into a JSON byte // array. func MarshalSPIFFEFederation(object *machineidv1.SPIFFEFederation, opts ...MarshalOption) ([]byte, error) { - object.Version = types.V1 - object.Kind = types.KindSPIFFEFederation return MarshalProtoResource(object, opts...) } @@ -71,6 +69,10 @@ func ValidateSPIFFEFederation(s *machineidv1.SPIFFEFederation) error { switch { case s == nil: return trace.BadParameter("object cannot be nil") + case s.Version != types.V1: + return trace.BadParameter("version: only %q is supported", types.V1) + case s.Kind != types.KindSPIFFEFederation: + return trace.BadParameter("kind: must be %q", types.KindSPIFFEFederation) case s.Metadata == nil: return trace.BadParameter("metadata: is required") case s.Metadata.Name == "": diff --git a/lib/services/spiffe_federations_test.go b/lib/services/spiffe_federations_test.go index 1dd540a583c6b..88a3914458fbc 100644 --- a/lib/services/spiffe_federations_test.go +++ b/lib/services/spiffe_federations_test.go @@ -173,6 +173,42 @@ func TestValidateSPIFFEFederation(t *testing.T) { }, requireErr: errContains("metadata.name: must not include the spiffe:// prefix"), }, + { + name: "fail - wrong kind", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindUser, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: errContains(`kind: must be "spiffe_federation"`), + }, + { + name: "fail - wrong version", + in: &machineidv1.SPIFFEFederation{ + Kind: types.KindUser, + Version: types.V3, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1.SPIFFEFederationSpec{ + BundleSource: &machineidv1.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/foo", + }, + }, + }, + }, + requireErr: errContains(`version: only "v1" is supported`), + }, } for _, tc := range testCases { From e9b258df1e5800c1fe0b0d3d01f50f48f95ef6cb Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 11:56:23 +0100 Subject: [PATCH 15/27] more linter appeasal --- lib/cache/resource_spiffe_federation.go | 1 + lib/cache/resource_spiffe_federation_test.go | 1 - lib/services/local/spiffe_federations_test.go | 2 +- lib/services/spiffe_federations_test.go | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/cache/resource_spiffe_federation.go b/lib/cache/resource_spiffe_federation.go index eb518d622e0e1..8e6a1b38adb10 100644 --- a/lib/cache/resource_spiffe_federation.go +++ b/lib/cache/resource_spiffe_federation.go @@ -14,6 +14,7 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +//nolint:unused // Because the executors generate a large amount of false positives. package cache import ( diff --git a/lib/cache/resource_spiffe_federation_test.go b/lib/cache/resource_spiffe_federation_test.go index 491646b8ad144..a31530f7389eb 100644 --- a/lib/cache/resource_spiffe_federation_test.go +++ b/lib/cache/resource_spiffe_federation_test.go @@ -14,7 +14,6 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . -//nolint:unused // Because the executors generate a large amount of false positives. package cache import ( diff --git a/lib/services/local/spiffe_federations_test.go b/lib/services/local/spiffe_federations_test.go index 86021fa9cae33..aac270fc631b4 100644 --- a/lib/services/local/spiffe_federations_test.go +++ b/lib/services/local/spiffe_federations_test.go @@ -76,7 +76,7 @@ func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { want := newSPIFFEFederation("example.com") got, err := service.CreateSPIFFEFederation( ctx, - // Clone to avoid Marshalling modifying want + // Clone to avoid Marshaling modifying want proto.Clone(want).(*machineidv1.SPIFFEFederation), ) require.NoError(t, err) diff --git a/lib/services/spiffe_federations_test.go b/lib/services/spiffe_federations_test.go index 88a3914458fbc..fb115542c2ed5 100644 --- a/lib/services/spiffe_federations_test.go +++ b/lib/services/spiffe_federations_test.go @@ -253,7 +253,7 @@ func TestSPIFFEFederationMarshaling(t *testing.T) { t.Run(tc.name, func(t *testing.T) { gotBytes, err := MarshalSPIFFEFederation(tc.in) require.NoError(t, err) - // Test that unmarshalling gives us the same object + // Test that unmarshaling gives us the same object got, err := UnmarshalSPIFFEFederation(gotBytes) require.NoError(t, err) require.Empty(t, cmp.Diff(tc.in, got, protocmp.Transform())) From 6fd541d0772e7ff886a7d5e467f388db266206a3 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 14:41:16 +0100 Subject: [PATCH 16/27] Add basics of SPIFFEFederation gRPC service --- api/client/client.go | 4 + lib/auth/grpcserver.go | 12 + .../machineidv1/spiffe_federation_service.go | 245 ++++++++++++++++++ 3 files changed, 261 insertions(+) create mode 100644 lib/auth/machineid/machineidv1/spiffe_federation_service.go diff --git a/api/client/client.go b/api/client/client.go index 0313deb561e4c..dabb7276e196b 100644 --- a/api/client/client.go +++ b/api/client/client.go @@ -858,6 +858,10 @@ func (c *Client) BotInstanceServiceClient() machineidv1pb.BotInstanceServiceClie return machineidv1pb.NewBotInstanceServiceClient(c.conn) } +func (c *Client) SPIFFEFederationServiceClient() machineidv1pb.SPIFFEFederationServiceClient { + return machineidv1pb.NewSPIFFEFederationServiceClient(c.conn) +} + // PresenceServiceClient returns an unadorned client for the presence service. func (c *Client) PresenceServiceClient() presencepb.PresenceServiceClient { return presencepb.NewPresenceServiceClient(c.conn) diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index 368bf60215f85..48b26e56271bc 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -5192,6 +5192,18 @@ func NewGRPCServer(cfg GRPCServerConfig) (*GRPCServer, error) { } machineidv1pb.RegisterWorkloadIdentityServiceServer(server, workloadIdentityService) + spiffeFederationService, err := machineidv1.NewSPIFFEFederationService(machineidv1.SPIFFEFederationServiceConfig{ + Authorizer: cfg.Authorizer, + Backend: cfg.AuthServer.Services.SPIFFEFederations, + Cache: cfg.AuthServer.Cache, + Clock: cfg.AuthServer.GetClock(), + Emitter: cfg.Emitter, + }) + if err != nil { + return nil, trace.Wrap(err, "creating SPIFFE federation service") + } + machineidv1pb.RegisterSPIFFEFederationServiceServer(server, spiffeFederationService) + dbObjectImportRuleService, err := dbobjectimportrulev1.NewDatabaseObjectImportRuleService(dbobjectimportrulev1.DatabaseObjectImportRuleServiceConfig{ Authorizer: cfg.Authorizer, Backend: cfg.AuthServer.Services, diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service.go b/lib/auth/machineid/machineidv1/spiffe_federation_service.go new file mode 100644 index 0000000000000..c6c0e54a714a7 --- /dev/null +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service.go @@ -0,0 +1,245 @@ +// 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 . + +package machineidv1 + +import ( + "context" + "log/slog" + + "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" + "google.golang.org/protobuf/types/known/emptypb" + + "github.com/gravitational/teleport" + machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" + apievents "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/authz" + "github.com/gravitational/teleport/lib/events" +) + +type spiffeFederationReader interface { + ListSPIFFEFederations(ctx context.Context, limit int, token string) ([]*machineidv1.SPIFFEFederation, string, error) + GetSPIFFEFederation(ctx context.Context, name string) (*machineidv1.SPIFFEFederation, error) +} + +type spiffeFederationReadWriter interface { + spiffeFederationReader + CreateSPIFFEFederation(ctx context.Context, federation *machineidv1.SPIFFEFederation) (*machineidv1.SPIFFEFederation, error) + DeleteSPIFFEFederation(ctx context.Context, name string) error +} + +// SPIFFEFederationServiceConfig holds configuration options for +// NewSPIFFEFederationService +type SPIFFEFederationServiceConfig struct { + // Authorizer is the authorizer service which checks access to resources. + Authorizer authz.Authorizer + // Backend will be used reading and writing the SPIFFE Federation resources. + Backend spiffeFederationReadWriter + // Cache will be used when reading SPIFFE Federation resources. + Cache spiffeFederationReader + // Clock is the clock instance to use. Useful for injecting in tests. + Clock clockwork.Clock + // Emitter is the event emitter to use when emitting audit events. + Emitter apievents.Emitter + // Logger is the logger instance to use. + Logger *slog.Logger +} + +// NewSPIFFEFederationService returns a new instance of the SPIFFEFederationService. +func NewSPIFFEFederationService( + cfg SPIFFEFederationServiceConfig, +) (*SPIFFEFederationService, error) { + switch { + case cfg.Backend == nil: + return nil, trace.BadParameter("backend service is required") + case cfg.Cache == nil: + return nil, trace.BadParameter("cache service is required") + case cfg.Authorizer == nil: + return nil, trace.BadParameter("authorizer is required") + case cfg.Emitter == nil: + return nil, trace.BadParameter("emitter is required") + } + + if cfg.Logger == nil { + cfg.Logger = slog.With(teleport.ComponentKey, "spiffe_federation.service") + } + if cfg.Clock == nil { + cfg.Clock = clockwork.NewRealClock() + } + + return &SPIFFEFederationService{ + authorizer: cfg.Authorizer, + backend: cfg.Backend, + cache: cfg.Cache, + clock: cfg.Clock, + emitter: cfg.Emitter, + logger: cfg.Logger, + }, nil +} + +// SPIFFEFederationService is an implementation of +// teleport.machineid.v1.SPIFFEFederationService +type SPIFFEFederationService struct { + machineidv1.UnimplementedSPIFFEFederationServiceServer + + authorizer authz.Authorizer + backend spiffeFederationReadWriter + cache spiffeFederationReader + clock clockwork.Clock + emitter apievents.Emitter + logger *slog.Logger +} + +// GetSPIFFEFederation returns a SPIFFE Federation by name. +// Implements teleport.machineid.v1.SPIFFEFederationService/GetSPIFFEFederation +func (s *SPIFFEFederationService) GetSPIFFEFederation( + ctx context.Context, req *machineidv1.GetSPIFFEFederationRequest, +) (*machineidv1.SPIFFEFederation, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindSPIFFEFederation, types.VerbRead); err != nil { + return nil, trace.Wrap(err) + } + + if req.Name == "" { + return nil, trace.BadParameter("name: must be non-empty") + } + + federation, err := s.cache.GetSPIFFEFederation(ctx, req.Name) + if err != nil { + return nil, trace.Wrap(err) + } + + return federation, nil +} + +// ListSPIFFEFederations returns a list of SPIFFE Federations. It follows the +// Google API design guidelines for list pagination. +// Implements teleport.machineid.v1.SPIFFEFederationService/ListSPIFFEFederations +func (s *SPIFFEFederationService) ListSPIFFEFederations( + ctx context.Context, req *machineidv1.ListSPIFFEFederationsRequest, +) (*machineidv1.ListSPIFFEFederationsResponse, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindSPIFFEFederation, types.VerbRead, types.VerbList); err != nil { + return nil, trace.Wrap(err) + } + + federations, nextToken, err := s.cache.ListSPIFFEFederations( + ctx, + int(req.PageSize), + req.PageToken, + ) + if err != nil { + return nil, trace.Wrap(err) + } + + return &machineidv1.ListSPIFFEFederationsResponse{ + SpiffeFederations: federations, + NextPageToken: nextToken, + }, nil +} + +// DeleteSPIFFEFederation deletes a SPIFFE Federation by name. +// Implements teleport.machineid.v1.SPIFFEFederationService/DeleteSPIFFEFederation +func (s *SPIFFEFederationService) DeleteSPIFFEFederation( + ctx context.Context, req *machineidv1.DeleteSPIFFEFederationRequest, +) (*emptypb.Empty, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindSPIFFEFederation, types.VerbDelete); err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + if req.Name == "" { + return nil, trace.BadParameter("name: must be non-empty") + } + + if err := s.backend.DeleteSPIFFEFederation(ctx, req.Name); err != nil { + return nil, trace.Wrap(err) + } + + if err := s.emitter.EmitAuditEvent(ctx, &apievents.SPIFFEFederationDelete{ + Metadata: apievents.Metadata{ + Code: events.SPIFFEFederationDeleteCode, + Type: events.SPIFFEFederationDeleteEvent, + }, + UserMetadata: authz.ClientUserMetadata(ctx), + ConnectionMetadata: authz.ConnectionMetadata(ctx), + ResourceMetadata: apievents.ResourceMetadata{ + Name: req.Name, + }, + }); err != nil { + s.logger.ErrorContext( + ctx, "Failed to emit audit event for deletion of SPIFFEFederation", + "error", err, + ) + } + + return &emptypb.Empty{}, nil +} + +// CreateSPIFFEFederation creates a new SPIFFE Federation. +// Implements teleport.machineid.v1.SPIFFEFederationService/CreateSPIFFEFederation +func (s *SPIFFEFederationService) CreateSPIFFEFederation( + ctx context.Context, req *machineidv1.CreateSPIFFEFederationRequest, +) (*machineidv1.SPIFFEFederation, error) { + authCtx, err := s.authorizer.Authorize(ctx) + if err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.CheckAccessToKind(types.KindSPIFFEFederation, types.VerbCreate); err != nil { + return nil, trace.Wrap(err) + } + if err := authCtx.AuthorizeAdminAction(); err != nil { + return nil, trace.Wrap(err) + } + + created, err := s.backend.CreateSPIFFEFederation(ctx, req.SpiffeFederation) + if err != nil { + return nil, trace.Wrap(err) + } + + if err := s.emitter.EmitAuditEvent(ctx, &apievents.SPIFFEFederationCreate{ + Metadata: apievents.Metadata{ + Code: events.SPIFFEFederationCreateCode, + Type: events.SPIFFEFederationCreateEvent, + }, + UserMetadata: authz.ClientUserMetadata(ctx), + ConnectionMetadata: authz.ConnectionMetadata(ctx), + ResourceMetadata: apievents.ResourceMetadata{ + Name: req.SpiffeFederation.Metadata.Name, + }, + }); err != nil { + s.logger.ErrorContext( + ctx, "Failed to emit audit event for creation of SPIFFEFederation", + "error", err, + ) + } + + return created, nil +} From 6fd0d44ecb7fe3179d4d8ee77c4cc3679f7388b4 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 15:14:01 +0100 Subject: [PATCH 17/27] Add support into tctl for create/delete/get/list --- tool/tctl/common/collection.go | 35 +++++++++++++++++ tool/tctl/common/resource_command.go | 58 ++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index 3a1466b9fd4e8..db646f9d41c9f 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -1698,3 +1698,38 @@ func (c *botInstanceCollection) writeText(w io.Writer, verbose bool) error { _, err := t.AsBuffer().WriteTo(w) return trace.Wrap(err) } + +type spiffeFederationCollection struct { + items []*machineidv1pb.SPIFFEFederation +} + +func (c *spiffeFederationCollection) resources() []types.Resource { + r := make([]types.Resource, 0, len(c.items)) + for _, resource := range c.items { + r = append(r, types.Resource153ToLegacy(resource)) + } + return r +} + +func (c *spiffeFederationCollection) writeText(w io.Writer, verbose bool) error { + headers := []string{"Name", "Last synced at"} + + var rows [][]string + for _, item := range c.items { + lastSynced := "never" + if !item.GetStatus().GetCurrentBundleSyncedAt().AsTime().IsZero() { + lastSynced = item.Status.CurrentBundleSyncedAt.AsTime().Format(time.RFC3339) + } + rows = append(rows, []string{ + item.Metadata.Name, + lastSynced, + }) + } + + t := asciitable.MakeTable(headers, rows...) + + // stable sort by name. + t.SortRowsBy([]int{0}, true) + _, err := t.AsBuffer().WriteTo(w) + return trace.Wrap(err) +} diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 364d5795639b1..60720bce1b2a9 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -165,6 +165,7 @@ func (rc *ResourceCommand) Initialize(app *kingpin.Application, config *servicec types.KindVnetConfig: rc.createVnetConfig, types.KindAccessGraphSettings: rc.upsertAccessGraphSettings, types.KindPlugin: rc.createPlugin, + types.KindSPIFFEFederation: rc.createSPIFFEFederation, } rc.UpdateHandlers = map[ResourceKind]ResourceCreateHandler{ types.KindUser: rc.updateUser, @@ -959,6 +960,23 @@ func (rc *ResourceCommand) createCrownJewel(ctx context.Context, client *authcli return nil } +func (rc *ResourceCommand) createSPIFFEFederation(ctx context.Context, client *authclient.Client, raw services.UnknownResource) error { + in, err := services.UnmarshalSPIFFEFederation(raw.Raw) + if err != nil { + return trace.Wrap(err) + } + + c := client.SPIFFEFederationServiceClient() + if _, err := c.CreateSPIFFEFederation(ctx, &machineidv1pb.CreateSPIFFEFederationRequest{ + SpiffeFederation: in, + }); err != nil { + return trace.Wrap(err) + } + fmt.Printf("SPIFFE Federation %q has been created\n", in.GetMetadata().GetName()) + + return nil +} + func (rc *ResourceCommand) updateCrownJewel(ctx context.Context, client *authclient.Client, resource services.UnknownResource) error { in, err := services.UnmarshalCrownJewel(resource.Raw) if err != nil { @@ -1785,6 +1803,15 @@ func (rc *ResourceCommand) Delete(ctx context.Context, client *authclient.Client return trace.Wrap(err) } fmt.Printf("Access monitoring rule %q has been deleted\n", rc.ref.Name) + case types.KindSPIFFEFederation: + if _, err := client.SPIFFEFederationServiceClient().DeleteSPIFFEFederation( + ctx, &machineidv1pb.DeleteSPIFFEFederationRequest{ + Name: rc.ref.Name, + }, + ); err != nil { + return trace.Wrap(err) + } + fmt.Printf("SPIFFE federation %q has been deleted\n", rc.ref.Name) default: return trace.BadParameter("deleting resources of type %q is not supported", rc.ref.Kind) } @@ -2833,6 +2860,37 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient return nil, trace.Wrap(err) } return &accessGraphSettings{accessGraphSettings: rec}, nil + case types.KindSPIFFEFederation: + if rc.ref.Name != "" { + resource, err := client.SPIFFEFederationServiceClient().GetSPIFFEFederation(ctx, &machineidv1pb.GetSPIFFEFederationRequest{ + Name: rc.ref.Name, + }) + if err != nil { + return nil, trace.Wrap(err) + } + return &spiffeFederationCollection{items: []*machineidv1pb.SPIFFEFederation{resource}}, nil + } + + var resources []*machineidv1pb.SPIFFEFederation + pageToken := "" + for { + resp, err := client.SPIFFEFederationServiceClient().ListSPIFFEFederations(ctx, &machineidv1pb.ListSPIFFEFederationsRequest{ + PageSize: 100, + PageToken: pageToken, + }) + if err != nil { + return nil, trace.Wrap(err) + } + + resources = append(resources, resp.SpiffeFederations...) + + if resp.NextPageToken == "" { + break + } + pageToken = resp.NextPageToken + } + + return &spiffeFederationCollection{items: resources}, nil case types.KindBotInstance: if rc.ref.Name != "" && rc.ref.SubKind != "" { // Gets a specific bot instance, e.g. bot_instance// From c9ea40b305ebd6ede35c9199af8bde326825cafd Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 15:14:40 +0100 Subject: [PATCH 18/27] Rely on default page size --- tool/tctl/common/resource_command.go | 1 - 1 file changed, 1 deletion(-) diff --git a/tool/tctl/common/resource_command.go b/tool/tctl/common/resource_command.go index 60720bce1b2a9..be0e740276922 100644 --- a/tool/tctl/common/resource_command.go +++ b/tool/tctl/common/resource_command.go @@ -2875,7 +2875,6 @@ func (rc *ResourceCommand) getCollection(ctx context.Context, client *authclient pageToken := "" for { resp, err := client.SPIFFEFederationServiceClient().ListSPIFFEFederations(ctx, &machineidv1pb.ListSPIFFEFederationsRequest{ - PageSize: 100, PageToken: pageToken, }) if err != nil { From 41f0673118200b07b54e88a56cd54685c4874467 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 8 Aug 2024 16:56:42 +0100 Subject: [PATCH 19/27] Prevent configuration of status field --- .../machineid/machineidv1/spiffe_federation_service.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service.go b/lib/auth/machineid/machineidv1/spiffe_federation_service.go index c6c0e54a714a7..62d4290c922b8 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service.go @@ -22,6 +22,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" "github.com/gravitational/teleport" @@ -219,6 +220,12 @@ func (s *SPIFFEFederationService) CreateSPIFFEFederation( return nil, trace.Wrap(err) } + if status := req.GetSpiffeFederation().GetStatus(); status != nil { + if !proto.Equal(status, &machineidv1.SPIFFEFederationStatus{}) { + return nil, trace.BadParameter("status: cannot be set") + } + } + created, err := s.backend.CreateSPIFFEFederation(ctx, req.SpiffeFederation) if err != nil { return nil, trace.Wrap(err) From 70d3143694b87956425b6a90581edd200bee33fe Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 9 Aug 2024 11:55:01 +0100 Subject: [PATCH 20/27] Add test for TestSPIFFEFederationService_CreateSPIFFEFederation --- .../machineid/machineidv1/machineidv1_test.go | 22 +- .../spiffe_federation_service_test.go | 219 ++++++++++++++++++ .../workload_identity_service_test.go | 2 +- 3 files changed, 233 insertions(+), 10 deletions(-) create mode 100644 lib/auth/machineid/machineidv1/spiffe_federation_service_test.go diff --git a/lib/auth/machineid/machineidv1/machineidv1_test.go b/lib/auth/machineid/machineidv1/machineidv1_test.go index 6e66704c674ee..285807f402d97 100644 --- a/lib/auth/machineid/machineidv1/machineidv1_test.go +++ b/lib/auth/machineid/machineidv1/machineidv1_test.go @@ -42,6 +42,7 @@ import ( "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1" + "github.com/gravitational/teleport/lib/events/eventstest" "github.com/gravitational/teleport/lib/modules" ) @@ -66,7 +67,7 @@ func TestBotResourceName(t *testing.T) { // TestCreateBot is an integration test that uses a real gRPC client/server. func TestCreateBot(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botCreator, _, err := auth.CreateUserAndRole( @@ -477,7 +478,7 @@ func TestCreateBot(t *testing.T) { // TestUpdateBot is an integration test that uses a real gRPC client/server. func TestUpdateBot(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botUpdaterUser, _, err := auth.CreateUserAndRole(srv.Auth(), "bot-updater", []string{}, []types.Rule{ @@ -840,7 +841,7 @@ func TestUpdateBot(t *testing.T) { // TestUpsertBot is an integration test that uses a real gRPC client/server. func TestUpsertBot(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botCreator, _, err := auth.CreateUserAndRole(srv.Auth(), "bot-creator", []string{}, []types.Rule{ @@ -1268,7 +1269,7 @@ func TestUpsertBot(t *testing.T) { // TestGetBot is an integration test that uses a real gRPC client/server. func TestGetBot(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botGetterUser, _, err := auth.CreateUserAndRole( @@ -1379,7 +1380,7 @@ func TestGetBot(t *testing.T) { // TestListBots is an integration test that uses a real gRPC client/server. func TestListBots(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botListerUser, _, err := auth.CreateUserAndRole( @@ -1490,7 +1491,7 @@ func TestListBots(t *testing.T) { // TestDeleteBot is an integration test that uses a real gRPC client/server. func TestDeleteBot(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() botDeleterUser, _, err := auth.CreateUserAndRole( @@ -1630,14 +1631,17 @@ func TestDeleteBot(t *testing.T) { } } -func newTestTLSServer(t testing.TB) *auth.TestTLSServer { +func newTestTLSServer(t testing.TB) (*auth.TestTLSServer, *eventstest.MockRecorderEmitter) { as, err := auth.NewTestAuthServer(auth.TestAuthServerConfig{ Dir: t.TempDir(), Clock: clockwork.NewFakeClockAt(time.Now().Round(time.Second).UTC()), }) require.NoError(t, err) - srv, err := as.NewTestTLSServer() + emitter := &eventstest.MockRecorderEmitter{} + srv, err := as.NewTestTLSServer(func(config *auth.TestTLSServerConfig) { + config.APIConfig.Emitter = emitter + }) require.NoError(t, err) t.Cleanup(func() { @@ -1648,5 +1652,5 @@ func newTestTLSServer(t testing.TB) *auth.TestTLSServer { require.NoError(t, err) }) - return srv + return srv, emitter } diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go new file mode 100644 index 0000000000000..4056074bb919e --- /dev/null +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -0,0 +1,219 @@ +/* + * 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 . + */ + +package machineidv1_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" + "github.com/gravitational/trace" + "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/testing/protocmp" + "google.golang.org/protobuf/types/known/timestamppb" + + headerv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/header/v1" + machineidv1pb "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/api/types/events" + "github.com/gravitational/teleport/lib/auth" + libevents "github.com/gravitational/teleport/lib/events" +) + +// TestSPIFFEFederationService_CreateSPIFFEFederation is an integration test +// that uses a real gRPC client/server. +func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { + t.Parallel() + srv, mockEmitter := newTestTLSServer(t) + ctx := context.Background() + + nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) + require.NoError(t, err) + unauthorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "unauthorized", + // Nothing role necessary as otherwise authz engine gets confused. + nothingRole, + ) + require.NoError(t, err) + + role, err := types.NewRole("federation-creator", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: []types.Rule{ + { + Resources: []string{types.KindSPIFFEFederation}, + Verbs: []string{types.VerbCreate}, + }, + }, + }, + }) + authorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "authorized", + // Nothing role necessary as otherwise authz engine gets confused. + role, + ) + require.NoError(t, err) + + good := &machineidv1pb.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: "example.com", + }, + Spec: &machineidv1pb.SPIFFEFederationSpec{ + BundleSource: &machineidv1pb.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1pb.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + } + + tests := []struct { + name string + user string + req *machineidv1pb.CreateSPIFFEFederationRequest + requireError require.ErrorAssertionFunc + requireSuccess bool + requireEvent *events.SPIFFEFederationCreate + }{ + { + name: "success", + user: authorizedUser.GetName(), + req: &machineidv1pb.CreateSPIFFEFederationRequest{ + SpiffeFederation: good, + }, + requireError: require.NoError, + requireSuccess: true, + requireEvent: &events.SPIFFEFederationCreate{ + Metadata: events.Metadata{ + Type: libevents.SPIFFEFederationCreateEvent, + Code: libevents.SPIFFEFederationCreateCode, + }, + ResourceMetadata: events.ResourceMetadata{ + Name: "example.com", + }, + UserMetadata: events.UserMetadata{ + User: authorizedUser.GetName(), + UserKind: events.UserKind_USER_KIND_HUMAN, + }, + }, + }, + { + name: "unable to set status", + user: authorizedUser.GetName(), + req: &machineidv1pb.CreateSPIFFEFederationRequest{ + SpiffeFederation: func() *machineidv1pb.SPIFFEFederation { + fed := proto.Clone(good).(*machineidv1pb.SPIFFEFederation) + fed.Status = &machineidv1pb.SPIFFEFederationStatus{ + CurrentBundleSyncedAt: timestamppb.Now(), + } + return fed + }(), + }, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsBadParameter(err)) + require.ErrorContains(t, err, "status: cannot be set") + }, + }, + { + name: "validation is run", + user: authorizedUser.GetName(), + req: &machineidv1pb.CreateSPIFFEFederationRequest{ + SpiffeFederation: func() *machineidv1pb.SPIFFEFederation { + fed := proto.Clone(good).(*machineidv1pb.SPIFFEFederation) + fed.Metadata.Name = "spiffe://im----invalid" + return fed + }(), + }, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsBadParameter(err)) + require.ErrorContains(t, err, "metadata.name: must not include the spiffe:// prefix") + }, + }, + { + name: "unauthorized", + user: unauthorizedUser.GetName(), + req: &machineidv1pb.CreateSPIFFEFederationRequest{ + SpiffeFederation: good, + }, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := srv.NewClient(auth.TestUser(tt.user)) + require.NoError(t, err) + + mockEmitter.Reset() + got, err := client.SPIFFEFederationServiceClient().CreateSPIFFEFederation(ctx, tt.req) + tt.requireError(t, err) + if tt.requireSuccess { + // First check the response object matches our requested object. + require.Empty( + t, + cmp.Diff( + tt.req.SpiffeFederation, + got, + protocmp.Transform(), + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + ), + ) + + // Then check the response is actually stored in the backend + got, err := srv.Auth().Services.SPIFFEFederations.GetSPIFFEFederation( + ctx, got.Metadata.GetName(), + ) + require.NoError(t, err) + require.Empty( + t, + cmp.Diff( + tt.req.SpiffeFederation, + got, + protocmp.Transform(), + protocmp.IgnoreFields(&headerv1.Metadata{}, "revision"), + ), + ) + } + // Now we can ensure that the appropriate audit event was + // generated. + if tt.requireEvent != nil { + evt, ok := mockEmitter.LastEvent().(*events.SPIFFEFederationCreate) + require.True(t, ok) + require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) + require.Empty(t, cmp.Diff( + evt, + tt.requireEvent, + cmpopts.IgnoreFields(events.SPIFFEFederationCreate{}, "ConnectionMetadata"), + )) + } + }) + } +} diff --git a/lib/auth/machineid/machineidv1/workload_identity_service_test.go b/lib/auth/machineid/machineidv1/workload_identity_service_test.go index b05d5d394bfa1..7216f99f635a9 100644 --- a/lib/auth/machineid/machineidv1/workload_identity_service_test.go +++ b/lib/auth/machineid/machineidv1/workload_identity_service_test.go @@ -38,7 +38,7 @@ import ( // real gRPC client/server. func TestWorkloadIdentityService_SignX509SVIDs(t *testing.T) { t.Parallel() - srv := newTestTLSServer(t) + srv, _ := newTestTLSServer(t) ctx := context.Background() nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) From 17dffdb629a9811af2ad8387dc2ece11046b9453 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 9 Aug 2024 12:25:02 +0100 Subject: [PATCH 21/27] Add TestSPIFFEFederationService_DeleteSPIFFEFederation --- .../spiffe_federation_service_test.go | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go index 4056074bb919e..3c581dc5c14d8 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -217,3 +217,145 @@ func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { }) } } + +// TestSPIFFEFederationService_DeleteSPIFFEFederation is an integration test +// that uses a real gRPC client/server. +func TestSPIFFEFederationService_DeleteSPIFFEFederation(t *testing.T) { + t.Parallel() + srv, mockEmitter := newTestTLSServer(t) + ctx := context.Background() + + nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) + require.NoError(t, err) + unauthorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "unauthorized", + // Nothing role necessary as otherwise authz engine gets confused. + nothingRole, + ) + require.NoError(t, err) + + role, err := types.NewRole("federation-deleter", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: []types.Rule{ + { + Resources: []string{types.KindSPIFFEFederation}, + Verbs: []string{types.VerbDelete}, + }, + }, + }, + }) + authorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "authorized", + // Nothing role necessary as otherwise authz engine gets confused. + role, + ) + require.NoError(t, err) + + name := "example.com" + + tests := []struct { + name string + user string + create bool + requireError require.ErrorAssertionFunc + requireSuccess bool + requireEvent *events.SPIFFEFederationDelete + }{ + { + name: "success", + user: authorizedUser.GetName(), + create: true, + requireError: require.NoError, + requireSuccess: true, + requireEvent: &events.SPIFFEFederationDelete{ + Metadata: events.Metadata{ + Type: libevents.SPIFFEFederationDeleteEvent, + Code: libevents.SPIFFEFederationDeleteCode, + }, + ResourceMetadata: events.ResourceMetadata{ + Name: name, + }, + UserMetadata: events.UserMetadata{ + User: authorizedUser.GetName(), + UserKind: events.UserKind_USER_KIND_HUMAN, + }, + }, + }, + { + name: "not-exist", + user: authorizedUser.GetName(), + create: false, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }, + }, + { + name: "unauthorized", + user: unauthorizedUser.GetName(), + create: true, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := srv.NewClient(auth.TestUser(tt.user)) + require.NoError(t, err) + + resource := &machineidv1pb.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: name, + }, + Spec: &machineidv1pb.SPIFFEFederationSpec{ + BundleSource: &machineidv1pb.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1pb.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + } + + if tt.create { + _, err := srv.Auth().Services.SPIFFEFederations.CreateSPIFFEFederation( + ctx, resource, + ) + require.NoError(t, err) + } + + mockEmitter.Reset() + _, err = client.SPIFFEFederationServiceClient().DeleteSPIFFEFederation(ctx, &machineidv1pb.DeleteSPIFFEFederationRequest{ + Name: resource.Metadata.GetName(), + }) + tt.requireError(t, err) + if tt.requireSuccess { + // Check that it is no longer in the backend + _, err := srv.Auth().Services.SPIFFEFederations.GetSPIFFEFederation( + ctx, resource.Metadata.GetName(), + ) + require.True(t, trace.IsNotFound(err)) + } + // Now we can ensure that the appropriate audit event was + // generated. + if tt.requireEvent != nil { + evt, ok := mockEmitter.LastEvent().(*events.SPIFFEFederationDelete) + require.True(t, ok) + require.NotEmpty(t, evt.ConnectionMetadata.RemoteAddr) + require.Empty(t, cmp.Diff( + evt, + tt.requireEvent, + cmpopts.IgnoreFields(events.SPIFFEFederationDelete{}, "ConnectionMetadata"), + )) + } + }) + } +} From 1d53301d06acd6839d9653db286447f3adc6ba95 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 9 Aug 2024 12:46:28 +0100 Subject: [PATCH 22/27] Add test for TestSPIFFEFederationService_GetSPIFFEFederation --- .../spiffe_federation_service_test.go | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go index 3c581dc5c14d8..d4ad087c642e2 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -359,3 +359,116 @@ func TestSPIFFEFederationService_DeleteSPIFFEFederation(t *testing.T) { }) } } + +// TestSPIFFEFederationService_GetSPIFFEFederation is an integration test +// that uses a real gRPC client/server. +func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { + t.Parallel() + srv, _ := newTestTLSServer(t) + ctx := context.Background() + + nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) + require.NoError(t, err) + unauthorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "unauthorized", + // Nothing role necessary as otherwise authz engine gets confused. + nothingRole, + ) + require.NoError(t, err) + + role, err := types.NewRole("federation-reader", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: []types.Rule{ + { + Resources: []string{types.KindSPIFFEFederation}, + Verbs: []string{types.VerbRead}, + }, + }, + }, + }) + authorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "authorized", + // Nothing role necessary as otherwise authz engine gets confused. + role, + ) + require.NoError(t, err) + + name := "example.com" + resource, err := srv.Auth().Services.SPIFFEFederations.CreateSPIFFEFederation( + ctx, &machineidv1pb.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: name, + }, + Spec: &machineidv1pb.SPIFFEFederationSpec{ + BundleSource: &machineidv1pb.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1pb.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + }, + ) + require.NoError(t, err) + + tests := []struct { + name string + user string + getName string + requireError require.ErrorAssertionFunc + requireSuccess bool + }{ + { + name: "success", + user: authorizedUser.GetName(), + getName: name, + requireError: require.NoError, + requireSuccess: true, + }, + { + name: "not-exist", + user: authorizedUser.GetName(), + getName: "do-not-exist", + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsNotFound(err)) + }, + }, + { + name: "unauthorized", + user: unauthorizedUser.GetName(), + getName: name, + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := srv.NewClient(auth.TestUser(tt.user)) + require.NoError(t, err) + + got, err := client.SPIFFEFederationServiceClient().GetSPIFFEFederation(ctx, &machineidv1pb.GetSPIFFEFederationRequest{ + Name: tt.getName, + }) + tt.requireError(t, err) + if tt.requireSuccess { + require.Empty( + t, + cmp.Diff( + resource, + got, + protocmp.Transform(), + ), + ) + } + }) + } +} From 67fb15a0c582d64d0217d49603a4c79cf939f135 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 9 Aug 2024 13:01:04 +0100 Subject: [PATCH 23/27] Add TestSPIFFEFederationService_ListSPIFFEFederations --- .../spiffe_federation_service_test.go | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go index d4ad087c642e2..71ffaffdda9ba 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -20,6 +20,8 @@ package machineidv1_test import ( "context" + "fmt" + "slices" "testing" "github.com/google/go-cmp/cmp" @@ -472,3 +474,135 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { }) } } + +// TestSPIFFEFederationService_ListSPIFFEFederations is an integration test +// that uses a real gRPC client/server. +func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { + t.Parallel() + srv, _ := newTestTLSServer(t) + ctx := context.Background() + + nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) + require.NoError(t, err) + unauthorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "unauthorized", + // Nothing role necessary as otherwise authz engine gets confused. + nothingRole, + ) + require.NoError(t, err) + + role, err := types.NewRole("federation-reader", types.RoleSpecV6{ + Allow: types.RoleConditions{ + Rules: []types.Rule{ + { + Resources: []string{types.KindSPIFFEFederation}, + Verbs: []string{types.VerbRead, types.VerbList}, + }, + }, + }, + }) + authorizedUser, err := auth.CreateUser( + ctx, + srv.Auth(), + "authorized", + // Nothing role necessary as otherwise authz engine gets confused. + role, + ) + require.NoError(t, err) + + // Create entities to list + createdObjects := []*machineidv1pb.SPIFFEFederation{} + // Create 49 entities to test an incomplete page at the end. + for i := 0; i < 49; i++ { + created, err := srv.AuthServer.AuthServer.Services.SPIFFEFederations.CreateSPIFFEFederation( + ctx, + &machineidv1pb.SPIFFEFederation{ + Kind: types.KindSPIFFEFederation, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Name: fmt.Sprintf("%d.example.com", i), + }, + Spec: &machineidv1pb.SPIFFEFederationSpec{ + BundleSource: &machineidv1pb.SPIFFEFederationBundleSource{ + HttpsWeb: &machineidv1pb.SPIFFEFederationBundleSourceHTTPSWeb{ + BundleEndpointUrl: "https://example.com/bundle.json", + }, + }, + }, + }, + ) + require.NoError(t, err) + createdObjects = append(createdObjects, created) + } + + tests := []struct { + name string + user string + pageSize int + wantIterations int + requireError require.ErrorAssertionFunc + assertResponse bool + }{ + { + name: "success - one page", + user: authorizedUser.GetName(), + wantIterations: 1, + requireError: require.NoError, + assertResponse: true, + }, + { + name: "success - small pages", + pageSize: 10, + wantIterations: 5, + user: authorizedUser.GetName(), + requireError: require.NoError, + assertResponse: true, + }, + { + name: "unauthorized", + user: unauthorizedUser.GetName(), + requireError: func(t require.TestingT, err error, i ...interface{}) { + require.Error(t, err) + require.True(t, trace.IsAccessDenied(err)) + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + client, err := srv.NewClient(auth.TestUser(tt.user)) + require.NoError(t, err) + + fetched := []*machineidv1pb.SPIFFEFederation{} + token := "" + iterations := 0 + for { + iterations++ + resp, err := client.SPIFFEFederationServiceClient().ListSPIFFEFederations(ctx, &machineidv1pb.ListSPIFFEFederationsRequest{ + PageSize: int32(tt.pageSize), + PageToken: token, + }) + tt.requireError(t, err) + if err != nil { + return + } + fetched = append(fetched, resp.SpiffeFederations...) + if resp.NextPageToken == "" { + break + } + token = resp.NextPageToken + } + if tt.assertResponse { + require.Equal(t, tt.wantIterations, iterations) + require.Len(t, fetched, 49) + for _, created := range createdObjects { + slices.ContainsFunc(fetched, func(federation *machineidv1pb.SPIFFEFederation) bool { + return proto.Equal(created, federation) + }) + } + } + }) + } +} From 9e3be3ccb97104a8c9e83baf4be75b900fc0f2d7 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Fri, 9 Aug 2024 13:03:07 +0100 Subject: [PATCH 24/27] Add mising error asserrtiion --- .../machineid/machineidv1/spiffe_federation_service_test.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go index 71ffaffdda9ba..d581d1c78a8f1 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -68,6 +68,7 @@ func TestSPIFFEFederationService_CreateSPIFFEFederation(t *testing.T) { }, }, }) + require.NoError(t, err) authorizedUser, err := auth.CreateUser( ctx, srv.Auth(), @@ -248,6 +249,7 @@ func TestSPIFFEFederationService_DeleteSPIFFEFederation(t *testing.T) { }, }, }) + require.NoError(t, err) authorizedUser, err := auth.CreateUser( ctx, srv.Auth(), @@ -390,6 +392,7 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { }, }, }) + require.NoError(t, err) authorizedUser, err := auth.CreateUser( ctx, srv.Auth(), @@ -503,6 +506,7 @@ func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { }, }, }) + require.NoError(t, err) authorizedUser, err := auth.CreateUser( ctx, srv.Auth(), From 8e543f74441f2bcea0bf5dcc6a608fa8a2e4610f Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Mon, 12 Aug 2024 09:58:07 +0100 Subject: [PATCH 25/27] Add Verbs to default roles for SPIFFEFederation --- lib/services/presets.go | 1 + lib/services/role.go | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/services/presets.go b/lib/services/presets.go index b24c51af4d3dc..bbb2bb5abc696 100644 --- a/lib/services/presets.go +++ b/lib/services/presets.go @@ -178,6 +178,7 @@ func NewPresetEditorRole() types.Role { types.NewRule(types.KindVnetConfig, RW()), types.NewRule(types.KindBotInstance, RW()), types.NewRule(types.KindAccessGraphSettings, RW()), + types.NewRule(types.KindSPIFFEFederation, RW()), }, }, }, diff --git a/lib/services/role.go b/lib/services/role.go index 21589e2b41cc3..2be85d47b8155 100644 --- a/lib/services/role.go +++ b/lib/services/role.go @@ -76,6 +76,7 @@ var DefaultImplicitRules = []types.Rule{ types.NewRule(types.KindKubernetesCluster, RO()), types.NewRule(types.KindUsageEvent, []string{types.VerbCreate}), types.NewRule(types.KindVnetConfig, RO()), + types.NewRule(types.KindSPIFFEFederation, RO()), } // DefaultCertAuthorityRules provides access the minimal set of resources From 95873515f4d306f25f00efc3a90aeb69b982e929 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Wed, 14 Aug 2024 14:21:55 +0100 Subject: [PATCH 26/27] Remove test cases that are now unneeded due to adding to implicit roleset --- .../spiffe_federation_service_test.go | 39 ------------------- 1 file changed, 39 deletions(-) diff --git a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go index d581d1c78a8f1..e69511523011b 100644 --- a/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go +++ b/lib/auth/machineid/machineidv1/spiffe_federation_service_test.go @@ -371,17 +371,6 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { srv, _ := newTestTLSServer(t) ctx := context.Background() - nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) - require.NoError(t, err) - unauthorizedUser, err := auth.CreateUser( - ctx, - srv.Auth(), - "unauthorized", - // Nothing role necessary as otherwise authz engine gets confused. - nothingRole, - ) - require.NoError(t, err) - role, err := types.NewRole("federation-reader", types.RoleSpecV6{ Allow: types.RoleConditions{ Rules: []types.Rule{ @@ -444,15 +433,6 @@ func TestSPIFFEFederationService_GetSPIFFEFederation(t *testing.T) { require.True(t, trace.IsNotFound(err)) }, }, - { - name: "unauthorized", - user: unauthorizedUser.GetName(), - getName: name, - requireError: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - require.True(t, trace.IsAccessDenied(err)) - }, - }, } for _, tt := range tests { @@ -485,17 +465,6 @@ func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { srv, _ := newTestTLSServer(t) ctx := context.Background() - nothingRole, err := types.NewRole("nothing", types.RoleSpecV6{}) - require.NoError(t, err) - unauthorizedUser, err := auth.CreateUser( - ctx, - srv.Auth(), - "unauthorized", - // Nothing role necessary as otherwise authz engine gets confused. - nothingRole, - ) - require.NoError(t, err) - role, err := types.NewRole("federation-reader", types.RoleSpecV6{ Allow: types.RoleConditions{ Rules: []types.Rule{ @@ -564,14 +533,6 @@ func TestSPIFFEFederationService_ListSPIFFEFederations(t *testing.T) { requireError: require.NoError, assertResponse: true, }, - { - name: "unauthorized", - user: unauthorizedUser.GetName(), - requireError: func(t require.TestingT, err error, i ...interface{}) { - require.Error(t, err) - require.True(t, trace.IsAccessDenied(err)) - }, - }, } for _, tt := range tests { From 293f95eadddf801bfd0d31d96fe2bdbdbe141b13 Mon Sep 17 00:00:00 2001 From: Noah Stride Date: Thu, 22 Aug 2024 09:06:16 +0100 Subject: [PATCH 27/27] Tidier if statement Co-authored-by: rosstimothy <39066650+rosstimothy@users.noreply.github.com> --- tool/tctl/common/collection.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tool/tctl/common/collection.go b/tool/tctl/common/collection.go index db646f9d41c9f..d74fcf21cd418 100644 --- a/tool/tctl/common/collection.go +++ b/tool/tctl/common/collection.go @@ -1717,8 +1717,8 @@ func (c *spiffeFederationCollection) writeText(w io.Writer, verbose bool) error var rows [][]string for _, item := range c.items { lastSynced := "never" - if !item.GetStatus().GetCurrentBundleSyncedAt().AsTime().IsZero() { - lastSynced = item.Status.CurrentBundleSyncedAt.AsTime().Format(time.RFC3339) + if t := item.GetStatus().GetCurrentBundleSyncedAt().AsTime(); !t.IsZero() { + lastSynced = t.Format(time.RFC3339) } rows = append(rows, []string{ item.Metadata.Name,