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 103247e62bad..62305888ddb5 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. @@ -354,8 +343,6 @@ type BotInstanceStatusAuthentication struct { 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"` - // 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() { @@ -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,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, 0x7a, 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, 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, + 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, 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, - 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, + 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, 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, + 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 ( @@ -652,28 +629,27 @@ var file_teleport_machineid_v1_bot_instance_proto_goTypes = []interface{}{ (*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 94ae95f82a75..bd231b4a0dac 100644 --- a/api/proto/teleport/machineid/v1/bot_instance.proto +++ b/api/proto/teleport/machineid/v1/bot_instance.proto @@ -47,10 +47,9 @@ 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; + + reserved 3; + reserved "ttl"; } // BotInstanceStatusHeartbeat contains information self-reported by an instance @@ -104,8 +103,9 @@ message BotInstanceStatusAuthentication { int32 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; + + reserved 7; + reserved "fingerprint"; } // BotInstanceStatus holds the status of a BotInstance. diff --git a/constants.go b/constants.go index fa231f3d0c4b..3b55a69080c6 100644 --- a/constants.go +++ b/constants.go @@ -502,6 +502,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 b4fa0ea2f37e..3bd69ea0cfae 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -1965,6 +1965,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. @@ -2790,6 +2793,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, @@ -2885,6 +2889,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 67da561b3a2e..3de03f101079 100644 --- a/lib/auth/auth_with_roles.go +++ b/lib/auth/auth_with_roles.go @@ -3153,6 +3153,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. 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() { certReq.impersonator = a.context.User.GetName() @@ -3205,6 +3211,12 @@ 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) } + + // 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) + } } certs, err := a.authServer.generateUserCert(ctx, certReq) diff --git a/lib/auth/bot.go b/lib/auth/bot.go index 472d5f672e92..7ba9a746fdf6 100644 --- a/lib/auth/bot.go +++ b/lib/auth/bot.go @@ -26,11 +26,17 @@ 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" + 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" "github.com/gravitational/teleport/lib/events" @@ -168,6 +174,120 @@ func (a *Server) validateGenerationLabel(ctx context.Context, username string, c return nil } +// 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, + + // 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: 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 + // (metadata, join token name, etc). + 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 { + 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 *machineidv1pb.BotInstanceSpec, + initialAuth *machineidv1pb.BotInstanceStatusAuthentication, + expires time.Time, +) *machineidv1pb.BotInstance { + return &machineidv1pb.BotInstance{ + Kind: types.KindBotInstance, + Version: types.V1, + Metadata: &headerv1.Metadata{ + Expires: timestamppb.New(expires), + }, + Spec: spec, + Status: &machineidv1pb.BotInstanceStatus{ + InitialAuthentication: initialAuth, + LatestAuthentications: []*machineidv1pb.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 +295,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 *machineidv1pb.BotInstanceStatusAuthentication, +) (*proto.Certs, error) { var err error // Extract the user and role set for whom the certificate will be generated. @@ -216,6 +339,27 @@ 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(&machineidv1pb.BotInstanceSpec{ + BotName: botName, + InstanceId: uuid.String(), + }, initialAuth, expires.Add(machineidv1.ExpiryMargin)) + + _, err = a.BotInstance.CreateBotInstance(ctx, bi) + if err != nil { + return nil, trace.Wrap(err) + } + + botInstanceID = uuid.String() } // Generate certificate @@ -230,6 +374,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/bot_test.go b/lib/auth/bot_test.go index 4c54114a9652..e053a7cf8b86 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" @@ -44,6 +45,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" @@ -60,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) @@ -83,9 +86,18 @@ func renewBotCerts( // TestRegisterBotCertificateGenerationCheck ensures bot cert generation checks // work in ordinary conditions, with several rapid renewals. func TestRegisterBotCertificateGenerationCheck(t *testing.T) { - t.Parallel() + // 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() + fakeClock := srv.Clock().(clockwork.FakeClock) _, err := CreateRole(ctx, srv.Auth(), "example", types.RoleSpecV6{}) require.NoError(t, err) @@ -131,29 +143,152 @@ 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.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] + 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) // 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.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) + + require.True(t, bi.Metadata.Expires.AsTime().After(lastExpires), "Metadata.Expires must be extended") } } +// 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() + + 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) { diff --git a/lib/auth/join.go b/lib/auth/join.go index 53572c677b05..73c659bfca43 100644 --- a/lib/auth/join.go +++ b/lib/auth/join.go @@ -30,8 +30,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 +349,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, @@ -379,6 +365,20 @@ func (a *Server) generateCertsBot( RemoteAddr: req.RemoteAddr, }, } + + 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, + + // Note: Generation will be set during `generateInitialBotCerts()` as + // needed. + } + if joinAttributeSrc != nil { attributes, err := joinAttributeSrc.JoinAuditAttributes() if err != nil { @@ -388,7 +388,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 2d432b6a8917..5aef3d45e9fc 100644 --- a/lib/auth/keygen/keygen.go +++ b/lib/auth/keygen/keygen.go @@ -209,6 +209,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 000000000000..a72e81f55f7e --- /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 bot instance experiment is enabled. +func Enabled() bool { + return enabled.Load() +} + +// SetEnabled sets the bot instance experiment flag to the given value. +func SetEnabled(value bool) { + enabled.Store(value) +} diff --git a/lib/auth/machineid/machineidv1/bot_instance_service.go b/lib/auth/machineid/machineidv1/bot_instance_service.go index 3d07f7ebbcfa..8947cb27242c 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/authority.go b/lib/services/authority.go index 1a5e60bf4549..bc66fde3ba0d 100644 --- a/lib/services/authority.go +++ b/lib/services/authority.go @@ -383,6 +383,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/services/local/bot_instance.go b/lib/services/local/bot_instance.go index 1c2d55bbf7a0..2db2712473da 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 62f6a3a2585c..68c3eb040c62 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) diff --git a/lib/srv/authhandlers.go b/lib/srv/authhandlers.go index 8404b02fc0ce..d80f5704acfe 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 cccf7e0f95ae..f6f682eb61e8 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 d10ca4af0f7b..70b88917b715 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 {