forked from lightningnetwork/lnd
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathconstraints.go
250 lines (222 loc) · 8.41 KB
/
constraints.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
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
package macaroons
import (
"bytes"
"context"
"fmt"
"net"
"strings"
"time"
"google.golang.org/grpc/peer"
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
macaroon "gopkg.in/macaroon.v2"
)
const (
// CondLndCustom is the first party caveat condition name that is used
// for all custom caveats in lnd. Every custom caveat entry will be
// encoded as the string
// "lnd-custom <custom-caveat-name> <custom-caveat-condition>"
// in the serialized macaroon. We choose a single space as the delimiter
// between the because that is also used by the macaroon bakery library.
CondLndCustom = "lnd-custom"
)
// CustomCaveatAcceptor is an interface that contains a single method for
// checking whether a macaroon with the given custom caveat name should be
// accepted or not.
type CustomCaveatAcceptor interface {
// CustomCaveatSupported returns nil if a macaroon with the given custom
// caveat name can be validated by any component in lnd (for example an
// RPC middleware). If no component is registered to handle the given
// custom caveat then an error must be returned. This method only checks
// the availability of a validating component, not the validity of the
// macaroon itself.
CustomCaveatSupported(customCaveatName string) error
}
// Constraint type adds a layer of indirection over macaroon caveats.
type Constraint func(*macaroon.Macaroon) error
// Checker type adds a layer of indirection over macaroon checkers. A Checker
// returns the name of the checker and the checker function; these are used to
// register the function with the bakery service's compound checker.
type Checker func() (string, checkers.Func)
// AddConstraints returns new derived macaroon by applying every passed
// constraint and tightening its restrictions.
func AddConstraints(mac *macaroon.Macaroon,
cs ...Constraint) (*macaroon.Macaroon, error) {
// The macaroon library's Clone() method has a subtle bug that doesn't
// correctly clone all caveats. We need to use our own, safe clone
// function instead.
newMac, err := SafeCopyMacaroon(mac)
if err != nil {
return nil, err
}
for _, constraint := range cs {
if err := constraint(newMac); err != nil {
return nil, err
}
}
return newMac, nil
}
// Each *Constraint function is a functional option, which takes a pointer
// to the macaroon and adds another restriction to it. For each *Constraint,
// the corresponding *Checker is provided if not provided by default.
// TimeoutConstraint restricts the lifetime of the macaroon
// to the amount of seconds given.
func TimeoutConstraint(seconds int64) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
macaroonTimeout := time.Duration(seconds)
requestTimeout := time.Now().Add(time.Second * macaroonTimeout)
caveat := checkers.TimeBeforeCaveat(requestTimeout)
return mac.AddFirstPartyCaveat([]byte(caveat.Condition))
}
}
// IPLockConstraint locks macaroon to a specific IP address.
// If address is an empty string, this constraint does nothing to
// accommodate default value's desired behavior.
func IPLockConstraint(ipAddr string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
if ipAddr != "" {
macaroonIPAddr := net.ParseIP(ipAddr)
if macaroonIPAddr == nil {
return fmt.Errorf("incorrect macaroon IP-" +
"lock address")
}
caveat := checkers.Condition("ipaddr",
macaroonIPAddr.String())
return mac.AddFirstPartyCaveat([]byte(caveat))
}
return nil
}
}
// IPLockChecker accepts client IP from the validation context and compares it
// with IP locked in the macaroon. It is of the `Checker` type.
func IPLockChecker() (string, checkers.Func) {
return "ipaddr", func(ctx context.Context, cond, arg string) error {
// Get peer info and extract IP address from it for macaroon
// check.
pr, ok := peer.FromContext(ctx)
if !ok {
return fmt.Errorf("unable to get peer info from context")
}
peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
if err != nil {
return fmt.Errorf("unable to parse peer address")
}
if !net.ParseIP(arg).Equal(net.ParseIP(peerAddr)) {
msg := "macaroon locked to different IP address"
return fmt.Errorf(msg)
}
return nil
}
}
// CustomConstraint returns a function that adds a custom caveat condition to
// a macaroon.
func CustomConstraint(name, condition string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
// We rely on a name being set for the interception, so don't
// allow creating a caveat without a name in the first place.
if name == "" {
return fmt.Errorf("name cannot be empty")
}
// The inner (custom) condition is optional.
outerCondition := fmt.Sprintf("%s %s", name, condition)
if condition == "" {
outerCondition = name
}
caveat := checkers.Condition(CondLndCustom, outerCondition)
return mac.AddFirstPartyCaveat([]byte(caveat))
}
}
// CustomChecker returns a Checker function that is used by the macaroon bakery
// library to check whether a custom caveat is supported by lnd in general or
// not. Support in this context means: An additional gRPC interceptor was set up
// that validates the content (=condition) of the custom caveat. If such an
// interceptor is in place then the acceptor should return a nil error. If no
// interceptor exists for the custom caveat in the macaroon of a request context
// then a non-nil error should be returned and the macaroon is rejected as a
// whole.
func CustomChecker(acceptor CustomCaveatAcceptor) Checker {
// We return the general name of all lnd custom macaroons and a function
// that splits the outer condition to extract the name of the custom
// condition and the condition itself. In the bakery library that's used
// here, a caveat always has the following form:
//
// <condition-name> <condition-value>
//
// Because a checker function needs to be bound to the condition name we
// have to choose a static name for the first part ("lnd-custom", see
// CondLndCustom. Otherwise we'd need to register a new Checker function
// for each custom caveat that's registered. To allow for a generic
// custom caveat handling, we just add another layer and expand the
// initial <condition-value> into
//
// "<custom-condition-name> <custom-condition-value>"
//
// The full caveat string entry of a macaroon that uses this generic
// mechanism would therefore look like this:
//
// "lnd-custom <custom-condition-name> <custom-condition-value>"
checker := func(_ context.Context, _, outerCondition string) error {
if outerCondition != strings.TrimSpace(outerCondition) {
return fmt.Errorf("unexpected white space found in " +
"caveat condition")
}
if outerCondition == "" {
return fmt.Errorf("expected custom caveat, got empty " +
"string")
}
// The condition part of the original caveat is now name and
// condition of the custom caveat (we add a layer of conditions
// to allow one custom checker to work for all custom lnd
// conditions that implement arbitrary business logic).
parts := strings.Split(outerCondition, " ")
customCaveatName := parts[0]
return acceptor.CustomCaveatSupported(customCaveatName)
}
return func() (string, checkers.Func) {
return CondLndCustom, checker
}
}
// HasCustomCaveat tests if the given macaroon has a custom caveat with the
// given custom caveat name.
func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool {
if mac == nil {
return false
}
caveatPrefix := []byte(fmt.Sprintf(
"%s %s", CondLndCustom, customCaveatName,
))
for _, caveat := range mac.Caveats() {
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
return true
}
}
return false
}
// GetCustomCaveatCondition returns the custom caveat condition for the given
// custom caveat name from the given macaroon.
func GetCustomCaveatCondition(mac *macaroon.Macaroon,
customCaveatName string) string {
if mac == nil {
return ""
}
caveatPrefix := []byte(fmt.Sprintf(
"%s %s ", CondLndCustom, customCaveatName,
))
for _, caveat := range mac.Caveats() {
// The caveat id has a format of
// "lnd-custom [custom-caveat-name] [custom-caveat-condition]"
// and we only want the condition part. If we match the prefix
// part we return the condition that comes after the prefix.
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
caveatSplit := strings.SplitN(
string(caveat.Id),
string(caveatPrefix),
2,
)
if len(caveatSplit) == 2 {
return caveatSplit[1]
}
}
}
// We didn't find a condition for the given custom caveat name.
return ""
}