From 05720915982fae41c8bedb79846b1d94c1d5c52a Mon Sep 17 00:00:00 2001 From: O Seibert Syseleven <36202147+oseiberts11@users.noreply.github.com> Date: Tue, 5 Oct 2021 16:52:11 +0200 Subject: [PATCH] Add login with certificate file. (#14) * Add login with certificate file. * Add certificate example to README. --- README.md | 5 +++++ collector/collector.go | 41 ++++++++++++++++++++++++++++++++++++++++- config/config.go | 5 +++++ ssh_exporter.go | 1 + 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6ca2655..db7c69a 100644 --- a/README.md +++ b/README.md @@ -33,6 +33,10 @@ modules: password: user: prometheus password: secret + certificate: + user: prometheus + private_key: /home/prometheus/.ssh/id_ed25519 + certificate: /home/prometheus/.ssh/id_ed25519-cert.pub verify: user: prometheus private_key: /home/prometheus/.ssh/id_rsa @@ -62,6 +66,7 @@ Configuration options for each module: * `user` - The username for the SSH connection * `password` - The password for the SSH connection, required if `private_key` is not specified * `private_key` - The SSH private key for the SSH connection, required if `password` is not specified +* `certificate` - The SSH certificate for the private key for the SSH connection * `known_hosts` - Optional SSH known hosts file to use to verify hosts * `host_key_algorithms` - Optional list of SSH host key algorithms to use * See constants beginning with `KeyAlgo*` in [crypto/ssh](https://godoc.org/golang.org/x/crypto/ssh#pkg-constants) diff --git a/collector/collector.go b/collector/collector.go index 8850327..c97c154 100644 --- a/collector/collector.go +++ b/collector/collector.go @@ -16,6 +16,7 @@ package collector import ( "bytes" "encoding/base64" + "fmt" "net" "os" "regexp" @@ -100,7 +101,14 @@ func (c *Collector) collect() Metric { var auth ssh.AuthMethod var sessionerror, autherror, commanderror error - if c.target.PrivateKey != "" { + if c.target.Certificate != "" { + auth, autherror = getCertificateAuth(c.target.PrivateKey, c.target.Certificate) + if autherror != nil { + metric.FailureReason = "error" + level.Error(c.logger).Log("msg", "Error setting up certificate auth", "err", autherror) + return metric + } + } else if c.target.PrivateKey != "" { auth, autherror = getPrivateKeyAuth(c.target.PrivateKey) if autherror != nil { metric.FailureReason = "error" @@ -196,6 +204,37 @@ func getPrivateKeyAuth(privatekey string) (ssh.AuthMethod, error) { return ssh.PublicKeys(key), nil } +func getCertificateAuth(privatekey string, certificate string) (ssh.AuthMethod, error) { + key, err := os.ReadFile(privatekey) + if err != nil { + return nil, fmt.Errorf("Unable to read private key: '%s' %v", privatekey, err) + } + + // Create the Signer for this private key. + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + return nil, fmt.Errorf("Unable to parse private key: '%s' %v", privatekey, err) + } + + // Load the certificate + cert, err := os.ReadFile(certificate) + if err != nil { + return nil, fmt.Errorf("Unable to read certificate file: '%s' %v", certificate, err) + } + + pk, _, _, _, err := ssh.ParseAuthorizedKey(cert) + if err != nil { + return nil, fmt.Errorf("Unable to parse public key: '%s' %v", certificate, err) + } + + certSigner, err := ssh.NewCertSigner(pk.(*ssh.Certificate), signer) + if err != nil { + return nil, fmt.Errorf("Unable to create cert signer: %v", err) + } + + return ssh.PublicKeys(certSigner), nil +} + func hostKeyCallback(metric *Metric, target *config.Target, logger log.Logger) ssh.HostKeyCallback { return func(hostname string, remote net.Addr, key ssh.PublicKey) error { var hostKeyCallback ssh.HostKeyCallback diff --git a/config/config.go b/config/config.go index 919baf6..ce54034 100644 --- a/config/config.go +++ b/config/config.go @@ -42,6 +42,7 @@ type Module struct { User string `yaml:"user"` Password string `yaml:"password"` PrivateKey string `yaml:"private_key"` + Certificate string `yaml:"certificate"` KnownHosts string `yaml:"known_hosts"` HostKeyAlgorithms []string `yaml:"host_key_algorithms"` Timeout int `yaml:"timeout"` @@ -56,6 +57,7 @@ type Target struct { User string Password string PrivateKey string + Certificate string KnownHosts string HostKeyAlgorithms []string Timeout int @@ -86,6 +88,9 @@ func (sc *SafeConfig) ReloadConfig(configFile string) error { if module.Password == "" && module.PrivateKey == "" { return fmt.Errorf("Module %s must define 'password' or 'private_key' value", key) } + if module.Certificate != "" && module.PrivateKey == "" { + return fmt.Errorf("Module %s must define 'private_key' if it defines a 'certificate' value", key) + } if module.Timeout == 0 { module.Timeout = *defaultTimeout } diff --git a/ssh_exporter.go b/ssh_exporter.go index 52bad90..138131b 100644 --- a/ssh_exporter.go +++ b/ssh_exporter.go @@ -65,6 +65,7 @@ func metricsHandler(c *config.Config, logger log.Logger) http.HandlerFunc { User: module.User, Password: module.Password, PrivateKey: module.PrivateKey, + Certificate: module.Certificate, KnownHosts: module.KnownHosts, HostKeyAlgorithms: module.HostKeyAlgorithms, Timeout: module.Timeout,