Skip to content

Commit

Permalink
webhook: remove keymanager usage
Browse files Browse the repository at this point in the history
Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Jul 15, 2021
1 parent 6184ce3 commit 79089a4
Show file tree
Hide file tree
Showing 2 changed files with 16 additions and 153 deletions.
56 changes: 11 additions & 45 deletions notifier/webhook/deliverer.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -39,41 +37,13 @@ func New(conf Config, client *http.Client, keymanager *keymanager.Manager) (*Del
return &Deliverer{
conf: c,
c: client,
kmgr: keymanager,
}, nil
}

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.
Expand Down Expand Up @@ -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).
Expand Down
113 changes: 5 additions & 108 deletions notifier/webhook/deliverer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,108 +2,29 @@ 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 (
callback = "http://clair-notifier/notifier/api/v1/notification"
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
Expand All @@ -126,6 +47,7 @@ func testDeliverer(t *testing.T) {
return
},
))
defer server.Close()
ctx := zlog.Test(context.Background(), t)
conf := Config{
Callback: callback,
Expand All @@ -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)
}
Expand All @@ -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
}

0 comments on commit 79089a4

Please sign in to comment.