From 9466c671cffbe5645c6eb82d9cbaa93f1c420dd9 Mon Sep 17 00:00:00 2001 From: Marco Dinis Date: Wed, 16 Oct 2024 15:15:15 +0100 Subject: [PATCH] UserTasks: Add SSM Document and Installer for DiscoverEC2 Issues This PR adds two new fields to the DiscoverEC2 User Task. SSM Document used to install teleport Teleport Installer script name used to install teleport This can be used to let the user know which scripts were used. For the SSM Document, users can then open it in webui. For the Installer Script, users can manage it using `tctl` client. --- .../go/teleport/usertasks/v1/user_tasks.pb.go | 45 ++++++++++++++----- .../teleport/usertasks/v1/user_tasks.proto | 6 +++ lib/srv/discovery/discovery.go | 2 + lib/srv/discovery/status.go | 2 + lib/srv/server/ssm_install.go | 20 +++++++++ lib/srv/server/ssm_install_test.go | 30 ++++++++----- 6 files changed, 85 insertions(+), 20 deletions(-) diff --git a/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go b/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go index 6e4362cf1943b..8ffd2d46bc67f 100644 --- a/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go +++ b/api/gen/proto/go/teleport/usertasks/v1/user_tasks.pb.go @@ -297,6 +297,12 @@ type DiscoverEC2Instance struct { DiscoveryGroup string `protobuf:"bytes,7,opt,name=discovery_group,json=discoveryGroup,proto3" json:"discovery_group,omitempty"` // SyncTime is the timestamp when the error was produced. SyncTime *timestamppb.Timestamp `protobuf:"bytes,8,opt,name=sync_time,json=syncTime,proto3" json:"sync_time,omitempty"` + // SSMDocument is the Amazon Systems Manager SSM Document name that was used to install teleport on the instance. + // In Amazon console, the document is at: + // https://REGION.console.aws.amazon.com/systems-manager/documents/SSM_DOCUMENT/description + SsmDocument string `protobuf:"bytes,9,opt,name=ssm_document,json=ssmDocument,proto3" json:"ssm_document,omitempty"` + // InstallerScript is the Teleport installer script that was used to install teleport on the instance. + InstallerScript string `protobuf:"bytes,10,opt,name=installer_script,json=installerScript,proto3" json:"installer_script,omitempty"` } func (x *DiscoverEC2Instance) Reset() { @@ -371,6 +377,20 @@ func (x *DiscoverEC2Instance) GetSyncTime() *timestamppb.Timestamp { return nil } +func (x *DiscoverEC2Instance) GetSsmDocument() string { + if x != nil { + return x.SsmDocument + } + return "" +} + +func (x *DiscoverEC2Instance) GetInstallerScript() string { + if x != nil { + return x.InstallerScript + } + return "" +} + var File_teleport_usertasks_v1_user_tasks_proto protoreflect.FileDescriptor var file_teleport_usertasks_v1_user_tasks_proto_rawDesc = []byte{ @@ -423,7 +443,7 @@ var file_teleport_usertasks_v1_user_tasks_proto_rawDesc = []byte{ 0x01, 0x28, 0x0b, 0x32, 0x2a, 0x2e, 0x74, 0x65, 0x6c, 0x65, 0x70, 0x6f, 0x72, 0x74, 0x2e, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x52, - 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x9e, 0x02, 0x0a, 0x13, 0x44, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xec, 0x02, 0x0a, 0x13, 0x44, 0x69, 0x73, 0x63, 0x6f, 0x76, 0x65, 0x72, 0x45, 0x43, 0x32, 0x49, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, 0x65, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6e, 0x63, @@ -439,15 +459,20 @@ var file_teleport_usertasks_v1_user_tasks_proto_rawDesc = []byte{ 0x75, 0x70, 0x12, 0x37, 0x0a, 0x09, 0x73, 0x79, 0x6e, 0x63, 0x5f, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x08, 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, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, - 0x04, 0x4a, 0x04, 0x08, 0x04, 0x10, 0x05, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, - 0x5f, 0x69, 0x64, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 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, 0x75, 0x73, 0x65, 0x72, 0x74, - 0x61, 0x73, 0x6b, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, - 0x73, 0x76, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x70, 0x52, 0x08, 0x73, 0x79, 0x6e, 0x63, 0x54, 0x69, 0x6d, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x73, + 0x73, 0x6d, 0x5f, 0x64, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x18, 0x09, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x0b, 0x73, 0x73, 0x6d, 0x44, 0x6f, 0x63, 0x75, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x29, + 0x0a, 0x10, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, 0x6c, 0x65, 0x72, 0x5f, 0x73, 0x63, 0x72, 0x69, + 0x70, 0x74, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x69, 0x6e, 0x73, 0x74, 0x61, 0x6c, + 0x6c, 0x65, 0x72, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, + 0x04, 0x08, 0x04, 0x10, 0x05, 0x52, 0x0a, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x5f, 0x69, + 0x64, 0x52, 0x06, 0x72, 0x65, 0x67, 0x69, 0x6f, 0x6e, 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, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, + 0x6b, 0x73, 0x2f, 0x76, 0x31, 0x3b, 0x75, 0x73, 0x65, 0x72, 0x74, 0x61, 0x73, 0x6b, 0x73, 0x76, + 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/api/proto/teleport/usertasks/v1/user_tasks.proto b/api/proto/teleport/usertasks/v1/user_tasks.proto index 81d8d52d17b63..dd7056d1651f3 100644 --- a/api/proto/teleport/usertasks/v1/user_tasks.proto +++ b/api/proto/teleport/usertasks/v1/user_tasks.proto @@ -89,4 +89,10 @@ message DiscoverEC2Instance { string discovery_group = 7; // SyncTime is the timestamp when the error was produced. google.protobuf.Timestamp sync_time = 8; + // SSMDocument is the Amazon Systems Manager SSM Document name that was used to install teleport on the instance. + // In Amazon console, the document is at: + // https://REGION.console.aws.amazon.com/systems-manager/documents/SSM_DOCUMENT/description + string ssm_document = 9; + // InstallerScript is the Teleport installer script that was used to install teleport on the instance. + string installer_script = 10; } diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index adb2c6727de8a..5f23a6252a86c 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -992,6 +992,8 @@ func (s *Server) handleEC2RemoteInstallation(instances *server.EC2Instances) err DiscoveryGroup: s.DiscoveryGroup, InstanceId: instance.InstanceID, SyncTime: timestamppb.New(s.clock.Now()), + SsmDocument: req.DocumentName, + InstallerScript: req.InstallerScriptName(), }, ) } diff --git a/lib/srv/discovery/status.go b/lib/srv/discovery/status.go index 8d6960e557e6d..7174b283d8ff1 100644 --- a/lib/srv/discovery/status.go +++ b/lib/srv/discovery/status.go @@ -314,6 +314,8 @@ func (s *Server) ReportEC2SSMInstallationResult(ctx context.Context, result *ser DiscoveryGroup: s.DiscoveryGroup, SyncTime: timestamppb.New(result.SSMRunEvent.Time), InstanceId: result.SSMRunEvent.InstanceID, + SsmDocument: result.SSMDocumentName, + InstallerScript: result.InstallerScript, }, ) diff --git a/lib/srv/server/ssm_install.go b/lib/srv/server/ssm_install.go index ac74601e7f3ae..6c0c2f427708e 100644 --- a/lib/srv/server/ssm_install.go +++ b/lib/srv/server/ssm_install.go @@ -63,6 +63,10 @@ type SSMInstallationResult struct { // IssueType identifies the type of issue that occurred if the installation failed. // These are well known identifiers that can be found at types.AutoDiscoverEC2Issue*. IssueType string + // SSMDocumentName is the Amazon SSM Document Name used to install Teleport into the instance. + SSMDocumentName string + // InstallerScript is the Teleport Installer script name used to install Teleport into the instance. + InstallerScript string } // SSMInstaller handles running SSM commands that install Teleport on EC2 instances. @@ -95,6 +99,16 @@ type SSMRunRequest struct { DiscoveryConfig string } +// InstallerScriptName returns the Teleport Installer script name. +// Returns empty string if not defined. +func (r *SSMRunRequest) InstallerScriptName() string { + if r == nil || r.Params == nil { + return "" + } + + return r.Params[ParamScriptName] +} + // CheckAndSetDefaults ensures the emitter is present and creates a default logger if one is not provided. func (c *SSMInstallerConfig) checkAndSetDefaults() error { if c.ReportSSMInstallationResultFunc == nil { @@ -212,6 +226,8 @@ func invalidSSMInstanceInstallationResult(req SSMRunRequest, instanceID, status, IntegrationName: req.IntegrationName, DiscoveryConfig: req.DiscoveryConfig, IssueType: issueType, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), } } @@ -359,6 +375,8 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com IntegrationName: req.IntegrationName, DiscoveryConfig: req.DiscoveryConfig, IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), })) } @@ -373,6 +391,8 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com IntegrationName: req.IntegrationName, DiscoveryConfig: req.DiscoveryConfig, IssueType: usertasks.AutoDiscoverEC2IssueSSMScriptFailure, + SSMDocumentName: req.DocumentName, + InstallerScript: req.InstallerScriptName(), })) } } diff --git a/lib/srv/server/ssm_install_test.go b/lib/srv/server/ssm_install_test.go index 5d24398433bcc..2107e91b0e590 100644 --- a/lib/srv/server/ssm_install_test.go +++ b/lib/srv/server/ssm_install_test.go @@ -144,7 +144,8 @@ func TestSSMInstaller(t *testing.T) { Status: ssm.CommandStatusSuccess, InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }}, }, { @@ -189,7 +190,8 @@ func TestSSMInstaller(t *testing.T) { Status: ssm.CommandStatusSuccess, InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument-without-sshdConfigPath-param", }}, }, { @@ -236,7 +238,8 @@ func TestSSMInstaller(t *testing.T) { StandardError: "timeout error", InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }}, }, { @@ -287,7 +290,8 @@ func TestSSMInstaller(t *testing.T) { StandardError: "timeout error", InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }}, }, { @@ -355,7 +359,8 @@ func TestSSMInstaller(t *testing.T) { Status: ssm.CommandStatusSuccess, InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }, { SSMRunEvent: &events.SSMRun{ @@ -370,7 +375,8 @@ func TestSSMInstaller(t *testing.T) { ExitCode: -1, Status: "SSM Agent in EC2 Instance is not connecting to SSM Service. Restart or reinstall the SSM service. See https://docs.aws.amazon.com/systems-manager/latest/userguide/ami-preinstalled-agent.html#verify-ssm-agent-status for more details.", }, - IssueType: "ec2-ssm-agent-connection-lost", + IssueType: "ec2-ssm-agent-connection-lost", + SSMDocumentName: "ssmdocument", }, { SSMRunEvent: &events.SSMRun{ @@ -385,7 +391,8 @@ func TestSSMInstaller(t *testing.T) { ExitCode: -1, Status: "EC2 instance is running an unsupported Operating System. Only Linux is supported.", }, - IssueType: "ec2-ssm-unsupported-os", + IssueType: "ec2-ssm-unsupported-os", + SSMDocumentName: "ssmdocument", }, { SSMRunEvent: &events.SSMRun{ @@ -400,7 +407,8 @@ func TestSSMInstaller(t *testing.T) { ExitCode: -1, Status: "EC2 Instance is not registered in SSM. Make sure that the instance has AmazonSSMManagedInstanceCore policy assigned.", }, - IssueType: "ec2-ssm-agent-not-registered", + IssueType: "ec2-ssm-agent-not-registered", + SSMDocumentName: "ssmdocument", }, }, }, @@ -456,7 +464,8 @@ func TestSSMInstaller(t *testing.T) { StandardOutput: "custom output", InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }}, }, { @@ -497,7 +506,8 @@ func TestSSMInstaller(t *testing.T) { Status: ssm.CommandStatusSuccess, InvocationURL: "https://eu-central-1.console.aws.amazon.com/systems-manager/run-command/command-id-1/instance-id-1", }, - IssueType: "ec2-ssm-script-failure", + IssueType: "ec2-ssm-script-failure", + SSMDocumentName: "ssmdocument", }}, }, // todo(amk): test that incomplete commands eventually return