Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

macaroons: ip range constraint #9546

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions cmd/commands/cmd_macaroon.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ var (
Name: "ip_address",
Usage: "the IP address the macaroon will be bound to",
}
macIPRangeFlag = cli.StringFlag{
Name: "ip_range",
Usage: "the IP range the macaroon will be bound to",
}
macCustomCaveatNameFlag = cli.StringFlag{
Name: "custom_caveat_name",
Usage: "the name of the custom caveat to add",
Expand Down Expand Up @@ -557,6 +561,19 @@ func applyMacaroonConstraints(ctx *cli.Context,
)
}

if ctx.IsSet(macIPRangeFlag.Name) {
_, net, err := net.ParseCIDR(ctx.String(macIPRangeFlag.Name))
if err != nil {
return nil, fmt.Errorf("unable to parse ip_range: %s",
ctx.String("ip_range"))
}

macConstraints = append(
macConstraints,
macaroons.IPLockConstraint(net.String()),
)
}

if ctx.IsSet(macCustomCaveatNameFlag.Name) {
customCaveatName := ctx.String(macCustomCaveatNameFlag.Name)
if containsWhiteSpace(customCaveatName) {
Expand Down
2 changes: 1 addition & 1 deletion config_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -472,7 +472,7 @@ func (d *DefaultWalletImpl) BuildWalletConfig(ctx context.Context,
}
macaroonService, err = macaroons.NewService(
rootKeyStore, "lnd", walletInitParams.StatelessInit,
macaroons.IPLockChecker,
macaroons.IPLockChecker, macaroons.IPRangeLockChecker,
macaroons.CustomChecker(interceptorChain),
)
if err != nil {
Expand Down
43 changes: 43 additions & 0 deletions itest/lnd_macaroons_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,49 @@ func testMacaroonAuthentication(ht *lntest.HarnessTest) {
require.NoError(t, err, "get new address")
assert.Contains(t, res.Address, "bcrt1")
},
}, {
// Fifth test: Check first-party caveat with invalid IP range.
name: "invalid IP range macaroon",
run: func(ctxt context.Context, t *testing.T) {
readonlyMac, err := testNode.ReadMacaroon(
testNode.Cfg.ReadMacPath, defaultTimeout,
)
require.NoError(t, err)
invalidIPRangeMac, err := macaroons.AddConstraints(
readonlyMac, macaroons.IPRangeLockConstraint(
"1.1.1.1/32",
),
)
require.NoError(t, err)
cleanup, client := macaroonClient(
t, testNode, invalidIPRangeMac,
)
defer cleanup()
_, err = client.GetInfo(ctxt, infoReq)
require.Error(t, err)
require.Contains(t, err.Error(), "different IP range")
},
}, {
// Sixth test: Make sure that if we do everything correct and
// send the admin macaroon with first-party caveats that we can
// satisfy, we get a correct answer.
name: "correct macaroon",
run: func(ctxt context.Context, t *testing.T) {
adminMac, err := testNode.ReadMacaroon(
testNode.Cfg.AdminMacPath, defaultTimeout,
)
require.NoError(t, err)
adminMac, err = macaroons.AddConstraints(
adminMac, macaroons.TimeoutConstraint(30),
macaroons.IPRangeLockConstraint("127.0.0.1/32"),
)
require.NoError(t, err)
cleanup, client := macaroonClient(t, testNode, adminMac)
defer cleanup()
res, err := client.NewAddress(ctxt, newAddrReq)
require.NoError(t, err, "get new address")
assert.Contains(t, res.Address, "bcrt1")
},
}, {
// Seventh test: Bake a macaroon that can only access exactly
// two RPCs and make sure it works as expected.
Expand Down
58 changes: 55 additions & 3 deletions macaroons/constraints.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,9 @@
}
}

// 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.
// IPLockConstraint locks a macaroon to a specific IP address. If ipAddr 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 != "" {
Expand All @@ -93,8 +93,30 @@
}
caveat := checkers.Condition("ipaddr",
macaroonIPAddr.String())

return mac.AddFirstPartyCaveat([]byte(caveat))
}

return nil
}
}

// IPRangeLockConstraint locks a macaroon to a specific IP address range. If
// ipRange is an empty string, this constraint does nothing to accommodate
// default value's desired behavior.
func IPRangeLockConstraint(ipRange string) func(*macaroon.Macaroon) error {
return func(mac *macaroon.Macaroon) error {
if ipRange != "" {
_, net, err := net.ParseCIDR(ipRange)
if err != nil {
return fmt.Errorf("incorrect macaroon IP " +
"range")
}
caveat := checkers.Condition("iprange", net.String())

return mac.AddFirstPartyCaveat([]byte(caveat))
}

return nil
}
}
Expand Down Expand Up @@ -122,6 +144,36 @@
}
}

// IPRangeLockChecker accepts client IP range from the validation context and
// compares it with the IP range locked in the macaroon. It is of the `Checker`
// type.
func IPRangeLockChecker() (string, checkers.Func) {
return "iprange", func(ctx context.Context, cond, arg string) error {
// Get peer info and extract IP range from it for macaroon
// check.
pr, ok := peer.FromContext(ctx)
if !ok {
return fmt.Errorf("unable to get peer info from context")

Check failure on line 156 in macaroons/constraints.go

View workflow job for this annotation

GitHub Actions / lint code

the line is 81 characters long, which exceeds the maximum of 80 characters. (ll)
}
peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
if err != nil {
return fmt.Errorf("unable to parse peer address")
}

_, ipNet, err := net.ParseCIDR(arg)
if err != nil {
return fmt.Errorf("unable to parse macaroon IP range")
}

if !ipNet.Contains(net.ParseIP(peerAddr)) {
msg := "macaroon locked to different IP range"
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 {
Expand Down
Loading