Skip to content

Commit

Permalink
httptransport: add integration tests
Browse files Browse the repository at this point in the history
This adds tests for configuring the authentication handler from a Config object.

Signed-off-by: Hank Donnay <hdonnay@redhat.com>
  • Loading branch information
hdonnay committed Sep 3, 2020
1 parent 050bc2d commit fb03692
Show file tree
Hide file tree
Showing 3 changed files with 213 additions and 34 deletions.
48 changes: 48 additions & 0 deletions httptransport/auth.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package httptransport

import (
"fmt"
"net/http"

"github.com/quay/clair/v4/config"
"github.com/quay/clair/v4/middleware/auth"
)

// AuthHandler returns an http.Handler wrapping the provided Handler, as
// described by the provided Config.
func authHandler(cfg *config.Config, next http.Handler) (http.Handler, error) {
var checks []auth.Checker

// Keep this ordered "best" to "worst".
switch {
case cfg.Auth.Keyserver != nil:
cfg := cfg.Auth.Keyserver
ks, err := auth.NewQuayKeyserver(cfg.API)
if err != nil {
return nil, fmt.Errorf("failed to initialize quay keyserver: %v", err)
}
checks = append(checks, ks)
if cfg.Intraservice != nil {
psk, err := auth.NewPSK(cfg.Intraservice, IntraserviceIssuer)
if err != nil {
return nil, fmt.Errorf("failed to initialize quay keyserver: %w", err)
}
checks = append(checks, psk)
}
case cfg.Auth.PSK != nil:
cfg := cfg.Auth.PSK
intra, err := auth.NewPSK(cfg.Key, IntraserviceIssuer)
if err != nil {
return nil, err
}
psk, err := auth.NewPSK(cfg.Key, cfg.Issuer)
if err != nil {
return nil, err
}
checks = append(checks, intra, psk)
default:
return next, nil
}

return auth.Handler(next, checks...), nil
}
159 changes: 159 additions & 0 deletions httptransport/auth_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package httptransport

import (
"bytes"
"crypto/rand"
"encoding/hex"
"fmt"
"io"
"net/http"
"net/http/httptest"
"testing"

"github.com/quay/clair/v4/config"
)

type authTestcase struct {
Name string
Config config.Config
ShouldFail bool
ConfigMod func(*testing.T, *config.Config)
}

func (tc *authTestcase) Run(t *testing.T) {
// Generate a nonce to return upon request.
b := make([]byte, 16)
if _, err := io.ReadFull(rand.Reader, b); err != nil {
t.Fatal(err)
}
nonce := hex.EncodeToString(b)

// Return the nonce when called.
next := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if a := r.Header.Get("authorization"); a != "" {
t.Logf("Authorization: %s", a)
}
fmt.Fprint(w, nonce)
})

// Create a handler that has auth according to the config.
h, err := authHandler(&tc.Config, next)
if err != nil {
t.Error(err)
}

// Wire up the handler to a test server.
srv := httptest.NewServer(h)
defer srv.Close()

// Modify the config, if present
if f := tc.ConfigMod; f != nil {
f(t, &tc.Config)
}

// Create a client that has auth according to the config.
c, authed, err := tc.Config.Client(nil)
if err != nil {
t.Error(err)
}
t.Logf("authed: %v", authed)

// Make the request.
res, err := c.Get(srv.URL)
if err != nil {
t.Fatal(err)
}
defer res.Body.Close()
wantStatus := http.StatusOK
if tc.ShouldFail {
wantStatus = http.StatusUnauthorized
}
t.Logf("status code: %v", res.StatusCode)
if res.StatusCode != wantStatus {
t.Fail()
}
var buf bytes.Buffer
if _, err := io.Copy(&buf, res.Body); err != nil {
t.Error(err)
}

// Compare the nonce.
got, want := buf.String(), nonce
t.Logf("http request, got: %q want: %q", got, want)
if got != want && !tc.ShouldFail {
t.Fail()
}
}

// TestAuth tests configuring both http server and client.
func TestAuth(t *testing.T) {
var fakeKey = []byte("deadbeef")
tt := []authTestcase{
{Name: "None"},
{
Name: "PSK",
Config: config.Config{
Auth: config.Auth{
PSK: &config.AuthPSK{
Issuer: `sweet-bro`,
Key: fakeKey,
},
},
},
},
{
Name: "FakeKeyserver",
Config: config.Config{
Auth: config.Auth{
Keyserver: &config.AuthKeyserver{
API: "http://localhost",
Intraservice: fakeKey,
},
},
},
},
{
Name: "PSKBadKey",
Config: config.Config{
Auth: config.Auth{
PSK: &config.AuthPSK{
Issuer: `sweet-bro`,
Key: fakeKey,
},
},
},
ShouldFail: true,
ConfigMod: func(t *testing.T, cfg *config.Config) { cfg.Auth.PSK.Key = []byte("badbeef") },
},
{
Name: "FakeKeyserverFail",
Config: config.Config{
Auth: config.Auth{
Keyserver: &config.AuthKeyserver{
API: "http://localhost",
Intraservice: fakeKey,
},
},
},
ShouldFail: true,
ConfigMod: func(t *testing.T, cfg *config.Config) { cfg.Auth.Keyserver = nil },
},
{
Name: "PSKFail",
Config: config.Config{
Auth: config.Auth{
PSK: &config.AuthPSK{
Issuer: `sweet-bro`,
Key: fakeKey,
},
},
},
ShouldFail: true,
ConfigMod: func(t *testing.T, cfg *config.Config) { cfg.Auth.PSK = nil },
},
}

for _, tc := range tt {
t.Run(tc.Name, tc.Run)
}
}
40 changes: 6 additions & 34 deletions httptransport/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@ package httptransport

import (
"context"
"fmt"
"net"
"net/http"

notifier "github.com/quay/clair/v4/notifier/service"
"github.com/rs/zerolog"
"go.opentelemetry.io/otel/api/global"
"go.opentelemetry.io/otel/plugin/othttp"
Expand All @@ -15,8 +13,8 @@ import (
"github.com/quay/clair/v4/config"
"github.com/quay/clair/v4/indexer"
"github.com/quay/clair/v4/matcher"
"github.com/quay/clair/v4/middleware/auth"
intromw "github.com/quay/clair/v4/middleware/introspection"
notifier "github.com/quay/clair/v4/notifier/service"
)

const (
Expand Down Expand Up @@ -319,41 +317,15 @@ const IntraserviceIssuer = `clair-intraservice`
//
// must be ran after the config*Mode method of choice.
func (t *Server) configureWithAuth(_ context.Context) error {
// Keep this ordered "best" to "worst".
switch {
case t.conf.Auth.Keyserver != nil:
cfg := t.conf.Auth.Keyserver
checks := []auth.Checker{}
ks, err := auth.NewQuayKeyserver(cfg.API)
if err != nil {
return fmt.Errorf("failed to initialize quay keyserver: %v", err)
}
checks = append(checks, ks)
if cfg.Intraservice != nil {
psk, err := auth.NewPSK(cfg.Intraservice, IntraserviceIssuer)
if err != nil {
return fmt.Errorf("failed to initialize quay keyserver: %w", err)
}
checks = append(checks, psk)
}
t.Server.Handler = auth.Handler(t.Server.Handler, checks...)
case t.conf.Auth.PSK != nil:
cfg := t.conf.Auth.PSK
intra, err := auth.NewPSK(cfg.Key, IntraserviceIssuer)
if err != nil {
return err
}
psk, err := auth.NewPSK(cfg.Key, cfg.Issuer)
if err != nil {
return err
}
t.Server.Handler = auth.Handler(t.Server.Handler, intra, psk)
default:
h, err := authHandler(&t.conf, t.Server.Handler)
if err != nil {
return err
}
t.Server.Handler = h
return nil
}

// unmodified determines whether to return a conditonal response
// Unmodified determines whether to return a conditional response.
func unmodified(r *http.Request, v string) bool {
if vs, ok := r.Header["If-None-Match"]; ok {
for _, rv := range vs {
Expand Down

0 comments on commit fb03692

Please sign in to comment.