Skip to content

Commit

Permalink
Merge pull request #229 from ahuret/main
Browse files Browse the repository at this point in the history
key passphrase + feature security-ike
  • Loading branch information
czerwonk committed Oct 12, 2023
2 parents fc2a49a + b113665 commit c774d50
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 8 deletions.
2 changes: 2 additions & 0 deletions collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down
8 changes: 4 additions & 4 deletions devices.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 != "" {
Expand All @@ -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")
}
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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"`
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
4 changes: 2 additions & 2 deletions pkg/connector/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
10 changes: 8 additions & 2 deletions pkg/connector/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
}
Expand Down
90 changes: 90 additions & 0 deletions pkg/features/securityike/collector.go
Original file line number Diff line number Diff line change
@@ -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
}
36 changes: 36 additions & 0 deletions pkg/features/securityike/rpc.go
Original file line number Diff line number Diff line change
@@ -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"`
}

0 comments on commit c774d50

Please sign in to comment.