2022-02-27 20:18:27 +01:00
package main
import (
2022-06-01 20:38:04 +02:00
"crypto/ed25519"
2022-12-27 10:45:16 +01:00
"crypto/x509"
2022-02-27 20:18:27 +01:00
_ "embed"
"encoding/json"
2022-12-27 10:45:16 +01:00
"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"
2022-02-27 22:31:40 +01:00
"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" )
2022-02-27 22:31:40 +01:00
err := godotenv . Load ( )
if err != nil {
log . Fatalf ( "Error loading .env file: %v" , err )
}
2022-12-27 10:45:16 +01:00
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" )
}
2022-12-27 10:45:16 +01:00
pemblock , _ := pem . Decode ( pubKeyRaw )
b , err := x509 . ParsePKIXPublicKey ( pemblock . Bytes )
2022-06-01 20:38:04 +02:00
if err != nil {
2022-12-27 10:45:16 +01:00
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
}
} )
2022-02-27 22:31:40 +01:00
err = http . ListenAndServe ( os . Getenv ( "CONFIG_SERVICE_HOST" ) , nil )
2022-02-27 20:18:27 +01:00
if err != nil {
2022-02-27 22:31:40 +01:00
log . Fatalf ( "Error on listen: %v" , err )
2022-02-27 20:18:27 +01:00
}
}