diff --git a/notifier/webhook/deliverer.go b/notifier/webhook/deliverer.go index 5fe2d66db2..cd72a7b93b 100644 --- a/notifier/webhook/deliverer.go +++ b/notifier/webhook/deliverer.go @@ -2,32 +2,30 @@ package webhook import ( "context" - "fmt" "net/http" - "time" + "sync" "github.com/google/uuid" "github.com/quay/zlog" "go.opentelemetry.io/otel/baggage" "go.opentelemetry.io/otel/label" - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" clairerror "github.com/quay/clair/v4/clair-error" "github.com/quay/clair/v4/internal/codec" "github.com/quay/clair/v4/notifier" - "github.com/quay/clair/v4/notifier/keymanager" ) +// SignedOnce is used to print a deprecation notice, but only once per run. +var signedOnce sync.Once + type Deliverer struct { conf Config // a client to use for POSTing webhooks - c *http.Client - kmgr *keymanager.Manager + c *http.Client } // New returns a new webhook Deliverer -func New(conf Config, client *http.Client, keymanager *keymanager.Manager) (*Deliverer, error) { +func New(conf Config, client *http.Client) (*Deliverer, error) { var c Config var err error if c, err = conf.Validate(); err != nil { @@ -39,7 +37,6 @@ func New(conf Config, client *http.Client, keymanager *keymanager.Manager) (*Del return &Deliverer{ conf: c, c: client, - kmgr: keymanager, }, nil } @@ -47,33 +44,6 @@ func (d *Deliverer) Name() string { return "webhook" } -// sign will use the provided private key to sign and attach a jwt to the provided -// request. -func (d *Deliverer) sign(ctx context.Context, req *http.Request, kp keymanager.KeyPair) error { - opts := (&jose.SignerOptions{}). - WithType("JWT"). - WithHeader(jose.HeaderKey("kid"), kp.ID.String()) - signer, err := jose.NewSigner(jose.SigningKey{Algorithm: jose.PS512, Key: kp.Private}, opts) - if err != nil { - return fmt.Errorf("failed to create jwt signer: %v", err) - } - now := jwt.NumericDate(time.Now().Unix()) - expire := jwt.NumericDate(time.Now().Add(1 * time.Hour).Unix()) - cl := jwt.Claims{ - Issuer: "notifier", - Expiry: &expire, - IssuedAt: &now, - Audience: jwt.Audience{d.conf.target.String()}, - Subject: d.conf.target.Hostname(), - } - token, err := jwt.Signed(signer).Claims(cl).CompactSerialize() - if err != nil { - return err - } - req.Header.Set("Authorization", "Bearer "+token) - return nil -} - // Deliver implements the notifier.Deliverer interface. // // Deliver POSTS a webhook data structure to the configured target. @@ -102,15 +72,11 @@ func (d *Deliverer) Deliver(ctx context.Context, nID uuid.UUID) error { // sign a jwt using key manager's private key if d.conf.Signed { - kp, err := d.kmgr.KeyPair() - if err != nil { - return fmt.Errorf("configured for signing but no private key available: %v", err) - } - err = d.sign(ctx, req, kp) - if err != nil { - return fmt.Errorf("failed to sign request: %v", err) - } - zlog.Debug(ctx).Msg("successfully signed request") + signedOnce.Do(func() { + zlog.Warn(ctx).Msg(`"signed" configuration key no longer does anything`) + zlog.Warn(ctx).Msg(`specifying "signed" will be an error in the future`) + // Make good on this threat in... 4.4? + }) } zlog.Info(ctx). diff --git a/notifier/webhook/deliverer_test.go b/notifier/webhook/deliverer_test.go index 6167105640..299b93462e 100644 --- a/notifier/webhook/deliverer_test.go +++ b/notifier/webhook/deliverer_test.go @@ -2,26 +2,19 @@ package webhook import ( "context" - "crypto/rand" - "crypto/rsa" - "crypto/x509" "encoding/json" "net/http" "net/http/httptest" "net/url" "path" - "strings" "sync" "testing" "github.com/google/go-cmp/cmp" "github.com/google/uuid" - "gopkg.in/square/go-jose.v2" - "gopkg.in/square/go-jose.v2/jwt" + "github.com/quay/zlog" "github.com/quay/clair/v4/notifier" - "github.com/quay/clair/v4/notifier/keymanager" - "github.com/quay/zlog" ) var ( @@ -29,81 +22,9 @@ var ( noteID = uuid.New() ) -// TestDeliverer is a parallel test harness +// TestDeliverer confirms the deliverer correctly sends the webhook +// data structure to the configured target. func TestDeliverer(t *testing.T) { - t.Run("TestSign", testSign) - t.Run("TestDeliverer", testDeliverer) -} - -// testSign confirms the deliverer correctly signs a webhook -func testSign(t *testing.T) { - t.Parallel() - - target, _ := url.Parse("https://example.com/endpoint") - d := Deliverer{ - conf: Config{ - target: target, - }, - } - kp := (genKeyPair(t, 1))[0] - req, err := http.NewRequest(http.MethodGet, "", nil) - if err != nil { - t.Fatalf("failed to create request: %v", err) - } - - ctx := zlog.Test(context.Background(), t) - err = d.sign(ctx, req, kp) - if err != nil { - t.Fatalf("failed to sign request: %v", err) - } - - token := req.Header.Get("Authorization") - if !strings.HasPrefix(token, "Bearer") { - t.Fatalf("no Bearer prefix on token") - } - token = strings.TrimPrefix(token, "Bearer ") - - j, err := jwt.ParseSigned(token) - if err != nil { - t.Errorf("failed to parse jwt: %v", err) - } - headers := j.Headers[0] - if headers.KeyID != kp.ID.String() { - t.Fatalf("got: %v want: %v", headers.KeyID, kp.ID.String()) - } - - var claims jwt.Claims - err = j.Claims(kp.Public, &claims) - if err != nil { - t.Errorf("failed to deserialize claims with public key: %v", err) - } - - if claims.Issuer != "notifier" { - t.Errorf("got: %v want: %v", claims.Issuer, "notifier") - } - if claims.Audience[0] != target.String() { - t.Errorf("got: %v want: %v", claims.Audience, target.String()) - } - if claims.Subject != target.Hostname() { - t.Errorf("got: %v want: %v", claims.Subject, target.Hostname()) - } - - object, err := jose.ParseSigned(token) - if err != nil { - t.Fatalf("unable to parse signed token") - } - - _, err = object.Verify(kp.Public) - if err != nil { - t.Fatalf("token failed public key validation: %v", err) - } -} - -// testDeliverer confirms the deliverer correctly sends the webhook -// datas structure to the configured target. -func testDeliverer(t *testing.T) { - t.Parallel() - var whResult struct { sync.Mutex cb notifier.Callback @@ -126,6 +47,7 @@ func testDeliverer(t *testing.T) { return }, )) + defer server.Close() ctx := zlog.Test(context.Background(), t) conf := Config{ Callback: callback, @@ -137,7 +59,7 @@ func testDeliverer(t *testing.T) { t.Fatalf("failed to validate webhook config: %v", err) } - d, err := New(conf, nil, nil) + d, err := New(conf, server.Client()) if err != nil { t.Fatalf("failed to create new webhook deliverer: %v", err) } @@ -164,28 +86,3 @@ func testDeliverer(t *testing.T) { t.Fatalf("got: %v, wanted: %v", wh.Callback, cbURL) } } - -func genKeyPair(t *testing.T, n int) (kps []keymanager.KeyPair) { - reader := rand.Reader - bitSize := 2048 - for i := 0; i < n; i++ { - key, err := rsa.GenerateKey(reader, bitSize) - if err != nil { - t.Fatalf("failed to generate test key pair: %v", err) - } - - pub, err := x509.MarshalPKIXPublicKey(&key.PublicKey) - if err != nil { - t.Fatalf("failed to marshal public key to PKIX") - } - id := uuid.New() - - kps = append(kps, keymanager.KeyPair{ - ID: id, - Private: key, - Public: &key.PublicKey, - Der: pub, - }) - } - return kps -}