Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added macsec feature #261

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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