157 lines
4.5 KiB
Go
157 lines
4.5 KiB
Go
package httpbakery
|
|
|
|
import (
|
|
"context"
|
|
"net"
|
|
"net/http"
|
|
|
|
"gopkg.in/errgo.v1"
|
|
|
|
"github.com/go-macaroon-bakery/macaroon-bakery/v3/bakery/checkers"
|
|
)
|
|
|
|
type httpRequestKey struct{}
|
|
|
|
// ContextWithRequest returns the context with information from the
|
|
// given request attached as context. This is used by the httpbakery
|
|
// checkers (see RegisterCheckers for details).
|
|
func ContextWithRequest(ctx context.Context, req *http.Request) context.Context {
|
|
return context.WithValue(ctx, httpRequestKey{}, req)
|
|
}
|
|
|
|
func requestFromContext(ctx context.Context) *http.Request {
|
|
req, _ := ctx.Value(httpRequestKey{}).(*http.Request)
|
|
return req
|
|
}
|
|
|
|
const (
|
|
// CondClientIPAddr holds the first party caveat condition
|
|
// that checks a client's IP address.
|
|
CondClientIPAddr = "client-ip-addr"
|
|
|
|
// CondClientOrigin holds the first party caveat condition that
|
|
// checks a client's origin header.
|
|
CondClientOrigin = "origin"
|
|
)
|
|
|
|
// CheckersNamespace holds the URI of the HTTP checkers schema.
|
|
const CheckersNamespace = "http"
|
|
|
|
var allCheckers = map[string]checkers.Func{
|
|
CondClientIPAddr: ipAddrCheck,
|
|
CondClientOrigin: clientOriginCheck,
|
|
}
|
|
|
|
// RegisterCheckers registers all the HTTP checkers with the given checker.
|
|
// Current checkers include:
|
|
//
|
|
// client-ip-addr <ip-address>
|
|
//
|
|
// The client-ip-addr caveat checks that the HTTP request has
|
|
// the given remote IP address.
|
|
//
|
|
// origin <name>
|
|
//
|
|
// The origin caveat checks that the HTTP Origin header has
|
|
// the given value.
|
|
func RegisterCheckers(c *checkers.Checker) {
|
|
c.Namespace().Register(CheckersNamespace, "http")
|
|
for cond, check := range allCheckers {
|
|
c.Register(cond, CheckersNamespace, check)
|
|
}
|
|
}
|
|
|
|
// NewChecker returns a new checker with the standard
|
|
// and HTTP checkers registered in it.
|
|
func NewChecker() *checkers.Checker {
|
|
c := checkers.New(nil)
|
|
RegisterCheckers(c)
|
|
return c
|
|
}
|
|
|
|
// ipAddrCheck implements the IP client address checker
|
|
// for an HTTP request.
|
|
func ipAddrCheck(ctx context.Context, cond, args string) error {
|
|
req := requestFromContext(ctx)
|
|
if req == nil {
|
|
return errgo.Newf("no IP address found in context")
|
|
}
|
|
ip := net.ParseIP(args)
|
|
if ip == nil {
|
|
return errgo.Newf("cannot parse IP address in caveat")
|
|
}
|
|
if req.RemoteAddr == "" {
|
|
return errgo.Newf("client has no remote address")
|
|
}
|
|
reqIP, err := requestIPAddr(req)
|
|
if err != nil {
|
|
return errgo.Mask(err)
|
|
}
|
|
if !reqIP.Equal(ip) {
|
|
return errgo.Newf("client IP address mismatch, got %s", reqIP)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// clientOriginCheck implements the Origin header checker
|
|
// for an HTTP request.
|
|
func clientOriginCheck(ctx context.Context, cond, args string) error {
|
|
req := requestFromContext(ctx)
|
|
if req == nil {
|
|
return errgo.Newf("no origin found in context")
|
|
}
|
|
// Note that web browsers may not provide the origin header when it's
|
|
// not a cross-site request with a GET method. There's nothing we
|
|
// can do about that, so just allow all requests with an empty origin.
|
|
if reqOrigin := req.Header.Get("Origin"); reqOrigin != "" && reqOrigin != args {
|
|
return errgo.Newf("request has invalid Origin header; got %q", reqOrigin)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// SameClientIPAddrCaveat returns a caveat that will check that
|
|
// the remote IP address is the same as that in the given HTTP request.
|
|
func SameClientIPAddrCaveat(req *http.Request) checkers.Caveat {
|
|
if req.RemoteAddr == "" {
|
|
return checkers.ErrorCaveatf("client has no remote IP address")
|
|
}
|
|
ip, err := requestIPAddr(req)
|
|
if err != nil {
|
|
return checkers.ErrorCaveatf("%v", err)
|
|
}
|
|
return ClientIPAddrCaveat(ip)
|
|
}
|
|
|
|
// ClientIPAddrCaveat returns a caveat that will check whether the
|
|
// client's IP address is as provided.
|
|
func ClientIPAddrCaveat(addr net.IP) checkers.Caveat {
|
|
if len(addr) != net.IPv4len && len(addr) != net.IPv6len {
|
|
return checkers.ErrorCaveatf("bad IP address %d", []byte(addr))
|
|
}
|
|
return httpCaveat(CondClientIPAddr, addr.String())
|
|
}
|
|
|
|
// ClientOriginCaveat returns a caveat that will check whether the
|
|
// client's Origin header in its HTTP request is as provided.
|
|
func ClientOriginCaveat(origin string) checkers.Caveat {
|
|
return httpCaveat(CondClientOrigin, origin)
|
|
}
|
|
|
|
func httpCaveat(cond, arg string) checkers.Caveat {
|
|
return checkers.Caveat{
|
|
Condition: checkers.Condition(cond, arg),
|
|
Namespace: CheckersNamespace,
|
|
}
|
|
}
|
|
|
|
func requestIPAddr(req *http.Request) (net.IP, error) {
|
|
reqHost, _, err := net.SplitHostPort(req.RemoteAddr)
|
|
if err != nil {
|
|
return nil, errgo.Newf("cannot parse host port in remote address: %v", err)
|
|
}
|
|
ip := net.ParseIP(reqHost)
|
|
if ip == nil {
|
|
return nil, errgo.Newf("invalid IP address in remote address %q", req.RemoteAddr)
|
|
}
|
|
return ip, nil
|
|
}
|