From e15e1d2d4938ce545ee37adf4bc9ecca1f91ac42 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Wed, 26 Jun 2024 18:43:16 -0600 Subject: [PATCH 1/9] Create Bot Instances during initial bot join This creates new instances for bots when they initially join the cluster, and persists instance IDs in new certificate fields on join and during renewal. Note that this does not yet handle instance reuse for non-token join methods. Additionally, bot instance creation is locked behind a `BOT_INSTANCE_EXPERIMENT` flag; it must be set to `1` to enable creation. --- constants.go | 4 ++ lib/auth/auth.go | 5 ++ lib/auth/auth_with_roles.go | 6 ++ lib/auth/bot.go | 43 +++++++++++- lib/auth/join.go | 65 ++++++++++++++----- lib/auth/keygen/keygen.go | 3 + .../bot_instance_experiment/experiment.go | 40 ++++++++++++ lib/services/authority.go | 3 + lib/srv/authhandlers.go | 3 + lib/srv/ctx.go | 4 ++ lib/tlsca/ca.go | 20 ++++++ 11 files changed, 177 insertions(+), 19 deletions(-) create mode 100644 lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go diff --git a/constants.go b/constants.go index fe3b19147053f..e37390fb87932 100644 --- a/constants.go +++ b/constants.go @@ -503,6 +503,10 @@ const ( // CertExtensionBotName indicates the name of the Machine ID bot this // certificate was issued to, if any. CertExtensionBotName = "bot-name@goteleport.com" + // CertExtensionBotInstanceID indicates the unique identifier of this + // Machine ID bot instance, if any. This identifier is persisted through + // certificate renewals. + CertExtensionBotInstanceID = "bot-instance-id@goteleport.com" // CertCriticalOptionSourceAddress is a critical option that defines IP addresses (in CIDR notation) // from which this certificate is accepted for authentication. diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 6e51346ee9a1d..a049c678ad224 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2000,6 +2000,9 @@ type certRequest struct { deviceExtensions DeviceExtensions // botName is the name of the bot requesting this cert, if any botName string + // botInstanceID is the unique identifier of the bot instance associated + // with this cert, if any + botInstanceID string } // check verifies the cert request is valid. @@ -2943,6 +2946,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. Renewable: req.renewable, Generation: req.generation, BotName: req.botName, + BotInstanceID: req.botInstanceID, CertificateExtensions: req.checker.CertificateExtensions(), AllowedResourceIDs: requestedResourcesStr, ConnectionDiagnosticID: req.connectionDiagnosticID, @@ -3038,6 +3042,7 @@ func generateCert(ctx context.Context, a *Server, req certRequest, caType types. Renewable: req.renewable, Generation: req.generation, BotName: req.botName, + BotInstanceID: req.botInstanceID, AllowedResourceIDs: req.checker.GetAllowedResourceIDs(), PrivateKeyPolicy: attestedKeyPolicy, ConnectionDiagnosticID: req.connectionDiagnosticID, diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index ceee92d0eb21b..65f0a74da81b2 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3251,6 +3251,12 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC connectionDiagnosticID: req.ConnectionDiagnosticID, attestationStatement: keys.AttestationStatementFromProto(req.AttestationStatement), botName: getBotName(user), + + // Always pass through a bot instance ID if available. Note that this + // method is only used for bot identity renewals and is not responsible + // for issuing new instance IDs; see `generateInitialBotCerts()` + // TODO: need to update bot instance with new authentication + generation counter + botInstanceID: a.context.Identity.GetIdentity().BotInstanceID, } if user.GetName() != a.context.User.GetName() { certReq.impersonator = a.context.User.GetName() diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 472d5f672e922..dabffa86421c2 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -28,9 +28,11 @@ import ( "github.com/gravitational/trace" "github.com/gravitational/teleport/api/client/proto" + 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" apiutils "github.com/gravitational/teleport/api/utils" + experiment "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/bot_instance_experiment" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/defaults" "github.com/gravitational/teleport/lib/events" @@ -168,6 +170,19 @@ func (a *Server) validateGenerationLabel(ctx context.Context, username string, c return nil } +// newBotInstance constructs a new bot instance from a spec and initial authentication +func newBotInstance(spec *machineidv1.BotInstanceSpec, initialAuth *machineidv1.BotInstanceStatusAuthentication) *machineidv1.BotInstance { + return &machineidv1.BotInstance{ + Kind: types.KindBotInstance, + Version: types.V1, + Spec: spec, + Status: &machineidv1.BotInstanceStatus{ + InitialAuthentication: initialAuth, + LatestAuthentications: []*machineidv1.BotInstanceStatusAuthentication{initialAuth}, + }, + } +} + // generateInitialBotCerts is used to generate bot certs and overlaps // significantly with `generateUserCerts()`. However, it omits a number of // options (impersonation, access requests, role requests, actual cert renewal, @@ -175,7 +190,10 @@ func (a *Server) validateGenerationLabel(ctx context.Context, username string, c // care if the current identity is Nop. This function does not validate the // current identity at all; the caller is expected to validate that the client // is allowed to issue the (possibly renewable) certificates. -func (a *Server) generateInitialBotCerts(ctx context.Context, botName, username, loginIP string, pubKey []byte, expires time.Time, renewable bool) (*proto.Certs, error) { +func (a *Server) generateInitialBotCerts( + ctx context.Context, botName, username, loginIP string, pubKey []byte, + expires time.Time, renewable bool, initialAuth *machineidv1.BotInstanceStatusAuthentication, +) (*proto.Certs, error) { var err error // Extract the user and role set for whom the certificate will be generated. @@ -216,6 +234,28 @@ func (a *Server) generateInitialBotCerts(ctx context.Context, botName, username, var generation uint64 if renewable { generation = 1 + initialAuth.Generation = 1 + } + + var botInstanceID string + if experiment.Enabled() { + uuid, err := uuid.NewRandom() + if err != nil { + return nil, trace.Wrap(err) + } + + bi := newBotInstance(&machineidv1.BotInstanceSpec{ + BotName: botName, + InstanceId: uuid.String(), + + // TODO: set TTL? should we rely on the known expiration instead? + // (auth may overwrite the value now or later) + }, initialAuth) + + _, err = a.BotInstance.CreateBotInstance(ctx, bi) + if err != nil { + return nil, trace.Wrap(err) + } } // Generate certificate @@ -230,6 +270,7 @@ func (a *Server) generateInitialBotCerts(ctx context.Context, botName, username, generation: generation, loginIP: loginIP, botName: botName, + botInstanceID: botInstanceID, } if err := a.validateGenerationLabel(ctx, userState.GetName(), &certReq, 0); err != nil { diff --git a/lib/auth/join.go b/lib/auth/join.go index e887f6fd922b5..1fa14a0e62dd4 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -21,7 +21,9 @@ package auth import ( "context" "crypto/rand" + "crypto/sha256" "encoding/base64" + "encoding/hex" "fmt" "net" "slices" @@ -30,8 +32,11 @@ import ( "github.com/gravitational/trace" "github.com/sirupsen/logrus" "google.golang.org/grpc/peer" + "google.golang.org/protobuf/types/known/structpb" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/client/proto" + machineidv1pb "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/auth/machineid/machineidv1" @@ -346,24 +351,7 @@ func (a *Server) generateCertsBot( expires = *req.Expires } - certs, err := a.generateInitialBotCerts( - ctx, botName, machineidv1.BotResourceName(botName), req.RemoteAddr, req.PublicSSHKey, expires, renewable, - ) - if err != nil { - return nil, trace.Wrap(err) - } - - if shouldDeleteToken { - // delete ephemeral bot join tokens so they can't be re-used - if err := a.DeleteToken(ctx, provisionToken.GetName()); err != nil { - log.WithError(err).Warnf("Could not delete bot provision token %q after generating certs", - provisionToken.GetSafeName(), - ) - } - } - - // Emit audit event for bot join. - log.Infof("Bot %q has joined the cluster.", botName) + // Construct a Join event to be sent later. joinEvent := &apievents.BotJoin{ Metadata: apievents.Metadata{ Type: events.BotJoinEvent, @@ -380,6 +368,22 @@ func (a *Server) generateCertsBot( RemoteAddr: req.RemoteAddr, }, } + + fingerprint := sha256.Sum256(req.PublicTLSKey) + auth := &machineidv1pb.BotInstanceStatusAuthentication{ + AuthenticatedAt: timestamppb.New(a.GetClock().Now()), + // TODO: GetSafeName may not return an appropriate value for later + // comparison / locking purposes, and this also shouldn't contain + // secrets. Should we hash it? + JoinToken: provisionToken.GetSafeName(), + JoinMethod: string(provisionToken.GetJoinMethod()), + PublicKey: req.PublicTLSKey, + Fingerprint: hex.EncodeToString(fingerprint[:]), + + // Note: Generation will be set during `generateInitialBotCerts()` as + // needed. + } + if joinAttributeSrc != nil { attributes, err := joinAttributeSrc.JoinAuditAttributes() if err != nil { @@ -389,7 +393,32 @@ func (a *Server) generateCertsBot( if err != nil { log.WithError(err).Warn("Unable to encode join attributes for audit event.") } + + auth.Metadata, err = structpb.NewStruct(attributes) + if err != nil { + log.WithError(err).Warn("Unable to encode struct value for join metadata.") + } } + + certs, err := a.generateInitialBotCerts( + ctx, botName, machineidv1.BotResourceName(botName), req.RemoteAddr, req.PublicSSHKey, expires, renewable, auth, + ) + if err != nil { + return nil, trace.Wrap(err) + } + + if shouldDeleteToken { + // delete ephemeral bot join tokens so they can't be re-used + if err := a.DeleteToken(ctx, provisionToken.GetName()); err != nil { + log.WithError(err).Warnf("Could not delete bot provision token %q after generating certs", + provisionToken.GetSafeName(), + ) + } + } + + // Emit audit event for bot join. + log.Infof("Bot %q has joined the cluster.", botName) + if err := a.emitter.EmitAuditEvent(ctx, joinEvent); err != nil { log.WithError(err).Warn("Failed to emit bot join event.") } diff --git a/lib/auth/keygen/keygen.go b/lib/auth/keygen/keygen.go index 8326febee7162..ce8a26dd78dfe 100644 --- a/lib/auth/keygen/keygen.go +++ b/lib/auth/keygen/keygen.go @@ -197,6 +197,9 @@ func (k *Keygen) GenerateUserCertWithoutValidation(c services.UserCertParams) ([ if c.BotName != "" { cert.Permissions.Extensions[teleport.CertExtensionBotName] = c.BotName } + if c.BotInstanceID != "" { + cert.Permissions.Extensions[teleport.CertExtensionBotInstanceID] = c.BotInstanceID + } if c.AllowedResourceIDs != "" { cert.Permissions.Extensions[teleport.CertExtensionAllowedResources] = c.AllowedResourceIDs } diff --git a/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go b/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go new file mode 100644 index 0000000000000..29a2f137fa8c6 --- /dev/null +++ b/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go @@ -0,0 +1,40 @@ +/* + * 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 experiment + +import ( + "os" + "sync/atomic" +) + +var enabled = atomic.Bool{} + +func init() { + enabled.Store(os.Getenv("BOT_INSTANCE_EXPERIMENT") == "1") +} + +// Enabled returns true if the workload identity experiment is enabled. +func Enabled() bool { + return enabled.Load() +} + +// SetEnabled sets the workload identity experiment to the given value. +func SetEnabled(value bool) { + enabled.Store(value) +} diff --git a/lib/services/authority.go b/lib/services/authority.go index d00d3d00fc3df..fdcc8d40820d2 100644 --- a/lib/services/authority.go +++ b/lib/services/authority.go @@ -380,6 +380,9 @@ type UserCertParams struct { // BotName is set to the name of the bot, if the user is a Machine ID bot user. // Empty for human users. BotName string + // BotInstanceID is the unique identifier for the bot instance, if this is a + // Machine ID bot. It is empty for human users. + BotInstanceID string // AllowedResourceIDs lists the resources the user should be able to access. AllowedResourceIDs string // ConnectionDiagnosticID references the ConnectionDiagnostic that we should use to append traces when testing a Connection. diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index 8404b02fc0ce9..d80f5704acfe4 100644 --- a/lib/srv/authhandlers.go +++ b/lib/srv/authhandlers.go @@ -209,6 +209,9 @@ func (h *AuthHandlers) CreateIdentityContext(sconn *ssh.ServerConn) (IdentityCon if botName, ok := certificate.Extensions[teleport.CertExtensionBotName]; ok { identity.BotName = botName } + if botInstanceID, ok := certificate.Extensions[teleport.CertExtensionBotInstanceID]; ok { + identity.BotInstanceID = botInstanceID + } if generationStr, ok := certificate.Extensions[teleport.CertExtensionGeneration]; ok { generation, err := strconv.ParseUint(generationStr, 10, 64) if err != nil { diff --git a/lib/srv/ctx.go b/lib/srv/ctx.go index cccf7e0f95ae6..f6f682eb61e8b 100644 --- a/lib/srv/ctx.go +++ b/lib/srv/ctx.go @@ -280,6 +280,10 @@ type IdentityContext struct { // with, if any. BotName string + // BotInstanceID is the unique identifier of the Machine ID bot instance + // this identity is associated with, if any. + BotInstanceID string + // AllowedResourceIDs lists the resources this identity should be allowed to // access AllowedResourceIDs []types.ResourceID diff --git a/lib/tlsca/ca.go b/lib/tlsca/ca.go index ff67923ae8ae6..151c0ef4aff72 100644 --- a/lib/tlsca/ca.go +++ b/lib/tlsca/ca.go @@ -184,6 +184,9 @@ type Identity struct { // BotName indicates the name of the Machine ID bot this identity was issued // to, if any. BotName string + // BotInstanceID is a unique identifier for Machine ID bots that is + // persisted through renewals. + BotInstanceID string // AllowedResourceIDs lists the resources the identity should be allowed to // access. AllowedResourceIDs []types.ResourceID @@ -528,6 +531,10 @@ var ( // RequestedDatabaseRolesExtensionOID is an extension OID used when // encoding/decoding requested database roles. RequestedDatabaseRolesExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 19} + + // BotInstanceASN1ExtensionOID is an extension that encodes a unique bot + // instance identifier into a certificate. + BotInstanceASN1ExtensionOID = asn1.ObjectIdentifier{1, 3, 9999, 2, 20} ) // Device Trust OIDs. @@ -810,6 +817,14 @@ func (id *Identity) Subject() (pkix.Name, error) { }) } + if id.BotInstanceID != "" { + subject.ExtraNames = append(subject.ExtraNames, + pkix.AttributeTypeAndValue{ + Type: BotInstanceASN1ExtensionOID, + Value: id.BotInstanceID, + }) + } + if len(id.AllowedResourceIDs) > 0 { allowedResourcesStr, err := types.ResourceIDsToString(id.AllowedResourceIDs) if err != nil { @@ -1050,6 +1065,11 @@ func FromSubject(subject pkix.Name, expires time.Time) (*Identity, error) { if ok { id.BotName = val } + case attr.Type.Equal(BotInstanceASN1ExtensionOID): + val, ok := attr.Value.(string) + if ok { + id.BotInstanceID = val + } case attr.Type.Equal(AllowedResourcesASN1ExtensionOID): allowedResourcesStr, ok := attr.Value.(string) if ok { From c0b7dc822b5dca91c64c87b3e5cc2931ed6d5be4 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Thu, 27 Jun 2024 19:48:59 -0600 Subject: [PATCH 2/9] Proto cleanup, and update bot auth records on cert renewal This makes various (admittedly breaking) protobuf changes, including removing the TTL field (calculating resource expiry based on cert requests), removing public key fingerprints, and changing the data type of the generation counter to match the preexisting internal datatype. These changes _should_ be safe as no consumers of the proto API currently exist. Additionally, this also updates bot authentications on renewal. --- .../teleport/machineid/v1/bot_instance.pb.go | 202 ++++++++---------- .../teleport/machineid/v1/bot_instance.proto | 8 +- lib/auth/auth_with_roles.go | 4 + lib/auth/bot.go | 81 +++++-- lib/auth/join.go | 10 +- .../machineidv1/bot_instance_service.go | 13 ++ lib/services/local/bot_instance.go | 21 +- lib/services/local/bot_instance_test.go | 38 ++-- 8 files changed, 213 insertions(+), 164 deletions(-) diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go index 8a6b2b25fdb72..68be0270cc23d 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go @@ -144,10 +144,6 @@ type BotInstanceSpec struct { BotName string `protobuf:"bytes,1,opt,name=bot_name,json=botName,proto3" json:"bot_name,omitempty"` // The unique identifier for this instance. InstanceId string `protobuf:"bytes,2,opt,name=instance_id,json=instanceId,proto3" json:"instance_id,omitempty"` - // The desired expiration offset for this bot instance. The expiration will be - // calculated from the current time at creation or authentication plus this - // value. A nil `ttl` will not expire. - Ttl *durationpb.Duration `protobuf:"bytes,3,opt,name=ttl,proto3" json:"ttl,omitempty"` } func (x *BotInstanceSpec) Reset() { @@ -196,13 +192,6 @@ func (x *BotInstanceSpec) GetInstanceId() string { return "" } -func (x *BotInstanceSpec) GetTtl() *durationpb.Duration { - if x != nil { - return x.Ttl - } - return nil -} - // BotInstanceStatusHeartbeat contains information self-reported by an instance // of a Bot. This information is not verified by the server and should not be // trusted. @@ -351,11 +340,9 @@ type BotInstanceStatusAuthentication struct { // method, this counter is checked during renewal and the Bot is locked out if // the counter in the certificate does not match the counter of the last // authentication. - Generation int32 `protobuf:"varint,5,opt,name=generation,proto3" json:"generation,omitempty"` + Generation uint64 `protobuf:"varint,5,opt,name=generation,proto3" json:"generation,omitempty"` // The public key of the Bot instance. PublicKey []byte `protobuf:"bytes,6,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` - // The fingerprint of the public key of the Bot instance. - Fingerprint string `protobuf:"bytes,7,opt,name=fingerprint,proto3" json:"fingerprint,omitempty"` } func (x *BotInstanceStatusAuthentication) Reset() { @@ -418,7 +405,7 @@ func (x *BotInstanceStatusAuthentication) GetMetadata() *structpb.Struct { return nil } -func (x *BotInstanceStatusAuthentication) GetGeneration() int32 { +func (x *BotInstanceStatusAuthentication) GetGeneration() uint64 { if x != nil { return x.Generation } @@ -432,13 +419,6 @@ func (x *BotInstanceStatusAuthentication) GetPublicKey() []byte { return nil } -func (x *BotInstanceStatusAuthentication) GetFingerprint() string { - if x != nil { - return x.Fingerprint - } - return "" -} - // BotInstanceStatus holds the status of a BotInstance. type BotInstanceStatus struct { state protoimpl.MessageState @@ -547,89 +527,84 @@ var file_teleport_machineid_v1_bot_instance_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x28, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x7a, 0x0a, 0x0f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x4d, 0x0a, 0x0f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6f, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x49, 0x64, 0x12, 0x2b, 0x0a, 0x03, 0x74, 0x74, 0x6c, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, - 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x03, 0x74, 0x74, 0x6c, - 0x22, 0xd1, 0x02, 0x0a, 0x1a, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, - 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, - 0x52, 0x0a, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, - 0x69, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, - 0x52, 0x09, 0x69, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, - 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, - 0x65, 0x12, 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x75, 0x70, - 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, - 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x65, 0x5f, 0x73, 0x68, 0x6f, - 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x65, 0x53, 0x68, 0x6f, 0x74, - 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, - 0x74, 0x75, 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x02, 0x6f, 0x73, 0x22, 0xbe, 0x02, 0x0a, 0x1f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, - 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x68, - 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, - 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, - 0x12, 0x1d, 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, - 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, - 0x4b, 0x65, 0x79, 0x12, 0x20, 0x0a, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, 0x72, 0x69, - 0x6e, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, - 0x70, 0x72, 0x69, 0x6e, 0x74, 0x22, 0xb1, 0x03, 0x0a, 0x11, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, - 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6d, 0x0a, 0x16, 0x69, - 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 0x2e, 0x74, 0x65, + 0x65, 0x49, 0x64, 0x22, 0xd1, 0x02, 0x0a, 0x1a, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, + 0x61, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x61, + 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x41, 0x74, 0x12, + 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, + 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, + 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, + 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, + 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x65, 0x5f, + 0x73, 0x68, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x65, 0x53, + 0x68, 0x6f, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, + 0x75, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, + 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x09, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x22, 0x9c, 0x02, 0x0a, 0x1f, 0x42, 0x6f, 0x74, 0x49, + 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x10, 0x61, + 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, + 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, + 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, + 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x67, 0x65, 0x6e, + 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, + 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb1, 0x03, 0x0a, 0x11, 0x42, 0x6f, 0x74, 0x49, 0x6e, + 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6d, 0x0a, 0x16, + 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 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, + 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x41, 0x75, 0x74, + 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6d, 0x0a, 0x16, 0x6c, + 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, - 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6d, 0x0a, 0x16, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, - 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5e, 0x0a, 0x11, 0x69, 0x6e, 0x69, - 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, - 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, - 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x5e, 0x0a, 0x11, 0x6c, 0x61, 0x74, - 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x18, 0x04, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, - 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x48, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x42, 0x56, 0x5a, 0x54, 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, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, - 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x69, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, 0x76, - 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5e, 0x0a, 0x11, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, + 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, + 0x6c, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x5e, 0x0a, 0x11, 0x6c, 0x61, + 0x74, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x18, + 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, + 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, + 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x42, 0x56, 0x5a, 0x54, 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, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, + 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x69, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, + 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -652,28 +627,27 @@ var file_teleport_machineid_v1_bot_instance_proto_goTypes = []any{ (*BotInstanceStatusAuthentication)(nil), // 3: teleport.machineid.v1.BotInstanceStatusAuthentication (*BotInstanceStatus)(nil), // 4: teleport.machineid.v1.BotInstanceStatus (*v1.Metadata)(nil), // 5: teleport.header.v1.Metadata - (*durationpb.Duration)(nil), // 6: google.protobuf.Duration - (*timestamppb.Timestamp)(nil), // 7: google.protobuf.Timestamp + (*timestamppb.Timestamp)(nil), // 6: google.protobuf.Timestamp + (*durationpb.Duration)(nil), // 7: google.protobuf.Duration (*structpb.Struct)(nil), // 8: google.protobuf.Struct } var file_teleport_machineid_v1_bot_instance_proto_depIdxs = []int32{ 5, // 0: teleport.machineid.v1.BotInstance.metadata:type_name -> teleport.header.v1.Metadata 1, // 1: teleport.machineid.v1.BotInstance.spec:type_name -> teleport.machineid.v1.BotInstanceSpec 4, // 2: teleport.machineid.v1.BotInstance.status:type_name -> teleport.machineid.v1.BotInstanceStatus - 6, // 3: teleport.machineid.v1.BotInstanceSpec.ttl:type_name -> google.protobuf.Duration - 7, // 4: teleport.machineid.v1.BotInstanceStatusHeartbeat.recorded_at:type_name -> google.protobuf.Timestamp - 6, // 5: teleport.machineid.v1.BotInstanceStatusHeartbeat.uptime:type_name -> google.protobuf.Duration - 7, // 6: teleport.machineid.v1.BotInstanceStatusAuthentication.authenticated_at:type_name -> google.protobuf.Timestamp - 8, // 7: teleport.machineid.v1.BotInstanceStatusAuthentication.metadata:type_name -> google.protobuf.Struct - 3, // 8: teleport.machineid.v1.BotInstanceStatus.initial_authentication:type_name -> teleport.machineid.v1.BotInstanceStatusAuthentication - 3, // 9: teleport.machineid.v1.BotInstanceStatus.latest_authentications:type_name -> teleport.machineid.v1.BotInstanceStatusAuthentication - 2, // 10: teleport.machineid.v1.BotInstanceStatus.initial_heartbeat:type_name -> teleport.machineid.v1.BotInstanceStatusHeartbeat - 2, // 11: teleport.machineid.v1.BotInstanceStatus.latest_heartbeats:type_name -> teleport.machineid.v1.BotInstanceStatusHeartbeat - 12, // [12:12] is the sub-list for method output_type - 12, // [12:12] is the sub-list for method input_type - 12, // [12:12] is the sub-list for extension type_name - 12, // [12:12] is the sub-list for extension extendee - 0, // [0:12] is the sub-list for field type_name + 6, // 3: teleport.machineid.v1.BotInstanceStatusHeartbeat.recorded_at:type_name -> google.protobuf.Timestamp + 7, // 4: teleport.machineid.v1.BotInstanceStatusHeartbeat.uptime:type_name -> google.protobuf.Duration + 6, // 5: teleport.machineid.v1.BotInstanceStatusAuthentication.authenticated_at:type_name -> google.protobuf.Timestamp + 8, // 6: teleport.machineid.v1.BotInstanceStatusAuthentication.metadata:type_name -> google.protobuf.Struct + 3, // 7: teleport.machineid.v1.BotInstanceStatus.initial_authentication:type_name -> teleport.machineid.v1.BotInstanceStatusAuthentication + 3, // 8: teleport.machineid.v1.BotInstanceStatus.latest_authentications:type_name -> teleport.machineid.v1.BotInstanceStatusAuthentication + 2, // 9: teleport.machineid.v1.BotInstanceStatus.initial_heartbeat:type_name -> teleport.machineid.v1.BotInstanceStatusHeartbeat + 2, // 10: teleport.machineid.v1.BotInstanceStatus.latest_heartbeats:type_name -> teleport.machineid.v1.BotInstanceStatusHeartbeat + 11, // [11:11] is the sub-list for method output_type + 11, // [11:11] is the sub-list for method input_type + 11, // [11:11] is the sub-list for extension type_name + 11, // [11:11] is the sub-list for extension extendee + 0, // [0:11] is the sub-list for field type_name } func init() { file_teleport_machineid_v1_bot_instance_proto_init() } diff --git a/api/proto/teleport/machineid/v1/bot_instance.proto b/api/proto/teleport/machineid/v1/bot_instance.proto index 94ae95f82a757..0871437775564 100644 --- a/api/proto/teleport/machineid/v1/bot_instance.proto +++ b/api/proto/teleport/machineid/v1/bot_instance.proto @@ -47,10 +47,6 @@ message BotInstanceSpec { string bot_name = 1; // The unique identifier for this instance. string instance_id = 2; - // The desired expiration offset for this bot instance. The expiration will be - // calculated from the current time at creation or authentication plus this - // value. A nil `ttl` will not expire. - google.protobuf.Duration ttl = 3; } // BotInstanceStatusHeartbeat contains information self-reported by an instance @@ -101,11 +97,9 @@ message BotInstanceStatusAuthentication { // method, this counter is checked during renewal and the Bot is locked out if // the counter in the certificate does not match the counter of the last // authentication. - int32 generation = 5; + uint64 generation = 5; // The public key of the Bot instance. bytes public_key = 6; - // The fingerprint of the public key of the Bot instance. - string fingerprint = 7; } // BotInstanceStatus holds the status of a BotInstance. diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 65f0a74da81b2..e40d0dcbac042 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3309,6 +3309,10 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC if err := a.authServer.validateGenerationLabel(ctx, user.GetName(), &certReq, currentIdentityGeneration); err != nil { return nil, trace.Wrap(err) } + + if err := a.updateBotAuthentications(ctx, &certReq); err != nil { + return nil, trace.Wrap(err) + } } certs, err := a.authServer.generateUserCert(ctx, certReq) diff --git a/lib/auth/bot.go b/lib/auth/bot.go index dabffa86421c2..38e1a0f1904a4 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -26,12 +26,16 @@ import ( "github.com/google/uuid" "github.com/gravitational/trace" + "github.com/sirupsen/logrus" + "google.golang.org/protobuf/types/known/timestamppb" "github.com/gravitational/teleport/api/client/proto" - machineidv1 "github.com/gravitational/teleport/api/gen/proto/go/teleport/machineid/v1" + 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" apievents "github.com/gravitational/teleport/api/types/events" apiutils "github.com/gravitational/teleport/api/utils" + "github.com/gravitational/teleport/lib/auth/machineid/machineidv1" experiment "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/bot_instance_experiment" "github.com/gravitational/teleport/lib/authz" "github.com/gravitational/teleport/lib/defaults" @@ -170,15 +174,71 @@ func (a *Server) validateGenerationLabel(ctx context.Context, username string, c return nil } +func (a *ServerWithRoles) updateBotAuthentications(ctx context.Context, req *certRequest) error { + ident := a.context.Identity.GetIdentity() + + authRecord := &machineidv1pb.BotInstanceStatusAuthentication{ + AuthenticatedAt: timestamppb.New(a.authServer.GetClock().Now()), + PublicKey: req.publicKey, + + // TODO: for now, this copy of the certificate generation only is + // informational. Future changes will transition to trusting (and + // verifying) this value in lieu of the old generation label on bot + // users. + Generation: req.generation, + + // Note: This auth path can only ever be for token joins; all other join + // types effectively rejoin every renewal. Other fields will be unset + // (metadata, join token name, etc). + JoinMethod: string(types.JoinMethodToken), + } + + _, err := a.authServer.BotInstance.PatchBotInstance(ctx, ident.BotName, ident.BotInstanceID, func(bi *machineidv1pb.BotInstance) (*machineidv1pb.BotInstance, error) { + if bi.Status == nil { + bi.Status = &machineidv1pb.BotInstanceStatus{} + } + + // If we're at or above the limit, remove enough of the front elements + // to make room for the new one at the end. + if len(bi.Status.LatestAuthentications) >= machineidv1.AuthenticationHistoryLimit { + toRemove := len(bi.Status.LatestAuthentications) - machineidv1.AuthenticationHistoryLimit + 1 + bi.Status.LatestAuthentications = bi.Status.LatestAuthentications[toRemove:] + } + + // An initial auth record should have been added during initial join, + // but if not, add it now. + if bi.Status.InitialAuthentication == nil { + log.WithFields(logrus.Fields{ + "bot_name": ident.BotName, + "bot_instance_id": ident.BotInstanceID, + }).Warn("bot instance is missing its initial authentication record, a new one will be added") + bi.Status.InitialAuthentication = authRecord + } + + bi.Status.LatestAuthentications = append(bi.Status.LatestAuthentications, authRecord) + + return bi, nil + }) + + return trace.Wrap(err) +} + // newBotInstance constructs a new bot instance from a spec and initial authentication -func newBotInstance(spec *machineidv1.BotInstanceSpec, initialAuth *machineidv1.BotInstanceStatusAuthentication) *machineidv1.BotInstance { - return &machineidv1.BotInstance{ +func newBotInstance( + spec *machineidv1pb.BotInstanceSpec, + initialAuth *machineidv1pb.BotInstanceStatusAuthentication, + expires time.Time, +) *machineidv1pb.BotInstance { + return &machineidv1pb.BotInstance{ Kind: types.KindBotInstance, Version: types.V1, - Spec: spec, - Status: &machineidv1.BotInstanceStatus{ + Metadata: &headerv1.Metadata{ + Expires: timestamppb.New(expires), + }, + Spec: spec, + Status: &machineidv1pb.BotInstanceStatus{ InitialAuthentication: initialAuth, - LatestAuthentications: []*machineidv1.BotInstanceStatusAuthentication{initialAuth}, + LatestAuthentications: []*machineidv1pb.BotInstanceStatusAuthentication{initialAuth}, }, } } @@ -192,7 +252,7 @@ func newBotInstance(spec *machineidv1.BotInstanceSpec, initialAuth *machineidv1. // is allowed to issue the (possibly renewable) certificates. func (a *Server) generateInitialBotCerts( ctx context.Context, botName, username, loginIP string, pubKey []byte, - expires time.Time, renewable bool, initialAuth *machineidv1.BotInstanceStatusAuthentication, + expires time.Time, renewable bool, initialAuth *machineidv1pb.BotInstanceStatusAuthentication, ) (*proto.Certs, error) { var err error @@ -244,13 +304,10 @@ func (a *Server) generateInitialBotCerts( return nil, trace.Wrap(err) } - bi := newBotInstance(&machineidv1.BotInstanceSpec{ + bi := newBotInstance(&machineidv1pb.BotInstanceSpec{ BotName: botName, InstanceId: uuid.String(), - - // TODO: set TTL? should we rely on the known expiration instead? - // (auth may overwrite the value now or later) - }, initialAuth) + }, initialAuth, expires.Add(machineidv1.ExpiryMargin)) _, err = a.BotInstance.CreateBotInstance(ctx, bi) if err != nil { diff --git a/lib/auth/join.go b/lib/auth/join.go index 1fa14a0e62dd4..91307b79ba34b 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -21,9 +21,7 @@ package auth import ( "context" "crypto/rand" - "crypto/sha256" "encoding/base64" - "encoding/hex" "fmt" "net" "slices" @@ -369,16 +367,14 @@ func (a *Server) generateCertsBot( }, } - fingerprint := sha256.Sum256(req.PublicTLSKey) auth := &machineidv1pb.BotInstanceStatusAuthentication{ AuthenticatedAt: timestamppb.New(a.GetClock().Now()), // TODO: GetSafeName may not return an appropriate value for later // comparison / locking purposes, and this also shouldn't contain // secrets. Should we hash it? - JoinToken: provisionToken.GetSafeName(), - JoinMethod: string(provisionToken.GetJoinMethod()), - PublicKey: req.PublicTLSKey, - Fingerprint: hex.EncodeToString(fingerprint[:]), + JoinToken: provisionToken.GetSafeName(), + JoinMethod: string(provisionToken.GetJoinMethod()), + PublicKey: req.PublicTLSKey, // Note: Generation will be set during `generateInitialBotCerts()` as // needed. diff --git a/lib/auth/machineid/machineidv1/bot_instance_service.go b/lib/auth/machineid/machineidv1/bot_instance_service.go index 3d07f7ebbcfa7..8947cb27242c1 100644 --- a/lib/auth/machineid/machineidv1/bot_instance_service.go +++ b/lib/auth/machineid/machineidv1/bot_instance_service.go @@ -21,6 +21,7 @@ package machineidv1 import ( "context" "log/slog" + "time" "github.com/gravitational/trace" "github.com/jonboulle/clockwork" @@ -33,6 +34,18 @@ import ( "github.com/gravitational/teleport/lib/services" ) +const ( + // AuthenticationHistoryLimit is the maximum number of authentication + // records to be recorded in a bot instance's .Status.LatestAuthentications + // field. + AuthenticationHistoryLimit = 10 + + // ExpiryMargin is the duration added to bot instance expiration times to + // ensure the instance remains accessible until shortly after the last + // issued certificate expires. + ExpiryMargin = time.Minute * 5 +) + // BotInstanceServiceConfig holds configuration options for the BotInstance gRPC // service. type BotInstanceServiceConfig struct { diff --git a/lib/services/local/bot_instance.go b/lib/services/local/bot_instance.go index 1c2d55bbf7a0c..2db2712473daa 100644 --- a/lib/services/local/bot_instance.go +++ b/lib/services/local/bot_instance.go @@ -21,7 +21,6 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" - "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" @@ -61,26 +60,22 @@ func NewBotInstanceService(backend backend.Backend, clock clockwork.Clock) (*Bot // CreateBotInstance inserts a new BotInstance into the backend. // -// Note that new BotInstances must have a nil metadata field; this will be -// generated automatically from the spec upon insert. +// Note that new BotInstances will have their .Metadata.Name overwritten by the +// instance UUID. func (b *BotInstanceService) CreateBotInstance(ctx context.Context, instance *machineidv1.BotInstance) (*machineidv1.BotInstance, error) { if err := services.ValidateBotInstance(instance); err != nil { return nil, trace.Wrap(err) } - if instance.Metadata != nil { - return nil, trace.BadParameter("metadata should be nil; initial parameters should be set in spec") - } - instance.Kind = types.KindBotInstance instance.Version = types.V1 - instance.Metadata = &headerv1.Metadata{Name: instance.Spec.InstanceId} - if instance.Spec.Ttl != nil { - expires := b.clock.Now().Add(instance.Spec.Ttl.AsDuration()) - instance.Metadata.Expires = timestamppb.New(expires) + if instance.Metadata == nil { + instance.Metadata = &headerv1.Metadata{} } + instance.Metadata.Name = instance.Spec.InstanceId + serviceWithPrefix := b.service.WithPrefix(instance.Spec.BotName) created, err := serviceWithPrefix.CreateResource(ctx, instance) return created, trace.Wrap(err) @@ -142,6 +137,10 @@ func (b *BotInstanceService) PatchBotInstance( return nil, trace.BadParameter("metadata.name: cannot be patched") case updated.GetMetadata().GetRevision() != existing.GetMetadata().GetRevision(): return nil, trace.BadParameter("metadata.revision: cannot be patched") + case updated.GetSpec().GetInstanceId() != existing.GetSpec().GetInstanceId(): + return nil, trace.BadParameter("spec.instance_id: cannot be patched") + case updated.GetSpec().GetBotName() != existing.GetSpec().GetBotName(): + return nil, trace.BadParameter("spec.bot_name: cannot be patched") } serviceWithPrefix := b.service.WithPrefix(botName) diff --git a/lib/services/local/bot_instance_test.go b/lib/services/local/bot_instance_test.go index 62f6a3a2585c1..68c3eb040c62e 100644 --- a/lib/services/local/bot_instance_test.go +++ b/lib/services/local/bot_instance_test.go @@ -27,7 +27,7 @@ import ( "github.com/gravitational/trace" "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" - "google.golang.org/protobuf/types/known/durationpb" + "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" @@ -59,13 +59,6 @@ func newBotInstance(botName string, fns ...func(*machineidv1.BotInstance)) *mach return bi } -// withBotInstanceTTL apples a TTL to the bot instance -func withBotInstanceTTL(d time.Duration) func(*machineidv1.BotInstance) { - return func(bi *machineidv1.BotInstance) { - bi.Spec.Ttl = durationpb.New(d) - } -} - // withBotInstanceInvalidMetadata modifies a BotInstance such that it should // raise an error during an insert attempt. func withBotInstanceInvalidMetadata() func(*machineidv1.BotInstance) { @@ -76,6 +69,18 @@ func withBotInstanceInvalidMetadata() func(*machineidv1.BotInstance) { } } +// withBotInstanceExpiry sets the .Metadata.Expires field of a bot instance to +// the given timestamp. +func withBotInstanceExpiry(expiry time.Time) func(*machineidv1.BotInstance) { + return func(bi *machineidv1.BotInstance) { + if bi.Metadata == nil { + bi.Metadata = &headerv1.Metadata{} + } + + bi.Metadata.Expires = timestamppb.New(expiry) + } +} + // createInstances creates new bot instances for the named bot with random UUIDs func createInstances(t *testing.T, ctx context.Context, service *BotInstanceService, botName string, count int) map[string]struct{} { t.Helper() @@ -132,11 +137,18 @@ func TestBotInstanceCreateMetadata(t *testing.T) { { name: "non-nil metadata", instance: newBotInstance("foo", withBotInstanceInvalidMetadata()), - assertError: require.Error, - assertValue: require.Nil, + assertError: require.NoError, + assertValue: func(t require.TestingT, i interface{}, _ ...interface{}) { + bi, ok := i.(*machineidv1.BotInstance) + require.True(t, ok) + + // .Metadata.Name should be overwritten with the correct value + require.Equal(t, bi.Spec.InstanceId, bi.Metadata.Name) + require.Nil(t, bi.Metadata.Expires) + }, }, { - name: "valid without ttl", + name: "valid without expiry", instance: newBotInstance("foo"), assertError: require.NoError, assertValue: func(t require.TestingT, i interface{}, _ ...interface{}) { @@ -148,8 +160,8 @@ func TestBotInstanceCreateMetadata(t *testing.T) { }, }, { - name: "valid with ttl", - instance: newBotInstance("foo", withBotInstanceTTL(time.Hour)), + name: "valid with expiry", + instance: newBotInstance("foo", withBotInstanceExpiry(clock.Now().Add(time.Hour))), assertError: require.NoError, assertValue: func(t require.TestingT, i interface{}, _ ...interface{}) { bi, ok := i.(*machineidv1.BotInstance) From 208ed2fa1fd5ba222dea76e7533d58df66b593e2 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Mon, 1 Jul 2024 17:29:02 -0600 Subject: [PATCH 3/9] Fix proto lints --- .../teleport/machineid/v1/bot_instance.pb.go | 154 +++++++++--------- .../teleport/machineid/v1/bot_instance.proto | 8 +- lib/auth/bot.go | 2 +- 3 files changed, 86 insertions(+), 78 deletions(-) diff --git a/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go b/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go index 68be0270cc23d..c89535a11afc2 100644 --- a/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go +++ b/api/gen/proto/go/teleport/machineid/v1/bot_instance.pb.go @@ -340,7 +340,7 @@ type BotInstanceStatusAuthentication struct { // method, this counter is checked during renewal and the Bot is locked out if // the counter in the certificate does not match the counter of the last // authentication. - Generation uint64 `protobuf:"varint,5,opt,name=generation,proto3" json:"generation,omitempty"` + Generation int32 `protobuf:"varint,5,opt,name=generation,proto3" json:"generation,omitempty"` // The public key of the Bot instance. PublicKey []byte `protobuf:"bytes,6,opt,name=public_key,json=publicKey,proto3" json:"public_key,omitempty"` } @@ -405,7 +405,7 @@ func (x *BotInstanceStatusAuthentication) GetMetadata() *structpb.Struct { return nil } -func (x *BotInstanceStatusAuthentication) GetGeneration() uint64 { +func (x *BotInstanceStatusAuthentication) GetGeneration() int32 { if x != nil { return x.Generation } @@ -527,84 +527,86 @@ var file_teleport_machineid_v1_bot_instance_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x28, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x4d, 0x0a, 0x0f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x58, 0x0a, 0x0f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x70, 0x65, 0x63, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6f, 0x74, 0x5f, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6f, 0x74, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, - 0x65, 0x49, 0x64, 0x22, 0xd1, 0x02, 0x0a, 0x1a, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, - 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, - 0x61, 0x74, 0x12, 0x3b, 0x0a, 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x61, - 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, - 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x52, 0x0a, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x41, 0x74, 0x12, - 0x1d, 0x0a, 0x0a, 0x69, 0x73, 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x09, 0x69, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, - 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, - 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, - 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, - 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, - 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x65, 0x5f, - 0x73, 0x68, 0x6f, 0x74, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x65, 0x53, - 0x68, 0x6f, 0x74, 0x12, 0x22, 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, - 0x75, 0x72, 0x65, 0x18, 0x08, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, - 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x09, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x02, 0x6f, 0x73, 0x22, 0x9c, 0x02, 0x0a, 0x1f, 0x42, 0x6f, 0x74, 0x49, - 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x10, 0x61, - 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, - 0x70, 0x52, 0x0f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, - 0x41, 0x74, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, - 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, 0x65, 0x74, - 0x68, 0x6f, 0x64, 0x12, 0x1d, 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, - 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x6b, - 0x65, 0x6e, 0x12, 0x33, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, - 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x04, 0x52, 0x0a, 0x67, 0x65, 0x6e, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, - 0x63, 0x5f, 0x6b, 0x65, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb1, 0x03, 0x0a, 0x11, 0x42, 0x6f, 0x74, 0x49, 0x6e, - 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6d, 0x0a, 0x16, - 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, - 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 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, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x41, 0x75, 0x74, - 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6d, 0x0a, 0x16, 0x6c, - 0x61, 0x74, 0x65, 0x73, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, - 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x52, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, - 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5e, 0x0a, 0x11, 0x69, 0x6e, - 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, - 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, - 0x6c, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x5e, 0x0a, 0x11, 0x6c, 0x61, - 0x74, 0x65, 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x18, - 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, - 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, - 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x42, 0x56, 0x5a, 0x54, 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, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, - 0x2f, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, - 0x65, 0x69, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, - 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x65, 0x49, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x52, 0x03, 0x74, 0x74, 0x6c, 0x22, 0xd1, + 0x02, 0x0a, 0x1a, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x3b, 0x0a, + 0x0b, 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0a, + 0x72, 0x65, 0x63, 0x6f, 0x72, 0x64, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x73, + 0x5f, 0x73, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, + 0x69, 0x73, 0x53, 0x74, 0x61, 0x72, 0x74, 0x75, 0x70, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, + 0x69, 0x6f, 0x6e, 0x12, 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x31, 0x0a, 0x06, 0x75, 0x70, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x19, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x44, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x75, 0x70, 0x74, 0x69, + 0x6d, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, + 0x64, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, 0x65, 0x74, + 0x68, 0x6f, 0x64, 0x12, 0x19, 0x0a, 0x08, 0x6f, 0x6e, 0x65, 0x5f, 0x73, 0x68, 0x6f, 0x74, 0x18, + 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x6f, 0x6e, 0x65, 0x53, 0x68, 0x6f, 0x74, 0x12, 0x22, + 0x0a, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, 0x72, 0x65, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x61, 0x72, 0x63, 0x68, 0x69, 0x74, 0x65, 0x63, 0x74, 0x75, + 0x72, 0x65, 0x12, 0x0e, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, + 0x6f, 0x73, 0x22, 0xaf, 0x02, 0x0a, 0x1f, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, + 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, + 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x45, 0x0a, 0x10, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, + 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, + 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x0f, 0x61, 0x75, + 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1f, 0x0a, + 0x0b, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x6d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x4d, 0x65, 0x74, 0x68, 0x6f, 0x64, 0x12, 0x1d, + 0x0a, 0x0a, 0x6a, 0x6f, 0x69, 0x6e, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x09, 0x6a, 0x6f, 0x69, 0x6e, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x33, 0x0a, + 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, + 0x66, 0x2e, 0x53, 0x74, 0x72, 0x75, 0x63, 0x74, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, + 0x74, 0x61, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x18, 0x05, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x5f, 0x6b, 0x65, 0x79, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x09, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, + 0x79, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x52, 0x0b, 0x66, 0x69, 0x6e, 0x67, 0x65, 0x72, 0x70, + 0x72, 0x69, 0x6e, 0x74, 0x22, 0xb1, 0x03, 0x0a, 0x11, 0x42, 0x6f, 0x74, 0x49, 0x6e, 0x73, 0x74, + 0x61, 0x6e, 0x63, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x6d, 0x0a, 0x16, 0x69, 0x6e, + 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x36, 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, 0x53, 0x74, + 0x61, 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x52, 0x15, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x41, 0x75, 0x74, 0x68, 0x65, + 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6d, 0x0a, 0x16, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x5f, 0x61, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x36, 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, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, + 0x6e, 0x52, 0x15, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x41, 0x75, 0x74, 0x68, 0x65, 0x6e, 0x74, + 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x5e, 0x0a, 0x11, 0x69, 0x6e, 0x69, 0x74, + 0x69, 0x61, 0x6c, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x48, + 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x12, 0x5e, 0x0a, 0x11, 0x6c, 0x61, 0x74, 0x65, + 0x73, 0x74, 0x5f, 0x68, 0x65, 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x31, 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, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x48, 0x65, 0x61, + 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x52, 0x10, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x48, 0x65, + 0x61, 0x72, 0x74, 0x62, 0x65, 0x61, 0x74, 0x73, 0x42, 0x56, 0x5a, 0x54, 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, 0x67, 0x65, 0x6e, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x67, 0x6f, 0x2f, 0x74, + 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2f, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, + 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x69, 0x64, 0x76, 0x31, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/teleport/machineid/v1/bot_instance.proto b/api/proto/teleport/machineid/v1/bot_instance.proto index 0871437775564..bd231b4a0dac4 100644 --- a/api/proto/teleport/machineid/v1/bot_instance.proto +++ b/api/proto/teleport/machineid/v1/bot_instance.proto @@ -47,6 +47,9 @@ message BotInstanceSpec { string bot_name = 1; // The unique identifier for this instance. string instance_id = 2; + + reserved 3; + reserved "ttl"; } // BotInstanceStatusHeartbeat contains information self-reported by an instance @@ -97,9 +100,12 @@ message BotInstanceStatusAuthentication { // method, this counter is checked during renewal and the Bot is locked out if // the counter in the certificate does not match the counter of the last // authentication. - uint64 generation = 5; + int32 generation = 5; // The public key of the Bot instance. bytes public_key = 6; + + reserved 7; + reserved "fingerprint"; } // BotInstanceStatus holds the status of a BotInstance. diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 38e1a0f1904a4..2b0d092bf9c24 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -185,7 +185,7 @@ func (a *ServerWithRoles) updateBotAuthentications(ctx context.Context, req *cer // informational. Future changes will transition to trusting (and // verifying) this value in lieu of the old generation label on bot // users. - Generation: req.generation, + Generation: int32(req.generation), // Note: This auth path can only ever be for token joins; all other join // types effectively rejoin every renewal. Other fields will be unset From d7e152427f89ba518dda43956c144b2af2bc0469 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Mon, 1 Jul 2024 18:42:29 -0600 Subject: [PATCH 4/9] Fix misleading doc comment in the bot instance experiment --- .../machineidv1/bot_instance_experiment/experiment.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go b/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go index 29a2f137fa8c6..a72e81f55f7ed 100644 --- a/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go +++ b/lib/auth/machineid/machineidv1/bot_instance_experiment/experiment.go @@ -29,12 +29,12 @@ func init() { enabled.Store(os.Getenv("BOT_INSTANCE_EXPERIMENT") == "1") } -// Enabled returns true if the workload identity experiment is enabled. +// Enabled returns true if the bot instance experiment is enabled. func Enabled() bool { return enabled.Load() } -// SetEnabled sets the workload identity experiment to the given value. +// SetEnabled sets the bot instance experiment flag to the given value. func SetEnabled(value bool) { enabled.Store(value) } From 64e0a525df32ad0a297fb843106b84b030a14e31 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Mon, 1 Jul 2024 18:43:21 -0600 Subject: [PATCH 5/9] Create bot instances for old bots on join; other fixes This now creates bot instances for bots whose certs are missing the BotInstanceID field. Additionally, it fixes two related bugs: expiration dates are extended on renewal, the generated UUID is properly appended to certs on initial join, and instances are only created or updated when the experiment is enabled. --- lib/auth/auth_with_roles.go | 4 ++- lib/auth/bot.go | 49 ++++++++++++++++++++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index e40d0dcbac042..79893e9a51dca 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3310,7 +3310,9 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC return nil, trace.Wrap(err) } - if err := a.updateBotAuthentications(ctx, &certReq); err != nil { + // Update the bot instance based on this authentication. This may create + // a new bot instance record if the identity is missing an instance ID. + if err := a.updateBotInstance(ctx, &certReq); err != nil { return nil, trace.Wrap(err) } } diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 2b0d092bf9c24..7ba9a746fdf62 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -174,9 +174,21 @@ func (a *Server) validateGenerationLabel(ctx context.Context, username string, c return nil } -func (a *ServerWithRoles) updateBotAuthentications(ctx context.Context, req *certRequest) error { +// updateBotInstance updates the bot instance associated with the context +// identity, if any. +func (a *ServerWithRoles) updateBotInstance(ctx context.Context, req *certRequest) error { ident := a.context.Identity.GetIdentity() + if !experiment.Enabled() { + // Only attempt to update bot instances if the experiment is enabled. + return nil + } + + if ident.BotName == "" { + // Only applies to bot identities + return nil + } + authRecord := &machineidv1pb.BotInstanceStatusAuthentication{ AuthenticatedAt: timestamppb.New(a.authServer.GetClock().Now()), PublicKey: req.publicKey, @@ -193,11 +205,44 @@ func (a *ServerWithRoles) updateBotAuthentications(ctx context.Context, req *cer JoinMethod: string(types.JoinMethodToken), } + // An empty bot instance most likely means a bot is rejoining after an + // upgrade, so a new bot instance should be generated. + if ident.BotInstanceID == "" { + log.WithFields(logrus.Fields{ + "bot_name": ident.BotName, + }).Info("bot has no instance ID, a new instance will be generated") + + instanceID, err := uuid.NewRandom() + if err != nil { + return trace.Wrap(err) + } + + expires := a.authServer.GetClock().Now().Add(req.ttl + machineidv1.ExpiryMargin) + + bi := newBotInstance(&machineidv1pb.BotInstanceSpec{ + BotName: ident.BotName, + InstanceId: instanceID.String(), + }, authRecord, expires) + + if _, err := a.authServer.BotInstance.CreateBotInstance(ctx, bi); err != nil { + return trace.Wrap(err) + } + + // Add the new ID to the cert request + req.botInstanceID = instanceID.String() + + return nil + } + _, err := a.authServer.BotInstance.PatchBotInstance(ctx, ident.BotName, ident.BotInstanceID, func(bi *machineidv1pb.BotInstance) (*machineidv1pb.BotInstance, error) { if bi.Status == nil { bi.Status = &machineidv1pb.BotInstanceStatus{} } + // Update the record's expiration timestamp based on the request TTL + // plus an expiry margin. + bi.Metadata.Expires = timestamppb.New(a.authServer.GetClock().Now().Add(req.ttl + machineidv1.ExpiryMargin)) + // If we're at or above the limit, remove enough of the front elements // to make room for the new one at the end. if len(bi.Status.LatestAuthentications) >= machineidv1.AuthenticationHistoryLimit { @@ -313,6 +358,8 @@ func (a *Server) generateInitialBotCerts( if err != nil { return nil, trace.Wrap(err) } + + botInstanceID = uuid.String() } // Generate certificate From c1a98337dcd94b0291ba1edf488b80fc6579af05 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Mon, 1 Jul 2024 18:45:19 -0600 Subject: [PATCH 6/9] Add a minimal test for bot instance creation on initial join --- lib/auth/bot_test.go | 86 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 4c54114a96522..57ede35fd4727 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -44,6 +44,7 @@ import ( "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/join" "github.com/gravitational/teleport/lib/auth/machineid/machineidv1" + experiment "github.com/gravitational/teleport/lib/auth/machineid/machineidv1/bot_instance_experiment" "github.com/gravitational/teleport/lib/auth/state" "github.com/gravitational/teleport/lib/auth/testauthority" "github.com/gravitational/teleport/lib/cloud/azure" @@ -154,6 +155,91 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { } } +func TestRegisterBotInstance(t *testing.T) { + // TODO: Enable parallel once the experiment is removed + // t.Parallel() + + experimentBefore := experiment.Enabled() + experiment.SetEnabled(true) + t.Cleanup(func() { + experiment.SetEnabled(experimentBefore) + }) + + srv := newTestTLSServer(t) + ctx := context.Background() + + _, err := CreateRole(ctx, srv.Auth(), "example", types.RoleSpecV6{}) + require.NoError(t, err) + + // Create a new bot. + client, err := srv.NewClient(TestAdmin()) + require.NoError(t, err) + bot, err := client.BotServiceClient().CreateBot(ctx, &machineidv1pb.CreateBotRequest{ + Bot: &machineidv1pb.Bot{ + Metadata: &headerv1.Metadata{ + Name: "test", + }, + Spec: &machineidv1pb.BotSpec{ + Roles: []string{"example"}, + }, + }, + }) + require.NoError(t, err) + + token, err := types.NewProvisionTokenFromSpec("testxyzzy", time.Time{}, types.ProvisionTokenSpecV2{ + Roles: types.SystemRoles{types.RoleBot}, + BotName: bot.Metadata.Name, + }) + require.NoError(t, err) + require.NoError(t, client.CreateToken(ctx, token)) + + privateKey, publicKey, err := testauthority.New().GenerateKeyPair() + require.NoError(t, err) + sshPrivateKey, err := ssh.ParseRawPrivateKey(privateKey) + require.NoError(t, err) + tlsPublicKey, err := tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivateKey) + require.NoError(t, err) + + certs, err := join.Register(ctx, join.RegisterParams{ + Token: token.GetName(), + ID: state.IdentityID{ + Role: types.RoleBot, + }, + AuthServers: []utils.NetAddr{*utils.MustParseAddr(srv.Addr().String())}, + PublicTLSKey: tlsPublicKey, + PublicSSHKey: publicKey, + }) + require.NoError(t, err) + + // The returned certs should have a bot instance ID. + cert, err := tlsca.ParseCertificatePEM(certs.TLS) + require.NoError(t, err) + + ident, err := tlsca.FromSubject(cert.Subject, cert.NotAfter) + require.NoError(t, err) + + require.NotEmpty(t, ident.BotInstanceID) + + // The instance ID should match a bot instance record. + botInstance, err := srv.Auth().BotInstance.GetBotInstance(ctx, ident.BotName, ident.BotInstanceID) + require.NoError(t, err) + + require.Equal(t, ident.BotName, botInstance.GetSpec().BotName) + require.Equal(t, ident.BotInstanceID, botInstance.GetSpec().InstanceId) + + // The initial authentication record should be sane + ia := botInstance.GetStatus().InitialAuthentication + require.NotNil(t, ia) + require.Equal(t, int32(1), ia.Generation) + require.Equal(t, string(types.JoinMethodToken), ia.JoinMethod) + require.Equal(t, token.GetSafeName(), ia.JoinToken) + + // The latest authentications field should contain the same record (and + // only that record.) + require.Len(t, botInstance.GetStatus().LatestAuthentications, 1) + require.EqualExportedValues(t, ia, botInstance.GetStatus().LatestAuthentications[0]) +} + // TestRegisterBotCertificateGenerationStolen simulates a stolen renewable // certificate where a generation check is expected to fail. func TestRegisterBotCertificateGenerationStolen(t *testing.T) { From 56bbc6421a68d52b1bf579e545b40978965d6200 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Tue, 2 Jul 2024 18:14:48 -0600 Subject: [PATCH 7/9] Validate bot instance state in generation counter checks --- lib/auth/bot_test.go | 50 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 5 deletions(-) diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 57ede35fd4727..06bfbf2c7bf55 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -84,6 +84,15 @@ func renewBotCerts( // TestRegisterBotCertificateGenerationCheck ensures bot cert generation checks // work in ordinary conditions, with several rapid renewals. func TestRegisterBotCertificateGenerationCheck(t *testing.T) { + // TODO: Enable parallel once the experiment is removed + // t.Parallel() + + experimentBefore := experiment.Enabled() + experiment.SetEnabled(true) + t.Cleanup(func() { + experiment.SetEnabled(experimentBefore) + }) + t.Parallel() srv := newTestTLSServer(t) ctx := context.Background() @@ -132,29 +141,60 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { require.NoError(t, err) checkCertLoginIP(t, certs.TLS, "127.0.0.1") + initialCert, err := tlsca.ParseCertificatePEM(certs.TLS) + require.NoError(t, err) + initialIdent, err := tlsca.FromSubject(initialCert.Subject, initialCert.NotAfter) + require.NoError(t, err) + + require.Equal(t, uint64(1), initialIdent.Generation) + require.Equal(t, "test", initialIdent.BotName) + require.NotEmpty(t, initialIdent.BotInstanceID) + tlsCert, err := tls.X509KeyPair(certs.TLS, privateKey) require.NoError(t, err) // Renew the cert a bunch of times. for i := 0; i < 10; i++ { + // Ensure the state of the bot instance before renewal is sane. + bi, err := srv.Auth().BotInstance.GetBotInstance(ctx, initialIdent.BotName, initialIdent.BotInstanceID) + require.NoError(t, err) + + // There should always be at least 1 entry as the initial join is + // duplicated in the list. + require.Equal(t, min(i+1, machineidv1.AuthenticationHistoryLimit), len(bi.Status.LatestAuthentications)) + + // Generation starts at 1 for initial certs. + latest := bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] + require.Equal(t, int32(i+1), latest.Generation) + _, certs, tlsCert, err = renewBotCerts(ctx, srv, tlsCert, bot.Status.UserName, publicKey, privateKey) require.NoError(t, err) // Parse the Identity - impersonatedTLSCert, err := tlsca.ParseCertificatePEM(certs.TLS) + renewedCert, err := tlsca.ParseCertificatePEM(certs.TLS) require.NoError(t, err) - impersonatedIdent, err := tlsca.FromSubject(impersonatedTLSCert.Subject, impersonatedTLSCert.NotAfter) + renewedIdent, err := tlsca.FromSubject(renewedCert.Subject, renewedCert.NotAfter) require.NoError(t, err) // Cert must be renewable. - require.True(t, impersonatedIdent.Renewable) - require.False(t, impersonatedIdent.DisallowReissue) + require.True(t, renewedIdent.Renewable) + require.False(t, renewedIdent.DisallowReissue) // Initial certs have generation=1 and we start the loop with a renewal, so add 2 - require.Equal(t, uint64(i+2), impersonatedIdent.Generation) + require.Equal(t, uint64(i+2), renewedIdent.Generation) + + // Ensure the bot instance after renewal is sane. + bi, err = srv.Auth().BotInstance.GetBotInstance(ctx, initialIdent.BotName, initialIdent.BotInstanceID) + require.NoError(t, err) + + require.Equal(t, min(i+2, machineidv1.AuthenticationHistoryLimit), len(bi.Status.LatestAuthentications)) + + latest = bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] + require.Equal(t, int32(i+2), latest.Generation) } } +// TestRegisterBotInstance tests that bot instances are created properly on join func TestRegisterBotInstance(t *testing.T) { // TODO: Enable parallel once the experiment is removed // t.Parallel() From 0a813a0bfa2792ae5bca84f019ae1750b1dce193 Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Tue, 2 Jul 2024 18:40:20 -0600 Subject: [PATCH 8/9] Remove outdated TODO comment and fix test lints --- lib/auth/auth_with_roles.go | 8 ++++---- lib/auth/bot_test.go | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/auth/auth_with_roles.go b/lib/auth/auth_with_roles.go index 79893e9a51dca..30d1d453c00ec 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3252,10 +3252,10 @@ func (a *ServerWithRoles) generateUserCerts(ctx context.Context, req proto.UserC attestationStatement: keys.AttestationStatementFromProto(req.AttestationStatement), botName: getBotName(user), - // Always pass through a bot instance ID if available. Note that this - // method is only used for bot identity renewals and is not responsible - // for issuing new instance IDs; see `generateInitialBotCerts()` - // TODO: need to update bot instance with new authentication + generation counter + // Always pass through a bot instance ID if available. Legacy bots + // joining without an instance ID may have one generated when + // `updateBotInstance()` is called below, and this (empty) value will be + // overridden. botInstanceID: a.context.Identity.GetIdentity().BotInstanceID, } if user.GetName() != a.context.User.GetName() { diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 06bfbf2c7bf55..6dfd917b3ca24 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -161,7 +161,7 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { // There should always be at least 1 entry as the initial join is // duplicated in the list. - require.Equal(t, min(i+1, machineidv1.AuthenticationHistoryLimit), len(bi.Status.LatestAuthentications)) + require.Len(t, bi.Status.LatestAuthentications, min(i+1, machineidv1.AuthenticationHistoryLimit)) // Generation starts at 1 for initial certs. latest := bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] @@ -187,7 +187,7 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { bi, err = srv.Auth().BotInstance.GetBotInstance(ctx, initialIdent.BotName, initialIdent.BotInstanceID) require.NoError(t, err) - require.Equal(t, min(i+2, machineidv1.AuthenticationHistoryLimit), len(bi.Status.LatestAuthentications)) + require.Len(t, bi.Status.LatestAuthentications, min(i+2, machineidv1.AuthenticationHistoryLimit)) latest = bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] require.Equal(t, int32(i+2), latest.Generation) From bed3e45f187d844f60aa67020b82319803998d2b Mon Sep 17 00:00:00 2001 From: Tim Buckley Date: Tue, 2 Jul 2024 19:32:42 -0600 Subject: [PATCH 9/9] Add an expiration change check to the generation test --- lib/auth/bot_test.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/auth/bot_test.go b/lib/auth/bot_test.go index 6dfd917b3ca24..e053a7cf8b860 100644 --- a/lib/auth/bot_test.go +++ b/lib/auth/bot_test.go @@ -34,6 +34,7 @@ import ( "github.com/digitorus/pkcs7" "github.com/google/uuid" "github.com/gravitational/trace" + "github.com/jonboulle/clockwork" "github.com/stretchr/testify/require" "golang.org/x/crypto/ssh" @@ -61,12 +62,13 @@ func renewBotCerts( publicKey []byte, privateKey []byte, ) (*authclient.Client, *proto.Certs, tls.Certificate, error) { + fakeClock := srv.Clock().(clockwork.FakeClock) client := srv.NewClientWithCert(tlsCert) certs, err := client.GenerateUserCerts(ctx, proto.UserCertsRequest{ PublicKey: publicKey, Username: botUser, - Expires: time.Now().Add(time.Hour), + Expires: fakeClock.Now().Add(time.Hour), }) if err != nil { return nil, nil, tls.Certificate{}, trace.Wrap(err) @@ -93,9 +95,9 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { experiment.SetEnabled(experimentBefore) }) - t.Parallel() srv := newTestTLSServer(t) ctx := context.Background() + fakeClock := srv.Clock().(clockwork.FakeClock) _, err := CreateRole(ctx, srv.Auth(), "example", types.RoleSpecV6{}) require.NoError(t, err) @@ -167,6 +169,11 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { latest := bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] require.Equal(t, int32(i+1), latest.Generation) + lastExpires := bi.Metadata.Expires.AsTime() + + // Advance the clock a bit. + fakeClock.Advance(time.Minute) + _, certs, tlsCert, err = renewBotCerts(ctx, srv, tlsCert, bot.Status.UserName, publicKey, privateKey) require.NoError(t, err) @@ -191,6 +198,8 @@ func TestRegisterBotCertificateGenerationCheck(t *testing.T) { latest = bi.Status.LatestAuthentications[len(bi.Status.LatestAuthentications)-1] require.Equal(t, int32(i+2), latest.Generation) + + require.True(t, bi.Metadata.Expires.AsTime().After(lastExpires), "Metadata.Expires must be extended") } }