From b330c19bf46ee36c41d68d4aff43a0c57b631616 Mon Sep 17 00:00:00 2001 From: qwerty287 <80460567+qwerty287@users.noreply.github.com> Date: Tue, 23 Jul 2024 20:41:37 +0200 Subject: [PATCH] Migrate to maintained httpsign library (#3839) Co-authored-by: 6543 <6543@obermui.de> --- .cspell.json | 2 +- .../100-external-configuration-api.md | 2 +- docs/docs/91-migrations.md | 1 + go.mod | 13 +++- go.sum | 30 +++++++++- server/services/config/combined_test.go | 26 ++++---- server/services/config/http.go | 6 +- server/services/setup.go | 4 +- server/services/utils/http.go | 24 ++++---- server/services/utils/http_test.go | 59 +++++++++---------- 10 files changed, 99 insertions(+), 68 deletions(-) diff --git a/.cspell.json b/.cspell.json index 4555dfa4e..1a9204e6a 100644 --- a/.cspell.json +++ b/.cspell.json @@ -78,7 +78,7 @@ "homelab", "HTMLURL", "HTTPFS", - "httpsig", + "httpsign", "HTTPURL", "httputil", "ianvs", diff --git a/docs/docs/30-administration/40-advanced/100-external-configuration-api.md b/docs/docs/30-administration/40-advanced/100-external-configuration-api.md index 8ed55baf5..db112126d 100644 --- a/docs/docs/30-administration/40-advanced/100-external-configuration-api.md +++ b/docs/docs/30-administration/40-advanced/100-external-configuration-api.md @@ -3,7 +3,7 @@ To provide additional management and preprocessing capabilities for pipeline configurations Woodpecker supports an HTTP API which can be enabled to call an external config service. Before the run or restart of any pipeline Woodpecker will make a POST request to an external HTTP API sending the current repository, build information and all current config files retrieved from the repository. The external API can then send back new pipeline configurations that will be used immediately or respond with `HTTP 204` to tell the system to use the existing configuration. -Every request sent by Woodpecker is signed using a [http-signature](https://datatracker.ietf.org/doc/html/draft-cavage-http-signatures) by a private key (ed25519) generated on the first start of the Woodpecker server. You can get the public key for the verification of the http-signature from `http(s)://your-woodpecker-server/api/signature/public-key`. +Every request sent by Woodpecker is signed using a [http-signature](https://datatracker.ietf.org/doc/html/rfc9421) by a private key (ed25519) generated on the first start of the Woodpecker server. You can get the public key for the verification of the http-signature from `http(s)://your-woodpecker-server/api/signature/public-key`. A simplistic example configuration service can be found here: [https://github.com/woodpecker-ci/example-config-service](https://github.com/woodpecker-ci/example-config-service) diff --git a/docs/docs/91-migrations.md b/docs/docs/91-migrations.md index 4becb088e..42528ce25 100644 --- a/docs/docs/91-migrations.md +++ b/docs/docs/91-migrations.md @@ -22,6 +22,7 @@ Some versions need some changes to the server configuration or the pipeline conf - Deprecated slice definition for env vars - Deprecated `environment` filter, use `when.evaluate` - Deprecated `WOODPECKER_WEBHOOK_HOST` in favor of `WOODPECKER_EXPERT_WEBHOOK_HOST` +- Migrated to rfc9421 for webhook signatures ## 2.0.0 diff --git a/go.mod b/go.mod index a9aba768e..1f232f2f7 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,6 @@ require ( github.com/getkin/kin-openapi v0.126.0 github.com/gin-gonic/gin v1.10.0 github.com/gitsight/go-vcsurl v1.0.1 - github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf github.com/go-sql-driver/mysql v1.8.1 github.com/golang-jwt/jwt/v5 v5.2.1 github.com/google/go-github/v63 v63.0.0 @@ -60,6 +59,7 @@ require ( github.com/urfave/cli/v3 v3.0.0-alpha9.0.20240711030030-937cfe918cb1 github.com/xanzy/go-gitlab v0.107.0 github.com/xeipuuv/gojsonschema v1.2.0 + github.com/yaronf/httpsign v0.3.1 github.com/zalando/go-keyring v0.2.5 go.uber.org/multierr v1.11.0 golang.org/x/crypto v0.25.0 @@ -107,8 +107,10 @@ require ( github.com/danieljoos/wincred v1.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect + github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 // indirect github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/docker-credential-helpers v0.8.0 // indirect + github.com/dunglas/httpsfv v1.0.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/emicklei/go-restful/v3 v3.11.0 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect @@ -124,7 +126,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.20.0 // indirect - github.com/goccy/go-json v0.10.2 // indirect + github.com/goccy/go-json v0.10.3 // indirect github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect github.com/golang/protobuf v1.5.4 // indirect @@ -145,6 +147,12 @@ require ( github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect + github.com/lestrrat-go/blackmagic v1.0.2 // indirect + github.com/lestrrat-go/httpcc v1.0.1 // indirect + github.com/lestrrat-go/httprc v1.0.5 // indirect + github.com/lestrrat-go/iter v1.0.2 // indirect + github.com/lestrrat-go/jwx/v2 v2.1.0 // indirect + github.com/lestrrat-go/option v1.0.1 // indirect github.com/libdns/libdns v0.2.2 // indirect github.com/lucasb-eyer/go-colorful v1.2.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect @@ -174,6 +182,7 @@ require ( github.com/prometheus/procfs v0.12.0 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/segmentio/asm v1.2.0 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/stretchr/objx v0.5.2 // indirect diff --git a/go.sum b/go.sum index f21935450..4c8201f10 100644 --- a/go.sum +++ b/go.sum @@ -30,6 +30,8 @@ github.com/adrg/xdg v0.5.0 h1:dDaZvhMXatArP1NPHhnfaQUqWBLBsmx1h1HXQdMoFCY= github.com/adrg/xdg v0.5.0/go.mod h1:dDdY4M4DF9Rjy4kHPeNL+ilVF+p2lK8IdM9/rTSGcI4= github.com/alessio/shellescape v1.4.1 h1:V7yhSDDn8LP4lc4jS8pFkt0zCnzVJlG5JXy9BVKJUX0= github.com/alessio/shellescape v1.4.1/go.mod h1:PZAiSCk0LJaZkiCSkPv8qIobYglO3FPpyFjDCtHLS30= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ= +github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8= github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= @@ -99,6 +101,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0 h1:rpfIENRNNilwHwZeG5+P150SMrnNEcHYvcCuK6dPZSg= +github.com/decred/dcrd/dcrec/secp256k1/v4 v4.3.0/go.mod h1:v57UDF4pDQJcEfFUCRop3lJL149eHGSe9Jvczhzjo/0= github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= github.com/distribution/reference v0.5.0 h1:/FUIFXtfc/x2gpa5/VGfiGLuOIdYa1t65IKK2OFGvA0= @@ -118,6 +122,8 @@ github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4 github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/drone/envsubst v1.0.3 h1:PCIBwNDYjs50AsLZPYdfhSATKaRg/FJmDc2D6+C2x8g= github.com/drone/envsubst v1.0.3/go.mod h1:N2jZmlMufstn1KEqvbHjw40h1KyTmnVzHcSc9bFiJ2g= +github.com/dunglas/httpsfv v1.0.2 h1:iERDp/YAfnojSDJ7PW3dj1AReJz4MrwbECSSE59JWL0= +github.com/dunglas/httpsfv v1.0.2/go.mod h1:zID2mqw9mFsnt7YC3vYQ9/cjq30q41W+1AnDwH8TiMg= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= @@ -147,8 +153,6 @@ github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gitsight/go-vcsurl v1.0.1 h1:wkijKsbVg9R2IBP97U7wOANeIW9WJJKkBwS9XqllzWo= github.com/gitsight/go-vcsurl v1.0.1/go.mod h1:qRFdKDa/0Lh9MT0xE+qQBYZ/01+mY1H40rZUHR24X9U= -github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf h1:Ab5yBsD/dXhFmgf2hX7T/YYr+VK0Df7SrIxyNztT9YE= -github.com/go-ap/httpsig v0.0.0-20221203064646-3647b4d88fdf/go.mod h1:+4SUDMvPlRMUPW5PlMTbxj3U5a4fWasBIbakUw7Kp6c= github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI= github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= @@ -180,8 +184,9 @@ github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572/go.mod h1:9Pwr4 github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= github.com/goccy/go-json v0.8.1/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= -github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= @@ -324,6 +329,18 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lestrrat-go/blackmagic v1.0.2 h1:Cg2gVSc9h7sz9NOByczrbUvLopQmXrfFx//N+AkAr5k= +github.com/lestrrat-go/blackmagic v1.0.2/go.mod h1:UrEqBzIR2U6CnzVyUtfM6oZNMt/7O7Vohk2J0OGSAtU= +github.com/lestrrat-go/httpcc v1.0.1 h1:ydWCStUeJLkpYyjLDHihupbn2tYmZ7m22BGkcvZZrIE= +github.com/lestrrat-go/httpcc v1.0.1/go.mod h1:qiltp3Mt56+55GPVCbTdM9MlqhvzyuL6W/NMDA8vA5E= +github.com/lestrrat-go/httprc v1.0.5 h1:bsTfiH8xaKOJPrg1R+E3iE/AWZr/x0Phj9PBTG/OLUk= +github.com/lestrrat-go/httprc v1.0.5/go.mod h1:mwwz3JMTPBjHUkkDv/IGJ39aALInZLrhBp0X7KGUZlo= +github.com/lestrrat-go/iter v1.0.2 h1:gMXo1q4c2pHmC3dn8LzRhJfP1ceCbgSiT9lUydIzltI= +github.com/lestrrat-go/iter v1.0.2/go.mod h1:Momfcq3AnRlRjI5b5O8/G5/BvpzrhoFTZcn06fEOPt4= +github.com/lestrrat-go/jwx/v2 v2.1.0 h1:0zs7Ya6+39qoit7gwAf+cYm1zzgS3fceIdo7RmQ5lkw= +github.com/lestrrat-go/jwx/v2 v2.1.0/go.mod h1:Xpw9QIaUGiIUD1Wx0NcY1sIHwFf8lDuZn/cmxtXYRys= +github.com/lestrrat-go/option v1.0.1 h1:oAzP2fvZGQKWkvHa1/SAcFolBEca1oN+mQ7eooNBEYU= +github.com/lestrrat-go/option v1.0.1/go.mod h1:5ZHFbivi4xwXxhxY9XHDe2FHo6/Z7WWmtT7T5nBBp3I= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -447,6 +464,10 @@ github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWR github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/segmentio/asm v1.2.0 h1:9BQrFxC+YOHJlTlHGkTrFWf59nbL3XnCoFLTwDCI7ys= +github.com/segmentio/asm v1.2.0/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/sergi/go-diff v1.3.1 h1:xkr+Oxo4BOQKmkn/B9eMK0g5Kg/983T9DqqPHwYqD+8= +github.com/sergi/go-diff v1.3.1/go.mod h1:aMJSSKb2lpPvRNec0+w3fl7LP9IOFzdc9Pa4NFbPK1I= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= @@ -467,6 +488,7 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= @@ -503,6 +525,8 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= +github.com/yaronf/httpsign v0.3.1 h1:A4pqjwOVCwlExyy/mPTTryCHcXn+xhq4+wxq4BKhi2k= +github.com/yaronf/httpsign v0.3.1/go.mod h1:+7d6GccMcoljvE3QtU00NCmR1iTXCVNfbMe5nqaxRG4= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= diff --git a/server/services/config/combined_test.go b/server/services/config/combined_test.go index d2a767cac..70c2f407e 100644 --- a/server/services/config/combined_test.go +++ b/server/services/config/combined_test.go @@ -27,9 +27,9 @@ import ( "testing" "time" - "github.com/go-ap/httpsig" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/yaronf/httpsign" "go.woodpecker-ci.org/woodpecker/v2/server/forge/mocks" forge_types "go.woodpecker-ci.org/woodpecker/v2/server/forge/types" @@ -120,25 +120,19 @@ func TestFetchFromConfigService(t *testing.T) { fixtureHandler := func(w http.ResponseWriter, r *http.Request) { // check signature - pubKeyID := "woodpecker-ci-plugins" + pubKeyID := "woodpecker-ci-extensions" - keystore := httpsig.NewMemoryKeyStore() - keystore.SetKey(pubKeyID, pubEd25519Key) + verifier, err := httpsign.NewEd25519Verifier(pubEd25519Key, + httpsign.NewVerifyConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + assert.NoError(t, err) - verifier := httpsig.NewVerifier(keystore) - verifier.SetRequiredHeaders([]string{"(request-target)", "date"}) - - keyID, err := verifier.Verify(r) + err = httpsign.VerifyRequest(pubKeyID, *verifier, r) if err != nil { http.Error(w, "Invalid signature", http.StatusBadRequest) return } - if keyID != pubKeyID { - http.Error(w, "Used wrong key", http.StatusBadRequest) - return - } - type config struct { Name string `json:"name"` Data string `json:"data"` @@ -163,12 +157,12 @@ func TestFetchFromConfigService(t *testing.T) { } if req.Repo.Name == "Fetch empty" { - w.WriteHeader(404) + w.WriteHeader(http.StatusNotFound) return } if req.Repo.Name == "Use old config" { - w.WriteHeader(204) + w.WriteHeader(http.StatusNoContent) return } @@ -192,7 +186,7 @@ func TestFetchFromConfigService(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(fixtureHandler)) defer ts.Close() - httpFetcher := config.NewHTTP(ts.URL, privEd25519Key) + httpFetcher := config.NewHTTP(ts.URL+"/", privEd25519Key) for _, tt := range testTable { t.Run(tt.name, func(t *testing.T) { diff --git a/server/services/config/http.go b/server/services/config/http.go index de3a0844a..3eb512cd4 100644 --- a/server/services/config/http.go +++ b/server/services/config/http.go @@ -16,7 +16,7 @@ package config import ( "context" - "crypto" + "crypto/ed25519" "fmt" net_http "net/http" @@ -28,7 +28,7 @@ import ( type http struct { endpoint string - privateKey crypto.PrivateKey + privateKey ed25519.PrivateKey } // configData same as forge.FileMeta but with json tags and string data. @@ -48,7 +48,7 @@ type responseStructure struct { Configs []*configData `json:"configs"` } -func NewHTTP(endpoint string, privateKey crypto.PrivateKey) Service { +func NewHTTP(endpoint string, privateKey ed25519.PrivateKey) Service { return &http{endpoint, privateKey} } diff --git a/server/services/setup.go b/server/services/setup.go index 8260159ae..751a30ddd 100644 --- a/server/services/setup.go +++ b/server/services/setup.go @@ -57,7 +57,7 @@ func setupSecretService(store store.Store) secret.Service { return secret.NewDB(store) } -func setupConfigService(c *cli.Command, privateSignatureKey crypto.PrivateKey) (config.Service, error) { +func setupConfigService(c *cli.Command, privateSignatureKey ed25519.PrivateKey) (config.Service, error) { timeout := c.Duration("forge-timeout") retries := c.Uint("forge-retry") if retries == 0 { @@ -74,7 +74,7 @@ func setupConfigService(c *cli.Command, privateSignatureKey crypto.PrivateKey) ( } // setupSignatureKeys generate or load key pair to sign webhooks requests (i.e. used for service extensions). -func setupSignatureKeys(_store store.Store) (crypto.PrivateKey, crypto.PublicKey, error) { +func setupSignatureKeys(_store store.Store) (ed25519.PrivateKey, crypto.PublicKey, error) { privKeyID := "signature-private-key" privKey, err := _store.ServerConfigGet(privKeyID) diff --git a/server/services/utils/http.go b/server/services/utils/http.go index c11bfe27f..40a50269c 100644 --- a/server/services/utils/http.go +++ b/server/services/utils/http.go @@ -17,19 +17,19 @@ package utils import ( "bytes" "context" - "crypto" + "crypto/ed25519" "encoding/json" "fmt" "io" "net/http" "net/url" - "github.com/go-ap/httpsig" + "github.com/yaronf/httpsign" ) // Send makes an http request to the given endpoint, writing the input // to the request body and un-marshaling the output from the response body. -func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey, in, out any) (int, error) { +func Send(ctx context.Context, method, path string, privateKey ed25519.PrivateKey, in, out any) (int, error) { uri, err := url.Parse(path) if err != nil { return 0, err @@ -54,12 +54,12 @@ func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey req.Header.Set("Content-Type", "application/json") } - err = SignHTTPRequest(privateKey, req) + client, err := signClient(privateKey) if err != nil { return 0, err } - resp, err := http.DefaultClient.Do(req) + resp, err := client.Do(req) if err != nil { return 0, err } @@ -79,10 +79,14 @@ func Send(ctx context.Context, method, path string, privateKey crypto.PrivateKey return resp.StatusCode, err } -func SignHTTPRequest(privateKey crypto.PrivateKey, req *http.Request) error { - pubKeyID := "woodpecker-ci-plugins" +func signClient(privateKey ed25519.PrivateKey) (*httpsign.Client, error) { + pubKeyID := "woodpecker-ci-extensions" - signer := httpsig.NewEd25519Signer(pubKeyID, privateKey, nil) - - return signer.Sign(req) + signer, err := httpsign.NewEd25519Signer(privateKey, + httpsign.NewSignConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + if err != nil { + return nil, err + } + return httpsign.NewDefaultClient(httpsign.NewClientConfig().SetSignatureName(pubKeyID).SetSigner(signer)), nil // sign requests, don't verify responses } diff --git a/server/services/utils/http_test.go b/server/services/utils/http_test.go index 3b47e8cc7..57d7eece2 100644 --- a/server/services/utils/http_test.go +++ b/server/services/utils/http_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package utils_test +package utils import ( "bytes" @@ -21,15 +21,14 @@ import ( "net/http" "net/http/httptest" "testing" + "time" - "github.com/go-ap/httpsig" "github.com/stretchr/testify/assert" - - "go.woodpecker-ci.org/woodpecker/v2/server/services/utils" + "github.com/yaronf/httpsign" ) -func TestSign(t *testing.T) { - pubKeyID := "woodpecker-ci-plugins" +func TestSignClient(t *testing.T) { + pubKeyID := "woodpecker-ci-extensions" pubEd25519Key, privEd25519Key, err := ed25519.GenerateKey(rand.Reader) if !assert.NoError(t, err) { @@ -38,36 +37,36 @@ func TestSign(t *testing.T) { body := []byte("{\"foo\":\"bar\"}") - req, err := http.NewRequest(http.MethodGet, "http://example.com", bytes.NewBuffer(body)) - if err != nil { - t.Fatal(err) - } - - req.Header.Set("Content-Type", "application/json") - - err = utils.SignHTTPRequest(privEd25519Key, req) - if err != nil { - t.Fatal(err) - } - verifyHandler := func(w http.ResponseWriter, r *http.Request) { - keystore := httpsig.NewMemoryKeyStore() - keystore.SetKey(pubKeyID, pubEd25519Key) - - verifier := httpsig.NewVerifier(keystore) - verifier.SetRequiredHeaders([]string{"(request-target)", "date"}) - - keyID, err := verifier.Verify(r) + verifier, err := httpsign.NewEd25519Verifier(pubEd25519Key, + httpsign.NewVerifyConfig(), + httpsign.Headers("@request-target", "content-digest")) // The Content-Digest header will be auto-generated + assert.NoError(t, err) + + err = httpsign.VerifyRequest(pubKeyID, *verifier, r) assert.NoError(t, err) - assert.Equal(t, pubKeyID, keyID) w.WriteHeader(http.StatusOK) } - rr := httptest.NewRecorder() - handler := http.HandlerFunc(verifyHandler) + server := httptest.NewServer(http.HandlerFunc(verifyHandler)) - handler.ServeHTTP(rr, req) + req, err := http.NewRequest("GET", server.URL+"/", bytes.NewBuffer(body)) + if !assert.NoError(t, err) { + return + } - assert.Equal(t, http.StatusOK, rr.Code) + req.Header.Set("Date", time.Now().Format(time.RFC3339)) + req.Header.Set("Content-Type", "application/json") + + client, err := signClient(privEd25519Key) + if !assert.NoError(t, err) { + return + } + + rr, err := client.Do(req) + assert.NoError(t, err) + defer rr.Body.Close() + + assert.Equal(t, http.StatusOK, rr.StatusCode) }