use asym key

This commit is contained in:
Anbraten 2022-06-01 20:38:04 +02:00
parent 48564b0271
commit fe3349df6c
4 changed files with 42 additions and 20 deletions

View File

@ -1,27 +1,26 @@
# Woodpecker CI Configuration Service example # Woodpecker CI Configuration Service example
This repository provides a very simplistic example of how to set up an external configuration service for **Woodpecker CI** 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. 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 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 - Centralized configuration for multiple repositories at once
- Preprocessing steps in the pipeline like templating, macros or conversion from different pipeline formats to woodpeckers format - 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` 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) 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 ```shell
# Server # Server
# ... # ...
WOODPECKER_CONFIG_SERVICE_ENDPOINT=http://<service>:8000/ciconfig WOODPECKER_CONFIG_SERVICE_ENDPOINT=http://<service>:8000/ciconfig
WOODPECKER_CONFIG_SERVICE_SECRET=mysecretsigningkey WOODPECKER_CONFIG_SERVICE_PUBLIC_KEY_FILE=public-key.pem
``` ```

7
go.mod
View File

@ -3,8 +3,7 @@ module github.com/woodpecker-ci/example-config-service
go 1.17 go 1.17
require ( require (
github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e github.com/go-ap/httpsig v0.0.0-20210714162115-62a09257db51
github.com/woodpecker-ci/woodpecker v0.15.0 github.com/joho/godotenv v1.4.0
github.com/woodpecker-ci/woodpecker v0.15.1
) )
require github.com/joho/godotenv v1.4.0

6
go.sum
View File

@ -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= 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= 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= 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/errname v0.1.5/go.mod h1:DugbBstvPFQbv/5uLcRRzfrNqKE9tVdVCqWCLp6Cifo=
github.com/Antonboom/nilnil v0.1.0/go.mod h1:PhHLvRPSghY5Y7mX4TW+BHZQYo1A8flE5H20D3IPZBo= 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= 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/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-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/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-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 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= 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/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 h1:p3svFdtoXwmsd7w0/PJwyqfGejk2HaS8f6cC69xRxUE=
github.com/woodpecker-ci/woodpecker v0.15.0/go.mod h1:gWRKIzkowiQmy8WC/fp5Ktn3QHZc15GtneRuoalysxg= 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/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-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=

34
main.go
View File

@ -1,7 +1,9 @@
package main package main
import ( import (
"crypto/ed25519"
_ "embed" _ "embed"
"encoding/hex"
"encoding/json" "encoding/json"
"io/ioutil" "io/ioutil"
"log" "log"
@ -9,7 +11,7 @@ import (
"os" "os"
"regexp" "regexp"
"github.com/99designs/httpsignatures-go" "github.com/go-ap/httpsig"
"github.com/joho/godotenv" "github.com/joho/godotenv"
"github.com/woodpecker-ci/woodpecker/server/model" "github.com/woodpecker-ci/woodpecker/server/model"
) )
@ -36,14 +38,24 @@ func main() {
log.Fatalf("Error loading .env file: %v", err) 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") host := os.Getenv("CONFIG_SERVICE_HOST")
filterRegex := os.Getenv("CONFIG_SERVICE_OVERRIDE_FILTER") filterRegex := os.Getenv("CONFIG_SERVICE_OVERRIDE_FILTER")
if secretToken == "" && host == "" { if pubKeyPath == "" && host == "" {
log.Fatal("Please make sure CONFIG_SERVICE_HOST and CONFIG_SERVICE_SECRET are set properly") 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) filter := regexp.MustCompile(filterRegex)
http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) { http.HandleFunc("/ciconfig", func(w http.ResponseWriter, r *http.Request) {
@ -52,13 +64,23 @@ func main() {
return 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 { if err != nil {
log.Printf("config: invalid or missing signature in http.Request") log.Printf("config: invalid or missing signature in http.Request")
http.Error(w, "Invalid or Missing Signature", http.StatusBadRequest) http.Error(w, "Invalid or Missing Signature", http.StatusBadRequest)
return return
} }
if !signature.IsValid(secretToken, r) {
if keyID != pubKeyID {
log.Printf("config: invalid signature in http.Request") log.Printf("config: invalid signature in http.Request")
http.Error(w, "Invalid Signature", http.StatusBadRequest) http.Error(w, "Invalid Signature", http.StatusBadRequest)
return return