From b113665844f9296a29d7c1c3dc8f2fe39e4db29c Mon Sep 17 00:00:00 2001 From: Antoine Huret Date: Sun, 8 Oct 2023 20:30:13 +0200 Subject: [PATCH] add a new config parameter to decrypt key file with a passphrase + implemented a new feature to get ike connected active users --- collectors.go | 2 + devices.go | 8 +-- internal/config/config.go | 2 + main.go | 1 + pkg/connector/device.go | 4 +- pkg/connector/helper.go | 10 ++- pkg/features/securityike/collector.go | 90 +++++++++++++++++++++++++++ pkg/features/securityike/rpc.go | 36 +++++++++++ 8 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 pkg/features/securityike/collector.go create mode 100644 pkg/features/securityike/rpc.go diff --git a/collectors.go b/collectors.go index 8ef5c2b5..16bd9a8e 100644 --- a/collectors.go +++ b/collectors.go @@ -32,6 +32,7 @@ import ( "github.com/czerwonk/junos_exporter/pkg/features/rpki" "github.com/czerwonk/junos_exporter/pkg/features/rpm" "github.com/czerwonk/junos_exporter/pkg/features/security" + "github.com/czerwonk/junos_exporter/pkg/features/securityike" "github.com/czerwonk/junos_exporter/pkg/features/securitypolicies" "github.com/czerwonk/junos_exporter/pkg/features/storage" "github.com/czerwonk/junos_exporter/pkg/features/subscriber" @@ -105,6 +106,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device) { c.addCollectorIfEnabledForDevice(device, "rpki", f.RPKI, rpki.NewCollector) c.addCollectorIfEnabledForDevice(device, "rpm", f.RPM, rpm.NewCollector) c.addCollectorIfEnabledForDevice(device, "security", f.Security, security.NewCollector) + c.addCollectorIfEnabledForDevice(device, "security_ike", f.SecurityIKE, securityike.NewCollector) c.addCollectorIfEnabledForDevice(device, "security_policies", f.SecurityPolicies, securitypolicies.NewCollector) c.addCollectorIfEnabledForDevice(device, "storage", f.Storage, storage.NewCollector) c.addCollectorIfEnabledForDevice(device, "system", (f.System || f.License), system.NewCollector) diff --git a/devices.go b/devices.go index 592ebcdd..ca6c55c1 100644 --- a/devices.go +++ b/devices.go @@ -75,11 +75,11 @@ func authForDevice(device *config.DeviceConfig, cfg *config.Config) (connector.A } if device.KeyFile != "" { - return authForKeyFile(user, device.KeyFile) + return authForKeyFile(user, device.KeyFile, device.KeyPassphrase) } if *sshKeyFile != "" { - return authForKeyFile(user, *sshKeyFile) + return authForKeyFile(user, *sshKeyFile, *sshKeyPassphrase) } if device.Password != "" { @@ -97,14 +97,14 @@ func authForDevice(device *config.DeviceConfig, cfg *config.Config) (connector.A return nil, errors.New("no valid authentication method available") } -func authForKeyFile(username, keyFile string) (connector.AuthMethod, error) { +func authForKeyFile(username, keyFile, keyPassphrase string) (connector.AuthMethod, error) { f, err := os.Open(keyFile) if err != nil { return nil, errors.Wrap(err, "could not open ssh key file") } defer f.Close() - auth, err := connector.AuthByKey(username, f) + auth, err := connector.AuthByKey(username, f, keyPassphrase) if err != nil { return nil, errors.Wrap(err, "could not load ssh private key file") } diff --git a/internal/config/config.go b/internal/config/config.go index c7bf82f4..d7b30f59 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -25,6 +25,7 @@ type DeviceConfig struct { Username string `yaml:"username,omitempty"` Password string `yaml:"password,omitempty"` KeyFile string `yaml:"key_file,omitempty"` + KeyPassphrase string `yaml:"key_passphrase,omitempty"` Features *FeatureConfig `yaml:"features,omitempty"` IfDescReg string `yaml:"interface_description_regex,omitempty"` IsHostPattern bool `yaml:"host_pattern,omitempty"` @@ -54,6 +55,7 @@ type FeatureConfig struct { Accounting bool `yaml:"accounting,omitempty"` IPSec bool `yaml:"ipsec,omitempty"` Security bool `yaml:"security,omitempty"` + SecurityIKE bool `yaml:"security_ike,omitempty"` SecurityPolicies bool `yaml:"security_policies,omitempty"` FPC bool `yaml:"fpc,omitempty"` RPKI bool `yaml:"rpki,omitempty"` diff --git a/main.go b/main.go index 4e36a5b0..43c8364c 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,7 @@ var ( sshHosts = flag.String("ssh.targets", "", "Hosts to scrape") sshUsername = flag.String("ssh.user", "junos_exporter", "Username to use when connecting to junos devices using ssh") sshKeyFile = flag.String("ssh.keyfile", "", "Public key file to use when connecting to junos devices using ssh") + sshKeyPassphrase = flag.String("ssh.keyPassphrase", "", "Passphrase to decrypt key file if it's encrypted") sshPassword = flag.String("ssh.password", "", "Password to use when connecting to junos devices using ssh") sshReconnectInterval = flag.Duration("ssh.reconnect-interval", 30*time.Second, "Duration to wait before reconnecting to a device after connection got lost") sshKeepAliveInterval = flag.Duration("ssh.keep-alive-interval", 10*time.Second, "Duration to wait between keep alive messages") diff --git a/pkg/connector/device.go b/pkg/connector/device.go index b671b324..aa28416d 100644 --- a/pkg/connector/device.go +++ b/pkg/connector/device.go @@ -26,8 +26,8 @@ func AuthByPassword(username, password string) AuthMethod { } // AuthByKey uses public key authentication -func AuthByKey(username string, key io.Reader) (AuthMethod, error) { - pk, err := loadPrivateKey(key) +func AuthByKey(username string, key io.Reader, keyPassphrase string) (AuthMethod, error) { + pk, err := loadPrivateKey(key, keyPassphrase) if err != nil { return nil, err } diff --git a/pkg/connector/helper.go b/pkg/connector/helper.go index 9786d637..41014b89 100644 --- a/pkg/connector/helper.go +++ b/pkg/connector/helper.go @@ -9,13 +9,19 @@ import ( "golang.org/x/crypto/ssh" ) -func loadPrivateKey(r io.Reader) (ssh.AuthMethod, error) { +func loadPrivateKey(r io.Reader, keyPassphrase string) (ssh.AuthMethod, error) { b, err := io.ReadAll(r) if err != nil { return nil, errors.Wrap(err, "could not read from reader") } - key, err := ssh.ParsePrivateKey(b) + var key ssh.Signer + if keyPassphrase == "" { + key, err = ssh.ParsePrivateKey(b) + } else { + key, err = ssh.ParsePrivateKeyWithPassphrase(b, []byte(keyPassphrase)) + } + if err != nil { return nil, errors.Wrap(err, "could not parse private key") } diff --git a/pkg/features/securityike/collector.go b/pkg/features/securityike/collector.go new file mode 100644 index 00000000..5beba342 --- /dev/null +++ b/pkg/features/securityike/collector.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: MIT + +package securityike + +import ( + "encoding/xml" + "strconv" + "strings" + + "github.com/czerwonk/junos_exporter/pkg/collector" + "github.com/prometheus/client_golang/prometheus" +) + +const prefix string = "junos_security_ike_" + +var ( + connectedActiveUsers *prometheus.Desc +) + +func init() { + l := []string{"target", "re_name"} + + connectedActiveUsers = prometheus.NewDesc(prefix+"connected_active_users", "Number of connected active users", append(l, "remote_address", "remote_port", "ike_id", "x_auth_username", "x_auth_user_assigned_ip"), nil) +} + +type securityIKECollector struct { +} + +// NewCollector creates a new collector +func NewCollector() collector.RPCCollector { + return &securityIKECollector{} +} + +// Name returns the name of the collector +func (*securityIKECollector) Name() string { + return "Security IKE" +} + +// Describe describes the metrics +func (*securityIKECollector) Describe(ch chan<- *prometheus.Desc) { + ch <- connectedActiveUsers +} + +// Collect collects metrics from JunOS +func (c *securityIKECollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error { + var x = multiEngineResult{} + err := client.RunCommandAndParseWithParser("show security ike active-peer", func(b []byte) error { + return parseXML(b, &x) + }) + if err != nil { + return err + } + + for _, re := range x.Results.RoutingEngines { + ls := append(labelValues, re.Name) + activePeersCounters := make(map[string]int) + for _, ap := range re.IKEActivePeersInformation.IKEActivePeers { + saRemotePort := strconv.Itoa(ap.IKESARemotePort) + key := ap.IKESARemoteAddress + saRemotePort + ap.IKEIKEID + ap.IKEXAuthUsername + ap.IKEXAuthUserAssignedIP + if _, exists := activePeersCounters[key]; !exists { + activePeersCounters[key] = 0 + } + activePeersCounters[key] += 1 + ch <- prometheus.MustNewConstMetric(connectedActiveUsers, prometheus.GaugeValue, float64(activePeersCounters[key]), append(ls, ap.IKESARemoteAddress, saRemotePort, ap.IKEIKEID, ap.IKEXAuthUsername, ap.IKEXAuthUserAssignedIP)...) + } + } + + return err +} + +func parseXML(b []byte, res *multiEngineResult) error { + if strings.Contains(string(b), "multi-routing-engine-results") { + return xml.Unmarshal(b, res) + } + + fi := singleEngineResult{} + + err := xml.Unmarshal(b, &fi) + if err != nil { + return err + } + + res.Results.RoutingEngines = []routingEngine{ + { + Name: "N/A", + IKEActivePeersInformation: fi.IKEActivePeersInformation, + }, + } + return nil +} diff --git a/pkg/features/securityike/rpc.go b/pkg/features/securityike/rpc.go new file mode 100644 index 00000000..7631bf7d --- /dev/null +++ b/pkg/features/securityike/rpc.go @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT + +package securityike + +import "encoding/xml" + +type multiEngineResult struct { + XMLName xml.Name `xml:"rpc-reply"` + Results routingEngines `xml:"multi-routing-engine-results"` +} + +type routingEngines struct { + RoutingEngines []routingEngine `xml:"multi-routing-engine-item"` +} + +type routingEngine struct { + Name string `xml:"re-name"` + IKEActivePeersInformation ikeActivePeersInformation `xml:"ike-active-peers-information"` +} + +type ikeActivePeersInformation struct { + IKEActivePeers []ikeActivePeer `xml:"ike-active-peers"` +} + +type ikeActivePeer struct { + IKESARemoteAddress string `xml:"ike-sa-remote-address"` + IKESARemotePort int `xml:"ike-sa-remote-port"` + IKEIKEID string `xml:"ike-ike-id"` + IKEXAuthUsername string `xml:"ike-xauth-username"` + IKEXAuthUserAssignedIP string `xml:"ike-xauth-user-assigned-ip"` +} + +type singleEngineResult struct { + XMLName xml.Name `xml:"rpc-reply"` + IKEActivePeersInformation ikeActivePeersInformation `xml:"ike-active-peers-information"` +}