Skip to content

Commit

Permalink
plumbing: transport/ssh, Add support for SSH @cert-authority.
Browse files Browse the repository at this point in the history
skeema/knownhosts v1.3.0 introduced a HostKeyDB type that extends the HostKeyCallback functionality
to support @cert-authority algorithms.

`known_hosts` files may contain lines with @cert-authority markers to indicate that a line corresponds
to a certificate instead of a key. If a git remote uses cert authorities as the preferred host
identification mechanism, the functionality added in skeema/knownhosts v1.3.0 is needed so that go-git
can interact with this remote.

See skeema/knownhosts#9 for details.
  • Loading branch information
Javier-varez committed Jul 24, 2024
1 parent 7cab950 commit 7f676ae
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 12 deletions.
13 changes: 7 additions & 6 deletions plumbing/transport/ssh/auth_method.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,11 @@ func (a *PublicKeysCallback) ClientConfig() (*ssh.ClientConfig, error) {
// ~/.ssh/known_hosts
// /etc/ssh/ssh_known_hosts
func NewKnownHostsCallback(files ...string) (ssh.HostKeyCallback, error) {
kh, err := newKnownHosts(files...)
return ssh.HostKeyCallback(kh), err
db, err := newKnownHostsDb(files...)
return db.HostKeyCallback(), err
}

func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) {
func newKnownHostsDb(files ...string) (*knownhosts.HostKeyDB, error) {
var err error

if len(files) == 0 {
Expand All @@ -247,7 +247,7 @@ func newKnownHosts(files ...string) (knownhosts.HostKeyCallback, error) {
return nil, err
}

return knownhosts.New(files...)
return knownhosts.NewDB(files...)
}

func getDefaultKnownHostsFiles() ([]string, error) {
Expand Down Expand Up @@ -301,11 +301,12 @@ type HostKeyCallbackHelper struct {
// HostKeyCallback is empty a default callback is created using
// NewKnownHostsCallback.
func (m *HostKeyCallbackHelper) SetHostKeyCallback(cfg *ssh.ClientConfig) (*ssh.ClientConfig, error) {
var err error
if m.HostKeyCallback == nil {
if m.HostKeyCallback, err = NewKnownHostsCallback(); err != nil {
db, err := newKnownHostsDb()
if err != nil {
return cfg, err
}
m.HostKeyCallback = db.HostKeyCallback()
}

cfg.HostKeyCallback = m.HostKeyCallback
Expand Down
106 changes: 105 additions & 1 deletion plumbing/transport/ssh/auth_method_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import (
type (
SuiteCommon struct{}

mockKnownHosts struct{}
mockKnownHosts struct{}
mockKnownHostsWithCert struct{}
)

func (mockKnownHosts) host() string { return "github.com" }
Expand All @@ -27,6 +28,19 @@ func (mockKnownHosts) knownHosts() []byte {
}
func (mockKnownHosts) Network() string { return "tcp" }
func (mockKnownHosts) String() string { return "github.com:22" }
func (mockKnownHosts) Algorithms() []string {
return []string{ssh.KeyAlgoRSA, ssh.KeyAlgoRSASHA256, ssh.KeyAlgoRSASHA512}
}

func (mockKnownHostsWithCert) host() string { return "github.com" }
func (mockKnownHostsWithCert) knownHosts() []byte {
return []byte(`@cert-authority github.com ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAq2A7hRGmdnm9tUDbO9IDSwBK6TbQa+PXYPCPy6rbTrTtw7PHkccKrpp0yVhp5HdEIcKr6pLlVDBfOLX9QUsyCOV0wzfjIJNlGEYsdlLJizHhbn2mUjvSAHQqZETYP81eFzLQNnPHt4EVVUh7VfDESU84KezmD5QlWpXLmvU31/yMf+Se8xhHTvKSCZIFImWwoG6mbUoWf9nzpIoaSjB+weqqUUmpaaasXVal72J+UX2B+2RPW3RcT0eOzQgqlJL3RKrTJvdsjE3JEAvGq3lGHSZXy28G3skua2SmVi/w4yCE6gbODqnTWlg7+wC604ydGXA8VJiS5ap43JXiUFFAaQ==`)
}
func (mockKnownHostsWithCert) Network() string { return "tcp" }
func (mockKnownHostsWithCert) String() string { return "github.com:22" }
func (mockKnownHostsWithCert) Algorithms() []string {
return []string{ssh.CertAlgoRSASHA512v01, ssh.CertAlgoRSASHA256v01, ssh.CertAlgoRSAv01}
}

var _ = Suite(&SuiteCommon{})

Expand Down Expand Up @@ -230,3 +244,93 @@ func (*SuiteCommon) TestNewKnownHostsCallback(c *C) {
err = clb(mock.String(), mock, hostKey)
c.Assert(err, IsNil)
}

func (*SuiteCommon) TestNewKnownHostsDbWithoutCert(c *C) {
if runtime.GOOS == "js" {
c.Skip("not available in wasm")
}

var mock = mockKnownHosts{}

f, err := util.TempFile(osfs.Default, "", "known-hosts")
c.Assert(err, IsNil)

_, err = f.Write(mock.knownHosts())
c.Assert(err, IsNil)

err = f.Close()
c.Assert(err, IsNil)

defer util.RemoveAll(osfs.Default, f.Name())

f, err = osfs.Default.Open(f.Name())
c.Assert(err, IsNil)

defer f.Close()

db, err := newKnownHostsDb(f.Name())
c.Assert(err, IsNil)

algos := db.HostKeyAlgorithms(mock.String())
c.Assert(algos, HasLen, len(mock.Algorithms()))

contains := func(container []string, value string) bool {
for _, inner := range container {
if inner == value {
return true
}
}
return false
}

for _, algorithm := range mock.Algorithms() {
if !contains(algos, algorithm) {
c.Error("algos does not contain ", algorithm)
}
}
}

func (*SuiteCommon) TestNewKnownHostsDbWithCert(c *C) {
if runtime.GOOS == "js" {
c.Skip("not available in wasm")
}

var mock = mockKnownHostsWithCert{}

f, err := util.TempFile(osfs.Default, "", "known-hosts")
c.Assert(err, IsNil)

_, err = f.Write(mock.knownHosts())
c.Assert(err, IsNil)

err = f.Close()
c.Assert(err, IsNil)

defer util.RemoveAll(osfs.Default, f.Name())

f, err = osfs.Default.Open(f.Name())
c.Assert(err, IsNil)

defer f.Close()

db, err := newKnownHostsDb(f.Name())
c.Assert(err, IsNil)

algos := db.HostKeyAlgorithms(mock.String())
c.Assert(algos, HasLen, len(mock.Algorithms()))

contains := func(container []string, value string) bool {
for _, inner := range container {
if inner == value {
return true
}
}
return false
}

for _, algorithm := range mock.Algorithms() {
if !contains(algos, algorithm) {
c.Error("algos does not contain ", algorithm)
}
}
}
17 changes: 12 additions & 5 deletions plumbing/transport/ssh/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (

"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/internal/common"
"github.com/skeema/knownhosts"

"github.com/kevinburke/ssh_config"
"golang.org/x/crypto/ssh"
Expand Down Expand Up @@ -127,17 +126,25 @@ func (c *command) connect() error {
}
hostWithPort := c.getHostWithPort()
if config.HostKeyCallback == nil {
kh, err := newKnownHosts()
db, err := newKnownHostsDb()
if err != nil {
return err
}
config.HostKeyCallback = kh.HostKeyCallback()
config.HostKeyAlgorithms = kh.HostKeyAlgorithms(hostWithPort)

config.HostKeyCallback = db.HostKeyCallback()
config.HostKeyAlgorithms = db.HostKeyAlgorithms(hostWithPort)
} else if len(config.HostKeyAlgorithms) == 0 {
// Set the HostKeyAlgorithms based on HostKeyCallback.
// For background see https://github.com/go-git/go-git/issues/411 as well as
// https://github.com/golang/go/issues/29286 for root cause.
config.HostKeyAlgorithms = knownhosts.HostKeyAlgorithms(config.HostKeyCallback, hostWithPort)
db, err := newKnownHostsDb()
if err != nil {
return err
}

// Note that the knownhost database is used, as it provides additional functionality
// to handle ssh cert-authorities.
config.HostKeyAlgorithms = db.HostKeyAlgorithms(hostWithPort)
}

overrideConfig(c.config, config)
Expand Down

0 comments on commit 7f676ae

Please sign in to comment.