-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
dynamic SSH host certs use configurable algorithms
- Loading branch information
Showing
34 changed files
with
143 additions
and
425 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 }}[email protected],[email protected],{{ end }}[email protected] | ||
{{- 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 }}[email protected],[email protected],{{ end }}[email protected] | ||
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) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,8 @@ | |
Host *.example.com | ||
Port 3022 | ||
UserKnownHostsFile '/opt/machine-id/known_hosts' | ||
HostKeyAlgorithms [email protected] | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,17 +3,15 @@ | |
Host *.example.com | ||
Port 3022 | ||
UserKnownHostsFile '/opt/machine-id/known_hosts' | ||
HostKeyAlgorithms [email protected],[email protected],[email protected] | ||
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 [email protected],[email protected],[email protected] | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,8 @@ | |
Host *.example.com | ||
Port 3022 | ||
UserKnownHostsFile '/opt/machine-id/known_hosts' | ||
HostKeyAlgorithms [email protected],[email protected],[email protected] | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 [email protected] | ||
|
||
# Flags for all example.com hosts except the proxy | ||
Host *.example.com !proxy.example.com | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 [email protected],[email protected],[email protected] | ||
|
||
# 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 [email protected],[email protected],[email protected] | ||
|
||
# Flags for all leaf hosts except the proxy | ||
Host *.leaf !proxy.example.com | ||
|
Oops, something went wrong.