Migrate to maintained httpsign library (#3839)

Co-authored-by: 6543 <6543@obermui.de>
This commit is contained in:
qwerty287
2024-07-23 20:41:37 +02:00
committed by GitHub
parent 047eb19d42
commit b330c19bf4
10 changed files with 99 additions and 68 deletions

View File

@@ -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) {

View File

@@ -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}
}

View File

@@ -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)

View File

@@ -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
}

View File

@@ -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)
}