diff --git a/Readme.md b/Readme.md index 1c3ee61..11773f1 100644 --- a/Readme.md +++ b/Readme.md @@ -1,27 +1,26 @@ # Woodpecker CI Configuration Service example This repository provides a very simplistic example of how to set up an external configuration service for **Woodpecker CI** -The external service gets a **HTTP POST** request with information about the repo, current build, and the configs that would normally be used. -It can then decide to acnowledge the current configs (By returning **HTTP 204**), or overriding the configurations and returning new ones in the response +The external service gets a **HTTP POST** request with information about the repo, current build, and the configs that would normally be used. +It can then decide to acknowledge the current configs (By returning **HTTP 204**), or overriding the configurations and returning new ones in the response -Usecases for this system are: +Use cases for this system are: - Centralized configuration for multiple repositories at once - Preprocessing steps in the pipeline like templating, macros or conversion from different pipeline formats to woodpeckers format This service is written in go, to run it first copy the config example: `cp .env.example .env` -Adjust the secret and add a filtering regex. The repositories that have a name match the filtering regex will receive the config from `central-pipeline-config.yaml`, while all other repositories will continue using their original configuration. +Download the public key from your woodpecker instance from `http(s)://your-woodpecker-server/api/signature/public-key` and save it to file. Set `WOODPECKER_CONFIG_SERVICE_PUBLIC_KEY_FILE` to the path to that file and add a filtering regex. The repositories that have a name match the filtering regex will receive the config from `central-pipeline-config.yaml`, while all other repositories will continue using their original configuration. -Then run using `go run .`. +Then run using `go run .`. Make sure to configure your woodpecker instance with the correct **endpoint** and configure the same **secret**. See [Woodpeckers documentation here](https://woodpecker-ci.org/docs/administration/external-configuration-api) -eg: +eg: ```shell # Server # ... WOODPECKER_CONFIG_SERVICE_ENDPOINT=http://:8000/ciconfig -WOODPECKER_CONFIG_SERVICE_SECRET=mysecretsigningkey - +WOODPECKER_CONFIG_SERVICE_PUBLIC_KEY_FILE=public-key.pem ``` diff --git a/go.mod b/go.mod index 84ccf32..32d4c02 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,7 @@ module github.com/woodpecker-ci/example-config-service go 1.17 require ( - github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e - github.com/woodpecker-ci/woodpecker v0.15.0 + github.com/go-ap/httpsig v0.0.0-20210714162115-62a09257db51 + github.com/joho/godotenv v1.4.0 + github.com/woodpecker-ci/woodpecker v0.15.1 ) - -require github.com/joho/godotenv v1.4.0 diff --git a/go.sum b/go.sum index f4defca..80850ec 100644 --- a/go.sum +++ b/go.sum @@ -57,8 +57,6 @@ code.gitea.io/sdk/gitea v0.15.0/go.mod h1:klY2LVI3s3NChzIk/MzMn7G1FHrfU7qd63iSMV contrib.go.opencensus.io/exporter/stackdriver v0.13.4/go.mod h1:aXENhDJ1Y4lIg4EUaVTwzvYETVNZk10Pu26tevFKLUc= dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU= -github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= -github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= github.com/Antonboom/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo= github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= @@ -397,6 +395,8 @@ github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2H github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.7.7/go.mod h1:axIBovoeJpVj8S3BwE0uPMTeReE4+AfFtqpqaZ1qq1U= +github.com/go-ap/httpsig v0.0.0-20210714162115-62a09257db51 h1:cytjZGyqtAu9JspfDt9ThCJ2KKCT/kPGDPCKWIZv8dw= +github.com/go-ap/httpsig v0.0.0-20210714162115-62a09257db51/go.mod h1:+4SUDMvPlRMUPW5PlMTbxj3U5a4fWasBIbakUw7Kp6c= github.com/go-critic/go-critic v0.6.2/go.mod h1:td1s27kfmLpe5G/DPjlnFI7o1UCzePptwU7Az0V5iCM= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -1196,6 +1196,8 @@ github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr github.com/woodpecker-ci/expr v0.0.0-20210628233344-164b8b3d0915/go.mod h1:PbzlZ93HrA1cf16OUP1vckAPq57gtF+ccnwZeDkmC9s= github.com/woodpecker-ci/woodpecker v0.15.0 h1:p3svFdtoXwmsd7w0/PJwyqfGejk2HaS8f6cC69xRxUE= github.com/woodpecker-ci/woodpecker v0.15.0/go.mod h1:gWRKIzkowiQmy8WC/fp5Ktn3QHZc15GtneRuoalysxg= +github.com/woodpecker-ci/woodpecker v0.15.1 h1:S9RgkDoUQ0tvOL8/yJDDvDxdXUdE+XOdlZf/I6D3SRg= +github.com/woodpecker-ci/woodpecker v0.15.1/go.mod h1:gWRKIzkowiQmy8WC/fp5Ktn3QHZc15GtneRuoalysxg= github.com/xanzy/go-gitlab v0.55.1/go.mod h1:F0QEXwmqiBUxCgJm8fE9S+1veX4XC9Z4cfaAbqwk4YM= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= diff --git a/main.go b/main.go index e8c6bb1..2605e08 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,9 @@ package main import ( + "crypto/ed25519" _ "embed" + "encoding/hex" "encoding/json" "io/ioutil" "log" @@ -9,7 +11,7 @@ import ( "os" "regexp" - "github.com/99designs/httpsignatures-go" + "github.com/go-ap/httpsig" "github.com/joho/godotenv" "github.com/woodpecker-ci/woodpecker/server/model" ) @@ -36,14 +38,24 @@ func main() { log.Fatalf("Error loading .env file: %v", err) } - secretToken := os.Getenv("CONFIG_SERVICE_SECRET") + pubKeyPath := os.Getenv("CONFIG_SERVICE_PUBLIC_KEY_FILE") host := os.Getenv("CONFIG_SERVICE_HOST") filterRegex := os.Getenv("CONFIG_SERVICE_OVERRIDE_FILTER") - if secretToken == "" && host == "" { - log.Fatal("Please make sure CONFIG_SERVICE_HOST and CONFIG_SERVICE_SECRET are set properly") + if pubKeyPath == "" && host == "" { + log.Fatal("Please make sure CONFIG_SERVICE_HOST and CONFIG_SERVICE_PUBLIC_KEY_FILE are set properly") } + pubKeyRaw, err := ioutil.ReadFile(pubKeyPath) + if err != nil { + log.Fatal("Failed to read public key file") + } + pubKeyStr, err := hex.DecodeString(string(pubKeyRaw)) + if err != nil { + log.Fatal("Failed to decode public key") + } + pubKey := ed25519.PublicKey(pubKeyStr) + filter := regexp.MustCompile(filterRegex) http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) { @@ -52,13 +64,23 @@ func main() { return } - signature, err := httpsignatures.FromRequest(r) + // 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 !signature.IsValid(secretToken, r) { + + if keyID != pubKeyID { log.Printf("config: invalid signature in http.Request") http.Error(w, "Invalid Signature", http.StatusBadRequest) return