From a3081487fd76cb67a9fc2b37771f8f4bf699f21a Mon Sep 17 00:00:00 2001 From: shaned24 Date: Mon, 6 Apr 2020 23:31:30 +0100 Subject: [PATCH] feat(ApiTokenAuth): Add support for using PiHole's api token --- README.md | 27 +++++++++++++++++++++++++++ config/configuration.go | 28 ++++++++++++++++++---------- internal/pihole/client.go | 25 ++++++++++++++++--------- main.go | 6 +++--- 4 files changed, 64 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 0947aff..575d313 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,18 @@ $ docker run \ ekofr/pihole-exporter:latest ``` +Or use PiHole's `WEBPASSWORD` as an API token instead of the password + +```bash +$ API_TOKEN=$(awk -F= -v key="WEBPASSWORD" '$1==key {print $2}' /etc/pihole/setupVars.conf) +$ docker run \ + -e 'PIHOLE_HOSTNAME=192.168.1.2' \ + -e "PIHOLE_APITOKEN=$API_TOKEN" \ + -e 'INTERVAL=30s' \ + -e 'PORT=9617' \ + ekofr/pihole-exporter:latest +``` + ### From sources Optionally, you can download and build it from the sources. You have to retrieve the project sources by using one of the following way: @@ -72,9 +84,20 @@ $ GOOS=linux GOARCH=arm GOARM=7 go build -o pihole_exporter . In order to run the exporter, type the following command (arguments are optional): +Using a password + ```bash $ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_password azerty +``` +Or use PiHole's `WEBPASSWORD` as an API token instead of the password + +```bash +$ API_TOKEN=$(awk -F= -v key="WEBPASSWORD" '$1==key {print $2}' /etc/pihole/setupVars.conf) +$ ./pihole_exporter -pihole_hostname 192.168.1.10 -pihole_apitoken $API_TOKEN +``` + +```bash 2019/05/09 20:19:52 ------------------------------------ 2019/05/09 20:19:52 - PI-Hole exporter configuration - 2019/05/09 20:19:52 ------------------------------------ @@ -125,6 +148,10 @@ scrape_configs: # Password defined on the PI-Hole interface -pihole_password string (optional) + +# WEBPASSWORD / api token defined on the PI-Hole interface at `/etc/pihole/setupVars.conf` + -pihole_apitoken string (optional) + # Port to be used for the exporter -port string (optional) (default "9617") ``` diff --git a/config/configuration.go b/config/configuration.go index 759051b..7675617 100644 --- a/config/configuration.go +++ b/config/configuration.go @@ -15,20 +15,20 @@ import ( // Config is the exporter CLI configuration. type Config struct { - PIHoleHostname string `config:"pihole_hostname"` - PIHolePassword string `config:"pihole_password"` - - Port string `config:"port"` - Interval time.Duration `config:"interval"` + PIHoleHostname string `config:"pihole_hostname"` + PIHolePassword string `config:"pihole_password"` + PIHoleApiToken string `config:"pihole_apitoken"` + Port string `config:"port"` + Interval time.Duration `config:"interval"` } func getDefaultConfig() *Config { return &Config{ PIHoleHostname: "127.0.0.1", PIHolePassword: "", - - Port: "9617", - Interval: 10 * time.Second, + PIHoleApiToken: "", + Port: "9617", + Interval: 10 * time.Second, } } @@ -61,10 +61,18 @@ func (c Config) show() { valueField := val.Field(i) typeField := val.Type().Field(i) - // Do not print password - if typeField.Name != "PIHolePassword" { + // Do not print password or api token but do print the authentication method + if typeField.Name != "PIHolePassword" && typeField.Name != "PIHoleApiToken" { log.Println(fmt.Sprintf("%s : %v", typeField.Name, valueField.Interface())) + } else { + showAuthenticationMethod(typeField.Name, valueField.String()) } } log.Println("------------------------------------") } + +func showAuthenticationMethod(name, value string) { + if len(value) > 0 { + log.Println(fmt.Sprintf("PiHole Authentication Method : %s", name)) + } +} diff --git a/internal/pihole/client.go b/internal/pihole/client.go index 25b6de9..e70ea8f 100644 --- a/internal/pihole/client.go +++ b/internal/pihole/client.go @@ -26,13 +26,15 @@ type Client struct { hostname string password string sessionID string + apiToken string } // NewClient method initializes a new PI-Hole client. -func NewClient(hostname, password string, interval time.Duration) *Client { +func NewClient(hostname, password, apiToken string, interval time.Duration) *Client { return &Client{ hostname: hostname, password: password, + apiToken: apiToken, interval: interval, httpClient: http.Client{ CheckRedirect: func(req *http.Request, via []*http.Request) error { @@ -42,15 +44,12 @@ func NewClient(hostname, password string, interval time.Duration) *Client { } } -// Scrape method logins and retrieves statistics from PI-Hole JSON API +// Scrape method authenticates and retrieves statistics from PI-Hole JSON API // and then pass them as Prometheus metrics. func (c *Client) Scrape() { for range time.Tick(c.interval) { - if c.isAuthenticated() { - c.sessionID = c.getPHPSessionID() - } - stats := c.getStatistics() + c.setMetrics(stats) log.Printf("New tick of statistics: %s", stats.ToString()) @@ -133,12 +132,16 @@ func (c *Client) getStatistics() *Stats { statsURL := fmt.Sprintf(statsURLPattern, c.hostname) + if c.isUsingApiToken() { + statsURL = fmt.Sprintf("%s&auth=%s", statsURL, c.apiToken) + } + req, err := http.NewRequest("GET", statsURL, nil) if err != nil { log.Fatal("An error has occured when creating HTTP statistics request", err) } - if c.isAuthenticated() { + if c.isUsingPassword() { c.authenticateRequest(req) } @@ -160,11 +163,15 @@ func (c *Client) getStatistics() *Stats { return &stats } -func (c *Client) isAuthenticated() bool { +func (c *Client) isUsingPassword() bool { return len(c.password) > 0 } +func (c *Client) isUsingApiToken() bool { + return len(c.apiToken) > 0 +} + func (c *Client) authenticateRequest(req *http.Request) { - cookie := http.Cookie{Name: "PHPSESSID", Value: c.sessionID} + cookie := http.Cookie{Name: "PHPSESSID", Value: c.getPHPSessionID()} req.AddCookie(&cookie) } diff --git a/main.go b/main.go index 89a5b3c..434083d 100644 --- a/main.go +++ b/main.go @@ -26,14 +26,14 @@ func main() { metrics.Init() - initPiholeClient(conf.PIHoleHostname, conf.PIHolePassword, conf.Interval) + initPiHoleClient(conf.PIHoleHostname, conf.PIHolePassword, conf.PIHoleApiToken, conf.Interval) initHttpServer(conf.Port) handleExitSignal() } -func initPiholeClient(hostname, password string, interval time.Duration) { - client := pihole.NewClient(hostname, password, interval) +func initPiHoleClient(hostname, password, apiToken string, interval time.Duration) { + client := pihole.NewClient(hostname, password, apiToken, interval) go client.Scrape() }