Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions api/types/installers/agentless-installer.sh.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#!/usr/bin/env bash

set -o errexit
set -o pipefail
set -o nounset

(
flock -n 9 || exit 1
if grep -q "Section created by 'teleport join openssh'" "$SSHD_CONFIG"; then
exit 0
fi

. /etc/os-release

if [ "$ID" = "debian" ] || [ "$ID" = "ubuntu" ]; 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 -
Comment thread
lxea marked this conversation as resolved.
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
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
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")
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}" \
--join-method=iam \
--token="$1" \
--proxy-server="{{ .PublicProxyAddr }}" \
--additional-principals="$PRINCIPALS" \
--restart-sshd

) 9>/var/lock/teleport_install.lock
11 changes: 11 additions & 0 deletions api/types/installers/installers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
19 changes: 16 additions & 3 deletions lib/auth/grpcserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -4310,13 +4319,17 @@ func (g *GRPCServer) GetInstallers(ctx context.Context, _ *emptypb.Empty) (*type
return &types.InstallerV1List{
Installers: []*types.InstallerV1{
installers.DefaultInstaller,
installers.DefaultAgentlessInstaller,
},
}, nil
}

if needDefault {
installersV1 = append(installersV1, installers.DefaultInstaller)
}
if needDefaultAgentless {
installersV1 = append(installersV1, installers.DefaultAgentlessInstaller)
}

return &types.InstallerV1List{
Installers: installersV1,
Expand Down
33 changes: 25 additions & 8 deletions lib/config/configuration.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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 principals 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)
Expand Down Expand Up @@ -433,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
Expand Down Expand Up @@ -1150,20 +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,
},
SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName},
Params: installParams,
SSM: &services.AWSSSM{DocumentName: matcher.SSM.DocumentName},
})
}

Expand Down Expand Up @@ -1192,6 +1208,7 @@ func applyDiscoveryConfig(fc *FileConfig, cfg *service.Config) {
)
}

return nil
}

// applyKubeConfig applies file configuration for the "kubernetes_service" section.
Expand Down
12 changes: 12 additions & 0 deletions lib/config/configuration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"},
Expand Down Expand Up @@ -829,6 +830,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,
Expand Down
61 changes: 57 additions & 4 deletions lib/config/fileconf.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,31 +512,58 @@ func checkAndSetDefaultsForAWSMatchers(matcherInput []AWSMatcher) error {
matcher.Tags = map[string]apiutils.Strings{types.Wildcard: {types.Wildcard}}
}

var installParams services.InstallerParams
var err error

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 == "" {
matcher.InstallParams.JoinParams.Method = types.JoinMethodIAM
} else if method != types.JoinMethodIAM {
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 == "" {
matcher.InstallParams.ScriptName = installers.InstallerScriptName
if installParams.InstallTeleport {
matcher.InstallParams.ScriptName = installers.InstallerScriptName
} else {
matcher.InstallParams.ScriptName = installers.InstallerScriptNameAgentless
}
}
}

if matcher.SSM.DocumentName == "" {
matcher.SSM.DocumentName = defaults.AWSInstallerDocument
if installParams.InstallTeleport {
matcher.SSM.DocumentName = defaults.AWSInstallerDocument
} else {
matcher.SSM.DocumentName = defaults.AWSAgentlessInstallerDocument
}
}
}
return nil
Expand Down Expand Up @@ -1505,6 +1532,32 @@ 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 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
Expand Down
3 changes: 3 additions & 0 deletions lib/config/fileconf_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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"},
Expand Down Expand Up @@ -989,6 +991,7 @@ func TestDiscoveryConfig(t *testing.T) {
Method: types.JoinMethodIAM,
},
ScriptName: installers.InstallerScriptName,
SSHDConfig: "/etc/ssh/sshd_config",
},
},
},
Expand Down
6 changes: 6 additions & 0 deletions lib/config/testdata_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
9 changes: 9 additions & 0 deletions lib/defaults/defaults.go
Original file line number Diff line number Diff line change
Expand Up @@ -845,7 +845,16 @@ 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"

// SSHDConfigPath is the path to the sshd config file to modify
// when using the agentless installer
SSHDConfigPath = "/etc/ssh/sshd_config"
)
Loading