-
Notifications
You must be signed in to change notification settings - Fork 2.1k
UX improvements for tbot #10833
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
UX improvements for tbot #10833
Changes from all commits
f885c2e
dd15532
758d8de
15bd716
31cd6f3
59c7a3d
b57ab7a
ef73b4a
82d1dee
6be0619
ce01786
ccff239
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,7 @@ import ( | |
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/coreos/go-semver/semver" | ||
| "github.com/gravitational/teleport/tool/tbot/identity" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
@@ -30,7 +31,7 @@ func TestConfigDefaults(t *testing.T) { | |
| require.NoError(t, err) | ||
|
|
||
| require.Equal(t, DefaultCertificateTTL, cfg.CertificateTTL) | ||
| require.Equal(t, DefaultRenewInterval, cfg.RenewInterval) | ||
| require.Equal(t, DefaultRenewInterval, cfg.RenewalInterval) | ||
|
|
||
| storageDest, err := cfg.Storage.GetDestination() | ||
| require.NoError(t, err) | ||
|
|
@@ -93,7 +94,7 @@ func TestConfigFile(t *testing.T) { | |
| require.NoError(t, err) | ||
|
|
||
| require.Equal(t, "auth.example.com", cfg.AuthServer) | ||
| require.Equal(t, time.Minute*5, cfg.RenewInterval) | ||
| require.Equal(t, time.Minute*5, cfg.RenewalInterval) | ||
|
|
||
| require.NotNil(t, cfg.Onboarding) | ||
| require.Equal(t, "foo", cfg.Onboarding.Token) | ||
|
|
@@ -125,9 +126,53 @@ func TestConfigFile(t *testing.T) { | |
| require.Equal(t, "/tmp/foo", destImplReal.Path) | ||
| } | ||
|
|
||
| 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) | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const exampleConfigFile = ` | ||
| auth_server: auth.example.com | ||
| renew_interval: 5m | ||
| renewal_interval: 5m | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there any docs that need to be updated for this rename?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like the docs PR (#10775) doesn't refer to this config parameter at all, luckily. |
||
| onboarding: | ||
| token: foo | ||
| ca_pins: | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,14 +17,18 @@ limitations under the License. | |
| package config | ||
|
|
||
| import ( | ||
| "bytes" | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "os/exec" | ||
| "path/filepath" | ||
| "regexp" | ||
| "strconv" | ||
| "strings" | ||
| "text/template" | ||
|
|
||
| "github.com/coreos/go-semver/semver" | ||
| "github.com/gravitational/teleport/api/types" | ||
| "github.com/gravitational/teleport/lib/auth" | ||
| "github.com/gravitational/teleport/lib/defaults" | ||
|
|
@@ -40,6 +44,66 @@ type TemplateSSHClient struct { | |
| ProxyPort uint16 `yaml:"proxy_port"` | ||
| } | ||
|
|
||
| // 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+))?`) | ||
|
|
||
| // openSSHMinVersionForRSAWorkaround is the OpenSSH version after which the | ||
| // RSA deprecation workaround should be added to generated ssh_config. | ||
| var openSSHMinVersionForRSAWorkaround = semver.New("8.5.0") | ||
|
|
||
| // parseSSHVersion attempts to parse | ||
| 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 | ||
| } | ||
|
|
||
| // getSSHVersion attempts to query the system SSH for its current version. | ||
| func getSSHVersion() (*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()) | ||
| } | ||
|
|
||
| func (c *TemplateSSHClient) CheckAndSetDefaults() error { | ||
| if c.ProxyPort == 0 { | ||
| c.ProxyPort = defaults.SSHProxyListenPort | ||
|
|
@@ -111,18 +175,31 @@ func (c *TemplateSSHClient) Render(ctx context.Context, authClient auth.ClientI, | |
| return trace.Wrap(err) | ||
| } | ||
|
|
||
| // Default to including the RSA deprecation workaround. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the RSA workaround? Do we have it explained somewhere?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'll add an explainer comment to the
|
||
| rsaWorkaround := true | ||
| version, err := getSSHVersion() | ||
| if err != nil { | ||
| log.WithError(err).Debugf("Could not determine SSH version, will include RSA workaround.") | ||
| } else if version.LessThan(*openSSHMinVersionForRSAWorkaround) { | ||
| log.Debugf("OpenSSH version %s does not require workaround for RSA deprecation", version) | ||
| rsaWorkaround = false | ||
| } else { | ||
| log.Debugf("OpenSSH version %s will use workaround for RSA deprecation", version) | ||
| } | ||
|
|
||
| var sshConfigBuilder strings.Builder | ||
| identityFilePath := filepath.Join(dataDir, identity.PrivateKeyKey) | ||
| certificateFilePath := filepath.Join(dataDir, identity.SSHCertKey) | ||
| sshConfigPath := filepath.Join(dataDir, "ssh_config") | ||
| if err := sshConfigTemplate.Execute(&sshConfigBuilder, sshConfigParameters{ | ||
| ClusterName: clusterName.GetClusterName(), | ||
| ProxyHost: proxyHost, | ||
| ProxyPort: proxyPort, | ||
| KnownHostsPath: knownHostsPath, | ||
| IdentityFilePath: identityFilePath, | ||
| CertificateFilePath: certificateFilePath, | ||
| SSHConfigPath: sshConfigPath, | ||
| ClusterName: clusterName.GetClusterName(), | ||
| ProxyHost: proxyHost, | ||
| ProxyPort: proxyPort, | ||
| KnownHostsPath: knownHostsPath, | ||
| IdentityFilePath: identityFilePath, | ||
| CertificateFilePath: certificateFilePath, | ||
| SSHConfigPath: sshConfigPath, | ||
| IncludeRSAWorkaround: rsaWorkaround, | ||
| }); err != nil { | ||
| return trace.Wrap(err) | ||
| } | ||
|
|
@@ -142,6 +219,15 @@ type sshConfigParameters struct { | |
| ProxyHost string | ||
| ProxyPort string | ||
| SSHConfigPath string | ||
|
|
||
| // IncludeRSAWorkaround controls whether the RSA deprecation workaround is | ||
| // included in the generated configuration. Newer versions of OpenSSH | ||
| // deprecate RSA certificates and, due to a bug in golang's ssh package, | ||
| // Teleport wrongly advertises its unaffected certificates as a | ||
| // now-deprecated certificate type. The workaround includes a config | ||
| // override to re-enable RSA certs for just Teleport hosts, however it is | ||
| // only supported on OpenSSH 8.5 and later. | ||
| IncludeRSAWorkaround bool | ||
| } | ||
|
|
||
| var sshConfigTemplate = template.Must(template.New("ssh-config").Parse(` | ||
|
|
@@ -152,13 +238,13 @@ Host *.{{ .ClusterName }} {{ .ProxyHost }} | |
| UserKnownHostsFile "{{ .KnownHostsPath }}" | ||
| IdentityFile "{{ .IdentityFilePath }}" | ||
| CertificateFile "{{ .CertificateFilePath }}" | ||
| HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com | ||
| PubkeyAcceptedAlgorithms +ssh-rsa-cert-v01@openssh.com | ||
| HostKeyAlgorithms ssh-rsa-cert-v01@openssh.com{{- if .IncludeRSAWorkaround }} | ||
| PubkeyAcceptedAlgorithms +ssh-rsa-cert-v01@openssh.com{{- end }} | ||
|
|
||
| # Flags for all {{ .ClusterName }} hosts except the proxy | ||
| Host *.{{ .ClusterName }} !{{ .ProxyHost }} | ||
| Port 3022 | ||
| ProxyCommand ssh -F {{ .SSHConfigPath }} -l %r -p {{ .ProxyPort }} {{ .ProxyHost }} -s proxy:%h:%p@{{ .ClusterName }} | ||
| ProxyCommand ssh -F {{ .SSHConfigPath }} -l %r -p {{ .ProxyPort }} {{ .ProxyHost }} -s proxy:$(echo %h | cut -d '.' -f 1):%p@{{ .ClusterName }} | ||
|
|
||
| # End generated Teleport configuration | ||
| `)) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.