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

Add TLS and basic authentication in web interface #413

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@ Rabbitmq_exporter can be configured using json config file or environment variab
Rabbitmq_exporter expects config file in "conf/rabbitmq.conf". If you are running the exporter in a container (docker/kubernetes) the config must be in "/conf/rabbitmq.conf"
The name of the file can be overriden with flag:

./rabbitmq_exporter -config-file config.example.json
./rabbitmq_exporter --config-file config.example.json --web.config.file web_config.yml --web.listen-address 127.0.0.1:9419

You can find an example [here](config.example.json). *Note:* If you are using a config file, you must provide all values as there is no default value.

## TLS and basic authentication

The RabbitMQ Exporter supports TLS and basic authentication.

To use TLS and/or basic authentication, you need to pass a configuration file
using the `--web.config.file` parameter. The format of the file is described
[in the exporter-toolkit repository](https://github.com/prometheus/exporter-toolkit/blob/master/docs/web-configuration.md).

### Settings

Environment variable|default|description
Expand All @@ -53,10 +61,8 @@ RABBIT_PASSWORD | guest | password for rabbitMQ management plugin
RABBIT_CONNECTION | direct | direct or loadbalancer, strips the self label when loadbalancer
RABBIT_USER_FILE| | location of file with username (useful for docker secrets)
RABBIT_PASSWORD_FILE | | location of file with password (useful for docker secrets)
PUBLISH_PORT | 9419 | Listening port for the exporter
PUBLISH_ADDR | "" | Listening host/IP for the exporter
OUTPUT_FORMAT | TTY | Log ouput format. TTY and JSON are suported
LOG_LEVEL | info | log level. possible values: "debug", "info", "warning", "error", "fatal", or "panic"
LOG_LEVEL | info | log level. possible case-insensitive values: "debug", "info", "warning", "error"
CAFILE | ca.pem | path to root certificate for access management plugin. Just needed if self signed certificate is used. Will be ignored if the file does not exist
CERTFILE | client-cert.pem | path to client certificate used to verify the exporter's authenticity. Will be ignored if the file does not exist
KEYFILE | client-key.pem | path to private key used with certificate to verify the exporter's authenticity. Will be ignored if the file does not exist
Expand Down
14 changes: 7 additions & 7 deletions bertmap.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"math/big"

bert "github.com/kbudde/gobert"
log "github.com/sirupsen/logrus"
"log/slog"
)

// rabbitBERTReply (along with its RabbitReply interface
Expand All @@ -26,7 +26,7 @@ func (rep *rabbitBERTReply) MakeStatsInfo(labels []string) []StatsInfo {

objects, ok := rawObjects.([]bert.Term)
if !ok {
log.WithField("got", rawObjects).Error("Statistics reply should contain a slice of objects")
slog.Error("Statistics reply should contain a slice of objects", "got", rawObjects)
return make([]StatsInfo, 0)
}

Expand All @@ -35,7 +35,7 @@ func (rep *rabbitBERTReply) MakeStatsInfo(labels []string) []StatsInfo {
for _, v := range objects {
obj, ok := parseSingleStatsObject(v, labels)
if !ok {
log.WithField("got", v).Error("Ignoring unparseable stats object")
slog.Error("Ignoring unparseable stats object", "got", v)
continue
}
statistics = append(statistics, *obj)
Expand All @@ -50,7 +50,7 @@ func (rep *rabbitBERTReply) MakeMap() MetricMap {

err := parseProplist(&flMap, "", term)
if err != nil {
log.WithField("error", err).Warn("Error parsing rabbitmq reply (bert, MakeMap)")
slog.Warn("Error parsing rabbitmq reply (bert, MakeMap)", "error", err)
}
return flMap
}
Expand Down Expand Up @@ -116,7 +116,7 @@ func parseSingleStatsObject(obj interface{}, labels []string) (*StatsInfo, bool)
if key == label {
tmp, ok := parseBertStringy(value)
if !ok {
log.WithField("got", value).WithField("label", label).Error("Non-string field")
slog.Error("Non-string field", "got", value, "label", label)
objectOk = false
return false
}
Expand Down Expand Up @@ -170,7 +170,7 @@ func parseProplist(toMap *MetricMap, basename string, maybeProplist interface{})
}

err := parseProplist(toMap, prefix+key, value) // This can fail, but we don't care
log.WithField("error", err).Debug("Error parsing rabbitmq reply (bert, parseProplist)")
slog.Debug("Error parsing rabbitmq reply (bert, parseProplist)", "error", err)
return true
})
}
Expand Down Expand Up @@ -341,7 +341,7 @@ func (rep *rabbitBERTReply) GetString(label string) (string, bool) {
return true
})
if err != nil {
log.WithField("error", err).Warn("Error parsing rabbitmq reply (bert, GetString)")
slog.Warn("Error parsing rabbitmq reply (bert, GetString)", "error", err)
}
return resValue, result
}
6 changes: 3 additions & 3 deletions bertmap_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package main

import (
"io/ioutil"
"os"
"testing"

"github.com/kylelemons/godebug/pretty"
Expand Down Expand Up @@ -40,13 +40,13 @@ func TestMetricMapEquivalence(t *testing.T) {

func tryReadFiles(t *testing.T, base, firstExt, secondExt string) ([]byte, []byte) {
firstFile := "testdata/" + base + "." + firstExt
first, err := ioutil.ReadFile(firstFile)
first, err := os.ReadFile(firstFile)
if err != nil {
t.Fatalf("Error reading %s", firstFile)
}

secondFile := "testdata/" + base + "." + secondExt
second, err := ioutil.ReadFile(secondFile)
second, err := os.ReadFile(secondFile)
if err != nil {
t.Fatalf("Error reading %s", secondFile)
}
Expand Down
2 changes: 0 additions & 2 deletions config.example.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@
"rabbit_url": "http://127.0.0.1:15672",
"rabbit_user": "guest",
"rabbit_pass": "guest",
"publish_port": "9419",
"publish_addr": "",
"output_format": "TTY",
"ca_file": "ca.pem",
"cert_file": "client-cert.pem",
Expand Down
22 changes: 2 additions & 20 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package main

import (
"fmt"
"io/ioutil"
"os"
"regexp"
"strconv"
Expand All @@ -18,8 +17,6 @@ var (
RabbitUsername: "guest",
RabbitPassword: "guest",
RabbitConnection: "direct",
PublishPort: "9419",
PublishAddr: "",
OutputFormat: "TTY", //JSON
CAFile: "ca.pem",
CertFile: "client-cert.pem",
Expand All @@ -45,8 +42,6 @@ type rabbitExporterConfig struct {
RabbitUsername string `json:"rabbit_user"`
RabbitPassword string `json:"rabbit_pass"`
RabbitConnection string `json:"rabbit_connection"`
PublishPort string `json:"publish_port"`
PublishAddr string `json:"publish_addr"`
OutputFormat string `json:"output_format"`
CAFile string `json:"ca_file"`
CertFile string `json:"cert_file"`
Expand Down Expand Up @@ -131,7 +126,7 @@ func initConfig() {
var pass string

if len(os.Getenv("RABBIT_USER_FILE")) != 0 {
fileContents, err := ioutil.ReadFile(os.Getenv("RABBIT_USER_FILE"))
fileContents, err := os.ReadFile(os.Getenv("RABBIT_USER_FILE"))
if err != nil {
panic(err)
}
Expand All @@ -145,7 +140,7 @@ func initConfig() {
}

if len(os.Getenv("RABBIT_PASSWORD_FILE")) != 0 {
fileContents, err := ioutil.ReadFile(os.Getenv("RABBIT_PASSWORD_FILE"))
fileContents, err := os.ReadFile(os.Getenv("RABBIT_PASSWORD_FILE"))
if err != nil {
panic(err)
}
Expand All @@ -157,19 +152,6 @@ func initConfig() {
config.RabbitPassword = pass
}

if port := os.Getenv("PUBLISH_PORT"); port != "" {
if _, err := strconv.Atoi(port); err == nil {
config.PublishPort = port
} else {
panic(fmt.Errorf("the configured port is not a valid number: %v", port))
}

}

if addr := os.Getenv("PUBLISH_ADDR"); addr != "" {
config.PublishAddr = addr
}

if output := os.Getenv("OUTPUT_FORMAT"); output != "" {
config.OutputFormat = output
}
Expand Down
45 changes: 0 additions & 45 deletions config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,26 +70,6 @@ func TestEnvironmentSettingPasswordFile(t *testing.T) {
}
}

func TestEnvironmentSettingPort(t *testing.T) {
newValue := "9091"
os.Setenv("PUBLISH_PORT", newValue)
defer os.Unsetenv("PUBLISH_PORT")
initConfig()
if config.PublishPort != newValue {
t.Errorf("Expected config.PUBLISH_PORT to be modified. Found=%v, expected=%v", config.PublishPort, newValue)
}
}

func TestEnvironmentSettingAddr(t *testing.T) {
newValue := "localhost"
os.Setenv("PUBLISH_ADDR", newValue)
defer os.Unsetenv("PUBLISH_ADDR")
initConfig()
if config.PublishAddr != newValue {
t.Errorf("Expected config.PUBLISH_ADDR to be modified. Found=%v, expected=%v", config.PublishAddr, newValue)
}
}

func TestEnvironmentSettingFormat(t *testing.T) {
newValue := "json"
os.Setenv("OUTPUT_FORMAT", newValue)
Expand All @@ -100,31 +80,6 @@ func TestEnvironmentSettingFormat(t *testing.T) {
}
}

func TestConfig_Port(t *testing.T) {
defer func() {
if r := recover(); r == nil {
t.Errorf("initConfig should panic on invalid port config")
}
}()
port := config.PublishPort
os.Setenv("PUBLISH_PORT", "noNumber")
defer os.Unsetenv("PUBLISH_PORT")
initConfig()
if config.PublishPort != port {
t.Errorf("Invalid Portnumber. It should not be set. expected=%v,got=%v", port, config.PublishPort)
}
}

func TestConfig_Addr(t *testing.T) {
addr := config.PublishAddr
os.Setenv("PUBLISH_ADDR", "")
defer os.Unsetenv("PUBLISH_ADDR")
initConfig()
if config.PublishAddr != addr {
t.Errorf("Invalid Addrress. It should not be set. expected=%v,got=%v", addr, config.PublishAddr)
}
}

func TestConfig_Http_URL(t *testing.T) {
defer func() {
if r := recover(); r == nil {
Expand Down
10 changes: 6 additions & 4 deletions exporter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import (
"time"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"log/slog"
)

var (
Expand Down Expand Up @@ -99,13 +99,15 @@ func (e *exporter) Collect(ch chan<- prometheus.Metric) {
allUp := true

if err := e.collectWithDuration(e.overviewExporter, "overview", ch); err != nil {
log.WithError(err).Warn("retrieving overview failed")
slog.Warn("retrieving overview failed")
slog.Any("error", err)
allUp = false
}

for name, ex := range e.exporter {
if err := e.collectWithDuration(ex, name, ch); err != nil {
log.WithError(err).Warn("retrieving " + name + " failed")
slog.Warn("retrieving " + name + " failed")
slog.Any("error", err)
allUp = false
}
}
Expand All @@ -125,7 +127,7 @@ func (e *exporter) Collect(ch chan<- prometheus.Metric) {
e.upMetric.Collect(ch)
e.endpointUpMetric.Collect(ch)
e.endpointScrapeDurationMetric.Collect(ch)
log.WithField("duration", time.Since(start)).Info("Metrics updated")
slog.Info("Metrics updated", "duration", time.Since(start))

}

Expand Down
6 changes: 3 additions & 3 deletions exporter_aliveness.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"log/slog"
)

func init() {
Expand Down Expand Up @@ -79,10 +79,10 @@ func (e *exporterAliveness) Collect(ctx context.Context, ch chan<- prometheus.Me
}
rabbitmqAlivenessMetric.WithLabelValues(e.alivenessInfo.Status, e.alivenessInfo.Error, e.alivenessInfo.Reason).Set(flag)

log.WithField("alivenesswData", rabbitMqAlivenessData).Debug("Aliveness data")
slog.Debug("Aliveness data", "alivenesswData", rabbitMqAlivenessData)
for key, gauge := range e.alivenessMetrics {
if value, ok := rabbitMqAlivenessData[key]; ok {
log.WithFields(log.Fields{"key": key, "value": value}).Debug("Set aliveness metric for key")
slog.Debug("Set aliveness metric for key", "key", key, "value", value)
gauge.WithLabelValues(e.alivenessInfo.Status).Set(value)
}
}
Expand Down
1 change: 0 additions & 1 deletion exporter_exchange.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,6 @@ func (e exporterExchange) Collect(ctx context.Context, ch chan<- prometheus.Metr
continue
}
if value, ok := exchange.metrics[key]; ok {
// log.WithFields(log.Fields{"vhost": exchange.vhost, "exchange": exchange.name, "key": key, "value": value}).Debug("Set exchange metric for key")
ch <- prometheus.MustNewConstMetric(countvec, prometheus.CounterValue, value, cluster, exchange.labels["vhost"], exchange.labels["name"])
}
}
Expand Down
6 changes: 3 additions & 3 deletions exporter_overview.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"context"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"log/slog"
)

func init() {
Expand Down Expand Up @@ -94,10 +94,10 @@ func (e *exporterOverview) Collect(ctx context.Context, ch chan<- prometheus.Met
rabbitmqVersionMetric.Reset()
rabbitmqVersionMetric.WithLabelValues(e.nodeInfo.RabbitmqVersion, e.nodeInfo.ErlangVersion, e.nodeInfo.Node, e.nodeInfo.ClusterName).Set(1)

log.WithField("overviewData", rabbitMqOverviewData).Debug("Overview data")
slog.Debug("Overview data", "overviewData", rabbitMqOverviewData)
for key, gauge := range e.overviewMetrics {
if value, ok := rabbitMqOverviewData[key]; ok {
log.WithFields(log.Fields{"key": key, "value": value}).Debug("Set overview metric for key")
slog.Debug("Set overview metric for key", "key", key, "value", value)
gauge.Reset()
gauge.WithLabelValues(e.nodeInfo.ClusterName).Set(value)
}
Expand Down
13 changes: 5 additions & 8 deletions exporter_queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (
"errors"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"
"log/slog"
)

func init() {
Expand Down Expand Up @@ -140,10 +140,7 @@ func (e exporterQueue) Collect(ctx context.Context, ch chan<- prometheus.Metric)
}

if totalQueues > config.MaxQueues {
log.WithFields(log.Fields{
"MaxQueues": config.MaxQueues,
"TotalQueues": totalQueues,
}).Debug("MaxQueues exceeded.")
slog.Debug("MaxQueues exceeded.", "MaxQueues", config.MaxQueues, "TotalQueues", totalQueues)
return nil
}
}
Expand All @@ -162,7 +159,7 @@ func (e exporterQueue) Collect(ctx context.Context, ch chan<- prometheus.Metric)
return err
}

log.WithField("queueData", rabbitMqQueueData).Debug("Queue data")
slog.Debug("Queue data", "queueData", rabbitMqQueueData)
for _, queue := range rabbitMqQueueData {
qname := queue.labels["name"]
vname := queue.labels["vhost"]
Expand All @@ -184,7 +181,6 @@ func (e exporterQueue) Collect(ctx context.Context, ch chan<- prometheus.Metric)

for key, gaugevec := range e.queueMetricsGauge {
if value, ok := queue.metrics[key]; ok {
// log.WithFields(log.Fields{"vhost": queue.labels["vhost"], "queue": queue.labels["name"], "key": key, "value": value}).Info("Set queue metric for key")
gaugevec.WithLabelValues(labelValues...).Set(value)
}
}
Expand All @@ -209,7 +205,8 @@ func (e exporterQueue) Collect(ctx context.Context, ch chan<- prometheus.Metric)
}
e.idleSinceMetric.WithLabelValues(labelValues...).Set(unixSeconds)
} else {
log.WithError(err).WithField("idle_since", idleSince).Warn("error parsing idle since time")
slog.Warn("error parsing idle since time", "idle_since", idleSince)
slog.Any("error", err)
}
}
e.stateMetric.WithLabelValues(append(labelValues, state)...).Set(1)
Expand Down
Loading