mirror of
https://github.com/gravitational/teleport.git
synced 2026-09-24 16:17:11 +08:00
use configured algorithms for dynamic SSH host certs (#46329)
* dynamic SSH host certs use configurable algorithms * lazily PrecomputeKeys everywhere * slightly speed up TestTSHConfigConnectWithOpenSSHClient
This commit is contained in:
@@ -215,7 +215,7 @@ func (e *permanentRetryError) Error() string {
|
||||
return e.err.Error()
|
||||
}
|
||||
|
||||
// RetryFastFor retries a function repeatedly for a set amount of
|
||||
// RetryStaticFor retries a function repeatedly for a set amount of
|
||||
// time before returning an error.
|
||||
//
|
||||
// Intended mostly for tests.
|
||||
|
||||
@@ -82,7 +82,6 @@ import (
|
||||
"github.com/gravitational/teleport/entitlements"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/keystore"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/auth/userloginstate"
|
||||
wanlib "github.com/gravitational/teleport/lib/auth/webauthn"
|
||||
wantypes "github.com/gravitational/teleport/lib/auth/webauthntypes"
|
||||
@@ -375,8 +374,6 @@ func NewServer(cfg *InitConfig, opts ...ServerOption) (*Server, error) {
|
||||
if !modules.GetModules().Features().GetEntitlement(entitlements.HSM).Enabled {
|
||||
return nil, fmt.Errorf("AWS KMS support requires a license with the HSM feature enabled: %w", ErrRequiresEnterprise)
|
||||
}
|
||||
} else {
|
||||
native.PrecomputeKeys()
|
||||
}
|
||||
keyStore, err := keystore.NewManager(context.Background(), &cfg.KeyStoreConfig, keystoreOpts)
|
||||
if err != nil {
|
||||
|
||||
@@ -21,6 +21,7 @@ package auth
|
||||
import (
|
||||
"context"
|
||||
"crypto"
|
||||
"crypto/rsa"
|
||||
"time"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
@@ -36,6 +37,7 @@ import (
|
||||
apievents "github.com/gravitational/teleport/api/types/events"
|
||||
"github.com/gravitational/teleport/api/utils/keys"
|
||||
"github.com/gravitational/teleport/entitlements"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/cryptosuites"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
dtconfig "github.com/gravitational/teleport/lib/devicetrust/config"
|
||||
@@ -253,6 +255,14 @@ func (a *Server) newWebSession(
|
||||
if err != nil {
|
||||
return nil, nil, trace.Wrap(err)
|
||||
}
|
||||
if _, isRSA := sshKey.Public().(*rsa.PublicKey); isRSA {
|
||||
// Ensure the native package is precomputing RSA keys if we ever
|
||||
// generate one. [native.PrecomputeKeys] is idempotent.
|
||||
// Doing this lazily easily handles changing signature algorithm
|
||||
// suites and won't start precomputing keys if they are never needed
|
||||
// (a major benefit in tests).
|
||||
native.PrecomputeKeys()
|
||||
}
|
||||
}
|
||||
|
||||
sessionTTL := req.SessionTTL
|
||||
|
||||
@@ -19,20 +19,14 @@
|
||||
package openssh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os/exec"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"io"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/gravitational/teleport"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// proxyCommandQuote prepares a string for insertion into the ssh_config
|
||||
@@ -57,7 +51,6 @@ Host *.{{ $clusterName }} {{ $dot.ProxyHost }}
|
||||
UserKnownHostsFile "{{ $dot.KnownHostsPath }}"
|
||||
IdentityFile "{{ $dot.IdentityFilePath }}"
|
||||
CertificateFile "{{ $dot.CertificateFilePath }}"
|
||||
HostKeyAlgorithms {{ if $dot.NewerHostKeyAlgorithmsSupported }}rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,{{ end }}ssh-rsa-cert-v01@openssh.com
|
||||
{{- if ne $dot.Username "" }}
|
||||
User "{{ $dot.Username }}"
|
||||
{{- end }}
|
||||
@@ -110,16 +103,8 @@ type SSHConfigParameters struct {
|
||||
|
||||
type sshTmplParams struct {
|
||||
SSHConfigParameters
|
||||
sshConfigOptions
|
||||
}
|
||||
|
||||
// openSSHVersionRegex is a regex used to parse OpenSSH version strings.
|
||||
var openSSHVersionRegex = regexp.MustCompile(`^OpenSSH_(?P<major>\d+)\.(?P<minor>\d+)(?:p(?P<patch>\d+))?`)
|
||||
|
||||
// openSSHMinVersionForHostAlgos is the first version that understands all host keys required by us.
|
||||
// HostKeyAlgorithms will be added to ssh config if the version is above listed here.
|
||||
var openSSHMinVersionForHostAlgos = semver.New("7.8.0")
|
||||
|
||||
// SSHConfigApps represent apps that support ssh config generation.
|
||||
type SSHConfigApps string
|
||||
|
||||
@@ -128,130 +113,14 @@ const (
|
||||
TbotApp SSHConfigApps = teleport.ComponentTBot
|
||||
)
|
||||
|
||||
// parseSSHVersion attempts to parse the local SSH version, used to determine
|
||||
// certain config template parameters for client version compatibility.
|
||||
func parseSSHVersion(versionString string) (*semver.Version, error) {
|
||||
versionTokens := strings.Split(versionString, " ")
|
||||
if len(versionTokens) == 0 {
|
||||
return nil, trace.BadParameter("invalid version string: %s", versionString)
|
||||
}
|
||||
|
||||
versionID := versionTokens[0]
|
||||
matches := openSSHVersionRegex.FindStringSubmatch(versionID)
|
||||
if matches == nil {
|
||||
return nil, trace.BadParameter("cannot parse version string: %q", versionID)
|
||||
}
|
||||
|
||||
major, err := strconv.Atoi(matches[1])
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "invalid major version number: %s", matches[1])
|
||||
}
|
||||
|
||||
minor, err := strconv.Atoi(matches[2])
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "invalid minor version number: %s", matches[2])
|
||||
}
|
||||
|
||||
patch := 0
|
||||
if matches[3] != "" {
|
||||
patch, err = strconv.Atoi(matches[3])
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err, "invalid patch version number: %s", matches[3])
|
||||
}
|
||||
}
|
||||
|
||||
return &semver.Version{
|
||||
Major: int64(major),
|
||||
Minor: int64(minor),
|
||||
Patch: int64(patch),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetSystemSSHVersion attempts to query the system SSH for its current version.
|
||||
func GetSystemSSHVersion() (*semver.Version, error) {
|
||||
var out bytes.Buffer
|
||||
|
||||
cmd := exec.Command("ssh", "-V")
|
||||
cmd.Stderr = &out
|
||||
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return parseSSHVersion(out.String())
|
||||
}
|
||||
|
||||
type sshConfigOptions struct {
|
||||
// NewerHostKeyAlgorithmsSupported when true sets HostKeyAlgorithms OpenSSH configuration option
|
||||
// to SHA256/512 compatible algorithms. Otherwise, SHA-1 is being used.
|
||||
NewerHostKeyAlgorithmsSupported bool
|
||||
}
|
||||
|
||||
func (c *sshConfigOptions) String() string {
|
||||
sb := &strings.Builder{}
|
||||
sb.WriteString("sshConfigOptions: ")
|
||||
|
||||
if c.NewerHostKeyAlgorithmsSupported {
|
||||
sb.WriteString("HostKeyAlgorithms will include SHA-256, SHA-512 and SHA-1")
|
||||
} else {
|
||||
sb.WriteString("HostKeyAlgorithms will include SHA-1")
|
||||
}
|
||||
|
||||
return sb.String()
|
||||
}
|
||||
|
||||
func isNewerHostKeyAlgorithmsSupported(ver *semver.Version) bool {
|
||||
return !ver.LessThan(*openSSHMinVersionForHostAlgos)
|
||||
}
|
||||
|
||||
func getSSHConfigOptions(sshVer *semver.Version) *sshConfigOptions {
|
||||
return &sshConfigOptions{
|
||||
NewerHostKeyAlgorithmsSupported: isNewerHostKeyAlgorithmsSupported(sshVer),
|
||||
}
|
||||
}
|
||||
|
||||
func getDefaultSSHConfigOptions() *sshConfigOptions {
|
||||
return &sshConfigOptions{
|
||||
NewerHostKeyAlgorithmsSupported: true,
|
||||
}
|
||||
}
|
||||
|
||||
type SSHConfig struct {
|
||||
getSSHVersion func() (*semver.Version, error)
|
||||
log logrus.FieldLogger
|
||||
}
|
||||
|
||||
// NewSSHConfig creates a SSHConfig initialized with provided values or defaults otherwise.
|
||||
func NewSSHConfig(getSSHVersion func() (*semver.Version, error), log logrus.FieldLogger) *SSHConfig {
|
||||
if getSSHVersion == nil {
|
||||
getSSHVersion = GetSystemSSHVersion
|
||||
}
|
||||
if log == nil {
|
||||
log = utils.NewLogger()
|
||||
}
|
||||
return &SSHConfig{getSSHVersion: getSSHVersion, log: log}
|
||||
}
|
||||
|
||||
func (c *SSHConfig) GetSSHConfig(sb *strings.Builder, config *SSHConfigParameters) error {
|
||||
var sshOptions *sshConfigOptions
|
||||
version, err := c.getSSHVersion()
|
||||
if err != nil {
|
||||
c.log.WithError(err).Debugf("Could not determine SSH version, using default SSH config")
|
||||
sshOptions = getDefaultSSHConfigOptions()
|
||||
} else {
|
||||
c.log.Debugf("Found OpenSSH version %s", version)
|
||||
sshOptions = getSSHConfigOptions(version)
|
||||
}
|
||||
// WriteSSHConfig generates an ssh_config file for OpenSSH clients.
|
||||
func WriteSSHConfig(w io.Writer, config *SSHConfigParameters) error {
|
||||
if config.Port == 0 {
|
||||
config.Port = defaults.SSHServerListenPort
|
||||
}
|
||||
|
||||
c.log.Debugf("Using SSH options: %s", sshOptions)
|
||||
|
||||
if err := sshConfigTemplate.Execute(sb, sshTmplParams{
|
||||
if err := sshConfigTemplate.Execute(w, sshTmplParams{
|
||||
SSHConfigParameters: *config,
|
||||
sshConfigOptions: *sshOptions,
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
@@ -268,9 +137,8 @@ var muxedSSHConfigTemplate = template.Must(template.New("muxed-ssh-config").Func
|
||||
Host *.{{ $clusterName }}
|
||||
Port {{ $dot.Port }}
|
||||
UserKnownHostsFile {{ proxyCommandQuote $dot.KnownHostsPath }}
|
||||
HostKeyAlgorithms {{ if $dot.NewerHostKeyAlgorithmsSupported }}rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,{{ end }}ssh-rsa-cert-v01@openssh.com
|
||||
IdentityFile none
|
||||
IdentityAgent {{ proxyCommandQuote $dot.AgentSocketPath }}
|
||||
IdentityAgent {{ proxyCommandQuote $dot.AgentSocketPath }}
|
||||
ProxyCommand {{range $v := $dot.ProxyCommand}}{{ proxyCommandQuote $v }} {{end}}{{ proxyCommandQuote $dot.MuxSocketPath }} '%h:%p|{{ $clusterName }}'
|
||||
ProxyUseFDPass yes
|
||||
{{- end }}
|
||||
@@ -292,29 +160,16 @@ type MuxedSSHConfigParameters struct {
|
||||
|
||||
type muxedSSHTmplParams struct {
|
||||
MuxedSSHConfigParameters
|
||||
sshConfigOptions
|
||||
}
|
||||
|
||||
// GetMuxedSSHConfig generates a ssh_config file for the ssh-multiplexer service.
|
||||
func (c *SSHConfig) GetMuxedSSHConfig(sb *strings.Builder, config *MuxedSSHConfigParameters) error {
|
||||
var sshOptions *sshConfigOptions
|
||||
version, err := c.getSSHVersion()
|
||||
if err != nil {
|
||||
c.log.WithError(err).Debugf("Could not determine SSH version, using default SSH config")
|
||||
sshOptions = getDefaultSSHConfigOptions()
|
||||
} else {
|
||||
c.log.Debugf("Found OpenSSH version %s", version)
|
||||
sshOptions = getSSHConfigOptions(version)
|
||||
}
|
||||
// WriteMuxedSSHConfig generates a ssh_config file for the ssh-multiplexer service.
|
||||
func WriteMuxedSSHConfig(w io.Writer, config *MuxedSSHConfigParameters) error {
|
||||
if config.Port == 0 {
|
||||
config.Port = defaults.SSHServerListenPort
|
||||
}
|
||||
|
||||
c.log.Debugf("Using SSH options: %s", sshOptions)
|
||||
|
||||
if err := muxedSSHConfigTemplate.Execute(sb, muxedSSHTmplParams{
|
||||
if err := muxedSSHConfigTemplate.Execute(w, muxedSSHTmplParams{
|
||||
MuxedSSHConfigParameters: *config,
|
||||
sshConfigOptions: *sshOptions,
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -22,58 +22,12 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/lib/utils/golden"
|
||||
)
|
||||
|
||||
func TestParseSSHVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
str string
|
||||
version *semver.Version
|
||||
err bool
|
||||
}{
|
||||
{
|
||||
str: "OpenSSH_8.2p1 Ubuntu-4ubuntu0.4, OpenSSL 1.1.1f 31 Mar 2020",
|
||||
version: semver.New("8.2.1"),
|
||||
},
|
||||
{
|
||||
str: "OpenSSH_8.8p1, OpenSSL 1.1.1m 14 Dec 2021",
|
||||
version: semver.New("8.8.1"),
|
||||
},
|
||||
{
|
||||
str: "OpenSSH_7.5p1, OpenSSL 1.0.2s-freebsd 28 May 2019",
|
||||
version: semver.New("7.5.1"),
|
||||
},
|
||||
{
|
||||
str: "OpenSSH_7.9p1 Raspbian-10+deb10u2, OpenSSL 1.1.1d 10 Sep 2019",
|
||||
version: semver.New("7.9.1"),
|
||||
},
|
||||
{
|
||||
// Couldn't find a full example but in theory patch is optional:
|
||||
str: "OpenSSH_8.1 foo",
|
||||
version: semver.New("8.1.0"),
|
||||
},
|
||||
{
|
||||
str: "Teleport v8.0.0-dev.40 git:v8.0.0-dev.40-0-ge9194c256 go1.17.2",
|
||||
err: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
version, err := parseSSHVersion(test.str)
|
||||
if test.err {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.True(t, version.Equal(*test.version), "got version = %v, want = %v", version, test.version)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHConfig_GetSSHConfig(t *testing.T) {
|
||||
func TestWriteSSHConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sshVersion string
|
||||
@@ -157,15 +111,8 @@ func TestSSHConfig_GetSSHConfig(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &SSHConfig{
|
||||
getSSHVersion: func() (*semver.Version, error) {
|
||||
return semver.New(tt.sshVersion), nil
|
||||
},
|
||||
log: logrus.New(),
|
||||
}
|
||||
|
||||
sb := &strings.Builder{}
|
||||
err := c.GetSSHConfig(sb, tt.config)
|
||||
err := WriteSSHConfig(sb, tt.config)
|
||||
if golden.ShouldSet() {
|
||||
golden.Set(t, []byte(sb.String()))
|
||||
}
|
||||
@@ -175,7 +122,7 @@ func TestSSHConfig_GetSSHConfig(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestSSHConfig_GetMuxedSSHConfig(t *testing.T) {
|
||||
func TestWriteMuxedSSHConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
sshVersion string
|
||||
@@ -221,15 +168,8 @@ func TestSSHConfig_GetMuxedSSHConfig(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
c := &SSHConfig{
|
||||
getSSHVersion: func() (*semver.Version, error) {
|
||||
return semver.New(tt.sshVersion), nil
|
||||
},
|
||||
log: logrus.New(),
|
||||
}
|
||||
|
||||
sb := &strings.Builder{}
|
||||
err := c.GetMuxedSSHConfig(sb, tt.config)
|
||||
err := WriteMuxedSSHConfig(sb, tt.config)
|
||||
if golden.ShouldSet() {
|
||||
golden.Set(t, []byte(sb.String()))
|
||||
}
|
||||
|
||||
+1
-2
@@ -3,9 +3,8 @@
|
||||
Host *.example.com
|
||||
Port 3022
|
||||
UserKnownHostsFile '/opt/machine-id/known_hosts'
|
||||
HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com
|
||||
IdentityFile none
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
ProxyCommand '/bin/fdpass-teleport' 'foo' '/opt/machine-id/v1.sock' '%h:%p|example.com'
|
||||
ProxyUseFDPass yes
|
||||
# End generated Teleport configuration
|
||||
+2
-4
@@ -3,17 +3,15 @@
|
||||
Host *.example.com
|
||||
Port 3022
|
||||
UserKnownHostsFile '/opt/machine-id/known_hosts'
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
IdentityFile none
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
ProxyCommand '/bin/fdpass-teleport' 'foo' '/opt/machine-id/v1.sock' '%h:%p|example.com'
|
||||
ProxyUseFDPass yes
|
||||
Host *.example.org
|
||||
Port 3022
|
||||
UserKnownHostsFile '/opt/machine-id/known_hosts'
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
IdentityFile none
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
ProxyCommand '/bin/fdpass-teleport' 'foo' '/opt/machine-id/v1.sock' '%h:%p|example.org'
|
||||
ProxyUseFDPass yes
|
||||
# End generated Teleport configuration
|
||||
+1
-2
@@ -3,9 +3,8 @@
|
||||
Host *.example.com
|
||||
Port 3022
|
||||
UserKnownHostsFile '/opt/machine-id/known_hosts'
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
IdentityFile none
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
IdentityAgent '/opt/machine-id/agent.sock'
|
||||
ProxyCommand '/bin/fdpass-teleport' 'foo' '/opt/machine-id/v1.sock' '%h:%p|example.com'
|
||||
ProxyUseFDPass yes
|
||||
# End generated Teleport configuration
|
||||
-1
@@ -5,7 +5,6 @@ Host *.example.com proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all example.com hosts except the proxy
|
||||
Host *.example.com !proxy.example.com
|
||||
-2
@@ -5,7 +5,6 @@ Host *.root proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all root hosts except the proxy
|
||||
Host *.root !proxy.example.com
|
||||
@@ -16,7 +15,6 @@ Host *.leaf proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all leaf hosts except the proxy
|
||||
Host *.leaf !proxy.example.com
|
||||
-1
@@ -5,7 +5,6 @@ Host *.example.com proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all example.com hosts except the proxy
|
||||
Host *.example.com !proxy.example.com
|
||||
-1
@@ -5,7 +5,6 @@ Host *.example.com proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
User "testuser"
|
||||
|
||||
# Flags for all example.com hosts except the proxy
|
||||
-1
@@ -5,7 +5,6 @@ Host *.example.com proxy.example.com
|
||||
UserKnownHostsFile "/home/alice/.tsh/known_hosts"
|
||||
IdentityFile "/home/alice/.tsh/keys/example.com/bob"
|
||||
CertificateFile "/home/alice/.tsh/keys/example.com/bob-ssh/example.com-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all example.com hosts except the proxy
|
||||
Host *.example.com !proxy.example.com
|
||||
@@ -89,6 +89,9 @@ const (
|
||||
// UserDatabase represents a user Database key.
|
||||
UserDatabase
|
||||
|
||||
// HostSSH represents a host SSH key.
|
||||
HostSSH
|
||||
|
||||
// TODO(nklaassen): define remaining key purposes.
|
||||
|
||||
// keyPurposeMax is 1 greater than the last valid key purpose, used to test that all values less than this
|
||||
@@ -149,6 +152,7 @@ var (
|
||||
UserSSH: RSA2048,
|
||||
UserTLS: RSA2048,
|
||||
UserDatabase: RSA2048,
|
||||
HostSSH: RSA2048,
|
||||
// We could consider updating these algorithms even in the legacy suite,
|
||||
// only teleport agents need to accept these connections and they have
|
||||
// never restricted algorithm support.
|
||||
@@ -176,6 +180,7 @@ var (
|
||||
UserSSH: Ed25519,
|
||||
UserTLS: ECDSAP256,
|
||||
UserDatabase: RSA2048,
|
||||
HostSSH: Ed25519,
|
||||
ProxyToDatabaseAgent: ECDSAP256,
|
||||
ProxyKubeClient: ECDSAP256,
|
||||
// TODO(nklaassen): define remaining key purposes.
|
||||
@@ -200,6 +205,7 @@ var (
|
||||
UserSSH: ECDSAP256,
|
||||
UserTLS: ECDSAP256,
|
||||
UserDatabase: RSA2048,
|
||||
HostSSH: ECDSAP256,
|
||||
ProxyToDatabaseAgent: ECDSAP256,
|
||||
ProxyKubeClient: ECDSAP256,
|
||||
// TODO(nklaassen): define remaining key purposes.
|
||||
@@ -226,6 +232,7 @@ var (
|
||||
UserSSH: Ed25519,
|
||||
UserTLS: ECDSAP256,
|
||||
UserDatabase: RSA2048,
|
||||
HostSSH: Ed25519,
|
||||
ProxyToDatabaseAgent: ECDSAP256,
|
||||
ProxyKubeClient: ECDSAP256,
|
||||
// TODO(nklaassen): define remaining key purposes.
|
||||
|
||||
@@ -34,7 +34,7 @@ import (
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/lib/agentless"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/cryptosuites"
|
||||
"github.com/gravitational/teleport/lib/observability/tracing"
|
||||
"github.com/gravitational/teleport/lib/reversetunnelclient"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
@@ -652,7 +652,7 @@ func TestRouter_DialHost(t *testing.T) {
|
||||
return nil, nil
|
||||
}
|
||||
createSigner := func(_ context.Context, _ agentless.CertGenerator) (ssh.Signer, error) {
|
||||
key, err := native.GeneratePrivateKey()
|
||||
key, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.Ed25519)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -20,8 +20,6 @@ package reversetunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/rsa"
|
||||
"testing"
|
||||
|
||||
"github.com/gravitational/trace"
|
||||
@@ -32,6 +30,7 @@ import (
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
apisshutils "github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/cryptosuites"
|
||||
"github.com/gravitational/teleport/lib/sshutils"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
@@ -82,7 +81,7 @@ func TestAgentCertChecker(t *testing.T) {
|
||||
t.Cleanup(func() { require.NoError(t, sshServer.Close()) })
|
||||
require.NoError(t, sshServer.Start())
|
||||
|
||||
priv, err := rsa.GenerateKey(rand.Reader, 2048)
|
||||
priv, err := cryptosuites.GenerateKeyWithAlgorithm(cryptosuites.Ed25519)
|
||||
require.NoError(t, err)
|
||||
|
||||
signer, err := ssh.NewSignerFromKey(priv)
|
||||
|
||||
+32
-10
@@ -20,6 +20,7 @@ package reversetunnel
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
@@ -34,28 +35,32 @@ import (
|
||||
"github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/cryptosuites"
|
||||
"github.com/gravitational/teleport/lib/defaults"
|
||||
)
|
||||
|
||||
type certificateCache struct {
|
||||
mu sync.Mutex
|
||||
|
||||
cache *ttlmap.TTLMap
|
||||
authClient authclient.ClientI
|
||||
cache *ttlmap.TTLMap
|
||||
authClient authclient.ClientI
|
||||
suiteGetter cryptosuites.GetSuiteFunc
|
||||
}
|
||||
|
||||
// newHostCertificateCache creates a shared host certificate cache that is
|
||||
// used by the forwarding server.
|
||||
func newHostCertificateCache(authClient authclient.ClientI) (*certificateCache, error) {
|
||||
native.PrecomputeKeys() // ensure native package is set to precompute keys
|
||||
// used by the forwarding server. [authPrefGetter] technically offers only a
|
||||
// subset of [authClient], but it allows for using a cached view of the auth
|
||||
// preference.
|
||||
func newHostCertificateCache(authClient authclient.ClientI, authPrefGetter cryptosuites.AuthPreferenceGetter) (*certificateCache, error) {
|
||||
cache, err := ttlmap.New(defaults.HostCertCacheSize)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
return &certificateCache{
|
||||
cache: cache,
|
||||
authClient: authClient,
|
||||
cache: cache,
|
||||
authClient: authClient,
|
||||
suiteGetter: cryptosuites.GetCurrentSuiteFromAuthPreference(authPrefGetter),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -130,17 +135,34 @@ func (c *certificateCache) generateHostCert(ctx context.Context, principals []st
|
||||
}
|
||||
|
||||
// Generate public/private keypair.
|
||||
privBytes, pubBytes, err := native.GenerateKeyPair()
|
||||
hostKey, err := cryptosuites.GenerateKey(ctx, c.suiteGetter, cryptosuites.HostSSH)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
if _, isRSA := hostKey.Public().(*rsa.PublicKey); isRSA {
|
||||
// Ensure the native package is precomputing RSA keys if we ever
|
||||
// generate one. [native.PrecomputeKeys] is idempotent.
|
||||
// Doing this lazily easily handles changing signature algorithm suites
|
||||
// and won't start precomputing keys if they are never needed (a major
|
||||
// benefit in tests).
|
||||
native.PrecomputeKeys()
|
||||
}
|
||||
|
||||
sshPub, err := ssh.NewPublicKey(hostKey.Public())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
pubBytes := ssh.MarshalAuthorizedKey(sshPub)
|
||||
|
||||
// Generate a SSH host certificate.
|
||||
clusterName, err := c.authClient.GetDomainName(context.TODO())
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// TODO(nklaassen): request only an SSH cert, we don't need TLS here.
|
||||
// GenerateHostCert needs support for this.
|
||||
res, err := c.authClient.TrustClient().GenerateHostCert(ctx, &trustpb.GenerateHostCertRequest{
|
||||
Key: pubBytes,
|
||||
HostId: principals[0],
|
||||
@@ -156,7 +178,7 @@ func (c *certificateCache) generateHostCert(ctx context.Context, principals []st
|
||||
certBytes := res.SshCertificate
|
||||
|
||||
// create a *ssh.Certificate
|
||||
privateKey, err := ssh.ParsePrivateKey(privBytes)
|
||||
sshSigner, err := ssh.NewSignerFromSigner(hostKey)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
@@ -166,7 +188,7 @@ func (c *certificateCache) generateHostCert(ctx context.Context, principals []st
|
||||
}
|
||||
|
||||
// return a ssh.Signer
|
||||
s, err := ssh.NewCertSigner(cert, privateKey)
|
||||
s, err := ssh.NewCertSigner(cert, sshSigner)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -77,30 +77,30 @@ func withProxySyncInterval(interval time.Duration) func(site *localSite) {
|
||||
}
|
||||
}
|
||||
|
||||
// withCertificateCache sets the certificateCache of the site. This is particularly
|
||||
// helpful for tests because construction of the default cache will
|
||||
// call [native.PrecomputeKeys] which will consume a decent amount of CPU
|
||||
// to generate keys.
|
||||
func withCertificateCache(cache *certificateCache) func(site *localSite) {
|
||||
return func(site *localSite) {
|
||||
site.certificateCache = cache
|
||||
}
|
||||
}
|
||||
|
||||
func newLocalSite(srv *server, domainName string, authServers []string, opts ...func(*localSite)) (*localSite, error) {
|
||||
err := metrics.RegisterPrometheusCollectors(localClusterCollectors...)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
// instantiate a cache of host certificates for the forwarding server. the
|
||||
// certificate cache is created in each site (instead of creating it in
|
||||
// reversetunnel.server and passing it along) so that the host certificate
|
||||
// is signed by the correct certificate authority.
|
||||
certificateCache, err := newHostCertificateCache(srv.localAuthClient, srv.localAccessPoint)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
s := &localSite{
|
||||
srv: srv,
|
||||
client: srv.localAuthClient,
|
||||
accessPoint: srv.LocalAccessPoint,
|
||||
domainName: domainName,
|
||||
authServers: authServers,
|
||||
remoteConns: make(map[connKey][]*remoteConn),
|
||||
clock: srv.Clock,
|
||||
srv: srv,
|
||||
client: srv.localAuthClient,
|
||||
accessPoint: srv.LocalAccessPoint,
|
||||
certificateCache: certificateCache,
|
||||
domainName: domainName,
|
||||
authServers: authServers,
|
||||
remoteConns: make(map[connKey][]*remoteConn),
|
||||
clock: srv.Clock,
|
||||
log: log.WithFields(log.Fields{
|
||||
teleport.ComponentKey: teleport.ComponentReverseTunnelServer,
|
||||
teleport.ComponentFields: map[string]string{
|
||||
@@ -117,19 +117,6 @@ func newLocalSite(srv *server, domainName string, authServers []string, opts ...
|
||||
opt(s)
|
||||
}
|
||||
|
||||
if s.certificateCache == nil {
|
||||
// instantiate a cache of host certificates for the forwarding server. the
|
||||
// certificate cache is created in each site (instead of creating it in
|
||||
// reversetunnel.server and passing it along) so that the host certificate
|
||||
// is signed by the correct certificate authority.
|
||||
certificateCache, err := newHostCertificateCache(srv.localAuthClient)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
s.certificateCache = certificateCache
|
||||
}
|
||||
|
||||
// Start periodic functions for the local cluster in the background.
|
||||
go s.periodicFunctions()
|
||||
|
||||
|
||||
@@ -38,14 +38,12 @@ import (
|
||||
"github.com/gravitational/teleport/api/types"
|
||||
"github.com/gravitational/teleport/api/utils/sshutils"
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/services"
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
utils.InitLoggerForTests()
|
||||
native.PrecomputeTestKeys(m)
|
||||
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
@@ -85,7 +83,6 @@ func TestRemoteConnCleanup(t *testing.T) {
|
||||
site, err := newLocalSite(srv, "clustername", nil,
|
||||
withPeriodicFunctionInterval(time.Hour),
|
||||
withProxySyncInterval(time.Hour),
|
||||
withCertificateCache(&certificateCache{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -156,7 +153,6 @@ func TestLocalSiteOverlap(t *testing.T) {
|
||||
|
||||
site, err := newLocalSite(srv, "clustername", nil,
|
||||
withPeriodicFunctionInterval(time.Hour),
|
||||
withCertificateCache(&certificateCache{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -280,7 +276,6 @@ func TestProxyResync(t *testing.T) {
|
||||
site, err := newLocalSite(srv, "clustername", nil,
|
||||
withProxySyncInterval(time.Second),
|
||||
withPeriodicFunctionInterval(24*time.Hour),
|
||||
withCertificateCache(&certificateCache{}),
|
||||
)
|
||||
require.NoError(t, err)
|
||||
|
||||
|
||||
@@ -1226,7 +1226,7 @@ func newRemoteSite(srv *server, domainName string, sconn ssh.Conn) (*remoteSite,
|
||||
// certificate cache is created in each site (instead of creating it in
|
||||
// reversetunnel.server and passing it along) so that the host certificate
|
||||
// is signed by the correct certificate authority.
|
||||
certificateCache, err := newHostCertificateCache(srv.localAuthClient)
|
||||
certificateCache, err := newHostCertificateCache(srv.localAuthClient, srv.localAccessPoint)
|
||||
if err != nil {
|
||||
return nil, trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -92,7 +92,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/auth/authclient"
|
||||
"github.com/gravitational/teleport/lib/auth/keygen"
|
||||
"github.com/gravitational/teleport/lib/auth/machineid/machineidv1"
|
||||
"github.com/gravitational/teleport/lib/auth/native"
|
||||
"github.com/gravitational/teleport/lib/auth/state"
|
||||
"github.com/gravitational/teleport/lib/auth/storage"
|
||||
"github.com/gravitational/teleport/lib/authz"
|
||||
@@ -1053,13 +1052,6 @@ func waitAndReload(ctx context.Context, sigC <-chan os.Signal, cfg servicecfg.Co
|
||||
func NewTeleport(cfg *servicecfg.Config) (*TeleportProcess, error) {
|
||||
var err error
|
||||
|
||||
// auth and proxy benefit from precomputing keys since they can experience spikes in key
|
||||
// generation due to web session creation and recorded session creation respectively.
|
||||
// for all other agents precomputing keys consumes excess resources.
|
||||
if cfg.Auth.Enabled || cfg.Proxy.Enabled {
|
||||
native.PrecomputeKeys()
|
||||
}
|
||||
|
||||
// Before we do anything reset the SIGINT handler back to the default.
|
||||
system.ResetInterruptSignalHandler()
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/client/proto"
|
||||
@@ -41,14 +40,6 @@ import (
|
||||
"github.com/gravitational/teleport/lib/utils"
|
||||
)
|
||||
|
||||
// sshConfigProxyModeEnv is the environment variable that controls whether or
|
||||
// not to use the new proxy command.
|
||||
// It supports:
|
||||
// - "legacy" (default in v15): use the legacy proxy command
|
||||
// - "new" (default in v16): use the new proxy command
|
||||
// In v17, it will be removed.
|
||||
const sshConfigProxyModeEnv = "TBOT_SSH_CONFIG_PROXY_COMMAND_MODE"
|
||||
|
||||
// IdentityOutputService produces credentials which can be used to connect to
|
||||
// Teleport's API or SSH.
|
||||
type IdentityOutputService struct {
|
||||
@@ -66,7 +57,6 @@ type IdentityOutputService struct {
|
||||
// executablePath is called to get the path to the tbot executable.
|
||||
// Usually this is os.Executable
|
||||
executablePath func() (string, error)
|
||||
getEnv func(key string) string
|
||||
alpnUpgradeCache *alpnProxyConnUpgradeRequiredCache
|
||||
}
|
||||
|
||||
@@ -191,8 +181,6 @@ func (s *IdentityOutputService) generate(ctx context.Context) error {
|
||||
s.cfg.Destination,
|
||||
s.botAuthClient,
|
||||
s.executablePath,
|
||||
openssh.GetSystemSSHVersion,
|
||||
s.getEnv,
|
||||
s.alpnUpgradeCache,
|
||||
s.botCfg,
|
||||
); err != nil {
|
||||
@@ -254,8 +242,6 @@ func renderSSHConfig(
|
||||
dest bot.Destination,
|
||||
certAuthGetter certAuthGetter,
|
||||
getExecutablePath func() (string, error),
|
||||
getOpenSSHVersion func() (*semver.Version, error),
|
||||
getEnv func(key string) string,
|
||||
alpnTester alpnTester,
|
||||
botCfg *config.BotConfig,
|
||||
) error {
|
||||
@@ -317,63 +303,42 @@ func renderSSHConfig(
|
||||
identityFilePath := filepath.Join(absDestPath, identity.PrivateKeyKey)
|
||||
certificateFilePath := filepath.Join(absDestPath, identity.SSHCertKey)
|
||||
|
||||
sshConf := openssh.NewSSHConfig(getOpenSSHVersion, nil)
|
||||
|
||||
if getEnv(sshConfigProxyModeEnv) == "legacy" {
|
||||
// Deprecated: this block will be removed in v17. It exists so users can
|
||||
// revert to the old behavior if necessary.
|
||||
// TODO(strideynet) DELETE IN 17.0.0
|
||||
if err := sshConf.GetSSHConfig(&sshConfigBuilder, &openssh.SSHConfigParameters{
|
||||
AppName: openssh.TbotApp,
|
||||
ClusterNames: clusterNames,
|
||||
KnownHostsPath: knownHostsPath,
|
||||
IdentityFilePath: identityFilePath,
|
||||
CertificateFilePath: certificateFilePath,
|
||||
ProxyHost: proxyHost,
|
||||
ProxyPort: proxyPort,
|
||||
ExecutablePath: executablePath,
|
||||
DestinationDir: absDestPath,
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
} else {
|
||||
// Test if ALPN upgrade is required, this will only be necessary if we
|
||||
// are using TLS routing.
|
||||
connUpgradeRequired := false
|
||||
if proxyPing.Proxy.TLSRoutingEnabled {
|
||||
connUpgradeRequired, err = alpnTester.isUpgradeRequired(
|
||||
ctx, proxyPing.Proxy.SSH.PublicAddr, botCfg.Insecure,
|
||||
)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "determining if ALPN upgrade is required")
|
||||
}
|
||||
// Test if ALPN upgrade is required, this will only be necessary if we
|
||||
// are using TLS routing.
|
||||
connUpgradeRequired := false
|
||||
if proxyPing.Proxy.TLSRoutingEnabled {
|
||||
connUpgradeRequired, err = alpnTester.isUpgradeRequired(
|
||||
ctx, proxyPing.Proxy.SSH.PublicAddr, botCfg.Insecure,
|
||||
)
|
||||
if err != nil {
|
||||
return trace.Wrap(err, "determining if ALPN upgrade is required")
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SSH config
|
||||
if err := sshConf.GetSSHConfig(&sshConfigBuilder, &openssh.SSHConfigParameters{
|
||||
AppName: openssh.TbotApp,
|
||||
ClusterNames: clusterNames,
|
||||
KnownHostsPath: knownHostsPath,
|
||||
IdentityFilePath: identityFilePath,
|
||||
CertificateFilePath: certificateFilePath,
|
||||
ProxyHost: proxyHost,
|
||||
ProxyPort: proxyPort,
|
||||
ExecutablePath: executablePath,
|
||||
DestinationDir: absDestPath,
|
||||
// Generate SSH config
|
||||
if err := openssh.WriteSSHConfig(&sshConfigBuilder, &openssh.SSHConfigParameters{
|
||||
AppName: openssh.TbotApp,
|
||||
ClusterNames: clusterNames,
|
||||
KnownHostsPath: knownHostsPath,
|
||||
IdentityFilePath: identityFilePath,
|
||||
CertificateFilePath: certificateFilePath,
|
||||
ProxyHost: proxyHost,
|
||||
ProxyPort: proxyPort,
|
||||
ExecutablePath: executablePath,
|
||||
DestinationDir: absDestPath,
|
||||
|
||||
PureTBotProxyCommand: true,
|
||||
Insecure: botCfg.Insecure,
|
||||
FIPS: botCfg.FIPS,
|
||||
TLSRouting: proxyPing.Proxy.TLSRoutingEnabled,
|
||||
ConnectionUpgrade: connUpgradeRequired,
|
||||
PureTBotProxyCommand: true,
|
||||
Insecure: botCfg.Insecure,
|
||||
FIPS: botCfg.FIPS,
|
||||
TLSRouting: proxyPing.Proxy.TLSRoutingEnabled,
|
||||
ConnectionUpgrade: connUpgradeRequired,
|
||||
|
||||
// Session resumption is enabled by default, this can be
|
||||
// configurable at a later date if we discover reasons for this to
|
||||
// be disabled.
|
||||
Resume: true,
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
// Session resumption is enabled by default, this can be
|
||||
// configurable at a later date if we discover reasons for this to
|
||||
// be disabled.
|
||||
Resume: true,
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
if err := destDirectory.Write(ctx, ssh.ConfigName, []byte(sshConfigBuilder.String())); err != nil {
|
||||
|
||||
@@ -26,7 +26,6 @@ import (
|
||||
"slices"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/gravitational/trace"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -117,39 +116,28 @@ func (p *mockALPNConnTester) isUpgradeRequired(ctx context.Context, addr string,
|
||||
func Test_renderSSHConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
Name string
|
||||
Version string
|
||||
Env map[string]string
|
||||
TLSRouting bool
|
||||
ALPNUpgrade bool
|
||||
}{
|
||||
{
|
||||
Name: "legacy OpenSSH",
|
||||
Version: "6.5.0",
|
||||
TLSRouting: true,
|
||||
Name: "no tls routing, no alpn upgrade",
|
||||
TLSRouting: false,
|
||||
ALPNUpgrade: false,
|
||||
},
|
||||
{
|
||||
Name: "latest OpenSSH",
|
||||
Version: "9.0.0",
|
||||
TLSRouting: true,
|
||||
},
|
||||
{
|
||||
Name: "latest OpenSSH no tls routing",
|
||||
Version: "9.0.0",
|
||||
TLSRouting: false,
|
||||
},
|
||||
{
|
||||
Name: "latest OpenSSH with alpn upgrade",
|
||||
Version: "9.0.0",
|
||||
Name: "no tls routing, alpn upgrade",
|
||||
TLSRouting: false,
|
||||
ALPNUpgrade: true,
|
||||
TLSRouting: true,
|
||||
},
|
||||
{
|
||||
Name: "latest OpenSSH with legacy proxycommand",
|
||||
Version: "9.0.0",
|
||||
Env: map[string]string{
|
||||
sshConfigProxyModeEnv: "legacy",
|
||||
},
|
||||
TLSRouting: true,
|
||||
Name: "tls routing, no alpn upgrade",
|
||||
TLSRouting: true,
|
||||
ALPNUpgrade: false,
|
||||
},
|
||||
{
|
||||
Name: "tls routing, alpn upgrade",
|
||||
TLSRouting: true,
|
||||
ALPNUpgrade: true,
|
||||
},
|
||||
}
|
||||
|
||||
@@ -182,15 +170,6 @@ func Test_renderSSHConfig(t *testing.T) {
|
||||
clusterName: mockClusterName,
|
||||
},
|
||||
fakeGetExecutablePath,
|
||||
func() (*semver.Version, error) {
|
||||
return semver.New(tc.Version), nil
|
||||
},
|
||||
func(key string) string {
|
||||
if tc.Env == nil {
|
||||
return ""
|
||||
}
|
||||
return tc.Env[key]
|
||||
},
|
||||
&mockALPNConnTester{
|
||||
isALPNUpgradeRequired: tc.ALPNUpgrade,
|
||||
},
|
||||
|
||||
@@ -192,8 +192,7 @@ func (s *SSHMultiplexerService) writeArtifacts(
|
||||
}
|
||||
|
||||
var sshConfigBuilder strings.Builder
|
||||
sshConf := openssh.NewSSHConfig(openssh.GetSystemSSHVersion, nil)
|
||||
err = sshConf.GetMuxedSSHConfig(&sshConfigBuilder, &openssh.MuxedSSHConfigParameters{
|
||||
err = openssh.WriteMuxedSSHConfig(&sshConfigBuilder, &openssh.MuxedSSHConfigParameters{
|
||||
AppName: openssh.TbotApp,
|
||||
ClusterNames: clusterNames,
|
||||
KnownHostsPath: filepath.Join(absPath, ssh.KnownHostsName),
|
||||
|
||||
@@ -434,7 +434,6 @@ func (b *Bot) Run(ctx context.Context) (err error) {
|
||||
reloadBroadcaster: reloadBroadcaster,
|
||||
resolver: resolver,
|
||||
executablePath: os.Executable,
|
||||
getEnv: os.Getenv,
|
||||
alpnUpgradeCache: alpnUpgradeCache,
|
||||
proxyPingCache: proxyPingCache,
|
||||
}
|
||||
|
||||
Vendored
-26
@@ -1,26 +0,0 @@
|
||||
# Begin generated Teleport configuration for tele.blackmesa.gov by tbot
|
||||
|
||||
# Common flags for all tele.blackmesa.gov hosts
|
||||
Host *.tele.blackmesa.gov tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.blackmesa.gov hosts except the proxy
|
||||
Host *.tele.blackmesa.gov !tele.blackmesa.gov
|
||||
Port 3022
|
||||
ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy-server=tele.blackmesa.gov:443 ssh --cluster=tele.blackmesa.gov %r@%h:%p
|
||||
# Common flags for all tele.aperture.labs hosts
|
||||
Host *.tele.aperture.labs tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.aperture.labs hosts except the proxy
|
||||
Host *.tele.aperture.labs !tele.blackmesa.gov
|
||||
Port 3022
|
||||
ProxyCommand "/path/to/tbot" proxy --destination-dir=/test/dir --proxy-server=tele.blackmesa.gov:443 ssh --cluster=tele.aperture.labs %r@%h:%p
|
||||
|
||||
# End generated Teleport configuration
|
||||
@@ -1,4 +0,0 @@
|
||||
@cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host
|
||||
@cert-authority tele.blackmesa.gov,tele.blackmesa.gov,*.tele.blackmesa.gov ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host
|
||||
@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host
|
||||
@cert-authority tele.blackmesa.gov,tele.aperture.labs,*.tele.aperture.labs ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8kYdyZA1ZSNjZ4pqybDXvWplHQHkU6fPL+cAYHUkAT5CiQV4GOjwaSTcvZNK5U2fQ0jm6jknCnsZi1t9JujCjXUT3bYHCnSwWhXN55QzIu530Q/MeXz5W8TxYRrWULgPhqqtq8B9N554+s40higG21fmhhdDtpmQzw3vJLspY05mnL1+fW+RIKkM4rb150sdZXKINxfNQvERteE8WX0vL2yG4RuqJzYtGCDEGeHd+HLne7xfmqPxun7bUYaxAlplhm1z2J41hqaj8pBwDSEV9SBOZXvh6FjS9nvJCT7Z1bbZwWrAO/7E2ac0eV+5iEc0J+TyufO3F9uod+J+AICtB type=host
|
||||
-2
@@ -5,7 +5,6 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.blackmesa.gov hosts except the proxy
|
||||
Host *.tele.blackmesa.gov !tele.blackmesa.gov
|
||||
@@ -16,7 +15,6 @@ Host *.tele.aperture.labs tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.aperture.labs hosts except the proxy
|
||||
Host *.tele.aperture.labs !tele.blackmesa.gov
|
||||
+2
-4
@@ -5,22 +5,20 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.blackmesa.gov hosts except the proxy
|
||||
Host *.tele.blackmesa.gov !tele.blackmesa.gov
|
||||
Port 3022
|
||||
ProxyCommand '/path/to/tbot' ssh-proxy-command --destination-dir='/test/dir' --proxy-server='tele.blackmesa.gov:443' --cluster='tele.blackmesa.gov' --tls-routing --no-connection-upgrade --resume --user=%r --host=%h --port=%p
|
||||
ProxyCommand '/path/to/tbot' ssh-proxy-command --destination-dir='/test/dir' --proxy-server='tele.blackmesa.gov:443' --cluster='tele.blackmesa.gov' --no-tls-routing --no-connection-upgrade --resume --user=%r --host=%h --port=%p
|
||||
# Common flags for all tele.aperture.labs hosts
|
||||
Host *.tele.aperture.labs tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.aperture.labs hosts except the proxy
|
||||
Host *.tele.aperture.labs !tele.blackmesa.gov
|
||||
Port 3022
|
||||
ProxyCommand '/path/to/tbot' ssh-proxy-command --destination-dir='/test/dir' --proxy-server='tele.blackmesa.gov:443' --cluster='tele.aperture.labs' --tls-routing --no-connection-upgrade --resume --user=%r --host=%h --port=%p
|
||||
ProxyCommand '/path/to/tbot' ssh-proxy-command --destination-dir='/test/dir' --proxy-server='tele.blackmesa.gov:443' --cluster='tele.aperture.labs' --no-tls-routing --no-connection-upgrade --resume --user=%r --host=%h --port=%p
|
||||
|
||||
# End generated Teleport configuration
|
||||
-2
@@ -5,7 +5,6 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.blackmesa.gov hosts except the proxy
|
||||
Host *.tele.blackmesa.gov !tele.blackmesa.gov
|
||||
@@ -16,7 +15,6 @@ Host *.tele.aperture.labs tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.aperture.labs hosts except the proxy
|
||||
Host *.tele.aperture.labs !tele.blackmesa.gov
|
||||
-2
@@ -5,7 +5,6 @@ Host *.tele.blackmesa.gov tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.blackmesa.gov hosts except the proxy
|
||||
Host *.tele.blackmesa.gov !tele.blackmesa.gov
|
||||
@@ -16,7 +15,6 @@ Host *.tele.aperture.labs tele.blackmesa.gov
|
||||
UserKnownHostsFile "/test/dir/known_hosts"
|
||||
IdentityFile "/test/dir/key"
|
||||
CertificateFile "/test/dir/key-cert.pub"
|
||||
HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all tele.aperture.labs hosts except the proxy
|
||||
Host *.tele.aperture.labs !tele.blackmesa.gov
|
||||
@@ -19,11 +19,9 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/gravitational/trace"
|
||||
|
||||
"github.com/gravitational/teleport/api/profile"
|
||||
@@ -33,9 +31,8 @@ import (
|
||||
|
||||
// writeSSHConfig generates an OpenSSH config block from the `sshConfigTemplate`
|
||||
// template string.
|
||||
func writeSSHConfig(sb *strings.Builder, params *openssh.SSHConfigParameters, getSSHVersion func() (*semver.Version, error)) error {
|
||||
sshConf := openssh.NewSSHConfig(getSSHVersion, log)
|
||||
if err := sshConf.GetSSHConfig(sb, params); err != nil {
|
||||
func writeSSHConfig(w io.Writer, params *openssh.SSHConfigParameters) error {
|
||||
if err := openssh.WriteSSHConfig(w, params); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
@@ -87,8 +84,7 @@ func onConfig(cf *CLIConf) error {
|
||||
leafClustersNames = append(leafClustersNames, leafCluster.GetName())
|
||||
}
|
||||
|
||||
var sb strings.Builder
|
||||
if err := writeSSHConfig(&sb, &openssh.SSHConfigParameters{
|
||||
if err := writeSSHConfig(cf.Stdout(), &openssh.SSHConfigParameters{
|
||||
AppName: openssh.TshApp,
|
||||
ClusterNames: append([]string{clusterClient.RootClusterName()}, leafClustersNames...),
|
||||
KnownHostsPath: knownHostsPath,
|
||||
@@ -99,11 +95,9 @@ func onConfig(cf *CLIConf) error {
|
||||
ExecutablePath: cf.executablePath,
|
||||
Username: cf.NodeLogin,
|
||||
Port: int(cf.NodePort),
|
||||
}, nil); err != nil {
|
||||
}); err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
stdout := cf.Stdout()
|
||||
fmt.Fprint(stdout, sb.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/gravitational/teleport/lib/config/openssh"
|
||||
@@ -39,7 +38,6 @@ Host *.test-cluster localhost
|
||||
UserKnownHostsFile "/tmp/know_host"
|
||||
IdentityFile "/tmp/alice"
|
||||
CertificateFile "/tmp/localhost-cert.pub"
|
||||
HostKeyAlgorithms rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ssh-rsa-cert-v01@openssh.com
|
||||
|
||||
# Flags for all test-cluster hosts except the proxy
|
||||
Host *.test-cluster !localhost
|
||||
@@ -59,8 +57,6 @@ Host *.test-cluster !localhost
|
||||
ProxyHost: "localhost",
|
||||
ProxyPort: "3080",
|
||||
ExecutablePath: "/bin/tsh",
|
||||
}, func() (*semver.Version, error) {
|
||||
return semver.New("9.0.0"), nil
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, want, sb.String())
|
||||
|
||||
@@ -715,7 +715,7 @@ Host *
|
||||
}
|
||||
|
||||
// TestTSHConfigConnectWithOpenSSHClient tests OpenSSH configuration generated by tsh config command and
|
||||
// connects to ssh node using native OpenSSH client with different session recording modes and proxy listener modes.
|
||||
// connects to ssh node using native OpenSSH client with different session recording modes and proxy listener modes.
|
||||
func TestTSHConfigConnectWithOpenSSHClient(t *testing.T) {
|
||||
// Only run this test if we have access to the external SSH binary.
|
||||
_, err := exec.LookPath("ssh")
|
||||
@@ -748,6 +748,18 @@ func TestTSHConfigConnectWithOpenSSHClient(t *testing.T) {
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
// Test with the legacy signature algorithm suite which generates
|
||||
// RSA keys.
|
||||
name: "proxy recording mode with TLS routing enabled legacy",
|
||||
opts: []testSuiteOptionFunc{
|
||||
withRootConfigFunc(func(cfg *servicecfg.Config) {
|
||||
cfg.Auth.SessionRecordingConfig.SetMode(types.RecordAtProxySync)
|
||||
cfg.Auth.NetworkingConfig.SetProxyListenerMode(types.ProxyListenerMode_Multiplex)
|
||||
cfg.Auth.Preference.SetSignatureAlgorithmSuite(types.SignatureAlgorithmSuite_SIGNATURE_ALGORITHM_SUITE_LEGACY)
|
||||
}),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "node recording mode with TLS routing disabled",
|
||||
opts: []testSuiteOptionFunc{
|
||||
@@ -1069,7 +1081,7 @@ func mustFailToRunOpenSSHCommand(t *testing.T, configFile string, sshConnString
|
||||
}
|
||||
|
||||
func mustFindFailedNodeLoginAttempt(t *testing.T, s *suite, nodeLogin string) {
|
||||
require.EventuallyWithT(t, func(t *assert.CollectT) {
|
||||
err := retryutils.RetryStaticFor(5*time.Second, 50*time.Millisecond, func() error {
|
||||
now := time.Now()
|
||||
ctx := context.Background()
|
||||
es, _, err := s.root.GetAuthServer().SearchEvents(ctx, events.SearchEventsRequest{
|
||||
@@ -1077,16 +1089,18 @@ func mustFindFailedNodeLoginAttempt(t *testing.T, s *suite, nodeLogin string) {
|
||||
To: now.Add(time.Hour),
|
||||
Order: types.EventOrderDescending,
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
if err != nil {
|
||||
return trace.Wrap(err)
|
||||
}
|
||||
|
||||
for _, e := range es {
|
||||
if e.GetCode() == events.AuthAttemptFailureCode {
|
||||
assert.Equal(t, e.(*apievents.AuthAttempt).Login, nodeLogin)
|
||||
return
|
||||
if e.GetCode() == events.AuthAttemptFailureCode && e.(*apievents.AuthAttempt).Login == nodeLogin {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.Errorf("failed to find AuthAttemptFailureCode event (0/%d events matched)", len(es))
|
||||
}, 5*time.Second, 500*time.Millisecond)
|
||||
return fmt.Errorf("failed to find AuthAttemptFailureCode event (0/%d events matched)", len(es))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestFormatCommand(t *testing.T) {
|
||||
|
||||
@@ -89,6 +89,9 @@ func (s *suite) setupRootCluster(t *testing.T, options testSuiteOptions) {
|
||||
},
|
||||
ClusterName: "root",
|
||||
SessionRecording: "node-sync",
|
||||
Authentication: &config.AuthenticationConfig{
|
||||
SignatureAlgorithmSuite: types.SignatureAlgorithmSuite_SIGNATURE_ALGORITHM_SUITE_BALANCED_V1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user