~bfiedler/go-threema

ee322e978724f217cdd7dcf83691b1ab5993066b — Ben Fiedler 1 year, 10 months ago b67ac09
Complete initial alertmanager hook
4 files changed, 99 insertions(+), 3 deletions(-)

M .gitignore
R cmd/{alertmanager/main.go => am-hook/main.go}
M go.mod
M go.sum
M .gitignore => .gitignore +1 -0
@@ 1,2 1,3 @@
/tcli
/am-hook
*.prof

R cmd/alertmanager/main.go => cmd/am-hook/main.go +96 -3
@@ 1,29 1,122 @@
// An Alertmanager-compatible webhook server, which bridges results to Threema
//
// Due to Threema messages costing credits to send, the implementation limits itself to
// to four requests/day with a maximum burst of 10.
package main

import (
	"encoding/hex"
	"encoding/json"
	"fmt"
	"log"
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/prometheus/alertmanager/template"
	"github.com/prometheus/common/model"
	"github.com/spf13/pflag"
	"golang.org/x/time/rate"

	"git.sr.ht/~bfiedler/go-threema/client"
)

var (
	c client.E2EClient
	c   client.Client
	pk  client.PrivateKey
	lim *rate.Limiter

	id          = pflag.String("id", os.Getenv("THREEMA_CLI_ID"), "Threema ID to use, required. Can also be set using the environment variable THREEMA_CLI_ID")
	apiSecret   = pflag.String("api-secret", os.Getenv("THREEMA_CLI_API_SECRET"), "API secret, required. Can also be set using the environment variable THREEMA_CLI_API_SECRET")
	mode        = pflag.String("mode", os.Getenv("THREEMA_CLI_MODE"), "Mode to use (simple/e2e). Defaults to simple, only relevant for sending messages. Can also be set using the environment variable THREEMA_CLI_MODE")
	privateKey  = pflag.String("private-key", os.Getenv("THREEMA_CLI_PRIVATE_KEY"), "Hex-encoded private key to derive shared keys for sent messages. Required if mode is e2e. Can also be set using the environment variable THREEMA_CLI_PRIVATE_KEY")
	recipientID = pflag.String("recipient-id", os.Getenv("THREEMA_CLI_RECIPIENT_ID"), "Threema ID send messages to, required. Can also be set using the environment variable THREEMA_CLI_RECIPIENT_ID")

	alertRate = rate.Every(24 * time.Hour / 4) // Refill four tokens per day
)

func main() {
	// TODO: read flags
	pflag.Parse()
	checkFlags()

	var err error
	switch *mode {
	case "simple":
		c, err = client.NewSimpleClient(*id, *apiSecret)
	case "e2e":
		c, err = client.NewE2EClient(*id, *apiSecret, pk)
	}
	if err != nil {
		log.Fatalf("could not create client: %v", err)
	}

	lim = rate.NewLimiter(alertRate, 10)
	err = http.ListenAndServe("localhost:8999", http.HandlerFunc(handle))
	if err != nil {
		log.Fatal(err)
	}
}

func checkFlags() {
	fatal := false
	if *id == "" {
		log.Printf("flag id missing, this is required")
		fatal = true
	}

	if *recipientID == "" {
		log.Printf("flag recipientID missing, this is required")
		fatal = true
	}

	if *apiSecret == "" {
		log.Printf("flag apiSecret missing, this is required")
		fatal = true
	}

	if *mode == "" {
		log.Printf("mode missing, defaulting to 'simple'")
		*mode = "simple"
	}

	switch *mode {
	case "simple":
		// intentionally left blank
	case "e2e":
		if *privateKey == "" {
			log.Printf("private key missing, this is requried for mode 'e2e'")
			fatal = true
		} else {
			decodedLen := hex.DecodedLen(len(*privateKey))
			if decodedLen != len(pk) {
				log.Fatalf("private key length error, expected: %v, got: %v", len(pk), decodedLen)
			}

			_, err := hex.Decode(pk[:], []byte(*privateKey))
			if err != nil {
				log.Fatalf("could not decode private key: %v", err)
			}
		}
	default:
		log.Printf("unknown mode, valid modes are 'simple' and 'e2e': %v", *mode)
		fatal = true
	}

	if fatal {
		log.Printf("fatal configuration error")
		os.Exit(1)
	}
}

func handle(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()

	if !lim.Allow() {
		// we have exceeded the number of possible messages
		http.Error(w, "rate limited", http.StatusTooManyRequests)
	}

	d := json.NewDecoder(r.Body)
	var data template.Data
	err := d.Decode(&data)


@@ 53,7 146,7 @@ func handle(w http.ResponseWriter, r *http.Request) {
}

func writeMessage(msg string) {
	err := c.SendTextMessage("TODO: PUT RECIPIENT HERE", msg)
	err := c.SendTextMessage(*recipientID, msg)
	if err != nil {
		log.Printf("error sending message: %v", err)
	}

M go.mod => go.mod +1 -0
@@ 8,4 8,5 @@ require (
	github.com/spf13/pflag v1.0.3
	github.com/stretchr/testify v1.5.1
	golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad
	golang.org/x/time v0.0.0-20191024005414-555d28b269f0
)

M go.sum => go.sum +1 -0
@@ 515,6 515,7 @@ golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXR
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=