149 lines
3.8 KiB
Go
149 lines
3.8 KiB
Go
package main
|
|
|
|
import (
|
|
"crypto/ed25519"
|
|
"crypto/x509"
|
|
_ "embed"
|
|
"encoding/json"
|
|
"encoding/pem"
|
|
"io/ioutil"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
|
|
"github.com/go-ap/httpsig"
|
|
"github.com/woodpecker-ci/woodpecker/server/model"
|
|
"gopkg.in/yaml.v3"
|
|
)
|
|
|
|
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"`
|
|
}
|
|
|
|
type pipeline struct {
|
|
Extend string `yaml:"extend"`
|
|
}
|
|
|
|
func main() {
|
|
log.Println("Woodpecker central config server")
|
|
|
|
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
|
|
|
|
if pubKeyPath == "" {
|
|
log.Fatal("Please make sure CONFIG_SERVICE_PUBLIC_KEY_FILE is set properly")
|
|
}
|
|
|
|
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)
|
|
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")
|
|
}
|
|
|
|
http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
w.WriteHeader(http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
|
|
// 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)
|
|
if err != nil {
|
|
log.Printf("config: invalid or missing signature in http.Request")
|
|
http.Error(w, "Invalid or Missing Signature", http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
if keyID != pubKeyID {
|
|
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
|
|
}
|
|
|
|
change := false
|
|
configOverride := []config{}
|
|
for _, conf := range req.Configuration {
|
|
originalPipeline := pipeline{}
|
|
err := yaml.Unmarshal([]byte(conf.Data), &originalPipeline)
|
|
if err != nil {
|
|
http.Error(w, "Failed to parse yaml"+err.Error(), http.StatusBadRequest)
|
|
}
|
|
if originalPipeline.Extend != "" {
|
|
resp, err := http.Get(originalPipeline.Extend)
|
|
if err != nil {
|
|
http.Error(w, "Failed to download extend pipeline: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
if resp.StatusCode != 200 {
|
|
http.Error(w, "Failed to download extend pipeline: "+resp.Status, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
http.Error(w, "Failed to download extend pipeline: "+err.Error(), http.StatusBadRequest)
|
|
return
|
|
}
|
|
splited := strings.Split(originalPipeline.Extend, "/")
|
|
confName := splited[len(splited)-1]
|
|
configOverride = append(configOverride, config{Name: confName, Data: string(body)})
|
|
change = true
|
|
}
|
|
|
|
}
|
|
if change {
|
|
w.WriteHeader(http.StatusOK)
|
|
err = json.NewEncoder(w).Encode(map[string]interface{}{"configs": configOverride})
|
|
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(":8000", nil)
|
|
if err != nil {
|
|
log.Fatalf("Error on listen: %v", err)
|
|
}
|
|
}
|