aboutsummaryrefslogtreecommitdiff
path: root/main.go
blob: 0190ad1f12ca602cd62c6d87e5df3365d154310f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package main

import (
	"context"
	"fmt"
	"io"
	"log"
	"net/http"
	"strings"

	"github.com/kelseyhightower/envconfig"
	"golang.org/x/sync/errgroup"

	"bnbl.io/csp_exporter/collector"
	"bnbl.io/csp_exporter/csp"
)

type config struct {
	CollectorBindAddr string `envconfig:"COLLECTOR_BIND_ADDR" default:":80"`
	PromBindAddr      string `envconfig:"PROM_BIND_ADDR" default:":9477"`
}

func main() {
	var cfg config
	if err := envconfig.Process("", &cfg); err != nil {
		log.Fatalf("could not read configuration from environment: %v", err)
	}

	coll, err := collector.NewCollector()
	if err != nil {
		log.Fatalf("could not create collector: %v", err)
	}

	cspHandler := http.NewServeMux()
	cspHandler.HandleFunc("/report/csp/", func(w http.ResponseWriter, r *http.Request) {
		if r.Method != "POST" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}
		if r.Header.Get("content-type") != "application/csp-report" {
			w.WriteHeader(http.StatusBadRequest)
			return
		}

		app := strings.TrimPrefix(r.URL.EscapedPath(), "/report/csp/")
		if app == "" {
			w.WriteHeader(http.StatusNotFound)
			return
		}

		report, err := csp.ReadReport(r.Body)
		if err == io.EOF {
			w.WriteHeader(http.StatusBadRequest)
			return
		} else if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			return
		}

		w.WriteHeader(http.StatusOK)
		coll.Collect(app, report)
	})

	g, ctx := errgroup.WithContext(context.Background())

	// start metrics server
	g.Go(func() error {
		h := http.NewServeMux()
		h.Handle("/metrics", coll.Handler())
		srv := &http.Server{Addr: cfg.PromBindAddr, Handler: h}
		go func() {
			<-ctx.Done()
			if err := srv.Shutdown(ctx); err != nil {
				log.Printf("shutdown metrics server: %v", err)
			}
		}()
		return fmt.Errorf("metrics server: %v", srv.ListenAndServe())
	})

	// start CSP collector server
	g.Go(func() error {
		srv := &http.Server{Addr: cfg.CollectorBindAddr, Handler: cspHandler}
		go func() {
			<-ctx.Done()
			if err := srv.Shutdown(ctx); err != nil {
				log.Printf("shutdown collector server: %v", err)
			}
		}()
		return fmt.Errorf("collector server: %v", srv.ListenAndServe())
	})

	if err := g.Wait(); err != nil {
		log.Fatal(err)
	}
}