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:
Nic Klaassen
2024-09-11 19:21:50 +00:00
committed by GitHub
parent ec750dd346
commit c15825c751
39 changed files with 166 additions and 461 deletions
+1 -1
View File
@@ -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.
-3
View File
@@ -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 {
+10
View File
@@ -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
+8 -153
View File
@@ -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)
}
+4 -64
View File
@@ -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()))
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+7
View File
@@ -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.
+2 -2
View File
@@ -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
}
+2 -3
View File
@@ -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
View File
@@ -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)
}
+17 -30
View File
@@ -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()
-5
View File
@@ -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)
+1 -1
View File
@@ -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)
}
-8
View File
@@ -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()
+32 -67
View File
@@ -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 {
+13 -34
View File
@@ -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,
},
+1 -2
View File
@@ -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),
-1
View File
@@ -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,
}
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
+5 -11
View File
@@ -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
}
-4
View File
@@ -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())
+22 -8
View File
@@ -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) {
+3
View File
@@ -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,
},
},
}