From a4989c50dfeee327976ad472716b303d6e163042 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 23 Nov 2022 11:10:55 +0000 Subject: [PATCH 01/12] Add agentless installer --- api/client/proto/certs.pb.go | 67 ++++++- .../teleport/legacy/client/proto/certs.proto | 2 + .../installers/agentless-installer.sh.tmpl | 53 ++++++ api/types/installers/installers.go | 11 ++ lib/auth/auth.go | 14 +- lib/auth/grpcserver.go | 19 +- lib/config/configuration.go | 21 +- lib/config/fileconf.go | 17 +- lib/defaults/defaults.go | 5 + lib/services/matchers.go | 4 + lib/srv/discovery/discovery.go | 10 +- lib/srv/server/ec2_watcher.go | 34 +++- lib/srv/server/ssm_install.go | 6 +- tool/teleport/common/teleport.go | 179 ++++++++++++++++++ 14 files changed, 408 insertions(+), 34 deletions(-) create mode 100644 api/types/installers/agentless-installer.sh.tmpl diff --git a/api/client/proto/certs.pb.go b/api/client/proto/certs.pb.go index 1f17e92026fbb..af3cd18a1412c 100644 --- a/api/client/proto/certs.pb.go +++ b/api/client/proto/certs.pb.go @@ -32,7 +32,9 @@ type Certs struct { // TLSCACerts is a list of TLS certificate authorities. TLSCACerts [][]byte `protobuf:"bytes,3,rep,name=TLSCACerts,proto3" json:"tls_ca_certs,omitempty"` // SSHCACerts is a list of SSH certificate authorities. - SSHCACerts [][]byte `protobuf:"bytes,4,rep,name=SSHCACerts,proto3" json:"ssh_ca_certs,omitempty"` + SSHCACerts [][]byte `protobuf:"bytes,4,rep,name=SSHCACerts,proto3" json:"ssh_ca_certs,omitempty"` + // SSHUserCACerts is a list of SSH user certificate authorities. + SSHUserCACerts [][]byte `protobuf:"bytes,5,rep,name=SSHUserCACerts,proto3" json:"ssh_user_ca_certs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -80,23 +82,25 @@ func init() { } var fileDescriptor_9c4dbdba9a1559ee = []byte{ - // 251 bytes of a gzipped FileDescriptorProto + // 283 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x28, 0x49, 0xcd, 0x49, 0x2d, 0xc8, 0x2f, 0x2a, 0xd1, 0xcf, 0x49, 0x4d, 0x4f, 0x4c, 0xae, 0xd4, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x4f, 0x4e, 0x2d, 0x2a, 0x29, 0xd6, 0x03, 0xb3, 0x85, 0x58, 0xc1, 0x94, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x44, 0x16, 0xc4, 0x82, 0x48, - 0x2a, 0x9d, 0x64, 0xe4, 0x62, 0x75, 0x06, 0x29, 0x16, 0x52, 0xe6, 0x62, 0x0e, 0x0e, 0xf6, 0x90, + 0x2a, 0x4d, 0x62, 0xe2, 0x62, 0x75, 0x06, 0x29, 0x16, 0x52, 0xe6, 0x62, 0x0e, 0x0e, 0xf6, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x71, 0x12, 0x7c, 0x75, 0x4f, 0x9e, 0xb7, 0xb8, 0x38, 0x43, 0x27, 0x3f, 0x37, 0xb3, 0x24, 0x35, 0xb7, 0xa0, 0xa4, 0x32, 0x08, 0x24, 0x0b, 0x52, 0x14, 0xe2, 0x13, 0x2c, 0xc1, 0x84, 0x50, 0x54, 0x92, 0x53, 0x8c, 0xac, 0x28, 0xc4, 0x27, 0x58, 0xc8, 0x8a, 0x8b, 0x2b, 0xc4, 0x27, 0xd8, 0xd9, 0x11, 0x6c, 0xae, 0x04, 0xb3, 0x02, 0xb3, 0x06, 0x8f, 0x93, 0xd4, 0xab, 0x7b, 0xf2, 0x62, 0x25, 0x39, 0xc5, 0xf1, 0xc9, 0x89, 0xf1, 0x60, 0xc7, 0x21, 0x69, 0x42, 0x52, 0x0d, 0xd2, 0x1b, 0x1c, 0xec, 0x01, 0xd3, 0xcb, 0x82, 0xd0, 0x5b, 0x5c, 0x9c, 0x81, 0x55, - 0x2f, 0x42, 0xb5, 0x93, 0xcb, 0x89, 0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, - 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0x63, 0x94, 0x51, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, - 0x7e, 0xae, 0x7e, 0x7a, 0x51, 0x62, 0x59, 0x66, 0x49, 0x62, 0x49, 0x66, 0x7e, 0x5e, 0x62, 0x8e, - 0x3e, 0x3c, 0xf4, 0x12, 0x0b, 0x32, 0x51, 0x82, 0x2e, 0x89, 0x0d, 0x4c, 0x19, 0x03, 0x02, 0x00, - 0x00, 0xff, 0xff, 0x86, 0x6e, 0xe8, 0x40, 0x61, 0x01, 0x00, 0x00, + 0x2f, 0x42, 0xb5, 0x90, 0x3b, 0x17, 0x5f, 0x70, 0xb0, 0x47, 0x68, 0x71, 0x6a, 0x11, 0x4c, 0x3f, + 0x2b, 0x58, 0xbf, 0xfc, 0xab, 0x7b, 0xf2, 0xd2, 0x20, 0xfd, 0xa5, 0xc5, 0xa9, 0x45, 0xd8, 0x0c, + 0x41, 0xd3, 0xe6, 0xe4, 0x72, 0xe2, 0xa1, 0x1c, 0xc3, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, + 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x18, 0x65, 0x94, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, + 0x9f, 0xab, 0x9f, 0x5e, 0x94, 0x58, 0x96, 0x59, 0x92, 0x58, 0x92, 0x99, 0x9f, 0x97, 0x98, 0xa3, + 0x0f, 0x8f, 0x86, 0xc4, 0x82, 0x4c, 0x94, 0x38, 0x48, 0x62, 0x03, 0x53, 0xc6, 0x80, 0x00, 0x00, + 0x00, 0xff, 0xff, 0x1f, 0xc2, 0x5e, 0xf5, 0xaa, 0x01, 0x00, 0x00, } func (m *Certs) Marshal() (dAtA []byte, err error) { @@ -123,6 +127,15 @@ func (m *Certs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } + if len(m.SSHUserCACerts) > 0 { + for iNdEx := len(m.SSHUserCACerts) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.SSHUserCACerts[iNdEx]) + copy(dAtA[i:], m.SSHUserCACerts[iNdEx]) + i = encodeVarintCerts(dAtA, i, uint64(len(m.SSHUserCACerts[iNdEx]))) + i-- + dAtA[i] = 0x2a + } + } if len(m.SSHCACerts) > 0 { for iNdEx := len(m.SSHCACerts) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.SSHCACerts[iNdEx]) @@ -195,6 +208,12 @@ func (m *Certs) Size() (n int) { n += 1 + l + sovCerts(uint64(l)) } } + if len(m.SSHUserCACerts) > 0 { + for _, b := range m.SSHUserCACerts { + l = len(b) + n += 1 + l + sovCerts(uint64(l)) + } + } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -368,6 +387,38 @@ func (m *Certs) Unmarshal(dAtA []byte) error { m.SSHCACerts = append(m.SSHCACerts, make([]byte, postIndex-iNdEx)) copy(m.SSHCACerts[len(m.SSHCACerts)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SSHUserCACerts", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowCerts + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthCerts + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthCerts + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SSHUserCACerts = append(m.SSHUserCACerts, make([]byte, postIndex-iNdEx)) + copy(m.SSHUserCACerts[len(m.SSHUserCACerts)-1], dAtA[iNdEx:postIndex]) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCerts(dAtA[iNdEx:]) diff --git a/api/proto/teleport/legacy/client/proto/certs.proto b/api/proto/teleport/legacy/client/proto/certs.proto index 31f99fda7c2c6..dd8e46d0d6679 100644 --- a/api/proto/teleport/legacy/client/proto/certs.proto +++ b/api/proto/teleport/legacy/client/proto/certs.proto @@ -33,4 +33,6 @@ message Certs { repeated bytes TLSCACerts = 3 [(gogoproto.jsontag) = "tls_ca_certs,omitempty"]; // SSHCACerts is a list of SSH certificate authorities. repeated bytes SSHCACerts = 4 [(gogoproto.jsontag) = "ssh_ca_certs,omitempty"]; + // SSHUserCACerts is a list of SSH user certificate authorities. + repeated bytes SSHUserCACerts = 5 [(gogoproto.jsontag) = "ssh_user_ca_certs,omitempty"]; } diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl new file mode 100644 index 0000000000000..fb91d2f050e71 --- /dev/null +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -0,0 +1,53 @@ +#!/bin/sh +( + flock -n 9 || exit 1 + if grep -q 'TrustedUserCAKeys /etc/ssh/teleport_user_ca.pub' "$SSHD_CONFIG"; then + exit 0 + fi + + . /etc/os-release + + # old versions of ubuntu require that keys get added by `apt-key add`, without + # adding the key apt shows a key signing error when installing teleport. + LEGACY_UBUNTU=false + if [ "$VERSION_CODENAME" = "xenial" ] || [ "$VERSION_CODENAME" = "trusty" ]; then + LEGACY_UBUNTU=true + fi + + if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then + if [ "$LEGACY_UBUNTU" = true ]; then + curl -o /tmp/teleport-pubkey.asc https://deb.releases.teleport.dev/teleport-pubkey.asc + cat /tmp/teleport-pubkey.asc | sudo apt-key add - + echo "deb https://apt.releases.teleport.dev/ubuntu ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list + rm /tmp/teleport-pubkey.asc + else + sudo curl https://deb.releases.teleport.dev/teleport-pubkey.asc \ + -o /usr/share/keyrings/teleport-archive-keyring.asc + echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null + fi + sudo apt-get update + sudo apt-get install -y teleport + elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ]; then + if [ "$ID" = "rhel" ]; then + VERSION_ID=$(echo "$VERSION_ID" | sed 's/\..*//') # convert version numbers like '7.2' to only include the major version + fi + sudo yum-config-manager --add-repo \ + "$(rpm --eval "https://yum.releases.teleport.dev/$ID/$VERSION_ID/Teleport/%{_arch}/stable/{{ .MajorVersion }}/teleport.repo")" + sudo yum install -y teleport + else + echo "Unsupported distro: $ID" + exit 1 + fi + + IMDS_TOKEN=$(curl -m5 -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300") + PUBLIC_IP=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/meta-data/public-ipv4) + + sudo /usr/bin/teleport join \ + --openssh-config=/etc/ssh/sshd_config \ + --join-method=iam \ + --token="$1" \ + --proxy-server="{{ .PublicProxyAddr }}" \ + --additional-principals="$PUBLIC_IP" \ + --restart-sshd + +) 9>/var/lock/teleport_install.lock diff --git a/api/types/installers/installers.go b/api/types/installers/installers.go index bf07be57afe7b..ad649b6193e48 100644 --- a/api/types/installers/installers.go +++ b/api/types/installers/installers.go @@ -25,14 +25,25 @@ import ( //go:embed installer.sh.tmpl var defaultInstallScript string +//go:embed agentless-installer.sh.tmpl +var defaultAgentlessInstallScript string + // InstallerScriptName is the name of the by default populated, EC2 // installer script const InstallerScriptName = "default-installer" +// InstallerScriptName is the name of the by default populated, EC2 +// installer script when agentless mode is enabled for a matcher +const InstallerScriptNameAgentless = "default-agentless-installer" + // DefaultInstaller represents a the default installer script provided // by teleport var DefaultInstaller = types.MustNewInstallerV1(InstallerScriptName, defaultInstallScript) +// DefaultAgentlessInstaller represents a the default agentless installer script provided +// by teleport +var DefaultAgentlessInstaller = types.MustNewInstallerV1(InstallerScriptNameAgentless, defaultAgentlessInstallScript) + // Template is used to fill proxy address and version information into // the installer script type Template struct { diff --git a/lib/auth/auth.go b/lib/auth/auth.go index a9f2ad2b7bd93..8b345f805aa56 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2790,6 +2790,11 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.BadParameter("failed to load host CA for %q: %v", clusterName.GetClusterName(), err) } + userCA, err := client.GetCertAuthority(ctx, types.CertAuthID{ + Type: types.UserCA, + DomainName: clusterName.GetClusterName(), + }, true) + // could be a couple of scenarios, either client data is out of sync, // or auth server is out of sync, either way, for now check that // cache is out of sync, this will result in higher read rate @@ -2897,10 +2902,11 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.Wrap(err) } return &proto.Certs{ - SSH: hostSSHCert, - TLS: hostTLSCert, - TLSCACerts: services.GetTLSCerts(ca), - SSHCACerts: services.GetSSHCheckingKeys(ca), + SSH: hostSSHCert, + TLS: hostTLSCert, + TLSCACerts: services.GetTLSCerts(ca), + SSHCACerts: services.GetSSHCheckingKeys(ca), + SSHUserCACerts: services.GetSSHCheckingKeys(userCA), }, nil } diff --git a/lib/auth/grpcserver.go b/lib/auth/grpcserver.go index f401b2afd498e..8a58d9dc2ab64 100644 --- a/lib/auth/grpcserver.go +++ b/lib/auth/grpcserver.go @@ -4271,8 +4271,13 @@ func (g *GRPCServer) GetInstaller(ctx context.Context, req *types.ResourceReques } res, err := auth.GetInstaller(ctx, req.Name) if err != nil { - if trace.IsNotFound(err) && req.Name == installers.InstallerScriptName { - return installers.DefaultInstaller, nil + if trace.IsNotFound(err) { + switch req.Name { + case installers.InstallerScriptName: + return installers.DefaultInstaller, nil + case installers.InstallerScriptNameAgentless: + return installers.DefaultAgentlessInstaller, nil + } } return nil, trace.Wrap(err) } @@ -4295,13 +4300,17 @@ func (g *GRPCServer) GetInstallers(ctx context.Context, _ *emptypb.Empty) (*type } var installersV1 []*types.InstallerV1 needDefault := true + needDefaultAgentless := true for _, inst := range res { instV1, ok := inst.(*types.InstallerV1) if !ok { return nil, trace.BadParameter("unsupported installer type %T", inst) } - if inst.GetName() == installers.InstallerScriptName { + switch inst.GetName() { + case installers.InstallerScriptName: needDefault = false + case installers.InstallerScriptNameAgentless: + needDefaultAgentless = false } installersV1 = append(installersV1, instV1) } @@ -4310,6 +4319,7 @@ func (g *GRPCServer) GetInstallers(ctx context.Context, _ *emptypb.Empty) (*type return &types.InstallerV1List{ Installers: []*types.InstallerV1{ installers.DefaultInstaller, + installers.DefaultAgentlessInstaller, }, }, nil } @@ -4317,6 +4327,9 @@ func (g *GRPCServer) GetInstallers(ctx context.Context, _ *emptypb.Empty) (*type if needDefault { installersV1 = append(installersV1, installers.DefaultInstaller) } + if needDefaultAgentless { + installersV1 = append(installersV1, installers.DefaultAgentlessInstaller) + } return &types.InstallerV1List{ Installers: installersV1, diff --git a/lib/config/configuration.go b/lib/config/configuration.go index a22ffa4a59195..5b40e76201fa4 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -66,6 +66,8 @@ type CommandLineFlags struct { AuthServerAddr []string // --token flag AuthToken string + // --join-method flag + JoinMethod string // CAPins are the SKPI hashes of the CAs used to verify the Auth Server. CAPins []string // --listen-ip flag @@ -164,6 +166,17 @@ type CommandLineFlags struct { // DatabaseMySQLServerVersion is the MySQL server version reported to a client // if the value cannot be obtained from the database. DatabaseMySQLServerVersion string + + // ProxyServer is the url of the proxy server to connect to + ProxyServer string + // OpenSSHConfigPath is the path of the file to write agentless configuration to + OpenSSHConfigPath string + // OpenSSHKeysPath is the path to write teleport keys and certs into + OpenSSHKeysPath string + // AdditionalPrincipals are a list of extra prinicpals to include when generating host keys. + AdditionalPrincipals string + // RestartOpenSSH indicates whether openssh should be restarted or not. + RestartOpenSSH bool } // ReadConfigFile reads /etc/teleport.yaml (or whatever is passed via --config flag) @@ -1159,9 +1172,11 @@ func applyDiscoveryConfig(fc *FileConfig, cfg *service.Config) { Regions: matcher.Regions, Tags: matcher.Tags, Params: services.InstallerParams{ - JoinMethod: matcher.InstallParams.JoinParams.Method, - JoinToken: matcher.InstallParams.JoinParams.TokenName, - ScriptName: matcher.InstallParams.ScriptName, + JoinMethod: matcher.InstallParams.JoinParams.Method, + JoinToken: matcher.InstallParams.JoinParams.TokenName, + ScriptName: matcher.InstallParams.ScriptName, + InstallTeleport: matcher.InstallParams.InstallTeleport, + SSHDConfig: matcher.InstallParams.SSHDConfig, }, SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName}, }) diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 6af9fc8a4106f..ea5cb658e8b4f 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -526,17 +526,26 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { } else if method != types.JoinMethodIAM { return trace.BadParameter("only IAM joining is supported for EC2 auto-discovery") } + if token := matcher.InstallParams.JoinParams.TokenName; token == "" { matcher.InstallParams.JoinParams.TokenName = defaults.IAMInviteTokenName } if installer := matcher.InstallParams.ScriptName; installer == "" { - matcher.InstallParams.ScriptName = installers.InstallerScriptName + if matcher.InstallParams.InstallTeleport { + matcher.InstallParams.ScriptName = installers.InstallerScriptName + } else { + matcher.InstallParams.ScriptName = installers.InstallerScriptNameAgentless + } } } if matcher.SSM.DocumentName == "" { - matcher.SSM.DocumentName = defaults.AWSInstallerDocument + if matcher.InstallParams.InstallTeleport { + matcher.SSM.DocumentName = defaults.AWSInstallerDocument + } else { + matcher.SSM.DocumentName = defaults.AWSAgentlessInstallerDocument + } } } return nil @@ -1505,6 +1514,10 @@ type InstallParams struct { // ScriptName is the name of the teleport installer script // resource for the EC2 instance to execute ScriptName string `yaml:"script_name,omitempty"` + // InstallTeleport disables agentless discovery + InstallTeleport bool `yaml:"install_teleport"` + // SSHDConfig provides the path to write sshd configuration changes + SSHDConfig string `yaml:"sshd_config,omitempty"` } // AWSSSM provides options to use when executing SSM documents diff --git a/lib/defaults/defaults.go b/lib/defaults/defaults.go index 8fb98e1a88021..8b69520e3f454 100644 --- a/lib/defaults/defaults.go +++ b/lib/defaults/defaults.go @@ -845,6 +845,11 @@ const ( // AWSInstallerDocument is the name of the default AWS document // that will be called when executing the SSM command. AWSInstallerDocument = "TeleportDiscoveryInstaller" + + // AWSAgentlessInstallerDocument is the name of the default AWS document + // that will be called when executing the SSM command . + AWSAgentlessInstallerDocument = "TeleportAgentlessDiscoveryInstaller" + // IAMInviteTokenName is the name of the default Teleport IAM // token to use when templating the script to be executed. IAMInviteTokenName = "aws-discovery-iam-token" diff --git a/lib/services/matchers.go b/lib/services/matchers.go index f3b2cdee09302..0abd6e2fe07b6 100644 --- a/lib/services/matchers.go +++ b/lib/services/matchers.go @@ -60,6 +60,10 @@ type InstallerParams struct { // ScriptName is the name of the teleport script for the EC2 // instance to execute ScriptName string + // InstallTeleport disables agentless discovery + InstallTeleport bool + // SSHDConfig provides the path to write sshd configuration changes + SSHDConfig string } // AWSMatcher matches AWS databases. diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index dc5cfc13d504f..9241e0dfd9e77 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -344,7 +344,10 @@ func genInstancesLogStr[T any](instances []T, getID func(T) string) string { } func (s *Server) handleEC2Instances(instances *server.EC2Instances) error { - client, err := s.Clients.GetAWSSSMClient(instances.Region) + // TODO(amk): once agentless node inventory management is + // implemented, create nodes after a sucessful SSM run + + ec2Client, err := s.Clients.GetAWSSSMClient(instances.Region) if err != nil { return trace.Wrap(err) } @@ -355,9 +358,10 @@ func (s *Server) handleEC2Instances(instances *server.EC2Instances) error { s.Log.Debugf("Running Teleport installation on these instances: AccountID: %s, Instances: %s", instances.AccountID, genEC2InstancesLogStr(instances.Instances)) + req := server.SSMRunRequest{ DocumentName: instances.DocumentName, - SSM: client, + SSM: ec2Client, Instances: instances.Instances, Params: instances.Parameters, Region: instances.Region, @@ -379,12 +383,14 @@ func (s *Server) handleEC2Discovery() { ec2Instances := instances.EC2Instances s.Log.Debugf("EC2 instances discovered (AccountID: %s, Instances: %v), starting installation", instances.AccountID, genEC2InstancesLogStr(ec2Instances.Instances)) + if err := s.handleEC2Instances(ec2Instances); err != nil { if trace.IsNotFound(err) { s.Log.Debug("All discovered EC2 instances are already part of the cluster.") } else { s.Log.WithError(err).Error("Failed to enroll discovered EC2 instances.") } + } case <-s.ctx.Done(): s.ec2Watcher.Stop() diff --git a/lib/srv/server/ec2_watcher.go b/lib/srv/server/ec2_watcher.go index 9eaa4ba020105..ca2d16f8aeb56 100644 --- a/lib/srv/server/ec2_watcher.go +++ b/lib/srv/server/ec2_watcher.go @@ -65,19 +65,24 @@ func NewEC2Watcher(ctx context.Context, matchers []services.AWSMatcher, clients fetchInterval: time.Minute, InstancesC: make(chan Instances), } + for _, matcher := range matchers { for _, region := range matcher.Regions { - cl, err := clients.GetAWSEC2Client(region) + ec2Client, err := clients.GetAWSEC2Client(region) if err != nil { return nil, trace.Wrap(err) } - fetcher := newEC2InstanceFetcher(ec2FetcherConfig{ + + fetcher, err := newEC2InstanceFetcher(ec2FetcherConfig{ Matcher: matcher, Region: region, Document: matcher.SSM.DocumentName, - EC2Client: cl, + EC2Client: ec2Client, Labels: matcher.Tags, }) + if err != nil { + return nil, trace.Wrap(err) + } watcher.fetchers = append(watcher.fetchers, fetcher) } } @@ -98,9 +103,10 @@ type ec2InstanceFetcher struct { Region string DocumentName string Parameters map[string]string + handler func(*ec2InstanceFetcher, *ec2.Reservation, []Instances) } -func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { +func newEC2InstanceFetcher(cfg ec2FetcherConfig) (*ec2InstanceFetcher, error) { tagFilters := []*ec2.Filter{{ Name: aws.String(AWSInstanceStateName), Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}), @@ -116,18 +122,28 @@ func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { } else { log.Debug("Not setting any tag filters as there is a '*:...' tag present and AWS doesnt allow globbing on keys") } + var parameters map[string]string + if cfg.Matcher.Params.InstallTeleport { + parameters = map[string]string{ + "token": cfg.Matcher.Params.JoinToken, + "scriptName": cfg.Matcher.Params.ScriptName, + } + } else { + parameters = map[string]string{ + "token": cfg.Matcher.Params.JoinToken, + "scriptName": cfg.Matcher.Params.ScriptName, + "sshdConfigPath": cfg.Matcher.Params.SSHDConfig, + } + } fetcherConfig := ec2InstanceFetcher{ EC2: cfg.EC2Client, Filters: tagFilters, Region: cfg.Region, DocumentName: cfg.Document, - Parameters: map[string]string{ - "token": cfg.Matcher.Params.JoinToken, - "scriptName": cfg.Matcher.Params.ScriptName, - }, + Parameters: parameters, } - return &fetcherConfig + return &fetcherConfig, nil } // GetInstances fetches all EC2 instances matching configured filters. diff --git a/lib/srv/server/ssm_install.go b/lib/srv/server/ssm_install.go index e55d07d7bc415..f817059bce537 100644 --- a/lib/srv/server/ssm_install.go +++ b/lib/srv/server/ssm_install.go @@ -29,7 +29,6 @@ import ( "github.com/gravitational/trace" "golang.org/x/sync/errgroup" - "github.com/gravitational/teleport/api/types/events" apievents "github.com/gravitational/teleport/api/types/events" libevents "github.com/gravitational/teleport/lib/events" ) @@ -83,6 +82,7 @@ func (si *SSMInstaller) Run(ctx context.Context, req SSMRunRequest) error { for k, v := range req.Params { params[k] = []*string{aws.String(v)} } + output, err := req.SSM.SendCommandWithContext(ctx, &ssm.SendCommandInput{ DocumentName: aws.String(req.DocumentName), InstanceIds: aws.StringSlice(ids), @@ -144,8 +144,8 @@ func (si *SSMInstaller) checkCommand(ctx context.Context, req SSMRunRequest, com if exitCode == 0 && code == libevents.SSMRunFailCode { exitCode = -1 } - event := events.SSMRun{ - Metadata: events.Metadata{ + event := apievents.SSMRun{ + Metadata: apievents.Metadata{ Type: libevents.SSMRunEvent, Code: code, }, diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 84f19b69de21e..d688f8a6d7f31 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -19,8 +19,10 @@ package common import ( "context" "fmt" + "io" "net/url" "os" + "os/exec" "os/user" "path" "path/filepath" @@ -29,10 +31,16 @@ import ( "github.com/gravitational/kingpin" "github.com/gravitational/trace" + "github.com/hashicorp/go-uuid" log "github.com/sirupsen/logrus" + "golang.org/x/crypto/ssh" "github.com/gravitational/teleport" + "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" + "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/auth/native" + "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/config" awsconfigurators "github.com/gravitational/teleport/lib/configurators/aws" "github.com/gravitational/teleport/lib/defaults" @@ -40,6 +48,7 @@ import ( "github.com/gravitational/teleport/lib/service" "github.com/gravitational/teleport/lib/srv" "github.com/gravitational/teleport/lib/sshutils/scp" + "github.com/gravitational/teleport/lib/tlsca" "github.com/gravitational/teleport/lib/utils" ) @@ -84,6 +93,7 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con status := app.Command("status", "Print the status of the current SSH session.") dump := app.Command("configure", "Generate a simple config file to get started.") ver := app.Command("version", "Print the version of your teleport binary.") + join := app.Command("join", "Download the Teleport CA and SSH host keys and update the SSH config to use them") scpc := app.Command("scp", "Server-side implementation of SCP.").Hidden() sftp := app.Command("sftp", "Server-side implementation of SFTP.").Hidden() exec := app.Command(teleport.ExecSubCommand, "Used internally by Teleport to re-exec itself to run a command.").Hidden() @@ -388,6 +398,16 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con kubeState := app.Command("kube-state", "Used internally by Teleport to operate Kubernetes Secrets where Teleport stores its state.").Hidden() kubeStateDelete := kubeState.Command("delete", "Used internally to delete Kubernetes states when the helm chart is uninstalled.").Hidden() + // teleport join --proxy-server=proxy.example.com --token=aws-join-token [--openssh-config=/path/to/sshd.conf] [--restart-sshd=true] + join.Flag("proxy-server", "Address of the proxy server.").StringVar(&ccf.ProxyServer) + join.Flag("token", "Invitation token to register with an auth server.").StringVar(&ccf.AuthToken) + join.Flag("join-method", "Method to use to join the cluster (token, iam, ec2)").EnumVar(&ccf.JoinMethod, "token", "iam", "ec2") + join.Flag("openssh-config", "Path to the OpenSSH config file").Default("/etc/ssh/sshd_config").StringVar(&ccf.OpenSSHConfigPath) + join.Flag("openssh-keys-path", "Path to the place teleport keys and certs").Default("/etc/ssh").StringVar(&ccf.OpenSSHKeysPath) + join.Flag("restart-sshd", "Restart OpenSSH").Default("true").BoolVar(&ccf.RestartOpenSSH) + join.Flag("insecure", "Insecure mode disables certificate validation").BoolVar(&ccf.InsecureMode) + join.Flag("additional-principals", "Comma separated list of host names the node can be accessed by").StringVar(&ccf.AdditionalPrincipals) + // parse CLI commands+flags: utils.UpdateAppUsageTemplate(app, options.Args) command, err := app.Parse(options.Args) @@ -465,6 +485,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con case discoveryBootstrapCmd.FullCommand(): configureDiscoveryBootstrapFlags.config.DiscoveryService = true err = onConfigureDiscoveryBootstrap(configureDiscoveryBootstrapFlags) + case join.FullCommand(): + err = onJoin(ccf) } if err != nil { utils.FatalError(err) @@ -489,6 +511,163 @@ func OnStart(clf config.CommandLineFlags, config *service.Config) error { return service.Run(context.TODO(), *config, nil) } +// GenerateKeys generates TLS and SSH keypairs. +func GenerateKeys() (private, sshpub, tlspub []byte, err error) { + privateKey, publicKey, err := native.GenerateKeyPair() + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + sshPrivateKey, err := ssh.ParseRawPrivateKey(privateKey) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + tlsPublicKey, err := tlsca.MarshalPublicKeyFromPrivateKeyPEM(sshPrivateKey) + if err != nil { + return nil, nil, nil, trace.Wrap(err) + } + + return privateKey, publicKey, tlsPublicKey, nil +} + +func onJoin(clf config.CommandLineFlags) error { + addr, err := utils.ParseAddr(clf.ProxyServer) + if err != nil { + return trace.Wrap(err) + } + privateKey, sshPublicKey, tlsPublicKey, err := GenerateKeys() + if err != nil { + return trace.Wrap(err, "unable to generate new keypairs") + } + + hostname, err := os.Hostname() + if err != nil { + return trace.Wrap(err) + } + + // TODO(amk) get uuid from a cli argument once agentless inventory management is implemented to allow tsh ssh access via uuid + uuid, err := uuid.GenerateUUID() + _ = err + principals := append(strings.Split(clf.AdditionalPrincipals, ","), uuid) + + certs, err := auth.Register( + auth.RegisterParams{ + Token: clf.AuthToken, + AdditionalPrincipals: principals, + JoinMethod: types.JoinMethod(clf.JoinMethod), + ID: auth.IdentityID{ + Role: types.RoleNode, + NodeName: hostname, + HostUUID: uuid, + }, + ProxyServer: *addr, + PublicTLSKey: tlsPublicKey, + PublicSSHKey: sshPublicKey, + GetHostCredentials: client.HostCredentials, + }, + ) + if err != nil { + return trace.Wrap(err) + } + + fmt.Printf("Writing Teleport keys to %s\n", clf.OpenSSHKeysPath) + if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs); err != nil { + return trace.Wrap(err) + } + + fmt.Println("Updating OpenSSH config") + if err := updateSSHDConfig(clf.OpenSSHKeysPath, clf.OpenSSHConfigPath); err != nil { + return trace.Wrap(err) + } + + fmt.Println("Restarting the OpenSSH daemon") + if err := restartSSHD(); err != nil { + return trace.Wrap(err) + } + + return nil +} + +func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs) error { + if err := os.WriteFile(filepath.Join(sshdConfigDir, "teleport"), private, 0600); err != nil { + return trace.Wrap(err) + } + + if err := os.WriteFile(filepath.Join(sshdConfigDir, "teleport-cert.pub"), certs.SSH, 0600); err != nil { + return trace.Wrap(err) + } + + certsFile, err := os.Create(filepath.Join(sshdConfigDir, "teleport_user_ca.pub")) + if err != nil { + return trace.Wrap(err) + } + defer certsFile.Close() + for _, cert := range certs.SSHUserCACerts { + if _, err := certsFile.Write(cert); err != nil { + return trace.Wrap(err) + } + if _, err := certsFile.WriteString("\n"); err != nil { + return trace.Wrap(err) + } + } + + return nil +} + +func updateSSHDConfig(keyDir, sshdConfigPath string) error { + // has to write to the beginning of the sshd_config file as + // openssh takes the first occurance of a setting + sshdConfig, err := os.OpenFile(sshdConfigPath, os.O_RDONLY|os.O_CREATE, 0644) + if err != nil { + return trace.Wrap(err) + } + defer sshdConfig.Close() + + configUpdate := fmt.Sprintf(` +### Section created by 'teleport join' +TrustedUserCaKeys %s +HostKey %s +HostCertificate %s +### Section end +`, + filepath.Join(keyDir, "teleport_user_ca.pub"), + filepath.Join(keyDir, "teleport"), + filepath.Join(keyDir, "teleport-cert.pub"), + ) + sshdConfigTmp, err := os.CreateTemp(keyDir, "") + if err != nil { + return trace.Wrap(err) + } + defer sshdConfigTmp.Close() + if _, err := sshdConfigTmp.Write([]byte(configUpdate)); err != nil { + return trace.Wrap(err) + } + + if _, err := io.Copy(sshdConfigTmp, sshdConfig); err != nil { + return trace.Wrap(err) + } + + if err := os.Rename(sshdConfigTmp.Name(), sshdConfigPath); err != nil { + return trace.Wrap(err) + } + + return nil +} + +func restartSSHD() error { + cmd := exec.Command("sshd", "-t") + if err := cmd.Run(); err != nil { + return trace.Wrap(err, "teleport generated an invalid ssh config file") + } + + cmd = exec.Command("systemctl", "restart", "sshd") + if err := cmd.Run(); err != nil { + return trace.Wrap(err, "teleport failed to restart the sshd service") + } + return nil +} + // onStatus is the handler for "status" CLI command func onStatus() error { sshClient := os.Getenv("SSH_CLIENT") From bef1bb02d4aa7fc1f9e38dfd19adb0fe65c42366 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Thu, 29 Dec 2022 11:45:07 +0000 Subject: [PATCH 02/12] Resolve comments --- .../installers/agentless-installer.sh.tmpl | 38 +++++++------- lib/auth/auth.go | 4 ++ lib/config/configuration.go | 22 ++++---- lib/config/configuration_test.go | 11 ++++ lib/config/fileconf.go | 50 +++++++++++++++++-- lib/config/testdata_test.go | 6 +++ lib/defaults/defaults.go | 4 ++ lib/srv/server/ec2_watcher.go | 7 ++- tool/teleport/common/teleport.go | 39 +++++++++------ 9 files changed, 127 insertions(+), 54 deletions(-) diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl index fb91d2f050e71..19aabb73a57e5 100644 --- a/api/types/installers/agentless-installer.sh.tmpl +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -11,24 +11,24 @@ # adding the key apt shows a key signing error when installing teleport. LEGACY_UBUNTU=false if [ "$VERSION_CODENAME" = "xenial" ] || [ "$VERSION_CODENAME" = "trusty" ]; then - LEGACY_UBUNTU=true + LEGACY_UBUNTU=true fi if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then - if [ "$LEGACY_UBUNTU" = true ]; then - curl -o /tmp/teleport-pubkey.asc https://deb.releases.teleport.dev/teleport-pubkey.asc - cat /tmp/teleport-pubkey.asc | sudo apt-key add - - echo "deb https://apt.releases.teleport.dev/ubuntu ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list - rm /tmp/teleport-pubkey.asc - else - sudo curl https://deb.releases.teleport.dev/teleport-pubkey.asc \ - -o /usr/share/keyrings/teleport-archive-keyring.asc - echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null - fi + if [ "$LEGACY_UBUNTU" = true ]; then + curl -o /tmp/teleport-pubkey.asc https://deb.releases.teleport.dev/teleport-pubkey.asc + cat /tmp/teleport-pubkey.asc | sudo apt-key add - + echo "deb https://apt.releases.teleport.dev/ubuntu ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list + rm /tmp/teleport-pubkey.asc + else + sudo curl https://deb.releases.teleport.dev/teleport-pubkey.asc \ + -o /usr/share/keyrings/teleport-archive-keyring.asc + echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null + fi sudo apt-get update sudo apt-get install -y teleport elif [ "$ID" = "amzn" ] || [ "$ID" = "rhel" ]; then - if [ "$ID" = "rhel" ]; then + if [ "$ID" = "rhel" ]; then VERSION_ID=$(echo "$VERSION_ID" | sed 's/\..*//') # convert version numbers like '7.2' to only include the major version fi sudo yum-config-manager --add-repo \ @@ -42,12 +42,12 @@ IMDS_TOKEN=$(curl -m5 -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300") PUBLIC_IP=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/meta-data/public-ipv4) - sudo /usr/bin/teleport join \ - --openssh-config=/etc/ssh/sshd_config \ - --join-method=iam \ - --token="$1" \ - --proxy-server="{{ .PublicProxyAddr }}" \ - --additional-principals="$PUBLIC_IP" \ - --restart-sshd + sudo /usr/bin/teleport join openssh \ + --openssh-config="${SSHD_CONFIG}" \ + --join-method=iam \ + --token="$1" \ + --proxy-server="{{ .PublicProxyAddr }}" \ + --additional-principals="$PUBLIC_IP" \ + --restart-sshd ) 9>/var/lock/teleport_install.lock diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 8b345f805aa56..5c5e0888294f1 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2795,6 +2795,10 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ DomainName: clusterName.GetClusterName(), }, true) + if err != nil { + return nil, trace.Wrap(err) + } + // could be a couple of scenarios, either client data is out of sync, // or auth server is out of sync, either way, for now check that // cache is out of sync, this will result in higher read rate diff --git a/lib/config/configuration.go b/lib/config/configuration.go index 5b40e76201fa4..ce2332abb996b 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -446,7 +446,9 @@ func ApplyFileConfig(fc *FileConfig, cfg *service.Config) error { } if fc.Discovery.Enabled() { - applyDiscoveryConfig(fc, cfg) + if err := applyDiscoveryConfig(fc, cfg); err != nil { + return trace.Wrap(err) + } } return nil @@ -1163,22 +1165,21 @@ func applySSHConfig(fc *FileConfig, cfg *service.Config) (err error) { return nil } -func applyDiscoveryConfig(fc *FileConfig, cfg *service.Config) { +func applyDiscoveryConfig(fc *FileConfig, cfg *service.Config) error { cfg.Discovery.Enabled = fc.Discovery.Enabled() for _, matcher := range fc.Discovery.AWSMatchers { + installParams, err := matcher.InstallParams.Parse() + if err != nil { + return trace.Wrap(err) + } + cfg.Discovery.AWSMatchers = append(cfg.Discovery.AWSMatchers, services.AWSMatcher{ Types: matcher.Types, Regions: matcher.Regions, Tags: matcher.Tags, - Params: services.InstallerParams{ - JoinMethod: matcher.InstallParams.JoinParams.Method, - JoinToken: matcher.InstallParams.JoinParams.TokenName, - ScriptName: matcher.InstallParams.ScriptName, - InstallTeleport: matcher.InstallParams.InstallTeleport, - SSHDConfig: matcher.InstallParams.SSHDConfig, - }, - SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName}, + Params: installParams, + SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName}, }) } @@ -1207,6 +1208,7 @@ func applyDiscoveryConfig(fc *FileConfig, cfg *service.Config) { ) } + return nil } // applyKubeConfig applies file configuration for the "kubernetes_service" section. diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index 7675a69c1eeff..cd94d5e730850 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -829,6 +829,17 @@ SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 "testKey": "testValue", }, )) + + require.True(t, cfg.Discovery.Enabled) + require.Equal(t, cfg.Discovery.AWSMatchers[0].Regions, []string{"eu-central-1"}) + require.Equal(t, cfg.Discovery.AWSMatchers[0].Types, []string{"ec2"}) + require.Equal(t, cfg.Discovery.AWSMatchers[0].Params, services.InstallerParams{ + InstallTeleport: true, + JoinMethod: "iam", + JoinToken: defaults.IAMInviteTokenName, + ScriptName: "default-installer", + SSHDConfig: defaults.SSHDConfigPath, + }) } // TestApplyConfigNoneEnabled makes sure that if a section is not enabled, diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index ea5cb658e8b4f..085c134ab1942 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -487,6 +487,7 @@ func awsRegions() []string { // and validates the provided types. func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { regions := awsRegions() + var err error for i := range matcherInput { matcher := &matcherInput[i] for _, matcherType := range matcher.Types { @@ -512,13 +513,21 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { matcher.Tags = map[string]apiutils.Strings{types.Wildcard: {types.Wildcard}} } + var installParams services.InstallerParams + if matcher.InstallParams == nil { matcher.InstallParams = &InstallParams{ JoinParams: JoinParams{ TokenName: defaults.IAMInviteTokenName, Method: types.JoinMethodIAM, }, - ScriptName: installers.InstallerScriptName, + ScriptName: installers.InstallerScriptName, + InstallTeleport: "", + SSHDConfig: defaults.SSHDConfigPath, + } + installParams, err = matcher.InstallParams.Parse() + if err != nil { + return trace.Wrap(err) } } else { if method := matcher.InstallParams.JoinParams.Method; method == "" { @@ -527,12 +536,21 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { return trace.BadParameter("only IAM joining is supported for EC2 auto-discovery") } - if token := matcher.InstallParams.JoinParams.TokenName; token == "" { + if matcher.InstallParams.JoinParams.TokenName == "" { matcher.InstallParams.JoinParams.TokenName = defaults.IAMInviteTokenName } + if matcher.InstallParams.SSHDConfig == "" { + matcher.InstallParams.SSHDConfig = defaults.SSHDConfigPath + } + + installParams, err = matcher.InstallParams.Parse() + if err != nil { + return trace.Wrap(err) + } + if installer := matcher.InstallParams.ScriptName; installer == "" { - if matcher.InstallParams.InstallTeleport { + if installParams.InstallTeleport { matcher.InstallParams.ScriptName = installers.InstallerScriptName } else { matcher.InstallParams.ScriptName = installers.InstallerScriptNameAgentless @@ -541,7 +559,7 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { } if matcher.SSM.DocumentName == "" { - if matcher.InstallParams.InstallTeleport { + if installParams.InstallTeleport { matcher.SSM.DocumentName = defaults.AWSInstallerDocument } else { matcher.SSM.DocumentName = defaults.AWSAgentlessInstallerDocument @@ -1515,11 +1533,33 @@ type InstallParams struct { // resource for the EC2 instance to execute ScriptName string `yaml:"script_name,omitempty"` // InstallTeleport disables agentless discovery - InstallTeleport bool `yaml:"install_teleport"` + InstallTeleport string `yaml:"install_teleport,omitempty"` // SSHDConfig provides the path to write sshd configuration changes SSHDConfig string `yaml:"sshd_config,omitempty"` } +func (ip *InstallParams) Parse() (services.InstallerParams, error) { + install := services.InstallerParams{ + JoinMethod: ip.JoinParams.Method, + JoinToken: ip.JoinParams.TokenName, + ScriptName: ip.ScriptName, + InstallTeleport: true, + SSHDConfig: ip.SSHDConfig, + } + + if ip.InstallTeleport == "" { + return install, nil + } + + var err error + install.InstallTeleport, err = apiutils.ParseBool(ip.InstallTeleport) + if err != nil { + return services.InstallerParams{}, trace.Wrap(err) + } + + return install, nil +} + // AWSSSM provides options to use when executing SSM documents type AWSSSM struct { // DocumentName is the name of the document to use when executing an diff --git a/lib/config/testdata_test.go b/lib/config/testdata_test.go index 86d799050a26c..6fc7f2c6a7d00 100644 --- a/lib/config/testdata_test.go +++ b/lib/config/testdata_test.go @@ -189,6 +189,12 @@ kubernetes_service: kubeconfig_file: /tmp/kubeconfig labels: 'testKey': 'testValue' + +discovery_service: + enabled: yes + aws: + - types: ["ec2"] + regions: ["eu-central-1"] ` // NoServicesConfigString is a configuration file with no services enabled diff --git a/lib/defaults/defaults.go b/lib/defaults/defaults.go index 8b69520e3f454..838a4e726b724 100644 --- a/lib/defaults/defaults.go +++ b/lib/defaults/defaults.go @@ -853,4 +853,8 @@ const ( // IAMInviteTokenName is the name of the default Teleport IAM // token to use when templating the script to be executed. IAMInviteTokenName = "aws-discovery-iam-token" + + // SSHDConfigPath is the path to the sshd config file to modify + // when using the agentless installer + SSHDConfigPath = "/etc/ssh/sshd_config" ) diff --git a/lib/srv/server/ec2_watcher.go b/lib/srv/server/ec2_watcher.go index ca2d16f8aeb56..881e8810e58e2 100644 --- a/lib/srv/server/ec2_watcher.go +++ b/lib/srv/server/ec2_watcher.go @@ -73,7 +73,7 @@ func NewEC2Watcher(ctx context.Context, matchers []services.AWSMatcher, clients return nil, trace.Wrap(err) } - fetcher, err := newEC2InstanceFetcher(ec2FetcherConfig{ + fetcher := newEC2InstanceFetcher(ec2FetcherConfig{ Matcher: matcher, Region: region, Document: matcher.SSM.DocumentName, @@ -103,10 +103,9 @@ type ec2InstanceFetcher struct { Region string DocumentName string Parameters map[string]string - handler func(*ec2InstanceFetcher, *ec2.Reservation, []Instances) } -func newEC2InstanceFetcher(cfg ec2FetcherConfig) (*ec2InstanceFetcher, error) { +func newEC2InstanceFetcher(cfg ec2FetcherConfig) *ec2InstanceFetcher { tagFilters := []*ec2.Filter{{ Name: aws.String(AWSInstanceStateName), Values: aws.StringSlice([]string{ec2.InstanceStateNameRunning}), @@ -143,7 +142,7 @@ func newEC2InstanceFetcher(cfg ec2FetcherConfig) (*ec2InstanceFetcher, error) { DocumentName: cfg.Document, Parameters: parameters, } - return &fetcherConfig, nil + return &fetcherConfig } // GetInstances fetches all EC2 instances matching configured filters. diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index d688f8a6d7f31..f16454bcfe6a5 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -93,7 +93,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con status := app.Command("status", "Print the status of the current SSH session.") dump := app.Command("configure", "Generate a simple config file to get started.") ver := app.Command("version", "Print the version of your teleport binary.") - join := app.Command("join", "Download the Teleport CA and SSH host keys and update the SSH config to use them") + join := app.Command("join", "Join a Teleport cluster without running the Teleport daemon") + joinOpenSSH := join.Command("openssh", "Join an SSH server to a Teleport cluster") scpc := app.Command("scp", "Server-side implementation of SCP.").Hidden() sftp := app.Command("sftp", "Server-side implementation of SFTP.").Hidden() exec := app.Command(teleport.ExecSubCommand, "Used internally by Teleport to re-exec itself to run a command.").Hidden() @@ -399,14 +400,14 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con kubeStateDelete := kubeState.Command("delete", "Used internally to delete Kubernetes states when the helm chart is uninstalled.").Hidden() // teleport join --proxy-server=proxy.example.com --token=aws-join-token [--openssh-config=/path/to/sshd.conf] [--restart-sshd=true] - join.Flag("proxy-server", "Address of the proxy server.").StringVar(&ccf.ProxyServer) - join.Flag("token", "Invitation token to register with an auth server.").StringVar(&ccf.AuthToken) - join.Flag("join-method", "Method to use to join the cluster (token, iam, ec2)").EnumVar(&ccf.JoinMethod, "token", "iam", "ec2") - join.Flag("openssh-config", "Path to the OpenSSH config file").Default("/etc/ssh/sshd_config").StringVar(&ccf.OpenSSHConfigPath) - join.Flag("openssh-keys-path", "Path to the place teleport keys and certs").Default("/etc/ssh").StringVar(&ccf.OpenSSHKeysPath) - join.Flag("restart-sshd", "Restart OpenSSH").Default("true").BoolVar(&ccf.RestartOpenSSH) - join.Flag("insecure", "Insecure mode disables certificate validation").BoolVar(&ccf.InsecureMode) - join.Flag("additional-principals", "Comma separated list of host names the node can be accessed by").StringVar(&ccf.AdditionalPrincipals) + joinOpenSSH.Flag("proxy-server", "Address of the proxy server.").StringVar(&ccf.ProxyServer) + joinOpenSSH.Flag("token", "Invitation token to register with an auth server.").StringVar(&ccf.AuthToken) + joinOpenSSH.Flag("join-method", "Method to use to join the cluster (token, iam, ec2)").EnumVar(&ccf.JoinMethod, "token", "iam", "ec2") + joinOpenSSH.Flag("openssh-config", "Path to the OpenSSH config file").Default("/etc/ssh/sshd_config").StringVar(&ccf.OpenSSHConfigPath) + joinOpenSSH.Flag("openssh-keys-path", "Path to the place teleport keys and certs").Default("/etc/ssh").StringVar(&ccf.OpenSSHKeysPath) + joinOpenSSH.Flag("restart-sshd", "Restart OpenSSH").Default("true").BoolVar(&ccf.RestartOpenSSH) + joinOpenSSH.Flag("insecure", "Insecure mode disables certificate validation").BoolVar(&ccf.InsecureMode) + joinOpenSSH.Flag("additional-principals", "Comma separated list of host names the node can be accessed by").StringVar(&ccf.AdditionalPrincipals) // parse CLI commands+flags: utils.UpdateAppUsageTemplate(app, options.Args) @@ -485,8 +486,8 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con case discoveryBootstrapCmd.FullCommand(): configureDiscoveryBootstrapFlags.config.DiscoveryService = true err = onConfigureDiscoveryBootstrap(configureDiscoveryBootstrapFlags) - case join.FullCommand(): - err = onJoin(ccf) + case joinOpenSSH.FullCommand(): + err = onJoinOpenSSH(ccf) } if err != nil { utils.FatalError(err) @@ -531,7 +532,7 @@ func GenerateKeys() (private, sshpub, tlspub []byte, err error) { return privateKey, publicKey, tlsPublicKey, nil } -func onJoin(clf config.CommandLineFlags) error { +func onJoinOpenSSH(clf config.CommandLineFlags) error { addr, err := utils.ParseAddr(clf.ProxyServer) if err != nil { return trace.Wrap(err) @@ -589,16 +590,22 @@ func onJoin(clf config.CommandLineFlags) error { return nil } +const ( + teleportKey = "teleport" + teleportCert = "teleport-cert.pub" + teleportUserCA = "teleport_user_ca.pub" +) + func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs) error { - if err := os.WriteFile(filepath.Join(sshdConfigDir, "teleport"), private, 0600); err != nil { + if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportKey), private, 0600); err != nil { return trace.Wrap(err) } - if err := os.WriteFile(filepath.Join(sshdConfigDir, "teleport-cert.pub"), certs.SSH, 0600); err != nil { + if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportCert), certs.SSH, 0600); err != nil { return trace.Wrap(err) } - certsFile, err := os.Create(filepath.Join(sshdConfigDir, "teleport_user_ca.pub")) + certsFile, err := os.Create(filepath.Join(sshdConfigDir, teleportUserCA)) if err != nil { return trace.Wrap(err) } @@ -625,7 +632,7 @@ func updateSSHDConfig(keyDir, sshdConfigPath string) error { defer sshdConfig.Close() configUpdate := fmt.Sprintf(` -### Section created by 'teleport join' +### Section created by 'teleport join openssh' TrustedUserCaKeys %s HostKey %s HostCertificate %s From a6e6e3aa65224d9d1cf1f6303d8045b5ba7ad47e Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Tue, 10 Jan 2023 14:38:15 +0000 Subject: [PATCH 03/12] Resolve comments --- .../installers/agentless-installer.sh.tmpl | 23 ++++++------- lib/config/fileconf.go | 2 +- tool/teleport/common/teleport.go | 34 ++++++++++++++++++- 3 files changed, 45 insertions(+), 14 deletions(-) diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl index 19aabb73a57e5..0fde1411c1398 100644 --- a/api/types/installers/agentless-installer.sh.tmpl +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -1,28 +1,27 @@ -#!/bin/sh +#!/usr/bin/env bash + +set -o errexit +set -o pipefail +set -o nounset + ( flock -n 9 || exit 1 - if grep -q 'TrustedUserCAKeys /etc/ssh/teleport_user_ca.pub' "$SSHD_CONFIG"; then + if grep -q "Section created by 'teleport join openssh'" "$SSHD_CONFIG"; then exit 0 fi . /etc/os-release - # old versions of ubuntu require that keys get added by `apt-key add`, without - # adding the key apt shows a key signing error when installing teleport. - LEGACY_UBUNTU=false - if [ "$VERSION_CODENAME" = "xenial" ] || [ "$VERSION_CODENAME" = "trusty" ]; then - LEGACY_UBUNTU=true - fi - if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; then - if [ "$LEGACY_UBUNTU" = true ]; then + # old versions of ubuntu require that keys get added by `apt-key add`, without + # adding the key apt shows a key signing error when installing teleport. + if [ "$VERSION_CODENAME" = "xenial" ] || [ "$VERSION_CODENAME" = "trusty" ]; then curl -o /tmp/teleport-pubkey.asc https://deb.releases.teleport.dev/teleport-pubkey.asc cat /tmp/teleport-pubkey.asc | sudo apt-key add - echo "deb https://apt.releases.teleport.dev/ubuntu ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list rm /tmp/teleport-pubkey.asc else - sudo curl https://deb.releases.teleport.dev/teleport-pubkey.asc \ - -o /usr/share/keyrings/teleport-archive-keyring.asc + curl https://deb.releases.teleport.dev/teleport-pubkey.asc | sudo tee /usr/share/keyrings/teleport-archive-keyring.asc echo "deb [signed-by=/usr/share/keyrings/teleport-archive-keyring.asc] https://apt.releases.teleport.dev/${ID?} ${VERSION_CODENAME?} stable/{{ .MajorVersion }}" | sudo tee /etc/apt/sources.list.d/teleport.list >/dev/null fi sudo apt-get update diff --git a/lib/config/fileconf.go b/lib/config/fileconf.go index 085c134ab1942..e971c846c1423 100644 --- a/lib/config/fileconf.go +++ b/lib/config/fileconf.go @@ -487,7 +487,6 @@ func awsRegions() []string { // and validates the provided types. func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { regions := awsRegions() - var err error for i := range matcherInput { matcher := &matcherInput[i] for _, matcherType := range matcher.Types { @@ -514,6 +513,7 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error { } var installParams services.InstallerParams + var err error if matcher.InstallParams == nil { matcher.InstallParams = &InstallParams{ diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index f16454bcfe6a5..0f7412478e847 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -533,6 +533,10 @@ func GenerateKeys() (private, sshpub, tlspub []byte, err error) { } func onJoinOpenSSH(clf config.CommandLineFlags) error { + if err := checkSSHDConfigAlreadyUpdated(clf.OpenSSHConfigPath); err != nil { + return trace.Wrap(err) + } + addr, err := utils.ParseAddr(clf.ProxyServer) if err != nil { return trace.Wrap(err) @@ -619,6 +623,24 @@ func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs) error { } } + if err := certsFile.Sync(); err != nil { + return trace.Wrap(err) + } + + return nil +} + +const sshdConfigSectionModificationHeader = "### Section created by 'teleport join openssh'" + +func checkSSHDConfigAlreadyUpdated(sshdConfigPath string) error { + contents, err := os.ReadFile(sshdConfigPath) + if err != nil { + return trace.Wrap(err) + } + + if strings.Contains(string(contents), sshdConfigSectionModificationHeader) { + return trace.AlreadyExists("not updating %s as it has already been modified by teleport", sshdConfigPath) + } return nil } @@ -632,12 +654,13 @@ func updateSSHDConfig(keyDir, sshdConfigPath string) error { defer sshdConfig.Close() configUpdate := fmt.Sprintf(` -### Section created by 'teleport join openssh' +%s TrustedUserCaKeys %s HostKey %s HostCertificate %s ### Section end `, + sshdConfigSectionModificationHeader, filepath.Join(keyDir, "teleport_user_ca.pub"), filepath.Join(keyDir, "teleport"), filepath.Join(keyDir, "teleport-cert.pub"), @@ -655,6 +678,15 @@ HostCertificate %s return trace.Wrap(err) } + if err := sshdConfigTmp.Sync(); err != nil { + return trace.Wrap(err) + } + + cmd := exec.Command("sshd", "-t", "-f", sshdConfigTmp.Name()) + if err := cmd.Run(); err != nil { + return trace.Wrap(err, "teleport generated an invalid ssh config file, not writing") + } + if err := os.Rename(sshdConfigTmp.Name(), sshdConfigPath); err != nil { return trace.Wrap(err) } From 47e883b2d8531bf07d3f55749fca63bdfb4ff9e4 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 11 Jan 2023 11:30:17 +0000 Subject: [PATCH 04/12] Use GetCertAuthorities locally --- api/client/proto/certs.pb.go | 67 ++-------------- .../teleport/legacy/client/proto/certs.proto | 2 - .../installers/agentless-installer.sh.tmpl | 11 ++- lib/auth/auth.go | 18 +---- tool/teleport/common/teleport.go | 76 ++++++++++++++----- 5 files changed, 80 insertions(+), 94 deletions(-) diff --git a/api/client/proto/certs.pb.go b/api/client/proto/certs.pb.go index af3cd18a1412c..1f17e92026fbb 100644 --- a/api/client/proto/certs.pb.go +++ b/api/client/proto/certs.pb.go @@ -32,9 +32,7 @@ type Certs struct { // TLSCACerts is a list of TLS certificate authorities. TLSCACerts [][]byte `protobuf:"bytes,3,rep,name=TLSCACerts,proto3" json:"tls_ca_certs,omitempty"` // SSHCACerts is a list of SSH certificate authorities. - SSHCACerts [][]byte `protobuf:"bytes,4,rep,name=SSHCACerts,proto3" json:"ssh_ca_certs,omitempty"` - // SSHUserCACerts is a list of SSH user certificate authorities. - SSHUserCACerts [][]byte `protobuf:"bytes,5,rep,name=SSHUserCACerts,proto3" json:"ssh_user_ca_certs,omitempty"` + SSHCACerts [][]byte `protobuf:"bytes,4,rep,name=SSHCACerts,proto3" json:"ssh_ca_certs,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` @@ -82,25 +80,23 @@ func init() { } var fileDescriptor_9c4dbdba9a1559ee = []byte{ - // 283 bytes of a gzipped FileDescriptorProto + // 251 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xd2, 0x28, 0x49, 0xcd, 0x49, 0x2d, 0xc8, 0x2f, 0x2a, 0xd1, 0xcf, 0x49, 0x4d, 0x4f, 0x4c, 0xae, 0xd4, 0x4f, 0xce, 0xc9, 0x4c, 0xcd, 0x2b, 0xd1, 0x2f, 0x28, 0xca, 0x2f, 0xc9, 0xd7, 0x4f, 0x4e, 0x2d, 0x2a, 0x29, 0xd6, 0x03, 0xb3, 0x85, 0x58, 0xc1, 0x94, 0x94, 0x48, 0x7a, 0x7e, 0x7a, 0x3e, 0x44, 0x16, 0xc4, 0x82, 0x48, - 0x2a, 0x4d, 0x62, 0xe2, 0x62, 0x75, 0x06, 0x29, 0x16, 0x52, 0xe6, 0x62, 0x0e, 0x0e, 0xf6, 0x90, + 0x2a, 0x9d, 0x64, 0xe4, 0x62, 0x75, 0x06, 0x29, 0x16, 0x52, 0xe6, 0x62, 0x0e, 0x0e, 0xf6, 0x90, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x71, 0x12, 0x7c, 0x75, 0x4f, 0x9e, 0xb7, 0xb8, 0x38, 0x43, 0x27, 0x3f, 0x37, 0xb3, 0x24, 0x35, 0xb7, 0xa0, 0xa4, 0x32, 0x08, 0x24, 0x0b, 0x52, 0x14, 0xe2, 0x13, 0x2c, 0xc1, 0x84, 0x50, 0x54, 0x92, 0x53, 0x8c, 0xac, 0x28, 0xc4, 0x27, 0x58, 0xc8, 0x8a, 0x8b, 0x2b, 0xc4, 0x27, 0xd8, 0xd9, 0x11, 0x6c, 0xae, 0x04, 0xb3, 0x02, 0xb3, 0x06, 0x8f, 0x93, 0xd4, 0xab, 0x7b, 0xf2, 0x62, 0x25, 0x39, 0xc5, 0xf1, 0xc9, 0x89, 0xf1, 0x60, 0xc7, 0x21, 0x69, 0x42, 0x52, 0x0d, 0xd2, 0x1b, 0x1c, 0xec, 0x01, 0xd3, 0xcb, 0x82, 0xd0, 0x5b, 0x5c, 0x9c, 0x81, 0x55, - 0x2f, 0x42, 0xb5, 0x90, 0x3b, 0x17, 0x5f, 0x70, 0xb0, 0x47, 0x68, 0x71, 0x6a, 0x11, 0x4c, 0x3f, - 0x2b, 0x58, 0xbf, 0xfc, 0xab, 0x7b, 0xf2, 0xd2, 0x20, 0xfd, 0xa5, 0xc5, 0xa9, 0x45, 0xd8, 0x0c, - 0x41, 0xd3, 0xe6, 0xe4, 0x72, 0xe2, 0xa1, 0x1c, 0xc3, 0x89, 0x47, 0x72, 0x8c, 0x17, 0x1e, 0xc9, - 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x18, 0x65, 0x94, 0x9e, 0x59, 0x92, 0x51, 0x9a, 0xa4, 0x97, 0x9c, - 0x9f, 0xab, 0x9f, 0x5e, 0x94, 0x58, 0x96, 0x59, 0x92, 0x58, 0x92, 0x99, 0x9f, 0x97, 0x98, 0xa3, - 0x0f, 0x8f, 0x86, 0xc4, 0x82, 0x4c, 0x94, 0x38, 0x48, 0x62, 0x03, 0x53, 0xc6, 0x80, 0x00, 0x00, - 0x00, 0xff, 0xff, 0x1f, 0xc2, 0x5e, 0xf5, 0xaa, 0x01, 0x00, 0x00, + 0x2f, 0x42, 0xb5, 0x93, 0xcb, 0x89, 0x87, 0x72, 0x0c, 0x27, 0x1e, 0xc9, 0x31, 0x5e, 0x78, 0x24, + 0xc7, 0xf8, 0xe0, 0x91, 0x1c, 0x63, 0x94, 0x51, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, + 0x7e, 0xae, 0x7e, 0x7a, 0x51, 0x62, 0x59, 0x66, 0x49, 0x62, 0x49, 0x66, 0x7e, 0x5e, 0x62, 0x8e, + 0x3e, 0x3c, 0xf4, 0x12, 0x0b, 0x32, 0x51, 0x82, 0x2e, 0x89, 0x0d, 0x4c, 0x19, 0x03, 0x02, 0x00, + 0x00, 0xff, 0xff, 0x86, 0x6e, 0xe8, 0x40, 0x61, 0x01, 0x00, 0x00, } func (m *Certs) Marshal() (dAtA []byte, err error) { @@ -127,15 +123,6 @@ func (m *Certs) MarshalToSizedBuffer(dAtA []byte) (int, error) { i -= len(m.XXX_unrecognized) copy(dAtA[i:], m.XXX_unrecognized) } - if len(m.SSHUserCACerts) > 0 { - for iNdEx := len(m.SSHUserCACerts) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.SSHUserCACerts[iNdEx]) - copy(dAtA[i:], m.SSHUserCACerts[iNdEx]) - i = encodeVarintCerts(dAtA, i, uint64(len(m.SSHUserCACerts[iNdEx]))) - i-- - dAtA[i] = 0x2a - } - } if len(m.SSHCACerts) > 0 { for iNdEx := len(m.SSHCACerts) - 1; iNdEx >= 0; iNdEx-- { i -= len(m.SSHCACerts[iNdEx]) @@ -208,12 +195,6 @@ func (m *Certs) Size() (n int) { n += 1 + l + sovCerts(uint64(l)) } } - if len(m.SSHUserCACerts) > 0 { - for _, b := range m.SSHUserCACerts { - l = len(b) - n += 1 + l + sovCerts(uint64(l)) - } - } if m.XXX_unrecognized != nil { n += len(m.XXX_unrecognized) } @@ -387,38 +368,6 @@ func (m *Certs) Unmarshal(dAtA []byte) error { m.SSHCACerts = append(m.SSHCACerts, make([]byte, postIndex-iNdEx)) copy(m.SSHCACerts[len(m.SSHCACerts)-1], dAtA[iNdEx:postIndex]) iNdEx = postIndex - case 5: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field SSHUserCACerts", wireType) - } - var byteLen int - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return ErrIntOverflowCerts - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - byteLen |= int(b&0x7F) << shift - if b < 0x80 { - break - } - } - if byteLen < 0 { - return ErrInvalidLengthCerts - } - postIndex := iNdEx + byteLen - if postIndex < 0 { - return ErrInvalidLengthCerts - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.SSHUserCACerts = append(m.SSHUserCACerts, make([]byte, postIndex-iNdEx)) - copy(m.SSHUserCACerts[len(m.SSHUserCACerts)-1], dAtA[iNdEx:postIndex]) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipCerts(dAtA[iNdEx:]) diff --git a/api/proto/teleport/legacy/client/proto/certs.proto b/api/proto/teleport/legacy/client/proto/certs.proto index dd8e46d0d6679..31f99fda7c2c6 100644 --- a/api/proto/teleport/legacy/client/proto/certs.proto +++ b/api/proto/teleport/legacy/client/proto/certs.proto @@ -33,6 +33,4 @@ message Certs { repeated bytes TLSCACerts = 3 [(gogoproto.jsontag) = "tls_ca_certs,omitempty"]; // SSHCACerts is a list of SSH certificate authorities. repeated bytes SSHCACerts = 4 [(gogoproto.jsontag) = "ssh_ca_certs,omitempty"]; - // SSHUserCACerts is a list of SSH user certificate authorities. - repeated bytes SSHUserCACerts = 5 [(gogoproto.jsontag) = "ssh_user_ca_certs,omitempty"]; } diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl index 0fde1411c1398..43e9f3f87b347 100644 --- a/api/types/installers/agentless-installer.sh.tmpl +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -39,7 +39,16 @@ set -o nounset fi IMDS_TOKEN=$(curl -m5 -sS -X PUT "http://169.254.169.254/latest/api/token" -H "X-aws-ec2-metadata-token-ttl-seconds: 300") - PUBLIC_IP=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/meta-data/public-ipv4) + LOCAL_IP=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/meta-data/local-ipv4) + PUBLIC_IP=$(curl -m5 -sS -H "X-aws-ec2-metadata-token: ${IMDS_TOKEN}" http://169.254.169.254/latest/meta-data/public-ipv4 || echo "") + + PRINCIPALS="" + if [ ! "$LOCAL_IP" = "" ]; then + PRINCIPALS="$LOCAL_IP,$PRINCIPALS" + fi + if [ ! "$PUBLIC_IP" = "" ]; then + PRINCIPALS="$PUBLIC_IP,$PRINCIPALS" + fi sudo /usr/bin/teleport join openssh \ --openssh-config="${SSHD_CONFIG}" \ diff --git a/lib/auth/auth.go b/lib/auth/auth.go index 5c5e0888294f1..a9f2ad2b7bd93 100644 --- a/lib/auth/auth.go +++ b/lib/auth/auth.go @@ -2790,15 +2790,6 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.BadParameter("failed to load host CA for %q: %v", clusterName.GetClusterName(), err) } - userCA, err := client.GetCertAuthority(ctx, types.CertAuthID{ - Type: types.UserCA, - DomainName: clusterName.GetClusterName(), - }, true) - - if err != nil { - return nil, trace.Wrap(err) - } - // could be a couple of scenarios, either client data is out of sync, // or auth server is out of sync, either way, for now check that // cache is out of sync, this will result in higher read rate @@ -2906,11 +2897,10 @@ func (a *Server) GenerateHostCerts(ctx context.Context, req *proto.HostCertsRequ return nil, trace.Wrap(err) } return &proto.Certs{ - SSH: hostSSHCert, - TLS: hostTLSCert, - TLSCACerts: services.GetTLSCerts(ca), - SSHCACerts: services.GetSSHCheckingKeys(ca), - SSHUserCACerts: services.GetSSHCheckingKeys(userCA), + SSH: hostSSHCert, + TLS: hostTLSCert, + TLSCACerts: services.GetTLSCerts(ca), + SSHCACerts: services.GetSSHCheckingKeys(ca), }, nil } diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 0f7412478e847..bf79d0fd2247e 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -39,6 +39,7 @@ import ( "github.com/gravitational/teleport/api/client/proto" "github.com/gravitational/teleport/api/types" "github.com/gravitational/teleport/lib/auth" + "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/client" "github.com/gravitational/teleport/lib/config" @@ -532,6 +533,28 @@ func GenerateKeys() (private, sshpub, tlspub []byte, err error) { return privateKey, publicKey, tlsPublicKey, nil } +func authenticatedUserClientFromIdentity(ctx context.Context, fips bool, proxy utils.NetAddr, id *auth.Identity) (auth.ClientI, error) { + tlsConfig, err := id.TLSConfig(nil /* cipherSuites */) + if err != nil { + return nil, trace.Wrap(err) + } + + sshConfig, err := id.SSHClientConfig(fips) + if err != nil { + return nil, trace.Wrap(err) + } + + authClientConfig := &authclient.Config{ + TLS: tlsConfig, + SSH: sshConfig, + AuthServers: []utils.NetAddr{proxy}, + Log: log.StandardLogger(), + } + + c, err := authclient.Connect(ctx, authClientConfig) + return c, trace.Wrap(err) +} + func onJoinOpenSSH(clf config.CommandLineFlags) error { if err := checkSSHDConfigAlreadyUpdated(clf.OpenSSHConfigPath); err != nil { return trace.Wrap(err) @@ -554,7 +577,14 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { // TODO(amk) get uuid from a cli argument once agentless inventory management is implemented to allow tsh ssh access via uuid uuid, err := uuid.GenerateUUID() _ = err - principals := append(strings.Split(clf.AdditionalPrincipals, ","), uuid) + + principals := []string{uuid} + for _, principal := range strings.Split(clf.AdditionalPrincipals, ",") { + if principal == "" { + continue + } + principals = append(principals, principal) + } certs, err := auth.Register( auth.RegisterParams{ @@ -576,8 +606,32 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { return trace.Wrap(err) } + identity, err := auth.ReadIdentityFromKeyPair(privateKey, certs) + if err != nil { + return trace.Wrap(err) + } + + ctx := context.Background() + client, err := authenticatedUserClientFromIdentity(ctx, clf.FIPS, *addr, identity) + if err != nil { + return trace.Wrap(err) + } + + cas, err := client.GetCertAuthorities(ctx, types.UserCA, false) + if err != nil { + return trace.Wrap(err) + } + + var userCA []byte + for _, ca := range cas { + for _, key := range ca.GetActiveKeys().SSH { + userCA = append(userCA, key.PublicKey...) + userCA = append(userCA, byte('\n')) + } + } + fmt.Printf("Writing Teleport keys to %s\n", clf.OpenSSHKeysPath) - if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs); err != nil { + if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs, userCA); err != nil { return trace.Wrap(err) } @@ -600,7 +654,7 @@ const ( teleportUserCA = "teleport_user_ca.pub" ) -func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs) error { +func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs, userCA []byte) error { if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportKey), private, 0600); err != nil { return trace.Wrap(err) } @@ -609,21 +663,7 @@ func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs) error { return trace.Wrap(err) } - certsFile, err := os.Create(filepath.Join(sshdConfigDir, teleportUserCA)) - if err != nil { - return trace.Wrap(err) - } - defer certsFile.Close() - for _, cert := range certs.SSHUserCACerts { - if _, err := certsFile.Write(cert); err != nil { - return trace.Wrap(err) - } - if _, err := certsFile.WriteString("\n"); err != nil { - return trace.Wrap(err) - } - } - - if err := certsFile.Sync(); err != nil { + if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportUserCA), userCA, 0600); err != nil { return trace.Wrap(err) } From ac469f38f8f792d55efb95192bf6e35c06bbfc73 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 11 Jan 2023 11:57:15 +0000 Subject: [PATCH 05/12] Try to get IMDS hostname --- tool/teleport/common/teleport.go | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index bf79d0fd2247e..a3838de8bea46 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -42,6 +42,7 @@ import ( "github.com/gravitational/teleport/lib/auth/authclient" "github.com/gravitational/teleport/lib/auth/native" "github.com/gravitational/teleport/lib/client" + "github.com/gravitational/teleport/lib/cloud/aws" "github.com/gravitational/teleport/lib/config" awsconfigurators "github.com/gravitational/teleport/lib/configurators/aws" "github.com/gravitational/teleport/lib/defaults" @@ -555,6 +556,22 @@ func authenticatedUserClientFromIdentity(ctx context.Context, fips bool, proxy u return c, trace.Wrap(err) } +func getAWSInstanceHostname(ctx context.Context) (string, error) { + imds, err := aws.NewInstanceMetadataClient(ctx) + if err != nil { + return "", trace.Wrap(err) + } + hostname, err := imds.GetHostname(ctx) + if err != nil { + return "", trace.Wrap(err) + } + hostname = strings.ReplaceAll(hostname, " ", "_") + if utils.IsValidHostname(hostname) { + return hostname, nil + } + return "", trace.NotFound("failed to get a valid hostname from IMDS") +} + func onJoinOpenSSH(clf config.CommandLineFlags) error { if err := checkSSHDConfigAlreadyUpdated(clf.OpenSSHConfigPath); err != nil { return trace.Wrap(err) @@ -569,9 +586,14 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { return trace.Wrap(err, "unable to generate new keypairs") } + ctx := context.Background() hostname, err := os.Hostname() if err != nil { - return trace.Wrap(err) + var imdsErr error + hostname, imdsErr = getAWSInstanceHostname(ctx) + if imdsErr != nil { + return trace.NewAggregate(err, imdsErr) + } } // TODO(amk) get uuid from a cli argument once agentless inventory management is implemented to allow tsh ssh access via uuid @@ -611,7 +633,6 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { return trace.Wrap(err) } - ctx := context.Background() client, err := authenticatedUserClientFromIdentity(ctx, clf.FIPS, *addr, identity) if err != nil { return trace.Wrap(err) From 44f260af2a119056b081f9f3348411ec1de23943 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 11 Jan 2023 14:02:14 +0000 Subject: [PATCH 06/12] Try get imds hostname first This seems to be how its implemented for non-agentless nodes --- tool/teleport/common/teleport.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index a3838de8bea46..2918e9073fee5 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -587,12 +587,12 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { } ctx := context.Background() - hostname, err := os.Hostname() + hostname, err := getAWSInstanceHostname(ctx) if err != nil { - var imdsErr error - hostname, imdsErr = getAWSInstanceHostname(ctx) - if imdsErr != nil { - return trace.NewAggregate(err, imdsErr) + var hostErr error + hostname, hostErr = os.Hostname() + if hostErr != nil { + return trace.NewAggregate(err, hostErr) } } From 4b1303ab507d5d67e451b773b5eb1ba1ca87af66 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 11 Jan 2023 14:21:23 +0000 Subject: [PATCH 07/12] Use FIPS cipher suites --- tool/teleport/common/teleport.go | 44 ++++++++++++++++++++------------ 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 2918e9073fee5..4a3c68d6b9f43 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -18,6 +18,7 @@ package common import ( "context" + "crypto/tls" "fmt" "io" "net/url" @@ -535,7 +536,13 @@ func GenerateKeys() (private, sshpub, tlspub []byte, err error) { } func authenticatedUserClientFromIdentity(ctx context.Context, fips bool, proxy utils.NetAddr, id *auth.Identity) (auth.ClientI, error) { - tlsConfig, err := id.TLSConfig(nil /* cipherSuites */) + var tlsConfig *tls.Config + var err error + if fips { + tlsConfig, err = id.TLSConfig(defaults.FIPSCipherSuites) + } else { + tlsConfig, err = id.TLSConfig(nil /* cipherSuites */) + } if err != nil { return nil, trace.Wrap(err) } @@ -608,22 +615,27 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { principals = append(principals, principal) } - certs, err := auth.Register( - auth.RegisterParams{ - Token: clf.AuthToken, - AdditionalPrincipals: principals, - JoinMethod: types.JoinMethod(clf.JoinMethod), - ID: auth.IdentityID{ - Role: types.RoleNode, - NodeName: hostname, - HostUUID: uuid, - }, - ProxyServer: *addr, - PublicTLSKey: tlsPublicKey, - PublicSSHKey: sshPublicKey, - GetHostCredentials: client.HostCredentials, + registerParams := auth.RegisterParams{ + Token: clf.AuthToken, + AdditionalPrincipals: principals, + JoinMethod: types.JoinMethod(clf.JoinMethod), + ID: auth.IdentityID{ + Role: types.RoleNode, + NodeName: hostname, + HostUUID: uuid, }, - ) + ProxyServer: *addr, + PublicTLSKey: tlsPublicKey, + PublicSSHKey: sshPublicKey, + GetHostCredentials: client.HostCredentials, + FIPS: clf.FIPS, + } + + if clf.FIPS { + registerParams.CipherSuites = defaults.FIPSCipherSuites + } + + certs, err := auth.Register(registerParams) if err != nil { return trace.Wrap(err) } From 0ebee359157298fcba745b95076764852d252449 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Tue, 17 Jan 2023 16:10:24 +0000 Subject: [PATCH 08/12] use the openssh ca, resolve comments --- tool/teleport/common/teleport.go | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 4a3c68d6b9f43..76927f6e52e0f 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -538,11 +538,11 @@ func GenerateKeys() (private, sshpub, tlspub []byte, err error) { func authenticatedUserClientFromIdentity(ctx context.Context, fips bool, proxy utils.NetAddr, id *auth.Identity) (auth.ClientI, error) { var tlsConfig *tls.Config var err error - if fips { - tlsConfig, err = id.TLSConfig(defaults.FIPSCipherSuites) - } else { - tlsConfig, err = id.TLSConfig(nil /* cipherSuites */) + var cipherSuites []uint16 + if !fips { + cipherSuites = defaults.FIPSCipherSuites } + tlsConfig, err = id.TLSConfig(cipherSuites) if err != nil { return nil, trace.Wrap(err) } @@ -650,21 +650,21 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { return trace.Wrap(err) } - cas, err := client.GetCertAuthorities(ctx, types.UserCA, false) + cas, err := client.GetCertAuthorities(ctx, types.OpenSSHCA, false) if err != nil { return trace.Wrap(err) } - var userCA []byte + var openSSHCA []byte for _, ca := range cas { for _, key := range ca.GetActiveKeys().SSH { - userCA = append(userCA, key.PublicKey...) - userCA = append(userCA, byte('\n')) + openSSHCA = append(openSSHCA, key.PublicKey...) + openSSHCA = append(openSSHCA, byte('\n')) } } fmt.Printf("Writing Teleport keys to %s\n", clf.OpenSSHKeysPath) - if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs, userCA); err != nil { + if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs, openSSHCA); err != nil { return trace.Wrap(err) } @@ -682,12 +682,12 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { } const ( - teleportKey = "teleport" - teleportCert = "teleport-cert.pub" - teleportUserCA = "teleport_user_ca.pub" + teleportKey = "teleport" + teleportCert = "teleport-cert.pub" + teleportOpenSSHCA = "teleport_user_ca.pub" ) -func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs, userCA []byte) error { +func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs, openSSHCA []byte) error { if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportKey), private, 0600); err != nil { return trace.Wrap(err) } @@ -696,7 +696,7 @@ func writeKeys(sshdConfigDir string, private []byte, certs *proto.Certs, userCA return trace.Wrap(err) } - if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportUserCA), userCA, 0600); err != nil { + if err := os.WriteFile(filepath.Join(sshdConfigDir, teleportOpenSSHCA), openSSHCA, 0600); err != nil { return trace.Wrap(err) } @@ -717,6 +717,8 @@ func checkSSHDConfigAlreadyUpdated(sshdConfigPath string) error { return nil } +const sshdBinary = "sshd" + func updateSSHDConfig(keyDir, sshdConfigPath string) error { // has to write to the beginning of the sshd_config file as // openssh takes the first occurance of a setting @@ -755,7 +757,7 @@ HostCertificate %s return trace.Wrap(err) } - cmd := exec.Command("sshd", "-t", "-f", sshdConfigTmp.Name()) + cmd := exec.Command(sshdBinary, "-t", "-f", sshdConfigTmp.Name()) if err := cmd.Run(); err != nil { return trace.Wrap(err, "teleport generated an invalid ssh config file, not writing") } From 2eacb77d85e2440bfced66cce04f6e0bd0520d36 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Wed, 18 Jan 2023 11:56:08 +0000 Subject: [PATCH 09/12] write keys to /etc/teleport/agentless by default --- tool/teleport/common/teleport.go | 55 +++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 76927f6e52e0f..125e6c221ec41 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -64,6 +64,8 @@ type Options struct { InitOnly bool } +const agentlessKeysDir = "/etc/teleport/agentless" + // Run inits/starts the process according to the provided options func Run(options Options) (app *kingpin.Application, executedCommand string, conf *service.Config) { var err error @@ -407,10 +409,11 @@ func Run(options Options) (app *kingpin.Application, executedCommand string, con joinOpenSSH.Flag("token", "Invitation token to register with an auth server.").StringVar(&ccf.AuthToken) joinOpenSSH.Flag("join-method", "Method to use to join the cluster (token, iam, ec2)").EnumVar(&ccf.JoinMethod, "token", "iam", "ec2") joinOpenSSH.Flag("openssh-config", "Path to the OpenSSH config file").Default("/etc/ssh/sshd_config").StringVar(&ccf.OpenSSHConfigPath) - joinOpenSSH.Flag("openssh-keys-path", "Path to the place teleport keys and certs").Default("/etc/ssh").StringVar(&ccf.OpenSSHKeysPath) + joinOpenSSH.Flag("openssh-keys-path", "Path to the place teleport keys and certs").Default(agentlessKeysDir).StringVar(&ccf.OpenSSHKeysPath) joinOpenSSH.Flag("restart-sshd", "Restart OpenSSH").Default("true").BoolVar(&ccf.RestartOpenSSH) joinOpenSSH.Flag("insecure", "Insecure mode disables certificate validation").BoolVar(&ccf.InsecureMode) joinOpenSSH.Flag("additional-principals", "Comma separated list of host names the node can be accessed by").StringVar(&ccf.AdditionalPrincipals) + joinOpenSSH.Flag("debug", "Enable verbose logging to stderr.").Short('d').BoolVar(&ccf.Debug) // parse CLI commands+flags: utils.UpdateAppUsageTemplate(app, options.Args) @@ -579,11 +582,46 @@ func getAWSInstanceHostname(ctx context.Context) (string, error) { return "", trace.NotFound("failed to get a valid hostname from IMDS") } +func tryCreateDefaultAgentlesKeysDir(agentlessKeysPath string) error { + baseTeleportDir := filepath.Dir(agentlessKeysPath) + _, err := os.Stat(baseTeleportDir) + if err != nil { + if os.IsNotExist(err) { + log.Debugf("%s did not exist, creating %s", baseTeleportDir, agentlessKeysPath) + return trace.Wrap(os.MkdirAll(agentlessKeysPath, 0700)) + } else { + return trace.Wrap(err) + } + } + + var alreadyExistedAndDeleted bool + _, err = os.Stat(agentlessKeysPath) + if err == nil { + log.Debugf("%s already existed, removing old files", agentlessKeysPath) + err = os.RemoveAll(agentlessKeysPath) + if err != nil { + return trace.Wrap(err) + } + alreadyExistedAndDeleted = true + } + + if os.IsNotExist(err) || alreadyExistedAndDeleted { + log.Debugf("%s did not exist, creating", agentlessKeysPath) + return trace.Wrap(os.Mkdir(agentlessKeysPath, 0700)) + } + + return trace.Wrap(err) +} + func onJoinOpenSSH(clf config.CommandLineFlags) error { if err := checkSSHDConfigAlreadyUpdated(clf.OpenSSHConfigPath); err != nil { return trace.Wrap(err) } + if clf.Debug { + log.SetLevel(log.DebugLevel) + } + addr, err := utils.ParseAddr(clf.ProxyServer) if err != nil { return trace.Wrap(err) @@ -663,8 +701,23 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { } } + defaultKeysPath := clf.OpenSSHKeysPath == agentlessKeysDir + if defaultKeysPath { + if err := tryCreateDefaultAgentlesKeysDir(agentlessKeysDir); err != nil { + return trace.Wrap(err) + } + } + fmt.Printf("Writing Teleport keys to %s\n", clf.OpenSSHKeysPath) if err := writeKeys(clf.OpenSSHKeysPath, privateKey, certs, openSSHCA); err != nil { + if defaultKeysPath { + rmdirErr := os.RemoveAll(agentlessKeysDir) + if rmdirErr != nil { + if rmdirErr != nil { + return trace.NewAggregate(err, rmdirErr) + } + } + } return trace.Wrap(err) } From e22ad5ee56a0b6e64fe199a4cdaa025848dd360f Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Thu, 19 Jan 2023 12:05:00 +0000 Subject: [PATCH 10/12] Resolve comment --- api/types/installers/agentless-installer.sh.tmpl | 2 +- tool/teleport/common/teleport.go | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/api/types/installers/agentless-installer.sh.tmpl b/api/types/installers/agentless-installer.sh.tmpl index 43e9f3f87b347..acfec4d73c372 100644 --- a/api/types/installers/agentless-installer.sh.tmpl +++ b/api/types/installers/agentless-installer.sh.tmpl @@ -55,7 +55,7 @@ set -o nounset --join-method=iam \ --token="$1" \ --proxy-server="{{ .PublicProxyAddr }}" \ - --additional-principals="$PUBLIC_IP" \ + --additional-principals="$PRINCIPALS" \ --restart-sshd ) 9>/var/lock/teleport_install.lock diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 125e6c221ec41..26573b8cf0b3e 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -713,9 +713,7 @@ func onJoinOpenSSH(clf config.CommandLineFlags) error { if defaultKeysPath { rmdirErr := os.RemoveAll(agentlessKeysDir) if rmdirErr != nil { - if rmdirErr != nil { - return trace.NewAggregate(err, rmdirErr) - } + return trace.NewAggregate(err, rmdirErr) } } return trace.Wrap(err) From 6e50fa4a012fa91b060118b72097959cc1159b92 Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Tue, 24 Jan 2023 13:21:48 +0000 Subject: [PATCH 11/12] lints --- lib/config/configuration.go | 2 +- lib/srv/discovery/discovery.go | 2 +- tool/teleport/common/teleport.go | 5 ++--- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/lib/config/configuration.go b/lib/config/configuration.go index ce2332abb996b..1808e10858174 100644 --- a/lib/config/configuration.go +++ b/lib/config/configuration.go @@ -173,7 +173,7 @@ type CommandLineFlags struct { OpenSSHConfigPath string // OpenSSHKeysPath is the path to write teleport keys and certs into OpenSSHKeysPath string - // AdditionalPrincipals are a list of extra prinicpals to include when generating host keys. + // AdditionalPrincipals are a list of extra principals to include when generating host keys. AdditionalPrincipals string // RestartOpenSSH indicates whether openssh should be restarted or not. RestartOpenSSH bool diff --git a/lib/srv/discovery/discovery.go b/lib/srv/discovery/discovery.go index 9241e0dfd9e77..c138ba1d81e23 100644 --- a/lib/srv/discovery/discovery.go +++ b/lib/srv/discovery/discovery.go @@ -345,7 +345,7 @@ func genInstancesLogStr[T any](instances []T, getID func(T) string) string { func (s *Server) handleEC2Instances(instances *server.EC2Instances) error { // TODO(amk): once agentless node inventory management is - // implemented, create nodes after a sucessful SSM run + // implemented, create nodes after a successful SSM run ec2Client, err := s.Clients.GetAWSSSMClient(instances.Region) if err != nil { diff --git a/tool/teleport/common/teleport.go b/tool/teleport/common/teleport.go index 26573b8cf0b3e..8de16016d3b27 100644 --- a/tool/teleport/common/teleport.go +++ b/tool/teleport/common/teleport.go @@ -589,9 +589,8 @@ func tryCreateDefaultAgentlesKeysDir(agentlessKeysPath string) error { if os.IsNotExist(err) { log.Debugf("%s did not exist, creating %s", baseTeleportDir, agentlessKeysPath) return trace.Wrap(os.MkdirAll(agentlessKeysPath, 0700)) - } else { - return trace.Wrap(err) } + return trace.Wrap(err) } var alreadyExistedAndDeleted bool @@ -772,7 +771,7 @@ const sshdBinary = "sshd" func updateSSHDConfig(keyDir, sshdConfigPath string) error { // has to write to the beginning of the sshd_config file as - // openssh takes the first occurance of a setting + // openssh takes the first occurrence of a setting sshdConfig, err := os.OpenFile(sshdConfigPath, os.O_RDONLY|os.O_CREATE, 0644) if err != nil { return trace.Wrap(err) From affd27859db7a79bc2dbd89bf7be32b35017d4fa Mon Sep 17 00:00:00 2001 From: Alex McGrath Date: Tue, 24 Jan 2023 13:55:42 +0000 Subject: [PATCH 12/12] test fixes --- lib/config/configuration_test.go | 1 + lib/config/fileconf_test.go | 3 +++ lib/srv/server/ec2_watcher_test.go | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/lib/config/configuration_test.go b/lib/config/configuration_test.go index cd94d5e730850..2dac7857f5296 100644 --- a/lib/config/configuration_test.go +++ b/lib/config/configuration_test.go @@ -358,6 +358,7 @@ func TestConfigReading(t *testing.T) { TokenName: "aws-discovery-iam-token", Method: "iam", }, + SSHDConfig: "/etc/ssh/sshd_config", ScriptName: "default-installer", }, SSM: AWSSSM{DocumentName: "TeleportDiscoveryInstaller"}, diff --git a/lib/config/fileconf_test.go b/lib/config/fileconf_test.go index 2048ab8f6c706..0281f83e488d5 100644 --- a/lib/config/fileconf_test.go +++ b/lib/config/fileconf_test.go @@ -868,6 +868,7 @@ func TestDiscoveryConfig(t *testing.T) { TokenName: defaults.IAMInviteTokenName, Method: types.JoinMethodIAM, }, + SSHDConfig: "/etc/ssh/sshd_config", ScriptName: installers.InstallerScriptName, }, SSM: AWSSSM{DocumentName: defaults.AWSInstallerDocument}, @@ -914,6 +915,7 @@ func TestDiscoveryConfig(t *testing.T) { TokenName: "hello-iam-a-token", Method: types.JoinMethodIAM, }, + SSHDConfig: "/etc/ssh/sshd_config", ScriptName: "installer-custom", }, SSM: AWSSSM{DocumentName: "hello_document"}, @@ -989,6 +991,7 @@ func TestDiscoveryConfig(t *testing.T) { Method: types.JoinMethodIAM, }, ScriptName: installers.InstallerScriptName, + SSHDConfig: "/etc/ssh/sshd_config", }, }, }, diff --git a/lib/srv/server/ec2_watcher_test.go b/lib/srv/server/ec2_watcher_test.go index e588f690f6a6a..f84c1491ac237 100644 --- a/lib/srv/server/ec2_watcher_test.go +++ b/lib/srv/server/ec2_watcher_test.go @@ -143,12 +143,18 @@ func TestEC2Watcher(t *testing.T) { } matchers := []services.AWSMatcher{ { + Params: services.InstallerParams{ + InstallTeleport: true, + }, Types: []string{"EC2"}, Regions: []string{"us-west-2"}, Tags: map[string]utils.Strings{"teleport": {"yes"}}, SSM: &services.AWSSSM{}, }, { + Params: services.InstallerParams{ + InstallTeleport: true, + }, Types: []string{"EC2"}, Regions: []string{"us-west-2"}, Tags: map[string]utils.Strings{"env": {"dev"}},