Skip to content

Commit

Permalink
Added macsec feature (#261)
Browse files Browse the repository at this point in the history
* added a feature for macsec

* small refactoring of some functions

* small fixes as per pull request

---------

Co-authored-by: Vincent Vilenchik <vincent.vilenchik@deepl.com>
  • Loading branch information
surprise30 and Vincent Vilenchik authored Nov 11, 2024
1 parent 972c00e commit 2802bce
Show file tree
Hide file tree
Showing 6 changed files with 575 additions and 0 deletions.
2 changes: 2 additions & 0 deletions collectors.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"github.com/czerwonk/junos_exporter/pkg/features/lacp"
"github.com/czerwonk/junos_exporter/pkg/features/ldp"
"github.com/czerwonk/junos_exporter/pkg/features/mac"
"github.com/czerwonk/junos_exporter/pkg/features/macsec"
"github.com/czerwonk/junos_exporter/pkg/features/mplslsp"
"github.com/czerwonk/junos_exporter/pkg/features/nat"
"github.com/czerwonk/junos_exporter/pkg/features/nat2"
Expand Down Expand Up @@ -117,6 +118,7 @@ func (c *collectors) initCollectorsForDevices(device *connector.Device, descRe *
c.addCollectorIfEnabledForDevice(device, "vpws", f.VPWS, vpws.NewCollector)
c.addCollectorIfEnabledForDevice(device, "mpls_lsp", f.MPLSLSP, mplslsp.NewCollector)
c.addCollectorIfEnabledForDevice(device, "subscriber", f.Subscriber, subscriber.NewCollector)
c.addCollectorIfEnabledForDevice(device, "macsec", f.MACSec, macsec.NewCollector)
}

func (c *collectors) addCollectorIfEnabledForDevice(device *connector.Device, key string, enabled bool, newCollector func() collector.RPCCollector) {
Expand Down
2 changes: 2 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ type FeatureConfig struct {
VRRP bool `yaml:"vrrp,omitempty"`
License bool `yaml:"license,omitempty"`
Subscriber bool `yaml:"subscriber,omitempty"`
MACSec bool `yaml:"macsec,omitempty"`
}

// New creates a new config
Expand Down Expand Up @@ -174,6 +175,7 @@ func setDefaultValues(c *Config) {
f.VRRP = false
f.BFD = false
f.License = false
f.MACSec = true
}

// FeaturesForDevice gets the feature set configured for a device
Expand Down
2 changes: 2 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ var (
tracingProvider = flag.String("tracing.provider", "", "Sets the tracing provider (stdout or collector)")
tracingCollectorEndpoint = flag.String("tracing.collector.grpc-endpoint", "", "Sets the tracing provider (stdout or collector)")
subscriberEnabled = flag.Bool("subscriber.enabled", false, "Scrape subscribers detail")
macsecEnabled = flag.Bool("macsec.enabled", true, "Scrape MACSec metrics")
cfg *config.Config
devices []*connector.Device
connManager *connector.SSHConnectionManager
Expand Down Expand Up @@ -249,6 +250,7 @@ func loadConfigFromFlags() *config.Config {
f.MPLSLSP = *mplsLSPEnabled
f.License = *licenseEnabled
f.Subscriber = *subscriberEnabled
f.MACSec = *macsecEnabled
return c
}

Expand Down
189 changes: 189 additions & 0 deletions pkg/features/macsec/collector.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
// This plugin for MACsec collects metrics from the command "show security macsec connections".
package macsec

import (
"github.com/czerwonk/junos_exporter/pkg/collector"
"github.com/pkg/errors"
"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"strconv"
"strings"
)

const prefix string = "junos_macsec_"

// Metrics to collect for the feature
var (
macsecTXPacketCountDesc *prometheus.Desc
macsecTXChannelStatusDesc *prometheus.Desc
macsecIncludeSCIDesc *prometheus.Desc
macsecReplayProtectDesc *prometheus.Desc
macsecKeyServerOffsetDesc *prometheus.Desc
macsecEncryptionDesc *prometheus.Desc
macsecSecureChannelTXEncryptedPacketsDesc *prometheus.Desc
macsecSecureChannelTXEncryptedBytessDesc *prometheus.Desc
macsecSecureChannelTXProtectedPacketsDesc *prometheus.Desc
macsecSecureChannelTXProtectedBytesDesc *prometheus.Desc
macsecSecureAssociationTXEncryptedPacketsDesc *prometheus.Desc
macsecSecureAssociationTXProtectedPacketsDesc *prometheus.Desc
macsecSecureChannelRXAcceptedPacketsDesc *prometheus.Desc
macsecSecureChannelRXValidatedBytesDesc *prometheus.Desc
macsecSecureChannelRXDecryptedBytesDesc *prometheus.Desc
macsecSecureAssociationRXAcceptedPacketsDesc *prometheus.Desc
macsecSecureAssociationRXValidatedBytesDesc *prometheus.Desc
macsecSecureAssociationRXDecryptedBytesDesc *prometheus.Desc
)

// Initialize metrics descriptions
func init() {
labelsInterface := []string{"target", "interface", "ca"}
labelsStats := []string{"target", "interface"}
macsecTXPacketCountDesc = prometheus.NewDesc(prefix+"interface_transmit_packet_count", "Information regarding transmitted packets by interface", labelsInterface, nil)
macsecTXChannelStatusDesc = prometheus.NewDesc(prefix+"tx_channel_status", "Information regarding the status of outbound channel secure association. 1 for inuse", labelsInterface, nil)
macsecIncludeSCIDesc = prometheus.NewDesc(prefix+"sci", "Information regarding if sci is included in the interface. 0 for not included, 1 for included, 2 for unknown", labelsInterface, nil)
macsecReplayProtectDesc = prometheus.NewDesc(prefix+"replay_protect", "Information if replay protect is on or off. 0 for off, 1 for on, 2 for unknown", labelsInterface, nil)
macsecKeyServerOffsetDesc = prometheus.NewDesc(prefix+"key_server_offset", "Information regarding key server offset", labelsInterface, nil)
macsecEncryptionDesc = prometheus.NewDesc(prefix+"encryption", "Information regarding encryption. 0 for off, 1 for on, 2 for unknown", labelsInterface, nil)
macsecSecureChannelTXEncryptedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_channel_tx_encrypted_packets_count", "Amount of secure channel sent encrypted packets", labelsStats, nil)
macsecSecureChannelTXEncryptedBytessDesc = prometheus.NewDesc(prefix+"statistics_secure_channel_tx_encrypted_bytes_count", "Amount of secure channel sent encrypted bytes", labelsStats, nil)
macsecSecureChannelTXProtectedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_channel_tx_protected_packets_count", "Amount of secure channel sent protected packets", labelsStats, nil)
macsecSecureChannelTXProtectedBytesDesc = prometheus.NewDesc(prefix+"statistics_secure_channel_tx_protected_bytes_count", "Amount of secure channel sent protected bytes", labelsStats, nil)
macsecSecureAssociationTXEncryptedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_association_tx_encrypted_packets_count", "Amount of secure association sent encrypted packets", labelsStats, nil)
macsecSecureAssociationTXProtectedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_association_tx_protected_packets_count", "Amount of secure association sent protected packets", labelsStats, nil)
macsecSecureChannelRXAcceptedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_channel_rx_accepted_packets_count", "Amount of secure channel received accepted packets", labelsStats, nil)
macsecSecureChannelRXValidatedBytesDesc = prometheus.NewDesc(prefix+"secure_channel_rx_validated_bytes_count", "Amount of secure channel received validated bytes", labelsStats, nil)
macsecSecureChannelRXDecryptedBytesDesc = prometheus.NewDesc(prefix+"secure_channel_rx_decrypted_bytes_count", "Amount of secure channel received decrypted bytes", labelsStats, nil)
macsecSecureAssociationRXAcceptedPacketsDesc = prometheus.NewDesc(prefix+"statistics_secure_association_rx_accepted_packets_count", "Amount of secure association received accepted packets", labelsStats, nil)
macsecSecureAssociationRXValidatedBytesDesc = prometheus.NewDesc(prefix+"statistics_secure_association_rx_validated_bytes_count", "Amount of secure association received validated bytes", labelsStats, nil)
macsecSecureAssociationRXDecryptedBytesDesc = prometheus.NewDesc(prefix+"statistics_secure_association_rx_decrypted_bytes_count", "Amount of secure association received decrypted bytes", labelsStats, nil)
}

// macsecCollector collects MACsec metrics
type macsecCollector struct{}

// NewCollector creates a new collector
func NewCollector() collector.RPCCollector {
return &macsecCollector{}
}

// Name returns the name of the collector
func (*macsecCollector) Name() string {
return "MACsec"
}

// Describe describes the metrics
func (*macsecCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- macsecTXPacketCountDesc
ch <- macsecTXChannelStatusDesc
ch <- macsecIncludeSCIDesc
ch <- macsecReplayProtectDesc
ch <- macsecKeyServerOffsetDesc
ch <- macsecEncryptionDesc
ch <- macsecSecureChannelTXEncryptedPacketsDesc
ch <- macsecSecureChannelTXEncryptedBytessDesc
ch <- macsecSecureChannelTXProtectedPacketsDesc
ch <- macsecSecureChannelTXProtectedBytesDesc
ch <- macsecSecureAssociationTXEncryptedPacketsDesc
ch <- macsecSecureAssociationTXProtectedPacketsDesc
ch <- macsecSecureChannelRXAcceptedPacketsDesc
ch <- macsecSecureChannelRXValidatedBytesDesc
ch <- macsecSecureChannelRXDecryptedBytesDesc
ch <- macsecSecureAssociationRXAcceptedPacketsDesc
ch <- macsecSecureAssociationRXValidatedBytesDesc
ch <- macsecSecureAssociationRXDecryptedBytesDesc
}

// Collect collects metrics from JunOS
func (c *macsecCollector) Collect(client collector.Client, ch chan<- prometheus.Metric, labelValues []string) error {
var i resultInt
err := client.RunCommandAndParse("show security macsec connections", &i)
if err != nil {
return errors.Wrap(err, "failed to run command 'show security macsec connections'")
}
c.collectForInterfaces(i, ch, labelValues)
var s resultStats
err = client.RunCommandAndParse("show security macsec statistics", &s)
if err != nil {
return errors.Wrap(err, "failed to run command 'show security macsec statistics'")
}
c.collectForStats(s, ch, labelValues)
return nil
}

// collectForSessions collects metrics for the sessions
func (c *macsecCollector) collectForInterfaces(sessions resultInt, ch chan<- prometheus.Metric, labelValues []string) {
for c, mici := range sessions.MacsecConnectionInformation.MacsecInterfaceCommonInformation {
labels := append(labelValues,
mici.InterfaceName,
mici.ConnectivityAssociationName)
pn, err := strconv.Atoi(sessions.MacsecConnectionInformation.OutboundSecureChannel[c].OutgoingPacketNumber)
if err != nil {
log.Errorf("unable to convert outgoing packets number: %q", sessions.MacsecConnectionInformation.OutboundSecureChannel[c].OutgoingPacketNumber)
}
sci := convertYesNoToInt(strings.TrimRight(mici.IncludeSci, "\n"))
rp := convertOnOffToInt(strings.TrimRight(mici.ReplayProtect, "\n"))
kso, err := strconv.Atoi(mici.Offset)
if err != nil {
log.Errorf("unable to convert offset: %q", mici.Offset)
}
status := stateToFloat(sessions.MacsecConnectionInformation.OutboundSecureChannel[c].OutboundSecureAssociation.AssociationNumberStatus)
enc := convertOnOffToInt(strings.TrimRight(mici.Encryption, "\n"))
ch <- prometheus.MustNewConstMetric(macsecTXPacketCountDesc, prometheus.CounterValue, float64(pn), labels...)
ch <- prometheus.MustNewConstMetric(macsecIncludeSCIDesc, prometheus.GaugeValue, float64(sci), labels...)
ch <- prometheus.MustNewConstMetric(macsecReplayProtectDesc, prometheus.GaugeValue, float64(rp), labels...)
ch <- prometheus.MustNewConstMetric(macsecKeyServerOffsetDesc, prometheus.GaugeValue, float64(kso), labels...)
ch <- prometheus.MustNewConstMetric(macsecEncryptionDesc, prometheus.GaugeValue, float64(enc), labels...)
ch <- prometheus.MustNewConstMetric(macsecTXChannelStatusDesc, prometheus.GaugeValue, status, labels...)
}
}

func (c *macsecCollector) collectForStats(sessions resultStats, ch chan<- prometheus.Metric, labelValues []string) {
for interfaceCounter := 0; interfaceCounter < (len(sessions.MacsecStatistics.Interfaces)); interfaceCounter++ {
labels := append(labelValues,
sessions.MacsecStatistics.Interfaces[interfaceCounter])
ch <- prometheus.MustNewConstMetric(macsecSecureChannelTXEncryptedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelSent[interfaceCounter].EncryptedPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelTXEncryptedBytessDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelSent[interfaceCounter].EncryptedBytes), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelTXProtectedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelSent[interfaceCounter].ProtectedPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelTXProtectedBytesDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelSent[interfaceCounter].ProtectedBytes), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureAssociationTXEncryptedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureAssociationSent[interfaceCounter].EncryptedPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureAssociationTXProtectedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureAssociationSent[interfaceCounter].ProtectedPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelRXAcceptedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelReceived[interfaceCounter].OkPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelRXValidatedBytesDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelReceived[interfaceCounter].ValidatedBytes), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureChannelRXDecryptedBytesDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureChannelReceived[interfaceCounter].DecryptedBytes), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureAssociationRXAcceptedPacketsDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureAssociationReceived[interfaceCounter].OkPackets), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureAssociationRXValidatedBytesDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureAssociationReceived[interfaceCounter].ValidatedBytes), labels...)
ch <- prometheus.MustNewConstMetric(macsecSecureAssociationRXDecryptedBytesDesc, prometheus.CounterValue, float64(sessions.MacsecStatistics.SecureAssociationReceived[interfaceCounter].DecryptedBytes), labels...)
}
}

// stateToFloat converts the status string to a float value
func stateToFloat(status string) float64 {
if strings.TrimRight(status, "\n") == "inuse" {
return 1
}
return 0
}

// convertYesNoToInt returns 0, 1 or 2 depending on the input string value
func convertYesNoToInt(s string) int {
switch s {
case "no":
return 0
case "yes":
return 1
default:
return 2
}
}

// convertOnOffToInt returns 0, 1 or 2 depending on the input string value
func convertOnOffToInt(s string) int {
switch s {
case "off":
return 0
case "on":
return 1
default:
return 2
}
}
103 changes: 103 additions & 0 deletions pkg/features/macsec/rpc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package macsec

import (
"encoding/xml"
)

type resultInt struct {
XMLName xml.Name `xml:"rpc-reply"`
Text string `xml:",chardata"`
Junos string `xml:"junos,attr"`
MacsecConnectionInformation struct {
Text string `xml:",chardata"`
MacsecInterfaceCommonInformation []struct {
Text string `xml:",chardata"`
InterfaceName string `xml:"interface-name"`
ConnectivityAssociationName string `xml:"connectivity-association-name"`
CipherSuite string `xml:"cipher-suite"`
Encryption string `xml:"encryption"`
Offset string `xml:"offset"`
IncludeSci string `xml:"include-sci"`
ReplayProtect string `xml:"replay-protect"`
ReplayProtectWindow string `xml:"replay-protect-window"`
} `xml:"macsec-interface-common-information"`
CreateTime []struct {
Text string `xml:",chardata"`
Seconds string `xml:"seconds,attr"`
} `xml:"create-time"`
OutboundSecureChannel []struct {
Text string `xml:",chardata"`
Sci string `xml:"sci"`
OutgoingPacketNumber string `xml:"outgoing-packet-number"`
OutboundSecureAssociation struct {
Text string `xml:",chardata"`
AssociationNumber string `xml:"association-number"`
AssociationNumberStatus string `xml:"association-number-status"`
CreateTime struct {
Text string `xml:",chardata"`
Seconds string `xml:"seconds,attr"`
} `xml:"create-time"`
} `xml:"outbound-secure-association"`
} `xml:"outbound-secure-channel"`
InboundSecureChannel []struct {
Text string `xml:",chardata"`
Sci string `xml:"sci"`
InboundSecureAssociation struct {
Text string `xml:",chardata"`
AssociationNumber string `xml:"association-number"`
AssociationNumberStatus string `xml:"association-number-status"`
CreateTime struct {
Text string `xml:",chardata"`
Seconds string `xml:"seconds,attr"`
} `xml:"create-time"`
} `xml:"inbound-secure-association"`
} `xml:"inbound-secure-channel"`
} `xml:"macsec-connection-information"`
Cli struct {
Text string `xml:",chardata"`
Banner string `xml:"banner"`
} `xml:"cli"`
}

// structure for the statistics reply
type resultStats struct {
XMLName xml.Name `xml:"rpc-reply"`
MacsecStatistics MacsecStatistics `xml:"macsec-statistics"`
}

// Struct for macsec statistics
type MacsecStatistics struct {
Interfaces []string `xml:"interface-name"`
SecureChannelSent []SecureChannelSentStats `xml:"secure-channel-sent"`
SecureAssociationSent []SecureAssociationSentStats `xml:"secure-association-sent"`
SecureChannelReceived []SecureChannelReceivedStats `xml:"secure-channel-received"`
SecureAssociationReceived []SecureAssociationReceivedStats `xml:"secure-association-received"`
}

// Struct for secure channel sent statistics
type SecureChannelSentStats struct {
EncryptedPackets uint64 `xml:"encrypted-packets"`
EncryptedBytes uint64 `xml:"encrypted-bytes"`
ProtectedPackets uint64 `xml:"protected-packets"`
ProtectedBytes uint64 `xml:"protected-bytes"`
}

// Struct for secure association sent statistics
type SecureAssociationSentStats struct {
EncryptedPackets uint64 `xml:"encrypted-packets"`
ProtectedPackets uint64 `xml:"protected-packets"`
}

// Struct for secure channel received statistics
type SecureChannelReceivedStats struct {
OkPackets uint64 `xml:"ok-packets"`
ValidatedBytes uint64 `xml:"validated-bytes"`
DecryptedBytes uint64 `xml:"decrypted-bytes"`
}

// Struct for secure association received statistics
type SecureAssociationReceivedStats struct {
OkPackets uint64 `xml:"ok-packets"`
ValidatedBytes uint64 `xml:"validated-bytes"`
DecryptedBytes uint64 `xml:"decrypted-bytes"`
}
Loading

0 comments on commit 2802bce

Please sign in to comment.