add flag to allow configuration of SSH kex algos

Adds a flag `ssh-kex-algos` which configures the gogit and libgit2
managed clients to use the specified list of kex algos for ssh. If not
used the default list in `golang/x/crypto/ssh` is used.

Signed-off-by: Sanskar Jaiswal <jaiswalsanskar078@gmail.com>
This commit is contained in:
Sanskar Jaiswal 2022-04-04 22:09:55 +05:30
parent 362bc56bd7
commit 5c84ea7e96
5 changed files with 83 additions and 12 deletions

View File

@ -46,6 +46,7 @@ import (
"github.com/fluxcd/source-controller/controllers" "github.com/fluxcd/source-controller/controllers"
"github.com/fluxcd/source-controller/internal/cache" "github.com/fluxcd/source-controller/internal/cache"
"github.com/fluxcd/source-controller/internal/helm" "github.com/fluxcd/source-controller/internal/helm"
"github.com/fluxcd/source-controller/pkg/git"
"github.com/fluxcd/source-controller/pkg/git/libgit2/managed" "github.com/fluxcd/source-controller/pkg/git/libgit2/managed"
// +kubebuilder:scaffold:imports // +kubebuilder:scaffold:imports
) )
@ -90,6 +91,7 @@ func main() {
helmCacheMaxSize int helmCacheMaxSize int
helmCacheTTL string helmCacheTTL string
helmCachePurgeInterval string helmCachePurgeInterval string
kexAlgos []string
) )
flag.StringVar(&metricsAddr, "metrics-addr", envOrDefault("METRICS_ADDR", ":8080"), flag.StringVar(&metricsAddr, "metrics-addr", envOrDefault("METRICS_ADDR", ":8080"),
@ -120,6 +122,8 @@ func main() {
"The TTL of an index in the cache. Valid time units are ns, us (or µs), ms, s, m, h.") "The TTL of an index in the cache. Valid time units are ns, us (or µs), ms, s, m, h.")
flag.StringVar(&helmCachePurgeInterval, "helm-cache-purge-interval", "1m", flag.StringVar(&helmCachePurgeInterval, "helm-cache-purge-interval", "1m",
"The interval at which the cache is purged. Valid time units are ns, us (or µs), ms, s, m, h.") "The interval at which the cache is purged. Valid time units are ns, us (or µs), ms, s, m, h.")
flag.StringSliceVar(&kexAlgos, "ssh-kex-algos", []string{},
"The list of key exchange algorithms to use for ssh connections, arranged from most preferred to the least.")
clientOptions.BindFlags(flag.CommandLine) clientOptions.BindFlags(flag.CommandLine)
logOptions.BindFlags(flag.CommandLine) logOptions.BindFlags(flag.CommandLine)
@ -174,6 +178,7 @@ func main() {
storageAdvAddr = determineAdvStorageAddr(storageAddr, setupLog) storageAdvAddr = determineAdvStorageAddr(storageAddr, setupLog)
} }
storage := mustInitStorage(storagePath, storageAdvAddr, setupLog) storage := mustInitStorage(storagePath, storageAdvAddr, setupLog)
setPreferredKexAlgos(kexAlgos)
if err = (&controllers.GitRepositoryReconciler{ if err = (&controllers.GitRepositoryReconciler{
Client: mgr.GetClient(), Client: mgr.GetClient(),
@ -333,3 +338,7 @@ func envOrDefault(envName, defaultValue string) string {
return defaultValue return defaultValue
} }
func setPreferredKexAlgos(algos []string) {
git.KexAlgos = algos
}

View File

@ -26,6 +26,8 @@ import (
"github.com/fluxcd/pkg/ssh/knownhosts" "github.com/fluxcd/pkg/ssh/knownhosts"
"github.com/fluxcd/source-controller/pkg/git" "github.com/fluxcd/source-controller/pkg/git"
gossh "golang.org/x/crypto/ssh"
) )
// transportAuth constructs the transport.AuthMethod for the git.Transport of // transportAuth constructs the transport.AuthMethod for the git.Transport of
@ -58,7 +60,10 @@ func transportAuth(opts *git.AuthOptions) (transport.AuthMethod, error) {
} }
pk.HostKeyCallback = callback pk.HostKeyCallback = callback
} }
return pk, nil customPK := &CustomPublicKeys{
pk: pk,
}
return customPK, nil
} }
case "": case "":
return nil, fmt.Errorf("no transport type set") return nil, fmt.Errorf("no transport type set")
@ -75,3 +80,28 @@ func caBundle(opts *git.AuthOptions) []byte {
} }
return opts.CAFile return opts.CAFile
} }
// CustomPublicKeys is a wrapper around ssh.PublicKeys to help us
// customize the ssh config. It implements ssh.AuthMethod.
type CustomPublicKeys struct {
pk *ssh.PublicKeys
}
func (a *CustomPublicKeys) Name() string {
return a.pk.Name()
}
func (a *CustomPublicKeys) String() string {
return a.pk.String()
}
func (a *CustomPublicKeys) ClientConfig() (*gossh.ClientConfig, error) {
config, err := a.pk.ClientConfig()
if err != nil {
return nil, err
}
if len(git.KexAlgos) > 0 {
config.Config.KeyExchanges = git.KexAlgos
}
return config, nil
}

View File

@ -22,7 +22,6 @@ import (
"github.com/go-git/go-git/v5/plumbing/transport" "github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http" "github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/go-git/go-git/v5/plumbing/transport/ssh"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/fluxcd/source-controller/pkg/git" "github.com/fluxcd/source-controller/pkg/git"
@ -72,6 +71,7 @@ func Test_transportAuth(t *testing.T) {
name string name string
opts *git.AuthOptions opts *git.AuthOptions
wantFunc func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) wantFunc func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions)
kexAlgos []string
wantErr error wantErr error
}{ }{
{ {
@ -128,10 +128,10 @@ func Test_transportAuth(t *testing.T) {
Identity: []byte(privateKeyFixture), Identity: []byte(privateKeyFixture),
}, },
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) { wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
tt, ok := t.(*ssh.PublicKeys) tt, ok := t.(*CustomPublicKeys)
g.Expect(ok).To(BeTrue()) g.Expect(ok).To(BeTrue())
g.Expect(tt.User).To(Equal(opts.Username)) g.Expect(tt.pk.User).To(Equal(opts.Username))
g.Expect(tt.Signer.PublicKey().Type()).To(Equal("ssh-rsa")) g.Expect(tt.pk.Signer.PublicKey().Type()).To(Equal("ssh-rsa"))
}, },
}, },
{ {
@ -143,10 +143,31 @@ func Test_transportAuth(t *testing.T) {
Identity: []byte(privateKeyPassphraseFixture), Identity: []byte(privateKeyPassphraseFixture),
}, },
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) { wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
tt, ok := t.(*ssh.PublicKeys) tt, ok := t.(*CustomPublicKeys)
g.Expect(ok).To(BeTrue()) g.Expect(ok).To(BeTrue())
g.Expect(tt.User).To(Equal(opts.Username)) g.Expect(tt.pk.User).To(Equal(opts.Username))
g.Expect(tt.Signer.PublicKey().Type()).To(Equal("ssh-rsa")) g.Expect(tt.pk.Signer.PublicKey().Type()).To(Equal("ssh-rsa"))
},
},
{
name: "SSH with custom key exchanges",
opts: &git.AuthOptions{
Transport: git.SSH,
Username: "example",
Identity: []byte(privateKeyFixture),
KnownHosts: []byte(knownHostsFixture),
},
kexAlgos: []string{"curve25519-sha256", "diffie-hellman-group-exchange-sha256"},
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
tt, ok := t.(*CustomPublicKeys)
g.Expect(ok).To(BeTrue())
g.Expect(tt.pk.User).To(Equal(opts.Username))
g.Expect(tt.pk.Signer.PublicKey().Type()).To(Equal("ssh-rsa"))
config, err := tt.ClientConfig()
g.Expect(err).ToNot(HaveOccurred())
g.Expect(config.Config.KeyExchanges).To(Equal(
[]string{"curve25519-sha256", "diffie-hellman-group-exchange-sha256"}),
)
}, },
}, },
{ {
@ -168,11 +189,11 @@ func Test_transportAuth(t *testing.T) {
KnownHosts: []byte(knownHostsFixture), KnownHosts: []byte(knownHostsFixture),
}, },
wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) { wantFunc: func(g *WithT, t transport.AuthMethod, opts *git.AuthOptions) {
tt, ok := t.(*ssh.PublicKeys) tt, ok := t.(*CustomPublicKeys)
g.Expect(ok).To(BeTrue()) g.Expect(ok).To(BeTrue())
g.Expect(tt.User).To(Equal(opts.Username)) g.Expect(tt.pk.User).To(Equal(opts.Username))
g.Expect(tt.Signer.PublicKey().Type()).To(Equal("ssh-rsa")) g.Expect(tt.pk.Signer.PublicKey().Type()).To(Equal("ssh-rsa"))
g.Expect(tt.HostKeyCallback).ToNot(BeNil()) g.Expect(tt.pk.HostKeyCallback).ToNot(BeNil())
}, },
}, },
{ {
@ -202,6 +223,10 @@ func Test_transportAuth(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t) g := NewWithT(t)
if len(tt.kexAlgos) > 0 {
git.KexAlgos = tt.kexAlgos
}
got, err := transportAuth(tt.opts) got, err := transportAuth(tt.opts)
if tt.wantErr != nil { if tt.wantErr != nil {
g.Expect(err).To(Equal(tt.wantErr)) g.Expect(err).To(Equal(tt.wantErr))

View File

@ -58,6 +58,7 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
"github.com/fluxcd/source-controller/pkg/git"
git2go "github.com/libgit2/git2go/v33" git2go "github.com/libgit2/git2go/v33"
) )
@ -344,6 +345,9 @@ func cacheKeyAndConfig(remoteAddress string, cred *git2go.Credential) (string, *
Auth: []ssh.AuthMethod{ssh.PublicKeys(key)}, Auth: []ssh.AuthMethod{ssh.PublicKeys(key)},
Timeout: sshConnectionTimeOut, Timeout: sshConnectionTimeOut,
} }
if len(git.KexAlgos) > 0 {
cfg.Config.KeyExchanges = git.KexAlgos
}
return ck, cfg, nil return ck, cfg, nil
} }

View File

@ -70,6 +70,9 @@ type AuthOptions struct {
CAFile []byte CAFile []byte
} }
// List of custom key exchange algorithms to be used for ssh connections.
var KexAlgos []string
// Validate the AuthOptions against the defined Transport. // Validate the AuthOptions against the defined Transport.
func (o AuthOptions) Validate() error { func (o AuthOptions) Validate() error {
switch o.Transport { switch o.Transport {