Skip to content

Commit

Permalink
Add login with certificate file. (#14)
Browse files Browse the repository at this point in the history
* Add login with certificate file.

* Add certificate example to README.
  • Loading branch information
oseiberts11 authored Oct 5, 2021
1 parent a2d473b commit 0572091
Show file tree
Hide file tree
Showing 4 changed files with 51 additions and 1 deletion.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
41 changes: 40 additions & 1 deletion collector/collector.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package collector
import (
"bytes"
"encoding/base64"
"fmt"
"net"
"os"
"regexp"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand All @@ -56,6 +57,7 @@ type Target struct {
User string
Password string
PrivateKey string
Certificate string
KnownHosts string
HostKeyAlgorithms []string
Timeout int
Expand Down Expand Up @@ -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
}
Expand Down
1 change: 1 addition & 0 deletions ssh_exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 0572091

Please sign in to comment.