Compare commits

...

3 commits

Author SHA1 Message Date
Tomy Guichard
e2089b202c Update chart 2023-06-03 11:21:01 +02:00
Tomy Guichard
8a3aa59272 crash on certificate verification error 2023-06-03 11:12:39 +02:00
Tomy Guichard
8215e950e9 Allow using a custom livebox address 2023-06-02 00:42:35 +02:00
16 changed files with 141 additions and 38 deletions

View file

@ -41,8 +41,10 @@ The exporter accepts the following command-line options:
The exporter reads the following environment variables: The exporter reads the following environment variables:
| Name | Description | Default value | | Name | Description | Default value |
| -------------- | --------------------------------------------------------------------------------------------------------- | ------------- | | --------------- | --------------------------------------------------------------------------------------------------------- | -------------------- |
| ADMIN_PASSWORD | Password of the Livebox "admin" user. The exporter will exit if this environment variable is not defined. | | | ADMIN_PASSWORD | Password of the Livebox "admin" user. The exporter will exit if this environment variable is not defined. | |
| LIVEBOX_ADDRESS | Address of the Livebox. | `http://192.168.1.1` |
| LIVEBOX_CACERT | Optional path to a PEM-encoded CA certificate file on the local disk. | |
### Docker ### Docker

View file

@ -2,5 +2,5 @@ apiVersion: v2
name: livebox-exporter name: livebox-exporter
description: A prometheus exporter for Livebox description: A prometheus exporter for Livebox
type: application type: application
version: 0.2.0 version: 0.3.0
appVersion: "v0.2.0" appVersion: "v0.3.0"

View file

@ -0,0 +1,10 @@
{{- if .Values.livebox.caCert }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "livebox-exporter.fullname" . }}
labels:
{{- include "livebox-exporter.labels" . | nindent 4 }}
data:
ca.crt: {{ toYaml .Values.livebox.caCert | indent 2 }}
{{- end }}

View file

@ -41,13 +41,26 @@ spec:
- name: ADMIN_PASSWORD - name: ADMIN_PASSWORD
valueFrom: valueFrom:
secretKeyRef: secretKeyRef:
{{- if .Values.adminPassword.secretKeyRef }} {{- if .Values.livebox.adminPassword.secretKeyRef }}
name: {{ .Values.adminPassword.secretKeyRef.name}} name: {{ .Values.livebox.adminPassword.secretKeyRef.name}}
key: {{ .Values.adminPassword.secretKeyRef.key }} key: {{ .Values.livebox.adminPassword.secretKeyRef.key }}
{{- else }} {{- else }}
name: {{ include "livebox-exporter.fullname" . }}-admin name: {{ include "livebox-exporter.fullname" . }}-admin
key: password key: password
{{- end }} {{- end }}
{{- with .Values.livebox.address }}
- name: LIVEBOX_ADDRESS
value: {{ . }}
{{- end }}
{{- if .Values.livebox.caCert }}
- name: LIVEBOX_CACERT
value: /etc/livebox/certs/ca.crt
{{- end }}
{{- if .Values.livebox.caCert }}
volumeMounts:
- name: livebox-crt
mountPath: /etc/livebox/certs
{{- end }}
{{- with .Values.nodeSelector }} {{- with .Values.nodeSelector }}
nodeSelector: nodeSelector:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
@ -60,3 +73,9 @@ spec:
tolerations: tolerations:
{{- toYaml . | nindent 8 }} {{- toYaml . | nindent 8 }}
{{- end }} {{- end }}
{{- if .Values.livebox.caCert }}
volumes:
- name: livebox-crt
configMap:
name: {{ include "livebox-exporter.fullname" . }}
{{- end }}

View file

@ -1,4 +1,4 @@
{{- if not .Values.adminPassword.secretKeyRef }} {{- if not .Values.livebox.adminPassword.secretKeyRef }}
apiVersion: v1 apiVersion: v1
kind: Secret kind: Secret
metadata: metadata:
@ -7,5 +7,5 @@ metadata:
{{- include "livebox-exporter.labels" . | nindent 4 }} {{- include "livebox-exporter.labels" . | nindent 4 }}
type: Opaque type: Opaque
data: data:
password: {{ .Values.adminPassword.value | b64enc }} password: {{ .Values.livebox.adminPassword.value | b64enc }}
{{- end }} {{- end }}

View file

@ -1,7 +1,3 @@
# Default values for livebox-exporter.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1 replicaCount: 1
image: image:
@ -10,13 +6,20 @@ image:
# Overrides the image tag whose default is the chart appVersion. # Overrides the image tag whose default is the chart appVersion.
tag: "" tag: ""
adminPassword: livebox:
adminPassword:
secretKeyRef: {} secretKeyRef: {}
# name: "" # name: ""
# key: "" # key: ""
value: "changeme" value: "changeme"
# Address of the Livebox. If empty the exporter will use its own default value.
address: ""
# CA cert of the Livebox.
caCert: ""
imagePullSecrets: [] imagePullSecrets: []
nameOverride: "" nameOverride: ""
fullnameOverride: "" fullnameOverride: ""

2
go.mod
View file

@ -3,7 +3,7 @@ module github.com/Tomy2e/livebox-exporter
go 1.18 go 1.18
require ( require (
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7 github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8
github.com/prometheus/client_golang v1.14.0 github.com/prometheus/client_golang v1.14.0
golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0 golang.org/x/exp v0.0.0-20230315142452-642cacee5cc0
golang.org/x/sync v0.1.0 golang.org/x/sync v0.1.0

4
go.sum
View file

@ -33,8 +33,8 @@ cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7 h1:aX04myQJxWqjP1I1S8jiE8fKyXSu4CHKMBOCUMTwlCI= github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8 h1:rVK1emWX3JW3nNhLdl76M4RJgmMj/hcgQVOJmrh2r9Y=
github.com/Tomy2e/livebox-api-client v0.0.0-20230304114924-a629a6a185e7/go.mod h1:3uvJQHRP5V3BKPTzWCOmUMxZrPbJNl45Wu7ueX9L8QM= github.com/Tomy2e/livebox-api-client v0.0.0-20230524112450-31caca47cbd8/go.mod h1:3uvJQHRP5V3BKPTzWCOmUMxZrPbJNl45Wu7ueX9L8QM=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=

View file

@ -14,7 +14,7 @@ var _ prometheus.Collector = &DeviceInfo{}
// DeviceInfo implements a prometheus Collector that returns Livebox specific metrics. // DeviceInfo implements a prometheus Collector that returns Livebox specific metrics.
type DeviceInfo struct { type DeviceInfo struct {
client livebox.Client client *livebox.Client
numberOfRebootsMetric *prometheus.Desc numberOfRebootsMetric *prometheus.Desc
uptimeMetric *prometheus.Desc uptimeMetric *prometheus.Desc
memoryTotalMetric *prometheus.Desc memoryTotalMetric *prometheus.Desc
@ -22,7 +22,7 @@ type DeviceInfo struct {
} }
// NewDeviceInfo returns a new DeviceInfo collector using the specified client. // NewDeviceInfo returns a new DeviceInfo collector using the specified client.
func NewDeviceInfo(client livebox.Client) *DeviceInfo { func NewDeviceInfo(client *livebox.Client) *DeviceInfo {
return &DeviceInfo{ return &DeviceInfo{
client: client, client: client,
numberOfRebootsMetric: prometheus.NewDesc( numberOfRebootsMetric: prometheus.NewDesc(

View file

@ -13,12 +13,12 @@ var _ Poller = &DevicesTotal{}
// DevicesTotal allows to poll the total number of active devices. // DevicesTotal allows to poll the total number of active devices.
type DevicesTotal struct { type DevicesTotal struct {
client livebox.Client client *livebox.Client
devicesTotal *prometheus.GaugeVec devicesTotal *prometheus.GaugeVec
} }
// NewDevicesTotal returns a new DevicesTotal poller. // NewDevicesTotal returns a new DevicesTotal poller.
func NewDevicesTotal(client livebox.Client) *DevicesTotal { func NewDevicesTotal(client *livebox.Client) *DevicesTotal {
return &DevicesTotal{ return &DevicesTotal{
client: client, client: client,
devicesTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{ devicesTotal: prometheus.NewGaugeVec(prometheus.GaugeOpts{

View file

@ -15,12 +15,12 @@ var _ Poller = &InterfaceMbits{}
// InterfaceMbits allows to poll the current bandwidth usage on the Livebox // InterfaceMbits allows to poll the current bandwidth usage on the Livebox
// interfaces. // interfaces.
type InterfaceMbits struct { type InterfaceMbits struct {
client livebox.Client client *livebox.Client
txMbits, rxMbits *prometheus.GaugeVec txMbits, rxMbits *prometheus.GaugeVec
} }
// NewInterfaceMbits returns a new InterfaceMbits poller. // NewInterfaceMbits returns a new InterfaceMbits poller.
func NewInterfaceMbits(client livebox.Client) *InterfaceMbits { func NewInterfaceMbits(client *livebox.Client) *InterfaceMbits {
return &InterfaceMbits{ return &InterfaceMbits{
client: client, client: client,
txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{ txMbits: prometheus.NewGaugeVec(prometheus.GaugeOpts{

View file

@ -22,14 +22,14 @@ var _ Poller = &InterfaceHomeLanMbits{}
// InterfaceHomeLanMbits is an experimental poller to get the current bandwidth // InterfaceHomeLanMbits is an experimental poller to get the current bandwidth
// usage on the Livebox interfaces. // usage on the Livebox interfaces.
type InterfaceHomeLanMbits struct { type InterfaceHomeLanMbits struct {
client livebox.Client client *livebox.Client
interfaces []*exporterLivebox.Interface interfaces []*exporterLivebox.Interface
bitrate *bitrate.Bitrate bitrate *bitrate.Bitrate
txMbits, rxMbits *prometheus.GaugeVec txMbits, rxMbits *prometheus.GaugeVec
} }
// NewInterfaceHomeLanMbits returns a new InterfaceMbits poller. // NewInterfaceHomeLanMbits returns a new InterfaceMbits poller.
func NewInterfaceHomeLanMbits(client livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceHomeLanMbits { func NewInterfaceHomeLanMbits(client *livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceHomeLanMbits {
return &InterfaceHomeLanMbits{ return &InterfaceHomeLanMbits{
client: client, client: client,
interfaces: interfaces, interfaces: interfaces,

View file

@ -16,14 +16,14 @@ var _ Poller = &InterfaceNetDevMbits{}
// InterfaceNetDevMbits is an experimental poller to get the current bandwidth // InterfaceNetDevMbits is an experimental poller to get the current bandwidth
// usage on the Livebox interfaces. // usage on the Livebox interfaces.
type InterfaceNetDevMbits struct { type InterfaceNetDevMbits struct {
client livebox.Client client *livebox.Client
interfaces []*exporterLivebox.Interface interfaces []*exporterLivebox.Interface
bitrate *bitrate.Bitrate bitrate *bitrate.Bitrate
txMbits, rxMbits *prometheus.GaugeVec txMbits, rxMbits *prometheus.GaugeVec
} }
// NewInterfaceNetDevMbits returns a new InterfaceNetDevMbits poller. // NewInterfaceNetDevMbits returns a new InterfaceNetDevMbits poller.
func NewInterfaceNetDevMbits(client livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceNetDevMbits { func NewInterfaceNetDevMbits(client *livebox.Client, interfaces []*exporterLivebox.Interface) *InterfaceNetDevMbits {
return &InterfaceNetDevMbits{ return &InterfaceNetDevMbits{
client: client, client: client,
interfaces: interfaces, interfaces: interfaces,

View file

@ -14,13 +14,13 @@ var _ Poller = &WANMbits{}
// WANMbits is an experimental poller to get the current bandwidth usage on the // WANMbits is an experimental poller to get the current bandwidth usage on the
// WAN interface of the Livebox. // WAN interface of the Livebox.
type WANMbits struct { type WANMbits struct {
client livebox.Client client *livebox.Client
bitrate *bitrate.Bitrate bitrate *bitrate.Bitrate
txMbits, rxMbits prometheus.Gauge txMbits, rxMbits prometheus.Gauge
} }
// NewWANMbits returns a new WANMbits poller. // NewWANMbits returns a new WANMbits poller.
func NewWANMbits(client livebox.Client) *WANMbits { func NewWANMbits(client *livebox.Client) *WANMbits {
return &WANMbits{ return &WANMbits{
client: client, client: client,
bitrate: bitrate.New(InterfaceHomeLanMbitsMinDelay), bitrate: bitrate.New(InterfaceHomeLanMbitsMinDelay),

75
main.go
View file

@ -2,9 +2,12 @@ package main
import ( import (
"context" "context"
"crypto/tls"
"crypto/x509"
"errors" "errors"
"flag" "flag"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"net/http" "net/http"
"os" "os"
@ -37,10 +40,14 @@ var experimentalMetrics = []string{
func parseExperimentalFlag( func parseExperimentalFlag(
ctx context.Context, ctx context.Context,
client livebox.Client, client *livebox.Client,
experimental string, experimental string,
pollingFrequency *uint, pollingFrequency *uint,
) (pollers []poller.Poller) { ) (pollers []poller.Poller) {
if experimental == "" {
return nil
}
var ( var (
interfaces []*exporterLivebox.Interface interfaces []*exporterLivebox.Interface
err error err error
@ -96,6 +103,50 @@ func parseExperimentalFlag(
return return
} }
func getHTTPClient() (*http.Client, error) {
liveboxCACertPath := os.Getenv("LIVEBOX_CACERT")
if liveboxCACertPath == "" {
return http.DefaultClient, nil
}
// Get the SystemCertPool, continue with an empty pool on error.
rootCAs, _ := x509.SystemCertPool()
if rootCAs == nil {
rootCAs = x509.NewCertPool()
}
certs, err := ioutil.ReadFile(liveboxCACertPath)
if err != nil {
return nil, fmt.Errorf("failed to read livebox CA cert: %w", err)
}
if ok := rootCAs.AppendCertsFromPEM(certs); !ok {
return nil, errors.New("no livebox CA cert was successfully added")
}
return &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: rootCAs,
},
},
}, nil
}
func isFatalError(err error) bool {
if errors.Is(err, livebox.ErrInvalidPassword) {
return true
}
var certError *tls.CertificateVerificationError
if errors.As(err, &certError) {
return true
}
return false
}
func main() { func main() {
pollingFrequency := flag.Uint("polling-frequency", defaultPollingFrequency, "Polling frequency") pollingFrequency := flag.Uint("polling-frequency", defaultPollingFrequency, "Polling frequency")
listen := flag.String("listen", ":8080", "Listening address") listen := flag.String("listen", ":8080", "Listening address")
@ -110,10 +161,28 @@ func main() {
log.Fatal("ADMIN_PASSWORD environment variable must be set") log.Fatal("ADMIN_PASSWORD environment variable must be set")
} }
liveboxAddress := os.Getenv("LIVEBOX_ADDRESS")
if liveboxAddress == "" {
liveboxAddress = livebox.DefaultAddress
}
httpClient, err := getHTTPClient()
if err != nil {
log.Fatal(err)
}
client, err := livebox.NewClient(
adminPassword,
livebox.WithAddress(liveboxAddress),
livebox.WithHTTPClient(httpClient),
)
if err != nil {
log.Fatalf("Failed to create Livebox client: %v", err)
}
var ( var (
ctx = context.Background() ctx = context.Background()
registry = prometheus.NewRegistry() registry = prometheus.NewRegistry()
client = livebox.NewClient(adminPassword)
pollers = poller.Pollers{ pollers = poller.Pollers{
poller.NewDevicesTotal(client), poller.NewDevicesTotal(client),
poller.NewInterfaceMbits(client), poller.NewInterfaceMbits(client),
@ -136,7 +205,7 @@ func main() {
go func() { go func() {
for { for {
if err := pollers.Poll(ctx); err != nil { if err := pollers.Poll(ctx); err != nil {
if errors.Is(err, livebox.ErrInvalidPassword) { if isFatalError(err) {
log.Fatal(err) log.Fatal(err)
} }

View file

@ -27,7 +27,7 @@ func (i *Interface) IsWLAN() bool {
} }
// DiscoverInterfaces discovers network interfaces on the Livebox. // DiscoverInterfaces discovers network interfaces on the Livebox.
func DiscoverInterfaces(ctx context.Context, client livebox.Client) ([]*Interface, error) { func DiscoverInterfaces(ctx context.Context, client *livebox.Client) ([]*Interface, error) {
var mibs struct { var mibs struct {
Status struct { Status struct {
Base map[string]struct { Base map[string]struct {