Skip to content

Commit

Permalink
Add basic tests for the verifier middleware
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgelbg committed Jun 26, 2020
1 parent e6b8584 commit 9cbfe1f
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 39 deletions.
9 changes: 6 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,13 @@ go 1.14

require (
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/google/go-cmp v0.4.1 // indirect
github.com/google/go-cmp v0.4.1
github.com/kelseyhightower/envconfig v1.4.0
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect
github.com/stretchr/testify v1.6.1 // indirect
github.com/quay/jwtproxy v0.0.4
github.com/stretchr/testify v1.6.1
golang.org/x/crypto v0.0.0-20200604202706-70a84ac30bf9 // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect
gopkg.in/square/go-jose.v2 v2.5.1
gopkg.in/yaml.v2 v2.3.0 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,14 @@ github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/google/go-cmp v0.4.1 h1:/exdXoGamhu5ONeUJH0deniYLWYvQwW66yvlfiiKTu0=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/kelseyhightower/envconfig v1.4.0 h1:Im6hONhd3pLkfDFsbRgu68RDNkGF1r3dvMUtDTo2cv8=
github.com/kelseyhightower/envconfig v1.4.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU=
github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA=
github.com/quay/jwtproxy v0.0.4 h1:M7YZxrqLaY0MA20AkWqH+1HGFjxQPLmNrC8TjrkfbwQ=
github.com/quay/jwtproxy v0.0.4/go.mod h1:Q0Zg96r0uvf49Ny3uRJ0Y09CCdtXU54LBntn6NZLShg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
Expand All @@ -37,5 +41,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/square/go-jose.v2 v2.5.1 h1:7odma5RETjNHWJnR32wx8t+Io4djHE1PqxCFx3iiZ2w=
gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
84 changes: 48 additions & 36 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,45 +20,44 @@ import (
"log"
"net/http"
"net/http/httputil"
"os"
"strings"

"github.com/coreos/go-oidc"
"github.com/kelseyhightower/envconfig"
)

var (
ctx = context.Background()
authDomain = os.Getenv("AUTHDOMAIN")
certsURL = fmt.Sprintf("%s/cdn-cgi/access/certs", authDomain)

// policyAUD is your application AUD value
policyAUD = os.Getenv("POLICYAUD")

// forwardHeader is the header to be set from the email claim embedded in the JWT token
forwardHeader = os.Getenv("FORWARDHEADER")
const (
// CFJWTHeader is the header key set by Cloudflare Access after a successful authentication
CFJWTHeader = "Cf-Access-Jwt-Assertion"
)

// forwardHost is the host to bet used to forward the request. If set it will override the Host
// header of the original request
forwardHost = os.Getenv("FORWARDHOST")
// CloudflareClaim holds the claims about the End-User/Authentication event.
type CloudflareClaim struct {
Email string `json:"email"`
Type string `json:"type"`
}

// listenAddr is the port where this proxy will be listening
listenAddr = os.Getenv("ADDR")
// Config is the general configuration (read from environment variables)
type Config struct {
AuthDomain string
PolicyAUD string
ForwardHeader string
ForwardHost string
ListenAddr string `envconfig:"ADDR"`
}

config = &oidc.Config{
ClientID: policyAUD,
}
keySet = oidc.NewRemoteKeySet(ctx, certsURL)
verifier = oidc.NewVerifier(authDomain, keySet, config)
var (
ctx = context.Background()
)

// VerifyToken is a middleware to verify a CF Access token
func VerifyToken(next http.Handler) http.Handler {
func VerifyToken(next http.Handler, tokenVerifier *oidc.IDTokenVerifier, cfg *Config) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
headers := r.Header

// Make sure that the incoming request has our token header
// Could also look in the cookies for CF_AUTHORIZATION
accessJWT := headers.Get("Cf-Access-Jwt-Assertion")
accessJWT := headers.Get(CFJWTHeader)
if accessJWT == "" {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte("No token on the request"))
Expand All @@ -67,26 +66,22 @@ func VerifyToken(next http.Handler) http.Handler {

// Verify the access token
ctx := r.Context()
token, err := verifier.Verify(ctx, accessJWT)
token, err := tokenVerifier.Verify(ctx, accessJWT)
if err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(fmt.Sprintf("Invalid token: %s", err.Error())))
return
}

// Extract custom claims
var claims struct {
Email string `json:"email"`
Type string `json:"type"`
}

var claims CloudflareClaim
if err := token.Claims(&claims); err != nil {
w.WriteHeader(http.StatusUnauthorized)
w.Write([]byte(fmt.Sprintf("Invalid claims in token: %s", err.Error())))
}

// set the authentication forward header before proxying the request
r.Header.Add(forwardHeader, claims.Email)
r.Header.Add(cfg.ForwardHeader, claims.Email)
log.Printf("Authenticated as: %s", claims.Email)

next.ServeHTTP(w, r)
Expand All @@ -96,23 +91,40 @@ func VerifyToken(next http.Handler) http.Handler {
}

func main() {
var cfg Config
err := envconfig.Process("", &cfg)
if err != nil {
log.Fatal(err.Error())
}

var (
certsURL = fmt.Sprintf("%s/cdn-cgi/access/certs", cfg.AuthDomain)

config = &oidc.Config{
ClientID: cfg.PolicyAUD,
}
keySet = oidc.NewRemoteKeySet(ctx, certsURL)
verifier = oidc.NewVerifier(cfg.AuthDomain, keySet, config)
)

director := func(req *http.Request) {
req.Header.Add("X-Forwarded-Host", req.Host)
req.Header.Add("X-Origin-Host", "cloudflare-access-proxy")
// TODO: should we trust on the Schema of the original request?
req.URL.Scheme = "http"

if len(strings.TrimSpace(forwardHost)) > 0 {
req.URL.Host = forwardHost
if len(strings.TrimSpace(cfg.ForwardHost)) > 0 {
req.URL.Host = cfg.ForwardHost
}
}

proxy := &httputil.ReverseProxy{Director: director}
http.Handle("/", VerifyToken(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
proxy.ServeHTTP(w, r)
})))
}), verifier, &cfg))

log.Printf("Listening on %s", listenAddr)
if err := http.ListenAndServe(listenAddr, nil); err != nil {
log.Fatalf("Unable to start server on [%s], error: %s", listenAddr, err.Error())
log.Printf("Listening on %s", cfg.ListenAddr)
if err := http.ListenAndServe(cfg.ListenAddr, nil); err != nil {
log.Fatalf("Unable to start server on [%s], error: %s", cfg.ListenAddr, err.Error())
}
}
Loading

0 comments on commit 9cbfe1f

Please sign in to comment.