/
checkers.go
157 lines (138 loc) · 4.52 KB
/
checkers.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
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
}