woodpecker-config-service/main.go

132 lines
3.2 KiB
Go
Raw Normal View History

2022-02-27 20:18:27 +01:00
package main
import (
2022-06-01 20:38:04 +02:00
"crypto/ed25519"
"crypto/x509"
2022-02-27 20:18:27 +01:00
_ "embed"
"encoding/json"
"encoding/pem"
2022-02-27 20:18:27 +01:00
"io/ioutil"
"log"
"net/http"
"os"
"regexp"
2022-06-01 20:38:04 +02:00
"github.com/go-ap/httpsig"
"github.com/joho/godotenv"
2022-02-27 20:18:27 +01:00
"github.com/woodpecker-ci/woodpecker/server/model"
)
type config struct {
Name string `json:"name"`
Data string `json:"data"`
}
type incoming struct {
Repo *model.Repo `json:"repo"`
Build *model.Build `json:"build"`
Configuration []*config `json:"configs"`
}
//go:embed central-pipeline-config.yml
var overrideConfiguration string
func main() {
log.Println("Woodpecker central config server")
err := godotenv.Load()
if err != nil {
log.Fatalf("Error loading .env file: %v", err)
}
pubKeyPath := os.Getenv("CONFIG_SERVICE_PUBLIC_KEY_FILE") // Key in format of the one fetched from http(s)://your-woodpecker-server/api/signature/public-key
2022-02-27 20:18:27 +01:00
host := os.Getenv("CONFIG_SERVICE_HOST")
filterRegex := os.Getenv("CONFIG_SERVICE_OVERRIDE_FILTER")
2022-06-01 20:38:04 +02:00
if pubKeyPath == "" && host == "" {
log.Fatal("Please make sure CONFIG_SERVICE_HOST and CONFIG_SERVICE_PUBLIC_KEY_FILE are set properly")
2022-02-27 20:18:27 +01:00
}
2022-06-01 20:38:04 +02:00
pubKeyRaw, err := ioutil.ReadFile(pubKeyPath)
if err != nil {
log.Fatal("Failed to read public key file")
}
pemblock, _ := pem.Decode(pubKeyRaw)
b, err := x509.ParsePKIXPublicKey(pemblock.Bytes)
2022-06-01 20:38:04 +02:00
if err != nil {
log.Fatal("Failed to parse public key file ", err)
}
pubKey, ok := b.(ed25519.PublicKey)
if !ok {
log.Fatal("Failed to parse public key file")
2022-06-01 20:38:04 +02:00
}
2022-02-27 20:18:27 +01:00
filter := regexp.MustCompile(filterRegex)
http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
w.WriteHeader(http.StatusMethodNotAllowed)
return
}
2022-06-01 20:38:04 +02:00
// check signature
pubKeyID := "woodpecker-ci-plugins"
keystore := httpsig.NewMemoryKeyStore()
keystore.SetKey(pubKeyID, pubKey)
verifier := httpsig.NewVerifier(keystore)
verifier.SetRequiredHeaders([]string{"(request-target)", "date"})
keyID, err := verifier.Verify(r)
2022-02-27 20:18:27 +01:00
if err != nil {
log.Printf("config: invalid or missing signature in http.Request")
http.Error(w, "Invalid or Missing Signature", http.StatusBadRequest)
return
}
2022-06-01 20:38:04 +02:00
if keyID != pubKeyID {
2022-02-27 20:18:27 +01:00
log.Printf("config: invalid signature in http.Request")
http.Error(w, "Invalid Signature", http.StatusBadRequest)
return
}
var req incoming
body, err := ioutil.ReadAll(r.Body)
if err != nil {
log.Printf("Error reading body: %v", err)
http.Error(w, "can't read body", http.StatusBadRequest)
return
}
err = json.Unmarshal(body, &req)
if err != nil {
http.Error(w, "Failed to parse JSON"+err.Error(), http.StatusBadRequest)
return
}
if filter.MatchString(req.Repo.Name) {
w.WriteHeader(http.StatusOK)
err = json.NewEncoder(w).Encode(map[string]interface{}{"configs": []config{
{
Name: "central pipe",
Data: overrideConfiguration,
},
}})
if err != nil {
log.Printf("Error on encoding json %v\n", err)
}
} else {
w.WriteHeader(http.StatusNoContent) // use default config
// No need to write a response body
}
})
err = http.ListenAndServe(os.Getenv("CONFIG_SERVICE_HOST"), nil)
2022-02-27 20:18:27 +01:00
if err != nil {
log.Fatalf("Error on listen: %v", err)
2022-02-27 20:18:27 +01:00
}
}